netbird/management/server/route_test.go
Misha Bragin f37b43a542
Save Peer Status separately in the FileStore (#554)
Due to peer reconnects when restarting the Management service,
there are lots of SaveStore operations to update peer status.

Store.SavePeerStatus stores peer status separately and the
FileStore implementation stores it in memory.
2022-11-08 10:46:12 +01:00

850 lines
20 KiB
Go

package server
import (
"github.com/netbirdio/netbird/route"
"github.com/rs/xid"
"github.com/stretchr/testify/require"
"net/netip"
"testing"
)
const peer1Key = "BhRPtynAAYRDy08+q4HTMsos8fs4plTP4NOSh7C1ry8="
const peer2Key = "/yF0+vCfv+mRR5k0dca0TrGdO/oiNeAI58gToZm5NyI="
func TestCreateRoute(t *testing.T) {
type input struct {
network string
netID string
peer string
description string
masquerade bool
metric int
enabled bool
}
testCases := []struct {
name string
inputArgs input
shouldCreate bool
errFunc require.ErrorAssertionFunc
expectedRoute *route.Route
}{
{
name: "Happy Path",
inputArgs: input{
network: "192.168.0.0/16",
netID: "happy",
peer: peer1Key,
description: "super",
masquerade: false,
metric: 9999,
enabled: true,
},
errFunc: require.NoError,
shouldCreate: true,
expectedRoute: &route.Route{
Network: netip.MustParsePrefix("192.168.0.0/16"),
NetworkType: route.IPv4Network,
NetID: "happy",
Peer: peer1Key,
Description: "super",
Masquerade: false,
Metric: 9999,
Enabled: true,
},
},
{
name: "Bad Prefix",
inputArgs: input{
network: "192.168.0.0/34",
netID: "happy",
peer: peer1Key,
description: "super",
masquerade: false,
metric: 9999,
enabled: true,
},
errFunc: require.Error,
shouldCreate: false,
},
{
name: "Bad Peer",
inputArgs: input{
network: "192.168.0.0/16",
netID: "happy",
peer: "notExistingPeer",
description: "super",
masquerade: false,
metric: 9999,
enabled: true,
},
errFunc: require.Error,
shouldCreate: false,
},
{
name: "Empty Peer",
inputArgs: input{
network: "192.168.0.0/16",
netID: "happy",
peer: "",
description: "super",
masquerade: false,
metric: 9999,
enabled: false,
},
errFunc: require.NoError,
shouldCreate: true,
expectedRoute: &route.Route{
Network: netip.MustParsePrefix("192.168.0.0/16"),
NetworkType: route.IPv4Network,
NetID: "happy",
Peer: "",
Description: "super",
Masquerade: false,
Metric: 9999,
Enabled: false,
},
},
{
name: "Large Metric",
inputArgs: input{
network: "192.168.0.0/16",
peer: peer1Key,
netID: "happy",
description: "super",
masquerade: false,
metric: 99999,
enabled: true,
},
errFunc: require.Error,
shouldCreate: false,
},
{
name: "Small Metric",
inputArgs: input{
network: "192.168.0.0/16",
netID: "happy",
peer: peer1Key,
description: "super",
masquerade: false,
metric: 0,
enabled: true,
},
errFunc: require.Error,
shouldCreate: false,
},
{
name: "Large NetID",
inputArgs: input{
network: "192.168.0.0/16",
peer: peer1Key,
netID: "12345678901234567890qwertyuiopqwertyuiop1",
description: "super",
masquerade: false,
metric: 9999,
enabled: true,
},
errFunc: require.Error,
shouldCreate: false,
},
{
name: "Small NetID",
inputArgs: input{
network: "192.168.0.0/16",
netID: "",
peer: peer1Key,
description: "",
masquerade: false,
metric: 9999,
enabled: true,
},
errFunc: require.Error,
shouldCreate: false,
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
am, err := createRouterManager(t)
if err != nil {
t.Error("failed to create account manager")
}
account, err := initTestRouteAccount(t, am)
if err != nil {
t.Error("failed to init testing account")
}
outRoute, err := am.CreateRoute(
account.Id,
testCase.inputArgs.network,
testCase.inputArgs.peer,
testCase.inputArgs.description,
testCase.inputArgs.netID,
testCase.inputArgs.masquerade,
testCase.inputArgs.metric,
testCase.inputArgs.enabled,
)
testCase.errFunc(t, err)
if !testCase.shouldCreate {
return
}
// assign generated ID
testCase.expectedRoute.ID = outRoute.ID
if !testCase.expectedRoute.IsEqual(outRoute) {
t.Errorf("new route didn't match expected route:\nGot %#v\nExpected:%#v\n", outRoute, testCase.expectedRoute)
}
})
}
}
func TestSaveRoute(t *testing.T) {
validPeer := peer2Key
invalidPeer := "nonExisting"
validPrefix := netip.MustParsePrefix("192.168.0.0/24")
invalidPrefix, _ := netip.ParsePrefix("192.168.0.0/34")
validMetric := 1000
invalidMetric := 99999
validNetID := "12345678901234567890qw"
invalidNetID := "12345678901234567890qwertyuiopqwertyuiop1"
testCases := []struct {
name string
existingRoute *route.Route
newPeer *string
newMetric *int
newPrefix *netip.Prefix
skipCopying bool
shouldCreate bool
errFunc require.ErrorAssertionFunc
expectedRoute *route.Route
}{
{
name: "Happy Path",
existingRoute: &route.Route{
ID: "testingRoute",
Network: netip.MustParsePrefix("192.168.0.0/16"),
NetID: validNetID,
NetworkType: route.IPv4Network,
Peer: peer1Key,
Description: "super",
Masquerade: false,
Metric: 9999,
Enabled: true,
},
newPeer: &validPeer,
newMetric: &validMetric,
newPrefix: &validPrefix,
errFunc: require.NoError,
shouldCreate: true,
expectedRoute: &route.Route{
ID: "testingRoute",
Network: validPrefix,
NetID: validNetID,
NetworkType: route.IPv4Network,
Peer: validPeer,
Description: "super",
Masquerade: false,
Metric: validMetric,
Enabled: true,
},
},
{
name: "Bad Prefix",
existingRoute: &route.Route{
ID: "testingRoute",
Network: netip.MustParsePrefix("192.168.0.0/16"),
NetID: validNetID,
NetworkType: route.IPv4Network,
Peer: peer1Key,
Description: "super",
Masquerade: false,
Metric: 9999,
Enabled: true,
},
newPrefix: &invalidPrefix,
errFunc: require.Error,
},
{
name: "Bad Peer",
existingRoute: &route.Route{
ID: "testingRoute",
Network: netip.MustParsePrefix("192.168.0.0/16"),
NetID: validNetID,
NetworkType: route.IPv4Network,
Peer: peer1Key,
Description: "super",
Masquerade: false,
Metric: 9999,
Enabled: true,
},
newPeer: &invalidPeer,
errFunc: require.Error,
},
{
name: "Invalid Metric",
existingRoute: &route.Route{
ID: "testingRoute",
Network: netip.MustParsePrefix("192.168.0.0/16"),
NetID: validNetID,
NetworkType: route.IPv4Network,
Peer: peer1Key,
Description: "super",
Masquerade: false,
Metric: 9999,
Enabled: true,
},
newMetric: &invalidMetric,
errFunc: require.Error,
},
{
name: "Invalid NetID",
existingRoute: &route.Route{
ID: "testingRoute",
Network: netip.MustParsePrefix("192.168.0.0/16"),
NetID: invalidNetID,
NetworkType: route.IPv4Network,
Peer: peer1Key,
Description: "super",
Masquerade: false,
Metric: 9999,
Enabled: true,
},
newMetric: &invalidMetric,
errFunc: require.Error,
},
{
name: "Nil Route",
existingRoute: &route.Route{
ID: "testingRoute",
Network: netip.MustParsePrefix("192.168.0.0/16"),
NetID: validNetID,
NetworkType: route.IPv4Network,
Peer: peer1Key,
Description: "super",
Masquerade: false,
Metric: 9999,
Enabled: true,
},
skipCopying: true,
errFunc: require.Error,
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
am, err := createRouterManager(t)
if err != nil {
t.Error("failed to create account manager")
}
account, err := initTestRouteAccount(t, am)
if err != nil {
t.Error("failed to init testing account")
}
account.Routes[testCase.existingRoute.ID] = testCase.existingRoute
err = am.Store.SaveAccount(account)
if err != nil {
t.Error("account should be saved")
}
var routeToSave *route.Route
if !testCase.skipCopying {
routeToSave = testCase.existingRoute.Copy()
if testCase.newPeer != nil {
routeToSave.Peer = *testCase.newPeer
}
if testCase.newMetric != nil {
routeToSave.Metric = *testCase.newMetric
}
if testCase.newPrefix != nil {
routeToSave.Network = *testCase.newPrefix
}
}
err = am.SaveRoute(account.Id, routeToSave)
testCase.errFunc(t, err)
if !testCase.shouldCreate {
return
}
account, err = am.Store.GetAccount(account.Id)
if err != nil {
t.Fatal(err)
}
savedRoute, saved := account.Routes[testCase.expectedRoute.ID]
require.True(t, saved)
if !testCase.expectedRoute.IsEqual(savedRoute) {
t.Errorf("new route didn't match expected route:\nGot %#v\nExpected:%#v\n", savedRoute, testCase.expectedRoute)
}
})
}
}
func TestUpdateRoute(t *testing.T) {
routeID := "testingRouteID"
existingRoute := &route.Route{
ID: routeID,
Network: netip.MustParsePrefix("192.168.0.0/16"),
NetID: "superRoute",
NetworkType: route.IPv4Network,
Peer: peer1Key,
Description: "super",
Masquerade: false,
Metric: 9999,
Enabled: true,
}
testCases := []struct {
name string
existingRoute *route.Route
operations []RouteUpdateOperation
shouldCreate bool
errFunc require.ErrorAssertionFunc
expectedRoute *route.Route
}{
{
name: "Happy Path Single OPS",
existingRoute: existingRoute,
operations: []RouteUpdateOperation{
RouteUpdateOperation{
Type: UpdateRoutePeer,
Values: []string{peer2Key},
},
},
errFunc: require.NoError,
shouldCreate: true,
expectedRoute: &route.Route{
ID: routeID,
Network: netip.MustParsePrefix("192.168.0.0/16"),
NetID: "superRoute",
NetworkType: route.IPv4Network,
Peer: peer2Key,
Description: "super",
Masquerade: false,
Metric: 9999,
Enabled: true,
},
},
{
name: "Happy Path Multiple OPS",
existingRoute: existingRoute,
operations: []RouteUpdateOperation{
RouteUpdateOperation{
Type: UpdateRouteDescription,
Values: []string{"great"},
},
RouteUpdateOperation{
Type: UpdateRouteNetwork,
Values: []string{"192.168.0.0/24"},
},
RouteUpdateOperation{
Type: UpdateRoutePeer,
Values: []string{peer2Key},
},
RouteUpdateOperation{
Type: UpdateRouteMetric,
Values: []string{"3030"},
},
RouteUpdateOperation{
Type: UpdateRouteMasquerade,
Values: []string{"true"},
},
RouteUpdateOperation{
Type: UpdateRouteEnabled,
Values: []string{"false"},
},
RouteUpdateOperation{
Type: UpdateRouteNetworkIdentifier,
Values: []string{"megaRoute"},
},
},
errFunc: require.NoError,
shouldCreate: true,
expectedRoute: &route.Route{
ID: routeID,
Network: netip.MustParsePrefix("192.168.0.0/24"),
NetID: "megaRoute",
NetworkType: route.IPv4Network,
Peer: peer2Key,
Description: "great",
Masquerade: true,
Metric: 3030,
Enabled: false,
},
},
{
name: "Empty Values",
existingRoute: existingRoute,
operations: []RouteUpdateOperation{
RouteUpdateOperation{
Type: UpdateRoutePeer,
},
},
errFunc: require.Error,
},
{
name: "Multiple Values",
existingRoute: existingRoute,
operations: []RouteUpdateOperation{
RouteUpdateOperation{
Type: UpdateRoutePeer,
Values: []string{peer2Key, peer1Key},
},
},
errFunc: require.Error,
},
{
name: "Bad Prefix",
existingRoute: existingRoute,
operations: []RouteUpdateOperation{
RouteUpdateOperation{
Type: UpdateRouteNetwork,
Values: []string{"192.168.0.0/34"},
},
},
errFunc: require.Error,
},
{
name: "Bad Peer",
existingRoute: existingRoute,
operations: []RouteUpdateOperation{
RouteUpdateOperation{
Type: UpdateRoutePeer,
Values: []string{"non existing Peer"},
},
},
errFunc: require.Error,
},
{
name: "Empty Peer",
existingRoute: existingRoute,
operations: []RouteUpdateOperation{
RouteUpdateOperation{
Type: UpdateRoutePeer,
Values: []string{""},
},
},
errFunc: require.NoError,
shouldCreate: true,
expectedRoute: &route.Route{
ID: routeID,
Network: netip.MustParsePrefix("192.168.0.0/16"),
NetID: "superRoute",
NetworkType: route.IPv4Network,
Peer: "",
Description: "super",
Masquerade: false,
Metric: 9999,
Enabled: true,
},
},
{
name: "Large Network ID",
existingRoute: existingRoute,
operations: []RouteUpdateOperation{
RouteUpdateOperation{
Type: UpdateRouteNetworkIdentifier,
Values: []string{"12345678901234567890qwertyuiopqwertyuiop1"},
},
},
errFunc: require.Error,
},
{
name: "Empty Network ID",
existingRoute: existingRoute,
operations: []RouteUpdateOperation{
RouteUpdateOperation{
Type: UpdateRouteNetworkIdentifier,
Values: []string{""},
},
},
errFunc: require.Error,
},
{
name: "Invalid Metric",
existingRoute: existingRoute,
operations: []RouteUpdateOperation{
RouteUpdateOperation{
Type: UpdateRouteMetric,
Values: []string{"999999"},
},
},
errFunc: require.Error,
},
{
name: "Invalid Boolean",
existingRoute: existingRoute,
operations: []RouteUpdateOperation{
RouteUpdateOperation{
Type: UpdateRouteMasquerade,
Values: []string{"yes"},
},
},
errFunc: require.Error,
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
am, err := createRouterManager(t)
if err != nil {
t.Error("failed to create account manager")
}
account, err := initTestRouteAccount(t, am)
if err != nil {
t.Error("failed to init testing account")
}
account.Routes[testCase.existingRoute.ID] = testCase.existingRoute
err = am.Store.SaveAccount(account)
if err != nil {
t.Error("account should be saved")
}
updatedRoute, err := am.UpdateRoute(account.Id, testCase.existingRoute.ID, testCase.operations)
testCase.errFunc(t, err)
if !testCase.shouldCreate {
return
}
testCase.expectedRoute.ID = updatedRoute.ID
if !testCase.expectedRoute.IsEqual(updatedRoute) {
t.Errorf("new route didn't match expected route:\nGot %#v\nExpected:%#v\n", updatedRoute, testCase.expectedRoute)
}
})
}
}
func TestDeleteRoute(t *testing.T) {
testingRoute := &route.Route{
ID: "testingRoute",
Network: netip.MustParsePrefix("192.168.0.0/16"),
NetworkType: route.IPv4Network,
Peer: peer1Key,
Description: "super",
Masquerade: false,
Metric: 9999,
Enabled: true,
}
am, err := createRouterManager(t)
if err != nil {
t.Error("failed to create account manager")
}
account, err := initTestRouteAccount(t, am)
if err != nil {
t.Error("failed to init testing account")
}
account.Routes[testingRoute.ID] = testingRoute
err = am.Store.SaveAccount(account)
if err != nil {
t.Error("failed to save account")
}
err = am.DeleteRoute(account.Id, testingRoute.ID)
if err != nil {
t.Error("deleting route failed with error: ", err)
}
savedAccount, err := am.Store.GetAccount(account.Id)
if err != nil {
t.Error("failed to retrieve saved account with error: ", err)
}
_, found := savedAccount.Routes[testingRoute.ID]
if found {
t.Error("route shouldn't be found after delete")
}
}
func TestGetNetworkMap_RouteSync(t *testing.T) {
// no routes for peer in different groups
// no routes when route is deleted
baseRoute := &route.Route{
ID: "testingRoute",
Network: netip.MustParsePrefix("192.168.0.0/16"),
NetID: "superNet",
NetworkType: route.IPv4Network,
Peer: peer1Key,
Description: "super",
Masquerade: false,
Metric: 9999,
Enabled: true,
}
am, err := createRouterManager(t)
if err != nil {
t.Error("failed to create account manager")
}
account, err := initTestRouteAccount(t, am)
if err != nil {
t.Error("failed to init testing account")
}
newAccountRoutes, err := am.GetNetworkMap(peer1Key)
require.NoError(t, err)
require.Len(t, newAccountRoutes.Routes, 0, "new accounts should have no routes")
createdRoute, err := am.CreateRoute(account.Id, baseRoute.Network.String(), baseRoute.Peer,
baseRoute.Description, baseRoute.NetID, baseRoute.Masquerade, baseRoute.Metric, false)
require.NoError(t, err)
noDisabledRoutes, err := am.GetNetworkMap(peer1Key)
require.NoError(t, err)
require.Len(t, noDisabledRoutes.Routes, 0, "no routes for disabled routes")
enabledRoute := createdRoute.Copy()
enabledRoute.Enabled = true
err = am.SaveRoute(account.Id, enabledRoute)
require.NoError(t, err)
peer1Routes, err := am.GetNetworkMap(peer1Key)
require.NoError(t, err)
require.Len(t, peer1Routes.Routes, 1, "we should receive one route for peer1")
require.True(t, enabledRoute.IsEqual(peer1Routes.Routes[0]), "received route should be equal")
peer2Routes, err := am.GetNetworkMap(peer2Key)
require.NoError(t, err)
require.Len(t, peer2Routes.Routes, 1, "we should receive one route for peer2")
require.True(t, peer1Routes.Routes[0].IsEqual(peer2Routes.Routes[0]), "routes should be the same for peers in the same group")
newGroup := &Group{
ID: xid.New().String(),
Name: "peer1 group",
Peers: []string{peer1Key},
}
err = am.SaveGroup(account.Id, newGroup)
require.NoError(t, err)
rules, err := am.ListRules(account.Id, "testingUser")
require.NoError(t, err)
defaultRule := rules[0]
newRule := defaultRule.Copy()
newRule.ID = xid.New().String()
newRule.Name = "peer1 only"
newRule.Source = []string{newGroup.ID}
newRule.Destination = []string{newGroup.ID}
err = am.SaveRule(account.Id, newRule)
require.NoError(t, err)
err = am.DeleteRule(account.Id, defaultRule.ID)
require.NoError(t, err)
peer1GroupRoutes, err := am.GetNetworkMap(peer1Key)
require.NoError(t, err)
require.Len(t, peer1GroupRoutes.Routes, 1, "we should receive one route for peer1")
peer2GroupRoutes, err := am.GetNetworkMap(peer2Key)
require.NoError(t, err)
require.Len(t, peer2GroupRoutes.Routes, 0, "we should not receive routes for peer2")
err = am.DeleteRoute(account.Id, enabledRoute.ID)
require.NoError(t, err)
peer1DeletedRoute, err := am.GetNetworkMap(peer1Key)
require.NoError(t, err)
require.Len(t, peer1DeletedRoute.Routes, 0, "we should receive one route for peer1")
}
func createRouterManager(t *testing.T) (*DefaultAccountManager, error) {
store, err := createRouterStore(t)
if err != nil {
return nil, err
}
return BuildManager(store, NewPeersUpdateManager(), nil, "", "")
}
func createRouterStore(t *testing.T) (Store, error) {
dataDir := t.TempDir()
store, err := NewFileStore(dataDir)
if err != nil {
return nil, err
}
return store, nil
}
func initTestRouteAccount(t *testing.T, am *DefaultAccountManager) (*Account, error) {
peer1 := &Peer{
Key: peer1Key,
Name: "test-host1@netbird.io",
Meta: PeerSystemMeta{
Hostname: "test-host1@netbird.io",
GoOS: "linux",
Kernel: "Linux",
Core: "21.04",
Platform: "x86_64",
OS: "Ubuntu",
WtVersion: "development",
UIVersion: "development",
},
}
peer2 := &Peer{
Key: peer2Key,
Name: "test-host2@netbird.io",
Meta: PeerSystemMeta{
Hostname: "test-host2@netbird.io",
GoOS: "linux",
Kernel: "Linux",
Core: "21.04",
Platform: "x86_64",
OS: "Ubuntu",
WtVersion: "development",
UIVersion: "development",
},
}
accountID := "testingAcc"
userID := "testingUser"
domain := "example.com"
account := newAccountWithId(accountID, userID, domain)
err := am.Store.SaveAccount(account)
if err != nil {
return nil, err
}
_, err = am.AddPeer("", userID, peer1)
if err != nil {
return nil, err
}
_, err = am.AddPeer("", userID, peer2)
if err != nil {
return nil, err
}
return am.Store.GetAccount(account.Id)
}