...

Package client

import "golang.conradwood.net/go-easyops/client"
Overview
Index
Subdirectories

Overview ▾

This package facilitates making load-balanced, fail-over, authenticated gRPC calls to server. (it also provides shortcuts to objectstore get/put)

Typically, in the yacloud, a new client is constructed via the proto package. For example

import "golang.conradwood.net/apis/getestservice"
...
getestservice.GetEchoClient()...

Load balancing

The client will maintain a list of available targets for grpc calls. Each service has a unique list of targets. The list is periodically and aysnchronously updated by polling the registry.

If no targets are available, a call to this service will be blocked for some time, then fail. Once failed, all subsequent calls will be failed immediately until a target becomes available. This allows for some basic recovery for circular service dependencies on boot. Whilst it is considered bad practice, it is a pattern commonly found and thus go-easyops attempts to make it work as well as can be expected. (Better to avoid circular dependencies altogether!!)

RPC calls are not retried - if they fail (for any reason) they will not be sent to a different server.

Routing

This client implements several and distinct features to determine where to route rpc calls too.

  • Round-Robin for multiple targets
  • By User: to user specific services
  • By Context-Tag: arbitrary tags in the context

In the absense of both user specific services and context-tags, a simple round-robin strategy is implemented for multiple targets.

Routing - by user

A service may be registered with a useraccount instead of a service account (the default if started by a user on the command line. Also see command line flag -ge_disable_user_token). The client determines the current useraccount from the context used to invoke the target. If a service running as the same user as is in the context, the client will route the rpc call to the service running as this user.

This is intended for debugging and "live" development. The user object is typically created and propagated and the edge of the system, for example at the webserver proxy. Thus, while developing or debugging any rpc server it is often useful to route some (and only some) calls into the development version. Developers should always start their work-in-progress servers under their own useraccount. Thus all calls the Developer makes are routed to their laptop. Subsequent calls go back into the infrastructure, but remain restricted to the useraccount, thus can be considered safe (subject to bugs in the backends of course).

Note: this is not intended for general production use. For various reasons, it is a really bad idea to fire up rpc servers specific to each user. Instead the rpc server should handle multiple users. The user's token for authentication is considered private, like a password.

Routing - by Context-Tag

A context-tag is a key with a value .

A service may register itself with one or more tags. A Context may carry one or more tags. On each rpc call the list of targets is iterated through. Any service that has all tags with exactly matching values will be considered for routing, all others dismissed. If after, the first iteration, one or more services remain, those will be used for routing in a round-robin fashion.

If no exact match is found, the context tags are inspected for "FallbackToPlain" option (see ctx package). If set, the list of all services with exactly 0 tags will be used for round-robin. If not set the rpc will be failed with "no target available".

Note: Whilst context-tags are often quite useful, their use is generally discouraged, especially for large sets of servers. It is intented to be used for a standardized, quick and reasonably efficient means to route low-volume (~20/s or less) calls to remote rpc servers. In small setups this can often be useful to send information to remote clusters for remote-processing. (using a tag, for example, cluster=lhr, cluster=fra, cluster=lgw, cluster=cdn etc...)

Routing - directly

One can bypass the fail-over and connection management altogether with functions such as ConnectWithIP. This is intented for circumstances where a standardised approach (round-robin/user/context) is unsuitable. Beware of dragons: This approach requires development of load-balancers, fail-over strategies, monitoring, recovery, start-up delays and many other features the default routing strategy implements. The complexity is often underestimated, but soon does become significant. (That is the point of the routing implementations above, really)

Standalone operation

Standalone operation means that no other services are required to make rpc calls between a client and server. Whilst this is very limited (e.g. no load-balancing, no fail-over and NO AUTHENTICATION, it is useful for quickly testing a binary). Both, server and client must be started in standalone mode.

Also see command line parameter -ge_standalone and package server

ObjectStore

Mostly because an extra package for objectstore seems overkill, this package also provides function to get/put objects into the objectstore.

Index ▾

Constants
func ClientMetricsUnaryInterceptor(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error
func Connect(serviceNameOrPath string) *grpc.ClientConn
func ConnectAt(registryadr string, serviceNameOrPath string) *grpc.ClientConn
func ConnectAtNoAuth(registryadr string, serviceNameOrPath string) *grpc.ClientConn
func ConnectWithIP(ip string) (*grpc.ClientConn, error)
func ConnectWithIPNoBlock(ip string) (*grpc.ClientConn, error)
func CustomDialer(ctx context.Context, name string) (net.Conn, error)
func DIS_OutboundContext(inbound context.Context) context.Context
func DialTCPWrapper(serviceName string) (net.Conn, error)
func Evict(ctx context.Context, key string) ([]byte, error)
func EvictNoResult(ctx context.Context, key string) error
func Get(ctx context.Context, key string) ([]byte, error)
func GetClientCreds() credentials.TransportCredentials
func GetConnectedInstanceCount(servicelookupid string) int
func GetDependencies() []string
func GetObjectStoreClient() os.ObjectStoreClient
func GetRegistryClient() pb.RegistryClient
func GetSignatureFromAuth()
func GotSig() bool
func PutWithID(ctx context.Context, key string, buf []byte) error
func PutWithIDAndExpiry(ctx context.Context, key string, buf []byte, expiry time.Time) error
func RegisterDependency(name string)
type FancyAddr
    func (fa *FancyAddr) Address() string
    func (fa *FancyAddr) Connection() (*grpc.ClientConn, error)
    func (fa *FancyAddr) IsReady() bool
    func (fa *FancyAddr) Key() string
    func (fa *FancyAddr) String() string
type FancyAddressList
    func ConnectNoBalance(serviceNameOrPath string) (*FancyAddressList, error)
    func ConnectNoBalanceAt(registryadr string, serviceNameOrPath string) (*FancyAddressList, error)
    func GetAllFancyAddressLists() []*FancyAddressList
    func NewFancyAddressListWithResolver(servicename string, resolver func(registryadr, servicename string) ([]*registry.Target, error)) (*FancyAddressList, error)
    func (fal *FancyAddressList) Add(f *FancyAddr)
    func (fal *FancyAddressList) AllAddresses() []*FancyAddr
    func (fal *FancyAddressList) AllReadyAddresses() []*FancyAddr
    func (fal *FancyAddressList) ByAddr(adr string) *FancyAddr
    func (fal *FancyAddressList) ByKey(key string) *FancyAddr
    func (fal *FancyAddressList) ByMatchingTags(tags map[string]string) []*FancyAddr
    func (fal *FancyAddressList) ByNoUserRoutingInfo() []*FancyAddr
    func (fal *FancyAddressList) BySubCon(sc balancer.SubConn) *FancyAddr
    func (fal *FancyAddressList) ByUser(userid string) []*FancyAddr
    func (fal *FancyAddressList) ByWithoutTags() []*FancyAddr
    func (fal *FancyAddressList) Count() int
    func (fal *FancyAddressList) IsEmpty() bool
    func (fal *FancyAddressList) RequiredList(addresses []resolver.Address) []*FancyAddr
    func (fal *FancyAddressList) SelectValid(ctx context.Context) []*FancyAddr
    func (fal *FancyAddressList) ServiceName() string
    func (fal *FancyAddressList) Updated()
type FancyBalancer
    func (f *FancyBalancer) Check()
    func (f *FancyBalancer) Close()
    func (f *FancyBalancer) ExitIdle()
    func (f *FancyBalancer) HandleResolvedAddrs(addresses []resolver.Address, err error)
    func (f *FancyBalancer) HandleSubConnStateChange(sc balancer.SubConn, state connectivity.State)
    func (f *FancyBalancer) Picker() *FancyPicker
    func (f *FancyBalancer) ResolverError(err error)
    func (f *FancyBalancer) ServiceName() string
    func (f *FancyBalancer) UpdateClientConnState(bc balancer.ClientConnState) error
    func (f *FancyBalancer) UpdateSubConnState(sc balancer.SubConn, bc balancer.SubConnState)
type FancyBuilder
    func (f *FancyBuilder) Build(cc balancer.ClientConn, opts balancer.BuildOptions) balancer.Balancer
    func (f *FancyBuilder) Name() string
    func (f *FancyBuilder) ServiceName() string
type FancyPicker
    func (f *FancyPicker) Pick(info balancer.PickInfo) (balancer.PickResult, error)
    func (f *FancyPicker) ServiceName() string
type FancyResolver
    func (f *FancyResolver) ActionResolve()
    func (f *FancyResolver) Close()
    func (f *FancyResolver) ResolveNow(opts resolver.ResolveNowOptions)
    func (f *FancyResolver) ServiceName() string
type FancyResolverBuilder
    func (f *FancyResolverBuilder) Build(target resolver.Target, cc resolver.ClientConn, opts resolver.BuildOptions) (resolver.Resolver, error)
    func (f *FancyResolverBuilder) Scheme() string
    func (f *FancyResolverBuilder) ServiceName() string
type ProxyTarget
    func GetProxyTarget(ctx context.Context, serviceid string) (*ProxyTarget, error)
    func (p *ProxyTarget) Start(c net.Conn) error
type TargetList
    func NewTargetList(servicename string) *TargetList
    func (t *TargetList) ByAddress(address string) []*TargetWithConnection
    func (t *TargetList) Connections() []*grpc.ClientConn
    func (t *TargetList) Targets() []*TargetWithConnection
type TargetWithConnection
    func (t *TargetWithConnection) Connection() *grpc.ClientConn
    func (t *TargetWithConnection) String() string

Package files

client.go client_metrics.go client_stream_interceptor.go compat.go connect.go create_context.go dialer.go fancy_addresslist.go fancy_addresslist_custom.go fancy_balancer.go fancy_helper.go fancy_picker.go fancy_querier.go fancy_resolver.go get_sig.go imported_services.go objectstore.go targetlist.go unary_interceptor.go

Constants

const (
    DIRECT_PREFIX = "direct://"
    PROXY_PREFIX  = "proxy://"
)
const (
    // if we have a long rpc call (e.g. 3 seconds)
    // then we can run into timeouts, during the
    // interceptor auth phase
    // imho - the auth should be handled by "normal"
    // loadbalancer function
    // (it seems) cnw 19/5/2018
    CONST_CALL_TIMEOUT = 4
)
const (
    REFRESH = time.Duration(5) * time.Second
)
const (
    RESOLVER_ATTRIBUTE_SERVICE_ADDRESS = "service_address"
)

func ClientMetricsUnaryInterceptor

func ClientMetricsUnaryInterceptor(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error

called for each outbound rpc

func Connect

func Connect(serviceNameOrPath string) *grpc.ClientConn

func ConnectAt

func ConnectAt(registryadr string, serviceNameOrPath string) *grpc.ClientConn

convenience method to get a loadbalanced connection to a service use path or servicename (path prefered, it contains the version) unless it successfullly connects it will NOT return (it will either terminate the process or loop)

func ConnectAtNoAuth

func ConnectAtNoAuth(registryadr string, serviceNameOrPath string) *grpc.ClientConn

connect to a service which we KNOW requires no authentication and no signature. it is public because of implementation details, but should not be used by clients of goeasyops

func ConnectWithIP

func ConnectWithIP(ip string) (*grpc.ClientConn, error)

opens a tcp connection to an ip:port (ip syntax matches argument to net.Dial())

func ConnectWithIPNoBlock

func ConnectWithIPNoBlock(ip string) (*grpc.ClientConn, error)

opens a tcp connection to an ip (no loadbalancing obviously)

func CustomDialer

func CustomDialer(ctx context.Context, name string) (net.Conn, error)

this is called by grpc to get a connection

func DIS_OutboundContext

func DIS_OutboundContext(inbound context.Context) context.Context

given an inbound Context (e.g. in an RPC call) this creates a new outbound context suitable to call other servers we keep user & org intact. we add/override 'service' token (with our token)

func DialTCPWrapper

func DialTCPWrapper(serviceName string) (net.Conn, error)

opens a tcp connection to a servicename

func Evict

func Evict(ctx context.Context, key string) ([]byte, error)

evict (remove) an object from the objectstore by key

func EvictNoResult

func EvictNoResult(ctx context.Context, key string) error

evict (remove) an object from the objectstore by key

func Get

func Get(ctx context.Context, key string) ([]byte, error)

get an object from the objectstore by key

func GetClientCreds

func GetClientCreds() credentials.TransportCredentials

get the Client Credentials we use to connect to other RPCs

func GetConnectedInstanceCount

func GetConnectedInstanceCount(servicelookupid string) int

get instances for a service currently being connected to (that is, it will return 0 for services which have not been dialled (yet)

func GetDependencies

func GetDependencies() []string

func GetObjectStoreClient

func GetObjectStoreClient() os.ObjectStoreClient

func GetRegistryClient

func GetRegistryClient() pb.RegistryClient

func GetSignatureFromAuth

func GetSignatureFromAuth()

func GotSig

func GotSig() bool

cannot use init function, because flags might not be initialised (for example registry flag)

func init() {
	GetSignatureFromAuth()
}

func PutWithID

func PutWithID(ctx context.Context, key string, buf []byte) error

func PutWithIDAndExpiry

func PutWithIDAndExpiry(ctx context.Context, key string, buf []byte, expiry time.Time) error

func RegisterDependency

func RegisterDependency(name string)

type FancyAddr

type FancyAddr struct {
    Target *registry.Target
    // contains filtered or unexported fields
}

func (*FancyAddr) Address

func (fa *FancyAddr) Address() string

address, including port, e.g. 10.1.1.1:6000

func (*FancyAddr) Connection

func (fa *FancyAddr) Connection() (*grpc.ClientConn, error)

func (*FancyAddr) IsReady

func (fa *FancyAddr) IsReady() bool

return true if this is _actually_ available. e.g. a TCP reset will cause this connection to be "not ready", but still be listed in the registry and caches

func (*FancyAddr) Key

func (fa *FancyAddr) Key() string

a key that can be used in maps to find this particular fancyaddress.

func (*FancyAddr) String

func (fa *FancyAddr) String() string

type FancyAddressList

this is essentially a list of addresses. the balancer removes/adds/updates addresses and the picker reads/chooses/sorts them. this struct synchronises access between them.

The list is semi-uptodate, that means, it is cached, but updated if go-easyops determines that the registry has better information than its cache. The Addresses in this list are still subject to the filtering done in the registry. The Registry "prefers" certain targets, for example, higher buildids

type FancyAddressList struct {
    Name string
    // contains filtered or unexported fields
}

func ConnectNoBalance

func ConnectNoBalance(serviceNameOrPath string) (*FancyAddressList, error)

func ConnectNoBalanceAt

func ConnectNoBalanceAt(registryadr string, serviceNameOrPath string) (*FancyAddressList, error)

this initiates a balancer for a service and returns an address list. this is not actually balanced, but the fancyaddresslist does maintain the list of active targets.

func GetAllFancyAddressLists

func GetAllFancyAddressLists() []*FancyAddressList

func NewFancyAddressListWithResolver

func NewFancyAddressListWithResolver(servicename string, resolver func(registryadr, servicename string) ([]*registry.Target, error)) (*FancyAddressList, error)

this creates a fancy address list, which does not automatically maintain its list of targets. it also does not (and cannot) be used for loadbalancing, because it has no connection to a grpc LoadBalancer or Picker. sometimes maybe might need a custom resolver for a fancy address list.

func (*FancyAddressList) Add

func (fal *FancyAddressList) Add(f *FancyAddr)

perhaps should check/panic on duplicates here?

func (*FancyAddressList) AllAddresses

func (fal *FancyAddressList) AllAddresses() []*FancyAddr

return all addresses the fancyaddresslist knows about.

func (*FancyAddressList) AllReadyAddresses

func (fal *FancyAddressList) AllReadyAddresses() []*FancyAddr

return all "ready" addresses (those with a TCP connection in ready state)

func (*FancyAddressList) ByAddr

func (fal *FancyAddressList) ByAddr(adr string) *FancyAddr

func (*FancyAddressList) ByKey

func (fal *FancyAddressList) ByKey(key string) *FancyAddr

returns the fancyaddress that matches the key. see fancyaddress.Key(). this might return nil if no such fancyaddress is known (any more)

func (*FancyAddressList) ByMatchingTags

func (fal *FancyAddressList) ByMatchingTags(tags map[string]string) []*FancyAddr

func (*FancyAddressList) ByNoUserRoutingInfo

func (fal *FancyAddressList) ByNoUserRoutingInfo() []*FancyAddr

get all those without routinginfo or no routinginfo.user

func (*FancyAddressList) BySubCon

func (fal *FancyAddressList) BySubCon(sc balancer.SubConn) *FancyAddr

func (*FancyAddressList) ByUser

func (fal *FancyAddressList) ByUser(userid string) []*FancyAddr

get all those with a routinginfo RunningAs user

func (*FancyAddressList) ByWithoutTags

func (fal *FancyAddressList) ByWithoutTags() []*FancyAddr

return addresses with 0 tags

func (*FancyAddressList) Count

func (fal *FancyAddressList) Count() int

func (*FancyAddressList) IsEmpty

func (fal *FancyAddressList) IsEmpty() bool

func (*FancyAddressList) RequiredList

func (fal *FancyAddressList) RequiredList(addresses []resolver.Address) []*FancyAddr

removes all addresses which are NOT in the array and returns the removed ones

func (*FancyAddressList) SelectValid

func (fal *FancyAddressList) SelectValid(ctx context.Context) []*FancyAddr
this is called for _every_ rpc call. it should be performance optimised
this returns a list of addresses for the picker to pick from.
this function is what the picker calls. if loadbalancing is implemented by the user, this
function should be used

the rules are: First: Never return any addresses which are not in connectivty state READY. Then from the remaining addresses (in ready state), follow these rules: 1. If we have 0 addresses with routinginfo for a user, return all. 2. if context has no user, return those without routinginfo.user 3. if 1 or more addresses have a routinginfo.user that matches user in context, return only those

  1. otherwise return those without routinguser.info

func (*FancyAddressList) ServiceName

func (fal *FancyAddressList) ServiceName() string

servicename, e.g. "registry.Registry"

func (*FancyAddressList) Updated

func (fal *FancyAddressList) Updated()

called by the balancer when a FancyAddr has been updated. (or anyone updating FancyAddr) we may need to clear some caches (now or in future...)

type FancyBalancer

type FancyBalancer struct {
    // contains filtered or unexported fields
}

func (*FancyBalancer) Check

func (f *FancyBalancer) Check()

periodically called by go routine, checks if it's blocking for too long

func (*FancyBalancer) Close

func (f *FancyBalancer) Close()

Close closes the balancer. The balancer is not required to call ClientConn.RemoveSubConn for its existing SubConns.

func (*FancyBalancer) ExitIdle

func (f *FancyBalancer) ExitIdle()

func (*FancyBalancer) HandleResolvedAddrs

func (f *FancyBalancer) HandleResolvedAddrs(addresses []resolver.Address, err error)

DEPRECATED - old version of grpc HandleResolvedAddrs is called by gRPC to send updated resolved addresses to balancers. Balancer can create new SubConn or remove SubConn with the addresses. An empty address slice and a non-nil error will be passed if the resolver returns non-nil error to gRPC. Note that each address MUST have an attribute with a registry.ServiceAddress because we cannot transport all the information in just ip/port

func (*FancyBalancer) HandleSubConnStateChange

func (f *FancyBalancer) HandleSubConnStateChange(sc balancer.SubConn, state connectivity.State)

DEPRECATED - old version of grpc HandleSubConnStateChange is called by gRPC when the connectivity state of sc has changed. Balancer is expected to aggregate all the state of SubConn and report that back to gRPC. Balancer should also generate and update Pickers when its internal state has been changed by the new state.

func (*FancyBalancer) Picker

func (f *FancyBalancer) Picker() *FancyPicker

create a new picker

func (*FancyBalancer) ResolverError

func (f *FancyBalancer) ResolverError(err error)

EXPERIMENTAL: this is the new-style grpc callback

func (*FancyBalancer) ServiceName

func (f *FancyBalancer) ServiceName() string

func (*FancyBalancer) UpdateClientConnState

func (f *FancyBalancer) UpdateClientConnState(bc balancer.ClientConnState) error

EXPERIMENTAL: this is the new-style grpc callback, called by the resolver when a state changes it feeds us new addresses

func (*FancyBalancer) UpdateSubConnState

func (f *FancyBalancer) UpdateSubConnState(sc balancer.SubConn, bc balancer.SubConnState)

EXPERIMENTAL: this is the new-style grpc callback

type FancyBuilder

********** the builder for our balancer ****************

type FancyBuilder struct {
}

func (*FancyBuilder) Build

func (f *FancyBuilder) Build(cc balancer.ClientConn, opts balancer.BuildOptions) balancer.Balancer

Build creates a new balancer for the (new) ClientConn.

func (*FancyBuilder) Name

func (f *FancyBuilder) Name() string

Name returns the name of balancers built by this builder. It will be used to pick balancers (for example in service config).

func (*FancyBuilder) ServiceName

func (f *FancyBuilder) ServiceName() string

type FancyPicker

type FancyPicker struct {
    // contains filtered or unexported fields
}

func (*FancyPicker) Pick

func (f *FancyPicker) Pick(info balancer.PickInfo) (balancer.PickResult, error)

Pick returns the connection to use for this RPC and related information.

Pick should not block. If the balancer needs to do I/O or any blocking or time-consuming work to service this call, it should return ErrNoSubConnAvailable, and the Pick call will be repeated by gRPC when the Picker is updated (using ClientConn.UpdateState).

If an error is returned:

func (*FancyPicker) ServiceName

func (f *FancyPicker) ServiceName() string

type FancyResolver

type FancyResolver struct {
    // contains filtered or unexported fields
}

func (*FancyResolver) ActionResolve

func (f *FancyResolver) ActionResolve()

called sync by the resolver_thread

func (*FancyResolver) Close

func (f *FancyResolver) Close()

func (*FancyResolver) ResolveNow

func (f *FancyResolver) ResolveNow(opts resolver.ResolveNowOptions)

func (*FancyResolver) ServiceName

func (f *FancyResolver) ServiceName() string

type FancyResolverBuilder

type FancyResolverBuilder struct {
}

func (*FancyResolverBuilder) Build

func (f *FancyResolverBuilder) Build(target resolver.Target, cc resolver.ClientConn, opts resolver.BuildOptions) (resolver.Resolver, error)

func (*FancyResolverBuilder) Scheme

func (f *FancyResolverBuilder) Scheme() string

this scheme matches the url

func (*FancyResolverBuilder) ServiceName

func (f *FancyResolverBuilder) ServiceName() string

type ProxyTarget

type ProxyTarget struct {
    Target *pb.Target
    // contains filtered or unexported fields
}

func GetProxyTarget

func GetProxyTarget(ctx context.Context, serviceid string) (*ProxyTarget, error)

func (*ProxyTarget) Start

func (p *ProxyTarget) Start(c net.Conn) error

send the initialisation sequence

type TargetList

type TargetList struct {
    // contains filtered or unexported fields
}

func NewTargetList

func NewTargetList(servicename string) *TargetList

func (*TargetList) ByAddress

func (t *TargetList) ByAddress(address string) []*TargetWithConnection

func (*TargetList) Connections

func (t *TargetList) Connections() []*grpc.ClientConn

returns a connection to exactly one target

func (*TargetList) Targets

func (t *TargetList) Targets() []*TargetWithConnection

type TargetWithConnection

type TargetWithConnection struct {
    // contains filtered or unexported fields
}

func (*TargetWithConnection) Connection

func (t *TargetWithConnection) Connection() *grpc.ClientConn

func (*TargetWithConnection) String

func (t *TargetWithConnection) String() string

Subdirectories

Name Synopsis
..
stop-service contains an example of how to use the /internal/ api of gRPC servers to shutdown a server gracefully.