Skip to content

Commit

Permalink
Fix down-conversion of IdentityRef
Browse files Browse the repository at this point in the history
We were not setting IdentityRef.Kind when down-converting an object with
no previous version annotation, which results in a validation error.
  • Loading branch information
mdbooth committed Jul 4, 2024
1 parent 1f87904 commit ade7f77
Show file tree
Hide file tree
Showing 8 changed files with 141 additions and 28 deletions.
5 changes: 4 additions & 1 deletion api/v1alpha6/openstackcluster_conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -364,7 +364,10 @@ func Convert_v1beta1_OpenStackClusterSpec_To_v1alpha6_OpenStackClusterSpec(in *i
}

out.CloudName = in.IdentityRef.CloudName
out.IdentityRef = &OpenStackIdentityReference{Name: in.IdentityRef.Name}
out.IdentityRef = &OpenStackIdentityReference{}
if err := Convert_v1beta1_OpenStackIdentityReference_To_v1alpha6_OpenStackIdentityReference(&in.IdentityRef, out.IdentityRef, s); err != nil {
return err
}

if in.APIServerLoadBalancer != nil {
if err := Convert_v1beta1_APIServerLoadBalancer_To_v1alpha6_APIServerLoadBalancer(in.APIServerLoadBalancer, &out.APIServerLoadBalancer, s); err != nil {
Expand Down
1 change: 0 additions & 1 deletion api/v1alpha6/openstackmachine_conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -373,7 +373,6 @@ func Convert_v1beta1_OpenStackMachineSpec_To_v1alpha6_OpenStackMachineSpec(in *i
}

if in.IdentityRef != nil {
out.IdentityRef = &OpenStackIdentityReference{Name: in.IdentityRef.Name}
out.CloudName = in.IdentityRef.CloudName
}

Expand Down
2 changes: 2 additions & 0 deletions api/v1alpha6/types_conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -534,6 +534,8 @@ func Convert_v1alpha6_OpenStackIdentityReference_To_v1beta1_OpenStackIdentityRef

func Convert_v1beta1_OpenStackIdentityReference_To_v1alpha6_OpenStackIdentityReference(in *infrav1.OpenStackIdentityReference, out *OpenStackIdentityReference, _ apiconversion.Scope) error {
out.Name = in.Name
// Kind will be overwritten during restore if it was previously set to some other value, but if not then some value is still required
out.Kind = "Secret"
return nil
}

Expand Down
5 changes: 4 additions & 1 deletion api/v1alpha7/openstackcluster_conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,10 @@ func Convert_v1beta1_OpenStackClusterSpec_To_v1alpha7_OpenStackClusterSpec(in *i
}

out.CloudName = in.IdentityRef.CloudName
out.IdentityRef = &OpenStackIdentityReference{Name: in.IdentityRef.Name}
out.IdentityRef = &OpenStackIdentityReference{}
if err := Convert_v1beta1_OpenStackIdentityReference_To_v1alpha7_OpenStackIdentityReference(&in.IdentityRef, out.IdentityRef, s); err != nil {
return err
}

if in.APIServerLoadBalancer != nil {
if err := Convert_v1beta1_APIServerLoadBalancer_To_v1alpha7_APIServerLoadBalancer(in.APIServerLoadBalancer, &out.APIServerLoadBalancer, s); err != nil {
Expand Down
2 changes: 2 additions & 0 deletions api/v1alpha7/types_conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -536,6 +536,8 @@ func Convert_v1alpha7_OpenStackIdentityReference_To_v1beta1_OpenStackIdentityRef

func Convert_v1beta1_OpenStackIdentityReference_To_v1alpha7_OpenStackIdentityReference(in *infrav1.OpenStackIdentityReference, out *OpenStackIdentityReference, _ apiconversion.Scope) error {
out.Name = in.Name
// Kind will be overwritten during restore if it was previously set to some other value, but if not then some value is still required
out.Kind = "Secret"
return nil
}

Expand Down
81 changes: 56 additions & 25 deletions test/e2e/suites/apivalidations/openstackcluster_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package apivalidations
import (
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/onsi/gomega/format"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/utils/ptr"
Expand All @@ -33,16 +34,6 @@ import (
var _ = Describe("OpenStackCluster API validations", func() {
var namespace *corev1.Namespace

create := func(obj client.Object) error {
err := k8sClient.Create(ctx, obj)
if err == nil {
DeferCleanup(func() error {
return k8sClient.Delete(ctx, obj)
})
}
return err
}

BeforeEach(func() {
namespace = createNamespace()
})
Expand All @@ -58,12 +49,12 @@ var _ = Describe("OpenStackCluster API validations", func() {
})

It("should allow the smallest permissible cluster spec", func() {
Expect(create(cluster)).To(Succeed(), "OpenStackCluster creation should succeed")
Expect(createObj(cluster)).To(Succeed(), "OpenStackCluster creation should succeed")
})

It("should only allow controlPlaneEndpoint to be set once", func() {
By("Creating a bare cluster")
Expect(create(cluster)).To(Succeed(), "OpenStackCluster creation should succeed")
Expect(createObj(cluster)).To(Succeed(), "OpenStackCluster creation should succeed")

By("Setting the control plane endpoint")
cluster.Spec.ControlPlaneEndpoint = &clusterv1.APIEndpoint{
Expand All @@ -79,12 +70,12 @@ var _ = Describe("OpenStackCluster API validations", func() {

It("should allow an empty managed security groups definition", func() {
cluster.Spec.ManagedSecurityGroups = &infrav1.ManagedSecurityGroups{}
Expect(create(cluster)).To(Succeed(), "OpenStackCluster creation should succeed")
Expect(createObj(cluster)).To(Succeed(), "OpenStackCluster creation should succeed")
})

It("should default enabled to true if APIServerLoadBalancer is specified without enabled=true", func() {
cluster.Spec.APIServerLoadBalancer = &infrav1.APIServerLoadBalancer{}
Expect(create(cluster)).To(Succeed(), "OpenStackCluster creation should succeed")
Expect(createObj(cluster)).To(Succeed(), "OpenStackCluster creation should succeed")

// Fetch the cluster and check the defaulting
fetchedCluster := &infrav1.OpenStackCluster{}
Expand All @@ -95,7 +86,7 @@ var _ = Describe("OpenStackCluster API validations", func() {
})

It("should not default APIServerLoadBalancer if it is not specifid", func() {
Expect(create(cluster)).To(Succeed(), "OpenStackCluster creation should succeed")
Expect(createObj(cluster)).To(Succeed(), "OpenStackCluster creation should succeed")

// Fetch the cluster and check the defaulting
fetchedCluster := &infrav1.OpenStackCluster{}
Expand All @@ -116,19 +107,19 @@ var _ = Describe("OpenStackCluster API validations", func() {
},
},
}
Expect(create(cluster)).To(Succeed(), "OpenStackCluster creation should succeed")
Expect(createObj(cluster)).To(Succeed(), "OpenStackCluster creation should succeed")
})

It("should not allow bastion.enabled=true without a spec", func() {
cluster.Spec.Bastion = &infrav1.Bastion{
Enabled: ptr.To(true),
}
Expect(create(cluster)).NotTo(Succeed(), "OpenStackCluster creation should not succeed")
Expect(createObj(cluster)).NotTo(Succeed(), "OpenStackCluster creation should not succeed")
})

It("should not allow an empty Bastion", func() {
cluster.Spec.Bastion = &infrav1.Bastion{}
Expect(create(cluster)).NotTo(Succeed(), "OpenStackCluster creation should not succeed")
Expect(createObj(cluster)).NotTo(Succeed(), "OpenStackCluster creation should not succeed")
})

It("should default bastion.enabled=true", func() {
Expand All @@ -141,7 +132,7 @@ var _ = Describe("OpenStackCluster API validations", func() {
},
},
}
Expect(create(cluster)).To(Succeed(), "OpenStackCluster creation should not succeed")
Expect(createObj(cluster)).To(Succeed(), "OpenStackCluster creation should not succeed")

// Fetch the cluster and check the defaulting
fetchedCluster := &infrav1.OpenStackCluster{}
Expand All @@ -162,7 +153,7 @@ var _ = Describe("OpenStackCluster API validations", func() {
},
FloatingIP: ptr.To("10.0.0.0"),
}
Expect(create(cluster)).To(Succeed(), "OpenStackCluster creation should succeed")
Expect(createObj(cluster)).To(Succeed(), "OpenStackCluster creation should succeed")
})

It("should not allow non-IPv4 as bastion floating IP", func() {
Expand All @@ -176,7 +167,7 @@ var _ = Describe("OpenStackCluster API validations", func() {
},
FloatingIP: ptr.To("foobar"),
}
Expect(create(cluster)).NotTo(Succeed(), "OpenStackCluster creation should not succeed")
Expect(createObj(cluster)).NotTo(Succeed(), "OpenStackCluster creation should not succeed")
})
})

Expand All @@ -196,7 +187,7 @@ var _ = Describe("OpenStackCluster API validations", func() {
Kind: "FakeKind",
Name: "identity-ref",
}
Expect(create(cluster)).To(Succeed(), "OpenStackCluster creation should succeed")
Expect(createObj(cluster)).To(Succeed(), "OpenStackCluster creation should succeed")

// Fetch the infrav1 version of the cluster
infrav1Cluster := &infrav1.OpenStackCluster{}
Expand All @@ -218,7 +209,7 @@ var _ = Describe("OpenStackCluster API validations", func() {

It("should not enable an explicitly disabled bastion when converting to v1beta1", func() {
cluster.Spec.Bastion = &infrav1alpha7.Bastion{Enabled: false}
Expect(create(cluster)).To(Succeed(), "OpenStackCluster creation should succeed")
Expect(createObj(cluster)).To(Succeed(), "OpenStackCluster creation should succeed")

// Fetch the infrav1 version of the cluster
infrav1Cluster := &infrav1.OpenStackCluster{}
Expand All @@ -233,6 +224,26 @@ var _ = Describe("OpenStackCluster API validations", func() {
Expect(infrav1Bastion).ToNot(BeNil(), "Bastion should not have been removed")
Expect(infrav1Bastion.Enabled).To(Equal(ptr.To(false)), "Bastion should remain disabled")
})

It("should downgrade cleanly from infrav1", func() {
infrav1Cluster := &infrav1.OpenStackCluster{}
infrav1Cluster.Namespace = namespace.Name
infrav1Cluster.GenerateName = clusterNamePrefix
infrav1Cluster.Spec.IdentityRef.CloudName = "test-cloud"
infrav1Cluster.Spec.IdentityRef.Name = "test-credentials"
Expect(createObj(infrav1Cluster)).To(Succeed(), "infrav1 OpenStackCluster creation should succeed")

// Just fetching the object as v1alpha6 doesn't trigger
// validation failure, so we first fetch it and then
// patch the object with identical contents. The patch
// triggers a validation failure.
cluster := &infrav1alpha7.OpenStackCluster{}
Expect(k8sClient.Get(ctx, types.NamespacedName{Name: infrav1Cluster.Name, Namespace: infrav1Cluster.Namespace}, cluster)).To(Succeed(), "OpenStackCluster fetch should succeed")

setObjectGVK(cluster)
cluster.ManagedFields = nil
Expect(k8sClient.Patch(ctx, cluster, client.Apply, client.FieldOwner("test"), client.ForceOwnership)).To(Succeed(), format.Object(cluster, 4))
})
})

Context("v1alpha6", func() {
Expand All @@ -251,7 +262,7 @@ var _ = Describe("OpenStackCluster API validations", func() {
Kind: "FakeKind",
Name: "identity-ref",
}
Expect(create(cluster)).To(Succeed(), "OpenStackCluster creation should succeed")
Expect(createObj(cluster)).To(Succeed(), "OpenStackCluster creation should succeed")

// Fetch the infrav1 version of the cluster
infrav1Cluster := &infrav1.OpenStackCluster{}
Expand All @@ -273,7 +284,7 @@ var _ = Describe("OpenStackCluster API validations", func() {

It("should not enable an explicitly disabled bastion when converting to v1beta1", func() {
cluster.Spec.Bastion = &infrav1alpha6.Bastion{Enabled: false}
Expect(create(cluster)).To(Succeed(), "OpenStackCluster creation should succeed")
Expect(createObj(cluster)).To(Succeed(), "OpenStackCluster creation should succeed")

// Fetch the infrav1 version of the cluster
infrav1Cluster := &infrav1.OpenStackCluster{}
Expand All @@ -288,5 +299,25 @@ var _ = Describe("OpenStackCluster API validations", func() {
Expect(infrav1Bastion).ToNot(BeNil(), "Bastion should not have been removed")
Expect(infrav1Bastion.Enabled).To(Equal(ptr.To(false)), "Bastion should remain disabled")
})

It("should downgrade cleanly from infrav1", func() {
infrav1Cluster := &infrav1.OpenStackCluster{}
infrav1Cluster.Namespace = namespace.Name
infrav1Cluster.GenerateName = clusterNamePrefix
infrav1Cluster.Spec.IdentityRef.CloudName = "test-cloud"
infrav1Cluster.Spec.IdentityRef.Name = "test-credentials"
Expect(createObj(infrav1Cluster)).To(Succeed(), "infrav1 OpenStackCluster creation should succeed")

// Just fetching the object as v1alpha6 doesn't trigger
// validation failure, so we first fetch it and then
// patch the object with identical contents. The patch
// triggers a validation failure.
cluster := &infrav1alpha6.OpenStackCluster{} //nolint:staticcheck
Expect(k8sClient.Get(ctx, types.NamespacedName{Name: infrav1Cluster.Name, Namespace: infrav1Cluster.Namespace}, cluster)).To(Succeed(), "OpenStackCluster fetch should succeed")

setObjectGVK(cluster)
cluster.ManagedFields = nil
Expect(k8sClient.Patch(ctx, cluster, client.Apply, client.FieldOwner("test"), client.ForceOwnership)).To(Succeed(), format.Object(cluster, 4))
})
})
})
55 changes: 55 additions & 0 deletions test/e2e/suites/apivalidations/openstackmachine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,14 @@ import (

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/onsi/gomega/format"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/utils/ptr"
"sigs.k8s.io/controller-runtime/pkg/client"

infrav1alpha6 "sigs.k8s.io/cluster-api-provider-openstack/api/v1alpha6"
infrav1alpha7 "sigs.k8s.io/cluster-api-provider-openstack/api/v1alpha7"
infrav1 "sigs.k8s.io/cluster-api-provider-openstack/api/v1beta1"
)

Expand Down Expand Up @@ -278,4 +283,54 @@ var _ = Describe("OpenStackMachine API validations", func() {
Expect(k8sClient.Create(ctx, machine)).NotTo(Succeed(), "Creating a machine with an additional block device with an empty name availability zone should fail")
})
})

Context("v1alpha7", func() {
It("should downgrade cleanly from infrav1", func() {
infrav1Machine := &infrav1.OpenStackMachine{}
infrav1Machine.Namespace = namespace.Name
infrav1Machine.GenerateName = clusterNamePrefix
infrav1Machine.Spec.IdentityRef = &infrav1.OpenStackIdentityReference{
CloudName: "test-cloud",
Name: "test-credentials",
}
infrav1Machine.Spec.Image.ID = ptr.To("de9872ee-0c2c-44ed-9414-90163c8b0e0d")
Expect(createObj(infrav1Machine)).To(Succeed(), "infrav1 OpenStackMachine creation should succeed")

// Just fetching the object as v1alpha6 doesn't trigger
// validation failure, so we first fetch it and then
// patch the object with identical contents. The patch
// triggers a validation failure.
machine := &infrav1alpha7.OpenStackMachine{}
Expect(k8sClient.Get(ctx, types.NamespacedName{Name: infrav1Machine.Name, Namespace: infrav1Machine.Namespace}, machine)).To(Succeed(), "OpenStackMachine fetch should succeed")

setObjectGVK(machine)
machine.ManagedFields = nil
Expect(k8sClient.Patch(ctx, machine, client.Apply, client.FieldOwner("test"), client.ForceOwnership)).To(Succeed(), format.Object(machine, 4))
})
})

Context("v1alpha6", func() {
It("should downgrade cleanly from infrav1", func() {
infrav1Machine := &infrav1.OpenStackMachine{}
infrav1Machine.Namespace = namespace.Name
infrav1Machine.GenerateName = clusterNamePrefix
infrav1Machine.Spec.IdentityRef = &infrav1.OpenStackIdentityReference{
CloudName: "test-cloud",
Name: "test-credentials",
}
infrav1Machine.Spec.Image.ID = ptr.To("de9872ee-0c2c-44ed-9414-90163c8b0e0d")
Expect(createObj(infrav1Machine)).To(Succeed(), "infrav1 OpenStackMachine creation should succeed")

// Just fetching the object as v1alpha6 doesn't trigger
// validation failure, so we first fetch it and then
// patch the object with identical contents. The patch
// triggers a validation failure.
machine := &infrav1alpha6.OpenStackMachine{} //nolint:staticcheck
Expect(k8sClient.Get(ctx, types.NamespacedName{Name: infrav1Machine.Name, Namespace: infrav1Machine.Namespace}, machine)).To(Succeed(), "OpenStackMachine fetch should succeed")

setObjectGVK(machine)
machine.ManagedFields = nil
Expect(k8sClient.Patch(ctx, machine, client.Apply, client.FieldOwner("test"), client.ForceOwnership)).To(Succeed(), format.Object(machine, 4))
})
})
})
18 changes: 18 additions & 0 deletions test/e2e/suites/apivalidations/suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,3 +165,21 @@ func createNamespace() *corev1.Namespace {
By(fmt.Sprintf("Using namespace %s", namespace.Name))
return &namespace
}

func createObj(obj client.Object) error {
err := k8sClient.Create(ctx, obj)
if err == nil {
DeferCleanup(func() error {
return k8sClient.Delete(ctx, obj)
})
}
return err
}

func setObjectGVK(obj runtime.Object) {
gvk, unversioned, err := testScheme.ObjectKinds(obj)
Expect(unversioned).To(BeFalse(), "Object is considered unversioned")
Expect(err).ToNot(HaveOccurred(), "Error fetching gvk for Object")
Expect(gvk).To(HaveLen(1), "Object should have only one gvk")
obj.GetObjectKind().SetGroupVersionKind(gvk[0])
}

0 comments on commit ade7f77

Please sign in to comment.