diff --git a/config/cmd/cmd.go b/config/cmd/cmd.go index ee2d4505..6c8cbb91 100644 --- a/config/cmd/cmd.go +++ b/config/cmd/cmd.go @@ -2,6 +2,7 @@ package cmd import ( + "fmt" "math/rand" "strings" "time" @@ -22,6 +23,7 @@ import ( "github.com/micro/go-micro/v2/logger" "github.com/micro/go-micro/v2/registry" registrySrv "github.com/micro/go-micro/v2/registry/service" + "github.com/micro/go-micro/v2/router" "github.com/micro/go-micro/v2/runtime" "github.com/micro/go-micro/v2/server" "github.com/micro/go-micro/v2/store" @@ -51,6 +53,12 @@ import ( rmem "github.com/micro/go-micro/v2/registry/memory" regSrv "github.com/micro/go-micro/v2/registry/service" + // routers + dnsRouter "github.com/micro/go-micro/v2/router/dns" + regRouter "github.com/micro/go-micro/v2/router/registry" + srvRouter "github.com/micro/go-micro/v2/router/service" + staticRouter "github.com/micro/go-micro/v2/router/static" + // runtimes kRuntime "github.com/micro/go-micro/v2/runtime/kubernetes" lRuntime "github.com/micro/go-micro/v2/runtime/local" @@ -58,7 +66,7 @@ import ( // selectors "github.com/micro/go-micro/v2/client/selector/dns" - "github.com/micro/go-micro/v2/client/selector/router" + sRouter "github.com/micro/go-micro/v2/client/selector/router" "github.com/micro/go-micro/v2/client/selector/static" // transports @@ -325,6 +333,11 @@ var ( EnvVars: []string{"MICRO_CONFIG"}, Usage: "The source of the config to be used to get configuration", }, + &cli.StringFlag{ + Name: "router", + EnvVars: []string{"MICRO_ROUTER"}, + Usage: "Router used for client requests", + }, } DefaultBrokers = map[string]func(...broker.Option) broker.Broker{ @@ -346,9 +359,16 @@ var ( "memory": rmem.NewRegistry, } + DefaultRouters = map[string]func(...router.Option) router.Router{ + "dns": dnsRouter.NewRouter, + "registry": regRouter.NewRouter, + "static": staticRouter.NewRouter, + "service": srvRouter.NewRouter, + } + DefaultSelectors = map[string]func(...selector.Option) selector.Selector{ "dns": dns.NewSelector, - "router": router.NewSelector, + "router": sRouter.NewSelector, "static": static.NewSelector, } @@ -411,6 +431,7 @@ func newCmd(opts ...Option) Cmd { Server: &server.DefaultServer, Selector: &selector.DefaultSelector, Transport: &transport.DefaultTransport, + Router: &router.DefaultRouter, Runtime: &runtime.DefaultRuntime, Store: &store.DefaultStore, Tracer: &trace.DefaultTracer, @@ -423,6 +444,7 @@ func newCmd(opts ...Option) Cmd { Selectors: DefaultSelectors, Servers: DefaultServers, Transports: DefaultTransports, + Routers: DefaultRouters, Runtimes: DefaultRuntimes, Stores: DefaultStores, Tracers: DefaultTracers, @@ -548,6 +570,28 @@ func (c *cmd) Before(ctx *cli.Context) error { microClient := wrapper.CacheClient(cacheFn, grpc.NewClient()) microClient = wrapper.AuthClient(authFn, microClient) + // Set the router, this must happen before the rest of the server as it'll route server requests + // such as go.micro.config if no address is specified + routerOpts := []router.Option{ + router.Network(ctx.String("service_namespace")), + router.Registry(*c.opts.Registry), + srvRouter.Client(microClient), + } + if name := ctx.String("router"); len(name) > 0 && (*c.opts.Router).String() != name { + r, ok := c.opts.Routers[name] + if !ok { + return fmt.Errorf("Router %s not found", name) + } + + *c.opts.Router = r(routerOpts...) + // todo: set the router in the client + // clientOpts = append(clientOpts, client.Router(*c.opts.Router)) + } else if len(routerOpts) > 0 { + if err := (*c.opts.Router).Init(routerOpts...); err != nil { + logger.Fatalf("Error configuring router: %v", err) + } + } + // Setup store options storeOpts := []store.Option{store.WithClient(microClient)} if len(ctx.String("store_address")) > 0 { diff --git a/config/cmd/options.go b/config/cmd/options.go index 1dfc8076..634ef6c7 100644 --- a/config/cmd/options.go +++ b/config/cmd/options.go @@ -11,6 +11,7 @@ import ( "github.com/micro/go-micro/v2/debug/profile" "github.com/micro/go-micro/v2/debug/trace" "github.com/micro/go-micro/v2/registry" + "github.com/micro/go-micro/v2/router" "github.com/micro/go-micro/v2/runtime" "github.com/micro/go-micro/v2/server" "github.com/micro/go-micro/v2/store" @@ -31,6 +32,7 @@ type Options struct { Config *config.Config Client *client.Client Server *server.Server + Router *router.Router Runtime *runtime.Runtime Store *store.Store Tracer *trace.Tracer @@ -44,6 +46,7 @@ type Options struct { Selectors map[string]func(...selector.Option) selector.Selector Servers map[string]func(...server.Option) server.Server Transports map[string]func(...transport.Option) transport.Transport + Routers map[string]func(...router.Option) router.Router Runtimes map[string]func(...runtime.Option) runtime.Runtime Stores map[string]func(...store.Option) store.Store Tracers map[string]func(...trace.Option) trace.Tracer @@ -100,6 +103,12 @@ func Registry(r *registry.Registry) Option { } } +func Router(r *router.Router) Option { + return func(o *Options) { + o.Router = r + } +} + func Runtime(r *runtime.Runtime) Option { return func(o *Options) { o.Runtime = r @@ -190,6 +199,13 @@ func NewTransport(name string, t func(...transport.Option) transport.Transport) } } +// New router func +func NewRouter(name string, r func(...router.Option) router.Router) Option { + return func(o *Options) { + o.Routers[name] = r + } +} + // New runtime func func NewRuntime(name string, r func(...runtime.Option) runtime.Runtime) Option { return func(o *Options) { diff --git a/router/dns/dns.go b/router/dns/dns.go new file mode 100644 index 00000000..4241b3d4 --- /dev/null +++ b/router/dns/dns.go @@ -0,0 +1,130 @@ +package dns + +import ( + "fmt" + "net" + "strconv" + + "github.com/micro/go-micro/v2/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) Advertise() (<-chan *router.Advert, error) { + return nil, nil +} + +func (d *dns) Process(*router.Advert) error { + return nil +} + +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 +} diff --git a/router/options.go b/router/options.go index a5f44b46..94c96c07 100644 --- a/router/options.go +++ b/router/options.go @@ -1,8 +1,9 @@ package router import ( + "context" + "github.com/google/uuid" - "github.com/micro/go-micro/v2/client" "github.com/micro/go-micro/v2/registry" ) @@ -20,8 +21,8 @@ type Options struct { Registry registry.Registry // Advertise is the advertising strategy Advertise Strategy - // Client for calling router - Client client.Client + // Context for additional options + Context context.Context } // Id sets Router Id @@ -38,13 +39,6 @@ func Address(a string) Option { } } -// Client to call router service -func Client(c client.Client) Option { - return func(o *Options) { - o.Client = c - } -} - // Gateway sets network gateway func Gateway(g string) Option { return func(o *Options) { @@ -81,5 +75,6 @@ func DefaultOptions() Options { Network: DefaultNetwork, Registry: registry.DefaultRegistry, Advertise: AdvertiseLocal, + Context: context.Background(), } } diff --git a/router/registry/registry.go b/router/registry/registry.go new file mode 100644 index 00000000..01172b46 --- /dev/null +++ b/router/registry/registry.go @@ -0,0 +1,8 @@ +package registry + +import "github.com/micro/go-micro/v2/router" + +// NewRouter returns an initialised registry router +func NewRouter(opts ...router.Option) router.Router { + return router.NewRouter(opts...) +} diff --git a/router/service/options.go b/router/service/options.go new file mode 100644 index 00000000..9f067a4a --- /dev/null +++ b/router/service/options.go @@ -0,0 +1,22 @@ +package service + +import ( + "context" + + "github.com/micro/go-micro/v2/client" + "github.com/micro/go-micro/v2/router" +) + +type clientKey struct{} + +// Client to call router service +func Client(c client.Client) router.Option { + return func(o *router.Options) { + if o.Context == nil { + o.Context = context.WithValue(context.Background(), clientKey{}, c) + return + } + + o.Context = context.WithValue(o.Context, clientKey{}, c) + } +} diff --git a/router/service/service.go b/router/service/service.go index 1f6ce1ea..5068e474 100644 --- a/router/service/service.go +++ b/router/service/service.go @@ -36,9 +36,10 @@ func NewRouter(opts ...router.Option) router.Router { // NOTE: might need some client opts here cli := client.DefaultClient - // set options client - if options.Client != nil { - cli = options.Client + // get options client from the context. We set this in the context to prevent an import loop, as + // the client depends on the router + if c, ok := options.Context.Value(clientKey{}).(client.Client); ok { + cli = c } // NOTE: should we have Client/Service option in router.Options? diff --git a/router/static/static.go b/router/static/static.go new file mode 100644 index 00000000..e7e6054e --- /dev/null +++ b/router/static/static.go @@ -0,0 +1,88 @@ +package static + +import "github.com/micro/go-micro/v2/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) Advertise() (<-chan *router.Advert, error) { + return nil, nil +} + +func (s *static) Process(*router.Advert) error { + return nil +} + +func (s *static) Lookup(...router.QueryOption) ([]router.Route, error) { + return nil, nil +} + +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{ + router.Route{ + Address: options.Service, + Service: options.Address, + Gateway: options.Gateway, + Network: options.Network, + Router: options.Router, + }, + }, nil +}