Skip to content

Commit 6d7f931

Browse files
Merge pull request #536 from fmount/topology_envtest
Improve topology testing coverage
2 parents 9d89c22 + cd6a7ce commit 6d7f931

File tree

2 files changed

+161
-22
lines changed

2 files changed

+161
-22
lines changed

tests/functional/base_test.go

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,17 +22,18 @@ import (
2222

2323
. "github.com/onsi/gomega" //revive:disable:dot-imports
2424

25+
topologyv1 "github.com/openstack-k8s-operators/infra-operator/apis/topology/v1beta1"
26+
ironicv1 "github.com/openstack-k8s-operators/ironic-operator/api/v1beta1"
2527
ironic_pkg "github.com/openstack-k8s-operators/ironic-operator/pkg/ironic"
2628
ironic_inspector_pkg "github.com/openstack-k8s-operators/ironic-operator/pkg/ironicinspector"
29+
condition "github.com/openstack-k8s-operators/lib-common/modules/common/condition"
2730
corev1 "k8s.io/api/core/v1"
31+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2832
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2933
"k8s.io/apimachinery/pkg/runtime/schema"
3034
"k8s.io/apimachinery/pkg/types"
3135
"sigs.k8s.io/controller-runtime/pkg/client"
3236
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
33-
34-
ironicv1 "github.com/openstack-k8s-operators/ironic-operator/api/v1beta1"
35-
condition "github.com/openstack-k8s-operators/lib-common/modules/common/condition"
3637
)
3738

3839
const (
@@ -655,9 +656,11 @@ func CreateUnstructured(rawObj map[string]interface{}) *unstructured.Unstructure
655656
return unstructuredObj
656657
}
657658

658-
// GetSampleTopologySpec - A sample (and opinionated) Topology Spec used to
659-
// test Ironic components
660-
func GetSampleTopologySpec(svcSelector string) map[string]interface{} {
659+
// GetSampleTopologySpec - An opinionated Topology Spec sample used to
660+
// test Service components. It returns both the user input representation
661+
// in the form of map[string]string, and the Golang expected representation
662+
// used in the test asserts.
663+
func GetSampleTopologySpec(label string) (map[string]interface{}, []corev1.TopologySpreadConstraint) {
661664
// Build the topology Spec
662665
topologySpec := map[string]interface{}{
663666
"topologySpreadConstraints": []map[string]interface{}{
@@ -667,13 +670,26 @@ func GetSampleTopologySpec(svcSelector string) map[string]interface{} {
667670
"whenUnsatisfiable": "ScheduleAnyway",
668671
"labelSelector": map[string]interface{}{
669672
"matchLabels": map[string]interface{}{
670-
"service": svcSelector,
673+
"component": label,
671674
},
672675
},
673676
},
674677
},
675678
}
676-
return topologySpec
679+
// Build the topologyObj representation
680+
topologySpecObj := []corev1.TopologySpreadConstraint{
681+
{
682+
MaxSkew: 1,
683+
TopologyKey: corev1.LabelHostname,
684+
WhenUnsatisfiable: corev1.ScheduleAnyway,
685+
LabelSelector: &metav1.LabelSelector{
686+
MatchLabels: map[string]string{
687+
"component": label,
688+
},
689+
},
690+
},
691+
}
692+
return topologySpec, topologySpecObj
677693
}
678694

679695
// CreateTopology - Creates a Topology CR based on the spec passed as input
@@ -689,3 +705,12 @@ func CreateTopology(topology types.NamespacedName, spec map[string]interface{})
689705
}
690706
return th.CreateUnstructured(raw)
691707
}
708+
709+
// GetTopology - Returns the referenced Topology
710+
func GetTopology(name types.NamespacedName) *topologyv1.Topology {
711+
instance := &topologyv1.Topology{}
712+
Eventually(func(g Gomega) {
713+
g.Expect(k8sClient.Get(ctx, name, instance)).Should(Succeed())
714+
}, timeout, interval).Should(Succeed())
715+
return instance
716+
}

tests/functional/ironic_controller_test.go

Lines changed: 128 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import (
2828

2929
. "github.com/openstack-k8s-operators/lib-common/modules/common/test/helpers"
3030

31+
topologyv1 "github.com/openstack-k8s-operators/infra-operator/apis/topology/v1beta1"
3132
ironicv1 "github.com/openstack-k8s-operators/ironic-operator/api/v1beta1"
3233
"github.com/openstack-k8s-operators/lib-common/modules/common/condition"
3334
mariadb_test "github.com/openstack-k8s-operators/mariadb-operator/api/test/helpers"
@@ -227,11 +228,21 @@ var _ = Describe("Ironic controller", func() {
227228
})
228229

229230
When("Ironic is created with topologyref", func() {
231+
var topologyRef, topologyRefAlt *topologyv1.TopoRef
230232
BeforeEach(func() {
231-
// Build the topology Spec
232-
topologySpec := GetSampleTopologySpec("ironic")
233+
// Define the two topology references used in this test
234+
topologyRef = &topologyv1.TopoRef{
235+
Name: ironicNames.IronicTopologies[0].Name,
236+
Namespace: ironicNames.IronicTopologies[0].Namespace,
237+
}
238+
topologyRefAlt = &topologyv1.TopoRef{
239+
Name: ironicNames.IronicTopologies[1].Name,
240+
Namespace: ironicNames.IronicTopologies[1].Namespace,
241+
}
233242
// Create Test Topologies
234243
for _, t := range ironicNames.IronicTopologies {
244+
// Build the topology Spec
245+
topologySpec, _ := GetSampleTopologySpec(t.Name)
235246
CreateTopology(t, topologySpec)
236247
}
237248
DeferCleanup(
@@ -254,7 +265,7 @@ var _ = Describe("Ironic controller", func() {
254265
keystone.CreateKeystoneAPI(ironicNames.Namespace))
255266
spec := GetDefaultIronicSpec()
256267
spec["topologyRef"] = map[string]interface{}{
257-
"name": ironicNames.IronicTopologies[0].Name,
268+
"name": topologyRef.Name,
258269
}
259270
DeferCleanup(
260271
th.DeleteInstance,
@@ -286,46 +297,96 @@ var _ = Describe("Ironic controller", func() {
286297

287298
It("sets topology in CR status", func() {
288299
Eventually(func(g Gomega) {
300+
tp := GetTopology(types.NamespacedName{
301+
Name: topologyRef.Name,
302+
Namespace: topologyRef.Namespace,
303+
})
304+
finalizers := tp.GetFinalizers()
305+
g.Expect(finalizers).To(HaveLen(4))
289306
ironicAPI := GetIronicAPI(ironicNames.APIName)
290307
g.Expect(ironicAPI.Status.LastAppliedTopology).ToNot(BeNil())
291-
g.Expect(ironicAPI.Status.LastAppliedTopology.Name).To(Equal(ironicNames.IronicTopologies[0].Name))
308+
g.Expect(ironicAPI.Status.LastAppliedTopology).To(Equal(topologyRef))
309+
g.Expect(finalizers).To(ContainElement(
310+
fmt.Sprintf("openstack.org/ironicapi-%s", ironicAPI.Name)))
311+
292312
ironicConductor := GetIronicConductor(ironicNames.ConductorName)
293313
g.Expect(ironicConductor.Status.LastAppliedTopology).ToNot(BeNil())
294-
g.Expect(ironicConductor.Status.LastAppliedTopology.Name).To(Equal(ironicNames.IronicTopologies[0].Name))
314+
g.Expect(ironicConductor.Status.LastAppliedTopology).To(Equal(topologyRef))
315+
g.Expect(finalizers).To(ContainElement(
316+
fmt.Sprintf("openstack.org/ironicconductor-%s", ironicConductor.Name)))
317+
295318
ironicInspector := GetIronicInspector(ironicNames.InspectorName)
296319
g.Expect(ironicInspector.Status.LastAppliedTopology).ToNot(BeNil())
297-
g.Expect(ironicInspector.Status.LastAppliedTopology.Name).To(Equal(ironicNames.IronicTopologies[0].Name))
320+
g.Expect(ironicInspector.Status.LastAppliedTopology).To(Equal(topologyRef))
321+
g.Expect(finalizers).To(ContainElement(
322+
fmt.Sprintf("openstack.org/ironicinspector-%s", ironicInspector.Name)))
298323
}, timeout, interval).Should(Succeed())
299324
})
300-
It("sets nodeSelector in resource specs", func() {
325+
It("sets topology in resource specs", func() {
301326
Eventually(func(g Gomega) {
327+
_, expectedTopologySpecObj := GetSampleTopologySpec(topologyRef.Name)
302328
g.Expect(th.GetDeployment(ironicNames.IronicName).Spec.Template.Spec.TopologySpreadConstraints).ToNot(BeNil())
329+
g.Expect(th.GetDeployment(ironicNames.IronicName).Spec.Template.Spec.TopologySpreadConstraints).To(Equal(expectedTopologySpecObj))
303330
g.Expect(th.GetDeployment(ironicNames.IronicName).Spec.Template.Spec.Affinity).To(BeNil())
331+
304332
g.Expect(th.GetStatefulSet(ironicNames.InspectorName).Spec.Template.Spec.TopologySpreadConstraints).ToNot(BeNil())
333+
g.Expect(th.GetStatefulSet(ironicNames.InspectorName).Spec.Template.Spec.TopologySpreadConstraints).To(Equal(expectedTopologySpecObj))
305334
g.Expect(th.GetStatefulSet(ironicNames.InspectorName).Spec.Template.Spec.Affinity).To(BeNil())
335+
336+
g.Expect(th.GetStatefulSet(ironicNames.ConductorName).Spec.Template.Spec.TopologySpreadConstraints).To(Equal(expectedTopologySpecObj))
306337
g.Expect(th.GetStatefulSet(ironicNames.ConductorName).Spec.Template.Spec.TopologySpreadConstraints).ToNot(BeNil())
307338
g.Expect(th.GetStatefulSet(ironicNames.ConductorName).Spec.Template.Spec.Affinity).To(BeNil())
339+
308340
g.Expect(th.GetDeployment(ironicNames.INAName).Spec.Template.Spec.TopologySpreadConstraints).ToNot(BeNil())
341+
g.Expect(th.GetDeployment(ironicNames.INAName).Spec.Template.Spec.TopologySpreadConstraints).To(Equal(expectedTopologySpecObj))
309342
g.Expect(th.GetDeployment(ironicNames.INAName).Spec.Template.Spec.Affinity).To(BeNil())
310343
}, timeout, interval).Should(Succeed())
311344
})
312345
It("updates topology when the reference changes", func() {
346+
expectedTopology := &topologyv1.TopoRef{
347+
Name: ironicNames.IronicTopologies[1].Name,
348+
Namespace: ironicNames.IronicTopologies[1].Namespace,
349+
}
350+
var finalizers []string
313351
Eventually(func(g Gomega) {
314352
ironic := GetIronic(ironicNames.IronicName)
315-
ironic.Spec.TopologyRef.Name = ironicNames.IronicTopologies[1].Name
353+
ironic.Spec.TopologyRef.Name = topologyRefAlt.Name
316354
g.Expect(k8sClient.Update(ctx, ironic)).To(Succeed())
317355
}, timeout, interval).Should(Succeed())
318356

319357
Eventually(func(g Gomega) {
358+
tp := GetTopology(types.NamespacedName{
359+
Name: expectedTopology.Name,
360+
Namespace: expectedTopology.Namespace,
361+
})
362+
finalizers = tp.GetFinalizers()
363+
g.Expect(finalizers).To(HaveLen(4))
364+
320365
ironicAPI := GetIronicAPI(ironicNames.APIName)
321366
g.Expect(ironicAPI.Status.LastAppliedTopology).ToNot(BeNil())
322-
g.Expect(ironicAPI.Status.LastAppliedTopology.Name).To(Equal(ironicNames.IronicTopologies[1].Name))
367+
g.Expect(ironicAPI.Status.LastAppliedTopology).To(Equal(topologyRefAlt))
368+
g.Expect(finalizers).To(ContainElement(
369+
fmt.Sprintf("openstack.org/ironicapi-%s", ironicAPI.Name)))
370+
323371
ironicConductor := GetIronicConductor(ironicNames.ConductorName)
324372
g.Expect(ironicConductor.Status.LastAppliedTopology).ToNot(BeNil())
325-
g.Expect(ironicConductor.Status.LastAppliedTopology.Name).To(Equal(ironicNames.IronicTopologies[1].Name))
373+
g.Expect(ironicConductor.Status.LastAppliedTopology).To(Equal(topologyRefAlt))
374+
g.Expect(finalizers).To(ContainElement(
375+
fmt.Sprintf("openstack.org/ironicconductor-%s", ironicConductor.Name)))
376+
326377
ironicInspector := GetIronicInspector(ironicNames.InspectorName)
327378
g.Expect(ironicInspector.Status.LastAppliedTopology).ToNot(BeNil())
328-
g.Expect(ironicInspector.Status.LastAppliedTopology.Name).To(Equal(ironicNames.IronicTopologies[1].Name))
379+
g.Expect(ironicInspector.Status.LastAppliedTopology).To(Equal(topologyRefAlt))
380+
g.Expect(finalizers).To(ContainElement(
381+
fmt.Sprintf("openstack.org/ironicinspector-%s", ironicInspector.Name)))
382+
383+
// Get the previous topology and verify there are no finalizers
384+
// anymore
385+
tp = GetTopology(types.NamespacedName{
386+
Name: topologyRef.Name,
387+
Namespace: topologyRef.Namespace,
388+
})
389+
g.Expect(tp.GetFinalizers()).To(BeEmpty())
329390
}, timeout, interval).Should(Succeed())
330391
})
331392
It("overrides topology when the reference changes", func() {
@@ -347,15 +408,57 @@ var _ = Describe("Ironic controller", func() {
347408
}, timeout, interval).Should(Succeed())
348409

349410
Eventually(func(g Gomega) {
411+
expectedTopology := &topologyv1.TopoRef{
412+
Name: ironicNames.IronicTopologies[1].Name,
413+
Namespace: ironicNames.IronicTopologies[1].Namespace,
414+
}
415+
tp := GetTopology(types.NamespacedName{
416+
Name: expectedTopology.Name,
417+
Namespace: expectedTopology.Namespace,
418+
})
419+
g.Expect(tp.GetFinalizers()).To(HaveLen(1))
420+
finalizers := tp.GetFinalizers()
350421
ironicAPI := GetIronicAPI(ironicNames.APIName)
351422
g.Expect(ironicAPI.Status.LastAppliedTopology).ToNot(BeNil())
352-
g.Expect(ironicAPI.Status.LastAppliedTopology.Name).To(Equal(ironicNames.IronicTopologies[1].Name))
423+
g.Expect(ironicAPI.Status.LastAppliedTopology).To(Equal(expectedTopology))
424+
g.Expect(finalizers).To(ContainElement(
425+
fmt.Sprintf("openstack.org/ironicapi-%s", ironicAPI.Name)))
426+
}, timeout, interval).Should(Succeed())
427+
428+
Eventually(func(g Gomega) {
429+
expectedTopology := &topologyv1.TopoRef{
430+
Name: ironicNames.IronicTopologies[2].Name,
431+
Namespace: ironicNames.IronicTopologies[2].Namespace,
432+
}
433+
tp := GetTopology(types.NamespacedName{
434+
Name: expectedTopology.Name,
435+
Namespace: expectedTopology.Namespace,
436+
})
437+
g.Expect(tp.GetFinalizers()).To(HaveLen(1))
438+
finalizers := tp.GetFinalizers()
353439
ironicConductor := GetIronicConductor(ironicNames.ConductorName)
354440
g.Expect(ironicConductor.Status.LastAppliedTopology).ToNot(BeNil())
355-
g.Expect(ironicConductor.Status.LastAppliedTopology.Name).To(Equal(ironicNames.IronicTopologies[2].Name))
441+
g.Expect(ironicConductor.Status.LastAppliedTopology).To(Equal(expectedTopology))
442+
g.Expect(finalizers).To(ContainElement(
443+
fmt.Sprintf("openstack.org/ironicconductor-%s", ironicConductor.Name)))
444+
}, timeout, interval).Should(Succeed())
445+
446+
Eventually(func(g Gomega) {
447+
expectedTopology := &topologyv1.TopoRef{
448+
Name: ironicNames.IronicTopologies[3].Name,
449+
Namespace: ironicNames.IronicTopologies[3].Namespace,
450+
}
451+
tp := GetTopology(types.NamespacedName{
452+
Name: expectedTopology.Name,
453+
Namespace: expectedTopology.Namespace,
454+
})
455+
g.Expect(tp.GetFinalizers()).To(HaveLen(1))
456+
finalizers := tp.GetFinalizers()
356457
ironicInspector := GetIronicInspector(ironicNames.InspectorName)
357458
g.Expect(ironicInspector.Status.LastAppliedTopology).ToNot(BeNil())
358-
g.Expect(ironicInspector.Status.LastAppliedTopology.Name).To(Equal(ironicNames.IronicTopologies[3].Name))
459+
g.Expect(ironicInspector.Status.LastAppliedTopology).To(Equal(expectedTopology))
460+
g.Expect(finalizers).To(ContainElement(
461+
fmt.Sprintf("openstack.org/ironicinspector-%s", ironicInspector.Name)))
359462
}, timeout, interval).Should(Succeed())
360463
})
361464
It("removes topologyRef from the spec", func() {
@@ -385,6 +488,17 @@ var _ = Describe("Ironic controller", func() {
385488
g.Expect(th.GetDeployment(ironicNames.INAName).Spec.Template.Spec.TopologySpreadConstraints).To(BeNil())
386489
g.Expect(th.GetDeployment(ironicNames.INAName).Spec.Template.Spec.Affinity).ToNot(BeNil())
387490
}, timeout, interval).Should(Succeed())
491+
492+
Eventually(func(g Gomega) {
493+
for _, topology := range ironicNames.IronicTopologies {
494+
// Get the current topology and verify there are no finalizers
495+
tp := GetTopology(types.NamespacedName{
496+
Name: topology.Name,
497+
Namespace: topology.Namespace,
498+
})
499+
g.Expect(tp.GetFinalizers()).To(BeEmpty())
500+
}
501+
}, timeout, interval).Should(Succeed())
388502
})
389503
})
390504
When("Ironic is created with nodeSelector", func() {

0 commit comments

Comments
 (0)