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 }