github.com/Axway/agent-sdk@v1.1.101/pkg/harvester/harvesterclient.go (about) 1 package harvester 2 3 import ( 4 "crypto/tls" 5 "encoding/json" 6 "fmt" 7 "net/http" 8 "net/url" 9 "strconv" 10 "time" 11 12 "github.com/Axway/agent-sdk/pkg/agent/events" 13 "github.com/Axway/agent-sdk/pkg/api" 14 "github.com/Axway/agent-sdk/pkg/apic/auth" 15 "github.com/Axway/agent-sdk/pkg/config" 16 corecfg "github.com/Axway/agent-sdk/pkg/config" 17 "github.com/Axway/agent-sdk/pkg/util" 18 "github.com/Axway/agent-sdk/pkg/util/log" 19 "github.com/Axway/agent-sdk/pkg/watchmanager/proto" 20 ) 21 22 const ( 23 defaultEventPageSize = 100 24 ) 25 26 // ErrSeqGone - error for purged sequence 27 type ErrSeqGone struct { 28 } 29 30 func (e *ErrSeqGone) Error() string { 31 return "sequence purged" 32 } 33 34 // Harvest is an interface for retrieving harvester events 35 type Harvest interface { 36 EventCatchUp(link string, events chan *proto.Event) error 37 ReceiveSyncEvents(topicSelfLink string, sequenceID int64, eventCh chan *proto.Event) (int64, error) 38 } 39 40 // Config for harvester 41 type Config struct { 42 ClientTimeout time.Duration 43 Host string 44 PageSize int 45 Port uint32 46 Protocol string 47 ProxyURL string 48 SequenceProvider events.SequenceProvider 49 TenantID string 50 TLSCfg *tls.Config 51 TokenGetter func() (string, error) 52 skipPublish bool 53 } 54 55 // Client for connecting to harvester 56 type Client struct { 57 Cfg *Config 58 Client api.Client 59 URL string 60 logger log.FieldLogger 61 skipPublish bool 62 } 63 64 // NewConfig creates a config for harvester connections 65 func NewConfig(cfg config.CentralConfig, getToken auth.TokenGetter, seq events.SequenceProvider) *Config { 66 parsed, _ := url.Parse(cfg.GetURL()) 67 hostname := parsed.Hostname() 68 port := util.ParsePort(parsed) 69 return &Config{ 70 ClientTimeout: cfg.GetClientTimeout(), 71 Host: hostname, 72 PageSize: cfg.GetPageSize(), 73 Port: uint32(port), 74 Protocol: parsed.Scheme, 75 ProxyURL: cfg.GetProxyURL(), 76 SequenceProvider: seq, 77 TenantID: cfg.GetTenantID(), 78 TLSCfg: cfg.GetTLSConfig().BuildTLSConfig(), 79 TokenGetter: getToken.GetToken, 80 } 81 } 82 83 // NewClient creates a new harvester client 84 func NewClient(cfg *Config) *Client { 85 if cfg.Protocol == "" { 86 cfg.Protocol = "https" 87 } 88 if cfg.PageSize == 0 { 89 cfg.PageSize = defaultEventPageSize 90 } 91 92 logger := log.NewFieldLogger(). 93 WithComponent("Client"). 94 WithPackage("harvester") 95 96 harvesterURL := fmt.Sprintf("%s://%s:%d/events", cfg.Protocol, cfg.Host, int(cfg.Port)) 97 98 return &Client{ 99 URL: harvesterURL, 100 Cfg: cfg, 101 Client: newSingleEntryClient(cfg), 102 logger: logger, 103 skipPublish: cfg.skipPublish, 104 } 105 } 106 107 // ReceiveSyncEvents fetches events based on the sequence id and watch topic self link, and publishes the events to the event channel 108 func (h *Client) ReceiveSyncEvents(topicSelfLink string, sequenceID int64, eventCh chan *proto.Event) (int64, error) { 109 h.logger.Tracef("receive sync events based on sequence id %v, and self link %v", sequenceID, topicSelfLink) 110 var lastID int64 111 token, err := h.Cfg.TokenGetter() 112 if err != nil { 113 return lastID, err 114 } 115 116 morePages := true 117 page := 1 118 119 for morePages { 120 pageableQueryParams := h.buildParams(sequenceID, page, h.Cfg.PageSize) 121 122 req := api.Request{ 123 Method: http.MethodGet, 124 URL: h.URL + topicSelfLink, 125 Headers: make(map[string]string), 126 QueryParams: pageableQueryParams, 127 } 128 129 req.Headers["Authorization"] = "Bearer " + token 130 req.Headers["X-Axway-Tenant-Id"] = h.Cfg.TenantID 131 req.Headers["Content-Type"] = "application/json" 132 133 msg := "sending request for URL - %s" 134 if morePages { 135 msg += ", more pages" 136 } 137 h.logger.Tracef(msg, req.URL) 138 res, err := h.Client.Send(req) 139 if err != nil { 140 return lastID, err 141 } 142 143 if res.Code != http.StatusOK && res.Code != http.StatusGone { 144 return lastID, fmt.Errorf("expected a 200 response but received %d", res.Code) 145 } 146 147 // requested sequence is purged get the current max sequence 148 if lastID == 0 && res.Code == http.StatusGone { 149 maxSeqId, ok := res.Headers["X-Axway-Max-Sequence-Id"] 150 if ok && len(maxSeqId) > 0 { 151 lastID, err = strconv.ParseInt(maxSeqId[0], 10, 64) 152 if err != nil { 153 return lastID, err 154 } 155 return lastID, &ErrSeqGone{} 156 } 157 } 158 159 pagedEvents := make([]*resourceEntryExternalEvent, 0) 160 err = json.Unmarshal(res.Body, &pagedEvents) 161 if err != nil { 162 return lastID, err 163 } 164 165 if len(pagedEvents) < h.Cfg.PageSize { 166 morePages = false 167 } 168 169 for _, event := range pagedEvents { 170 lastID = event.Metadata.GetSequenceID() 171 if !h.skipPublish && eventCh != nil { 172 eventCh <- event.toProtoEvent() 173 } 174 } 175 page++ 176 } 177 178 return lastID, err 179 } 180 181 func (h *Client) buildParams(sequenceID int64, page, pageSize int) map[string]string { 182 if sequenceID > 0 { 183 return map[string]string{ 184 "page": strconv.Itoa(page), 185 "pageSize": strconv.Itoa(pageSize), 186 "query": fmt.Sprintf("sequenceID>%d", sequenceID), 187 "sort": "sequenceID,ASC", 188 } 189 } 190 191 // if the sequence id is 0, then there are no events to catch up on, 192 // so make a request to get the latest event so that we can save the sequence id to the cache. 193 194 return map[string]string{ 195 "pageSize": strconv.Itoa(1), 196 "sort": "sequenceID,DESC", 197 } 198 } 199 200 // EventCatchUp syncs all events 201 func (h *Client) EventCatchUp(link string, events chan *proto.Event) error { 202 h.logger.Trace("event catchup, to sync all events") 203 if h.Client == nil || h.Cfg.SequenceProvider == nil { 204 return nil 205 } 206 207 sequenceID := h.Cfg.SequenceProvider.GetSequence() 208 if sequenceID > 0 { 209 var err error 210 lastSequenceID, err := h.ReceiveSyncEvents(link, sequenceID, events) 211 if err != nil { 212 if _, ok := err.(*ErrSeqGone); ok { 213 // Set the max sequence returned from 410 to sequence provider as processed 214 h.Cfg.SequenceProvider.SetSequence(lastSequenceID) 215 return nil 216 } 217 return err 218 } 219 220 if lastSequenceID > 0 { 221 // wait for all current sequences to be processed before processing new ones 222 for sequenceID < lastSequenceID { 223 sequenceID = h.Cfg.SequenceProvider.GetSequence() 224 } 225 } else { 226 return nil 227 } 228 } else { 229 return nil 230 } 231 232 return h.EventCatchUp(link, events) 233 } 234 235 func newSingleEntryClient(cfg *Config) api.Client { 236 tlsCfg := corecfg.NewTLSConfig().(*corecfg.TLSConfiguration) 237 tlsCfg.LoadFrom(cfg.TLSCfg) 238 clientTimeout := cfg.ClientTimeout 239 if clientTimeout == 0 { 240 clientTimeout = util.DefaultKeepAliveTimeout 241 } 242 243 return api.NewClient(tlsCfg, cfg.ProxyURL, 244 api.WithTimeout(clientTimeout), api.WithSingleURL()) 245 }