diff --git a/k8sclient/k8sclient.go b/k8sclient/k8sclient.go index c45492a21..e1909d971 100644 --- a/k8sclient/k8sclient.go +++ b/k8sclient/k8sclient.go @@ -17,12 +17,14 @@ package k8sclient import ( "encoding/json" "fmt" + "os" "regexp" "strings" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" v1 "k8s.io/client-go/pkg/api/v1" + "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" "github.com/containernetworking/cni/libcni" @@ -53,22 +55,6 @@ func (d *defaultKubeClient) GetPod(namespace, name string) (*v1.Pod, error) { return d.client.Core().Pods(namespace).Get(name, metav1.GetOptions{}) } -func createK8sClient(kubeconfig string) (KubeClient, error) { - // uses the current context in kubeconfig - config, err := clientcmd.BuildConfigFromFlags("", kubeconfig) - if err != nil { - return nil, fmt.Errorf("createK8sClient: failed to get context for the kubeconfig %v, refer Multus README.md for the usage guide: %v", kubeconfig, err) - } - - // creates the clientset - client, err := kubernetes.NewForConfig(config) - if err != nil { - return nil, err - } - - return &defaultKubeClient{client: client}, nil -} - func getPodNetworkAnnotation(client KubeClient, k8sArgs types.K8sArgs) (string, string, error) { var err error @@ -276,7 +262,44 @@ type KubeClient interface { GetPod(namespace, name string) (*v1.Pod, error) } -func GetK8sNetwork(args *skel.CmdArgs, kubeconfig string, k8sclient KubeClient, confdir string) ([]*types.DelegateNetConf, error) { +func GetK8sClient(kubeconfig string, kubeClient KubeClient) (KubeClient, error) { + // If we get a valid kubeClient (eg from testcases) just return that + // one. + if kubeClient != nil { + return kubeClient, nil + } + + var err error + var config *rest.Config + + // Otherwise try to create a kubeClient from a given kubeConfig + if kubeconfig != "" { + // uses the current context in kubeconfig + config, err = clientcmd.BuildConfigFromFlags("", kubeconfig) + if err != nil { + return nil, fmt.Errorf("GetK8sClient: failed to get context for the kubeconfig %v, refer Multus README.md for the usage guide: %v", kubeconfig, err) + } + } else if os.Getenv("KUBERNETES_SERVICE_HOST") != "" && os.Getenv("KUBERNETES_SERVICE_PORT") != "" { + // Try in-cluster config where multus might be running in a kubernetes pod + config, err = rest.InClusterConfig() + if err != nil { + return nil, fmt.Errorf("createK8sClient: failed to get context for in-cluster kube config, refer Multus README.md for the usage guide: %v", err) + } + } else { + // No kubernetes config; assume we shouldn't talk to Kube at all + return nil, nil + } + + // creates the clientset + client, err := kubernetes.NewForConfig(config) + if err != nil { + return nil, err + } + + return &defaultKubeClient{client: client}, nil +} + +func GetK8sNetwork(k8sclient KubeClient, args *skel.CmdArgs, confdir string) ([]*types.DelegateNetConf, error) { k8sArgs := types.K8sArgs{} err := cnitypes.LoadArgs(args.Args, &k8sArgs) @@ -284,13 +307,6 @@ func GetK8sNetwork(args *skel.CmdArgs, kubeconfig string, k8sclient KubeClient, return nil, err } - if k8sclient == nil { - k8sclient, err = createK8sClient(kubeconfig) - if err != nil { - return nil, err - } - } - netAnnot, defaultNamespace, err := getPodNetworkAnnotation(k8sclient, k8sArgs) if err != nil { return nil, err diff --git a/k8sclient/k8sclient_test.go b/k8sclient/k8sclient_test.go index d7a390710..25f3a9170 100644 --- a/k8sclient/k8sclient_test.go +++ b/k8sclient/k8sclient_test.go @@ -77,7 +77,9 @@ var _ = Describe("k8sclient operations", func() { // net3 is not used; make sure it's not accessed fKubeClient.AddNetConfig(fakePod.ObjectMeta.Namespace, "net3", net3) - delegates, err := GetK8sNetwork(args, "", fKubeClient, tmpDir) + kubeClient, err := GetK8sClient("", fKubeClient) + Expect(err).NotTo(HaveOccurred()) + delegates, err := GetK8sNetwork(kubeClient, args, tmpDir) Expect(err).NotTo(HaveOccurred()) Expect(fKubeClient.PodCount).To(Equal(1)) Expect(fKubeClient.NetCount).To(Equal(2)) @@ -106,7 +108,9 @@ var _ = Describe("k8sclient operations", func() { fKubeClient.AddPod(fakePod) fKubeClient.AddNetConfig(fakePod.ObjectMeta.Namespace, "net3", net3) - delegates, err := GetK8sNetwork(args, "", fKubeClient, tmpDir) + kubeClient, err := GetK8sClient("", fKubeClient) + Expect(err).NotTo(HaveOccurred()) + delegates, err := GetK8sNetwork(kubeClient, args, tmpDir) Expect(len(delegates)).To(Equal(0)) Expect(err).To(MatchError("GetK8sNetwork: failed getting the delegate: getKubernetesDelegate: failed to get network resource, refer Multus README.md for the usage guide: resource not found")) }) @@ -146,7 +150,9 @@ var _ = Describe("k8sclient operations", func() { "cniVersion": "0.2.0" }`) - delegates, err := GetK8sNetwork(args, "", fKubeClient, tmpDir) + kubeClient, err := GetK8sClient("", fKubeClient) + Expect(err).NotTo(HaveOccurred()) + delegates, err := GetK8sNetwork(kubeClient, args, tmpDir) Expect(err).NotTo(HaveOccurred()) Expect(fKubeClient.PodCount).To(Equal(1)) Expect(fKubeClient.NetCount).To(Equal(3)) @@ -169,7 +175,9 @@ var _ = Describe("k8sclient operations", func() { fKubeClient := testutils.NewFakeKubeClient() fKubeClient.AddPod(fakePod) - delegates, err := GetK8sNetwork(args, "", fKubeClient, tmpDir) + kubeClient, err := GetK8sClient("", fKubeClient) + Expect(err).NotTo(HaveOccurred()) + delegates, err := GetK8sNetwork(kubeClient, args, tmpDir) Expect(len(delegates)).To(Equal(0)) Expect(err).To(MatchError("parsePodNetworkAnnotation: failed to parse pod Network Attachment Selection Annotation JSON format: invalid character 'a' looking for beginning of value")) }) @@ -195,7 +203,9 @@ var _ = Describe("k8sclient operations", func() { "cniVersion": "0.2.0" }`) - delegates, err := GetK8sNetwork(args, "", fKubeClient, tmpDir) + kubeClient, err := GetK8sClient("", fKubeClient) + Expect(err).NotTo(HaveOccurred()) + delegates, err := GetK8sNetwork(kubeClient, args, tmpDir) Expect(err).NotTo(HaveOccurred()) Expect(fKubeClient.PodCount).To(Equal(1)) Expect(fKubeClient.NetCount).To(Equal(2)) @@ -244,7 +254,9 @@ var _ = Describe("k8sclient operations", func() { net2Name := filepath.Join(tmpDir, "20-net2.conf") fKubeClient.AddNetFile(fakePod.ObjectMeta.Namespace, "net2", net2Name, "asdfasdfasfdasfd") - delegates, err := GetK8sNetwork(args, "", fKubeClient, tmpDir) + kubeClient, err := GetK8sClient("", fKubeClient) + Expect(err).NotTo(HaveOccurred()) + delegates, err := GetK8sNetwork(kubeClient, args, tmpDir) Expect(len(delegates)).To(Equal(0)) Expect(err).To(MatchError(fmt.Sprintf("GetK8sNetwork: failed getting the delegate: cniConfigFromNetworkResource: err in getCNIConfigFromFile: Error loading CNI config file %s: error parsing configuration: invalid character 'a' looking for beginning of value", net2Name))) }) diff --git a/multus/multus.go b/multus/multus.go index ad9fcd599..e46d39854 100644 --- a/multus/multus.go +++ b/multus/multus.go @@ -148,29 +148,52 @@ func delPlugins(exec invoke.Exec, argIfname string, delegates []*types.DelegateN return nil } +// Attempts to load Kubernetes-defined delegates and add them to the Multus config. +// Returns the number of Kubernetes-defined delegates added or an error. +func tryLoadK8sDelegates(args *skel.CmdArgs, conf *types.NetConf, kubeClient k8s.KubeClient) (int, error) { + var err error + + kubeClient, err = k8s.GetK8sClient(conf.Kubeconfig, kubeClient) + if err != nil { + return 0, err + } + + if kubeClient == nil { + if len(conf.Delegates) == 0 { + // No available kube client and no delegates, we can't do anything + return 0, fmt.Errorf("must have either Kubernetes config or delegates, refer Multus README.md for the usage guide") + } + return 0, nil + } + + delegates, err := k8s.GetK8sNetwork(kubeClient, args, conf.ConfDir) + if err != nil { + if _, ok := err.(*k8s.NoK8sNetworkError); ok { + return 0, nil + } + return 0, fmt.Errorf("Multus: Err in getting k8s network from pod: %v", err) + } + + if err = conf.AddDelegates(delegates); err != nil { + return 0, err + } + + return len(delegates), nil +} + func cmdAdd(args *skel.CmdArgs, exec invoke.Exec, kubeClient k8s.KubeClient) (cnitypes.Result, error) { - var nopodnet bool n, err := types.LoadNetConf(args.StdinData) if err != nil { return nil, fmt.Errorf("err in loading netconf: %v", err) } - if n.Kubeconfig != "" { - delegates, err := k8s.GetK8sNetwork(args, n.Kubeconfig, kubeClient, n.ConfDir) - if err != nil { - if _, ok := err.(*k8s.NoK8sNetworkError); ok { - nopodnet = true - } else { - return nil, fmt.Errorf("Multus: Err in getting k8s network from pod: %v", err) - } - } - - if err = n.AddDelegates(delegates); err != nil { - return nil, err - } + numK8sDelegates, err := tryLoadK8sDelegates(args, n, kubeClient) + if err != nil { + return nil, err } - if n.Kubeconfig == "" || nopodnet { + if numK8sDelegates == 0 { + // cache the multus config if we have only Multus delegates if err := saveDelegates(args.ContainerID, n.CNIDir, n.Delegates); err != nil { return nil, fmt.Errorf("Multus: Err in saving the delegates: %v", err) } @@ -213,29 +236,18 @@ func cmdGet(args *skel.CmdArgs, exec invoke.Exec, kubeClient k8s.KubeClient) (cn } func cmdDel(args *skel.CmdArgs, exec invoke.Exec, kubeClient k8s.KubeClient) error { - var nopodnet bool - in, err := types.LoadNetConf(args.StdinData) if err != nil { return err } - if in.Kubeconfig != "" { - delegates, err := k8s.GetK8sNetwork(args, in.Kubeconfig, kubeClient, in.ConfDir) - if err != nil { - if _, ok := err.(*k8s.NoK8sNetworkError); ok { - nopodnet = true - } else { - return fmt.Errorf("Multus: Err in getting k8s network from pod: %v", err) - } - } - - if err = in.AddDelegates(delegates); err != nil { - return err - } + numK8sDelegates, err := tryLoadK8sDelegates(args, in, kubeClient) + if err != nil { + return err } - if in.Kubeconfig == "" || nopodnet { + if numK8sDelegates == 0 { + // re-read the scratch multus config if we have only Multus delegates netconfBytes, err := consumeScratchNetConf(args.ContainerID, in.CNIDir) if err != nil { if os.IsNotExist(err) {