From dfbaad3402c104f86f44a320aa9c3854967d1db1 Mon Sep 17 00:00:00 2001 From: Marcelo Bartsch Date: Tue, 7 Dec 2021 04:14:38 +0100 Subject: [PATCH 01/17] new-relic scaler Add an option to make empty data returned by query as an error Make sure test-app has at least 1 pod and keda-test-app is 0 before running tests Signed-off-by: Marcelo Bartsch --- go.mod | 6 +- go.sum | 11 ++ pkg/scalers/newrelic_scaler.go | 198 +++++++++++++++++++++ pkg/scalers/newrelic_scaler_test.go | 73 ++++++++ pkg/scaling/scale_handler.go | 2 + tests/scalers/new-relic.test.ts | 255 ++++++++++++++++++++++++++++ 6 files changed, 544 insertions(+), 1 deletion(-) create mode 100644 pkg/scalers/newrelic_scaler.go create mode 100644 pkg/scalers/newrelic_scaler_test.go create mode 100644 tests/scalers/new-relic.test.ts diff --git a/go.mod b/go.mod index 1b138ea0169..59db30a8f31 100644 --- a/go.mod +++ b/go.mod @@ -115,6 +115,7 @@ require ( github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/snappy v0.0.4 // indirect + github.com/google/go-querystring v1.1.0 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/uuid v1.3.0 // indirect github.com/googleapis/gax-go/v2 v2.1.1 // indirect @@ -128,7 +129,7 @@ require ( github.com/hashicorp/go-immutable-radix v1.3.1 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-plugin v1.4.3 // indirect - github.com/hashicorp/go-retryablehttp v0.6.6 // indirect + github.com/hashicorp/go-retryablehttp v0.7.0 // indirect github.com/hashicorp/go-rootcerts v1.0.2 // indirect github.com/hashicorp/go-secure-stdlib/mlock v0.1.1 // indirect github.com/hashicorp/go-secure-stdlib/parseutil v0.1.1 // indirect @@ -165,6 +166,7 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.1 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/newrelic/newrelic-client-go v0.68.3 // indirect github.com/nxadm/tail v1.4.8 // indirect github.com/oklog/run v1.0.0 // indirect github.com/pierrec/lz4 v2.6.1+incompatible // indirect @@ -173,11 +175,13 @@ require ( github.com/prometheus/common v0.32.1 // indirect github.com/prometheus/procfs v0.6.0 // indirect github.com/ryanuber/go-glob v1.0.0 // indirect + github.com/sirupsen/logrus v1.8.1 // indirect github.com/spf13/afero v1.6.0 // indirect github.com/spf13/cobra v1.1.3 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.0 // indirect + github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80 // indirect github.com/xdg-go/pbkdf2 v1.0.0 // indirect github.com/xdg-go/scram v1.0.2 // indirect github.com/xdg-go/stringprep v1.0.2 // indirect diff --git a/go.sum b/go.sum index a5ef4e76b78..4406f76bd1c 100644 --- a/go.sum +++ b/go.sum @@ -263,6 +263,8 @@ github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFP github.com/elastic/go-elasticsearch/v7 v7.15.1 h1:Wd8RLHb5D8xPBU8vGlnLXyflkso9G+rCmsXjqH8LLQQ= github.com/elastic/go-elasticsearch/v7 v7.15.1/go.mod h1:OJ4wdbtDNk5g503kvlHLyErCgQwwzmDtaFC4XyOxXA4= github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= +github.com/elazarl/goproxy v0.0.0-20210801061803-8e322dfb79c4/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= +github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8= github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emicklei/go-restful v2.15.0+incompatible h1:8KpYO/Xl/ZudZs5RNOEhWMBY4hmzlZhhRd9cu+jrZP4= @@ -454,6 +456,8 @@ github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-github/v27 v27.0.6/go.mod h1:/0Gr8pJ55COkmv+S/yPKCczSkUPIM/LnFyubufRNIS0= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= +github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= @@ -542,6 +546,8 @@ github.com/hashicorp/go-plugin v1.4.3/go.mod h1:5fGEH17QVwTTcR0zV7yhDPLLmFX9YSZ3 github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= github.com/hashicorp/go-retryablehttp v0.6.6 h1:HJunrbHTDDbBb/ay4kxa1n+dLmttUlnP3V9oNE4hmsM= github.com/hashicorp/go-retryablehttp v0.6.6/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= +github.com/hashicorp/go-retryablehttp v0.7.0 h1:eu1EI/mbirUgP5C8hVsTNaGZreBDlYiwC1FZWkvQPQ4= +github.com/hashicorp/go-retryablehttp v0.7.0/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= @@ -735,6 +741,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8m github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +github.com/newrelic/newrelic-client-go v0.68.3 h1:PUGGKNakTV75+EG6tiiUvun8yqLzYr9ou9H9w0QiW+E= +github.com/newrelic/newrelic-client-go v0.68.3/go.mod h1:VXjhsfui0rvhM9cVwnKwlidF8NbXlHZvh63ZKi6fImA= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= @@ -822,6 +830,7 @@ github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= @@ -888,6 +897,8 @@ github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1 github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802 h1:uruHq4dN7GR16kFc5fp3d1RIYzJW5onx8Ybykw2YQFA= github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80 h1:nrZ3ySNYwJbSpD6ce9duiP+QkD3JuLCcWkdaehUS/3Y= +github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80/go.mod h1:iFyPdL66DjUD96XmzVL3ZntbzcflLnznH0fr99w5VqE= github.com/tsenart/go-tsz v0.0.0-20180814232043-cdeb9e1e981e/go.mod h1:SWZznP1z5Ki7hDT2ioqiFKEse8K9tU2OUvaRI0NeGQo= github.com/tsenart/vegeta/v12 v12.8.4/go.mod h1:ZiJtwLn/9M4fTPdMY7bdbIeyNeFVE8/AHbWFqCsUuho= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= diff --git a/pkg/scalers/newrelic_scaler.go b/pkg/scalers/newrelic_scaler.go new file mode 100644 index 00000000000..3744cc2c185 --- /dev/null +++ b/pkg/scalers/newrelic_scaler.go @@ -0,0 +1,198 @@ +package scalers + +import ( + "context" + "fmt" + "log" + "strconv" + + kedautil "github.com/kedacore/keda/v2/pkg/util" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/metrics/pkg/apis/external_metrics" + + v2beta2 "k8s.io/api/autoscaling/v2beta2" + "k8s.io/apimachinery/pkg/labels" + logf "sigs.k8s.io/controller-runtime/pkg/log" + + "github.com/newrelic/newrelic-client-go/newrelic" + "github.com/newrelic/newrelic-client-go/pkg/nrdb" +) + +const ( + account = "account" + queryKey = "queryKey" + region = "region" + metricName = "metricName" + nrql = "nrql" + threshold = "threshold" + nodataerr = "nodataerr" +) + +type newrelicScaler struct { + metadata *newrelicMetadata + nrClient *newrelic.NewRelic +} + +type newrelicMetadata struct { + account int + region string + queryKey string + metricName string + noDataErr bool + nrql string + threshold int + scalerIndex int +} + +var newrelicLog = logf.Log.WithName("new-relic_scaler") + +func NewNewRelicScaler(config *ScalerConfig) (Scaler, error) { + meta, err := parseNewRelicMetadata(config) + if err != nil { + return nil, fmt.Errorf("error parsing new-relic metadata: %s", err) + } + + nrClient, err := newrelic.New( + newrelic.ConfigPersonalAPIKey(meta.queryKey), + newrelic.ConfigRegion(meta.region)) + + if err != nil { + log.Fatal("error initializing client:", err) + } + + logMsg := fmt.Sprintf("Initializing New Relic Scaler (account %d in region %s)", meta.account, meta.region) + + newrelicLog.Info(logMsg) + + return &newrelicScaler{ + metadata: meta, + nrClient: nrClient}, nil +} + +func parseNewRelicMetadata(config *ScalerConfig) (*newrelicMetadata, error) { + meta := newrelicMetadata{} + var err error + if val, ok := config.TriggerMetadata[account]; ok && val != "" { + t, err := strconv.Atoi(val) + if err != nil { + return nil, fmt.Errorf("error parsing %s: %s", account, err) + } + meta.account = t + } else { + return nil, fmt.Errorf("no %s given", account) + } + + meta.queryKey, err = GetFromAuthOrMeta(config, queryKey) + if err != nil { + return nil, fmt.Errorf("no %s given", queryKey) + } + + if val, ok := config.TriggerMetadata[metricName]; ok && val != "" { + meta.metricName = val + } else { + return nil, fmt.Errorf("no %s given", metricName) + } + + if val, ok := config.TriggerMetadata[nrql]; ok && val != "" { + meta.nrql = val + } else { + return nil, fmt.Errorf("no %s given", nrql) + } + + meta.region, err = GetFromAuthOrMeta(config, region) + if err != nil { + meta.region = "US" + newrelicLog.Info("Using default \"US\" region") + } + + if val, ok := config.TriggerMetadata[threshold]; ok && val != "" { + t, err := strconv.Atoi(val) + if err != nil { + return nil, fmt.Errorf("error parsing %s", threshold) + } + meta.threshold = t + } else { + return nil, fmt.Errorf("missing %s value", threshold) + } + + // If Query Return an Empty Data , shall we treat it as an error or not + // default is NO error is returned + if val, ok := config.TriggerMetadata[nodataerr]; ok { + noDataErr, err := strconv.ParseBool(val) + if err != nil { + return nil, fmt.Errorf("useRegex has invalid value") + } + meta.noDataErr = noDataErr + } else { + meta.noDataErr = true + } + + meta.scalerIndex = config.ScalerIndex + return &meta, nil +} + +func (s *newrelicScaler) IsActive(ctx context.Context) (bool, error) { + val, err := s.ExecuteNewRelicQuery(ctx) + if err != nil { + newrelicLog.Error(err, "error executing newrelic query") + return false, err + } + return val > 0, nil +} + +func (s *newrelicScaler) Close(context.Context) error { + return nil +} + +func (s *newrelicScaler) ExecuteNewRelicQuery(ctx context.Context) (float64, error) { + nrdbQuery := nrdb.NRQL(s.metadata.nrql) + resp, err := s.nrClient.Nrdb.QueryWithContext(ctx, s.metadata.account, nrdbQuery) + if err != nil { + return 0, fmt.Errorf("error running NerdGraph query \"%s\"", s.metadata.nrql) + } + for _, r := range resp.Results { + val, ok := r[s.metadata.metricName].(float64) + if ok { + return val, nil + } + } + if s.metadata.noDataErr { + return 0, nil + } + return 0, fmt.Errorf("query return no results '%s'", s.metadata.nrql) +} + +func (s *newrelicScaler) GetMetrics(ctx context.Context, metricName string, metricSelector labels.Selector) ([]external_metrics.ExternalMetricValue, error) { + val, err := s.ExecuteNewRelicQuery(ctx) + if err != nil { + newrelicLog.Error(err, "error executing New Relic query") + return []external_metrics.ExternalMetricValue{}, err + } + + metric := external_metrics.ExternalMetricValue{ + MetricName: metricName, + Value: *resource.NewQuantity(int64(val), resource.DecimalSI), + Timestamp: metav1.Now(), + } + + return append([]external_metrics.ExternalMetricValue{}, metric), nil +} + +func (s *newrelicScaler) GetMetricSpecForScaling(context.Context) []v2beta2.MetricSpec { + targetMetricValue := resource.NewQuantity(int64(s.metadata.threshold), resource.DecimalSI) + metricName := kedautil.NormalizeString(fmt.Sprintf("newrelic-%s", s.metadata.metricName)) + externalMetric := &v2beta2.ExternalMetricSource{ + Metric: v2beta2.MetricIdentifier{ + Name: GenerateMetricNameWithIndex(s.metadata.scalerIndex, metricName), + }, + Target: v2beta2.MetricTarget{ + Type: v2beta2.AverageValueMetricType, + AverageValue: targetMetricValue, + }, + } + metricSpec := v2beta2.MetricSpec{ + External: externalMetric, Type: externalMetricType, + } + return []v2beta2.MetricSpec{metricSpec} +} diff --git a/pkg/scalers/newrelic_scaler_test.go b/pkg/scalers/newrelic_scaler_test.go new file mode 100644 index 00000000000..902ced47f61 --- /dev/null +++ b/pkg/scalers/newrelic_scaler_test.go @@ -0,0 +1,73 @@ +package scalers + +import ( + "context" + "fmt" + "testing" +) + +type parseNewRelicMetadataTestData struct { + metadata map[string]string + isError bool +} + +type newrelicMetricIdentifier struct { + metadataTestData *parseNewRelicMetadataTestData + scalerIndex int + name string +} + +var testNewRelicMetadata = []parseNewRelicMetadataTestData{ + {map[string]string{}, true}, + // all properly formed + {map[string]string{"account": "0", "metricName": "results", "threshold": "100", "queryKey": "somekey", "nrql": "SELECT average(cpuUsedCores) as result FROM K8sContainerSample WHERE containerName='coredns'"}, false}, + // all properly formed + {map[string]string{"account": "0", "region": "EU", "metricName": "results", "threshold": "100", "queryKey": "somekey", "nrql": "SELECT average(cpuUsedCores) as result FROM K8sContainerSample WHERE containerName='coredns'"}, false}, + // account as String + {map[string]string{"account": "ABC", "metricName": "results", "threshold": "100", "queryKey": "somekey", "nrql": "SELECT average(cpuUsedCores) as result FROM K8sContainerSample WHERE containerName='coredns'"}, true}, + // missing account + {map[string]string{"metricName": "results", "threshold": "100", "queryKey": "somekey", "nrql": "SELECT average(cpuUsedCores) as result FROM K8sContainerSample WHERE containerName='coredns'"}, true}, + // missing metricName + {map[string]string{"account": "0", "threshold": "100", "queryKey": "somekey", "nrql": "SELECT average(cpuUsedCores) as result FROM K8sContainerSample WHERE containerName='coredns'"}, true}, + // malformed threshold + {map[string]string{"account": "0", "metricName": "results", "threshold": "one", "queryKey": "somekey", "nrql": "SELECT average(cpuUsedCores) as result FROM K8sContainerSample WHERE containerName='coredns'"}, true}, + // missing query + {map[string]string{"account": "0", "metricName": "results", "threshold": "100", "queryKey": "somekey"}, true}, +} + +var newrelicMetricIdentifiers = []newrelicMetricIdentifier{ + {&testNewRelicMetadata[1], 0, "s0-newrelic-results"}, + {&testNewRelicMetadata[1], 1, "s1-newrelic-results"}, +} + +func TestNewRelicParseMetadata(t *testing.T) { + for _, testData := range testNewRelicMetadata { + _, err := parseNewRelicMetadata(&ScalerConfig{TriggerMetadata: testData.metadata}) + if err != nil && !testData.isError { + fmt.Printf("X: %s", testData.metadata) + t.Error("Expected success but got error", err) + } + if testData.isError && err == nil { + fmt.Printf("X: %s", testData.metadata) + t.Error("Expected error but got success") + } + } +} +func TestNewRelicGetMetricSpecForScaling(t *testing.T) { + for _, testData := range newrelicMetricIdentifiers { + meta, err := parseNewRelicMetadata(&ScalerConfig{TriggerMetadata: testData.metadataTestData.metadata, ScalerIndex: testData.scalerIndex}) + if err != nil { + t.Fatal("Could not parse metadata:", err) + } + mockNewRelicScaler := newrelicScaler{ + metadata: meta, + nrClient: nil, + } + + metricSpec := mockNewRelicScaler.GetMetricSpecForScaling(context.Background()) + metricName := metricSpec[0].External.Metric.Name + if metricName != testData.name { + t.Error("Wrong External metric source name:", metricName) + } + } +} diff --git a/pkg/scaling/scale_handler.go b/pkg/scaling/scale_handler.go index 4f815dfc59f..893e2e1237c 100644 --- a/pkg/scaling/scale_handler.go +++ b/pkg/scaling/scale_handler.go @@ -387,6 +387,8 @@ func buildScaler(ctx context.Context, client client.Client, triggerType string, return scalers.NewMSSQLScaler(config) case "mysql": return scalers.NewMySQLScaler(config) + case "new-relic": + return scalers.NewNewRelicScaler(config) case "openstack-metric": return scalers.NewOpenstackMetricScaler(ctx, config) case "openstack-swift": diff --git a/tests/scalers/new-relic.test.ts b/tests/scalers/new-relic.test.ts new file mode 100644 index 00000000000..2146b128fdd --- /dev/null +++ b/tests/scalers/new-relic.test.ts @@ -0,0 +1,255 @@ +/* +To use this test you will need: +* NewRelic License Key +* NewRelic API Key + +You can get a free account on https://www.newrelic.com/ + +once you have your license and api key you need to setup the following +environment variables + +NEWRELIC_API_KEY +NEWRELIC_LICENSE + +the API key starts with 'NRAK' and the license ends in 'NRAL' + + */ +import * as fs from 'fs' +import * as sh from 'shelljs' +import * as tmp from 'tmp' +import test from 'ava' + +const newRelicApiKey = process.env['NEWRELIC_API_KEY'] +const newRelicAccountId = process.env['NEWRELIC_ACCOUNT_ID'] +const testNamespace = 'new-relic-test' +const newRelicNamespace = 'new-relic' +const newRelicRepoUrl = 'https://helm-charts.newrelic.com' +const newRelicRepoName = 'new-relic' +const newRelicHelmPackageName = 'newrelic/nri-bundle' +const newRelicLicenseKey = process.env['NEWRELIC_LICENSE'] +const kuberneteClusterName = 'keda-new-relic' + + +test.before(t => { + if (!newRelicApiKey) { + t.fail('NEWRELIC_API_KEY environment variable is required for newrelic tests tests') + } + if (!newRelicLicenseKey) { + t.fail('NEWRELIC_LICENSE environment variable is required for newrelic tests tests') + } + if (!newRelicAccountId) { + t.fail('NEWRELIC_ACCOUNT_ID environment variable is required for newrelic tests tests') + } + sh.exec(`kubectl create namespace ${newRelicNamespace}`) + sh.exec(`helm repo add ${newRelicRepoName} ${newRelicRepoUrl}`) + + let clusterStatus = sh.exec(`helm upgrade --install --set global.cluster=${kuberneteClusterName} --set prometheus.enabled=true --set ksm.enabled=true --set global.lowDataMode=true --set global.licenseKey=${newRelicLicenseKey} --timeout 600s --set ksm.enabled=true --set logging.enabled=true --namespace ${newRelicNamespace} nri-keda ${newRelicHelmPackageName}`).code + t.is(0, + clusterStatus, + 'creating a New Relic Bundle Install should work.' + ) + + sh.config.silent = true + const tmpFile = tmp.fileSync() + fs.writeFileSync(tmpFile.name, deployYaml.replace('{{NEWRELIC_API_KEY}}', Buffer.from(newRelicApiKey).toString('base64')).replace('{{NEWRELIC_ACCOUNT_ID}}',newRelicAccountId)) + sh.exec(`kubectl create namespace ${testNamespace}`) + sh.exec(`cp ${tmpFile.name} /tmp/paso.yaml`) + t.is( + 0, + sh.exec(`kubectl apply -f ${tmpFile.name} --namespace ${testNamespace}`).code, + 'creating a deployment should work.' + ) + for (let i = 0; i < 10; i++) { + const readyReplicaCount = sh.exec(`kubectl get deployment.apps/test-app --namespace ${testNamespace} -o jsonpath="{.status.readyReplicas}"`).stdout + if (readyReplicaCount != '1') { + sh.exec('sleep 2s') + } + } +}) + +test.serial('Keda Deployment should have 0 replicas on start', t => { + const replicaCount = sh.exec( + `kubectl get deployment.apps/keda-test-app --namespace ${testNamespace} -o jsonpath="{.spec.replicas}"` + ).stdout + t.is(replicaCount, '0', 'replica count should start out as 0') +}) + +test.serial('Deployment should have 1 replicas on start', t => { + const replicaCount = sh.exec( + `kubectl get deployment.apps/test-app --namespace ${testNamespace} -o jsonpath="{.spec.replicas}"` + ).stdout + t.is(replicaCount, '1', 'replica count should start out as 0') +}) + +test.serial(`Deployment should scale to 5 (the max) with HTTP Requests exceeding in the rate then back to 0`, t => { + // generate a large number of HTTP requests (using Apache Bench) that will take some time + // so prometheus has some time to scrape it + const tmpFile = tmp.fileSync() + fs.writeFileSync(tmpFile.name, generateRequestsYaml.replace('{{NAMESPACE}}', testNamespace)) + t.is( + 0, + sh.exec(`kubectl apply -f ${tmpFile.name} --namespace ${testNamespace}`).code, + 'creating job should work.' + ) + + t.is( + '1', + sh.exec( + `kubectl get deployment.apps/test-app --namespace ${testNamespace} -o jsonpath="{.status.readyReplicas}"` + ).stdout, + 'There should be 1 replica for the test-app deployment' + ) + + // keda based deployment should start scaling up with http requests issued + let replicaCount = '0' + for (let i = 0; i < 60 && replicaCount !== '5'; i++) { + t.log(`Waited ${5 * i} seconds for new-relic-based deployments to scale up`) + const jobLogs = sh.exec(`kubectl logs -l job-name=generate-requests -n ${testNamespace}`).stdout + t.log(`Logs from the generate requests: ${jobLogs}`) + + replicaCount = sh.exec( + `kubectl get deployment.apps/keda-test-app --namespace ${testNamespace} -o jsonpath="{.spec.replicas}"` + ).stdout + if (replicaCount !== '5') { + sh.exec('sleep 5s') + } + } + + t.is('5', replicaCount, 'Replica count should be maxed at 5') + + for (let i = 0; i < 50 && replicaCount !== '0'; i++) { + replicaCount = sh.exec( + `kubectl get deployment.apps/keda-test-app --namespace ${testNamespace} -o jsonpath="{.spec.replicas}"` + ).stdout + if (replicaCount !== '0') { + sh.exec('sleep 5s') + } + } + + t.is('0', replicaCount, 'Replica count should be 0 after 3 minutes') +}) + + +test.after.always.cb('clean up newrelic resources', t => { + sh.exec(`helm delete --namespace ${newRelicNamespace} nri-keda`) + sh.exec(`kubectl delete namespace ${newRelicNamespace} --force`) + sh.exec(`kubectl delete namespace ${testNamespace} --force`) + t.end() +}) + +const generateRequestsYaml = `apiVersion: batch/v1 +kind: Job +metadata: + name: generate-requests +spec: + template: + spec: + containers: + - image: jordi/ab + name: test + command: ["/bin/sh"] + args: ["-c", "for i in $(seq 1 60);do echo $i;ab -c 5 -n 1000 -v 2 http://test-app/;sleep 1;done"] + restartPolicy: Never + activeDeadlineSeconds: 120 + backoffLimit: 2` + +const deployYaml = `apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: test-app + name: test-app +spec: + replicas: 1 + selector: + matchLabels: + app: test-app + template: + metadata: + labels: + app: test-app + type: keda-testing + spec: + containers: + - name: prom-test-app + image: tbickford/simple-web-app-prometheus:a13ade9 + imagePullPolicy: IfNotPresent +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: keda-test-app + name: keda-test-app +spec: + replicas: 0 + selector: + matchLabels: + app: keda-test-app + template: + metadata: + labels: + app: keda-test-app + type: keda-testing + spec: + containers: + - name: prom-test-app + image: tbickford/simple-web-app-prometheus:a13ade9 + imagePullPolicy: IfNotPresent +--- +apiVersion: v1 +kind: Service +metadata: + labels: + app: test-app + annotations: + prometheus.io/scrape: "true" + name: test-app +spec: + ports: + - name: http + port: 80 + protocol: TCP + targetPort: 8080 + selector: + type: keda-testing +--- +apiVersion: keda.sh/v1alpha1 +kind: TriggerAuthentication +metadata: + name: newrelic-trigger +spec: + secretTargetRef: + - parameter: queryKey + name: newrelic-secret + key: newRelicApiKey +--- +apiVersion: v1 +kind: Secret +metadata: + name: newrelic-secret +type: Opaque +data: + newRelicApiKey: {{NEWRELIC_API_KEY}} +--- +apiVersion: keda.sh/v1alpha1 +kind: ScaledObject +metadata: + name: new-relic-scaledobject +spec: + scaleTargetRef: + name: keda-test-app + minReplicaCount: 0 + maxReplicaCount: 5 + pollingInterval: 5 + cooldownPeriod: 10 + triggers: + - type: new-relic + metadata: + account: '{{NEWRELIC_ACCOUNT_ID}}' + metricName: result + threshold: '100' + nrql: SELECT average(\`http_requests_total\`) as result FROM Metric where serviceName='test-app' and namespaceName='new-relic-test' since 60 seconds ago + authenticationRef: + name: newrelic-trigger +` From 17d9d2922e76394de6e61ef5d437c2b50c8e07ba Mon Sep 17 00:00:00 2001 From: Marcelo Bartsch Date: Tue, 7 Dec 2021 05:57:10 +0100 Subject: [PATCH 02/17] go.mod/go.sum from main Signed-off-by: Marcelo Bartsch --- go.mod | 6 +----- go.sum | 11 ----------- 2 files changed, 1 insertion(+), 16 deletions(-) diff --git a/go.mod b/go.mod index 59db30a8f31..1b138ea0169 100644 --- a/go.mod +++ b/go.mod @@ -115,7 +115,6 @@ require ( github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/snappy v0.0.4 // indirect - github.com/google/go-querystring v1.1.0 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/uuid v1.3.0 // indirect github.com/googleapis/gax-go/v2 v2.1.1 // indirect @@ -129,7 +128,7 @@ require ( github.com/hashicorp/go-immutable-radix v1.3.1 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-plugin v1.4.3 // indirect - github.com/hashicorp/go-retryablehttp v0.7.0 // indirect + github.com/hashicorp/go-retryablehttp v0.6.6 // indirect github.com/hashicorp/go-rootcerts v1.0.2 // indirect github.com/hashicorp/go-secure-stdlib/mlock v0.1.1 // indirect github.com/hashicorp/go-secure-stdlib/parseutil v0.1.1 // indirect @@ -166,7 +165,6 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.1 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/newrelic/newrelic-client-go v0.68.3 // indirect github.com/nxadm/tail v1.4.8 // indirect github.com/oklog/run v1.0.0 // indirect github.com/pierrec/lz4 v2.6.1+incompatible // indirect @@ -175,13 +173,11 @@ require ( github.com/prometheus/common v0.32.1 // indirect github.com/prometheus/procfs v0.6.0 // indirect github.com/ryanuber/go-glob v1.0.0 // indirect - github.com/sirupsen/logrus v1.8.1 // indirect github.com/spf13/afero v1.6.0 // indirect github.com/spf13/cobra v1.1.3 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.0 // indirect - github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80 // indirect github.com/xdg-go/pbkdf2 v1.0.0 // indirect github.com/xdg-go/scram v1.0.2 // indirect github.com/xdg-go/stringprep v1.0.2 // indirect diff --git a/go.sum b/go.sum index 4406f76bd1c..a5ef4e76b78 100644 --- a/go.sum +++ b/go.sum @@ -263,8 +263,6 @@ github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFP github.com/elastic/go-elasticsearch/v7 v7.15.1 h1:Wd8RLHb5D8xPBU8vGlnLXyflkso9G+rCmsXjqH8LLQQ= github.com/elastic/go-elasticsearch/v7 v7.15.1/go.mod h1:OJ4wdbtDNk5g503kvlHLyErCgQwwzmDtaFC4XyOxXA4= github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= -github.com/elazarl/goproxy v0.0.0-20210801061803-8e322dfb79c4/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= -github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8= github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emicklei/go-restful v2.15.0+incompatible h1:8KpYO/Xl/ZudZs5RNOEhWMBY4hmzlZhhRd9cu+jrZP4= @@ -456,8 +454,6 @@ github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-github/v27 v27.0.6/go.mod h1:/0Gr8pJ55COkmv+S/yPKCczSkUPIM/LnFyubufRNIS0= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= -github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= -github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= @@ -546,8 +542,6 @@ github.com/hashicorp/go-plugin v1.4.3/go.mod h1:5fGEH17QVwTTcR0zV7yhDPLLmFX9YSZ3 github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= github.com/hashicorp/go-retryablehttp v0.6.6 h1:HJunrbHTDDbBb/ay4kxa1n+dLmttUlnP3V9oNE4hmsM= github.com/hashicorp/go-retryablehttp v0.6.6/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= -github.com/hashicorp/go-retryablehttp v0.7.0 h1:eu1EI/mbirUgP5C8hVsTNaGZreBDlYiwC1FZWkvQPQ4= -github.com/hashicorp/go-retryablehttp v0.7.0/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= @@ -741,8 +735,6 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8m github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= -github.com/newrelic/newrelic-client-go v0.68.3 h1:PUGGKNakTV75+EG6tiiUvun8yqLzYr9ou9H9w0QiW+E= -github.com/newrelic/newrelic-client-go v0.68.3/go.mod h1:VXjhsfui0rvhM9cVwnKwlidF8NbXlHZvh63ZKi6fImA= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= @@ -830,7 +822,6 @@ github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= -github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= @@ -897,8 +888,6 @@ github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1 github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802 h1:uruHq4dN7GR16kFc5fp3d1RIYzJW5onx8Ybykw2YQFA= github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80 h1:nrZ3ySNYwJbSpD6ce9duiP+QkD3JuLCcWkdaehUS/3Y= -github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80/go.mod h1:iFyPdL66DjUD96XmzVL3ZntbzcflLnznH0fr99w5VqE= github.com/tsenart/go-tsz v0.0.0-20180814232043-cdeb9e1e981e/go.mod h1:SWZznP1z5Ki7hDT2ioqiFKEse8K9tU2OUvaRI0NeGQo= github.com/tsenart/vegeta/v12 v12.8.4/go.mod h1:ZiJtwLn/9M4fTPdMY7bdbIeyNeFVE8/AHbWFqCsUuho= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= From d8441e4be2c917053983008f13244808d2d96f56 Mon Sep 17 00:00:00 2001 From: Marcelo Bartsch Date: Tue, 7 Dec 2021 06:28:10 +0100 Subject: [PATCH 03/17] Fix noDataErr string case add noDataErr to tests add missinng threshold to tests add better error description when NerdGraph query fails Signed-off-by: Marcelo Bartsch --- pkg/scalers/newrelic_scaler.go | 19 +++++++++---------- pkg/scalers/newrelic_scaler_test.go | 9 +++++++++ 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/pkg/scalers/newrelic_scaler.go b/pkg/scalers/newrelic_scaler.go index 3744cc2c185..f92de031f19 100644 --- a/pkg/scalers/newrelic_scaler.go +++ b/pkg/scalers/newrelic_scaler.go @@ -26,7 +26,7 @@ const ( metricName = "metricName" nrql = "nrql" threshold = "threshold" - nodataerr = "nodataerr" + noDataErr = "noDataErr" ) type newrelicScaler struct { @@ -103,7 +103,7 @@ func parseNewRelicMetadata(config *ScalerConfig) (*newrelicMetadata, error) { meta.region, err = GetFromAuthOrMeta(config, region) if err != nil { meta.region = "US" - newrelicLog.Info("Using default \"US\" region") + newrelicLog.Info("Using default 'US' region") } if val, ok := config.TriggerMetadata[threshold]; ok && val != "" { @@ -117,17 +117,16 @@ func parseNewRelicMetadata(config *ScalerConfig) (*newrelicMetadata, error) { } // If Query Return an Empty Data , shall we treat it as an error or not - // default is NO error is returned - if val, ok := config.TriggerMetadata[nodataerr]; ok { + // default is NO error is returned when query result is empty/no data + if val, ok := config.TriggerMetadata[noDataErr]; ok { noDataErr, err := strconv.ParseBool(val) if err != nil { - return nil, fmt.Errorf("useRegex has invalid value") + return nil, fmt.Errorf("noDataErr has invalid value") } meta.noDataErr = noDataErr } else { - meta.noDataErr = true + meta.noDataErr = false } - meta.scalerIndex = config.ScalerIndex return &meta, nil } @@ -149,7 +148,7 @@ func (s *newrelicScaler) ExecuteNewRelicQuery(ctx context.Context) (float64, err nrdbQuery := nrdb.NRQL(s.metadata.nrql) resp, err := s.nrClient.Nrdb.QueryWithContext(ctx, s.metadata.account, nrdbQuery) if err != nil { - return 0, fmt.Errorf("error running NerdGraph query \"%s\"", s.metadata.nrql) + return 0, fmt.Errorf("error running NerdGraph query %s (%s)", s.metadata.nrql, err.Error()) } for _, r := range resp.Results { val, ok := r[s.metadata.metricName].(float64) @@ -158,9 +157,9 @@ func (s *newrelicScaler) ExecuteNewRelicQuery(ctx context.Context) (float64, err } } if s.metadata.noDataErr { - return 0, nil + return 0, fmt.Errorf("query return no results %s", s.metadata.nrql) } - return 0, fmt.Errorf("query return no results '%s'", s.metadata.nrql) + return 0, nil } func (s *newrelicScaler) GetMetrics(ctx context.Context, metricName string, metricSelector labels.Selector) ([]external_metrics.ExternalMetricValue, error) { diff --git a/pkg/scalers/newrelic_scaler_test.go b/pkg/scalers/newrelic_scaler_test.go index 902ced47f61..ce85416af40 100644 --- a/pkg/scalers/newrelic_scaler_test.go +++ b/pkg/scalers/newrelic_scaler_test.go @@ -31,8 +31,17 @@ var testNewRelicMetadata = []parseNewRelicMetadataTestData{ {map[string]string{"account": "0", "threshold": "100", "queryKey": "somekey", "nrql": "SELECT average(cpuUsedCores) as result FROM K8sContainerSample WHERE containerName='coredns'"}, true}, // malformed threshold {map[string]string{"account": "0", "metricName": "results", "threshold": "one", "queryKey": "somekey", "nrql": "SELECT average(cpuUsedCores) as result FROM K8sContainerSample WHERE containerName='coredns'"}, true}, + // missing threshold + {map[string]string{"account": "0", "metricName": "results", "queryKey": "somekey", "nrql": "SELECT average(cpuUsedCores) as result FROM K8sContainerSample WHERE containerName='coredns'"}, true}, // missing query {map[string]string{"account": "0", "metricName": "results", "threshold": "100", "queryKey": "somekey"}, true}, + // noDataErr invalid value + {map[string]string{"account": "0", "metricName": "results", "threshold": "100", "queryKey": "somekey", "noDataErr": "invalid", "nrql": "SELECT average(cpuUsedCores) as result FROM K8sContainerSample WHERE containerName='coredns'"}, true}, + // noDataErr valid value + {map[string]string{"account": "0", "metricName": "results", "threshold": "100", "queryKey": "somekey", "noDataErr": "true", "nrql": "SELECT average(cpuUsedCores) as result FROM K8sContainerSample WHERE containerName='coredns'"}, false}, + {map[string]string{"account": "0", "metricName": "results", "threshold": "100", "queryKey": "somekey", "noDataErr": "false", "nrql": "SELECT average(cpuUsedCores) as result FROM K8sContainerSample WHERE containerName='coredns'"}, false}, + {map[string]string{"account": "0", "metricName": "results", "threshold": "100", "queryKey": "somekey", "noDataErr": "0", "nrql": "SELECT average(cpuUsedCores) as result FROM K8sContainerSample WHERE containerName='coredns'"}, false}, + {map[string]string{"account": "0", "metricName": "results", "threshold": "100", "queryKey": "somekey", "noDataErr": "1", "nrql": "SELECT average(cpuUsedCores) as result FROM K8sContainerSample WHERE containerName='coredns'"}, false}, } var newrelicMetricIdentifiers = []newrelicMetricIdentifier{ From 1fd4536fde5b2620821dca6fb7a512b3e6b5d224 Mon Sep 17 00:00:00 2001 From: Marcelo Bartsch Date: Tue, 7 Dec 2021 09:02:00 +0100 Subject: [PATCH 04/17] Make metricName optional also fix the test so newrelic is replaced by new-relic to keep consistency on the names Signed-off-by: Marcelo Bartsch --- pkg/scalers/newrelic_scaler.go | 30 ++++++++++++++++++----------- pkg/scalers/newrelic_scaler_test.go | 6 +++--- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/pkg/scalers/newrelic_scaler.go b/pkg/scalers/newrelic_scaler.go index f92de031f19..db21f3d561b 100644 --- a/pkg/scalers/newrelic_scaler.go +++ b/pkg/scalers/newrelic_scaler.go @@ -2,10 +2,12 @@ package scalers import ( "context" + "encoding/hex" "fmt" "log" "strconv" + "crypto/md5" kedautil "github.com/kedacore/keda/v2/pkg/util" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -83,6 +85,12 @@ func parseNewRelicMetadata(config *ScalerConfig) (*newrelicMetadata, error) { return nil, fmt.Errorf("no %s given", account) } + if val, ok := config.TriggerMetadata[nrql]; ok && val != "" { + meta.nrql = val + } else { + return nil, fmt.Errorf("no %s given", nrql) + } + meta.queryKey, err = GetFromAuthOrMeta(config, queryKey) if err != nil { return nil, fmt.Errorf("no %s given", queryKey) @@ -90,14 +98,6 @@ func parseNewRelicMetadata(config *ScalerConfig) (*newrelicMetadata, error) { if val, ok := config.TriggerMetadata[metricName]; ok && val != "" { meta.metricName = val - } else { - return nil, fmt.Errorf("no %s given", metricName) - } - - if val, ok := config.TriggerMetadata[nrql]; ok && val != "" { - meta.nrql = val - } else { - return nil, fmt.Errorf("no %s given", nrql) } meta.region, err = GetFromAuthOrMeta(config, region) @@ -150,8 +150,9 @@ func (s *newrelicScaler) ExecuteNewRelicQuery(ctx context.Context) (float64, err if err != nil { return 0, fmt.Errorf("error running NerdGraph query %s (%s)", s.metadata.nrql, err.Error()) } - for _, r := range resp.Results { - val, ok := r[s.metadata.metricName].(float64) + // Only use the first result from the query, as the query should not be multi row + for _, v := range resp.Results[0] { + val, ok := v.(float64) if ok { return val, nil } @@ -180,7 +181,14 @@ func (s *newrelicScaler) GetMetrics(ctx context.Context, metricName string, metr func (s *newrelicScaler) GetMetricSpecForScaling(context.Context) []v2beta2.MetricSpec { targetMetricValue := resource.NewQuantity(int64(s.metadata.threshold), resource.DecimalSI) - metricName := kedautil.NormalizeString(fmt.Sprintf("newrelic-%s", s.metadata.metricName)) + var metricName string + if s.metadata.metricName == "" { + // TODO: Find a better way to generate a small string based on the nrql string, or maybe just use UUID? + hash := md5.Sum([]byte(s.metadata.nrql)) + metricName = kedautil.NormalizeString(fmt.Sprintf("new-relic-%s", hex.EncodeToString(hash[:]))) + } else { + metricName = kedautil.NormalizeString(fmt.Sprintf("new-relic-%s", s.metadata.metricName)) + } externalMetric := &v2beta2.ExternalMetricSource{ Metric: v2beta2.MetricIdentifier{ Name: GenerateMetricNameWithIndex(s.metadata.scalerIndex, metricName), diff --git a/pkg/scalers/newrelic_scaler_test.go b/pkg/scalers/newrelic_scaler_test.go index ce85416af40..9e8332dc641 100644 --- a/pkg/scalers/newrelic_scaler_test.go +++ b/pkg/scalers/newrelic_scaler_test.go @@ -28,7 +28,7 @@ var testNewRelicMetadata = []parseNewRelicMetadataTestData{ // missing account {map[string]string{"metricName": "results", "threshold": "100", "queryKey": "somekey", "nrql": "SELECT average(cpuUsedCores) as result FROM K8sContainerSample WHERE containerName='coredns'"}, true}, // missing metricName - {map[string]string{"account": "0", "threshold": "100", "queryKey": "somekey", "nrql": "SELECT average(cpuUsedCores) as result FROM K8sContainerSample WHERE containerName='coredns'"}, true}, + {map[string]string{"account": "0", "threshold": "100", "queryKey": "somekey", "nrql": "SELECT average(cpuUsedCores) as result FROM K8sContainerSample WHERE containerName='coredns'"}, false}, // malformed threshold {map[string]string{"account": "0", "metricName": "results", "threshold": "one", "queryKey": "somekey", "nrql": "SELECT average(cpuUsedCores) as result FROM K8sContainerSample WHERE containerName='coredns'"}, true}, // missing threshold @@ -45,8 +45,8 @@ var testNewRelicMetadata = []parseNewRelicMetadataTestData{ } var newrelicMetricIdentifiers = []newrelicMetricIdentifier{ - {&testNewRelicMetadata[1], 0, "s0-newrelic-results"}, - {&testNewRelicMetadata[1], 1, "s1-newrelic-results"}, + {&testNewRelicMetadata[1], 0, "s0-new-relic-results"}, + {&testNewRelicMetadata[1], 1, "s1-new-relic-results"}, } func TestNewRelicParseMetadata(t *testing.T) { From 7c33ba47bedba098931248dc7a2b6dca3041f812 Mon Sep 17 00:00:00 2001 From: Marcelo Bartsch Date: Tue, 7 Dec 2021 09:04:00 +0100 Subject: [PATCH 05/17] fix e2e test Signed-off-by: Marcelo Bartsch --- tests/scalers/new-relic.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/scalers/new-relic.test.ts b/tests/scalers/new-relic.test.ts index 2146b128fdd..0e5a86ccc9f 100644 --- a/tests/scalers/new-relic.test.ts +++ b/tests/scalers/new-relic.test.ts @@ -127,6 +127,7 @@ test.serial(`Deployment should scale to 5 (the max) with HTTP Requests exceeding } t.is('0', replicaCount, 'Replica count should be 0 after 3 minutes') + sh.exec('sleep 10s') }) @@ -247,9 +248,8 @@ spec: - type: new-relic metadata: account: '{{NEWRELIC_ACCOUNT_ID}}' - metricName: result threshold: '100' - nrql: SELECT average(\`http_requests_total\`) as result FROM Metric where serviceName='test-app' and namespaceName='new-relic-test' since 60 seconds ago + nrql: SELECT average(\`http_requests_total\`) FROM Metric where serviceName='test-app' and namespaceName='new-relic-test' since 60 seconds ago authenticationRef: name: newrelic-trigger ` From 0a97185164be7e68161897a3f2d09b2bfd4501ae Mon Sep 17 00:00:00 2001 From: Marcelo Bartsch Date: Tue, 7 Dec 2021 16:32:48 +0100 Subject: [PATCH 06/17] add NewRelic variables to e2e test Signed-off-by: Marcelo Bartsch --- .github/workflows/main-build.yml | 3 +++ .github/workflows/nightly-e2e.yml | 3 +++ .github/workflows/pr-e2e.yml | 3 +++ 3 files changed, 9 insertions(+) diff --git a/.github/workflows/main-build.yml b/.github/workflows/main-build.yml index 4f625363eae..c981ed3ba63 100644 --- a/.github/workflows/main-build.yml +++ b/.github/workflows/main-build.yml @@ -79,4 +79,7 @@ jobs: AZURE_DEVOPS_PAT: ${{ secrets.AZURE_DEVOPS_PAT }} AZURE_DEVOPS_POOL_NAME: ${{ secrets.AZURE_DEVOPS_POOL_NAME }} AZURE_DEVOPS_PROJECT: ${{ secrets.AZURE_DEVOPS_PROJECT }} + NEWRELIC_ACCOUNT_ID: ${{ secrets.NEWRELIC_ACCOUNT_ID}} + NEWRELIC_API_KEY: ${{ secrets.NEWRELIC_API_KEY}} + NEWRELIC_LICENSE: ${{ secrets.NEWRELIC_LICENSE}} run: make e2e-test diff --git a/.github/workflows/nightly-e2e.yml b/.github/workflows/nightly-e2e.yml index 366fd858eed..2f035c7fad2 100644 --- a/.github/workflows/nightly-e2e.yml +++ b/.github/workflows/nightly-e2e.yml @@ -33,4 +33,7 @@ jobs: AZURE_DEVOPS_PAT: ${{ secrets.AZURE_DEVOPS_PAT }} AZURE_DEVOPS_POOL_NAME: ${{ secrets.AZURE_DEVOPS_POOL_NAME }} AZURE_DEVOPS_PROJECT: ${{ secrets.AZURE_DEVOPS_PROJECT }} + NEWRELIC_ACCOUNT_ID: ${{ secrets.NEWRELIC_ACCOUNT_ID}} + NEWRELIC_API_KEY: ${{ secrets.NEWRELIC_API_KEY}} + NEWRELIC_LICENSE: ${{ secrets.NEWRELIC_LICENSE}} run: make e2e-test diff --git a/.github/workflows/pr-e2e.yml b/.github/workflows/pr-e2e.yml index 9a86e2e84f0..b5d7444f15d 100644 --- a/.github/workflows/pr-e2e.yml +++ b/.github/workflows/pr-e2e.yml @@ -79,6 +79,9 @@ jobs: AZURE_DEVOPS_PAT: ${{ secrets.AZURE_DEVOPS_PAT }} AZURE_DEVOPS_POOL_NAME: ${{ secrets.AZURE_DEVOPS_POOL_NAME }} AZURE_DEVOPS_PROJECT: ${{ secrets.AZURE_DEVOPS_PROJECT }} + NEWRELIC_ACCOUNT_ID: ${{ secrets.NEWRELIC_ACCOUNT_ID}} + NEWRELIC_API_KEY: ${{ secrets.NEWRELIC_API_KEY}} + NEWRELIC_LICENSE: ${{ secrets.NEWRELIC_LICENSE}} E2E_IMAGE_TAG: "pr-${{ steps.checkout.outputs.pr_num }}" TEST_CLUSTER_NAME: keda-pr-run run: | From ea03c282bd3a5782ec43a50c0df685574f2801bb Mon Sep 17 00:00:00 2001 From: Marcelo Bartsch Date: Tue, 7 Dec 2021 16:35:22 +0100 Subject: [PATCH 07/17] Added missing NEWRELIC_ACCOUNT_ID to test description on requiered env variables Signed-off-by: Marcelo Bartsch --- tests/scalers/new-relic.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/scalers/new-relic.test.ts b/tests/scalers/new-relic.test.ts index 0e5a86ccc9f..76616e7697a 100644 --- a/tests/scalers/new-relic.test.ts +++ b/tests/scalers/new-relic.test.ts @@ -10,6 +10,7 @@ environment variables NEWRELIC_API_KEY NEWRELIC_LICENSE +NEWRELIC_ACCOUNT_ID the API key starts with 'NRAK' and the license ends in 'NRAL' From 91ee020381389248055b5a443abc45938c9cd5d1 Mon Sep 17 00:00:00 2001 From: Marcelo Bartsch Date: Tue, 7 Dec 2021 17:24:00 +0100 Subject: [PATCH 08/17] add go.mod/go.sum files Signed-off-by: Marcelo Bartsch --- go.mod | 6 +++++- go.sum | 11 +++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 1b138ea0169..59db30a8f31 100644 --- a/go.mod +++ b/go.mod @@ -115,6 +115,7 @@ require ( github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/snappy v0.0.4 // indirect + github.com/google/go-querystring v1.1.0 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/uuid v1.3.0 // indirect github.com/googleapis/gax-go/v2 v2.1.1 // indirect @@ -128,7 +129,7 @@ require ( github.com/hashicorp/go-immutable-radix v1.3.1 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-plugin v1.4.3 // indirect - github.com/hashicorp/go-retryablehttp v0.6.6 // indirect + github.com/hashicorp/go-retryablehttp v0.7.0 // indirect github.com/hashicorp/go-rootcerts v1.0.2 // indirect github.com/hashicorp/go-secure-stdlib/mlock v0.1.1 // indirect github.com/hashicorp/go-secure-stdlib/parseutil v0.1.1 // indirect @@ -165,6 +166,7 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.1 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/newrelic/newrelic-client-go v0.68.3 // indirect github.com/nxadm/tail v1.4.8 // indirect github.com/oklog/run v1.0.0 // indirect github.com/pierrec/lz4 v2.6.1+incompatible // indirect @@ -173,11 +175,13 @@ require ( github.com/prometheus/common v0.32.1 // indirect github.com/prometheus/procfs v0.6.0 // indirect github.com/ryanuber/go-glob v1.0.0 // indirect + github.com/sirupsen/logrus v1.8.1 // indirect github.com/spf13/afero v1.6.0 // indirect github.com/spf13/cobra v1.1.3 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.0 // indirect + github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80 // indirect github.com/xdg-go/pbkdf2 v1.0.0 // indirect github.com/xdg-go/scram v1.0.2 // indirect github.com/xdg-go/stringprep v1.0.2 // indirect diff --git a/go.sum b/go.sum index a5ef4e76b78..4406f76bd1c 100644 --- a/go.sum +++ b/go.sum @@ -263,6 +263,8 @@ github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFP github.com/elastic/go-elasticsearch/v7 v7.15.1 h1:Wd8RLHb5D8xPBU8vGlnLXyflkso9G+rCmsXjqH8LLQQ= github.com/elastic/go-elasticsearch/v7 v7.15.1/go.mod h1:OJ4wdbtDNk5g503kvlHLyErCgQwwzmDtaFC4XyOxXA4= github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= +github.com/elazarl/goproxy v0.0.0-20210801061803-8e322dfb79c4/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= +github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8= github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emicklei/go-restful v2.15.0+incompatible h1:8KpYO/Xl/ZudZs5RNOEhWMBY4hmzlZhhRd9cu+jrZP4= @@ -454,6 +456,8 @@ github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-github/v27 v27.0.6/go.mod h1:/0Gr8pJ55COkmv+S/yPKCczSkUPIM/LnFyubufRNIS0= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= +github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= @@ -542,6 +546,8 @@ github.com/hashicorp/go-plugin v1.4.3/go.mod h1:5fGEH17QVwTTcR0zV7yhDPLLmFX9YSZ3 github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= github.com/hashicorp/go-retryablehttp v0.6.6 h1:HJunrbHTDDbBb/ay4kxa1n+dLmttUlnP3V9oNE4hmsM= github.com/hashicorp/go-retryablehttp v0.6.6/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= +github.com/hashicorp/go-retryablehttp v0.7.0 h1:eu1EI/mbirUgP5C8hVsTNaGZreBDlYiwC1FZWkvQPQ4= +github.com/hashicorp/go-retryablehttp v0.7.0/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= @@ -735,6 +741,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8m github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +github.com/newrelic/newrelic-client-go v0.68.3 h1:PUGGKNakTV75+EG6tiiUvun8yqLzYr9ou9H9w0QiW+E= +github.com/newrelic/newrelic-client-go v0.68.3/go.mod h1:VXjhsfui0rvhM9cVwnKwlidF8NbXlHZvh63ZKi6fImA= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= @@ -822,6 +830,7 @@ github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= @@ -888,6 +897,8 @@ github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1 github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802 h1:uruHq4dN7GR16kFc5fp3d1RIYzJW5onx8Ybykw2YQFA= github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80 h1:nrZ3ySNYwJbSpD6ce9duiP+QkD3JuLCcWkdaehUS/3Y= +github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80/go.mod h1:iFyPdL66DjUD96XmzVL3ZntbzcflLnznH0fr99w5VqE= github.com/tsenart/go-tsz v0.0.0-20180814232043-cdeb9e1e981e/go.mod h1:SWZznP1z5Ki7hDT2ioqiFKEse8K9tU2OUvaRI0NeGQo= github.com/tsenart/vegeta/v12 v12.8.4/go.mod h1:ZiJtwLn/9M4fTPdMY7bdbIeyNeFVE8/AHbWFqCsUuho= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= From 06ee0ead5a7155293a8fc1fdb3ee74c705351776 Mon Sep 17 00:00:00 2001 From: Marcelo Bartsch Date: Tue, 7 Dec 2021 21:14:50 +0100 Subject: [PATCH 09/17] Update a variable name to make more sense of it use Signed-off-by: Marcelo Bartsch --- tests/scalers/new-relic.test.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/scalers/new-relic.test.ts b/tests/scalers/new-relic.test.ts index 76616e7697a..ccb1b7697de 100644 --- a/tests/scalers/new-relic.test.ts +++ b/tests/scalers/new-relic.test.ts @@ -44,9 +44,10 @@ test.before(t => { sh.exec(`kubectl create namespace ${newRelicNamespace}`) sh.exec(`helm repo add ${newRelicRepoName} ${newRelicRepoUrl}`) - let clusterStatus = sh.exec(`helm upgrade --install --set global.cluster=${kuberneteClusterName} --set prometheus.enabled=true --set ksm.enabled=true --set global.lowDataMode=true --set global.licenseKey=${newRelicLicenseKey} --timeout 600s --set ksm.enabled=true --set logging.enabled=true --namespace ${newRelicNamespace} nri-keda ${newRelicHelmPackageName}`).code + let helmInstallStatus = sh.exec(`helm upgrade --install --set global.cluster=${kuberneteClusterName} --set prometheus.enabled=true --set ksm.enabled=true --set global.lowDataMode=true --set global.licenseKey=${newRelicLicenseKey} --timeout 600s --set logging.enabled=false --set ksm.enabled=true --set logging.enabled=true --namespace ${newRelicNamespace} nri-keda ${newRelicHelmPackageName}`).code + sh.echo(`${helmInstallStatus}`) t.is(0, - clusterStatus, + helmInstallStatus, 'creating a New Relic Bundle Install should work.' ) From 6c122b1c37e829783f1248087dcb0c0634a38605 Mon Sep 17 00:00:00 2001 From: Marcelo Bartsch Date: Tue, 7 Dec 2021 21:19:00 +0100 Subject: [PATCH 10/17] fix imports Signed-off-by: Marcelo Bartsch --- pkg/scalers/newrelic_scaler.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/scalers/newrelic_scaler.go b/pkg/scalers/newrelic_scaler.go index db21f3d561b..4d7224d391d 100644 --- a/pkg/scalers/newrelic_scaler.go +++ b/pkg/scalers/newrelic_scaler.go @@ -2,12 +2,12 @@ package scalers import ( "context" + "crypto/md5" "encoding/hex" "fmt" "log" "strconv" - "crypto/md5" kedautil "github.com/kedacore/keda/v2/pkg/util" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" From a925013582ca4829debe3b4ca5c4447954b4de75 Mon Sep 17 00:00:00 2001 From: Marcelo Bartsch Date: Tue, 7 Dec 2021 21:42:10 +0100 Subject: [PATCH 11/17] add helm repo update to test Signed-off-by: Marcelo Bartsch --- tests/scalers/new-relic.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/scalers/new-relic.test.ts b/tests/scalers/new-relic.test.ts index ccb1b7697de..39f7381962e 100644 --- a/tests/scalers/new-relic.test.ts +++ b/tests/scalers/new-relic.test.ts @@ -43,7 +43,7 @@ test.before(t => { } sh.exec(`kubectl create namespace ${newRelicNamespace}`) sh.exec(`helm repo add ${newRelicRepoName} ${newRelicRepoUrl}`) - + sh.exec(`helm repo update`) let helmInstallStatus = sh.exec(`helm upgrade --install --set global.cluster=${kuberneteClusterName} --set prometheus.enabled=true --set ksm.enabled=true --set global.lowDataMode=true --set global.licenseKey=${newRelicLicenseKey} --timeout 600s --set logging.enabled=false --set ksm.enabled=true --set logging.enabled=true --namespace ${newRelicNamespace} nri-keda ${newRelicHelmPackageName}`).code sh.echo(`${helmInstallStatus}`) t.is(0, From 3238c9115f9ddc3fe674c50b74f757428d360e56 Mon Sep 17 00:00:00 2001 From: Marcelo Bartsch Date: Wed, 8 Dec 2021 10:33:10 +0100 Subject: [PATCH 12/17] another fix to e2e test. Make use of variables for helm package repo instead of hardcoded it (Thanks Jorge!) Split long lines on the test to make more easy to ready and catch errors Signed-off-by: Marcelo Bartsch --- tests/scalers/new-relic.test.ts | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/tests/scalers/new-relic.test.ts b/tests/scalers/new-relic.test.ts index 39f7381962e..6dc792cd6ae 100644 --- a/tests/scalers/new-relic.test.ts +++ b/tests/scalers/new-relic.test.ts @@ -25,8 +25,8 @@ const newRelicAccountId = process.env['NEWRELIC_ACCOUNT_ID'] const testNamespace = 'new-relic-test' const newRelicNamespace = 'new-relic' const newRelicRepoUrl = 'https://helm-charts.newrelic.com' -const newRelicRepoName = 'new-relic' -const newRelicHelmPackageName = 'newrelic/nri-bundle' +const newRelicHelmRepoName = 'new-relic' +const newRelicHelmPackageName = 'nri-bundle' const newRelicLicenseKey = process.env['NEWRELIC_LICENSE'] const kuberneteClusterName = 'keda-new-relic' @@ -42,9 +42,20 @@ test.before(t => { t.fail('NEWRELIC_ACCOUNT_ID environment variable is required for newrelic tests tests') } sh.exec(`kubectl create namespace ${newRelicNamespace}`) - sh.exec(`helm repo add ${newRelicRepoName} ${newRelicRepoUrl}`) + sh.exec(`helm repo add ${newRelicHelmRepoName} ${newRelicRepoUrl}`) sh.exec(`helm repo update`) - let helmInstallStatus = sh.exec(`helm upgrade --install --set global.cluster=${kuberneteClusterName} --set prometheus.enabled=true --set ksm.enabled=true --set global.lowDataMode=true --set global.licenseKey=${newRelicLicenseKey} --timeout 600s --set logging.enabled=false --set ksm.enabled=true --set logging.enabled=true --namespace ${newRelicNamespace} nri-keda ${newRelicHelmPackageName}`).code + let helmInstallStatus = sh.exec(`helm upgrade \ + --install --set global.cluster=${kuberneteClusterName} \ + --set prometheus.enabled=true \ + --set ksm.enabled=true \ + --set global.lowDataMode=true \ + --set global.licenseKey=${newRelicLicenseKey} \ + --timeout 600s \ + --set logging.enabled=false \ + --set ksm.enabled=true \ + --set logging.enabled=true \ + --namespace ${newRelicNamespace} \ + nri-keda ${newRelicHelmRepoName}/${newRelicHelmPackageName}`).code sh.echo(`${helmInstallStatus}`) t.is(0, helmInstallStatus, @@ -53,7 +64,9 @@ test.before(t => { sh.config.silent = true const tmpFile = tmp.fileSync() - fs.writeFileSync(tmpFile.name, deployYaml.replace('{{NEWRELIC_API_KEY}}', Buffer.from(newRelicApiKey).toString('base64')).replace('{{NEWRELIC_ACCOUNT_ID}}',newRelicAccountId)) + fs.writeFileSync(tmpFile.name, deployYaml.replace('{{NEWRELIC_API_KEY}}', + Buffer.from(newRelicApiKey).toString('base64')) + .replace('{{NEWRELIC_ACCOUNT_ID}}',newRelicAccountId)) sh.exec(`kubectl create namespace ${testNamespace}`) sh.exec(`cp ${tmpFile.name} /tmp/paso.yaml`) t.is( @@ -62,7 +75,8 @@ test.before(t => { 'creating a deployment should work.' ) for (let i = 0; i < 10; i++) { - const readyReplicaCount = sh.exec(`kubectl get deployment.apps/test-app --namespace ${testNamespace} -o jsonpath="{.status.readyReplicas}"`).stdout + const readyReplicaCount = sh.exec(`kubectl get deployment.apps/test-app \ + --namespace ${testNamespace} -o jsonpath="{.status.readyReplicas}"`).stdout if (readyReplicaCount != '1') { sh.exec('sleep 2s') } @@ -135,6 +149,7 @@ test.serial(`Deployment should scale to 5 (the max) with HTTP Requests exceeding test.after.always.cb('clean up newrelic resources', t => { sh.exec(`helm delete --namespace ${newRelicNamespace} nri-keda`) + sh.exec(`helm repo rm ${newRelicHelmRepoName}`) sh.exec(`kubectl delete namespace ${newRelicNamespace} --force`) sh.exec(`kubectl delete namespace ${testNamespace} --force`) t.end() From d9470daca68bfd1f01afe45fa3dfa3bbbcb021b5 Mon Sep 17 00:00:00 2001 From: Marcelo Bartsch Date: Wed, 8 Dec 2021 10:40:02 +0100 Subject: [PATCH 13/17] added region as parameter to the e2e test Signed-off-by: Marcelo Bartsch --- tests/scalers/new-relic.test.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/tests/scalers/new-relic.test.ts b/tests/scalers/new-relic.test.ts index 6dc792cd6ae..6a2ecb7d245 100644 --- a/tests/scalers/new-relic.test.ts +++ b/tests/scalers/new-relic.test.ts @@ -29,7 +29,7 @@ const newRelicHelmRepoName = 'new-relic' const newRelicHelmPackageName = 'nri-bundle' const newRelicLicenseKey = process.env['NEWRELIC_LICENSE'] const kuberneteClusterName = 'keda-new-relic' - +let newRelicRegion = process.env['NEWRELIC_REGION'] test.before(t => { if (!newRelicApiKey) { @@ -41,6 +41,9 @@ test.before(t => { if (!newRelicAccountId) { t.fail('NEWRELIC_ACCOUNT_ID environment variable is required for newrelic tests tests') } + if (!newRelicRegion) { + newRelicRegion = 'EU' + } sh.exec(`kubectl create namespace ${newRelicNamespace}`) sh.exec(`helm repo add ${newRelicHelmRepoName} ${newRelicRepoUrl}`) sh.exec(`helm repo update`) @@ -64,9 +67,11 @@ test.before(t => { sh.config.silent = true const tmpFile = tmp.fileSync() - fs.writeFileSync(tmpFile.name, deployYaml.replace('{{NEWRELIC_API_KEY}}', - Buffer.from(newRelicApiKey).toString('base64')) - .replace('{{NEWRELIC_ACCOUNT_ID}}',newRelicAccountId)) + fs.writeFileSync(tmpFile.name, deployYaml + .replace('{{NEWRELIC_API_KEY}}', Buffer.from(newRelicApiKey).toString('base64')) + .replace('{{NEWRELIC_ACCOUNT_ID}}', newRelicAccountId) + .replace('{{NEWRELIC_REGION}}', newRelicRegion) + ) sh.exec(`kubectl create namespace ${testNamespace}`) sh.exec(`cp ${tmpFile.name} /tmp/paso.yaml`) t.is( @@ -265,6 +270,7 @@ spec: - type: new-relic metadata: account: '{{NEWRELIC_ACCOUNT_ID}}' + region: '{{NEWRELIC_REGION}}' threshold: '100' nrql: SELECT average(\`http_requests_total\`) FROM Metric where serviceName='test-app' and namespaceName='new-relic-test' since 60 seconds ago authenticationRef: From 6ed8d22c78023081fa6a04250847eca1415720b6 Mon Sep 17 00:00:00 2001 From: Marcelo Bartsch Date: Wed, 8 Dec 2021 11:34:15 +0100 Subject: [PATCH 14/17] update CHANGELOG.md Signed-off-by: Marcelo Bartsch --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 072f1d7b55d..a5797f2836d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ ### New +- Add New Relic Scaler ([#2387](https://github.com/kedacore/keda/pull/2387)) - TODO ([#XXX](https://github.com/kedacore/keda/pull/XXX)) ### Improvements From 8aa68e428c30188edd627e2bf7d34135094dda81 Mon Sep 17 00:00:00 2001 From: Marcelo Bartsch Date: Thu, 9 Dec 2021 12:28:16 +0100 Subject: [PATCH 15/17] Update pkg/scalers/newrelic_scaler.go Co-authored-by: Zbynek Roubalik <726523+zroubalik@users.noreply.github.com> Signed-off-by: Marcelo Bartsch --- pkg/scalers/newrelic_scaler.go | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/pkg/scalers/newrelic_scaler.go b/pkg/scalers/newrelic_scaler.go index 4d7224d391d..331a2a57b02 100644 --- a/pkg/scalers/newrelic_scaler.go +++ b/pkg/scalers/newrelic_scaler.go @@ -8,17 +8,16 @@ import ( "log" "strconv" - kedautil "github.com/kedacore/keda/v2/pkg/util" + "github.com/newrelic/newrelic-client-go/newrelic" + "github.com/newrelic/newrelic-client-go/pkg/nrdb" + v2beta2 "k8s.io/api/autoscaling/v2beta2" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/metrics/pkg/apis/external_metrics" - - v2beta2 "k8s.io/api/autoscaling/v2beta2" "k8s.io/apimachinery/pkg/labels" + "k8s.io/metrics/pkg/apis/external_metrics" logf "sigs.k8s.io/controller-runtime/pkg/log" - "github.com/newrelic/newrelic-client-go/newrelic" - "github.com/newrelic/newrelic-client-go/pkg/nrdb" + kedautil "github.com/kedacore/keda/v2/pkg/util" ) const ( From 01ce224ee50eb5822932a6d0743701d8763cdba6 Mon Sep 17 00:00:00 2001 From: Marcelo Bartsch Date: Thu, 9 Dec 2021 14:00:32 +0100 Subject: [PATCH 16/17] Remove metricName from scaler Rename noDataErr to noDataError Move 'new-relic' scaler name to a const so don't repeat the string Signed-off-by: Marcelo Bartsch --- pkg/scalers/newrelic_scaler.go | 55 +++++++++++------------------ pkg/scalers/newrelic_scaler_test.go | 28 +++++++-------- 2 files changed, 35 insertions(+), 48 deletions(-) diff --git a/pkg/scalers/newrelic_scaler.go b/pkg/scalers/newrelic_scaler.go index 331a2a57b02..ff9d4f3f0d0 100644 --- a/pkg/scalers/newrelic_scaler.go +++ b/pkg/scalers/newrelic_scaler.go @@ -2,8 +2,6 @@ package scalers import ( "context" - "crypto/md5" - "encoding/hex" "fmt" "log" "strconv" @@ -21,13 +19,13 @@ import ( ) const ( - account = "account" - queryKey = "queryKey" - region = "region" - metricName = "metricName" - nrql = "nrql" - threshold = "threshold" - noDataErr = "noDataErr" + account = "account" + queryKey = "queryKey" + region = "region" + nrql = "nrql" + threshold = "threshold" + noDataError = "noDataError" + scalerName = "new-relic" ) type newrelicScaler struct { @@ -39,19 +37,18 @@ type newrelicMetadata struct { account int region string queryKey string - metricName string - noDataErr bool + noDataError bool nrql string threshold int scalerIndex int } -var newrelicLog = logf.Log.WithName("new-relic_scaler") +var newrelicLog = logf.Log.WithName(fmt.Sprintf("%s_scaler", scalerName)) func NewNewRelicScaler(config *ScalerConfig) (Scaler, error) { meta, err := parseNewRelicMetadata(config) if err != nil { - return nil, fmt.Errorf("error parsing new-relic metadata: %s", err) + return nil, fmt.Errorf("error parsing %s metadata: %s", scalerName, err) } nrClient, err := newrelic.New( @@ -95,10 +92,6 @@ func parseNewRelicMetadata(config *ScalerConfig) (*newrelicMetadata, error) { return nil, fmt.Errorf("no %s given", queryKey) } - if val, ok := config.TriggerMetadata[metricName]; ok && val != "" { - meta.metricName = val - } - meta.region, err = GetFromAuthOrMeta(config, region) if err != nil { meta.region = "US" @@ -117,14 +110,14 @@ func parseNewRelicMetadata(config *ScalerConfig) (*newrelicMetadata, error) { // If Query Return an Empty Data , shall we treat it as an error or not // default is NO error is returned when query result is empty/no data - if val, ok := config.TriggerMetadata[noDataErr]; ok { - noDataErr, err := strconv.ParseBool(val) + if val, ok := config.TriggerMetadata[noDataError]; ok { + noDataError, err := strconv.ParseBool(val) if err != nil { - return nil, fmt.Errorf("noDataErr has invalid value") + return nil, fmt.Errorf("noDataError has invalid value") } - meta.noDataErr = noDataErr + meta.noDataError = noDataError } else { - meta.noDataErr = false + meta.noDataError = false } meta.scalerIndex = config.ScalerIndex return &meta, nil @@ -133,7 +126,7 @@ func parseNewRelicMetadata(config *ScalerConfig) (*newrelicMetadata, error) { func (s *newrelicScaler) IsActive(ctx context.Context) (bool, error) { val, err := s.ExecuteNewRelicQuery(ctx) if err != nil { - newrelicLog.Error(err, "error executing newrelic query") + newrelicLog.Error(err, "error executing NRQL") return false, err } return val > 0, nil @@ -147,7 +140,7 @@ func (s *newrelicScaler) ExecuteNewRelicQuery(ctx context.Context) (float64, err nrdbQuery := nrdb.NRQL(s.metadata.nrql) resp, err := s.nrClient.Nrdb.QueryWithContext(ctx, s.metadata.account, nrdbQuery) if err != nil { - return 0, fmt.Errorf("error running NerdGraph query %s (%s)", s.metadata.nrql, err.Error()) + return 0, fmt.Errorf("error running NRQL %s (%s)", s.metadata.nrql, err.Error()) } // Only use the first result from the query, as the query should not be multi row for _, v := range resp.Results[0] { @@ -156,7 +149,7 @@ func (s *newrelicScaler) ExecuteNewRelicQuery(ctx context.Context) (float64, err return val, nil } } - if s.metadata.noDataErr { + if s.metadata.noDataError { return 0, fmt.Errorf("query return no results %s", s.metadata.nrql) } return 0, nil @@ -165,7 +158,7 @@ func (s *newrelicScaler) ExecuteNewRelicQuery(ctx context.Context) (float64, err func (s *newrelicScaler) GetMetrics(ctx context.Context, metricName string, metricSelector labels.Selector) ([]external_metrics.ExternalMetricValue, error) { val, err := s.ExecuteNewRelicQuery(ctx) if err != nil { - newrelicLog.Error(err, "error executing New Relic query") + newrelicLog.Error(err, "error executing NRQL query") return []external_metrics.ExternalMetricValue{}, err } @@ -180,14 +173,8 @@ func (s *newrelicScaler) GetMetrics(ctx context.Context, metricName string, metr func (s *newrelicScaler) GetMetricSpecForScaling(context.Context) []v2beta2.MetricSpec { targetMetricValue := resource.NewQuantity(int64(s.metadata.threshold), resource.DecimalSI) - var metricName string - if s.metadata.metricName == "" { - // TODO: Find a better way to generate a small string based on the nrql string, or maybe just use UUID? - hash := md5.Sum([]byte(s.metadata.nrql)) - metricName = kedautil.NormalizeString(fmt.Sprintf("new-relic-%s", hex.EncodeToString(hash[:]))) - } else { - metricName = kedautil.NormalizeString(fmt.Sprintf("new-relic-%s", s.metadata.metricName)) - } + metricName := kedautil.NormalizeString(scalerName) + externalMetric := &v2beta2.ExternalMetricSource{ Metric: v2beta2.MetricIdentifier{ Name: GenerateMetricNameWithIndex(s.metadata.scalerIndex, metricName), diff --git a/pkg/scalers/newrelic_scaler_test.go b/pkg/scalers/newrelic_scaler_test.go index 9e8332dc641..7a0a40a91eb 100644 --- a/pkg/scalers/newrelic_scaler_test.go +++ b/pkg/scalers/newrelic_scaler_test.go @@ -20,9 +20,9 @@ type newrelicMetricIdentifier struct { var testNewRelicMetadata = []parseNewRelicMetadataTestData{ {map[string]string{}, true}, // all properly formed - {map[string]string{"account": "0", "metricName": "results", "threshold": "100", "queryKey": "somekey", "nrql": "SELECT average(cpuUsedCores) as result FROM K8sContainerSample WHERE containerName='coredns'"}, false}, + {map[string]string{"account": "0", "threshold": "100", "queryKey": "somekey", "nrql": "SELECT average(cpuUsedCores) as result FROM K8sContainerSample WHERE containerName='coredns'"}, false}, // all properly formed - {map[string]string{"account": "0", "region": "EU", "metricName": "results", "threshold": "100", "queryKey": "somekey", "nrql": "SELECT average(cpuUsedCores) as result FROM K8sContainerSample WHERE containerName='coredns'"}, false}, + {map[string]string{"account": "0", "region": "EU", "threshold": "100", "queryKey": "somekey", "nrql": "SELECT average(cpuUsedCores) as result FROM K8sContainerSample WHERE containerName='coredns'"}, false}, // account as String {map[string]string{"account": "ABC", "metricName": "results", "threshold": "100", "queryKey": "somekey", "nrql": "SELECT average(cpuUsedCores) as result FROM K8sContainerSample WHERE containerName='coredns'"}, true}, // missing account @@ -30,23 +30,23 @@ var testNewRelicMetadata = []parseNewRelicMetadataTestData{ // missing metricName {map[string]string{"account": "0", "threshold": "100", "queryKey": "somekey", "nrql": "SELECT average(cpuUsedCores) as result FROM K8sContainerSample WHERE containerName='coredns'"}, false}, // malformed threshold - {map[string]string{"account": "0", "metricName": "results", "threshold": "one", "queryKey": "somekey", "nrql": "SELECT average(cpuUsedCores) as result FROM K8sContainerSample WHERE containerName='coredns'"}, true}, + {map[string]string{"account": "0", "threshold": "one", "queryKey": "somekey", "nrql": "SELECT average(cpuUsedCores) as result FROM K8sContainerSample WHERE containerName='coredns'"}, true}, // missing threshold - {map[string]string{"account": "0", "metricName": "results", "queryKey": "somekey", "nrql": "SELECT average(cpuUsedCores) as result FROM K8sContainerSample WHERE containerName='coredns'"}, true}, + {map[string]string{"account": "0", "queryKey": "somekey", "nrql": "SELECT average(cpuUsedCores) as result FROM K8sContainerSample WHERE containerName='coredns'"}, true}, // missing query - {map[string]string{"account": "0", "metricName": "results", "threshold": "100", "queryKey": "somekey"}, true}, - // noDataErr invalid value - {map[string]string{"account": "0", "metricName": "results", "threshold": "100", "queryKey": "somekey", "noDataErr": "invalid", "nrql": "SELECT average(cpuUsedCores) as result FROM K8sContainerSample WHERE containerName='coredns'"}, true}, - // noDataErr valid value - {map[string]string{"account": "0", "metricName": "results", "threshold": "100", "queryKey": "somekey", "noDataErr": "true", "nrql": "SELECT average(cpuUsedCores) as result FROM K8sContainerSample WHERE containerName='coredns'"}, false}, - {map[string]string{"account": "0", "metricName": "results", "threshold": "100", "queryKey": "somekey", "noDataErr": "false", "nrql": "SELECT average(cpuUsedCores) as result FROM K8sContainerSample WHERE containerName='coredns'"}, false}, - {map[string]string{"account": "0", "metricName": "results", "threshold": "100", "queryKey": "somekey", "noDataErr": "0", "nrql": "SELECT average(cpuUsedCores) as result FROM K8sContainerSample WHERE containerName='coredns'"}, false}, - {map[string]string{"account": "0", "metricName": "results", "threshold": "100", "queryKey": "somekey", "noDataErr": "1", "nrql": "SELECT average(cpuUsedCores) as result FROM K8sContainerSample WHERE containerName='coredns'"}, false}, + {map[string]string{"account": "0", "threshold": "100", "queryKey": "somekey"}, true}, + // noDataError invalid value + {map[string]string{"account": "0", "threshold": "100", "queryKey": "somekey", "noDataError": "invalid", "nrql": "SELECT average(cpuUsedCores) as result FROM K8sContainerSample WHERE containerName='coredns'"}, true}, + // noDataError valid value + {map[string]string{"account": "0", "threshold": "100", "queryKey": "somekey", "noDataError": "true", "nrql": "SELECT average(cpuUsedCores) as result FROM K8sContainerSample WHERE containerName='coredns'"}, false}, + {map[string]string{"account": "0", "threshold": "100", "queryKey": "somekey", "noDataError": "false", "nrql": "SELECT average(cpuUsedCores) as result FROM K8sContainerSample WHERE containerName='coredns'"}, false}, + {map[string]string{"account": "0", "threshold": "100", "queryKey": "somekey", "noDataError": "0", "nrql": "SELECT average(cpuUsedCores) as result FROM K8sContainerSample WHERE containerName='coredns'"}, false}, + {map[string]string{"account": "0", "threshold": "100", "queryKey": "somekey", "noDataError": "1", "nrql": "SELECT average(cpuUsedCores) as result FROM K8sContainerSample WHERE containerName='coredns'"}, false}, } var newrelicMetricIdentifiers = []newrelicMetricIdentifier{ - {&testNewRelicMetadata[1], 0, "s0-new-relic-results"}, - {&testNewRelicMetadata[1], 1, "s1-new-relic-results"}, + {&testNewRelicMetadata[1], 0, "s0-new-relic"}, + {&testNewRelicMetadata[1], 1, "s1-new-relic"}, } func TestNewRelicParseMetadata(t *testing.T) { From 22519f7d34422af43e01e3213a62e99ab062f352 Mon Sep 17 00:00:00 2001 From: Marcelo Bartsch Date: Mon, 3 Jan 2022 13:53:44 +0100 Subject: [PATCH 17/17] remove metricName from tests Signed-off-by: Marcelo Bartsch --- pkg/scalers/newrelic_scaler_test.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/pkg/scalers/newrelic_scaler_test.go b/pkg/scalers/newrelic_scaler_test.go index 7a0a40a91eb..66d1f236c69 100644 --- a/pkg/scalers/newrelic_scaler_test.go +++ b/pkg/scalers/newrelic_scaler_test.go @@ -24,11 +24,9 @@ var testNewRelicMetadata = []parseNewRelicMetadataTestData{ // all properly formed {map[string]string{"account": "0", "region": "EU", "threshold": "100", "queryKey": "somekey", "nrql": "SELECT average(cpuUsedCores) as result FROM K8sContainerSample WHERE containerName='coredns'"}, false}, // account as String - {map[string]string{"account": "ABC", "metricName": "results", "threshold": "100", "queryKey": "somekey", "nrql": "SELECT average(cpuUsedCores) as result FROM K8sContainerSample WHERE containerName='coredns'"}, true}, + {map[string]string{"account": "ABC", "threshold": "100", "queryKey": "somekey", "nrql": "SELECT average(cpuUsedCores) as result FROM K8sContainerSample WHERE containerName='coredns'"}, true}, // missing account - {map[string]string{"metricName": "results", "threshold": "100", "queryKey": "somekey", "nrql": "SELECT average(cpuUsedCores) as result FROM K8sContainerSample WHERE containerName='coredns'"}, true}, - // missing metricName - {map[string]string{"account": "0", "threshold": "100", "queryKey": "somekey", "nrql": "SELECT average(cpuUsedCores) as result FROM K8sContainerSample WHERE containerName='coredns'"}, false}, + {map[string]string{"threshold": "100", "queryKey": "somekey", "nrql": "SELECT average(cpuUsedCores) as result FROM K8sContainerSample WHERE containerName='coredns'"}, true}, // malformed threshold {map[string]string{"account": "0", "threshold": "one", "queryKey": "somekey", "nrql": "SELECT average(cpuUsedCores) as result FROM K8sContainerSample WHERE containerName='coredns'"}, true}, // missing threshold