github.com/Axway/agent-sdk@v1.1.101/pkg/watchmanager/manager.go (about)

     1  package watchmanager
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"net/url"
     7  	"sync"
     8  
     9  	"google.golang.org/grpc/connectivity"
    10  
    11  	"github.com/Axway/agent-sdk/pkg/util"
    12  	"github.com/Axway/agent-sdk/pkg/util/log"
    13  	"github.com/Axway/agent-sdk/pkg/watchmanager/proto"
    14  	"github.com/google/uuid"
    15  	"google.golang.org/grpc"
    16  )
    17  
    18  // NewManagerFunc func signature to create a Manager
    19  type NewManagerFunc func(cfg *Config, opts ...Option) (Manager, error)
    20  
    21  // Manager - Interface to manage watch connections
    22  type Manager interface {
    23  	RegisterWatch(topic string, eventChan chan *proto.Event, errChan chan error) (string, error)
    24  	CloseWatch(id string) error
    25  	CloseConn()
    26  	Status() bool
    27  }
    28  
    29  // TokenGetter - function to acquire token
    30  type TokenGetter func() (string, error)
    31  
    32  type watchManager struct {
    33  	cfg                *Config
    34  	clientMap          map[string]*watchClient
    35  	connection         *grpc.ClientConn
    36  	logger             log.FieldLogger
    37  	mutex              sync.Mutex
    38  	newWatchClientFunc newWatchClientFunc
    39  	options            *watchOptions
    40  }
    41  
    42  // New - Creates a new watch manager
    43  func New(cfg *Config, opts ...Option) (Manager, error) {
    44  	err := cfg.validateCfg()
    45  	if err != nil {
    46  		return nil, err
    47  	}
    48  
    49  	logger := log.NewFieldLogger().
    50  		WithComponent("watchManager").
    51  		WithPackage("sdk.watchmanager")
    52  	manager := &watchManager{
    53  		cfg:                cfg,
    54  		logger:             logger,
    55  		clientMap:          make(map[string]*watchClient),
    56  		options:            newWatchOptions(),
    57  		newWatchClientFunc: proto.NewWatchClient,
    58  	}
    59  
    60  	for _, opt := range opts {
    61  		opt.apply(manager.options)
    62  	}
    63  
    64  	manager.connection, err = manager.createConnection()
    65  	if err != nil {
    66  		manager.logger.
    67  			WithError(err).
    68  			Errorf("failed to establish connection with watch service")
    69  	}
    70  
    71  	return manager, err
    72  }
    73  
    74  func (m *watchManager) createConnection() (*grpc.ClientConn, error) {
    75  	address := fmt.Sprintf("%s:%d", m.cfg.Host, m.cfg.Port)
    76  	dialer, err := m.getDialer(address)
    77  	if err != nil {
    78  		return nil, err
    79  	}
    80  
    81  	grpcDialOptions := []grpc.DialOption{
    82  		withKeepaliveParams(m.options.keepAlive.time, m.options.keepAlive.timeout),
    83  		withRPCCredentials(m.cfg.TenantID, m.cfg.TokenGetter),
    84  		withTLSConfig(m.options.tlsCfg),
    85  		withDialer(dialer),
    86  		chainStreamClientInterceptor(
    87  			logrusStreamClientInterceptor(m.options.loggerEntry),
    88  		),
    89  		grpc.WithUserAgent(m.cfg.UserAgent),
    90  	}
    91  
    92  	m.logger.
    93  		WithField("host", m.cfg.Host).
    94  		WithField("port", m.cfg.Port).
    95  		Infof("connecting to watch service")
    96  
    97  	return grpc.NewClient(address, grpcDialOptions...)
    98  }
    99  
   100  func (m *watchManager) getDialer(targetAddr string) (util.Dialer, error) {
   101  	if m.options.singleEntryAddr == "" && m.options.proxyURL == "" {
   102  		return nil, nil
   103  	}
   104  	var proxyURL *url.URL
   105  	var err error
   106  	if m.options.proxyURL != "" {
   107  		proxyURL, err = url.Parse(m.options.proxyURL)
   108  		if err != nil {
   109  			return nil, err
   110  		}
   111  	}
   112  	singleEntryHostMap := make(map[string]string)
   113  	if m.options.singleEntryAddr != "" {
   114  		singleEntryHostMap[targetAddr] = m.options.singleEntryAddr
   115  	}
   116  	return util.NewDialer(proxyURL, singleEntryHostMap), nil
   117  }
   118  
   119  // eventCatchUp - called until lastSequenceID is 0, caught up on events
   120  func (m *watchManager) eventCatchUp(link string, events chan *proto.Event) error {
   121  	if m.options.harvester == nil || m.options.sequence == nil {
   122  		return nil
   123  	}
   124  
   125  	err := m.options.harvester.EventCatchUp(link, events)
   126  	if err != nil {
   127  		return err
   128  	}
   129  
   130  	return nil
   131  }
   132  
   133  // RegisterWatch - Registers a subscription with watch service using topic
   134  func (m *watchManager) RegisterWatch(link string, events chan *proto.Event, errors chan error) (string, error) {
   135  	client, err := newWatchClient(
   136  		m.connection,
   137  		clientConfig{
   138  			errors:        errors,
   139  			events:        events,
   140  			tokenGetter:   m.cfg.TokenGetter,
   141  			topicSelfLink: link,
   142  		},
   143  		m.newWatchClientFunc,
   144  	)
   145  	if err != nil {
   146  		return "", err
   147  	}
   148  
   149  	subscriptionID, _ := uuid.NewUUID()
   150  	subID := subscriptionID.String()
   151  
   152  	if m.options.sequence != nil && m.options.sequence.GetSequence() < 0 {
   153  		err := fmt.Errorf("do not have a sequence id, stopping watch manager")
   154  		m.logger.Error(err.Error())
   155  		m.CloseWatch(subID)
   156  		m.onHarvesterErr()
   157  		return subID, err
   158  	}
   159  
   160  	if err := m.eventCatchUp(link, events); err != nil {
   161  		m.logger.WithError(err).Error("failed to sync events from harvester")
   162  		m.CloseWatch(subID)
   163  		m.onHarvesterErr()
   164  		return subID, err
   165  	}
   166  
   167  	if err := client.processRequest(); err != nil {
   168  		m.logger.WithError(err).Error("failed to connect with watch service")
   169  		m.CloseWatch(subID)
   170  		return subID, err
   171  	}
   172  	go client.processEvents()
   173  
   174  	m.mutex.Lock()
   175  	m.clientMap[subID] = client
   176  	m.mutex.Unlock()
   177  
   178  	m.logger.
   179  		WithField("id", subID).
   180  		WithField("watchtopic", link).
   181  		Infof("registered watch client")
   182  
   183  	return subID, nil
   184  }
   185  
   186  // CloseWatch closes the specified watch stream by id
   187  func (m *watchManager) CloseWatch(id string) error {
   188  	m.mutex.Lock()
   189  	defer m.mutex.Unlock()
   190  
   191  	client, ok := m.clientMap[id]
   192  	if !ok {
   193  		return errors.New("invalid watch subscription ID")
   194  	}
   195  	m.logger.WithField("watch-id", id).Info("closing connection for subscription")
   196  	client.cancelStreamCtx()
   197  	delete(m.clientMap, id)
   198  	return nil
   199  }
   200  
   201  // CloseConn closes watch service connection, and all open streams
   202  func (m *watchManager) CloseConn() {
   203  	m.logger.Info("closing watch service connection")
   204  
   205  	m.connection.Close()
   206  	for id := range m.clientMap {
   207  		delete(m.clientMap, id)
   208  	}
   209  }
   210  
   211  // Status returns a boolean to indicate if the clients connected to central are active.
   212  func (m *watchManager) Status() bool {
   213  	m.mutex.Lock()
   214  	defer m.mutex.Unlock()
   215  
   216  	ok := true
   217  
   218  	if len(m.clientMap) == 0 {
   219  		ok = false
   220  	}
   221  
   222  	for k, c := range m.clientMap {
   223  		if !c.isRunning {
   224  			m.logger.Debug("watch client is not running")
   225  			ok = false
   226  			delete(m.clientMap, k)
   227  		}
   228  	}
   229  
   230  	return ok && m.connection.GetState() == connectivity.Ready
   231  }
   232  
   233  func (m *watchManager) onHarvesterErr() {
   234  	if m.options.onEventSyncError == nil {
   235  		return
   236  	}
   237  	m.options.onEventSyncError()
   238  }