pkg/metrics/config_test.go

969 lines
30 KiB
Go

/*
Copyright 2018 The Knative Authors
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.
*/
package metrics
import (
"context"
"os"
"path"
"testing"
"time"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"go.opencensus.io/stats"
"go.opencensus.io/stats/view"
. "knative.dev/pkg/logging/testing"
"knative.dev/pkg/metrics/metricstest"
corev1 "k8s.io/api/core/v1"
)
// TODO UTs should move to eventing and serving, as appropriate.
// See https://github.com/knative/pkg/issues/608
const (
servingDomain = "knative.dev/serving"
internalServingDomain = "knative.dev/internal/serving"
eventingDomain = "knative.dev/eventing"
internalEventingDomain = "knative.dev/internal/eventing"
customSubDomain = "test.domain"
testComponent = "testComponent"
testProj = "test-project"
anotherProj = "another-project"
)
var (
errorTests = []struct {
name string
ops ExporterOptions
expectedErr string
}{{
name: "empty config",
ops: ExporterOptions{
Domain: servingDomain,
Component: testComponent,
},
expectedErr: "metrics config map cannot be empty",
}, {
name: "unsupportedBackend",
ops: ExporterOptions{
ConfigMap: map[string]string{
"metrics.backend-destination": "unsupported",
"metrics.stackdriver-project-id": testProj,
},
Domain: servingDomain,
Component: testComponent,
},
expectedErr: "unsupported metrics backend value \"unsupported\"",
}, {
name: "emptyDomain",
ops: ExporterOptions{
ConfigMap: map[string]string{
"metrics.backend-destination": "prometheus",
},
Domain: "",
Component: testComponent,
},
expectedErr: "metrics domain cannot be empty",
}, {
name: "invalidComponent",
ops: ExporterOptions{
ConfigMap: map[string]string{
"metrics.backend-destination": "opencensus",
},
Domain: servingDomain,
Component: "",
},
expectedErr: "metrics component name cannot be empty",
}, {
name: "invalidReportingPeriod",
ops: ExporterOptions{
ConfigMap: map[string]string{
"metrics.backend-destination": "opencensus",
"metrics.reporting-period-seconds": "test",
},
Domain: servingDomain,
Component: testComponent,
},
expectedErr: "invalid metrics.reporting-period-seconds value \"test\"",
}, {
name: "invalidOpenCensusSecuritySetting",
ops: ExporterOptions{
ConfigMap: map[string]string{
"metrics.backend-destination": "opencensus",
"metrics.opencensus-require-tls": "yep",
},
Domain: servingDomain,
Component: testComponent,
},
expectedErr: "invalid metrics.opencensus-require-tls value \"yep\"",
}, {
name: "invalidAllowStackdriverCustomMetrics",
ops: ExporterOptions{
ConfigMap: map[string]string{
"metrics.backend-destination": "stackdriver",
"metrics.allow-stackdriver-custom-metrics": "test",
},
Domain: servingDomain,
Component: testComponent,
},
expectedErr: "invalid metrics.allow-stackdriver-custom-metrics value \"test\"",
}, {
name: "tooSmallPrometheusPort",
ops: ExporterOptions{
ConfigMap: map[string]string{
"metrics.backend-destination": "prometheus",
},
Domain: servingDomain,
Component: testComponent,
PrometheusPort: 1023,
},
expectedErr: "invalid port 1023, should between 1024 and 65535",
}, {
name: "tooBigPrometheusPort",
ops: ExporterOptions{
ConfigMap: map[string]string{
"metrics.backend-destination": "prometheus",
},
Domain: servingDomain,
Component: testComponent,
PrometheusPort: 65536,
},
expectedErr: "invalid port 65536, should between 1024 and 65535",
}}
successTests = []struct {
name string
ops ExporterOptions
expectedConfig metricsConfig
expectedNewExporter bool // Whether the config requires a new exporter compared to previous test case
}{
// Note the first unit test is skipped in TestUpdateExporterFromConfigMap since
// unit test does not have application default credentials.
{
name: "stackdriverProjectIDMissing",
ops: ExporterOptions{
ConfigMap: map[string]string{
"metrics.backend-destination": "stackdriver",
},
Domain: servingDomain,
Component: testComponent,
},
expectedConfig: metricsConfig{
domain: servingDomain,
component: testComponent,
backendDestination: Stackdriver,
reportingPeriod: 60 * time.Second,
isStackdriverBackend: true,
stackdriverMetricTypePrefix: path.Join(servingDomain, testComponent),
stackdriverCustomMetricTypePrefix: path.Join(customMetricTypePrefix, defaultCustomMetricSubDomain, testComponent),
},
expectedNewExporter: true,
}, {
name: "backendKeyMissing",
ops: ExporterOptions{
ConfigMap: map[string]string{},
Domain: servingDomain,
Component: testComponent,
},
expectedConfig: metricsConfig{
domain: servingDomain,
component: testComponent,
backendDestination: Prometheus,
reportingPeriod: 5 * time.Second,
prometheusPort: defaultPrometheusPort,
},
expectedNewExporter: true,
}, {
name: "validStackdriver",
ops: ExporterOptions{
ConfigMap: map[string]string{
"metrics.backend-destination": "stackdriver",
"metrics.stackdriver-project-id": anotherProj,
"metrics.stackdriver-gcp-location": "us-west1",
"metrics.stackdriver-cluster-name": "cluster",
"metrics.stackdriver-use-secret": "true",
},
Domain: servingDomain,
Component: testComponent,
},
expectedConfig: metricsConfig{
domain: servingDomain,
component: testComponent,
backendDestination: Stackdriver,
reportingPeriod: 60 * time.Second,
isStackdriverBackend: true,
stackdriverMetricTypePrefix: path.Join(servingDomain, testComponent),
stackdriverCustomMetricTypePrefix: path.Join(customMetricTypePrefix, defaultCustomMetricSubDomain, testComponent),
stackdriverClientConfig: StackdriverClientConfig{
ProjectID: anotherProj,
GCPLocation: "us-west1",
ClusterName: "cluster",
UseSecret: true,
},
},
expectedNewExporter: true,
}, {
name: "validPartialStackdriver",
ops: ExporterOptions{
ConfigMap: map[string]string{
"metrics.backend-destination": "stackdriver",
"metrics.stackdriver-project-id": anotherProj,
"metrics.stackdriver-cluster-name": "cluster",
},
Domain: servingDomain,
Component: testComponent,
},
expectedConfig: metricsConfig{
domain: servingDomain,
component: testComponent,
backendDestination: Stackdriver,
reportingPeriod: 60 * time.Second,
isStackdriverBackend: true,
stackdriverMetricTypePrefix: path.Join(servingDomain, testComponent),
stackdriverCustomMetricTypePrefix: path.Join(customMetricTypePrefix, defaultCustomMetricSubDomain, testComponent),
stackdriverClientConfig: StackdriverClientConfig{
ProjectID: anotherProj,
ClusterName: "cluster",
},
},
expectedNewExporter: true,
}, {
name: "validOpenCensusSettings",
ops: ExporterOptions{
ConfigMap: map[string]string{
"metrics.backend-destination": "opencensus",
"metrics.opencensus-address": "external-svc:55678",
"metrics.opencensus-require-tls": "true",
},
Domain: servingDomain,
Component: testComponent,
},
expectedConfig: metricsConfig{
domain: servingDomain,
component: testComponent,
backendDestination: OpenCensus,
collectorAddress: "external-svc:55678",
requireSecure: true,
},
expectedNewExporter: true,
}, {
name: "validPrometheus",
ops: ExporterOptions{
ConfigMap: map[string]string{
"metrics.backend-destination": "prometheus",
},
Domain: servingDomain,
Component: testComponent,
},
expectedConfig: metricsConfig{
domain: servingDomain,
component: testComponent,
backendDestination: Prometheus,
reportingPeriod: 5 * time.Second,
prometheusPort: defaultPrometheusPort,
},
expectedNewExporter: true,
}, {
name: "validCapitalStackdriver",
ops: ExporterOptions{
ConfigMap: map[string]string{
"metrics.backend-destination": "Stackdriver",
"metrics.stackdriver-project-id": testProj,
},
Domain: servingDomain,
Component: testComponent,
},
expectedConfig: metricsConfig{
domain: servingDomain,
component: testComponent,
backendDestination: Stackdriver,
reportingPeriod: 60 * time.Second,
isStackdriverBackend: true,
stackdriverMetricTypePrefix: path.Join(servingDomain, testComponent),
stackdriverCustomMetricTypePrefix: path.Join(customMetricTypePrefix, defaultCustomMetricSubDomain, testComponent),
stackdriverClientConfig: StackdriverClientConfig{
ProjectID: testProj,
},
},
expectedNewExporter: true,
}, {
name: "overriddenReportingPeriodPrometheus",
ops: ExporterOptions{
ConfigMap: map[string]string{
"metrics.backend-destination": "prometheus",
"metrics.reporting-period-seconds": "12",
},
Domain: servingDomain,
Component: testComponent,
},
expectedConfig: metricsConfig{
domain: servingDomain,
component: testComponent,
backendDestination: Prometheus,
reportingPeriod: 12 * time.Second,
prometheusPort: defaultPrometheusPort,
},
expectedNewExporter: true,
}, {
name: "overriddenReportingPeriodStackdriver",
ops: ExporterOptions{
ConfigMap: map[string]string{
"metrics.backend-destination": "stackdriver",
"metrics.stackdriver-project-id": "test2",
"metrics.reporting-period-seconds": "7",
},
Domain: servingDomain,
Component: testComponent,
},
expectedConfig: metricsConfig{
domain: servingDomain,
component: testComponent,
backendDestination: Stackdriver,
reportingPeriod: 7 * time.Second,
isStackdriverBackend: true,
stackdriverMetricTypePrefix: path.Join(servingDomain, testComponent),
stackdriverCustomMetricTypePrefix: path.Join(customMetricTypePrefix, defaultCustomMetricSubDomain, testComponent),
stackdriverClientConfig: StackdriverClientConfig{
ProjectID: "test2",
},
},
expectedNewExporter: true,
}, {
name: "overriddenReportingPeriodStackdriver2",
ops: ExporterOptions{
ConfigMap: map[string]string{
"metrics.backend-destination": "stackdriver",
"metrics.stackdriver-project-id": "test2",
"metrics.reporting-period-seconds": "3",
},
Domain: servingDomain,
Component: testComponent,
},
expectedConfig: metricsConfig{
domain: servingDomain,
component: testComponent,
backendDestination: Stackdriver,
reportingPeriod: 3 * time.Second,
isStackdriverBackend: true,
stackdriverMetricTypePrefix: path.Join(servingDomain, testComponent),
stackdriverCustomMetricTypePrefix: path.Join(customMetricTypePrefix, defaultCustomMetricSubDomain, testComponent),
stackdriverClientConfig: StackdriverClientConfig{
ProjectID: "test2",
},
},
}, {
name: "emptyReportingPeriodPrometheus",
ops: ExporterOptions{
ConfigMap: map[string]string{
"metrics.backend-destination": "prometheus",
"metrics.reporting-period-seconds": "",
},
Domain: servingDomain,
Component: testComponent,
},
expectedConfig: metricsConfig{
domain: servingDomain,
component: testComponent,
backendDestination: Prometheus,
reportingPeriod: 5 * time.Second,
prometheusPort: defaultPrometheusPort,
},
expectedNewExporter: true,
}, {
name: "emptyReportingPeriodStackdriver",
ops: ExporterOptions{
ConfigMap: map[string]string{
"metrics.backend-destination": "stackdriver",
"metrics.stackdriver-project-id": "test2",
"metrics.reporting-period-seconds": "",
},
Domain: servingDomain,
Component: testComponent,
},
expectedConfig: metricsConfig{
domain: servingDomain,
component: testComponent,
backendDestination: Stackdriver,
reportingPeriod: 60 * time.Second,
isStackdriverBackend: true,
stackdriverMetricTypePrefix: path.Join(servingDomain, testComponent),
stackdriverCustomMetricTypePrefix: path.Join(customMetricTypePrefix, defaultCustomMetricSubDomain, testComponent),
stackdriverClientConfig: StackdriverClientConfig{
ProjectID: "test2",
},
},
expectedNewExporter: true,
}, {
name: "allowStackdriverCustomMetric",
ops: ExporterOptions{
ConfigMap: map[string]string{
"metrics.backend-destination": "stackdriver",
"metrics.stackdriver-project-id": "test2",
"metrics.reporting-period-seconds": "",
"metrics.allow-stackdriver-custom-metrics": "true",
},
Domain: servingDomain,
Component: testComponent,
},
expectedConfig: metricsConfig{
domain: servingDomain,
component: testComponent,
backendDestination: Stackdriver,
reportingPeriod: 60 * time.Second,
isStackdriverBackend: true,
stackdriverMetricTypePrefix: path.Join(servingDomain, testComponent),
stackdriverCustomMetricTypePrefix: path.Join(customMetricTypePrefix, defaultCustomMetricSubDomain, testComponent),
stackdriverClientConfig: StackdriverClientConfig{
ProjectID: "test2",
},
},
}, {
name: "allowStackdriverCustomMetric with subdomain",
ops: ExporterOptions{
ConfigMap: map[string]string{
"metrics.backend-destination": "stackdriver",
"metrics.stackdriver-project-id": "test2",
"metrics.reporting-period-seconds": "",
"metrics.stackdriver-custom-metrics-subdomain": customSubDomain,
},
Domain: servingDomain,
Component: testComponent,
},
expectedConfig: metricsConfig{
domain: servingDomain,
component: testComponent,
backendDestination: Stackdriver,
reportingPeriod: 60 * time.Second,
isStackdriverBackend: true,
stackdriverMetricTypePrefix: path.Join(servingDomain, testComponent),
stackdriverCustomMetricTypePrefix: path.Join(customMetricTypePrefix, customSubDomain, testComponent),
stackdriverClientConfig: StackdriverClientConfig{
ProjectID: "test2",
},
},
}, {
name: "overridePrometheusPort",
ops: ExporterOptions{
ConfigMap: map[string]string{
"metrics.backend-destination": "prometheus",
},
Domain: servingDomain,
Component: testComponent,
PrometheusPort: 9091,
},
expectedConfig: metricsConfig{
domain: servingDomain,
component: testComponent,
backendDestination: Prometheus,
reportingPeriod: 5 * time.Second,
prometheusPort: 9091,
},
expectedNewExporter: true,
}}
envTests = []struct {
name string
ops ExporterOptions
expectedConfig metricsConfig
}{
{
name: "stackdriverFromEnv",
ops: ExporterOptions{
ConfigMap: map[string]string{},
Domain: servingDomain,
Component: testComponent,
},
expectedConfig: metricsConfig{
domain: servingDomain,
component: testComponent,
backendDestination: Stackdriver,
reportingPeriod: 60 * time.Second,
isStackdriverBackend: true,
stackdriverMetricTypePrefix: path.Join(servingDomain, testComponent),
stackdriverCustomMetricTypePrefix: path.Join(customMetricTypePrefix, defaultCustomMetricSubDomain, testComponent),
},
}, {
name: "validPrometheus",
ops: ExporterOptions{
ConfigMap: map[string]string{"metrics.backend-destination": "prometheus"},
Domain: servingDomain,
Component: testComponent,
},
expectedConfig: metricsConfig{
domain: servingDomain,
component: testComponent,
backendDestination: Prometheus,
reportingPeriod: 5 * time.Second,
prometheusPort: defaultPrometheusPort,
},
}}
)
func TestGetMetricsConfig(t *testing.T) {
for _, test := range errorTests {
t.Run(test.name, func(t *testing.T) {
defer ClearAll()
_, err := createMetricsConfig(test.ops, TestLogger(t))
if err == nil || err.Error() != test.expectedErr {
t.Errorf("Wanted err: %v, got: %v", test.expectedErr, err)
}
})
}
for _, test := range successTests {
t.Run(test.name, func(t *testing.T) {
defer ClearAll()
mc, err := createMetricsConfig(test.ops, TestLogger(t))
if err != nil {
t.Errorf("Wanted valid config %v, got error %v", test.expectedConfig, err)
}
if diff := cmp.Diff(test.expectedConfig, *mc, cmp.AllowUnexported(*mc), cmpopts.IgnoreTypes(mc.recorder)); diff != "" {
t.Errorf("Invalid config (-want +got):\n%s", diff)
}
})
}
}
func TestGetMetricsConfig_fromEnv(t *testing.T) {
os.Setenv(defaultBackendEnvName, "stackdriver")
for _, test := range envTests {
t.Run(test.name, func(t *testing.T) {
defer ClearAll()
mc, err := createMetricsConfig(test.ops, TestLogger(t))
if err != nil {
t.Errorf("Wanted valid config %v, got error %v", test.expectedConfig, err)
}
if diff := cmp.Diff(test.expectedConfig, *mc, cmp.AllowUnexported(*mc), cmpopts.IgnoreTypes(mc.recorder)); diff != "" {
t.Errorf("Invalid config (-want +got):\n%s", diff)
}
})
}
os.Unsetenv(defaultBackendEnvName)
}
func TestIsNewExporterRequiredFromNilConfig(t *testing.T) {
setCurMetricsConfig(nil)
for _, test := range successTests {
t.Run(test.name, func(t *testing.T) {
defer ClearAll()
mc, err := createMetricsConfig(test.ops, TestLogger(t))
if err != nil {
t.Errorf("Wanted valid config %v, got error %v", test.expectedConfig, err)
}
changed := isNewExporterRequired(mc)
if changed != test.expectedNewExporter {
t.Errorf("isMetricsConfigChanged=%v wanted %v", changed, test.expectedNewExporter)
}
setCurMetricsConfig(mc)
})
}
}
func TestIsNewExporterRequired(t *testing.T) {
tests := []struct {
name string
oldConfig metricsConfig
newConfig metricsConfig
newExporterRequired bool
}{{
name: "backendPrometheusChangeStackdriverClientConfig",
oldConfig: metricsConfig{
domain: servingDomain,
component: testComponent,
backendDestination: Prometheus,
},
newConfig: metricsConfig{
domain: servingDomain,
component: testComponent,
backendDestination: Prometheus,
stackdriverClientConfig: StackdriverClientConfig{
ProjectID: testProj,
ClusterName: "cluster",
},
},
newExporterRequired: false,
}, {
name: "changeMetricsBackend",
oldConfig: metricsConfig{
domain: servingDomain,
component: testComponent,
backendDestination: Stackdriver,
reportingPeriod: 60 * time.Second,
isStackdriverBackend: true,
stackdriverMetricTypePrefix: path.Join(servingDomain, testComponent),
stackdriverCustomMetricTypePrefix: path.Join(customMetricTypePrefix, defaultCustomMetricSubDomain, testComponent),
},
newConfig: metricsConfig{
domain: servingDomain,
component: testComponent,
backendDestination: Prometheus,
reportingPeriod: 60 * time.Second,
isStackdriverBackend: true,
stackdriverMetricTypePrefix: path.Join(servingDomain, testComponent),
stackdriverCustomMetricTypePrefix: path.Join(customMetricTypePrefix, defaultCustomMetricSubDomain, testComponent),
},
newExporterRequired: true,
}, {
name: "changeComponent",
oldConfig: metricsConfig{
domain: servingDomain,
component: "component1",
},
newConfig: metricsConfig{
domain: servingDomain,
component: "component2",
},
newExporterRequired: false,
}, {
name: "backendStackdriverChangeProjectID",
oldConfig: metricsConfig{
domain: servingDomain,
component: testComponent,
backendDestination: Stackdriver,
stackdriverClientConfig: StackdriverClientConfig{
ProjectID: "proj1",
},
},
newConfig: metricsConfig{
domain: servingDomain,
component: testComponent,
backendDestination: Stackdriver,
stackdriverClientConfig: StackdriverClientConfig{
ProjectID: "proj2",
},
},
newExporterRequired: true,
}, {
name: "backendStackdriverChangeStackdriverClientConfig",
oldConfig: metricsConfig{
domain: servingDomain,
component: testComponent,
backendDestination: Stackdriver,
stackdriverClientConfig: StackdriverClientConfig{
ProjectID: testProj,
ClusterName: "cluster1",
},
},
newConfig: metricsConfig{
domain: servingDomain,
component: testComponent,
backendDestination: Stackdriver,
stackdriverClientConfig: StackdriverClientConfig{
ProjectID: testProj,
ClusterName: "cluster2",
},
},
newExporterRequired: true,
}}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
setCurMetricsConfig(&test.oldConfig)
actualNewExporterRequired := isNewExporterRequired(&test.newConfig)
if test.newExporterRequired != actualNewExporterRequired {
t.Errorf("isNewExporterRequired returned incorrect value. Expected: [%v], Got: [%v]. Old config: [%v], New config: [%v]", test.newExporterRequired, actualNewExporterRequired, test.oldConfig, test.newConfig)
}
})
}
}
func TestUpdateExporter(t *testing.T) {
setCurMetricsConfig(nil)
oldConfig := getCurMetricsConfig()
for _, test := range successTests[1:] {
t.Run(test.name, func(t *testing.T) {
defer ClearAll()
UpdateExporter(test.ops, TestLogger(t))
mConfig := getCurMetricsConfig()
if mConfig == oldConfig {
t.Error("Expected metrics config change")
}
if diff := cmp.Diff(test.expectedConfig, *mConfig, cmp.AllowUnexported(*mConfig), cmpopts.IgnoreTypes(mConfig.recorder)); diff != "" {
t.Errorf("Invalid config (-want +got):\n%s", diff)
}
oldConfig = mConfig
})
}
for _, test := range errorTests {
t.Run(test.name, func(t *testing.T) {
defer ClearAll()
UpdateExporter(test.ops, TestLogger(t))
mConfig := getCurMetricsConfig()
if mConfig != oldConfig {
t.Error("mConfig should not change")
}
})
}
}
func TestUpdateExporterFromConfigMapWithOpts(t *testing.T) {
setCurMetricsConfig(nil)
oldConfig := getCurMetricsConfig()
for _, test := range successTests[1:] {
t.Run(test.name, func(t *testing.T) {
defer ClearAll()
opts := ExporterOptions{
Component: test.ops.Component,
Domain: test.ops.Domain,
PrometheusPort: test.ops.PrometheusPort,
}
updateFunc, err := UpdateExporterFromConfigMapWithOpts(opts, TestLogger(t))
if err != nil {
t.Errorf("failed to call UpdateExporterFromConfigMapWithOpts: %v", err)
}
updateFunc(&corev1.ConfigMap{Data: test.ops.ConfigMap})
mConfig := getCurMetricsConfig()
if mConfig == oldConfig {
t.Error("Expected metrics config change")
}
if diff := cmp.Diff(test.expectedConfig, *mConfig, cmp.AllowUnexported(*mConfig), cmpopts.IgnoreTypes(mConfig.recorder)); diff != "" {
t.Errorf("Invalid config (-want +got):\n%s", diff)
}
oldConfig = mConfig
})
}
t.Run("ConfigMapSetErr", func(t *testing.T) {
defer ClearAll()
opts := ExporterOptions{
Component: testComponent,
Domain: servingDomain,
PrometheusPort: defaultPrometheusPort,
ConfigMap: map[string]string{"some": "data"},
}
_, err := UpdateExporterFromConfigMapWithOpts(opts, TestLogger(t))
if err == nil {
t.Error("got err=nil want err")
}
})
t.Run("MissingComponentErr", func(t *testing.T) {
defer ClearAll()
opts := ExporterOptions{
Component: "",
Domain: servingDomain,
PrometheusPort: defaultPrometheusPort,
}
_, err := UpdateExporterFromConfigMapWithOpts(opts, TestLogger(t))
if err == nil {
t.Error("got err=nil want err")
}
})
}
func TestUpdateExporter_doesNotCreateExporter(t *testing.T) {
setCurMetricsConfig(nil)
for _, test := range errorTests {
t.Run(test.name, func(t *testing.T) {
defer ClearAll()
UpdateExporter(test.ops, TestLogger(t))
mConfig := getCurMetricsConfig()
if mConfig != nil {
t.Error("mConfig should not be created")
}
})
}
}
func TestMetricsOptions(t *testing.T) {
testCases := map[string]struct {
opts *ExporterOptions
want string
wantErr string
}{
"nil": {
opts: nil,
want: "",
wantErr: "json options string is empty",
},
"happy": {
opts: &ExporterOptions{
Domain: "domain",
Component: "component",
PrometheusPort: 9090,
ConfigMap: map[string]string{
"foo": "bar",
"boosh": "kakow",
},
},
want: `{"Domain":"domain","Component":"component","PrometheusPort":9090,"ConfigMap":{"boosh":"kakow","foo":"bar"}}`,
},
}
for n, tc := range testCases {
t.Run(n, func(t *testing.T) {
jsonOpts, err := MetricsOptionsToJson(tc.opts)
if err != nil {
t.Errorf("error while converting metrics config to json: %v", err)
}
// Test to json.
{
want := tc.want
got := jsonOpts
if diff := cmp.Diff(want, got); diff != "" {
t.Errorf("unexpected (-want, +got) = %v", diff)
t.Log(got)
}
}
// Test to options.
{
want := tc.opts
got, gotErr := JsonToMetricsOptions(jsonOpts)
if gotErr != nil {
if diff := cmp.Diff(tc.wantErr, gotErr.Error()); diff != "" {
t.Errorf("unexpected err (-want, +got) = %v", diff)
}
} else if tc.wantErr != "" {
t.Errorf("expected err %v", tc.wantErr)
}
if diff := cmp.Diff(want, got); diff != "" {
t.Errorf("unexpected (-want, +got) = %v", diff)
t.Log(got)
}
}
})
}
}
func TestNewStackdriverConfigFromMap(t *testing.T) {
tests := []struct {
name string
stringMap map[string]string
expectedConfig StackdriverClientConfig
}{{
name: "fullSdConfig",
stringMap: map[string]string{
"metrics.stackdriver-project-id": "project",
"metrics.stackdriver-gcp-location": "us-west1",
"metrics.stackdriver-cluster-name": "cluster",
"metrics.stackdriver-use-secret": "true",
},
expectedConfig: StackdriverClientConfig{
ProjectID: "project",
GCPLocation: "us-west1",
ClusterName: "cluster",
UseSecret: true,
},
}, {
name: "emptySdConfig",
stringMap: map[string]string{},
expectedConfig: StackdriverClientConfig{},
}, {
name: "partialSdConfig",
stringMap: map[string]string{
"metrics.stackdriver-project-id": "project",
"metrics.stackdriver-gcp-location": "us-west1",
"metrics.stackdriver-cluster-name": "cluster",
},
expectedConfig: StackdriverClientConfig{
ProjectID: "project",
GCPLocation: "us-west1",
ClusterName: "cluster",
UseSecret: false,
},
}, {
name: "nil",
stringMap: nil,
expectedConfig: StackdriverClientConfig{},
}}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
c := NewStackdriverClientConfigFromMap(test.stringMap)
if test.expectedConfig != *c {
t.Errorf("Incorrect stackdriver config. Expected: [%v], Got: [%v]", test.expectedConfig, *c)
}
})
}
}
// TODO(evankanderson): Move the Stackdriver / Record patching out of config.go
func TestStackdriverRecord(t *testing.T) {
testCases := map[string]struct {
opts map[string]string
servedCounter int64
statCounter int64
}{
"non-stackdriver": {
opts: map[string]string{
"metrics.backend-destination": "prometheus",
},
servedCounter: 1,
statCounter: 1,
},
"stackdriver with custom metrics": {
opts: map[string]string{
"metrics.backend-destination": "stackdriver",
"metrics.allow-stackdriver-custom-metrics": "true",
},
servedCounter: 1,
statCounter: 1,
},
"stackdriver no custom metrics": {
opts: map[string]string{
"metrics.backend-destination": "stackdriver",
},
servedCounter: 1,
statCounter: 0,
},
}
servedCount := stats.Int64("request_count", "Number of requests", stats.UnitNone)
statCount := stats.Int64("stat_errors", "Number of errors calling stat", stats.UnitNone)
emptyTags := map[string]string{}
for name, data := range testCases {
t.Run(name, func(t *testing.T) {
defer ClearAll()
opts := ExporterOptions{
ConfigMap: data.opts,
Domain: "knative.dev/internal/serving",
Component: "activator",
}
mc, err := createMetricsConfig(opts, TestLogger(t))
if err != nil {
t.Errorf("Expected valid config %+v, got error: %v\n", opts, err)
}
setCurMetricsConfig(mc)
ctx := context.Background()
v := []*view.View{
{Measure: servedCount, Aggregation: view.Count()},
{Measure: statCount, Aggregation: view.Count()},
}
err = view.Register(v...)
if err != nil {
t.Errorf("Failed to register %+v in stats backend: %v", v, err)
}
defer view.Unregister(v...)
// Try recording each metric and checking the result.
Record(ctx, servedCount.M(1))
metricstest.CheckCountData(t, servedCount.Name(), emptyTags, data.servedCounter)
Record(ctx, statCount.M(1))
if data.statCounter != 0 {
metricstest.CheckCountData(t, statCount.Name(), emptyTags, data.statCounter)
} else {
metricstest.CheckStatsNotReported(t, statCount.Name())
}
})
}
}