VRF CNI: Add an optional table parameter.
When specified from the user, the VRF will get assigned to the given tableid instead of having the CNI to choose for a free one. Signed-off-by: Federico Paolinelli <fpaoline@redhat.com>
This commit is contained in:
parent
ccd872bd7a
commit
b34402abd3
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
This plugin creates a [VRF](https://www.kernel.org/doc/Documentation/networking/vrf.txt) in the network namespace and assigns it the interface passed in the arguments. If the VRF is already present in the namespace, it only adds the interface to it.
|
This plugin creates a [VRF](https://www.kernel.org/doc/Documentation/networking/vrf.txt) in the network namespace and assigns it the interface passed in the arguments. If the VRF is already present in the namespace, it only adds the interface to it.
|
||||||
|
|
||||||
As a table id is mandatory, the plugin generates a new one for each different VRF that is added to the namespace.
|
The table id is mandatory for VRF but optional in the CNI configuration. If not specified, the plugin generates a new one for each different VRF that is added to the namespace.
|
||||||
|
|
||||||
It does not create any network interfaces and therefore does not bring connectivity by itself.
|
It does not create any network interfaces and therefore does not bring connectivity by itself.
|
||||||
It is only useful when used in addition to other plugins.
|
It is only useful when used in addition to other plugins.
|
||||||
@ -50,4 +50,5 @@ The only configuration is the name of the VRF, as per the following example:
|
|||||||
|
|
||||||
The following [args conventions](https://github.com/containernetworking/cni/blob/master/CONVENTIONS.md#args-in-network-config) are supported:
|
The following [args conventions](https://github.com/containernetworking/cni/blob/master/CONVENTIONS.md#args-in-network-config) are supported:
|
||||||
|
|
||||||
* `vrfname` (string, optional): The name of the VRF to be created and to be set as master of the interface
|
* `vrfname` (string): The name of the VRF to be created and to be set as master of the interface
|
||||||
|
* `table` (int, optional): The route table to be associated to the created VRF.
|
||||||
|
@ -35,6 +35,8 @@ type VRFNetConf struct {
|
|||||||
|
|
||||||
// VRFName is the name of the vrf to add the interface to.
|
// VRFName is the name of the vrf to add the interface to.
|
||||||
VRFName string `json:"vrfname"`
|
VRFName string `json:"vrfname"`
|
||||||
|
// Table is the optional name of the routing table set for the vrf
|
||||||
|
Table uint32 `json:"table"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@ -54,8 +56,14 @@ func cmdAdd(args *skel.CmdArgs) error {
|
|||||||
err = ns.WithNetNSPath(args.Netns, func(_ ns.NetNS) error {
|
err = ns.WithNetNSPath(args.Netns, func(_ ns.NetNS) error {
|
||||||
vrf, err := findVRF(conf.VRFName)
|
vrf, err := findVRF(conf.VRFName)
|
||||||
|
|
||||||
|
// If the user set a tableid and the vrf is already in the namespace
|
||||||
|
// we check if the tableid is the same one already assigned to the vrf.
|
||||||
|
if err == nil && conf.Table != 0 && vrf.Table != conf.Table {
|
||||||
|
return fmt.Errorf("VRF %s already exist with different routing table %d", conf.VRFName, vrf.Table)
|
||||||
|
}
|
||||||
|
|
||||||
if _, ok := err.(netlink.LinkNotFoundError); ok {
|
if _, ok := err.(netlink.LinkNotFoundError); ok {
|
||||||
vrf, err = createVRF(conf.VRFName)
|
vrf, err = createVRF(conf.VRFName, conf.Table)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -35,15 +35,19 @@ func findVRF(name string) (*netlink.Vrf, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// createVRF creates a new VRF and sets it up.
|
// createVRF creates a new VRF and sets it up.
|
||||||
func createVRF(name string) (*netlink.Vrf, error) {
|
func createVRF(name string, tableID uint32) (*netlink.Vrf, error) {
|
||||||
links, err := netlink.LinkList()
|
links, err := netlink.LinkList()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("createVRF: Failed to find links %v", err)
|
return nil, fmt.Errorf("createVRF: Failed to find links %v", err)
|
||||||
}
|
}
|
||||||
tableID, err := findFreeRoutingTableID(links)
|
|
||||||
if err != nil {
|
if tableID == 0 {
|
||||||
return nil, err
|
tableID, err = findFreeRoutingTableID(links)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
vrf := &netlink.Vrf{
|
vrf := &netlink.Vrf{
|
||||||
LinkAttrs: netlink.LinkAttrs{
|
LinkAttrs: netlink.LinkAttrs{
|
||||||
Name: name,
|
Name: name,
|
||||||
|
@ -354,6 +354,69 @@ var _ = Describe("vrf plugin", func() {
|
|||||||
Entry("added to different vrfs with same ip IPV6", VRF0Name, VRF1Name, "2A00:0C98:2060:A000:0001:0000:1d1e:ca75/64", "2A00:0C98:2060:A000:0001:0000:1d1e:ca75/64"),
|
Entry("added to different vrfs with same ip IPV6", VRF0Name, VRF1Name, "2A00:0C98:2060:A000:0001:0000:1d1e:ca75/64", "2A00:0C98:2060:A000:0001:0000:1d1e:ca75/64"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
DescribeTable("handles tableid conflicts",
|
||||||
|
func(vrf0, vrf1 string, tableid0, tableid1 int, expectedError string) {
|
||||||
|
conf0 := configWithTableFor("test", IF0Name, vrf0, "10.0.0.2/24", tableid0)
|
||||||
|
conf1 := configWithTableFor("test1", IF1Name, vrf1, "10.0.0.2/24", tableid1)
|
||||||
|
|
||||||
|
By("Adding the first interface to first vrf", func() {
|
||||||
|
err := originalNS.Do(func(ns.NetNS) error {
|
||||||
|
defer GinkgoRecover()
|
||||||
|
args := &skel.CmdArgs{
|
||||||
|
ContainerID: "dummy",
|
||||||
|
Netns: targetNS.Path(),
|
||||||
|
IfName: IF0Name,
|
||||||
|
StdinData: conf0,
|
||||||
|
}
|
||||||
|
_, _, err := testutils.CmdAddWithArgs(args, func() error {
|
||||||
|
return cmdAdd(args)
|
||||||
|
})
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
})
|
||||||
|
|
||||||
|
By("Checking that the first vrf has the right routing table", func() {
|
||||||
|
err := targetNS.Do(func(ns.NetNS) error {
|
||||||
|
defer GinkgoRecover()
|
||||||
|
|
||||||
|
l, err := netlink.LinkByName(vrf0)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
vrf := l.(*netlink.Vrf)
|
||||||
|
Expect(vrf.Table).To(Equal(uint32(tableid0)))
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
})
|
||||||
|
|
||||||
|
By("Adding the second interface to second vrf", func() {
|
||||||
|
err := originalNS.Do(func(ns.NetNS) error {
|
||||||
|
defer GinkgoRecover()
|
||||||
|
args := &skel.CmdArgs{
|
||||||
|
ContainerID: "dummy",
|
||||||
|
Netns: targetNS.Path(),
|
||||||
|
IfName: IF1Name,
|
||||||
|
StdinData: conf1,
|
||||||
|
}
|
||||||
|
_, _, err := testutils.CmdAddWithArgs(args, func() error {
|
||||||
|
return cmdAdd(args)
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
if expectedError != "" {
|
||||||
|
Expect(err).To(HaveOccurred())
|
||||||
|
Expect(err.Error()).To(ContainSubstring(expectedError))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
})
|
||||||
|
},
|
||||||
|
Entry("same vrf with same tableid", VRF0Name, VRF0Name, 1001, 1001, ""),
|
||||||
|
Entry("different vrf with same tableid", VRF0Name, VRF1Name, 1001, 1001, ""),
|
||||||
|
Entry("same vrf with different tableids", VRF0Name, VRF0Name, 1001, 1002, "already exist with different routing table"),
|
||||||
|
)
|
||||||
|
|
||||||
It("removes the VRF only when the last interface is removed", func() {
|
It("removes the VRF only when the last interface is removed", func() {
|
||||||
conf0 := configFor("test", IF0Name, VRF0Name, "10.0.0.2/24")
|
conf0 := configFor("test", IF0Name, VRF0Name, "10.0.0.2/24")
|
||||||
conf1 := configFor("test1", IF1Name, VRF0Name, "10.0.0.2/24")
|
conf1 := configFor("test1", IF1Name, VRF0Name, "10.0.0.2/24")
|
||||||
@ -605,6 +668,30 @@ func configFor(name, intf, vrf, ip string) []byte {
|
|||||||
return []byte(conf)
|
return []byte(conf)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func configWithTableFor(name, intf, vrf, ip string, tableID int) []byte {
|
||||||
|
conf := fmt.Sprintf(`{
|
||||||
|
"name": "%s",
|
||||||
|
"type": "vrf",
|
||||||
|
"cniVersion": "0.3.1",
|
||||||
|
"vrfName": "%s",
|
||||||
|
"table": %d,
|
||||||
|
"prevResult": {
|
||||||
|
"interfaces": [
|
||||||
|
{"name": "%s", "sandbox":"netns"}
|
||||||
|
],
|
||||||
|
"ips": [
|
||||||
|
{
|
||||||
|
"version": "4",
|
||||||
|
"address": "%s",
|
||||||
|
"gateway": "10.0.0.1",
|
||||||
|
"interface": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}`, name, vrf, tableID, intf, ip)
|
||||||
|
return []byte(conf)
|
||||||
|
}
|
||||||
|
|
||||||
func checkInterfaceOnVRF(vrfName, intfName string) {
|
func checkInterfaceOnVRF(vrfName, intfName string) {
|
||||||
vrf, err := netlink.LinkByName(vrfName)
|
vrf, err := netlink.LinkByName(vrfName)
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
Loading…
x
Reference in New Issue
Block a user