github.com/willyham/dosa@v2.3.1-0.20171024181418-1e446d37ee71+incompatible/connectors/routing/connector.go (about)

     1  // Copyright (c) 2017 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  package routing
    22  
    23  import (
    24  	"context"
    25  	"fmt"
    26  
    27  	"github.com/pkg/errors"
    28  	"github.com/uber-go/dosa"
    29  	"github.com/uber-go/dosa/connectors/base"
    30  )
    31  
    32  // PluginFunc is a plugin function that takes scope, namePrefix and operation name,
    33  // then gives wanted scope and namePrefix
    34  type PluginFunc func(scope, namePrefix, opName string) (string, string, error)
    35  
    36  // Connector holds a slice of configured connectors to route to
    37  type Connector struct {
    38  	base.Connector
    39  	// config connector slice is sorted in a manner:
    40  	// for the value of Config name prefix, strict string without "*" always comes first,
    41  	// and then string with "*" suffix (glob match) and pure "*".
    42  	// There shouldn't be any scope with a prefix "*" like "*.service.v1"
    43  	config     Config
    44  	connectors map[string]dosa.Connector
    45  	// PluginFunc is a plugin that passes in
    46  	// the scope, namePrefix and operation name, returns wanted scope and namePrefix
    47  	PluginFunc PluginFunc
    48  }
    49  
    50  // NewConnector initializes the Connector
    51  // connectorMap has a key of connectorName, and the value is a dosa.connector instance
    52  func NewConnector(cfg Config, connectorMap map[string]dosa.Connector, plugin PluginFunc) *Connector {
    53  	return &Connector{
    54  		connectors: connectorMap,
    55  		config:     cfg,
    56  		PluginFunc: plugin,
    57  	}
    58  }
    59  
    60  // get connector by scope, namePrefix and operation name provided
    61  func (rc *Connector) getConnector(scope string, namePrefix string, opName string) (_ dosa.Connector, err error) {
    62  	if rc.PluginFunc != nil {
    63  		// plugin operation
    64  		// plugin should always be first considered if it exists
    65  		scope, namePrefix, err = rc.PluginFunc(scope, namePrefix, opName)
    66  		if err != nil {
    67  			return nil, errors.Wrap(err, "failed to execute getConnector due to Plugin function error")
    68  		}
    69  	}
    70  	return rc._getConnector(scope, namePrefix)
    71  }
    72  
    73  // if no specific scope is found,
    74  // Connector routes to the default scope that defined in routing config yaml file
    75  func (rc *Connector) _getConnector(scope, namePrefix string) (dosa.Connector, error) {
    76  	router := rc.config.FindRouter(scope, namePrefix)
    77  
    78  	c, ok := rc.connectors[router.Connector]
    79  	if !ok {
    80  		return nil, fmt.Errorf("can't find %q connector", router.Connector)
    81  	}
    82  
    83  	return c, nil
    84  }
    85  
    86  // CreateIfNotExists selects corresponding connector
    87  func (rc *Connector) CreateIfNotExists(ctx context.Context, ei *dosa.EntityInfo, values map[string]dosa.FieldValue) error {
    88  	connector, err := rc.getConnector(ei.Ref.Scope, ei.Ref.NamePrefix, "CreateIfNotExists")
    89  	if err != nil {
    90  		return err
    91  	}
    92  	return connector.CreateIfNotExists(ctx, ei, values)
    93  }
    94  
    95  // Read selects corresponding connector
    96  func (rc *Connector) Read(ctx context.Context, ei *dosa.EntityInfo, values map[string]dosa.FieldValue, minimumFields []string) (map[string]dosa.FieldValue, error) {
    97  	connector, err := rc.getConnector(ei.Ref.Scope, ei.Ref.NamePrefix, "Read")
    98  	if err != nil {
    99  		return nil, err
   100  	}
   101  	return connector.Read(ctx, ei, values, minimumFields)
   102  }
   103  
   104  // MultiRead selects corresponding connector
   105  func (rc *Connector) MultiRead(ctx context.Context, ei *dosa.EntityInfo, values []map[string]dosa.FieldValue, minimumFields []string) ([]*dosa.FieldValuesOrError, error) {
   106  	connector, err := rc.getConnector(ei.Ref.Scope, ei.Ref.NamePrefix, "MultiRead")
   107  	if err != nil {
   108  		return nil, err
   109  	}
   110  	return connector.MultiRead(ctx, ei, values, minimumFields)
   111  }
   112  
   113  // Upsert selects corresponding connector
   114  func (rc *Connector) Upsert(ctx context.Context, ei *dosa.EntityInfo, values map[string]dosa.FieldValue) error {
   115  	connector, err := rc.getConnector(ei.Ref.Scope, ei.Ref.NamePrefix, "Upsert")
   116  	if err != nil {
   117  		return err
   118  	}
   119  	return connector.Upsert(ctx, ei, values)
   120  }
   121  
   122  // MultiUpsert selects corresponding connector
   123  func (rc *Connector) MultiUpsert(ctx context.Context, ei *dosa.EntityInfo, values []map[string]dosa.FieldValue) ([]error, error) {
   124  	connector, err := rc.getConnector(ei.Ref.Scope, ei.Ref.NamePrefix, "MultiUpsert")
   125  	if err != nil {
   126  		return nil, err
   127  	}
   128  	return connector.MultiUpsert(ctx, ei, values)
   129  }
   130  
   131  // Remove selects corresponding connector
   132  func (rc *Connector) Remove(ctx context.Context, ei *dosa.EntityInfo, values map[string]dosa.FieldValue) error {
   133  	connector, err := rc.getConnector(ei.Ref.Scope, ei.Ref.NamePrefix, "Remove")
   134  	if err != nil {
   135  		// here returns err because connector is not found
   136  		return err
   137  	}
   138  	// original remove method should never return err
   139  	return connector.Remove(ctx, ei, values)
   140  }
   141  
   142  // RemoveRange selects corresponding connector
   143  func (rc *Connector) RemoveRange(ctx context.Context, ei *dosa.EntityInfo, columnConditions map[string][]*dosa.Condition) error {
   144  	connector, err := rc.getConnector(ei.Ref.Scope, ei.Ref.NamePrefix, "RemoveRange")
   145  	if err != nil {
   146  		return err
   147  	}
   148  	return connector.RemoveRange(ctx, ei, columnConditions)
   149  }
   150  
   151  // MultiRemove selects corresponding connector
   152  func (rc *Connector) MultiRemove(ctx context.Context, ei *dosa.EntityInfo, multiValues []map[string]dosa.FieldValue) ([]error, error) {
   153  	connector, err := rc.getConnector(ei.Ref.Scope, ei.Ref.NamePrefix, "MultiRemove")
   154  	if err != nil {
   155  		return nil, err
   156  	}
   157  	return connector.MultiRemove(ctx, ei, multiValues)
   158  }
   159  
   160  // Range selects corresponding connector
   161  func (rc *Connector) Range(ctx context.Context, ei *dosa.EntityInfo, columnConditions map[string][]*dosa.Condition, minimumFields []string, token string, limit int) ([]map[string]dosa.FieldValue, string, error) {
   162  	connector, err := rc.getConnector(ei.Ref.Scope, ei.Ref.NamePrefix, "Range")
   163  	if err != nil {
   164  		return nil, "", err
   165  	}
   166  	return connector.Range(ctx, ei, columnConditions, minimumFields, token, limit)
   167  }
   168  
   169  // Scan selects corresponding connector
   170  func (rc *Connector) Scan(ctx context.Context, ei *dosa.EntityInfo, minimumFields []string, token string, limit int) ([]map[string]dosa.FieldValue, string, error) {
   171  	connector, err := rc.getConnector(ei.Ref.Scope, ei.Ref.NamePrefix, "Scan")
   172  	if err != nil {
   173  		return nil, "", err
   174  	}
   175  	return connector.Scan(ctx, ei, minimumFields, token, limit)
   176  }
   177  
   178  // CheckSchema calls selected connector
   179  func (rc *Connector) CheckSchema(ctx context.Context, scope, namePrefix string, ed []*dosa.EntityDefinition) (int32, error) {
   180  	connector, err := rc.getConnector(scope, namePrefix, "CheckSchema")
   181  	if err != nil {
   182  		return dosa.InvalidVersion, base.ErrNoMoreConnector{}
   183  	}
   184  	return connector.CheckSchema(ctx, scope, namePrefix, ed)
   185  }
   186  
   187  // UpsertSchema calls selected connector
   188  func (rc *Connector) UpsertSchema(ctx context.Context, scope, namePrefix string, ed []*dosa.EntityDefinition) (*dosa.SchemaStatus, error) {
   189  	connector, err := rc.getConnector(scope, namePrefix, "UpsertSchema")
   190  	if err != nil {
   191  		return nil, base.ErrNoMoreConnector{}
   192  	}
   193  	return connector.UpsertSchema(ctx, scope, namePrefix, ed)
   194  }
   195  
   196  // CheckSchemaStatus calls selected connector
   197  func (rc *Connector) CheckSchemaStatus(ctx context.Context, scope string, namePrefix string, version int32) (*dosa.SchemaStatus, error) {
   198  	connector, err := rc.getConnector(scope, namePrefix, "CheckSchemaStatus")
   199  	if err != nil {
   200  		return nil, base.ErrNoMoreConnector{}
   201  	}
   202  	return connector.CheckSchemaStatus(ctx, scope, namePrefix, version)
   203  }
   204  
   205  // CreateScope calls selected connector
   206  func (rc *Connector) CreateScope(ctx context.Context, scope string) error {
   207  	// will fall to default connector
   208  	connector, err := rc.getConnector(scope, "", "CreateScope")
   209  	if err != nil {
   210  		return base.ErrNoMoreConnector{}
   211  	}
   212  	return connector.CreateScope(ctx, scope)
   213  }
   214  
   215  // TruncateScope calls selected connector
   216  func (rc *Connector) TruncateScope(ctx context.Context, scope string) error {
   217  	// will fall to default connector
   218  	connector, err := rc.getConnector(scope, "", "TruncateScope")
   219  	if err != nil {
   220  		return base.ErrNoMoreConnector{}
   221  	}
   222  	return connector.TruncateScope(ctx, scope)
   223  }
   224  
   225  // DropScope calls selected connector
   226  func (rc *Connector) DropScope(ctx context.Context, scope string) error {
   227  	// will fall to default connector
   228  	connector, err := rc.getConnector(scope, "", "DropScope")
   229  	if err != nil {
   230  		return base.ErrNoMoreConnector{}
   231  	}
   232  	return connector.DropScope(ctx, scope)
   233  }
   234  
   235  // ScopeExists calls selected connector
   236  func (rc *Connector) ScopeExists(ctx context.Context, scope string) (bool, error) {
   237  	// will fall to default connector
   238  	connector, err := rc.getConnector(scope, "", "ScopeExists")
   239  	if err != nil {
   240  		return false, base.ErrNoMoreConnector{}
   241  	}
   242  	return connector.ScopeExists(ctx, scope)
   243  }
   244  
   245  // Shutdown shut down all connectors that routing connector talks to
   246  func (rc *Connector) Shutdown() error {
   247  	hasError := false
   248  	rConnErr := errors.New("failed to shutdown")
   249  	for _, c := range rc.connectors {
   250  		err := c.Shutdown()
   251  		if err != nil {
   252  			// save errors here, continue to shut down other connectors
   253  			hasError = true
   254  			err = errors.Wrap(rConnErr, err.Error())
   255  			continue
   256  		}
   257  	}
   258  
   259  	if hasError {
   260  		return rConnErr
   261  	}
   262  	return nil
   263  }