Add support for in-container master for ipvlan

Signed-off-by: mmirecki <mmirecki@redhat.com>
This commit is contained in:
mmirecki 2023-01-18 13:07:09 +01:00
parent c4d24e80d6
commit c798f80912
2 changed files with 187 additions and 105 deletions

View File

@ -36,9 +36,10 @@ import (
type NetConf struct { type NetConf struct {
types.NetConf types.NetConf
Master string `json:"master"` Master string `json:"master"`
Mode string `json:"mode"` Mode string `json:"mode"`
MTU int `json:"mtu"` MTU int `json:"mtu"`
LinkContNs bool `json:"linkInContainer,omitempty"`
} }
func init() { func init() {
@ -48,9 +49,9 @@ func init() {
runtime.LockOSThread() runtime.LockOSThread()
} }
func loadConf(bytes []byte, cmdCheck bool) (*NetConf, string, error) { func loadConf(args *skel.CmdArgs, cmdCheck bool) (*NetConf, string, error) {
n := &NetConf{} n := &NetConf{}
if err := json.Unmarshal(bytes, n); err != nil { if err := json.Unmarshal(args.StdinData, n); err != nil {
return nil, "", fmt.Errorf("failed to load netconf: %v", err) return nil, "", fmt.Errorf("failed to load netconf: %v", err)
} }
@ -73,8 +74,8 @@ func loadConf(bytes []byte, cmdCheck bool) (*NetConf, string, error) {
} }
if n.Master == "" { if n.Master == "" {
if result == nil { if result == nil {
defaultRouteInterface, err := getDefaultRouteInterfaceName() var defaultRouteInterface string
defaultRouteInterface, err = getNamespacedDefaultRouteInterfaceName(args.Netns, n.LinkContNs)
if err != nil { if err != nil {
return nil, "", err return nil, "", err
} }
@ -124,7 +125,15 @@ func createIpvlan(conf *NetConf, ifName string, netns ns.NetNS) (*current.Interf
return nil, err return nil, err
} }
m, err := netlink.LinkByName(conf.Master) var m netlink.Link
if conf.LinkContNs {
err = netns.Do(func(_ ns.NetNS) error {
m, err = netlink.LinkByName(conf.Master)
return err
})
} else {
m, err = netlink.LinkByName(conf.Master)
}
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to lookup master %q: %v", conf.Master, err) return nil, fmt.Errorf("failed to lookup master %q: %v", conf.Master, err)
} }
@ -146,8 +155,14 @@ func createIpvlan(conf *NetConf, ifName string, netns ns.NetNS) (*current.Interf
Mode: mode, Mode: mode,
} }
if err := netlink.LinkAdd(mv); err != nil { if conf.LinkContNs {
return nil, fmt.Errorf("failed to create ipvlan: %v", err) err = netns.Do(func(_ ns.NetNS) error {
return netlink.LinkAdd(mv)
})
} else {
if err := netlink.LinkAdd(mv); err != nil {
return nil, fmt.Errorf("failed to create ipvlan: %v", err)
}
} }
err = netns.Do(func(_ ns.NetNS) error { err = netns.Do(func(_ ns.NetNS) error {
@ -193,8 +208,31 @@ func getDefaultRouteInterfaceName() (string, error) {
return "", fmt.Errorf("no default route interface found") return "", fmt.Errorf("no default route interface found")
} }
func getNamespacedDefaultRouteInterfaceName(namespace string, inContainer bool) (string, error) {
if !inContainer {
return getDefaultRouteInterfaceName()
}
netns, err := ns.GetNS(namespace)
if err != nil {
return "", fmt.Errorf("failed to open netns %q: %v", netns, err)
}
defer netns.Close()
var defaultRouteInterface string
err = netns.Do(func(_ ns.NetNS) error {
defaultRouteInterface, err = getDefaultRouteInterfaceName()
if err != nil {
return err
}
return nil
})
if err != nil {
return "", err
}
return defaultRouteInterface, nil
}
func cmdAdd(args *skel.CmdArgs) error { func cmdAdd(args *skel.CmdArgs) error {
n, cniVersion, err := loadConf(args.StdinData, false) n, cniVersion, err := loadConf(args, false)
if err != nil { if err != nil {
return err return err
} }
@ -272,7 +310,7 @@ func cmdAdd(args *skel.CmdArgs) error {
} }
func cmdDel(args *skel.CmdArgs) error { func cmdDel(args *skel.CmdArgs) error {
n, _, err := loadConf(args.StdinData, false) n, _, err := loadConf(args, false)
if err != nil { if err != nil {
return err return err
} }
@ -320,7 +358,7 @@ func main() {
func cmdCheck(args *skel.CmdArgs) error { func cmdCheck(args *skel.CmdArgs) error {
n, _, err := loadConf(args.StdinData, true) n, _, err := loadConf(args, true)
if err != nil { if err != nil {
return err return err
} }
@ -369,7 +407,16 @@ func cmdCheck(args *skel.CmdArgs) error {
contMap.Sandbox, args.Netns) contMap.Sandbox, args.Netns)
} }
m, err := netlink.LinkByName(n.Master) var m netlink.Link
if n.LinkContNs {
err = netns.Do(func(_ ns.NetNS) error {
m, err = netlink.LinkByName(n.Master)
return err
})
} else {
m, err = netlink.LinkByName(n.Master)
}
if err != nil { if err != nil {
return fmt.Errorf("failed to lookup master %q: %v", n.Master, err) return fmt.Errorf("failed to lookup master %q: %v", n.Master, err)
} }

View File

@ -38,6 +38,7 @@ import (
) )
const MASTER_NAME = "eth0" const MASTER_NAME = "eth0"
const MASTER_NAME_INCONTAINER = "eth1"
type Net struct { type Net struct {
Name string `json:"name"` Name string `json:"name"`
@ -49,6 +50,7 @@ type Net struct {
DNS types.DNS `json:"dns"` DNS types.DNS `json:"dns"`
RawPrevResult map[string]interface{} `json:"prevResult,omitempty"` RawPrevResult map[string]interface{} `json:"prevResult,omitempty"`
PrevResult types100.Result `json:"-"` PrevResult types100.Result `json:"-"`
LinkContNs bool `json:"linkInContainer"`
} }
func buildOneConfig(cniVersion string, master string, orig *Net, prevResult types.Result) (*Net, error) { func buildOneConfig(cniVersion string, master string, orig *Net, prevResult types.Result) (*Net, error) {
@ -289,6 +291,22 @@ var _ = Describe("ipvlan Operations", func() {
return nil return nil
}) })
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
err = targetNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
// Add master
err = netlink.LinkAdd(&netlink.Dummy{
LinkAttrs: netlink.LinkAttrs{
Name: MASTER_NAME_INCONTAINER,
},
})
Expect(err).NotTo(HaveOccurred())
_, err = netlink.LinkByName(MASTER_NAME_INCONTAINER)
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
}) })
AfterEach(func() { AfterEach(func() {
@ -301,67 +319,77 @@ var _ = Describe("ipvlan Operations", func() {
Expect(testutils.UnmountNS(targetNS)).To(Succeed()) Expect(testutils.UnmountNS(targetNS)).To(Succeed())
}) })
for _, ver := range testutils.AllSpecVersions { for _, inContainer := range []bool{false, true} {
// Redefine ver inside for scope so real value is picked up by each dynamically defined It() masterInterface := MASTER_NAME
// See Gingkgo's "Patterns for dynamically generating tests" documentation. if inContainer {
ver := ver masterInterface = MASTER_NAME_INCONTAINER
}
//for _, ver := range testutils.AllSpecVersions {
for _, ver := range [...]string{"1.0.0"} {
// Redefine ver inside for scope so real value is picked up by each dynamically defined It()
// See Gingkgo's "Patterns for dynamically generating tests" documentation.
ver := ver
isInContainer := inContainer // Tests need a local var with constant value
It(fmt.Sprintf("[%s] creates an ipvlan link in a non-default namespace", ver), func() { It(fmt.Sprintf("[%s] creates an ipvlan link in a non-default namespace", ver), func() {
conf := &NetConf{ conf := &NetConf{
NetConf: types.NetConf{ NetConf: types.NetConf{
CNIVersion: ver, CNIVersion: ver,
Name: "testConfig", Name: "testConfig",
Type: "ipvlan", Type: "ipvlan",
}, },
Master: MASTER_NAME, Master: masterInterface,
Mode: "l2", Mode: "l2",
MTU: 1500, MTU: 1500,
} LinkContNs: isInContainer,
}
err := originalNS.Do(func(ns.NetNS) error { err := originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover() defer GinkgoRecover()
_, err := createIpvlan(conf, "foobar0", targetNS)
Expect(err).NotTo(HaveOccurred())
return nil
})
_, err := createIpvlan(conf, "foobar0", targetNS)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
return nil
// Make sure ipvlan 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())
}) })
Expect(err).NotTo(HaveOccurred()) It(fmt.Sprintf("[%s] configures and deconfigures an iplvan link with ADD/DEL", ver), func() {
conf := fmt.Sprintf(`{
// Make sure ipvlan 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(fmt.Sprintf("[%s] configures and deconfigures an iplvan link with ADD/DEL", ver), func() {
conf := fmt.Sprintf(`{
"cniVersion": "%s", "cniVersion": "%s",
"name": "mynet", "name": "mynet",
"type": "ipvlan", "type": "ipvlan",
"master": "%s", "master": "%s",
"linkInContainer": %t,
"ipam": { "ipam": {
"type": "host-local", "type": "host-local",
"subnet": "10.1.2.0/24", "subnet": "10.1.2.0/24",
"dataDir": "%s" "dataDir": "%s"
} }
}`, ver, MASTER_NAME, dataDir) }`, ver, masterInterface, isInContainer, dataDir)
ipvlanAddCheckDelTest(conf, "", originalNS, targetNS) ipvlanAddCheckDelTest(conf, "", originalNS, targetNS)
}) })
if testutils.SpecVersionHasChaining(ver) { if testutils.SpecVersionHasChaining(ver) {
It(fmt.Sprintf("[%s] configures and deconfigures an iplvan link with ADD/DEL when chained", ver), func() { It(fmt.Sprintf("[%s] configures and deconfigures an iplvan link with ADD/DEL when chained", ver), func() {
conf := fmt.Sprintf(`{ conf := fmt.Sprintf(`{
"cniVersion": "%s", "cniVersion": "%s",
"name": "mynet", "name": "mynet",
"type": "ipvlan", "type": "ipvlan",
"linkInContainer": %t,
"prevResult": { "prevResult": {
"interfaces": [ "interfaces": [
{ {
@ -378,84 +406,91 @@ var _ = Describe("ipvlan Operations", func() {
], ],
"routes": [] "routes": []
} }
}`, ver, MASTER_NAME) }`, ver, isInContainer, masterInterface)
ipvlanAddCheckDelTest(conf, MASTER_NAME, originalNS, targetNS) ipvlanAddCheckDelTest(conf, masterInterface, originalNS, targetNS)
}) })
} }
It(fmt.Sprintf("[%s] deconfigures an unconfigured ipvlan link with DEL", ver), func() { It(fmt.Sprintf("[%s] deconfigures an unconfigured ipvlan link with DEL", ver), func() {
conf := fmt.Sprintf(`{ conf := fmt.Sprintf(`{
"cniVersion": "%s", "cniVersion": "%s",
"name": "mynet", "name": "mynet",
"type": "ipvlan", "type": "ipvlan",
"master": "%s", "master": "%s",
"linkInContainer": %t,
"ipam": { "ipam": {
"type": "host-local", "type": "host-local",
"subnet": "10.1.2.0/24", "subnet": "10.1.2.0/24",
"dataDir": "%s" "dataDir": "%s"
} }
}`, ver, MASTER_NAME, dataDir) }`, ver, masterInterface, isInContainer, dataDir)
args := &skel.CmdArgs{ args := &skel.CmdArgs{
ContainerID: "dummy", ContainerID: "dummy",
Netns: targetNS.Path(), Netns: targetNS.Path(),
IfName: "ipvl0", IfName: "ipvl0",
StdinData: []byte(conf), StdinData: []byte(conf),
} }
err := originalNS.Do(func(ns.NetNS) error { err := originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover() defer GinkgoRecover()
err := testutils.CmdDelWithArgs(args, func() error { err := testutils.CmdDelWithArgs(args, func() error {
return cmdDel(args) return cmdDel(args)
})
Expect(err).NotTo(HaveOccurred())
return nil
}) })
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
return nil
}) })
Expect(err).NotTo(HaveOccurred())
})
It(fmt.Sprintf("[%s] configures and deconfigures a ipvlan link with ADD/DEL, without master config", ver), func() { It(fmt.Sprintf("[%s] configures and deconfigures a ipvlan link with ADD/DEL, without master config", ver), func() {
conf := fmt.Sprintf(`{ conf := fmt.Sprintf(`{
"cniVersion": "%s", "cniVersion": "%s",
"name": "mynet", "name": "mynet",
"type": "ipvlan", "type": "ipvlan",
"linkInContainer": %t,
"ipam": { "ipam": {
"type": "host-local", "type": "host-local",
"subnet": "10.1.2.0/24", "subnet": "10.1.2.0/24",
"dataDir": "%s" "dataDir": "%s"
} }
}`, ver, dataDir) }`, ver, isInContainer, dataDir)
// Make MASTER_NAME as default route interface // Make MASTER_NAME as default route interface
err := originalNS.Do(func(ns.NetNS) error { currentNs := originalNS
defer GinkgoRecover() if isInContainer {
currentNs = targetNS
link, err := netlink.LinkByName(MASTER_NAME)
Expect(err).NotTo(HaveOccurred())
err = netlink.LinkSetUp(link)
Expect(err).NotTo(HaveOccurred())
var address = &net.IPNet{IP: net.IPv4(192, 0, 0, 1), Mask: net.CIDRMask(24, 32)}
var addr = &netlink.Addr{IPNet: address}
err = netlink.AddrAdd(link, addr)
Expect(err).NotTo(HaveOccurred())
// add default gateway into MASTER
dst := &net.IPNet{
IP: net.IPv4(0, 0, 0, 0),
Mask: net.CIDRMask(0, 0),
} }
ip := net.IPv4(192, 0, 0, 254) err := currentNs.Do(func(ns.NetNS) error {
route := netlink.Route{LinkIndex: link.Attrs().Index, Dst: dst, Gw: ip} defer GinkgoRecover()
err = netlink.RouteAdd(&route)
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
ipvlanAddCheckDelTest(conf, MASTER_NAME, originalNS, targetNS) link, err := netlink.LinkByName(masterInterface)
}) Expect(err).NotTo(HaveOccurred())
err = netlink.LinkSetUp(link)
Expect(err).NotTo(HaveOccurred())
var address = &net.IPNet{IP: net.IPv4(192, 0, 0, 1), Mask: net.CIDRMask(24, 32)}
var addr = &netlink.Addr{IPNet: address}
err = netlink.AddrAdd(link, addr)
Expect(err).NotTo(HaveOccurred())
// add default gateway into MASTER
dst := &net.IPNet{
IP: net.IPv4(0, 0, 0, 0),
Mask: net.CIDRMask(0, 0),
}
ip := net.IPv4(192, 0, 0, 254)
route := netlink.Route{LinkIndex: link.Attrs().Index, Dst: dst, Gw: ip}
err = netlink.RouteAdd(&route)
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
ipvlanAddCheckDelTest(conf, masterInterface, originalNS, targetNS)
})
}
} }
}) })