diff --git a/pkg/shared/errors.go b/pkg/shared/errors.go index 6052402a06..7e582f2c95 100644 --- a/pkg/shared/errors.go +++ b/pkg/shared/errors.go @@ -102,10 +102,10 @@ func MissingURLSigningSecret(service string) error { service, defaults.BaseConfigPath()) } -func AllComponentsDisabledError(service string) error { +func AllApiHandlersDisabledError(service string) error { return fmt.Errorf("All request handlers and event consumers are disabled for %s; at least one component must be enabled."+ "Make sure your %s config contains the proper values "+ "(e.g. by using 'opencloud init --diff' and applying the patch or setting a value manually in "+ - "the config/corresponding environment variable).", + "the config/corresponding environment variable).", service, defaults.BaseConfigPath()) } diff --git a/services/userlog/pkg/command/server.go b/services/userlog/pkg/command/server.go index e461b4ba3b..e9ec4aa8f0 100644 --- a/services/userlog/pkg/command/server.go +++ b/services/userlog/pkg/command/server.go @@ -19,11 +19,11 @@ import ( "github.com/opencloud-eu/opencloud/services/userlog/pkg/config" "github.com/opencloud-eu/opencloud/services/userlog/pkg/config/parser" "github.com/opencloud-eu/opencloud/services/userlog/pkg/metrics" + "github.com/opencloud-eu/opencloud/services/userlog/pkg/server/consumer" "github.com/opencloud-eu/opencloud/services/userlog/pkg/server/debug" "github.com/opencloud-eu/opencloud/services/userlog/pkg/server/http" "github.com/opencloud-eu/opencloud/services/userlog/pkg/service" - "github.com/go-chi/chi/v5" "github.com/opencloud-eu/reva/v2/pkg/events" "github.com/opencloud-eu/reva/v2/pkg/events/stream" "github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool" @@ -87,8 +87,16 @@ func Server(cfg *config.Config) *cobra.Command { connName := generators.GenerateConnectionName(cfg.Service.Name, generators.NTypeBus) var evStream events.Stream - if !cfg.EventsDisabled { - evStream, err = stream.NatsFromConfig(connName, false, stream.NatsConfig(cfg.Events)) + if !cfg.Events.Disabled { + evStream, err = stream.NatsFromConfig(connName, false, stream.NatsConfig{ + Endpoint: cfg.Events.Endpoint, + Cluster: cfg.Events.Cluster, + TLSInsecure: cfg.Events.TLSInsecure, + TLSRootCACertificate: cfg.Events.TLSRootCACertificate, + EnableTLS: cfg.Events.EnableTLS, + AuthUsername: cfg.Events.AuthUsername, + AuthPassword: cfg.Events.AuthPassword, + }) if err != nil { return err } @@ -125,21 +133,31 @@ func Server(cfg *config.Config) *cobra.Command { vClient := settingssvc.NewValueService("eu.opencloud.api.settings", grpcClient) rClient := settingssvc.NewRoleService("eu.opencloud.api.settings", grpcClient) + handle, err := service.NewUserlogService( + service.Logger(logger), + service.Stream(evStream), + service.Store(st), + service.Config(cfg), + service.HistoryClient(hClient), + service.GatewaySelector(gatewaySelector), + service.ValueClient(vClient), + service.RegisteredEvents(_registeredEvents), + service.TraceProvider(tracerProvider), + ) + if err != nil { + return err + } + gr := runner.NewGroup() - if !cfg.HTTPDisabled { + if !cfg.HTTP.Disabled { server, err := http.Server( http.Logger(logger), http.Context(ctx), http.Config(cfg), http.Metrics(mtrcs), - http.Store(st), - http.Stream(evStream), - http.GatewaySelector(gatewaySelector), - http.History(hClient), - http.Value(vClient), http.Role(rClient), - http.RegisteredEvents(_registeredEvents), + http.UserlogService(handle), http.TracerProvider(tracerProvider), ) @@ -151,33 +169,23 @@ func Server(cfg *config.Config) *cobra.Command { gr.Add(runner.NewGoMicroHttpServerRunner(cfg.Service.Name+".http", server)) } else { logger.Info().Msg("HTTP server disabled, not starting HTTP service") - - if !cfg.EventsDisabled { - _, err := service.NewUserlogService( - service.Logger(logger), - service.Stream(evStream), - service.Mux(chi.NewMux()), - service.Store(st), - service.Config(cfg), - service.HistoryClient(hClient), - service.GatewaySelector(gatewaySelector), - service.ValueClient(vClient), - service.RoleClient(rClient), - service.RegisteredEvents(_registeredEvents), - service.TraceProvider(tracerProvider), - ) - if err != nil { - return err - } - - gr.Add(runner.New(cfg.Service.Name+".consumer", func() error { - <-ctx.Done() - return nil - }, func() {})) - } } - if cfg.EventsDisabled { + if !cfg.Events.Disabled { + server, err := consumer.Server( + consumer.Logger(logger), + consumer.Context(ctx), + consumer.Config(cfg), + consumer.Stream(evStream), + consumer.UserlogService(handle), + consumer.RegisteredEvents(_registeredEvents), + ) + if err != nil { + return err + } + + gr.Add(server) + } else { logger.Info().Msg("event listening disabled, not starting event consumer") } diff --git a/services/userlog/pkg/config/config.go b/services/userlog/pkg/config/config.go index d168185e42..22c516f27e 100644 --- a/services/userlog/pkg/config/config.go +++ b/services/userlog/pkg/config/config.go @@ -30,9 +30,6 @@ type Config struct { DisableSSE bool `yaml:"disable_sse" env:"OC_DISABLE_SSE,USERLOG_DISABLE_SSE" desc:"Disables server-sent events (sse). When disabled, clients will no longer receive sse notifications." introductionVersion:"1.0.0"` - EventsDisabled bool `yaml:"events_disabled" env:"USERLOG_EVENTS_DISABLED" desc:"Disables listening for events. Set this to true if the service should only handle HTTP requests." introductionVersion:"%NEXT%"` - HTTPDisabled bool `yaml:"http_disabled" env:"USERLOG_HTTP_DISABLED" desc:"Disables the HTTP service. Set this to true if the service should only handle events." introductionVersion:"%NEXT%"` - GlobalNotificationsSecret string `yaml:"global_notifications_secret" env:"USERLOG_GLOBAL_NOTIFICATIONS_SECRET" desc:"The secret to secure the global notifications endpoint. Only system admins and users knowing that secret can call the global notifications POST/DELETE endpoints." introductionVersion:"1.0.0"` ServiceAccount ServiceAccount `yaml:"service_account"` @@ -56,6 +53,7 @@ type Persistence struct { // Events combines the configuration options for the event bus. type Events struct { + Disabled bool `yaml:"disabled" env:"USERLOG_EVENTS_DISABLED" desc:"Disables listening for events. Set this to true if the service should only handle HTTP requests." introductionVersion:"%NEXT%"` Endpoint string `yaml:"endpoint" env:"OC_EVENTS_ENDPOINT;USERLOG_EVENTS_ENDPOINT" desc:"The address of the event system. The event system is the message queuing service. It is used as message broker for the microservice architecture." introductionVersion:"1.0.0"` Cluster string `yaml:"cluster" env:"OC_EVENTS_CLUSTER;USERLOG_EVENTS_CLUSTER" desc:"The clusterID of the event system. The event system is the message queuing service. It is used as message broker for the microservice architecture. Mandatory when using NATS as event system." introductionVersion:"1.0.0"` TLSInsecure bool `yaml:"tls_insecure" env:"OC_INSECURE;OC_EVENTS_TLS_INSECURE;USERLOG_EVENTS_TLS_INSECURE" desc:"Whether to verify the server TLS certificates." introductionVersion:"1.0.0"` @@ -75,6 +73,7 @@ type CORS struct { // HTTP defines the available http configuration. type HTTP struct { + Disabled bool `yaml:"disabled" env:"USERLOG_HTTP_DISABLED" desc:"Disables the HTTP service. Set this to true if the service should only handle events." introductionVersion:"%NEXT%"` Addr string `yaml:"addr" env:"USERLOG_HTTP_ADDR" desc:"The bind address of the HTTP service." introductionVersion:"1.0.0"` Namespace string `yaml:"-"` Root string `yaml:"root" env:"USERLOG_HTTP_ROOT" desc:"Subdirectory that serves as the root for this HTTP service." introductionVersion:"1.0.0"` diff --git a/services/userlog/pkg/config/parser/parse.go b/services/userlog/pkg/config/parser/parse.go index 20bea22d36..e2b4a6d8cf 100644 --- a/services/userlog/pkg/config/parser/parse.go +++ b/services/userlog/pkg/config/parser/parse.go @@ -46,8 +46,8 @@ func Validate(cfg *config.Config) error { return shared.MissingServiceAccountSecret(cfg.Service.Name) } - if cfg.EventsDisabled && cfg.HTTPDisabled { - return shared.AllComponentsDisabledError(cfg.Service.Name) + if cfg.Events.Disabled && cfg.HTTP.Disabled { + return shared.AllApiHandlersDisabledError(cfg.Service.Name) } return nil diff --git a/services/userlog/pkg/server/consumer/option.go b/services/userlog/pkg/server/consumer/option.go new file mode 100644 index 0000000000..6e3299f38d --- /dev/null +++ b/services/userlog/pkg/server/consumer/option.go @@ -0,0 +1,76 @@ +package consumer + +import ( + "context" + + "github.com/opencloud-eu/opencloud/pkg/log" + "github.com/opencloud-eu/opencloud/services/userlog/pkg/config" + svc "github.com/opencloud-eu/opencloud/services/userlog/pkg/service" + "github.com/opencloud-eu/reva/v2/pkg/events" +) + +// Option defines a single option function. +type Option func(o *Options) + +// Options defines the available options for this package. +type Options struct { + Logger log.Logger + Context context.Context + Config *config.Config + Stream events.Stream + UserlogService *svc.UserlogService + RegisteredEvents []events.Unmarshaller +} + +// newOptions initializes the available default options. +func newOptions(opts ...Option) Options { + opt := Options{} + + for _, o := range opts { + o(&opt) + } + + return opt +} + +// Logger provides a function to set the logger option. +func Logger(val log.Logger) Option { + return func(o *Options) { + o.Logger = val + } +} + +// Context provides a function to set the context option. +func Context(val context.Context) Option { + return func(o *Options) { + o.Context = val + } +} + +// Config provides a function to set the config option. +func Config(val *config.Config) Option { + return func(o *Options) { + o.Config = val + } +} + +// Stream provides a function to configure the stream +func Stream(stream events.Stream) Option { + return func(o *Options) { + o.Stream = stream + } +} + +// UserlogService provides a function to set the userlog service +func UserlogService(s *svc.UserlogService) Option { + return func(o *Options) { + o.UserlogService = s + } +} + +// RegisteredEvents provides a function to register events +func RegisteredEvents(evs []events.Unmarshaller) Option { + return func(o *Options) { + o.RegisteredEvents = evs + } +} diff --git a/services/userlog/pkg/server/consumer/server.go b/services/userlog/pkg/server/consumer/server.go new file mode 100644 index 0000000000..2e631e6743 --- /dev/null +++ b/services/userlog/pkg/server/consumer/server.go @@ -0,0 +1,29 @@ +package consumer + +import ( + "errors" + + "github.com/opencloud-eu/opencloud/pkg/runner" + "github.com/opencloud-eu/reva/v2/pkg/events" +) + +// Server starts the event consumer for the userlog service +func Server(opts ...Option) (*runner.Runner, error) { + options := newOptions(opts...) + + if options.UserlogService == nil { + return nil, errors.New("need non nil userlog service to consume events") + } + + ch, err := events.Consume(options.Stream, "userlog", options.RegisteredEvents...) + if err != nil { + return nil, err + } + + options.UserlogService.MemorizeEvents(ch) + + return runner.New(options.Config.Service.Name+".consumer", func() error { + <-options.Context.Done() + return nil + }, func() {}), nil +} diff --git a/services/userlog/pkg/server/http/option.go b/services/userlog/pkg/server/http/option.go index aee27b1258..3d8473bbc3 100644 --- a/services/userlog/pkg/server/http/option.go +++ b/services/userlog/pkg/server/http/option.go @@ -3,17 +3,13 @@ package http import ( "context" - gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" "github.com/opencloud-eu/opencloud/pkg/log" - ehsvc "github.com/opencloud-eu/opencloud/protogen/gen/opencloud/services/eventhistory/v0" settingssvc "github.com/opencloud-eu/opencloud/protogen/gen/opencloud/services/settings/v0" "github.com/opencloud-eu/opencloud/services/userlog/pkg/config" "github.com/opencloud-eu/opencloud/services/userlog/pkg/metrics" - "github.com/opencloud-eu/reva/v2/pkg/events" - "github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool" + svc "github.com/opencloud-eu/opencloud/services/userlog/pkg/service" "github.com/spf13/pflag" - "go-micro.dev/v4/store" "go.opentelemetry.io/otel/trace" ) @@ -22,20 +18,15 @@ type Option func(o *Options) // Options defines the available options for this package. type Options struct { - Logger log.Logger - Context context.Context - Config *config.Config - Metrics *metrics.Metrics - Flags []pflag.Flag - Namespace string - Store store.Store - Stream events.Stream - GatewaySelector pool.Selectable[gateway.GatewayAPIClient] - HistoryClient ehsvc.EventHistoryService - ValueClient settingssvc.ValueService - RoleClient settingssvc.RoleService - RegisteredEvents []events.Unmarshaller - TracerProvider trace.TracerProvider + Logger log.Logger + Context context.Context + Config *config.Config + Metrics *metrics.Metrics + Flags []pflag.Flag + Namespace string + RoleClient settingssvc.RoleService + UserlogService *svc.UserlogService + TracerProvider trace.TracerProvider } // newOptions initializes the available default options. @@ -91,45 +82,10 @@ func Namespace(val string) Option { } } -// Store provides a function to configure the store -func Store(store store.Store) Option { +// UserlogService provides a function to set the userlog service +func UserlogService(s *svc.UserlogService) Option { return func(o *Options) { - o.Store = store - } -} - -// Stream provides a function to configure the stream -func Stream(stream events.Stream) Option { - return func(o *Options) { - o.Stream = stream - } -} - -// GatewaySelector provides a function to configure the gateway client selector -func GatewaySelector(gatewaySelector pool.Selectable[gateway.GatewayAPIClient]) Option { - return func(o *Options) { - o.GatewaySelector = gatewaySelector - } -} - -// History provides a function to configure the event history client -func History(h ehsvc.EventHistoryService) Option { - return func(o *Options) { - o.HistoryClient = h - } -} - -// RegisteredEvents provides a function to register events -func RegisteredEvents(evs []events.Unmarshaller) Option { - return func(o *Options) { - o.RegisteredEvents = evs - } -} - -// Value provides a function to configure the value service client -func Value(vs settingssvc.ValueService) Option { - return func(o *Options) { - o.ValueClient = vs + o.UserlogService = s } } diff --git a/services/userlog/pkg/server/http/server.go b/services/userlog/pkg/server/http/server.go index a26d8a4242..343c1042e8 100644 --- a/services/userlog/pkg/server/http/server.go +++ b/services/userlog/pkg/server/http/server.go @@ -1,6 +1,7 @@ package http import ( + "errors" "fmt" stdhttp "net/http" @@ -10,6 +11,7 @@ import ( "github.com/opencloud-eu/opencloud/pkg/account" "github.com/opencloud-eu/opencloud/pkg/cors" "github.com/opencloud-eu/opencloud/pkg/middleware" + "github.com/opencloud-eu/opencloud/pkg/roles" "github.com/opencloud-eu/opencloud/pkg/service/http" "github.com/opencloud-eu/opencloud/pkg/tracing" "github.com/opencloud-eu/opencloud/pkg/version" @@ -77,24 +79,24 @@ func Server(opts ...Option) (http.Service, error) { ), ) - handle, err := svc.NewUserlogService( - svc.Logger(options.Logger), - svc.Stream(options.Stream), - svc.Mux(mux), - svc.Store(options.Store), - svc.Config(options.Config), - svc.HistoryClient(options.HistoryClient), - svc.GatewaySelector(options.GatewaySelector), - svc.ValueClient(options.ValueClient), - svc.RoleClient(options.RoleClient), - svc.RegisteredEvents(options.RegisteredEvents), - svc.TraceProvider(options.TracerProvider), - ) - if err != nil { - return http.Service{}, err + if options.UserlogService == nil { + return http.Service{}, errors.New("need non nil userlog service to serve http requests") } - if err := micro.RegisterHandler(service.Server(), handle); err != nil { + m := roles.NewManager( + // TODO: caching? + roles.Logger(options.Logger), + roles.RoleService(options.RoleClient), + ) + + mux.Route("/ocs/v2.php/apps/notifications/api/v1/notifications", func(r chi.Router) { + r.Get("/", options.UserlogService.HandleGetEvents) + r.Delete("/", options.UserlogService.HandleDeleteEvents) + r.Post("/global", svc.RequireAdminOrSecret(&m, options.Config.GlobalNotificationsSecret)(options.UserlogService.HandlePostGlobalEvent)) + r.Delete("/global", svc.RequireAdminOrSecret(&m, options.Config.GlobalNotificationsSecret)(options.UserlogService.HandleDeleteGlobalEvent)) + }) + + if err := micro.RegisterHandler(service.Server(), mux); err != nil { return http.Service{}, err } diff --git a/services/userlog/pkg/service/http.go b/services/userlog/pkg/service/http.go index ba67452e83..c7ec3300da 100644 --- a/services/userlog/pkg/service/http.go +++ b/services/userlog/pkg/service/http.go @@ -18,11 +18,6 @@ import ( // HeaderAcceptLanguage is the header where the client can set the locale var HeaderAcceptLanguage = "Accept-Language" -// ServeHTTP fulfills Handler interface -func (ul *UserlogService) ServeHTTP(w http.ResponseWriter, r *http.Request) { - ul.m.ServeHTTP(w, r) -} - // HandleGetEvents is the GET handler for events func (ul *UserlogService) HandleGetEvents(w http.ResponseWriter, r *http.Request) { ctx, span := ul.tracer.Start(r.Context(), "HandleGetEvents") diff --git a/services/userlog/pkg/service/options.go b/services/userlog/pkg/service/options.go index 4c60100a8a..03230ed33d 100644 --- a/services/userlog/pkg/service/options.go +++ b/services/userlog/pkg/service/options.go @@ -2,7 +2,6 @@ package service import ( gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" - "github.com/go-chi/chi/v5" "github.com/opencloud-eu/opencloud/pkg/log" ehsvc "github.com/opencloud-eu/opencloud/protogen/gen/opencloud/services/eventhistory/v0" settingssvc "github.com/opencloud-eu/opencloud/protogen/gen/opencloud/services/settings/v0" @@ -20,13 +19,11 @@ type Option func(*Options) type Options struct { Logger log.Logger Stream events.Stream - Mux *chi.Mux Store store.Store Config *config.Config HistoryClient ehsvc.EventHistoryService GatewaySelector pool.Selectable[gateway.GatewayAPIClient] ValueClient settingssvc.ValueService - RoleClient settingssvc.RoleService RegisteredEvents []events.Unmarshaller TraceProvider trace.TracerProvider } @@ -45,13 +42,6 @@ func Stream(s events.Stream) Option { } } -// Mux defines the muxer for the userlog service -func Mux(m *chi.Mux) Option { - return func(o *Options) { - o.Mux = m - } -} - // Store defines the store for the userlog service func Store(s store.Store) Option { return func(o *Options) { @@ -94,13 +84,6 @@ func ValueClient(vs settingssvc.ValueService) Option { } } -// RoleClient adds a grpc client for the role service -func RoleClient(rs settingssvc.RoleService) Option { - return func(o *Options) { - o.RoleClient = rs - } -} - // TraceProvider adds a tracer provider for the userlog service func TraceProvider(tp trace.TracerProvider) Option { return func(o *Options) { diff --git a/services/userlog/pkg/service/service.go b/services/userlog/pkg/service/service.go index ef0234ad31..810a027429 100644 --- a/services/userlog/pkg/service/service.go +++ b/services/userlog/pkg/service/service.go @@ -10,7 +10,6 @@ import ( gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" - "github.com/go-chi/chi/v5" "github.com/opencloud-eu/reva/v2/pkg/events" "github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool" "github.com/opencloud-eu/reva/v2/pkg/utils" @@ -20,7 +19,6 @@ import ( ocEvents "github.com/opencloud-eu/opencloud/pkg/events" "github.com/opencloud-eu/opencloud/pkg/l10n" "github.com/opencloud-eu/opencloud/pkg/log" - "github.com/opencloud-eu/opencloud/pkg/roles" ehmsg "github.com/opencloud-eu/opencloud/protogen/gen/opencloud/messages/eventhistory/v0" ehsvc "github.com/opencloud-eu/opencloud/protogen/gen/opencloud/services/eventhistory/v0" settingssvc "github.com/opencloud-eu/opencloud/protogen/gen/opencloud/services/settings/v0" @@ -30,7 +28,6 @@ import ( // UserlogService is the service responsible for user activities type UserlogService struct { log log.Logger - m *chi.Mux store store.Store cfg *config.Config historyClient ehsvc.EventHistoryService @@ -56,7 +53,6 @@ func NewUserlogService(opts ...Option) (*UserlogService, error) { ul := &UserlogService{ log: o.Logger, - m: o.Mux, store: o.Store, cfg: o.Config, historyClient: o.HistoryClient, @@ -74,27 +70,6 @@ func NewUserlogService(opts ...Option) (*UserlogService, error) { ul.registeredEvents[typ.String()] = e } - m := roles.NewManager( - // TODO: caching? - roles.Logger(o.Logger), - roles.RoleService(o.RoleClient), - ) - - ul.m.Route("/ocs/v2.php/apps/notifications/api/v1/notifications", func(r chi.Router) { - r.Get("/", ul.HandleGetEvents) - r.Delete("/", ul.HandleDeleteEvents) - r.Post("/global", RequireAdminOrSecret(&m, o.Config.GlobalNotificationsSecret)(ul.HandlePostGlobalEvent)) - r.Delete("/global", RequireAdminOrSecret(&m, o.Config.GlobalNotificationsSecret)(ul.HandleDeleteGlobalEvent)) - }) - - if !o.Config.EventsDisabled { - ch, err := events.Consume(o.Stream, "userlog", o.RegisteredEvents...) - if err != nil { - return nil, err - } - go ul.MemorizeEvents(ch) - } - return ul, nil } diff --git a/services/userlog/pkg/service/service_test.go b/services/userlog/pkg/service/service_test.go index 40795295b1..3def67afea 100644 --- a/services/userlog/pkg/service/service_test.go +++ b/services/userlog/pkg/service/service_test.go @@ -12,7 +12,6 @@ import ( user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" - "github.com/go-chi/chi/v5" "github.com/google/uuid" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -100,7 +99,6 @@ var _ = Describe("UserlogService", func() { service.Stream(bus), service.Store(sto), service.Logger(log.NewLogger()), - service.Mux(chi.NewMux()), service.GatewaySelector(gatewaySelector), service.HistoryClient(&ehc), service.ValueClient(&vc), @@ -110,6 +108,10 @@ var _ = Describe("UserlogService", func() { service.TraceProvider(trace.NewNoopTracerProvider()), ) Expect(err).ToNot(HaveOccurred()) + + ch, err := events.Consume(bus, "userlog", events.SpaceDisabled{}) + Expect(err).ToNot(HaveOccurred()) + go ul.MemorizeEvents(ch) }) It("it stores, returns and deletes a couple of events", func() { @@ -172,14 +174,15 @@ var _ = Describe("UserlogService", func() { localEhc := mocks.EventHistoryService{} cfgNoEvents := &config.Config{ MaxConcurrency: 5, - EventsDisabled: true, + Events: config.Events{ + Disabled: true, + }, } ulNoConsumer, err := service.NewUserlogService( service.Config(cfgNoEvents), service.Stream(bus), service.Store(sto), service.Logger(log.NewLogger()), - service.Mux(chi.NewMux()), service.GatewaySelector(gatewaySelector), service.HistoryClient(&localEhc), service.ValueClient(&vc),