From ec3eb35c8e26a7a2999dfc3574c255ccd94d5c9c Mon Sep 17 00:00:00 2001 From: VZ Cambria Date: Wed, 28 Dec 2016 11:17:33 -0500 Subject: [PATCH] New vz-macvlan plugin --- plugins/main/vz-macvlan/macvlan.go | 198 ++++++++++++++++++ plugins/main/vz-macvlan/macvlan_suite_test.go | 27 +++ plugins/main/vz-macvlan/macvlan_test.go | 175 ++++++++++++++++ 3 files changed, 400 insertions(+) create mode 100644 plugins/main/vz-macvlan/macvlan.go create mode 100644 plugins/main/vz-macvlan/macvlan_suite_test.go create mode 100644 plugins/main/vz-macvlan/macvlan_test.go diff --git a/plugins/main/vz-macvlan/macvlan.go b/plugins/main/vz-macvlan/macvlan.go new file mode 100644 index 0000000..ef01269 --- /dev/null +++ b/plugins/main/vz-macvlan/macvlan.go @@ -0,0 +1,198 @@ +// Copyright 2015 CNI authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "encoding/json" + "errors" + "fmt" + "runtime" + + "github.com/containernetworking/cni/pkg/ip" + "github.com/containernetworking/cni/pkg/ipam" + "github.com/containernetworking/cni/pkg/ns" + "github.com/containernetworking/cni/pkg/skel" + "github.com/containernetworking/cni/pkg/types" + "github.com/containernetworking/cni/pkg/utils/sysctl" + "github.com/containernetworking/cni/pkg/version" + "github.com/vishvananda/netlink" +) + +const ( + IPv4InterfaceArpProxySysctlTemplate = "net.ipv4.conf.%s.proxy_arp" +) + +type NetConf struct { + types.NetConf + Master string `json:"master"` + Mode string `json:"mode"` + MTU int `json:"mtu"` +} + +func init() { + // this ensures that main runs only on main thread (thread group leader). + // since namespace ops (unshare, setns) are done for a single thread, we + // must ensure that the goroutine does not jump from OS thread to thread + runtime.LockOSThread() +} + +func loadConf(bytes []byte) (*NetConf, error) { + n := &NetConf{} + if err := json.Unmarshal(bytes, n); err != nil { + return nil, fmt.Errorf("failed to load netconf: %v", err) + } + if n.Master == "" { + return nil, fmt.Errorf(`"master" field is required. It specifies the host interface name to virtualize`) + } + return n, nil +} + +func modeFromString(s string) (netlink.MacvlanMode, error) { + switch s { + case "", "bridge": + return netlink.MACVLAN_MODE_BRIDGE, nil + case "private": + return netlink.MACVLAN_MODE_PRIVATE, nil + case "vepa": + return netlink.MACVLAN_MODE_VEPA, nil + case "passthru": + return netlink.MACVLAN_MODE_PASSTHRU, nil + default: + return 0, fmt.Errorf("unknown macvlan mode: %q", s) + } +} + +func createMacvlan(conf *NetConf, ifName string, netns ns.NetNS) error { + mode, err := modeFromString(conf.Mode) + if err != nil { + return err + } + + m, err := netlink.LinkByName(conf.Master) + if err != nil { + return fmt.Errorf("failed to lookup master %q: %v", conf.Master, err) + } + + // due to kernel bug we have to create with tmpName or it might + // collide with the name on the host and error out + tmpName, err := ip.RandomVethName() + if err != nil { + return err + } + + mv := &netlink.Macvlan{ + LinkAttrs: netlink.LinkAttrs{ + MTU: conf.MTU, + Name: tmpName, + ParentIndex: m.Attrs().Index, + Namespace: netlink.NsFd(int(netns.Fd())), + }, + Mode: mode, + } + + if err := netlink.LinkAdd(mv); err != nil { + return fmt.Errorf("failed to create macvlan: %v", err) + } + + return netns.Do(func(_ ns.NetNS) error { + // TODO: duplicate following lines for ipv6 support, when it will be added in other places + ipv4SysctlValueName := fmt.Sprintf(IPv4InterfaceArpProxySysctlTemplate, tmpName) + if _, err := sysctl.Sysctl(ipv4SysctlValueName, "1"); err != nil { + // remove the newly added link and ignore errors, because we already are in a failed state + _ = netlink.LinkDel(mv) + return fmt.Errorf("failed to set proxy_arp on newly added interface %q: %v", tmpName, err) + } + + err := renameLink(tmpName, ifName) + if err != nil { + _ = netlink.LinkDel(mv) + return fmt.Errorf("failed to rename macvlan to %q: %v", ifName, err) + } + return nil + }) +} + +func cmdAdd(args *skel.CmdArgs) error { + n, err := loadConf(args.StdinData) + if err != nil { + return err + } + + netns, err := ns.GetNS(args.Netns) + if err != nil { + return fmt.Errorf("failed to open netns %q: %v", netns, err) + } + defer netns.Close() + + if err = createMacvlan(n, args.IfName, netns); err != nil { + return err + } + + // run the IPAM plugin and get back the config to apply + result, err := ipam.ExecAdd(n.IPAM.Type, args.StdinData) + if err != nil { + return err + } + if result.IP4 == nil { + return errors.New("IPAM plugin returned missing IPv4 config") + } + + err = netns.Do(func(_ ns.NetNS) error { + if err := ip.SetHWAddrByIP(args.IfName, result.IP4.IP.IP, nil /* TODO IPv6 */); err != nil { + return err + } + + return ipam.ConfigureIface(args.IfName, result) + }) + if err != nil { + return err + } + + result.DNS = n.DNS + return result.Print() +} + +func cmdDel(args *skel.CmdArgs) error { + n, err := loadConf(args.StdinData) + if err != nil { + return err + } + + err = ipam.ExecDel(n.IPAM.Type, args.StdinData) + if err != nil { + return err + } + + if args.Netns == "" { + return nil + } + + return ns.WithNetNSPath(args.Netns, func(_ ns.NetNS) error { + return ip.DelLinkByName(args.IfName) + }) +} + +func renameLink(curName, newName string) error { + link, err := netlink.LinkByName(curName) + if err != nil { + return err + } + + return netlink.LinkSetName(link, newName) +} + +func main() { + skel.PluginMain(cmdAdd, cmdDel, version.Legacy) +} diff --git a/plugins/main/vz-macvlan/macvlan_suite_test.go b/plugins/main/vz-macvlan/macvlan_suite_test.go new file mode 100644 index 0000000..844e567 --- /dev/null +++ b/plugins/main/vz-macvlan/macvlan_suite_test.go @@ -0,0 +1,27 @@ +// Copyright 2016 CNI authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + "testing" +) + +func TestMacvlan(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "macvlan Suite") +} diff --git a/plugins/main/vz-macvlan/macvlan_test.go b/plugins/main/vz-macvlan/macvlan_test.go new file mode 100644 index 0000000..9594a59 --- /dev/null +++ b/plugins/main/vz-macvlan/macvlan_test.go @@ -0,0 +1,175 @@ +// Copyright 2015 CNI authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "fmt" + + "github.com/containernetworking/cni/pkg/ns" + "github.com/containernetworking/cni/pkg/skel" + "github.com/containernetworking/cni/pkg/testutils" + "github.com/containernetworking/cni/pkg/types" + "github.com/containernetworking/cni/pkg/utils/hwaddr" + + "github.com/vishvananda/netlink" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +const MASTER_NAME = "eth0" + +var _ = Describe("macvlan Operations", func() { + var originalNS ns.NetNS + + BeforeEach(func() { + // Create a new NetNS so we don't modify the host + var err error + originalNS, err = ns.NewNS() + Expect(err).NotTo(HaveOccurred()) + + err = originalNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + // Add master + err = netlink.LinkAdd(&netlink.Dummy{ + LinkAttrs: netlink.LinkAttrs{ + Name: MASTER_NAME, + }, + }) + Expect(err).NotTo(HaveOccurred()) + _, err = netlink.LinkByName(MASTER_NAME) + Expect(err).NotTo(HaveOccurred()) + return nil + }) + Expect(err).NotTo(HaveOccurred()) + }) + + AfterEach(func() { + Expect(originalNS.Close()).To(Succeed()) + }) + + It("creates an macvlan link in a non-default namespace", func() { + conf := &NetConf{ + NetConf: types.NetConf{ + CNIVersion: "0.2.0", + Name: "testConfig", + Type: "macvlan", + }, + Master: MASTER_NAME, + Mode: "bridge", + MTU: 1500, + } + + targetNs, err := ns.NewNS() + Expect(err).NotTo(HaveOccurred()) + defer targetNs.Close() + + err = originalNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + err = createMacvlan(conf, "foobar0", targetNs) + Expect(err).NotTo(HaveOccurred()) + return nil + }) + Expect(err).NotTo(HaveOccurred()) + + // Make sure macvlan link exists in the target namespace + err = targetNs.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + link, err := netlink.LinkByName("foobar0") + Expect(err).NotTo(HaveOccurred()) + Expect(link.Attrs().Name).To(Equal("foobar0")) + return nil + }) + Expect(err).NotTo(HaveOccurred()) + }) + + It("configures and deconfigures a macvlan link with ADD/DEL", func() { + const IFNAME = "macvl0" + + conf := fmt.Sprintf(`{ + "cniVersion": "0.2.0", + "name": "mynet", + "type": "macvlan", + "master": "%s", + "ipam": { + "type": "host-local", + "subnet": "10.1.2.0/24" + } +}`, MASTER_NAME) + + targetNs, err := ns.NewNS() + Expect(err).NotTo(HaveOccurred()) + defer targetNs.Close() + + args := &skel.CmdArgs{ + ContainerID: "dummy", + Netns: targetNs.Path(), + IfName: IFNAME, + StdinData: []byte(conf), + } + + // Make sure macvlan link exists in the target namespace + err = originalNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + _, err := testutils.CmdAddWithResult(targetNs.Path(), IFNAME, func() error { + return cmdAdd(args) + }) + Expect(err).NotTo(HaveOccurred()) + return nil + }) + Expect(err).NotTo(HaveOccurred()) + + // Make sure macvlan link exists in the target namespace + err = targetNs.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + link, err := netlink.LinkByName(IFNAME) + Expect(err).NotTo(HaveOccurred()) + Expect(link.Attrs().Name).To(Equal(IFNAME)) + + hwAddr := fmt.Sprintf("%s", link.Attrs().HardwareAddr) + Expect(hwAddr).To(HavePrefix(hwaddr.PrivateMACPrefixString)) + + return nil + }) + Expect(err).NotTo(HaveOccurred()) + + err = originalNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + err := testutils.CmdDelWithResult(targetNs.Path(), IFNAME, func() error { + return cmdDel(args) + }) + Expect(err).NotTo(HaveOccurred()) + return nil + }) + Expect(err).NotTo(HaveOccurred()) + + // Make sure macvlan link has been deleted + err = targetNs.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + link, err := netlink.LinkByName(IFNAME) + Expect(err).To(HaveOccurred()) + Expect(link).To(BeNil()) + return nil + }) + Expect(err).NotTo(HaveOccurred()) + }) +}) -- 2.44.0