github.com/xmidt-org/webpa-common@v1.11.9/xhttp/fanout/serviceEndpoints.go (about)

     1  package fanout
     2  
     3  import (
     4  	"net/http"
     5  	"net/url"
     6  	"sync"
     7  
     8  	"github.com/xmidt-org/webpa-common/device"
     9  	"github.com/xmidt-org/webpa-common/service"
    10  	"github.com/xmidt-org/webpa-common/service/monitor"
    11  	"github.com/xmidt-org/webpa-common/service/servicehttp"
    12  	"github.com/xmidt-org/webpa-common/xhttp"
    13  )
    14  
    15  // ServiceEndpoints is an Endpoints implementation that is driven by service discovery.
    16  // This type is a monitor.Listener, and fanout URLs are computed from all configured
    17  // instancers.
    18  type ServiceEndpoints struct {
    19  	lock            sync.RWMutex
    20  	keyFunc         servicehttp.KeyFunc
    21  	accessorFactory service.AccessorFactory
    22  	accessors       map[string]service.Accessor
    23  }
    24  
    25  // FanoutURLs uses the currently available discovered endpoints to produce a set of URLs.
    26  // The original request is used to produce a hash key, then each accessor is consulted for
    27  // the endpoint that matches that key.
    28  func (se *ServiceEndpoints) FanoutURLs(original *http.Request) ([]*url.URL, error) {
    29  	hashKey, err := se.keyFunc(original)
    30  	if err != nil {
    31  		return nil, err
    32  	}
    33  
    34  	se.lock.RLock()
    35  	endpoints := make([]string, 0, len(se.accessors))
    36  	for _, a := range se.accessors {
    37  		e, err := a.Get(hashKey)
    38  		if err != nil {
    39  			continue
    40  		}
    41  
    42  		endpoints = append(endpoints, e)
    43  	}
    44  
    45  	se.lock.RUnlock()
    46  	if len(endpoints) == 0 {
    47  		return []*url.URL{}, errNoFanoutURLs
    48  	}
    49  	return xhttp.ApplyURLParser(url.Parse, endpoints...)
    50  }
    51  
    52  // MonitorEvent supplies the monitor.Listener behavior.  An accessor is created and stored under
    53  // the event Key.
    54  func (se *ServiceEndpoints) MonitorEvent(e monitor.Event) {
    55  	accessor := se.accessorFactory(e.Instances)
    56  	se.lock.Lock()
    57  	se.accessors[e.Key] = accessor
    58  	se.lock.Unlock()
    59  }
    60  
    61  // ServiceEndpointsOption is a strategy for configuring a ServiceEndpoints
    62  type ServiceEndpointsOption func(*ServiceEndpoints)
    63  
    64  // WithKeyFunc configures a hash function for the given service endpoints.  If nil,
    65  // then device.IDHashParser is used.
    66  func WithKeyFunc(kf servicehttp.KeyFunc) ServiceEndpointsOption {
    67  	return func(se *ServiceEndpoints) {
    68  		if kf != nil {
    69  			se.keyFunc = kf
    70  		} else {
    71  			se.keyFunc = device.IDHashParser
    72  		}
    73  	}
    74  }
    75  
    76  // WithAccessorFactory configures an accessor factory for the given service endpoints.
    77  // If nil, then service.DefaultAccessorFactory is used.
    78  func WithAccessorFactory(af service.AccessorFactory) ServiceEndpointsOption {
    79  	return func(se *ServiceEndpoints) {
    80  		if af != nil {
    81  			se.accessorFactory = af
    82  		} else {
    83  			se.accessorFactory = service.DefaultAccessorFactory
    84  		}
    85  	}
    86  }
    87  
    88  // NewServiceEndpoints creates a ServiceEndpoints instance.  By default, device.IDHashParser is used as the KeyFunc
    89  // and service.DefaultAccessorFactory is used as the accessor factory.
    90  func NewServiceEndpoints(options ...ServiceEndpointsOption) *ServiceEndpoints {
    91  	se := &ServiceEndpoints{
    92  		keyFunc:         device.IDHashParser,
    93  		accessorFactory: service.DefaultAccessorFactory,
    94  		accessors:       make(map[string]service.Accessor),
    95  	}
    96  
    97  	for _, o := range options {
    98  		o(se)
    99  	}
   100  
   101  	return se
   102  }
   103  
   104  // ServiceEndpointsAlternate creates an alternate closure appropriate for NewEndpoints.  This function
   105  // allows service discovery to be used for fanout endpoints when no fixed endpoints are provided via Options.
   106  //
   107  // The returned Endpoints, being a ServiceEndpoints, will implement monitor.Listener.  This function does not
   108  // start listening to service discovery events.  That must be done by application code.
   109  func ServiceEndpointsAlternate(options ...ServiceEndpointsOption) func() (Endpoints, error) {
   110  	return func() (Endpoints, error) {
   111  		return NewServiceEndpoints(options...), nil
   112  	}
   113  }
   114  
   115  // MonitorEndpoints applies service discovery updates to the given Endpoints, if and only if the
   116  // Endpoints implements monitor.Listener.  If Endpoints does not implement monitor.Listener, this
   117  // function return a nil monitor and a nil error.
   118  func MonitorEndpoints(e Endpoints, options ...monitor.Option) (monitor.Interface, error) {
   119  	if l, ok := e.(monitor.Listener); ok {
   120  		options = append(options, monitor.WithListeners(l))
   121  		return monitor.New(options...)
   122  	}
   123  
   124  	return nil, nil
   125  }