mirror of https://github.com/knative/pkg.git
590 lines
18 KiB
Go
590 lines
18 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 (
|
|
"github.com/google/go-cmp/cmp"
|
|
"os"
|
|
"path"
|
|
"reflect"
|
|
"testing"
|
|
"time"
|
|
|
|
. "knative.dev/pkg/logging/testing"
|
|
)
|
|
|
|
// TODO UTs should move to eventing and serving, as appropriate.
|
|
// See https://github.com/knative/pkg/issues/608
|
|
|
|
const (
|
|
servingDomain = "knative.dev/serving"
|
|
eventingDomain = "knative.dev/eventing"
|
|
badDomain = "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": "prometheus",
|
|
},
|
|
Domain: servingDomain,
|
|
Component: "",
|
|
},
|
|
expectedErr: "metrics component name cannot be empty",
|
|
}, {
|
|
name: "invalidReportingPeriod",
|
|
ops: ExporterOptions{
|
|
ConfigMap: map[string]string{
|
|
"metrics.backend-destination": "prometheus",
|
|
"metrics.reporting-period-seconds": "test",
|
|
},
|
|
Domain: servingDomain,
|
|
Component: testComponent,
|
|
},
|
|
expectedErr: "invalid metrics.reporting-period-seconds value \"test\"",
|
|
}, {
|
|
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, 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,
|
|
},
|
|
Domain: servingDomain,
|
|
Component: testComponent,
|
|
},
|
|
expectedConfig: metricsConfig{
|
|
domain: servingDomain,
|
|
component: testComponent,
|
|
backendDestination: Stackdriver,
|
|
stackdriverProjectID: anotherProj,
|
|
reportingPeriod: 60 * time.Second,
|
|
isStackdriverBackend: true,
|
|
stackdriverMetricTypePrefix: path.Join(servingDomain, testComponent),
|
|
stackdriverCustomMetricTypePrefix: path.Join(customMetricTypePrefix, testComponent),
|
|
},
|
|
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,
|
|
stackdriverProjectID: testProj,
|
|
reportingPeriod: 60 * time.Second,
|
|
isStackdriverBackend: true,
|
|
stackdriverMetricTypePrefix: path.Join(servingDomain, testComponent),
|
|
stackdriverCustomMetricTypePrefix: path.Join(customMetricTypePrefix, testComponent),
|
|
},
|
|
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,
|
|
stackdriverProjectID: "test2",
|
|
reportingPeriod: 7 * time.Second,
|
|
isStackdriverBackend: true,
|
|
stackdriverMetricTypePrefix: path.Join(servingDomain, testComponent),
|
|
stackdriverCustomMetricTypePrefix: path.Join(customMetricTypePrefix, testComponent),
|
|
},
|
|
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,
|
|
stackdriverProjectID: "test2",
|
|
reportingPeriod: 3 * time.Second,
|
|
isStackdriverBackend: true,
|
|
stackdriverMetricTypePrefix: path.Join(servingDomain, testComponent),
|
|
stackdriverCustomMetricTypePrefix: path.Join(customMetricTypePrefix, testComponent),
|
|
},
|
|
}, {
|
|
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,
|
|
stackdriverProjectID: "test2",
|
|
reportingPeriod: 60 * time.Second,
|
|
isStackdriverBackend: true,
|
|
stackdriverMetricTypePrefix: path.Join(servingDomain, testComponent),
|
|
stackdriverCustomMetricTypePrefix: path.Join(customMetricTypePrefix, testComponent),
|
|
},
|
|
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,
|
|
stackdriverProjectID: "test2",
|
|
reportingPeriod: 60 * time.Second,
|
|
isStackdriverBackend: true,
|
|
stackdriverMetricTypePrefix: path.Join(servingDomain, testComponent),
|
|
stackdriverCustomMetricTypePrefix: path.Join(customMetricTypePrefix, testComponent),
|
|
allowStackdriverCustomMetrics: true,
|
|
},
|
|
}, {
|
|
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, 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 := getMetricsConfig(test.ops, TestLogger(t))
|
|
if 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 := getMetricsConfig(test.ops, TestLogger(t))
|
|
if err != nil {
|
|
t.Errorf("Wanted valid config %v, got error %v", test.expectedConfig, err)
|
|
}
|
|
if !reflect.DeepEqual(*mc, test.expectedConfig) {
|
|
t.Errorf("Wanted config %v, got config %v", test.expectedConfig, *mc)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
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 := getMetricsConfig(test.ops, TestLogger(t))
|
|
if err != nil {
|
|
t.Errorf("Wanted valid config %v, got error %v", test.expectedConfig, err)
|
|
}
|
|
if !reflect.DeepEqual(*mc, test.expectedConfig) {
|
|
t.Errorf("Wanted config %v, got config %v", test.expectedConfig, *mc)
|
|
}
|
|
})
|
|
}
|
|
os.Unsetenv(defaultBackendEnvName)
|
|
}
|
|
|
|
func TestIsNewExporterRequired(t *testing.T) {
|
|
setCurMetricsConfig(nil)
|
|
for _, test := range successTests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
defer ClearAll()
|
|
mc, err := getMetricsConfig(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)
|
|
})
|
|
}
|
|
|
|
setCurMetricsConfig(&metricsConfig{
|
|
domain: servingDomain,
|
|
component: testComponent,
|
|
backendDestination: Prometheus})
|
|
newConfig := &metricsConfig{
|
|
domain: servingDomain,
|
|
component: testComponent,
|
|
backendDestination: Prometheus,
|
|
stackdriverProjectID: testProj,
|
|
}
|
|
changed := isNewExporterRequired(newConfig)
|
|
if changed {
|
|
t.Error("isNewExporterRequired should be false if stackdriver project ID changes for prometheus backend")
|
|
}
|
|
}
|
|
|
|
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 !reflect.DeepEqual(*mConfig, test.expectedConfig) {
|
|
t.Errorf("Expected config: %v; got config %v", test.expectedConfig, mConfig)
|
|
}
|
|
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 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)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|