Skip to content

Commit f02179d

Browse files
committed
unit test
1 parent e27f451 commit f02179d

File tree

11 files changed

+286
-98
lines changed

11 files changed

+286
-98
lines changed

cmd/openmcp-operator/app/run.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -312,9 +312,8 @@ func (o *RunOptions) Run(ctx context.Context) error {
312312
if slices.Contains(o.Controllers, strings.ToLower(provider.ControllerName)) {
313313
utilruntime.Must(clientgoscheme.AddToScheme(mgr.GetScheme()))
314314
utilruntime.Must(api.AddToScheme(mgr.GetScheme()))
315-
316-
if err = provider.NewDeploymentController().SetupWithManager(mgr, o.ProviderGVKList, o.Environment); err != nil {
317-
return fmt.Errorf("unable to setup provider controllers: %w", err)
315+
if err = provider.NewDeploymentController().SetupWithManager(&setupLog, mgr, o.ProviderGVKList, o.Environment); err != nil {
316+
return fmt.Errorf("unable to setup deployment controllers: %w", err)
318317
}
319318
}
320319

internal/controllers/provider/controller.go

Lines changed: 23 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -11,28 +11,33 @@ import (
1111
"github.com/openmcp-project/controller-utils/pkg/logging"
1212
apimeta "k8s.io/apimachinery/pkg/api/meta"
1313
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
14-
"k8s.io/apimachinery/pkg/runtime"
1514
"k8s.io/apimachinery/pkg/runtime/schema"
1615
ctrl "sigs.k8s.io/controller-runtime"
1716
"sigs.k8s.io/controller-runtime/pkg/client"
1817
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
1918
"sigs.k8s.io/controller-runtime/pkg/reconcile"
2019

20+
"github.com/openmcp-project/openmcp-operator/api/constants"
2121
"github.com/openmcp-project/openmcp-operator/api/provider/v1alpha1"
2222
"github.com/openmcp-project/openmcp-operator/internal/controllers/provider/install"
2323
)
2424

2525
const (
26-
openmcpDomain = "openmcp.cloud"
27-
openmcpFinalizer = openmcpDomain + "/finalizer"
28-
openmcpOperationAnnotation = openmcpDomain + "/operation"
29-
operationReconcile = "reconcile"
26+
openmcpFinalizer = constants.OpenMCPGroupName + "/finalizer"
3027

3128
phaseProgressing = "Progressing"
3229
phaseTerminating = "Terminating"
3330
phaseReady = "Ready"
3431
)
3532

33+
func NewProviderReconciler(gvk schema.GroupVersionKind, client client.Client, environment string) *ProviderReconciler {
34+
return &ProviderReconciler{
35+
GroupVersionKind: gvk,
36+
PlatformClient: client,
37+
Environment: environment,
38+
}
39+
}
40+
3641
type ProviderReconciler struct {
3742
schema.GroupVersionKind
3843
PlatformClient client.Client
@@ -55,6 +60,11 @@ func (r *ProviderReconciler) Reconcile(ctx context.Context, req ctrl.Request) (r
5560
return ctrl.Result{}, fmt.Errorf("failed to get provider resource: %w", err)
5661
}
5762

63+
if controller.HasAnnotationWithValue(provider, constants.OperationAnnotation, constants.OperationAnnotationValueIgnore) {
64+
log.Info("Ignoring resource due to ignore operation annotation")
65+
return ctrl.Result{}, nil
66+
}
67+
5868
providerOrig := provider.DeepCopy()
5969

6070
if provider.GetDeletionTimestamp().IsZero() {
@@ -73,42 +83,19 @@ func (r *ProviderReconciler) Reconcile(ctx context.Context, req ctrl.Request) (r
7383
return res, err
7484
}
7585

76-
// HandleJob - the ProviderReconciler reconciles the provider resources (ClusterProviders, ServiceProviders, PlatformServices).
77-
// During a reconcile, the ProviderReconciler creates the init job of the provider and must wait for it to complete.
78-
// Therefore, the ProviderReconciler watches also jobs. The present method handles the job events and creates a reconcile request.
79-
func (r *ProviderReconciler) HandleJob(_ context.Context, job client.Object) []reconcile.Request {
80-
providerKind, found := job.GetLabels()[install.ProviderKindLabel]
81-
if !found || providerKind != r.Kind {
82-
return nil
83-
}
84-
85-
providerName, found := job.GetLabels()[install.ProviderNameLabel]
86-
if !found {
87-
return nil
88-
}
89-
90-
return []reconcile.Request{
91-
{
92-
NamespacedName: client.ObjectKey{
93-
Name: providerName,
94-
},
95-
},
96-
}
97-
}
98-
9986
func (r *ProviderReconciler) handleCreateUpdateOperation(ctx context.Context, provider *unstructured.Unstructured) (ctrl.Result, error) {
10087
log := logging.FromContextOrPanic(ctx)
10188

10289
if err := r.ensureFinalizer(ctx, provider); err != nil {
10390
return reconcile.Result{}, err
10491
}
10592

106-
deploymentSpec, err := r.deploymentSpecFromUnstructured(provider)
93+
deploymentSpec, err := DeploymentSpecFromUnstructured(provider)
10794
if err != nil {
10895
return reconcile.Result{}, err
10996
}
11097

111-
deploymentStatus, err := r.deploymentStatusFromUnstructured(provider)
98+
deploymentStatus, err := DeploymentStatusFromUnstructured(provider)
11299
if err != nil {
113100
return reconcile.Result{}, err
114101
}
@@ -128,7 +115,7 @@ func (r *ProviderReconciler) handleCreateUpdateOperation(ctx context.Context, pr
128115

129116
res, err := r.install(ctx, provider, deploymentSpec, deploymentStatus)
130117

131-
conversionErr := r.deploymentStatusIntoUnstructured(deploymentStatus, provider)
118+
conversionErr := DeploymentStatusIntoUnstructured(deploymentStatus, provider)
132119
err = errors.Join(err, conversionErr)
133120

134121
return res, err
@@ -192,7 +179,7 @@ func (r *ProviderReconciler) install(
192179

193180
func (r *ProviderReconciler) handleDeleteOperation(ctx context.Context, provider *unstructured.Unstructured) (deleted bool, res ctrl.Result, err error) {
194181

195-
deploymentStatus, err := r.deploymentStatusFromUnstructured(provider)
182+
deploymentStatus, err := DeploymentStatusFromUnstructured(provider)
196183
if err != nil {
197184
return false, reconcile.Result{}, err
198185
}
@@ -208,7 +195,7 @@ func (r *ProviderReconciler) handleDeleteOperation(ctx context.Context, provider
208195

209196
deleted, err = installer.UninstallProvider(ctx)
210197
if err != nil {
211-
conversionErr := r.deploymentStatusIntoUnstructured(deploymentStatus, provider)
198+
conversionErr := DeploymentStatusIntoUnstructured(deploymentStatus, provider)
212199
err = errors.Join(err, conversionErr)
213200
return false, reconcile.Result{}, err
214201

@@ -230,11 +217,11 @@ func (r *ProviderReconciler) checkReconcileAnnotation(
230217
provider *unstructured.Unstructured,
231218
deploymentStatus *v1alpha1.DeploymentStatus,
232219
) error {
233-
if controller.HasAnnotationWithValue(provider, openmcpOperationAnnotation, operationReconcile) && deploymentStatus.Phase == phaseReady {
220+
if controller.HasAnnotationWithValue(provider, constants.OperationAnnotation, constants.OperationAnnotationValueReconcile) && deploymentStatus.Phase == phaseReady {
234221
log := logging.FromContextOrPanic(ctx)
235222
deploymentStatus.Phase = phaseProgressing
236223

237-
if err := r.deploymentStatusIntoUnstructured(deploymentStatus, provider); err != nil {
224+
if err := DeploymentStatusIntoUnstructured(deploymentStatus, provider); err != nil {
238225
log.Error(err, "failed to handle reconcile annotation")
239226
return fmt.Errorf("failed to handle reconcile annotation: %w", err)
240227
}
@@ -244,7 +231,7 @@ func (r *ProviderReconciler) checkReconcileAnnotation(
244231
return fmt.Errorf("failed to handle reconcile annotation: unable to change phase of provider %s/%s to progressing: %w", provider.GetNamespace(), provider.GetName(), err)
245232
}
246233

247-
if err := controller.EnsureAnnotation(ctx, r.PlatformClient, provider, openmcpOperationAnnotation, operationReconcile, true, controller.DELETE); err != nil {
234+
if err := controller.EnsureAnnotation(ctx, r.PlatformClient, provider, constants.OperationAnnotation, constants.OperationAnnotationValueReconcile, true, controller.DELETE); err != nil {
248235
log.Error(err, "failed to remove reconcile annotation from provider")
249236
return fmt.Errorf("failed to remove reconcile annotation from provider %s/%s: %w", provider.GetNamespace(), provider.GetName(), err)
250237
}
@@ -284,42 +271,3 @@ func (r *ProviderReconciler) observeGeneration(provider *unstructured.Unstructur
284271
resetConditions(provider, status)
285272
}
286273
}
287-
288-
func (r *ProviderReconciler) deploymentSpecFromUnstructured(provider *unstructured.Unstructured) (*v1alpha1.DeploymentSpec, error) {
289-
deploymentSpec := v1alpha1.DeploymentSpec{}
290-
deploymentSpecRaw, found, err := unstructured.NestedFieldNoCopy(provider.Object, "spec")
291-
if !found {
292-
return nil, fmt.Errorf("provider spec not found")
293-
}
294-
if err != nil {
295-
return nil, fmt.Errorf("failed to get provider spec: %w", err)
296-
}
297-
298-
if err = runtime.DefaultUnstructuredConverter.FromUnstructured(deploymentSpecRaw.(map[string]interface{}), &deploymentSpec); err != nil {
299-
return nil, fmt.Errorf("failed to convert provider spec: %w", err)
300-
}
301-
return &deploymentSpec, nil
302-
}
303-
304-
func (r *ProviderReconciler) deploymentStatusFromUnstructured(provider *unstructured.Unstructured) (*v1alpha1.DeploymentStatus, error) {
305-
status := v1alpha1.DeploymentStatus{}
306-
statusRaw, found, _ := unstructured.NestedFieldNoCopy(provider.Object, "status")
307-
if found {
308-
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(statusRaw.(map[string]interface{}), &status); err != nil {
309-
return nil, fmt.Errorf("failed to convert provider status from unstructured: %w", err)
310-
}
311-
}
312-
return &status, nil
313-
}
314-
315-
func (r *ProviderReconciler) deploymentStatusIntoUnstructured(status *v1alpha1.DeploymentStatus, provider *unstructured.Unstructured) error {
316-
statusRaw, err := runtime.DefaultUnstructuredConverter.ToUnstructured(status)
317-
if err != nil {
318-
return fmt.Errorf("failed to convert provider status to unstructured: %w", err)
319-
}
320-
321-
if err = unstructured.SetNestedField(provider.Object, statusRaw, "status"); err != nil {
322-
return fmt.Errorf("failed to set provider status: %w", err)
323-
}
324-
return nil
325-
}

internal/controllers/provider/controller_test.go

Lines changed: 146 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,29 +3,169 @@ package provider
33
import (
44
. "github.com/onsi/ginkgo/v2"
55
. "github.com/onsi/gomega"
6+
testutils "github.com/openmcp-project/controller-utils/pkg/testing"
67
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
8+
"k8s.io/apimachinery/pkg/runtime"
9+
"k8s.io/apimachinery/pkg/runtime/schema"
10+
"sigs.k8s.io/controller-runtime/pkg/client"
11+
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
12+
"sigs.k8s.io/controller-runtime/pkg/reconcile"
713

14+
apiinstall "github.com/openmcp-project/openmcp-operator/api/install"
815
"github.com/openmcp-project/openmcp-operator/api/provider/v1alpha1"
16+
"github.com/openmcp-project/openmcp-operator/internal/controllers/provider/install"
917
)
1018

1119
var _ = Describe("Deployment Controller", func() {
1220

21+
Context("Reconcile", func() {
22+
23+
var (
24+
scheme = apiinstall.InstallOperatorAPIs(runtime.NewScheme())
25+
environment = "test-environment"
26+
)
27+
28+
buildTestEnvironment := func(testdataDir string, gvk schema.GroupVersionKind) *testutils.Environment {
29+
return testutils.NewEnvironmentBuilder().
30+
WithFakeClient(scheme).
31+
WithInitObjectPath("testdata", testdataDir).
32+
WithReconcilerConstructor(func(c client.Client) reconcile.Reconciler {
33+
return NewProviderReconciler(gvk, c, environment)
34+
}).
35+
Build()
36+
}
37+
38+
getProvider := func(env *testutils.Environment, provider *unstructured.Unstructured) (*v1alpha1.DeploymentSpec, *v1alpha1.DeploymentStatus) {
39+
Expect(env.Client().Get(env.Ctx, client.ObjectKeyFromObject(provider), provider)).To(Succeed())
40+
deploymentSpec, err := DeploymentSpecFromUnstructured(provider)
41+
Expect(err).NotTo(HaveOccurred(), "DeploymentSpec should be retrievable from provider")
42+
deploymentStatus, err := DeploymentStatusFromUnstructured(provider)
43+
Expect(err).NotTo(HaveOccurred(), "DeploymentStatus should be retrievable from provider")
44+
return deploymentSpec, deploymentStatus
45+
}
46+
47+
reconcileProvider := func(env *testutils.Environment, req reconcile.Request, gvk schema.GroupVersionKind) {
48+
provider := &unstructured.Unstructured{}
49+
provider.SetGroupVersionKind(gvk)
50+
provider.SetName(req.Name)
51+
52+
env.ShouldReconcile(req, "Reconcile should not return an error")
53+
deploymentSpec, deploymentStatus := getProvider(env, provider)
54+
Expect(deploymentStatus).NotTo(BeNil(), "Status should not be nil")
55+
Expect(controllerutil.ContainsFinalizer(provider, openmcpFinalizer)).To(BeTrue(), "Finalizer should be present")
56+
Expect(deploymentStatus.ObservedGeneration).To(Equal(provider.GetGeneration()), "ObservedGeneration should be set")
57+
Expect(deploymentStatus.Phase).To(Equal(phaseProgressing), "Phase should be progressing")
58+
Expect(isInitialized(deploymentStatus)).To(BeFalse(), "Provider should not yet be initialized")
59+
Expect(isProviderInstalledAndReady(deploymentStatus)).To(BeFalse(), "Provider should not yet be ready")
60+
values := install.NewValues(provider, deploymentSpec, environment)
61+
job := install.NewJobMutator(values, deploymentSpec, nil).Empty()
62+
Expect(env.Client().Get(env.Ctx, client.ObjectKeyFromObject(job), job)).To(Succeed())
63+
Expect(job.Spec.Template.Spec.Containers[0].Image).To(Equal("test-image:v0.1.0"), "Job container image should match the provider spec")
64+
Expect(job.Spec.Template.Spec.Containers[0].Args).To(ContainElement("init"), "Job container args should contain the init command")
65+
Expect(job.Spec.Template.Spec.Containers[0].Args).To(ContainElement("--environment=test-environment"), "Job container args should contain the environment")
66+
Expect(job.Spec.Template.Spec.Containers[0].Args).To(ContainElement("--verbosity=DEBUG"), "Job container args should contain the verbosity")
67+
Expect(job.Spec.Template.Spec.Containers[0].Env).To(HaveLen(1), "Job container should have an environment variables")
68+
Expect(job.Spec.Template.Spec.Containers[0].Env[0].Name).To(Equal("NAME"), "Job container environment variable name should match the provider spec")
69+
Expect(job.Spec.Template.Spec.Containers[0].Env[0].Value).To(Equal("test-name"), "Job container environment variable value should match the provider spec")
70+
71+
// 2nd reconcile (after init job is completed)
72+
job.Status.Succeeded = 1
73+
Expect(env.Client().Status().Update(env.Ctx, job)).To(Succeed(), "Status update of init job should succeed")
74+
env.ShouldReconcile(req, "2nd reconcile should not return an error")
75+
deploymentSpec, deploymentStatus = getProvider(env, provider)
76+
Expect(deploymentStatus.Phase).To(Equal(phaseProgressing), "Phase should be progressing")
77+
Expect(isInitialized(deploymentStatus)).To(BeTrue(), "Provider should be initialized")
78+
Expect(isProviderInstalledAndReady(deploymentStatus)).To(BeFalse(), "Provider should not yet be ready")
79+
deploy := install.NewDeploymentMutator(values).Empty()
80+
Expect(env.Client().Get(env.Ctx, client.ObjectKeyFromObject(deploy), deploy)).To(Succeed())
81+
Expect(deploy.Spec.Template.Spec.Containers[0].Image).To(Equal("test-image:v0.1.0"), "Deployment container image should match the provider spec")
82+
Expect(deploy.Spec.Template.Spec.Containers[0].Args).To(ContainElement("run"), "Deployment container args should contain the run command")
83+
Expect(deploy.Spec.Template.Spec.Containers[0].Args).To(ContainElement("--environment=test-environment"), "Deployment container args should contain the environment")
84+
Expect(deploy.Spec.Template.Spec.Containers[0].Args).To(ContainElement("--verbosity=DEBUG"), "Deployment container args should contain the verbosity")
85+
Expect(deploy.Spec.Template.Spec.Containers[0].Env).To(HaveLen(1), "Deployment container should have an environment variables")
86+
Expect(deploy.Spec.Template.Spec.Containers[0].Env[0].Name).To(Equal("NAME"), "Deployment container environment variable name should match the provider spec")
87+
Expect(deploy.Spec.Template.Spec.Containers[0].Env[0].Value).To(Equal("test-name"), "Deployment container environment variable value should match the provider spec")
88+
89+
// 3rd reconcile (after deployment is ready)
90+
deploy.Status.Replicas = 1
91+
deploy.Status.UpdatedReplicas = 1
92+
deploy.Status.AvailableReplicas = 1
93+
Expect(env.Client().Status().Update(env.Ctx, deploy)).To(Succeed(), "Status update of deployment should succeed")
94+
env.ShouldReconcile(req, "3rd reconcile should not return an error")
95+
deploymentSpec, deploymentStatus = getProvider(env, provider)
96+
Expect(deploymentStatus.Phase).To(Equal(phaseReady), "Phase should be ready")
97+
Expect(isInitialized(deploymentStatus)).To(BeTrue(), "Provider should be initialized")
98+
Expect(isProviderInstalledAndReady(deploymentStatus)).To(BeTrue(), "Provider should be ready")
99+
100+
// delete the provider
101+
Expect(env.Client().Delete(env.Ctx, provider)).To(Succeed(), "Provider deletion should succeed")
102+
env.ShouldReconcile(req, "Reconcile after provider deletion should not return an error")
103+
}
104+
105+
It("should reconcile a cluster provider", func() {
106+
env := buildTestEnvironment("test-01", v1alpha1.ClusterProviderGKV())
107+
req := testutils.RequestFromStrings("cluster-provider-test-01")
108+
reconcileProvider(env, req, v1alpha1.ClusterProviderGKV())
109+
})
110+
111+
It("should reconcile a service provider", func() {
112+
env := buildTestEnvironment("test-02", v1alpha1.ServiceProviderGKV())
113+
req := testutils.RequestFromStrings("service-provider-test-02")
114+
reconcileProvider(env, req, v1alpha1.ServiceProviderGKV())
115+
})
116+
117+
It("should reconcile a platform service", func() {
118+
env := buildTestEnvironment("test-03", v1alpha1.PlatformServiceGKV())
119+
req := testutils.RequestFromStrings("platform-service-test-03")
120+
reconcileProvider(env, req, v1alpha1.PlatformServiceGKV())
121+
})
122+
123+
})
124+
13125
Context("Converter", func() {
14126

127+
It("should convert a deploymentSpec into an unstructured and back", func() {
128+
spec := &v1alpha1.DeploymentSpec{
129+
Image: "test-image:v0.1.0",
130+
ImagePullSecrets: []v1alpha1.ObjectReference{
131+
{Name: "test-secret-1"},
132+
{Name: "test-secret-2"},
133+
},
134+
}
135+
136+
provider := &unstructured.Unstructured{}
137+
provider.Object = map[string]interface{}{}
138+
Expect(DeploymentSpecIntoUnstructured(spec, provider)).To(Succeed())
139+
140+
spec2, err := DeploymentSpecFromUnstructured(provider)
141+
Expect(err).NotTo(HaveOccurred())
142+
Expect(spec2).To(Equal(spec2))
143+
144+
provider2 := &unstructured.Unstructured{}
145+
provider2.Object = map[string]interface{}{}
146+
Expect(DeploymentSpecIntoUnstructured(spec2, provider2)).To(Succeed())
147+
Expect(provider2.Object).To(Equal(provider.Object))
148+
})
149+
15150
It("should convert a deploymentStatus into an unstructured and back", func() {
16-
originalStatus := &v1alpha1.DeploymentStatus{
151+
status := &v1alpha1.DeploymentStatus{
17152
Conditions: nil,
18153
ObservedGeneration: 6,
19154
Phase: phaseProgressing,
20155
}
21-
r := &ProviderReconciler{}
156+
22157
provider := &unstructured.Unstructured{}
23158
provider.Object = map[string]interface{}{}
24-
err := r.deploymentStatusIntoUnstructured(originalStatus, provider)
25-
Expect(err).NotTo(HaveOccurred())
26-
status, err := r.deploymentStatusFromUnstructured(provider)
159+
Expect(DeploymentStatusIntoUnstructured(status, provider)).To(Succeed())
160+
161+
status2, err := DeploymentStatusFromUnstructured(provider)
27162
Expect(err).NotTo(HaveOccurred())
28-
Expect(status).To(Equal(originalStatus))
163+
Expect(status2).To(Equal(status))
164+
165+
provider2 := &unstructured.Unstructured{}
166+
provider2.Object = map[string]interface{}{}
167+
Expect(DeploymentStatusIntoUnstructured(status2, provider2)).To(Succeed())
168+
Expect(provider2.Object).To(Equal(provider.Object))
29169
})
30170
})
31171
})

0 commit comments

Comments
 (0)