From 0712ebd157045ebe76968c41149b009a139bc721 Mon Sep 17 00:00:00 2001 From: justinsb Date: Tue, 16 Apr 2024 22:17:33 -0400 Subject: [PATCH] mockkubeapiserver: set generation on objects Aiming for a little bit more fidelity, also enables us to test observedGeneration (in future). Co-authored-by: Tomas Aschan <1550920+tomasaschan@users.noreply.github.com> --- mockkubeapiserver/hooks/crd.go | 6 ------ mockkubeapiserver/hooks/deployment.go | 8 +------- mockkubeapiserver/patchresource.go | 15 +++++++++++++++ mockkubeapiserver/postresource.go | 4 ++++ mockkubeapiserver/putresource.go | 9 +++++++++ .../storage/memorystorage/memorystorage.go | 4 ++++ .../storage/memorystorage/resourceinfo.go | 16 ++++++++++++++++ mockkubeapiserver/storage/resourceinfo.go | 3 +++ .../reconcile/direct/create/expected-http.yaml | 16 ++++++++-------- .../reconcile/ssa/create/expected-http.yaml | 16 ++++++++-------- 10 files changed, 68 insertions(+), 29 deletions(-) diff --git a/mockkubeapiserver/hooks/crd.go b/mockkubeapiserver/hooks/crd.go index a928dd66..59e97827 100644 --- a/mockkubeapiserver/hooks/crd.go +++ b/mockkubeapiserver/hooks/crd.go @@ -57,12 +57,6 @@ func (s *CRDHook) updateCRDConditions(ev *storage.WatchEvent) error { return fmt.Errorf("status was of unexpected type %T", statusObj) } - generation := u.GetGeneration() - if generation == 0 { - generation = 1 - u.SetGeneration(generation) - } - var conditions []interface{} conditions = append(conditions, map[string]interface{}{ "type": "NamesAccepted", diff --git a/mockkubeapiserver/hooks/deployment.go b/mockkubeapiserver/hooks/deployment.go index 84e470e2..7b045797 100644 --- a/mockkubeapiserver/hooks/deployment.go +++ b/mockkubeapiserver/hooks/deployment.go @@ -53,12 +53,6 @@ func (s *DeploymentHook) deploymentChanged(ev *storage.WatchEvent) error { return fmt.Errorf("status was of unexpected type %T", statusObj) } - generation := u.GetGeneration() - if generation == 0 { - generation = 1 - u.SetGeneration(generation) - } - replicasVal, _, err := unstructured.NestedFieldNoCopy(u.Object, "spec", "replicas") if err != nil { return fmt.Errorf("error getting spec.replicas: %w", err) @@ -91,7 +85,7 @@ func (s *DeploymentHook) deploymentChanged(ev *storage.WatchEvent) error { status["replicas"] = replicas status["updatedReplicas"] = replicas - observedGeneration := generation + observedGeneration := u.GetGeneration() status["observedGeneration"] = observedGeneration return nil diff --git a/mockkubeapiserver/patchresource.go b/mockkubeapiserver/patchresource.go index 94d05273..8853302d 100644 --- a/mockkubeapiserver/patchresource.go +++ b/mockkubeapiserver/patchresource.go @@ -22,6 +22,7 @@ import ( "fmt" "io" "net/http" + "reflect" "strconv" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -79,6 +80,11 @@ func (req *patchResource) Run(ctx context.Context, s *MockKubeAPIServer) error { // TODO: Should we treat this like an apply to an empty object? patched := body + + if resource.SetsGeneration() { + patched.SetGeneration(1) + } + if err := resource.CreateObject(ctx, id, patched); err != nil { return err } @@ -124,6 +130,15 @@ func (req *patchResource) Run(ctx context.Context, s *MockKubeAPIServer) error { klog.Infof("skipping write, object not changed") return req.writeResponse(existingObj) } else { + if resource.SetsGeneration() { + specIsSame := reflect.DeepEqual(existingObj.Object["spec"], updated.Object["spec"]) + if !specIsSame { + generation := updated.GetGeneration() + generation++ + updated.SetGeneration(generation) + } + } + if err := resource.UpdateObject(ctx, id, updated); err != nil { return err } diff --git a/mockkubeapiserver/postresource.go b/mockkubeapiserver/postresource.go index 9e16d162..1b2ddda5 100644 --- a/mockkubeapiserver/postresource.go +++ b/mockkubeapiserver/postresource.go @@ -69,6 +69,10 @@ func (req *postResource) Run(ctx context.Context, s *MockKubeAPIServer) error { return fmt.Errorf("name must be provided in payload") } + if resource.SetsGeneration() { + obj.SetGeneration(1) + } + if err := resource.CreateObject(ctx, id, obj); err != nil { return err } diff --git a/mockkubeapiserver/putresource.go b/mockkubeapiserver/putresource.go index 6c2fae5b..b5ad6404 100644 --- a/mockkubeapiserver/putresource.go +++ b/mockkubeapiserver/putresource.go @@ -89,6 +89,15 @@ func (req *putResource) Run(ctx context.Context, s *MockKubeAPIServer) error { return req.writeResponse(original) } + if resource.SetsGeneration() { + specIsSame := reflect.DeepEqual(original.Object["spec"], updated.Object["spec"]) + if !specIsSame { + generation := updated.GetGeneration() + generation++ + updated.SetGeneration(generation) + } + } + if err := resource.UpdateObject(ctx, id, updated); err != nil { return err } diff --git a/mockkubeapiserver/storage/memorystorage/memorystorage.go b/mockkubeapiserver/storage/memorystorage/memorystorage.go index e71358b6..6ad12532 100644 --- a/mockkubeapiserver/storage/memorystorage/memorystorage.go +++ b/mockkubeapiserver/storage/memorystorage/memorystorage.go @@ -125,6 +125,10 @@ func (s *MemoryStorage) AddObject(obj *unstructured.Unstructured) error { return fmt.Errorf("object group/version/kind %v not known", gvk) } + if resource.SetsGeneration() { + obj.SetGeneration(1) + } + return resource.CreateObject(ctx, id, obj) } diff --git a/mockkubeapiserver/storage/memorystorage/resourceinfo.go b/mockkubeapiserver/storage/memorystorage/resourceinfo.go index 713d3239..379bf311 100644 --- a/mockkubeapiserver/storage/memorystorage/resourceinfo.go +++ b/mockkubeapiserver/storage/memorystorage/resourceinfo.go @@ -33,3 +33,19 @@ func (r *memoryResourceInfo) ListGVK() schema.GroupVersionKind { func (r *memoryResourceInfo) ParseableType() *typed.ParseableType { return r.parseableType } + +func (r *memoryResourceInfo) SetsGeneration() bool { + // Not all resources support metadata.generation; it looks like only those with status do (?) + // For now, exclude some well-known types that do not set metadata.generation. + switch r.gvk.GroupKind() { + case schema.GroupKind{Group: "", Kind: "ConfigMap"}: + return false + case schema.GroupKind{Group: "", Kind: "Secret"}: + return false + case schema.GroupKind{Group: "", Kind: "Namespace"}: + return false + + default: + return true + } +} diff --git a/mockkubeapiserver/storage/resourceinfo.go b/mockkubeapiserver/storage/resourceinfo.go index 33322382..add51af8 100644 --- a/mockkubeapiserver/storage/resourceinfo.go +++ b/mockkubeapiserver/storage/resourceinfo.go @@ -22,6 +22,9 @@ type ResourceInfo interface { ListGVK() schema.GroupVersionKind ParseableType() *typed.ParseableType + // SetsGeneration is true if we should automatically set metadata.generation for this resource kind. + SetsGeneration() bool + GetObject(ctx context.Context, id types.NamespacedName) (*unstructured.Unstructured, bool, error) ListObjects(ctx context.Context, filter ListFilter) (*unstructured.UnstructuredList, error) diff --git a/pkg/test/testreconciler/simpletest/testdata/reconcile/direct/create/expected-http.yaml b/pkg/test/testreconciler/simpletest/testdata/reconcile/direct/create/expected-http.yaml index 8a07e382..2db23713 100644 --- a/pkg/test/testreconciler/simpletest/testdata/reconcile/direct/create/expected-http.yaml +++ b/pkg/test/testreconciler/simpletest/testdata/reconcile/direct/create/expected-http.yaml @@ -16,11 +16,11 @@ Accept: application/json, */* 200 OK Cache-Control: no-cache, private -Content-Length: 364 +Content-Length: 379 Content-Type: application/json Date: (removed) -{"apiVersion":"addons.example.org/v1alpha1","items":[{"apiVersion":"addons.example.org/v1alpha1","kind":"SimpleTest","metadata":{"creationTimestamp":"2022-01-01T00:00:01Z","name":"simple1","namespace":"ns1","resourceVersion":"2","uid":"00000000-0000-0000-0000-000000000002"},"spec":{"channel":"stable"}}],"kind":"SimpleTestList","metadata":{"resourceVersion":"2"}} +{"apiVersion":"addons.example.org/v1alpha1","items":[{"apiVersion":"addons.example.org/v1alpha1","kind":"SimpleTest","metadata":{"creationTimestamp":"2022-01-01T00:00:01Z","generation":1,"name":"simple1","namespace":"ns1","resourceVersion":"2","uid":"00000000-0000-0000-0000-000000000002"},"spec":{"channel":"stable"}}],"kind":"SimpleTestList","metadata":{"resourceVersion":"2"}} --- @@ -219,16 +219,16 @@ PUT http://kube-apiserver/apis/addons.example.org/v1alpha1/namespaces/ns1/simple Accept: application/json, */* Content-Type: application/json -{"kind":"SimpleTest","apiVersion":"addons.example.org/v1alpha1","metadata":{"name":"simple1","namespace":"ns1","uid":"00000000-0000-0000-0000-000000000002","resourceVersion":"2","creationTimestamp":"2022-01-01T00:00:01Z"},"spec":{"channel":"stable"},"status":{"healthy":true}} +{"kind":"SimpleTest","apiVersion":"addons.example.org/v1alpha1","metadata":{"name":"simple1","namespace":"ns1","uid":"00000000-0000-0000-0000-000000000002","resourceVersion":"2","generation":1,"creationTimestamp":"2022-01-01T00:00:01Z"},"spec":{"channel":"stable"},"status":{"healthy":true}} 200 OK Cache-Control: no-cache, private -Content-Length: 276 +Content-Length: 291 Content-Type: application/json Date: (removed) -{"apiVersion":"addons.example.org/v1alpha1","kind":"SimpleTest","metadata":{"creationTimestamp":"2022-01-01T00:00:01Z","name":"simple1","namespace":"ns1","resourceVersion":"5","uid":"00000000-0000-0000-0000-000000000002"},"spec":{"channel":"stable"},"status":{"healthy":true}} +{"apiVersion":"addons.example.org/v1alpha1","kind":"SimpleTest","metadata":{"creationTimestamp":"2022-01-01T00:00:01Z","generation":1,"name":"simple1","namespace":"ns1","resourceVersion":"5","uid":"00000000-0000-0000-0000-000000000002"},"spec":{"channel":"stable"},"status":{"healthy":true}} --- @@ -301,13 +301,13 @@ PUT http://kube-apiserver/apis/addons.example.org/v1alpha1/namespaces/ns1/simple Accept: application/json, */* Content-Type: application/json -{"kind":"SimpleTest","apiVersion":"addons.example.org/v1alpha1","metadata":{"name":"simple1","namespace":"ns1","uid":"00000000-0000-0000-0000-000000000002","resourceVersion":"5","creationTimestamp":"2022-01-01T00:00:01Z"},"spec":{"channel":"stable"},"status":{"healthy":true}} +{"kind":"SimpleTest","apiVersion":"addons.example.org/v1alpha1","metadata":{"name":"simple1","namespace":"ns1","uid":"00000000-0000-0000-0000-000000000002","resourceVersion":"5","generation":1,"creationTimestamp":"2022-01-01T00:00:01Z"},"spec":{"channel":"stable"},"status":{"healthy":true}} 200 OK Cache-Control: no-cache, private -Content-Length: 276 +Content-Length: 291 Content-Type: application/json Date: (removed) -{"apiVersion":"addons.example.org/v1alpha1","kind":"SimpleTest","metadata":{"creationTimestamp":"2022-01-01T00:00:01Z","name":"simple1","namespace":"ns1","resourceVersion":"5","uid":"00000000-0000-0000-0000-000000000002"},"spec":{"channel":"stable"},"status":{"healthy":true}} +{"apiVersion":"addons.example.org/v1alpha1","kind":"SimpleTest","metadata":{"creationTimestamp":"2022-01-01T00:00:01Z","generation":1,"name":"simple1","namespace":"ns1","resourceVersion":"5","uid":"00000000-0000-0000-0000-000000000002"},"spec":{"channel":"stable"},"status":{"healthy":true}} diff --git a/pkg/test/testreconciler/simpletest/testdata/reconcile/ssa/create/expected-http.yaml b/pkg/test/testreconciler/simpletest/testdata/reconcile/ssa/create/expected-http.yaml index 9c91c7db..6637b717 100644 --- a/pkg/test/testreconciler/simpletest/testdata/reconcile/ssa/create/expected-http.yaml +++ b/pkg/test/testreconciler/simpletest/testdata/reconcile/ssa/create/expected-http.yaml @@ -16,11 +16,11 @@ Accept: application/json, */* 200 OK Cache-Control: no-cache, private -Content-Length: 364 +Content-Length: 379 Content-Type: application/json Date: (removed) -{"apiVersion":"addons.example.org/v1alpha1","items":[{"apiVersion":"addons.example.org/v1alpha1","kind":"SimpleTest","metadata":{"creationTimestamp":"2022-01-01T00:00:01Z","name":"simple1","namespace":"ns1","resourceVersion":"2","uid":"00000000-0000-0000-0000-000000000002"},"spec":{"channel":"stable"}}],"kind":"SimpleTestList","metadata":{"resourceVersion":"2"}} +{"apiVersion":"addons.example.org/v1alpha1","items":[{"apiVersion":"addons.example.org/v1alpha1","kind":"SimpleTest","metadata":{"creationTimestamp":"2022-01-01T00:00:01Z","generation":1,"name":"simple1","namespace":"ns1","resourceVersion":"2","uid":"00000000-0000-0000-0000-000000000002"},"spec":{"channel":"stable"}}],"kind":"SimpleTestList","metadata":{"resourceVersion":"2"}} --- @@ -118,16 +118,16 @@ PUT http://kube-apiserver/apis/addons.example.org/v1alpha1/namespaces/ns1/simple Accept: application/json, */* Content-Type: application/json -{"kind":"SimpleTest","apiVersion":"addons.example.org/v1alpha1","metadata":{"name":"simple1","namespace":"ns1","uid":"00000000-0000-0000-0000-000000000002","resourceVersion":"2","creationTimestamp":"2022-01-01T00:00:01Z","labels":{"applyset.kubernetes.io/id":"applyset-xbxAWnAItX3p1Gxrs86F-ZQAGwGoys9xxQGK3IED7bY-v1"},"annotations":{"applyset.kubernetes.io/additional-namespaces":"","applyset.kubernetes.io/contains-group-kinds":"ConfigMap,Deployment.apps","applyset.kubernetes.io/tooling":"SimpleTest/"}},"spec":{"channel":"stable"},"status":{"healthy":false}} +{"kind":"SimpleTest","apiVersion":"addons.example.org/v1alpha1","metadata":{"name":"simple1","namespace":"ns1","uid":"00000000-0000-0000-0000-000000000002","resourceVersion":"2","generation":1,"creationTimestamp":"2022-01-01T00:00:01Z","labels":{"applyset.kubernetes.io/id":"applyset-xbxAWnAItX3p1Gxrs86F-ZQAGwGoys9xxQGK3IED7bY-v1"},"annotations":{"applyset.kubernetes.io/additional-namespaces":"","applyset.kubernetes.io/contains-group-kinds":"ConfigMap,Deployment.apps","applyset.kubernetes.io/tooling":"SimpleTest/"}},"spec":{"channel":"stable"},"status":{"healthy":false}} 200 OK Cache-Control: no-cache, private -Content-Length: 561 +Content-Length: 576 Content-Type: application/json Date: (removed) -{"apiVersion":"addons.example.org/v1alpha1","kind":"SimpleTest","metadata":{"annotations":{"applyset.kubernetes.io/additional-namespaces":"","applyset.kubernetes.io/contains-group-kinds":"ConfigMap,Deployment.apps","applyset.kubernetes.io/tooling":"SimpleTest/"},"creationTimestamp":"2022-01-01T00:00:01Z","labels":{"applyset.kubernetes.io/id":"applyset-xbxAWnAItX3p1Gxrs86F-ZQAGwGoys9xxQGK3IED7bY-v1"},"name":"simple1","namespace":"ns1","resourceVersion":"3","uid":"00000000-0000-0000-0000-000000000002"},"spec":{"channel":"stable"},"status":{"healthy":false}} +{"apiVersion":"addons.example.org/v1alpha1","kind":"SimpleTest","metadata":{"annotations":{"applyset.kubernetes.io/additional-namespaces":"","applyset.kubernetes.io/contains-group-kinds":"ConfigMap,Deployment.apps","applyset.kubernetes.io/tooling":"SimpleTest/"},"creationTimestamp":"2022-01-01T00:00:01Z","generation":1,"labels":{"applyset.kubernetes.io/id":"applyset-xbxAWnAItX3p1Gxrs86F-ZQAGwGoys9xxQGK3IED7bY-v1"},"name":"simple1","namespace":"ns1","resourceVersion":"3","uid":"00000000-0000-0000-0000-000000000002"},"spec":{"channel":"stable"},"status":{"healthy":false}} --- @@ -193,15 +193,15 @@ PUT http://kube-apiserver/apis/addons.example.org/v1alpha1/namespaces/ns1/simple Accept: application/json, */* Content-Type: application/json -{"apiVersion":"addons.example.org/v1alpha1","kind":"SimpleTest","metadata":{"annotations":{"applyset.kubernetes.io/additional-namespaces":"","applyset.kubernetes.io/contains-group-kinds":"ConfigMap,Deployment.apps","applyset.kubernetes.io/tooling":"SimpleTest/"},"creationTimestamp":"2022-01-01T00:00:01Z","labels":{"applyset.kubernetes.io/id":"applyset-xbxAWnAItX3p1Gxrs86F-ZQAGwGoys9xxQGK3IED7bY-v1"},"name":"simple1","namespace":"ns1","resourceVersion":"3","uid":"00000000-0000-0000-0000-000000000002"},"spec":{"channel":"stable"},"status":{"conditions":[{"lastTransitionTime":"2022-01-01T00:00:00Z","message":"all manifests are reconciled.","reason":"Normal","status":"True","type":"Ready"}],"healthy":true,"phase":"Current"}} +{"apiVersion":"addons.example.org/v1alpha1","kind":"SimpleTest","metadata":{"annotations":{"applyset.kubernetes.io/additional-namespaces":"","applyset.kubernetes.io/contains-group-kinds":"ConfigMap,Deployment.apps","applyset.kubernetes.io/tooling":"SimpleTest/"},"creationTimestamp":"2022-01-01T00:00:01Z","generation":1,"labels":{"applyset.kubernetes.io/id":"applyset-xbxAWnAItX3p1Gxrs86F-ZQAGwGoys9xxQGK3IED7bY-v1"},"name":"simple1","namespace":"ns1","resourceVersion":"3","uid":"00000000-0000-0000-0000-000000000002"},"spec":{"channel":"stable"},"status":{"conditions":[{"lastTransitionTime":"2022-01-01T00:00:00Z","message":"all manifests are reconciled.","reason":"Normal","status":"True","type":"Ready"}],"healthy":true,"phase":"Current"}} 200 OK Cache-Control: no-cache, private -Content-Length: 730 +Content-Length: 745 Content-Type: application/json Date: (removed) -{"apiVersion":"addons.example.org/v1alpha1","kind":"SimpleTest","metadata":{"annotations":{"applyset.kubernetes.io/additional-namespaces":"","applyset.kubernetes.io/contains-group-kinds":"ConfigMap,Deployment.apps","applyset.kubernetes.io/tooling":"SimpleTest/"},"creationTimestamp":"2022-01-01T00:00:01Z","labels":{"applyset.kubernetes.io/id":"applyset-xbxAWnAItX3p1Gxrs86F-ZQAGwGoys9xxQGK3IED7bY-v1"},"name":"simple1","namespace":"ns1","resourceVersion":"6","uid":"00000000-0000-0000-0000-000000000002"},"spec":{"channel":"stable"},"status":{"conditions":[{"lastTransitionTime":"2022-01-01T00:00:00Z","message":"all manifests are reconciled.","reason":"Normal","status":"True","type":"Ready"}],"healthy":true,"phase":"Current"}} +{"apiVersion":"addons.example.org/v1alpha1","kind":"SimpleTest","metadata":{"annotations":{"applyset.kubernetes.io/additional-namespaces":"","applyset.kubernetes.io/contains-group-kinds":"ConfigMap,Deployment.apps","applyset.kubernetes.io/tooling":"SimpleTest/"},"creationTimestamp":"2022-01-01T00:00:01Z","generation":1,"labels":{"applyset.kubernetes.io/id":"applyset-xbxAWnAItX3p1Gxrs86F-ZQAGwGoys9xxQGK3IED7bY-v1"},"name":"simple1","namespace":"ns1","resourceVersion":"6","uid":"00000000-0000-0000-0000-000000000002"},"spec":{"channel":"stable"},"status":{"conditions":[{"lastTransitionTime":"2022-01-01T00:00:00Z","message":"all manifests are reconciled.","reason":"Normal","status":"True","type":"Ready"}],"healthy":true,"phase":"Current"}} ---