mirror of
				https://github.com/kubenetworks/kubevpn.git
				synced 2025-10-31 18:52:50 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			489 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			489 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package util
 | |
| 
 | |
| import (
 | |
| 	"bytes"
 | |
| 	"context"
 | |
| 	"encoding/binary"
 | |
| 	"encoding/json"
 | |
| 	"fmt"
 | |
| 	"io"
 | |
| 	"net"
 | |
| 	"net/http"
 | |
| 	"os"
 | |
| 	osexec "os/exec"
 | |
| 	"runtime"
 | |
| 	"strconv"
 | |
| 	"strings"
 | |
| 	"time"
 | |
| 
 | |
| 	dockerterm "github.com/moby/term"
 | |
| 	"github.com/pkg/errors"
 | |
| 	log "github.com/sirupsen/logrus"
 | |
| 	"golang.org/x/net/icmp"
 | |
| 	"golang.org/x/net/ipv4"
 | |
| 	"k8s.io/api/core/v1"
 | |
| 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | |
| 	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
 | |
| 	"k8s.io/apimachinery/pkg/fields"
 | |
| 	k8sruntime "k8s.io/apimachinery/pkg/runtime"
 | |
| 	"k8s.io/apimachinery/pkg/runtime/schema"
 | |
| 	"k8s.io/apimachinery/pkg/util/sets"
 | |
| 	"k8s.io/apimachinery/pkg/watch"
 | |
| 	"k8s.io/cli-runtime/pkg/genericclioptions"
 | |
| 	runtimeresource "k8s.io/cli-runtime/pkg/resource"
 | |
| 	"k8s.io/client-go/kubernetes"
 | |
| 	"k8s.io/client-go/kubernetes/scheme"
 | |
| 	v12 "k8s.io/client-go/kubernetes/typed/core/v1"
 | |
| 	"k8s.io/client-go/rest"
 | |
| 	"k8s.io/client-go/tools/cache"
 | |
| 	"k8s.io/client-go/tools/remotecommand"
 | |
| 	watchtools "k8s.io/client-go/tools/watch"
 | |
| 	"k8s.io/client-go/transport/spdy"
 | |
| 	"k8s.io/kubectl/pkg/cmd/exec"
 | |
| 	cmdutil "k8s.io/kubectl/pkg/cmd/util"
 | |
| 	"k8s.io/kubectl/pkg/polymorphichelpers"
 | |
| 	"k8s.io/kubectl/pkg/util/interrupt"
 | |
| 
 | |
| 	"github.com/wencaiwulue/kubevpn/pkg/config"
 | |
| )
 | |
| 
 | |
| func GetAvailableUDPPortOrDie() int {
 | |
| 	address, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:0", "0.0.0.0"))
 | |
| 	if err != nil {
 | |
| 		log.Fatal(err)
 | |
| 	}
 | |
| 	listener, err := net.ListenUDP("udp", address)
 | |
| 	if err != nil {
 | |
| 		log.Fatal(err)
 | |
| 	}
 | |
| 	defer listener.Close()
 | |
| 	return listener.LocalAddr().(*net.UDPAddr).Port
 | |
| }
 | |
| 
 | |
| func WaitPod(podInterface v12.PodInterface, list metav1.ListOptions, checker func(*v1.Pod) bool) error {
 | |
| 	ctx, cancelFunc := context.WithTimeout(context.TODO(), time.Minute*10)
 | |
| 	defer cancelFunc()
 | |
| 	watch, err := podInterface.Watch(ctx, list)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	defer watch.Stop()
 | |
| 	for {
 | |
| 		select {
 | |
| 		case e := <-watch.ResultChan():
 | |
| 			if pod, ok := e.Object.(*v1.Pod); ok {
 | |
| 				if checker(pod) {
 | |
| 					return nil
 | |
| 				}
 | |
| 			}
 | |
| 		case <-ctx.Done():
 | |
| 			return errors.New("wait for pod to be ready timeout")
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func PortForwardPod(config *rest.Config, clientset *rest.RESTClient, podName, namespace, port string, readyChan chan struct{}, stopChan <-chan struct{}) error {
 | |
| 	url := clientset.
 | |
| 		Post().
 | |
| 		Resource("pods").
 | |
| 		Namespace(namespace).
 | |
| 		Name(podName).
 | |
| 		SubResource("portforward").
 | |
| 		URL()
 | |
| 	transport, upgrader, err := spdy.RoundTripperFor(config)
 | |
| 	if err != nil {
 | |
| 		log.Error(err)
 | |
| 		return err
 | |
| 	}
 | |
| 	dialer := spdy.NewDialer(upgrader, &http.Client{Transport: transport}, "POST", url)
 | |
| 	p := []string{port}
 | |
| 	forwarder, err := NewOnAddresses(dialer, []string{"0.0.0.0"}, p, stopChan, readyChan, nil, os.Stderr)
 | |
| 	if err != nil {
 | |
| 		log.Error(err)
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	if err = forwarder.ForwardPorts(); err != nil {
 | |
| 		log.Error(err)
 | |
| 		return err
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func GetTopOwnerReference(factory cmdutil.Factory, namespace, workload string) (*runtimeresource.Info, error) {
 | |
| 	for {
 | |
| 		object, err := GetUnstructuredObject(factory, namespace, workload)
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 		ownerReference := metav1.GetControllerOf(object.Object.(*unstructured.Unstructured))
 | |
| 		if ownerReference == nil {
 | |
| 			return object, nil
 | |
| 		}
 | |
| 		// apiVersion format is Group/Version is like: apps/v1, apps.kruise.io/v1beta1
 | |
| 		version, err := schema.ParseGroupVersion(ownerReference.APIVersion)
 | |
| 		if err != nil {
 | |
| 			return object, nil
 | |
| 		}
 | |
| 		gk := metav1.GroupKind{
 | |
| 			Group: version.Group,
 | |
| 			Kind:  ownerReference.Kind,
 | |
| 		}
 | |
| 		workload = fmt.Sprintf("%s/%s", gk.String(), ownerReference.Name)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // GetTopOwnerReferenceBySelector assume pods, controller has same labels
 | |
| func GetTopOwnerReferenceBySelector(factory cmdutil.Factory, namespace, selector string) (sets.String, error) {
 | |
| 	object, err := GetUnstructuredObjectBySelector(factory, namespace, selector)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	set := sets.NewString()
 | |
| 	for _, info := range object {
 | |
| 		reference, err := GetTopOwnerReference(factory, namespace, fmt.Sprintf("%s/%s", info.Mapping.Resource.GroupResource().String(), info.Name))
 | |
| 		if err == nil && reference.Mapping.Resource.Resource != "services" {
 | |
| 			set.Insert(fmt.Sprintf("%s/%s", reference.Mapping.GroupVersionKind.GroupKind().String(), reference.Name))
 | |
| 		}
 | |
| 	}
 | |
| 	return set, nil
 | |
| }
 | |
| 
 | |
| func Shell(clientset *kubernetes.Clientset, restclient *rest.RESTClient, config *rest.Config, podName, namespace, cmd string) (string, error) {
 | |
| 	pod, err := clientset.CoreV1().Pods(namespace).Get(context.Background(), podName, metav1.GetOptions{})
 | |
| 
 | |
| 	if err != nil {
 | |
| 		return "", err
 | |
| 	}
 | |
| 	if pod.Status.Phase == v1.PodSucceeded || pod.Status.Phase == v1.PodFailed {
 | |
| 		err = fmt.Errorf("cannot exec into a container in a completed pod; current phase is %s", pod.Status.Phase)
 | |
| 		return "", err
 | |
| 	}
 | |
| 	containerName := pod.Spec.Containers[0].Name
 | |
| 	stdin, _, stderr := dockerterm.StdStreams()
 | |
| 
 | |
| 	stdoutBuf := bytes.NewBuffer(nil)
 | |
| 	stdout := io.MultiWriter(stdoutBuf)
 | |
| 	StreamOptions := exec.StreamOptions{
 | |
| 		Namespace:     namespace,
 | |
| 		PodName:       podName,
 | |
| 		ContainerName: containerName,
 | |
| 		IOStreams:     genericclioptions.IOStreams{In: stdin, Out: stdout, ErrOut: stderr},
 | |
| 	}
 | |
| 	Executor := &exec.DefaultRemoteExecutor{}
 | |
| 	// ensure we can recover the terminal while attached
 | |
| 	tt := StreamOptions.SetupTTY()
 | |
| 
 | |
| 	var sizeQueue remotecommand.TerminalSizeQueue
 | |
| 	if tt.Raw {
 | |
| 		// this call spawns a goroutine to monitor/update the terminal size
 | |
| 		sizeQueue = tt.MonitorSize(tt.GetSize())
 | |
| 
 | |
| 		// unset p.Err if it was previously set because both stdout and stderr go over p.Out when tty is
 | |
| 		// true
 | |
| 		StreamOptions.ErrOut = nil
 | |
| 	}
 | |
| 
 | |
| 	fn := func() error {
 | |
| 		req := restclient.Post().
 | |
| 			Resource("pods").
 | |
| 			Name(pod.Name).
 | |
| 			Namespace(pod.Namespace).
 | |
| 			SubResource("exec")
 | |
| 		req.VersionedParams(&v1.PodExecOptions{
 | |
| 			Container: containerName,
 | |
| 			Command:   []string{"sh", "-c", cmd},
 | |
| 			Stdin:     StreamOptions.Stdin,
 | |
| 			Stdout:    StreamOptions.Out != nil,
 | |
| 			Stderr:    StreamOptions.ErrOut != nil,
 | |
| 			TTY:       tt.Raw,
 | |
| 		}, scheme.ParameterCodec)
 | |
| 		return Executor.Execute("POST", req.URL(), config, StreamOptions.In, StreamOptions.Out, StreamOptions.ErrOut, tt.Raw, sizeQueue)
 | |
| 	}
 | |
| 
 | |
| 	err = tt.Safe(fn)
 | |
| 	return strings.TrimRight(stdoutBuf.String(), "\n"), err
 | |
| }
 | |
| 
 | |
| func IsWindows() bool {
 | |
| 	return runtime.GOOS == "windows"
 | |
| }
 | |
| 
 | |
| func GetUnstructuredObject(f cmdutil.Factory, namespace string, workloads string) (*runtimeresource.Info, error) {
 | |
| 	do := f.NewBuilder().
 | |
| 		Unstructured().
 | |
| 		NamespaceParam(namespace).DefaultNamespace().AllNamespaces(false).
 | |
| 		ResourceTypeOrNameArgs(true, workloads).
 | |
| 		ContinueOnError().
 | |
| 		Latest().
 | |
| 		Flatten().
 | |
| 		TransformRequests(func(req *rest.Request) { req.Param("includeObject", "Object") }).
 | |
| 		Do()
 | |
| 	if err := do.Err(); err != nil {
 | |
| 		log.Warn(err)
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	infos, err := do.Infos()
 | |
| 	if err != nil {
 | |
| 		log.Println(err)
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	if len(infos) == 0 {
 | |
| 		return nil, errors.New("Not found")
 | |
| 	}
 | |
| 	return infos[0], err
 | |
| }
 | |
| 
 | |
| func GetUnstructuredObjectBySelector(f cmdutil.Factory, namespace string, selector string) ([]*runtimeresource.Info, error) {
 | |
| 	do := f.NewBuilder().
 | |
| 		Unstructured().
 | |
| 		NamespaceParam(namespace).DefaultNamespace().AllNamespaces(false).
 | |
| 		ResourceTypeOrNameArgs(true, "all").
 | |
| 		LabelSelector(selector).
 | |
| 		ContinueOnError().
 | |
| 		Latest().
 | |
| 		Flatten().
 | |
| 		TransformRequests(func(req *rest.Request) { req.Param("includeObject", "Object") }).
 | |
| 		Do()
 | |
| 	if err := do.Err(); err != nil {
 | |
| 		log.Warn(err)
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	infos, err := do.Infos()
 | |
| 	if err != nil {
 | |
| 		log.Println(err)
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	if len(infos) == 0 {
 | |
| 		return nil, errors.New("Not found")
 | |
| 	}
 | |
| 	return infos, err
 | |
| }
 | |
| 
 | |
| func GetPodTemplateSpecPath(u *unstructured.Unstructured) (*v1.PodTemplateSpec, []string, error) {
 | |
| 	var stringMap map[string]interface{}
 | |
| 	var b bool
 | |
| 	var err error
 | |
| 	var path []string
 | |
| 	if stringMap, b, err = unstructured.NestedMap(u.Object, "spec", "template"); b && err == nil {
 | |
| 		path = []string{"spec", "template"}
 | |
| 	} else if stringMap, b, err = unstructured.NestedMap(u.Object); b && err == nil {
 | |
| 		path = []string{}
 | |
| 	} else {
 | |
| 		return nil, nil, err
 | |
| 	}
 | |
| 	marshal, err := json.Marshal(stringMap)
 | |
| 	if err != nil {
 | |
| 		return nil, nil, err
 | |
| 	}
 | |
| 	var p v1.PodTemplateSpec
 | |
| 	if err = json.Unmarshal(marshal, &p); err != nil {
 | |
| 		return nil, nil, err
 | |
| 	}
 | |
| 	return &p, path, nil
 | |
| }
 | |
| 
 | |
| func BytesToInt(b []byte) uint32 {
 | |
| 	buffer := bytes.NewBuffer(b)
 | |
| 	var u uint32
 | |
| 	if err := binary.Read(buffer, binary.BigEndian, &u); err != nil {
 | |
| 		log.Warn(err)
 | |
| 	}
 | |
| 	return u
 | |
| }
 | |
| 
 | |
| func Ping(targetIP string) (bool, error) {
 | |
| 	conn, err := icmp.ListenPacket("ip4:icmp", "0.0.0.0")
 | |
| 	if err != nil {
 | |
| 		return false, err
 | |
| 	}
 | |
| 	defer conn.Close()
 | |
| 
 | |
| 	message := icmp.Message{
 | |
| 		Type: ipv4.ICMPTypeEcho, Code: 0,
 | |
| 		Body: &icmp.Echo{
 | |
| 			ID:   os.Getpid() & 0xffff,
 | |
| 			Seq:  1,
 | |
| 			Data: []byte("HELLO-R-U-THERE"),
 | |
| 		},
 | |
| 	}
 | |
| 	data, err := message.Marshal(nil)
 | |
| 	if err != nil {
 | |
| 		return false, nil
 | |
| 	}
 | |
| 	if _, err = conn.WriteTo(data, &net.IPAddr{IP: net.ParseIP(targetIP)}); err != nil {
 | |
| 		return false, err
 | |
| 	}
 | |
| 
 | |
| 	rb := make([]byte, 1500)
 | |
| 	n, _, err := conn.ReadFrom(rb)
 | |
| 	if err != nil {
 | |
| 		return false, err
 | |
| 	}
 | |
| 	rm, err := icmp.ParseMessage(ipv4.ICMPTypeEchoReply.Protocol(), rb[:n])
 | |
| 	if err != nil {
 | |
| 		return false, err
 | |
| 	}
 | |
| 	switch rm.Type {
 | |
| 	case ipv4.ICMPTypeEchoReply:
 | |
| 		return true, nil
 | |
| 	default:
 | |
| 		return false, nil
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func RolloutStatus(factory cmdutil.Factory, namespace, workloads string, timeout time.Duration) error {
 | |
| 	client, _ := factory.DynamicClient()
 | |
| 	r := factory.NewBuilder().
 | |
| 		WithScheme(scheme.Scheme, scheme.Scheme.PrioritizedVersionsAllGroups()...).
 | |
| 		NamespaceParam(namespace).DefaultNamespace().
 | |
| 		ResourceTypeOrNameArgs(true, workloads).
 | |
| 		SingleResourceType().
 | |
| 		Latest().
 | |
| 		Do()
 | |
| 	err := r.Err()
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	infos, err := r.Infos()
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	if len(infos) != 1 {
 | |
| 		return fmt.Errorf("rollout status is only supported on individual resources and resource collections - %d resources were found", len(infos))
 | |
| 	}
 | |
| 	info := infos[0]
 | |
| 	mapping := info.ResourceMapping()
 | |
| 
 | |
| 	statusViewer, err := polymorphichelpers.StatusViewerFn(mapping)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	fieldSelector := fields.OneTermEqualSelector("metadata.name", info.Name).String()
 | |
| 	lw := &cache.ListWatch{
 | |
| 		ListFunc: func(options metav1.ListOptions) (k8sruntime.Object, error) {
 | |
| 			options.FieldSelector = fieldSelector
 | |
| 			return client.Resource(info.Mapping.Resource).Namespace(info.Namespace).List(context.TODO(), options)
 | |
| 		},
 | |
| 		WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
 | |
| 			options.FieldSelector = fieldSelector
 | |
| 			return client.Resource(info.Mapping.Resource).Namespace(info.Namespace).Watch(context.TODO(), options)
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	// if the rollout isn't done yet, keep watching deployment status
 | |
| 	ctx, cancel := watchtools.ContextWithOptionalTimeout(context.Background(), timeout)
 | |
| 	intr := interrupt.New(nil, cancel)
 | |
| 	return intr.Run(func() error {
 | |
| 		_, err = watchtools.UntilWithSync(ctx, lw, &unstructured.Unstructured{}, nil, func(e watch.Event) (bool, error) {
 | |
| 			switch t := e.Type; t {
 | |
| 			case watch.Added, watch.Modified:
 | |
| 				status, done, err := statusViewer.Status(e.Object.(k8sruntime.Unstructured), 0)
 | |
| 				if err != nil {
 | |
| 					return false, err
 | |
| 				}
 | |
| 				_, _ = fmt.Fprintf(os.Stdout, "%s", status)
 | |
| 				// Quit waiting if the rollout is done
 | |
| 				if done {
 | |
| 					return true, nil
 | |
| 				}
 | |
| 
 | |
| 				return false, nil
 | |
| 
 | |
| 			case watch.Deleted:
 | |
| 				// We need to abort to avoid cases of recreation and not to silently watch the wrong (new) object
 | |
| 				return true, fmt.Errorf("object has been deleted")
 | |
| 
 | |
| 			default:
 | |
| 				return true, fmt.Errorf("internal error: unexpected event %#v", e)
 | |
| 			}
 | |
| 		})
 | |
| 		return err
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func RunWithRollingOutWithChecker(cmd *osexec.Cmd, checker func(log string) bool) (string, string, error) {
 | |
| 	stdoutBuf := bytes.NewBuffer(make([]byte, 0))
 | |
| 	stderrBuf := bytes.NewBuffer(make([]byte, 0))
 | |
| 
 | |
| 	stdoutPipe, _ := cmd.StdoutPipe()
 | |
| 	stderrPipe, _ := cmd.StderrPipe()
 | |
| 	stdout := io.MultiWriter(os.Stdout, stdoutBuf)
 | |
| 	stderr := io.MultiWriter(os.Stderr, stderrBuf)
 | |
| 	go func() {
 | |
| 		_, _ = io.Copy(stdout, stdoutPipe)
 | |
| 	}()
 | |
| 	go func() {
 | |
| 		_, _ = io.Copy(stderr, stderrPipe)
 | |
| 	}()
 | |
| 	go func() {
 | |
| 		if checker != nil {
 | |
| 			for {
 | |
| 				if checker(stdoutBuf.String()) || checker(stderrBuf.String()) {
 | |
| 					break
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 	}()
 | |
| 	if err := cmd.Start(); err != nil {
 | |
| 		if cmd.Process != nil {
 | |
| 			_ = cmd.Process.Kill()
 | |
| 		}
 | |
| 		return stdoutBuf.String(), stderrBuf.String(), err
 | |
| 	}
 | |
| 	_ = cmd.Wait()
 | |
| 	var err error
 | |
| 	if !cmd.ProcessState.Success() {
 | |
| 		err = errors.New("exit code is not 0")
 | |
| 	}
 | |
| 
 | |
| 	stdoutStr := strings.TrimSpace(stdoutBuf.String())
 | |
| 	stderrStr := strings.TrimSpace(stderrBuf.String())
 | |
| 
 | |
| 	return stdoutStr, stderrStr, err
 | |
| }
 | |
| 
 | |
| func Heartbeats(ctx context.Context) {
 | |
| 	c2 := make(chan struct{}, 1)
 | |
| 	c2 <- struct{}{}
 | |
| 	for {
 | |
| 		select {
 | |
| 		case <-time.Tick(time.Second * 15):
 | |
| 			c2 <- struct{}{}
 | |
| 		case <-c2:
 | |
| 			for i := 0; i < 4; i++ {
 | |
| 				_, _ = Ping(config.RouterIP.String())
 | |
| 			}
 | |
| 		case <-ctx.Done():
 | |
| 			return
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func WaitPortToBeFree(ctx context.Context, port int) error {
 | |
| 	log.Infoln(fmt.Sprintf("wait port %v to be free...", port))
 | |
| 	for {
 | |
| 		select {
 | |
| 		case <-ctx.Done():
 | |
| 			return fmt.Errorf("wait port %v to be free timeout", port)
 | |
| 		case <-time.Tick(time.Second * 2):
 | |
| 			if !IsPortListening(port) {
 | |
| 				log.Infoln(fmt.Sprintf("port %v are free", port))
 | |
| 				return nil
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func IsPortListening(port int) bool {
 | |
| 	listener, err := net.Listen("tcp4", net.JoinHostPort("0.0.0.0", strconv.Itoa(port)))
 | |
| 	if err != nil {
 | |
| 		return true
 | |
| 	} else {
 | |
| 		listener.Close()
 | |
| 		return false
 | |
| 	}
 | |
| }
 | 
