From e550bf19566e5ab8d1e60e2ebd0a81af157421f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patryk=20Ma=C5=82ek?= Date: Sun, 12 Dec 2021 21:59:04 +0100 Subject: [PATCH] wip --- tests/integration/Makefile | 2 +- tests/integration/README.md | 22 ++- tests/integration/go.mod | 2 +- .../helm_default_installation_test.go | 100 +++++++++++++- .../helm_otc_metadata_installation_test.go | 126 ++++++++++++++++-- tests/integration/internal/constants.go | 5 +- tests/integration/internal/k8s/tunnel.go | 30 +++++ .../internal/receivermock/labels.go | 48 +++++++ .../internal/receivermock/receiver_mock.go | 80 ++++++++++- .../internal/stepfuncs/assess_funcs.go | 14 +- tests/integration/internal/stepfuncs/debug.go | 18 +++ .../integration/internal/stepfuncs/kubectl.go | 2 +- tests/integration/internal/strings/strings.go | 5 + tests/integration/main_test.go | 13 +- tests/integration/yamls/receiver-mock.yaml | 3 +- 15 files changed, 431 insertions(+), 39 deletions(-) create mode 100644 tests/integration/internal/k8s/tunnel.go create mode 100644 tests/integration/internal/receivermock/labels.go diff --git a/tests/integration/Makefile b/tests/integration/Makefile index 21a46b2936..2d6ffb7b99 100644 --- a/tests/integration/Makefile +++ b/tests/integration/Makefile @@ -20,7 +20,7 @@ delete-cluster: .PHONY: test test: KIND_NODE_IMAGE=$(KIND_NODE_IMAGE) \ - go test -timeout 20m -v -count 1 -run=$(TEST_NAME) . + go test -timeout 20m -v -count 1 -run=$(TEST_NAME) . -args $(TEST_ARGS) .PHONY: gomod-tidy gomod-tidy: diff --git a/tests/integration/README.md b/tests/integration/README.md index cbbd42631b..24bd774354 100644 --- a/tests/integration/README.md +++ b/tests/integration/README.md @@ -99,7 +99,7 @@ The test framework has the following runtime options that can be utilized: HELM_NO_DEPENDENCY_UPDATE=1 make test ``` -## Running specified tests +## Filtering tests [Testing framework][sig_e2e_testing_harness] that's used in the tests allows filtering tests by feature names, labels and step names. @@ -108,7 +108,9 @@ This might be handy if you'd like to use those abstractions but its consequence it will run all the code related to setup and/or teardown so e.g. all kind clusters that were supposed to be created for tests that got filtered out would be created anyway. -Because of that reason we suggest to use `go` related test filtering like so: +### Running specific tests + +In order to run specific tests you can use `go` related test filtering like so: ```shell make test TEST_NAME="Test_Helm_Default" @@ -120,7 +122,23 @@ or go test -v -count 1 -run=Test_Helm_Default . ``` +### Running specific features/assessments + +In order to run specific features you can use `TEST_ARGS` makefile argument in +which you can specify [e2e framework's flags][sig_e2e_testing_harness_filtering_tests]: + +```shell +make test TEST_NAME="Test_Helm_Default_OT_Metadata" TEST_ARGS="--assess '(metrics)'" +``` + +or + +```shell +make test TEST_NAME="Test_Helm_Default_OT_Metadata" TEST_ARGS="--feature '(installation)'" +``` + [sig_e2e_testing_harness]: https://github.com/kubernetes-sigs/e2e-framework/blob/main/docs/design/test-harness-framework.md +[sig_e2e_testing_harness_filtering_tests]: https://github.com/kubernetes-sigs/e2e-framework/blob/fee1391aeccdc260069bd5e0b25c6b187c2293c4/docs/design/test-harness-framework.md#filtering-feature-tests ## K8s node images matrix diff --git a/tests/integration/go.mod b/tests/integration/go.mod index 9251744e7b..9c79821044 100644 --- a/tests/integration/go.mod +++ b/tests/integration/go.mod @@ -7,6 +7,7 @@ require ( github.com/stretchr/testify v1.7.0 k8s.io/api v0.22.4 k8s.io/apimachinery v0.22.4 + k8s.io/klog/v2 v2.9.0 sigs.k8s.io/e2e-framework v0.0.5 ) @@ -58,7 +59,6 @@ require ( gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect k8s.io/client-go v0.22.4 // indirect - k8s.io/klog/v2 v2.9.0 // indirect k8s.io/kube-openapi v0.0.0-20211109043538-20434351676c // indirect k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a // indirect sigs.k8s.io/controller-runtime v0.9.0 // indirect diff --git a/tests/integration/helm_default_installation_test.go b/tests/integration/helm_default_installation_test.go index 6bd13a3e57..5eed0f6ae1 100644 --- a/tests/integration/helm_default_installation_test.go +++ b/tests/integration/helm_default_installation_test.go @@ -3,11 +3,17 @@ package integration import ( "context" "fmt" + "sort" "testing" "time" appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + log "k8s.io/klog/v2" + "sigs.k8s.io/e2e-framework/klient/k8s/resources" + "sigs.k8s.io/e2e-framework/klient/wait" + "sigs.k8s.io/e2e-framework/klient/wait/conditions" "sigs.k8s.io/e2e-framework/pkg/envconf" "sigs.k8s.io/e2e-framework/pkg/features" @@ -17,6 +23,7 @@ import ( "github.com/SumoLogic/sumologic-kubernetes-collection/tests/integration/internal" "github.com/SumoLogic/sumologic-kubernetes-collection/tests/integration/internal/ctxopts" + "github.com/SumoLogic/sumologic-kubernetes-collection/tests/integration/internal/receivermock" "github.com/SumoLogic/sumologic-kubernetes-collection/tests/integration/internal/stepfuncs" "github.com/SumoLogic/sumologic-kubernetes-collection/tests/integration/internal/strings" ) @@ -36,7 +43,7 @@ func Test_Helm_Default_FluentD_Metadata(t *testing.T) { // detail. releaseName := strings.ReleaseNameFromT(t) - feat := features.New("installation"). + featInstall := features.New("installation"). Assess("sumologic secret is created", func(ctx context.Context, t *testing.T, envConf *envconf.Config) context.Context { k8s.WaitUntilSecretAvailable(t, ctxopts.KubectlOptions(ctx), "sumologic", 60, tickDuration) @@ -166,8 +173,11 @@ func Test_Helm_Default_FluentD_Metadata(t *testing.T) { require.EqualValues(t, 0, daemonsets[0].Status.NumberUnavailable) return ctx - }). - Assess("metrics are present", // TODO: extract this out to a separate feature + }). + Feature() + + featMetrics := features.New("metrics"). + Assess("expected metrics are present", stepfuncs.WaitUntilExpectedMetricsPresent( expectedMetrics, "receiver-mock", @@ -177,7 +187,89 @@ func Test_Helm_Default_FluentD_Metadata(t *testing.T) { tickDuration, ), ). + Assess("expected labels are present", + func(ctx context.Context, t *testing.T, envConf *envconf.Config) context.Context { + // TODO: refactor into a step func? + waitDuration := 5 * time.Minute + tickDuration := 3 * time.Second + + // Get the receiver mock pod as metrics source + res := envConf.Client().Resources(internal.ReceiverMockNamespace) + podList := corev1.PodList{} + require.NoError(t, + wait.For( + conditions.New(res). + ResourceListN( + &podList, + 1, + resources.WithLabelSelector("app=receiver-mock"), + ), + wait.WithTimeout(waitDuration), + wait.WithInterval(tickDuration), + ), + ) + rClient, tunnelCloseFunc := receivermock.NewClientWithK8sTunnel(ctx, t) + defer tunnelCloseFunc() + + assert.Eventually(t, func() bool { + filters := receivermock.MetadataFilters{ + "__name__": "container_memory_working_set_bytes", + "pod": podList.Items[0].Name, + } + metricsSamples, err := rClient.GetMetricsSamples(t, filters) + if err != nil { + log.ErrorS(err, "failed getting samples from receiver-mock") + return false + } + + if len(metricsSamples) == 0 { + log.InfoS("got 0 metrics samples", "filters", filters) + return false + } + + sort.Sort(receivermock.MetricsSamplesByTime(metricsSamples)) + // For now let's take the newest metric sample only because it will have the most + // accurate labels and the most labels attached (for instance service/deployment + // labels might not be attached at the very first record). + sample := metricsSamples[0] + labels := sample.Labels + expectedLabels := receivermock.Labels{ + "_origin": "kubernetes", + "container": "receiver-mock", + // TODO: figure out why is this flaky and sometimes it's not there + // https://github.com/SumoLogic/sumologic-kubernetes-collection/runs/4508796836?check_suite_focus=true + // "deployment": "receiver-mock", + "endpoint": "https-metrics", + "image": "", + "instance": "", + "job": "kubelet", + "metrics_path": "/metrics/cadvisor", + "namespace": "receiver-mock", + "node": "", + "pod_labels_app": "receiver-mock", + "pod_labels_pod-template-hash": "", + "pod_labels_service": "receiver-mock", + "pod": podList.Items[0].Name, + "prometheus_replica": "", + "prometheus_service": "", + "prometheus": "", + // TODO: figure out why is this flaky and sometimes it's not there + // https://github.com/SumoLogic/sumologic-kubernetes-collection/runs/4508796836?check_suite_focus=true + // "replicaset": "", + "service": "receiver-mock", + } + + log.V(0).InfoS("sample's labels", "labels", labels) + if !labels.MatchAll(expectedLabels) { + return false + } + + return true + }, waitDuration, tickDuration) + return ctx + }, + ). Feature() - testenv.Test(t, feat) + testenv.Test(t, featInstall, featMetrics) } diff --git a/tests/integration/helm_otc_metadata_installation_test.go b/tests/integration/helm_otc_metadata_installation_test.go index 3d3a8efd8a..6425e8a484 100644 --- a/tests/integration/helm_otc_metadata_installation_test.go +++ b/tests/integration/helm_otc_metadata_installation_test.go @@ -3,20 +3,27 @@ package integration import ( "context" "fmt" + "sort" "testing" "time" appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + log "k8s.io/klog/v2" + "sigs.k8s.io/e2e-framework/klient/k8s/resources" + "sigs.k8s.io/e2e-framework/klient/wait" + "sigs.k8s.io/e2e-framework/klient/wait/conditions" "sigs.k8s.io/e2e-framework/pkg/envconf" "sigs.k8s.io/e2e-framework/pkg/features" - "github.com/gruntwork-io/terratest/modules/k8s" + terrak8s "github.com/gruntwork-io/terratest/modules/k8s" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/SumoLogic/sumologic-kubernetes-collection/tests/integration/internal" "github.com/SumoLogic/sumologic-kubernetes-collection/tests/integration/internal/ctxopts" + "github.com/SumoLogic/sumologic-kubernetes-collection/tests/integration/internal/receivermock" "github.com/SumoLogic/sumologic-kubernetes-collection/tests/integration/internal/stepfuncs" "github.com/SumoLogic/sumologic-kubernetes-collection/tests/integration/internal/strings" ) @@ -37,11 +44,11 @@ func Test_Helm_Default_OT_Metadata(t *testing.T) { // detail. releaseName := strings.ReleaseNameFromT(t) - feat := features.New("installation"). + featInstall := features.New("installation"). Assess("sumologic secret is created", func(ctx context.Context, t *testing.T, envConf *envconf.Config) context.Context { - k8s.WaitUntilSecretAvailable(t, ctxopts.KubectlOptions(ctx), "sumologic", 60, tickDuration) - secret := k8s.GetSecret(t, ctxopts.KubectlOptions(ctx), "sumologic") + terrak8s.WaitUntilSecretAvailable(t, ctxopts.KubectlOptions(ctx), "sumologic", 60, tickDuration) + secret := terrak8s.GetSecret(t, ctxopts.KubectlOptions(ctx), "sumologic") require.Len(t, secret.Data, 10) return ctx }). @@ -62,7 +69,7 @@ func Test_Helm_Default_OT_Metadata(t *testing.T) { kubectlOptions := ctxopts.KubectlOptions(ctx) t.Logf("kubeconfig: %s", kubectlOptions.ConfigPath) - cl, err := k8s.GetKubernetesClientFromOptionsE(t, kubectlOptions) + cl, err := terrak8s.GetKubernetesClientFromOptionsE(t, kubectlOptions) require.NoError(t, err) assert.Eventually(t, func() bool { @@ -95,7 +102,7 @@ func Test_Helm_Default_OT_Metadata(t *testing.T) { kubectlOptions := ctxopts.KubectlOptions(ctx) t.Logf("kubeconfig: %s", kubectlOptions.ConfigPath) - cl, err := k8s.GetKubernetesClientFromOptionsE(t, kubectlOptions) + cl, err := terrak8s.GetKubernetesClientFromOptionsE(t, kubectlOptions) require.NoError(t, err) assert.Eventually(t, func() bool { @@ -128,7 +135,7 @@ func Test_Helm_Default_OT_Metadata(t *testing.T) { kubectlOptions := ctxopts.KubectlOptions(ctx) t.Logf("kubeconfig: %s", kubectlOptions.ConfigPath) - cl, err := k8s.GetKubernetesClientFromOptionsE(t, kubectlOptions) + cl, err := terrak8s.GetKubernetesClientFromOptionsE(t, kubectlOptions) require.NoError(t, err) assert.Eventually(t, func() bool { @@ -159,7 +166,7 @@ func Test_Helm_Default_OT_Metadata(t *testing.T) { func(ctx context.Context, t *testing.T, envConf *envconf.Config) context.Context { var daemonsets []appsv1.DaemonSet require.Eventually(t, func() bool { - daemonsets = k8s.ListDaemonSets(t, ctxopts.KubectlOptions(ctx), v1.ListOptions{ + daemonsets = terrak8s.ListDaemonSets(t, ctxopts.KubectlOptions(ctx), v1.ListOptions{ LabelSelector: "app.kubernetes.io/name=fluent-bit", }) @@ -168,18 +175,111 @@ func Test_Helm_Default_OT_Metadata(t *testing.T) { require.EqualValues(t, 0, daemonsets[0].Status.NumberUnavailable) return ctx - }). - Assess("metrics are present", // TODO: extract this out to a separate feature + }). + Feature() + + featMetrics := features.New("metrics"). + Assess("expected metrics are present", stepfuncs.WaitUntilExpectedMetricsPresent( expectedMetrics, - "receiver-mock", - "receiver-mock", + internal.ReceiverMockNamespace, + internal.ReceiverMockServiceName, internal.ReceiverMockServicePort, waitDuration, tickDuration, ), ). + Assess("expected labels are present", + func(ctx context.Context, t *testing.T, envConf *envconf.Config) context.Context { + // TODO: refactor into a step func? + waitDuration := 5 * time.Minute + tickDuration := 3 * time.Second + + // Get the receiver mock pod as metrics source + res := envConf.Client().Resources(internal.ReceiverMockNamespace) + podList := corev1.PodList{} + require.NoError(t, + wait.For( + conditions.New(res). + ResourceListN( + &podList, + 1, + resources.WithLabelSelector("app=receiver-mock"), + ), + wait.WithTimeout(waitDuration), + wait.WithInterval(tickDuration), + ), + ) + rClient, tunnelCloseFunc := receivermock.NewClientWithK8sTunnel(ctx, t) + defer tunnelCloseFunc() + + assert.Eventually(t, func() bool { + filters := receivermock.MetadataFilters{ + "__name__": "container_memory_working_set_bytes", + "pod": podList.Items[0].Name, + } + metricsSamples, err := rClient.GetMetricsSamples(t, filters) + if err != nil { + log.ErrorS(err, "failed getting samples from receiver-mock") + return false + } + + if len(metricsSamples) == 0 { + log.InfoS("got 0 metrics samples", "filters", filters) + return false + } + + sort.Sort(receivermock.MetricsSamplesByTime(metricsSamples)) + // For now let's take the newest metric sample only because it will have the most + // accurate labels and the most labels attached (for instance service/deployment + // labels might not be attached at the very first record). + sample := metricsSamples[0] + labels := sample.Labels + expectedLabels := receivermock.Labels{ + "_origin": "kubernetes", + "container": "receiver-mock", + // TODO: figure out why is this flaky and sometimes it's not there + // https://github.com/SumoLogic/sumologic-kubernetes-collection/runs/4508796836?check_suite_focus=true + // "deployment": "receiver-mock", + "endpoint": "https-metrics", + // TODO: verify the source of label's value. + // For OTC metadata enrichment this is set to -sumologic-otelcol-metrics- + // hence with longer time range the time series about a particular metric + // that we receive diverge into n, where n is the number of metrics + // enrichment pods. + "host": "", + "http_listener_v2_path": "/prometheus.metrics.container", + "image": "", + "instance": "", + "job": "kubelet", + "k8s.node.name": "", + "metrics_path": "/metrics/cadvisor", + "namespace": "receiver-mock", + "node": "", + "pod_labels_app": "receiver-mock", + "pod_labels_pod-template-hash": "", + "pod_labels_service": "receiver-mock", + "pod": podList.Items[0].Name, + "prometheus_replica": "", + "prometheus_service": "", + "prometheus": "", + // TODO: figure out why is this flaky and sometimes it's not there + // https://github.com/SumoLogic/sumologic-kubernetes-collection/runs/4508796836?check_suite_focus=true + // "replicaset": "", + "service": "receiver-mock", + } + + log.V(0).InfoS("sample's labels", "labels", labels) + if !labels.MatchAll(expectedLabels) { + return false + } + + return true + }, waitDuration, tickDuration) + return ctx + }, + ). Feature() - testenv.Test(t, feat) + testenv.Test(t, featInstall, featMetrics) } diff --git a/tests/integration/internal/constants.go b/tests/integration/internal/constants.go index da4278726e..32cb37b416 100644 --- a/tests/integration/internal/constants.go +++ b/tests/integration/internal/constants.go @@ -13,8 +13,11 @@ const ( EnvNameKindImage = "KIND_NODE_IMAGE" - YamlPathReceiverMock = "yamls/receiver-mock.yaml" + YamlPathReceiverMock = "yamls/receiver-mock.yaml" + ReceiverMockServicePort = 3000 + ReceiverMockServiceName = "receiver-mock" + ReceiverMockNamespace = "receiver-mock" ) // metrics we expect the receiver to get diff --git a/tests/integration/internal/k8s/tunnel.go b/tests/integration/internal/k8s/tunnel.go new file mode 100644 index 0000000000..3add9dbe69 --- /dev/null +++ b/tests/integration/internal/k8s/tunnel.go @@ -0,0 +1,30 @@ +package k8s + +import ( + "context" + "testing" + + terrak8s "github.com/gruntwork-io/terratest/modules/k8s" + + "github.com/SumoLogic/sumologic-kubernetes-collection/tests/integration/internal" + "github.com/SumoLogic/sumologic-kubernetes-collection/tests/integration/internal/ctxopts" +) + +// TunnelForReceiverMock creates a tunnel with port forward to receiver-mock service. +func TunnelForReceiverMock( + ctx context.Context, + t *testing.T, +) *terrak8s.Tunnel { + kubectlOptions := *ctxopts.KubectlOptions(ctx) + kubectlOptions.Namespace = internal.ReceiverMockNamespace + + tunnel := terrak8s.NewTunnel( + &kubectlOptions, + terrak8s.ResourceTypeService, + internal.ReceiverMockServiceName, + 0, + internal.ReceiverMockServicePort, + ) + tunnel.ForwardPort(t) + return tunnel +} diff --git a/tests/integration/internal/receivermock/labels.go b/tests/integration/internal/receivermock/labels.go new file mode 100644 index 0000000000..0b04454541 --- /dev/null +++ b/tests/integration/internal/receivermock/labels.go @@ -0,0 +1,48 @@ +package receivermock + +import ( + "regexp" + + log "k8s.io/klog/v2" +) + +type Labels map[string]string + +func (labels Labels) Match(label, value string) bool { + v, ok := labels[label] + if !ok { + log.Infof("Label: %q not present in labels", label) + return false + } + + if value != "" && value != v { + log.Infof("Requested label %q exists in label set but has a different value %q", label, v) + return false + } + return true +} + +func (labels Labels) MatchRegex(label string, re *regexp.Regexp) bool { + v, ok := labels[label] + if !ok { + log.Infof("Label: %q not present in labels", label) + return false + } + if !re.MatchString(v) { + log.Infof("Label %q (value %v) doesn't match the designated regex %q", label, v, re.String()) + return false + } + return true +} + +// MatchAll matches returns whether all the requested labels are present (if set as empty string - "") +// and (if a corresponding value has been provided) that all values match. +func (labels Labels) MatchAll(requested Labels) bool { + ret := true + for label, value := range requested { + if !labels.Match(label, value) { + ret = false + } + } + return ret +} diff --git a/tests/integration/internal/receivermock/receiver_mock.go b/tests/integration/internal/receivermock/receiver_mock.go index 3678b07fff..cc9aa8ae82 100644 --- a/tests/integration/internal/receivermock/receiver_mock.go +++ b/tests/integration/internal/receivermock/receiver_mock.go @@ -1,17 +1,23 @@ package receivermock import ( + "context" "crypto/tls" + "encoding/json" "fmt" + "net/http" "net/url" "strconv" "strings" "testing" http_helper "github.com/gruntwork-io/terratest/modules/http-helper" + "github.com/stretchr/testify/require" + + "github.com/SumoLogic/sumologic-kubernetes-collection/tests/integration/internal/k8s" ) -// Mapping of metric names to the number of times the metric was observed +// Mapping of metric names to the number of times the metric was observed type MetricCounts map[string]int // A HTTP client for the receiver-mock API @@ -20,10 +26,32 @@ type ReceiverMockClient struct { tlsConfig tls.Config } -func NewReceiverMockClient(t *testing.T, baseUrl url.URL) *ReceiverMockClient { +func NewClient(t *testing.T, baseUrl url.URL) *ReceiverMockClient { return &ReceiverMockClient{baseUrl: baseUrl, tlsConfig: tls.Config{}} } +// NewClientWithK8sTunnel creates a client for receiver-mock. +// It return the client itself and a tunnel teardown func which should be called +// by the caller when they're done with it. +func NewClientWithK8sTunnel( + ctx context.Context, + t *testing.T, +) (*ReceiverMockClient, func()) { + tunnel := k8s.TunnelForReceiverMock(ctx, t) + baseUrl := url.URL{ + Scheme: "http", + Host: tunnel.Endpoint(), + Path: "/", + } + + return &ReceiverMockClient{ + baseUrl: baseUrl, + tlsConfig: tls.Config{}, + }, func() { + tunnel.Close() + } +} + func (client *ReceiverMockClient) GetMetricCounts(t *testing.T) (MetricCounts, error) { path, err := url.Parse("metrics-list") if err != nil { @@ -46,6 +74,54 @@ func (client *ReceiverMockClient) GetMetricCounts(t *testing.T) (MetricCounts, e return metricCounts, nil } +type MetricSample struct { + Metric string `json:"metric,omitempty"` + Value float64 `json:"value,omitempty"` + Labels Labels `json:"labels,omitempty"` + Timestamp uint64 `json:"timestamp,omitempty"` +} + +type MetricsSamplesByTime []MetricSample + +func (m MetricsSamplesByTime) Len() int { return len(m) } +func (m MetricsSamplesByTime) Swap(i, j int) { m[i], m[j] = m[j], m[i] } +func (m MetricsSamplesByTime) Less(i, j int) bool { return m[i].Timestamp > m[j].Timestamp } + +type MetadataFilters map[string]string + +func (client *ReceiverMockClient) GetMetricsSamples( + t *testing.T, + metadataFilters MetadataFilters, +) ([]MetricSample, error) { + path, err := url.Parse("metrics-samples") + require.NoError(t, err) + u := client.baseUrl.ResolveReference(path) + + q := u.Query() + for k, v := range metadataFilters { + q.Add(k, v) + } + u.RawQuery = q.Encode() + + resp, err := http.Get(u.String()) + if err != nil { + return nil, fmt.Errorf("failed fetching %s, err: %w", u, err) + } + + if resp.StatusCode != 200 { + return nil, fmt.Errorf( + "received status code %d in response to receiver request at %q", + resp.StatusCode, u, + ) + } + + var metricsSamples []MetricSample + if err := json.NewDecoder(resp.Body).Decode(&metricsSamples); err != nil { + return nil, err + } + return metricsSamples, nil +} + // parse metrics list returned by /metrics-list // https://github.com/SumoLogic/sumologic-kubernetes-tools/tree/main/src/rust/receiver-mock#statistics func parseMetricList(rawMetricsValues string) (map[string]int, error) { diff --git a/tests/integration/internal/stepfuncs/assess_funcs.go b/tests/integration/internal/stepfuncs/assess_funcs.go index 93b91e6820..1f32b68f75 100644 --- a/tests/integration/internal/stepfuncs/assess_funcs.go +++ b/tests/integration/internal/stepfuncs/assess_funcs.go @@ -7,9 +7,9 @@ import ( "testing" "time" - "github.com/gruntwork-io/terratest/modules/k8s" + terrak8s "github.com/gruntwork-io/terratest/modules/k8s" "github.com/gruntwork-io/terratest/modules/retry" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/e2e-framework/pkg/envconf" "sigs.k8s.io/e2e-framework/pkg/features" @@ -21,7 +21,7 @@ import ( // WaitUntilPodsAvailable returns a features.Func that can be used in `Assess` calls. // It will wait until the selected pods are available, using the provided total // `wait` and `tick` times as well as the provided list options and the desired count. -func WaitUntilPodsAvailable(listOptions v1.ListOptions, count int, wait time.Duration, tick time.Duration) features.Func { +func WaitUntilPodsAvailable(listOptions metav1.ListOptions, count int, wait time.Duration, tick time.Duration) features.Func { return func(ctx context.Context, t *testing.T, envConf *envconf.Config) context.Context { k8s_internal.WaitUntilPodsAvailable(t, ctxopts.KubectlOptions(ctx), listOptions, count, wait, tick, @@ -44,10 +44,10 @@ func WaitUntilExpectedMetricsPresent( return func(ctx context.Context, t *testing.T, envConf *envconf.Config) context.Context { kubectlOpts := *ctxopts.KubectlOptions(ctx) kubectlOpts.Namespace = receiverMockNamespace - k8s.WaitUntilServiceAvailable(t, &kubectlOpts, receiverMockServiceName, int(waitDuration), tickDuration) - tunnel := k8s.NewTunnel( + terrak8s.WaitUntilServiceAvailable(t, &kubectlOpts, receiverMockServiceName, int(waitDuration), tickDuration) + tunnel := terrak8s.NewTunnel( &kubectlOpts, - k8s.ResourceTypeService, + terrak8s.ResourceTypeService, receiverMockServiceName, 0, receiverMockServicePort, @@ -59,7 +59,7 @@ func WaitUntilExpectedMetricsPresent( Host: tunnel.Endpoint(), Path: "/", } - client := receivermock.NewReceiverMockClient(t, baseUrl) + client := receivermock.NewClient(t, baseUrl) retries := int(waitDuration / tickDuration) message, err := retry.DoWithRetryE( t, diff --git a/tests/integration/internal/stepfuncs/debug.go b/tests/integration/internal/stepfuncs/debug.go index 60ea1e9e16..b2316b677a 100644 --- a/tests/integration/internal/stepfuncs/debug.go +++ b/tests/integration/internal/stepfuncs/debug.go @@ -2,7 +2,10 @@ package stepfuncs import ( "context" + "os" + "os/signal" "testing" + "time" "github.com/gruntwork-io/terratest/modules/k8s" "sigs.k8s.io/e2e-framework/pkg/envconf" @@ -28,3 +31,18 @@ func PrintClusterStateOpt(force ...bool) features.Func { return ctx } } + +// Wait is a step func that will wait for an hour or until a SIGKILL or SIGINT +// will be received. +// It can be used as a helper func in order to inspect cluster state at a particular step. +func Wait() features.Func { + return func(ctx context.Context, t *testing.T, envConf *envconf.Config) context.Context { + ch := make(chan os.Signal, 1) + signal.Notify(ch, os.Interrupt, os.Kill) + select { + case <-time.After(time.Hour): + case <-ch: + } + return ctx + } +} diff --git a/tests/integration/internal/stepfuncs/kubectl.go b/tests/integration/internal/stepfuncs/kubectl.go index 1b3991c863..9e9b04f17d 100644 --- a/tests/integration/internal/stepfuncs/kubectl.go +++ b/tests/integration/internal/stepfuncs/kubectl.go @@ -45,7 +45,7 @@ func KubectlCreateNamespaceOpt(namespace string) features.Func { // a namespace name for test. func KubectlCreateNamespaceTestOpt() features.Func { return func(ctx context.Context, t *testing.T, envConf *envconf.Config) context.Context { - name := strings.NameFromT(t) + name := strings.NamespaceFromT(t) return KubectlCreateNamespaceOpt(name)(ctx, t, envConf) } } diff --git a/tests/integration/internal/strings/strings.go b/tests/integration/internal/strings/strings.go index 724c297c85..24c2d01ae4 100644 --- a/tests/integration/internal/strings/strings.go +++ b/tests/integration/internal/strings/strings.go @@ -5,12 +5,17 @@ import ( "hash/fnv" "strings" "testing" + "time" ) func NameFromT(t *testing.T) string { return strings.ReplaceAll(strings.ToLower(t.Name()), "_", "-") } +func NamespaceFromT(t *testing.T) string { + return fmt.Sprintf("%s-%d", NameFromT(t), time.Now().UnixMilli()%1000) +} + func ValueFileFromT(t *testing.T) string { testname := strings.ToLower(t.Name()) testname = strings.TrimPrefix(testname, "test") diff --git a/tests/integration/main_test.go b/tests/integration/main_test.go index 864f7fd110..88f38d48b9 100644 --- a/tests/integration/main_test.go +++ b/tests/integration/main_test.go @@ -33,19 +33,20 @@ var ( func TestMain(m *testing.M) { internal.InitializeConstants() + cfg, err := envconf.NewFromFlags() + if err != nil { + log.Fatalf("envconf.NewFromFlags() failed: %s", err) + } + if useKubeConfig := os.Getenv(envNameUseKubeConfig); len(useKubeConfig) > 0 { kubeconfig := os.Getenv(envNameKubeConfig) - testenv = env. - NewWithKubeConfig(kubeconfig). + cfg.WithKubeconfigFile(kubeconfig) + testenv = env.NewWithConfig(cfg). BeforeEachTest(InjectKubectlOptionsFromKubeconfig(kubeconfig)) ConfigureTestEnv(testenv) } else { - cfg, err := envconf.NewFromFlags() - if err != nil { - log.Fatalf("envconf.NewFromFlags() failed: %s", err) - } testenv = env.NewWithConfig(cfg) testenv.BeforeEachTest(CreateKindCluster()) diff --git a/tests/integration/yamls/receiver-mock.yaml b/tests/integration/yamls/receiver-mock.yaml index 0194f7592c..1191c735af 100644 --- a/tests/integration/yamls/receiver-mock.yaml +++ b/tests/integration/yamls/receiver-mock.yaml @@ -26,7 +26,7 @@ spec: - ports: - containerPort: 3000 - containerPort: 3001 - image: sumologic/kubernetes-tools:2.6.0 + image: sumologic/kubernetes-tools:2.7.0 name: receiver-mock args: - receiver-mock @@ -34,6 +34,7 @@ spec: - --print-headers - --print-logs - --print-metrics + - --store-metrics resources: {} securityContext: capabilities: