split router implementations
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
This commit is contained in:
parent
2c136b005e
commit
9c695ac343
@ -1,122 +0,0 @@
|
|||||||
package dns
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/unistack-org/micro/v3/router"
|
|
||||||
)
|
|
||||||
|
|
||||||
// NewRouter returns an initialized dns router
|
|
||||||
func NewRouter(opts ...router.Option) router.Router {
|
|
||||||
options := router.DefaultOptions()
|
|
||||||
for _, o := range opts {
|
|
||||||
o(&options)
|
|
||||||
}
|
|
||||||
if len(options.Network) == 0 {
|
|
||||||
options.Network = "micro"
|
|
||||||
}
|
|
||||||
return &dns{options, &table{options}}
|
|
||||||
}
|
|
||||||
|
|
||||||
type dns struct {
|
|
||||||
options router.Options
|
|
||||||
table *table
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *dns) Init(opts ...router.Option) error {
|
|
||||||
for _, o := range opts {
|
|
||||||
o(&d.options)
|
|
||||||
}
|
|
||||||
d.table.options = d.options
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *dns) Options() router.Options {
|
|
||||||
return d.options
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *dns) Table() router.Table {
|
|
||||||
return d.table
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *dns) Lookup(opts ...router.QueryOption) ([]router.Route, error) {
|
|
||||||
return d.table.Query(opts...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *dns) Watch(opts ...router.WatchOption) (router.Watcher, error) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *dns) Close() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *dns) String() string {
|
|
||||||
return "dns"
|
|
||||||
}
|
|
||||||
|
|
||||||
type table struct {
|
|
||||||
options router.Options
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *table) Create(router.Route) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *table) Delete(router.Route) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *table) Update(router.Route) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *table) List() ([]router.Route, error) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *table) Query(opts ...router.QueryOption) ([]router.Route, error) {
|
|
||||||
options := router.NewQuery(opts...)
|
|
||||||
|
|
||||||
// check to see if we have the port provided in the service, e.g. go-micro-srv-foo:8000
|
|
||||||
host, port, err := net.SplitHostPort(options.Service)
|
|
||||||
if err == nil {
|
|
||||||
// lookup the service using A records
|
|
||||||
ips, err := net.LookupHost(host)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
p, _ := strconv.Atoi(port)
|
|
||||||
|
|
||||||
// convert the ip addresses to routes
|
|
||||||
result := make([]router.Route, len(ips))
|
|
||||||
for i, ip := range ips {
|
|
||||||
result[i] = router.Route{
|
|
||||||
Service: options.Service,
|
|
||||||
Address: fmt.Sprintf("%s:%d", ip, uint16(p)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// we didn't get the port so we'll lookup the service using SRV records. If we can't lookup the
|
|
||||||
// service using the SRV record, we return the error.
|
|
||||||
_, nodes, err := net.LookupSRV(options.Service, "tcp", t.options.Network)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// convert the nodes (net services) to routes
|
|
||||||
result := make([]router.Route, len(nodes))
|
|
||||||
for i, n := range nodes {
|
|
||||||
result[i] = router.Route{
|
|
||||||
Service: options.Service,
|
|
||||||
Address: fmt.Sprintf("%s:%d", n.Target, n.Port),
|
|
||||||
Network: t.options.Network,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result, nil
|
|
||||||
}
|
|
@ -1,117 +0,0 @@
|
|||||||
// Package mdns is an mdns router
|
|
||||||
package mdns
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/unistack-org/micro/v3/router"
|
|
||||||
"github.com/unistack-org/micro/v3/util/mdns"
|
|
||||||
)
|
|
||||||
|
|
||||||
// NewRouter returns an initialized dns router
|
|
||||||
func NewRouter(opts ...router.Option) router.Router {
|
|
||||||
options := router.DefaultOptions()
|
|
||||||
for _, o := range opts {
|
|
||||||
o(&options)
|
|
||||||
}
|
|
||||||
if len(options.Network) == 0 {
|
|
||||||
options.Network = "micro"
|
|
||||||
}
|
|
||||||
return &mdnsRouter{options}
|
|
||||||
}
|
|
||||||
|
|
||||||
type mdnsRouter struct {
|
|
||||||
options router.Options
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *mdnsRouter) Init(opts ...router.Option) error {
|
|
||||||
for _, o := range opts {
|
|
||||||
o(&m.options)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *mdnsRouter) Options() router.Options {
|
|
||||||
return m.options
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *mdnsRouter) Table() router.Table {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *mdnsRouter) Lookup(opts ...router.QueryOption) ([]router.Route, error) {
|
|
||||||
options := router.NewQuery(opts...)
|
|
||||||
|
|
||||||
// check to see if we have the port provided in the service, e.g. go-micro-srv-foo:8000
|
|
||||||
service, port, err := net.SplitHostPort(options.Service)
|
|
||||||
if err != nil {
|
|
||||||
service = options.Service
|
|
||||||
}
|
|
||||||
|
|
||||||
// query for the host
|
|
||||||
entries := make(chan *mdns.ServiceEntry)
|
|
||||||
|
|
||||||
p := mdns.DefaultParams(service)
|
|
||||||
p.Timeout = time.Millisecond * 100
|
|
||||||
p.Entries = entries
|
|
||||||
|
|
||||||
// check if we're using our own network
|
|
||||||
if len(options.Network) > 0 {
|
|
||||||
p.Domain = options.Network
|
|
||||||
}
|
|
||||||
|
|
||||||
// do the query
|
|
||||||
if err := mdns.Query(p); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var routes []router.Route
|
|
||||||
|
|
||||||
// compose the routes based on the entries
|
|
||||||
for e := range entries {
|
|
||||||
addr := e.Host
|
|
||||||
// prefer ipv4 addrs
|
|
||||||
if len(e.AddrV4) > 0 {
|
|
||||||
addr = e.AddrV4.String()
|
|
||||||
// else use ipv6
|
|
||||||
} else if len(e.AddrV6) > 0 {
|
|
||||||
addr = "[" + e.AddrV6.String() + "]"
|
|
||||||
} else if len(addr) == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
pt := 443
|
|
||||||
|
|
||||||
if e.Port > 0 {
|
|
||||||
pt = e.Port
|
|
||||||
}
|
|
||||||
|
|
||||||
// set the port
|
|
||||||
if len(port) > 0 {
|
|
||||||
pt, _ = strconv.Atoi(port)
|
|
||||||
}
|
|
||||||
|
|
||||||
routes = append(routes, router.Route{
|
|
||||||
Service: service,
|
|
||||||
Address: fmt.Sprintf("%s:%d", addr, pt),
|
|
||||||
Network: p.Domain,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return routes, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *mdnsRouter) Watch(opts ...router.WatchOption) (router.Watcher, error) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *mdnsRouter) Close() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *mdnsRouter) String() string {
|
|
||||||
return "mdns"
|
|
||||||
}
|
|
@ -1,445 +0,0 @@
|
|||||||
package registry
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/unistack-org/micro/v3/logger"
|
|
||||||
"github.com/unistack-org/micro/v3/registry"
|
|
||||||
"github.com/unistack-org/micro/v3/router"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// RefreshInterval is the time at which we completely refresh the table
|
|
||||||
RefreshInterval = time.Second * 120
|
|
||||||
// PruneInterval is how often we prune the routing table
|
|
||||||
PruneInterval = time.Second * 10
|
|
||||||
)
|
|
||||||
|
|
||||||
// rtr implements router interface
|
|
||||||
type rtr struct {
|
|
||||||
sync.RWMutex
|
|
||||||
|
|
||||||
running bool
|
|
||||||
table *table
|
|
||||||
options router.Options
|
|
||||||
exit chan bool
|
|
||||||
initChan chan bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewRouter creates new router and returns it
|
|
||||||
func NewRouter(opts ...router.Option) (router.Router, error) {
|
|
||||||
// get default options
|
|
||||||
options := router.DefaultOptions()
|
|
||||||
|
|
||||||
// apply requested options
|
|
||||||
for _, o := range opts {
|
|
||||||
o(&options)
|
|
||||||
}
|
|
||||||
|
|
||||||
// construct the router
|
|
||||||
r := &rtr{
|
|
||||||
options: options,
|
|
||||||
initChan: make(chan bool),
|
|
||||||
}
|
|
||||||
|
|
||||||
if options.Registry == nil {
|
|
||||||
return nil, fmt.Errorf("registry not set")
|
|
||||||
}
|
|
||||||
|
|
||||||
// create the new table, passing the fetchRoute method in as a fallback if
|
|
||||||
// the table doesn't contain the result for a query.
|
|
||||||
r.table = newTable(r.lookup)
|
|
||||||
|
|
||||||
// start the router
|
|
||||||
r.start()
|
|
||||||
return r, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Init initializes router with given options
|
|
||||||
func (r *rtr) Init(opts ...router.Option) error {
|
|
||||||
r.Lock()
|
|
||||||
for _, o := range opts {
|
|
||||||
o(&r.options)
|
|
||||||
}
|
|
||||||
r.Unlock()
|
|
||||||
|
|
||||||
// push a message to the init chan so the watchers
|
|
||||||
// can reset in the case the registry was changed
|
|
||||||
go func() {
|
|
||||||
r.initChan <- true
|
|
||||||
}()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Options returns router options
|
|
||||||
func (r *rtr) Options() router.Options {
|
|
||||||
r.RLock()
|
|
||||||
defer r.RUnlock()
|
|
||||||
|
|
||||||
options := r.options
|
|
||||||
|
|
||||||
return options
|
|
||||||
}
|
|
||||||
|
|
||||||
// Table returns routing table
|
|
||||||
func (r *rtr) Table() router.Table {
|
|
||||||
r.Lock()
|
|
||||||
defer r.Unlock()
|
|
||||||
return r.table
|
|
||||||
}
|
|
||||||
|
|
||||||
func getDomain(srv *registry.Service) string {
|
|
||||||
// check the service metadata for domain
|
|
||||||
// TODO: domain as Domain field in registry?
|
|
||||||
if srv.Metadata != nil && len(srv.Metadata["domain"]) > 0 {
|
|
||||||
return srv.Metadata["domain"]
|
|
||||||
} else if len(srv.Nodes) > 0 && srv.Nodes[0].Metadata != nil {
|
|
||||||
return srv.Nodes[0].Metadata["domain"]
|
|
||||||
}
|
|
||||||
|
|
||||||
// otherwise return wildcard
|
|
||||||
// TODO: return GlobalDomain or PublicDomain
|
|
||||||
return registry.DefaultDomain
|
|
||||||
}
|
|
||||||
|
|
||||||
// manageRoute applies action on a given route
|
|
||||||
func (r *rtr) manageRoute(route router.Route, action string) error {
|
|
||||||
switch action {
|
|
||||||
case "create":
|
|
||||||
if err := r.table.Create(route); err != nil && err != router.ErrDuplicateRoute {
|
|
||||||
return fmt.Errorf("failed adding route for service %s: %s", route.Service, err)
|
|
||||||
}
|
|
||||||
case "delete":
|
|
||||||
if err := r.table.Delete(route); err != nil && err != router.ErrRouteNotFound {
|
|
||||||
return fmt.Errorf("failed deleting route for service %s: %s", route.Service, err)
|
|
||||||
}
|
|
||||||
case "update":
|
|
||||||
if err := r.table.Update(route); err != nil {
|
|
||||||
return fmt.Errorf("failed updating route for service %s: %s", route.Service, err)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("failed to manage route for service %s: unknown action %s", route.Service, action)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// createRoutes turns a service into a list routes basically converting nodes to routes
|
|
||||||
func (r *rtr) createRoutes(service *registry.Service, network string) []router.Route {
|
|
||||||
routes := make([]router.Route, 0, len(service.Nodes))
|
|
||||||
|
|
||||||
for _, node := range service.Nodes {
|
|
||||||
routes = append(routes, router.Route{
|
|
||||||
Service: service.Name,
|
|
||||||
Address: node.Address,
|
|
||||||
Gateway: "",
|
|
||||||
Network: network,
|
|
||||||
Router: r.options.Id,
|
|
||||||
Link: router.DefaultLink,
|
|
||||||
Metric: router.DefaultLocalMetric,
|
|
||||||
Metadata: node.Metadata,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return routes
|
|
||||||
}
|
|
||||||
|
|
||||||
// manageServiceRoutes applies action to all routes of the service.
|
|
||||||
// It returns error of the action fails with error.
|
|
||||||
func (r *rtr) manageRoutes(service *registry.Service, action, network string) error {
|
|
||||||
// action is the routing table action
|
|
||||||
action = strings.ToLower(action)
|
|
||||||
|
|
||||||
// create a set of routes from the service
|
|
||||||
routes := r.createRoutes(service, network)
|
|
||||||
|
|
||||||
// if its a delete action and there's no nodes
|
|
||||||
// it means we need to wipe out all the routes
|
|
||||||
// for that service
|
|
||||||
if action == "delete" && len(routes) == 0 {
|
|
||||||
// delete the service entirely
|
|
||||||
r.table.deleteService(service.Name, network)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// create the routes in the table
|
|
||||||
for _, route := range routes {
|
|
||||||
logger.Tracef("Creating route %v domain: %v", route, network)
|
|
||||||
if err := r.manageRoute(route, action); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// manageRegistryRoutes applies action to 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 *rtr) loadRoutes(reg registry.Registry) error {
|
|
||||||
services, err := reg.ListServices(registry.ListDomain(registry.WildcardDomain))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed listing services: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// add each service node as a separate route
|
|
||||||
for _, service := range services {
|
|
||||||
// get the services domain from metadata. Fallback to wildcard.
|
|
||||||
domain := getDomain(service)
|
|
||||||
|
|
||||||
// create the routes
|
|
||||||
routes := r.createRoutes(service, domain)
|
|
||||||
|
|
||||||
// if the routes exist save them
|
|
||||||
if len(routes) > 0 {
|
|
||||||
logger.Tracef("Creating routes for service %v domain: %v", service, domain)
|
|
||||||
for _, rt := range routes {
|
|
||||||
err := r.table.Create(rt)
|
|
||||||
|
|
||||||
// update the route to prevent it from expiring
|
|
||||||
if err == router.ErrDuplicateRoute {
|
|
||||||
err = r.table.Update(rt)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
logger.Errorf("Error creating route for service %v in domain %v: %v", service, domain, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// otherwise get all the service info
|
|
||||||
|
|
||||||
// get the service to retrieve all its info
|
|
||||||
srvs, err := reg.GetService(service.Name, registry.GetDomain(domain))
|
|
||||||
if err != nil {
|
|
||||||
logger.Tracef("Failed to get service %s domain: %s", service.Name, domain)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// manage the routes for all returned services
|
|
||||||
for _, srv := range srvs {
|
|
||||||
routes := r.createRoutes(srv, domain)
|
|
||||||
|
|
||||||
if len(routes) > 0 {
|
|
||||||
logger.Tracef("Creating routes for service %v domain: %v", srv, domain)
|
|
||||||
for _, rt := range routes {
|
|
||||||
err := r.table.Create(rt)
|
|
||||||
|
|
||||||
// update the route to prevent it from expiring
|
|
||||||
if err == router.ErrDuplicateRoute {
|
|
||||||
err = r.table.Update(rt)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
logger.Errorf("Error creating route for service %v in domain %v: %v", service, domain, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// lookup retrieves all the routes for a given service and creates them in the routing table
|
|
||||||
func (r *rtr) lookup(service string) ([]router.Route, error) {
|
|
||||||
logger.Tracef("Fetching route for %s domain: %v", service, registry.WildcardDomain)
|
|
||||||
|
|
||||||
services, err := r.options.Registry.GetService(service, registry.GetDomain(registry.WildcardDomain))
|
|
||||||
if err == registry.ErrNotFound {
|
|
||||||
logger.Tracef("Failed to find route for %s", service)
|
|
||||||
return nil, router.ErrRouteNotFound
|
|
||||||
} else if err != nil {
|
|
||||||
logger.Tracef("Failed to find route for %s: %v", service, err)
|
|
||||||
return nil, fmt.Errorf("failed getting services: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var routes []router.Route
|
|
||||||
|
|
||||||
for _, srv := range services {
|
|
||||||
domain := getDomain(srv)
|
|
||||||
// TODO: should we continue to send the event indicating we created a route?
|
|
||||||
// lookup is only called in the query path so probably not
|
|
||||||
routes = append(routes, r.createRoutes(srv, domain)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
return routes, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// watchRegistry watches registry and updates routing table based on the received events.
|
|
||||||
// It returns error if either the registry watcher fails with error or if the routing table update fails.
|
|
||||||
func (r *rtr) watchRegistry(w registry.Watcher) error {
|
|
||||||
exit := make(chan bool)
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
close(exit)
|
|
||||||
}()
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
defer w.Stop()
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-exit:
|
|
||||||
return
|
|
||||||
case <-r.initChan:
|
|
||||||
return
|
|
||||||
case <-r.exit:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
for {
|
|
||||||
// get the next service
|
|
||||||
res, err := w.Next()
|
|
||||||
if err != nil {
|
|
||||||
if err != registry.ErrWatcherStopped {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
// don't process nil entries
|
|
||||||
if res.Service == nil {
|
|
||||||
logger.Trace("Received a nil service")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Tracef("Router dealing with next route %s %+v\n", res.Action, res.Service)
|
|
||||||
|
|
||||||
// get the services domain from metadata. Fallback to wildcard.
|
|
||||||
domain := getDomain(res.Service)
|
|
||||||
|
|
||||||
// create/update or delete the route
|
|
||||||
if err := r.manageRoutes(res.Service, res.Action, domain); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// start the router. Should be called under lock.
|
|
||||||
func (r *rtr) start() error {
|
|
||||||
if r.running {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.options.Precache {
|
|
||||||
// add all local service routes into the routing table
|
|
||||||
if err := r.loadRoutes(r.options.Registry); err != nil {
|
|
||||||
return fmt.Errorf("failed loading registry routes: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// add default gateway into routing table
|
|
||||||
if r.options.Gateway != "" {
|
|
||||||
// note, the only non-default value is the gateway
|
|
||||||
route := router.Route{
|
|
||||||
Service: "*",
|
|
||||||
Address: "*",
|
|
||||||
Gateway: r.options.Gateway,
|
|
||||||
Network: "*",
|
|
||||||
Router: r.options.Id,
|
|
||||||
Link: router.DefaultLink,
|
|
||||||
Metric: router.DefaultLocalMetric,
|
|
||||||
}
|
|
||||||
if err := r.table.Create(route); err != nil {
|
|
||||||
return fmt.Errorf("failed adding default gateway route: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// create error and exit channels
|
|
||||||
r.exit = make(chan bool)
|
|
||||||
|
|
||||||
// periodically refresh all the routes
|
|
||||||
go func() {
|
|
||||||
t1 := time.NewTicker(RefreshInterval)
|
|
||||||
defer t1.Stop()
|
|
||||||
|
|
||||||
t2 := time.NewTicker(PruneInterval)
|
|
||||||
defer t2.Stop()
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-r.exit:
|
|
||||||
return
|
|
||||||
case <-t2.C:
|
|
||||||
r.table.pruneRoutes(RefreshInterval)
|
|
||||||
case <-t1.C:
|
|
||||||
if err := r.loadRoutes(r.options.Registry); err != nil {
|
|
||||||
logger.Debugf("failed refreshing registry routes: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-r.exit:
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
logger.Tracef("Router starting registry watch")
|
|
||||||
w, err := r.options.Registry.Watch(registry.WatchDomain(registry.WildcardDomain))
|
|
||||||
if err != nil {
|
|
||||||
if logger.V(logger.DebugLevel, logger.DefaultLogger) {
|
|
||||||
logger.Debugf("failed creating registry watcher: %v", err)
|
|
||||||
}
|
|
||||||
time.Sleep(time.Second)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// watchRegistry calls stop when it's done
|
|
||||||
if err := r.watchRegistry(w); err != nil {
|
|
||||||
if logger.V(logger.DebugLevel, logger.DefaultLogger) {
|
|
||||||
logger.Debugf("Error watching the registry: %v", err)
|
|
||||||
}
|
|
||||||
time.Sleep(time.Second)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
r.running = true
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lookup routes in the routing table
|
|
||||||
func (r *rtr) Lookup(q ...router.QueryOption) ([]router.Route, error) {
|
|
||||||
return r.Table().Query(q...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Watch routes
|
|
||||||
func (r *rtr) Watch(opts ...router.WatchOption) (router.Watcher, error) {
|
|
||||||
return r.table.Watch(opts...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close the router
|
|
||||||
func (r *rtr) Close() error {
|
|
||||||
r.Lock()
|
|
||||||
defer r.Unlock()
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-r.exit:
|
|
||||||
return nil
|
|
||||||
default:
|
|
||||||
if !r.running {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
close(r.exit)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
r.running = false
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// String prints debugging information about router
|
|
||||||
func (r *rtr) String() string {
|
|
||||||
return "registry"
|
|
||||||
}
|
|
@ -1,27 +0,0 @@
|
|||||||
// +build ignore
|
|
||||||
|
|
||||||
package registry
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/unistack-org/micro/v3/registry/memory"
|
|
||||||
"github.com/unistack-org/micro/v3/router"
|
|
||||||
)
|
|
||||||
|
|
||||||
func routerTestSetup() router.Router {
|
|
||||||
r := memory.NewRegistry()
|
|
||||||
return NewRouter(router.Registry(r))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRouterClose(t *testing.T) {
|
|
||||||
r := routerTestSetup()
|
|
||||||
|
|
||||||
if err := r.Close(); err != nil {
|
|
||||||
t.Errorf("failed to stop router: %v", err)
|
|
||||||
}
|
|
||||||
if len(os.Getenv("INTEGRATION_TESTS")) == 0 {
|
|
||||||
t.Logf("TestRouterStartStop STOPPED")
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,385 +0,0 @@
|
|||||||
package registry
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/google/uuid"
|
|
||||||
"github.com/unistack-org/micro/v3/logger"
|
|
||||||
"github.com/unistack-org/micro/v3/router"
|
|
||||||
)
|
|
||||||
|
|
||||||
// table is an in-memory routing table
|
|
||||||
type table struct {
|
|
||||||
sync.RWMutex
|
|
||||||
// lookup for a service
|
|
||||||
lookup func(string) ([]router.Route, error)
|
|
||||||
// routes stores service routes
|
|
||||||
routes map[string]map[uint64]*route
|
|
||||||
// watchers stores table watchers
|
|
||||||
watchers map[string]*tableWatcher
|
|
||||||
}
|
|
||||||
|
|
||||||
type route struct {
|
|
||||||
route router.Route
|
|
||||||
updated time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
// newtable creates a new routing table and returns it
|
|
||||||
func newTable(lookup func(string) ([]router.Route, error), opts ...router.Option) *table {
|
|
||||||
return &table{
|
|
||||||
lookup: lookup,
|
|
||||||
routes: make(map[string]map[uint64]*route),
|
|
||||||
watchers: make(map[string]*tableWatcher),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// pruneRoutes will prune routes older than the time specified
|
|
||||||
func (t *table) pruneRoutes(olderThan time.Duration) {
|
|
||||||
var routes []router.Route
|
|
||||||
|
|
||||||
t.Lock()
|
|
||||||
|
|
||||||
// search for all the routes
|
|
||||||
for _, routeList := range t.routes {
|
|
||||||
for _, r := range routeList {
|
|
||||||
// if any route is older than
|
|
||||||
if time.Since(r.updated).Seconds() > olderThan.Seconds() {
|
|
||||||
routes = append(routes, r.route)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Unlock()
|
|
||||||
|
|
||||||
// delete the routes we've found
|
|
||||||
for _, route := range routes {
|
|
||||||
t.Delete(route)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// deleteService removes the entire service
|
|
||||||
func (t *table) deleteService(service, network string) {
|
|
||||||
t.Lock()
|
|
||||||
defer t.Unlock()
|
|
||||||
|
|
||||||
routes, ok := t.routes[service]
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// delete the routes for the service
|
|
||||||
for hash, rt := range routes {
|
|
||||||
// TODO: check if this causes a problem
|
|
||||||
// with * in the network if that is a thing
|
|
||||||
// or blank strings
|
|
||||||
if rt.route.Network != network {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
delete(routes, hash)
|
|
||||||
}
|
|
||||||
|
|
||||||
// delete the map for the service if its empty
|
|
||||||
if len(routes) == 0 {
|
|
||||||
delete(t.routes, service)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// save the routes
|
|
||||||
t.routes[service] = routes
|
|
||||||
}
|
|
||||||
|
|
||||||
// sendEvent sends events to all subscribed watchers
|
|
||||||
func (t *table) sendEvent(e *router.Event) {
|
|
||||||
t.RLock()
|
|
||||||
defer t.RUnlock()
|
|
||||||
|
|
||||||
if len(e.Id) == 0 {
|
|
||||||
e.Id = uuid.New().String()
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, w := range t.watchers {
|
|
||||||
select {
|
|
||||||
case w.resChan <- e:
|
|
||||||
case <-w.done:
|
|
||||||
// don't block forever
|
|
||||||
case <-time.After(time.Second):
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create creates new route in the routing table
|
|
||||||
func (t *table) Create(r router.Route) error {
|
|
||||||
service := r.Service
|
|
||||||
sum := r.Hash()
|
|
||||||
|
|
||||||
t.Lock()
|
|
||||||
defer t.Unlock()
|
|
||||||
|
|
||||||
// check if there are any routes in the table for the route destination
|
|
||||||
if _, ok := t.routes[service]; !ok {
|
|
||||||
t.routes[service] = make(map[uint64]*route)
|
|
||||||
}
|
|
||||||
|
|
||||||
// add new route to the table for the route destination
|
|
||||||
if _, ok := t.routes[service][sum]; ok {
|
|
||||||
return router.ErrDuplicateRoute
|
|
||||||
}
|
|
||||||
|
|
||||||
// create the route
|
|
||||||
t.routes[service][sum] = &route{r, time.Now()}
|
|
||||||
|
|
||||||
if logger.V(logger.DebugLevel, logger.DefaultLogger) {
|
|
||||||
logger.Debugf("Router emitting %s for route: %s", router.Create, r.Address)
|
|
||||||
}
|
|
||||||
|
|
||||||
// send a route created event
|
|
||||||
go t.sendEvent(&router.Event{Type: router.Create, Timestamp: time.Now(), Route: r})
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete deletes the route from the routing table
|
|
||||||
func (t *table) Delete(r router.Route) error {
|
|
||||||
service := r.Service
|
|
||||||
sum := r.Hash()
|
|
||||||
|
|
||||||
t.Lock()
|
|
||||||
defer t.Unlock()
|
|
||||||
|
|
||||||
if _, ok := t.routes[service]; !ok {
|
|
||||||
return router.ErrRouteNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, ok := t.routes[service][sum]; !ok {
|
|
||||||
return router.ErrRouteNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
// delete the route from the service
|
|
||||||
delete(t.routes[service], sum)
|
|
||||||
|
|
||||||
// delete the whole map if there are no routes left
|
|
||||||
if len(t.routes[service]) == 0 {
|
|
||||||
delete(t.routes, service)
|
|
||||||
}
|
|
||||||
|
|
||||||
if logger.V(logger.DebugLevel, logger.DefaultLogger) {
|
|
||||||
logger.Debugf("Router emitting %s for route: %s", router.Delete, r.Address)
|
|
||||||
}
|
|
||||||
go t.sendEvent(&router.Event{Type: router.Delete, Timestamp: time.Now(), Route: r})
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update updates routing table with the new route
|
|
||||||
func (t *table) Update(r router.Route) error {
|
|
||||||
service := r.Service
|
|
||||||
sum := r.Hash()
|
|
||||||
|
|
||||||
t.Lock()
|
|
||||||
defer t.Unlock()
|
|
||||||
|
|
||||||
// check if the route destination has any routes in the table
|
|
||||||
if _, ok := t.routes[service]; !ok {
|
|
||||||
t.routes[service] = make(map[uint64]*route)
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, ok := t.routes[service][sum]; !ok {
|
|
||||||
// update the route
|
|
||||||
t.routes[service][sum] = &route{r, time.Now()}
|
|
||||||
|
|
||||||
if logger.V(logger.DebugLevel, logger.DefaultLogger) {
|
|
||||||
logger.Debugf("Router emitting %s for route: %s", router.Update, r.Address)
|
|
||||||
}
|
|
||||||
go t.sendEvent(&router.Event{Type: router.Update, Timestamp: time.Now(), Route: r})
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// just update the route, but dont emit Update event
|
|
||||||
t.routes[service][sum] = &route{r, time.Now()}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// List returns a list of all routes in the table
|
|
||||||
func (t *table) List() ([]router.Route, error) {
|
|
||||||
t.RLock()
|
|
||||||
defer t.RUnlock()
|
|
||||||
|
|
||||||
var routes []router.Route
|
|
||||||
for _, rmap := range t.routes {
|
|
||||||
for _, route := range rmap {
|
|
||||||
routes = append(routes, route.route)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return routes, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// isMatch checks if the route matches given query options
|
|
||||||
func isMatch(route router.Route, address, gateway, network, rtr, link string) bool {
|
|
||||||
// matches the values provided
|
|
||||||
match := func(a, b string) bool {
|
|
||||||
if a == "*" || b == "*" || a == b {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// a simple struct to hold our values
|
|
||||||
type compare struct {
|
|
||||||
a string
|
|
||||||
b string
|
|
||||||
}
|
|
||||||
|
|
||||||
// compare the following values
|
|
||||||
values := []compare{
|
|
||||||
{gateway, route.Gateway},
|
|
||||||
{network, route.Network},
|
|
||||||
{rtr, route.Router},
|
|
||||||
{address, route.Address},
|
|
||||||
{link, route.Link},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, v := range values {
|
|
||||||
// attempt to match each value
|
|
||||||
if !match(v.a, v.b) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// filterRoutes finds all the routes for given network and router and returns them
|
|
||||||
func filterRoutes(routes map[uint64]*route, opts router.QueryOptions) []router.Route {
|
|
||||||
address := opts.Address
|
|
||||||
gateway := opts.Gateway
|
|
||||||
network := opts.Network
|
|
||||||
rtr := opts.Router
|
|
||||||
link := opts.Link
|
|
||||||
|
|
||||||
// routeMap stores the routes we're going to advertise
|
|
||||||
routeMap := make(map[string][]router.Route)
|
|
||||||
|
|
||||||
var routeCnt int
|
|
||||||
|
|
||||||
for _, rt := range routes {
|
|
||||||
// get the actual route
|
|
||||||
route := rt.route
|
|
||||||
|
|
||||||
if isMatch(route, address, gateway, network, rtr, link) {
|
|
||||||
// add matchihg route to the routeMap
|
|
||||||
routeKey := route.Service + "@" + route.Network
|
|
||||||
routeMap[routeKey] = append(routeMap[routeKey], route)
|
|
||||||
routeCnt++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
results := make([]router.Route, 0, routeCnt)
|
|
||||||
for _, route := range routeMap {
|
|
||||||
results = append(results, route...)
|
|
||||||
}
|
|
||||||
|
|
||||||
return results
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lookup queries routing table and returns all routes that match the lookup query
|
|
||||||
func (t *table) Query(q ...router.QueryOption) ([]router.Route, error) {
|
|
||||||
// create new query options
|
|
||||||
opts := router.NewQuery(q...)
|
|
||||||
|
|
||||||
// create a cwslicelist of query results
|
|
||||||
results := make([]router.Route, 0, len(t.routes))
|
|
||||||
|
|
||||||
// readAndFilter routes for this service under read lock.
|
|
||||||
readAndFilter := func(q router.QueryOptions) ([]router.Route, bool) {
|
|
||||||
t.RLock()
|
|
||||||
defer t.RUnlock()
|
|
||||||
|
|
||||||
routes, ok := t.routes[q.Service]
|
|
||||||
if !ok || len(routes) == 0 {
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
|
|
||||||
return filterRoutes(routes, q), true
|
|
||||||
}
|
|
||||||
|
|
||||||
if opts.Service != "*" {
|
|
||||||
// try and load services from the cache
|
|
||||||
if routes, ok := readAndFilter(opts); ok {
|
|
||||||
return routes, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// lookup the route and try again
|
|
||||||
// TODO: move this logic out of the hot path
|
|
||||||
// being hammered on queries will require multiple lookups
|
|
||||||
routes, err := t.lookup(opts.Service)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// cache the routes
|
|
||||||
for _, rt := range routes {
|
|
||||||
t.Create(rt)
|
|
||||||
}
|
|
||||||
|
|
||||||
// try again
|
|
||||||
if routes, ok := readAndFilter(opts); ok {
|
|
||||||
return routes, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, router.ErrRouteNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
// search through all destinations
|
|
||||||
t.RLock()
|
|
||||||
|
|
||||||
for _, routes := range t.routes {
|
|
||||||
// filter the routes
|
|
||||||
found := filterRoutes(routes, opts)
|
|
||||||
// ensure we don't append zero length routes
|
|
||||||
if len(found) == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
results = append(results, found...)
|
|
||||||
}
|
|
||||||
|
|
||||||
t.RUnlock()
|
|
||||||
|
|
||||||
return results, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Watch returns routing table entry watcher
|
|
||||||
func (t *table) Watch(opts ...router.WatchOption) (router.Watcher, error) {
|
|
||||||
// by default watch everything
|
|
||||||
wopts := router.WatchOptions{
|
|
||||||
Service: "*",
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, o := range opts {
|
|
||||||
o(&wopts)
|
|
||||||
}
|
|
||||||
|
|
||||||
w := &tableWatcher{
|
|
||||||
id: uuid.New().String(),
|
|
||||||
opts: wopts,
|
|
||||||
resChan: make(chan *router.Event, 10),
|
|
||||||
done: make(chan struct{}),
|
|
||||||
}
|
|
||||||
|
|
||||||
// when the watcher is stopped delete it
|
|
||||||
go func() {
|
|
||||||
<-w.done
|
|
||||||
t.Lock()
|
|
||||||
delete(t.watchers, w.id)
|
|
||||||
t.Unlock()
|
|
||||||
}()
|
|
||||||
|
|
||||||
// save the watcher
|
|
||||||
t.Lock()
|
|
||||||
t.watchers[w.id] = w
|
|
||||||
t.Unlock()
|
|
||||||
|
|
||||||
return w, nil
|
|
||||||
}
|
|
@ -1,355 +0,0 @@
|
|||||||
// +build ignore
|
|
||||||
|
|
||||||
package registry
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/unistack-org/micro/v3/router"
|
|
||||||
)
|
|
||||||
|
|
||||||
func testSetup(t *testing.T) (*table, router.Route) {
|
|
||||||
r, err := NewRouter()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
routr := r.(*rtr)
|
|
||||||
|
|
||||||
table := newTable(routr.lookup)
|
|
||||||
|
|
||||||
route := router.Route{
|
|
||||||
Service: "dest.svc",
|
|
||||||
Address: "dest.addr",
|
|
||||||
Gateway: "dest.gw",
|
|
||||||
Network: "dest.network",
|
|
||||||
Router: "src.router",
|
|
||||||
Link: "det.link",
|
|
||||||
Metric: 10,
|
|
||||||
}
|
|
||||||
|
|
||||||
return table, route
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCreate(t *testing.T) {
|
|
||||||
table, route := testSetup(t)
|
|
||||||
|
|
||||||
if err := table.Create(route); err != nil {
|
|
||||||
t.Fatalf("error adding route: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// adds new route for the original destination
|
|
||||||
route.Gateway = "dest.gw2"
|
|
||||||
|
|
||||||
if err := table.Create(route); err != nil {
|
|
||||||
t.Fatalf("error adding route: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// adding the same route under Insert policy must error
|
|
||||||
if err := table.Create(route); err != router.ErrDuplicateRoute {
|
|
||||||
t.Fatalf("error adding route. Expected error: %s, found: %s", router.ErrDuplicateRoute, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDelete(t *testing.T) {
|
|
||||||
table, route := testSetup(t)
|
|
||||||
|
|
||||||
if err := table.Create(route); err != nil {
|
|
||||||
t.Fatalf("error adding route: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// should fail to delete non-existent route
|
|
||||||
prevSvc := route.Service
|
|
||||||
route.Service = "randDest"
|
|
||||||
|
|
||||||
if err := table.Delete(route); err != router.ErrRouteNotFound {
|
|
||||||
t.Fatalf("error deleting route. Expected: %s, found: %s", router.ErrRouteNotFound, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// we should be able to delete the existing route
|
|
||||||
route.Service = prevSvc
|
|
||||||
|
|
||||||
if err := table.Delete(route); err != nil {
|
|
||||||
t.Fatalf("error deleting route: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUpdate(t *testing.T) {
|
|
||||||
table, route := testSetup(t)
|
|
||||||
|
|
||||||
if err := table.Create(route); err != nil {
|
|
||||||
t.Fatalf("error adding route: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// change the metric of the original route
|
|
||||||
route.Metric = 200
|
|
||||||
|
|
||||||
if err := table.Update(route); err != nil {
|
|
||||||
t.Fatalf("error updating route: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// this should add a new route
|
|
||||||
route.Service = "rand.dest"
|
|
||||||
|
|
||||||
if err := table.Update(route); err != nil {
|
|
||||||
t.Fatalf("error updating route: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestList(t *testing.T) {
|
|
||||||
table, route := testSetup(t)
|
|
||||||
|
|
||||||
svc := []string{"one.svc", "two.svc", "three.svc"}
|
|
||||||
|
|
||||||
for i := 0; i < len(svc); i++ {
|
|
||||||
route.Service = svc[i]
|
|
||||||
if err := table.Create(route); err != nil {
|
|
||||||
t.Fatalf("error adding route: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
routes, err := table.List()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error listing routes: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(routes) != len(svc) {
|
|
||||||
t.Fatalf("incorrect number of routes listed. Expected: %d, found: %d", len(svc), len(routes))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestQuery(t *testing.T) {
|
|
||||||
table, route := testSetup(t)
|
|
||||||
|
|
||||||
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]
|
|
||||||
route.Network = net[i]
|
|
||||||
route.Gateway = gw[i]
|
|
||||||
route.Router = rtr[i]
|
|
||||||
route.Link = router.DefaultLink
|
|
||||||
|
|
||||||
if err := table.Create(route); err != nil {
|
|
||||||
t.Fatalf("error adding route: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// return all routes
|
|
||||||
routes, err := table.Query()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error looking up routes: %s", err)
|
|
||||||
} else if len(routes) == 0 {
|
|
||||||
t.Fatalf("error looking up routes: not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
// query routes particular network
|
|
||||||
network := "net1"
|
|
||||||
|
|
||||||
routes, err = table.Query(router.QueryNetwork(network))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error looking up routes: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(routes) != 2 {
|
|
||||||
t.Fatalf("incorrect number of routes returned. Expected: %d, found: %d", 2, len(routes))
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, route := range routes {
|
|
||||||
if route.Network != network {
|
|
||||||
t.Fatalf("incorrect route returned. Expected network: %s, found: %s", network, route.Network)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// query routes for particular gateway
|
|
||||||
gateway := "gw1"
|
|
||||||
|
|
||||||
routes, err = table.Query(router.QueryGateway(gateway))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error looking up routes: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(routes) != 1 {
|
|
||||||
t.Fatalf("incorrect number of routes returned. Expected: %d, found: %d", 1, len(routes))
|
|
||||||
}
|
|
||||||
|
|
||||||
if routes[0].Gateway != gateway {
|
|
||||||
t.Fatalf("incorrect route returned. Expected gateway: %s, found: %s", gateway, routes[0].Gateway)
|
|
||||||
}
|
|
||||||
|
|
||||||
// query routes for particular router
|
|
||||||
rt := "rtr1"
|
|
||||||
|
|
||||||
routes, err = table.Query(router.QueryRouter(rt))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error looking up routes: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(routes) != 1 {
|
|
||||||
t.Fatalf("incorrect number of routes returned. Expected: %d, found: %d", 1, len(routes))
|
|
||||||
}
|
|
||||||
|
|
||||||
if routes[0].Router != rt {
|
|
||||||
t.Fatalf("incorrect route returned. Expected router: %s, found: %s", rt, routes[0].Router)
|
|
||||||
}
|
|
||||||
|
|
||||||
// query particular gateway and network
|
|
||||||
query := []router.QueryOption{
|
|
||||||
router.QueryGateway(gateway),
|
|
||||||
router.QueryNetwork(network),
|
|
||||||
router.QueryRouter(rt),
|
|
||||||
}
|
|
||||||
|
|
||||||
routes, err = table.Query(query...)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error looking up routes: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(routes) != 1 {
|
|
||||||
t.Fatalf("incorrect number of routes returned. Expected: %d, found: %d", 1, len(routes))
|
|
||||||
}
|
|
||||||
|
|
||||||
if routes[0].Gateway != gateway {
|
|
||||||
t.Fatalf("incorrect route returned. Expected gateway: %s, found: %s", gateway, routes[0].Gateway)
|
|
||||||
}
|
|
||||||
|
|
||||||
if routes[0].Network != network {
|
|
||||||
t.Fatalf("incorrect network returned. Expected network: %s, found: %s", network, routes[0].Network)
|
|
||||||
}
|
|
||||||
|
|
||||||
if routes[0].Router != rt {
|
|
||||||
t.Fatalf("incorrect route returned. Expected router: %s, found: %s", rt, routes[0].Router)
|
|
||||||
}
|
|
||||||
|
|
||||||
// non-existen route query
|
|
||||||
routes, err = table.Query(router.QueryService("foobar"))
|
|
||||||
if err != router.ErrRouteNotFound {
|
|
||||||
t.Fatalf("error looking up routes. Expected: %s, found: %s", router.ErrRouteNotFound, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(routes) != 0 {
|
|
||||||
t.Fatalf("incorrect number of routes returned. Expected: %d, found: %d", 0, len(routes))
|
|
||||||
}
|
|
||||||
|
|
||||||
// query NO routes
|
|
||||||
query = []router.QueryOption{
|
|
||||||
router.QueryGateway(gateway),
|
|
||||||
router.QueryNetwork(network),
|
|
||||||
router.QueryLink("network"),
|
|
||||||
}
|
|
||||||
|
|
||||||
routes, err = table.Query(query...)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error looking up routes: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(routes) > 0 {
|
|
||||||
t.Fatalf("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 = "foobar"
|
|
||||||
route.Address = fmt.Sprintf("local.route.address-%d", i)
|
|
||||||
if err := table.Create(route); err != nil {
|
|
||||||
t.Fatalf("error adding route: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// query local routes
|
|
||||||
query = []router.QueryOption{
|
|
||||||
router.QueryGateway("*"),
|
|
||||||
router.QueryNetwork("*"),
|
|
||||||
router.QueryLink("foobar"),
|
|
||||||
}
|
|
||||||
|
|
||||||
routes, err = table.Query(query...)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error looking up routes: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(routes) != 2 {
|
|
||||||
t.Fatalf("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)
|
|
||||||
route.Link = router.DefaultLink
|
|
||||||
if err := table.Create(route); err != nil {
|
|
||||||
t.Fatalf("error adding route: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
query = []router.QueryOption{
|
|
||||||
router.QueryService("svcX"),
|
|
||||||
}
|
|
||||||
|
|
||||||
routes, err = table.Query(query...)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error looking up routes: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(routes) != 2 {
|
|
||||||
t.Fatalf("incorrect number of routes returned. Expected: %d, found: %d", 1, len(routes))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFallback(t *testing.T) {
|
|
||||||
|
|
||||||
r := &rtr{
|
|
||||||
options: router.DefaultOptions(),
|
|
||||||
}
|
|
||||||
route := router.Route{
|
|
||||||
Service: "go.micro.service.foo",
|
|
||||||
Router: r.options.Id,
|
|
||||||
Link: router.DefaultLink,
|
|
||||||
Metric: router.DefaultLocalMetric,
|
|
||||||
}
|
|
||||||
r.table = newTable(func(s string) ([]router.Route, error) {
|
|
||||||
return []router.Route{route}, nil
|
|
||||||
})
|
|
||||||
r.start()
|
|
||||||
|
|
||||||
rts, err := r.Lookup(router.QueryService("go.micro.service.foo"))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error looking up service %s", err)
|
|
||||||
}
|
|
||||||
if len(rts) != 1 {
|
|
||||||
t.Fatalf("incorrect number of routes returned %d", len(rts))
|
|
||||||
}
|
|
||||||
|
|
||||||
// deleting from the table but the next query should invoke the fallback that we passed during new table creation
|
|
||||||
if err := r.table.Delete(route); err != nil {
|
|
||||||
t.Fatalf("error deleting route %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
rts, err = r.Lookup(router.QueryService("go.micro.service.foo"))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error looking up service %s", err)
|
|
||||||
}
|
|
||||||
if len(rts) != 1 {
|
|
||||||
t.Fatalf("incorrect number of routes returned %d", len(rts))
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFallbackError(t *testing.T) {
|
|
||||||
r := &rtr{
|
|
||||||
options: router.DefaultOptions(),
|
|
||||||
}
|
|
||||||
r.table = newTable(func(s string) ([]router.Route, error) {
|
|
||||||
return nil, fmt.Errorf("ERROR")
|
|
||||||
})
|
|
||||||
r.start()
|
|
||||||
_, err := r.Lookup(router.QueryService("go.micro.service.foo"))
|
|
||||||
if err == nil {
|
|
||||||
t.Fatalf("expected error looking up service but none returned")
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,52 +0,0 @@
|
|||||||
package registry
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/unistack-org/micro/v3/router"
|
|
||||||
)
|
|
||||||
|
|
||||||
// tableWatcher implements routing table Watcher
|
|
||||||
type tableWatcher struct {
|
|
||||||
sync.RWMutex
|
|
||||||
id string
|
|
||||||
opts router.WatchOptions
|
|
||||||
resChan chan *router.Event
|
|
||||||
done chan struct{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Next returns the next noticed action taken on table
|
|
||||||
// TODO: right now we only allow to watch particular service
|
|
||||||
func (w *tableWatcher) Next() (*router.Event, error) {
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case res := <-w.resChan:
|
|
||||||
switch w.opts.Service {
|
|
||||||
case res.Route.Service, "*":
|
|
||||||
return res, nil
|
|
||||||
default:
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
case <-w.done:
|
|
||||||
return nil, router.ErrWatcherStopped
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Chan returns watcher events channel
|
|
||||||
func (w *tableWatcher) Chan() (<-chan *router.Event, error) {
|
|
||||||
return w.resChan, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop stops routing table watcher
|
|
||||||
func (w *tableWatcher) Stop() {
|
|
||||||
w.Lock()
|
|
||||||
defer w.Unlock()
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-w.done:
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
close(w.done)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,82 +0,0 @@
|
|||||||
package static
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/unistack-org/micro/v3/router"
|
|
||||||
)
|
|
||||||
|
|
||||||
// NewRouter returns an initialized static router
|
|
||||||
func NewRouter(opts ...router.Option) router.Router {
|
|
||||||
options := router.DefaultOptions()
|
|
||||||
for _, o := range opts {
|
|
||||||
o(&options)
|
|
||||||
}
|
|
||||||
return &static{options, new(table)}
|
|
||||||
}
|
|
||||||
|
|
||||||
type static struct {
|
|
||||||
options router.Options
|
|
||||||
table router.Table
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *static) Init(opts ...router.Option) error {
|
|
||||||
for _, o := range opts {
|
|
||||||
o(&s.options)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *static) Options() router.Options {
|
|
||||||
return s.options
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *static) Table() router.Table {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *static) Lookup(opts ...router.QueryOption) ([]router.Route, error) {
|
|
||||||
return s.table.Query(opts...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *static) Watch(opts ...router.WatchOption) (router.Watcher, error) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *static) Close() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *static) String() string {
|
|
||||||
return "static"
|
|
||||||
}
|
|
||||||
|
|
||||||
type table struct{}
|
|
||||||
|
|
||||||
func (t *table) Create(router.Route) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *table) Delete(router.Route) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *table) Update(router.Route) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *table) List() ([]router.Route, error) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *table) Query(opts ...router.QueryOption) ([]router.Route, error) {
|
|
||||||
options := router.NewQuery(opts...)
|
|
||||||
|
|
||||||
return []router.Route{
|
|
||||||
{
|
|
||||||
Address: options.Service,
|
|
||||||
Service: options.Address,
|
|
||||||
Gateway: options.Gateway,
|
|
||||||
Network: options.Network,
|
|
||||||
Router: options.Router,
|
|
||||||
},
|
|
||||||
}, nil
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user