Skip to content

Commit 4ed385c

Browse files
committed
Unit tests.
1 parent 823d281 commit 4ed385c

3 files changed

Lines changed: 511 additions & 29 deletions

File tree

internal/controller/assistant/openstackassistant_controller_test.go

Lines changed: 195 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -21,64 +21,230 @@ import (
2121

2222
. "github.com/onsi/ginkgo/v2"
2323
. "github.com/onsi/gomega"
24+
corev1 "k8s.io/api/core/v1"
25+
rbacv1 "k8s.io/api/rbac/v1"
2426
"k8s.io/apimachinery/pkg/api/errors"
27+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2528
"k8s.io/apimachinery/pkg/types"
29+
"k8s.io/client-go/kubernetes"
2630
"sigs.k8s.io/controller-runtime/pkg/reconcile"
2731

28-
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
29-
3032
assistantv1beta1 "github.com/openstack-k8s-operators/openstack-operator/api/assistant/v1beta1"
3133
)
3234

3335
var _ = Describe("OpenStackAssistant Controller", func() {
34-
Context("When reconciling a resource", func() {
35-
const resourceName = "test-resource"
36+
const resourceName = "test-assistant"
37+
const namespace = "default"
38+
const providerSecretName = "test-provider-secret"
39+
40+
ctx := context.Background()
3641

37-
ctx := context.Background()
42+
typeNamespacedName := types.NamespacedName{
43+
Name: resourceName,
44+
Namespace: namespace,
45+
}
3846

39-
typeNamespacedName := types.NamespacedName{
40-
Name: resourceName,
41-
Namespace: "default", // TODO(user):Modify as needed
47+
BeforeEach(func() {
48+
By("creating the provider secret")
49+
secret := &corev1.Secret{
50+
ObjectMeta: metav1.ObjectMeta{
51+
Name: providerSecretName,
52+
Namespace: namespace,
53+
},
54+
Data: map[string][]byte{
55+
"lightspeed.json": []byte(`{"name":"lightspeed"}`),
56+
},
4257
}
43-
openstackassistant := &assistantv1beta1.OpenStackAssistant{}
58+
err := k8sClient.Get(ctx, types.NamespacedName{Name: providerSecretName, Namespace: namespace}, &corev1.Secret{})
59+
if errors.IsNotFound(err) {
60+
Expect(k8sClient.Create(ctx, secret)).To(Succeed())
61+
}
62+
})
4463

64+
Context("When creating an OpenStackAssistant resource", func() {
4565
BeforeEach(func() {
46-
By("creating the custom resource for the Kind OpenStackAssistant")
47-
err := k8sClient.Get(ctx, typeNamespacedName, openstackassistant)
48-
if err != nil && errors.IsNotFound(err) {
49-
resource := &assistantv1beta1.OpenStackAssistant{
50-
ObjectMeta: metav1.ObjectMeta{
51-
Name: resourceName,
52-
Namespace: "default",
66+
By("creating the OpenStackAssistant resource")
67+
resource := &assistantv1beta1.OpenStackAssistant{
68+
ObjectMeta: metav1.ObjectMeta{
69+
Name: resourceName,
70+
Namespace: namespace,
71+
},
72+
Spec: assistantv1beta1.OpenStackAssistantSpec{
73+
ContainerImage: "quay.io/dprince/goose:oc-fedora",
74+
Provider: assistantv1beta1.ProviderGoose,
75+
LightspeedStack: assistantv1beta1.LightspeedStackSpec{
76+
ProviderSecret: providerSecretName,
5377
},
54-
// TODO(user): Specify other spec details if needed.
55-
}
78+
},
79+
}
80+
err := k8sClient.Get(ctx, typeNamespacedName, &assistantv1beta1.OpenStackAssistant{})
81+
if errors.IsNotFound(err) {
5682
Expect(k8sClient.Create(ctx, resource)).To(Succeed())
5783
}
5884
})
5985

6086
AfterEach(func() {
61-
// TODO(user): Cleanup logic after each test, like removing the resource instance.
6287
resource := &assistantv1beta1.OpenStackAssistant{}
6388
err := k8sClient.Get(ctx, typeNamespacedName, resource)
89+
if err == nil {
90+
resource.Finalizers = nil
91+
Expect(k8sClient.Update(ctx, resource)).To(Succeed())
92+
Expect(k8sClient.Delete(ctx, resource)).To(Succeed())
93+
}
94+
// Clean up cluster-scoped resources
95+
clusterRoleName := "openstackassistant-" + namespace + "-" + resourceName
96+
cr := &rbacv1.ClusterRole{}
97+
if err := k8sClient.Get(ctx, types.NamespacedName{Name: clusterRoleName}, cr); err == nil {
98+
_ = k8sClient.Delete(ctx, cr)
99+
}
100+
crb := &rbacv1.ClusterRoleBinding{}
101+
if err := k8sClient.Get(ctx, types.NamespacedName{Name: clusterRoleName}, crb); err == nil {
102+
_ = k8sClient.Delete(ctx, crb)
103+
}
104+
})
105+
106+
It("should add a finalizer on first reconcile", func() {
107+
reconciler := &OpenStackAssistantReconciler{
108+
Client: k8sClient,
109+
Scheme: k8sClient.Scheme(),
110+
Kclient: kubernetes.NewForConfigOrDie(cfg),
111+
}
112+
113+
result, err := reconciler.Reconcile(ctx, reconcile.Request{
114+
NamespacedName: typeNamespacedName,
115+
})
64116
Expect(err).NotTo(HaveOccurred())
117+
Expect(result.Requeue).To(BeTrue())
65118

66-
By("Cleanup the specific resource instance OpenStackAssistant")
67-
Expect(k8sClient.Delete(ctx, resource)).To(Succeed())
119+
updated := &assistantv1beta1.OpenStackAssistant{}
120+
Expect(k8sClient.Get(ctx, typeNamespacedName, updated)).To(Succeed())
121+
Expect(updated.Finalizers).To(ContainElement(assistantFinalizer))
68122
})
69-
It("should successfully reconcile the resource", func() {
70-
By("Reconciling the created resource")
71-
controllerReconciler := &OpenStackAssistantReconciler{
72-
Client: k8sClient,
73-
Scheme: k8sClient.Scheme(),
123+
124+
It("should create an entrypoint ConfigMap after reconciliation", func() {
125+
reconciler := &OpenStackAssistantReconciler{
126+
Client: k8sClient,
127+
Scheme: k8sClient.Scheme(),
128+
Kclient: kubernetes.NewForConfigOrDie(cfg),
74129
}
75130

76-
_, err := controllerReconciler.Reconcile(ctx, reconcile.Request{
131+
// First reconcile adds finalizer
132+
_, err := reconciler.Reconcile(ctx, reconcile.Request{
77133
NamespacedName: typeNamespacedName,
78134
})
79135
Expect(err).NotTo(HaveOccurred())
80-
// TODO(user): Add more specific assertions depending on your controller's reconciliation logic.
81-
// Example: If you expect a certain status condition after reconciliation, verify it here.
136+
137+
// Second reconcile does the actual work
138+
_, err = reconciler.Reconcile(ctx, reconcile.Request{
139+
NamespacedName: typeNamespacedName,
140+
})
141+
Expect(err).NotTo(HaveOccurred())
142+
143+
cm := &corev1.ConfigMap{}
144+
Expect(k8sClient.Get(ctx, types.NamespacedName{
145+
Name: resourceName + "-entrypoint",
146+
Namespace: namespace,
147+
}, cm)).To(Succeed())
148+
Expect(cm.Data).To(HaveKey("entrypoint.sh"))
149+
Expect(cm.Data["entrypoint.sh"]).To(ContainSubstring("sleep infinity"))
150+
})
151+
152+
It("should create a ClusterRole and ClusterRoleBinding", func() {
153+
reconciler := &OpenStackAssistantReconciler{
154+
Client: k8sClient,
155+
Scheme: k8sClient.Scheme(),
156+
Kclient: kubernetes.NewForConfigOrDie(cfg),
157+
}
158+
159+
// First reconcile adds finalizer
160+
_, _ = reconciler.Reconcile(ctx, reconcile.Request{NamespacedName: typeNamespacedName})
161+
// Second reconcile creates resources
162+
_, err := reconciler.Reconcile(ctx, reconcile.Request{NamespacedName: typeNamespacedName})
163+
Expect(err).NotTo(HaveOccurred())
164+
165+
clusterRoleName := "openstackassistant-" + namespace + "-" + resourceName
166+
167+
cr := &rbacv1.ClusterRole{}
168+
Expect(k8sClient.Get(ctx, types.NamespacedName{Name: clusterRoleName}, cr)).To(Succeed())
169+
Expect(cr.Rules).NotTo(BeEmpty())
170+
171+
hasNodesRule := false
172+
for _, rule := range cr.Rules {
173+
for _, resource := range rule.Resources {
174+
if resource == "nodes" {
175+
hasNodesRule = true
176+
break
177+
}
178+
}
179+
}
180+
Expect(hasNodesRule).To(BeTrue(), "ClusterRole should include nodes resource")
181+
182+
crb := &rbacv1.ClusterRoleBinding{}
183+
Expect(k8sClient.Get(ctx, types.NamespacedName{Name: clusterRoleName}, crb)).To(Succeed())
184+
Expect(crb.RoleRef.Name).To(Equal(clusterRoleName))
185+
Expect(crb.Subjects).To(HaveLen(1))
186+
Expect(crb.Subjects[0].Name).To(Equal("openstackassistant-" + resourceName))
187+
Expect(crb.Subjects[0].Namespace).To(Equal(namespace))
188+
})
189+
190+
It("should create a Pod", func() {
191+
reconciler := &OpenStackAssistantReconciler{
192+
Client: k8sClient,
193+
Scheme: k8sClient.Scheme(),
194+
Kclient: kubernetes.NewForConfigOrDie(cfg),
195+
}
196+
197+
// First reconcile adds finalizer
198+
_, _ = reconciler.Reconcile(ctx, reconcile.Request{NamespacedName: typeNamespacedName})
199+
// Second reconcile creates resources
200+
_, err := reconciler.Reconcile(ctx, reconcile.Request{NamespacedName: typeNamespacedName})
201+
Expect(err).NotTo(HaveOccurred())
202+
203+
pod := &corev1.Pod{}
204+
Expect(k8sClient.Get(ctx, typeNamespacedName, pod)).To(Succeed())
205+
Expect(pod.Spec.Containers).To(HaveLen(1))
206+
Expect(pod.Spec.Containers[0].Name).To(Equal("goose"))
207+
Expect(pod.Spec.Containers[0].Image).To(Equal("quay.io/dprince/goose:oc-fedora"))
208+
Expect(pod.Labels).To(HaveKeyWithValue("service", "openstackassistant"))
209+
})
210+
211+
It("should set status conditions after reconciliation", func() {
212+
reconciler := &OpenStackAssistantReconciler{
213+
Client: k8sClient,
214+
Scheme: k8sClient.Scheme(),
215+
Kclient: kubernetes.NewForConfigOrDie(cfg),
216+
}
217+
218+
// First reconcile adds finalizer
219+
_, _ = reconciler.Reconcile(ctx, reconcile.Request{NamespacedName: typeNamespacedName})
220+
// Second reconcile creates resources
221+
_, err := reconciler.Reconcile(ctx, reconcile.Request{NamespacedName: typeNamespacedName})
222+
Expect(err).NotTo(HaveOccurred())
223+
224+
instance := &assistantv1beta1.OpenStackAssistant{}
225+
Expect(k8sClient.Get(ctx, typeNamespacedName, instance)).To(Succeed())
226+
Expect(instance.Status.Conditions).NotTo(BeEmpty())
227+
Expect(instance.Status.PodName).To(Equal(resourceName))
228+
Expect(instance.Status.Hash).To(HaveKey("podSpec"))
229+
})
230+
})
231+
232+
Context("When the CR does not exist", func() {
233+
It("should return no error", func() {
234+
reconciler := &OpenStackAssistantReconciler{
235+
Client: k8sClient,
236+
Scheme: k8sClient.Scheme(),
237+
Kclient: kubernetes.NewForConfigOrDie(cfg),
238+
}
239+
240+
result, err := reconciler.Reconcile(ctx, reconcile.Request{
241+
NamespacedName: types.NamespacedName{
242+
Name: "nonexistent",
243+
Namespace: namespace,
244+
},
245+
})
246+
Expect(err).NotTo(HaveOccurred())
247+
Expect(result).To(Equal(reconcile.Result{}))
82248
})
83249
})
84250
})

internal/controller/assistant/suite_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
. "github.com/onsi/ginkgo/v2"
2626
. "github.com/onsi/gomega"
2727

28+
rbacv1 "k8s.io/api/rbac/v1"
2829
"k8s.io/client-go/kubernetes/scheme"
2930
"k8s.io/client-go/rest"
3031
"sigs.k8s.io/controller-runtime/pkg/client"
@@ -62,6 +63,9 @@ var _ = BeforeSuite(func() {
6263
err = assistantv1beta1.AddToScheme(scheme.Scheme)
6364
Expect(err).NotTo(HaveOccurred())
6465

66+
err = rbacv1.AddToScheme(scheme.Scheme)
67+
Expect(err).NotTo(HaveOccurred())
68+
6569
// +kubebuilder:scaffold:scheme
6670

6771
By("bootstrapping test environment")

0 commit comments

Comments
 (0)