MM-28859 Add feature flag managment system using split.io and remove viper. (#15954)

* Add feature flag managment system using split.io and remove viper.

* Fixing tests.

* Attempt to fix postgres tests.

* Fix watch filepath for advanced logging.

* Review fixes.

* Some error wrapping.

* Remove unessisary store interface.

* Desanitize SplitKey

* Simplify.

* Review feedback.

* Rename split mlog adatper to split logger.

* fsInner

* Style.

* Restore oldcfg test.

* Downgrading non-actionable feature flag errors to warnings.

Co-authored-by: Mattermod <mattermod@users.noreply.github.com>
Этот коммит содержится в:
Christopher Speller
2020-10-29 15:54:39 -07:00
коммит произвёл GitHub
родитель 8bb772638c
Коммит 1aadd36644
423 изменённых файлов: 37646 добавлений и 20257 удалений

27
vendor/github.com/splitio/go-client/v6/CONTRIBUTORS-GUIDE.md сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,27 @@
# Contributing to the Split GO SDK
Split SDK is an open source project and we welcome feedback and contribution. The information below describes how to build the project with your changes, run the tests, and send the Pull Request(PR).
## Development
### Development process
1. Fork the repository and create a topic branch from `development` branch. Please use a descriptive name for your branch.
2. While developing, use descriptive messages in your commits. Avoid short or meaningless sentences like "fix bug".
3. Make sure to add tests for both positive and negative cases.
4. <if applicable> Run the linter script of the project and fix any issues you find.
5. Run the build script and make sure it runs with no errors.
6. Run all tests and make sure there are no failures.
7. `git push` your changes to GitHub within your topic branch.
8. Open a Pull Request(PR) from your forked repo and into the `development` branch of the original repository.
9. When creating your PR, please fill out all the fields of the PR template, as applicable, for the project.
10. Check for conflicts once the pull request is created to make sure your PR can be merged cleanly into `development`.
11. Keep an eye out for any feedback or comments from Split's SDK team.
### Running tests
To run test you can execute the command `go test ./...` on the root folder.
# Contact
If you have any other questions or need to contact us directly in a private manner send us a note at sdks@split.io.

13
vendor/github.com/splitio/go-client/v6/LICENSE.txt сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,13 @@
Copyright © 2020 Split Software, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

16
vendor/github.com/splitio/go-client/v6/splitio/client/bucketingkey.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,16 @@
package client
// Key struct to be used when supplying two keys. One for matching purposes and another one
// for hashing.
type Key struct {
MatchingKey string
BucketingKey string
}
// NewKey instantiates a new key
func NewKey(matchingKey string, bucketingKey string) *Key {
return &Key{
MatchingKey: matchingKey,
BucketingKey: bucketingKey,
}
}

390
vendor/github.com/splitio/go-client/v6/splitio/client/client.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,390 @@
package client
import (
"errors"
"runtime/debug"
"time"
"github.com/splitio/go-client/v6/splitio/conf"
"github.com/splitio/go-client/v6/splitio/engine/evaluator"
"github.com/splitio/go-client/v6/splitio/engine/evaluator/impressionlabels"
impressionlistener "github.com/splitio/go-client/v6/splitio/impressionListener"
"github.com/splitio/go-split-commons/v2/dtos"
"github.com/splitio/go-split-commons/v2/provisional"
"github.com/splitio/go-split-commons/v2/storage"
"github.com/splitio/go-split-commons/v2/util"
"github.com/splitio/go-toolkit/v3/logging"
)
// SplitClient is the entry-point of the split SDK.
type SplitClient struct {
logger logging.LoggerInterface
evaluator evaluator.Interface
impressions storage.ImpressionStorageProducer
metrics storage.MetricsStorageProducer
events storage.EventStorageProducer
validator inputValidation
factory *SplitFactory
impressionListener *impressionlistener.WrapperImpressionListener
impressionManager provisional.ImpressionManager
}
// TreatmentResult struct that includes the Treatment evaluation with the corresponding Config
type TreatmentResult struct {
Treatment string `json:"treatment"`
Config *string `json:"config"`
}
// getEvaluationResult calls evaluation for one particular split
func (c *SplitClient) getEvaluationResult(
matchingKey string,
bucketingKey *string,
feature string,
attributes map[string]interface{},
operation string,
) *evaluator.Result {
if c.isReady() {
return c.evaluator.EvaluateFeature(matchingKey, bucketingKey, feature, attributes)
}
c.logger.Warning(operation + ": the SDK is not ready, results may be incorrect. Make sure to wait for SDK readiness before using this method")
return &evaluator.Result{
Treatment: evaluator.Control,
Label: impressionlabels.ClientNotReady,
Config: nil,
}
}
// getEvaluationsResult calls evaluation for multiple treatments at once
func (c *SplitClient) getEvaluationsResult(
matchingKey string,
bucketingKey *string,
features []string,
attributes map[string]interface{},
operation string,
) evaluator.Results {
if c.isReady() {
return c.evaluator.EvaluateFeatures(matchingKey, bucketingKey, features, attributes)
}
c.logger.Warning(operation + ": the SDK is not ready, results may be incorrect. Make sure to wait for SDK readiness before using this method")
result := evaluator.Results{
EvaluationTimeNs: 0,
Evaluations: make(map[string]evaluator.Result),
}
for _, feature := range features {
result.Evaluations[feature] = evaluator.Result{
Treatment: evaluator.Control,
Label: impressionlabels.ClientNotReady,
Config: nil,
}
}
return result
}
// createImpression creates impression to be stored and used by listener
func (c *SplitClient) createImpression(
feature string,
bucketingKey *string,
evaluationLabel string,
matchingKey string,
treatment string,
changeNumber int64,
) dtos.Impression {
var label string
if c.factory.cfg.LabelsEnabled {
label = evaluationLabel
}
impressionBucketingKey := ""
if bucketingKey != nil {
impressionBucketingKey = *bucketingKey
}
return dtos.Impression{
FeatureName: feature,
BucketingKey: impressionBucketingKey,
ChangeNumber: changeNumber,
KeyName: matchingKey,
Label: label,
Treatment: treatment,
Time: time.Now().UTC().UnixNano() / int64(time.Millisecond), // Convert standard timestamp to java's ms timestamps
}
}
// storeData stores impression, runs listener and stores metrics
func (c *SplitClient) storeData(impressions []dtos.Impression, attributes map[string]interface{}, metricsLabel string, evaluationTimeNs int64) {
// Store impression
if c.impressions != nil {
forLog, forListener := c.impressionManager.ProcessImpressions(impressions)
c.impressions.LogImpressions(forLog)
// Custom Impression Listener
if c.impressionListener != nil {
c.impressionListener.SendDataToClient(forListener, attributes)
}
} else {
c.logger.Warning("No impression storage set in client. Not sending impressions!")
}
// Store latency
if c.metrics != nil {
bucket := util.Bucket(evaluationTimeNs)
c.metrics.IncLatency(metricsLabel, bucket)
} else {
c.logger.Warning("No metrics storage set in client. Not sending latencies!")
}
}
// doTreatmentCall retrieves treatments of an specific feature with configurations object if it is present
// for a certain key and set of attributes
func (c *SplitClient) doTreatmentCall(
key interface{},
feature string,
attributes map[string]interface{},
operation string,
metricsLabel string,
) (t TreatmentResult) {
controlTreatment := TreatmentResult{
Treatment: evaluator.Control,
Config: nil,
}
// Set up a guard deferred function to recover if the SDK starts panicking
defer func() {
if r := recover(); r != nil {
// At this point we'll only trust that the logger isn't panicking trust
// that the logger isn't panicking
c.logger.Error(
"SDK is panicking with the following error", r, "\n",
string(debug.Stack()), "\n",
"Returning CONTROL", "\n")
t = controlTreatment
}
}()
if c.isDestroyed() {
c.logger.Error("Client has already been destroyed - no calls possible")
return controlTreatment
}
matchingKey, bucketingKey, err := c.validator.ValidateTreatmentKey(key, operation)
if err != nil {
c.logger.Error(err.Error())
return controlTreatment
}
feature, err = c.validator.ValidateFeatureName(feature, operation)
if err != nil {
c.logger.Error(err.Error())
return controlTreatment
}
evaluationResult := c.getEvaluationResult(matchingKey, bucketingKey, feature, attributes, operation)
if !c.validator.IsSplitFound(evaluationResult.Label, feature, operation) {
return controlTreatment
}
c.storeData(
[]dtos.Impression{c.createImpression(feature, bucketingKey, evaluationResult.Label, matchingKey, evaluationResult.Treatment, evaluationResult.SplitChangeNumber)},
attributes,
metricsLabel,
evaluationResult.EvaluationTimeNs,
)
return TreatmentResult{
Treatment: evaluationResult.Treatment,
Config: evaluationResult.Config,
}
}
// Treatment implements the main functionality of split. Retrieve treatments of a specific feature
// for a certain key and set of attributes
func (c *SplitClient) Treatment(key interface{}, feature string, attributes map[string]interface{}) string {
return c.doTreatmentCall(key, feature, attributes, "Treatment", "sdk.getTreatment").Treatment
}
// TreatmentWithConfig implements the main functionality of split. Retrieves the treatment of a specific feature with
// the corresponding configuration if it is present
func (c *SplitClient) TreatmentWithConfig(key interface{}, feature string, attributes map[string]interface{}) TreatmentResult {
return c.doTreatmentCall(key, feature, attributes, "TreatmentWithConfig", "sdk.getTreatmentWithConfig")
}
// Generates control treatments
func (c *SplitClient) generateControlTreatments(features []string, operation string) map[string]TreatmentResult {
treatments := make(map[string]TreatmentResult)
filtered, err := c.validator.ValidateFeatureNames(features, operation)
if err != nil {
return treatments
}
for _, feature := range filtered {
treatments[feature] = TreatmentResult{
Treatment: evaluator.Control,
Config: nil,
}
}
return treatments
}
// doTreatmentsCall retrieves treatments of an specific array of features with configurations object if it is present
// for a certain key and set of attributes
func (c *SplitClient) doTreatmentsCall(
key interface{},
features []string,
attributes map[string]interface{},
operation string,
metricsLabel string,
) (t map[string]TreatmentResult) {
treatments := make(map[string]TreatmentResult)
// Set up a guard deferred function to recover if the SDK starts panicking
defer func() {
if r := recover(); r != nil {
// At this point we'll only trust that the logger isn't panicking trust
// that the logger isn't panicking
c.logger.Error(
"SDK is panicking with the following error", r, "\n",
string(debug.Stack()), "\n")
t = treatments
}
}()
if c.isDestroyed() {
c.logger.Error("Client has already been destroyed - no calls possible")
return c.generateControlTreatments(features, operation)
}
matchingKey, bucketingKey, err := c.validator.ValidateTreatmentKey(key, operation)
if err != nil {
c.logger.Error(err.Error())
return c.generateControlTreatments(features, operation)
}
filteredFeatures, err := c.validator.ValidateFeatureNames(features, operation)
if err != nil {
c.logger.Error(err.Error())
return map[string]TreatmentResult{}
}
var bulkImpressions []dtos.Impression
evaluationsResult := c.getEvaluationsResult(matchingKey, bucketingKey, filteredFeatures, attributes, operation)
for feature, evaluation := range evaluationsResult.Evaluations {
if !c.validator.IsSplitFound(evaluation.Label, feature, operation) {
treatments[feature] = TreatmentResult{
Treatment: evaluator.Control,
Config: nil,
}
} else {
bulkImpressions = append(bulkImpressions, c.createImpression(feature, bucketingKey, evaluation.Label, matchingKey, evaluation.Treatment, evaluation.SplitChangeNumber))
treatments[feature] = TreatmentResult{
Treatment: evaluation.Treatment,
Config: evaluation.Config,
}
}
}
c.storeData(bulkImpressions, attributes, metricsLabel, evaluationsResult.EvaluationTimeNs)
return treatments
}
// Treatments evaluates multiple featers for a single user and set of attributes at once
func (c *SplitClient) Treatments(key interface{}, features []string, attributes map[string]interface{}) map[string]string {
treatments := map[string]string{}
result := c.doTreatmentsCall(key, features, attributes, "Treatments", "sdk.getTreatments")
for feature, treatmentResult := range result {
treatments[feature] = treatmentResult.Treatment
}
return treatments
}
// TreatmentsWithConfig evaluates multiple featers for a single user and set of attributes at once and returns configurations
func (c *SplitClient) TreatmentsWithConfig(key interface{}, features []string, attributes map[string]interface{}) map[string]TreatmentResult {
return c.doTreatmentsCall(key, features, attributes, "TreatmentsWithConfig", "sdk.getTreatmentsWithConfig")
}
// isDestroyed returns true if the client has been destroyed
func (c *SplitClient) isDestroyed() bool {
return c.factory.IsDestroyed()
}
// isReady returns true if the client is ready
func (c *SplitClient) isReady() bool {
return c.factory.IsReady()
}
// Destroy the client and the underlying factory.
func (c *SplitClient) Destroy() {
if !c.isDestroyed() {
c.factory.Destroy()
}
}
// Track an event and its custom value
func (c *SplitClient) Track(
key string,
trafficType string,
eventType string,
value interface{},
properties map[string]interface{},
) (ret error) {
defer func() {
if r := recover(); r != nil {
// At this point we'll only trust that the logger isn't panicking
c.logger.Error(
"SDK is panicking with the following error", r, "\n",
string(debug.Stack()), "\n",
)
ret = errors.New("Track is panicking. Please check logs")
}
return
}()
if c.isDestroyed() {
c.logger.Error("Client has already been destroyed - no calls possible")
return errors.New("Client has already been destroyed - no calls possible")
}
if !c.isReady() {
c.logger.Warning("Track: the SDK is not ready, results may be incorrect. Make sure to wait for SDK readiness before using this method")
}
key, trafficType, eventType, value, err := c.validator.ValidateTrackInputs(
key,
trafficType,
eventType,
value,
c.isReady() && c.factory.apikey != conf.Localhost,
)
if err != nil {
c.logger.Error(err.Error())
return err
}
properties, size, err := c.validator.validateTrackProperties(properties)
if err != nil {
return err
}
err = c.events.Push(dtos.EventDTO{
Key: key,
TrafficTypeName: trafficType,
EventTypeID: eventType,
Value: value,
Timestamp: time.Now().UTC().UnixNano() / int64(time.Millisecond), // Convert standard timestamp to java's ms timestamps
Properties: properties,
}, size)
if err != nil {
c.logger.Error("Error tracking event", err.Error())
return err
}
return nil
}
// BlockUntilReady Calls BlockUntilReady on factory to block client on readiness
func (c *SplitClient) BlockUntilReady(timer int) error {
return c.factory.BlockUntilReady(timer)
}

463
vendor/github.com/splitio/go-client/v6/splitio/client/factory.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,463 @@
// Package client contains implementations of the Split SDK client and the factory used
// to instantiate it.
package client
import (
"errors"
"fmt"
"strings"
"sync"
"sync/atomic"
"time"
"github.com/splitio/go-client/v6/splitio"
"github.com/splitio/go-client/v6/splitio/conf"
"github.com/splitio/go-client/v6/splitio/engine"
"github.com/splitio/go-client/v6/splitio/engine/evaluator"
impressionlistener "github.com/splitio/go-client/v6/splitio/impressionListener"
config "github.com/splitio/go-split-commons/v2/conf"
"github.com/splitio/go-split-commons/v2/dtos"
"github.com/splitio/go-split-commons/v2/provisional"
"github.com/splitio/go-split-commons/v2/service"
"github.com/splitio/go-split-commons/v2/service/local"
"github.com/splitio/go-split-commons/v2/storage"
"github.com/splitio/go-split-commons/v2/storage/mutexmap"
"github.com/splitio/go-split-commons/v2/storage/mutexqueue"
"github.com/splitio/go-split-commons/v2/storage/redis"
"github.com/splitio/go-split-commons/v2/synchronizer"
"github.com/splitio/go-split-commons/v2/synchronizer/worker/event"
"github.com/splitio/go-split-commons/v2/synchronizer/worker/impression"
"github.com/splitio/go-split-commons/v2/synchronizer/worker/impressionscount"
"github.com/splitio/go-split-commons/v2/synchronizer/worker/metric"
"github.com/splitio/go-split-commons/v2/synchronizer/worker/segment"
"github.com/splitio/go-split-commons/v2/synchronizer/worker/split"
"github.com/splitio/go-split-commons/v2/tasks"
"github.com/splitio/go-toolkit/v3/logging"
)
const (
sdkStatusDestroyed = iota
sdkStatusInitializing
sdkStatusReady
sdkInitializationFailed = -1
)
type sdkStorages struct {
splits storage.SplitStorageConsumer
segments storage.SegmentStorageConsumer
impressions storage.ImpressionStorageProducer
events storage.EventStorageProducer
telemetry storage.MetricsStorageProducer
}
// SplitFactory struct is responsible for instantiating and storing instances of client and manager.
type SplitFactory struct {
metadata dtos.Metadata
storages sdkStorages
apikey string
status atomic.Value
readinessSubscriptors map[int]chan int
operationMode string
mutex sync.Mutex
cfg *conf.SplitSdkConfig
impressionListener *impressionlistener.WrapperImpressionListener
logger logging.LoggerInterface
syncManager *synchronizer.Manager
impressionManager provisional.ImpressionManager
}
// Client returns the split client instantiated by the factory
func (f *SplitFactory) Client() *SplitClient {
return &SplitClient{
logger: f.logger,
evaluator: evaluator.NewEvaluator(f.storages.splits, f.storages.segments, engine.NewEngine(f.logger), f.logger),
impressions: f.storages.impressions,
metrics: f.storages.telemetry,
events: f.storages.events,
validator: inputValidation{
logger: f.logger,
splitStorage: f.storages.splits,
},
factory: f,
impressionListener: f.impressionListener,
impressionManager: f.impressionManager,
}
}
// Manager returns the split manager instantiated by the factory
func (f *SplitFactory) Manager() *SplitManager {
return &SplitManager{
splitStorage: f.storages.splits,
validator: inputValidation{logger: f.logger},
logger: f.logger,
factory: f,
}
}
// IsDestroyed returns true if tbe client has been destroyed
func (f *SplitFactory) IsDestroyed() bool {
return f.status.Load() == sdkStatusDestroyed
}
// IsReady returns true if the factory is ready
func (f *SplitFactory) IsReady() bool {
return f.status.Load() == sdkStatusReady
}
// initializates task for localhost mode
func (f *SplitFactory) initializationLocalhost(readyChannel chan int) {
f.syncManager.Start()
<-readyChannel
f.broadcastReadiness(sdkStatusReady)
}
// initializates tasks for in-memory mode
func (f *SplitFactory) initializationInMemory(readyChannel chan int) {
go f.syncManager.Start()
msg := <-readyChannel
switch msg {
case synchronizer.Ready:
// Broadcast ready status for SDK
f.broadcastReadiness(sdkStatusReady)
default:
f.broadcastReadiness(sdkInitializationFailed)
}
}
// broadcastReadiness broadcasts message to all the subscriptors
func (f *SplitFactory) broadcastReadiness(status int) {
f.mutex.Lock()
defer f.mutex.Unlock()
if f.status.Load() == sdkStatusInitializing && status == sdkStatusReady {
f.status.Store(sdkStatusReady)
}
for _, subscriptor := range f.readinessSubscriptors {
subscriptor <- status
}
}
// subscribes listener
func (f *SplitFactory) subscribe(name int, subscriptor chan int) {
f.mutex.Lock()
defer f.mutex.Unlock()
f.readinessSubscriptors[name] = subscriptor
}
// removes a particular subscriptor from the list
func (f *SplitFactory) unsubscribe(name int, subscriptor chan int) {
f.mutex.Lock()
defer f.mutex.Unlock()
_, ok := f.readinessSubscriptors[name]
if ok {
delete(f.readinessSubscriptors, name)
}
}
// BlockUntilReady blocks client or manager until the SDK is ready, error occurs or times out
func (f *SplitFactory) BlockUntilReady(timer int) error {
if f.IsReady() {
return nil
}
if timer <= 0 {
return errors.New("SDK Initialization: timer must be positive number")
}
if f.IsDestroyed() {
return errors.New("SDK Initialization: Client is destroyed")
}
block := make(chan int, 1)
f.mutex.Lock()
subscriptorName := len(f.readinessSubscriptors)
f.mutex.Unlock()
defer func() {
// Unsubscription will happen only if a block channel has been created
if block != nil {
f.unsubscribe(subscriptorName, block)
close(block)
}
}()
f.subscribe(subscriptorName, block)
select {
case status := <-block:
switch status {
case sdkStatusReady:
break
case sdkInitializationFailed:
return errors.New("SDK Initialization failed")
}
case <-time.After(time.Second * time.Duration(timer)):
return fmt.Errorf("SDK Initialization: time of %d exceeded", timer)
}
return nil
}
// Destroy stops all async tasks and clears all storages
func (f *SplitFactory) Destroy() {
if !f.IsDestroyed() {
removeInstanceFromTracker(f.apikey)
}
f.status.Store(sdkStatusDestroyed)
if f.cfg.OperationMode == conf.RedisConsumer {
return
}
f.syncManager.Stop()
}
// setupLogger sets up the logger according to the parameters submitted by the sdk user
func setupLogger(cfg *conf.SplitSdkConfig) logging.LoggerInterface {
var logger logging.LoggerInterface
if cfg.Logger != nil {
// If a custom logger is supplied, use it.
logger = cfg.Logger
} else {
logger = logging.NewLogger(&cfg.LoggerConfig)
}
return logger
}
func setupInMemoryFactory(
apikey string,
cfg *conf.SplitSdkConfig,
logger logging.LoggerInterface,
metadata dtos.Metadata,
) (*SplitFactory, error) {
advanced := conf.NormalizeSDKConf(cfg.Advanced)
if strings.TrimSpace(cfg.SplitSyncProxyURL) != "" {
advanced.StreamingEnabled = false
}
/*
err := api.ValidateApikey(apikey, *advanced)
if err != nil {
return nil, err
}
*/
inMememoryFullQueue := make(chan string, 2) // Size 2: So that it's able to accept one event from each resource simultaneously.
splitsStorage := mutexmap.NewMMSplitStorage()
segmentsStorage := mutexmap.NewMMSegmentStorage()
impressionsStorage := mutexqueue.NewMQImpressionsStorage(cfg.Advanced.ImpressionsQueueSize, inMememoryFullQueue, logger)
telemetryStorage := mutexmap.NewMMMetricsStorage()
eventsStorage := mutexqueue.NewMQEventsStorage(cfg.Advanced.EventsQueueSize, inMememoryFullQueue, logger)
metricsWrapper := storage.NewMetricWrapper(telemetryStorage, nil, logger)
managerConfig := config.ManagerConfig{
ImpressionsMode: cfg.ImpressionsMode,
OperationMode: cfg.OperationMode,
ListenerEnabled: cfg.Advanced.ImpressionListener != nil,
}
splitAPI := service.NewSplitAPI(apikey, advanced, logger, metadata)
workers := synchronizer.Workers{
SplitFetcher: split.NewSplitFetcher(splitsStorage, splitAPI.SplitFetcher, metricsWrapper, logger),
SegmentFetcher: segment.NewSegmentFetcher(splitsStorage, segmentsStorage, splitAPI.SegmentFetcher, metricsWrapper, logger),
EventRecorder: event.NewEventRecorderSingle(eventsStorage, splitAPI.EventRecorder, metricsWrapper, logger, metadata),
ImpressionRecorder: impression.NewRecorderSingle(impressionsStorage, splitAPI.ImpressionRecorder, metricsWrapper, logger, metadata, managerConfig),
TelemetryRecorder: metric.NewRecorderSingle(telemetryStorage, splitAPI.MetricRecorder, metadata),
}
splitTasks := synchronizer.SplitTasks{
SplitSyncTask: tasks.NewFetchSplitsTask(workers.SplitFetcher, cfg.TaskPeriods.SplitSync, logger),
SegmentSyncTask: tasks.NewFetchSegmentsTask(workers.SegmentFetcher, cfg.TaskPeriods.SegmentSync, advanced.SegmentWorkers, advanced.SegmentQueueSize, logger),
EventSyncTask: tasks.NewRecordEventsTask(workers.EventRecorder, advanced.EventsBulkSize, cfg.TaskPeriods.EventsSync, logger),
ImpressionSyncTask: tasks.NewRecordImpressionsTask(workers.ImpressionRecorder, cfg.TaskPeriods.ImpressionSync, logger, advanced.ImpressionsBulkSize),
TelemetrySyncTask: tasks.NewRecordTelemetryTask(workers.TelemetryRecorder, cfg.TaskPeriods.LatencySync, logger),
}
var impressionsCounter *provisional.ImpressionsCounter
if cfg.ImpressionsMode == config.ImpressionsModeOptimized {
impressionsCounter = provisional.NewImpressionsCounter()
workers.ImpressionsCountRecorder = impressionscount.NewRecorderSingle(impressionsCounter, splitAPI.ImpressionRecorder, metadata, logger)
splitTasks.ImpressionsCountSyncTask = tasks.NewRecordImpressionsCountTask(workers.ImpressionsCountRecorder, logger)
}
impressionManager, err := provisional.NewImpressionManager(managerConfig, impressionsCounter)
if err != nil {
return nil, err
}
syncImpl := synchronizer.NewSynchronizer(
advanced,
splitTasks,
workers,
logger,
inMememoryFullQueue,
)
readyChannel := make(chan int, 1)
syncManager, err := synchronizer.NewSynchronizerManager(
syncImpl,
logger,
advanced,
splitAPI.AuthClient,
splitsStorage,
readyChannel,
)
if err != nil {
return nil, err
}
splitFactory := SplitFactory{
apikey: apikey,
cfg: cfg,
metadata: metadata,
logger: logger,
operationMode: conf.InMemoryStandAlone,
storages: sdkStorages{
splits: splitsStorage,
events: eventsStorage,
impressions: impressionsStorage,
segments: segmentsStorage,
telemetry: telemetryStorage,
},
readinessSubscriptors: make(map[int]chan int),
syncManager: syncManager,
}
splitFactory.status.Store(sdkStatusInitializing)
splitFactory.impressionManager = impressionManager
go splitFactory.initializationInMemory(readyChannel)
return &splitFactory, nil
}
func setupRedisFactory(apikey string, cfg *conf.SplitSdkConfig, logger logging.LoggerInterface, metadata dtos.Metadata) (*SplitFactory, error) {
redisClient, err := redis.NewRedisClient(&cfg.Redis, logger)
if err != nil {
logger.Error("Failed to instantiate redis client.")
return nil, err
}
storages := sdkStorages{
splits: redis.NewSplitStorage(redisClient, logger),
segments: redis.NewSegmentStorage(redisClient, logger),
impressions: redis.NewImpressionStorage(redisClient, metadata, logger),
telemetry: redis.NewMetricsStorage(redisClient, metadata, logger),
events: redis.NewEventsStorage(redisClient, metadata, logger),
}
factory := &SplitFactory{
apikey: apikey,
cfg: cfg,
metadata: metadata,
logger: logger,
operationMode: conf.RedisConsumer,
storages: storages,
readinessSubscriptors: make(map[int]chan int),
}
impressionManager, err := provisional.NewImpressionManager(config.ManagerConfig{
OperationMode: cfg.OperationMode,
ImpressionsMode: cfg.ImpressionsMode,
ListenerEnabled: cfg.Advanced.ImpressionListener != nil,
}, nil)
if err != nil {
return nil, err
}
factory.impressionManager = impressionManager
factory.status.Store(sdkStatusReady)
return factory, nil
}
func setupLocalhostFactory(
apikey string,
cfg *conf.SplitSdkConfig,
logger logging.LoggerInterface,
metadata dtos.Metadata,
) (*SplitFactory, error) {
splitStorage := mutexmap.NewMMSplitStorage()
splitPeriod := cfg.TaskPeriods.SplitSync
readyChannel := make(chan int, 1)
syncManager, err := synchronizer.NewSynchronizerManager(
synchronizer.NewLocal(
splitPeriod,
&service.SplitAPI{
SplitFetcher: local.NewFileSplitFetcher(cfg.SplitFile, logger),
},
splitStorage,
logger,
),
logger,
config.AdvancedConfig{},
nil,
splitStorage,
readyChannel,
)
if err != nil {
return nil, err
}
splitFactory := &SplitFactory{
apikey: apikey,
cfg: cfg,
metadata: metadata,
logger: logger,
storages: sdkStorages{
splits: splitStorage,
impressions: mutexqueue.NewMQImpressionsStorage(cfg.Advanced.ImpressionsQueueSize, make(chan string, 1), logger),
telemetry: mutexmap.NewMMMetricsStorage(),
events: mutexqueue.NewMQEventsStorage(cfg.Advanced.EventsQueueSize, make(chan string, 1), logger),
segments: mutexmap.NewMMSegmentStorage(),
},
readinessSubscriptors: make(map[int]chan int),
syncManager: syncManager,
}
splitFactory.status.Store(sdkStatusInitializing)
impressionManager, err := provisional.NewImpressionManager(config.ManagerConfig{
OperationMode: cfg.OperationMode,
ImpressionsMode: cfg.ImpressionsMode,
ListenerEnabled: cfg.Advanced.ImpressionListener != nil,
}, nil)
if err != nil {
return nil, err
}
splitFactory.impressionManager = impressionManager
// Call fetching tasks as goroutine
go splitFactory.initializationLocalhost(readyChannel)
return splitFactory, nil
}
// newFactory instantiates a new SplitFactory object. Accepts a SplitSdkConfig struct as an argument,
// which will be used to instantiate both the client and the manager
func newFactory(apikey string, cfg *conf.SplitSdkConfig, logger logging.LoggerInterface) (*SplitFactory, error) {
metadata := dtos.Metadata{
SDKVersion: "go-" + splitio.Version,
MachineIP: cfg.IPAddress,
MachineName: cfg.InstanceName,
}
var splitFactory *SplitFactory
var err error
switch cfg.OperationMode {
case conf.InMemoryStandAlone:
splitFactory, err = setupInMemoryFactory(apikey, cfg, logger, metadata)
case conf.RedisConsumer:
splitFactory, err = setupRedisFactory(apikey, cfg, logger, metadata)
case conf.Localhost:
splitFactory, err = setupLocalhostFactory(apikey, cfg, logger, metadata)
default:
err = fmt.Errorf("Invalid operation mode \"%s\"", cfg.OperationMode)
}
if err != nil {
return nil, err
}
if cfg.Advanced.ImpressionListener != nil {
splitFactory.impressionListener = impressionlistener.NewImpressionListenerWrapper(
cfg.Advanced.ImpressionListener,
metadata,
)
}
return splitFactory, nil
}

72
vendor/github.com/splitio/go-client/v6/splitio/client/factory_tracker.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,72 @@
package client
import (
"fmt"
"sync"
"github.com/splitio/go-client/v6/splitio/conf"
"github.com/splitio/go-toolkit/v3/logging"
)
// factoryInstances factory tracker instantiations
var factoryInstances = make(map[string]int64)
var mutex = &sync.Mutex{}
func setFactory(apikey string, logger logging.LoggerInterface) {
mutex.Lock()
defer mutex.Unlock()
counter, exists := factoryInstances[apikey]
if !exists {
if len(factoryInstances) > 0 {
logger.Warning("Factory Instantiation: You already have an instance of the Split factory. Make sure you definitely want " +
"this additional instance. We recommend keeping only one instance of the factory at all times (Singleton pattern) and " +
"reusing it throughout your application.")
}
factoryInstances[apikey] = 1
} else {
if counter == 1 {
logger.Warning("Factory Instantiation: You already have 1 factory with this API Key. We recommend keeping only one instance of the factory " +
"at all times (Singleton pattern) and reusing it throughout your application.")
} else {
logger.Warning(fmt.Sprintf("Factory Instantiation: You already have %d factories with this API Key.", counter) +
" We recommend keeping only one instance of the factory at all times (Singleton pattern) and reusing it throughout your application.")
}
factoryInstances[apikey]++
}
}
// removeInstanceFromTracker decrease the instance of factory track
func removeInstanceFromTracker(apikey string) {
mutex.Lock()
defer mutex.Unlock()
counter, exists := factoryInstances[apikey]
if exists {
if counter == 1 {
delete(factoryInstances, apikey)
} else {
factoryInstances[apikey]--
}
}
}
// NewSplitFactory instantiates a new SplitFactory object. Accepts a SplitSdkConfig struct as an argument,
// which will be used to instantiate both the client and the manager
func NewSplitFactory(apikey string, cfg *conf.SplitSdkConfig) (*SplitFactory, error) {
if cfg == nil {
cfg = conf.Default()
}
logger := setupLogger(cfg)
err := conf.Normalize(apikey, cfg)
if err != nil {
logger.Error(err.Error())
return nil, err
}
splitFactory, err := newFactory(apikey, cfg, logger)
setFactory(apikey, logger)
return splitFactory, err
}

294
vendor/github.com/splitio/go-client/v6/splitio/client/input_validator.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,294 @@
package client
import (
"errors"
"fmt"
"math"
"regexp"
"strconv"
"strings"
"github.com/splitio/go-client/v6/splitio/engine/evaluator/impressionlabels"
"github.com/splitio/go-split-commons/v2/storage"
"github.com/splitio/go-toolkit/v3/datastructures/set"
"github.com/splitio/go-toolkit/v3/logging"
)
// InputValidation struct is responsible for cheking any input of treatment and
// track methods.
// MaxLength constant to check the length of the splits
const MaxLength = 250
// MaxEventLength constant to limit the event size
const MaxEventLength = 32768
// RegExpEventType constant that EventType must match
const RegExpEventType = "^[a-zA-Z0-9][-_.:a-zA-Z0-9]{0,79}$"
type inputValidation struct {
logger logging.LoggerInterface
splitStorage storage.SplitStorageConsumer
}
func parseIfNumeric(value interface{}, operation string) (string, error) {
f, float := value.(float64)
i, integer := value.(int)
i32, integer32 := value.(int32)
i64, integer64 := value.(int64)
if float {
if math.IsNaN(f) || math.IsInf(f, -1) || math.IsInf(f, 1) || math.IsInf(f, 0) {
return "", errors.New(operation + ": you passed an invalid key, key must be a non-empty string")
}
return strconv.FormatFloat(f, 'f', -1, 64), nil
}
if integer {
return strconv.Itoa(i), nil
}
if integer32 {
return strconv.FormatInt(int64(i32), 10), nil
}
if integer64 {
return strconv.FormatInt(i64, 10), nil
}
return "", errors.New(operation + ": you passed an invalid key, key must be a non-empty string")
}
func (i *inputValidation) checkWhitespaces(value string, operation string) string {
trimmed := strings.TrimSpace(value)
if strings.TrimSpace(value) != value {
i.logger.Warning(fmt.Sprintf(operation+": split name '%s' has extra whitespace, trimming", value))
}
return trimmed
}
func checkIsEmptyString(value string, name string, operation string) error {
if strings.TrimSpace(value) == "" {
return errors.New(operation + ": you passed an empty " + name + ", " + name + " must be a non-empty string")
}
return nil
}
func checkIsNotValidLength(value string, name string, operation string) error {
if len(value) > MaxLength {
return errors.New(operation + ": " + name + " too long - must be " + strconv.Itoa(MaxLength) + " characters or less")
}
return nil
}
func checkIsValidString(value string, name string, operation string) error {
err := checkIsEmptyString(value, name, operation)
if err != nil {
return err
}
return checkIsNotValidLength(value, name, operation)
}
func checkValidKeyObject(matchingKey string, bucketingKey *string, operation string) (string, *string, error) {
if bucketingKey == nil {
return "", nil, errors.New(operation + ": you passed a nil bucketingKey, bucketingKey must be a non-empty string")
}
err := checkIsValidString(matchingKey, "matchingKey", operation)
if err != nil {
return "", nil, err
}
err = checkIsValidString(*bucketingKey, "bucketingKey", operation)
if err != nil {
return "", nil, err
}
return matchingKey, bucketingKey, nil
}
// ValidateTreatmentKey implements the validation for Treatment call
func (i *inputValidation) ValidateTreatmentKey(key interface{}, operation string) (string, *string, error) {
if key == nil {
return "", nil, errors.New(operation + ": you passed a nil key, key must be a non-empty string")
}
okey, ok := key.(*Key)
if ok {
return checkValidKeyObject(okey.MatchingKey, &okey.BucketingKey, operation)
}
var sMatchingKey string
var err error
sMatchingKey, ok = key.(string)
if !ok {
sMatchingKey, err = parseIfNumeric(key, operation)
if err != nil {
return "", nil, err
}
i.logger.Warning(fmt.Sprintf(operation+": key %s is not of type string, converting", key))
}
err = checkIsValidString(sMatchingKey, "key", operation)
if err != nil {
return "", nil, err
}
return sMatchingKey, nil, nil
}
// ValidateFeatureName implements the validation for FetureName
func (i *inputValidation) ValidateFeatureName(featureName string, operation string) (string, error) {
err := checkIsEmptyString(featureName, "featureName", operation)
if err != nil {
return "", err
}
return i.checkWhitespaces(featureName, operation), nil
}
func checkEventType(eventType string) error {
err := checkIsEmptyString(eventType, "event type", "Track")
if err != nil {
return err
}
var r = regexp.MustCompile(RegExpEventType)
if !r.MatchString(eventType) {
return errors.New("Track: you passed " + eventType + ", event name must adhere to " +
"the regular expression " + RegExpEventType + ". This means an event " +
"name must be alphanumeric, cannot be more than 80 characters long, and can " +
"only include a dash, underscore, period, or colon as separators of " +
"alphanumeric characters")
}
return nil
}
func (i *inputValidation) checkTrafficType(trafficType string, shouldValidateExistence bool) (string, error) {
err := checkIsEmptyString(trafficType, "traffic type", "Track")
if err != nil {
return "", err
}
toLower := strings.ToLower(trafficType)
if toLower != trafficType {
i.logger.Warning("Track: traffic type should be all lowercase - converting string to lowercase")
}
if shouldValidateExistence && !i.splitStorage.TrafficTypeExists(toLower) {
i.logger.Warning("Track: traffic type " + toLower + " does not have any corresponding Splits in this environment, " +
"make sure youre tracking your events to a valid traffic type defined in the Split console")
}
return toLower, nil
}
func checkValue(value interface{}) error {
if value == nil {
return nil
}
_, float := value.(float64)
_, integer := value.(int)
_, integer32 := value.(int32)
_, integer64 := value.(int64)
if float || integer || integer32 || integer64 {
return nil
}
return errors.New("Track: value must be a number")
}
// ValidateTrackInputs implements the validation for Track call
func (i *inputValidation) ValidateTrackInputs(
key string,
trafficType string,
eventType string,
value interface{},
shouldValidateExistence bool,
) (string, string, string, interface{}, error) {
err := checkIsValidString(key, "key", "Track")
if err != nil {
return "", trafficType, eventType, value, err
}
err = checkEventType(eventType)
if err != nil {
return key, trafficType, "", value, err
}
trafficType, err = i.checkTrafficType(trafficType, shouldValidateExistence)
if err != nil {
return key, "", eventType, value, err
}
err = checkValue(value)
if err != nil {
return key, trafficType, eventType, nil, err
}
return key, trafficType, eventType, value, nil
}
// ValidateManagerInputs implements the validation for Track call
func (i *inputValidation) ValidateManagerInputs(feature string) error {
return checkIsEmptyString(feature, "split name", "Split")
}
// ValidateFeatureNames implements the validation for Treatments call
func (i *inputValidation) ValidateFeatureNames(features []string, operation string) ([]string, error) {
var featuresSet = set.NewSet()
if len(features) == 0 {
return []string{}, errors.New(operation + ": features must be a non-empty array")
}
for _, feature := range features {
f, err := i.ValidateFeatureName(feature, operation)
if err != nil {
i.logger.Error(err.Error())
} else {
featuresSet.Add(f)
}
}
if featuresSet.IsEmpty() {
return []string{}, errors.New(operation + ": features must be a non-empty array")
}
f := make([]string, featuresSet.Size())
for i, v := range featuresSet.List() {
s, ok := v.(string)
if ok {
f[i] = s
}
}
return f, nil
}
func (i *inputValidation) validateTrackProperties(properties map[string]interface{}) (map[string]interface{}, int, error) {
if len(properties) == 0 {
return nil, 0, nil
}
if len(properties) > 300 {
i.logger.Warning("Track: Event has more than 300 properties. Some of them will be trimmed when processed")
}
processed := make(map[string]interface{})
size := 1024 // Average event size is ~750 bytes. Using 1kbyte as a starting point.
for name, value := range properties {
size += len(name)
switch value.(type) {
case int, int32, int64, uint, uint32, uint64, float32, float64, bool, nil:
processed[name] = value
case string:
asStr := value.(string)
size += len(asStr)
processed[name] = value
default:
i.logger.Warning("Property %s is of invalid type. Setting value to nil")
processed[name] = nil
}
if size > MaxEventLength {
i.logger.Error(
"The maximum size allowed for the properties is 32kb. Event not queued",
)
return nil, size, errors.New("The maximum size allowed for the properties is 32kb. Event not queued")
}
}
return processed, size, nil
}
func (i *inputValidation) IsSplitFound(label string, feature string, operation string) bool {
if label == impressionlabels.SplitNotFound {
i.logger.Error(fmt.Sprintf(operation+": you passed %s that does not exist in this environment, please double check what Splits exist in the web console.", feature))
return false
}
return true
}

115
vendor/github.com/splitio/go-client/v6/splitio/client/manager.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,115 @@
package client
import (
"fmt"
"github.com/splitio/go-split-commons/v2/dtos"
"github.com/splitio/go-split-commons/v2/storage"
"github.com/splitio/go-toolkit/v3/logging"
)
// SplitManager provides information of the currently stored splits
type SplitManager struct {
splitStorage storage.SplitStorageConsumer
validator inputValidation
logger logging.LoggerInterface
factory *SplitFactory
}
// SplitView is a partial representation of a currently stored split
type SplitView struct {
Name string `json:"name"`
TrafficType string `json:"trafficType"`
Killed bool `json:"killed"`
Treatments []string `json:"treatments"`
ChangeNumber int64 `json:"changeNumber"`
Configs map[string]string `json:"configs"`
}
func newSplitView(splitDto *dtos.SplitDTO) *SplitView {
treatments := make([]string, 0)
for _, condition := range splitDto.Conditions {
for _, partition := range condition.Partitions {
treatments = append(treatments, partition.Treatment)
}
}
return &SplitView{
ChangeNumber: splitDto.ChangeNumber,
Killed: splitDto.Killed,
Name: splitDto.Name,
TrafficType: splitDto.TrafficTypeName,
Treatments: treatments,
Configs: splitDto.Configurations,
}
}
// SplitNames returns a list with the name of all the currently stored splits
func (m *SplitManager) SplitNames() []string {
if m.isDestroyed() {
m.logger.Error("Client has already been destroyed - no calls possible")
return []string{}
}
if !m.isReady() {
m.logger.Warning("splitNames: the SDK is not ready, results may be incorrect. Make sure to wait for SDK readiness before using this method")
}
return m.splitStorage.SplitNames()
}
// Splits returns a list of a partial view of every currently stored split
func (m *SplitManager) Splits() []SplitView {
if m.isDestroyed() {
m.logger.Error("Client has already been destroyed - no calls possible")
return []SplitView{}
}
if !m.isReady() {
m.logger.Warning("splits: the SDK is not ready, results may be incorrect. Make sure to wait for SDK readiness before using this method")
}
splitViews := make([]SplitView, 0)
splits := m.splitStorage.All()
for _, split := range splits {
splitViews = append(splitViews, *newSplitView(&split))
}
return splitViews
}
// Split returns a partial view of a particular split
func (m *SplitManager) Split(feature string) *SplitView {
if m.isDestroyed() {
m.logger.Error("Client has already been destroyed - no calls possible")
return nil
}
if !m.isReady() {
m.logger.Warning("split: the SDK is not ready, results may be incorrect. Make sure to wait for SDK readiness before using this method")
}
err := m.validator.ValidateManagerInputs(feature)
if err != nil {
m.logger.Error(err.Error())
return nil
}
split := m.splitStorage.Split(feature)
if split != nil {
return newSplitView(split)
}
m.logger.Error(fmt.Sprintf("Split: you passed %s that does not exist in this environment, please double check what Splits exist in the web console.", feature))
return nil
}
// BlockUntilReady Calls BlockUntilReady on factory to block manager on readiness
func (m *SplitManager) BlockUntilReady(timer int) error {
return m.factory.BlockUntilReady(timer)
}
func (m *SplitManager) isDestroyed() bool {
return m.factory.IsDestroyed()
}
func (m *SplitManager) isReady() bool {
return m.factory.IsReady()
}

22
vendor/github.com/splitio/go-client/v6/splitio/conf/defaults.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,22 @@
package conf
const (
defaultHTTPTimeout = 30
defaultTaskPeriod = 60
defaultRedisHost = "localhost"
defaultRedisPort = 6379
defaultRedisDb = 0
defaultSegmentQueueSize = 500
defaultSegmentWorkers = 10
defaultImpressionSyncOptimized = 300
defaultImpressionSyncDebug = 60
)
const (
minSplitSync = 5
minSegmentSync = 30
minImpressionSync = 1
minImpressionSyncOptimized = 60
minEventSync = 1
minTelemetrySync = 30
)

259
vendor/github.com/splitio/go-client/v6/splitio/conf/sdkconf.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,259 @@
// Package conf contains configuration structures used to setup the SDK
package conf
import (
"errors"
"fmt"
"math"
"os/user"
"path"
"strings"
impressionlistener "github.com/splitio/go-client/v6/splitio/impressionListener"
"github.com/splitio/go-split-commons/v2/conf"
"github.com/splitio/go-toolkit/v3/datastructures/set"
"github.com/splitio/go-toolkit/v3/logging"
"github.com/splitio/go-toolkit/v3/nethelpers"
)
const (
// RedisConsumer mode
RedisConsumer = "redis-consumer"
// Localhost mode
Localhost = "localhost"
// InMemoryStandAlone mode
InMemoryStandAlone = "inmemory-standalone"
)
// SplitSdkConfig struct ...
// struct used to setup a Split.io SDK client.
//
// Parameters:
// - OperationMode (Required) Must be one of ["inmemory-standalone", "redis-consumer"]
// - InstanceName (Optional) Name to be used when submitting metrics & impressions to split servers
// - IPAddress (Optional) Address to be used when submitting metrics & impressions to split servers
// - BlockUntilReady (Optional) How much to wait until the sdk is ready
// - SplitFile (Optional) File with splits to use when running in localhost mode
// - LabelsEnabled (Optional) Can be used to disable labels if the user does not want to send that info to split servers.
// - Logger: (Optional) Custom logger complying with logging.LoggerInterface
// - LoggerConfig: (Optional) Options to setup the sdk's own logger
// - TaskPeriods: (Optional) How often should each task run
// - Redis: (Required for "redis-consumer". Sets up Redis config
// - Advanced: (Optional) Sets up various advanced options for the sdk
// - ImpressionsMode (Optional) Flag for enabling local impressions dedupe - Possible values <'optimized'|'debug'>
type SplitSdkConfig struct {
OperationMode string
InstanceName string
IPAddress string
IPAddressesEnabled bool
BlockUntilReady int
SplitFile string
LabelsEnabled bool
SplitSyncProxyURL string
Logger logging.LoggerInterface
LoggerConfig logging.LoggerOptions
TaskPeriods TaskPeriods
Advanced AdvancedConfig
Redis conf.RedisConfig
ImpressionsMode string
}
// TaskPeriods struct is used to configure the period for each synchronization task
type TaskPeriods struct {
SplitSync int
SegmentSync int
ImpressionSync int
GaugeSync int
CounterSync int
LatencySync int
EventsSync int
}
// AdvancedConfig exposes more configurable parameters that can be used to further tailor the sdk to the user's needs
// - ImpressionListener - struct that will be notified each time an impression bulk is ready
// - HTTPTimeout - Timeout for HTTP requests when doing synchronization
// - SegmentQueueSize - How many segments can be queued for updating (should be >= # segments the user has)
// - SegmentWorkers - How many workers will be used when performing segments sync.
type AdvancedConfig struct {
ImpressionListener impressionlistener.ImpressionListener
HTTPTimeout int
SegmentQueueSize int
SegmentWorkers int
AuthServiceURL string
SdkURL string
EventsURL string
StreamingServiceURL string
EventsBulkSize int64
EventsQueueSize int
ImpressionsQueueSize int
ImpressionsBulkSize int64
StreamingEnabled bool
}
// Default returns a config struct with all the default values
func Default() *SplitSdkConfig {
instanceName := "unknown"
ipAddress, err := nethelpers.ExternalIP()
if err != nil {
ipAddress = "unknown"
} else {
instanceName = fmt.Sprintf("ip-%s", strings.Replace(ipAddress, ".", "-", -1))
}
var splitFile string
usr, err := user.Current()
if err != nil {
splitFile = "splits"
} else {
splitFile = path.Join(usr.HomeDir, ".splits")
}
return &SplitSdkConfig{
OperationMode: InMemoryStandAlone,
LabelsEnabled: true,
IPAddress: ipAddress,
IPAddressesEnabled: true,
InstanceName: instanceName,
Logger: nil,
LoggerConfig: logging.LoggerOptions{},
SplitFile: splitFile,
ImpressionsMode: conf.ImpressionsModeOptimized,
Redis: conf.RedisConfig{
Database: 0,
Host: "localhost",
Password: "",
Port: 6379,
Prefix: "",
},
TaskPeriods: TaskPeriods{
GaugeSync: defaultTaskPeriod,
CounterSync: defaultTaskPeriod,
LatencySync: defaultTaskPeriod,
ImpressionSync: defaultImpressionSyncOptimized,
SegmentSync: defaultTaskPeriod,
SplitSync: defaultTaskPeriod,
EventsSync: defaultTaskPeriod,
},
Advanced: AdvancedConfig{
AuthServiceURL: "",
EventsURL: "",
SdkURL: "",
StreamingServiceURL: "",
HTTPTimeout: 0,
ImpressionListener: nil,
SegmentQueueSize: 500,
SegmentWorkers: 10,
EventsBulkSize: 5000,
EventsQueueSize: 10000,
ImpressionsQueueSize: 10000,
ImpressionsBulkSize: 5000,
StreamingEnabled: true,
},
}
}
func checkImpressionSync(cfg *SplitSdkConfig) error {
if cfg.TaskPeriods.ImpressionSync == 0 {
cfg.TaskPeriods.ImpressionSync = defaultImpressionSyncOptimized
} else {
if cfg.TaskPeriods.ImpressionSync < minImpressionSyncOptimized {
return fmt.Errorf("ImpressionSync must be >= %d. Actual is: %d", minImpressionSyncOptimized, cfg.TaskPeriods.ImpressionSync)
}
cfg.TaskPeriods.ImpressionSync = int(math.Max(float64(minImpressionSyncOptimized), float64(cfg.TaskPeriods.ImpressionSync)))
}
return nil
}
func validConfigRates(cfg *SplitSdkConfig) error {
if cfg.OperationMode == RedisConsumer {
return nil
}
if cfg.TaskPeriods.SplitSync < minSplitSync {
return fmt.Errorf("SplitSync must be >= %d. Actual is: %d", minSplitSync, cfg.TaskPeriods.SplitSync)
}
if cfg.TaskPeriods.SegmentSync < minSegmentSync {
return fmt.Errorf("SegmentSync must be >= %d. Actual is: %d", minSegmentSync, cfg.TaskPeriods.SegmentSync)
}
cfg.ImpressionsMode = strings.ToLower(cfg.ImpressionsMode)
switch cfg.ImpressionsMode {
case conf.ImpressionsModeOptimized:
err := checkImpressionSync(cfg)
if err != nil {
return err
}
case conf.ImpressionsModeDebug:
if cfg.TaskPeriods.ImpressionSync == 0 {
cfg.TaskPeriods.ImpressionSync = defaultImpressionSyncDebug
} else {
if cfg.TaskPeriods.ImpressionSync < minImpressionSync {
return fmt.Errorf("ImpressionSync must be >= %d. Actual is: %d", minImpressionSync, cfg.TaskPeriods.ImpressionSync)
}
}
default:
fmt.Println(`You passed an invalid impressionsMode, impressionsMode should be one of the following values: 'debug' or 'optimized'. Defaulting to 'optimized' mode.`)
cfg.ImpressionsMode = conf.ImpressionsModeOptimized
err := checkImpressionSync(cfg)
if err != nil {
return err
}
}
if cfg.TaskPeriods.EventsSync < minEventSync {
return fmt.Errorf("EventsSync must be >= %d. Actual is: %d", minEventSync, cfg.TaskPeriods.EventsSync)
}
if cfg.TaskPeriods.LatencySync < minTelemetrySync {
return fmt.Errorf("LatencySync must be >= %d. Actual is: %d", minTelemetrySync, cfg.TaskPeriods.LatencySync)
}
if cfg.TaskPeriods.GaugeSync < minTelemetrySync {
return fmt.Errorf("GaugeSync must be >= %d. Actual is: %d", minTelemetrySync, cfg.TaskPeriods.GaugeSync)
}
if cfg.TaskPeriods.CounterSync < minTelemetrySync {
return fmt.Errorf("CounterSync must be >= %d. Actual is: %d", minTelemetrySync, cfg.TaskPeriods.CounterSync)
}
if cfg.Advanced.SegmentWorkers <= 0 {
return errors.New("Number of workers for fetching segments MUST be greater than zero")
}
return nil
}
// Normalize checks that the parameters passed by the user are correct and updates parameters if necessary.
// returns an error if something is wrong
func Normalize(apikey string, cfg *SplitSdkConfig) error {
// Fail if no apikey is provided
if apikey == "" && cfg.OperationMode != Localhost {
return errors.New("Factory instantiation: you passed an empty apikey, apikey must be a non-empty string")
}
// To keep the interface consistent with other sdks we accept "localhost" as an apikey,
// which sets the operation mode to localhost
if apikey == Localhost {
cfg.OperationMode = Localhost
}
// Fail if an invalid operation-mode is provided
operationModes := set.NewSet(
Localhost,
InMemoryStandAlone,
RedisConsumer,
)
if !operationModes.Has(cfg.OperationMode) {
return fmt.Errorf("OperationMode parameter must be one of: %v", operationModes.List())
}
if cfg.SplitSyncProxyURL != "" {
cfg.Advanced.AuthServiceURL = cfg.SplitSyncProxyURL
cfg.Advanced.SdkURL = cfg.SplitSyncProxyURL
cfg.Advanced.EventsURL = cfg.SplitSyncProxyURL
cfg.Advanced.StreamingServiceURL = cfg.SplitSyncProxyURL
}
if !cfg.IPAddressesEnabled {
cfg.IPAddress = "NA"
cfg.InstanceName = "NA"
}
return validConfigRates(cfg)
}

48
vendor/github.com/splitio/go-client/v6/splitio/conf/util.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,48 @@
package conf
import (
"strings"
"github.com/splitio/go-split-commons/v2/conf"
)
// NormalizeSDKConf compares against SDK Config to set defaults
func NormalizeSDKConf(sdkConfig AdvancedConfig) conf.AdvancedConfig {
config := conf.GetDefaultAdvancedConfig()
if sdkConfig.HTTPTimeout > 0 {
config.HTTPTimeout = sdkConfig.HTTPTimeout
}
if sdkConfig.EventsBulkSize > 0 {
config.EventsBulkSize = sdkConfig.EventsBulkSize
}
if sdkConfig.EventsQueueSize > 0 {
config.EventsQueueSize = sdkConfig.EventsQueueSize
}
if sdkConfig.ImpressionsBulkSize > 0 {
config.ImpressionsBulkSize = sdkConfig.ImpressionsBulkSize
}
if sdkConfig.ImpressionsQueueSize > 0 {
config.ImpressionsQueueSize = sdkConfig.ImpressionsQueueSize
}
if sdkConfig.SegmentQueueSize > 0 {
config.SegmentQueueSize = sdkConfig.SegmentQueueSize
}
if sdkConfig.SegmentWorkers > 0 {
config.SegmentWorkers = sdkConfig.SegmentWorkers
}
if strings.TrimSpace(sdkConfig.EventsURL) != "" {
config.EventsURL = sdkConfig.EventsURL
}
if strings.TrimSpace(sdkConfig.SdkURL) != "" {
config.SdkURL = sdkConfig.SdkURL
}
if strings.TrimSpace(sdkConfig.AuthServiceURL) != "" {
config.AuthServiceURL = sdkConfig.AuthServiceURL
}
if strings.TrimSpace(sdkConfig.StreamingServiceURL) != "" {
config.StreamingServiceURL = sdkConfig.StreamingServiceURL
}
config.StreamingEnabled = sdkConfig.StreamingEnabled
return config
}

70
vendor/github.com/splitio/go-client/v6/splitio/engine/engine.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,70 @@
package engine
import (
"fmt"
"math"
"github.com/splitio/go-client/v6/splitio/engine/evaluator/impressionlabels"
"github.com/splitio/go-client/v6/splitio/engine/grammar"
"github.com/splitio/go-client/v6/splitio/engine/hash"
"github.com/splitio/go-toolkit/v3/logging"
)
// Engine struct is responsible for cheking if any of the conditions of the split matches,
// performing traffic allocation, calculating the bucket and returning the appropriate treatment
type Engine struct {
logger logging.LoggerInterface
}
// DoEvaluation performs the main evaluation against each condition
func (e *Engine) DoEvaluation(
split *grammar.Split,
key string,
bucketingKey string,
attributes map[string]interface{},
) (*string, string) {
inRollOut := false
for _, condition := range split.Conditions() {
if !inRollOut && condition.ConditionType() == grammar.ConditionTypeRollout {
if split.TrafficAllocation() < 100 {
bucket := e.calculateBucket(split.Algo(), bucketingKey, split.TrafficAllocationSeed())
if bucket > split.TrafficAllocation() {
e.logger.Debug(fmt.Sprintf(
"Traffic allocation exceeded for feature %s and key %s."+
" Returning default treatment", split.Name(), key,
))
defaultTreatment := split.DefaultTreatment()
return &defaultTreatment, impressionlabels.NotInSplit
}
inRollOut = true
}
}
if condition.Matches(key, &bucketingKey, attributes) {
bucket := e.calculateBucket(split.Algo(), bucketingKey, split.Seed())
treatment := condition.CalculateTreatment(bucket)
return treatment, condition.Label()
}
}
return nil, impressionlabels.NoConditionMatched
}
func (e *Engine) calculateBucket(algo int, bucketingKey string, seed int64) int {
var hashedKey uint32
switch algo {
case grammar.SplitAlgoMurmur:
hashedKey = hash.Murmur3_32([]byte(bucketingKey), uint32(seed))
case grammar.SplitAlgoLegacy:
fallthrough
default:
hashedKey = hash.Legacy([]byte(bucketingKey), uint32(seed))
}
return int(math.Abs(float64(hashedKey%100)) + 1)
}
// NewEngine instantiates and returns a new engine
func NewEngine(logger logging.LoggerInterface) *Engine {
return &Engine{logger: logger}
}

160
vendor/github.com/splitio/go-client/v6/splitio/engine/evaluator/evaluator.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,160 @@
package evaluator
import (
"fmt"
"time"
"github.com/splitio/go-client/v6/splitio/engine"
"github.com/splitio/go-client/v6/splitio/engine/evaluator/impressionlabels"
"github.com/splitio/go-client/v6/splitio/engine/grammar"
"github.com/splitio/go-split-commons/v2/dtos"
"github.com/splitio/go-split-commons/v2/storage"
"github.com/splitio/go-toolkit/v3/injection"
"github.com/splitio/go-toolkit/v3/logging"
)
const (
// Control is the treatment returned when something goes wrong
Control = "control"
)
// Result represents the result of an evaluation, including the resulting treatment, the label for the impression,
// the latency and error if any
type Result struct {
Treatment string
Label string
EvaluationTimeNs int64
SplitChangeNumber int64
Config *string
}
// Results represents the result of multiple evaluations at once
type Results struct {
Evaluations map[string]Result
EvaluationTimeNs int64
}
// Evaluator struct is the main evaluator
type Evaluator struct {
splitStorage storage.SplitStorageConsumer
segmentStorage storage.SegmentStorageConsumer
eng *engine.Engine
logger logging.LoggerInterface
}
// NewEvaluator instantiates an Evaluator struct and returns a reference to it
func NewEvaluator(
splitStorage storage.SplitStorageConsumer,
segmentStorage storage.SegmentStorageConsumer,
eng *engine.Engine,
logger logging.LoggerInterface,
) *Evaluator {
return &Evaluator{
splitStorage: splitStorage,
segmentStorage: segmentStorage,
eng: eng,
logger: logger,
}
}
func (e *Evaluator) evaluateTreatment(key string, bucketingKey string, feature string, splitDto *dtos.SplitDTO, attributes map[string]interface{}) *Result {
var config *string
if splitDto == nil {
e.logger.Warning(fmt.Sprintf("Feature %s not found, returning control.", feature))
return &Result{Treatment: Control, Label: impressionlabels.SplitNotFound, Config: config}
}
ctx := injection.NewContext()
ctx.AddDependency("segmentStorage", e.segmentStorage)
ctx.AddDependency("evaluator", e)
split := grammar.NewSplit(splitDto, ctx, e.logger)
if split.Killed() {
e.logger.Warning(fmt.Sprintf(
"Feature %s has been killed, returning default treatment: %s",
feature,
split.DefaultTreatment(),
))
if _, ok := split.Configurations()[split.DefaultTreatment()]; ok {
treatmentConfig := split.Configurations()[split.DefaultTreatment()]
config = &treatmentConfig
}
return &Result{
Treatment: split.DefaultTreatment(),
Label: impressionlabels.Killed,
SplitChangeNumber: split.ChangeNumber(),
Config: config,
}
}
treatment, label := e.eng.DoEvaluation(split, key, bucketingKey, attributes)
if treatment == nil {
e.logger.Warning(fmt.Sprintf(
"No condition matched, returning default treatment: %s",
split.DefaultTreatment(),
))
defaultTreatment := split.DefaultTreatment()
treatment = &defaultTreatment
label = impressionlabels.NoConditionMatched
}
if _, ok := split.Configurations()[*treatment]; ok {
treatmentConfig := split.Configurations()[*treatment]
config = &treatmentConfig
}
return &Result{
Treatment: *treatment,
Label: label,
SplitChangeNumber: split.ChangeNumber(),
Config: config,
}
}
// EvaluateFeature returns a struct with the resulting treatment and extra information for the impression
func (e *Evaluator) EvaluateFeature(key string, bucketingKey *string, feature string, attributes map[string]interface{}) *Result {
before := time.Now()
splitDto := e.splitStorage.Split(feature)
if bucketingKey == nil {
bucketingKey = &key
}
result := e.evaluateTreatment(key, *bucketingKey, feature, splitDto, attributes)
after := time.Now()
result.EvaluationTimeNs = after.Sub(before).Nanoseconds()
return result
}
// EvaluateFeatures returns a struct with the resulting treatment and extra information for the impression
func (e *Evaluator) EvaluateFeatures(key string, bucketingKey *string, features []string, attributes map[string]interface{}) Results {
var results = Results{
Evaluations: make(map[string]Result),
EvaluationTimeNs: 0,
}
before := time.Now()
splits := e.splitStorage.FetchMany(features)
if bucketingKey == nil {
bucketingKey = &key
}
for _, feature := range features {
results.Evaluations[feature] = *e.evaluateTreatment(key, *bucketingKey, feature, splits[feature], attributes)
}
after := time.Now()
results.EvaluationTimeNs = after.Sub(before).Nanoseconds()
return results
}
// EvaluateDependency SHOULD ONLY BE USED by DependencyMatcher.
// It's used to break the dependency cycle between matchers and evaluators.
func (e *Evaluator) EvaluateDependency(key string, bucketingKey *string, feature string, attributes map[string]interface{}) string {
res := e.EvaluateFeature(key, bucketingKey, feature, attributes)
return res.Treatment
}

22
vendor/github.com/splitio/go-client/v6/splitio/engine/evaluator/impressionlabels/impression_labels.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,22 @@
package impressionlabels
// SplitNotFound label will be returned when the split requested is not present in storage
const SplitNotFound = "definition not found"
// Killed label will be returned when the split requested has been killed
const Killed = "killed"
// NoConditionMatched label will be returned when no condition of the split has matched
const NoConditionMatched = "default rule"
// MatcherNotFound label will be returned when matchertype is unknown
const MatcherNotFound = "matcher not found"
// NotInSplit label will be returned when traffic allocation fails
const NotInSplit = "not in split"
// Exception label will be returned if something goes wrong during the split evaluation
const Exception = "exception"
// ClientNotReady label will be returned when the client is not ready
const ClientNotReady = "not ready"

7
vendor/github.com/splitio/go-client/v6/splitio/engine/evaluator/interface.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,7 @@
package evaluator
// Interface should be implemented by concrete treatment evaluator structs
type Interface interface {
EvaluateFeature(key string, bucketingKey *string, feature string, attributes map[string]interface{}) *Result
EvaluateFeatures(key string, bucketingKey *string, features []string, attributes map[string]interface{}) Results
}

99
vendor/github.com/splitio/go-client/v6/splitio/engine/grammar/condition.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,99 @@
package grammar
import (
"github.com/splitio/go-client/v6/splitio/engine/grammar/matchers"
"github.com/splitio/go-split-commons/v2/dtos"
"github.com/splitio/go-toolkit/v3/injection"
"github.com/splitio/go-toolkit/v3/logging"
)
// Condition struct with added logic that wraps around a DTO
type Condition struct {
matchers []matchers.MatcherInterface
combiner string
partitions []Partition
label string
conditionType string
}
// NewCondition instantiates a new Condition struct with appropriate wrappers around dtos and returns it.
func NewCondition(cond *dtos.ConditionDTO, ctx *injection.Context, logger logging.LoggerInterface) *Condition {
partitions := make([]Partition, 0)
for _, part := range cond.Partitions {
partitions = append(partitions, Partition{partitionData: part})
}
matcherObjs := make([]matchers.MatcherInterface, 0)
for _, matcher := range cond.MatcherGroup.Matchers {
m, err := matchers.BuildMatcher(&matcher, ctx, logger)
if err == nil {
matcherObjs = append(matcherObjs, m)
}
}
return &Condition{
combiner: cond.MatcherGroup.Combiner,
matchers: matcherObjs,
partitions: partitions,
label: cond.Label,
conditionType: cond.ConditionType,
}
}
// Partition struct with added logic that wraps around a DTO
type Partition struct {
partitionData dtos.PartitionDTO
}
// ConditionType returns validated condition type. Whitelist by default
func (c *Condition) ConditionType() string {
switch c.conditionType {
case ConditionTypeRollout:
return ConditionTypeRollout
case ConditionTypeWhitelist:
return ConditionTypeWhitelist
default:
return ConditionTypeWhitelist
}
}
// Label returns the condition's label
func (c *Condition) Label() string {
return c.label
}
// Matches returns true if the condition matches for a specific key and/or set of attributes
func (c *Condition) Matches(key string, bucketingKey *string, attributes map[string]interface{}) bool {
partial := make([]bool, len(c.matchers))
for i, matcher := range c.matchers {
partial[i] = matcher.Match(key, attributes, bucketingKey)
if matcher.Negate() {
partial[i] = !partial[i]
}
}
return applyCombiner(partial, c.combiner)
}
// CalculateTreatment calulates the treatment for a specific condition based on the bucket
func (c *Condition) CalculateTreatment(bucket int) *string {
accum := 0
for _, partition := range c.partitions {
accum += partition.partitionData.Size
if bucket <= accum {
return &partition.partitionData.Treatment
}
}
return nil
}
func applyCombiner(results []bool, combiner string) bool {
temp := true
switch combiner {
case "AND":
for _, result := range results {
temp = temp && result
}
default:
return false
}
return temp
}

22
vendor/github.com/splitio/go-client/v6/splitio/engine/grammar/constants.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,22 @@
package grammar
const (
// SplitStatusActive represents an active split
SplitStatusActive = "ACTIVE"
// SplitStatusArchived represents an archived split
SplitStatusArchived = "ARCHIVED"
// SplitAlgoLegacy represents the legacy implementation of hash function for bucketing
SplitAlgoLegacy = 1
// SplitAlgoMurmur represents the murmur implementation of the hash funcion for bucketing
SplitAlgoMurmur = 2
// ConditionTypeWhitelist represents a normal condition
ConditionTypeWhitelist = "WHITELIST"
// ConditionTypeRollout represents a condition that will return default if traffic allocatio is exceeded
ConditionTypeRollout = "ROLLOUT"
// MatcherCombinerAnd represents that all matchers in the group are required
MatcherCombinerAnd = 0
)

16
vendor/github.com/splitio/go-client/v6/splitio/engine/grammar/matchers/allkeys.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,16 @@
package matchers
// AllKeysMatcher matches any given key and set of attributes
type AllKeysMatcher struct {
Matcher
}
// Match implementation for AllKeysMatcher
func (m AllKeysMatcher) Match(key string, attributes map[string]interface{}, bucketingKey *string) bool {
return true
}
// NewAllKeysMatcher returns a pointer to a new instance of AllKeysMatcher
func NewAllKeysMatcher(negate bool) *AllKeysMatcher {
return &AllKeysMatcher{Matcher: Matcher{negate: negate}}
}

56
vendor/github.com/splitio/go-client/v6/splitio/engine/grammar/matchers/allofset.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,56 @@
package matchers
import (
"fmt"
"github.com/splitio/go-toolkit/v3/datastructures/set"
"reflect"
)
// ContainsAllOfSetMatcher matches if the set supplied to the getTreatment is a superset of the one in the split
type ContainsAllOfSetMatcher struct {
Matcher
comparisonSet *set.ThreadUnsafeSet
}
// Match returns true if the set provided is a superset of the one in the split
func (m *ContainsAllOfSetMatcher) Match(key string, attributes map[string]interface{}, bucketingKey *string) bool {
matchingKey, err := m.matchingKey(key, attributes)
if err != nil {
m.logger.Error("AllOfSetMatcher: ", err)
return false
}
conv, ok := matchingKey.([]string)
if !ok {
m.logger.Error(
"AllOfSetMatcher: Attribute passed is not a slice of strings. ",
fmt.Sprintf("Key is of type %s\n", reflect.TypeOf(matchingKey).String()),
)
return false
}
matchingSet := set.NewSet()
for _, x := range conv {
matchingSet.Add(x)
}
res := m.comparisonSet.IsSuperset(matchingSet)
return res
}
// NewContainsAllOfSetMatcher returns a pointer to a new instance of ContainsAllOfSetMatcher
func NewContainsAllOfSetMatcher(negate bool, setItems []string, attributeName *string) *ContainsAllOfSetMatcher {
setObj := set.NewSet()
for _, item := range setItems {
setObj.Add(item)
}
return &ContainsAllOfSetMatcher{
Matcher: Matcher{
negate: negate,
attributeName: attributeName,
},
comparisonSet: setObj,
}
}

51
vendor/github.com/splitio/go-client/v6/splitio/engine/grammar/matchers/anyofset.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,51 @@
package matchers
import (
"github.com/splitio/go-toolkit/v3/datastructures/set"
)
// ContainsAnyOfSetMatcher matches if the set supplied to the getTreatment is a superset of the one in the split
type ContainsAnyOfSetMatcher struct {
Matcher
comparisonSet *set.ThreadUnsafeSet
}
// Match returns true if the set provided is a superset of the one in the split
func (m *ContainsAnyOfSetMatcher) Match(key string, attributes map[string]interface{}, bucketingKey *string) bool {
matchingKey, err := m.matchingKey(key, attributes)
if err != nil {
m.logger.Error("AnyOfSetMatcher: ", err)
return false
}
conv, ok := matchingKey.([]string)
if !ok {
m.logger.Error("AnyOfSetMatcher: Failed to parse the key as a []string")
return false
}
matchingSet := set.NewSet()
for _, x := range conv {
matchingSet.Add(x)
}
intersection := set.Intersection(matchingSet, m.comparisonSet)
return intersection.Size() > 0
}
// NewContainsAnyOfSetMatcher returns a pointer to a new instance of ContainsAnyOfSetMatcher
func NewContainsAnyOfSetMatcher(negate bool, setItems []string, attributeName *string) *ContainsAnyOfSetMatcher {
setObj := set.NewSet()
for _, item := range setItems {
setObj.Add(item)
}
return &ContainsAnyOfSetMatcher{
Matcher: Matcher{
negate: negate,
attributeName: attributeName,
},
comparisonSet: setObj,
}
}

70
vendor/github.com/splitio/go-client/v6/splitio/engine/grammar/matchers/between.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,70 @@
package matchers
import (
"fmt"
"github.com/splitio/go-client/v6/splitio/engine/grammar/matchers/datatypes"
"reflect"
)
// BetweenMatcher will match if two numbers or two datetimes are equal
type BetweenMatcher struct {
Matcher
ComparisonDataType string
LowerComparisonValue int64
UpperComparisonValue int64
}
// Match will match if the matchingValue is between lowerComparisonValue and upperComparisonValue
func (m *BetweenMatcher) Match(key string, attributes map[string]interface{}, bucketingKey *string) bool {
matchingRaw, err := m.matchingKey(key, attributes)
if err != nil {
m.logger.Error("BetweenMatcher: Could not retrieve matching key. ", err)
return false
}
var matchingValue int64
matchingValue, okMatching := matchingRaw.(int64)
if !okMatching {
var asInt int
asInt, okMatching = matchingRaw.(int)
if okMatching {
matchingValue = int64(asInt)
}
}
if !okMatching {
m.logger.Error(
"BetweenMatcher: Could not parse attribute to an int. ",
fmt.Sprintf("Attribute is of type %s\n", reflect.TypeOf(matchingRaw).String()),
)
return false
}
var comparisonLower int64
var comparisonUpper int64
switch m.ComparisonDataType {
case datatypes.Number:
comparisonLower = m.LowerComparisonValue
comparisonUpper = m.UpperComparisonValue
case datatypes.Datetime:
matchingValue = datatypes.ZeroSecondsTS(matchingValue)
comparisonLower = datatypes.ZeroSecondsTS(datatypes.TsFromJava(m.LowerComparisonValue))
comparisonUpper = datatypes.ZeroSecondsTS(datatypes.TsFromJava(m.UpperComparisonValue))
default:
m.base().logger.Error(fmt.Sprintf("BetweenMatcher: Incorrect type %s", m.ComparisonDataType))
return false
}
return matchingValue >= comparisonLower && matchingValue <= comparisonUpper
}
// NewBetweenMatcher returns a pointer to a new instance of BetweenMatcher
func NewBetweenMatcher(negate bool, lower int64, upper int64, cmpType string, attributeName *string) *BetweenMatcher {
return &BetweenMatcher{
Matcher: Matcher{
negate: negate,
attributeName: attributeName,
},
LowerComparisonValue: lower,
UpperComparisonValue: upper,
ComparisonDataType: cmpType,
}
}

60
vendor/github.com/splitio/go-client/v6/splitio/engine/grammar/matchers/boolean.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,60 @@
package matchers
import (
"reflect"
"strconv"
"strings"
)
// BooleanMatcher returns true if the value supplied can be interpreted as a boolean and is equal to the one stored
type BooleanMatcher struct {
Matcher
value *bool
}
// Match returns true if the value supplied can be interpreted as a boolean and is equal to the one stored
func (m *BooleanMatcher) Match(key string, attributes map[string]interface{}, bucketingKey *string) bool {
matchingKey, err := m.matchingKey(key, attributes)
if err != nil {
m.logger.Error("BooleanMatcher: Couldn't parse matching key to a boolean")
return false
}
var asBool bool
var ok bool
switch reflect.TypeOf(matchingKey).Kind() {
case reflect.String:
asStr, ok := matchingKey.(string)
if !ok {
m.logger.Error("BooleanMatcher: Couldn't type-assert string")
return false
}
asBool, err = strconv.ParseBool(strings.ToLower(asStr))
if err != nil {
m.logger.Error("BooleanMatcher: Couldn't parse boolean from string")
return false
}
case reflect.Bool:
asBool, ok = matchingKey.(bool)
if !ok {
m.logger.Error("BooleanMatcher: Couldn't type assert boolean")
return false
}
default:
m.logger.Error("BooleanMatcher: Incompatible type: ", reflect.TypeOf(matchingKey).String())
return false
}
return m.value != nil && *m.value == asBool
}
// NewBooleanMatcher instantiates a new BooleanMatcher
func NewBooleanMatcher(negate bool, value *bool, attributeName *string) *BooleanMatcher {
return &BooleanMatcher{
Matcher: Matcher{
negate: negate,
attributeName: attributeName,
},
value: value,
}
}

45
vendor/github.com/splitio/go-client/v6/splitio/engine/grammar/matchers/contains.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,45 @@
package matchers
import (
"strings"
)
// ContainsStringMatcher matches strings contain one of the substrings in the split
type ContainsStringMatcher struct {
Matcher
substrings []string
}
// Match returns true if the key contains one of the substrings in the split
func (m *ContainsStringMatcher) Match(key string, attributes map[string]interface{}, bucketingKey *string) bool {
matchingKey, err := m.matchingKey(key, attributes)
if err != nil {
m.logger.Error("ContainsAllOfSetMatcher: Error retrieving matching key")
return false
}
asString, ok := matchingKey.(string)
if !ok {
m.logger.Error("ContainsAllOfSetMatcher: Failed to type-assert string")
return false
}
for _, substring := range m.substrings {
if strings.Contains(asString, substring) {
return true
}
}
return false
}
// NewContainsStringMatcher returns a new instance of ContainsStringMatcher
func NewContainsStringMatcher(negate bool, substrings []string, attributeName *string) *ContainsStringMatcher {
return &ContainsStringMatcher{
Matcher: Matcher{
negate: negate,
attributeName: attributeName,
},
substrings: substrings,
}
}

33
vendor/github.com/splitio/go-client/v6/splitio/engine/grammar/matchers/datatypes/datatypes.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,33 @@
package datatypes
import (
"time"
)
const (
// Number data type
Number = "NUMBER"
// Datetime data type
Datetime = "DATETIME"
)
// TsFromJava converts a java timestamp to standard unix format
func TsFromJava(ts int64) int64 {
return ts / 1000
}
// ZeroTimeTS Takes a timestamp in milliseconds as a parameter and
// returns another timestamp in seconds with the same date and zero time.
func ZeroTimeTS(ts int64) int64 {
t := time.Unix(ts, 0).UTC() // Timestamp is converted from milliseconds to seconds
rounded := time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, time.UTC)
return rounded.Unix()
}
// ZeroSecondsTS Takes a timestamp in milliseconds as a parameter and
// returns another timestamp in seconds with the same date & time but zero seconds.
func ZeroSecondsTS(ts int64) int64 {
t := time.Unix(ts, 0).UTC() // Timestamp is converted from milliseconds to seconds
rounded := time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), 0, 0, time.UTC)
return rounded.Unix()
}

43
vendor/github.com/splitio/go-client/v6/splitio/engine/grammar/matchers/dependency.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,43 @@
package matchers
type dependencyEvaluator interface {
EvaluateDependency(key string, bucketingKey *string, feature string, attributes map[string]interface{}) string
}
// DependencyMatcher will match if the evaluation of another split results in one of the treatments defined
// in the split
type DependencyMatcher struct {
Matcher
feature string
treatments []string
}
// Match will return true if the evaluation of another split results in one of the treatments defined in the
// split
func (m *DependencyMatcher) Match(key string, attributes map[string]interface{}, bucketingKey *string) bool {
evaluator, ok := m.Context.Dependency("evaluator").(dependencyEvaluator)
if !ok {
m.logger.Error("DependencyMatcher: Error retrieving matching key")
return false
}
result := evaluator.EvaluateDependency(key, bucketingKey, m.feature, attributes)
for _, treatment := range m.treatments {
if treatment == result {
return true
}
}
return false
}
// NewDependencyMatcher will return a new instance of DependencyMatcher
func NewDependencyMatcher(negate bool, feature string, treatments []string) *DependencyMatcher {
return &DependencyMatcher{
Matcher: Matcher{
negate: negate,
},
feature: feature,
treatments: treatments,
}
}

45
vendor/github.com/splitio/go-client/v6/splitio/engine/grammar/matchers/endswith.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,45 @@
package matchers
import (
"strings"
)
// EndsWithMatcher matches strings which end with one of the suffixes in the split
type EndsWithMatcher struct {
Matcher
suffixes []string
}
// Match returns true if the key provided ends with one of the suffixes in the split.
func (m *EndsWithMatcher) Match(key string, attributes map[string]interface{}, bucketingKey *string) bool {
matchingKey, err := m.matchingKey(key, attributes)
if err != nil {
m.logger.Error("EndsWithMatcher: ", err)
return false
}
asString, ok := matchingKey.(string)
if !ok {
m.logger.Error("EndsWithMatcher: Error type-asserting string")
return false
}
for _, suffix := range m.suffixes {
if strings.HasSuffix(asString, suffix) {
return true
}
}
return false
}
// NewEndsWithMatcher returns a new instance of EndsWithMatcher
func NewEndsWithMatcher(negate bool, suffixes []string, attributeName *string) *EndsWithMatcher {
return &EndsWithMatcher{
Matcher: Matcher{
negate: negate,
attributeName: attributeName,
},
suffixes: suffixes,
}
}

65
vendor/github.com/splitio/go-client/v6/splitio/engine/grammar/matchers/equalto.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,65 @@
package matchers
import (
"fmt"
"github.com/splitio/go-client/v6/splitio/engine/grammar/matchers/datatypes"
"reflect"
)
// EqualToMatcher will match if two numbers or two datetimes are equal
type EqualToMatcher struct {
Matcher
ComparisonDataType string
ComparisonValue int64
}
// Match will match if the comparisonValue is equal to the matchingValue
func (m *EqualToMatcher) Match(key string, attributes map[string]interface{}, bucketingKey *string) bool {
matchingRaw, err := m.matchingKey(key, attributes)
if err != nil {
m.logger.Error("EqualToMatcher: ", err)
return false
}
matchingValue, ok := matchingRaw.(int64)
if !ok {
var asInt int
asInt, ok = matchingRaw.(int)
if ok {
matchingValue = int64(asInt)
}
}
if !ok {
m.base().logger.Error(
"EqualToMatcher: Error type-asserting matching key to an int",
fmt.Sprintf("%s is a %s\n", matchingRaw, reflect.TypeOf(matchingRaw).String()),
)
return false
}
var comparisonValue int64
switch m.ComparisonDataType {
case datatypes.Number:
comparisonValue = m.ComparisonValue
case datatypes.Datetime:
matchingValue = datatypes.ZeroTimeTS(matchingValue)
comparisonValue = datatypes.ZeroTimeTS(datatypes.TsFromJava(m.ComparisonValue))
default:
m.logger.Error(fmt.Sprintf("EqualToMatcher: Invalid comparison type %s\n", m.ComparisonDataType))
return false
}
return matchingValue == comparisonValue
}
// NewEqualToMatcher returns a pointer to a new instance of EqualToMatcher
func NewEqualToMatcher(negate bool, cmpVal int64, cmpType string, attributeName *string) *EqualToMatcher {
return &EqualToMatcher{
Matcher: Matcher{
negate: negate,
attributeName: attributeName,
},
ComparisonValue: cmpVal,
ComparisonDataType: cmpType,
}
}

50
vendor/github.com/splitio/go-client/v6/splitio/engine/grammar/matchers/equaltoset.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,50 @@
package matchers
import (
"github.com/splitio/go-toolkit/v3/datastructures/set"
)
// EqualToSetMatcher matches if the set supplied to the getTreatment is equal to the one in the split
type EqualToSetMatcher struct {
Matcher
comparisonSet *set.ThreadUnsafeSet
}
// Match returns true if the match provided and the one in the split are equal
func (m *EqualToSetMatcher) Match(key string, attributes map[string]interface{}, bucketingKey *string) bool {
matchingKey, err := m.matchingKey(key, attributes)
if err != nil {
m.logger.Error("EqualToSetMatcher: ", err)
return false
}
conv, ok := matchingKey.([]string)
if !ok {
m.logger.Error("EqualToSetMatcher: Cannot type assert to []string")
return false
}
matchingSet := set.NewSet()
for _, x := range conv {
matchingSet.Add(x)
}
return matchingSet.IsEqual(m.comparisonSet)
}
// NewEqualToSetMatcher returns a pointer to a new instance of EqualToSetMatcher
func NewEqualToSetMatcher(negate bool, setItems []string, attributeName *string) *EqualToSetMatcher {
setObj := set.NewSet()
for _, item := range setItems {
setObj.Add(item)
}
return &EqualToSetMatcher{
Matcher: Matcher{
negate: negate,
attributeName: attributeName,
},
comparisonSet: setObj,
}
}

59
vendor/github.com/splitio/go-client/v6/splitio/engine/grammar/matchers/gtoet.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,59 @@
package matchers
import (
"github.com/splitio/go-client/v6/splitio/engine/grammar/matchers/datatypes"
)
// GreaterThanOrEqualToMatcher will match if two numbers or two datetimes are equal
type GreaterThanOrEqualToMatcher struct {
Matcher
ComparisonDataType string
ComparisonValue int64
}
// Match will match if the comparisonValue is greater than or equal to the matchingValue
func (m *GreaterThanOrEqualToMatcher) Match(key string, attributes map[string]interface{}, bucketingKey *string) bool {
matchingRaw, err := m.matchingKey(key, attributes)
if err != nil {
m.logger.Error("GreaterThanOrEqualToMatcher: ", err)
return false
}
matchingValue, ok := matchingRaw.(int64)
if !ok {
var asInt int
asInt, ok = matchingRaw.(int)
if ok {
matchingValue = int64(asInt)
}
}
if !ok {
m.logger.Error("GreaterThanOrEqualToMatcher: Cannot type-assert key matching key to int")
return false
}
var comparisonValue int64
switch m.ComparisonDataType {
case datatypes.Number:
comparisonValue = m.ComparisonValue
case datatypes.Datetime:
matchingValue = datatypes.ZeroSecondsTS(matchingValue)
comparisonValue = datatypes.ZeroSecondsTS(datatypes.TsFromJava(m.ComparisonValue))
default:
m.logger.Error("GreaterThanOrEqualToMatcher: Incorrect attribute type")
return false
}
return matchingValue >= comparisonValue
}
// NewGreaterThanOrEqualToMatcher returns a pointer to a new instance of GreaterThanOrEqualToMatcher
func NewGreaterThanOrEqualToMatcher(negate bool, cmpVal int64, cmpType string, attributeName *string) *GreaterThanOrEqualToMatcher {
return &GreaterThanOrEqualToMatcher{
Matcher: Matcher{
negate: negate,
attributeName: attributeName,
},
ComparisonValue: cmpVal,
ComparisonDataType: cmpType,
}
}

39
vendor/github.com/splitio/go-client/v6/splitio/engine/grammar/matchers/insegment.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,39 @@
package matchers
import (
"fmt"
"github.com/splitio/go-split-commons/v2/storage"
)
// InSegmentMatcher matches if the key passed is in the segment which the matcher was constructed with
type InSegmentMatcher struct {
Matcher
segmentName string
}
// Match returns true if the key is in the matcher's segment
func (m *InSegmentMatcher) Match(key string, attributes map[string]interface{}, bucketingKey *string) bool {
segmentStorage, ok := m.Context.Dependency("segmentStorage").(storage.SegmentStorageConsumer)
if !ok {
m.logger.Error("InSegmentMatcher: Unable to retrieve segment storage!")
return false
}
isInSegment, err := segmentStorage.SegmentContainsKey(m.segmentName, key)
if err != nil {
m.logger.Error(fmt.Printf("InSegmentMatcher: Segment %s not found", m.segmentName))
}
return isInSegment
}
// NewInSegmentMatcher instantiates a new InSegmentMatcher
func NewInSegmentMatcher(negate bool, segmentName string, attributeName *string) *InSegmentMatcher {
return &InSegmentMatcher{
Matcher: Matcher{
negate: negate,
attributeName: attributeName,
},
segmentName: segmentName,
}
}

61
vendor/github.com/splitio/go-client/v6/splitio/engine/grammar/matchers/ltoet.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,61 @@
package matchers
import (
"github.com/splitio/go-client/v6/splitio/engine/grammar/matchers/datatypes"
)
// LessThanOrEqualToMatcher will match if two numbers or two datetimes are equal
type LessThanOrEqualToMatcher struct {
Matcher
ComparisonDataType string
ComparisonValue int64
}
// Match will match if the comparisonValue is less than or equal to the matchingValue
func (m *LessThanOrEqualToMatcher) Match(key string, attributes map[string]interface{}, bucketingKey *string) bool {
matchingRaw, err := m.matchingKey(key, attributes)
if err != nil {
m.logger.Error("LessThanOrEqualToMatcher: ", err)
return false
}
matchingValue, ok := matchingRaw.(int64)
if !ok {
var asInt int
asInt, ok = matchingRaw.(int)
if ok {
matchingValue = int64(asInt)
}
}
if !ok {
m.logger.Error("LessThanOrEqualToMatcher: Unable to type-assert key to int")
return false
}
var comparisonValue int64
switch m.ComparisonDataType {
case datatypes.Number:
comparisonValue = m.ComparisonValue
case datatypes.Datetime:
matchingValue = datatypes.ZeroSecondsTS(matchingValue)
comparisonValue = datatypes.ZeroSecondsTS(datatypes.TsFromJava(m.ComparisonValue))
default:
m.logger.Error("LessThanOrEqualToMatcher: Incorrect data type")
return false
}
return matchingValue <= comparisonValue
}
// NewLessThanOrEqualToMatcher returns a pointer to a new instance of LessThanOrEqualToMatcher
func NewLessThanOrEqualToMatcher(negate bool, cmpVal int64, cmpType string, attributeName *string) *LessThanOrEqualToMatcher {
return &LessThanOrEqualToMatcher{
Matcher: Matcher{
negate: negate,
attributeName: attributeName,
},
ComparisonValue: cmpVal,
ComparisonDataType: cmpType,
}
}

350
vendor/github.com/splitio/go-client/v6/splitio/engine/grammar/matchers/matchers.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,350 @@
package matchers
import (
"errors"
"fmt"
"github.com/splitio/go-split-commons/v2/dtos"
"github.com/splitio/go-toolkit/v3/injection"
"github.com/splitio/go-toolkit/v3/logging"
)
const (
// MatcherTypeAllKeys string value
MatcherTypeAllKeys = "ALL_KEYS"
// MatcherTypeInSegment string value
MatcherTypeInSegment = "IN_SEGMENT"
// MatcherTypeWhitelist string value
MatcherTypeWhitelist = "WHITELIST"
// MatcherTypeEqualTo string value
MatcherTypeEqualTo = "EQUAL_TO"
// MatcherTypeGreaterThanOrEqualTo string value
MatcherTypeGreaterThanOrEqualTo = "GREATER_THAN_OR_EQUAL_TO"
// MatcherTypeLessThanOrEqualTo string value
MatcherTypeLessThanOrEqualTo = "LESS_THAN_OR_EQUAL_TO"
// MatcherTypeBetween string value
MatcherTypeBetween = "BETWEEN"
// MatcherTypeEqualToSet string value
MatcherTypeEqualToSet = "EQUAL_TO_SET"
// MatcherTypePartOfSet string value
MatcherTypePartOfSet = "PART_OF_SET"
// MatcherTypeContainsAllOfSet string value
MatcherTypeContainsAllOfSet = "CONTAINS_ALL_OF_SET"
// MatcherTypeContainsAnyOfSet string value
MatcherTypeContainsAnyOfSet = "CONTAINS_ANY_OF_SET"
// MatcherTypeStartsWith string value
MatcherTypeStartsWith = "STARTS_WITH"
// MatcherTypeEndsWith string value
MatcherTypeEndsWith = "ENDS_WITH"
// MatcherTypeContainsString string value
MatcherTypeContainsString = "CONTAINS_STRING"
// MatcherTypeInSplitTreatment string value
MatcherTypeInSplitTreatment = "IN_SPLIT_TREATMENT"
// MatcherTypeEqualToBoolean string value
MatcherTypeEqualToBoolean = "EQUAL_TO_BOOLEAN"
// MatcherTypeMatchesString string value
MatcherTypeMatchesString = "MATCHES_STRING"
)
// MatcherInterface should be implemented by all matchers
type MatcherInterface interface {
Match(key string, attributes map[string]interface{}, bucketingKey *string) bool
Negate() bool
base() *Matcher // This method is used to return the embedded matcher when iterating over interfaces
matchingKey(key string, attributes map[string]interface{}) (interface{}, error)
}
// Matcher struct with added logic that wraps around a DTO
type Matcher struct {
*injection.Context
negate bool
attributeName *string
logger logging.LoggerInterface
}
// Negate returns whether this mather is negated or not
func (m *Matcher) Negate() bool {
return m.negate
}
func (m *Matcher) matchingKey(key string, attributes map[string]interface{}) (interface{}, error) {
if m.attributeName == nil {
return key, nil
}
// Reaching this point means WE NEED attributes
if attributes == nil {
return nil, errors.New("Attribute required but no attributes provided")
}
attrValue, found := attributes[*m.attributeName]
if !found {
return nil, fmt.Errorf(
"Attribute \"%s\" required but not present in provided attribute map",
*m.attributeName,
)
}
return attrValue, nil
}
// matcher returns the matcher instance embbeded in structs
func (m *Matcher) base() *Matcher {
return m
}
// BuildMatcher constructs the appropriate matcher based on the MatcherType attribute of the dto
func BuildMatcher(dto *dtos.MatcherDTO, ctx *injection.Context, logger logging.LoggerInterface) (MatcherInterface, error) {
var matcher MatcherInterface
var attributeName *string
if dto.KeySelector != nil {
attributeName = dto.KeySelector.Attribute
}
switch dto.MatcherType {
case MatcherTypeAllKeys:
logger.Debug(fmt.Sprintf("Building AllKeysMatcher with negate=%t", dto.Negate))
matcher = NewAllKeysMatcher(dto.Negate)
case MatcherTypeEqualTo:
if dto.UnaryNumeric == nil {
return nil, errors.New("UnaryNumeric is required for EQUAL_TO matcher type")
}
logger.Debug(fmt.Sprintf(
"Building EqualToMatcher with negate=%t, value=%d, type=%s, attributeName=%v",
dto.Negate, dto.UnaryNumeric.Value, dto.UnaryNumeric.DataType, attributeName,
))
matcher = NewEqualToMatcher(
dto.Negate,
dto.UnaryNumeric.Value,
dto.UnaryNumeric.DataType,
attributeName,
)
case MatcherTypeInSegment:
if dto.UserDefinedSegment == nil {
return nil, errors.New("UserDefinedSegment is required for IN_SEGMENT matcher type")
}
logger.Debug(fmt.Sprintf(
"Building InSegmentMatcher with negate=%t, segmentName=%s, attributeName=%v",
dto.Negate, dto.UserDefinedSegment.SegmentName, attributeName,
))
matcher = NewInSegmentMatcher(
dto.Negate,
dto.UserDefinedSegment.SegmentName,
attributeName,
)
case MatcherTypeWhitelist:
if dto.Whitelist == nil {
return nil, errors.New("Whitelist is required for WHITELIST matcher type")
}
logger.Debug(fmt.Sprintf(
"Building WhitelistMatcher with negate=%t, whitelist=%v, attributeName=%v",
dto.Negate, dto.Whitelist.Whitelist, attributeName,
))
matcher = NewWhitelistMatcher(
dto.Negate,
dto.Whitelist.Whitelist,
attributeName,
)
case MatcherTypeGreaterThanOrEqualTo:
if dto.UnaryNumeric == nil {
return nil, errors.New("UnaryNumeric is required for GREATER_THAN_OR_EQUAL_TO matcher type")
}
logger.Debug(fmt.Sprintf(
"Building GreaterThanOrEqualToMatcher with negate=%t, value=%d, type=%s, attributeName=%v",
dto.Negate, dto.UnaryNumeric.Value, dto.UnaryNumeric.DataType, attributeName,
))
matcher = NewGreaterThanOrEqualToMatcher(
dto.Negate,
dto.UnaryNumeric.Value,
dto.UnaryNumeric.DataType,
attributeName,
)
case MatcherTypeLessThanOrEqualTo:
if dto.UnaryNumeric == nil {
return nil, errors.New("UnaryNumeric is required for LESS_THAN_OR_EQUAL_TO matcher type")
}
logger.Debug(fmt.Sprintf(
"Building LessThanOrEqualToMatcher with negate=%t, value=%d, type=%s, attributeName=%v",
dto.Negate, dto.UnaryNumeric.Value, dto.UnaryNumeric.DataType, attributeName,
))
matcher = NewLessThanOrEqualToMatcher(
dto.Negate,
dto.UnaryNumeric.Value,
dto.UnaryNumeric.DataType,
attributeName,
)
case MatcherTypeBetween:
if dto.Between == nil {
return nil, errors.New("Between is required for BETWEEN matcher type")
}
logger.Debug(fmt.Sprintf(
"Building BetweenMatcher with negate=%t, start=%d, end=%d, type=%s, attributeName=%v",
dto.Negate, dto.Between.Start, dto.Between.End, dto.Between.DataType, attributeName,
))
matcher = NewBetweenMatcher(
dto.Negate,
dto.Between.Start,
dto.Between.End,
dto.Between.DataType,
attributeName,
)
case MatcherTypeEqualToSet:
if dto.Whitelist == nil {
return nil, errors.New("Whitelist is required for EQUAL_TO_SET matcher type")
}
logger.Debug(fmt.Sprintf(
"Building EqualToSetMatcher with negate=%t, set=%v, attributeName=%v",
dto.Negate, dto.Whitelist.Whitelist, attributeName,
))
matcher = NewEqualToSetMatcher(
dto.Negate,
dto.Whitelist.Whitelist,
attributeName,
)
case MatcherTypePartOfSet:
if dto.Whitelist == nil {
return nil, errors.New("Whitelist is required for PART_OF_SET matcher type")
}
logger.Debug(fmt.Sprintf(
"Building PartOfSetMatcher with negate=%t, set=%v, attributeName=%v",
dto.Negate, dto.Whitelist.Whitelist, attributeName,
))
matcher = NewPartOfSetMatcher(
dto.Negate,
dto.Whitelist.Whitelist,
attributeName,
)
case MatcherTypeContainsAllOfSet:
if dto.Whitelist == nil {
return nil, errors.New("Whitelist is required for CONTAINS_ALL_OF_SET matcher type")
}
logger.Debug(fmt.Sprintf(
"Building AllOfSetMatcher with negate=%t, set=%v, attributeName=%v",
dto.Negate, dto.Whitelist.Whitelist, attributeName,
))
matcher = NewContainsAllOfSetMatcher(
dto.Negate,
dto.Whitelist.Whitelist,
attributeName,
)
case MatcherTypeContainsAnyOfSet:
if dto.Whitelist == nil {
return nil, errors.New("Whitelist is required for CONTAINS_ANY_OF_SET matcher type")
}
logger.Debug(fmt.Sprintf(
"Building AnyOfSetMatcher with negate=%t, set=%v, attributeName=%v",
dto.Negate, dto.Whitelist.Whitelist, attributeName,
))
matcher = NewContainsAnyOfSetMatcher(
dto.Negate,
dto.Whitelist.Whitelist,
attributeName,
)
case MatcherTypeStartsWith:
if dto.Whitelist == nil {
return nil, errors.New("Whitelist is required for STARTS_WITH matcher type")
}
logger.Debug(fmt.Sprintf(
"Building StartsWithMatcher with negate=%t, set=%v, attributeName=%v",
dto.Negate, dto.Whitelist.Whitelist, attributeName,
))
matcher = NewStartsWithMatcher(
dto.Negate,
dto.Whitelist.Whitelist,
attributeName,
)
case MatcherTypeEndsWith:
if dto.Whitelist == nil {
return nil, errors.New("Whitelist is required for ENDS_WITH matcher type")
}
logger.Debug(fmt.Sprintf(
"Building EndsWithMatcher with negate=%t, set=%v, attributeName=%v",
dto.Negate, dto.Whitelist.Whitelist, attributeName,
))
matcher = NewEndsWithMatcher(
dto.Negate,
dto.Whitelist.Whitelist,
attributeName,
)
case MatcherTypeContainsString:
if dto.Whitelist == nil {
return nil, errors.New("Whitelist is required for CONTAINS_STRING matcher type")
}
logger.Debug(fmt.Sprintf(
"Building ContainsStringMatcher with negate=%t, set=%v, attributeName=%v",
dto.Negate, dto.Whitelist.Whitelist, attributeName,
))
matcher = NewContainsStringMatcher(
dto.Negate,
dto.Whitelist.Whitelist,
attributeName,
)
case MatcherTypeInSplitTreatment:
if dto.Dependency == nil {
return nil, errors.New("Dependency is required for IN_SPLIT_TREATMENT matcher type")
}
logger.Debug(fmt.Sprintf(
"Building DependencyMatcher with negate=%t, feature=%s, treatments=%v, attributeName=%v",
dto.Negate, dto.Dependency.Split, dto.Dependency.Treatments, attributeName,
))
matcher = NewDependencyMatcher(
dto.Negate,
dto.Dependency.Split,
dto.Dependency.Treatments,
)
case MatcherTypeEqualToBoolean:
if dto.Boolean == nil {
return nil, errors.New("Boolean is required for EQUAL_TO_BOOLEAN matcher type")
}
logger.Debug(fmt.Sprintf(
"Building BooleanMatcher with negate=%t, value=%t, attributeName=%v",
dto.Negate, *dto.Boolean, attributeName,
))
matcher = NewBooleanMatcher(
dto.Negate,
dto.Boolean,
attributeName,
)
case MatcherTypeMatchesString:
if dto.String == nil {
return nil, errors.New("String is required for MATCHES_STRING matcher type")
}
logger.Debug(fmt.Sprintf(
"Building RegexMatcher with negate=%t, regex=%s, attributeName=%v",
dto.Negate, *dto.String, attributeName,
))
matcher = NewRegexMatcher(
dto.Negate,
*dto.String,
attributeName,
)
default:
return nil, errors.New("Matcher not found")
}
if ctx != nil {
ctx.Inject(matcher.base())
}
matcher.base().logger = logger
return matcher, nil
}

52
vendor/github.com/splitio/go-client/v6/splitio/engine/grammar/matchers/partofset.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,52 @@
package matchers
import (
"github.com/splitio/go-toolkit/v3/datastructures/set"
)
// PartOfSetMatcher matches if the set supplied to the getTreatment is a subset of the one in the split
type PartOfSetMatcher struct {
Matcher
comparisonSet *set.ThreadUnsafeSet
}
// Match returns true if the match provided is a subset of the one in the split
func (m *PartOfSetMatcher) Match(key string, attributes map[string]interface{}, bucketingKey *string) bool {
matchingKey, err := m.matchingKey(key, attributes)
if err != nil {
m.logger.Error("PartOfSetMatcher: ", err)
return false
}
conv, ok := matchingKey.([]string)
if !ok {
m.logger.Error("Unable to type-assert key to []string")
return false
}
matchingSet := set.NewSet()
for _, x := range conv {
matchingSet.Add(x)
}
if matchingSet.IsEmpty() {
return false
}
return m.comparisonSet.IsSubset(matchingSet)
}
// NewPartOfSetMatcher returns a pointer to a new instance of PartOfSetMatcher
func NewPartOfSetMatcher(negate bool, setItems []string, attributeName *string) *PartOfSetMatcher {
setObj := set.NewSet()
for _, item := range setItems {
setObj.Add(item)
}
return &PartOfSetMatcher{
Matcher: Matcher{
negate: negate,
attributeName: attributeName,
},
comparisonSet: setObj,
}
}

48
vendor/github.com/splitio/go-client/v6/splitio/engine/grammar/matchers/regex.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,48 @@
package matchers
import (
"reflect"
"regexp"
)
// RegexMatcher matches if the supplied key matches the split's regex
type RegexMatcher struct {
Matcher
regex string
}
// Match returns true if the supplied key matches the split's regex
func (m *RegexMatcher) Match(key string, attributes map[string]interface{}, bucketingKey *string) bool {
matchingKey, err := m.matchingKey(key, attributes)
if err != nil {
m.logger.Error("RegexMatcher: ", err)
return false
}
conv, ok := matchingKey.(string)
if !ok {
m.logger.Error(
"RegexMatcher: Incorrect type. Expected string and received ",
reflect.TypeOf(matchingKey).String(),
)
return false
}
re, err := regexp.Compile(m.regex)
if err != nil {
m.logger.Error("RegexMatcher: Failed to compile regexp. ", err)
return false
}
return re.MatchString(conv)
}
// NewRegexMatcher returns a new instance to a RegexMatcher
func NewRegexMatcher(negate bool, regex string, attributeName *string) *RegexMatcher {
return &RegexMatcher{
Matcher: Matcher{
negate: negate,
attributeName: attributeName,
},
regex: regex,
}
}

45
vendor/github.com/splitio/go-client/v6/splitio/engine/grammar/matchers/startswith.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,45 @@
package matchers
import (
"strings"
)
// StartsWithMatcher matches strings which start with one of the prefixes in the split
type StartsWithMatcher struct {
Matcher
prefixes []string
}
// Match returns true if the key provided starts with one of the prefixes in the split.
func (m *StartsWithMatcher) Match(key string, attributes map[string]interface{}, bucketingKey *string) bool {
matchingKey, err := m.matchingKey(key, attributes)
if err != nil {
m.logger.Error("StartsWithMatcher: ", err)
return false
}
asString, ok := matchingKey.(string)
if !ok {
m.logger.Error("StartsWithMatcher: Failed to type-assert key to string")
return false
}
for _, prefix := range m.prefixes {
if strings.HasPrefix(asString, prefix) {
return true
}
}
return false
}
// NewStartsWithMatcher returns a new instance of StartsWithMatcher
func NewStartsWithMatcher(negate bool, prefixes []string, attributeName *string) *StartsWithMatcher {
return &StartsWithMatcher{
Matcher: Matcher{
negate: negate,
attributeName: attributeName,
},
prefixes: prefixes,
}
}

43
vendor/github.com/splitio/go-client/v6/splitio/engine/grammar/matchers/whitelist.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,43 @@
package matchers
import (
"github.com/splitio/go-toolkit/v3/datastructures/set"
)
// WhitelistMatcher matches if the key received is present in the matcher's whitelist
type WhitelistMatcher struct {
Matcher
whitelist *set.ThreadUnsafeSet
}
// Match returns true if the key is present in the whitelist.
func (m *WhitelistMatcher) Match(key string, attributes map[string]interface{}, bucketingKey *string) bool {
matchingKey, err := m.matchingKey(key, attributes)
if err != nil {
m.logger.Error("WhitelistMatcher: ", err)
return false
}
stringMatchingKey, ok := matchingKey.(string)
if !ok {
m.logger.Error("WhitelistMatcher: Cannot type-assert key to string")
return false
}
return m.whitelist.Has(stringMatchingKey)
}
// NewWhitelistMatcher returns a new WhitelistMatcher
func NewWhitelistMatcher(negate bool, whitelist []string, attributeName *string) *WhitelistMatcher {
wlSet := set.NewSet()
for _, elem := range whitelist {
wlSet.Add(elem)
}
return &WhitelistMatcher{
Matcher: Matcher{
negate: negate,
attributeName: attributeName,
},
whitelist: wlSet,
}
}

94
vendor/github.com/splitio/go-client/v6/splitio/engine/grammar/split.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,94 @@
package grammar
import (
"github.com/splitio/go-split-commons/v2/dtos"
"github.com/splitio/go-toolkit/v3/injection"
"github.com/splitio/go-toolkit/v3/logging"
)
// Split struct with added logic that wraps around a DTO
type Split struct {
splitData *dtos.SplitDTO
conditions []*Condition
}
// NewSplit instantiates a new Split object and all it's internal structures mapped to model classes
func NewSplit(splitDTO *dtos.SplitDTO, ctx *injection.Context, logger logging.LoggerInterface) *Split {
conditions := make([]*Condition, 0)
for _, cond := range splitDTO.Conditions {
conditions = append(conditions, NewCondition(&cond, ctx, logger))
}
split := Split{
conditions: conditions,
splitData: splitDTO,
}
return &split
}
// Name returns the name of the feature
func (s *Split) Name() string {
return s.splitData.Name
}
// Seed returns the seed use for hashing
func (s *Split) Seed() int64 {
return s.splitData.Seed
}
// Status returns whether the split is active or arhived
func (s *Split) Status() string {
status := s.splitData.Status
if status == "" || (status != SplitStatusActive && status != SplitStatusArchived) {
return SplitStatusActive
}
return status
}
// Killed returns whether the split has been killed or not
func (s *Split) Killed() bool {
return s.splitData.Killed
}
// DefaultTreatment returns the default treatment for the current split
func (s *Split) DefaultTreatment() string {
return s.splitData.DefaultTreatment
}
// TrafficAllocation returns the traffic allocation configured for the current split
func (s *Split) TrafficAllocation() int {
return s.splitData.TrafficAllocation
}
// TrafficAllocationSeed returns the seed for traffic allocation configured for this split
func (s *Split) TrafficAllocationSeed() int64 {
return s.splitData.TrafficAllocationSeed
}
// Algo returns the hashing algorithm configured for this split
func (s *Split) Algo() int {
switch s.splitData.Algo {
case SplitAlgoLegacy:
return SplitAlgoLegacy
case SplitAlgoMurmur:
return SplitAlgoMurmur
default:
return SplitAlgoLegacy
}
}
// Conditions returns a slice of Condition objects
func (s *Split) Conditions() []*Condition {
return s.conditions
}
// ChangeNumber returns the change number for this split
func (s *Split) ChangeNumber() int64 {
return s.splitData.ChangeNumber
}
// Configurations returns the configurations for this split
func (s *Split) Configurations() map[string]string {
return s.splitData.Configurations
}

10
vendor/github.com/splitio/go-client/v6/splitio/engine/hash/legacy.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,10 @@
package hash
// Legacy calculates the bucket for the key and seed provided using the legacy algorithm
func Legacy(key []byte, seed uint32) uint32 {
var h uint32
for _, char := range key {
h = 31*h + uint32(char)
}
return uint32(h ^ seed)
}

66
vendor/github.com/splitio/go-client/v6/splitio/engine/hash/murmur.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,66 @@
package hash
// Murmur calculates the bucket for the key and seed provided using the legacy algorithm
// © Copyright 2014 Lawrence E. Bakst All Rights Reserved
// THIS SOURCE CODE IS THE PROPRIETARY INTELLECTUAL PROPERTY AND CONFIDENTIAL
// INFORMATION OF LAWRENCE E. BAKST AND IS PROTECTED UNDER U.S. AND
// INTERNATIONAL LAW. ANY USE OF THIS SOURCE CODE WITHOUT THE
// AUTHORIZATION OF LAWRENCE E. BAKST IS STRICTLY PROHIBITED.
// This package implements the 32 bit version of the MurmurHash3 hash code.
// With the exception of the interface check, this version was developed independtly.
// However, the "spaolacci" implementation with it's bmixer interface is da bomb, although
// this version is slightly faster.
//
// https://en.wikipedia.org/wiki/MurmurHash
// https://github.com/spaolacci/murmur3
const (
c1 uint32 = 0xcc9e2d51
c2 uint32 = 0x1b873593
r1 uint32 = 15
r2 uint32 = 13
m uint32 = 5
n uint32 = 0xe6546b64
)
// Murmur3_32 returns the 32 bit hash of data given the seed.
// This is code is what I started with before I added the hash.Hash and hash.Hash32 interfaces.
func Murmur3_32(data []byte, seed uint32) uint32 {
hash := seed
nblocks := len(data) / 4
for i := 0; i < nblocks; i++ {
// k := *(*uint32)(unsafe.Pointer(&data[i*4]))
k := uint32(data[i*4+0])<<0 | uint32(data[i*4+1])<<8 | uint32(data[i*4+2])<<16 | uint32(data[i*4+3])<<24
k *= c1
k = (k << r1) | (k >> (32 - r1))
k *= c2
hash ^= k
hash = ((hash<<r2)|(hash>>(32-r2)))*m + n
}
l := nblocks * 4
k1 := uint32(0)
switch len(data) & 3 {
case 3:
k1 ^= uint32(data[l+2]) << 16
fallthrough
case 2:
k1 ^= uint32(data[l+1]) << 8
fallthrough
case 1:
k1 ^= uint32(data[l+0])
k1 *= c1
k1 = (k1 << r1) | (k1 >> (32 - r1))
k1 *= c2
hash ^= k1
}
hash ^= uint32(len(data))
hash ^= hash >> 16
hash *= 0x85ebca6b
hash ^= hash >> 13
hash *= 0xc2b2ae35
hash ^= hash >> 16
return hash
}

6
vendor/github.com/splitio/go-client/v6/splitio/impressionListener/impression_listener.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,6 @@
package impressionlistener
// ImpressionListener declaration of ImpressionListener interface
type ImpressionListener interface {
LogImpression(data ILObject)
}

41
vendor/github.com/splitio/go-client/v6/splitio/impressionListener/impressions_listener_wrapper.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,41 @@
package impressionlistener
import (
"github.com/splitio/go-split-commons/v2/dtos"
)
// ILObject struct to map entire data for listener
type ILObject struct {
Impression dtos.Impression
Attributes map[string]interface{}
InstanceID string
SDKLanguageVersion string
}
// WrapperImpressionListener struct
type WrapperImpressionListener struct {
ImpressionListener ImpressionListener
metadata dtos.Metadata
}
// NewImpressionListenerWrapper instantiates a new ImpressionListenerWrapper
func NewImpressionListenerWrapper(impressionListener ImpressionListener, metadata dtos.Metadata) *WrapperImpressionListener {
return &WrapperImpressionListener{
ImpressionListener: impressionListener,
metadata: metadata,
}
}
// SendDataToClient sends the data to client
func (i *WrapperImpressionListener) SendDataToClient(impressions []dtos.Impression, attributes map[string]interface{}) {
for _, impression := range impressions {
datToSend := ILObject{
Impression: impression,
Attributes: attributes,
InstanceID: i.metadata.MachineName,
SDKLanguageVersion: i.metadata.SDKVersion,
}
i.ImpressionListener.LogImpression(datToSend)
}
}

4
vendor/github.com/splitio/go-client/v6/splitio/version.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,4 @@
package splitio
// Version contains a string with the split sdk version
const Version = "6.0.0"

13
vendor/github.com/splitio/go-split-commons/v2/LICENSE сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,13 @@
Copyright © 2020 Split Software, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

89
vendor/github.com/splitio/go-split-commons/v2/conf/conf.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,89 @@
package conf
import (
"crypto/tls"
)
// RedisConfig struct is used to cofigure the redis parameters
type RedisConfig struct {
Host string
Port int
Database int
Password string
Prefix string
// The network type, either tcp or unix.
// Default is tcp.
Network string
// Maximum number of retries before giving up.
// Default is to not retry failed commands.
MaxRetries int
// Dial timeout for establishing new connections.
// Default is 5 seconds.
DialTimeout int
// Timeout for socket reads. If reached, commands will fail
// with a timeout instead of blocking.
// Default is 10 seconds.
ReadTimeout int
// Timeout for socket writes. If reached, commands will fail
// with a timeout instead of blocking.
// Default is 3 seconds.
WriteTimeout int
// Maximum number of socket connections.
// Default is 10 connections.
PoolSize int
// Redis sentinel replication support
SentinelAddresses []string
SentinelMaster string
// Redis cluster replication support
ClusterNodes []string
ClusterKeyHashTag string
TLSConfig *tls.Config
}
// TaskPeriods struct is used to configure the period for each synchronization task
type TaskPeriods struct {
SplitSync int
SegmentSync int
ImpressionSync int
GaugeSync int
CounterSync int
LatencySync int
EventsSync int
}
// AdvancedConfig exposes more configurable parameters that can be used to further tailor the sdk to the user's needs
// - HTTPTimeout - Timeout for HTTP requests when doing synchronization
// - SegmentQueueSize - How many segments can be queued for updating (should be >= # segments the user has)
// - SegmentWorkers - How many workers will be used when performing segments sync.
type AdvancedConfig struct {
HTTPTimeout int
SegmentQueueSize int
SegmentWorkers int
SdkURL string
EventsURL string
EventsBulkSize int64
EventsQueueSize int
ImpressionsQueueSize int
ImpressionsBulkSize int64
StreamingEnabled bool
AuthServiceURL string
StreamingServiceURL string
SplitUpdateQueueSize int64
SegmentUpdateQueueSize int64
}
// ManagerConfig exposes configurable parameters for ImpressionManager
type ManagerConfig struct {
OperationMode string
ImpressionsMode string
ListenerEnabled bool
}

52
vendor/github.com/splitio/go-split-commons/v2/conf/defaults.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,52 @@
package conf
const (
defaultHTTPTimeout = 30
defaultSegmentQueueSize = 500
defaultSegmentWorkers = 10
defaultEventsBulkSize = 5000
defaultEventsQueueSize = 10000
defaultImpressionsQueueSize = 10000
defaultImpressionsBulkSize = 5000
defaultStreamingEnabled = true
defaultSplitUpdateQueueSize = 5000
defaultSegmentUpdateQueueSize = 5000
defaultAuthServiceURL = "https://auth.split.io"
defaultEventsURL = "https://events.split.io/api"
defaultSdkURL = "https://sdk.split.io/api"
defaultStreamingServiceURL = "https://streaming.split.io/sse"
)
const (
// ImpressionsModeOptimized will avoid sending duplicated events
ImpressionsModeOptimized = "optimized"
// ImpressionsModeDebug will send all the impressions generated
ImpressionsModeDebug = "debug"
)
const (
// Standalone mode
Standalone = "inmemory-standalone"
// ProducerSync mode
ProducerSync = "producer-sync"
)
// GetDefaultAdvancedConfig returns default conf
func GetDefaultAdvancedConfig() AdvancedConfig {
return AdvancedConfig{
EventsQueueSize: defaultEventsQueueSize,
HTTPTimeout: defaultHTTPTimeout,
EventsBulkSize: defaultEventsBulkSize,
ImpressionsBulkSize: defaultImpressionsBulkSize,
ImpressionsQueueSize: defaultImpressionsQueueSize,
SegmentQueueSize: defaultSegmentQueueSize,
SegmentUpdateQueueSize: defaultSegmentUpdateQueueSize,
SegmentWorkers: defaultSegmentWorkers,
SplitUpdateQueueSize: defaultSplitUpdateQueueSize,
StreamingEnabled: defaultStreamingEnabled,
AuthServiceURL: defaultAuthServiceURL,
EventsURL: defaultEventsURL,
SdkURL: defaultSdkURL,
StreamingServiceURL: defaultStreamingServiceURL,
}
}

35
vendor/github.com/splitio/go-split-commons/v2/dtos/event.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,35 @@
package dtos
// EventDTO struct mapping events json
type EventDTO struct {
Key string `json:"key"`
TrafficTypeName string `json:"trafficTypeName"`
EventTypeID string `json:"eventTypeId"`
Value interface{} `json:"value"`
Timestamp int64 `json:"timestamp"`
Properties map[string]interface{} `json:"properties,omitempty"`
}
// Size returns a relatively accurate estimation of the size of the event
func (e *EventDTO) Size() int {
size := 1024
if e.Properties == nil {
return size
}
for key, value := range e.Properties {
size += len(key)
switch typedValue := value.(type) {
case string:
size += len(typedValue)
default:
}
}
return size
}
// QueueStoredEventDTO maps the stored JSON object in redis by SDKs
type QueueStoredEventDTO struct {
Metadata Metadata `json:"m"`
Event EventDTO `json:"e"`
}

12
vendor/github.com/splitio/go-split-commons/v2/dtos/http.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,12 @@
package dtos
// HTTPError represents a http error
type HTTPError struct {
Code int
Message string
}
// Error implements golang error interface
func (h HTTPError) Error() string {
return h.Message
}

48
vendor/github.com/splitio/go-split-commons/v2/dtos/impression.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,48 @@
package dtos
// Impression struct to map an impression
type Impression struct {
KeyName string `json:"k"`
BucketingKey string `json:"b"`
FeatureName string `json:"f"`
Treatment string `json:"t"`
Label string `json:"r"`
ChangeNumber int64 `json:"c"`
Time int64 `json:"m"`
Pt int64 `json:"pt,omitempty"`
}
// ImpressionQueueObject struct mapping impressions
type ImpressionQueueObject struct {
Metadata Metadata `json:"m"`
Impression Impression `json:"i"`
}
// ImpressionDTO struct to map an impression
type ImpressionDTO struct {
KeyName string `json:"k"`
Treatment string `json:"t"`
Time int64 `json:"m"`
ChangeNumber int64 `json:"c"`
Label string `json:"r"`
BucketingKey string `json:"b,omitempty"`
Pt int64 `json:"pt,omitempty"`
}
// ImpressionsDTO struct mapping impressions to post
type ImpressionsDTO struct {
TestName string `json:"f"`
KeyImpressions []ImpressionDTO `json:"i"`
}
// ImpressionsInTimeFrameDTO struct mapping impressionsCount in time window
type ImpressionsInTimeFrameDTO struct {
FeatureName string `json:"f"`
TimeFrame int64 `json:"m"`
RawCount int64 `json:"rc"`
}
// ImpressionsCountDTO struct mapping impressions count to post
type ImpressionsCountDTO struct {
PerFeature []ImpressionsInTimeFrameDTO `json:"pf"`
}

8
vendor/github.com/splitio/go-split-commons/v2/dtos/metadata.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,8 @@
package dtos
// Metadata is used to store sdk metadata
type Metadata struct {
SDKVersion string `json:"s"`
MachineIP string `json:"i"`
MachineName string `json:"n"`
}

132
vendor/github.com/splitio/go-split-commons/v2/dtos/metrics.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,132 @@
package dtos
// LatenciesDTO struct mapping latencies post
type LatenciesDTO struct {
MetricName string `json:"name"`
Latencies []int64 `json:"latencies"`
}
// CounterDTO struct mapping counts post
type CounterDTO struct {
MetricName string `json:"name"`
Count int64 `json:"delta"`
}
// GaugeDTO struct mapping gauges post
type GaugeDTO struct {
MetricName string `json:"name"`
Gauge float64 `json:"value"`
}
const maxBuckets = 23
// LatencyDataBulk holds all latencies fetched from storage sorted properly.
type LatencyDataBulk struct {
data map[string]map[string]map[string][]int64
}
// PutLatency adds a new latency to the structure
func (l *LatencyDataBulk) PutLatency(sdk string, machineIP string, metricName string, bucketNumber int, value int64) {
if _, ok := l.data[sdk]; !ok {
l.data[sdk] = make(map[string]map[string][]int64)
}
if _, ok := l.data[sdk][machineIP]; !ok {
l.data[sdk][machineIP] = make(map[string][]int64)
}
if _, ok := l.data[sdk][machineIP][metricName]; !ok {
l.data[sdk][machineIP][metricName] = make([]int64, maxBuckets)
}
l.data[sdk][machineIP][metricName][bucketNumber] = value
}
// ForEach iterates thru all latencies
func (l *LatencyDataBulk) ForEach(callback func(string, string, map[string][]int64)) {
for sdk, byIP := range l.data {
for ip, byName := range byIP {
callback(sdk, ip, byName)
}
}
}
// NewLatencyDataBulk creates a new Latency holding structure
func NewLatencyDataBulk() *LatencyDataBulk {
return &LatencyDataBulk{
data: make(map[string]map[string]map[string][]int64),
}
}
// CounterDataBulk holds all counters fetched from storage sorted properly.
type CounterDataBulk struct {
data map[string]map[string]map[string]int64
}
// PutCounter adds a counter to the structure
func (l *CounterDataBulk) PutCounter(sdk string, machineIP string, metricName string, value int64) {
if _, ok := l.data[sdk]; !ok {
l.data[sdk] = make(map[string]map[string]int64)
}
if _, ok := l.data[sdk][machineIP]; !ok {
l.data[sdk][machineIP] = make(map[string]int64)
}
l.data[sdk][machineIP][metricName] = value
}
// ForEach iterates thru all counters
func (l *CounterDataBulk) ForEach(callback func(string, string, map[string]int64)) {
for sdk, byIP := range l.data {
for ip, byName := range byIP {
callback(sdk, ip, byName)
}
}
}
// NewCounterDataBulk creates a new Counter holding structure
func NewCounterDataBulk() *CounterDataBulk {
return &CounterDataBulk{
data: make(map[string]map[string]map[string]int64),
}
}
// GaugeDataBulk holds all gauges fetched from storage sorted properly.
type GaugeDataBulk struct {
data map[string]map[string]map[string]float64
}
// PutGauge adds a gauge to the structure
func (l *GaugeDataBulk) PutGauge(sdk string, machineIP string, metricName string, value float64) {
if _, ok := l.data[sdk]; !ok {
l.data[sdk] = make(map[string]map[string]float64)
}
if _, ok := l.data[sdk][machineIP]; !ok {
l.data[sdk][machineIP] = make(map[string]float64)
}
l.data[sdk][machineIP][metricName] = value
}
// ForEach iterates thru all gauges
func (l *GaugeDataBulk) ForEach(callback func(string, string, string, float64)) {
for sdk, byIP := range l.data {
for ip, byName := range byIP {
for name, value := range byName {
callback(sdk, ip, name, value)
}
}
}
}
// NewGaugeDataBulk creates a new Gauge holding structure
func NewGaugeDataBulk() *GaugeDataBulk {
return &GaugeDataBulk{
data: make(map[string]map[string]map[string]float64),
}
}

149
vendor/github.com/splitio/go-split-commons/v2/dtos/notification.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,149 @@
package dtos
const (
// SplitUpdate used when split is updated
SplitUpdate = "SPLIT_UPDATE"
// SplitKill used when split is killed
SplitKill = "SPLIT_KILL"
// SegmentUpdate used when segment is updated
SegmentUpdate = "SEGMENT_UPDATE"
// MySegmentsUpdate used when mySegment is updated
MySegmentsUpdate = "MY_SEGMENTS_UPDATE"
// Control for control
Control = "CONTROL"
// StreamingPause for controlType
StreamingPause = "STREAMING_PAUSED"
// StreamingResumed for controlType
StreamingResumed = "STREAMING_RESUMED"
// StreamingDisabled for controlType
StreamingDisabled = "STREAMING_DISABLED"
)
// IncomingNotification struct for incoming notification from streaming
type IncomingNotification struct {
Channel string `json:"channel"`
ChangeNumber *int64 `json:"changeNumber,omitempty"`
ControlType *string `json:"controlType,omitempty"`
DefaultTreatment *string `json:"defaultTreatment,omitempty"`
SegmentName *string `json:"segmentName,omitempty"`
SplitName *string `json:"splitName,omitempty"`
Timestamp *int64 `json:"timestamp,omitempty"`
Type string `json:"type"`
}
// Notification should be implemented by all notification types
type Notification interface {
ChannelName() string
NotificationType() string
}
// base struct with added logic that wraps around a DTO
type base struct {
channelName string
notificationType string
}
// ChannelName returns channel name
func (b base) ChannelName() string {
return b.channelName
}
// NotificationType returns the type of the notification
func (b base) NotificationType() string {
return b.notificationType
}
// ControlNotification notification for control channels
type ControlNotification struct {
base
ControlType string
}
// NewControlNotification builds a notification for controlling connection
func NewControlNotification(channelName string, controlType string) ControlNotification {
return ControlNotification{
base: base{
channelName: channelName,
notificationType: Control,
},
ControlType: controlType,
}
}
// MySegmentsNotification notification when MySegments is updated
type MySegmentsNotification struct {
base
IncludesPayload bool
Payload []string
ChangeNumber int64
}
// NewMySegmentsNotification builds a MySegments notification
func NewMySegmentsNotification(channelName string, includesPayload bool, payload []string, changeNumber int64) MySegmentsNotification {
return MySegmentsNotification{
base: base{
channelName: channelName,
notificationType: MySegmentsUpdate,
},
IncludesPayload: includesPayload,
Payload: payload,
ChangeNumber: changeNumber,
}
}
// SegmentChangeNotification notification when a Segment is updated
type SegmentChangeNotification struct {
base
ChangeNumber int64
SegmentName string
}
// NewSegmentChangeNotification builds a segment change notification
func NewSegmentChangeNotification(channelName string, changeNumber int64, segmentName string) SegmentChangeNotification {
return SegmentChangeNotification{
base: base{
channelName: channelName,
notificationType: SegmentUpdate,
},
ChangeNumber: changeNumber,
SegmentName: segmentName,
}
}
// SplitChangeNotification notification to send a fetch to splitChanges
type SplitChangeNotification struct {
base
ChangeNumber int64
}
// NewSplitChangeNotification builds a split change notification
func NewSplitChangeNotification(channelName string, changeNumber int64) SplitChangeNotification {
return SplitChangeNotification{
base: base{
channelName: channelName,
notificationType: SplitUpdate,
},
ChangeNumber: changeNumber,
}
}
// SplitKillNotification notification when Split is killed
type SplitKillNotification struct {
base
ChangeNumber int64
DefaultTreatment string
SplitName string
}
// NewSplitKillNotification builds a killed split notification
func NewSplitKillNotification(channelName string, changeNumber int64, defaultTreatment string, splitName string) SplitKillNotification {
return SplitKillNotification{
base: base{
channelName: channelName,
notificationType: SplitKill,
},
ChangeNumber: changeNumber,
DefaultTreatment: defaultTreatment,
SplitName: splitName,
}
}

22
vendor/github.com/splitio/go-split-commons/v2/dtos/segment.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,22 @@
package dtos
// SegmentChangesDTO struct to map a segment change message.
type SegmentChangesDTO struct {
Name string `json:"name"`
Added []string `json:"added"`
Removed []string `json:"removed"`
Since int64 `json:"since"`
Till int64 `json:"till"`
}
// SegmentKeyDTO maps key data
type SegmentKeyDTO struct {
Name string `json:"name"`
LastModified int64 `json:"lastModified"`
Removed bool `json:"removed"`
}
// MySegmentDTO struct mapping segment data for mySegments endpoint
type MySegmentDTO struct {
Name string `json:"name"`
}

100
vendor/github.com/splitio/go-split-commons/v2/dtos/split.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,100 @@
package dtos
import "encoding/json"
// SplitChangesDTO structure to map JSON message sent by Split servers.
type SplitChangesDTO struct {
Till int64 `json:"till"`
Since int64 `json:"since"`
Splits []SplitDTO `json:"splits"`
}
// SplitDTO structure to map an Split definition fetched from JSON message.
type SplitDTO struct {
ChangeNumber int64 `json:"changeNumber"`
TrafficTypeName string `json:"trafficTypeName"`
Name string `json:"name"`
TrafficAllocation int `json:"trafficAllocation"`
TrafficAllocationSeed int64 `json:"trafficAllocationSeed"`
Seed int64 `json:"seed"`
Status string `json:"status"`
Killed bool `json:"killed"`
DefaultTreatment string `json:"defaultTreatment"`
Algo int `json:"algo"`
Conditions []ConditionDTO `json:"conditions"`
Configurations map[string]string `json:"configurations"`
}
// MarshalBinary exports SplitDTO to JSON string
func (s SplitDTO) MarshalBinary() (data []byte, err error) {
return json.Marshal(s)
}
// ConditionDTO structure to map a Condition fetched from JSON message.
type ConditionDTO struct {
ConditionType string `json:"conditionType"`
MatcherGroup MatcherGroupDTO `json:"matcherGroup"`
Partitions []PartitionDTO `json:"partitions"`
Label string `json:"label"`
}
// PartitionDTO structure to map a Partition definition fetched from JSON message.
type PartitionDTO struct {
Treatment string `json:"treatment"`
Size int `json:"size"`
}
// MatcherGroupDTO structure to map a Matcher Group definition fetched from JSON message.
type MatcherGroupDTO struct {
Combiner string `json:"combiner"`
Matchers []MatcherDTO `json:"matchers"`
}
// MatcherDTO structure to map a Matcher definition fetched from JSON message.
type MatcherDTO struct {
KeySelector *KeySelectorDTO `json:"keySelector"`
MatcherType string `json:"matcherType"`
Negate bool `json:"negate"`
UserDefinedSegment *UserDefinedSegmentMatcherDataDTO `json:"userDefinedSegmentMatcherData"`
Whitelist *WhitelistMatcherDataDTO `json:"whitelistMatcherData"`
UnaryNumeric *UnaryNumericMatcherDataDTO `json:"unaryNumericMatcherData"`
Between *BetweenMatcherDataDTO `json:"betweenMatcherData"`
Dependency *DependencyMatcherDataDTO `json:"dependencyMatcherData"`
Boolean *bool `json:"booleanMatcherData"`
String *string `json:"stringMatcherData"`
}
// UserDefinedSegmentMatcherDataDTO structure to map a Matcher definition fetched from JSON message.
type UserDefinedSegmentMatcherDataDTO struct {
SegmentName string `json:"segmentName"`
}
// BetweenMatcherDataDTO structure to map a Matcher definition fetched from JSON message.
type BetweenMatcherDataDTO struct {
DataType string `json:"dataType"` //NUMBER or DATETIME
Start int64 `json:"start"`
End int64 `json:"end"`
}
// UnaryNumericMatcherDataDTO structure to map a Matcher definition fetched from JSON message.
type UnaryNumericMatcherDataDTO struct {
DataType string `json:"dataType"` //NUMBER or DATETIME
Value int64 `json:"value"`
}
// WhitelistMatcherDataDTO structure to map a Matcher definition fetched from JSON message.
type WhitelistMatcherDataDTO struct {
Whitelist []string `json:"whitelist"`
}
// DependencyMatcherDataDTO structure to map matcher definition fetched from JSON message.
type DependencyMatcherDataDTO struct {
Split string `json:"split"`
Treatments []string `json:"treatments"`
}
// KeySelectorDTO structure to map a Key slector definition fetched from JSON message.
type KeySelectorDTO struct {
TrafficType string `json:"trafficType"`
Attribute *string `json:"attribute"`
}

103
vendor/github.com/splitio/go-split-commons/v2/dtos/token.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,103 @@
package dtos
import (
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"strings"
"time"
)
const gracePeriod = 10 * time.Minute
const metadataPlaceHolder = "channel-metadata:publishers"
const occupancy = "[?occupancy=metrics.publishers]"
// Token dto
type Token struct {
Token string `json:"token"`
PushEnabled bool `json:"pushEnabled"`
}
// TokenPayload payload dto
type TokenPayload struct {
Capabilitites string `json:"x-ably-capability"`
Exp int64 `json:"exp"`
Iat int64 `json:"iat"`
}
// ParsedCapabilities capabilities
type ParsedCapabilities map[string][]string
func isMetadataType(capabilities []string) bool {
for _, capability := range capabilities {
if capability == metadataPlaceHolder {
return true
}
}
return false
}
// ChannelList grabs the channel list from capabilities
func (t *Token) ChannelList() ([]string, error) {
if !t.PushEnabled || t.Token == "" {
return nil, errors.New("Push disabled or no token set")
}
tokenParts := strings.Split(t.Token, ".")
if len(tokenParts) < 2 {
return nil, errors.New("Cannot decode token")
}
decodedPayload, err := base64.RawURLEncoding.DecodeString(tokenParts[1])
if err != nil {
return nil, err
}
var parsedPayload TokenPayload
err = json.Unmarshal(decodedPayload, &parsedPayload)
if err != nil {
return nil, err
}
var parsedCapabilities ParsedCapabilities
err = json.Unmarshal([]byte(parsedPayload.Capabilitites), &parsedCapabilities)
if err != nil {
return nil, err
}
channelList := make([]string, 0, len(parsedCapabilities))
for channelName := range parsedCapabilities {
if isMetadataType(parsedCapabilities[channelName]) {
channelList = append(channelList, fmt.Sprintf("%s%s", occupancy, channelName))
} else {
channelList = append(channelList, channelName)
}
}
return channelList, nil
}
// CalculateNextTokenExpiration calculates next token expiration
func (t *Token) CalculateNextTokenExpiration() (time.Duration, error) {
if !t.PushEnabled || t.Token == "" {
return 0, errors.New("Push disabled or no token set")
}
tokenParts := strings.Split(t.Token, ".")
if len(tokenParts) < 2 {
return 0, errors.New("Cannot decode token")
}
decodedPayload, err := base64.RawURLEncoding.DecodeString(tokenParts[1])
if err != nil {
return 0, err
}
var parsedPayload TokenPayload
err = json.Unmarshal(decodedPayload, &parsedPayload)
if err != nil {
return 0, err
}
tokenDuration := parsedPayload.Exp - parsedPayload.Iat
return time.Duration(tokenDuration)*time.Second - gracePeriod, nil
}

59
vendor/github.com/splitio/go-split-commons/v2/provisional/impcounter.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,59 @@
package provisional
import (
"sync"
"github.com/splitio/go-split-commons/v2/util"
)
// Key struct for mapping each key to an amount
type Key struct {
FeatureName string
TimeFrame int64
}
// ImpressionsCounter struct for storing generated impressions counts
type ImpressionsCounter struct {
impressionsCounts map[Key]int64
mutex *sync.RWMutex
}
// NewImpressionsCounter creates new ImpressionsCounter
func NewImpressionsCounter() *ImpressionsCounter {
return &ImpressionsCounter{
impressionsCounts: make(map[Key]int64),
mutex: &sync.RWMutex{},
}
}
func makeKey(splitName string, timeFrame int64) Key {
return Key{
FeatureName: splitName,
TimeFrame: util.TruncateTimeFrame(timeFrame),
}
}
// Inc increments the quantity of impressions with the passed splitName and timeFrame
func (i *ImpressionsCounter) Inc(splitName string, timeFrame int64, amount int64) {
i.mutex.Lock()
defer i.mutex.Unlock()
key := makeKey(splitName, timeFrame)
currentAmount, _ := i.impressionsCounts[key]
i.impressionsCounts[key] = currentAmount + amount
}
// PopAll returns all the elements stored in the cache and resets the cache
func (i *ImpressionsCounter) PopAll() map[Key]int64 {
i.mutex.Lock()
defer i.mutex.Unlock()
toReturn := i.impressionsCounts
i.impressionsCounts = make(map[Key]int64)
return toReturn
}
// Size returns how many keys are stored in cache
func (i *ImpressionsCounter) Size() int {
i.mutex.RLock()
defer i.mutex.RUnlock()
return len(i.impressionsCounts)
}

43
vendor/github.com/splitio/go-split-commons/v2/provisional/imphasher.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,43 @@
package provisional
import (
"fmt"
"strings"
"github.com/splitio/go-split-commons/v2/dtos"
"github.com/splitio/go-toolkit/v3/provisional/hashing"
)
const hashKeyTemplate = "%s:%s:%s:%s:%d"
func unknownIfEmpty(s string) string {
if len(strings.TrimSpace(s)) == 0 {
return "UNKNOWN"
}
return s
}
// ImpressionHasher interface
type ImpressionHasher interface {
Process(featureName string, impression *dtos.Impression) (int64, error)
}
// ImpressionHasherImpl implements the hasher interface, mapping certain fields to an int64
type ImpressionHasherImpl struct{}
// Process an impression and return the 64 LSBs of a murmur3-128 digest
func (h *ImpressionHasherImpl) Process(featureName string, impression *dtos.Impression) (int64, error) {
if impression == nil {
return 0, fmt.Errorf("keyImpression cannot be nil")
}
toHash := fmt.Sprintf(hashKeyTemplate,
unknownIfEmpty(impression.KeyName),
unknownIfEmpty(featureName),
unknownIfEmpty(impression.Treatment),
unknownIfEmpty(impression.Label),
impression.ChangeNumber)
h1, _ := hashing.Sum128([]byte(toHash))
return int64(h1), nil
}

76
vendor/github.com/splitio/go-split-commons/v2/provisional/impmanager.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,76 @@
package provisional
import (
"time"
"github.com/splitio/go-split-commons/v2/conf"
"github.com/splitio/go-split-commons/v2/dtos"
"github.com/splitio/go-split-commons/v2/util"
)
const lastSeenCacheSize = 500000 // cache up to 500k impression hashes
// ImpressionManager interface
type ImpressionManager interface {
ProcessImpressions(impressions []dtos.Impression) ([]dtos.Impression, []dtos.Impression)
}
// ImpressionManagerImpl implements
type ImpressionManagerImpl struct {
impressionObserver ImpressionObserver
impressionsCounter *ImpressionsCounter
shouldAddPreviousTime bool
isOptimized bool
listenerEnabled bool
}
// NewImpressionManager creates new ImpManager
func NewImpressionManager(managerConfig conf.ManagerConfig, impressionCounter *ImpressionsCounter) (ImpressionManager, error) {
impressionObserver, err := NewImpressionObserver(lastSeenCacheSize)
if err != nil {
return nil, err
}
impManager := &ImpressionManagerImpl{
impressionObserver: impressionObserver,
impressionsCounter: impressionCounter,
shouldAddPreviousTime: util.ShouldAddPreviousTime(managerConfig),
isOptimized: impressionCounter != nil && util.ShouldBeOptimized(managerConfig),
listenerEnabled: managerConfig.ListenerEnabled,
}
return impManager, nil
}
func (i *ImpressionManagerImpl) processImpression(impression dtos.Impression, forLog []dtos.Impression, forListener []dtos.Impression) ([]dtos.Impression, []dtos.Impression) {
if i.shouldAddPreviousTime {
impression.Pt, _ = i.impressionObserver.TestAndSet(impression.FeatureName, &impression) // Adds previous time if it is enabled
}
now := time.Now().UTC().UnixNano()
if i.isOptimized { // isOptimized
i.impressionsCounter.Inc(impression.FeatureName, now, 1) // Increments impression counter per featureName
}
if !i.isOptimized || impression.Pt == 0 || impression.Pt < util.TruncateTimeFrame(now) {
forLog = append(forLog, impression)
}
if i.listenerEnabled {
forListener = append(forListener, impression)
}
return forLog, forListener
}
// ProcessImpressions bulk processes
func (i *ImpressionManagerImpl) ProcessImpressions(impressions []dtos.Impression) ([]dtos.Impression, []dtos.Impression) {
forLog := make([]dtos.Impression, 0, len(impressions))
forListener := make([]dtos.Impression, 0, len(impressions))
for _, impression := range impressions {
forLog, forListener = i.processImpression(impression, forLog, forListener)
}
return forLog, forListener
}

61
vendor/github.com/splitio/go-split-commons/v2/provisional/impobserver.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,61 @@
package provisional
import (
"fmt"
"sync"
"github.com/splitio/go-split-commons/v2/dtos"
"github.com/splitio/go-toolkit/v3/provisional/int64cache"
)
// ImpressionObserver is used to check wether an impression has been previously seen
type ImpressionObserver interface {
TestAndSet(featureName string, impression *dtos.Impression) (int64, error)
}
// ImpressionObserverImpl is an implementation of the ImpressionObserver interface
type ImpressionObserverImpl struct {
cache int64cache.Int64Cache
hasher ImpressionHasher
mutex sync.Mutex
}
// Atomically fetch cache data and update it
func (o *ImpressionObserverImpl) testAndSet(key int64, newValue int64) (int64, error) {
o.mutex.Lock()
defer o.mutex.Unlock()
old, err := o.cache.Get(key)
o.cache.Set(key, newValue)
return old, err
}
// TestAndSet hashes the impression, updates the cache and returns the previous value
func (o *ImpressionObserverImpl) TestAndSet(featureName string, impression *dtos.Impression) (int64, error) {
hash, err := o.hasher.Process(featureName, impression)
if err != nil {
return 0, fmt.Errorf("error hashing impression: %s", err.Error())
}
return o.testAndSet(hash, impression.Time)
}
// NewImpressionObserver constructs a new ImpressionObserver
func NewImpressionObserver(size int) (*ImpressionObserverImpl, error) {
cache, err := int64cache.NewInt64Cache(size)
if err != nil {
return nil, fmt.Errorf("error building cache: %s", err.Error())
}
return &ImpressionObserverImpl{
cache: cache,
hasher: &ImpressionHasherImpl{},
mutex: sync.Mutex{},
}, nil
}
// ImpressionObserverNoOp is an implementation of the ImpressionObserver interface
type ImpressionObserverNoOp struct{}
// TestAndSet that does nothing
func (o *ImpressionObserverNoOp) TestAndSet(featureName string, impression *dtos.Impression) (int64, error) {
return 0, nil
}

27
vendor/github.com/splitio/go-split-commons/v2/push/dtos.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,27 @@
package push
// IncomingEvent struct to process every kind of notification that comes from streaming
type IncomingEvent struct {
id *string
timestamp *int64
encoding *string
data *string
name *string
clientID *string
event string
channel *string
message *string
code *int
statusCode *int
href *string
}
// Metrics dto
type Metrics struct {
Publishers int `json:"publishers"`
}
// Occupancy dto
type Occupancy struct {
Data Metrics `json:"metrics"`
}

89
vendor/github.com/splitio/go-split-commons/v2/push/eventhandler.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,89 @@
package push
import (
"encoding/json"
"fmt"
"github.com/splitio/go-split-commons/v2/dtos"
"github.com/splitio/go-toolkit/v3/logging"
)
// EventHandler struct
type EventHandler struct {
keeper *Keeper
parser *NotificationParser
processor *Processor
logger logging.LoggerInterface
}
// NewEventHandler builds new EventHandler
func NewEventHandler(keeper *Keeper, parser *NotificationParser, processor *Processor, logger logging.LoggerInterface) *EventHandler {
return &EventHandler{
keeper: keeper,
parser: parser,
processor: processor,
logger: logger,
}
}
func (e *EventHandler) wrapOccupancy(incomingEvent IncomingEvent) *Occupancy {
if incomingEvent.data == nil {
return nil
}
var occupancy *Occupancy
err := json.Unmarshal([]byte(*incomingEvent.data), &occupancy)
if err != nil {
return nil
}
return occupancy
}
func (e *EventHandler) wrapUpdateEvent(incomingEvent IncomingEvent) *dtos.IncomingNotification {
if incomingEvent.data == nil {
return nil
}
var incomingNotification *dtos.IncomingNotification
err := json.Unmarshal([]byte(*incomingEvent.data), &incomingNotification)
if err != nil {
e.logger.Error("cannot parse data as IncomingNotification type")
return nil
}
incomingNotification.Channel = *incomingEvent.channel
return incomingNotification
}
// HandleIncomingMessage handles incoming message from streaming
func (e *EventHandler) HandleIncomingMessage(event map[string]interface{}) {
incomingEvent := e.parser.Parse(event)
switch incomingEvent.event {
case update:
e.logger.Debug("Update event received")
incomingNotification := e.wrapUpdateEvent(incomingEvent)
if incomingNotification == nil {
e.logger.Debug("Skipping incoming notification...")
return
}
e.logger.Debug("Incoming Notification:", incomingNotification)
err := e.processor.Process(*incomingNotification)
if err != nil {
e.logger.Debug("Could not process notification", err.Error())
return
}
case occupancy:
e.logger.Debug("Presence event received")
occupancy := e.wrapOccupancy(incomingEvent)
if occupancy == nil || incomingEvent.channel == nil {
e.logger.Debug("Skipping occupancy...")
return
}
e.keeper.UpdateManagers(*incomingEvent.channel, occupancy.Data.Publishers)
return
case errorType: // TODO: Update this when logic is fully defined
e.logger.Error(fmt.Sprintf("Error received: %+v", incomingEvent))
default:
e.logger.Debug(fmt.Sprintf("Unexpected incomingEvent: %+v", incomingEvent))
e.logger.Error("Unexpected type of event received")
}
}

10
vendor/github.com/splitio/go-split-commons/v2/push/interface.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,10 @@
package push
// Manager interface for Push Manager
type Manager interface {
Start()
Stop()
StartWorkers()
StopWorkers()
IsRunning() bool
}

98
vendor/github.com/splitio/go-split-commons/v2/push/keeper.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,98 @@
package push
import (
"strings"
"sync"
)
const (
// PublisherNotPresent there are no publishers sending data
PublisherNotPresent = iota
// PublisherAvailable there are publishers running
PublisherAvailable
)
const (
prefix = "[?occupancy=metrics.publishers]"
)
// last struct for storing the last notification
type last struct {
manager string
timestamp int64
mutex *sync.RWMutex
}
// Keeper struct
type Keeper struct {
managers map[string]int
activeRegion string
last last
publishers chan<- int
mutex *sync.RWMutex
}
// NewKeeper creates new keeper
func NewKeeper(publishers chan int) *Keeper {
last := last{
mutex: &sync.RWMutex{},
}
return &Keeper{
managers: make(map[string]int),
activeRegion: "us-east-1",
mutex: &sync.RWMutex{},
publishers: publishers,
last: last,
}
}
func (k *Keeper) cleanManagerPrefix(manager string) string {
return strings.Replace(manager, prefix, "", -1)
}
// Publishers returns the quantity of publishers for a particular manager
func (k *Keeper) Publishers(manager string) *int {
k.mutex.RLock()
defer k.mutex.RUnlock()
publisher, ok := k.managers[manager]
if ok {
return &publisher
}
return nil
}
// UpdateManagers updates current manager count
func (k *Keeper) UpdateManagers(manager string, publishers int) {
parsedManager := k.cleanManagerPrefix(manager)
k.mutex.Lock()
defer k.mutex.Unlock()
k.managers[parsedManager] = publishers
isAvailable := false
for _, publishers := range k.managers {
if publishers > 0 {
isAvailable = true
break
}
}
if !isAvailable {
k.publishers <- PublisherNotPresent
return
}
k.publishers <- PublisherAvailable
}
// LastNotification return the latest notification saved
func (k *Keeper) LastNotification() (string, int64) {
k.last.mutex.RLock()
defer k.last.mutex.RUnlock()
return k.last.manager, k.last.timestamp
}
// UpdateLastNotification updates last message received
func (k *Keeper) UpdateLastNotification(manager string, timestamp int64) {
k.last.mutex.Lock()
defer k.last.mutex.Unlock()
k.last.manager = k.cleanManagerPrefix(manager)
k.last.timestamp = timestamp
}

64
vendor/github.com/splitio/go-split-commons/v2/push/parser.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,64 @@
package push
import (
"github.com/splitio/go-toolkit/v3/common"
"github.com/splitio/go-toolkit/v3/logging"
)
const (
update = "update"
errorType = "error"
occupancy = "[meta]occupancy"
)
// NotificationParser struct
type NotificationParser struct {
logger logging.LoggerInterface
}
// NewNotificationParser creates notifcation parser
func NewNotificationParser(logger logging.LoggerInterface) *NotificationParser {
return &NotificationParser{
logger: logger,
}
}
// Parse parses incoming event from streaming
func (n *NotificationParser) Parse(event map[string]interface{}) IncomingEvent {
incomingEvent := IncomingEvent{
id: common.AsStringOrNil(event["id"]),
encoding: common.AsStringOrNil(event["encoding"]),
data: common.AsStringOrNil(event["data"]),
name: common.AsStringOrNil(event["name"]),
clientID: common.AsStringOrNil(event["clientId"]),
channel: common.AsStringOrNil(event["channel"]),
message: common.AsStringOrNil(event["message"]),
href: common.AsStringOrNil(event["href"]),
}
timestamp := common.AsFloat64OrNil(event["timestamp"])
if timestamp != nil {
incomingEvent.timestamp = common.Int64Ref(int64(*timestamp))
}
code := common.AsFloat64OrNil(event["code"])
if code != nil {
incomingEvent.code = common.IntRef(int(*code))
}
statusCode := common.AsFloat64OrNil(event["statusCode"])
if statusCode != nil {
incomingEvent.statusCode = common.IntRef(int(*statusCode))
}
if incomingEvent.code != nil && incomingEvent.statusCode != nil {
incomingEvent.event = errorType
return incomingEvent
}
if incomingEvent.name != nil && *incomingEvent.name == occupancy {
incomingEvent.event = occupancy
return incomingEvent
}
incomingEvent.event = update
return incomingEvent
}

112
vendor/github.com/splitio/go-split-commons/v2/push/processor.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,112 @@
package push
import (
"errors"
"fmt"
"github.com/splitio/go-split-commons/v2/dtos"
"github.com/splitio/go-split-commons/v2/storage"
"github.com/splitio/go-toolkit/v3/logging"
)
const (
segmentQueueCheck = 5000
splitQueueCheck = 5000
streamingPausedType = "STREAMING_PAUSED"
streamingResumedType = "STREAMING_RESUMED"
streamingDisabledType = "STREAMING_DISABLED"
)
const (
// StreamingPaused The SDK should stop processing incoming UPDATE-type events
streamingPaused = iota
// StreamingResumed The SDK should resume processing UPDATE-type events (if not already)
streamingResumed
// StreamingDisabled The SDK should disable streaming completely and dont try to reconnect until the SDK is re-instantiated
streamingDisabled
)
// Processor struct for notification processor
type Processor struct {
segmentQueue chan dtos.SegmentChangeNotification
splitQueue chan dtos.SplitChangeNotification
splitStorage storage.SplitStorageProducer
controlStatus chan<- int
logger logging.LoggerInterface
}
// NewProcessor creates new processor
func NewProcessor(segmentQueue chan dtos.SegmentChangeNotification, splitQueue chan dtos.SplitChangeNotification, splitStorage storage.SplitStorageProducer, logger logging.LoggerInterface, controlStatus chan int) (*Processor, error) {
if cap(segmentQueue) < segmentQueueCheck {
return nil, errors.New("Small size of segmentQueue")
}
if cap(splitQueue) < splitQueueCheck {
return nil, errors.New("Small size of splitQueue")
}
if cap(controlStatus) < 1 {
return nil, errors.New("Small size for control chan")
}
return &Processor{
segmentQueue: segmentQueue,
splitQueue: splitQueue,
splitStorage: splitStorage,
controlStatus: controlStatus,
logger: logger,
}, nil
}
// Process takes an incoming notification and generates appropriate notifications for it.
func (p *Processor) Process(i dtos.IncomingNotification) error {
switch i.Type {
case dtos.SplitUpdate:
if i.ChangeNumber == nil {
return errors.New("ChangeNumber could not be nil, discarded")
}
splitUpdate := dtos.NewSplitChangeNotification(i.Channel, *i.ChangeNumber)
p.splitQueue <- splitUpdate
case dtos.SegmentUpdate:
if i.ChangeNumber == nil {
return errors.New("ChangeNumber could not be nil, discarded")
}
if i.SegmentName == nil {
return errors.New("SegmentName could not be nil, discarded")
}
segmentUpdate := dtos.NewSegmentChangeNotification(i.Channel, *i.ChangeNumber, *i.SegmentName)
p.segmentQueue <- segmentUpdate
case dtos.SplitKill:
if i.ChangeNumber == nil {
return errors.New("ChangeNumber could not be nil, discarded")
}
if i.SplitName == nil {
return errors.New("SplitName could not be nil, discarded")
}
if i.DefaultTreatment == nil {
return errors.New("DefaultTreatment could not be nil, discarded")
}
splitUpdate := dtos.NewSplitChangeNotification(i.Channel, *i.ChangeNumber)
p.splitStorage.KillLocally(*i.SplitName, *i.DefaultTreatment, *i.ChangeNumber)
p.splitQueue <- splitUpdate
case dtos.Control:
if i.ControlType == nil {
return errors.New("ControlType could not be nil, discarded")
}
control := dtos.NewControlNotification(i.Channel, *i.ControlType)
switch control.ControlType {
case streamingDisabledType:
p.logger.Debug("Received notification for disabling streaming")
p.controlStatus <- streamingDisabled
case streamingPausedType:
p.logger.Debug("Received notification for pausing streaming")
p.controlStatus <- streamingPaused
case streamingResumedType:
p.logger.Debug("Received notification for resuming streaming")
p.controlStatus <- streamingResumed
default:
p.logger.Debug(fmt.Sprintf("%s Unexpected type of Control Notification", control.ControlType))
}
default:
return fmt.Errorf("Unknown IncomingNotification type: %T", i)
}
return nil
}

370
vendor/github.com/splitio/go-split-commons/v2/push/pushmanager.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,370 @@
package push
import (
"errors"
"fmt"
"net/http"
"sync/atomic"
"time"
"github.com/splitio/go-split-commons/v2/conf"
"github.com/splitio/go-split-commons/v2/dtos"
"github.com/splitio/go-split-commons/v2/service"
"github.com/splitio/go-split-commons/v2/service/api/sse"
"github.com/splitio/go-split-commons/v2/storage"
"github.com/splitio/go-toolkit/v3/common"
"github.com/splitio/go-toolkit/v3/logging"
sseStatus "github.com/splitio/go-toolkit/v3/sse"
)
const (
resetTimer = 120
maxPeriod = 30 * time.Minute
)
const (
// Ready represents ready
Ready = iota
// PushIsDown there are no publishers for streaming
PushIsDown
// PushIsUp there are publishers presents
PushIsUp
// BackoffAuth backoff is running for authentication
BackoffAuth
// BackoffSSE backoff is running for connecting to stream
BackoffSSE
// TokenExpiration flag to restart push services
TokenExpiration
// StreamingPaused flag for pausing streaming
StreamingPaused
// StreamingResumed flag for resuming streaming
StreamingResumed
// StreamingDisabled flag for disabling streaming
StreamingDisabled
// Reconnect flag to reconnect
Reconnect
// NonRetriableError represents an error that will force switching to polling
NonRetriableError
)
// PushManager struct for managing push services
type PushManager struct {
authClient service.AuthClient
sseClient *sse.StreamingClient
segmentWorker *SegmentUpdateWorker
splitWorker *SplitUpdateWorker
eventHandler *EventHandler
managerStatus chan<- int
streamingStatus chan int
publishers chan int
logger logging.LoggerInterface
cancelAuthBackoff chan struct{}
cancelSSEBackoff chan struct{}
cancelTokenExpiration chan struct{}
cancelStreamingWatcher chan struct{}
control chan int
status atomic.Value
}
// NewPushManager creates new PushManager
func NewPushManager(
logger logging.LoggerInterface,
synchronizeSegmentHandler func(segmentName string, till *int64) error,
synchronizeSplitsHandler func(till *int64) error,
splitStorage storage.SplitStorage,
config *conf.AdvancedConfig,
managerStatus chan int,
authClient service.AuthClient,
) (Manager, error) {
splitQueue := make(chan dtos.SplitChangeNotification, config.SplitUpdateQueueSize)
segmentQueue := make(chan dtos.SegmentChangeNotification, config.SegmentUpdateQueueSize)
control := make(chan int, 1)
processor, err := NewProcessor(segmentQueue, splitQueue, splitStorage, logger, control)
if err != nil {
return nil, err
}
parser := NewNotificationParser(logger)
if parser == nil {
return nil, errors.New("Could not instantiate NotificationParser")
}
publishers := make(chan int, 1000)
keeper := NewKeeper(publishers)
if keeper == nil {
return nil, errors.New("Could not instantiate Keeper")
}
eventHandler := NewEventHandler(keeper, parser, processor, logger)
segmentWorker, err := NewSegmentUpdateWorker(segmentQueue, synchronizeSegmentHandler, logger)
if err != nil {
return nil, err
}
splitWorker, err := NewSplitUpdateWorker(splitQueue, synchronizeSplitsHandler, logger)
if err != nil {
return nil, err
}
streamingStatus := make(chan int, 1000)
status := atomic.Value{}
status.Store(Ready)
return &PushManager{
authClient: authClient,
sseClient: sse.NewStreamingClient(config, streamingStatus, logger),
segmentWorker: segmentWorker,
splitWorker: splitWorker,
managerStatus: managerStatus,
streamingStatus: streamingStatus,
eventHandler: eventHandler,
publishers: publishers,
logger: logger,
cancelAuthBackoff: make(chan struct{}, 1),
cancelSSEBackoff: make(chan struct{}, 1),
cancelTokenExpiration: make(chan struct{}, 1),
cancelStreamingWatcher: make(chan struct{}, 1),
control: control,
status: status,
}, nil
}
func (p *PushManager) cancelStreaming() {
p.logger.Error("Error, switching to polling")
p.managerStatus <- NonRetriableError
}
func (p *PushManager) performAuthentication(errResult chan error) *dtos.Token {
select {
case <-p.cancelAuthBackoff:
// Discarding previous msg
default:
}
tokenResult := make(chan *dtos.Token, 1)
cancelAuthBackoff := common.WithBackoffCancelling(1*time.Second, maxPeriod, func() bool {
token, err := p.authClient.Authenticate()
if err != nil {
errType, ok := err.(dtos.HTTPError)
if ok && errType.Code >= http.StatusInternalServerError {
p.managerStatus <- BackoffAuth
return false // It will continue retrying
}
errResult <- errors.New("Error authenticating")
return true
}
tokenResult <- token
return true // Result is OK, Stopping Here, no more backoff
})
defer cancelAuthBackoff()
select {
case token := <-tokenResult:
if !token.PushEnabled {
return nil
}
return token
case err := <-errResult:
p.logger.Error(err.Error())
return nil
case <-p.cancelAuthBackoff:
return nil
}
}
func (p *PushManager) connectToStreaming(errResult chan error, token string, channels []string) error {
select {
case <-p.cancelSSEBackoff:
// Discarding previous msg
default:
}
sseResult := make(chan struct{}, 1)
cancelSSEBackoff := common.WithBackoffCancelling(1*time.Second, maxPeriod, func() bool {
p.sseClient.ConnectStreaming(token, channels, p.eventHandler.HandleIncomingMessage)
status := <-p.streamingStatus
switch status {
case sseStatus.OK:
sseResult <- struct{}{}
return true
case sseStatus.ErrorInternal:
p.managerStatus <- BackoffSSE
return false // It will continue retrying
default:
errResult <- errors.New("Error connecting streaming")
return true
}
})
defer cancelSSEBackoff()
select {
case <-sseResult:
return nil
case err := <-errResult:
p.logger.Error(err.Error())
return err
case <-p.cancelSSEBackoff:
return nil
}
}
func (p *PushManager) fetchStreamingToken(errResult chan error) (string, []string, error) {
token := p.performAuthentication(errResult)
if token == nil {
return "", []string{}, errors.New("Could not perform authentication")
}
channels, err := token.ChannelList()
if err != nil {
return "", []string{}, errors.New("Could not perform authentication")
}
nextTokenExpiration, err := token.CalculateNextTokenExpiration()
if err != nil {
return "", []string{}, errors.New("Could not perform authentication")
}
go func() {
// Create timeout timer for calculating next token expiration
idleDuration := nextTokenExpiration
tokenExpirationTimer := time.NewTimer(idleDuration)
defer tokenExpirationTimer.Stop()
select {
case <-tokenExpirationTimer.C: // Timedout
p.logger.Info("Token expired")
p.managerStatus <- TokenExpiration
return
case <-p.cancelTokenExpiration:
return
}
}()
return token.Token, channels, nil
}
func (p *PushManager) streamingStatusWatcher() {
for {
select {
case status := <-p.streamingStatus: // Streaming SSE Status
switch status {
case sseStatus.ErrorKeepAlive: // On ConnectionTimedOut -> Reconnect
fallthrough
case sseStatus.ErrorInternal: // On Error >= 500 -> Reconnect
fallthrough
case sseStatus.ErrorReadingStream: // On IOF -> Reconnect
p.managerStatus <- Reconnect
default: // Whatever other errors -> Send Error to disconnect
p.cancelStreaming()
}
case publisherStatus := <-p.publishers: // Publisher Available/Not Available
switch publisherStatus {
case PublisherNotPresent:
if p.status.Load().(int) != StreamingPaused {
p.managerStatus <- PushIsDown
}
case PublisherAvailable:
if p.status.Load().(int) != StreamingPaused {
p.managerStatus <- PushIsUp
}
default:
p.logger.Debug(fmt.Sprintf("Unexpected publisher status received %d", publisherStatus))
}
case controlStatus := <-p.control:
switch controlStatus {
case streamingPaused:
p.logger.Debug("Received Pause Streaming Notification")
if p.status.Load().(int) != StreamingPaused {
p.logger.Info("Sending Pause Streaming")
p.status.Store(StreamingPaused)
p.managerStatus <- PushIsDown
}
case streamingResumed:
p.logger.Debug("Received Resume Streaming Notification")
if p.status.Load().(int) == StreamingPaused {
p.status.Store(StreamingResumed)
publishersAvailable := p.eventHandler.keeper.Publishers("control_pri")
if publishersAvailable != nil && *publishersAvailable > 0 {
p.logger.Info("Sending Resume Streaming")
p.managerStatus <- PushIsUp
}
}
case streamingDisabled:
p.logger.Info("Received Streaming Disabled Notification")
p.managerStatus <- StreamingDisabled
default:
p.logger.Debug(fmt.Sprintf("Unexpected control status received %d", controlStatus))
}
case <-p.cancelStreamingWatcher: // Stopping Watcher
return
}
}
}
func (p *PushManager) drainStatus() {
select {
case <-p.cancelStreamingWatcher: // Discarding previous msg
default:
}
select {
case <-p.cancelTokenExpiration: // Discarding previous token expiration
default:
}
}
// Start push services
func (p *PushManager) Start() {
if p.IsRunning() {
p.logger.Info("PushManager is already running, skipping Start")
return
}
p.drainStatus()
// errResult listener for fetching token and connecting to SSE
errResult := make(chan error, 1)
token, channels, err := p.fetchStreamingToken(errResult)
if err != nil {
p.cancelStreaming()
return
}
err = p.connectToStreaming(errResult, token, channels)
if err != nil {
p.cancelStreaming()
return
}
// Everything is good, starting workers
p.splitWorker.Start()
p.segmentWorker.Start()
// Sending Ready
p.managerStatus <- Ready
// Starting streaming status watcher, it will listen 1) errors in SSE, 2) publishers changes, 3) stop
go p.streamingStatusWatcher()
}
// Stop push services
func (p *PushManager) Stop() {
p.logger.Info("Stopping Push Services")
p.cancelAuthBackoff <- struct{}{}
p.cancelSSEBackoff <- struct{}{}
p.cancelTokenExpiration <- struct{}{}
p.cancelStreamingWatcher <- struct{}{}
if p.sseClient.IsRunning() {
p.sseClient.StopStreaming(true)
}
p.StopWorkers()
}
// IsRunning returns true if the services are running
func (p *PushManager) IsRunning() bool {
return p.sseClient.IsRunning() || p.splitWorker.IsRunning() || p.segmentWorker.IsRunning()
}
// StopWorkers stops workers
func (p *PushManager) StopWorkers() {
if p.splitWorker.IsRunning() {
p.splitWorker.Stop()
}
if p.segmentWorker.IsRunning() {
p.segmentWorker.Stop()
}
}
// StartWorkers starts workers
func (p *PushManager) StartWorkers() {
if !p.splitWorker.IsRunning() {
p.splitWorker.Start()
}
if !p.segmentWorker.IsRunning() {
p.segmentWorker.Start()
}
}

76
vendor/github.com/splitio/go-split-commons/v2/push/segment.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,76 @@
package push
import (
"errors"
"fmt"
"sync"
"sync/atomic"
"github.com/splitio/go-split-commons/v2/dtos"
"github.com/splitio/go-toolkit/v3/logging"
)
// SegmentUpdateWorker struct
type SegmentUpdateWorker struct {
activeGoroutines *sync.WaitGroup
segmentQueue chan dtos.SegmentChangeNotification
handler func(segmentName string, till *int64) error
logger logging.LoggerInterface
stop chan struct{}
running atomic.Value
}
// NewSegmentUpdateWorker creates SegmentUpdateWorker
func NewSegmentUpdateWorker(segmentQueue chan dtos.SegmentChangeNotification, handler func(segmentName string, till *int64) error, logger logging.LoggerInterface) (*SegmentUpdateWorker, error) {
if cap(segmentQueue) < 5000 {
return nil, errors.New("")
}
running := atomic.Value{}
running.Store(false)
return &SegmentUpdateWorker{
segmentQueue: segmentQueue,
handler: handler,
logger: logger,
stop: make(chan struct{}, 1),
running: running,
}, nil
}
// Start starts worker
func (s *SegmentUpdateWorker) Start() {
s.logger.Debug("Started SegmentUpdateWorker")
if s.IsRunning() {
s.logger.Debug("Segment worker is already running")
return
}
s.running.Store(true)
go func() {
for {
select {
case segmentUpdate := <-s.segmentQueue:
s.logger.Debug("Received Segment update and proceding to perform fetch")
s.logger.Debug(fmt.Sprintf("SegmentName: %s\nChangeNumber: %d", segmentUpdate.SegmentName, &segmentUpdate.ChangeNumber))
err := s.handler(segmentUpdate.SegmentName, &segmentUpdate.ChangeNumber)
if err != nil {
s.logger.Error(err)
}
case <-s.stop:
return
}
}
}()
}
// Stop stops worker
func (s *SegmentUpdateWorker) Stop() {
if s.IsRunning() {
s.stop <- struct{}{}
s.running.Store(false)
}
}
// IsRunning indicates if worker is running or not
func (s *SegmentUpdateWorker) IsRunning() bool {
return s.running.Load().(bool)
}

77
vendor/github.com/splitio/go-split-commons/v2/push/split.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,77 @@
package push
import (
"errors"
"fmt"
"sync"
"sync/atomic"
"github.com/splitio/go-split-commons/v2/dtos"
"github.com/splitio/go-toolkit/v3/logging"
)
// SplitUpdateWorker struct
type SplitUpdateWorker struct {
activeGoroutines *sync.WaitGroup
splitQueue chan dtos.SplitChangeNotification
handler func(till *int64) error
logger logging.LoggerInterface
stop chan struct{}
running atomic.Value
}
// NewSplitUpdateWorker creates SplitUpdateWorker
func NewSplitUpdateWorker(splitQueue chan dtos.SplitChangeNotification, handler func(till *int64) error, logger logging.LoggerInterface) (*SplitUpdateWorker, error) {
if cap(splitQueue) < 5000 {
return nil, errors.New("")
}
running := atomic.Value{}
running.Store(false)
return &SplitUpdateWorker{
activeGoroutines: &sync.WaitGroup{},
splitQueue: splitQueue,
handler: handler,
logger: logger,
running: running,
stop: make(chan struct{}, 1),
}, nil
}
// Start starts worker
func (s *SplitUpdateWorker) Start() {
s.logger.Debug("Started SplitUpdateWorker")
if s.IsRunning() {
s.logger.Info("Split worker is already running")
return
}
s.running.Store(true)
go func() {
for {
select {
case splitUpdate := <-s.splitQueue:
s.logger.Debug("Received Split update and proceding to perform fetch")
s.logger.Debug(fmt.Sprintf("ChangeNumber: %d", splitUpdate.ChangeNumber))
err := s.handler(&splitUpdate.ChangeNumber)
if err != nil {
s.logger.Error(err)
}
case <-s.stop:
return
}
}
}()
}
// Stop stops worker
func (s *SplitUpdateWorker) Stop() {
if s.IsRunning() {
s.stop <- struct{}{}
s.running.Store(false)
}
}
// IsRunning indicates if worker is running or not
func (s *SplitUpdateWorker) IsRunning() bool {
return s.running.Load().(bool)
}

39
vendor/github.com/splitio/go-split-commons/v2/service/api/auth.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,39 @@
package api
import (
"encoding/json"
"github.com/splitio/go-split-commons/v2/conf"
"github.com/splitio/go-split-commons/v2/dtos"
"github.com/splitio/go-toolkit/v3/logging"
)
// AuthAPIClient struct is responsible for authenticating client for push services
type AuthAPIClient struct {
client Client
logger logging.LoggerInterface
}
// NewAuthAPIClient instantiates and return an AuthAPIClient
func NewAuthAPIClient(apikey string, cfg conf.AdvancedConfig, logger logging.LoggerInterface, metadata dtos.Metadata) *AuthAPIClient {
return &AuthAPIClient{
client: NewHTTPClient(apikey, cfg, cfg.AuthServiceURL, logger, metadata),
logger: logger,
}
}
// Authenticate performs authentication for push services
func (a *AuthAPIClient) Authenticate() (*dtos.Token, error) {
raw, err := a.client.Get("/api/auth")
if err != nil {
a.logger.Error("Error while authenticating for streaming", err)
return nil, err
}
token := dtos.Token{}
err = json.Unmarshal(raw, &token)
if err != nil {
return nil, err
}
return &token, nil
}

157
vendor/github.com/splitio/go-split-commons/v2/service/api/client.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,157 @@
package api
import (
"bytes"
"compress/gzip"
"fmt"
"io"
"io/ioutil"
"net/http"
"time"
"github.com/splitio/go-split-commons/v2/conf"
"github.com/splitio/go-split-commons/v2/dtos"
"github.com/splitio/go-toolkit/v3/logging"
)
// Client interface for HTTPClient
type Client interface {
Get(service string) ([]byte, error)
Post(service string, body []byte, headers map[string]string) error
}
// HTTPClient structure to wrap up the net/http.Client
type HTTPClient struct {
url string
httpClient *http.Client
headers map[string]string
logger logging.LoggerInterface
apikey string
metadata dtos.Metadata
}
// NewHTTPClient instance of HttpClient
func NewHTTPClient(
apikey string,
cfg conf.AdvancedConfig,
endpoint string,
logger logging.LoggerInterface,
metadata dtos.Metadata,
) Client {
var timeout int
timeout = cfg.HTTPTimeout
client := &http.Client{Timeout: time.Duration(timeout) * time.Second}
return &HTTPClient{
url: endpoint,
httpClient: client,
logger: logger,
apikey: apikey,
metadata: metadata,
}
}
// Get method is a get call to an url
func (c *HTTPClient) Get(service string) ([]byte, error) {
serviceURL := c.url + service
c.logger.Debug("[GET] ", serviceURL)
req, _ := http.NewRequest("GET", serviceURL, nil)
authorization := c.apikey
c.logger.Debug("Authorization [ApiKey]: ", logging.ObfuscateAPIKey(authorization))
req.Header.Add("Accept-Encoding", "gzip")
req.Header.Add("Content-Type", "application/json")
c.logger.Debug(fmt.Sprintf("Headers: %v", req.Header))
req.Header.Add("Authorization", "Bearer "+authorization)
req.Header.Add("SplitSDKVersion", c.metadata.SDKVersion)
req.Header.Add("SplitSDKMachineName", c.metadata.MachineName)
req.Header.Add("SplitSDKMachineIP", c.metadata.MachineIP)
resp, err := c.httpClient.Do(req)
if err != nil {
c.logger.Error("Error requesting data to API: ", req.URL.String(), err.Error())
return nil, err
}
defer resp.Body.Close()
// Check that the server actually sent compressed data
var reader io.ReadCloser
switch resp.Header.Get("Content-Encoding") {
case "gzip":
reader, _ = gzip.NewReader(resp.Body)
defer reader.Close()
default:
reader = resp.Body
}
body, err := ioutil.ReadAll(reader)
if err != nil {
c.logger.Error(err.Error())
return nil, err
}
c.logger.Verbose("[RESPONSE_BODY]", string(body), "[END_RESPONSE_BODY]")
if resp.StatusCode >= 200 && resp.StatusCode < 300 {
return body, nil
}
c.logger.Error(fmt.Sprintf("GET method: Status Code: %d - %s", resp.StatusCode, resp.Status))
return nil, &dtos.HTTPError{
Code: resp.StatusCode,
Message: resp.Status,
}
}
// Post performs a HTTP POST request
func (c *HTTPClient) Post(service string, body []byte, headers map[string]string) error {
serviceURL := c.url + service
c.logger.Debug("[POST] ", serviceURL)
req, _ := http.NewRequest("POST", serviceURL, bytes.NewBuffer(body))
//****************
req.Close = true // To prevent EOF error when connection is closed
//****************
authorization := c.apikey
c.logger.Debug("Authorization [ApiKey]: ", logging.ObfuscateAPIKey(authorization))
req.Header.Add("Accept-Encoding", "gzip")
req.Header.Add("Content-Type", "application/json")
for headerName, headerValue := range headers {
req.Header.Add(headerName, headerValue)
}
c.logger.Debug(fmt.Sprintf("Headers: %v", req.Header))
req.Header.Add("Authorization", "Bearer "+authorization)
c.logger.Verbose("[REQUEST_BODY]", string(body), "[END_REQUEST_BODY]")
resp, err := c.httpClient.Do(req)
if err != nil {
c.logger.Error("Error posting data to API: ", req.URL.String(), err.Error())
return err
}
defer resp.Body.Close()
respBody, err := ioutil.ReadAll(resp.Body)
if err != nil {
c.logger.Error(err.Error())
return err
}
c.logger.Verbose("[RESPONSE_BODY]", string(respBody), "[END_RESPONSE_BODY]")
if resp.StatusCode >= 200 && resp.StatusCode < 300 {
return nil
}
c.logger.Error(fmt.Sprintf("POST method: Status Code: %d - %s", resp.StatusCode, resp.Status))
return &dtos.HTTPError{
Code: resp.StatusCode,
Message: resp.Status,
}
}

110
vendor/github.com/splitio/go-split-commons/v2/service/api/http_fetchers.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,110 @@
package api
import (
"bytes"
"encoding/json"
"strconv"
"github.com/splitio/go-split-commons/v2/conf"
"github.com/splitio/go-split-commons/v2/dtos"
"github.com/splitio/go-toolkit/v3/logging"
)
type httpFetcherBase struct {
client Client
logger logging.LoggerInterface
}
func (h *httpFetcherBase) fetchRaw(url string, since int64) ([]byte, error) {
var bufferQuery bytes.Buffer
bufferQuery.WriteString(url)
if since >= -1 {
bufferQuery.WriteString("?since=")
bufferQuery.WriteString(strconv.FormatInt(since, 10))
}
data, err := h.client.Get(bufferQuery.String())
if err != nil {
return nil, err
}
return data, nil
}
// HTTPSplitFetcher struct is responsible for fetching splits from the backend via HTTP protocol
type HTTPSplitFetcher struct {
httpFetcherBase
}
// NewHTTPSplitFetcher instantiates and return an HTTPSplitFetcher
func NewHTTPSplitFetcher(
apikey string,
cfg conf.AdvancedConfig,
logger logging.LoggerInterface,
metadata dtos.Metadata,
) *HTTPSplitFetcher {
return &HTTPSplitFetcher{
httpFetcherBase: httpFetcherBase{
client: NewHTTPClient(apikey, cfg, cfg.SdkURL, logger, metadata),
logger: logger,
},
}
}
// Fetch makes an http call to the split backend and returns the list of updated splits
func (f *HTTPSplitFetcher) Fetch(since int64) (*dtos.SplitChangesDTO, error) {
data, err := f.fetchRaw("/splitChanges", since)
if err != nil {
f.logger.Error("Error fetching split changes ", err)
return nil, err
}
var splitChangesDto dtos.SplitChangesDTO
err = json.Unmarshal(data, &splitChangesDto)
if err != nil {
f.logger.Error("Error parsing split changes JSON ", err)
return nil, err
}
return &splitChangesDto, nil
}
// HTTPSegmentFetcher struct is responsible for fetching segment by name from the API via HTTP method
type HTTPSegmentFetcher struct {
httpFetcherBase
}
// NewHTTPSegmentFetcher instantiates and returns a new HTTPSegmentFetcher.
func NewHTTPSegmentFetcher(
apikey string,
cfg conf.AdvancedConfig,
logger logging.LoggerInterface,
metadata dtos.Metadata,
) *HTTPSegmentFetcher {
return &HTTPSegmentFetcher{
httpFetcherBase: httpFetcherBase{
client: NewHTTPClient(apikey, cfg, cfg.SdkURL, logger, metadata),
logger: logger,
},
}
}
// Fetch issues a GET request to the split backend and returns the contents of a particular segment
func (f *HTTPSegmentFetcher) Fetch(segmentName string, since int64) (*dtos.SegmentChangesDTO, error) {
var bufferQuery bytes.Buffer
bufferQuery.WriteString("/segmentChanges/")
bufferQuery.WriteString(segmentName)
data, err := f.fetchRaw(bufferQuery.String(), since)
if err != nil {
f.logger.Error(err.Error())
return nil, err
}
var segmentChangesDto dtos.SegmentChangesDTO
err = json.Unmarshal(data, &segmentChangesDto)
if err != nil {
f.logger.Error("Error parsing segment changes JSON for segment ", segmentName, err)
return nil, err
}
return &segmentChangesDto, nil
}

197
vendor/github.com/splitio/go-split-commons/v2/service/api/http_recorders.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,197 @@
package api
import (
"encoding/json"
"github.com/splitio/go-split-commons/v2/conf"
"github.com/splitio/go-split-commons/v2/dtos"
"github.com/splitio/go-toolkit/v3/logging"
)
type httpRecorderBase struct {
client Client
logger logging.LoggerInterface
}
// RecordRaw records raw data
func (h *httpRecorderBase) RecordRaw(url string, data []byte, metadata dtos.Metadata, extraHeaders map[string]string) error {
headers := make(map[string]string)
headers["SplitSDKVersion"] = metadata.SDKVersion
if metadata.MachineName != "NA" && metadata.MachineName != "unknown" {
headers["SplitSDKMachineName"] = metadata.MachineName
}
if metadata.MachineIP != "NA" && metadata.MachineIP != "unknown" {
headers["SplitSDKMachineIP"] = metadata.MachineIP
}
if extraHeaders != nil {
for header, value := range extraHeaders {
headers[header] = value
}
}
return h.client.Post(url, data, headers)
}
// HTTPImpressionRecorder is a struct responsible for submitting impression bulks to the backend
type HTTPImpressionRecorder struct {
httpRecorderBase
}
// Record sends an array (or slice) of impressionsRecord to the backend
func (i *HTTPImpressionRecorder) Record(impressions []dtos.ImpressionsDTO, metadata dtos.Metadata, extraHeaders map[string]string) error {
data, err := json.Marshal(impressions)
if err != nil {
i.logger.Error("Error marshaling JSON", err.Error())
return err
}
err = i.RecordRaw("/testImpressions/bulk", data, metadata, extraHeaders)
if err != nil {
i.logger.Error("Error posting impressions", err.Error())
return err
}
return nil
}
// RecordImpressionsCount sens impressionsCount
func (i *HTTPImpressionRecorder) RecordImpressionsCount(pf dtos.ImpressionsCountDTO, metadata dtos.Metadata) error {
if len(pf.PerFeature) == 0 {
return nil
}
data, err := json.Marshal(pf)
if err != nil {
i.logger.Error("Error marshaling JSON", err.Error())
return err
}
err = i.RecordRaw("/testImpressions/count", data, metadata, nil)
if err != nil {
i.logger.Error("Error posting impressionsCount", err.Error())
return err
}
return nil
}
// NewHTTPImpressionRecorder instantiates an HTTPImpressionRecorder
func NewHTTPImpressionRecorder(
apikey string,
cfg conf.AdvancedConfig,
logger logging.LoggerInterface,
) *HTTPImpressionRecorder {
client := NewHTTPClient(apikey, cfg, cfg.EventsURL, logger, dtos.Metadata{})
return &HTTPImpressionRecorder{
httpRecorderBase: httpRecorderBase{
client: client,
logger: logger,
},
}
}
// HTTPMetricsRecorder is a struct responsible for submitting metrics (latency, gauge, counters) to the backend
type HTTPMetricsRecorder struct {
httpRecorderBase
}
// RecordCounters method submits counter metrics to the backend
func (m *HTTPMetricsRecorder) RecordCounters(counters []dtos.CounterDTO, metadata dtos.Metadata) error {
data, err := json.Marshal(counters)
if err != nil {
m.logger.Error("Error marshaling JSON", err.Error())
return err
}
err = m.RecordRaw("/metrics/counters", data, metadata, nil)
if err != nil {
m.logger.Error("Error posting counters", err.Error())
return err
}
return nil
}
// RecordLatencies method submits latency metrics to the backend
func (m *HTTPMetricsRecorder) RecordLatencies(latencies []dtos.LatenciesDTO, metadata dtos.Metadata) error {
data, err := json.Marshal(latencies)
if err != nil {
m.logger.Error("Error marshaling JSON", err.Error())
return err
}
err = m.RecordRaw("/metrics/times", data, metadata, nil)
if err != nil {
m.logger.Error("Error posting latencies", err.Error())
return err
}
return nil
}
// RecordGauge method submits gauge metrics to the backend
func (m *HTTPMetricsRecorder) RecordGauge(gauge dtos.GaugeDTO, metadata dtos.Metadata) error {
data, err := json.Marshal(gauge)
if err != nil {
m.logger.Error("Error marshaling JSON", err.Error())
return err
}
err = m.RecordRaw("/metrics/gauge", data, metadata, nil)
if err != nil {
m.logger.Error("Error posting gauges", err.Error())
return err
}
return nil
}
// NewHTTPMetricsRecorder instantiates an HTTPMetricsRecorder
func NewHTTPMetricsRecorder(
apikey string,
cfg conf.AdvancedConfig,
logger logging.LoggerInterface,
) *HTTPMetricsRecorder {
client := NewHTTPClient(apikey, cfg, cfg.EventsURL, logger, dtos.Metadata{})
return &HTTPMetricsRecorder{
httpRecorderBase: httpRecorderBase{
client: client,
logger: logger,
},
}
}
// HTTPEventsRecorder is a struct responsible for submitting events bulks to the backend
type HTTPEventsRecorder struct {
httpRecorderBase
}
// Record sends an array (or slice) of dtos.EventDTO to the backend
func (i *HTTPEventsRecorder) Record(events []dtos.EventDTO, metadata dtos.Metadata) error {
data, err := json.Marshal(events)
if err != nil {
i.logger.Error("Error marshaling JSON", err.Error())
return err
}
err = i.RecordRaw("/events/bulk", data, metadata, nil)
if err != nil {
i.logger.Error("Error posting events", err.Error())
return err
}
return nil
}
// NewHTTPEventsRecorder instantiates an HTTPEventsRecorder
func NewHTTPEventsRecorder(
apikey string,
cfg conf.AdvancedConfig,
logger logging.LoggerInterface,
) *HTTPEventsRecorder {
client := NewHTTPClient(apikey, cfg, cfg.EventsURL, logger, dtos.Metadata{})
return &HTTPEventsRecorder{
httpRecorderBase: httpRecorderBase{
client: client,
logger: logger,
},
}
}

127
vendor/github.com/splitio/go-split-commons/v2/service/api/sse/client.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,127 @@
package sse
import (
"strings"
"sync"
"sync/atomic"
"github.com/splitio/go-split-commons/v2/conf"
"github.com/splitio/go-toolkit/v3/logging"
"github.com/splitio/go-toolkit/v3/sse"
)
const (
version = "1.1"
keepAlive = 120
)
// StreamingClient struct
type StreamingClient struct {
mutex *sync.RWMutex
sseClient *sse.SSEClient
sseStatus chan int
streamingStatus chan<- int
running atomic.Value
logger logging.LoggerInterface
stopped chan struct{}
}
// NewStreamingClient creates new SSE Client
func NewStreamingClient(cfg *conf.AdvancedConfig, streamingStatus chan int, logger logging.LoggerInterface) *StreamingClient {
sseStatus := make(chan int, 1)
sseClient, _ := sse.NewSSEClient(cfg.StreamingServiceURL, sseStatus, keepAlive, logger)
running := atomic.Value{}
running.Store(false)
return &StreamingClient{
mutex: &sync.RWMutex{},
sseClient: sseClient,
sseStatus: sseStatus,
streamingStatus: streamingStatus,
logger: logger,
running: running,
stopped: make(chan struct{}, 1),
}
}
// ConnectStreaming connects to streaming
func (s *StreamingClient) ConnectStreaming(token string, channelList []string, handleIncomingMessage func(e map[string]interface{})) {
params := make(map[string]string)
params["channels"] = strings.Join(append(channelList), ",")
params["accessToken"] = token
params["v"] = version
httpHandlerExited := make(chan struct{}, 1)
go func() {
s.sseClient.Do(params, handleIncomingMessage)
httpHandlerExited <- struct{}{}
}()
// Consume remaining message in completion signaling channel if any:
select {
case <-s.stopped:
default:
}
select {
case <-s.sseStatus:
default:
}
go func() {
defer func() { // When this goroutine exits, StopStreaming is freed
select {
case s.stopped <- struct{}{}:
default:
}
}()
for {
select {
case <-httpHandlerExited:
return
case status := <-s.sseStatus:
switch status {
case sse.OK:
s.logger.Info("SSE OK")
s.running.Store(true)
s.streamingStatus <- sse.OK
case sse.ErrorConnectToStreaming:
s.logger.Error("Error connecting to streaming")
s.streamingStatus <- sse.ErrorConnectToStreaming
case sse.ErrorKeepAlive:
s.logger.Error("Connection timed out")
s.streamingStatus <- sse.ErrorKeepAlive
case sse.ErrorOnClientCreation:
s.logger.Error("Could not create client for streaming")
s.streamingStatus <- sse.ErrorOnClientCreation
case sse.ErrorReadingStream:
s.logger.Error("Error reading streaming buffer")
s.streamingStatus <- sse.ErrorReadingStream
case sse.ErrorRequestPerformed:
s.logger.Error("Error performing request when connect to stream service")
s.streamingStatus <- sse.ErrorRequestPerformed
case sse.ErrorInternal:
s.logger.Error("Internal Error when connect to stream service")
s.streamingStatus <- sse.ErrorInternal
default:
s.logger.Error("Unexpected error occured with streaming")
s.streamingStatus <- sse.ErrorUnexpected
}
}
}
}()
}
// StopStreaming stops streaming
func (s *StreamingClient) StopStreaming(blocking bool) {
s.sseClient.Shutdown()
s.logger.Info("Stopped streaming")
s.running.Store(false)
if blocking {
<-s.stopped
}
}
// IsRunning returns true if it's running
func (s *StreamingClient) IsRunning() bool {
return s.running.Load().(bool)
}

38
vendor/github.com/splitio/go-split-commons/v2/service/interfaces.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,38 @@
package service
import (
"github.com/splitio/go-split-commons/v2/dtos"
)
// AuthClient inteface to be implemneted by AuthClient
type AuthClient interface {
Authenticate() (*dtos.Token, error)
}
// SplitFetcher interface to be implemented by Split Fetchers
type SplitFetcher interface {
Fetch(changeNumber int64) (*dtos.SplitChangesDTO, error)
}
// SegmentFetcher interface to be implemented by Split Fetchers
type SegmentFetcher interface {
Fetch(name string, changeNumber int64) (*dtos.SegmentChangesDTO, error)
}
// ImpressionsRecorder interface to be implemented by Impressions loggers
type ImpressionsRecorder interface {
Record(impressions []dtos.ImpressionsDTO, metadata dtos.Metadata, extraHeaders map[string]string) error
RecordImpressionsCount(pf dtos.ImpressionsCountDTO, metadata dtos.Metadata) error
}
// MetricsRecorder interface to be implemented by Metrics loggers
type MetricsRecorder interface {
RecordLatencies(latencies []dtos.LatenciesDTO, metadata dtos.Metadata) error
RecordCounters(counters []dtos.CounterDTO, metadata dtos.Metadata) error
RecordGauge(gauge dtos.GaugeDTO, metadata dtos.Metadata) error
}
// EventsRecorder interface to post events
type EventsRecorder interface {
Record(events []dtos.EventDTO, metadata dtos.Metadata) error
}

258
vendor/github.com/splitio/go-split-commons/v2/service/local/splitFetcher.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,258 @@
package local
import (
"fmt"
"io/ioutil"
"log"
"regexp"
"runtime/debug"
"strings"
"github.com/splitio/go-split-commons/v2/dtos"
"github.com/splitio/go-toolkit/v3/logging"
yaml "gopkg.in/yaml.v2"
)
const (
// SplitFileFormatClassic represents the file format of the standard split definition file <feature treatment>
SplitFileFormatClassic = iota
// SplitFileFormatJSON represents the file format of a JSON representation of split dtos
SplitFileFormatJSON
// SplitFileFormatYAML represents the file format of a YAML representation of split dtos
SplitFileFormatYAML
)
// FileSplitFetcher struct fetches splits from a file
type FileSplitFetcher struct {
splitFile string
fileFormat int
lastChangeNumber int64
}
// NewFileSplitFetcher returns a new instance of LocalFileSplitFetcher
func NewFileSplitFetcher(splitFile string, logger logging.LoggerInterface) *FileSplitFetcher {
var r = regexp.MustCompile("(?i)(.yml$|.yaml$)")
if r.MatchString(splitFile) {
return &FileSplitFetcher{
splitFile: splitFile,
fileFormat: SplitFileFormatYAML,
}
}
logger.Warning("Localhost mode: .split mocks will be deprecated soon in favor of YAML files, which provide more targeting power. Take a look in our documentation.")
return &FileSplitFetcher{
splitFile: splitFile,
fileFormat: SplitFileFormatClassic,
}
}
func parseSplitsClassic(data string) []dtos.SplitDTO {
splits := make([]dtos.SplitDTO, 0)
lines := strings.Split(data, "\n")
for _, line := range lines {
words := strings.Fields(line)
if len(words) < 2 || len(words[0]) < 1 || words[0][0] == '#' {
// Skip the line if it has less than two words, the words are empty strings or
// it begins with '#' character
continue
}
splitName := words[0]
treatment := words[1]
splits = append(splits, createSplit(
splitName,
treatment,
createRolloutCondition(treatment),
make(map[string]string),
))
}
return splits
}
func createSplit(splitName string, treatment string, condition dtos.ConditionDTO, configurations map[string]string) dtos.SplitDTO {
split := dtos.SplitDTO{
Name: splitName,
TrafficAllocation: 100,
Conditions: []dtos.ConditionDTO{condition},
Status: "ACTIVE",
DefaultTreatment: "control",
Configurations: configurations,
}
return split
}
func createWhitelistedCondition(treatment string, keys interface{}) dtos.ConditionDTO {
var whitelist []string
switch keys := keys.(type) {
case string:
whitelist = []string{keys}
case []string:
whitelist = keys
case []interface{}:
whitelist = make([]string, 0)
for _, key := range keys {
k, ok := key.(string)
if ok {
whitelist = append(whitelist, k)
}
}
default:
whitelist = make([]string, 0)
}
return dtos.ConditionDTO{
ConditionType: "WHITELIST",
Label: "LOCAL_",
MatcherGroup: dtos.MatcherGroupDTO{
Combiner: "AND",
Matchers: []dtos.MatcherDTO{
{
MatcherType: "WHITELIST",
Negate: false,
Whitelist: &dtos.WhitelistMatcherDataDTO{
Whitelist: whitelist,
},
},
},
},
Partitions: []dtos.PartitionDTO{
{
Size: 100,
Treatment: treatment,
},
},
}
}
func createRolloutCondition(treatment string) dtos.ConditionDTO {
return dtos.ConditionDTO{
ConditionType: "ROLLOUT",
Label: "LOCAL_ROLLOUT",
MatcherGroup: dtos.MatcherGroupDTO{
Combiner: "AND",
Matchers: []dtos.MatcherDTO{
{
MatcherType: "ALL_KEYS",
Negate: false,
},
},
},
Partitions: []dtos.PartitionDTO{
{
Size: 100,
Treatment: treatment,
},
{
Size: 0,
Treatment: "_",
},
},
}
}
func createCondition(keys interface{}, treatment string) dtos.ConditionDTO {
if keys != nil {
return createWhitelistedCondition(treatment, keys)
}
return createRolloutCondition(treatment)
}
func parseSplitsYAML(data string) (d []dtos.SplitDTO) {
// Set up a guard deferred function to recover if some error occurs during parsing
defer func() {
if r := recover(); r != nil {
// At this point we'll only trust that the logger isn't panicking trust
// that the logger isn't panicking
log.Fatalf("Localhost Parsing: %v", string(debug.Stack()))
d = make([]dtos.SplitDTO, 0)
}
}()
splits := make([]dtos.SplitDTO, 0)
var splitsFromYAML []map[string]map[string]interface{}
err := yaml.Unmarshal([]byte(data), &splitsFromYAML)
if err != nil {
log.Fatalf("error: %v", err)
return splits
}
splitsToParse := make(map[string]dtos.SplitDTO, 0)
for _, splitMap := range splitsFromYAML {
for splitName, splitParsed := range splitMap {
split, ok := splitsToParse[splitName]
treatment, isString := splitParsed["treatment"].(string)
if !isString {
break
}
config, isValidConfig := splitParsed["config"].(string)
if !ok {
configurations := make(map[string]string)
if isValidConfig {
configurations[treatment] = config
}
splitsToParse[splitName] = createSplit(
splitName,
treatment,
createCondition(splitParsed["keys"], treatment),
configurations,
)
} else {
newCondition := createCondition(splitParsed["keys"], treatment)
if newCondition.ConditionType == "ROLLOUT" {
split.Conditions = append(split.Conditions, newCondition)
} else {
split.Conditions = append([]dtos.ConditionDTO{newCondition}, split.Conditions...)
}
configurations := split.Configurations
if isValidConfig {
configurations[treatment] = config
}
split.Configurations = configurations
splitsToParse[splitName] = split
}
}
}
for _, split := range splitsToParse {
splits = append(splits, split)
}
return splits
}
// Fetch parses the file and returns the appropriate structures
func (s *FileSplitFetcher) Fetch(changeNumber int64) (*dtos.SplitChangesDTO, error) {
fileContents, err := ioutil.ReadFile(s.splitFile)
if err != nil {
return nil, err
}
var splits []dtos.SplitDTO
var till int64
since := s.lastChangeNumber
if s.lastChangeNumber != 0 {
//The first time we should return since == till
till = since + 1
}
data := string(fileContents)
switch s.fileFormat {
case SplitFileFormatClassic:
splits = parseSplitsClassic(data)
case SplitFileFormatYAML:
splits = parseSplitsYAML(data)
case SplitFileFormatJSON:
return nil, fmt.Errorf("JSON is not yet supported")
default:
return nil, fmt.Errorf("Unsupported file format")
}
s.lastChangeNumber++
return &dtos.SplitChangesDTO{
Splits: splits,
Since: since,
Till: till,
}, nil
}

35
vendor/github.com/splitio/go-split-commons/v2/service/split.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,35 @@
package service
import (
"github.com/splitio/go-split-commons/v2/conf"
"github.com/splitio/go-split-commons/v2/dtos"
"github.com/splitio/go-split-commons/v2/service/api"
"github.com/splitio/go-toolkit/v3/logging"
)
// SplitAPI struct for fetchers and recorders
type SplitAPI struct {
AuthClient AuthClient
SplitFetcher SplitFetcher
SegmentFetcher SegmentFetcher
ImpressionRecorder ImpressionsRecorder
EventRecorder EventsRecorder
MetricRecorder MetricsRecorder
}
// NewSplitAPI creates new splitAPI
func NewSplitAPI(
apikey string,
conf conf.AdvancedConfig,
logger logging.LoggerInterface,
metadata dtos.Metadata,
) *SplitAPI {
return &SplitAPI{
AuthClient: api.NewAuthAPIClient(apikey, conf, logger, metadata),
SplitFetcher: api.NewHTTPSplitFetcher(apikey, conf, logger, metadata),
SegmentFetcher: api.NewHTTPSegmentFetcher(apikey, conf, logger, metadata),
ImpressionRecorder: api.NewHTTPImpressionRecorder(apikey, conf, logger),
EventRecorder: api.NewHTTPEventsRecorder(apikey, conf, logger),
MetricRecorder: api.NewHTTPMetricsRecorder(apikey, conf, logger),
}
}

118
vendor/github.com/splitio/go-split-commons/v2/storage/interfaces.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,118 @@
package storage
import (
"github.com/splitio/go-split-commons/v2/dtos"
"github.com/splitio/go-toolkit/v3/datastructures/set"
)
// SplitStorageProducer should be implemented by structs that offer writing splits in storage
type SplitStorageProducer interface {
KillLocally(splitName string, defaultTreatment string, changeNumber int64)
PutMany(splits []dtos.SplitDTO, changeNumber int64)
Remove(splitName string)
SetChangeNumber(changeNumber int64) error
}
// SplitStorageConsumer should be implemented by structs that offer reading splits from storage
type SplitStorageConsumer interface {
All() []dtos.SplitDTO
ChangeNumber() (int64, error)
FetchMany(splitNames []string) map[string]*dtos.SplitDTO
SegmentNames() *set.ThreadUnsafeSet // Not in Spec
Split(splitName string) *dtos.SplitDTO
SplitNames() []string
TrafficTypeExists(trafficType string) bool
}
// SegmentStorageProducer interface should be implemented by all structs that offer writing segments
type SegmentStorageProducer interface {
Update(name string, toAdd *set.ThreadUnsafeSet, toRemove *set.ThreadUnsafeSet, changeNumber int64) error
SetChangeNumber(segmentName string, till int64) error
}
// SegmentStorageConsumer interface should be implemented by all structs that ofer reading segments
type SegmentStorageConsumer interface {
ChangeNumber(segmentName string) (int64, error)
CountRemovedKeys(segmentName string) int64
Keys(segmentName string) *set.ThreadUnsafeSet
SegmentContainsKey(segmentName string, key string) (bool, error)
}
// ImpressionStorageProducer interface should be impemented by structs that accept incoming impressions
type ImpressionStorageProducer interface {
LogImpressions(impressions []dtos.Impression) error
}
// ImpressionStorageConsumer interface should be implemented by structs that offer popping impressions
type ImpressionStorageConsumer interface {
Count() int64
Drop(size *int64) error
Empty() bool
PopN(n int64) ([]dtos.Impression, error)
PopNWithMetadata(n int64) ([]dtos.ImpressionQueueObject, error)
}
// MetricsStorageProducer interface should be impemented by structs that accept incoming metrics
type MetricsStorageProducer interface {
PutGauge(key string, gauge float64)
IncLatency(metricName string, index int)
IncCounter(key string)
}
// MetricsStorageConsumer interface should be implemented by structs that offer popping metrics
type MetricsStorageConsumer interface {
PeekCounters() map[string]int64
PeekLatencies() map[string][]int64
PopGauges() []dtos.GaugeDTO
PopLatencies() []dtos.LatenciesDTO
PopCounters() []dtos.CounterDTO
PopGaugesWithMetadata() (*dtos.GaugeDataBulk, error)
PopLatenciesWithMetadata() (*dtos.LatencyDataBulk, error)
PopCountersWithMetadata() (*dtos.CounterDataBulk, error)
}
// EventStorageProducer interface should be implemented by structs that accept incoming events
type EventStorageProducer interface {
Push(event dtos.EventDTO, size int) error
}
// EventStorageConsumer interface should be implemented by structs that offer popping impressions
type EventStorageConsumer interface {
Count() int64
Drop(size *int64) error
Empty() bool
PopN(n int64) ([]dtos.EventDTO, error)
PopNWithMetadata(n int64) ([]dtos.QueueStoredEventDTO, error)
}
// --- Wide Interfaces
// SplitStorage wraps consumer & producer interfaces
type SplitStorage interface {
SplitStorageProducer
SplitStorageConsumer
}
// SegmentStorage wraps consumer and producer interfaces
type SegmentStorage interface {
SegmentStorageProducer
SegmentStorageConsumer
}
// ImpressionStorage wraps consumer & producer interfaces
type ImpressionStorage interface {
ImpressionStorageConsumer
ImpressionStorageProducer
}
// MetricsStorage wraps consumer and producer interfaces
type MetricsStorage interface {
MetricsStorageConsumer
MetricsStorageProducer
}
// EventsStorage wraps consumer and producer interfaces
type EventsStorage interface {
EventStorageConsumer
EventStorageProducer
}

125
vendor/github.com/splitio/go-split-commons/v2/storage/metricWrapper.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,125 @@
package storage
import (
"errors"
"strings"
"github.com/splitio/go-toolkit/v3/logging"
)
// MetricWrapper struct
type MetricWrapper struct {
Telemetry MetricsStorage
LocalTelemetry MetricsStorage
logger logging.LoggerInterface
}
const (
// SplitChangesCounter counters
SplitChangesCounter = iota
// SplitChangesLatency latencies
SplitChangesLatency
// SegmentChangesCounter counters
SegmentChangesCounter
// SegmentChangesLatency latencies
SegmentChangesLatency
// TestImpressionsCounter counter
TestImpressionsCounter
// TestImpressionsLatency latencies
TestImpressionsLatency
// PostEventsCounter counter
PostEventsCounter
//PostEventsLatency latencies
PostEventsLatency
// MySegmentsCounter counters
MySegmentsCounter
// MySegmentsLatency latencies
MySegmentsLatency
)
const (
counter = "backend::request.{status}"
splitChangesCounter = "splitChangeFetcher.status.{status}"
splitChangesLatency = "splitChangeFetcher.time"
localSplitChangesLatency = "backend::/api/splitChanges"
segmentChangesCounter = "segmentChangeFetcher.status.{status}"
segmentChangesLatency = "segmentChangeFetcher.time"
localSegmentChangesLatency = "backend::/api/segmentChanges"
testImpressionsCounter = "testImpressions.status.{status}"
testImpressionsLatency = "testImpressions.time"
localTestImpressionsLatency = "backend::/api/testImpressions/bulk"
postEventsCounter = "events.status.{status}"
postEventsLatency = "events.time"
localPostEventsLatency = "backend::/api/events/bulk"
mySegmentsCounter = "mySegments.status.{status}"
mySegmentsLatency = "mySegments.time"
localMySegmentsLatency = "backend::/api/mySegments"
)
// NewMetricWrapper builds new wrapper
func NewMetricWrapper(telemetry MetricsStorage, localTelemetry MetricsStorage, logger logging.LoggerInterface) *MetricWrapper {
return &MetricWrapper{
LocalTelemetry: localTelemetry,
logger: logger,
Telemetry: telemetry,
}
}
func (m *MetricWrapper) getKey(key int) (string, string, error) {
switch key {
case SplitChangesCounter:
return splitChangesCounter, counter, nil
case SplitChangesLatency:
return splitChangesLatency, localSplitChangesLatency, nil
case SegmentChangesCounter:
return segmentChangesCounter, counter, nil
case SegmentChangesLatency:
return segmentChangesLatency, localSegmentChangesLatency, nil
case TestImpressionsCounter:
return testImpressionsCounter, counter, nil
case TestImpressionsLatency:
return testImpressionsLatency, localTestImpressionsLatency, nil
case PostEventsCounter:
return postEventsCounter, counter, nil
case PostEventsLatency:
return postEventsLatency, localPostEventsLatency, nil
case MySegmentsCounter:
return mySegmentsCounter, counter, nil
case MySegmentsLatency:
return mySegmentsLatency, localMySegmentsLatency, nil
default:
return "", "", errors.New("Key does not exist")
}
}
// StoreCounters stores counters
func (m *MetricWrapper) StoreCounters(key int, value string) {
common, local, err := m.getKey(key)
if err != nil {
return
}
if m.LocalTelemetry != nil {
m.LocalTelemetry.IncCounter(strings.Replace(local, "{status}", value, 1))
}
if value == "ok" {
value = "200"
}
m.Telemetry.IncCounter(strings.Replace(common, "{status}", value, 1))
}
// StoreLatencies stores counters
func (m *MetricWrapper) StoreLatencies(key int, bucket int) {
common, local, err := m.getKey(key)
if err != nil {
return
}
if m.LocalTelemetry != nil {
m.LocalTelemetry.IncLatency(local, bucket)
}
m.Telemetry.IncLatency(common, bucket)
}

43
vendor/github.com/splitio/go-split-commons/v2/storage/mocks/event.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,43 @@
package mocks
import "github.com/splitio/go-split-commons/v2/dtos"
// MockEventStorage is a mocked implementation of Event Storage
type MockEventStorage struct {
EmptyCall func() bool
CountCall func() int64
PopNCall func(n int64) ([]dtos.EventDTO, error)
PopNWithMetadataCall func(n int64) ([]dtos.QueueStoredEventDTO, error)
PushCall func(event dtos.EventDTO, size int) error
DropCall func(size *int64) error
}
// Empty mock
func (m MockEventStorage) Empty() bool {
return m.EmptyCall()
}
// Count mock
func (m MockEventStorage) Count() int64 {
return m.CountCall()
}
// PopN mock
func (m MockEventStorage) PopN(n int64) ([]dtos.EventDTO, error) {
return m.PopNCall(n)
}
// PopNWithMetadata mock
func (m MockEventStorage) PopNWithMetadata(n int64) ([]dtos.QueueStoredEventDTO, error) {
return m.PopNWithMetadataCall(n)
}
// Push mock
func (m MockEventStorage) Push(event dtos.EventDTO, size int) error {
return m.PushCall(event, size)
}
// Drop mock
func (m MockEventStorage) Drop(size *int64) error {
return m.Drop(size)
}

43
vendor/github.com/splitio/go-split-commons/v2/storage/mocks/impression.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,43 @@
package mocks
import "github.com/splitio/go-split-commons/v2/dtos"
// MockImpressionStorage is a mocked implementation of Impression Storage
type MockImpressionStorage struct {
EmptyCall func() bool
CountCall func() int64
LogImpressionsCall func(impressions []dtos.Impression) error
PopNCall func(n int64) ([]dtos.Impression, error)
PopNWithMetadataCall func(n int64) ([]dtos.ImpressionQueueObject, error)
DropCall func(size *int64) error
}
// Empty mock
func (m MockImpressionStorage) Empty() bool {
return m.EmptyCall()
}
// Count mock
func (m MockImpressionStorage) Count() int64 {
return m.CountCall()
}
// LogImpressions mock
func (m MockImpressionStorage) LogImpressions(impressions []dtos.Impression) error {
return m.LogImpressionsCall(impressions)
}
// PopN mock
func (m MockImpressionStorage) PopN(n int64) ([]dtos.Impression, error) {
return m.PopNCall(n)
}
// PopNWithMetadata mock
func (m MockImpressionStorage) PopNWithMetadata(n int64) ([]dtos.ImpressionQueueObject, error) {
return m.PopNWithMetadataCall(n)
}
// Drop mock
func (m MockImpressionStorage) Drop(size *int64) error {
return m.Drop(size)
}

73
vendor/github.com/splitio/go-split-commons/v2/storage/mocks/metric.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,73 @@
package mocks
import "github.com/splitio/go-split-commons/v2/dtos"
// MockMetricStorage is a mocked implementation of Metric Storage
type MockMetricStorage struct {
IncCounterCall func(key string)
IncLatencyCall func(metricName string, index int)
PutGaugeCall func(key string, gauge float64)
PopGaugesCall func() []dtos.GaugeDTO
PopLatenciesCall func() []dtos.LatenciesDTO
PopCountersCall func() []dtos.CounterDTO
PeekCountersCall func() map[string]int64
PeekLatenciesCall func() map[string][]int64
PopGaugesWithMetadataCall func() (*dtos.GaugeDataBulk, error)
PopCountersWithMetadataCall func() (*dtos.CounterDataBulk, error)
PopLatenciesWithMetadataCall func() (*dtos.LatencyDataBulk, error)
}
// IncCounter mock
func (m MockMetricStorage) IncCounter(key string) {
m.IncCounterCall(key)
}
// IncLatency mock
func (m MockMetricStorage) IncLatency(metricName string, index int) {
m.IncLatencyCall(metricName, index)
}
// PutGauge mock
func (m MockMetricStorage) PutGauge(key string, gauge float64) {
m.PutGaugeCall(key, gauge)
}
// PopGauges mock
func (m MockMetricStorage) PopGauges() []dtos.GaugeDTO {
return m.PopGaugesCall()
}
// PopLatencies mock
func (m MockMetricStorage) PopLatencies() []dtos.LatenciesDTO {
return m.PopLatenciesCall()
}
// PopCounters mock
func (m MockMetricStorage) PopCounters() []dtos.CounterDTO {
return m.PopCountersCall()
}
// PeekCounters mock
func (m MockMetricStorage) PeekCounters() map[string]int64 {
return m.PeekCountersCall()
}
// PeekLatencies mock
func (m MockMetricStorage) PeekLatencies() map[string][]int64 {
return m.PeekLatenciesCall()
}
// PopGaugesWithMetadata mock
func (m MockMetricStorage) PopGaugesWithMetadata() (*dtos.GaugeDataBulk, error) {
return m.PopGaugesWithMetadataCall()
}
// PopCountersWithMetadata mock
func (m MockMetricStorage) PopCountersWithMetadata() (*dtos.CounterDataBulk, error) {
return m.PopCountersWithMetadataCall()
}
// PopLatenciesWithMetadata mock
func (m MockMetricStorage) PopLatenciesWithMetadata() (*dtos.LatencyDataBulk, error) {
return m.PopLatenciesWithMetadataCall()
}

43
vendor/github.com/splitio/go-split-commons/v2/storage/mocks/segment.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,43 @@
package mocks
import "github.com/splitio/go-toolkit/v3/datastructures/set"
// MockSegmentStorage is a mocked implementation of Segment Storage
type MockSegmentStorage struct {
ChangeNumberCall func(segmentName string) (int64, error)
KeysCall func(segmentName string) *set.ThreadUnsafeSet
UpdateCall func(name string, toAdd *set.ThreadUnsafeSet, toRemove *set.ThreadUnsafeSet, changeNumber int64) error
SegmentContainsKeyCall func(segmentName string, key string) (bool, error)
SetChangeNumberCall func(segmentName string, till int64) error
CountRemovedKeysCall func(segmentName string) int64
}
// ChangeNumber mock
func (m MockSegmentStorage) ChangeNumber(segmentName string) (int64, error) {
return m.ChangeNumberCall(segmentName)
}
// Keys mock
func (m MockSegmentStorage) Keys(segmentName string) *set.ThreadUnsafeSet {
return m.KeysCall(segmentName)
}
// Update mock
func (m MockSegmentStorage) Update(name string, toAdd *set.ThreadUnsafeSet, toRemove *set.ThreadUnsafeSet, changeNumber int64) error {
return m.UpdateCall(name, toAdd, toRemove, changeNumber)
}
// SegmentContainsKey mock
func (m MockSegmentStorage) SegmentContainsKey(segmentName string, key string) (bool, error) {
return m.SegmentContainsKeyCall(segmentName, key)
}
// SetChangeNumber mock
func (m MockSegmentStorage) SetChangeNumber(segmentName string, till int64) error {
return m.SetChangeNumberCall(segmentName, till)
}
// CountRemovedKeys mock
func (m MockSegmentStorage) CountRemovedKeys(segmentName string) int64 {
return m.CountRemovedKeysCall(segmentName)
}

76
vendor/github.com/splitio/go-split-commons/v2/storage/mocks/split.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,76 @@
package mocks
import (
"github.com/splitio/go-split-commons/v2/dtos"
"github.com/splitio/go-toolkit/v3/datastructures/set"
)
// MockSplitStorage is a mocked implementation of Split Storage
type MockSplitStorage struct {
AllCall func() []dtos.SplitDTO
ChangeNumberCall func() (int64, error)
FetchManyCall func(splitNames []string) map[string]*dtos.SplitDTO
KillLocallyCall func(splitName string, defaultTreatment string, changeNumber int64)
PutManyCall func(splits []dtos.SplitDTO, changeNumber int64)
RemoveCall func(splitName string)
SegmentNamesCall func() *set.ThreadUnsafeSet
SetChangeNumberCall func(changeNumber int64) error
SplitCall func(splitName string) *dtos.SplitDTO
SplitNamesCall func() []string
TrafficTypeExistsCall func(trafficType string) bool
}
// All mock
func (m MockSplitStorage) All() []dtos.SplitDTO {
return m.AllCall()
}
// ChangeNumber mock
func (m MockSplitStorage) ChangeNumber() (int64, error) {
return m.ChangeNumberCall()
}
// FetchMany mock
func (m MockSplitStorage) FetchMany(splitNames []string) map[string]*dtos.SplitDTO {
return m.FetchManyCall(splitNames)
}
// KillLocally mock
func (m MockSplitStorage) KillLocally(splitName string, defaultTreatment string, changeNumber int64) {
m.KillLocallyCall(splitName, defaultTreatment, changeNumber)
}
// PutMany mock
func (m MockSplitStorage) PutMany(splits []dtos.SplitDTO, changeNumber int64) {
m.PutManyCall(splits, changeNumber)
}
// Remove mock
func (m MockSplitStorage) Remove(splitname string) {
m.RemoveCall(splitname)
}
// SegmentNames mock
func (m MockSplitStorage) SegmentNames() *set.ThreadUnsafeSet {
return m.SegmentNamesCall()
}
// SetChangeNumber mock
func (m MockSplitStorage) SetChangeNumber(changeNumber int64) error {
return m.SetChangeNumberCall(changeNumber)
}
// Split mock
func (m MockSplitStorage) Split(splitName string) *dtos.SplitDTO {
return m.SplitCall(splitName)
}
// SplitNames mock
func (m MockSplitStorage) SplitNames() []string {
return m.SplitNamesCall()
}
// TrafficTypeExists mock
func (m MockSplitStorage) TrafficTypeExists(trafficType string) bool {
return m.TrafficTypeExistsCall(trafficType)
}

149
vendor/github.com/splitio/go-split-commons/v2/storage/mutexmap/metrics.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,149 @@
package mutexmap
import (
"sync"
"github.com/splitio/go-split-commons/v2/dtos"
)
// MMMetricsStorage contains an in-memory implementation of Metrics storage
type MMMetricsStorage struct {
gaugeData map[string]float64
gaugeMutex *sync.Mutex
counterData map[string]int64
countersMutex *sync.RWMutex
latenciesData map[string][]int64
latenciesMutex *sync.RWMutex
}
// NewMMMetricsStorage instantiates a new MMMetricsStorage
func NewMMMetricsStorage() *MMMetricsStorage {
return &MMMetricsStorage{
counterData: make(map[string]int64),
countersMutex: &sync.RWMutex{},
gaugeData: make(map[string]float64),
gaugeMutex: &sync.Mutex{},
latenciesData: make(map[string][]int64),
latenciesMutex: &sync.RWMutex{},
}
}
// PutGauge stores a new gauge value for a specific key
func (m *MMMetricsStorage) PutGauge(key string, gauge float64) {
m.gaugeMutex.Lock()
defer m.gaugeMutex.Unlock()
m.gaugeData[key] = gauge
}
// PopGauges returns and deletes all gauges currently stored
func (m *MMMetricsStorage) PopGauges() []dtos.GaugeDTO {
m.gaugeMutex.Lock()
defer func() {
m.gaugeData = make(map[string]float64)
m.gaugeMutex.Unlock()
}()
gauges := make([]dtos.GaugeDTO, 0)
for key, gauge := range m.gaugeData {
gauges = append(gauges, dtos.GaugeDTO{
MetricName: key,
Gauge: gauge,
})
}
return gauges
}
// IncCounter increments the counter for a specific key. It initializes it in 1 if it doesn't exist when this function
// is called.
func (m *MMMetricsStorage) IncCounter(key string) {
m.countersMutex.Lock()
defer m.countersMutex.Unlock()
_, exists := m.counterData[key]
if !exists {
m.counterData[key] = 1
} else {
m.counterData[key]++
}
}
// PopCounters returns and deletes all the counters stored
func (m *MMMetricsStorage) PopCounters() []dtos.CounterDTO {
m.countersMutex.Lock()
defer func() {
m.counterData = make(map[string]int64)
m.countersMutex.Unlock()
}()
counters := make([]dtos.CounterDTO, 0)
for key, counter := range m.counterData {
counters = append(counters, dtos.CounterDTO{
MetricName: key,
Count: counter,
})
}
return counters
}
// PeekCounters returns Counters
func (m *MMMetricsStorage) PeekCounters() map[string]int64 {
m.countersMutex.RLock()
defer m.countersMutex.RUnlock()
return m.counterData
}
// PeekLatencies returns Latencies
func (m *MMMetricsStorage) PeekLatencies() map[string][]int64 {
m.latenciesMutex.RLock()
defer m.latenciesMutex.RUnlock()
return m.latenciesData
}
// IncLatency increments the latency for a specific key and bucket. If the key doesn't exist it's initialized to
// an empty array of 23 items.
func (m *MMMetricsStorage) IncLatency(metricName string, index int) {
if index < 0 || index > 22 {
return
}
m.latenciesMutex.Lock()
defer m.latenciesMutex.Unlock()
_, exists := m.latenciesData[metricName]
if !exists {
m.latenciesData[metricName] = make([]int64, 23)
m.latenciesData[metricName][index] = 1
} else {
m.latenciesData[metricName][index]++
}
}
// PopLatencies Returns and delete all the latencies currently stored
func (m *MMMetricsStorage) PopLatencies() []dtos.LatenciesDTO {
m.latenciesMutex.Lock()
defer func() {
m.latenciesData = make(map[string][]int64)
m.latenciesMutex.Unlock()
}()
latencies := make([]dtos.LatenciesDTO, 0)
for key, latency := range m.latenciesData {
latencies = append(latencies, dtos.LatenciesDTO{
Latencies: latency,
MetricName: key,
})
}
return latencies
}
// PopGaugesWithMetadata mock
func (m *MMMetricsStorage) PopGaugesWithMetadata() (*dtos.GaugeDataBulk, error) {
panic("Not implemented for inmemory")
}
// PopLatenciesWithMetadata mock
func (m *MMMetricsStorage) PopLatenciesWithMetadata() (*dtos.LatencyDataBulk, error) {
panic("Not implemented for inmemory")
}
// PopCountersWithMetadata mock
func (m *MMMetricsStorage) PopCountersWithMetadata() (*dtos.CounterDataBulk, error) {
panic("Not implemented for inmemory")
}

88
vendor/github.com/splitio/go-split-commons/v2/storage/mutexmap/segments.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,88 @@
package mutexmap
import (
"fmt"
"sync"
"github.com/splitio/go-toolkit/v3/datastructures/set"
)
// MMSegmentStorage contains is an in-memory implementation of segment storage
type MMSegmentStorage struct {
data map[string]*set.ThreadUnsafeSet
till map[string]int64
mutex *sync.RWMutex
tillMutex *sync.RWMutex
}
// NewMMSegmentStorage instantiates a new MMSegmentStorage
func NewMMSegmentStorage() *MMSegmentStorage {
return &MMSegmentStorage{
data: make(map[string]*set.ThreadUnsafeSet),
till: make(map[string]int64),
mutex: &sync.RWMutex{},
tillMutex: &sync.RWMutex{},
}
}
// ChangeNumber returns the latest timestamp the segment was fetched
func (m *MMSegmentStorage) ChangeNumber(segmentName string) (int64, error) {
m.tillMutex.RLock()
defer m.tillMutex.RUnlock()
return m.till[segmentName], nil
}
// Keys retrieves a segment from the in-memory storage
// NOTE: A pointer TO A COPY is returned, in order to avoid race conditions between
// evaluations and sdk <-> backend sync
func (m *MMSegmentStorage) Keys(segmentName string) *set.ThreadUnsafeSet {
// @TODO replace to IsInSegment
m.mutex.RLock()
defer m.mutex.RUnlock()
item, exists := m.data[segmentName]
if !exists {
return nil
}
s := item.Copy().(*set.ThreadUnsafeSet)
return s
}
// SegmentContainsKey returns true if the segment contains a specific key
func (m *MMSegmentStorage) SegmentContainsKey(segmentName string, key string) (bool, error) {
m.mutex.RLock()
defer m.mutex.RUnlock()
item, exists := m.data[segmentName]
if !exists {
return false, fmt.Errorf("segment %s not found in storage", segmentName)
}
return item.Has(key), nil
}
// SetChangeNumber sets the till value belong to segmentName
func (m *MMSegmentStorage) SetChangeNumber(name string, till int64) error {
m.tillMutex.Lock()
defer m.tillMutex.Unlock()
m.till[name] = till
return nil
}
// Update adds a new segment to the in-memory storage
func (m *MMSegmentStorage) Update(name string, toAdd *set.ThreadUnsafeSet, toRemove *set.ThreadUnsafeSet, till int64) error {
m.mutex.Lock()
defer m.mutex.Unlock()
_, ok := m.data[name]
if !ok {
m.data[name] = set.NewSet()
}
if !toRemove.IsEmpty() {
m.data[name].Remove(toRemove.List()...)
}
if !toAdd.IsEmpty() {
m.data[name].Add(toAdd.List()...)
}
m.SetChangeNumber(name, till)
return nil
}
// CountRemovedKeys method
func (m *MMSegmentStorage) CountRemovedKeys(segmentName string) int64 { return 0 }

194
vendor/github.com/splitio/go-split-commons/v2/storage/mutexmap/splits.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,194 @@
package mutexmap
import (
"sync"
"github.com/splitio/go-split-commons/v2/dtos"
"github.com/splitio/go-toolkit/v3/datastructures/set"
)
// MMSplitStorage struct contains is an in-memory implementation of split storage
type MMSplitStorage struct {
data map[string]dtos.SplitDTO
trafficTypes map[string]int64
till int64
mutex *sync.RWMutex
ttMutex *sync.RWMutex
tillMutex *sync.RWMutex
}
// NewMMSplitStorage instantiates a new MMSplitStorage
func NewMMSplitStorage() *MMSplitStorage {
return &MMSplitStorage{
data: make(map[string]dtos.SplitDTO),
trafficTypes: make(map[string]int64),
till: 0,
mutex: &sync.RWMutex{},
ttMutex: &sync.RWMutex{},
tillMutex: &sync.RWMutex{},
}
}
// All returns a list with a copy of each split.
// NOTE: This method will block any further operations regarding splits. Use with caution
func (m *MMSplitStorage) All() []dtos.SplitDTO {
m.mutex.RLock()
defer m.mutex.RUnlock()
splitList := make([]dtos.SplitDTO, 0)
for _, split := range m.data {
splitList = append(splitList, split)
}
return splitList
}
// ChangeNumber returns the last timestamp the split was fetched
func (m *MMSplitStorage) ChangeNumber() (int64, error) {
m.tillMutex.RLock()
defer m.tillMutex.RUnlock()
return m.till, nil
}
func (m *MMSplitStorage) _get(splitName string) *dtos.SplitDTO {
item, exists := m.data[splitName]
if !exists {
return nil
}
return &item
}
// FetchMany fetches features in redis and returns an array of split dtos
func (m *MMSplitStorage) FetchMany(splitNames []string) map[string]*dtos.SplitDTO {
m.mutex.RLock()
defer m.mutex.RUnlock()
splits := make(map[string]*dtos.SplitDTO)
for _, splitName := range splitNames {
splits[splitName] = m._get(splitName)
}
return splits
}
// KillLocally kills the split locally
func (m *MMSplitStorage) KillLocally(splitName string, defaultTreatment string, changeNumber int64) {
m.mutex.Lock()
defer m.mutex.Unlock()
split := m._get(splitName)
till, err := m.ChangeNumber()
if err != nil {
return
}
if split != nil && till < changeNumber {
split.DefaultTreatment = defaultTreatment
split.Killed = true
split.ChangeNumber = changeNumber
m.data[split.Name] = *split
}
}
// increaseTrafficTypeCount increases value for a traffic type
func (m *MMSplitStorage) increaseTrafficTypeCount(trafficType string) {
m.ttMutex.Lock()
defer m.ttMutex.Unlock()
_, exists := m.trafficTypes[trafficType]
if !exists {
m.trafficTypes[trafficType] = 1
} else {
m.trafficTypes[trafficType]++
}
}
// decreaseTrafficTypeCount decreases value for a traffic type
func (m *MMSplitStorage) decreaseTrafficTypeCount(trafficType string) {
m.ttMutex.Lock()
defer m.ttMutex.Unlock()
value, exists := m.trafficTypes[trafficType]
if exists {
if value > 0 {
m.trafficTypes[trafficType]--
} else {
delete(m.trafficTypes, trafficType)
}
}
}
// PutMany bulk inserts splits into the in-memory storage
func (m *MMSplitStorage) PutMany(splits []dtos.SplitDTO, till int64) {
m.mutex.Lock()
defer m.mutex.Unlock()
for _, split := range splits {
existing, thisIsAnUpdate := m.data[split.Name]
if thisIsAnUpdate {
// If it's an update, we decrement the traffic type count of the existing split,
// and then add the updated one (as part of the normal flow), in case it's different.
m.decreaseTrafficTypeCount(existing.TrafficTypeName)
}
m.data[split.Name] = split
m.increaseTrafficTypeCount(split.TrafficTypeName)
}
m.SetChangeNumber(till)
}
// Remove deletes a split from the in-memory storage
func (m *MMSplitStorage) Remove(splitName string) {
m.mutex.Lock()
defer m.mutex.Unlock()
split, exists := m.data[splitName]
if exists {
delete(m.data, splitName)
m.decreaseTrafficTypeCount(split.TrafficTypeName)
}
}
// SegmentNames returns a slice with the names of all segments referenced in splits
func (m *MMSplitStorage) SegmentNames() *set.ThreadUnsafeSet {
segments := set.NewSet()
m.mutex.RLock()
defer m.mutex.RUnlock()
for _, split := range m.data {
for _, condition := range split.Conditions {
for _, matcher := range condition.MatcherGroup.Matchers {
if matcher.UserDefinedSegment != nil {
segments.Add(matcher.UserDefinedSegment.SegmentName)
}
}
}
}
return segments
}
// SetChangeNumber sets the till value belong to split
func (m *MMSplitStorage) SetChangeNumber(till int64) error {
m.tillMutex.Lock()
defer m.tillMutex.Unlock()
m.till = till
return nil
}
// Split retrieves a split from the MMSplitStorage
// NOTE: A pointer TO A COPY is returned, in order to avoid race conditions between
// evaluations and sdk <-> backend sync
func (m *MMSplitStorage) Split(splitName string) *dtos.SplitDTO {
m.mutex.RLock()
defer m.mutex.RUnlock()
return m._get(splitName)
}
// SplitNames returns a slice with the names of all the current splits
func (m *MMSplitStorage) SplitNames() []string {
m.mutex.RLock()
defer m.mutex.RUnlock()
splitNames := make([]string, 0)
for key := range m.data {
splitNames = append(splitNames, key)
}
return splitNames
}
// TrafficTypeExists returns true or false depending on existence and counter
// of trafficType
func (m *MMSplitStorage) TrafficTypeExists(trafficType string) bool {
m.ttMutex.RLock()
defer m.ttMutex.RUnlock()
value, exists := m.trafficTypes[trafficType]
return exists && value > 0
}

6
vendor/github.com/splitio/go-split-commons/v2/storage/mutexqueue/constants.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,6 @@
package mutexqueue
import "errors"
// ErrorMaxSizeReached queue max size error
var ErrorMaxSizeReached = errors.New("Queue max size has been reached")

136
vendor/github.com/splitio/go-split-commons/v2/storage/mutexqueue/events.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,136 @@
package mutexqueue
import (
"container/list"
"fmt"
"sync"
"github.com/splitio/go-split-commons/v2/dtos"
"github.com/splitio/go-toolkit/v3/logging"
)
// MaxAccumulatedBytes is the maximum size to accumulate in events before flush (in bytes)
const MaxAccumulatedBytes = 5 * 1024 * 1024
// NewMQEventsStorage returns an instance of MQEventsStorage
func NewMQEventsStorage(queueSize int, isFull chan string, logger logging.LoggerInterface) *MQEventsStorage {
return &MQEventsStorage{
queue: list.New(),
size: queueSize,
mutexQueue: &sync.Mutex{},
fullChan: isFull,
logger: logger,
}
}
type eventWrapper struct {
event dtos.EventDTO
size int
}
// MQEventsStorage in memory events storage
type MQEventsStorage struct {
queue *list.List
size int
accumulatedBytes int
mutexQueue *sync.Mutex
fullChan chan string //only write channel
logger logging.LoggerInterface
}
func (s *MQEventsStorage) sendSignalIsFull() {
// Nom blocking select
select {
case s.fullChan <- "EVENTS_FULL":
// Send "queue is full" signal
break
default:
s.logger.Debug("Some error occurred on sending signal for events")
}
}
// Push an event into slice
func (s *MQEventsStorage) Push(event dtos.EventDTO, size int) error {
s.mutexQueue.Lock()
defer s.mutexQueue.Unlock()
if s.queue.Len()+1 > s.size {
s.sendSignalIsFull()
return ErrorMaxSizeReached
}
// Add element
s.queue.PushBack(eventWrapper{event: event, size: size})
s.accumulatedBytes += size
if s.queue.Len() == s.size || s.accumulatedBytes >= MaxAccumulatedBytes {
s.sendSignalIsFull()
}
return nil
}
// PopN pop N elements from queue
func (s *MQEventsStorage) PopN(n int64) ([]dtos.EventDTO, error) {
var toReturn []dtos.EventDTO
var totalItems int
// Mutexing queue
s.mutexQueue.Lock()
defer s.mutexQueue.Unlock()
if int64(s.queue.Len()) >= n {
totalItems = int(n)
} else {
totalItems = s.queue.Len()
}
toReturn = make([]dtos.EventDTO, 0)
accumulated := 0
errorCount := 0
for i := 0; i < totalItems; i++ {
bundled, ok := s.queue.Remove(s.queue.Front()).(eventWrapper)
if !ok {
errorCount++
continue
}
toReturn = append(toReturn, bundled.event)
accumulated += bundled.size
if accumulated >= MaxAccumulatedBytes {
// If we reached the maximum allowed size, break the loop so that we don't sent huge POST bodies to the BE
break
}
}
s.accumulatedBytes -= accumulated
if errorCount > 0 {
return toReturn, fmt.Errorf("%d elements could not be decoded", errorCount)
}
return toReturn, nil
}
// PopNWithMetadata pop N elements from queue
func (s *MQEventsStorage) PopNWithMetadata(n int64) ([]dtos.QueueStoredEventDTO, error) {
panic("Not implemented for inmemory")
}
// Empty returns if slice len if zero
func (s *MQEventsStorage) Empty() bool {
s.mutexQueue.Lock()
defer s.mutexQueue.Unlock()
return s.queue.Len() == 0
}
// Count returns the number of events into slice
func (s *MQEventsStorage) Count() int64 {
s.mutexQueue.Lock()
defer s.mutexQueue.Unlock()
return int64(s.queue.Len())
}
// Drop drops
func (s *MQEventsStorage) Drop(size *int64) error {
panic("Not implemented for inmemory")
}

108
vendor/github.com/splitio/go-split-commons/v2/storage/mutexqueue/impressions.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,108 @@
package mutexqueue
import (
"container/list"
"sync"
"github.com/splitio/go-split-commons/v2/dtos"
"github.com/splitio/go-toolkit/v3/logging"
)
// NewMQImpressionsStorage returns an instance of MQEventsStorage
func NewMQImpressionsStorage(queueSize int, isFull chan<- string, logger logging.LoggerInterface) *MQImpressionsStorage {
return &MQImpressionsStorage{
queue: list.New(),
size: queueSize,
mutexQueue: &sync.Mutex{},
fullChan: isFull,
logger: logger,
}
}
// MQImpressionsStorage in memory events storage
type MQImpressionsStorage struct {
queue *list.List
size int
mutexQueue *sync.Mutex
fullChan chan<- string //only write channel
logger logging.LoggerInterface
}
func (s *MQImpressionsStorage) sendSignalIsFull() {
// Nom blocking select
select {
case s.fullChan <- "IMPRESSIONS_FULL":
// Send "queue is full" signal
break
default:
s.logger.Debug("Some error occurred on sending signal for impressions")
break
}
}
// Empty returns if slice len if zero
func (s *MQImpressionsStorage) Empty() bool {
s.mutexQueue.Lock()
defer s.mutexQueue.Unlock()
return s.queue.Len() == 0
}
// Count returns len
func (s *MQImpressionsStorage) Count() int64 {
s.mutexQueue.Lock()
defer s.mutexQueue.Unlock()
return int64(s.queue.Len())
}
// LogImpressions inserts impressions into the queue
func (s *MQImpressionsStorage) LogImpressions(impressions []dtos.Impression) error {
s.mutexQueue.Lock()
defer s.mutexQueue.Unlock()
for _, impression := range impressions {
if s.queue.Len()+1 > s.size {
s.sendSignalIsFull()
return ErrorMaxSizeReached
}
// Add element
s.queue.PushBack(impression)
if s.queue.Len() == s.size {
s.sendSignalIsFull()
}
}
return nil
}
// PopN pop N elements from queue
func (s *MQImpressionsStorage) PopN(n int64) ([]dtos.Impression, error) {
var toReturn []dtos.Impression
var totalItems int
// Mutexing queue
s.mutexQueue.Lock()
defer s.mutexQueue.Unlock()
if int64(s.queue.Len()) >= n {
totalItems = int(n)
} else {
totalItems = s.queue.Len()
}
toReturn = make([]dtos.Impression, totalItems)
for i := 0; i < totalItems; i++ {
toReturn[i] = s.queue.Remove(s.queue.Front()).(dtos.Impression)
}
return toReturn, nil
}
// PopNWithMetadata pop N elements from queue
func (s *MQImpressionsStorage) PopNWithMetadata(n int64) ([]dtos.ImpressionQueueObject, error) {
panic("Not implemented for inmemory")
}
// Drop drops
func (s *MQImpressionsStorage) Drop(size *int64) error {
panic("Not implemented for inmemory")
}

17
vendor/github.com/splitio/go-split-commons/v2/storage/redis/constants.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,17 @@
package redis
const (
redisSplit = "SPLITIO.split.{split}" // split object
redisSplitTill = "SPLITIO.splits.till" // last split fetch
redisSegment = "SPLITIO.segment.{segment}" // segment object
redisSegmentTill = "SPLITIO.segment.{segment}.till" // last segment fetch
redisImpressions = "SPLITIO/{sdkVersion}/{instanceId}/impressions.{feature}" // impressions for a feature
redisLatency = "SPLITIO/{sdkVersion}/{instanceId}/latency.{metric}.bucket.{bucket}" // latency bucket
redisCounter = "SPLITIO/{sdkVersion}/{instanceId}/count.{metric}" // counter
redisGauge = "SPLITIO/{sdkVersion}/{instanceId}/gauge.{metric}" // gauge
redisEvents = "SPLITIO.events" // events LIST key
redisImpressionsQueue = "SPLITIO.impressions" // impressions LIST key
redisImpressionsTTL = 60 // impressions default TTL
redisTrafficType = "SPLITIO.trafficType.{trafficType}" // traffic Type fetch
redisHash = "SPLITIO.hash"
)

177
vendor/github.com/splitio/go-split-commons/v2/storage/redis/events.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,177 @@
package redis
import (
"encoding/json"
"math"
"sync"
"github.com/splitio/go-split-commons/v2/dtos"
"github.com/splitio/go-toolkit/v3/logging"
"github.com/splitio/go-toolkit/v3/queuecache"
"github.com/splitio/go-toolkit/v3/redis"
)
// EventsStorage redis implementation of EventsStorage interface
type EventsStorage struct {
cache queuecache.InMemoryQueueCacheOverlay
client *redis.PrefixedRedisClient
logger logging.LoggerInterface
metadata dtos.Metadata
redisKey string
refillMutex *sync.RWMutex
mutex *sync.RWMutex
}
// maxAccumulatedSize is the maximum number of bytes to be fetched from cache before posting to the backend
const maxAccumulatedSize = 5 * 1024 * 1024
// maxEventSize is the maximum allowed event size
const maxEventSize = 32 * 1024
// NewEventStorageConsumer storage for consumer
func NewEventStorageConsumer(redisClient *redis.PrefixedRedisClient, metadata dtos.Metadata, logger logging.LoggerInterface) *EventsStorage {
return &EventsStorage{
cache: queuecache.InMemoryQueueCacheOverlay{},
client: redisClient,
logger: logger,
metadata: metadata,
redisKey: redisEvents,
refillMutex: &sync.RWMutex{},
mutex: &sync.RWMutex{},
}
}
// NewEventsStorage returns an instance of RedisEventsStorage
func NewEventsStorage(redisClient *redis.PrefixedRedisClient, metadata dtos.Metadata, logger logging.LoggerInterface) *EventsStorage {
refillMutex := &sync.RWMutex{}
refillFunc := func(count int) ([]interface{}, error) {
refillMutex.Lock()
defer refillMutex.Unlock()
lrange, err := redisClient.LRange(redisEvents, 0, int64(count-1))
if err != nil {
logger.Error("Fetching events", err)
return nil, err
}
totalFetchedEvents := len(lrange)
idxFrom := count
if totalFetchedEvents < count {
idxFrom = totalFetchedEvents
}
err = redisClient.LTrim(redisEvents, int64(idxFrom), -1)
if err != nil {
logger.Error("Trim events", err)
return nil, err
}
toReturn := make([]interface{}, len(lrange))
for index, item := range lrange {
toReturn[index] = item
}
return toReturn, nil
}
return &EventsStorage{
cache: *queuecache.New(10000, refillFunc),
client: redisClient,
logger: logger,
metadata: metadata,
redisKey: redisEvents,
refillMutex: refillMutex,
mutex: &sync.RWMutex{},
}
}
// Push events into Redis LIST data type with RPUSH command
func (r *EventsStorage) Push(event dtos.EventDTO, _ int) error {
var queueMessage = dtos.QueueStoredEventDTO{Metadata: r.metadata, Event: event}
eventJSON, err := json.Marshal(queueMessage)
if err != nil {
r.logger.Error("Something were wrong marshaling provided event to JSON", err.Error())
return err
}
r.logger.Debug("Pushing events to:", r.redisKey, string(eventJSON))
_, errPush := r.client.RPush(r.redisKey, eventJSON)
if errPush != nil {
r.logger.Error("Something were wrong pushing event to redis", errPush)
return errPush
}
return nil
}
// PopN return N elements from 0 to N
func (r *EventsStorage) PopN(n int64) ([]dtos.EventDTO, error) {
panic("Not implemented for redis")
}
// PopNWithMetadata pop N elements from queue
func (r *EventsStorage) PopNWithMetadata(n int64) ([]dtos.QueueStoredEventDTO, error) {
r.mutex.Lock()
defer r.mutex.Unlock()
toReturn := make([]dtos.QueueStoredEventDTO, n)
var err error
fetchedCount := 0
accumulatedSize := 0
writeIndex := 0
for r.Count() > 0 && int64(fetchedCount) < n && accumulatedSize+maxEventSize < maxAccumulatedSize && err == nil {
numberOfItemsToFetch := int(math.Min(
float64((maxAccumulatedSize-accumulatedSize)/maxEventSize),
float64(n-int64(fetchedCount)),
))
elems, err := r.cache.Fetch(numberOfItemsToFetch)
if err != nil {
r.logger.Error("Error fetching events", err.Error())
break
}
for _, elem := range elems {
asStr, ok := elem.(string)
if !ok {
r.logger.Error("Error type-asserting event as string", err.Error())
continue
}
storedEventDTO := dtos.QueueStoredEventDTO{}
err = json.Unmarshal([]byte(asStr), &storedEventDTO)
if err != nil {
r.logger.Error("Error decoding event JSON", err.Error())
continue
}
accumulatedSize += storedEventDTO.Event.Size()
toReturn[writeIndex] = storedEventDTO
writeIndex++
}
fetchedCount += len(elems)
}
return toReturn[0:writeIndex], nil
}
// Count returns the number of items in the redis list
func (r *EventsStorage) Count() int64 {
val, err := r.client.LLen(r.redisKey)
if err != nil {
return 0
}
return val
}
// Empty returns true if redis list is zero length
func (r *EventsStorage) Empty() bool {
return r.Count() == 0
}
// Drop drops events from queue
func (r *EventsStorage) Drop(size *int64) error {
r.mutex.Lock()
defer r.mutex.Unlock()
if size == nil {
_, err := r.client.Del(r.redisKey)
return err
}
return r.client.LTrim(r.redisKey, *size, -1)
}

149
vendor/github.com/splitio/go-split-commons/v2/storage/redis/impressions.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,149 @@
package redis
import (
"encoding/json"
"sync"
"time"
"github.com/splitio/go-split-commons/v2/dtos"
"github.com/splitio/go-toolkit/v3/logging"
"github.com/splitio/go-toolkit/v3/redis"
)
const impressionsTTLRefresh = time.Duration(3600) * time.Second
// ImpressionStorage is a redis-based implementation of split storage
type ImpressionStorage struct {
client *redis.PrefixedRedisClient
mutex *sync.Mutex
logger logging.LoggerInterface
redisKey string
impressionsTTL time.Duration
metadata dtos.Metadata
}
// NewImpressionStorage creates a new RedisSplitStorage and returns a reference to it
func NewImpressionStorage(client *redis.PrefixedRedisClient, metadata dtos.Metadata, logger logging.LoggerInterface) *ImpressionStorage {
return &ImpressionStorage{
client: client,
mutex: &sync.Mutex{},
logger: logger,
redisKey: redisImpressionsQueue,
impressionsTTL: redisImpressionsTTL,
metadata: metadata,
}
}
// Count returns the size of the impressions queue
func (r *ImpressionStorage) Count() int64 {
val, err := r.client.LLen(r.redisKey)
if err != nil {
return 0
}
return val
}
// Drop drops impressions from queue
func (r *ImpressionStorage) Drop(size *int64) error {
r.mutex.Lock()
defer r.mutex.Unlock()
if size == nil {
_, err := r.client.Del(r.redisKey)
return err
}
return r.client.LTrim(r.redisKey, *size, -1)
}
// Empty returns true if redis list is zero length
func (r *ImpressionStorage) Empty() bool {
return r.Count() == 0
}
// push stores impressions in redis
func (r *ImpressionStorage) push(impressions []dtos.ImpressionQueueObject) error {
var impressionsJSON []interface{}
for _, impression := range impressions {
iJSON, err := json.Marshal(impression)
if err != nil {
r.logger.Error("Error encoding impression in json")
r.logger.Error(err)
} else {
impressionsJSON = append(impressionsJSON, iJSON)
}
}
r.logger.Debug("Pushing impressions to: ", r.redisKey, len(impressionsJSON))
inserted, errPush := r.client.RPush(r.redisKey, impressionsJSON...)
if errPush != nil {
r.logger.Error("Something were wrong pushing impressions to redis", errPush)
return errPush
}
// Checks if expiration needs to be set
if inserted == int64(len(impressionsJSON)) {
r.logger.Debug("Proceeding to set expiration for: ", r.redisKey)
result := r.client.Expire(r.redisKey, time.Duration(r.impressionsTTL)*time.Minute)
if result == false {
r.logger.Error("Something were wrong setting expiration", errPush)
}
}
return nil
}
// LogImpressions stores impressions in redis as Queue
func (r *ImpressionStorage) LogImpressions(impressions []dtos.Impression) error {
var impressionsToStore []dtos.ImpressionQueueObject
for _, i := range impressions {
var impression = dtos.ImpressionQueueObject{Metadata: r.metadata, Impression: i}
impressionsToStore = append(impressionsToStore, impression)
}
if len(impressionsToStore) > 0 {
return r.push(impressionsToStore)
}
return nil
}
// PopN return N elements from 0 to N
func (r *ImpressionStorage) PopN(n int64) ([]dtos.Impression, error) {
panic("Not implemented for redis")
}
// PopNWithMetadata pop N elements from queue
func (r *ImpressionStorage) PopNWithMetadata(n int64) ([]dtos.ImpressionQueueObject, error) {
r.mutex.Lock()
defer r.mutex.Unlock()
toReturn := make([]dtos.ImpressionQueueObject, 0, n)
lrange, err := r.client.LRange(r.redisKey, 0, n-1)
if err != nil {
r.logger.Error("Error fetching impressions")
return toReturn, err
}
fetchedCount := int64(len(lrange))
err = r.client.LTrim(r.redisKey, fetchedCount, int64(-1))
if err != nil {
r.logger.Error("Error trimming impressions")
return toReturn, err
}
// This operation will simply do nothing if the key no longer exists (queue is empty)
// It's only done in the "successful" exit path so that the TTL is not overriden if impressons weren't
// popped correctly. This will result in impressions getting lost but will prevent the queue from taking
// a huge amount of memory.
r.client.Expire(r.redisKey, impressionsTTLRefresh)
for _, asStr := range lrange {
storedImpressionDTO := dtos.ImpressionQueueObject{}
err = json.Unmarshal([]byte(asStr), &storedImpressionDTO)
if err != nil {
r.logger.Error("Error decoding event JSON", err.Error())
continue
}
toReturn = append(toReturn, storedImpressionDTO)
}
return toReturn, nil
}

322
vendor/github.com/splitio/go-split-commons/v2/storage/redis/metrics.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,322 @@
package redis
import (
"fmt"
"regexp"
"strconv"
"strings"
"sync"
"github.com/splitio/go-split-commons/v2/dtos"
"github.com/splitio/go-toolkit/v3/logging"
"github.com/splitio/go-toolkit/v3/redis"
)
// MetricsStorage is a redis-based implementation of split storage
type MetricsStorage struct {
client redis.PrefixedRedisClient
logger logging.LoggerInterface
gaugeSingleTemplate string
countersSingleTemplate string
latenciesSingleTemplate string
gaugeMultiTemplate string
countersMultiTemplate string
latenciesMultiTemplate string
mutex *sync.RWMutex
}
// NewMetricsStorage creates a new RedisSplitStorage and returns a reference to it
func NewMetricsStorage(redisClient *redis.PrefixedRedisClient, metadata dtos.Metadata, logger logging.LoggerInterface) *MetricsStorage {
// @Todo Split Storages between Go-Client and Redis
gaugeSingleTemplate := strings.Replace(redisGauge, "{sdkVersion}", metadata.SDKVersion, 1)
gaugeSingleTemplate = strings.Replace(gaugeSingleTemplate, "{instanceId}", metadata.MachineName, 1)
countersSingleTemplate := strings.Replace(redisCounter, "{sdkVersion}", metadata.SDKVersion, 1)
countersSingleTemplate = strings.Replace(countersSingleTemplate, "{instanceId}", metadata.MachineName, 1)
latenciesSingleTemplate := strings.Replace(redisLatency, "{sdkVersion}", metadata.SDKVersion, 1)
latenciesSingleTemplate = strings.Replace(latenciesSingleTemplate, "{instanceId}", metadata.MachineName, 1)
gaugeMultiTemplate := strings.Replace(redisGauge, "{sdkVersion}", "*", 1)
gaugeMultiTemplate = strings.Replace(gaugeMultiTemplate, "{instanceId}", "*", 1)
gaugeMultiTemplate = strings.Replace(gaugeMultiTemplate, "{metric}", "*", 1)
countersMultiTemplate := strings.Replace(redisCounter, "{sdkVersion}", "*", 1)
countersMultiTemplate = strings.Replace(countersMultiTemplate, "{instanceId}", "*", 1)
countersMultiTemplate = strings.Replace(countersMultiTemplate, "{metric}", "*", 1)
latenciesMultiTemplate := strings.Replace(redisLatency, "{sdkVersion}", "*", 1)
latenciesMultiTemplate = strings.Replace(latenciesMultiTemplate, "{instanceId}", "*", 1)
latenciesMultiTemplate = strings.Replace(latenciesMultiTemplate, "{metric}", "*", 1)
latenciesMultiTemplate = strings.Replace(latenciesMultiTemplate, "{bucket}", "*", 1)
return &MetricsStorage{
client: *redisClient,
logger: logger,
gaugeSingleTemplate: gaugeSingleTemplate,
countersSingleTemplate: countersSingleTemplate,
latenciesSingleTemplate: latenciesSingleTemplate,
gaugeMultiTemplate: gaugeMultiTemplate,
countersMultiTemplate: countersMultiTemplate,
latenciesMultiTemplate: latenciesMultiTemplate,
mutex: &sync.RWMutex{},
}
}
// IncCounter incraeses the count for a specific metric
func (r *MetricsStorage) IncCounter(metric string) {
keyToIncr := strings.Replace(r.countersSingleTemplate, "{metric}", metric, 1)
_, err := r.client.Incr(keyToIncr)
if err != nil {
r.logger.Error(fmt.Sprintf("Error incrementing counterfor metric \"%s\" in redis: %s", metric, err.Error()))
}
}
// IncLatency incraeses the latency of a bucket for a specific metric
func (r *MetricsStorage) IncLatency(metric string, index int) {
keyToIncr := strings.Replace(r.latenciesSingleTemplate, "{metric}", metric, 1)
keyToIncr = strings.Replace(keyToIncr, "{bucket}", strconv.FormatInt(int64(index), 10), 1)
_, err := r.client.Incr(keyToIncr)
if err != nil {
r.logger.Error(fmt.Sprintf(
"Error incrementing latency bucket %d for metric \"%s\" in redis: %s", index, metric, err.Error(),
))
}
}
// PutGauge stores a gauge in redis
func (r *MetricsStorage) PutGauge(key string, gauge float64) {
keyToStore := strings.Replace(r.gaugeSingleTemplate, "{metric}", key, 1)
err := r.client.Set(keyToStore, gauge, 0)
if err != nil {
r.logger.Error(fmt.Sprintf("Error storing gauge \"%s\" in redis: %s\n", key, err))
}
}
// PopCounters some
func (r *MetricsStorage) PopCounters() []dtos.CounterDTO {
panic("Not implemented for redis")
}
// PopGauges some
func (r *MetricsStorage) PopGauges() []dtos.GaugeDTO {
panic("Not implemented for redis")
}
// PopLatencies some
func (r *MetricsStorage) PopLatencies() []dtos.LatenciesDTO {
panic("Not implemented for redis")
}
func (r *MetricsStorage) popByPattern(pattern string, useTransaction bool) (map[string]interface{}, error) {
r.mutex.Lock()
defer r.mutex.Unlock()
keys, err := r.client.Keys(pattern)
if err != nil {
r.logger.Error(err.Error())
return nil, err
}
if len(keys) == 0 {
return map[string]interface{}{}, nil
}
values, err := r.client.MGet(keys)
if err != nil {
r.logger.Error(err.Error())
return nil, err
}
_, err = r.client.Del(keys...)
if err != nil {
// if we failed to delete the keys, log an error and continue working.
r.logger.Error(err.Error())
}
toReturn := make(map[string]interface{})
for index := range keys {
if index >= len(keys) || index >= len(values) {
break
}
toReturn[keys[index]] = values[index]
}
return toReturn, nil
}
func parseIntRedisValue(s interface{}) (int64, error) {
asStr, ok := s.(string)
if !ok {
return 0, fmt.Errorf("%+v is not a string", s)
}
asInt64, err := strconv.ParseInt(asStr, 10, 64)
if err != nil {
return 0, err
}
return asInt64, nil
}
func parseFloatRedisValue(s interface{}) (float64, error) {
asStr, ok := s.(string)
if !ok {
return 0, fmt.Errorf("%+v is not a string", s)
}
asFloat64, err := strconv.ParseFloat(asStr, 64)
if err != nil {
return 0, err
}
return asFloat64, nil
}
func (r *MetricsStorage) parseLatencyKey(key string) (string, string, string, int, error) {
re := regexp.MustCompile(`(\w+.)?SPLITIO\/([^\/]+)\/([^\/]+)\/latency.([^\/]+).bucket.([0-9]*)`)
match := re.FindStringSubmatch(key)
if len(match) < 6 {
return "", "", "", 0, fmt.Errorf("Error parsing key %s", key)
}
sdkNameAndVersion := match[2]
if sdkNameAndVersion == "" {
return "", "", "", 0, fmt.Errorf("Invalid sdk name/version")
}
machineIP := match[3]
if machineIP == "" {
return "", "", "", 0, fmt.Errorf("Invalid machine IP")
}
metricName := match[4]
if metricName == "" {
return "", "", "", 0, fmt.Errorf("Invalid feature name")
}
bucketNumber, err := strconv.Atoi(match[5])
if err != nil {
return "", "", "", 0, fmt.Errorf("Error parsing bucket number: %s", err.Error())
}
r.logger.Verbose("Impression parsed key", match)
return sdkNameAndVersion, machineIP, metricName, bucketNumber, nil
}
func (r *MetricsStorage) parseMetricKey(metricType string, key string) (string, string, string, error) {
var re = regexp.MustCompile(strings.Replace(
`(\w+.)?SPLITIO\/([^\/]+)\/([^\/]+)\/{metricType}.([\s\S]*)`,
"{metricType}",
metricType,
1,
))
match := re.FindStringSubmatch(key)
if len(match) < 5 {
return "", "", "", fmt.Errorf("Error parsing key %s", key)
}
sdkNameAndVersion := match[2]
if sdkNameAndVersion == "" {
return "", "", "", fmt.Errorf("Invalid sdk name/version")
}
machineIP := match[3]
if machineIP == "" {
return "", "", "", fmt.Errorf("Invalid machine IP")
}
metricName := match[4]
if metricName == "" {
return "", "", "", fmt.Errorf("Invalid feature name")
}
r.logger.Verbose("Impression parsed key", match)
return sdkNameAndVersion, machineIP, metricName, nil
}
// PopGaugesWithMetadata returns gauges values saved in Redis by SDKs
func (r *MetricsStorage) PopGaugesWithMetadata() (*dtos.GaugeDataBulk, error) {
data, err := r.popByPattern(r.gaugeMultiTemplate, false)
if err != nil {
r.logger.Error(err.Error())
return nil, err
}
gaugesToReturn := dtos.NewGaugeDataBulk()
for key, value := range data {
sdkNameAndVersion, machineIP, metricName, err := r.parseMetricKey("gauge", key)
if err != nil {
r.logger.Error(fmt.Sprintf("Unable to parse key %s. Skipping", key))
continue
}
asFloat, err := parseFloatRedisValue(value)
if err != nil {
r.logger.Error(fmt.Sprintf("Unable to parse value %+v. Skipping", value))
continue
}
gaugesToReturn.PutGauge(sdkNameAndVersion, machineIP, metricName, asFloat)
}
return gaugesToReturn, nil
}
// PopCountersWithMetadata returns counter values saved in Redis by SDKs
func (r *MetricsStorage) PopCountersWithMetadata() (*dtos.CounterDataBulk, error) {
data, err := r.popByPattern(r.countersMultiTemplate, false)
if err != nil {
r.logger.Error(err.Error())
return nil, err
}
countersToReturn := dtos.NewCounterDataBulk()
for key, value := range data {
sdkNameAndVersion, machineIP, metricName, err := r.parseMetricKey("count", key)
if err != nil {
r.logger.Error("Unable to parse key %s. Skipping", key)
continue
}
asInt, err := parseIntRedisValue(value)
if err != nil {
r.logger.Error(err.Error())
continue
}
countersToReturn.PutCounter(sdkNameAndVersion, machineIP, metricName, asInt)
}
return countersToReturn, nil
}
// PopLatenciesWithMetadata returns latency values saved in Redis by SDKs
func (r *MetricsStorage) PopLatenciesWithMetadata() (*dtos.LatencyDataBulk, error) {
data, err := r.popByPattern(r.latenciesMultiTemplate, false)
if err != nil {
r.logger.Error(err.Error())
return nil, err
}
latenciesToReturn := dtos.NewLatencyDataBulk()
for key, value := range data {
value, err := parseIntRedisValue(value)
if err != nil {
r.logger.Warning(fmt.Sprintf("Unable to parse value of key %s. Skipping", key))
continue
}
sdkNameAndVersion, machineIP, metricName, bucketNumber, err := r.parseLatencyKey(key)
if err != nil {
r.logger.Warning(fmt.Sprintf("Unable to parse key %s. Skipping", key))
continue
}
latenciesToReturn.PutLatency(sdkNameAndVersion, machineIP, metricName, bucketNumber, value)
}
r.logger.Verbose(latenciesToReturn)
return latenciesToReturn, nil
}
// PeekCounters returns Counters
func (r *MetricsStorage) PeekCounters() map[string]int64 {
return make(map[string]int64, 0)
}
// PeekLatencies returns Latencies
func (r *MetricsStorage) PeekLatencies() map[string][]int64 {
return make(map[string][]int64, 0)
}

56
vendor/github.com/splitio/go-split-commons/v2/storage/redis/miscstorage.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,56 @@
package redis
import (
"errors"
"strings"
"github.com/splitio/go-toolkit/v3/logging"
"github.com/splitio/go-toolkit/v3/redis"
)
// ErrorHashNotPresent constant
const ErrorHashNotPresent = "hash-not-present"
const clearAllSCriptTemplate = `
local toDelete = redis.call('KEYS', '{KEY_NAMESPACE}*')
local count = 0
for _, key in ipairs(toDelete) do
redis.call('DEL', key)
count = count + 1
end
return count
`
// MiscStorage provides methods to handle the synchronizer's initialization procedure
type MiscStorage struct {
client *redis.PrefixedRedisClient
logger logging.LoggerInterface
}
// GetApikeyHash gets hashed apikey from redis
func (m *MiscStorage) GetApikeyHash() (string, error) {
res, err := m.client.Get(redisHash)
if err != nil && err.Error() == "redis: nil" {
return "", errors.New(ErrorHashNotPresent)
}
return res, err
}
// SetApikeyHash sets hashed apikey in redis
func (m *MiscStorage) SetApikeyHash(newApikeyHash string) error {
return m.client.Set(redisHash, newApikeyHash, 0)
}
// ClearAll cleans previous used data
func (m *MiscStorage) ClearAll() error {
luaCMD := strings.Replace(clearAllSCriptTemplate, "{KEY_NAMESPACE}", m.client.Prefix, 1)
return m.client.Eval(luaCMD, []string{}, nil)
}
// NewMiscStorage creates a new MiscStorageAdapter and returns a reference to it
func NewMiscStorage(client *redis.PrefixedRedisClient, logger logging.LoggerInterface) *MiscStorage {
return &MiscStorage{
client: client,
logger: logger,
}
}

74
vendor/github.com/splitio/go-split-commons/v2/storage/redis/redis.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,74 @@
package redis
import (
"errors"
"fmt"
"strings"
"time"
"github.com/splitio/go-split-commons/v2/conf"
"github.com/splitio/go-toolkit/v3/logging"
"github.com/splitio/go-toolkit/v3/redis"
"github.com/splitio/go-toolkit/v3/redis/helpers"
)
// NewRedisClient returns a new Prefixed Redis Client
func NewRedisClient(config *conf.RedisConfig, logger logging.LoggerInterface) (*redis.PrefixedRedisClient, error) {
prefix := config.Prefix
if len(config.SentinelAddresses) > 0 && len(config.ClusterNodes) > 0 {
return nil, errors.New("Incompatible configuration of redis, Sentinel and Cluster cannot be enabled at the same time")
}
universalOptions := &redis.UniversalOptions{
Password: config.Password,
DB: config.Database,
TLSConfig: config.TLSConfig,
MaxRetries: config.MaxRetries,
PoolSize: config.PoolSize,
DialTimeout: time.Duration(config.DialTimeout) * time.Second,
ReadTimeout: time.Duration(config.ReadTimeout) * time.Second,
WriteTimeout: time.Duration(config.WriteTimeout) * time.Second,
}
if len(config.SentinelAddresses) > 0 {
logger.Info("To start as Sentinel Mode")
if config.SentinelMaster == "" {
return nil, errors.New("Missing redis sentinel master name")
}
universalOptions.MasterName = config.SentinelMaster
universalOptions.Addrs = config.SentinelAddresses
} else {
if len(config.ClusterNodes) > 0 {
logger.Info("To start as Cluster Mode")
var keyHashTag = "{SPLITIO}"
if config.ClusterKeyHashTag != "" {
keyHashTag = config.ClusterKeyHashTag
if len(keyHashTag) < 3 ||
string(keyHashTag[0]) != "{" ||
string(keyHashTag[len(keyHashTag)-1]) != "}" ||
strings.Count(keyHashTag, "{") != 1 ||
strings.Count(keyHashTag, "}") != 1 {
return nil, errors.New("keyHashTag is not valid")
}
}
prefix = keyHashTag + prefix
universalOptions.Addrs = config.ClusterNodes
} else {
logger.Info("To start as Single Mode")
universalOptions.Addrs = []string{fmt.Sprintf("%s:%d", config.Host, config.Port)}
}
}
rClient, err := redis.NewClient(universalOptions)
if err != nil {
logger.Error(err.Error())
}
helpers.EnsureConnected(rClient)
return redis.NewPrefixedRedisClient(rClient, prefix)
}

100
vendor/github.com/splitio/go-split-commons/v2/storage/redis/segments.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,100 @@
package redis
import (
"fmt"
"strconv"
"strings"
"sync"
"github.com/splitio/go-toolkit/v3/datastructures/set"
"github.com/splitio/go-toolkit/v3/logging"
"github.com/splitio/go-toolkit/v3/redis"
)
// SegmentStorage is a redis implementation of a storage for segments
type SegmentStorage struct {
client redis.PrefixedRedisClient
logger logging.LoggerInterface
mutext *sync.RWMutex
}
// NewSegmentStorage creates a new RedisSegmentStorage and returns a reference to it
func NewSegmentStorage(redisClient *redis.PrefixedRedisClient, logger logging.LoggerInterface) *SegmentStorage {
return &SegmentStorage{
client: *redisClient,
logger: logger,
mutext: &sync.RWMutex{},
}
}
// ChangeNumber returns the changeNumber for a particular segment
func (r *SegmentStorage) ChangeNumber(segmentName string) (int64, error) {
segmentKey := strings.Replace(redisSegmentTill, "{segment}", segmentName, 1)
tillStr, err := r.client.Get(segmentKey)
if err != nil {
return -1, err
}
asInt, err := strconv.ParseInt(tillStr, 10, 64)
if err != nil {
r.logger.Error("Error retrieving till. Returning -1: ", err.Error())
return -1, err
}
return asInt, nil
}
// Keys returns segments keys for segment if it's present
func (r *SegmentStorage) Keys(segmentName string) *set.ThreadUnsafeSet {
keyToFetch := strings.Replace(redisSegment, "{segment}", segmentName, 1)
segmentKeys, err := r.client.SMembers(keyToFetch)
if len(segmentKeys) <= 0 {
r.logger.Debug(fmt.Sprintf("Nonexsitent segment requested: %s", segmentName))
return nil
}
if err != nil {
r.logger.Error(fmt.Sprintf("Error retrieving members from set %s", segmentName))
return nil
}
segment := set.NewSet()
for _, member := range segmentKeys {
segment.Add(member)
}
return segment
}
// SetChangeNumber sets the till value belong to segmentName
func (r *SegmentStorage) SetChangeNumber(segmentName string, changeNumber int64) error {
segmentKey := strings.Replace(redisSegmentTill, "{segment}", segmentName, 1)
return r.client.Set(segmentKey, changeNumber, 0)
}
// Update adds a new segment
func (r *SegmentStorage) Update(name string, toAdd *set.ThreadUnsafeSet, toRemove *set.ThreadUnsafeSet, till int64) error {
r.mutext.Lock()
defer r.mutext.Unlock()
segmentKey := strings.Replace(redisSegment, "{segment}", name, 1)
if !toRemove.IsEmpty() {
_, err := r.client.SRem(segmentKey, toRemove.List()...)
if err != nil {
r.logger.Error(fmt.Sprintf("Error removing keys in redis: %s", err.Error()))
}
}
if !toAdd.IsEmpty() {
_, err := r.client.SAdd(segmentKey, toAdd.List()...)
if err != nil {
r.logger.Error(fmt.Sprintf("Error removing keys in redis: %s", err.Error()))
}
}
r.SetChangeNumber(name, till)
return nil
}
// SegmentContainsKey returns true if the segment contains a specific key
func (r *SegmentStorage) SegmentContainsKey(segmentName string, key string) (bool, error) {
segmentKey := strings.Replace(redisSegment, "{segment}", segmentName, 1)
exists := r.client.SIsMember(segmentKey, key)
return exists, nil
}
// CountRemovedKeys method
func (r *SegmentStorage) CountRemovedKeys(segmentName string) int64 { return 0 }

261
vendor/github.com/splitio/go-split-commons/v2/storage/redis/splits.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,261 @@
package redis
import (
"encoding/json"
"errors"
"fmt"
"strconv"
"strings"
"sync"
"github.com/splitio/go-split-commons/v2/dtos"
"github.com/splitio/go-toolkit/v3/datastructures/set"
"github.com/splitio/go-toolkit/v3/logging"
"github.com/splitio/go-toolkit/v3/redis"
)
// SplitStorage is a redis-based implementation of split storage
type SplitStorage struct {
client *redis.PrefixedRedisClient
logger logging.LoggerInterface
mutext *sync.RWMutex
}
// NewSplitStorage creates a new RedisSplitStorage and returns a reference to it
func NewSplitStorage(redisClient *redis.PrefixedRedisClient, logger logging.LoggerInterface) *SplitStorage {
return &SplitStorage{
client: redisClient,
logger: logger,
mutext: &sync.RWMutex{},
}
}
// All returns a slice of splits dtos.
func (r *SplitStorage) All() []dtos.SplitDTO {
splits := make([]dtos.SplitDTO, 0)
keyPattern := strings.Replace(redisSplit, "{split}", "*", 1)
keys, err := r.client.Keys(keyPattern)
if err != nil {
r.logger.Error("Error fetching split keys. Returning empty split list")
return splits
}
rawSplits, err := r.client.MGet(keys)
if err != nil {
r.logger.Error("Could not get splits")
return splits
}
for idx, raw := range rawSplits {
var split dtos.SplitDTO
rawSplit, ok := rawSplits[idx].(string)
if ok {
err = json.Unmarshal([]byte(rawSplit), &split)
if err != nil {
r.logger.Error(fmt.Sprintf("Error parsing json for split %s", raw))
continue
}
}
splits = append(splits, split)
}
return splits
}
// ChangeNumber returns the latest split changeNumber
func (r *SplitStorage) ChangeNumber() (int64, error) {
val, err := r.client.Get(redisSplitTill)
if err != nil {
return -1, err
}
asInt, err := strconv.ParseInt(val, 10, 64)
if err != nil {
r.logger.Error("Could not parse Till value from redis")
return -1, err
}
return asInt, nil
}
// FetchMany retrieves features from redis storage
func (r *SplitStorage) FetchMany(features []string) map[string]*dtos.SplitDTO {
keysToFetch := make([]string, 0)
for _, feature := range features {
keysToFetch = append(keysToFetch, strings.Replace(redisSplit, "{split}", feature, 1))
}
rawSplits, err := r.client.MGet(keysToFetch)
if err != nil {
r.logger.Error(fmt.Sprintf("Could not fetch features from redis: %s", err.Error()))
return nil
}
splits := make(map[string]*dtos.SplitDTO)
for idx, feature := range features {
var split *dtos.SplitDTO
rawSplit, ok := rawSplits[idx].(string)
if ok {
err = json.Unmarshal([]byte(rawSplit), &split)
if err != nil {
r.logger.Error("Could not parse feature \"%s\" fetched from redis", feature)
return nil
}
}
splits[feature] = split
}
return splits
}
// KillLocally mock
func (r *SplitStorage) KillLocally(splitName string, defaultTreatment string, changeNumber int64) {
// @TODO Implement for Sync
}
// incr stores/increments trafficType in Redis
func (r *SplitStorage) incr(trafficType string) error {
key := strings.Replace(redisTrafficType, "{trafficType}", trafficType, 1)
_, err := r.client.Incr(key)
if err != nil {
r.logger.Error(fmt.Sprintf("Error storing trafficType %s in redis", trafficType))
r.logger.Error(err)
return errors.New("Error incrementing trafficType")
}
return nil
}
// decr decrements trafficType count in Redis
func (r *SplitStorage) decr(trafficType string) error {
key := strings.Replace(redisTrafficType, "{trafficType}", trafficType, 1)
val, _ := r.client.Decr(key)
if val <= 0 {
_, err := r.client.Del(key)
if err != nil {
r.logger.Verbose(fmt.Sprintf("Error removing trafficType %s in redis", trafficType))
}
}
return nil
}
// PutMany bulk stores splits in redis
func (r *SplitStorage) PutMany(splits []dtos.SplitDTO, changeNumber int64) {
r.mutext.Lock()
defer r.mutext.Unlock()
for _, split := range splits {
keyToStore := strings.Replace(redisSplit, "{split}", split.Name, 1)
raw, err := json.Marshal(split)
if err != nil {
r.logger.Error(fmt.Sprintf("Could not dump feature \"%s\" to json", split.Name))
continue
}
existing := r.Split(split.Name)
if existing != nil {
// If it's an update, we decrement the traffic type count of the existing split,
// and then add the updated one (as part of the normal flow), in case it's different.
r.decr(existing.TrafficTypeName)
}
r.incr(split.TrafficTypeName)
err = r.client.Set(keyToStore, raw, 0)
if err != nil {
r.logger.Error(fmt.Sprintf("Could not store split \"%s\" in redis: %s", split.Name, err.Error()))
}
}
err := r.client.Set(redisSplitTill, changeNumber, 0)
if err != nil {
r.logger.Error("Could not update split changenumber")
}
}
// Remove removes split item from redis
func (r *SplitStorage) Remove(splitName string) {
r.mutext.Lock()
defer r.mutext.Unlock()
keyToDelete := strings.Replace(redisSplit, "{split}", splitName, 1)
existing := r.Split(splitName)
if existing == nil {
r.logger.Warning("Tried to delete split " + splitName + " which doesn't exist. ignoring")
return
}
r.decr(existing.TrafficTypeName)
_, err := r.client.Del(keyToDelete)
if err != nil {
r.logger.Error(fmt.Sprintf("Error deleting split \"%s\".", splitName))
}
}
// SegmentNames returns a slice of strings with all the segment names
func (r *SplitStorage) SegmentNames() *set.ThreadUnsafeSet {
segmentNames := set.NewSet()
splits := r.All()
for _, split := range splits {
for _, condition := range split.Conditions {
for _, matcher := range condition.MatcherGroup.Matchers {
if matcher.UserDefinedSegment != nil {
segmentNames.Add(matcher.UserDefinedSegment.SegmentName)
}
}
}
}
return segmentNames
}
// SetChangeNumber sets the till value belong to segmentName
func (r *SplitStorage) SetChangeNumber(changeNumber int64) error {
return r.client.Set(redisSplitTill, changeNumber, 0)
}
// Split fetches a feature in redis and returns a pointer to a split dto
func (r *SplitStorage) Split(feature string) *dtos.SplitDTO {
keyToFetch := strings.Replace(redisSplit, "{split}", feature, 1)
val, err := r.client.Get(keyToFetch)
if err != nil {
r.logger.Error(fmt.Sprintf("Could not fetch feature %s from redis: %s", feature, err.Error()))
return nil
}
var split dtos.SplitDTO
err = json.Unmarshal([]byte(val), &split)
if err != nil {
r.logger.Error(fmt.Sprintf("Could not parse feature %s fetched from redis", feature))
return nil
}
return &split
}
// SplitNames returns a slice of strings with all the split names
func (r *SplitStorage) SplitNames() []string {
splitNames := make([]string, 0)
keyPattern := strings.Replace(redisSplit, "{split}", "*", 1)
keys, err := r.client.Keys(keyPattern)
if err == nil {
toRemove := strings.Replace(redisSplit, "{split}", "", 1) // Create a string with all the prefix to remove
for _, key := range keys {
splitNames = append(splitNames, strings.Replace(key, toRemove, "", 1)) // Extract split name from key
}
}
return splitNames
}
// TrafficTypeExists returns true or false depending on existence and counter
// of trafficType
func (r *SplitStorage) TrafficTypeExists(trafficType string) bool {
keyToFetch := strings.Replace(redisTrafficType, "{trafficType}", trafficType, 1)
res, err := r.client.Get(keyToFetch)
if err != nil {
r.logger.Error(fmt.Sprintf("Could not fetch trafficType \"%s\" from redis: %s", trafficType, err.Error()))
return false
}
val, err := strconv.ParseInt(res, 10, 64)
if err != nil {
r.logger.Error("TrafficType could not be converted")
return false
}
return val > 0
}

12
vendor/github.com/splitio/go-split-commons/v2/synchronizer/interface.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,12 @@
package synchronizer
// Synchronizer interface for syncing data to and from splits servers
type Synchronizer interface {
SyncAll() error
SynchronizeSplits(till *int64) error
SynchronizeSegment(segmentName string, till *int64) error
StartPeriodicFetching()
StopPeriodicFetching()
StartPeriodicDataRecording()
StopPeriodicDataRecording()
}

80
vendor/github.com/splitio/go-split-commons/v2/synchronizer/local.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,80 @@
package synchronizer
import (
"github.com/splitio/go-split-commons/v2/dtos"
"github.com/splitio/go-split-commons/v2/service"
"github.com/splitio/go-split-commons/v2/storage"
storageMock "github.com/splitio/go-split-commons/v2/storage/mocks"
"github.com/splitio/go-split-commons/v2/synchronizer/worker/split"
"github.com/splitio/go-split-commons/v2/tasks"
"github.com/splitio/go-toolkit/v3/logging"
)
// Local implements Local Synchronizer
type Local struct {
splitTasks SplitTasks
workers Workers
logger logging.LoggerInterface
inMememoryFullQueue chan string
}
// NewLocal creates new Local
func NewLocal(
period int,
splitAPI *service.SplitAPI,
splitStorage storage.SplitStorage,
logger logging.LoggerInterface,
) Synchronizer {
metricStorageMock := storageMock.MockMetricStorage{
IncCounterCall: func(key string) {},
IncLatencyCall: func(metricName string, index int) {},
PopCountersCall: func() []dtos.CounterDTO { return make([]dtos.CounterDTO, 0, 0) },
PopGaugesCall: func() []dtos.GaugeDTO { return make([]dtos.GaugeDTO, 0, 0) },
PopLatenciesCall: func() []dtos.LatenciesDTO { return make([]dtos.LatenciesDTO, 0, 0) },
PutGaugeCall: func(key string, gauge float64) {},
}
metricsWrapper := storage.NewMetricWrapper(metricStorageMock, nil, logger)
workers := Workers{
SplitFetcher: split.NewSplitFetcher(splitStorage, splitAPI.SplitFetcher, metricsWrapper, logger),
}
return &Local{
splitTasks: SplitTasks{
SplitSyncTask: tasks.NewFetchSplitsTask(workers.SplitFetcher, period, logger),
},
workers: workers,
logger: logger,
}
}
// SyncAll syncs splits and segments
func (s *Local) SyncAll() error {
return s.workers.SplitFetcher.SynchronizeSplits(nil)
}
// StartPeriodicFetching starts periodic fetchers tasks
func (s *Local) StartPeriodicFetching() {
s.splitTasks.SplitSyncTask.Start()
}
// StopPeriodicFetching stops periodic fetchers tasks
func (s *Local) StopPeriodicFetching() {
s.splitTasks.SplitSyncTask.Stop(false)
}
// StartPeriodicDataRecording starts periodic recorders tasks
func (s *Local) StartPeriodicDataRecording() {
}
// StopPeriodicDataRecording stops periodic recorders tasks
func (s *Local) StopPeriodicDataRecording() {
}
// SynchronizeSplits syncs splits
func (s *Local) SynchronizeSplits(till *int64) error {
return s.workers.SplitFetcher.SynchronizeSplits(till)
}
// SynchronizeSegment syncs segment
func (s *Local) SynchronizeSegment(name string, till *int64) error {
return nil
}

184
vendor/github.com/splitio/go-split-commons/v2/synchronizer/manager.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,184 @@
package synchronizer
import (
"errors"
"sync/atomic"
"github.com/splitio/go-split-commons/v2/conf"
"github.com/splitio/go-split-commons/v2/push"
"github.com/splitio/go-split-commons/v2/service"
"github.com/splitio/go-split-commons/v2/storage"
"github.com/splitio/go-toolkit/v3/logging"
)
const (
// Ready represents ready
Ready = iota
// StreamingReady ready
StreamingReady
// Error represents some error in SSE streaming
Error
)
const (
// Idle flags
Idle = iota
// Streaming flags
Streaming
// Polling flags
Polling
)
// Manager struct
type Manager struct {
synchronizer Synchronizer
logger logging.LoggerInterface
config conf.AdvancedConfig
pushManager push.Manager
managerStatus chan int
streamingStatus chan int
status atomic.Value
}
// NewSynchronizerManager creates new sync manager
func NewSynchronizerManager(
synchronizer Synchronizer,
logger logging.LoggerInterface,
config conf.AdvancedConfig,
authClient service.AuthClient,
splitStorage storage.SplitStorage,
managerStatus chan int,
) (*Manager, error) {
if managerStatus == nil || cap(managerStatus) < 1 {
return nil, errors.New("Status channel cannot be nil nor having capacity")
}
status := atomic.Value{}
status.Store(Idle)
manager := &Manager{
synchronizer: synchronizer,
logger: logger,
config: config,
managerStatus: managerStatus,
status: status,
}
if config.StreamingEnabled {
streamingStatus := make(chan int, 1000)
pushManager, err := push.NewPushManager(logger, synchronizer.SynchronizeSegment, synchronizer.SynchronizeSplits, splitStorage, &config, streamingStatus, authClient)
if err != nil {
return nil, err
}
manager.pushManager = pushManager
manager.streamingStatus = streamingStatus
}
return manager, nil
}
func (s *Manager) startPolling() {
s.status.Store(Polling)
s.pushManager.StopWorkers()
s.synchronizer.StartPeriodicFetching()
}
// IsRunning returns true if is in Streaming or Polling
func (s *Manager) IsRunning() bool {
return s.status.Load().(int) != Idle
}
// Start starts synchronization through Split
func (s *Manager) Start() {
if s.IsRunning() {
s.logger.Info("Manager is already running, skipping start")
return
}
select {
case <-s.managerStatus:
// Discarding previous status before starting
default:
}
err := s.synchronizer.SyncAll()
if err != nil {
s.managerStatus <- Error
return
}
s.logger.Debug("SyncAll Ready")
s.managerStatus <- Ready
s.synchronizer.StartPeriodicDataRecording()
if s.config.StreamingEnabled {
s.logger.Info("Start Streaming")
go s.pushManager.Start()
// Listens Streaming Status
for {
status := <-s.streamingStatus
switch status {
// Backoff is running -> start polling until auth is ok
case push.BackoffAuth:
fallthrough
// Backoff is running -> start polling until sse is connected
case push.BackoffSSE:
if s.status.Load().(int) != Polling {
s.logger.Info("Start periodic polling due backoff")
s.startPolling()
}
// SSE Streaming and workers are ready
case push.Ready:
// If Ready comes eventually when Backoff is done and polling is running
if s.status.Load().(int) == Polling {
s.synchronizer.StopPeriodicFetching()
}
s.logger.Info("SSE Streaming is ready")
s.status.Store(Streaming)
go s.synchronizer.SyncAll()
case push.StreamingDisabled:
fallthrough
// NonRetriableError occurs and it will switch to polling
case push.NonRetriableError:
s.pushManager.Stop()
s.logger.Info("Start periodic polling in Streaming")
s.startPolling()
return
// Publisher sends that there is no Notification Managers available
case push.PushIsDown:
// If streaming is already running, proceeding to stop workers
// and keeping SSE running
if s.status.Load().(int) == Streaming {
s.logger.Info("Start periodic polling in Streaming")
s.startPolling()
}
// Publisher sends that there are at least one Notification Manager available
case push.PushIsUp:
// If streaming is not already running, proceeding to start workers
if s.status.Load().(int) != Streaming {
s.logger.Info("Stop periodic polling")
s.pushManager.StartWorkers()
s.synchronizer.StopPeriodicFetching()
s.status.Store(Streaming)
go s.synchronizer.SyncAll()
}
// Reconnect received due error in streaming -> reconnecting
case push.Reconnect:
fallthrough
// Token expired -> reconnecting
case push.TokenExpiration:
s.pushManager.Stop()
go s.pushManager.Start()
}
}
} else {
s.logger.Info("Start periodic polling")
s.synchronizer.StartPeriodicFetching()
s.status.Store(Polling)
}
}
// Stop stop synchronizaation through Split
func (s *Manager) Stop() {
s.logger.Info("STOPPING MANAGER TASKS")
if s.pushManager != nil && s.pushManager.IsRunning() {
s.pushManager.Stop()
}
s.synchronizer.StopPeriodicFetching()
s.synchronizer.StopPeriodicDataRecording()
s.status.Store(Idle)
}

158
vendor/github.com/splitio/go-split-commons/v2/synchronizer/synchronizer.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,158 @@
package synchronizer
import (
"github.com/splitio/go-split-commons/v2/conf"
"github.com/splitio/go-split-commons/v2/synchronizer/worker/event"
"github.com/splitio/go-split-commons/v2/synchronizer/worker/impression"
"github.com/splitio/go-split-commons/v2/synchronizer/worker/impressionscount"
"github.com/splitio/go-split-commons/v2/synchronizer/worker/metric"
"github.com/splitio/go-split-commons/v2/synchronizer/worker/segment"
"github.com/splitio/go-split-commons/v2/synchronizer/worker/split"
"github.com/splitio/go-split-commons/v2/tasks"
"github.com/splitio/go-toolkit/v3/asynctask"
"github.com/splitio/go-toolkit/v3/logging"
)
// SplitTasks struct for tasks
type SplitTasks struct {
SplitSyncTask *asynctask.AsyncTask
SegmentSyncTask *asynctask.AsyncTask
TelemetrySyncTask *asynctask.AsyncTask
ImpressionSyncTask tasks.Task
EventSyncTask tasks.Task
ImpressionsCountSyncTask *asynctask.AsyncTask
}
// Workers struct for workers
type Workers struct {
SplitFetcher split.SplitFetcher
SegmentFetcher segment.SegmentFetcher
TelemetryRecorder metric.MetricRecorder
ImpressionRecorder impression.ImpressionRecorder
EventRecorder event.EventRecorder
ImpressionsCountRecorder impressionscount.ImpressionsCountRecorder
}
// SynchronizerImpl implements Synchronizer
type SynchronizerImpl struct {
splitTasks SplitTasks
workers Workers
logger logging.LoggerInterface
inMememoryFullQueue chan string
impressionBulkSize int64
eventBulkSize int64
}
// NewSynchronizer creates new SynchronizerImpl
func NewSynchronizer(
confAdvanced conf.AdvancedConfig,
splitTasks SplitTasks,
workers Workers,
logger logging.LoggerInterface,
inMememoryFullQueue chan string,
) Synchronizer {
return &SynchronizerImpl{
impressionBulkSize: confAdvanced.ImpressionsBulkSize,
eventBulkSize: confAdvanced.EventsBulkSize,
splitTasks: splitTasks,
workers: workers,
logger: logger,
inMememoryFullQueue: inMememoryFullQueue,
}
}
func (s *SynchronizerImpl) dataFlusher() {
for true {
msg := <-s.inMememoryFullQueue
switch msg {
case "EVENTS_FULL":
s.logger.Debug("FLUSHING storage queue")
err := s.workers.EventRecorder.SynchronizeEvents(s.eventBulkSize)
if err != nil {
s.logger.Error("Error flushing storage queue", err)
}
break
case "IMPRESSIONS_FULL":
s.logger.Debug("FLUSHING storage queue")
err := s.workers.ImpressionRecorder.SynchronizeImpressions(s.impressionBulkSize)
if err != nil {
s.logger.Error("Error flushing storage queue", err)
}
}
}
}
// SyncAll syncs splits and segments
func (s *SynchronizerImpl) SyncAll() error {
err := s.workers.SplitFetcher.SynchronizeSplits(nil)
if err != nil {
return err
}
return s.workers.SegmentFetcher.SynchronizeSegments()
}
// StartPeriodicFetching starts periodic fetchers tasks
func (s *SynchronizerImpl) StartPeriodicFetching() {
if s.splitTasks.SplitSyncTask != nil {
s.splitTasks.SplitSyncTask.Start()
}
if s.splitTasks.SegmentSyncTask != nil {
s.splitTasks.SegmentSyncTask.Start()
}
}
// StopPeriodicFetching stops periodic fetchers tasks
func (s *SynchronizerImpl) StopPeriodicFetching() {
if s.splitTasks.SplitSyncTask != nil {
s.splitTasks.SplitSyncTask.Stop(false)
}
if s.splitTasks.SegmentSyncTask != nil {
s.splitTasks.SegmentSyncTask.Stop(true)
}
}
// StartPeriodicDataRecording starts periodic recorders tasks
func (s *SynchronizerImpl) StartPeriodicDataRecording() {
if s.inMememoryFullQueue != nil {
go s.dataFlusher()
}
if s.splitTasks.ImpressionSyncTask != nil {
s.splitTasks.ImpressionSyncTask.Start()
}
if s.splitTasks.TelemetrySyncTask != nil {
s.splitTasks.TelemetrySyncTask.Start()
}
if s.splitTasks.EventSyncTask != nil {
s.splitTasks.EventSyncTask.Start()
}
if s.splitTasks.ImpressionsCountSyncTask != nil {
s.splitTasks.ImpressionsCountSyncTask.Start()
}
}
// StopPeriodicDataRecording stops periodic recorders tasks
func (s *SynchronizerImpl) StopPeriodicDataRecording() {
if s.splitTasks.ImpressionSyncTask != nil {
s.splitTasks.ImpressionSyncTask.Stop(true)
}
if s.splitTasks.TelemetrySyncTask != nil {
s.splitTasks.TelemetrySyncTask.Stop(false)
}
if s.splitTasks.EventSyncTask != nil {
s.splitTasks.EventSyncTask.Stop(true)
}
if s.splitTasks.ImpressionsCountSyncTask != nil {
s.splitTasks.ImpressionsCountSyncTask.Stop(true)
}
}
// SynchronizeSplits syncs splits
func (s *SynchronizerImpl) SynchronizeSplits(till *int64) error {
return s.workers.SplitFetcher.SynchronizeSplits(till)
}
// SynchronizeSegment syncs segment
func (s *SynchronizerImpl) SynchronizeSegment(name string, till *int64) error {
return s.workers.SegmentFetcher.SynchronizeSegment(name, till)
}

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше