diff --git a/README.md b/README.md index 48f871853..f1b047d6d 100644 --- a/README.md +++ b/README.md @@ -344,6 +344,48 @@ NAME READY STATUS RESTARTS AGE multus-multi-net-poc 1/1 Running 0 30s ``` +#### Pod Annotation Parameters + +JSON formated network annotation in Pod can have several parameters as following: + +- namespace: Kubernetes namespace that the target network attach definition is defined in. +- mac: MAC address (e.g "c2:11:22:33:44:66") for target network interface +- interfaceRequest: interface name for target network interface + +Note: If you add `mac`, please add 'tuning' plugin into target network attach definition as CNI plugin chaining as following. + +``` +apiVersion: "k8s.cni.cncf.io/v1" +kind: NetworkAttachmentDefinition +metadata: + name: macvlan with tuning +spec: + config: '{ + "cniVersion": "0.3.0", + "name": "chains", + "plugins": [ { + "type": "macvlan", + "master": "eth0", + "mode": "bridge", + "ipam": { + "type": "host-local", + "subnet": "192.168.1.0/24", + "rangeStart": "192.168.1.200", + "rangeEnd": "192.168.1.216", + "routes": [ + { "dst": "0.0.0.0/0" } + ], + "gateway": "192.168.1.1" + } + }, + { + "name":"macchange", + "type":"tuning" + }] + + }' +``` + ### Verifying Pod network interfaces 1. Run `ifconfig` command in Pod: diff --git a/k8sclient/k8sclient.go b/k8sclient/k8sclient.go index fe5b548af..d1a5a5363 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 400ff1f57..eb5f41b5b 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,19 @@ 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("CNI_ARGS", fmt.Sprintf("IgnoreUnknown=true;MAC=%s", delegate.MacRequest)) != nil { + return nil, logging.Errorf("cannot set %q mac to %q: %v", delegate.Conf.Type, delegate.MacRequest, err) + } + logging.Debugf("Set MAC address %q to %q", delegate.MacRequest, ifName) + } + if delegate.ConfListPlugin != false { result, err := conflistAdd(rt, delegate.Bytes, binDir, exec) if err != nil { diff --git a/multus/multus_test.go b/multus/multus_test.go index fb96c3a82..6926aea8c 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", + "mac": "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"), @@ -358,7 +439,7 @@ var _ = Describe("multus operations", func() { ] } }` - fExec.addPlugin(nil, "eth0", expectedConf1, nil, nil) + fExec.addPlugin(nil, "eth0", "", expectedConf1, nil, nil) os.Setenv("CNI_COMMAND", "ADD") os.Setenv("CNI_IFNAME", "eth0") _, err := cmdAdd(args, fExec, nil) diff --git a/types/conf.go b/types/conf.go index 3081ad1d4..f651d2406 100644 --- a/types/conf.go +++ b/types/conf.go @@ -51,7 +51,9 @@ 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) { + logging.Debugf("LoadDelegateNetConf: %s, %v, %s", string(bytes), net, deviceID) + // If deviceID is present, inject this into delegate config if deviceID != "" { if updatedBytes, err := delegateAddDeviceID(bytes, deviceID); err != nil { @@ -62,7 +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 @@ -210,7 +216,7 @@ func LoadNetConf(bytes []byte) (*NetConf, error) { if err != nil { return nil, logging.Errorf("error marshalling delegate %d config: %v", idx, err) } - delegateConf, err := LoadDelegateNetConf(bytes, "", "") + delegateConf, err := LoadDelegateNetConf(bytes, nil, "") if err != nil { return nil, logging.Errorf("failed to load delegate %d config: %v", idx, err) } diff --git a/types/types.go b/types/types.go index fa5e48ace..c89e43819 100644 --- a/types/types.go +++ b/types/types.go @@ -71,6 +71,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 @@ -116,10 +117,10 @@ type NetworkSelectionElement struct { Namespace string `json:"namespace,omitempty"` // IPRequest contains an optional requested IP address for this network // attachment - IPRequest string `json:"ipRequest,omitempty"` + IPRequest string `json:"ips,omitempty"` // MacRequest contains an optional requested MAC address for this // network attachment - MacRequest string `json:"macRequest,omitempty"` + MacRequest string `json:"mac,omitempty"` // InterfaceRequest contains an optional requested name for the // network interface this attachment will create in the container InterfaceRequest string `json:"interfaceRequest,omitempty"`