Skip to content

Commit

Permalink
Added unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
p-strusiewiczsurmacki-mobica committed Sep 15, 2023
1 parent 0315ff9 commit fb32b5b
Show file tree
Hide file tree
Showing 23 changed files with 3,436 additions and 123 deletions.
3 changes: 2 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import (
"github.com/telekom/das-schiff-network-operator/pkg/healthcheck"
"github.com/telekom/das-schiff-network-operator/pkg/macvlan"
"github.com/telekom/das-schiff-network-operator/pkg/monitoring"
"github.com/telekom/das-schiff-network-operator/pkg/nl"
"github.com/telekom/das-schiff-network-operator/pkg/notrack"
"github.com/telekom/das-schiff-network-operator/pkg/reconciler"
"k8s.io/apimachinery/pkg/runtime"
Expand Down Expand Up @@ -136,7 +137,7 @@ func main() {
os.Exit(1)
}

anycastTracker := &anycast.Tracker{}
anycastTracker := anycast.NewTracker(&nl.NetlinkToolkit{})

if err = (&networkv1alpha1.VRFRouteConfiguration{}).SetupWebhookWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create webhook", "webhook", "VRFRouteConfiguration")
Expand Down
47 changes: 29 additions & 18 deletions pkg/anycast/anycast.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"net"
"time"

"github.com/telekom/das-schiff-network-operator/pkg/nl"
"github.com/vishvananda/netlink"
"golang.org/x/sys/unix"
)
Expand All @@ -18,19 +19,25 @@ var (

type Tracker struct {
TrackedBridges []int
netlinkToolkit nl.NetlinkToolkitInterface
}

func NewTracker(netlinkToolkit nl.NetlinkToolkitInterface) *Tracker {
return &Tracker{TrackedBridges: []int{},
netlinkToolkit: netlinkToolkit}
}

// TODO: Anycast Support is currently highly experimental.

func (t *Tracker) checkTrackedInterfaces() {
for _, intfIdx := range t.TrackedBridges {
intf, err := netlink.LinkByIndex(intfIdx)
intf, err := t.netlinkToolkit.LinkByIndex(intfIdx)
if err != nil {
fmt.Printf("Couldn't load interface idx %d: %v\n", intfIdx, err)
continue
}

syncInterface(intf.(*netlink.Bridge))
_ = syncInterface(intf.(*netlink.Bridge), t.netlinkToolkit)
}
}

Expand Down Expand Up @@ -79,11 +86,11 @@ func filterNeighbors(neighIn []netlink.Neigh) (neighOut []netlink.Neigh) {
return neighOut
}

func syncInterfaceByFamily(intf *netlink.Bridge, family int, routingTable uint32) {
bridgeNeighbors, err := netlink.NeighList(intf.Attrs().Index, family)
func syncInterfaceByFamily(intf *netlink.Bridge, family int, routingTable uint32, netlinkToolkit nl.NetlinkToolkitInterface) error {
bridgeNeighbors, err := netlinkToolkit.NeighList(intf.Attrs().Index, family)
if err != nil {
fmt.Printf("Error getting v4 neighbors of interface %s: %v\n", intf.Attrs().Name, err)
return
return fmt.Errorf("error getting v4 neighbors of interface %s: %w", intf.Attrs().Name, err)
}
bridgeNeighbors = filterNeighbors(bridgeNeighbors)

Expand All @@ -92,16 +99,16 @@ func syncInterfaceByFamily(intf *netlink.Bridge, family int, routingTable uint32
Table: int(routingTable),
Protocol: anycastRoutesProt,
}
routes, err := netlink.RouteListFiltered(family, routeFilterV4, netlink.RT_FILTER_OIF|netlink.RT_FILTER_TABLE|netlink.RT_FILTER_PROTOCOL)
routes, err := netlinkToolkit.RouteListFiltered(family, routeFilterV4, netlink.RT_FILTER_OIF|netlink.RT_FILTER_TABLE|netlink.RT_FILTER_PROTOCOL)
if err != nil {
fmt.Printf("Error getting v4 routes of interface %s: %v\n", intf.Attrs().Name, err)
return
return fmt.Errorf("error getting v4 routes of interface %s: %w", intf.Attrs().Name, err)
}

alreadyV4Existing := []*net.IPNet{}
for i := range routes {
if !containsIPAddress(bridgeNeighbors, routes[i].Dst) {
if err := netlink.RouteDel(&routes[i]); err != nil {
if err := netlinkToolkit.RouteDel(&routes[i]); err != nil {
fmt.Printf("Error deleting route %v: %v\n", routes[i], err)
}
} else {
Expand All @@ -110,33 +117,37 @@ func syncInterfaceByFamily(intf *netlink.Bridge, family int, routingTable uint32
}

for i := range bridgeNeighbors {
ipnet := netlink.NewIPNet(bridgeNeighbors[i].IP)
ipnet := netlinkToolkit.NewIPNet(bridgeNeighbors[i].IP)
if !containsIPNetwork(alreadyV4Existing, ipnet) {
route := buildRoute(family, intf, ipnet, routingTable)
if err := netlink.RouteAdd(route); err != nil {
if err := netlinkToolkit.RouteAdd(route); err != nil {
fmt.Printf("Error adding route %v: %v\n", route, err)
}
}
}

return nil
}

func syncInterface(intf *netlink.Bridge) {
func syncInterface(intf *netlink.Bridge, netlinkToolkit nl.NetlinkToolkitInterface) error {
routingTable := uint32(defaultVrfAnycastTable)
if intf.Attrs().MasterIndex > 0 {
nl, err := netlink.LinkByIndex(intf.Attrs().MasterIndex)
link, err := netlinkToolkit.LinkByIndex(intf.Attrs().MasterIndex)
if err != nil {
fmt.Printf("Error getting VRF parent of interface %s: %v\n", intf.Attrs().Name, err)
return
return fmt.Errorf("error getting VRF parent of interface %s: %w", intf.Attrs().Name, err)
}
if nl.Type() != "vrf" {
if link.Type() != "vrf" {
fmt.Printf("Parent interface of %s is not a VRF: %v\n", intf.Attrs().Name, err)
return
return fmt.Errorf("parent interface of %s is not a VRF", intf.Attrs().Name)
}
routingTable = nl.(*netlink.Vrf).Table
routingTable = link.(*netlink.Vrf).Table
}

syncInterfaceByFamily(intf, unix.AF_INET, routingTable)
syncInterfaceByFamily(intf, unix.AF_INET6, routingTable)
_ = syncInterfaceByFamily(intf, unix.AF_INET, routingTable, netlinkToolkit)
_ = syncInterfaceByFamily(intf, unix.AF_INET6, routingTable, netlinkToolkit)

return nil
}

func (t *Tracker) RunAnycastSync() {
Expand Down
181 changes: 181 additions & 0 deletions pkg/anycast/anycast_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
package anycast

import (
"errors"
"net"
"testing"

. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/telekom/das-schiff-network-operator/pkg/nl"
mock_nl "github.com/telekom/das-schiff-network-operator/pkg/nl/mock"
"github.com/vishvananda/netlink"
"go.uber.org/mock/gomock"
)

var (
mockctrl *gomock.Controller
)

var _ = BeforeSuite(func() {

})

func TestAnycast(t *testing.T) {
RegisterFailHandler(Fail)
mockctrl = gomock.NewController(t)
defer mockctrl.Finish()
RunSpecs(t,
"Anycast Suite")
}

var _ = Describe("anycast", func() {
Context("buildRoute() should", func() {
It("build route", func() {
route := buildRoute(0, &netlink.Bridge{}, &net.IPNet{}, 0)
Expect(route).ToNot(BeNil())
})
})
Context("containsIPAddress() should", func() {
It("return false if list does not contain IP address", func() {
result := containsIPAddress([]netlink.Neigh{{IP: net.IPv4(0, 0, 0, 0)}}, &net.IPNet{IP: net.IPv4(1, 1, 1, 1)})
Expect(result).To(BeFalse())
})
It("return true if list does contain IP address", func() {
result := containsIPAddress([]netlink.Neigh{{IP: net.IPv4(0, 0, 0, 0)}}, &net.IPNet{IP: net.IPv4(0, 0, 0, 0)})
Expect(result).To(BeTrue())
})
})
Context("containsIPNetwork() should", func() {
It("return false if list does not contain IP network", func() {
result := containsIPNetwork([]*net.IPNet{{IP: net.IPv4(0, 0, 0, 0), Mask: net.IPv4Mask(255, 255, 255, 0)}},
&net.IPNet{IP: net.IPv4(0, 0, 0, 0), Mask: net.IPv4Mask(255, 255, 255, 254)})
Expect(result).To(BeFalse())
})
It("return true if list does contain IP network", func() {
result := containsIPNetwork([]*net.IPNet{{IP: net.IPv4(0, 0, 0, 0), Mask: net.IPv4Mask(255, 255, 255, 0)}},
&net.IPNet{IP: net.IPv4(0, 0, 0, 0), Mask: net.IPv4Mask(255, 255, 255, 0)})
Expect(result).To(BeTrue())
})
})
Context("filterNeighbors() should", func() {
It("return empty list if flags not as expected", func() {
neighIn := []netlink.Neigh{{Flags: netlink.NTF_EXT_LEARNED}}
result := filterNeighbors(neighIn)
Expect(result).To(BeEmpty())
})
It("return empty list if state not as expected", func() {
neighIn := []netlink.Neigh{{Flags: netlink.NTF_EXT_MANAGED, State: netlink.NUD_INCOMPLETE}}
result := filterNeighbors(neighIn)
Expect(result).To(BeEmpty())
})
It("return non empty list", func() {
neighIn := []netlink.Neigh{{Flags: netlink.NTF_EXT_MANAGED}}
result := filterNeighbors(neighIn)
Expect(result).ToNot(BeEmpty())
})
})
Context("syncInterfaceByFamily() should", func() {
netlinkMock := mock_nl.NewMockNetlinkToolkitInterface(mockctrl)
It("return error if cannot get neighbors", func() {
netlinkMock.EXPECT().NeighList(0, 0).Return(nil, errors.New("fake error"))
err := syncInterfaceByFamily(&netlink.Bridge{}, 0, 0, netlinkMock)
Expect(err).To(HaveOccurred())
})
It("return error if cannot filter routes", func() {
family := 0
linkIndex := 0
netlinkMock.EXPECT().NeighList(linkIndex, family).Return([]netlink.Neigh{{Flags: netlink.NTF_EXT_MANAGED}}, nil)
netlinkMock.EXPECT().RouteListFiltered(0, gomock.Any(), netlink.RT_FILTER_OIF|netlink.RT_FILTER_TABLE|netlink.RT_FILTER_PROTOCOL).
Return(nil, errors.New("fake error"))
err := syncInterfaceByFamily(&netlink.Bridge{}, family, 0, netlinkMock)
Expect(err).To(HaveOccurred())
})
It("return no error if cannot delete route", func() {
family := 0
linkIndex := 0
route := netlink.Route{Flags: netlink.NTF_EXT_MANAGED, Dst: netlink.NewIPNet(net.IPv4(1, 1, 1, 1))}
netlinkMock.EXPECT().NeighList(linkIndex, family).Return([]netlink.Neigh{{Flags: netlink.NTF_EXT_MANAGED, IP: net.IPv4(0, 0, 0, 0)}}, nil)
netlinkMock.EXPECT().RouteListFiltered(0, gomock.Any(), netlink.RT_FILTER_OIF|netlink.RT_FILTER_TABLE|netlink.RT_FILTER_PROTOCOL).
Return([]netlink.Route{route}, nil)
netlinkMock.EXPECT().RouteDel(gomock.Any()).Return(errors.New("fake error")) // error is only logged, not returned
netlinkMock.EXPECT().NewIPNet(gomock.Any()).Return(netlink.NewIPNet(net.IPv4(1, 1, 1, 1)))
netlinkMock.EXPECT().RouteAdd(gomock.Any()).Return(errors.New("fake error")) // error is only logged, not returned
err := syncInterfaceByFamily(&netlink.Bridge{}, family, 0, netlinkMock)
Expect(err).ToNot(HaveOccurred())
})
It("return no error if appended route", func() {
family := 0
linkIndex := 0
route := netlink.Route{Flags: netlink.NTF_EXT_MANAGED, Dst: netlink.NewIPNet(net.IPv4(1, 1, 1, 1))}
netlinkMock.EXPECT().NeighList(linkIndex, family).Return([]netlink.Neigh{{Flags: netlink.NTF_EXT_MANAGED, IP: net.IPv4(1, 1, 1, 1)}}, nil)
netlinkMock.EXPECT().RouteListFiltered(0, gomock.Any(), netlink.RT_FILTER_OIF|netlink.RT_FILTER_TABLE|netlink.RT_FILTER_PROTOCOL).
Return([]netlink.Route{route}, nil)
netlinkMock.EXPECT().NewIPNet(gomock.Any()).Return(netlink.NewIPNet(net.IPv4(1, 1, 1, 1)))
err := syncInterfaceByFamily(&netlink.Bridge{}, family, 0, netlinkMock)
Expect(err).ToNot(HaveOccurred())
})
})
Context("syncInterface() should", func() {
netlinkMock := mock_nl.NewMockNetlinkToolkitInterface(mockctrl)
It("return no error if interface's Master Index <= 0", func() {
intf := &netlink.Bridge{LinkAttrs: netlink.LinkAttrs{MasterIndex: 0}}
// returning error just to quit syncInterfaceByFamily call
netlinkMock.EXPECT().NeighList(gomock.Any(), gomock.Any()).Return(nil, errors.New("fake error")).Times(2)
err := syncInterface(intf, netlinkMock)
Expect(err).ToNot(HaveOccurred())
})
It("return error if cannot get interface by index", func() {
masterIndex := 1
intf := &netlink.Bridge{LinkAttrs: netlink.LinkAttrs{MasterIndex: masterIndex}}
netlinkMock.EXPECT().LinkByIndex(masterIndex).Return(nil, errors.New("fake error"))
err := syncInterface(intf, netlinkMock)
Expect(err).To(HaveOccurred())
})
It("return error if could get interface but it's type is not vrf", func() {
masterIndex := 1
intf := &netlink.Bridge{LinkAttrs: netlink.LinkAttrs{MasterIndex: masterIndex}}
netlinkMock.EXPECT().LinkByIndex(masterIndex).Return(&netlink.Gretun{}, nil)
err := syncInterface(intf, netlinkMock)
Expect(err).To(HaveOccurred())
})
It("return no error", func() {
masterIndex := 1
intf := &netlink.Bridge{LinkAttrs: netlink.LinkAttrs{MasterIndex: masterIndex}}
netlinkMock.EXPECT().LinkByIndex(masterIndex).Return(&netlink.Vrf{}, nil)
// returning error just to quit syncInterfaceByFamily call
netlinkMock.EXPECT().NeighList(gomock.Any(), gomock.Any()).Return(nil, errors.New("fake error")).Times(2)
err := syncInterface(intf, netlinkMock)
Expect(err).ToNot(HaveOccurred())
})
})
Context("NewTracker() should", func() {
It("return new anycast tracker", func() {
toolkit := &nl.NetlinkToolkit{}
tracker := NewTracker(toolkit)
Expect(tracker).ToNot(BeNil())
Expect(tracker.netlinkToolkit).To(Equal(toolkit))
})
})
Context("checkTrackedInterfaces() should", func() {
netlinkMock := mock_nl.NewMockNetlinkToolkitInterface(mockctrl)
It("return no error if cannot get link by index", func() {
netlinkMock.EXPECT().LinkByIndex(gomock.Any()).Return(nil, errors.New("fake error"))
tracker := NewTracker(netlinkMock)
Expect(tracker).ToNot(BeNil())
Expect(tracker.netlinkToolkit).To(Equal(netlinkMock))
tracker.TrackedBridges = []int{0}
tracker.checkTrackedInterfaces()
})
It("return no error", func() {
netlinkMock.EXPECT().LinkByIndex(gomock.Any()).Return(&netlink.Bridge{}, nil)
// returning error just to quit syncInterface call
netlinkMock.EXPECT().NeighList(gomock.Any(), gomock.Any()).Return(nil, errors.New("fake error")).Times(2)
tracker := NewTracker(netlinkMock)
Expect(tracker).ToNot(BeNil())
Expect(tracker.netlinkToolkit).To(Equal(netlinkMock))
tracker.TrackedBridges = []int{0}
tracker.checkTrackedInterfaces()
})
})
})
77 changes: 77 additions & 0 deletions pkg/config/config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package config

import (
"os"
"testing"

. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)

const operatorConfigEnv = "OPERATOR_CONFIG"

var _ = BeforeSuite(func() {

})

func TestDebounce(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t,
"Config Suite")
}

var _ = Describe("config", func() {
Context("LoadConfig() should", func() {
It("return error if cannot read config", func() {
oldEnv, isSet := os.LookupEnv(operatorConfigEnv)
os.Setenv(operatorConfigEnv, "some-invalid-path")
_, err := LoadConfig()
Expect(err).To(HaveOccurred())
if isSet {
err = os.Setenv(operatorConfigEnv, oldEnv)
Expect(err).ToNot(HaveOccurred())
} else {
err = os.Unsetenv(operatorConfigEnv)
Expect(err).ToNot(HaveOccurred())
}
})
It("return error if cannot unmarshal config", func() {
oldEnv, isSet := os.LookupEnv(operatorConfigEnv)
os.Setenv(operatorConfigEnv, "./testdata/invalidConfig.yaml")
_, err := LoadConfig()
Expect(err).To(HaveOccurred())
if isSet {
err = os.Setenv(operatorConfigEnv, oldEnv)
Expect(err).ToNot(HaveOccurred())
} else {
err = os.Unsetenv(operatorConfigEnv)
Expect(err).ToNot(HaveOccurred())
}
})
It("return no error", func() {
oldEnv, isSet := os.LookupEnv(operatorConfigEnv)
os.Setenv(operatorConfigEnv, "./testdata/config.yaml")
_, err := LoadConfig()
Expect(err).ToNot(HaveOccurred())
if isSet {
err = os.Setenv(operatorConfigEnv, oldEnv)
Expect(err).ToNot(HaveOccurred())
} else {
err = os.Unsetenv(operatorConfigEnv)
Expect(err).ToNot(HaveOccurred())
}
})
})
Context("ShouldSkipVRFConfig() should", func() {
It("return false", func() {
cfg := &Config{SkipVRFConfig: []string{"", "", ""}}
result := cfg.ShouldSkipVRFConfig("skip")
Expect(result).To(BeFalse())
})
It("return true", func() {
cfg := &Config{SkipVRFConfig: []string{"", "skip", ""}}
result := cfg.ShouldSkipVRFConfig("skip")
Expect(result).To(BeTrue())
})
})
})
Loading

0 comments on commit fb32b5b

Please sign in to comment.