From fb171294eb371a18bed08f9a9c9e9c14ccd86a85 Mon Sep 17 00:00:00 2001 From: fabriziopandini Date: Mon, 26 Jul 2021 13:06:15 +0200 Subject: [PATCH] Add test utils for cluster class --- .../clustertopology_controller_compute.go | 50 +++ ...clustertopology_controller_compute_test.go | 324 ++++++++++++++++++ 2 files changed, 374 insertions(+) create mode 100644 controllers/clustertopology_controller_compute_test.go diff --git a/controllers/clustertopology_controller_compute.go b/controllers/clustertopology_controller_compute.go index 753a0720e00e..ba75ab92a977 100644 --- a/controllers/clustertopology_controller_compute.go +++ b/controllers/clustertopology_controller_compute.go @@ -19,8 +19,11 @@ package controllers import ( "context" + "github.com/pkg/errors" + corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" clusterv1 "sigs.k8s.io/cluster-api/api/v1alpha4" + "sigs.k8s.io/controller-runtime/pkg/client" ) // clusterTopologyClass holds all the objects required for computing the desired state of a managed Cluster topology. @@ -83,3 +86,50 @@ func (r *ClusterTopologyReconciler) computeDesiredState(ctx context.Context, inp // TODO: add compute logic return nil, nil } + +// getNestedRef returns the ref value of a nested field. +func getNestedRef(obj *unstructured.Unstructured, fields ...string) (*corev1.ObjectReference, error) { + ref := &corev1.ObjectReference{} + if v, ok, err := unstructured.NestedString(obj.UnstructuredContent(), append(fields, "apiVersion")...); ok && err == nil { + ref.APIVersion = v + } else { + return nil, errors.Errorf("failed to get reference apiVersion") + } + if v, ok, err := unstructured.NestedString(obj.UnstructuredContent(), append(fields, "kind")...); ok && err == nil { + ref.Kind = v + } else { + return nil, errors.Errorf("failed to get reference Kind") + } + if v, ok, err := unstructured.NestedString(obj.UnstructuredContent(), append(fields, "name")...); ok && err == nil { + ref.Name = v + } else { + return nil, errors.Errorf("failed to get reference name") + } + if v, ok, err := unstructured.NestedString(obj.UnstructuredContent(), append(fields, "namespace")...); ok && err == nil { + ref.Namespace = v + } else { + return nil, errors.Errorf("failed to get reference namespace") + } + return ref, nil +} + +// setNestedRef sets the value of a nested field to a reference to the refObj provided. +func setNestedRef(obj, refObj *unstructured.Unstructured, fields ...string) error { + ref := map[string]interface{}{ + "kind": refObj.GetKind(), + "namespace": refObj.GetNamespace(), + "name": refObj.GetName(), + "apiVersion": refObj.GetAPIVersion(), + } + return unstructured.SetNestedField(obj.UnstructuredContent(), ref, fields...) +} + +func objToRef(obj client.Object) *corev1.ObjectReference { + gvk := obj.GetObjectKind().GroupVersionKind() + return &corev1.ObjectReference{ + Kind: gvk.Kind, + APIVersion: gvk.GroupVersion().String(), + Namespace: obj.GetNamespace(), + Name: obj.GetName(), + } +} diff --git a/controllers/clustertopology_controller_compute_test.go b/controllers/clustertopology_controller_compute_test.go new file mode 100644 index 000000000000..321b36461f44 --- /dev/null +++ b/controllers/clustertopology_controller_compute_test.go @@ -0,0 +1,324 @@ +/* +Copyright 2021 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controllers + +import ( + "testing" + + . "github.com/onsi/gomega" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" + clusterv1 "sigs.k8s.io/cluster-api/api/v1alpha4" +) + +func TestGetNestedRef(t *testing.T) { + t.Run("Gets a nested ref if defined", func(t *testing.T) { + g := NewWithT(t) + + infrastructureMachineTemplate := newFakeInfrastructureMachineTemplate(metav1.NamespaceDefault, "control-plane-machine-infrastructure-template1").Obj() + controlPlaneTemplate := newFakeControlPlane(metav1.NamespaceDefault, "control-plane-template"). + WithInfrastructureMachineTemplate(infrastructureMachineTemplate). + Obj() + + ref, err := getNestedRef(controlPlaneTemplate, "spec", "machineTemplate", "infrastructureRef") + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(ref).ToNot(BeNil()) + g.Expect(ref.APIVersion).To(Equal(infrastructureMachineTemplate.GetAPIVersion())) + g.Expect(ref.Kind).To(Equal(infrastructureMachineTemplate.GetKind())) + g.Expect(ref.Name).To(Equal(infrastructureMachineTemplate.GetName())) + g.Expect(ref.Namespace).To(Equal(infrastructureMachineTemplate.GetNamespace())) + }) + t.Run("getNestedRef fails if the nested ref does not exist", func(t *testing.T) { + g := NewWithT(t) + + controlPlaneTemplate := newFakeControlPlane(metav1.NamespaceDefault, "control-plane-template").Obj() + + ref, err := getNestedRef(controlPlaneTemplate, "spec", "machineTemplate", "infrastructureRef") + g.Expect(err).To(HaveOccurred()) + g.Expect(ref).To(BeNil()) + }) + t.Run("getNestedRef fails if the nested ref exist but it is incomplete", func(t *testing.T) { + g := NewWithT(t) + + controlPlaneTemplate := newFakeControlPlane(metav1.NamespaceDefault, "control-plane-template").Obj() + + err := unstructured.SetNestedField(controlPlaneTemplate.UnstructuredContent(), "foo", "spec", "machineTemplate", "infrastructureRef", "kind") + g.Expect(err).ToNot(HaveOccurred()) + err = unstructured.SetNestedField(controlPlaneTemplate.UnstructuredContent(), "bar", "spec", "machineTemplate", "infrastructureRef", "namespace") + g.Expect(err).ToNot(HaveOccurred()) + err = unstructured.SetNestedField(controlPlaneTemplate.UnstructuredContent(), "baz", "spec", "machineTemplate", "infrastructureRef", "apiVersion") + g.Expect(err).ToNot(HaveOccurred()) + // Reference name missing + + ref, err := getNestedRef(controlPlaneTemplate, "spec", "machineTemplate", "infrastructureRef") + g.Expect(err).To(HaveOccurred()) + g.Expect(ref).To(BeNil()) + }) +} + +func TestSetNestedRef(t *testing.T) { + t.Run("Sets a nested ref", func(t *testing.T) { + g := NewWithT(t) + infrastructureMachineTemplate := newFakeInfrastructureMachineTemplate(metav1.NamespaceDefault, "control-plane-machine-infrastructure-template1").Obj() + controlPlaneTemplate := newFakeControlPlane(metav1.NamespaceDefault, "control-plane-template").Obj() + + err := setNestedRef(controlPlaneTemplate, infrastructureMachineTemplate, "spec", "machineTemplate", "infrastructureRef") + g.Expect(err).To(BeNil()) + + ref, err := getNestedRef(controlPlaneTemplate, "spec", "machineTemplate", "infrastructureRef") + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(ref).ToNot(BeNil()) + g.Expect(ref.APIVersion).To(Equal(infrastructureMachineTemplate.GetAPIVersion())) + g.Expect(ref.Kind).To(Equal(infrastructureMachineTemplate.GetKind())) + g.Expect(ref.Name).To(Equal(infrastructureMachineTemplate.GetName())) + g.Expect(ref.Namespace).To(Equal(infrastructureMachineTemplate.GetNamespace())) + }) +} + +func TestObjToRef(t *testing.T) { + t.Run("Gets a ref from an obj", func(t *testing.T) { + g := NewWithT(t) + infrastructureMachineTemplate := newFakeInfrastructureMachineTemplate(metav1.NamespaceDefault, "control-plane-machine-infrastructure-template1").Obj() + ref := objToRef(infrastructureMachineTemplate) + + g.Expect(ref).ToNot(BeNil()) + g.Expect(ref.APIVersion).To(Equal(infrastructureMachineTemplate.GetAPIVersion())) + g.Expect(ref.Kind).To(Equal(infrastructureMachineTemplate.GetKind())) + g.Expect(ref.Name).To(Equal(infrastructureMachineTemplate.GetName())) + g.Expect(ref.Namespace).To(Equal(infrastructureMachineTemplate.GetNamespace())) + }) +} + +var ( + fakeInfrastructureProviderGroupVersion = schema.GroupVersion{Group: "infrastructure.cluster.x-k8s.io", Version: "v1alpha4"} + fakeControlPlaneProviderGroupVersion = schema.GroupVersion{Group: "controlplane.cluster.x-k8s.io", Version: "v1alpha4"} +) + +type fakeClusterClass struct { + namespace string + name string + infrastructureClusterTemplate *unstructured.Unstructured + controlPlaneTemplate *unstructured.Unstructured + controlPlaneInfrastructureMachineTemplate *unstructured.Unstructured +} + +func newFakeClusterClass(namespace, name string) *fakeClusterClass { //nolint:deadcode + return &fakeClusterClass{ + namespace: namespace, + name: name, + } +} + +func (f *fakeClusterClass) WithInfrastructureClusterTemplate(t *unstructured.Unstructured) *fakeClusterClass { + f.infrastructureClusterTemplate = t + return f +} + +func (f *fakeClusterClass) WithControlPlaneTemplate(t *unstructured.Unstructured) *fakeClusterClass { + f.controlPlaneTemplate = t + return f +} + +func (f *fakeClusterClass) WithControlPlaneInfrastructureMachineTemplate(t *unstructured.Unstructured) *fakeClusterClass { + f.controlPlaneInfrastructureMachineTemplate = t + return f +} + +func (f *fakeClusterClass) Obj() *clusterv1.ClusterClass { + obj := &clusterv1.ClusterClass{ + TypeMeta: metav1.TypeMeta{ + Kind: "ClusterClass", + APIVersion: clusterv1.GroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: f.name, + Namespace: f.namespace, + }, + Spec: clusterv1.ClusterClassSpec{}, + } + if f.infrastructureClusterTemplate != nil { + obj.Spec.Infrastructure = clusterv1.LocalObjectTemplate{ + Ref: objToRef(f.infrastructureClusterTemplate), + } + } + if f.controlPlaneTemplate != nil { + obj.Spec.ControlPlane = clusterv1.ControlPlaneClass{ + LocalObjectTemplate: clusterv1.LocalObjectTemplate{ + Ref: objToRef(f.controlPlaneTemplate), + }, + } + if f.controlPlaneInfrastructureMachineTemplate != nil { + obj.Spec.ControlPlane.MachineInfrastructure = &clusterv1.LocalObjectTemplate{ + Ref: objToRef(f.controlPlaneInfrastructureMachineTemplate), + } + } + } + return obj +} + +type fakeInfrastructureClusterTemplate struct { + namespace string + name string +} + +func newFakeInfrastructureClusterTemplate(namespace, name string) *fakeInfrastructureClusterTemplate { //nolint:deadcode + return &fakeInfrastructureClusterTemplate{ + namespace: namespace, + name: name, + } +} + +func (f *fakeInfrastructureClusterTemplate) Obj() *unstructured.Unstructured { + obj := &unstructured.Unstructured{} + obj.SetAPIVersion(fakeInfrastructureProviderGroupVersion.String()) + obj.SetKind("FakeInfrastructureClusterTemplate") + obj.SetNamespace(f.namespace) + obj.SetName(f.name) + + if err := unstructured.SetNestedField(obj.UnstructuredContent(), true, "spec", "template", "spec", "fakeSetting"); err != nil { + panic(err) + } + + return obj +} + +type fakeInfrastructureMachineTemplate struct { + namespace string + name string +} + +func newFakeInfrastructureMachineTemplate(namespace, name string) *fakeInfrastructureMachineTemplate { + return &fakeInfrastructureMachineTemplate{ + namespace: namespace, + name: name, + } +} + +func (f *fakeInfrastructureMachineTemplate) Obj() *unstructured.Unstructured { + obj := &unstructured.Unstructured{} + obj.SetAPIVersion(fakeInfrastructureProviderGroupVersion.String()) + obj.SetKind("FakeInfrastructureMachineTemplate") + obj.SetNamespace(f.namespace) + obj.SetName(f.name) + + if err := unstructured.SetNestedField(obj.UnstructuredContent(), true, "spec", "template", "spec", "fakeSetting"); err != nil { + panic(err) + } + + return obj +} + +type fakeControlPlaneTemplate struct { + namespace string + name string + infrastructureMachineTemplate *unstructured.Unstructured +} + +func newFakeControlPlaneTemplate(namespace, name string) *fakeControlPlaneTemplate { //nolint:deadcode + return &fakeControlPlaneTemplate{ + namespace: namespace, + name: name, + } +} + +func (f *fakeControlPlaneTemplate) WithInfrastructureMachineTemplate(t *unstructured.Unstructured) *fakeControlPlaneTemplate { + f.infrastructureMachineTemplate = t + return f +} + +func (f *fakeControlPlaneTemplate) Obj() *unstructured.Unstructured { + obj := &unstructured.Unstructured{} + obj.SetAPIVersion(fakeControlPlaneProviderGroupVersion.String()) + obj.SetKind("FakeControlPlaneTemplate") + obj.SetNamespace(f.namespace) + obj.SetName(f.name) + + if err := unstructured.SetNestedField(obj.UnstructuredContent(), true, "spec", "template", "spec", "fakeSetting"); err != nil { + panic(err) + } + + if f.infrastructureMachineTemplate != nil { + if err := setNestedRef(obj, f.infrastructureMachineTemplate, "spec", "template", "spec", "machineTemplate", "infrastructureRef"); err != nil { + panic(err) + } + } + return obj +} + +type fakeInfrastructureCluster struct { + namespace string + name string +} + +func newFakeInfrastructureCluster(namespace, name string) *fakeInfrastructureCluster { //nolint:deadcode + return &fakeInfrastructureCluster{ + namespace: namespace, + name: name, + } +} + +func (f *fakeInfrastructureCluster) Obj() *unstructured.Unstructured { + obj := &unstructured.Unstructured{} + obj.SetAPIVersion(fakeControlPlaneProviderGroupVersion.String()) + obj.SetKind("FakeInfrastructureCluster") + obj.SetNamespace(f.namespace) + obj.SetName(f.name) + + if err := unstructured.SetNestedField(obj.UnstructuredContent(), true, "spec", "fakeSetting"); err != nil { + panic(err) + } + + return obj +} + +type fakeControlPlane struct { + namespace string + name string + infrastructureMachineTemplate *unstructured.Unstructured +} + +func newFakeControlPlane(namespace, name string) *fakeControlPlane { + return &fakeControlPlane{ + namespace: namespace, + name: name, + } +} + +func (f *fakeControlPlane) WithInfrastructureMachineTemplate(t *unstructured.Unstructured) *fakeControlPlane { + f.infrastructureMachineTemplate = t + return f +} + +func (f *fakeControlPlane) Obj() *unstructured.Unstructured { + obj := &unstructured.Unstructured{} + obj.SetAPIVersion(fakeControlPlaneProviderGroupVersion.String()) + obj.SetKind("FakeControlPlane") + obj.SetNamespace(f.namespace) + obj.SetName(f.name) + + if err := unstructured.SetNestedField(obj.UnstructuredContent(), true, "spec", "fakeSetting"); err != nil { + panic(err) + } + + if f.infrastructureMachineTemplate != nil { + if err := setNestedRef(obj, f.infrastructureMachineTemplate, "spec", "machineTemplate", "infrastructureRef"); err != nil { + panic(err) + } + } + return obj +}