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>
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
8bb772638c
Коммит
1aadd36644
27
vendor/github.com/splitio/go-client/v6/CONTRIBUTORS-GUIDE.md
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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 you’re 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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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 don’t 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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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)
|
||||
}
|
||||
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Ссылка в новой задаче
Block a user