summaryrefslogtreecommitdiff
path: root/_old/container.go
diff options
context:
space:
mode:
Diffstat (limited to '_old/container.go')
-rw-r--r--_old/container.go371
1 files changed, 371 insertions, 0 deletions
diff --git a/_old/container.go b/_old/container.go
new file mode 100644
index 00000000..daab7339
--- /dev/null
+++ b/_old/container.go
@@ -0,0 +1,371 @@
+package _old
+
+import (
+ "fmt"
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+ "reflect"
+ "sync"
+)
+
+var errNoConfig = fmt.Errorf("no config has been provided")
+var errTempFix223 = fmt.Errorf("temporary error for fix #223") // meant no error here, just shutdown the server
+
+// InitMethod contains name of the method to be automatically invoked while service initialization. Must return
+// (bool, error). Container can be requested as well. Config can be requested in a form
+// of service.Config or pointer to service specific config struct (automatically unmarshalled), config argument must
+// implement service.HydrateConfig.
+const InitMethod = "Init"
+
+// Service can serve. Services can provide Init method which must return (bool, error) signature and might accept
+// other services and/or configs as dependency.
+type Service interface {
+ // Serve serves.
+ Serve() error
+
+ // Detach stops the service.
+ Stop()
+}
+
+// Container controls all internal RR services and provides plugin based system.
+type Container interface {
+ // Register add new service to the container under given name.
+ Register(name string, service interface{})
+
+ // Reconfigure configures all underlying services with given configuration.
+ Init(cfg Config) error
+
+ // Check if svc has been registered.
+ Has(service string) bool
+
+ // get returns svc instance by it's name or nil if svc not found. Method returns current service status
+ // as second value.
+ Get(service string) (svc interface{}, status int)
+
+ // Serve all configured services. Non blocking.
+ Serve() error
+
+ // Close all active services.
+ Stop()
+
+ // List service names.
+ List() []string
+}
+
+// Config provides ability to slice configuration sections and unmarshal configuration data into
+// given structure.
+type Config interface {
+ // get nested config section (sub-map), returns nil if section not found.
+ Get(service string) Config
+
+ // Unmarshal unmarshal config data into given struct.
+ Unmarshal(out interface{}) error
+}
+
+// HydrateConfig provides ability to automatically hydrate config with values using
+// service.Config as the source.
+type HydrateConfig interface {
+ // Hydrate must populate config values using given config source.
+ // Must return error if config is not valid.
+ Hydrate(cfg Config) error
+}
+
+// DefaultsConfig declares ability to be initated without config data provided.
+type DefaultsConfig interface {
+ // InitDefaults allows to init blank config with pre-defined set of default values.
+ InitDefaults() error
+}
+
+type container struct {
+ log logrus.FieldLogger
+ mu sync.Mutex
+ services []*entry
+ errc chan struct {
+ name string
+ err error
+ }
+}
+
+// NewContainer creates new service container.
+func NewContainer(log logrus.FieldLogger) Container {
+ return &container{
+ log: log,
+ services: make([]*entry, 0),
+ errc: make(chan struct {
+ name string
+ err error
+ }, 1),
+ }
+}
+
+// Register add new service to the container under given name.
+func (c *container) Register(name string, service interface{}) {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+
+ c.services = append(c.services, &entry{
+ name: name,
+ svc: service,
+ status: StatusInactive,
+ })
+}
+
+// Check hasStatus svc has been registered.
+func (c *container) Has(target string) bool {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+
+ for _, e := range c.services {
+ if e.name == target {
+ return true
+ }
+ }
+
+ return false
+}
+
+// get returns svc instance by it's name or nil if svc not found.
+func (c *container) Get(target string) (svc interface{}, status int) {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+
+ for _, e := range c.services {
+ if e.name == target {
+ return e.svc, e.getStatus()
+ }
+ }
+
+ return nil, StatusUndefined
+}
+
+// Init configures all underlying services with given configuration.
+func (c *container) Init(cfg Config) error {
+ for _, e := range c.services {
+ if e.getStatus() >= StatusOK {
+ return fmt.Errorf("service [%s] has already been configured", e.name)
+ }
+
+ // inject service dependencies
+ if ok, err := c.initService(e.svc, cfg.Get(e.name)); err != nil {
+ // soft error (skipping)
+ if err == errNoConfig {
+ c.log.Debugf("[%s]: disabled", e.name)
+ continue
+ }
+
+ return errors.Wrap(err, fmt.Sprintf("[%s]", e.name))
+ } else if ok {
+ e.setStatus(StatusOK)
+ } else {
+ c.log.Debugf("[%s]: disabled", e.name)
+ }
+ }
+
+ return nil
+}
+
+// Serve all configured services. Non blocking.
+func (c *container) Serve() error {
+ var running = 0
+ for _, e := range c.services {
+ if e.hasStatus(StatusOK) && e.canServe() {
+ running++
+ c.log.Debugf("[%s]: started", e.name)
+ go func(e *entry) {
+ e.setStatus(StatusServing)
+ defer e.setStatus(StatusStopped)
+ if err := e.svc.(Service).Serve(); err != nil {
+ c.errc <- struct {
+ name string
+ err error
+ }{name: e.name, err: errors.Wrap(err, fmt.Sprintf("[%s]", e.name))}
+ } else {
+ c.errc <- struct {
+ name string
+ err error
+ }{name: e.name, err: errTempFix223}
+ }
+ }(e)
+ }
+ }
+
+ // simple handler to handle empty configs
+ if running == 0 {
+ return nil
+ }
+
+ for fail := range c.errc {
+ if fail.err == errTempFix223 {
+ // if we call stop, then stop all plugins
+ break
+ } else {
+ c.log.Errorf("[%s]: %s", fail.name, fail.err)
+ c.Stop()
+ return fail.err
+ }
+ }
+
+ return nil
+}
+
+// Detach sends stop command to all running services.
+func (c *container) Stop() {
+ for _, e := range c.services {
+ if e.hasStatus(StatusServing) {
+ e.setStatus(StatusStopping)
+ e.svc.(Service).Stop()
+ e.setStatus(StatusStopped)
+
+ c.log.Debugf("[%s]: stopped", e.name)
+ }
+ }
+}
+
+// List all service names.
+func (c *container) List() []string {
+ names := make([]string, 0, len(c.services))
+ for _, e := range c.services {
+ names = append(names, e.name)
+ }
+
+ return names
+}
+
+// calls Init method with automatically resolved arguments.
+func (c *container) initService(s interface{}, segment Config) (bool, error) {
+ r := reflect.TypeOf(s)
+
+ m, ok := r.MethodByName(InitMethod)
+ if !ok {
+ // no Init method is presented, assuming service does not need initialization.
+ return true, nil
+ }
+
+ if err := c.verifySignature(m); err != nil {
+ return false, err
+ }
+
+ // hydrating
+ values, err := c.resolveValues(s, m, segment)
+ if err != nil {
+ return false, err
+ }
+
+ // initiating service
+ out := m.Func.Call(values)
+
+ if out[1].IsNil() {
+ return out[0].Bool(), nil
+ }
+
+ return out[0].Bool(), out[1].Interface().(error)
+}
+
+// resolveValues returns slice of call arguments for service Init method.
+func (c *container) resolveValues(s interface{}, m reflect.Method, cfg Config) (values []reflect.Value, err error) {
+ for i := 0; i < m.Type.NumIn(); i++ {
+ v := m.Type.In(i)
+
+ switch {
+ case v.ConvertibleTo(reflect.ValueOf(s).Type()): // service itself
+ values = append(values, reflect.ValueOf(s))
+
+ case v.Implements(reflect.TypeOf((*Container)(nil)).Elem()): // container
+ values = append(values, reflect.ValueOf(c))
+
+ case v.Implements(reflect.TypeOf((*logrus.StdLogger)(nil)).Elem()),
+ v.Implements(reflect.TypeOf((*logrus.FieldLogger)(nil)).Elem()),
+ v.ConvertibleTo(reflect.ValueOf(c.log).Type()): // logger
+ values = append(values, reflect.ValueOf(c.log))
+
+ case v.Implements(reflect.TypeOf((*HydrateConfig)(nil)).Elem()): // injectable config
+ sc := reflect.New(v.Elem())
+
+ if dsc, ok := sc.Interface().(DefaultsConfig); ok {
+ err := dsc.InitDefaults()
+ if err != nil {
+ return nil, err
+ }
+ if cfg == nil {
+ values = append(values, sc)
+ continue
+ }
+
+ } else if cfg == nil {
+ return nil, errNoConfig
+ }
+
+ if err := sc.Interface().(HydrateConfig).Hydrate(cfg); err != nil {
+ return nil, err
+ }
+
+ values = append(values, sc)
+
+ case v.Implements(reflect.TypeOf((*Config)(nil)).Elem()): // generic config section
+ if cfg == nil {
+ return nil, errNoConfig
+ }
+
+ values = append(values, reflect.ValueOf(cfg))
+
+ default: // dependency on other service (resolution to nil if service can't be found)
+ value, err := c.resolveValue(v)
+ if err != nil {
+ return nil, err
+ }
+
+ values = append(values, value)
+ }
+ }
+
+ return
+}
+
+// verifySignature checks if Init method has valid signature
+func (c *container) verifySignature(m reflect.Method) error {
+ if m.Type.NumOut() != 2 {
+ return fmt.Errorf("method Init must have exact 2 return values")
+ }
+
+ if m.Type.Out(0).Kind() != reflect.Bool {
+ return fmt.Errorf("first return value of Init method must be bool type")
+ }
+
+ if !m.Type.Out(1).Implements(reflect.TypeOf((*error)(nil)).Elem()) {
+ return fmt.Errorf("second return value of Init method value must be error type")
+ }
+
+ return nil
+}
+
+func (c *container) resolveValue(v reflect.Type) (reflect.Value, error) {
+ value := reflect.Value{}
+ for _, e := range c.services {
+ if !e.hasStatus(StatusOK) {
+ continue
+ }
+
+ if v.Kind() == reflect.Interface && reflect.TypeOf(e.svc).Implements(v) {
+ if value.IsValid() {
+ return value, fmt.Errorf("disambiguous dependency `%s`", v)
+ }
+
+ value = reflect.ValueOf(e.svc)
+ }
+
+ if v.ConvertibleTo(reflect.ValueOf(e.svc).Type()) {
+ if value.IsValid() {
+ return value, fmt.Errorf("disambiguous dependency `%s`", v)
+ }
+
+ value = reflect.ValueOf(e.svc)
+ }
+ }
+
+ if !value.IsValid() {
+ // placeholder (make sure to check inside the method)
+ value = reflect.New(v).Elem()
+ }
+
+ return value, nil
+}