bridge: add vlan trunk support

add vlan trunk support for veth
vlan trunk only support L2 only mode without any IPAM
refer ovs-cni design
https://github.com/k8snetworkplumbingwg/ovs-cni/blob/main/pkg/plugin/plugin.go

design:
origin "vlan" option will be PVID or untagged vlan for the network.
"vlanTrunk" will setup tagged vlan for veth.

entry type:
`{ "id": 100 }` will specify only tagged vlan 100
`{ "minID": 100, "maxID": 120 }` will specify tagged vlan from 100 to
120 (include 100 and 120)
vlanTrunk is a list of above entry type, so you can use this to add
tagged vlan
`[
  { "id": 100 },
  {
    "minID": 1000,
    "maxID": 2000
  }
]`

complete config will be like this
{
  "cniVersion": "0.3.1",
  "name": "mynet",
  "type": "bridge",
  "bridge": "mynet0",
  "vlan": 100,
  "vlanTrunk": [
    { "id": 101 },
    { "minID": 1000, "maxID": 2000 },
    { "minID": 3000, "maxID": 4000 }
  ],
  "ipam": {}
}

Signed-off-by: Date Huang <date.huang@suse.com>
This commit is contained in:
Date Huang
2022-01-06 15:34:11 +08:00
committed by Date Huang
parent 9f1f9a588b
commit 090af7db9a
2 changed files with 278 additions and 26 deletions

View File

@ -74,6 +74,7 @@ type testCase struct {
isLayer2 bool
expGWCIDRs []string // Expected gateway addresses in CIDR form
vlan int
vlanTrunk []*VlanTrunk
removeDefaultVlan bool
ipMasq bool
macspoofchk bool
@ -130,6 +131,23 @@ const (
vlan = `,
"vlan": %d`
vlanTrunkStartStr = `,
"vlanTrunk": [`
vlanTrunk = `
{
"id": %d
}`
vlanTrunkRange = `
{
"minID": %d,
"maxID": %d
}`
vlanTrunkEndStr = `
]`
preserveDefaultVlan = `,
"preserveDefaultVlan": false`
@ -200,6 +218,23 @@ func (tc testCase) netConfJSON(dataDir string) string {
conf += preserveDefaultVlan
}
}
if tc.isLayer2 && tc.vlanTrunk != nil {
conf += vlanTrunkStartStr
for i, vlan := range tc.vlanTrunk {
if i > 0 {
conf += ","
}
if vlan.ID != nil {
conf += fmt.Sprintf(vlanTrunk, *vlan.ID)
}
if vlan.MinID != nil && vlan.MaxID != nil {
conf += fmt.Sprintf(vlanTrunkRange, *vlan.MinID, *vlan.MaxID)
}
}
conf += vlanTrunkEndStr
}
if tc.ipMasq {
conf += tc.ipMasqConfig()
}
@ -541,7 +576,7 @@ func (tester *testerV10x) cmdAddTest(tc testCase, dataDir string) (types.Result,
}
// Check the bridge vlan filtering equals true
if tc.vlan != 0 {
if tc.vlan != 0 || tc.vlanTrunk != nil {
Expect(*link.(*netlink.Bridge).VlanFiltering).To(BeTrue())
} else {
Expect(*link.(*netlink.Bridge).VlanFiltering).To(BeFalse())
@ -598,6 +633,25 @@ func (tester *testerV10x) cmdAddTest(tc testCase, dataDir string) (types.Result,
}
}
// check VlanTrunks exist on the veth interface
if tc.vlanTrunk != nil {
interfaceMap, err := netlink.BridgeVlanList()
Expect(err).NotTo(HaveOccurred())
vlans, isExist := interfaceMap[int32(link.Attrs().Index)]
Expect(isExist).To(BeTrue())
for _, vlanEntry := range tc.vlanTrunk {
if vlanEntry.ID != nil {
Expect(checkVlan(*vlanEntry.ID, vlans)).To(BeTrue())
}
if vlanEntry.MinID != nil && vlanEntry.MaxID != nil {
for vid := *vlanEntry.MinID; vid <= *vlanEntry.MaxID; vid++ {
Expect(checkVlan(vid, vlans)).To(BeTrue())
}
}
}
}
// Check that the bridge has a different mac from the veth
// If not, it means the bridge has an unstable mac and will change
// as ifs are added and removed
@ -852,7 +906,7 @@ func (tester *testerV04x) cmdAddTest(tc testCase, dataDir string) (types.Result,
}
// Check the bridge vlan filtering equals true
if tc.vlan != 0 {
if tc.vlan != 0 || tc.vlanTrunk != nil {
Expect(*link.(*netlink.Bridge).VlanFiltering).To(BeTrue())
} else {
Expect(*link.(*netlink.Bridge).VlanFiltering).To(BeFalse())
@ -909,6 +963,25 @@ func (tester *testerV04x) cmdAddTest(tc testCase, dataDir string) (types.Result,
}
}
// check VlanTrunks exist on the veth interface
if tc.vlanTrunk != nil {
interfaceMap, err := netlink.BridgeVlanList()
Expect(err).NotTo(HaveOccurred())
vlans, isExist := interfaceMap[int32(link.Attrs().Index)]
Expect(isExist).To(BeTrue())
for _, vlanEntry := range tc.vlanTrunk {
if vlanEntry.ID != nil {
Expect(checkVlan(*vlanEntry.ID, vlans)).To(BeTrue())
}
if vlanEntry.MinID != nil && vlanEntry.MaxID != nil {
for vid := *vlanEntry.MinID; vid <= *vlanEntry.MaxID; vid++ {
Expect(checkVlan(vid, vlans)).To(BeTrue())
}
}
}
}
// Check that the bridge has a different mac from the veth
// If not, it means the bridge has an unstable mac and will change
// as ifs are added and removed
@ -1158,7 +1231,7 @@ func (tester *testerV03x) cmdAddTest(tc testCase, dataDir string) (types.Result,
}
// Check the bridge vlan filtering equals true
if tc.vlan != 0 {
if tc.vlan != 0 || tc.vlanTrunk != nil {
Expect(*link.(*netlink.Bridge).VlanFiltering).To(BeTrue())
} else {
Expect(*link.(*netlink.Bridge).VlanFiltering).To(BeFalse())
@ -1215,6 +1288,25 @@ func (tester *testerV03x) cmdAddTest(tc testCase, dataDir string) (types.Result,
}
}
// check VlanTrunks exist on the veth interface
if tc.vlanTrunk != nil {
interfaceMap, err := netlink.BridgeVlanList()
Expect(err).NotTo(HaveOccurred())
vlans, isExist := interfaceMap[int32(link.Attrs().Index)]
Expect(isExist).To(BeTrue())
for _, vlanEntry := range tc.vlanTrunk {
if vlanEntry.ID != nil {
Expect(checkVlan(*vlanEntry.ID, vlans)).To(BeTrue())
}
if vlanEntry.MinID != nil && vlanEntry.MaxID != nil {
for vid := *vlanEntry.MinID; vid <= *vlanEntry.MaxID; vid++ {
Expect(checkVlan(vid, vlans)).To(BeTrue())
}
}
}
}
// Check that the bridge has a different mac from the veth
// If not, it means the bridge has an unstable mac and will change
// as ifs are added and removed
@ -1672,6 +1764,61 @@ var _ = Describe("bridge Operations", func() {
Expect(testutils.UnmountNS(targetNS)).To(Succeed())
})
var (
correctID int = 10
correctMinID int = 100
correctMaxID int = 105
incorrectMinID int = 1000
incorrectMaxID int = 100
overID int = 5000
negativeID int = -1
)
DescribeTable(
"collectVlanTrunk succeeds",
func(vlanTrunks []*VlanTrunk, expectedVIDs []int) {
Expect(collectVlanTrunk(vlanTrunks)).To(ConsistOf(expectedVIDs))
},
Entry("when provided an empty VLAN trunk configuration", []*VlanTrunk{}, nil),
Entry("when provided a VLAN trunk configuration with both min / max range", []*VlanTrunk{
{
MinID: &correctMinID,
MaxID: &correctMaxID,
},
}, []int{100, 101, 102, 103, 104, 105}),
Entry("when provided a VLAN trunk configuration with id only", []*VlanTrunk{
{
ID: &correctID,
},
}, []int{10}),
Entry("when provided a VLAN trunk configuration with id and range", []*VlanTrunk{
{
ID: &correctID,
},
{
MinID: &correctMinID,
MaxID: &correctMaxID,
},
}, []int{10, 100, 101, 102, 103, 104, 105}),
)
DescribeTable(
"collectVlanTrunk failed",
func(vlanTrunks []*VlanTrunk, expectedError error) {
_, err := collectVlanTrunk(vlanTrunks)
Expect(err).To(MatchError(expectedError))
},
Entry("when not passed the maxID", []*VlanTrunk{{MinID: &correctMinID}}, fmt.Errorf("minID and maxID should be configured simultaneously, maxID is missing")),
Entry("when not passed the minID", []*VlanTrunk{{MaxID: &correctMaxID}}, fmt.Errorf("minID and maxID should be configured simultaneously, minID is missing")),
Entry("when the minID is negative", []*VlanTrunk{{MinID: &negativeID, MaxID: &correctMaxID}}, fmt.Errorf("incorrect trunk minID parameter")),
Entry("when the minID is larger than 4094", []*VlanTrunk{{MinID: &overID, MaxID: &correctMaxID}}, fmt.Errorf("incorrect trunk minID parameter")),
Entry("when the maxID is larger than 4094", []*VlanTrunk{{MinID: &correctMinID, MaxID: &overID}}, fmt.Errorf("incorrect trunk maxID parameter")),
Entry("when the maxID is negative", []*VlanTrunk{{MinID: &correctMinID, MaxID: &overID}}, fmt.Errorf("incorrect trunk maxID parameter")),
Entry("when the ID is larger than 4094", []*VlanTrunk{{ID: &overID}}, fmt.Errorf("incorrect trunk id parameter")),
Entry("when the ID is negative", []*VlanTrunk{{ID: &negativeID}}, fmt.Errorf("incorrect trunk id parameter")),
Entry("when the maxID is smaller than minID", []*VlanTrunk{{MinID: &incorrectMinID, MaxID: &incorrectMaxID}}, fmt.Errorf("minID is greater than maxID in trunk parameter")),
)
for _, ver := range testutils.AllSpecVersions {
// 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.
@ -1804,6 +1951,25 @@ var _ = Describe("bridge Operations", func() {
cmdAddDelTest(originalNS, targetNS, tc, dataDir)
})
// TODO find some way to put pointer
It(fmt.Sprintf("[%s] configures and deconfigures a l2 bridge with vlan id 100, vlanTrunk 101,200~210 using ADD/DEL", ver), func() {
id, minID, maxID := 101, 200, 210
tc := testCase{
cniVersion: ver,
isLayer2: true,
vlanTrunk: []*VlanTrunk{
{ID: &id},
{
MinID: &minID,
MaxID: &maxID,
},
},
AddErr020: "cannot convert: no valid IP addresses",
AddErr010: "cannot convert: no valid IP addresses",
}
cmdAddDelTest(originalNS, targetNS, tc, dataDir)
})
It(fmt.Sprintf("[%s] configures and deconfigures a l2 bridge with vlan id 100 and no default vlan using ADD/DEL", ver), func() {
tc := testCase{
cniVersion: ver,