Merge pull request #195 from SchSeba/l2-bridge

L2 bridge Implementation
This commit is contained in:
Gabe Rosenhouse 2018-11-21 08:41:24 -08:00 committed by GitHub
commit 3fb464786f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 144 additions and 99 deletions

View File

@ -28,6 +28,17 @@ If the bridge is missing, the plugin will create one on first use and, if gatewa
} }
``` ```
## Example L2-only configuration
```
{
"cniVersion": "0.3.1",
"name": "mynet",
"type": "bridge",
"bridge": "mynet0",
"ipam": {}
}
```
## Network configuration reference ## Network configuration reference
* `name` (string, required): the name of the network. * `name` (string, required): the name of the network.
@ -39,5 +50,5 @@ If the bridge is missing, the plugin will create one on first use and, if gatewa
* `ipMasq` (boolean, optional): set up IP Masquerade on the host for traffic originating from this network and destined outside of it. Defaults to false. * `ipMasq` (boolean, optional): set up IP Masquerade on the host for traffic originating from this network and destined outside of it. Defaults to false.
* `mtu` (integer, optional): explicitly set MTU to the specified value. Defaults to the value chosen by the kernel. * `mtu` (integer, optional): explicitly set MTU to the specified value. Defaults to the value chosen by the kernel.
* `hairpinMode` (boolean, optional): set hairpin mode for interfaces on the bridge. Defaults to false. * `hairpinMode` (boolean, optional): set hairpin mode for interfaces on the bridge. Defaults to false.
* `ipam` (dictionary, required): IPAM configuration to be used for this network. * `ipam` (dictionary, required): IPAM configuration to be used for this network. For L2-only network, create empty dictionary.
* `promiscMode` (boolean, optional): set promiscuous mode on the bridge. Defaults to false. * `promiscMode` (boolean, optional): set promiscuous mode on the bridge. Defaults to false.

View File

@ -334,6 +334,8 @@ func cmdAdd(args *skel.CmdArgs) error {
return err return err
} }
isLayer3 := n.IPAM.Type != ""
if n.IsDefaultGW { if n.IsDefaultGW {
n.IsGW = true n.IsGW = true
} }
@ -358,6 +360,10 @@ func cmdAdd(args *skel.CmdArgs) error {
return err return err
} }
// Assume L2 interface only
result := &current.Result{CNIVersion: cniVersion, Interfaces: []*current.Interface{brInterface, hostInterface, containerInterface}}
if isLayer3 {
// run the IPAM plugin and get back the config to apply // run the IPAM plugin and get back the config to apply
r, err := ipam.ExecAdd(n.IPAM.Type, args.StdinData) r, err := ipam.ExecAdd(n.IPAM.Type, args.StdinData)
if err != nil { if err != nil {
@ -374,17 +380,18 @@ func cmdAdd(args *skel.CmdArgs) error {
}() }()
// Convert whatever the IPAM result was into the current Result type // Convert whatever the IPAM result was into the current Result type
result, err := current.NewResultFromResult(r) ipamResult, err := current.NewResultFromResult(r)
if err != nil { if err != nil {
return err return err
} }
result.IPs = ipamResult.IPs
result.Routes = ipamResult.Routes
if len(result.IPs) == 0 { if len(result.IPs) == 0 {
return errors.New("IPAM plugin returned missing IP config") return errors.New("IPAM plugin returned missing IP config")
} }
result.Interfaces = []*current.Interface{brInterface, hostInterface, containerInterface}
// Gather gateway information for each IP family // Gather gateway information for each IP family
gwsV4, gwsV6, err := calcGateways(result, n) gwsV4, gwsV6, err := calcGateways(result, n)
if err != nil { if err != nil {
@ -458,6 +465,7 @@ func cmdAdd(args *skel.CmdArgs) error {
} }
} }
} }
}
// Refetch the bridge since its MAC address may change when the first // Refetch the bridge since its MAC address may change when the first
// veth is added or after its IP address is set // veth is added or after its IP address is set
@ -485,9 +493,13 @@ func cmdDel(args *skel.CmdArgs) error {
return err return err
} }
isLayer3 := n.IPAM.Type != ""
if isLayer3 {
if err := ipam.ExecDel(n.IPAM.Type, args.StdinData); err != nil { if err := ipam.ExecDel(n.IPAM.Type, args.StdinData); err != nil {
return err return err
} }
}
if args.Netns == "" { if args.Netns == "" {
return nil return nil
@ -510,7 +522,7 @@ func cmdDel(args *skel.CmdArgs) error {
return err return err
} }
if n.IPMasq { if isLayer3 && n.IPMasq {
chain := utils.FormatChainName(n.Name, args.ContainerID) chain := utils.FormatChainName(n.Name, args.ContainerID)
comment := utils.FormatComment(n.Name, args.ContainerID) comment := utils.FormatComment(n.Name, args.ContainerID)
for _, ipn := range ipnets { for _, ipn := range ipnets {

View File

@ -47,6 +47,7 @@ type testCase struct {
gateway string // Single subnet config: Gateway gateway string // Single subnet config: Gateway
ranges []rangeInfo // Ranges list (multiple subnets config) ranges []rangeInfo // Ranges list (multiple subnets config)
isGW bool isGW bool
isLayer2 bool
expGWCIDRs []string // Expected gateway addresses in CIDR form expGWCIDRs []string // Expected gateway addresses in CIDR form
} }
@ -77,7 +78,9 @@ const (
"cniVersion": "%s", "cniVersion": "%s",
"name": "testConfig", "name": "testConfig",
"type": "bridge", "type": "bridge",
"bridge": "%s", "bridge": "%s",`
netDefault = `
"isDefaultGateway": true, "isDefaultGateway": true,
"ipMasq": false` "ipMasq": false`
@ -117,6 +120,8 @@ const (
// for a test case. // for a test case.
func (tc testCase) netConfJSON(dataDir string) string { func (tc testCase) netConfJSON(dataDir string) string {
conf := fmt.Sprintf(netConfStr, tc.cniVersion, BRNAME) conf := fmt.Sprintf(netConfStr, tc.cniVersion, BRNAME)
if !tc.isLayer2 {
conf += netDefault
if tc.subnet != "" || tc.ranges != nil { if tc.subnet != "" || tc.ranges != nil {
conf += ipamStartStr conf += ipamStartStr
if dataDir != "" { if dataDir != "" {
@ -130,6 +135,10 @@ func (tc testCase) netConfJSON(dataDir string) string {
} }
conf += ipamEndStr conf += ipamEndStr
} }
} else {
conf += `
"ipam": {}`
}
return "{" + conf + "\n}" return "{" + conf + "\n}"
} }
@ -336,7 +345,10 @@ func (tester *testerV03x) cmdAddTest(tc testCase, dataDir string) {
// Check that the bridge has a different mac from the veth // Check that the bridge has a different mac from the veth
// If not, it means the bridge has an unstable mac and will change // If not, it means the bridge has an unstable mac and will change
// as ifs are added and removed // as ifs are added and removed
// this check is not relevant for a layer 2 bridge
if !tc.isLayer2 {
Expect(link.Attrs().HardwareAddr.String()).NotTo(Equal(bridgeMAC)) Expect(link.Attrs().HardwareAddr.String()).NotTo(Equal(bridgeMAC))
}
return nil return nil
}) })
@ -700,6 +712,16 @@ var _ = Describe("bridge Operations", func() {
} }
}) })
It("configures and deconfigures a l2 bridge and veth with ADD/DEL for 0.3.1 config", func() {
tc := testCase{cniVersion: "0.3.0", isLayer2: true}
cmdAddDelTest(originalNS, tc, dataDir)
})
It("configures and deconfigures a l2 bridge and veth with ADD/DEL for 0.3.1 config", func() {
tc := testCase{cniVersion: "0.3.1", isLayer2: true}
cmdAddDelTest(originalNS, tc, dataDir)
})
It("configures and deconfigures a bridge and veth with default route with ADD/DEL for 0.3.1 config", func() { It("configures and deconfigures a bridge and veth with default route with ADD/DEL for 0.3.1 config", func() {
testCases := []testCase{ testCases := []testCase{
{ {