diff --git a/k8sclient/k8sclient.go b/k8sclient/k8sclient.go index 58691f5d6..6368aa499 100644 --- a/k8sclient/k8sclient.go +++ b/k8sclient/k8sclient.go @@ -379,7 +379,7 @@ func getKubernetesDelegate(client KubeClient, net *types.NetworkSelectionElement return nil, resourceMap, err } - delegate, err := types.LoadDelegateNetConf(configBytes, net.InterfaceRequest, deviceID) + delegate, err := types.LoadDelegateNetConf(configBytes, net, deviceID) if err != nil { return nil, resourceMap, err } diff --git a/multus/multus.go b/multus/multus.go index 2ddda2356..62500b9dc 100644 --- a/multus/multus.go +++ b/multus/multus.go @@ -22,6 +22,7 @@ import ( "encoding/json" "fmt" "io/ioutil" + "net" "os" "path/filepath" "time" @@ -169,6 +170,18 @@ func delegateAdd(exec invoke.Exec, ifName string, delegate *types.DelegateNetCon return nil, logging.Errorf("cannot set %q ifname to %q: %v", delegate.Conf.Type, ifName, err) } + if delegate.MacRequest != "" { + // validate Mac address + _, err := net.ParseMAC(delegate.MacRequest) + if err != nil { + return nil, logging.Errorf("failed to parse mac address %q", delegate.MacRequest) + } + + if os.Setenv("MAC", delegate.MacRequest) != nil { + return nil, logging.Errorf("cannot set %q mac to %q: %v", delegate.Conf.Type, delegate.MacRequest, err) + } + } + if delegate.ConfListPlugin != false { result, err := conflistAdd(rt, delegate.Bytes, binDir) if err != nil { diff --git a/multus/multus_test.go b/multus/multus_test.go index d05a0dbb1..8cb2ca8d5 100644 --- a/multus/multus_test.go +++ b/multus/multus_test.go @@ -44,11 +44,12 @@ func TestMultus(t *testing.T) { } type fakePlugin struct { - expectedEnv []string - expectedConf string - expectedIfname string - result cnitypes.Result - err error + expectedEnv []string + expectedConf string + expectedIfname string + expectedMacAddr string + result cnitypes.Result + err error } type fakeExec struct { @@ -59,7 +60,7 @@ type fakeExec struct { plugins []*fakePlugin } -func (f *fakeExec) addPlugin(expectedEnv []string, expectedIfname, expectedConf string, result *types020.Result, err error) { +func (f *fakeExec) addPlugin(expectedEnv []string, expectedIfname, expectedMacAddr, expectedConf string, result *types020.Result, err error) { f.plugins = append(f.plugins, &fakePlugin{ expectedEnv: expectedEnv, expectedConf: expectedConf, @@ -123,6 +124,9 @@ func (f *fakeExec) ExecPlugin(pluginPath string, stdinData []byte, environ []str if plugin.expectedIfname != "" { Expect(os.Getenv("CNI_IFNAME")).To(Equal(plugin.expectedIfname)) } + if plugin.expectedMacAddr != "" { + Expect(os.Getenv("MAC")).To(Equal(plugin.expectedMacAddr)) + } if len(plugin.expectedEnv) > 0 { matchArray(gatherCNIEnv(), plugin.expectedEnv) } @@ -203,7 +207,7 @@ var _ = Describe("multus operations", func() { "cniVersion": "0.2.0", "type": "weave-net" }` - fExec.addPlugin(nil, "eth0", expectedConf1, expectedResult1, nil) + fExec.addPlugin(nil, "eth0", "", expectedConf1, expectedResult1, nil) expectedResult2 := &types020.Result{ CNIVersion: "0.2.0", @@ -216,7 +220,7 @@ var _ = Describe("multus operations", func() { "cniVersion": "0.2.0", "type": "other-plugin" }` - fExec.addPlugin(nil, "net1", expectedConf2, expectedResult2, nil) + fExec.addPlugin(nil, "net1", "", expectedConf2, expectedResult2, nil) os.Setenv("CNI_COMMAND", "ADD") os.Setenv("CNI_IFNAME", "eth0") @@ -241,6 +245,83 @@ var _ = Describe("multus operations", func() { }) + It("executes delegates with interface name and MAC addr", func() { + podNet := `[{"name":"net1", + "interfaceRequest": "test1"}, + {"name":"net2", + "macRequest": "c2:11:22:33:44:66"} +]` + fakePod := testhelpers.NewFakePod("testpod", podNet) + net1 := `{ + "name": "net1", + "type": "mynet", + "cniVersion": "0.2.0" +}` + net2 := `{ + "name": "net2", + "type": "mynet2", + "cniVersion": "0.2.0" +}` + args := &skel.CmdArgs{ + ContainerID: "123456789", + Netns: testNS.Path(), + IfName: "eth0", + Args: fmt.Sprintf("K8S_POD_NAME=%s;K8S_POD_NAMESPACE=%s", fakePod.ObjectMeta.Name, fakePod.ObjectMeta.Namespace), + StdinData: []byte(`{ + "name": "node-cni-network", + "type": "multus", + "kubeconfig": "/etc/kubernetes/node-kubeconfig.yaml", + "delegates": [{ + "name": "weave1", + "cniVersion": "0.2.0", + "type": "weave-net" + }] +}`), + } + + fExec := &fakeExec{} + expectedResult1 := &types020.Result{ + CNIVersion: "0.2.0", + IP4: &types020.IPConfig{ + IP: *testhelpers.EnsureCIDR("1.1.1.2/24"), + }, + } + expectedConf1 := `{ + "name": "weave1", + "cniVersion": "0.2.0", + "type": "weave-net" +}` + fExec.addPlugin(nil, "eth0", "", expectedConf1, expectedResult1, nil) + fExec.addPlugin(nil, "test1", "", net1, &types020.Result{ + CNIVersion: "0.2.0", + IP4: &types020.IPConfig{ + IP: *testhelpers.EnsureCIDR("1.1.1.3/24"), + }, + }, nil) + fExec.addPlugin(nil, "net2", "c2:11:22:33:44:66", net2, &types020.Result{ + CNIVersion: "0.2.0", + IP4: &types020.IPConfig{ + IP: *testhelpers.EnsureCIDR("1.1.1.4/24"), + }, + }, nil) + + fKubeClient := testhelpers.NewFakeKubeClient() + fKubeClient.AddPod(fakePod) + fKubeClient.AddNetConfig(fakePod.ObjectMeta.Namespace, "net1", net1) + fKubeClient.AddNetConfig(fakePod.ObjectMeta.Namespace, "net2", net2) + + os.Setenv("CNI_COMMAND", "ADD") + os.Setenv("CNI_IFNAME", "eth0") + result, err := cmdAdd(args, fExec, fKubeClient) + Expect(err).NotTo(HaveOccurred()) + Expect(fExec.addIndex).To(Equal(len(fExec.plugins))) + Expect(fKubeClient.PodCount).To(Equal(2)) + Expect(fKubeClient.NetCount).To(Equal(2)) + r := result.(*types020.Result) + // plugin 1 is the masterplugin + Expect(reflect.DeepEqual(r, expectedResult1)).To(BeTrue()) + }) + It("executes delegates and kubernetes networks", func() { fakePod := testhelpers.NewFakePod("testpod", "net1,net2") net1 := `{ @@ -287,14 +368,14 @@ var _ = Describe("multus operations", func() { "cniVersion": "0.2.0", "type": "weave-net" }` - fExec.addPlugin(nil, "eth0", expectedConf1, expectedResult1, nil) - fExec.addPlugin(nil, "net1", net1, &types020.Result{ + fExec.addPlugin(nil, "eth0", "", expectedConf1, expectedResult1, nil) + fExec.addPlugin(nil, "net1", "", net1, &types020.Result{ CNIVersion: "0.2.0", IP4: &types020.IPConfig{ IP: *testhelpers.EnsureCIDR("1.1.1.3/24"), }, }, nil) - fExec.addPlugin(nil, "net2", net2, &types020.Result{ + fExec.addPlugin(nil, "net2", "", net2, &types020.Result{ CNIVersion: "0.2.0", IP4: &types020.IPConfig{ IP: *testhelpers.EnsureCIDR("1.1.1.4/24"), diff --git a/types/conf.go b/types/conf.go index 4f4d9295d..77e1ed405 100644 --- a/types/conf.go +++ b/types/conf.go @@ -51,7 +51,10 @@ func LoadDelegateNetConfList(bytes []byte, delegateConf *DelegateNetConf) error } // Convert raw CNI JSON into a DelegateNetConf structure -func LoadDelegateNetConf(bytes []byte, ifnameRequest, deviceID string) (*DelegateNetConf, error) { +func LoadDelegateNetConf(bytes []byte, net *NetworkSelectionElement, deviceID string) (*DelegateNetConf, error) { + delegateConf := &DelegateNetConf{} + logging.Debugf("LoadDelegateNetConf: %s, %v", string(bytes), net) + // If deviceID is present, inject this into delegate config if deviceID != "" { if updatedBytes, err := delegateAddDeviceID(bytes, deviceID); err != nil { @@ -61,8 +64,6 @@ func LoadDelegateNetConf(bytes []byte, ifnameRequest, deviceID string) (*Delegat } } - delegateConf := &DelegateNetConf{} - logging.Debugf("LoadDelegateNetConf: %s, %s", string(bytes), ifnameRequest) if err := json.Unmarshal(bytes, &delegateConf.Conf); err != nil { return nil, logging.Errorf("error in LoadDelegateNetConf - unmarshalling delegate config: %v", err) } @@ -74,8 +75,13 @@ func LoadDelegateNetConf(bytes []byte, ifnameRequest, deviceID string) (*Delegat } } - if ifnameRequest != "" { - delegateConf.IfnameRequest = ifnameRequest + if net != nil { + if net.InterfaceRequest != "" { + delegateConf.IfnameRequest = net.InterfaceRequest + } + if net.MacRequest != "" { + delegateConf.MacRequest = net.MacRequest + } } delegateConf.Bytes = bytes diff --git a/types/types.go b/types/types.go index 932d71aaa..dadeb309d 100644 --- a/types/types.go +++ b/types/types.go @@ -62,6 +62,7 @@ type DelegateNetConf struct { Conf types.NetConf ConfList types.NetConfList IfnameRequest string `json:"ifnameRequest,omitempty"` + MacRequest string `json:"macRequest,omitempty"` // MasterPlugin is only used internal housekeeping MasterPlugin bool `json:"-"` // Conflist plugin is only used internal housekeeping