Skip to content

Commit

Permalink
Datadog Integration Acceptance Tests / Bug fixes (#3685)
Browse files Browse the repository at this point in the history
* datadog: acceptance tests - initial commit (not fully working yet)
* server-statefulset: update logic for prometheus annotations (only enabled if using dogstatsd, otherwise disabled)
* datadog: acceptance test working with dd-client api and operator deployment frameword
* datadog-acceptance: main branch rebase merge conflict cherry-pick
* datadog: acceptance testing update to metric name matching using regex
* datadog: acceptance testing helper update for backoff retry
* datadog: acceptance testing working timeseries query verification udp + uds
* datadog: update helpers for /v1/query
* server-statefulset.yaml: update to correct release name prepend to consul-server URL
* datadog: acceptance testing consul integration checks working
* server-statefulset: yaml and bats updates for datadog openmetrics and consul integration check URLs to use consul.fullname-server
* PR3685: changelog update
* datadog: openmetrics acceptance test update
* datadog: added OTEL_EXPORTER_OTLP_ENDPOINT to consul telemetry collector deployment for dd-agent ingestion (passes tag info to DD)
* otlp: datadog otlp acceptance test updates for telemetry-collector (grpc => http prefix) | staged otlp acceptance test
* datadog-acceptance: fake-intake fixture addition
* datadog-acceptance: update _helpers.tpl for consul version sanitization (truncate to <64)
* datadog-acceptance: update base fixture for fake-intake
* datadog-acceptance: add DogstatsD stats enablement (required for curling agent local endpoint)
* datadog-acceptance: add DogstatsD stats enablement (required for curling agent local endpoint)
* datadog-acceptance: first-round fake-intake testing - works but is innaccurate
* datadog-acceptance: datadog framework - remove dd client agent requirement (fake-intake)
* datadog-acceptance: update flags to not require API and APP key (fake-intake)
* datadog-acceptance: go mod updates for uuid downgrade
* acceptance-test: remove otlp acceptance test -- no fake-intake or agent endpoint to verify
* datadog-acceptance: acceptance test lint fixes
* acceptance-test: update control-plane/cni/main.go l:272 comment with period for lint testing.
* acceptance-test: retry lint fixes
* acceptance-test: correct telemetry collector URL from grpc:// to http://
  • Loading branch information
natemollica-nm committed Apr 21, 2024
1 parent cf76890 commit 897cf11
Show file tree
Hide file tree
Showing 25 changed files with 1,036 additions and 33 deletions.
6 changes: 6 additions & 0 deletions .changelog/3685.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
```release-note:bug
helm: corrected datadog openmetrics and consul-checks consul server URLs set during automation to use full consul deployment release name
```
```release-note:bug
helm: bug fix for `prometheus.io` annotation omission while using datadog integration with openmetrics/prometheus and consul integration checks
```
3 changes: 3 additions & 0 deletions acceptance/framework/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ type TestConfig struct {
EnableEnterprise bool
EnterpriseLicense string

SkipDataDogTests bool
DatadogHelmChartVersion string

EnableOpenshift bool

EnablePodSecurityPolicies bool
Expand Down
65 changes: 48 additions & 17 deletions acceptance/framework/consul/helm_cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,9 @@ import (

"github.com/gruntwork-io/terratest/modules/helm"
terratestLogger "github.com/gruntwork-io/terratest/modules/logger"
"github.com/hashicorp/consul-k8s/acceptance/framework/config"
"github.com/hashicorp/consul-k8s/acceptance/framework/environment"
"github.com/hashicorp/consul-k8s/acceptance/framework/helpers"
"github.com/hashicorp/consul-k8s/acceptance/framework/k8s"
"github.com/hashicorp/consul-k8s/acceptance/framework/logger"
"github.com/hashicorp/consul-k8s/acceptance/framework/portforward"
"github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1"
"github.com/hashicorp/consul/api"
"github.com/hashicorp/consul/sdk/testutil/retry"
"github.com/stretchr/testify/require"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
corev1 "k8s.io/api/core/v1"
policyv1beta "k8s.io/api/policy/v1beta1"
rbacv1 "k8s.io/api/rbac/v1"
Expand All @@ -32,6 +25,18 @@ import (
"k8s.io/client-go/kubernetes"
"sigs.k8s.io/controller-runtime/pkg/client"
gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1"

"github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1"
"github.com/hashicorp/consul/api"
"github.com/hashicorp/consul/proto-public/pbresource"
"github.com/hashicorp/consul/sdk/testutil/retry"

"github.com/hashicorp/consul-k8s/acceptance/framework/config"
"github.com/hashicorp/consul-k8s/acceptance/framework/environment"
"github.com/hashicorp/consul-k8s/acceptance/framework/helpers"
"github.com/hashicorp/consul-k8s/acceptance/framework/k8s"
"github.com/hashicorp/consul-k8s/acceptance/framework/logger"
"github.com/hashicorp/consul-k8s/acceptance/framework/portforward"
)

// HelmCluster implements Cluster and uses Helm
Expand Down Expand Up @@ -153,7 +158,12 @@ func (h *HelmCluster) Create(t *testing.T) {
if h.ChartPath != "" {
chartName = h.ChartPath
}
helm.Install(t, h.helmOptions, chartName, h.releaseName)

// Retry the install in case previous tests have not finished cleaning up.
retry.RunWith(&retry.Counter{Wait: 2 * time.Second, Count: 30}, t, func(r *retry.R) {
err := helm.InstallE(r, h.helmOptions, chartName, h.releaseName)
require.NoError(r, err)
})

k8s.WaitForAllPodsToBeReady(t, h.kubernetesClient, h.helmOptions.KubectlOptions.Namespace, fmt.Sprintf("release=%s", h.releaseName))
}
Expand Down Expand Up @@ -203,7 +213,7 @@ func (h *HelmCluster) Destroy(t *testing.T) {
}

retry.RunWith(&retry.Counter{Wait: 2 * time.Second, Count: 30}, t, func(r *retry.R) {
err := helm.DeleteE(t, h.helmOptions, h.releaseName, false)
err := helm.DeleteE(r, h.helmOptions, h.releaseName, false)
require.NoError(r, err)
})

Expand All @@ -220,7 +230,7 @@ func (h *HelmCluster) Destroy(t *testing.T) {
var gracePeriod int64 = 0
err := h.kubernetesClient.CoreV1().Pods(h.helmOptions.KubectlOptions.Namespace).Delete(context.Background(), pod.Name, metav1.DeleteOptions{GracePeriodSeconds: &gracePeriod})
if !errors.IsNotFound(err) {
require.NoError(t, err)
require.NoError(r, err)
}
}
}
Expand Down Expand Up @@ -298,7 +308,7 @@ func (h *HelmCluster) Destroy(t *testing.T) {
if strings.Contains(sa.Name, h.releaseName) {
err := h.kubernetesClient.CoreV1().ServiceAccounts(h.helmOptions.KubectlOptions.Namespace).Delete(context.Background(), sa.Name, metav1.DeleteOptions{})
if !errors.IsNotFound(err) {
require.NoError(t, err)
require.NoError(r, err)
}
}
}
Expand All @@ -310,7 +320,7 @@ func (h *HelmCluster) Destroy(t *testing.T) {
if strings.Contains(role.Name, h.releaseName) {
err := h.kubernetesClient.RbacV1().Roles(h.helmOptions.KubectlOptions.Namespace).Delete(context.Background(), role.Name, metav1.DeleteOptions{})
if !errors.IsNotFound(err) {
require.NoError(t, err)
require.NoError(r, err)
}
}
}
Expand All @@ -322,7 +332,7 @@ func (h *HelmCluster) Destroy(t *testing.T) {
if strings.Contains(roleBinding.Name, h.releaseName) {
err := h.kubernetesClient.RbacV1().RoleBindings(h.helmOptions.KubectlOptions.Namespace).Delete(context.Background(), roleBinding.Name, metav1.DeleteOptions{})
if !errors.IsNotFound(err) {
require.NoError(t, err)
require.NoError(r, err)
}
}
}
Expand All @@ -334,7 +344,7 @@ func (h *HelmCluster) Destroy(t *testing.T) {
if strings.Contains(secret.Name, h.releaseName) {
err := h.kubernetesClient.CoreV1().Secrets(h.helmOptions.KubectlOptions.Namespace).Delete(context.Background(), secret.Name, metav1.DeleteOptions{})
if !errors.IsNotFound(err) {
require.NoError(t, err)
require.NoError(r, err)
}
}
}
Expand All @@ -346,7 +356,7 @@ func (h *HelmCluster) Destroy(t *testing.T) {
if strings.Contains(job.Name, h.releaseName) {
err := h.kubernetesClient.BatchV1().Jobs(h.helmOptions.KubectlOptions.Namespace).Delete(context.Background(), job.Name, metav1.DeleteOptions{})
if !errors.IsNotFound(err) {
require.NoError(t, err)
require.NoError(r, err)
}
}
}
Expand Down Expand Up @@ -469,6 +479,7 @@ func (h *HelmCluster) Upgrade(t *testing.T, helmValues map[string]string) {
k8s.WaitForAllPodsToBeReady(t, h.kubernetesClient, h.helmOptions.KubectlOptions.Namespace, fmt.Sprintf("release=%s", h.releaseName))
}

// CreatePortForwardTunnel returns the local address:port of a tunnel to the consul server pod in the given release.
func (h *HelmCluster) CreatePortForwardTunnel(t *testing.T, remotePort int, release ...string) string {
releaseName := h.releaseName
if len(release) > 0 {
Expand All @@ -478,6 +489,26 @@ func (h *HelmCluster) CreatePortForwardTunnel(t *testing.T, remotePort int, rele
return portforward.CreateTunnelToResourcePort(t, serverPod, remotePort, h.helmOptions.KubectlOptions, h.logger)
}

// ResourceClient returns a resource service grpc client for the given helm release.
func (h *HelmCluster) ResourceClient(t *testing.T, secure bool, release ...string) (client pbresource.ResourceServiceClient) {
if secure {
panic("TODO: add support for secure resource client")
}
releaseName := h.releaseName
if len(release) > 0 {
releaseName = release[0]
}

// TODO: get grpc port from somewhere
localTunnelAddr := h.CreatePortForwardTunnel(t, 8502, releaseName)

// Create a grpc connection to the server pod.
grpcConn, err := grpc.Dial(localTunnelAddr, grpc.WithTransportCredentials(insecure.NewCredentials()))
require.NoError(t, err)
resourceClient := pbresource.NewResourceServiceClient(grpcConn)
return resourceClient
}

func (h *HelmCluster) SetupConsulClient(t *testing.T, secure bool, release ...string) (client *api.Client, configAddress string) {
t.Helper()

Expand Down
190 changes: 190 additions & 0 deletions acceptance/framework/datadog/datadog.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
package datadog

import (
"context"
"fmt"
"github.com/hashicorp/consul-k8s/acceptance/framework/k8s"
"github.com/hashicorp/consul/sdk/testutil/retry"
"github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"testing"
"time"

"github.com/hashicorp/consul-k8s/acceptance/framework/config"
"github.com/hashicorp/consul-k8s/acceptance/framework/helpers"
"github.com/hashicorp/consul-k8s/acceptance/framework/logger"

"github.com/gruntwork-io/terratest/modules/helm"
terratestk8s "github.com/gruntwork-io/terratest/modules/k8s"
terratestLogger "github.com/gruntwork-io/terratest/modules/logger"
"github.com/hashicorp/consul-k8s/acceptance/framework/environment"
"k8s.io/client-go/kubernetes"
)

const (
releaseLabel = "app.kubernetes.io/name"
OperatorReleaseName = "datadog-operator"
DefaultHelmChartVersion = "1.4.0"
datadogSecretName = "datadog-secret"
datadogAPIKey = "api-key"
datadogAppKey = "app-key"
datadogFakeAPIKey = "DD_FAKEAPIKEY"
datadogFakeAPPKey = "DD_FAKEAPPKEY"
)

type DatadogCluster struct {
ctx environment.TestContext

helmOptions *helm.Options
releaseName string

kubectlOptions *terratestk8s.KubectlOptions

kubernetesClient kubernetes.Interface

noCleanupOnFailure bool
noCleanup bool
debugDirectory string
logger terratestLogger.TestLogger
}

// releaseLabelSelector returns label selector that selects all pods
// from a Datadog installation.
func (d *DatadogCluster) releaseLabelSelector() string {
return fmt.Sprintf("%s=%s", releaseLabel, d.releaseName)
}

func NewDatadogCluster(t *testing.T, ctx environment.TestContext, cfg *config.TestConfig, releaseName string, releaseNamespace string, helmValues map[string]string) *DatadogCluster {
logger := terratestLogger.New(logger.TestLogger{})

configureNamespace(t, ctx.KubernetesClient(t), cfg, releaseNamespace)

createOrUpdateDatadogSecret(t, ctx.KubernetesClient(t), cfg, releaseNamespace)

kopts := ctx.KubectlOptionsForNamespace(releaseNamespace)

values := defaultHelmValues()

ddHelmChartVersion := DefaultHelmChartVersion
if cfg.DatadogHelmChartVersion != "" {
ddHelmChartVersion = cfg.DatadogHelmChartVersion
}

helpers.MergeMaps(values, helmValues)
datadogHelmOpts := &helm.Options{
SetValues: values,
KubectlOptions: kopts,
Logger: logger,
Version: ddHelmChartVersion,
}

helm.AddRepo(t, datadogHelmOpts, "datadog", "https://helm.datadoghq.com")
// Ignoring the error from `helm repo update` as it could fail due to stale cache or unreachable servers and we're
// asserting a chart version on Install which would fail in an obvious way should this not succeed.
_, err := helm.RunHelmCommandAndGetOutputE(t, &helm.Options{}, "repo", "update")
if err != nil {
logger.Logf(t, "Unable to update helm repository, proceeding anyway: %s.", err)
}

return &DatadogCluster{
ctx: ctx,
helmOptions: datadogHelmOpts,
kubectlOptions: kopts,
kubernetesClient: ctx.KubernetesClient(t),
noCleanupOnFailure: cfg.NoCleanupOnFailure,
noCleanup: cfg.NoCleanup,
debugDirectory: cfg.DebugDirectory,
logger: logger,
releaseName: releaseName,
}
}

func (d *DatadogCluster) Create(t *testing.T) {
t.Helper()

helpers.Cleanup(t, d.noCleanupOnFailure, d.noCleanup, func() {
d.Destroy(t)
})

helm.Install(t, d.helmOptions, "datadog/datadog-operator", d.releaseName)
// Wait for the datadog-operator to become ready
k8s.WaitForAllPodsToBeReady(t, d.kubernetesClient, d.helmOptions.KubectlOptions.Namespace, d.releaseLabelSelector())
}

func (d *DatadogCluster) Destroy(t *testing.T) {
t.Helper()

k8s.WritePodsDebugInfoIfFailed(t, d.kubectlOptions, d.debugDirectory, d.releaseLabelSelector())
// Ignore the error returned by the helm delete here so that we can
// always idempotent clean up resources in the cluster.
_ = helm.DeleteE(t, d.helmOptions, d.releaseName, true)
}

func defaultHelmValues() map[string]string {
return map[string]string{
"replicaCount": "1",
"image.tag": DefaultHelmChartVersion,
"image.repository": "gcr.io/datadoghq/operator",
}
}

func configureNamespace(t *testing.T, client kubernetes.Interface, cfg *config.TestConfig, namespace string) {
ctx := context.Background()

ns := &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: namespace,
Labels: map[string]string{},
},
}
if cfg.EnableRestrictedPSAEnforcement {
ns.ObjectMeta.Labels["pod-security.kubernetes.io/enforce"] = "restricted"
ns.ObjectMeta.Labels["pod-security.kubernetes.io/enforce-version"] = "latest"
}

_, createErr := client.CoreV1().Namespaces().Create(ctx, ns, metav1.CreateOptions{})
if createErr == nil {
logger.Logf(t, "Created namespace %s", namespace)
return
}

_, updateErr := client.CoreV1().Namespaces().Update(ctx, ns, metav1.UpdateOptions{})
if updateErr == nil {
logger.Logf(t, "Updated namespace %s", namespace)
return
}

require.Failf(t, "Failed to create or update namespace", "Namespace=%s, CreateError=%s, UpdateError=%s", namespace, createErr, updateErr)
}

func createOrUpdateDatadogSecret(t *testing.T, client kubernetes.Interface, cfg *config.TestConfig, namespace string) {
secretMap := map[string]string{
datadogAPIKey: datadogFakeAPIKey,
datadogAppKey: datadogFakeAPPKey,
}
createMultiKeyK8sSecret(t, client, cfg, namespace, datadogSecretName, secretMap)
}

func createMultiKeyK8sSecret(t *testing.T, client kubernetes.Interface, cfg *config.TestConfig, namespace, secretName string, secretMap map[string]string) {
retry.RunWith(&retry.Counter{Wait: 2 * time.Second, Count: 15}, t, func(r *retry.R) {
_, err := client.CoreV1().Secrets(namespace).Get(context.Background(), secretName, metav1.GetOptions{})
if errors.IsNotFound(err) {
_, err := client.CoreV1().Secrets(namespace).Create(context.Background(), &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: secretName,
},
StringData: secretMap,
Type: corev1.SecretTypeOpaque,
}, metav1.CreateOptions{})
require.NoError(r, err)
} else {
require.NoError(r, err)
}
})

helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() {
_ = client.CoreV1().Secrets(namespace).Delete(context.Background(), secretName, metav1.DeleteOptions{})
})
}
9 changes: 7 additions & 2 deletions acceptance/framework/flags/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ type TestFlags struct {

flagEnableOpenshift bool

flagSkipDatadogTests bool

flagEnablePodSecurityPolicies bool

flagEnableCNI bool
Expand Down Expand Up @@ -155,6 +157,9 @@ func (t *TestFlags) init() {
flag.BoolVar(&t.flagDisablePeering, "disable-peering", false,
"If true, the peering tests will not run.")

flag.BoolVar(&t.flagSkipDatadogTests, "skip-datadog", false,
"If true, datadog acceptance tests will not run.")

if t.flagEnterpriseLicense == "" {
t.flagEnterpriseLicense = os.Getenv("CONSUL_ENT_LICENSE")
}
Expand Down Expand Up @@ -198,11 +203,9 @@ func (t *TestFlags) TestConfigFromFlags() *config.TestConfig {
// if the Version is empty consulVersion will be nil
consulVersion, _ := version.NewVersion(t.flagConsulVersion)
consulDataplaneVersion, _ := version.NewVersion(t.flagConsulDataplaneVersion)
//vaultserverVersion, _ := version.NewVersion(t.flagVaultServerVersion)
kubeEnvs := config.NewKubeTestConfigList(t.flagKubeconfigs, t.flagKubecontexts, t.flagKubeNamespaces)

c := &config.TestConfig{

EnableEnterprise: t.flagEnableEnterprise,
EnterpriseLicense: t.flagEnterpriseLicense,

Expand All @@ -211,6 +214,8 @@ func (t *TestFlags) TestConfigFromFlags() *config.TestConfig {

EnableOpenshift: t.flagEnableOpenshift,

SkipDataDogTests: t.flagSkipDatadogTests,

EnablePodSecurityPolicies: t.flagEnablePodSecurityPolicies,

EnableCNI: t.flagEnableCNI,
Expand Down
Loading

0 comments on commit 897cf11

Please sign in to comment.