From a28ae92595b80bc179009e6a43f00c144360c335 Mon Sep 17 00:00:00 2001 From: Lukasz Zajaczkowski Date: Wed, 20 Nov 2024 10:46:52 +0100 Subject: [PATCH] add tests for custom status readers (#316) --- .../kstatus/statusreaders/deployment_test.go | 114 ++++++++++++ .../kstatus/statusreaders/replicaset_test.go | 132 ++++++++++++++ .../kstatus/statusreaders/statefulset_test.go | 163 ++++++++++++++++++ 3 files changed, 409 insertions(+) create mode 100644 internal/kstatus/statusreaders/deployment_test.go create mode 100644 internal/kstatus/statusreaders/replicaset_test.go create mode 100644 internal/kstatus/statusreaders/statefulset_test.go diff --git a/internal/kstatus/statusreaders/deployment_test.go b/internal/kstatus/statusreaders/deployment_test.go new file mode 100644 index 00000000..effecd1d --- /dev/null +++ b/internal/kstatus/statusreaders/deployment_test.go @@ -0,0 +1,114 @@ +package statusreaders_test + +import ( + "context" + "strings" + "testing" + + "github.com/pluralsh/deployment-operator/internal/kstatus/statusreaders" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + appsv1 "k8s.io/api/apps/v1" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + fakecr "sigs.k8s.io/cli-utils/pkg/kstatus/polling/clusterreader/fake" + "sigs.k8s.io/cli-utils/pkg/kstatus/polling/event" + "sigs.k8s.io/cli-utils/pkg/kstatus/polling/testutil" + "sigs.k8s.io/cli-utils/pkg/kstatus/status" + "sigs.k8s.io/cli-utils/pkg/object" + fakemapper "sigs.k8s.io/cli-utils/pkg/testutil" +) + +var ( + deploymentGVK = appsv1.SchemeGroupVersion.WithKind("Deployment") + deploymentGVR = appsv1.SchemeGroupVersion.WithResource("deployments") +) + +var ( + currentDeployment = strings.TrimSpace(` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: test + generation: 1 + namespace: qual +spec: + selector: + matchLabels: + app: app +status: + observedGeneration: 1 + updatedReplicas: 1 + readyReplicas: 1 + availableReplicas: 1 + replicas: 1 + conditions: + - type: Progressing + status: "True" + reason: NewReplicaSetAvailable + - type: Available + status: "True" +`) +) + +func TestDeploymentReadStatus(t *testing.T) { + testCases := map[string]struct { + identifier object.ObjMetadata + readerResource *unstructured.Unstructured + readerErr error + expectedErr error + expectedResourceStatus *event.ResourceStatus + }{ + "Current resource": { + identifier: object.UnstructuredToObjMetadata(testutil.YamlToUnstructured(t, currentDeployment)), + readerResource: testutil.YamlToUnstructured(t, currentDeployment), + expectedResourceStatus: &event.ResourceStatus{ + Identifier: object.UnstructuredToObjMetadata(testutil.YamlToUnstructured(t, currentDeployment)), + Status: status.CurrentStatus, + Resource: testutil.YamlToUnstructured(t, currentDeployment), + Message: "Deployment is available. Replicas: 1", + GeneratedResources: make(event.ResourceStatuses, 0), + }, + }, + "Resource not found": { + identifier: object.UnstructuredToObjMetadata(testutil.YamlToUnstructured(t, currentDeployment)), + readerErr: errors.NewNotFound(deploymentGVR.GroupResource(), "test"), + expectedResourceStatus: &event.ResourceStatus{ + Identifier: object.UnstructuredToObjMetadata(testutil.YamlToUnstructured(t, currentDeployment)), + Status: status.NotFoundStatus, + Message: "Resource not found", + }, + }, + "Context cancelled": { + identifier: object.UnstructuredToObjMetadata(testutil.YamlToUnstructured(t, currentDeployment)), + readerErr: context.Canceled, + expectedErr: context.Canceled, + }, + } + + for tn := range testCases { + tc := testCases[tn] + t.Run(tn, func(t *testing.T) { + fakeReader := &fakecr.ClusterReader{ + GetResource: tc.readerResource, + GetErr: tc.readerErr, + } + fakeMapper := fakemapper.NewFakeRESTMapper(deploymentGVK) + statusReader := statusreaders.NewDeploymentResourceReader(fakeMapper) + + rs, err := statusReader.ReadStatus(context.Background(), fakeReader, tc.identifier) + + if tc.expectedErr != nil { + if err == nil { + t.Errorf("expected error, but didn't get one") + } else { + assert.EqualError(t, err, tc.expectedErr.Error()) + } + return + } + + require.NoError(t, err) + assert.Equal(t, tc.expectedResourceStatus, rs) + }) + } +} diff --git a/internal/kstatus/statusreaders/replicaset_test.go b/internal/kstatus/statusreaders/replicaset_test.go new file mode 100644 index 00000000..b250b814 --- /dev/null +++ b/internal/kstatus/statusreaders/replicaset_test.go @@ -0,0 +1,132 @@ +package statusreaders_test + +import ( + "context" + "strings" + "testing" + + "github.com/pluralsh/deployment-operator/internal/kstatus/statusreaders" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + appsv1 "k8s.io/api/apps/v1" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + fakecr "sigs.k8s.io/cli-utils/pkg/kstatus/polling/clusterreader/fake" + "sigs.k8s.io/cli-utils/pkg/kstatus/polling/event" + "sigs.k8s.io/cli-utils/pkg/kstatus/polling/testutil" + "sigs.k8s.io/cli-utils/pkg/kstatus/status" + "sigs.k8s.io/cli-utils/pkg/object" + fakemapper "sigs.k8s.io/cli-utils/pkg/testutil" +) + +var ( + rsGVK = appsv1.SchemeGroupVersion.WithKind("ReplicaSet") + rsGVR = appsv1.SchemeGroupVersion.WithResource("replicasets") +) + +var ( + currentReplicaset = strings.TrimSpace(` +apiVersion: apps/v1 +kind: ReplicaSet +metadata: + labels: + app: guestbook + plural.sh/managed-by: agent + tier: frontend + name: frontend + namespace: test-do + resourceVersion: "4869207" + uid: 437e2329-59e4-42b9-ae40-48da3562d17e +spec: + replicas: 3 + selector: + matchLabels: + tier: frontend + template: + metadata: + creationTimestamp: null + labels: + tier: frontend + spec: + containers: + - image: us-docker.pkg.dev/google-samples/containers/gke/gb-frontend:v5 + imagePullPolicy: IfNotPresent + name: php-redis + resources: {} + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + dnsPolicy: ClusterFirst + restartPolicy: Always + schedulerName: default-scheduler + securityContext: {} + terminationGracePeriodSeconds: 30 +status: + availableReplicas: 3 + fullyLabeledReplicas: 3 + observedGeneration: 1 + readyReplicas: 3 + replicas: 3 +`) +) + +func TestReplicaSetReadStatus(t *testing.T) { + testCases := map[string]struct { + identifier object.ObjMetadata + readerResource *unstructured.Unstructured + readerErr error + expectedErr error + expectedResourceStatus *event.ResourceStatus + }{ + "Current resource": { + identifier: object.UnstructuredToObjMetadata(testutil.YamlToUnstructured(t, currentReplicaset)), + readerResource: testutil.YamlToUnstructured(t, currentReplicaset), + expectedResourceStatus: &event.ResourceStatus{ + Identifier: object.UnstructuredToObjMetadata(testutil.YamlToUnstructured(t, currentReplicaset)), + Status: status.CurrentStatus, + Resource: testutil.YamlToUnstructured(t, currentReplicaset), + Message: "ReplicaSet is available. Replicas: 3", + GeneratedResources: make(event.ResourceStatuses, 0), + }, + }, + "Resource not found": { + identifier: object.UnstructuredToObjMetadata(testutil.YamlToUnstructured(t, currentReplicaset)), + readerErr: errors.NewNotFound(rsGVR.GroupResource(), "test"), + expectedResourceStatus: &event.ResourceStatus{ + Identifier: object.UnstructuredToObjMetadata(testutil.YamlToUnstructured(t, currentReplicaset)), + Status: status.NotFoundStatus, + Message: "Resource not found", + }, + }, + "Context cancelled": { + identifier: object.UnstructuredToObjMetadata(testutil.YamlToUnstructured(t, currentReplicaset)), + readerErr: context.Canceled, + expectedErr: context.Canceled, + }, + } + + for tn := range testCases { + tc := testCases[tn] + t.Run(tn, func(t *testing.T) { + fakeReader := &fakecr.ClusterReader{ + GetResource: tc.readerResource, + GetErr: tc.readerErr, + } + fakeMapper := fakemapper.NewFakeRESTMapper(rsGVK) + statusReader := statusreaders.NewReplicaSetStatusReader(fakeMapper) + + rs, err := statusReader.ReadStatus(context.Background(), fakeReader, tc.identifier) + + if tc.expectedErr != nil { + if err == nil { + t.Errorf("expected error, but didn't get one") + } else { + assert.EqualError(t, err, tc.expectedErr.Error()) + } + return + } + + require.NoError(t, err) + assert.Equal(t, tc.expectedResourceStatus, rs) + }) + } +} diff --git a/internal/kstatus/statusreaders/statefulset_test.go b/internal/kstatus/statusreaders/statefulset_test.go new file mode 100644 index 00000000..546bf306 --- /dev/null +++ b/internal/kstatus/statusreaders/statefulset_test.go @@ -0,0 +1,163 @@ +package statusreaders_test + +import ( + "context" + "strings" + "testing" + + "github.com/pluralsh/deployment-operator/internal/kstatus/statusreaders" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + appsv1 "k8s.io/api/apps/v1" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + fakecr "sigs.k8s.io/cli-utils/pkg/kstatus/polling/clusterreader/fake" + "sigs.k8s.io/cli-utils/pkg/kstatus/polling/event" + "sigs.k8s.io/cli-utils/pkg/kstatus/polling/testutil" + "sigs.k8s.io/cli-utils/pkg/kstatus/status" + "sigs.k8s.io/cli-utils/pkg/object" + fakemapper "sigs.k8s.io/cli-utils/pkg/testutil" +) + +var ( + ssGVK = appsv1.SchemeGroupVersion.WithKind("StatefulSet") + ssGVR = appsv1.SchemeGroupVersion.WithResource("statefulsets") +) + +var ( + currentStatefulset = strings.TrimSpace(` +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: web + namespace: test-do +spec: + minReadySeconds: 10 + persistentVolumeClaimRetentionPolicy: + whenDeleted: Retain + whenScaled: Retain + podManagementPolicy: OrderedReady + replicas: 3 + revisionHistoryLimit: 10 + selector: + matchLabels: + app: nginx + serviceName: nginx + template: + metadata: + creationTimestamp: null + labels: + app: nginx + spec: + containers: + - image: registry.k8s.io/nginx-slim:0.24 + imagePullPolicy: IfNotPresent + name: nginx + ports: + - containerPort: 80 + name: web + protocol: TCP + resources: {} + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + volumeMounts: + - mountPath: /usr/share/nginx/html + name: www + dnsPolicy: ClusterFirst + restartPolicy: Always + schedulerName: default-scheduler + securityContext: {} + terminationGracePeriodSeconds: 10 + updateStrategy: + rollingUpdate: + partition: 0 + type: RollingUpdate + volumeClaimTemplates: + - apiVersion: v1 + kind: PersistentVolumeClaim + metadata: + creationTimestamp: null + name: www + spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi + storageClassName: my-storage-class + volumeMode: Filesystem + status: + phase: Pending +status: + availableReplicas: 0 + collisionCount: 0 + currentReplicas: 1 + currentRevision: web-7757fc6447 + observedGeneration: 1 + replicas: 1 + updateRevision: web-7757fc6447 + updatedReplicas: 1 +`) +) + +func TestStateFulSetReadStatus(t *testing.T) { + testCases := map[string]struct { + identifier object.ObjMetadata + readerResource *unstructured.Unstructured + readerErr error + expectedErr error + expectedResourceStatus *event.ResourceStatus + }{ + "In progress resource": { + identifier: object.UnstructuredToObjMetadata(testutil.YamlToUnstructured(t, currentStatefulset)), + readerResource: testutil.YamlToUnstructured(t, currentStatefulset), + expectedResourceStatus: &event.ResourceStatus{ + Identifier: object.UnstructuredToObjMetadata(testutil.YamlToUnstructured(t, currentStatefulset)), + Status: status.InProgressStatus, + Resource: testutil.YamlToUnstructured(t, currentStatefulset), + Message: "Replicas: 1/3", + GeneratedResources: make(event.ResourceStatuses, 0), + }, + }, + "Resource not found": { + identifier: object.UnstructuredToObjMetadata(testutil.YamlToUnstructured(t, currentStatefulset)), + readerErr: errors.NewNotFound(ssGVR.GroupResource(), "test"), + expectedResourceStatus: &event.ResourceStatus{ + Identifier: object.UnstructuredToObjMetadata(testutil.YamlToUnstructured(t, currentStatefulset)), + Status: status.NotFoundStatus, + Message: "Resource not found", + }, + }, + "Context cancelled": { + identifier: object.UnstructuredToObjMetadata(testutil.YamlToUnstructured(t, currentStatefulset)), + readerErr: context.Canceled, + expectedErr: context.Canceled, + }, + } + + for tn := range testCases { + tc := testCases[tn] + t.Run(tn, func(t *testing.T) { + fakeReader := &fakecr.ClusterReader{ + GetResource: tc.readerResource, + GetErr: tc.readerErr, + } + fakeMapper := fakemapper.NewFakeRESTMapper(ssGVK) + statusReader := statusreaders.NewStatefulSetResourceReader(fakeMapper) + + rs, err := statusReader.ReadStatus(context.Background(), fakeReader, tc.identifier) + + if tc.expectedErr != nil { + if err == nil { + t.Errorf("expected error, but didn't get one") + } else { + assert.EqualError(t, err, tc.expectedErr.Error()) + } + return + } + + require.NoError(t, err) + assert.Equal(t, tc.expectedResourceStatus, rs) + }) + } +}