github.com/willyham/dosa@v2.3.1-0.20171024181418-1e446d37ee71+incompatible/connectors/yarpc/yarpc.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 yarpc
    22  
    23  import (
    24  	"context"
    25  	"fmt"
    26  	"strings"
    27  
    28  	"os"
    29  
    30  	"github.com/pkg/errors"
    31  	"github.com/uber-go/dosa"
    32  	"github.com/uber-go/dosa/connectors/base"
    33  	dosarpc "github.com/uber/dosa-idl/.gen/dosa"
    34  	"github.com/uber/dosa-idl/.gen/dosa/dosaclient"
    35  	rpc "go.uber.org/yarpc"
    36  	"go.uber.org/yarpc/api/transport"
    37  	"go.uber.org/yarpc/transport/http"
    38  	"go.uber.org/yarpc/transport/tchannel"
    39  )
    40  
    41  const (
    42  	_version                    = "version"
    43  	_defaultServiceName         = "dosa-gateway"
    44  	errCodeNotFound      int32  = 404
    45  	errCodeAlreadyExists int32  = 409
    46  	errInvalidHandler    string = "no handler for service"
    47  	errConnectionRefused string = "getsockopt: connection refused"
    48  )
    49  
    50  // ErrInvalidHandler is used to help deliver a better error message when
    51  // users have misconfigured the yarpc connector
    52  type ErrInvalidHandler struct {
    53  	service string
    54  	scope   string
    55  }
    56  
    57  // Error implements the error interface
    58  func (e *ErrInvalidHandler) Error() string {
    59  	return fmt.Sprintf("the gateway %q refused to handle scope %q; probably because of a mismatch between gateway and scope in your configuration or initialization", e.service, e.scope)
    60  }
    61  
    62  // ErrorIsInvalidHandler check if the error is "ErrInvalidHandler"
    63  func ErrorIsInvalidHandler(err error) bool {
    64  	return strings.Contains(err.Error(), errInvalidHandler)
    65  }
    66  
    67  // ErrConnectionRefused is used to help deliver a better error message when
    68  // users have misconfigured the yarpc connector
    69  type ErrConnectionRefused struct {
    70  	cause error
    71  }
    72  
    73  // Error implements the error interface
    74  func (e *ErrConnectionRefused) Error() string {
    75  	return fmt.Sprintf("the gateway is not reachable, make sure the hostname and port are correct for your environment: %s", e.cause)
    76  }
    77  
    78  // ErrorIsConnectionRefused check if the error is "ErrConnectionRefused"
    79  func ErrorIsConnectionRefused(err error) bool {
    80  	return strings.Contains(err.Error(), errConnectionRefused)
    81  }
    82  
    83  // Config contains the YARPC client parameters
    84  type Config struct {
    85  	Transport    string                  `yaml:"transport"`
    86  	Host         string                  `yaml:"host"`
    87  	Port         string                  `yaml:"port"`
    88  	CallerName   string                  `yaml:"callerName"`
    89  	ServiceName  string                  `yaml:"serviceName"`
    90  	ClientConfig *transport.ClientConfig `yaml:"clientConfig"`
    91  }
    92  
    93  // Connector holds the client-side RPC interface and some schema information
    94  type Connector struct {
    95  	base.Connector
    96  	Client     dosaclient.Interface
    97  	Config     *Config
    98  	dispatcher *rpc.Dispatcher
    99  }
   100  
   101  // NewConnectorWithTransport creates a new instance with user provided transport
   102  func NewConnectorWithTransport(cc transport.ClientConfig) *Connector {
   103  	client := dosaclient.New(cc)
   104  	config := &Config{
   105  		CallerName:   cc.Caller(),
   106  		ServiceName:  cc.Service(),
   107  		ClientConfig: &cc,
   108  	}
   109  	return &Connector{
   110  		Client: client,
   111  		Config: config,
   112  	}
   113  }
   114  
   115  // NewConnectorWithChannel creates a new instance using the given tchannel.
   116  // Note that the method refers to the YARPC tchannel interface which should
   117  // be satisfied in order to be used with this connector (and YARPC). Use this
   118  // if you are using a raw TChannel configuration instead of YARPC directly.
   119  func NewConnectorWithChannel(ch tchannel.Channel) (*Connector, error) {
   120  	ts, err := tchannel.NewChannelTransport(tchannel.WithChannel(ch))
   121  	if err != nil {
   122  		return nil, err
   123  	}
   124  	// use the channel's service name for "caller" and use default for outbound
   125  	// configuration since we just need a consistent mapping to get client.
   126  	ycfg := rpc.Config{
   127  		Name: ts.Channel().ServiceName(),
   128  		Outbounds: rpc.Outbounds{
   129  			_defaultServiceName: {
   130  				Unary: ts.NewOutbound(),
   131  			},
   132  		},
   133  	}
   134  
   135  	dispatcher := rpc.NewDispatcher(ycfg)
   136  	if err := dispatcher.Start(); err != nil {
   137  		// this should never happen since we're always providing sane defaults
   138  		return nil, err
   139  	}
   140  
   141  	client := dosaclient.New(dispatcher.ClientConfig(_defaultServiceName))
   142  	config := &Config{
   143  		CallerName:  ycfg.Name,
   144  		ServiceName: _defaultServiceName,
   145  	}
   146  	return &Connector{
   147  		Client:     client,
   148  		Config:     config,
   149  		dispatcher: dispatcher,
   150  	}, nil
   151  }
   152  
   153  // NewConnector returns a new YARPC connector with the given configuration.
   154  func NewConnector(cfg *Config) (*Connector, error) {
   155  	ycfg := rpc.Config{Name: cfg.CallerName}
   156  
   157  	// host and port are required
   158  	if cfg.Host == "" {
   159  		return nil, errors.New("invalid host")
   160  	}
   161  
   162  	if cfg.Port == "" {
   163  		return nil, errors.New("invalid port")
   164  	}
   165  
   166  	// service name is not required, defaults to "dosa-gateway"
   167  	if cfg.ServiceName == "" {
   168  		cfg.ServiceName = _defaultServiceName
   169  	}
   170  
   171  	switch cfg.Transport {
   172  	case "http":
   173  		uri := fmt.Sprintf("http://%s:%s", cfg.Host, cfg.Port)
   174  		ts := http.NewTransport()
   175  		ycfg.Outbounds = rpc.Outbounds{
   176  			cfg.ServiceName: {
   177  				Unary: ts.NewSingleOutbound(uri),
   178  			},
   179  		}
   180  	case "tchannel":
   181  		hostPort := fmt.Sprintf("%s:%s", cfg.Host, cfg.Port)
   182  		// this looks wrong, BUT since it's a uni-directional tchannel
   183  		// connection, we have to pass CallerName as the tchannel "ServiceName"
   184  		// for source/destination to be reported correctly by RPC layer.
   185  		ts, err := tchannel.NewChannelTransport(tchannel.ServiceName(cfg.CallerName))
   186  		if err != nil {
   187  			return nil, err
   188  		}
   189  		ycfg.Outbounds = rpc.Outbounds{
   190  			cfg.ServiceName: {
   191  				Unary: ts.NewSingleOutbound(hostPort),
   192  			},
   193  		}
   194  	default:
   195  		return nil, errors.New("invalid transport (only http or tchannel supported)")
   196  	}
   197  
   198  	// important to note that this will panic if config contains invalid
   199  	// values such as service name containing invalid characters
   200  	dispatcher := rpc.NewDispatcher(ycfg)
   201  	if err := dispatcher.Start(); err != nil {
   202  		return nil, err
   203  	}
   204  
   205  	client := dosaclient.New(dispatcher.ClientConfig(cfg.ServiceName))
   206  	return &Connector{
   207  		Client:     client,
   208  		Config:     cfg,
   209  		dispatcher: dispatcher,
   210  	}, nil
   211  }
   212  
   213  // CreateIfNotExists ...
   214  func (c *Connector) CreateIfNotExists(ctx context.Context, ei *dosa.EntityInfo, values map[string]dosa.FieldValue) error {
   215  	ev, err := fieldValueMapFromClientMap(values)
   216  	if err != nil {
   217  		return err
   218  	}
   219  	createRequest := dosarpc.CreateRequest{
   220  		Ref:          entityInfoToSchemaRef(ei),
   221  		EntityValues: ev,
   222  	}
   223  
   224  	err = c.Client.CreateIfNotExists(ctx, &createRequest, VersionHeader())
   225  	if err != nil {
   226  		if be, ok := err.(*dosarpc.BadRequestError); ok {
   227  			if be.ErrorCode != nil && *be.ErrorCode == errCodeAlreadyExists {
   228  				return errors.Wrap(&dosa.ErrAlreadyExists{}, "failed to create")
   229  			}
   230  		}
   231  	}
   232  	return errors.Wrap(err, "failed to create")
   233  }
   234  
   235  // Upsert inserts or updates your data
   236  func (c *Connector) Upsert(ctx context.Context, ei *dosa.EntityInfo, values map[string]dosa.FieldValue) error {
   237  	ev, err := fieldValueMapFromClientMap(values)
   238  	if err != nil {
   239  		return err
   240  	}
   241  	upsertRequest := dosarpc.UpsertRequest{
   242  		Ref:          entityInfoToSchemaRef(ei),
   243  		EntityValues: ev,
   244  	}
   245  	return c.Client.Upsert(ctx, &upsertRequest, VersionHeader())
   246  }
   247  
   248  // Read reads a single entity
   249  func (c *Connector) Read(ctx context.Context, ei *dosa.EntityInfo, keys map[string]dosa.FieldValue, minimumFields []string) (map[string]dosa.FieldValue, error) {
   250  	// Convert the fields from the client's map to a set of fields to read
   251  	var rpcMinimumFields map[string]struct{}
   252  	if minimumFields != nil {
   253  		rpcMinimumFields = map[string]struct{}{}
   254  		for _, field := range minimumFields {
   255  			rpcMinimumFields[field] = struct{}{}
   256  		}
   257  	}
   258  
   259  	// convert the key values from interface{} to RPC's Value
   260  	rpcFields := make(dosarpc.FieldValueMap)
   261  	for key, value := range keys {
   262  		rv, err := RawValueFromInterface(value)
   263  		if err != nil {
   264  			return nil, errors.Wrapf(err, "Key field %q", key)
   265  		}
   266  		if rv == nil {
   267  			continue
   268  		}
   269  		rpcValue := &dosarpc.Value{ElemValue: rv}
   270  		rpcFields[key] = rpcValue
   271  	}
   272  
   273  	// perform the read request
   274  	readRequest := &dosarpc.ReadRequest{
   275  		Ref:          entityInfoToSchemaRef(ei),
   276  		KeyValues:    rpcFields,
   277  		FieldsToRead: rpcMinimumFields,
   278  	}
   279  
   280  	response, err := c.Client.Read(ctx, readRequest, VersionHeader())
   281  	if err != nil {
   282  		if be, ok := err.(*dosarpc.BadRequestError); ok {
   283  			if be.ErrorCode != nil && *be.ErrorCode == errCodeNotFound {
   284  				return nil, errors.Wrap(&dosa.ErrNotFound{}, "Read failed: not found")
   285  			}
   286  		}
   287  		return nil, errors.Wrap(err, "Read failed")
   288  	}
   289  
   290  	// no error, so for each column, transform it into the map of (col->value) items
   291  
   292  	return decodeResults(ei, response.EntityValues), nil
   293  }
   294  
   295  // MultiRead reads multiple entities at one time
   296  func (c *Connector) MultiRead(ctx context.Context, ei *dosa.EntityInfo, keys []map[string]dosa.FieldValue, minimumFields []string) ([]*dosa.FieldValuesOrError, error) {
   297  	// Convert the fields from the client's map to a set of fields to read
   298  	rpcMinimumFields := makeRPCminimumFields(minimumFields)
   299  
   300  	// convert the keys to RPC's Value
   301  	rpcFields := make([]dosarpc.FieldValueMap, len(keys))
   302  	for i, kmap := range keys {
   303  		rpcFields[i] = make(dosarpc.FieldValueMap)
   304  		for key, value := range kmap {
   305  			rv, err := RawValueFromInterface(value)
   306  			if err != nil {
   307  				return nil, err
   308  			}
   309  			if rv == nil {
   310  				continue
   311  			}
   312  			rpcValue := &dosarpc.Value{ElemValue: rv}
   313  			rpcFields[i][key] = rpcValue
   314  		}
   315  	}
   316  
   317  	// perform the multi read request
   318  	request := &dosarpc.MultiReadRequest{
   319  		Ref:          entityInfoToSchemaRef(ei),
   320  		KeyValues:    rpcFields,
   321  		FieldsToRead: rpcMinimumFields,
   322  	}
   323  
   324  	response, err := c.Client.MultiRead(ctx, request, VersionHeader())
   325  	if err != nil {
   326  		return nil, errors.Wrap(err, "MultiRead failed")
   327  	}
   328  
   329  	rpcResults := response.Results
   330  	results := make([]*dosa.FieldValuesOrError, len(rpcResults))
   331  	for i, rpcResult := range rpcResults {
   332  		results[i] = &dosa.FieldValuesOrError{Values: make(map[string]dosa.FieldValue), Error: nil}
   333  		for name, value := range rpcResult.EntityValues {
   334  			for _, col := range ei.Def.Columns {
   335  				if col.Name == name {
   336  					results[i].Values[name] = RawValueAsInterface(*value.ElemValue, col.Type)
   337  					break
   338  				}
   339  			}
   340  		}
   341  		if rpcResult.Error != nil {
   342  			// TODO check other fields in the thrift error object such as ShouldRetry
   343  			results[i].Error = errors.New(*rpcResult.Error.Msg)
   344  		}
   345  	}
   346  
   347  	return results, nil
   348  }
   349  
   350  // MultiUpsert is not yet implemented
   351  func (c *Connector) MultiUpsert(ctx context.Context, ei *dosa.EntityInfo, multiValues []map[string]dosa.FieldValue) ([]error, error) {
   352  	panic("not implemented")
   353  }
   354  
   355  // Remove marshals a request to the YARPC remove call
   356  func (c *Connector) Remove(ctx context.Context, ei *dosa.EntityInfo, keys map[string]dosa.FieldValue) error {
   357  	// convert the key values from interface{} to RPC's Value
   358  	rpcFields := make(dosarpc.FieldValueMap)
   359  	for key, value := range keys {
   360  		rv, err := RawValueFromInterface(value)
   361  		if err != nil {
   362  			return errors.Wrapf(err, "Key field %q", key)
   363  		}
   364  		if rv == nil {
   365  			continue
   366  		}
   367  		rpcValue := &dosarpc.Value{ElemValue: rv}
   368  		rpcFields[key] = rpcValue
   369  	}
   370  
   371  	// perform the remove request
   372  	removeRequest := &dosarpc.RemoveRequest{
   373  		Ref:       entityInfoToSchemaRef(ei),
   374  		KeyValues: rpcFields,
   375  	}
   376  
   377  	err := c.Client.Remove(ctx, removeRequest, VersionHeader())
   378  	if err != nil {
   379  		return errors.Wrap(err, "Remove failed")
   380  	}
   381  	return nil
   382  }
   383  
   384  // RemoveRange removes all entities within the range specified by the columnConditions.
   385  func (c *Connector) RemoveRange(ctx context.Context, ei *dosa.EntityInfo, columnConditions map[string][]*dosa.Condition) error {
   386  	rpcConditions, err := createRPCConditions(columnConditions)
   387  	if err != nil {
   388  		return errors.Wrap(err, "RemoveRange failed: invalid column conditions")
   389  	}
   390  
   391  	request := &dosarpc.RemoveRangeRequest{
   392  		Ref:        entityInfoToSchemaRef(ei),
   393  		Conditions: rpcConditions,
   394  	}
   395  
   396  	if err := c.Client.RemoveRange(ctx, request, VersionHeader()); err != nil {
   397  		return errors.Wrap(err, "RemoveRange failed")
   398  	}
   399  	return nil
   400  }
   401  
   402  // MultiRemove is not yet implemented
   403  func (c *Connector) MultiRemove(ctx context.Context, ei *dosa.EntityInfo, multiKeys []map[string]dosa.FieldValue) ([]error, error) {
   404  	panic("not implemented")
   405  }
   406  
   407  // Range does a scan across a range
   408  func (c *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) {
   409  	limit32 := int32(limit)
   410  	rpcMinimumFields := makeRPCminimumFields(minimumFields)
   411  	rpcConditions, err := createRPCConditions(columnConditions)
   412  	if err != nil {
   413  		return nil, "", errors.Wrap(err, "Range failed: invalid column conditions")
   414  	}
   415  	rangeRequest := dosarpc.RangeRequest{
   416  		Ref:          entityInfoToSchemaRef(ei),
   417  		Token:        &token,
   418  		Limit:        &limit32,
   419  		Conditions:   rpcConditions,
   420  		FieldsToRead: rpcMinimumFields,
   421  	}
   422  	response, err := c.Client.Range(ctx, &rangeRequest, VersionHeader())
   423  	if err != nil {
   424  		return nil, "", errors.Wrap(err, "Range failed")
   425  	}
   426  	results := []map[string]dosa.FieldValue{}
   427  	for _, entity := range response.Entities {
   428  		results = append(results, decodeResults(ei, entity))
   429  	}
   430  	return results, *response.NextToken, nil
   431  }
   432  
   433  func createRPCConditions(columnConditions map[string][]*dosa.Condition) ([]*dosarpc.Condition, error) {
   434  	rpcConditions := []*dosarpc.Condition{}
   435  	for field, conditions := range columnConditions {
   436  		// Warning: Don't remove this line.
   437  		// field variable always has the same address. If we want to dereference it, we have to assign the value to a new variable.
   438  		fieldName := field
   439  		for _, condition := range conditions {
   440  			rv, err := RawValueFromInterface(condition.Value)
   441  			if err != nil {
   442  				return nil, errors.Wrap(err, "Bad range value")
   443  			}
   444  			if rv == nil {
   445  				continue
   446  			}
   447  			rpcConditions = append(rpcConditions, &dosarpc.Condition{
   448  				Op:    encodeOperator(condition.Op),
   449  				Field: &dosarpc.Field{Name: &fieldName, Value: &dosarpc.Value{ElemValue: rv}},
   450  			})
   451  		}
   452  	}
   453  
   454  	return rpcConditions, nil
   455  }
   456  
   457  // Scan marshals a scan request into YARPC
   458  func (c *Connector) Scan(ctx context.Context, ei *dosa.EntityInfo, minimumFields []string, token string, limit int) ([]map[string]dosa.FieldValue, string, error) {
   459  	limit32 := int32(limit)
   460  	rpcMinimumFields := makeRPCminimumFields(minimumFields)
   461  	scanRequest := dosarpc.ScanRequest{
   462  		Ref:          entityInfoToSchemaRef(ei),
   463  		Token:        &token,
   464  		Limit:        &limit32,
   465  		FieldsToRead: rpcMinimumFields,
   466  	}
   467  	response, err := c.Client.Scan(ctx, &scanRequest, VersionHeader())
   468  	if err != nil {
   469  		return nil, "", errors.Wrap(err, "Scan failed")
   470  	}
   471  	results := []map[string]dosa.FieldValue{}
   472  	for _, entity := range response.Entities {
   473  		results = append(results, decodeResults(ei, entity))
   474  	}
   475  	return results, *response.NextToken, nil
   476  }
   477  
   478  // CheckSchema is one way to register a set of entities. This can be further validated by
   479  // a schema service downstream.
   480  func (c *Connector) CheckSchema(ctx context.Context, scope, namePrefix string, eds []*dosa.EntityDefinition) (int32, error) {
   481  	// convert the client EntityDefinition to the RPC EntityDefinition
   482  	rpcEntityDefinition := make([]*dosarpc.EntityDefinition, len(eds))
   483  	for i, ed := range eds {
   484  		rpcEntityDefinition[i] = EntityDefinitionToThrift(ed)
   485  	}
   486  	csr := dosarpc.CheckSchemaRequest{EntityDefs: rpcEntityDefinition, Scope: &scope, NamePrefix: &namePrefix}
   487  
   488  	response, err := c.Client.CheckSchema(ctx, &csr, VersionHeader())
   489  
   490  	if err != nil {
   491  		return dosa.InvalidVersion, wrapError(err, "CheckSchema failed", scope, c.Config.ServiceName)
   492  	}
   493  
   494  	return *response.Version, nil
   495  }
   496  
   497  // UpsertSchema upserts the schema through RPC
   498  func (c *Connector) UpsertSchema(ctx context.Context, scope, namePrefix string, eds []*dosa.EntityDefinition) (*dosa.SchemaStatus, error) {
   499  	rpcEds := make([]*dosarpc.EntityDefinition, len(eds))
   500  	for i, ed := range eds {
   501  		rpcEds[i] = EntityDefinitionToThrift(ed)
   502  	}
   503  
   504  	request := &dosarpc.UpsertSchemaRequest{
   505  		Scope:      &scope,
   506  		NamePrefix: &namePrefix,
   507  		EntityDefs: rpcEds,
   508  	}
   509  
   510  	response, err := c.Client.UpsertSchema(ctx, request, VersionHeader())
   511  	if err != nil {
   512  		return nil, wrapError(err, "UpsertSchema failed", scope, c.Config.ServiceName)
   513  	}
   514  
   515  	status := ""
   516  	if response.Status != nil {
   517  		status = *response.Status
   518  	}
   519  
   520  	if response.Version == nil {
   521  		return nil, errors.New("UpsertSchema failed: server returns version nil")
   522  	}
   523  
   524  	return &dosa.SchemaStatus{
   525  		Version: *response.Version,
   526  		Status:  status,
   527  	}, nil
   528  }
   529  
   530  // CheckSchemaStatus checks the status of specific version of schema
   531  func (c *Connector) CheckSchemaStatus(ctx context.Context, scope, namePrefix string, version int32) (*dosa.SchemaStatus, error) {
   532  	request := dosarpc.CheckSchemaStatusRequest{Scope: &scope, NamePrefix: &namePrefix, Version: &version}
   533  	response, err := c.Client.CheckSchemaStatus(ctx, &request, VersionHeader())
   534  
   535  	if err != nil {
   536  		return nil, wrapError(err, "ChecksShemaStatus failed", scope, c.Config.ServiceName)
   537  	}
   538  
   539  	status := ""
   540  	if response.Status != nil {
   541  		status = *response.Status
   542  	}
   543  
   544  	if response.Version == nil {
   545  		return nil, errors.New("ChecksShemaStatus failed: server returns version nil")
   546  	}
   547  
   548  	return &dosa.SchemaStatus{
   549  		Version: *response.Version,
   550  		Status:  status,
   551  	}, nil
   552  }
   553  
   554  // CreateScope creates the scope specified
   555  func (c *Connector) CreateScope(ctx context.Context, scope string) error {
   556  	request := &dosarpc.CreateScopeRequest{
   557  		Name: &scope,
   558  	}
   559  
   560  	if err := c.Client.CreateScope(ctx, request, VersionHeader()); err != nil {
   561  		return errors.Wrap(err, "CreateScope failed")
   562  	}
   563  
   564  	return nil
   565  }
   566  
   567  // TruncateScope truncates all data in the scope specified
   568  func (c *Connector) TruncateScope(ctx context.Context, scope string) error {
   569  	request := &dosarpc.TruncateScopeRequest{
   570  		Name: &scope,
   571  	}
   572  
   573  	if err := c.Client.TruncateScope(ctx, request, VersionHeader()); err != nil {
   574  		return errors.Wrap(err, "TruncateScope failed")
   575  	}
   576  
   577  	return nil
   578  }
   579  
   580  // DropScope removes the scope specified
   581  func (c *Connector) DropScope(ctx context.Context, scope string) error {
   582  	request := &dosarpc.DropScopeRequest{
   583  		Name: &scope,
   584  	}
   585  
   586  	if err := c.Client.DropScope(ctx, request, VersionHeader()); err != nil {
   587  		return errors.Wrap(err, "DropScope failed")
   588  	}
   589  
   590  	return nil
   591  }
   592  
   593  // ScopeExists is not implemented yet
   594  func (c *Connector) ScopeExists(ctx context.Context, scope string) (bool, error) {
   595  	panic("not implemented")
   596  }
   597  
   598  // Shutdown stops the dispatcher and drains client
   599  func (c *Connector) Shutdown() error {
   600  	// instances w/ mocked client shouldn't require a dispatcher
   601  	if c.dispatcher == nil {
   602  		return nil
   603  	}
   604  	return c.dispatcher.Stop()
   605  }
   606  
   607  func wrapError(err error, message, scope, service string) error {
   608  	if ErrorIsInvalidHandler(err) {
   609  		err = &ErrInvalidHandler{scope: scope, service: service}
   610  	}
   611  	if ErrorIsConnectionRefused(err) {
   612  		err = &ErrConnectionRefused{err}
   613  	}
   614  	return errors.Wrap(err, message)
   615  }
   616  
   617  func getWithDefault(args map[string]interface{}, elem string, def string) string {
   618  	v, ok := args[elem]
   619  	if ok {
   620  		return v.(string)
   621  	}
   622  	return def
   623  
   624  }
   625  
   626  func init() {
   627  	dosa.RegisterConnector("yarpc", func(args dosa.CreationArgs) (dosa.Connector, error) {
   628  		host, ok := args["host"]
   629  		if !ok {
   630  			return nil, errors.New("Missing connector host value")
   631  		}
   632  
   633  		port, ok := args["port"]
   634  		if !ok {
   635  			return nil, errors.New("Missing connector port value")
   636  		}
   637  
   638  		trans := getWithDefault(args, "transport", "tchannel")
   639  		callername := getWithDefault(args, "callername", os.Getenv("USER"))
   640  		servicename := getWithDefault(args, "servicename", "test")
   641  		cfg := Config{
   642  			Transport:   trans,
   643  			Host:        host.(string),
   644  			Port:        port.(string),
   645  			CallerName:  callername,
   646  			ServiceName: servicename,
   647  		}
   648  		return NewConnector(&cfg)
   649  	})
   650  }