Skip to content

Commit c7bc650

Browse files
committed
Add new e2e2 test for new controllers
Signed-off-by: jose.vazquez <[email protected]>
1 parent 989da28 commit c7bc650

File tree

11 files changed

+634
-7
lines changed

11 files changed

+634
-7
lines changed

Makefile

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -571,12 +571,26 @@ run: prepare-run ## Run a freshly compiled manager against kind
571571
ifdef RUN_YAML
572572
kubectl apply -n $(OPERATOR_NAMESPACE) -f $(RUN_YAML)
573573
endif
574+
ifdef BACKGROUND
575+
@bash -c '(VERSION=$(NEXT_VERSION) \
576+
OPERATOR_POD_NAME=$(OPERATOR_POD_NAME) \
577+
OPERATOR_NAMESPACE=$(OPERATOR_NAMESPACE) \
578+
nohup bin/manager --object-deletion-protection=false --log-level=$(RUN_LOG_LEVEL) \
579+
--atlas-domain=$(ATLAS_DOMAIN) \
580+
--global-api-secret-name=$(ATLAS_KEY_SECRET_NAME) > ako.log 2>&1 & echo $$! > ako.pid \
581+
&& echo "OPERATOR_PID=$$!")'
582+
else
574583
VERSION=$(NEXT_VERSION) \
575584
OPERATOR_POD_NAME=$(OPERATOR_POD_NAME) \
576585
OPERATOR_NAMESPACE=$(OPERATOR_NAMESPACE) \
577586
bin/manager --object-deletion-protection=false --log-level=$(RUN_LOG_LEVEL) \
578587
--atlas-domain=$(ATLAS_DOMAIN) \
579588
--global-api-secret-name=$(ATLAS_KEY_SECRET_NAME)
589+
endif
590+
591+
.PHONY: stop-ako
592+
stop-ako:
593+
@kill `cat ako.pid` && rm ako.pid || echo "AKO process not found or already stopped!"
580594

581595
.PHONY: local-docker-build
582596
local-docker-build:

test/e2e/dry_run_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ import (
3939
"github.com/mongodb/mongodb-atlas-kubernetes/v2/test/helper/e2e/api/atlas"
4040
e2e_config "github.com/mongodb/mongodb-atlas-kubernetes/v2/test/helper/e2e/config"
4141
"github.com/mongodb/mongodb-atlas-kubernetes/v2/test/helper/e2e/model"
42-
"github.com/mongodb/mongodb-atlas-kubernetes/v2/test/helper/e2e/operator"
42+
"github.com/mongodb/mongodb-atlas-kubernetes/v2/test/helper/e2e2/operator"
4343
)
4444

4545
type (

test/e2e2/configs/datadog.sample.yml

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
apiVersion: atlas.mongodb.com/v1
2+
kind: AtlasProject
3+
metadata:
4+
name: atlas-project-test-datadog
5+
spec:
6+
name: atlas-project-test-datadog
7+
---
8+
apiVersion: v1
9+
kind: Secret
10+
metadata:
11+
name: datadog-secret
12+
namespace:
13+
labels:
14+
atlas.mongodb.com/type: credentials
15+
stringData:
16+
apiKey: 1117e51ce6725368c37c3535959a3a75
17+
---
18+
apiVersion: atlas.nextapi.mongodb.com/v1
19+
kind: AtlasThirdPartyIntegration
20+
metadata:
21+
name: atlas-datadog-integ
22+
spec:
23+
projectRef:
24+
name: atlas-project-test-datadog
25+
type: DATADOG
26+
datadog:
27+
apiKeySecretRef:
28+
name: datadog-secret
29+
region: US
30+
sendCollectionLatencyMetrics: enabled
31+
sendDatabaseMetrics: enabled
32+

test/e2e2/integration_test.go

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
// Copyright 2025 MongoDB Inc
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package e2e2_test
16+
17+
import (
18+
"context"
19+
"embed"
20+
"os"
21+
"testing"
22+
"time"
23+
24+
"github.com/go-logr/zapr"
25+
"github.com/stretchr/testify/assert"
26+
"github.com/stretchr/testify/require"
27+
"go.uber.org/zap/zaptest"
28+
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
29+
"sigs.k8s.io/controller-runtime/pkg/client"
30+
ctrllog "sigs.k8s.io/controller-runtime/pkg/log"
31+
32+
akov2next "github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/nextapi/v1"
33+
"github.com/mongodb/mongodb-atlas-kubernetes/v2/test/helper/control"
34+
"github.com/mongodb/mongodb-atlas-kubernetes/v2/test/helper/e2e2/kube"
35+
"github.com/mongodb/mongodb-atlas-kubernetes/v2/test/helper/e2e2/operator"
36+
"github.com/mongodb/mongodb-atlas-kubernetes/v2/test/helper/e2e2/yml"
37+
)
38+
39+
//go:embed configs/*
40+
var configs embed.FS
41+
42+
const (
43+
AtlasThirdPartyIntegrationsCRD = "atlasthirdpartyintegrations.atlas.nextapi.mongodb.com"
44+
)
45+
46+
func TestAtlasThirdPartyIntegrationsCreate(t *testing.T) {
47+
control.SkipTestUnless(t, "AKO_E2E2_TEST")
48+
ns := control.MustEnvVar("OPERATOR_NAMESPACE")
49+
50+
ako := runTestAKO()
51+
ako.Start(t)
52+
defer ako.Stop(t)
53+
ctx := context.Background()
54+
logger := zaptest.NewLogger(t)
55+
logrLogger := zapr.NewLogger(logger)
56+
ctrllog.SetLogger(logrLogger.WithName("test"))
57+
for _, tc := range []struct {
58+
name string
59+
objs []client.Object
60+
wantReady string
61+
}{
62+
{
63+
name: "simple datadog sample",
64+
objs: yml.MustParseCRs(yml.MustOpen(configs, "configs/datadog.sample.yml")),
65+
wantReady: "atlas-datadog-integ",
66+
},
67+
} {
68+
t.Run(tc.name, func(t *testing.T) {
69+
kubeClient, err := kube.NewK8sTest(ctx, AtlasThirdPartyIntegrationsCRD)
70+
require.NoError(t, err, "Kubernetes test env is not available")
71+
72+
require.NoError(t, kube.Apply(ctx, kubeClient, ns, tc.objs...))
73+
integration := akov2next.AtlasThirdPartyIntegration{
74+
ObjectMeta: v1.ObjectMeta{
75+
Name: tc.wantReady,
76+
Namespace: ns,
77+
},
78+
}
79+
key := client.ObjectKeyFromObject(&integration)
80+
assert.NoError(t, kube.WaitConditionOrFailure(time.Minute, func() (bool, error) {
81+
return kube.AssertObjReady(ctx, kubeClient, key, &integration)
82+
}))
83+
})
84+
}
85+
}
86+
87+
func runTestAKO() *operator.Operator {
88+
os.Setenv("EXPERIMENTAL", "true")
89+
return operator.NewOperator(control.MustEnvVar("OPERATOR_NAMESPACE"), os.Stdout, os.Stderr,
90+
"--log-level=-9",
91+
"--global-api-secret-name=mongodb-atlas-operator-api-key",
92+
`--atlas-domain=https://cloud-qa.mongodb.com`,
93+
)
94+
}

test/helper/control/enable.go

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,23 @@
1515
package control
1616

1717
import (
18+
"fmt"
1819
"os"
19-
"strings"
20+
"strconv"
2021
"testing"
2122
)
2223

2324
func Enabled(envvar string) bool {
24-
value := strings.ToLower(os.Getenv(envvar))
25-
return value == "1"
25+
envSet, _ := strconv.ParseBool(os.Getenv(envvar))
26+
return envSet
27+
}
28+
29+
func MustEnvVar(envvar string) string {
30+
value, ok := os.LookupEnv(envvar)
31+
if !ok {
32+
panic(fmt.Errorf("missing required environment variable: %v", envvar))
33+
}
34+
return value
2635
}
2736

2837
func SkipTestUnless(t *testing.T, envvar string) {

test/helper/e2e2/kube/kube.go

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
// Copyright 2025 MongoDB Inc
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package kube
16+
17+
import (
18+
"context"
19+
"errors"
20+
"fmt"
21+
"time"
22+
23+
corev1 "k8s.io/api/core/v1"
24+
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
25+
apierrors "k8s.io/apimachinery/pkg/api/errors"
26+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
27+
"k8s.io/apimachinery/pkg/runtime"
28+
ctrl "sigs.k8s.io/controller-runtime"
29+
"sigs.k8s.io/controller-runtime/pkg/client"
30+
31+
akov2 "github.com/mongodb/mongodb-atlas-kubernetes/v2/api/v1"
32+
akov2next "github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/nextapi/v1"
33+
)
34+
35+
const (
36+
Pause = time.Second
37+
)
38+
39+
type ObjectWithStatus interface {
40+
client.Object
41+
GetConditions() []metav1.Condition
42+
}
43+
44+
// NewK8sTest initializes a test environment on Kubernetes.
45+
// It requires:
46+
// - A running Kubernetes cluster with a local configuration bound to it.
47+
// - The given set CRDs installed in that cluster
48+
func NewK8sTest(ctx context.Context, crds ...string) (client.Client, error) {
49+
kubeClient, err := TestKubeClient()
50+
if err != nil {
51+
return nil, fmt.Errorf("failed to setup Kubernetes test env client: %w", err)
52+
}
53+
54+
for _, targetCRD := range crds {
55+
if err := assertCRD(ctx, kubeClient, targetCRD); err != nil {
56+
return nil, fmt.Errorf("failed to asert for test-required CRD: %w", err)
57+
}
58+
}
59+
return kubeClient, nil
60+
}
61+
62+
// TestKubeClient returns a Kubernetes client for tests.
63+
// It requires a running Kubernetes cluster and a local configuration to it.
64+
// It supports core Kubernetes types, production and experimental CRDs.
65+
func TestKubeClient() (client.Client, error) {
66+
testScheme, err := getTestScheme(
67+
corev1.AddToScheme,
68+
apiextensionsv1.AddToScheme,
69+
akov2.AddToScheme,
70+
akov2next.AddToScheme)
71+
if err != nil {
72+
return nil, fmt.Errorf("failed to setup Kubernetes test env scheme: %w", err)
73+
}
74+
return getKubeClient(testScheme)
75+
}
76+
77+
func Apply(ctx context.Context, kubeClient client.Client, defaultNamespace string, objs ...client.Object) error {
78+
for i, obj := range objs {
79+
if obj.GetNamespace() == "" {
80+
obj = obj.DeepCopyObject().(client.Object)
81+
obj.SetNamespace(defaultNamespace)
82+
}
83+
if err := apply(ctx, kubeClient, obj); err != nil {
84+
return fmt.Errorf("failed to apply object %d: %w", (i + 1), err)
85+
}
86+
}
87+
return nil
88+
}
89+
90+
func apply(ctx context.Context, kubeClient client.Client, obj client.Object) error {
91+
key := client.ObjectKeyFromObject(obj)
92+
old := obj.DeepCopyObject().(client.Object)
93+
err := kubeClient.Get(ctx, key, old)
94+
switch {
95+
case err == nil:
96+
obj = obj.DeepCopyObject().(client.Object)
97+
obj.SetResourceVersion(old.GetResourceVersion())
98+
if err := kubeClient.Update(ctx, obj); err != nil {
99+
return fmt.Errorf("failed to update %s: %w", key, err)
100+
}
101+
case apierrors.IsNotFound(err):
102+
if err := kubeClient.Create(ctx, obj); err != nil {
103+
return fmt.Errorf("failed to create %s: %w", key, err)
104+
}
105+
default:
106+
return fmt.Errorf("failed to apply %s: %w", key, err)
107+
}
108+
return nil
109+
}
110+
111+
type OKOrFailureFunc func() (bool, error)
112+
113+
func WaitConditionOrFailure(timeout time.Duration, okOrFailFn OKOrFailureFunc) error {
114+
start := time.Now()
115+
for {
116+
ok, err := okOrFailFn()
117+
if ok {
118+
return nil
119+
}
120+
if err != nil {
121+
return fmt.Errorf("failed to check condition: %w", err)
122+
}
123+
if time.Since(start) > timeout {
124+
return errors.New("wait condition timed out")
125+
}
126+
time.Sleep(Pause)
127+
}
128+
}
129+
130+
func AssertObjReady(ctx context.Context, kubeClient client.Client, key client.ObjectKey, obj ObjectWithStatus) (bool, error) {
131+
err := kubeClient.Get(ctx, key, obj)
132+
if err != nil {
133+
return false, fmt.Errorf("failed to get object %v: %w", key, err)
134+
}
135+
for _, condition := range obj.GetConditions() {
136+
if condition.Type == "Ready" && condition.Status == metav1.ConditionTrue {
137+
return true, nil
138+
}
139+
}
140+
return false, nil
141+
}
142+
143+
func getTestScheme(addToSchemeFunctions ...func(*runtime.Scheme) error) (*runtime.Scheme, error) {
144+
testScheme := runtime.NewScheme()
145+
for _, addToSchemeFn := range addToSchemeFunctions {
146+
if err := addToSchemeFn(testScheme); err != nil {
147+
return nil, fmt.Errorf("failed to add to testScheme: %w", err)
148+
}
149+
}
150+
return testScheme, nil
151+
}
152+
153+
func getKubeClient(scheme *runtime.Scheme) (client.Client, error) {
154+
restCfg, err := ctrl.GetConfig()
155+
if err != nil {
156+
return nil, fmt.Errorf("failed to get Kubernetes config (is cluster configured?): %w", err)
157+
}
158+
kubeClient, err := client.New(restCfg, client.Options{Scheme: scheme})
159+
if err != nil {
160+
return nil, fmt.Errorf("failed to get Kubernetes client (is cluster up?): %w", err)
161+
}
162+
return kubeClient, nil
163+
}
164+
165+
func assertCRD(ctx context.Context, kubeClient client.Client, targetCRD string) error {
166+
crds := apiextensionsv1.CustomResourceDefinitionList{}
167+
if err := kubeClient.List(ctx, &crds, &client.ListOptions{}); err != nil {
168+
return fmt.Errorf("failed to list CRDs: %w", err)
169+
}
170+
for _, crd := range crds.Items {
171+
if crd.Name == targetCRD {
172+
return nil
173+
}
174+
}
175+
return fmt.Errorf("%s not found", targetCRD)
176+
}
File renamed without changes.

0 commit comments

Comments
 (0)