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  }