From 9c57f32f5836f865b5d6e44a711f7ac533b8819c Mon Sep 17 00:00:00 2001 From: Milos Gajdos Date: Sun, 9 Jun 2019 23:09:38 +0100 Subject: [PATCH] Added Entry type. Basic implementation of Router and Table --- router/default.go | 30 ++++++++--- router/entry.go | 89 +++++++++++++++++++++++++++++++ router/options.go | 24 --------- router/query.go | 22 ++++---- router/router.go | 18 +++---- router/table.go | 133 ++++++++++++++++++++++++++++++++++++---------- 6 files changed, 237 insertions(+), 79 deletions(-) create mode 100644 router/entry.go diff --git a/router/default.go b/router/default.go index 1eefd644..64770ce9 100644 --- a/router/default.go +++ b/router/default.go @@ -1,6 +1,9 @@ package router import ( + "fmt" + "strings" + "github.com/google/uuid" "github.com/micro/go-micro/registry" "github.com/micro/go-micro/registry/gossip" @@ -45,25 +48,25 @@ func (r *router) Options() Options { // Add adds new entry into routing table with given options. // It returns error if the entry could not be added. -func (r *router) Add(e *Entry, opts ...RouteOption) error { - return nil +func (r *router) Add(e Entry) error { + return r.table.Add(e) } // Remove removes entry from the routing table. // It returns error if either the entry could not be removed or it does not exist. -func (r *router) Remove(e *Entry) error { - return nil +func (r *router) Remove(e Entry) error { + return r.table.Remove(e) } // Update updates an entry in the router's routing table // It returns error if the entry was not found or it failed to be updated. -func (r *router) Update(e *Entry) error { - return nil +func (r *router) Update(opts ...EntryOption) error { + return r.table.Update(opts...) } // Lookup makes a query lookup and returns all matching entries func (r *router) Lookup(q Query) ([]*Entry, error) { - return nil, nil + return nil, ErrNotImplemented } // Table returns routing table @@ -83,5 +86,16 @@ func (r *router) Address() string { // String prints debugging information about router func (r *router) String() string { - return "" + sb := &strings.Builder{} + + s := fmt.Sprintf("Router Local Address: %s\n", r.opts.Address) + sb.WriteString(s) + + s = fmt.Sprintf("Router Network Address: %s\n", r.opts.Network) + sb.WriteString(s) + + s = fmt.Sprintf("Routing table size: %d\n", r.opts.Table.Size()) + sb.WriteString(s) + + return sb.String() } diff --git a/router/entry.go b/router/entry.go new file mode 100644 index 00000000..1aee4d65 --- /dev/null +++ b/router/entry.go @@ -0,0 +1,89 @@ +package router + +// AddPolicy defines routing table addition policy +type AddPolicy int + +const ( + // Override overrides existing routing table entry + OverrideIfExists AddPolicy = iota + // ErrIfExists returns error if the entry already exists + ErrIfExists +) + +// EntryOptions defines micro network routing table entry options +type EntryOptions struct { + // DestAddr is destination address + DestAddr string + // Hop is the next route hop + Hop Router + // SrcAddr defines local routing address + // On local networkss, this will be the address of local router + SrcAddr string + // Metric is route cost metric + Metric int + // Policy defines entry addition policy + Policy AddPolicy +} + +// DestAddr sets destination address +func DestAddr(a string) EntryOption { + return func(o *EntryOptions) { + o.DestAddr = a + } +} + +// Hop allows to set the route entry options +func Hop(r Router) EntryOption { + return func(o *EntryOptions) { + o.Hop = r + } +} + +// SrcAddr sets source address +func SrcAddr(a string) EntryOption { + return func(o *EntryOptions) { + o.SrcAddr = a + } +} + +// Metric sets entry metric +func Metric(m int) EntryOption { + return func(o *EntryOptions) { + o.Metric = m + } +} + +// AddEntryPolicy sets add entry policy +func AddEntryPolicy(p AddPolicy) EntryOption { + return func(o *EntryOptions) { + o.Policy = p + } +} + +// Entry is routing table entry +type Entry interface { + // Options returns entry options + Options() EntryOptions +} + +type entry struct { + opts EntryOptions +} + +// NewEntry returns new routing table entry +func NewEntry(opts ...EntryOption) Entry { + eopts := EntryOptions{} + + for _, o := range opts { + o(&eopts) + } + + return &entry{ + opts: eopts, + } +} + +// Options returns entry options +func (e *entry) Options() EntryOptions { + return e.opts +} diff --git a/router/options.go b/router/options.go index 277e1a30..8e1077c1 100644 --- a/router/options.go +++ b/router/options.go @@ -45,27 +45,3 @@ func RoutingTable(t Table) Option { o.Table = t } } - -// RouteOptions allows to specify routing table options -type RouteOptions struct { - // NetID is network ID - NetID string - // Metric is route metric - Metric int - // COntext allows to specify other arbitrary options - Context context.Context -} - -// NetID allows to set micro network ID -func NetID(id string) RouteOption { - return func(o *RouteOptions) { - o.NetID = id - } -} - -// Metric allows to set route cost metric -func Metric(m int) RouteOption { - return func(o *RouteOptions) { - o.Metric = m - } -} diff --git a/router/query.go b/router/query.go index 05679600..9632e062 100644 --- a/router/query.go +++ b/router/query.go @@ -1,27 +1,27 @@ package router -// Policy defines query policy -type QueryPolicy int +// LookupPolicy defines query policy +type LookupPolicy int const ( - // DiscardNoRoute discards query when no rout is found - DiscardNoRoute QueryPolicy = iota - // ClosestMatch returns closest match to query + // DiscardNoRoute discards query when no route is found + DiscardNoRoute LookupPolicy = iota + // ClosestMatch returns closest match to supplied query ClosestMatch ) // QueryOptions allow to define routing table query options type QueryOptions struct { // Route allows to set route options - Route *RouteOptions + Route *EntryOptions // Service is micro service name Service string // Policy defines query lookup policy - Policy QueryPolicy + Policy LookupPolicy } -// Route allows to set the route query options -func Route(r *RouteOptions) QueryOption { +// EntryOpts allows to set the route query options +func EntryOpts(r *EntryOptions) QueryOption { return func(o *QueryOptions) { o.Route = r } @@ -34,8 +34,8 @@ func Service(s string) QueryOption { } } -// Policy allows to define query lookup policy -func Policy(p QueryPolicy) QueryOption { +// QueryLookupPolicy allows to define query lookup policy +func QueryLookupPolicy(p LookupPolicy) QueryOption { return func(o *QueryOptions) { o.Policy = p } diff --git a/router/router.go b/router/router.go index 319b296a..ef3b8117 100644 --- a/router/router.go +++ b/router/router.go @@ -8,18 +8,18 @@ type Router interface { // Options returns Router options Options() Options // Add adds new entry into routing table - Add(*Entry, ...RouteOption) error + Add(Entry) error // Remove removes entry from the routing table - Remove(*Entry) error + Remove(Entry) error // Update updates entry in the routing table - Update(*Entry) error + Update(...EntryOption) error // Lookup queries the routing table and returns matching entries Lookup(Query) ([]*Entry, error) // Table returns routing table Table() Table - // Address is Router adddress + // Address returns the router bind adddress Address() string - // Network defines network router is in + // Network returns router's micro network bind address Network() string // String implemens fmt.Stringer interface String() string @@ -31,13 +31,13 @@ type RIB interface { String() string } -// Option used by the Router +// Option used by the router type Option func(*Options) -// RouteOption is used by Router for adding routing table entries -type RouteOption func(*RouteOptions) +// EntryOption is used to define routing entry options +type EntryOption func(*EntryOptions) -// QueryOption is used to defined routing table lookup query +// QueryOption is used to define query options type QueryOption func(*QueryOptions) // NewRouter creates new Router and returns it diff --git a/router/table.go b/router/table.go index 931ee9a2..9039bb1e 100644 --- a/router/table.go +++ b/router/table.go @@ -2,7 +2,12 @@ package router import ( "errors" + "fmt" + "hash/fnv" + "strings" "sync" + + "github.com/olekukonko/tablewriter" ) var ( @@ -10,77 +15,151 @@ var ( ErrRouteNotFound = errors.New("route not found") // ErrDuplicateRoute is return when route already exists ErrDuplicateRoute = errors.New("duplicate route") + // ErrNotImplemented is returned when some functionality has not been implemented + ErrNotImplemented = errors.New("not implemented") ) // Table is routing table type Table interface { // Add adds new route to the table - Add(*Entry) error + Add(Entry) error // Remove removes route from the table - Remove(*Entry) error + Remove(Entry) error // Update updates route in the table - Update(*Entry) error + Update(...EntryOption) error // Lookup looks up routes in the table - Lookup(Query) ([]*Entry, error) + Lookup(Query) ([]Entry, error) // Size returns the size of the table Size() int // String prints the routing table String() string } -// Entry is micro network routing table entry -type Entry struct { - // Addr is destination address - Addr string - // NetID is micro network ID - NetID string - // Hop is the next route hop - Hop Router - // Metric is route cost metric - Metric int -} - // table is routing table // It maps service name to routes type table struct { // m stores routing table map - m map[string][]Entry + m map[string]map[uint64]Entry sync.RWMutex } // NewTable creates new routing table and returns it func NewTable() Table { return &table{ - m: make(map[string][]Entry), + m: make(map[string]map[uint64]Entry), } } // Add adds new routing entry -func (t *table) Add(e *Entry) error { - return nil +func (t *table) Add(e Entry) error { + t.Lock() + defer t.Unlock() + + destAddr := e.Options().DestAddr + h := fnv.New64() + h.Write([]byte(e.Options().DestAddr + e.Options().Hop.Address())) + + if _, ok := t.m[destAddr]; !ok { + // create new map for DestAddr routes + t.m[destAddr] = make(map[uint64]Entry) + t.m[destAddr][h.Sum64()] = e + return nil + } + + if _, ok := t.m[destAddr][h.Sum64()]; ok && e.Options().Policy == OverrideIfExists { + t.m[destAddr][h.Sum64()] = e + return nil + } + + return ErrDuplicateRoute } // Remove removes entry from the routing table -func (t *table) Remove(e *Entry) error { +func (t *table) Remove(e Entry) error { + t.Lock() + defer t.Unlock() + + destAddr := e.Options().DestAddr + h := fnv.New64() + h.Write([]byte(e.Options().DestAddr + e.Options().Hop.Address())) + + if _, ok := t.m[destAddr]; !ok { + return ErrRouteNotFound + } else { + delete(t.m[destAddr], h.Sum64()) + return nil + } + return nil } -// Update updates routin entry -func (t *table) Update(e *Entry) error { - return nil +// Update updates routing entry +func (t *table) Update(opts ...EntryOption) error { + t.Lock() + defer t.Unlock() + + e := NewEntry(opts...) + + destAddr := e.Options().DestAddr + h := fnv.New64() + h.Write([]byte(e.Options().DestAddr + e.Options().Hop.Address())) + + if _, ok := t.m[destAddr]; !ok { + return ErrRouteNotFound + } + + if _, ok := t.m[destAddr][h.Sum64()]; ok { + t.m[destAddr][h.Sum64()] = e + return nil + } + + return ErrRouteNotFound } // Lookup looks up entry in the routing table -func (t *table) Lookup(q Query) ([]*Entry, error) { - return nil, nil +func (t *table) Lookup(q Query) ([]Entry, error) { + return nil, ErrNotImplemented } // Size returns the size of the routing table func (t *table) Size() int { + t.RLock() + defer t.RUnlock() + return len(t.m) } // String returns text representation of routing table func (t *table) String() string { - return "" + t.RLock() + defer t.RUnlock() + + // this will help us build routing table string + sb := &strings.Builder{} + + // create nice table printing structure + table := tablewriter.NewWriter(sb) + table.SetHeader([]string{"Dest", "Hop", "Src", "Metric"}) + + var destAddr, prevAddr string + + for _, entries := range t.m { + for _, entry := range entries { + destAddr = entry.Options().DestAddr + // we want to avoid printing the same dest address + if prevAddr == destAddr { + destAddr = "" + } + strEntry := []string{ + destAddr, + entry.Options().Hop.Address(), + fmt.Sprintf("%d", entry.Options().SrcAddr), + fmt.Sprintf("%d", entry.Options().Metric), + } + table.Append(strEntry) + prevAddr = destAddr + } + } + + return sb.String() }