Skip to content

Commit

Permalink
Use test CRDs with OpenAPI schema in patchhelper
Browse files Browse the repository at this point in the history
  • Loading branch information
fabriziopandini committed May 30, 2022
1 parent d6a12f7 commit b74e38d
Showing 1 changed file with 46 additions and 38 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -33,25 +33,26 @@ func TestServerSideApply(t *testing.T) {

// Write the config file to access the test env for debugging.
// g.Expect(os.WriteFile("test.conf", kubeconfig.FromEnvTestConfig(env.Config, &clusterv1.Cluster{
// ObjectMeta: metav1.ObjectMeta{Name: "test"},
// ObjectMeta: metav1.ObjectMeta{Name: "test"},
// }), 0777)).To(Succeed())

// Create a namespace for running the test
ns, err := env.CreateNamespace(ctx, "ssa")
g.Expect(err).ToNot(HaveOccurred())

// Build the test object to work with.
obj := builder.InfrastructureClusterTemplate(ns.Name, "obj1").WithSpecFields(map[string]interface{}{
"spec.version": "v1.2.3",
"spec.ignoreThisField": "", // this field is then explicitly ignored by the patch helper
obj := builder.TestInfrastructureCluster(ns.Name, "obj1").WithSpecFields(map[string]interface{}{
"spec.controlPlaneEndpoint.host": "1.2.3.4",
"spec.controlPlaneEndpoint.port": int64(1234),
"spec.foo": "", // this field is then explicitly ignored by the patch helper
}).Build()
g.Expect(unstructured.SetNestedField(obj.Object, "", "status", "foo")).To(Succeed()) // this field is then ignored by the patch helper (not allowed path).

t.Run("When creating an object using server side apply, it should track managed fields for the topology controller", func(t *testing.T) {
g := NewWithT(t)

// Create a patch helper with original == nil and modified == obj, ensure this is detected as operation that triggers changes.
p0, err := NewServerSidePatchHelper(fakeScheme, nil, obj.DeepCopy(), env.GetClient(), IgnorePaths{{"spec", "ignoreThisField"}})
p0, err := NewServerSidePatchHelper(fakeScheme, nil, obj.DeepCopy(), env.GetClient(), IgnorePaths{{"spec", "foo"}})
g.Expect(err).ToNot(HaveOccurred())
g.Expect(p0.HasChanges()).To(BeTrue())
g.Expect(p0.HasSpecChanges()).To(BeTrue())
Expand All @@ -69,8 +70,13 @@ func TestServerSideApply(t *testing.T) {

specFieldV1 := fieldV1["f:spec"].(map[string]interface{})
g.Expect(specFieldV1).ToNot(BeEmpty())
g.Expect(specFieldV1).To(HaveKey("f:version")) // topology controller should express opinions on spec.version.
g.Expect(specFieldV1).ToNot(HaveKey("f:ignoreThisField")) // topology controller should not express opinions on ignore paths.
g.Expect(specFieldV1).To(HaveKey("f:controlPlaneEndpoint")) // topology controller should express opinions on spec.controlPlaneEndpoint.
g.Expect(specFieldV1).ToNot(HaveKey("f:foo")) // topology controller should not express opinions on ignore paths.

controlPlaneEndpointFieldV1 := specFieldV1["f:controlPlaneEndpoint"].(map[string]interface{})
g.Expect(controlPlaneEndpointFieldV1).ToNot(BeEmpty())
g.Expect(controlPlaneEndpointFieldV1).To(HaveKey("f:host")) // topology controller should express opinions on spec.controlPlaneEndpoint.host.
g.Expect(controlPlaneEndpointFieldV1).To(HaveKey("f:port")) // topology controller should express opinions on spec.controlPlaneEndpoint.port.
})

t.Run("Server side apply patch helper detects no changes", func(t *testing.T) {
Expand All @@ -82,7 +88,7 @@ func TestServerSideApply(t *testing.T) {

// Create a patch helper for a modified object with no changes.
modified := obj.DeepCopy()
p0, err := NewServerSidePatchHelper(fakeScheme, original, modified, env.GetClient(), IgnorePaths{{"spec", "ignoreThisField"}})
p0, err := NewServerSidePatchHelper(fakeScheme, original, modified, env.GetClient(), IgnorePaths{{"spec", "foo"}})
g.Expect(err).ToNot(HaveOccurred())
g.Expect(p0.HasChanges()).To(BeFalse())
g.Expect(p0.HasSpecChanges()).To(BeFalse())
Expand All @@ -99,7 +105,7 @@ func TestServerSideApply(t *testing.T) {
modified := obj.DeepCopy()
g.Expect(unstructured.SetNestedField(modified.Object, "changed", "status", "foo")).To(Succeed())

p0, err := NewServerSidePatchHelper(fakeScheme, original, modified, env.GetClient(), IgnorePaths{{"spec", "ignoreThisField"}})
p0, err := NewServerSidePatchHelper(fakeScheme, original, modified, env.GetClient(), IgnorePaths{{"spec", "foo"}})
g.Expect(err).ToNot(HaveOccurred())
g.Expect(p0.HasChanges()).To(BeFalse())
g.Expect(p0.HasSpecChanges()).To(BeFalse())
Expand All @@ -112,11 +118,11 @@ func TestServerSideApply(t *testing.T) {
original := obj.DeepCopy()
g.Expect(env.GetAPIReader().Get(ctx, client.ObjectKeyFromObject(original), original)).To(Succeed())

// Create a patch helper for a modified object with changes only in metadata.
// Create a patch helper for a modified object with changes in spec.
modified := obj.DeepCopy()
g.Expect(unstructured.SetNestedField(modified.Object, "changed", "spec", "version")).To(Succeed())
g.Expect(unstructured.SetNestedField(modified.Object, "changed", "spec", "bar")).To(Succeed())

p0, err := NewServerSidePatchHelper(fakeScheme, original, modified, env.GetClient(), IgnorePaths{{"spec", "ignoreThisField"}})
p0, err := NewServerSidePatchHelper(fakeScheme, original, modified, env.GetClient(), IgnorePaths{{"spec", "foo"}})
g.Expect(err).ToNot(HaveOccurred())
g.Expect(p0.HasChanges()).To(BeTrue())
g.Expect(p0.HasSpecChanges()).To(BeTrue())
Expand All @@ -133,7 +139,7 @@ func TestServerSideApply(t *testing.T) {
modified := obj.DeepCopy()
modified.SetLabels(map[string]string{"foo": "changed"})

p0, err := NewServerSidePatchHelper(fakeScheme, original, modified, env.GetClient(), IgnorePaths{{"spec", "ignoreThisField"}})
p0, err := NewServerSidePatchHelper(fakeScheme, original, modified, env.GetClient(), IgnorePaths{{"spec", "foo"}})
g.Expect(err).ToNot(HaveOccurred())
g.Expect(p0.HasChanges()).To(BeTrue())
g.Expect(p0.HasSpecChanges()).To(BeFalse())
Expand All @@ -148,9 +154,9 @@ func TestServerSideApply(t *testing.T) {

// Create a patch helper for a modified object with changes only in an ignoredField.
modified := obj.DeepCopy()
g.Expect(unstructured.SetNestedField(modified.Object, "changed", "spec", "ignoreThisField")).To(Succeed())
g.Expect(unstructured.SetNestedField(modified.Object, "changed", "spec", "foo")).To(Succeed())

p0, err := NewServerSidePatchHelper(fakeScheme, original, modified, env.GetClient(), IgnorePaths{{"spec", "ignoreThisField"}})
p0, err := NewServerSidePatchHelper(fakeScheme, original, modified, env.GetClient(), IgnorePaths{{"spec", "foo"}})
g.Expect(err).ToNot(HaveOccurred())
g.Expect(p0.HasChanges()).To(BeFalse())
g.Expect(p0.HasSpecChanges()).To(BeFalse())
Expand All @@ -167,9 +173,10 @@ func TestServerSideApply(t *testing.T) {
p, err := patch.NewHelper(obj, env.Client)
g.Expect(err).ToNot(HaveOccurred())

g.Expect(unstructured.SetNestedField(obj.Object, "changed", "spec", "ignoreThisField")).To(Succeed()) // Controller sets a well known field ignored in the topology controller
g.Expect(unstructured.SetNestedField(obj.Object, "changed", "spec", "infra-foo")).To(Succeed()) // Controller sets an infra specific field the topology controller is not aware of
g.Expect(unstructured.SetNestedField(obj.Object, "changed", "status", "infra-foo")).To(Succeed()) // Controller sets something in status
g.Expect(unstructured.SetNestedField(obj.Object, "changed", "spec", "foo")).To(Succeed()) // Controller sets a well known field ignored in the topology controller
g.Expect(unstructured.SetNestedField(obj.Object, "changed", "spec", "bar")).To(Succeed()) // Controller sets an infra specific field the topology controller is not aware of
g.Expect(unstructured.SetNestedField(obj.Object, "changed", "status", "foo")).To(Succeed()) // Controller sets something in status
g.Expect(unstructured.SetNestedField(obj.Object, true, "status", "ready")).To(Succeed()) // Required field

g.Expect(p.Patch(ctx, obj)).To(Succeed())
})
Expand All @@ -184,7 +191,7 @@ func TestServerSideApply(t *testing.T) {
// Create a patch helper for a modified object with no changes to what previously applied by th topology manager.
modified := obj.DeepCopy()

p0, err := NewServerSidePatchHelper(fakeScheme, original, modified, env.GetClient(), IgnorePaths{{"spec", "ignoreThisField"}})
p0, err := NewServerSidePatchHelper(fakeScheme, original, modified, env.GetClient(), IgnorePaths{{"spec", "foo"}})
g.Expect(err).ToNot(HaveOccurred())
g.Expect(p0.HasChanges()).To(BeFalse())
g.Expect(p0.HasSpecChanges()).To(BeFalse())
Expand All @@ -196,11 +203,11 @@ func TestServerSideApply(t *testing.T) {
got := obj.DeepCopy()
g.Expect(env.GetAPIReader().Get(ctx, client.ObjectKeyFromObject(got), got)).To(Succeed())

v1, _, _ := unstructured.NestedString(got.Object, "spec", "ignoreThisField")
v1, _, _ := unstructured.NestedString(got.Object, "spec", "foo")
g.Expect(v1).To(Equal("changed"))
v2, _, _ := unstructured.NestedString(got.Object, "spec", "infra-foo")
v2, _, _ := unstructured.NestedString(got.Object, "spec", "bar")
g.Expect(v2).To(Equal("changed"))
v3, _, _ := unstructured.NestedString(got.Object, "status", "infra-foo")
v3, _, _ := unstructured.NestedString(got.Object, "status", "foo")
g.Expect(v3).To(Equal("changed"))

fieldV1 := getTopologyManagedFields(got)
Expand All @@ -210,9 +217,9 @@ func TestServerSideApply(t *testing.T) {

specFieldV1 := fieldV1["f:spec"].(map[string]interface{})
g.Expect(specFieldV1).ToNot(BeEmpty())
g.Expect(specFieldV1).To(HaveKey("f:version")) // topology controller should express opinions on spec.version.
g.Expect(specFieldV1).ToNot(HaveKey("f:ignoreThisField")) // topology controller should not express opinions on ignore paths.
g.Expect(specFieldV1).ToNot(HaveKey("f:infra-foo")) // topology controller should not express opinions on fields managed by other controllers.
g.Expect(specFieldV1).To(HaveKey("f:controlPlaneEndpoint")) // topology controller should express opinions on spec.controlPlaneEndpoint.
g.Expect(specFieldV1).ToNot(HaveKey("f:foo")) // topology controller should not express opinions on ignore paths.
g.Expect(specFieldV1).ToNot(HaveKey("f:bar")) // topology controller should not express opinions on fields managed by other controllers.
})

t.Run("Topology controller reconcile again with some changes on topology managed fields", func(t *testing.T) {
Expand All @@ -224,9 +231,9 @@ func TestServerSideApply(t *testing.T) {

// Create a patch helper for a modified object with some changes to what previously applied by th topology manager.
modified := obj.DeepCopy()
g.Expect(unstructured.SetNestedField(modified.Object, "changed", "spec", "version")).To(Succeed())
g.Expect(unstructured.SetNestedField(modified.Object, "changed", "spec", "controlPlaneEndpoint", "host")).To(Succeed())

p0, err := NewServerSidePatchHelper(fakeScheme, original, modified, env.GetClient(), IgnorePaths{{"spec", "ignoreThisField"}})
p0, err := NewServerSidePatchHelper(fakeScheme, original, modified, env.GetClient(), IgnorePaths{{"spec", "foo"}})
g.Expect(err).ToNot(HaveOccurred())
g.Expect(p0.HasChanges()).To(BeTrue())
g.Expect(p0.HasSpecChanges()).To(BeTrue())
Expand All @@ -238,13 +245,13 @@ func TestServerSideApply(t *testing.T) {
got := obj.DeepCopy()
g.Expect(env.GetAPIReader().Get(ctx, client.ObjectKeyFromObject(got), got)).To(Succeed())

v0, _, _ := unstructured.NestedString(got.Object, "spec", "version")
v0, _, _ := unstructured.NestedString(got.Object, "spec", "controlPlaneEndpoint", "host")
g.Expect(v0).To(Equal("changed"))
v1, _, _ := unstructured.NestedString(got.Object, "spec", "ignoreThisField")
v1, _, _ := unstructured.NestedString(got.Object, "spec", "foo")
g.Expect(v1).To(Equal("changed"))
v2, _, _ := unstructured.NestedString(got.Object, "spec", "infra-foo")
v2, _, _ := unstructured.NestedString(got.Object, "spec", "bar")
g.Expect(v2).To(Equal("changed"))
v3, _, _ := unstructured.NestedString(got.Object, "status", "infra-foo")
v3, _, _ := unstructured.NestedString(got.Object, "status", "foo")
g.Expect(v3).To(Equal("changed"))
})

Expand All @@ -257,10 +264,10 @@ func TestServerSideApply(t *testing.T) {

// Create a patch helper for a modified object with some changes to what previously applied by th topology manager.
modified := obj.DeepCopy()
g.Expect(unstructured.SetNestedField(modified.Object, "changed", "spec", "version")).To(Succeed())
g.Expect(unstructured.SetNestedField(modified.Object, "changed-by-topology-controller", "spec", "infra-foo")).To(Succeed())
g.Expect(unstructured.SetNestedField(modified.Object, "changed", "spec", "controlPlaneEndpoint", "host")).To(Succeed())
g.Expect(unstructured.SetNestedField(modified.Object, "changed-by-topology-controller", "spec", "bar")).To(Succeed())

p0, err := NewServerSidePatchHelper(fakeScheme, original, modified, env.GetClient(), IgnorePaths{{"spec", "ignoreThisField"}})
p0, err := NewServerSidePatchHelper(fakeScheme, original, modified, env.GetClient(), IgnorePaths{{"spec", "foo"}})
g.Expect(err).ToNot(HaveOccurred())
g.Expect(p0.HasChanges()).To(BeTrue())
g.Expect(p0.HasSpecChanges()).To(BeTrue())
Expand All @@ -272,7 +279,7 @@ func TestServerSideApply(t *testing.T) {
got := obj.DeepCopy()
g.Expect(env.GetAPIReader().Get(ctx, client.ObjectKeyFromObject(got), got)).To(Succeed())

v2, _, _ := unstructured.NestedString(got.Object, "spec", "infra-foo")
v2, _, _ := unstructured.NestedString(got.Object, "spec", "bar")
g.Expect(v2).To(Equal("changed-by-topology-controller"))

fieldV1 := getTopologyManagedFields(got)
Expand All @@ -281,11 +288,12 @@ func TestServerSideApply(t *testing.T) {

specFieldV1 := fieldV1["f:spec"].(map[string]interface{})
g.Expect(specFieldV1).ToNot(BeEmpty())
g.Expect(specFieldV1).To(HaveKey("f:version")) // topology controller should express opinions on spec.version.
g.Expect(specFieldV1).ToNot(HaveKey("f:ignoreThisField")) // topology controller should not express opinions on ignore paths.
g.Expect(specFieldV1).To(HaveKey("f:infra-foo")) // topology controller now has an opinion on a field previously managed by other controllers (force ownership).
g.Expect(specFieldV1).To(HaveKey("f:controlPlaneEndpoint")) // topology controller should express opinions on spec.controlPlaneEndpoint.
g.Expect(specFieldV1).ToNot(HaveKey("f:foo")) // topology controller should not express opinions on ignore paths.
g.Expect(specFieldV1).To(HaveKey("f:bar")) // topology controller now has an opinion on a field previously managed by other controllers (force ownership).
})
t.Run("No-op on unstructured object having empty map[string]interface in spec", func(t *testing.T) {
// TODO: this should be either a unit test or a test with a typed CRD
g := NewWithT(t)

// Second test object having an empty map and slice as value
Expand Down

0 comments on commit b74e38d

Please sign in to comment.