From 5e85194a13e6ebd6a9eb858b7560c8f8d958d149 Mon Sep 17 00:00:00 2001 From: Milos Gajdos Date: Thu, 16 Jan 2020 12:48:36 +0000 Subject: [PATCH] QueryStrategy to allow querying routes based on Advertising Strategy --- router/query.go | 20 ++++++++--- router/router.go | 1 + router/table.go | 64 ++++++++++++++++++++++++++++++----- router/table_test.go | 79 +++++++++++++++++++++++++++++++++++++++++--- 4 files changed, 146 insertions(+), 18 deletions(-) diff --git a/router/query.go b/router/query.go index 557d44e0..ec079887 100644 --- a/router/query.go +++ b/router/query.go @@ -15,6 +15,8 @@ type QueryOptions struct { Network string // Router is router id Router string + // Strategy is routing strategy + Strategy Strategy } // QueryService sets service to query @@ -52,15 +54,23 @@ func QueryRouter(r string) QueryOption { } } +// QueryStrategy sets strategy to query +func QueryStrategy(s Strategy) QueryOption { + return func(o *QueryOptions) { + o.Strategy = s + } +} + // NewQuery creates new query and returns it func NewQuery(opts ...QueryOption) QueryOptions { // default options qopts := QueryOptions{ - Service: "*", - Address: "*", - Gateway: "*", - Network: "*", - Router: "*", + Service: "*", + Address: "*", + Gateway: "*", + Network: "*", + Router: "*", + Strategy: AdvertiseAll, } for _, o := range opts { diff --git a/router/router.go b/router/router.go index fc6f18c2..cf6294ef 100644 --- a/router/router.go +++ b/router/router.go @@ -146,6 +146,7 @@ type Advert struct { // Strategy is route advertisement strategy type Strategy int +// TODO: remove the "Advertise" prefix from these const ( // AdvertiseAll advertises all routes to the network AdvertiseAll Strategy = iota diff --git a/router/table.go b/router/table.go index 2b05ada0..821d782c 100644 --- a/router/table.go +++ b/router/table.go @@ -135,7 +135,7 @@ func (t *table) List() ([]Route, error) { } // isMatch checks if the route matches given query options -func isMatch(route Route, address, gateway, network, router string) bool { +func isMatch(route Route, address, gateway, network, router string, strategy Strategy) bool { // matches the values provided match := func(a, b string) bool { if a == "*" || a == b { @@ -150,12 +150,20 @@ func isMatch(route Route, address, gateway, network, router string) bool { b string } + // by default assume we are querying all routes + link := "*" + // if AdvertiseLocal change the link query accordingly + if strategy == AdvertiseLocal { + link = "local" + } + // compare the following values values := []compare{ {gateway, route.Gateway}, {network, route.Network}, {router, route.Router}, {address, route.Address}, + {link, route.Link}, } for _, v := range values { @@ -169,13 +177,46 @@ func isMatch(route Route, address, gateway, network, router string) bool { } // findRoutes finds all the routes for given network and router and returns them -func findRoutes(routes map[uint64]Route, address, gateway, network, router string) []Route { - var results []Route +func findRoutes(routes map[uint64]Route, address, gateway, network, router string, strategy Strategy) []Route { + // routeMap stores the routes we're going to advertise + routeMap := make(map[string][]Route) + for _, route := range routes { - if isMatch(route, address, gateway, network, router) { - results = append(results, route) + if isMatch(route, address, gateway, network, router, strategy) { + // add matchihg route to the routeMap + routeKey := route.Service + "@" + route.Network + // append the first found route to routeMap + _, ok := routeMap[routeKey] + if !ok { + routeMap[routeKey] = append(routeMap[routeKey], route) + continue + } + + // if AdvertiseAll, keep appending + if strategy == AdvertiseAll || strategy == AdvertiseLocal { + routeMap[routeKey] = append(routeMap[routeKey], route) + continue + } + + // now we're going to find the best routes + if strategy == AdvertiseBest { + // if the current optimal route metric is higher than routing table route, replace it + if len(routeMap[routeKey]) > 0 { + // NOTE: we know that when AdvertiseBest is set, we only ever have one item in current + if routeMap[routeKey][0].Metric > route.Metric { + routeMap[routeKey][0] = route + continue + } + } + } } } + + var results []Route + for _, route := range routeMap { + results = append(results, route...) + } + return results } @@ -187,17 +228,24 @@ func (t *table) Query(q ...QueryOption) ([]Route, error) { // create new query options opts := NewQuery(q...) + // create a cwslicelist of query results + results := make([]Route, 0, len(t.routes)) + + // if No routes are queried, return early + if opts.Strategy == AdvertiseNone { + return results, nil + } + if opts.Service != "*" { if _, ok := t.routes[opts.Service]; !ok { return nil, ErrRouteNotFound } - return findRoutes(t.routes[opts.Service], opts.Address, opts.Gateway, opts.Network, opts.Router), nil + return findRoutes(t.routes[opts.Service], opts.Address, opts.Gateway, opts.Network, opts.Router, opts.Strategy), nil } - results := make([]Route, 0, len(t.routes)) // search through all destinations for _, routes := range t.routes { - results = append(results, findRoutes(routes, opts.Address, opts.Gateway, opts.Network, opts.Router)...) + results = append(results, findRoutes(routes, opts.Address, opts.Gateway, opts.Network, opts.Router, opts.Strategy)...) } return results, nil diff --git a/router/table_test.go b/router/table_test.go index a05cb956..21c72510 100644 --- a/router/table_test.go +++ b/router/table_test.go @@ -1,6 +1,9 @@ package router -import "testing" +import ( + "fmt" + "testing" +) func testSetup() (*table, Route) { table := newTable() @@ -108,10 +111,10 @@ func TestList(t *testing.T) { func TestQuery(t *testing.T) { table, route := testSetup() - svc := []string{"svc1", "svc2", "svc3"} - net := []string{"net1", "net2", "net1"} - gw := []string{"gw1", "gw2", "gw3"} - rtr := []string{"rtr1", "rt2", "rt3"} + svc := []string{"svc1", "svc2", "svc3", "svc1"} + net := []string{"net1", "net2", "net1", "net3"} + gw := []string{"gw1", "gw2", "gw3", "gw3"} + rtr := []string{"rtr1", "rt2", "rt3", "rtr3"} for i := 0; i < len(svc); i++ { route.Service = svc[i] @@ -218,4 +221,70 @@ func TestQuery(t *testing.T) { if len(routes) != 0 { t.Errorf("incorrect number of routes returned. Expected: %d, found: %d", 0, len(routes)) } + + // query NO routes + query = []QueryOption{ + QueryGateway(gateway), + QueryNetwork(network), + QueryStrategy(AdvertiseNone), + } + + routes, err = table.Query(query...) + if err != nil { + t.Errorf("error looking up routes: %s", err) + } + + if len(routes) > 0 { + t.Errorf("incorrect number of routes returned. Expected: %d, found: %d", 0, len(routes)) + } + + // insert local routes to query + for i := 0; i < 2; i++ { + route.Link = "local" + route.Address = fmt.Sprintf("local.route.address-%d", i) + if err := table.Create(route); err != nil { + t.Errorf("error adding route: %s", err) + } + } + + // query local routes + query = []QueryOption{ + QueryGateway("*"), + QueryNetwork("*"), + QueryStrategy(AdvertiseLocal), + } + + routes, err = table.Query(query...) + if err != nil { + t.Errorf("error looking up routes: %s", err) + } + + if len(routes) != 2 { + t.Errorf("incorrect number of routes returned. Expected: %d, found: %d", 2, len(routes)) + } + + // add two different routes for svcX with different metric + for i := 0; i < 2; i++ { + route.Service = "svcX" + route.Address = fmt.Sprintf("svcX.route.address-%d", i) + route.Metric = int64(100 + i) + if err := table.Create(route); err != nil { + t.Errorf("error adding route: %s", err) + } + } + + // query best routes for svcX + query = []QueryOption{ + QueryService("svcX"), + QueryStrategy(AdvertiseBest), + } + + routes, err = table.Query(query...) + if err != nil { + t.Errorf("error looking up routes: %s", err) + } + + if len(routes) != 1 { + t.Errorf("incorrect number of routes returned. Expected: %d, found: %d", 1, len(routes)) + } }