Router cleanup and refactoring for win and profit.

This commit adds the following changes to router package:
* it refactors Advertise() function which now does only what
it claims to do: advertising
* various router packages functions/methods have been renamed to make
their functionality more obvious and more in line with what they actually do
* function documentation changes related to the above bullet points
This commit is contained in:
Milos Gajdos 2019-07-24 17:16:52 +01:00
parent 49dcc3d1bd
commit 1e94d9fe5a
No known key found for this signature in database
GPG Key ID: 8B31058CC55DFD4F
3 changed files with 193 additions and 155 deletions

View File

@ -17,6 +17,8 @@ const (
AdvertiseEventsTick = 5 * time.Second
// AdvertiseTableTick is time interval in which router advertises all routes found in routing table
AdvertiseTableTick = 1 * time.Minute
// AdvertiseFlushTick is time the yet unconsumed advertisements are flush i.e. discarded
AdvertiseFlushTick = 15 * time.Second
// AdvertSuppress is advert suppression threshold
AdvertSuppress = 2000.0
// AdvertRecover is advert recovery threshold
@ -38,13 +40,14 @@ var (
PenaltyDecay = math.Log(2) / PenaltyHalfLife
)
// router provides default router implementation
// router implements default router
type router struct {
// embed the table
table.Table
opts Options
status Status
exit chan struct{}
errChan chan error
eventChan chan *table.Event
advertChan chan *Advert
advertWg *sync.WaitGroup
@ -52,7 +55,7 @@ type router struct {
sync.RWMutex
}
// newRouter creates a new router and returns it
// newRouter creates new router and returns it
func newRouter(opts ...Option) Router {
// get default options
options := DefaultOptions()
@ -62,16 +65,17 @@ func newRouter(opts ...Option) Router {
o(&options)
}
return &router{
r := &router{
Table: options.Table,
opts: options,
status: Status{Error: nil, Code: Stopped},
exit: make(chan struct{}),
eventChan: make(chan *table.Event),
advertChan: make(chan *Advert),
status: Status{Code: Stopped, Error: nil},
advertWg: &sync.WaitGroup{},
wg: &sync.WaitGroup{},
}
go r.run()
return r
}
// Init initializes router with given options
@ -87,7 +91,7 @@ func (r *router) Options() Options {
return r.opts
}
// manageRoute applies route action on the routing table
// manageRoute applies action on a given route
func (r *router) manageRoute(route table.Route, action string) error {
switch action {
case "create":
@ -109,8 +113,8 @@ func (r *router) manageRoute(route table.Route, action string) error {
return nil
}
// manageServiceRoutes manages routes for a given service.
// It returns error of the routing table action fails.
// manageServiceRoutes applies action on all routes of given service.
// It returns error of the action fails with error.
func (r *router) manageServiceRoutes(service *registry.Service, action string) error {
// action is the routing table action
action = strings.ToLower(action)
@ -134,7 +138,7 @@ func (r *router) manageServiceRoutes(service *registry.Service, action string) e
return nil
}
// manageRegistryRoutes manages routes for each service found in the registry.
// manageRegistryRoutes applies action on all routes of each service found in the registry.
// It returns error if either the services failed to be listed or the routing table action fails.
func (r *router) manageRegistryRoutes(reg registry.Registry, action string) error {
services, err := reg.ListServices()
@ -228,8 +232,9 @@ func (r *router) watchTable(w table.Watcher) error {
return watchErr
}
// advertiseEvents advertises events to event subscribers
func (r *router) advertiseEvents(advType AdvertType, events []*table.Event) {
// publishAdvert publishes router advert to advert channel
// NOTE: this might cease to be a dedicated method in the future
func (r *router) publishAdvert(advType AdvertType, events []*table.Event) {
defer r.advertWg.Done()
a := &Advert{
@ -274,7 +279,7 @@ func (r *router) advertiseTable() error {
// advertise all routes as Update events to subscribers
if len(events) > 0 {
r.advertWg.Add(1)
go r.advertiseEvents(Update, events)
go r.publishAdvert(Update, events)
}
case <-r.exit:
return nil
@ -295,14 +300,29 @@ type routeAdvert struct {
suppressTime time.Time
}
// processEvents processes routing table events.
// advertiseEvents advertises routing table events
// It suppresses unhealthy flapping events and advertises healthy events upstream.
func (r *router) processEvents() error {
func (r *router) advertiseEvents() error {
// ticker to periodically scan event for advertising
ticker := time.NewTicker(AdvertiseEventsTick)
// advertMap is a map of advert events
advertMap := make(map[uint64]*routeAdvert)
// routing table watcher
tableWatcher, err := r.Watch()
if err != nil {
return fmt.Errorf("failed creating routing table watcher: %v", err)
}
r.wg.Add(1)
go func() {
defer r.wg.Done()
select {
case r.errChan <- r.watchTable(tableWatcher):
case <-r.exit:
}
}()
for {
select {
case <-ticker.C:
@ -344,7 +364,7 @@ func (r *router) processEvents() error {
// advertise all Update events to subscribers
if len(events) > 0 {
r.advertWg.Add(1)
go r.advertiseEvents(Update, events)
go r.publishAdvert(Update, events)
}
case e := <-r.eventChan:
// if event is nil, continue
@ -399,30 +419,18 @@ func (r *router) processEvents() error {
}
// watchErrors watches router errors and takes appropriate actions
func (r *router) watchErrors(errChan <-chan error) {
defer r.wg.Done()
var code StatusCode
func (r *router) watchErrors() {
var err error
select {
case <-r.exit:
code = Stopped
case err = <-errChan:
code = Error
case err = <-r.errChan:
}
r.Lock()
defer r.Unlock()
status := Status{
Code: code,
Error: err,
}
r.status = status
// stop the router if some error happened
if err != nil && code != Stopped {
// this will stop watchers which will close r.advertChan
if r.status.Code != Stopped {
// notify all goroutines to finish
close(r.exit)
// drain the advertise channel
for range r.advertChan {
@ -432,20 +440,88 @@ func (r *router) watchErrors(errChan <-chan error) {
}
}
if err != nil {
r.status = Status{Code: Error, Error: err}
}
}
// Advertise advertises the routes to the network.
// It returns error if any of the launched goroutines fail with error.
// Run runs the router.
// It returns error if the router is already running.
func (r *router) run() {
r.Lock()
defer r.Unlock()
switch r.status.Code {
case Stopped, Error:
// add all local service routes into the routing table
if err := r.manageRegistryRoutes(r.opts.Registry, "create"); err != nil {
r.status = Status{Code: Error, Error: fmt.Errorf("failed adding registry routes: %s", err)}
return
}
// add default gateway into routing table
if r.opts.Gateway != "" {
// note, the only non-default value is the gateway
route := table.Route{
Service: "*",
Address: "*",
Gateway: r.opts.Gateway,
Network: "*",
Metric: table.DefaultLocalMetric,
}
if err := r.Create(route); err != nil {
r.status = Status{Code: Error, Error: fmt.Errorf("failed adding default gateway route: %s", err)}
return
}
}
// create error and exit channels
r.errChan = make(chan error, 1)
r.exit = make(chan struct{})
// registry watcher
regWatcher, err := r.opts.Registry.Watch()
if err != nil {
r.status = Status{Code: Error, Error: fmt.Errorf("failed creating registry watcher: %v", err)}
return
}
r.wg.Add(1)
go func() {
defer r.wg.Done()
select {
case r.errChan <- r.watchRegistry(regWatcher):
case <-r.exit:
}
}()
// watch for errors and cleanup
r.wg.Add(1)
go func() {
defer r.wg.Done()
r.watchErrors()
}()
// mark router as Running and set its Error to nil
r.status = Status{Code: Running, Error: nil}
return
}
return
}
// Advertise stars advertising the routes to the network and returns the advertisements channel to consume from.
// If the router is already advertising it returns the channel to consume from.
// It returns error if either the router is not running or if the routing table fails to list the routes to advertise.
func (r *router) Advertise() (<-chan *Advert, error) {
r.Lock()
defer r.Unlock()
if r.status.Code != Running {
// add all local service routes into the routing table
if err := r.manageRegistryRoutes(r.opts.Registry, "create"); err != nil {
return nil, fmt.Errorf("failed adding routes: %s", err)
}
switch r.status.Code {
case Advertising:
return r.advertChan, nil
case Running:
// list routing table routes to announce
routes, err := r.List()
if err != nil {
@ -462,85 +538,42 @@ func (r *router) Advertise() (<-chan *Advert, error) {
events[i] = event
}
// add default gateway into routing table
if r.opts.Gateway != "" {
// note, the only non-default value is the gateway
route := table.Route{
Service: "*",
Address: "*",
Gateway: r.opts.Gateway,
Network: "*",
Metric: table.DefaultLocalMetric,
}
if err := r.Create(route); err != nil {
return nil, fmt.Errorf("failed adding default gateway route: %s", err)
}
}
// NOTE: we only need to recreate these if the router errored or was stopped
// TODO: These probably dont need to be struct members
if r.status.Code == Error || r.status.Code == Stopped {
r.exit = make(chan struct{})
r.eventChan = make(chan *table.Event)
// create advertise and event channels
r.advertChan = make(chan *Advert)
}
r.eventChan = make(chan *table.Event)
// routing table watcher
tableWatcher, err := r.Watch()
if err != nil {
return nil, fmt.Errorf("failed creating routing table watcher: %v", err)
}
// registry watcher
regWatcher, err := r.opts.Registry.Watch()
if err != nil {
return nil, fmt.Errorf("failed creating service registry watcher: %v", err)
}
// error channel collecting goroutine errors
errChan := make(chan error, 4)
// advertise your presence
r.advertWg.Add(1)
go r.publishAdvert(Announce, events)
r.wg.Add(1)
go func() {
defer r.wg.Done()
// watch local registry and register routes in routine table
errChan <- r.watchRegistry(regWatcher)
}()
r.wg.Add(1)
go func() {
defer r.wg.Done()
// watch local registry and register routes in routing table
errChan <- r.watchTable(tableWatcher)
}()
r.wg.Add(1)
go func() {
defer r.wg.Done()
// watch routing table events and process them
errChan <- r.processEvents()
select {
case r.errChan <- r.advertiseEvents():
case <-r.exit:
}
}()
r.advertWg.Add(1)
go func() {
defer r.advertWg.Done()
// advertise the whole routing table
errChan <- r.advertiseTable()
select {
case r.errChan <- r.advertiseTable():
case <-r.exit:
}
}()
// advertise your presence
r.advertWg.Add(1)
go r.advertiseEvents(Announce, events)
// watch for errors and cleanup
r.wg.Add(1)
go r.watchErrors(errChan)
// mark router as running and set its Error to nil
r.status = Status{Code: Running, Error: nil}
}
// mark router as Running and set its Error to nil
r.status = Status{Code: Advertising, Error: nil}
return r.advertChan, nil
case Stopped:
return nil, fmt.Errorf("not running")
}
return nil, fmt.Errorf("error: %s", r.status.Error)
}
// Process updates the routing table using the advertised values
@ -579,9 +612,9 @@ func (r *router) Status() Status {
// Stop stops the router
func (r *router) Stop() error {
r.RLock()
// only close the channel if the router is running
if r.status.Code == Running {
r.Lock()
// only close the channel if the router is running and/or advertising
if r.status.Code == Running || r.status.Code == Advertising {
// notify all goroutines to finish
close(r.exit)
// drain the advertise channel
@ -590,8 +623,11 @@ func (r *router) Stop() error {
// drain the event channel
for range r.eventChan {
}
// mark the router as Stopped and set its Error to nil
r.status = Status{Code: Stopped, Error: nil}
}
r.RUnlock()
r.Unlock()
// wait for all goroutines to finish
r.wg.Wait()

View File

@ -19,9 +19,9 @@ type Options struct {
Id string
// Address is router address
Address string
// Gateway is micro network gateway
// Gateway is network gateway
Gateway string
// Network is micro network
// Network is network address
Network string
// Registry is the local registry
Registry registry.Registry
@ -57,13 +57,6 @@ func Network(n string) Option {
}
}
// Table sets the routing table
func Table(t table.Table) Option {
return func(o *Options) {
o.Table = t
}
}
// Registry sets the local registry
func Registry(r registry.Registry) Option {
return func(o *Options) {
@ -71,6 +64,13 @@ func Registry(r registry.Registry) Option {
}
}
// Table sets the routing table
func Table(t table.Table) Option {
return func(o *Options) {
o.Table = t
}
}
// DefaultOptions returns router default options
func DefaultOptions() Options {
return Options{

View File

@ -7,20 +7,9 @@ import (
"github.com/micro/go-micro/network/router/table"
)
const (
// Status codes
// Running means the router is up and running
Running StatusCode = iota
// Stopped means the router has been stopped
Stopped
// Error means the router has encountered error
Error
// Advert types
// Announce is advertised when the router announces itself
Announce AdvertType = iota
// Update advertises route updates
Update
var (
// DefaultRouter is default network router
DefaultRouter = NewRouter()
)
// Router is an interface for a routing control plane
@ -46,9 +35,38 @@ type Router interface {
// Option used by the router
type Option func(*Options)
// StatusCode defines router status
type StatusCode int
const (
// Running means the router is up and running
Running StatusCode = iota
// Advertising means the router is advertising
Advertising
// Stopped means the router has been stopped
Stopped
// Error means the router has encountered error
Error
)
// Status is router status
type Status struct {
// Error is router error
Error error
// Code defines router status
Code StatusCode
}
// AdvertType is route advertisement type
type AdvertType int
const (
// Announce is advertised when the router announces itself
Announce AdvertType = iota
// Update advertises route updates
Update
)
// Advert contains a list of events advertised by the router to the network
type Advert struct {
// Id is the router Id
@ -63,22 +81,6 @@ type Advert struct {
Events []*table.Event
}
// StatusCode defines router status
type StatusCode int
// Status is router status
type Status struct {
// Error is router error
Error error
// Code defines router status
Code StatusCode
}
var (
// DefaultRouter is default network router
DefaultRouter = NewRouter()
)
// NewRouter creates new Router and returns it
func NewRouter(opts ...Option) Router {
return newRouter(opts...)