QueryStrategy to allow querying routes based on Advertising Strategy

This commit is contained in:
Milos Gajdos 2020-01-16 12:48:36 +00:00
parent 689ae7cfc7
commit 5e85194a13
No known key found for this signature in database
GPG Key ID: 8B31058CC55DFD4F
4 changed files with 146 additions and 18 deletions

View File

@ -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 {

View File

@ -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

View File

@ -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

View File

@ -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))
}
}