From 66f03bcba535f905df1c151278b4d77bda83523a Mon Sep 17 00:00:00 2001 From: alacuku Date: Mon, 14 Jun 2021 18:49:58 +0200 Subject: [PATCH] added gateway routing manager implementation and test suite for the gateway operator. --- pkg/liqonet/routing/gatewayRouting.go | 84 ++++++++ pkg/liqonet/routing/gatewayRouting_test.go | 219 +++++++++++++++++++++ pkg/liqonet/routing/routing_suite_test.go | 18 +- 3 files changed, 319 insertions(+), 2 deletions(-) create mode 100644 pkg/liqonet/routing/gatewayRouting.go create mode 100644 pkg/liqonet/routing/gatewayRouting_test.go diff --git a/pkg/liqonet/routing/gatewayRouting.go b/pkg/liqonet/routing/gatewayRouting.go new file mode 100644 index 0000000000..ab928f745b --- /dev/null +++ b/pkg/liqonet/routing/gatewayRouting.go @@ -0,0 +1,84 @@ +package routing + +import ( + "strconv" + + "github.com/vishvananda/netlink" + "golang.org/x/sys/unix" + + netv1alpha1 "github.com/liqotech/liqo/apis/net/v1alpha1" + "github.com/liqotech/liqo/pkg/liqonet" + liqoerrors "github.com/liqotech/liqo/pkg/liqonet/errors" +) + +// GatewayRoutingManager implements the routing manager interface. +// Used by the gateway operator to configure the routes for remote clusters. +type GatewayRoutingManager struct { + routingTableID int + tunnelDevice netlink.Link +} + +// NewGatewayRoutingManager returns a GatewayRoutingManager ready to be used or an error. +func NewGatewayRoutingManager(routingTableID int, tunnelDevice netlink.Link) (Routing, error) { + // Check the validity of input parameters. + if routingTableID > unix.RT_TABLE_MAX { + return nil, &liqoerrors.WrongParameter{Parameter: "routingTableID", Reason: liqoerrors.MinorOrEqual + strconv.Itoa(unix.RT_TABLE_MAX)} + } + if routingTableID < 0 { + return nil, &liqoerrors.WrongParameter{Parameter: "routingTableID", Reason: liqoerrors.GreaterOrEqual + strconv.Itoa(0)} + } + if tunnelDevice == nil { + return nil, &liqoerrors.WrongParameter{ + Reason: liqoerrors.NotNil, + Parameter: "tunnelDevice", + } + } + return &GatewayRoutingManager{ + routingTableID: routingTableID, + tunnelDevice: tunnelDevice, + }, nil +} + +// EnsureRoutesPerCluster accepts as input a netv1alpha.tunnelendpoint. +// It inserts the routes if they do not exist or updates them if they are outdated. +// Returns true if the routes have been configured, false if the routes are already configured. +// An error if something goes wrong and the routes can not be configured. +func (grm *GatewayRoutingManager) EnsureRoutesPerCluster(tep *netv1alpha1.TunnelEndpoint) (bool, error) { + var routeAdd bool + var err error + // Extract and save route information from the given tep. + _, dstNet := liqonet.GetPodCIDRS(tep) + // Add route for the given cluster. + routeAdd, err = AddRoute(dstNet, "", grm.tunnelDevice.Attrs().Index, grm.routingTableID) + if err != nil { + return routeAdd, err + } + return routeAdd, nil +} + +// RemoveRoutesPerCluster accepts as input a netv1alpha.tunnelendpoint. +// It deletes the routes if they do exist. +// Returns true if the routes exist and have been deleted, false if nothing is removed. +// An error if something goes wrong and the routes can not be removed. +func (grm *GatewayRoutingManager) RemoveRoutesPerCluster(tep *netv1alpha1.TunnelEndpoint) (bool, error) { + var routeDel bool + var err error + // Extract and save route information from the given tep. + _, dstNet := liqonet.GetPodCIDRS(tep) + // Delete route for the given cluster. + routeDel, err = delRoute(dstNet, "", grm.tunnelDevice.Attrs().Index, grm.routingTableID) + if err != nil { + return routeDel, err + } + return routeDel, nil +} + +// CleanRoutingTable stub function, as the gateway only operates in custom network namespace. +func (grm *GatewayRoutingManager) CleanRoutingTable() error { + return flushRoutesForRoutingTable(grm.routingTableID) +} + +// CleanPolicyRules stub function, as the gateway only operates in custom network namespace. +func (grm *GatewayRoutingManager) CleanPolicyRules() error { + return flushRulesForRoutingTable(grm.routingTableID) +} diff --git a/pkg/liqonet/routing/gatewayRouting_test.go b/pkg/liqonet/routing/gatewayRouting_test.go new file mode 100644 index 0000000000..695e308213 --- /dev/null +++ b/pkg/liqonet/routing/gatewayRouting_test.go @@ -0,0 +1,219 @@ +package routing + +import ( + "net" + "strconv" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + "github.com/vishvananda/netlink" + "golang.org/x/sys/unix" + + netv1alpha1 "github.com/liqotech/liqo/apis/net/v1alpha1" + liqoerrors "github.com/liqotech/liqo/pkg/liqonet/errors" +) + +var ( + routingTableIDGRM = 19773 + routesGRM = []routingInfo{ + { + destinationNet: "10.120.0.0/16", + routingTableID: routingTableIDGRM, + }, + { + destinationNet: "10.121.0.0/16", + routingTableID: routingTableIDGRM, + }} + existingRoutesGRM []*netlink.Route + tepGRM netv1alpha1.TunnelEndpoint + tunnelDevice netlink.Link +) + +var _ = Describe("GatewayRouting", func() { + BeforeEach(func() { + // Populate the index of the routes with the correct one. + for i := range routesGRM { + routesGRM[i].iFaceIndex = tunnelDevice.Attrs().Index + } + }) + + Describe("creating new Gateway Route Manager", func() { + + Context("when parameters are not valid", func() { + It("routingTableID parameter out of range: a negative number", func() { + grm, err := NewGatewayRoutingManager(-244, tunnelDevice) + Expect(grm).Should(BeNil()) + Expect(err).Should(Equal(&liqoerrors.WrongParameter{Parameter: "routingTableID", Reason: liqoerrors.GreaterOrEqual + strconv.Itoa(0)})) + }) + + It("routingTableID parameter out of range: superior to max value ", func() { + grm, err := NewGatewayRoutingManager(unix.RT_TABLE_MAX+1, tunnelDevice) + Expect(grm).Should(BeNil()) + Expect(err).Should(Equal(&liqoerrors.WrongParameter{Parameter: "routingTableID", Reason: liqoerrors.MinorOrEqual + strconv.Itoa(unix.RT_TABLE_MAX)})) + }) + It("tunnelDevice is nil", func() { + grm, err := NewGatewayRoutingManager(routingTableIDGRM, nil) + Expect(grm).Should(BeNil()) + Expect(err).Should(Equal(&liqoerrors.WrongParameter{Parameter: "tunnelDevice", Reason: liqoerrors.NotNil})) + }) + }) + + Context("when parameters are correct", func() { + It("right parameters", func() { + grm, err := NewGatewayRoutingManager(routingTableIDGRM, tunnelDevice) + Expect(grm).ShouldNot(BeNil()) + Expect(err).ShouldNot(HaveOccurred()) + }) + }) + }) + + Describe("configuring routes for a remote peering cluster", func() { + JustBeforeEach(func() { + tepGRM = netv1alpha1.TunnelEndpoint{ + Status: netv1alpha1.TunnelEndpointStatus{ + LocalNATPodCIDR: "10.150.0.0/16", + RemoteNATPodCIDR: "10.250.0.0/16", + VethIFaceIndex: 12345, + GatewayIP: ipAddress2NoSubnet, + }} + }) + Context("when tep holds malformed parameters", func() { + It("route configuration fails while adding route", func() { + tepGRM.Status.RemoteNATPodCIDR = "10.150.000/16" + added, err := grm.EnsureRoutesPerCluster(&tepGRM) + Expect(err).Should(Equal(&net.ParseError{ + Type: "CIDR address", + Text: "10.150.000/16", + })) + Expect(added).Should(BeFalse()) + Expect(err).NotTo(BeNil()) + }) + }) + + Context("when tep holds correct parameters", func() { + JustBeforeEach(func() { + existingRoutesGRM = setUpRoutes(routesGRM) + }) + + JustAfterEach(func() { + tearDownRoutes(routingTableIDGRM) + }) + + It("route configuration should be correctly inserted", func() { + added, err := grm.EnsureRoutesPerCluster(&tep) + Expect(err).ShouldNot(HaveOccurred()) + Expect(added).Should(BeTrue()) + // Get the inserted route + _, dstNet, err := net.ParseCIDR(tep.Status.RemoteNATPodCIDR) + Expect(err).ShouldNot(HaveOccurred()) + routes, err := netlink.RouteListFiltered(netlink.FAMILY_V4, &netlink.Route{Dst: dstNet, Table: routingTableIDGRM}, netlink.RT_FILTER_DST|netlink.RT_FILTER_TABLE) + Expect(err).ShouldNot(HaveOccurred()) + Expect(routes[0].Dst.String()).Should(Equal(tep.Status.RemoteNATPodCIDR)) + Expect(routes[0].Gw).Should(BeNil()) + }) + + It("route already exists, should return false and nil", func() { + tepGRM.Status.RemoteNATPodCIDR = existingRoutesGRM[0].Dst.String() + tepGRM.Status.GatewayIP = existingRoutesGRM[0].Gw.String() + added, err := grm.EnsureRoutesPerCluster(&tepGRM) + Expect(err).ShouldNot(HaveOccurred()) + Expect(added).Should(BeFalse()) + }) + }) + }) + + Describe("removing route configuration for a remote peering cluster", func() { + Context("when tep holds malformed parameters", func() { + It("fails to remove route configuration while removing the route", func() { + tepGRM.Status.RemoteNATPodCIDR = "" + added, err := grm.RemoveRoutesPerCluster(&tepGRM) + Expect(err).Should(Equal(&net.ParseError{ + Type: "CIDR address", + Text: "", + })) + Expect(added).Should(BeFalse()) + Expect(err).NotTo(BeNil()) + }) + }) + + Context("when tep holds correct parameters", func() { + JustBeforeEach(func() { + existingRoutesGRM = setUpRoutes(routesGRM) + }) + + JustAfterEach(func() { + tearDownRoutes(routingTableIDGRM) + }) + + It("route configuration should be correctly removed", func() { + tepGRM.Status.RemoteNATPodCIDR = existingRoutesGRM[0].Dst.String() + deleted, err := grm.RemoveRoutesPerCluster(&tepGRM) + Expect(err).ShouldNot(HaveOccurred()) + Expect(deleted).Should(BeTrue()) + // Try to get the remove route. + routes, err := netlink.RouteListFiltered(netlink.FAMILY_V4, existingRoutesGRM[0], netlink.RT_FILTER_DST|netlink.RT_FILTER_TABLE) + Expect(err).ShouldNot(HaveOccurred()) + Expect(len(routes)).Should(BeZero()) + }) + + It("route configuration should be correctly removed from veth pair", func() { + tepGRM.Status.RemoteNATPodCIDR = existingRoutesGRM[1].Dst.String() + tepGRM.Status.GatewayIP = ipAddress1NoSubnet + tepGRM.Status.VethIFaceIndex = overlayDevice.Link.Index + added, err := grm.RemoveRoutesPerCluster(&tepGRM) + Expect(err).ShouldNot(HaveOccurred()) + Expect(added).Should(BeTrue()) + // Try to get the remove route. + routes, err := netlink.RouteListFiltered(netlink.FAMILY_V4, existingRoutesGRM[1], netlink.RT_FILTER_DST|netlink.RT_FILTER_TABLE) + Expect(err).ShouldNot(HaveOccurred()) + Expect(len(routes)).Should(BeZero()) + }) + + It("route does not exist, should return false and nil", func() { + added, err := grm.RemoveRoutesPerCluster(&tep) + Expect(err).ShouldNot(HaveOccurred()) + Expect(added).Should(BeFalse()) + }) + }) + }) + + Describe("removing all routes configurations managed by the gateway route manager", func() { + Context("removing routes, should return nil", func() { + JustBeforeEach(func() { + existingRoutesGRM = setUpRoutes(routesGRM) + }) + + JustAfterEach(func() { + tearDownRoutes(routingTableIDGRM) + }) + + It("routes should be correctly removed", func() { + err := grm.CleanRoutingTable() + Expect(err).ShouldNot(HaveOccurred()) + // Try to list rules + routes, err := netlink.RouteListFiltered(netlink.FAMILY_V4, existingRoutesGRM[0], netlink.RT_FILTER_TABLE) + Expect(err).ShouldNot(HaveOccurred()) + Expect(len(routes)).Should(BeZero()) + }) + }) + + Context("removing policy routing rules, should return nil", func() { + JustBeforeEach(func() { + existingRoutesGRM = setUpRoutes(routesGRM) + }) + + JustAfterEach(func() { + tearDownRoutes(routingTableIDGRM) + }) + + It("policy routing rules should be correctly removed", func() { + err := grm.CleanPolicyRules() + Expect(err).ShouldNot(HaveOccurred()) + // Try to list rules + exists, err := existsRuleForRoutingTable(routingTableIDGRM) + Expect(err).ShouldNot(HaveOccurred()) + Expect(exists).Should(BeFalse()) + }) + }) + }) +}) diff --git a/pkg/liqonet/routing/routing_suite_test.go b/pkg/liqonet/routing/routing_suite_test.go index 31bc33d8e0..e0c82cd63d 100644 --- a/pkg/liqonet/routing/routing_suite_test.go +++ b/pkg/liqonet/routing/routing_suite_test.go @@ -26,7 +26,7 @@ var ( ipAddress2NoSubnetOverlay = "240.0.0.2" dummylink1, dummyLink2 netlink.Link iFacesNames = []string{"liqo-test-1", "liqo-test-2"} - drm, vrm Routing + drm, vrm, grm Routing tep netv1alpha1.TunnelEndpoint ) @@ -65,11 +65,25 @@ var _ = BeforeSuite(func() { vrm, err = NewVxlanRoutingManager(routingTableIDVRM, ipAddress1NoSubnet, overlayNetPrexif, overlayDevice) Expect(err).Should(BeNil()) Expect(vrm).NotTo(BeNil()) + + //*** Gateway Route Manager Configuration ***/ + // Create a dummy interface used as tunnel device. + link = &netlink.Dummy{netlink.LinkAttrs{Name: "dummy-tunnel"}} + Expect(netlink.LinkAdd(link)).To(BeNil()) + tunnelDevice, err = netlink.LinkByName("dummy-tunnel") + Expect(err).To(BeNil()) + Expect(tunnelDevice).NotTo(BeNil()) + // Set up dummy tunnel device + Expect(netlink.LinkSetUp(tunnelDevice)).To(BeNil()) + grm, err = NewGatewayRoutingManager(routingTableIDGRM, tunnelDevice) + Expect(err).Should(BeNil()) + Expect(grm).NotTo(BeNil()) }) var _ = AfterSuite(func() { tearDownInterfaces() - deleteLink(vxlanConfig.Name) + Expect(deleteLink(vxlanConfig.Name)).To(BeNil()) + Expect(deleteLink(tunnelDevice.Attrs().Name)).To(BeNil()) }) func setUpInterfaces() {