From 5a6ae74545092dedd2b0f372b8dc90ada218548d Mon Sep 17 00:00:00 2001 From: Rafael Benevides Date: Wed, 1 Jul 2026 12:22:44 -0300 Subject: [PATCH] HYPERFLEET-1202 - refactor: derive E2E HTTP client from api-spec-template Refactor E2E tests to generate the HTTP client from api-spec-template (partner contract) instead of api-spec (core contract). Both specs are downloaded from GitHub releases via curl, keeping spec repos language-agnostic with no Go module dependency. - Template spec generates typed client for partner entities (Clusters, NodePools, Channels, Versions, WifConfigs) into pkg/api/openapi/ - Core spec generates internal types (AdapterStatus, AdapterCondition, ForceDeleteRequest) into pkg/api/core/ - Channel, Version, WifConfig client methods now use generated typed models instead of generic Resource with map[string]any - Cluster/NodePool status and force-delete endpoints use doJSON since they are internal-only and not in the template spec - E2E tests updated to use openapi.* for partner types and core.* for internal types - Spec versions pinned to immutable tags with curl --fail - Cluster payloads updated to use platform type template --- .gitignore | 4 +- Makefile | 25 ++++--- e2e/adapter/adapter_failover.go | 15 +++-- e2e/adapter/adapter_with_maestro.go | 64 +++++++++--------- e2e/adapter/maestro_unavailability.go | 23 ++++--- e2e/channel/crud_lifecycle.go | 20 +++--- e2e/cluster/adapter_failure.go | 13 ++-- e2e/cluster/concurrent_creation.go | 13 ++-- e2e/cluster/crash_recovery.go | 15 +++-- e2e/cluster/creation.go | 27 ++++---- e2e/cluster/delete.go | 11 +-- e2e/cluster/delete_edge_cases.go | 10 +-- e2e/cluster/delete_external.go | 7 +- e2e/cluster/force_delete.go | 6 +- e2e/cluster/lifecycle_smoke.go | 4 +- e2e/cluster/perf_cascade_delete_latency.go | 4 +- e2e/cluster/perf_create_latency.go | 2 +- e2e/cluster/perf_delete_latency.go | 2 +- e2e/cluster/perf_read_entity_size_latency.go | 2 +- e2e/cluster/perf_update_latency.go | 4 +- e2e/cluster/stuck_deletion.go | 13 ++-- e2e/cluster/update.go | 6 +- e2e/cluster/update_edge_cases.go | 10 +-- e2e/nodepool/concurrent_creation.go | 13 ++-- e2e/nodepool/creation.go | 19 +++--- e2e/nodepool/delete.go | 9 +-- e2e/nodepool/delete_edge_cases.go | 13 ++-- e2e/nodepool/lifecycle_smoke.go | 10 +-- e2e/nodepool/perf_create_latency.go | 2 +- e2e/nodepool/perf_delete_latency.go | 2 +- e2e/nodepool/update.go | 12 ++-- e2e/nodepool/update_edge_cases.go | 10 +-- e2e/version/crud_lifecycle.go | 24 +++---- e2e/wifconfig/crud_lifecycle.go | 16 ++--- go.mod | 1 - go.sum | 4 -- hack/tools.go | 2 - openapi/oapi-codegen-core.yaml | 11 +++ pkg/client/channel.go | 67 +++++++++++++++---- pkg/client/cluster.go | 9 +-- pkg/client/nodepool.go | 9 +-- pkg/client/version.go | 67 +++++++++++++++---- pkg/client/wifconfig.go | 67 +++++++++++++++---- pkg/helper/matchers.go | 17 ++--- pkg/helper/pollers.go | 9 +-- pkg/helper/validation.go | 13 ++-- testdata/payloads/clusters/cluster-patch.json | 4 +- .../clusters/cluster-request-large.json | 4 +- .../clusters/cluster-request-small.json | 2 +- .../payloads/clusters/cluster-request.json | 4 +- .../payloads/nodepools/nodepool-patch.json | 13 ++-- 51 files changed, 450 insertions(+), 283 deletions(-) create mode 100644 openapi/oapi-codegen-core.yaml diff --git a/.gitignore b/.gitignore index d3c12a4..c077f46 100644 --- a/.gitignore +++ b/.gitignore @@ -56,6 +56,8 @@ perf/results/ # Generated code pkg/api/openapi/ +pkg/api/core/ -# Downloaded from hyperfleet-api during make generate +# Downloaded specs during make generate openapi/openapi.yaml +openapi/core-openapi.yaml diff --git a/Makefile b/Makefile index 5993620..57d6455 100644 --- a/Makefile +++ b/Makefile @@ -43,15 +43,24 @@ help: ## Display this help ##@ Code Generation +# Spec versions — bump when consuming new releases +API_SPEC_VERSION ?= v1.0.25 +API_SPEC_TEMPLATE_VERSION ?= v1.0.27 + .PHONY: generate generate: $(OAPI_CODEGEN) ## Generate API client code from OpenAPI schema - $(GO) mod download - rm -rf pkg/api/openapi - mkdir -p pkg/api/openapi openapi - @rm -f openapi/openapi.yaml - @cp "$$($(GO) list -m -f '{{.Dir}}' github.com/openshift-hyperfleet/hyperfleet-api-spec)/schemas/core/openapi.yaml" openapi/openapi.yaml + rm -f pkg/api/openapi/openapi.gen.go pkg/api/core/core.gen.go + mkdir -p pkg/api/openapi pkg/api/core openapi + @rm -f openapi/openapi.yaml openapi/core-openapi.yaml + @echo "Downloading template spec ($(API_SPEC_TEMPLATE_VERSION))..." + @curl -sfL -o openapi/openapi.yaml \ + "https://github.com/openshift-hyperfleet/hyperfleet-api-spec-template/releases/download/$(API_SPEC_TEMPLATE_VERSION)/template-openapi.yaml" + @echo "Downloading core spec ($(API_SPEC_VERSION))..." + @curl -sfL -o openapi/core-openapi.yaml \ + "https://github.com/openshift-hyperfleet/hyperfleet-api-spec/releases/download/$(API_SPEC_VERSION)/core-openapi.yaml" $(OAPI_CODEGEN) --config openapi/oapi-codegen.yaml openapi/openapi.yaml - @echo "✓ API client code generated in pkg/api/openapi/" + $(OAPI_CODEGEN) --config openapi/oapi-codegen-core.yaml openapi/core-openapi.yaml + @echo "✓ API client code generated in pkg/api/openapi/ and pkg/api/core/" ##@ Development @@ -72,8 +81,8 @@ run: build ## Build and run with help clean: ## Remove build artifacts rm -rf $(BIN_DIR) rm -rf $(OUTPUT_DIR) - rm -f openapi/openapi.yaml - rm -rf pkg/api/openapi + rm -f openapi/openapi.yaml openapi/core-openapi.yaml + rm -f pkg/api/openapi/openapi.gen.go pkg/api/core/core.gen.go rm -f coverage.out coverage.html ##@ Testing diff --git a/e2e/adapter/adapter_failover.go b/e2e/adapter/adapter_failover.go index 6574649..df24aef 100644 --- a/e2e/adapter/adapter_failover.go +++ b/e2e/adapter/adapter_failover.go @@ -7,6 +7,7 @@ import ( "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" //nolint:staticcheck // dot import for test readability + "github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/api/core" "github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/api/openapi" "github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/client" "github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/helper" @@ -117,12 +118,12 @@ var _ = ginkgo.Describe("[Suite: adapter-failures][negative] Adapter framework c g.Expect(cluster.Status).NotTo(BeNil(), "cluster status should be present") hasReconciledFalse := h.HasResourceCondition(cluster.Status.Conditions, - client.ConditionTypeReconciled, openapi.ResourceConditionStatusFalse) + client.ConditionTypeReconciled, openapi.False) g.Expect(hasReconciledFalse).To(BeTrue(), "initial cluster conditions should have Reconciled=False") hasAvailableFalse := h.HasResourceCondition(cluster.Status.Conditions, - client.ConditionTypeLastKnownReconciled, openapi.ResourceConditionStatusFalse) + client.ConditionTypeLastKnownReconciled, openapi.False) g.Expect(hasAvailableFalse).To(BeTrue(), "initial cluster conditions should have LastKnownReconciled=False") }, h.Cfg.Timeouts.Adapter.Processing, h.Cfg.Polling.Interval).Should(Succeed()) @@ -135,7 +136,7 @@ var _ = ginkgo.Describe("[Suite: adapter-failures][negative] Adapter framework c g.Expect(statuses.Items).NotTo(BeEmpty(), "adapter should have reported status") // Find the test adapter status - var adapterStatus *openapi.AdapterStatus + var adapterStatus *core.AdapterStatus for i, adapter := range statuses.Items { if adapter.Adapter == adapterName { adapterStatus = &statuses.Items[i] @@ -155,7 +156,7 @@ var _ = ginkgo.Describe("[Suite: adapter-failures][negative] Adapter framework c "adapter should have observed_generation=1") // Find Available condition - var availableCondition *openapi.AdapterCondition + var availableCondition *core.AdapterCondition for i, condition := range adapterStatus.Conditions { if condition.Type == client.ConditionTypeAvailable { availableCondition = &adapterStatus.Conditions[i] @@ -167,7 +168,7 @@ var _ = ginkgo.Describe("[Suite: adapter-failures][negative] Adapter framework c "adapter should have Available condition") // Verify Available condition reports failure - g.Expect(availableCondition.Status).To(Equal(openapi.AdapterConditionStatusFalse), + g.Expect(availableCondition.Status).To(Equal(core.AdapterConditionStatusFalse), "adapter Available condition should be False due to invalid K8s resource") // Verify error details are present in reason and message @@ -196,11 +197,11 @@ var _ = ginkgo.Describe("[Suite: adapter-failures][negative] Adapter framework c g.Expect(cl.Status).NotTo(BeNil(), "cluster status should be present") g.Expect(h.HasResourceCondition(cl.Status.Conditions, - client.ConditionTypeReconciled, openapi.ResourceConditionStatusTrue)).To(BeTrue(), + client.ConditionTypeReconciled, openapi.True)).To(BeTrue(), "cluster Reconciled should become True despite non-required adapter failure") g.Expect(h.HasResourceCondition(cl.Status.Conditions, - client.ConditionTypeLastKnownReconciled, openapi.ResourceConditionStatusTrue)).To(BeTrue(), + client.ConditionTypeLastKnownReconciled, openapi.True)).To(BeTrue(), "cluster LastKnownReconciled should become True despite non-required adapter failure") }, h.Cfg.Timeouts.Cluster.Reconciled, h.Cfg.Polling.Interval).Should(Succeed()) diff --git a/e2e/adapter/adapter_with_maestro.go b/e2e/adapter/adapter_with_maestro.go index c442d27..ef52a7a 100644 --- a/e2e/adapter/adapter_with_maestro.go +++ b/e2e/adapter/adapter_with_maestro.go @@ -10,7 +10,7 @@ import ( . "github.com/onsi/gomega" //nolint:staticcheck // Gomega matchers are designed to be used with dot import corev1 "k8s.io/api/core/v1" - "github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/api/openapi" + "github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/api/core" "github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/client" "github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/client/maestro" "github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/helper" @@ -110,7 +110,7 @@ var _ = ginkgo.Describe("[Suite: adapter][maestro-transport] Adapter Framework - // Verify Go template conditional label: platformType captured from cluster spec ({{ if .platformType }}) Expect(resourceBundle.Metadata.Labels).To(HaveKey("hyperfleet.io/platform-type"), "ManifestWork should have platform-type label from {{ if .platformType }} Go template") - Expect(resourceBundle.Metadata.Labels["hyperfleet.io/platform-type"]).To(Equal("gcp"), + Expect(resourceBundle.Metadata.Labels["hyperfleet.io/platform-type"]).To(Equal("template"), "platform-type label should match cluster spec.platform.type") // Verify annotations @@ -195,13 +195,13 @@ var _ = ginkgo.Describe("[Suite: adapter][maestro-transport] Adapter Framework - g.Expect(cm.Data["cluster_name"]).To(Equal(clusterName)) // Verify Go template {{ if }}/{{ else }} conditional: - // platformType is captured from spec.platform.type via CEL; cluster payload has type="gcp" - // so {{ if eq .platformType "gcp" }} renders platform_tier="cloud", else "onprem" + // platformType is captured from spec.platform.type via CEL; cluster payload has type="template" + // so {{ if eq .platformType "template" }} renders platform_tier="cloud", else "onprem" g.Expect(cm.Data).To(HaveKeyWithValue("platform_tier", "cloud"), - "ConfigMap should have platform_tier=cloud from {{ if eq .platformType \"gcp\" }} Go template") + "ConfigMap should have platform_tier=cloud from {{ if eq .platformType \"template\" }} Go template") // Verify Go template {{ range }} over dynamic subnet list captured from cluster spec - // Each subnet in spec.platform.gcp.subnets produces 3 keys: subnet_{id}_name, subnet_{id}_cidr, subnet_{id}_role + // Each subnet in spec.platform.template.subnets produces 3 keys: subnet_{id}_name, subnet_{id}_cidr, subnet_{id}_role expectedSubnets := []struct { id, name, cidr, role string }{ @@ -231,7 +231,7 @@ var _ = ginkgo.Describe("[Suite: adapter][maestro-transport] Adapter Framework - g.Expect(err).NotTo(HaveOccurred(), "failed to get cluster statuses") // Find the adapter status - var adapterStatus *openapi.AdapterStatus + var adapterStatus *core.AdapterStatus for _, status := range statuses.Items { if status.Adapter == adapterName { adapterStatus = &status @@ -253,7 +253,7 @@ var _ = ginkgo.Describe("[Suite: adapter][maestro-transport] Adapter Framework - hasApplied := h.HasAdapterCondition( adapterStatus.Conditions, client.ConditionTypeApplied, - openapi.AdapterConditionStatusTrue, + core.AdapterConditionStatusTrue, ) g.Expect(hasApplied).To(BeTrue(), "adapter should have Applied=True") @@ -267,7 +267,7 @@ var _ = ginkgo.Describe("[Suite: adapter][maestro-transport] Adapter Framework - hasAvailable := h.HasAdapterCondition( adapterStatus.Conditions, client.ConditionTypeAvailable, - openapi.AdapterConditionStatusTrue, + core.AdapterConditionStatusTrue, ) g.Expect(hasAvailable).To(BeTrue(), "adapter should have Available=True") @@ -281,7 +281,7 @@ var _ = ginkgo.Describe("[Suite: adapter][maestro-transport] Adapter Framework - hasHealth := h.HasAdapterCondition( adapterStatus.Conditions, client.ConditionTypeHealth, - openapi.AdapterConditionStatusTrue, + core.AdapterConditionStatusTrue, ) g.Expect(hasHealth).To(BeTrue(), "adapter should have Health=True") @@ -365,7 +365,7 @@ var _ = ginkgo.Describe("[Suite: adapter][maestro-transport] Adapter Framework - statuses, err := h.Client.GetClusterStatuses(ctx, clusterID) g.Expect(err).NotTo(HaveOccurred(), "failed to get cluster statuses") - var adapterStatus *openapi.AdapterStatus + var adapterStatus *core.AdapterStatus for _, status := range statuses.Items { if status.Adapter == adapterName { adapterStatus = &status @@ -385,7 +385,7 @@ var _ = ginkgo.Describe("[Suite: adapter][maestro-transport] Adapter Framework - statuses, err := h.Client.GetClusterStatuses(ctx, clusterID) g.Expect(err).NotTo(HaveOccurred(), "failed to get cluster statuses") - var adapterStatus *openapi.AdapterStatus + var adapterStatus *core.AdapterStatus for _, status := range statuses.Items { if status.Adapter == adapterName { adapterStatus = &status @@ -532,7 +532,7 @@ var _ = ginkgo.Describe("[Suite: adapter][maestro-transport][negative] Adapter F g.Expect(statuses.Items).NotTo(BeEmpty(), "adapter should have reported status") // Find the test adapter status - var adapterStatus *openapi.AdapterStatus + var adapterStatus *core.AdapterStatus for i, adapter := range statuses.Items { if adapter.Adapter == adapterName { adapterStatus = &statuses.Items[i] @@ -548,7 +548,7 @@ var _ = ginkgo.Describe("[Suite: adapter][maestro-transport][negative] Adapter F "adapter should have observed_generation=1") // Find Health condition - var healthCondition *openapi.AdapterCondition + var healthCondition *core.AdapterCondition for i, condition := range adapterStatus.Conditions { if condition.Type == client.ConditionTypeHealth { healthCondition = &adapterStatus.Conditions[i] @@ -560,7 +560,7 @@ var _ = ginkgo.Describe("[Suite: adapter][maestro-transport][negative] Adapter F "adapter should have Health condition") // Verify Health condition reports failure - g.Expect(healthCondition.Status).To(Equal(openapi.AdapterConditionStatusFalse), + g.Expect(healthCondition.Status).To(Equal(core.AdapterConditionStatusFalse), "adapter Health condition should be False due to unregistered consumer") // Verify error details mention consumer not found/registered @@ -574,7 +574,7 @@ var _ = ginkgo.Describe("[Suite: adapter][maestro-transport][negative] Adapter F ), "error message should mention unregistered consumer") // Find Applied condition - should be False - var appliedCondition *openapi.AdapterCondition + var appliedCondition *core.AdapterCondition for i, condition := range adapterStatus.Conditions { if condition.Type == client.ConditionTypeApplied { appliedCondition = &adapterStatus.Conditions[i] @@ -584,7 +584,7 @@ var _ = ginkgo.Describe("[Suite: adapter][maestro-transport][negative] Adapter F g.Expect(appliedCondition).NotTo(BeNil(), "adapter should have Applied condition") - g.Expect(appliedCondition.Status).To(Equal(openapi.AdapterConditionStatusFalse), + g.Expect(appliedCondition.Status).To(Equal(core.AdapterConditionStatusFalse), "adapter Applied condition should be False since ManifestWork was not created") ginkgo.GinkgoWriter.Printf("Verified adapter failure for unregistered consumer: Health=%s, Applied=%s\n", @@ -713,7 +713,7 @@ var _ = ginkgo.Describe("[Suite: adapter][maestro-transport][negative] Adapter F g.Expect(statuses.Items).NotTo(BeEmpty(), "adapter should have reported status") // Find the test adapter status - var adapterStatus *openapi.AdapterStatus + var adapterStatus *core.AdapterStatus for i, adapter := range statuses.Items { if adapter.Adapter == adapterName { adapterStatus = &statuses.Items[i] @@ -729,7 +729,7 @@ var _ = ginkgo.Describe("[Suite: adapter][maestro-transport][negative] Adapter F "adapter should have observed_generation=1") // Find Applied condition - should be False (ManifestWork not discovered) - var appliedCondition *openapi.AdapterCondition + var appliedCondition *core.AdapterCondition for i, condition := range adapterStatus.Conditions { if condition.Type == client.ConditionTypeApplied { appliedCondition = &adapterStatus.Conditions[i] @@ -739,14 +739,14 @@ var _ = ginkgo.Describe("[Suite: adapter][maestro-transport][negative] Adapter F g.Expect(appliedCondition).NotTo(BeNil(), "adapter should have Applied condition") - g.Expect(appliedCondition.Status).To(Equal(openapi.AdapterConditionStatusFalse), + g.Expect(appliedCondition.Status).To(Equal(core.AdapterConditionStatusFalse), "Applied should be False - ManifestWork not discovered") g.Expect(appliedCondition.Reason).NotTo(BeNil()) g.Expect(*appliedCondition.Reason).To(Equal("ManifestWorkNotDiscovered"), "Applied reason should be ManifestWorkNotDiscovered") // Find Available condition - should be False - var availableCondition *openapi.AdapterCondition + var availableCondition *core.AdapterCondition for i, condition := range adapterStatus.Conditions { if condition.Type == client.ConditionTypeAvailable { availableCondition = &adapterStatus.Conditions[i] @@ -756,14 +756,14 @@ var _ = ginkgo.Describe("[Suite: adapter][maestro-transport][negative] Adapter F g.Expect(availableCondition).NotTo(BeNil(), "adapter should have Available condition") - g.Expect(availableCondition.Status).To(Equal(openapi.AdapterConditionStatusFalse), + g.Expect(availableCondition.Status).To(Equal(core.AdapterConditionStatusFalse), "Available should be False") g.Expect(availableCondition.Reason).NotTo(BeNil()) g.Expect(*availableCondition.Reason).To(Equal("NamespaceNotDiscovered"), "Available reason should be NamespaceNotDiscovered") // Find Health condition - should be False (execution failed) - var healthCondition *openapi.AdapterCondition + var healthCondition *core.AdapterCondition for i, condition := range adapterStatus.Conditions { if condition.Type == client.ConditionTypeHealth { healthCondition = &adapterStatus.Conditions[i] @@ -773,7 +773,7 @@ var _ = ginkgo.Describe("[Suite: adapter][maestro-transport][negative] Adapter F g.Expect(healthCondition).NotTo(BeNil(), "adapter should have Health condition") - g.Expect(healthCondition.Status).To(Equal(openapi.AdapterConditionStatusFalse), + g.Expect(healthCondition.Status).To(Equal(core.AdapterConditionStatusFalse), "Health should be False - discovery failed") g.Expect(healthCondition.Reason).NotTo(BeNil()) g.Expect(*healthCondition.Reason).To(Equal("ExecutionFailed:resources"), @@ -894,7 +894,7 @@ var _ = ginkgo.Describe("[Suite: adapter][maestro-transport][negative] Adapter F g.Expect(statuses.Items).NotTo(BeEmpty(), "adapter should have reported status") // Find the test adapter status - var adapterStatus *openapi.AdapterStatus + var adapterStatus *core.AdapterStatus for i, adapter := range statuses.Items { if adapter.Adapter == adapterName { adapterStatus = &statuses.Items[i] @@ -910,7 +910,7 @@ var _ = ginkgo.Describe("[Suite: adapter][maestro-transport][negative] Adapter F "adapter should have observed_generation=1") // Find Applied condition - should be True (ManifestWork created successfully) - var appliedCondition *openapi.AdapterCondition + var appliedCondition *core.AdapterCondition for i, condition := range adapterStatus.Conditions { if condition.Type == client.ConditionTypeApplied { appliedCondition = &adapterStatus.Conditions[i] @@ -920,11 +920,11 @@ var _ = ginkgo.Describe("[Suite: adapter][maestro-transport][negative] Adapter F g.Expect(appliedCondition).NotTo(BeNil(), "adapter should have Applied condition") - g.Expect(appliedCondition.Status).To(Equal(openapi.AdapterConditionStatusTrue), + g.Expect(appliedCondition.Status).To(Equal(core.AdapterConditionStatusTrue), "adapter Applied condition should be True since ManifestWork was created") // Find Available condition - should be False (nested resources not discovered) - var availableCondition *openapi.AdapterCondition + var availableCondition *core.AdapterCondition for i, condition := range adapterStatus.Conditions { if condition.Type == client.ConditionTypeAvailable { availableCondition = &adapterStatus.Conditions[i] @@ -934,12 +934,12 @@ var _ = ginkgo.Describe("[Suite: adapter][maestro-transport][negative] Adapter F g.Expect(availableCondition).NotTo(BeNil(), "adapter should have Available condition") - g.Expect(availableCondition.Status).To(Equal(openapi.AdapterConditionStatusFalse), + g.Expect(availableCondition.Status).To(Equal(core.AdapterConditionStatusFalse), "adapter Available condition should be False since nested discovery returned empty") // Find Health condition - should be True (adapter executed successfully) // Note: Nested discovery failure doesn't affect Health - the adapter ran successfully - var healthCondition *openapi.AdapterCondition + var healthCondition *core.AdapterCondition for i, condition := range adapterStatus.Conditions { if condition.Type == client.ConditionTypeHealth { healthCondition = &adapterStatus.Conditions[i] @@ -949,7 +949,7 @@ var _ = ginkgo.Describe("[Suite: adapter][maestro-transport][negative] Adapter F g.Expect(healthCondition).NotTo(BeNil(), "adapter should have Health condition") - g.Expect(healthCondition.Status).To(Equal(openapi.AdapterConditionStatusTrue), + g.Expect(healthCondition.Status).To(Equal(core.AdapterConditionStatusTrue), "adapter Health condition should be True - nested discovery failure doesn't affect health") g.Expect(healthCondition.Reason).NotTo(BeNil(), "Health condition should have a reason") @@ -1065,7 +1065,7 @@ var _ = ginkgo.Describe("[Suite: adapter][maestro-transport][negative] Adapter F g.Expect(err).NotTo(HaveOccurred(), "failed to get cluster statuses") // Find the test adapter status - var adapterStatus *openapi.AdapterStatus + var adapterStatus *core.AdapterStatus for _, adapter := range statuses.Items { if adapter.Adapter == adapterName { adapterStatus = &adapter diff --git a/e2e/adapter/maestro_unavailability.go b/e2e/adapter/maestro_unavailability.go index 630598d..68746de 100644 --- a/e2e/adapter/maestro_unavailability.go +++ b/e2e/adapter/maestro_unavailability.go @@ -6,6 +6,7 @@ import ( "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" //nolint:staticcheck // Gomega matchers are designed to be used with dot import + "github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/api/core" "github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/api/openapi" "github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/client" "github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/client/maestro" @@ -80,7 +81,7 @@ func verifyMaestroAdapterFailure(ctx context.Context, h *helper.Helper, clusterI statuses, err := h.Client.GetClusterStatuses(ctx, clusterID) g.Expect(err).NotTo(HaveOccurred(), "failed to get cluster statuses") - var adapterStatus *openapi.AdapterStatus + var adapterStatus *core.AdapterStatus for i, status := range statuses.Items { if status.Adapter == adapterName { adapterStatus = &statuses.Items[i] @@ -91,15 +92,15 @@ func verifyMaestroAdapterFailure(ctx context.Context, h *helper.Helper, clusterI "Maestro adapter should be present in statuses") g.Expect(h.HasAdapterCondition(adapterStatus.Conditions, - client.ConditionTypeApplied, openapi.AdapterConditionStatusFalse)).To(BeTrue(), + client.ConditionTypeApplied, core.AdapterConditionStatusFalse)).To(BeTrue(), "Maestro adapter Applied should be False") g.Expect(h.HasAdapterCondition(adapterStatus.Conditions, - client.ConditionTypeAvailable, openapi.AdapterConditionStatusFalse)).To(BeTrue(), + client.ConditionTypeAvailable, core.AdapterConditionStatusFalse)).To(BeTrue(), "Maestro adapter Available should be False") g.Expect(h.HasAdapterCondition(adapterStatus.Conditions, - client.ConditionTypeHealth, openapi.AdapterConditionStatusFalse)).To(BeTrue(), + client.ConditionTypeHealth, core.AdapterConditionStatusFalse)).To(BeTrue(), "Maestro adapter Health should be False") }, h.Cfg.Timeouts.Adapter.Processing, h.Cfg.Polling.Interval).Should(Succeed()) } @@ -112,7 +113,7 @@ func verifyClusterNotReconciledDuringOutage(ctx context.Context, h *helper.Helpe g.Expect(cl.Status).NotTo(BeNil(), "cluster status should be present") g.Expect(h.HasResourceCondition(cl.Status.Conditions, - client.ConditionTypeReconciled, openapi.ResourceConditionStatusFalse)).To(BeTrue(), + client.ConditionTypeReconciled, openapi.False)).To(BeTrue(), "cluster Reconciled should remain False while Maestro is unavailable") }, h.Cfg.Polling.Interval*3, h.Cfg.Polling.Interval).Should(Succeed()) } @@ -123,7 +124,7 @@ func verifyMaestroAdapterRecovery(ctx context.Context, h *helper.Helper, cluster statuses, err := h.Client.GetClusterStatuses(ctx, clusterID) g.Expect(err).NotTo(HaveOccurred(), "failed to get cluster statuses") - var adapterStatus *openapi.AdapterStatus + var adapterStatus *core.AdapterStatus for i, status := range statuses.Items { if status.Adapter == adapterName { adapterStatus = &statuses.Items[i] @@ -134,15 +135,15 @@ func verifyMaestroAdapterRecovery(ctx context.Context, h *helper.Helper, cluster "Maestro adapter should be present in statuses after recovery") g.Expect(h.HasAdapterCondition(adapterStatus.Conditions, - client.ConditionTypeApplied, openapi.AdapterConditionStatusTrue)).To(BeTrue(), + client.ConditionTypeApplied, core.AdapterConditionStatusTrue)).To(BeTrue(), "Maestro adapter Applied should be True after recovery") g.Expect(h.HasAdapterCondition(adapterStatus.Conditions, - client.ConditionTypeAvailable, openapi.AdapterConditionStatusTrue)).To(BeTrue(), + client.ConditionTypeAvailable, core.AdapterConditionStatusTrue)).To(BeTrue(), "Maestro adapter Available should be True after recovery") g.Expect(h.HasAdapterCondition(adapterStatus.Conditions, - client.ConditionTypeHealth, openapi.AdapterConditionStatusTrue)).To(BeTrue(), + client.ConditionTypeHealth, core.AdapterConditionStatusTrue)).To(BeTrue(), "Maestro adapter Health should be True after recovery") }, h.Cfg.Timeouts.Cluster.Reconciled, h.Cfg.Polling.Interval).Should(Succeed()) } @@ -155,11 +156,11 @@ func verifyClusterReconciledAfterRecovery(ctx context.Context, h *helper.Helper, g.Expect(cl.Status).NotTo(BeNil(), "cluster status should be present") g.Expect(h.HasResourceCondition(cl.Status.Conditions, - client.ConditionTypeReconciled, openapi.ResourceConditionStatusTrue)).To(BeTrue(), + client.ConditionTypeReconciled, openapi.True)).To(BeTrue(), "cluster Reconciled should transition to True") g.Expect(h.HasResourceCondition(cl.Status.Conditions, - client.ConditionTypeLastKnownReconciled, openapi.ResourceConditionStatusTrue)).To(BeTrue(), + client.ConditionTypeLastKnownReconciled, openapi.True)).To(BeTrue(), "cluster LastKnownReconciled should transition to True") }, h.Cfg.Timeouts.Cluster.Reconciled, h.Cfg.Polling.Interval).Should(Succeed()) } diff --git a/e2e/channel/crud_lifecycle.go b/e2e/channel/crud_lifecycle.go index cc2af7c..2c6ca66 100644 --- a/e2e/channel/crud_lifecycle.go +++ b/e2e/channel/crud_lifecycle.go @@ -6,7 +6,7 @@ import ( "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" //nolint:staticcheck // dot import for test readability - "github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/client" + "github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/api/openapi" "github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/helper" "github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/labels" ) @@ -16,7 +16,7 @@ var _ = ginkgo.Describe("[Suite: channel][crud] Channel CRUD Lifecycle", func() { var h *helper.Helper var channelID string - var channel *client.Resource + var channel *openapi.Channel ginkgo.BeforeEach(func(ctx context.Context) { h = helper.New() @@ -72,10 +72,12 @@ var _ = ginkgo.Describe("[Suite: channel][crud] Channel CRUD Lifecycle", ginkgo.It("should update channel via PATCH", func(ctx context.Context) { ginkgo.By("patching channel spec") - patched, err := h.Client.PatchChannel(ctx, channelID, client.ResourcePatchRequest{ - Spec: map[string]any{ - "is_default": true, - "enabled_regex": ".*", + isDefault := true + enabledRegex := ".*" + patched, err := h.Client.PatchChannel(ctx, channelID, openapi.ChannelPatchRequest{ + Spec: &openapi.ChannelSpec{ + IsDefault: isDefault, + EnabledRegex: enabledRegex, }, }) Expect(err).NotTo(HaveOccurred(), "failed to patch channel") @@ -84,13 +86,13 @@ var _ = ginkgo.Describe("[Suite: channel][crud] Channel CRUD Lifecycle", ginkgo.By("verifying patched spec via GET") fetched, err := h.Client.GetChannel(ctx, channelID) Expect(err).NotTo(HaveOccurred()) - Expect(fetched.Spec["is_default"]).To(Equal(true)) + Expect(fetched.Spec.IsDefault).To(BeTrue()) }) ginkgo.It("should update channel labels via PATCH", func(ctx context.Context) { ginkgo.By("patching channel labels") - patched, err := h.Client.PatchChannel(ctx, channelID, client.ResourcePatchRequest{ - Labels: map[string]string{ + patched, err := h.Client.PatchChannel(ctx, channelID, openapi.ChannelPatchRequest{ + Labels: &map[string]string{ "crud": channelID, }, }) diff --git a/e2e/cluster/adapter_failure.go b/e2e/cluster/adapter_failure.go index 994a5c3..4ee63ab 100644 --- a/e2e/cluster/adapter_failure.go +++ b/e2e/cluster/adapter_failure.go @@ -7,6 +7,7 @@ import ( "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" //nolint:staticcheck // dot import for test readability + "github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/api/core" "github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/api/openapi" "github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/client" "github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/helper" @@ -119,7 +120,7 @@ var _ = ginkgo.Describe("[Suite: cluster][negative] Cluster Can Reflect Adapter g.Expect(err).NotTo(HaveOccurred(), "failed to get cluster statuses") // Find the precondition-error-adapter in statuses - var adapterStatus *openapi.AdapterStatus + var adapterStatus *core.AdapterStatus for i, status := range statuses.Items { if status.Adapter == adapterName { adapterStatus = &statuses.Items[i] @@ -131,17 +132,17 @@ var _ = ginkgo.Describe("[Suite: cluster][negative] Cluster Can Reflect Adapter // Verify Applied=False g.Expect(h.HasAdapterCondition(adapterStatus.Conditions, - client.ConditionTypeApplied, openapi.AdapterConditionStatusFalse)).To(BeTrue(), + client.ConditionTypeApplied, core.AdapterConditionStatusFalse)).To(BeTrue(), "precondition-error-adapter should have Applied=False") // Verify Available=False g.Expect(h.HasAdapterCondition(adapterStatus.Conditions, - client.ConditionTypeAvailable, openapi.AdapterConditionStatusFalse)).To(BeTrue(), + client.ConditionTypeAvailable, core.AdapterConditionStatusFalse)).To(BeTrue(), "precondition-error-adapter should have Available=False") // Verify Health=False with reason/message indicating precondition failure g.Expect(h.HasAdapterCondition(adapterStatus.Conditions, - client.ConditionTypeHealth, openapi.AdapterConditionStatusFalse)).To(BeTrue(), + client.ConditionTypeHealth, core.AdapterConditionStatusFalse)).To(BeTrue(), "precondition-error-adapter should have Health=False") healthCond := h.GetCondition(adapterStatus.Conditions, client.ConditionTypeHealth) @@ -167,11 +168,11 @@ var _ = ginkgo.Describe("[Suite: cluster][negative] Cluster Can Reflect Adapter g.Expect(cl.Status).NotTo(BeNil(), "cluster status should be present") g.Expect(h.HasResourceCondition(cl.Status.Conditions, - client.ConditionTypeReconciled, openapi.ResourceConditionStatusTrue)).To(BeTrue(), + client.ConditionTypeReconciled, openapi.True)).To(BeTrue(), "cluster Reconciled should become True despite non-required adapter failure") g.Expect(h.HasResourceCondition(cl.Status.Conditions, - client.ConditionTypeLastKnownReconciled, openapi.ResourceConditionStatusTrue)).To(BeTrue(), + client.ConditionTypeLastKnownReconciled, openapi.True)).To(BeTrue(), "cluster LastKnownReconciled should become True despite non-required adapter failure") }, h.Cfg.Timeouts.Cluster.Reconciled, h.Cfg.Polling.Interval).Should(Succeed()) diff --git a/e2e/cluster/concurrent_creation.go b/e2e/cluster/concurrent_creation.go index d86e540..2ba9103 100644 --- a/e2e/cluster/concurrent_creation.go +++ b/e2e/cluster/concurrent_creation.go @@ -8,6 +8,7 @@ import ( "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" //nolint:staticcheck // dot import for test readability + "github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/api/core" "github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/api/openapi" "github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/client" "github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/helper" @@ -95,13 +96,13 @@ var _ = ginkgo.Describe("[Suite: cluster][concurrent] System can process concurr for i, clusterID := range clusterIDs { ginkgo.GinkgoWriter.Printf("Waiting for cluster %d (%s) to become Reconciled...\n", i, clusterID) Eventually(h.PollCluster(ctx, clusterID), h.Cfg.Timeouts.Cluster.Reconciled, h.Cfg.Polling.Interval). - Should(helper.HaveResourceCondition(client.ConditionTypeReconciled, openapi.ResourceConditionStatusTrue)) + Should(helper.HaveResourceCondition(client.ConditionTypeReconciled, openapi.True)) cluster, err := h.Client.GetCluster(ctx, clusterID) Expect(err).NotTo(HaveOccurred(), "failed to get cluster %d (%s)", i, clusterID) hasAvailable := h.HasResourceCondition(cluster.Status.Conditions, - client.ConditionTypeLastKnownReconciled, openapi.ResourceConditionStatusTrue) + client.ConditionTypeLastKnownReconciled, openapi.True) Expect(hasAvailable).To(BeTrue(), "cluster %d (%s) should have LastKnownReconciled=True", i, clusterID) @@ -127,7 +128,7 @@ var _ = ginkgo.Describe("[Suite: cluster][concurrent] System can process concurr g.Expect(statuses.Items).NotTo(BeEmpty(), "cluster %d (%s) should have adapter statuses", i, clusterID) // Build adapter status map - adapterMap := make(map[string]openapi.AdapterStatus) + adapterMap := make(map[string]core.AdapterStatus) for _, adapter := range statuses.Items { adapterMap[adapter.Adapter] = adapter } @@ -139,15 +140,15 @@ var _ = ginkgo.Describe("[Suite: cluster][concurrent] System can process concurr "cluster %d (%s): required adapter %s should be present", i, clusterID, requiredAdapter) g.Expect(h.HasAdapterCondition(adapter.Conditions, - client.ConditionTypeApplied, openapi.AdapterConditionStatusTrue)).To(BeTrue(), + client.ConditionTypeApplied, core.AdapterConditionStatusTrue)).To(BeTrue(), "cluster %d (%s): adapter %s should have Applied=True", i, clusterID, requiredAdapter) g.Expect(h.HasAdapterCondition(adapter.Conditions, - client.ConditionTypeAvailable, openapi.AdapterConditionStatusTrue)).To(BeTrue(), + client.ConditionTypeAvailable, core.AdapterConditionStatusTrue)).To(BeTrue(), "cluster %d (%s): adapter %s should have Available=True", i, clusterID, requiredAdapter) g.Expect(h.HasAdapterCondition(adapter.Conditions, - client.ConditionTypeHealth, openapi.AdapterConditionStatusTrue)).To(BeTrue(), + client.ConditionTypeHealth, core.AdapterConditionStatusTrue)).To(BeTrue(), "cluster %d (%s): adapter %s should have Health=True", i, clusterID, requiredAdapter) } }, h.Cfg.Timeouts.Adapter.Processing, h.Cfg.Polling.Interval).Should(Succeed()) diff --git a/e2e/cluster/crash_recovery.go b/e2e/cluster/crash_recovery.go index 9c45dd4..5338b5b 100644 --- a/e2e/cluster/crash_recovery.go +++ b/e2e/cluster/crash_recovery.go @@ -7,6 +7,7 @@ import ( "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" //nolint:staticcheck // dot import for test readability + "github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/api/core" "github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/api/openapi" "github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/client" "github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/helper" @@ -196,7 +197,7 @@ func verifyClusterNotReconciled(ctx context.Context, h *helper.Helper, clusterID g.Expect(cl.Status).NotTo(BeNil(), "cluster status should be present") g.Expect(h.HasResourceCondition(cl.Status.Conditions, - client.ConditionTypeReconciled, openapi.ResourceConditionStatusFalse)).To(BeTrue(), + client.ConditionTypeReconciled, openapi.False)).To(BeTrue(), "cluster Reconciled condition should remain False while crash-adapter is unavailable") }, h.Cfg.Polling.Interval*3, h.Cfg.Polling.Interval).Should(Succeed()) } @@ -207,7 +208,7 @@ func verifyAdapterRecovery(ctx context.Context, h *helper.Helper, clusterID, ada statuses, err := h.Client.GetClusterStatuses(ctx, clusterID) g.Expect(err).NotTo(HaveOccurred(), "failed to get cluster statuses") - var adapterStatus *openapi.AdapterStatus + var adapterStatus *core.AdapterStatus for i, status := range statuses.Items { if status.Adapter == adapterName { adapterStatus = &statuses.Items[i] @@ -218,15 +219,15 @@ func verifyAdapterRecovery(ctx context.Context, h *helper.Helper, clusterID, ada "crash-adapter should be present in statuses after recovery") g.Expect(h.HasAdapterCondition(adapterStatus.Conditions, - client.ConditionTypeApplied, openapi.AdapterConditionStatusTrue)).To(BeTrue(), + client.ConditionTypeApplied, core.AdapterConditionStatusTrue)).To(BeTrue(), "crash-adapter should have Applied=True after recovery") g.Expect(h.HasAdapterCondition(adapterStatus.Conditions, - client.ConditionTypeAvailable, openapi.AdapterConditionStatusTrue)).To(BeTrue(), + client.ConditionTypeAvailable, core.AdapterConditionStatusTrue)).To(BeTrue(), "crash-adapter should have Available=True after recovery") g.Expect(h.HasAdapterCondition(adapterStatus.Conditions, - client.ConditionTypeHealth, openapi.AdapterConditionStatusTrue)).To(BeTrue(), + client.ConditionTypeHealth, core.AdapterConditionStatusTrue)).To(BeTrue(), "crash-adapter should have Health=True after recovery") g.Expect(adapterStatus.ObservedGeneration).To(Equal(int32(1)), @@ -242,11 +243,11 @@ func verifyClusterReconciled(ctx context.Context, h *helper.Helper, clusterID st g.Expect(cl.Status).NotTo(BeNil(), "cluster status should be present") g.Expect(h.HasResourceCondition(cl.Status.Conditions, - client.ConditionTypeReconciled, openapi.ResourceConditionStatusTrue)).To(BeTrue(), + client.ConditionTypeReconciled, openapi.True)).To(BeTrue(), "cluster Reconciled condition should transition to True") g.Expect(h.HasResourceCondition(cl.Status.Conditions, - client.ConditionTypeLastKnownReconciled, openapi.ResourceConditionStatusTrue)).To(BeTrue(), + client.ConditionTypeLastKnownReconciled, openapi.True)).To(BeTrue(), "cluster LastKnownReconciled condition should transition to True") }, h.Cfg.Timeouts.Cluster.Reconciled, h.Cfg.Polling.Interval).Should(Succeed()) } diff --git a/e2e/cluster/creation.go b/e2e/cluster/creation.go index 705f8b7..88a59f1 100644 --- a/e2e/cluster/creation.go +++ b/e2e/cluster/creation.go @@ -6,6 +6,7 @@ import ( "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" //nolint:staticcheck // dot import for test readability + "github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/api/core" "github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/api/openapi" "github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/client" "github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/helper" @@ -53,7 +54,7 @@ var _ = ginkgo.Describe("[Suite: cluster][baseline] Cluster Resource Type Lifecy g.Expect(statuses.Items).NotTo(BeEmpty(), "at least one adapter should have executed") // Build a map of adapter statuses for easy lookup - adapterMap := make(map[string]openapi.AdapterStatus) + adapterMap := make(map[string]core.AdapterStatus) for _, adapter := range statuses.Items { adapterMap[adapter.Adapter] = adapter } @@ -75,7 +76,7 @@ var _ = ginkgo.Describe("[Suite: cluster][baseline] Cluster Resource Type Lifecy hasApplied := h.HasAdapterCondition( adapter.Conditions, client.ConditionTypeApplied, - openapi.AdapterConditionStatusTrue, + core.AdapterConditionStatusTrue, ) g.Expect(hasApplied).To(BeTrue(), "adapter %s should have Applied=True", adapter.Adapter) @@ -83,7 +84,7 @@ var _ = ginkgo.Describe("[Suite: cluster][baseline] Cluster Resource Type Lifecy hasAvailable := h.HasAdapterCondition( adapter.Conditions, client.ConditionTypeAvailable, - openapi.AdapterConditionStatusTrue, + core.AdapterConditionStatusTrue, ) g.Expect(hasAvailable).To(BeTrue(), "adapter %s should have Available=True", adapter.Adapter) @@ -91,7 +92,7 @@ var _ = ginkgo.Describe("[Suite: cluster][baseline] Cluster Resource Type Lifecy hasHealth := h.HasAdapterCondition( adapter.Conditions, client.ConditionTypeHealth, - openapi.AdapterConditionStatusTrue, + core.AdapterConditionStatusTrue, ) g.Expect(hasHealth).To(BeTrue(), "adapter %s should have Health=True", adapter.Adapter) @@ -118,18 +119,18 @@ var _ = ginkgo.Describe("[Suite: cluster][baseline] Cluster Resource Type Lifecy // Wait for cluster Reconciled condition and verify both Reconciled and Available conditions are True // This confirms the cluster has reached the desired end state Eventually(h.PollCluster(ctx, clusterID), h.Cfg.Timeouts.Cluster.Reconciled, h.Cfg.Polling.Interval). - Should(helper.HaveResourceCondition(client.ConditionTypeReconciled, openapi.ResourceConditionStatusTrue)) + Should(helper.HaveResourceCondition(client.ConditionTypeReconciled, openapi.True)) finalCluster, err := h.Client.GetCluster(ctx, clusterID) Expect(err).NotTo(HaveOccurred(), "failed to get final cluster state") Expect(finalCluster.Status).NotTo(BeNil(), "cluster status should be present") hasReconciled := h.HasResourceCondition(finalCluster.Status.Conditions, - client.ConditionTypeReconciled, openapi.ResourceConditionStatusTrue) + client.ConditionTypeReconciled, openapi.True) Expect(hasReconciled).To(BeTrue(), "cluster should have Reconciled=True condition") hasAvailable := h.HasResourceCondition(finalCluster.Status.Conditions, - client.ConditionTypeLastKnownReconciled, openapi.ResourceConditionStatusTrue) + client.ConditionTypeLastKnownReconciled, openapi.True) Expect(hasAvailable).To(BeTrue(), "cluster should have LastKnownReconciled=True condition") // Validate observedGeneration for Reconciled and LastKnownReconciled conditions @@ -147,7 +148,7 @@ var _ = ginkgo.Describe("[Suite: cluster][baseline] Cluster Resource Type Lifecy hasAdapterCondition := h.HasResourceCondition( finalCluster.Status.Conditions, expectedCondType, - openapi.ResourceConditionStatusTrue, + openapi.True, ) Expect(hasAdapterCondition).To(BeTrue(), "cluster should have %s=True condition for adapter %s", @@ -220,7 +221,7 @@ var _ = ginkgo.Describe("[Suite: cluster][baseline] Cluster Resource Type Lifecy // Wait for cluster Reconciled condition to prevent namespace deletion conflicts // Without this, adapters may still be creating resources during cleanup Eventually(h.PollCluster(ctx, clusterID), h.Cfg.Timeouts.Cluster.Reconciled, h.Cfg.Polling.Interval). - Should(helper.HaveResourceCondition(client.ConditionTypeReconciled, openapi.ResourceConditionStatusTrue)) + Should(helper.HaveResourceCondition(client.ConditionTypeReconciled, openapi.True)) }) }) @@ -235,7 +236,7 @@ var _ = ginkgo.Describe("[Suite: cluster][baseline] Cluster Resource Type Lifecy statuses, err := h.Client.GetClusterStatuses(ctx, clusterID) g.Expect(err).NotTo(HaveOccurred(), "failed to get cluster statuses") - adapterMap := make(map[string]openapi.AdapterStatus) + adapterMap := make(map[string]core.AdapterStatus) for _, adapter := range statuses.Items { adapterMap[adapter.Adapter] = adapter } @@ -248,15 +249,15 @@ var _ = ginkgo.Describe("[Suite: cluster][baseline] Cluster Resource Type Lifecy "adapter %s should have observed_generation=1 for new creation request", name) g.Expect(h.HasAdapterCondition(adapter.Conditions, - client.ConditionTypeApplied, openapi.AdapterConditionStatusTrue)).To(BeTrue(), + client.ConditionTypeApplied, core.AdapterConditionStatusTrue)).To(BeTrue(), "adapter %s should have Applied=True", name) g.Expect(h.HasAdapterCondition(adapter.Conditions, - client.ConditionTypeAvailable, openapi.AdapterConditionStatusTrue)).To(BeTrue(), + client.ConditionTypeAvailable, core.AdapterConditionStatusTrue)).To(BeTrue(), "adapter %s should have Available=True", name) g.Expect(h.HasAdapterCondition(adapter.Conditions, - client.ConditionTypeHealth, openapi.AdapterConditionStatusTrue)).To(BeTrue(), + client.ConditionTypeHealth, core.AdapterConditionStatusTrue)).To(BeTrue(), "adapter %s should have Health=True", name) for _, condition := range adapter.Conditions { diff --git a/e2e/cluster/delete.go b/e2e/cluster/delete.go index 3c47e7a..e18c507 100644 --- a/e2e/cluster/delete.go +++ b/e2e/cluster/delete.go @@ -8,6 +8,7 @@ import ( "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" //nolint:staticcheck // dot import for test readability + "github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/api/core" "github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/api/openapi" "github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/client" "github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/helper" @@ -40,7 +41,7 @@ var _ = ginkgo.Describe("[Suite: cluster][delete] Cluster Deletion Lifecycle", }) Eventually(h.PollCluster(ctx, clusterID), h.Cfg.Timeouts.Cluster.Reconciled, h.Cfg.Polling.Interval). - Should(helper.HaveResourceCondition(client.ConditionTypeReconciled, openapi.ResourceConditionStatusTrue)) + Should(helper.HaveResourceCondition(client.ConditionTypeReconciled, openapi.True)) }) ginkgo.It("should complete full deletion lifecycle from soft-delete through hard-delete", func(ctx context.Context) { @@ -70,7 +71,7 @@ var _ = ginkgo.Describe("[Suite: cluster][delete] Cluster Deletion Lifecycle", } g.Expect(err).NotTo(HaveOccurred()) g.Expect(statuses).To(helper.HaveAllAdaptersWithCondition( - h.Cfg.Adapters.Cluster, client.ConditionTypeFinalized, openapi.AdapterConditionStatusTrue)) + h.Cfg.Adapters.Cluster, client.ConditionTypeFinalized, core.AdapterConditionStatusTrue)) }, h.Cfg.Timeouts.Adapter.Processing, h.Cfg.Polling.Interval).Should(Succeed()) ginkgo.By("confirming cluster is hard-deleted") @@ -113,7 +114,7 @@ var _ = ginkgo.Describe("[Suite: cluster][delete] Cluster Cascade Deletion", }) Eventually(h.PollCluster(ctx, clusterID), h.Cfg.Timeouts.Cluster.Reconciled, h.Cfg.Polling.Interval). - Should(helper.HaveResourceCondition(client.ConditionTypeReconciled, openapi.ResourceConditionStatusTrue)) + Should(helper.HaveResourceCondition(client.ConditionTypeReconciled, openapi.True)) ginkgo.By("creating two nodepools") np1, err := h.Client.CreateNodePoolFromPayload(ctx, clusterID, h.TestDataPath("payloads/nodepools/nodepool-request.json")) @@ -128,10 +129,10 @@ var _ = ginkgo.Describe("[Suite: cluster][delete] Cluster Cascade Deletion", ginkgo.By("waiting for both nodepools to reach Reconciled") Eventually(h.PollNodePool(ctx, clusterID, nodepoolID1), h.Cfg.Timeouts.NodePool.Reconciled, h.Cfg.Polling.Interval). - Should(helper.HaveResourceCondition(client.ConditionTypeReconciled, openapi.ResourceConditionStatusTrue)) + Should(helper.HaveResourceCondition(client.ConditionTypeReconciled, openapi.True)) Eventually(h.PollNodePool(ctx, clusterID, nodepoolID2), h.Cfg.Timeouts.NodePool.Reconciled, h.Cfg.Polling.Interval). - Should(helper.HaveResourceCondition(client.ConditionTypeReconciled, openapi.ResourceConditionStatusTrue)) + Should(helper.HaveResourceCondition(client.ConditionTypeReconciled, openapi.True)) }) ginkgo.It("should cascade deletion to child nodepools and hard-delete all resources", func(ctx context.Context) { diff --git a/e2e/cluster/delete_edge_cases.go b/e2e/cluster/delete_edge_cases.go index e294c63..70fae57 100644 --- a/e2e/cluster/delete_edge_cases.go +++ b/e2e/cluster/delete_edge_cases.go @@ -29,7 +29,7 @@ var _ = ginkgo.Describe("[Suite: cluster][delete] Re-DELETE Idempotency and API Expect(err).NotTo(HaveOccurred(), "failed to create cluster") Eventually(h.PollCluster(ctx, clusterID), h.Cfg.Timeouts.Cluster.Reconciled, h.Cfg.Polling.Interval). - Should(helper.HaveResourceCondition(client.ConditionTypeReconciled, openapi.ResourceConditionStatusTrue)) + Should(helper.HaveResourceCondition(client.ConditionTypeReconciled, openapi.True)) }) ginkgo.It("should handle re-DELETE idempotently without changing deleted_time or generation", @@ -93,13 +93,13 @@ var _ = ginkgo.Describe("[Suite: cluster][delete] DELETE During Update Reconcili clusterID = *cluster.Id Eventually(h.PollCluster(ctx, clusterID), h.Cfg.Timeouts.Cluster.Reconciled, h.Cfg.Polling.Interval). - Should(helper.HaveResourceCondition(client.ConditionTypeReconciled, openapi.ResourceConditionStatusTrue)) + Should(helper.HaveResourceCondition(client.ConditionTypeReconciled, openapi.True)) }) ginkgo.It("should complete deletion when DELETE is sent during update reconciliation", func(ctx context.Context) { ginkgo.By("sending PATCH to trigger generation 2 (do NOT wait for reconciliation)") patchedCluster, err := h.Client.PatchCluster(ctx, clusterID, openapi.ClusterPatchRequest{ - Spec: &openapi.ClusterSpec{"trigger-update": "true"}, + Labels: &map[string]string{"trigger-update": "true"}, }) Expect(err).NotTo(HaveOccurred(), "PATCH should succeed") Expect(patchedCluster.Generation).To(Equal(int32(2))) @@ -156,7 +156,7 @@ var _ = ginkgo.Describe("[Suite: cluster][delete] Recreate Cluster After Hard-De originalCluster = cluster Eventually(h.PollCluster(ctx, firstClusterID), h.Cfg.Timeouts.Cluster.Reconciled, h.Cfg.Polling.Interval). - Should(helper.HaveResourceCondition(client.ConditionTypeReconciled, openapi.ResourceConditionStatusTrue)) + Should(helper.HaveResourceCondition(client.ConditionTypeReconciled, openapi.True)) }) ginkgo.It("should create a new cluster with the same name after the original is hard-deleted", func(ctx context.Context) { @@ -189,7 +189,7 @@ var _ = ginkgo.Describe("[Suite: cluster][delete] Recreate Cluster After Hard-De ginkgo.By("waiting for the new cluster to reach Reconciled") Eventually(h.PollCluster(ctx, secondClusterID), h.Cfg.Timeouts.Cluster.Reconciled, h.Cfg.Polling.Interval). - Should(helper.HaveResourceCondition(client.ConditionTypeReconciled, openapi.ResourceConditionStatusTrue)) + Should(helper.HaveResourceCondition(client.ConditionTypeReconciled, openapi.True)) ginkgo.By("verifying the old cluster is still gone") _, err = h.Client.GetCluster(ctx, firstClusterID) diff --git a/e2e/cluster/delete_external.go b/e2e/cluster/delete_external.go index 926d23d..145087a 100644 --- a/e2e/cluster/delete_external.go +++ b/e2e/cluster/delete_external.go @@ -8,6 +8,7 @@ import ( "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" //nolint:staticcheck // dot import for test readability + "github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/api/core" "github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/api/openapi" "github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/client" "github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/helper" @@ -29,7 +30,7 @@ var _ = ginkgo.Describe("[Suite: cluster][delete] External K8s Resource Deletion Expect(err).NotTo(HaveOccurred(), "failed to create cluster") Eventually(h.PollCluster(ctx, clusterID), h.Cfg.Timeouts.Cluster.Reconciled, h.Cfg.Polling.Interval). - Should(helper.HaveResourceCondition(client.ConditionTypeReconciled, openapi.ResourceConditionStatusTrue)) + Should(helper.HaveResourceCondition(client.ConditionTypeReconciled, openapi.True)) ginkgo.By("confirming managed K8s namespaces exist") namespaces, err := h.K8sClient.FindNamespacesByPrefix(ctx, clusterID) @@ -65,9 +66,9 @@ var _ = ginkgo.Describe("[Suite: cluster][delete] External K8s Resource Deletion } g.Expect(err).NotTo(HaveOccurred()) g.Expect(statuses).To(helper.HaveAllAdaptersWithCondition( - h.Cfg.Adapters.Cluster, client.ConditionTypeFinalized, openapi.AdapterConditionStatusTrue)) + h.Cfg.Adapters.Cluster, client.ConditionTypeFinalized, core.AdapterConditionStatusTrue)) g.Expect(statuses).To(helper.HaveAllAdaptersWithCondition( - h.Cfg.Adapters.Cluster, client.ConditionTypeHealth, openapi.AdapterConditionStatusTrue)) + h.Cfg.Adapters.Cluster, client.ConditionTypeHealth, core.AdapterConditionStatusTrue)) }, h.Cfg.Timeouts.Adapter.Processing, h.Cfg.Polling.Interval).Should(Succeed()) ginkgo.By("verifying cluster is hard-deleted") diff --git a/e2e/cluster/force_delete.go b/e2e/cluster/force_delete.go index f9a7939..d5c914a 100644 --- a/e2e/cluster/force_delete.go +++ b/e2e/cluster/force_delete.go @@ -36,7 +36,7 @@ var _ = ginkgo.Describe("[Suite: cluster][delete] Force-Delete Cluster Stuck in h.DeferClusterCleanup(clusterID) Eventually(h.PollCluster(ctx, clusterID), h.Cfg.Timeouts.Cluster.Reconciled, h.Cfg.Polling.Interval). - Should(helper.HaveResourceCondition(client.ConditionTypeReconciled, openapi.ResourceConditionStatusTrue)) + Should(helper.HaveResourceCondition(client.ConditionTypeReconciled, openapi.True)) ginkgo.By("Create a nodepool and wait for Reconciled") np, err := h.Client.CreateNodePoolFromPayload(ctx, clusterID, h.TestDataPath("payloads/nodepools/nodepool-request.json")) @@ -45,7 +45,7 @@ var _ = ginkgo.Describe("[Suite: cluster][delete] Force-Delete Cluster Stuck in nodepoolID := *np.Id Eventually(h.PollNodePool(ctx, clusterID, nodepoolID), h.Cfg.Timeouts.NodePool.Reconciled, h.Cfg.Polling.Interval). - Should(helper.HaveResourceCondition(client.ConditionTypeReconciled, openapi.ResourceConditionStatusTrue)) + Should(helper.HaveResourceCondition(client.ConditionTypeReconciled, openapi.True)) // --- Simulate stuck deletion by scaling down an existing cluster adapter --- @@ -80,7 +80,7 @@ var _ = ginkgo.Describe("[Suite: cluster][delete] Force-Delete Cluster Stuck in g.Expect(err).NotTo(HaveOccurred(), "cluster should still be accessible") g.Expect(cl.DeletedTime).NotTo(BeNil(), "cluster should still be soft-deleted") g.Expect(h.HasResourceCondition(cl.Status.Conditions, - client.ConditionTypeReconciled, openapi.ResourceConditionStatusFalse)).To(BeTrue(), + client.ConditionTypeReconciled, openapi.False)).To(BeTrue(), "Reconciled should be False while stuck") }, h.Cfg.Timeouts.Adapter.Processing/2, h.Cfg.Polling.Interval).Should(Succeed()) diff --git a/e2e/cluster/lifecycle_smoke.go b/e2e/cluster/lifecycle_smoke.go index 18dd9bb..b5bc5ad 100644 --- a/e2e/cluster/lifecycle_smoke.go +++ b/e2e/cluster/lifecycle_smoke.go @@ -35,7 +35,7 @@ var _ = ginkgo.Describe("[Suite: cluster][baseline] Cluster Full Lifecycle Smoke ginkgo.By("waiting for Reconciled=True") Eventually(h.PollCluster(ctx, clusterID), h.Cfg.Timeouts.Cluster.Reconciled, h.Cfg.Polling.Interval). - Should(helper.HaveResourceCondition(client.ConditionTypeReconciled, openapi.ResourceConditionStatusTrue)) + Should(helper.HaveResourceCondition(client.ConditionTypeReconciled, openapi.True)) ginkgo.By("confirming reconciled state via GET") reconciledCluster, err := h.Client.GetCluster(ctx, clusterID) @@ -43,7 +43,7 @@ var _ = ginkgo.Describe("[Suite: cluster][baseline] Cluster Full Lifecycle Smoke Expect(reconciledCluster).NotTo(BeNil(), "GET should return cluster object") Expect(reconciledCluster.Status).NotTo(BeNil()) Expect(h.HasResourceCondition(reconciledCluster.Status.Conditions, - client.ConditionTypeReconciled, openapi.ResourceConditionStatusTrue)).To(BeTrue()) + client.ConditionTypeReconciled, openapi.True)).To(BeTrue()) ginkgo.By("soft-deleting the cluster") deletedCluster, err := h.Client.DeleteCluster(ctx, clusterID) diff --git a/e2e/cluster/perf_cascade_delete_latency.go b/e2e/cluster/perf_cascade_delete_latency.go index d5d9ad5..e5e53be 100644 --- a/e2e/cluster/perf_cascade_delete_latency.go +++ b/e2e/cluster/perf_cascade_delete_latency.go @@ -38,7 +38,7 @@ var _ = ginkgo.Describe("[Suite: cluster][perf] Cascade delete-to-hard-delete la ginkgo.By("waiting for cluster to reach Reconciled") Eventually(h.PollCluster(ctx, clusterID), h.Cfg.Timeouts.Cluster.Reconciled, h.Cfg.Polling.Interval). - Should(helper.HaveResourceCondition(client.ConditionTypeReconciled, openapi.ResourceConditionStatusTrue)) + Should(helper.HaveResourceCondition(client.ConditionTypeReconciled, openapi.True)) ginkgo.By("creating a nodepool on the cluster") nodepool, err := h.Client.CreateNodePoolFromPayload(ctx, clusterID, h.TestDataPath("payloads/nodepools/nodepool-request.json")) @@ -54,7 +54,7 @@ var _ = ginkgo.Describe("[Suite: cluster][perf] Cascade delete-to-hard-delete la ginkgo.By("waiting for nodepool to reach Reconciled") Eventually(h.PollNodePool(ctx, clusterID, nodepoolID), h.Cfg.Timeouts.NodePool.Reconciled, h.Cfg.Polling.Interval). - Should(helper.HaveResourceCondition(client.ConditionTypeReconciled, openapi.ResourceConditionStatusTrue)) + Should(helper.HaveResourceCondition(client.ConditionTypeReconciled, openapi.True)) }) ginkgo.It("should cascade-delete a cluster with nodepools and reach hard-delete within acceptable latency", func(ctx context.Context) { diff --git a/e2e/cluster/perf_create_latency.go b/e2e/cluster/perf_create_latency.go index 6082a87..dab763b 100644 --- a/e2e/cluster/perf_create_latency.go +++ b/e2e/cluster/perf_create_latency.go @@ -40,7 +40,7 @@ var _ = ginkgo.Describe("[Suite: cluster][perf] Create-to-reconciled latency", }) Eventually(h.PollCluster(ctx, clusterID), h.Cfg.Timeouts.Cluster.Reconciled, h.Cfg.Polling.Interval). - Should(helper.HaveResourceCondition(client.ConditionTypeReconciled, openapi.ResourceConditionStatusTrue)) + Should(helper.HaveResourceCondition(client.ConditionTypeReconciled, openapi.True)) elapsed := time.Since(start) ginkgo.GinkgoWriter.Printf("[PERF] Cluster create-to-reconciled latency: %v\n", elapsed) diff --git a/e2e/cluster/perf_delete_latency.go b/e2e/cluster/perf_delete_latency.go index 19ca54d..59e407a 100644 --- a/e2e/cluster/perf_delete_latency.go +++ b/e2e/cluster/perf_delete_latency.go @@ -37,7 +37,7 @@ var _ = ginkgo.Describe("[Suite: cluster][perf] Delete-to-hard-delete latency", ginkgo.By("waiting for cluster to reach Reconciled before delete") Eventually(h.PollCluster(ctx, clusterID), h.Cfg.Timeouts.Cluster.Reconciled, h.Cfg.Polling.Interval). - Should(helper.HaveResourceCondition(client.ConditionTypeReconciled, openapi.ResourceConditionStatusTrue)) + Should(helper.HaveResourceCondition(client.ConditionTypeReconciled, openapi.True)) }) ginkgo.It("should delete a cluster and reach hard-delete within acceptable latency", func(ctx context.Context) { diff --git a/e2e/cluster/perf_read_entity_size_latency.go b/e2e/cluster/perf_read_entity_size_latency.go index 85999d0..89676f0 100644 --- a/e2e/cluster/perf_read_entity_size_latency.go +++ b/e2e/cluster/perf_read_entity_size_latency.go @@ -49,7 +49,7 @@ var _ = ginkgo.Describe("[Suite: cluster][perf] API read latency by entity size" ginkgo.By("waiting for cluster to reach Reconciled before read") Eventually(h.PollCluster(ctx, clusterID), h.Cfg.Timeouts.Cluster.Reconciled, h.Cfg.Polling.Interval). - Should(helper.HaveResourceCondition(client.ConditionTypeReconciled, openapi.ResourceConditionStatusTrue)) + Should(helper.HaveResourceCondition(client.ConditionTypeReconciled, openapi.True)) ginkgo.By("warming up with untimed read") _, err = h.Client.GetCluster(ctx, clusterID) diff --git a/e2e/cluster/perf_update_latency.go b/e2e/cluster/perf_update_latency.go index 65e022c..c8b709b 100644 --- a/e2e/cluster/perf_update_latency.go +++ b/e2e/cluster/perf_update_latency.go @@ -36,7 +36,7 @@ var _ = ginkgo.Describe("[Suite: cluster][perf] Update-to-re-reconciled latency" ginkgo.By("waiting for cluster to reach Reconciled before update") Eventually(h.PollCluster(ctx, clusterID), h.Cfg.Timeouts.Cluster.Reconciled, h.Cfg.Polling.Interval). - Should(helper.HaveResourceCondition(client.ConditionTypeReconciled, openapi.ResourceConditionStatusTrue)) + Should(helper.HaveResourceCondition(client.ConditionTypeReconciled, openapi.True)) }) ginkgo.It("should update a cluster and reach Reconciled within acceptable latency", func(ctx context.Context) { @@ -52,7 +52,7 @@ var _ = ginkgo.Describe("[Suite: cluster][perf] Update-to-re-reconciled latency" g.Expect(err).NotTo(HaveOccurred()) g.Expect(cluster.Generation).To(BeNumerically(">=", expectedGen)) g.Expect(h.HasResourceCondition(cluster.Status.Conditions, - client.ConditionTypeReconciled, openapi.ResourceConditionStatusTrue)). + client.ConditionTypeReconciled, openapi.True)). To(BeTrue(), "expected Reconciled=True at generation %d", expectedGen) }, h.Cfg.Timeouts.Cluster.Reconciled, h.Cfg.Polling.Interval).Should(Succeed()) diff --git a/e2e/cluster/stuck_deletion.go b/e2e/cluster/stuck_deletion.go index 305a0b5..bf127d2 100644 --- a/e2e/cluster/stuck_deletion.go +++ b/e2e/cluster/stuck_deletion.go @@ -8,6 +8,7 @@ import ( "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" //nolint:staticcheck // dot import for test readability + "github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/api/core" "github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/api/openapi" "github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/client" "github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/helper" @@ -137,7 +138,7 @@ var _ = ginkgo.Describe("[Suite: cluster][negative] Stuck Deletion -- Adapter Un }) Eventually(h.PollCluster(ctx, clusterID), h.Cfg.Timeouts.Cluster.Reconciled, h.Cfg.Polling.Interval). - Should(helper.HaveResourceCondition(client.ConditionTypeReconciled, openapi.ResourceConditionStatusTrue)) + Should(helper.HaveResourceCondition(client.ConditionTypeReconciled, openapi.True)) ginkgo.By("Verify stuck-adapter reported Applied=True") Eventually(func(g Gomega) { @@ -149,7 +150,7 @@ var _ = ginkgo.Describe("[Suite: cluster][negative] Stuck Deletion -- Adapter Un if s.Adapter == adapterName { found = true g.Expect(h.HasAdapterCondition(s.Conditions, - client.ConditionTypeApplied, openapi.AdapterConditionStatusTrue)).To(BeTrue(), + client.ConditionTypeApplied, core.AdapterConditionStatusTrue)).To(BeTrue(), "stuck-adapter should have Applied=True before scale-down") break } @@ -171,7 +172,7 @@ var _ = ginkgo.Describe("[Suite: cluster][negative] Stuck Deletion -- Adapter Un statuses, err := h.Client.GetClusterStatuses(ctx, clusterID) g.Expect(err).NotTo(HaveOccurred(), "failed to get cluster statuses") - adapterMap := make(map[string]openapi.AdapterStatus, len(statuses.Items)) + adapterMap := make(map[string]core.AdapterStatus, len(statuses.Items)) for _, s := range statuses.Items { adapterMap[s.Adapter] = s } @@ -180,7 +181,7 @@ var _ = ginkgo.Describe("[Suite: cluster][negative] Stuck Deletion -- Adapter Un adapter, exists := adapterMap[name] g.Expect(exists).To(BeTrue(), "adapter %s should be present", name) g.Expect(h.HasAdapterCondition(adapter.Conditions, - client.ConditionTypeFinalized, openapi.AdapterConditionStatusTrue)).To(BeTrue(), + client.ConditionTypeFinalized, core.AdapterConditionStatusTrue)).To(BeTrue(), "adapter %s should have Finalized=True", name) } }, h.Cfg.Timeouts.Adapter.Processing, h.Cfg.Polling.Interval).Should(Succeed()) @@ -192,7 +193,7 @@ var _ = ginkgo.Describe("[Suite: cluster][negative] Stuck Deletion -- Adapter Un g.Expect(cl.DeletedTime).NotTo(BeNil(), "cluster should still be soft-deleted") g.Expect(h.HasResourceCondition(cl.Status.Conditions, - client.ConditionTypeReconciled, openapi.ResourceConditionStatusFalse)).To(BeTrue(), + client.ConditionTypeReconciled, openapi.False)).To(BeTrue(), "cluster Reconciled should remain False while stuck-adapter is unavailable") statuses, err := h.Client.GetClusterStatuses(ctx, clusterID) @@ -201,7 +202,7 @@ var _ = ginkgo.Describe("[Suite: cluster][negative] Stuck Deletion -- Adapter Un for _, s := range statuses.Items { if s.Adapter == adapterName { g.Expect(h.HasAdapterCondition(s.Conditions, - client.ConditionTypeFinalized, openapi.AdapterConditionStatusTrue)).To(BeFalse(), + client.ConditionTypeFinalized, core.AdapterConditionStatusTrue)).To(BeFalse(), "stuck-adapter should NOT have Finalized=True while scaled to 0") break } diff --git a/e2e/cluster/update.go b/e2e/cluster/update.go index caebcc6..fe53862 100644 --- a/e2e/cluster/update.go +++ b/e2e/cluster/update.go @@ -34,7 +34,7 @@ var _ = ginkgo.Describe("[Suite: cluster][update] Cluster Update Lifecycle", }) Eventually(h.PollCluster(ctx, clusterID), h.Cfg.Timeouts.Cluster.Reconciled, h.Cfg.Polling.Interval). - Should(helper.HaveResourceCondition(client.ConditionTypeReconciled, openapi.ResourceConditionStatusTrue)) + Should(helper.HaveResourceCondition(client.ConditionTypeReconciled, openapi.True)) }) ginkgo.It("should update cluster via PATCH, trigger reconciliation, and reach Reconciled at new generation", func(ctx context.Context) { @@ -48,7 +48,7 @@ var _ = ginkgo.Describe("[Suite: cluster][update] Cluster Update Lifecycle", Expect(err).NotTo(HaveOccurred(), "PATCH request should succeed") expectedGen := clusterBefore.Generation + 1 Expect(patchedCluster.Generation).To(Equal(expectedGen), "generation should increment after PATCH") - Expect(patchedCluster.Spec).To(HaveKey("dns"), + Expect(patchedCluster.Spec.Dns).NotTo(BeNil(), "PATCH response should reflect updated spec fields") ginkgo.By("waiting for all adapters to reconcile at new generation") @@ -64,7 +64,7 @@ var _ = ginkgo.Describe("[Suite: cluster][update] Cluster Update Lifecycle", found := false for _, cond := range finalCluster.Status.Conditions { - if cond.Type == client.ConditionTypeReconciled && cond.Status == openapi.ResourceConditionStatusTrue { + if cond.Type == client.ConditionTypeReconciled && cond.Status == openapi.True { found = true g.Expect(cond.ObservedGeneration).To(Equal(expectedGen), "Reconciled condition observed_generation should match expected") } diff --git a/e2e/cluster/update_edge_cases.go b/e2e/cluster/update_edge_cases.go index 1cbffee..cc54618 100644 --- a/e2e/cluster/update_edge_cases.go +++ b/e2e/cluster/update_edge_cases.go @@ -28,25 +28,25 @@ var _ = ginkgo.Describe("[Suite: cluster][update] Rapid Update Coalescing", clusterID = *cluster.Id Eventually(h.PollCluster(ctx, clusterID), h.Cfg.Timeouts.Cluster.Reconciled, h.Cfg.Polling.Interval). - Should(helper.HaveResourceCondition(client.ConditionTypeReconciled, openapi.ResourceConditionStatusTrue)) + Should(helper.HaveResourceCondition(client.ConditionTypeReconciled, openapi.True)) }) ginkgo.It("should coalesce multiple rapid updates and reconcile to the latest generation", func(ctx context.Context) { ginkgo.By("sending three PATCH requests in rapid succession") patch1, err := h.Client.PatchCluster(ctx, clusterID, openapi.ClusterPatchRequest{ - Spec: &openapi.ClusterSpec{"update": "first"}, + Labels: &map[string]string{"update": "first"}, }) Expect(err).NotTo(HaveOccurred()) Expect(patch1.Generation).To(Equal(int32(2))) patch2, err := h.Client.PatchCluster(ctx, clusterID, openapi.ClusterPatchRequest{ - Spec: &openapi.ClusterSpec{"update": "second"}, + Labels: &map[string]string{"update": "second"}, }) Expect(err).NotTo(HaveOccurred()) Expect(patch2.Generation).To(Equal(int32(3))) patch3, err := h.Client.PatchCluster(ctx, clusterID, openapi.ClusterPatchRequest{ - Spec: &openapi.ClusterSpec{"update": "third"}, + Labels: &map[string]string{"update": "third"}, }) Expect(err).NotTo(HaveOccurred()) Expect(patch3.Generation).To(Equal(int32(4))) @@ -63,7 +63,7 @@ var _ = ginkgo.Describe("[Suite: cluster][update] Rapid Update Coalescing", found := false for _, cond := range finalCluster.Status.Conditions { - if cond.Type == client.ConditionTypeReconciled && cond.Status == openapi.ResourceConditionStatusTrue { + if cond.Type == client.ConditionTypeReconciled && cond.Status == openapi.True { found = true g.Expect(cond.ObservedGeneration).To(Equal(int32(4))) } diff --git a/e2e/nodepool/concurrent_creation.go b/e2e/nodepool/concurrent_creation.go index bf202bd..07e5934 100644 --- a/e2e/nodepool/concurrent_creation.go +++ b/e2e/nodepool/concurrent_creation.go @@ -8,6 +8,7 @@ import ( "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" //nolint:staticcheck // dot import for test readability + "github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/api/core" "github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/api/openapi" "github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/client" "github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/helper" @@ -106,13 +107,13 @@ var _ = ginkgo.Describe("[Suite: nodepool][concurrent] Multiple nodepools can co for i, npID := range nodepoolIDs { ginkgo.GinkgoWriter.Printf("Waiting for nodepool %d (%s) to become Reconciled...\n", i, npID) Eventually(h.PollNodePool(ctx, clusterID, npID), h.Cfg.Timeouts.NodePool.Reconciled, h.Cfg.Polling.Interval). - Should(helper.HaveResourceCondition(client.ConditionTypeReconciled, openapi.ResourceConditionStatusTrue)) + Should(helper.HaveResourceCondition(client.ConditionTypeReconciled, openapi.True)) np, err := h.Client.GetNodePool(ctx, clusterID, npID) Expect(err).NotTo(HaveOccurred(), "failed to get nodepool %d (%s)", i, npID) hasAvailable := h.HasResourceCondition(np.Status.Conditions, - client.ConditionTypeLastKnownReconciled, openapi.ResourceConditionStatusTrue) + client.ConditionTypeLastKnownReconciled, openapi.True) Expect(hasAvailable).To(BeTrue(), "nodepool %d (%s) should have LastKnownReconciled=True", i, npID) @@ -139,7 +140,7 @@ var _ = ginkgo.Describe("[Suite: nodepool][concurrent] Multiple nodepools can co g.Expect(err).NotTo(HaveOccurred(), "failed to get nodepool statuses for nodepool %d (%s)", i, npID) g.Expect(statuses.Items).NotTo(BeEmpty(), "nodepool %d (%s) should have adapter statuses", i, npID) - adapterMap := make(map[string]openapi.AdapterStatus) + adapterMap := make(map[string]core.AdapterStatus) for _, adapter := range statuses.Items { adapterMap[adapter.Adapter] = adapter } @@ -150,15 +151,15 @@ var _ = ginkgo.Describe("[Suite: nodepool][concurrent] Multiple nodepools can co "nodepool %d (%s): required adapter %s should be present", i, npID, requiredAdapter) g.Expect(h.HasAdapterCondition(adapter.Conditions, - client.ConditionTypeApplied, openapi.AdapterConditionStatusTrue)).To(BeTrue(), + client.ConditionTypeApplied, core.AdapterConditionStatusTrue)).To(BeTrue(), "nodepool %d (%s): adapter %s should have Applied=True", i, npID, requiredAdapter) g.Expect(h.HasAdapterCondition(adapter.Conditions, - client.ConditionTypeAvailable, openapi.AdapterConditionStatusTrue)).To(BeTrue(), + client.ConditionTypeAvailable, core.AdapterConditionStatusTrue)).To(BeTrue(), "nodepool %d (%s): adapter %s should have Available=True", i, npID, requiredAdapter) g.Expect(h.HasAdapterCondition(adapter.Conditions, - client.ConditionTypeHealth, openapi.AdapterConditionStatusTrue)).To(BeTrue(), + client.ConditionTypeHealth, core.AdapterConditionStatusTrue)).To(BeTrue(), "nodepool %d (%s): adapter %s should have Health=True", i, npID, requiredAdapter) } }, h.Cfg.Timeouts.Adapter.Processing, h.Cfg.Polling.Interval).Should(Succeed()) diff --git a/e2e/nodepool/creation.go b/e2e/nodepool/creation.go index 58f997c..9e09b83 100644 --- a/e2e/nodepool/creation.go +++ b/e2e/nodepool/creation.go @@ -7,6 +7,7 @@ import ( "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" //nolint:staticcheck // dot import for test readability + "github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/api/core" "github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/api/openapi" "github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/client" "github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/helper" @@ -55,7 +56,7 @@ var _ = ginkgo.Describe("[Suite: nodepool][baseline] NodePool Resource Type Life g.Expect(statuses.Items).NotTo(BeEmpty(), "at least one adapter should have executed") // Build a map of adapter statuses for easy lookup - adapterMap := make(map[string]openapi.AdapterStatus) + adapterMap := make(map[string]core.AdapterStatus) for _, adapter := range statuses.Items { adapterMap[adapter.Adapter] = adapter } @@ -77,7 +78,7 @@ var _ = ginkgo.Describe("[Suite: nodepool][baseline] NodePool Resource Type Life hasApplied := h.HasAdapterCondition( adapter.Conditions, client.ConditionTypeApplied, - openapi.AdapterConditionStatusTrue, + core.AdapterConditionStatusTrue, ) g.Expect(hasApplied).To(BeTrue(), "adapter %s should have Applied=True", adapter.Adapter) @@ -85,7 +86,7 @@ var _ = ginkgo.Describe("[Suite: nodepool][baseline] NodePool Resource Type Life hasAvailable := h.HasAdapterCondition( adapter.Conditions, client.ConditionTypeAvailable, - openapi.AdapterConditionStatusTrue, + core.AdapterConditionStatusTrue, ) g.Expect(hasAvailable).To(BeTrue(), "adapter %s should have Available=True", adapter.Adapter) @@ -93,7 +94,7 @@ var _ = ginkgo.Describe("[Suite: nodepool][baseline] NodePool Resource Type Life hasHealth := h.HasAdapterCondition( adapter.Conditions, client.ConditionTypeHealth, - openapi.AdapterConditionStatusTrue, + core.AdapterConditionStatusTrue, ) g.Expect(hasHealth).To(BeTrue(), "adapter %s should have Health=True", adapter.Adapter) @@ -120,18 +121,18 @@ var _ = ginkgo.Describe("[Suite: nodepool][baseline] NodePool Resource Type Life // Wait for nodepool Reconciled condition and verify both Reconciled and Available conditions are True // This confirms the nodepool has reached the desired end state Eventually(h.PollNodePool(ctx, clusterID, nodepoolID), h.Cfg.Timeouts.NodePool.Reconciled, h.Cfg.Polling.Interval). - Should(helper.HaveResourceCondition(client.ConditionTypeReconciled, openapi.ResourceConditionStatusTrue)) + Should(helper.HaveResourceCondition(client.ConditionTypeReconciled, openapi.True)) finalNodePool, err := h.Client.GetNodePool(ctx, clusterID, nodepoolID) Expect(err).NotTo(HaveOccurred(), "failed to get final nodepool state") Expect(finalNodePool.Status).NotTo(BeNil(), "nodepool status should be present") hasReconciled := h.HasResourceCondition(finalNodePool.Status.Conditions, - client.ConditionTypeReconciled, openapi.ResourceConditionStatusTrue) + client.ConditionTypeReconciled, openapi.True) Expect(hasReconciled).To(BeTrue(), "nodepool should have Reconciled=True condition") hasAvailable := h.HasResourceCondition(finalNodePool.Status.Conditions, - client.ConditionTypeLastKnownReconciled, openapi.ResourceConditionStatusTrue) + client.ConditionTypeLastKnownReconciled, openapi.True) Expect(hasAvailable).To(BeTrue(), "nodepool should have LastKnownReconciled=True condition") // Validate observedGeneration for Reconciled and LastKnownReconciled conditions @@ -150,7 +151,7 @@ var _ = ginkgo.Describe("[Suite: nodepool][baseline] NodePool Resource Type Life hasAdapterCondition := h.HasResourceCondition( finalNodePool.Status.Conditions, expectedCondType, - openapi.ResourceConditionStatusTrue, + openapi.True, ) Expect(hasAdapterCondition).To(BeTrue(), "nodepool should have %s=True condition for adapter %s", @@ -205,7 +206,7 @@ var _ = ginkgo.Describe("[Suite: nodepool][baseline] NodePool Resource Type Life // This confirms the nodepool workflow completed successfully and all K8s resources were created // Without this, adapters may still be creating resources during cleanup Eventually(h.PollNodePool(ctx, clusterID, nodepoolID), h.Cfg.Timeouts.NodePool.Reconciled, h.Cfg.Polling.Interval). - Should(helper.HaveResourceCondition(client.ConditionTypeReconciled, openapi.ResourceConditionStatusTrue)) + Should(helper.HaveResourceCondition(client.ConditionTypeReconciled, openapi.True)) }) }) diff --git a/e2e/nodepool/delete.go b/e2e/nodepool/delete.go index f3615c8..31d0a84 100644 --- a/e2e/nodepool/delete.go +++ b/e2e/nodepool/delete.go @@ -8,6 +8,7 @@ import ( "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" //nolint:staticcheck // dot import for test readability + "github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/api/core" "github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/api/openapi" "github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/client" "github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/helper" @@ -41,7 +42,7 @@ var _ = ginkgo.Describe("[Suite: nodepool][delete] NodePool Deletion Lifecycle", }) Eventually(h.PollCluster(ctx, clusterID), h.Cfg.Timeouts.Cluster.Reconciled, h.Cfg.Polling.Interval). - Should(helper.HaveResourceCondition(client.ConditionTypeReconciled, openapi.ResourceConditionStatusTrue)) + Should(helper.HaveResourceCondition(client.ConditionTypeReconciled, openapi.True)) ginkgo.By("creating nodepool and waiting for Reconciled") np, err := h.Client.CreateNodePoolFromPayload(ctx, clusterID, h.TestDataPath("payloads/nodepools/nodepool-request.json")) @@ -50,7 +51,7 @@ var _ = ginkgo.Describe("[Suite: nodepool][delete] NodePool Deletion Lifecycle", nodepoolID = *np.Id Eventually(h.PollNodePool(ctx, clusterID, nodepoolID), h.Cfg.Timeouts.NodePool.Reconciled, h.Cfg.Polling.Interval). - Should(helper.HaveResourceCondition(client.ConditionTypeReconciled, openapi.ResourceConditionStatusTrue)) + Should(helper.HaveResourceCondition(client.ConditionTypeReconciled, openapi.True)) }) ginkgo.It("should complete full deletion lifecycle from soft-delete through hard-delete", func(ctx context.Context) { @@ -82,7 +83,7 @@ var _ = ginkgo.Describe("[Suite: nodepool][delete] NodePool Deletion Lifecycle", } g.Expect(err).NotTo(HaveOccurred()) g.Expect(statuses).To(helper.HaveAllAdaptersWithCondition( - h.Cfg.Adapters.NodePool, client.ConditionTypeFinalized, openapi.AdapterConditionStatusTrue)) + h.Cfg.Adapters.NodePool, client.ConditionTypeFinalized, core.AdapterConditionStatusTrue)) }, h.Cfg.Timeouts.Adapter.Processing, h.Cfg.Polling.Interval).Should(Succeed()) ginkgo.By("confirming nodepool is hard-deleted") @@ -95,7 +96,7 @@ var _ = ginkgo.Describe("[Suite: nodepool][delete] NodePool Deletion Lifecycle", Expect(parentCluster.DeletedTime).To(BeNil(), "parent cluster should not have deleted_time") Expect(parentCluster.Generation).To(Equal(parentBefore.Generation), "parent cluster generation should remain unchanged") - Expect(parentCluster).To(helper.HaveResourceCondition(client.ConditionTypeReconciled, openapi.ResourceConditionStatusTrue), + Expect(parentCluster).To(helper.HaveResourceCondition(client.ConditionTypeReconciled, openapi.True), "parent cluster should remain Reconciled=True") }) diff --git a/e2e/nodepool/delete_edge_cases.go b/e2e/nodepool/delete_edge_cases.go index 192e710..26c71f2 100644 --- a/e2e/nodepool/delete_edge_cases.go +++ b/e2e/nodepool/delete_edge_cases.go @@ -7,6 +7,7 @@ import ( "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" //nolint:staticcheck // dot import for test readability + "github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/api/core" "github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/api/openapi" "github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/client" "github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/helper" @@ -30,7 +31,7 @@ var _ = ginkgo.Describe("[Suite: nodepool][delete] Sibling Nodepool Isolation Du Expect(err).NotTo(HaveOccurred(), "failed to create cluster") Eventually(h.PollCluster(ctx, clusterID), h.Cfg.Timeouts.Cluster.Reconciled, h.Cfg.Polling.Interval). - Should(helper.HaveResourceCondition(client.ConditionTypeReconciled, openapi.ResourceConditionStatusTrue)) + Should(helper.HaveResourceCondition(client.ConditionTypeReconciled, openapi.True)) ginkgo.By("creating two nodepools") np1, err := h.Client.CreateNodePoolFromPayload(ctx, clusterID, h.TestDataPath("payloads/nodepools/nodepool-request.json")) @@ -45,10 +46,10 @@ var _ = ginkgo.Describe("[Suite: nodepool][delete] Sibling Nodepool Isolation Du ginkgo.By("waiting for both nodepools to reach Reconciled") Eventually(h.PollNodePool(ctx, clusterID, nodepoolID1), h.Cfg.Timeouts.NodePool.Reconciled, h.Cfg.Polling.Interval). - Should(helper.HaveResourceCondition(client.ConditionTypeReconciled, openapi.ResourceConditionStatusTrue)) + Should(helper.HaveResourceCondition(client.ConditionTypeReconciled, openapi.True)) Eventually(h.PollNodePool(ctx, clusterID, nodepoolID2), h.Cfg.Timeouts.NodePool.Reconciled, h.Cfg.Polling.Interval). - Should(helper.HaveResourceCondition(client.ConditionTypeReconciled, openapi.ResourceConditionStatusTrue)) + Should(helper.HaveResourceCondition(client.ConditionTypeReconciled, openapi.True)) }) ginkgo.It("should not affect sibling nodepool when one is deleted", func(ctx context.Context) { @@ -66,20 +67,20 @@ var _ = ginkgo.Describe("[Suite: nodepool][delete] Sibling Nodepool Isolation Du Expect(err).NotTo(HaveOccurred(), "sibling nodepool should still be accessible") Expect(siblingNP.DeletedTime).To(BeNil(), "sibling nodepool should not have deleted_time") - hasReconciled := h.HasResourceCondition(siblingNP.Status.Conditions, client.ConditionTypeReconciled, openapi.ResourceConditionStatusTrue) + hasReconciled := h.HasResourceCondition(siblingNP.Status.Conditions, client.ConditionTypeReconciled, openapi.True) Expect(hasReconciled).To(BeTrue(), "sibling nodepool should remain Reconciled=True") ginkgo.By("verifying sibling nodepool adapter statuses are intact") Eventually(h.PollNodePoolAdapterStatuses(ctx, clusterID, nodepoolID2), h.Cfg.Timeouts.Adapter.Processing, h.Cfg.Polling.Interval). Should(helper.HaveAllAdaptersWithCondition( - h.Cfg.Adapters.NodePool, client.ConditionTypeApplied, openapi.AdapterConditionStatusTrue)) + h.Cfg.Adapters.NodePool, client.ConditionTypeApplied, core.AdapterConditionStatusTrue)) ginkgo.By("verifying parent cluster is unaffected") parentCluster, err := h.Client.GetCluster(ctx, clusterID) Expect(err).NotTo(HaveOccurred(), "parent cluster should still exist") Expect(parentCluster.DeletedTime).To(BeNil(), "parent cluster should not have deleted_time") - hasParentReconciled := h.HasResourceCondition(parentCluster.Status.Conditions, client.ConditionTypeReconciled, openapi.ResourceConditionStatusTrue) + hasParentReconciled := h.HasResourceCondition(parentCluster.Status.Conditions, client.ConditionTypeReconciled, openapi.True) Expect(hasParentReconciled).To(BeTrue(), "parent cluster should remain Reconciled=True") }) diff --git a/e2e/nodepool/lifecycle_smoke.go b/e2e/nodepool/lifecycle_smoke.go index d42974e..7affa0d 100644 --- a/e2e/nodepool/lifecycle_smoke.go +++ b/e2e/nodepool/lifecycle_smoke.go @@ -31,7 +31,7 @@ var _ = ginkgo.Describe("[Suite: nodepool][baseline] NodePool Full Lifecycle Smo }) Eventually(h.PollCluster(ctx, clusterID), h.Cfg.Timeouts.Cluster.Reconciled, h.Cfg.Polling.Interval). - Should(helper.HaveResourceCondition(client.ConditionTypeReconciled, openapi.ResourceConditionStatusTrue)) + Should(helper.HaveResourceCondition(client.ConditionTypeReconciled, openapi.True)) ginkgo.By("creating a nodepool") np, err := h.Client.CreateNodePoolFromPayload(ctx, clusterID, h.TestDataPath("payloads/nodepools/nodepool-request.json")) @@ -42,11 +42,11 @@ var _ = ginkgo.Describe("[Suite: nodepool][baseline] NodePool Full Lifecycle Smo ginkgo.By("waiting for Reconciled=True at generation 1") Eventually(h.PollNodePool(ctx, clusterID, nodepoolID), h.Cfg.Timeouts.NodePool.Reconciled, h.Cfg.Polling.Interval). - Should(helper.HaveResourceCondition(client.ConditionTypeReconciled, openapi.ResourceConditionStatusTrue)) + Should(helper.HaveResourceCondition(client.ConditionTypeReconciled, openapi.True)) ginkgo.By("PATCHing nodepool spec to trigger generation bump") patched, err := h.Client.PatchNodePool(ctx, clusterID, nodepoolID, openapi.NodePoolPatchRequest{ - Spec: &openapi.NodePoolSpec{"lifecycle-test": "updated"}, + Labels: &map[string]string{"lifecycle-test": "updated"}, }) Expect(err).NotTo(HaveOccurred(), "PATCH should succeed") Expect(patched).NotTo(BeNil(), "PATCH should return nodepool object") @@ -58,7 +58,7 @@ var _ = ginkgo.Describe("[Suite: nodepool][baseline] NodePool Full Lifecycle Smo Should(helper.HaveAllAdaptersAtGeneration(h.Cfg.Adapters.NodePool, int32(2))) Eventually(h.PollNodePool(ctx, clusterID, nodepoolID), h.Cfg.Timeouts.NodePool.Reconciled, h.Cfg.Polling.Interval). - Should(helper.HaveResourceCondition(client.ConditionTypeReconciled, openapi.ResourceConditionStatusTrue)) + Should(helper.HaveResourceCondition(client.ConditionTypeReconciled, openapi.True)) ginkgo.By("soft-deleting the nodepool") deletedNP, err := h.Client.DeleteNodePool(ctx, clusterID, nodepoolID) @@ -77,7 +77,7 @@ var _ = ginkgo.Describe("[Suite: nodepool][baseline] NodePool Full Lifecycle Smo Expect(parentCluster).NotTo(BeNil(), "GET should return parent cluster object") Expect(parentCluster.DeletedTime).To(BeNil(), "parent cluster should not be deleted") Expect(parentCluster).To(helper.HaveResourceCondition( - client.ConditionTypeReconciled, openapi.ResourceConditionStatusTrue), + client.ConditionTypeReconciled, openapi.True), "parent cluster should remain Reconciled=True") }) }, diff --git a/e2e/nodepool/perf_create_latency.go b/e2e/nodepool/perf_create_latency.go index 623582f..ff1efd1 100644 --- a/e2e/nodepool/perf_create_latency.go +++ b/e2e/nodepool/perf_create_latency.go @@ -50,7 +50,7 @@ var _ = ginkgo.Describe("[Suite: nodepool][perf] Create-to-reconciled latency", }) Eventually(h.PollNodePool(ctx, clusterID, nodepoolID), h.Cfg.Timeouts.NodePool.Reconciled, h.Cfg.Polling.Interval). - Should(helper.HaveResourceCondition(client.ConditionTypeReconciled, openapi.ResourceConditionStatusTrue)) + Should(helper.HaveResourceCondition(client.ConditionTypeReconciled, openapi.True)) elapsed := time.Since(start) ginkgo.GinkgoWriter.Printf("[PERF] NodePool create-to-reconciled latency: %v\n", elapsed) diff --git a/e2e/nodepool/perf_delete_latency.go b/e2e/nodepool/perf_delete_latency.go index 28a2397..9cc193f 100644 --- a/e2e/nodepool/perf_delete_latency.go +++ b/e2e/nodepool/perf_delete_latency.go @@ -48,7 +48,7 @@ var _ = ginkgo.Describe("[Suite: nodepool][perf] Delete-to-hard-delete latency", ginkgo.By("waiting for nodepool to reach Reconciled before delete") Eventually(h.PollNodePool(ctx, clusterID, nodepoolID), h.Cfg.Timeouts.NodePool.Reconciled, h.Cfg.Polling.Interval). - Should(helper.HaveResourceCondition(client.ConditionTypeReconciled, openapi.ResourceConditionStatusTrue)) + Should(helper.HaveResourceCondition(client.ConditionTypeReconciled, openapi.True)) }) ginkgo.It("should delete a nodepool and reach hard-delete within acceptable latency", func(ctx context.Context) { diff --git a/e2e/nodepool/update.go b/e2e/nodepool/update.go index 6bf2716..0281273 100644 --- a/e2e/nodepool/update.go +++ b/e2e/nodepool/update.go @@ -34,7 +34,7 @@ var _ = ginkgo.Describe("[Suite: nodepool][update] NodePool Update Lifecycle", }) Eventually(h.PollCluster(ctx, clusterID), h.Cfg.Timeouts.Cluster.Reconciled, h.Cfg.Polling.Interval). - Should(helper.HaveResourceCondition(client.ConditionTypeReconciled, openapi.ResourceConditionStatusTrue)) + Should(helper.HaveResourceCondition(client.ConditionTypeReconciled, openapi.True)) ginkgo.By("creating nodepool and waiting for Reconciled at generation 1") np, err := h.Client.CreateNodePoolFromPayload(ctx, clusterID, h.TestDataPath("payloads/nodepools/nodepool-request.json")) @@ -43,7 +43,7 @@ var _ = ginkgo.Describe("[Suite: nodepool][update] NodePool Update Lifecycle", nodepoolID = *np.Id Eventually(h.PollNodePool(ctx, clusterID, nodepoolID), h.Cfg.Timeouts.NodePool.Reconciled, h.Cfg.Polling.Interval). - Should(helper.HaveResourceCondition(client.ConditionTypeReconciled, openapi.ResourceConditionStatusTrue)) + Should(helper.HaveResourceCondition(client.ConditionTypeReconciled, openapi.True)) }) ginkgo.It("should update nodepool via PATCH, trigger reconciliation, and reach Reconciled at new generation", func(ctx context.Context) { @@ -59,8 +59,8 @@ var _ = ginkgo.Describe("[Suite: nodepool][update] NodePool Update Lifecycle", Expect(err).NotTo(HaveOccurred(), "PATCH request should succeed") expectedGen := npBefore.Generation + 1 Expect(patchedNP.Generation).To(Equal(expectedGen), "generation should increment after PATCH") - Expect(patchedNP.Spec).To(HaveKeyWithValue("replicas", BeNumerically("==", 3)), - "PATCH response should reflect updated replicas field") + Expect(patchedNP.Spec.NodeCount).To(HaveValue(Equal(3)), + "PATCH response should reflect updated nodeCount field") ginkgo.By("waiting for all nodepool adapters to reconcile at new generation") Eventually(h.PollNodePoolAdapterStatuses(ctx, clusterID, nodepoolID), h.Cfg.Timeouts.Adapter.Processing, h.Cfg.Polling.Interval). @@ -75,7 +75,7 @@ var _ = ginkgo.Describe("[Suite: nodepool][update] NodePool Update Lifecycle", found := false for _, cond := range finalNP.Status.Conditions { - if cond.Type == client.ConditionTypeReconciled && cond.Status == openapi.ResourceConditionStatusTrue { + if cond.Type == client.ConditionTypeReconciled && cond.Status == openapi.True { found = true g.Expect(cond.ObservedGeneration).To(Equal(expectedGen), "Reconciled condition observed_generation should match expected") } @@ -88,7 +88,7 @@ var _ = ginkgo.Describe("[Suite: nodepool][update] NodePool Update Lifecycle", Expect(err).NotTo(HaveOccurred()) Expect(parentCluster.Generation).To(Equal(parentBefore.Generation), "nodepool update should not affect cluster generation") - Expect(parentCluster).To(helper.HaveResourceCondition(client.ConditionTypeReconciled, openapi.ResourceConditionStatusTrue), + Expect(parentCluster).To(helper.HaveResourceCondition(client.ConditionTypeReconciled, openapi.True), "parent cluster should remain Reconciled=True") }) diff --git a/e2e/nodepool/update_edge_cases.go b/e2e/nodepool/update_edge_cases.go index ef33708..21dd47e 100644 --- a/e2e/nodepool/update_edge_cases.go +++ b/e2e/nodepool/update_edge_cases.go @@ -28,7 +28,7 @@ var _ = ginkgo.Describe("[Suite: nodepool][update] Labels-Only PATCH", Expect(err).NotTo(HaveOccurred(), "failed to create cluster") Eventually(h.PollCluster(ctx, clusterID), h.Cfg.Timeouts.Cluster.Reconciled, h.Cfg.Polling.Interval). - Should(helper.HaveResourceCondition(client.ConditionTypeReconciled, openapi.ResourceConditionStatusTrue)) + Should(helper.HaveResourceCondition(client.ConditionTypeReconciled, openapi.True)) ginkgo.By("creating nodepool and waiting for Reconciled at generation 1") np, err := h.Client.CreateNodePoolFromPayload(ctx, clusterID, h.TestDataPath("payloads/nodepools/nodepool-request.json")) @@ -37,7 +37,7 @@ var _ = ginkgo.Describe("[Suite: nodepool][update] Labels-Only PATCH", nodepoolID = *np.Id Eventually(h.PollNodePool(ctx, clusterID, nodepoolID), h.Cfg.Timeouts.NodePool.Reconciled, h.Cfg.Polling.Interval). - Should(helper.HaveResourceCondition(client.ConditionTypeReconciled, openapi.ResourceConditionStatusTrue)) + Should(helper.HaveResourceCondition(client.ConditionTypeReconciled, openapi.True)) }) ginkgo.It("should bump generation and trigger reconciliation from a labels-only PATCH", func(ctx context.Context) { @@ -70,9 +70,9 @@ var _ = ginkgo.Describe("[Suite: nodepool][update] Labels-Only PATCH", ginkgo.By("verifying nodepool reaches Reconciled=True and LastKnownReconciled=True") Eventually(h.PollNodePool(ctx, clusterID, nodepoolID), h.Cfg.Timeouts.NodePool.Reconciled, h.Cfg.Polling.Interval). - Should(helper.HaveResourceCondition(client.ConditionTypeReconciled, openapi.ResourceConditionStatusTrue)) + Should(helper.HaveResourceCondition(client.ConditionTypeReconciled, openapi.True)) Eventually(h.PollNodePool(ctx, clusterID, nodepoolID), h.Cfg.Timeouts.NodePool.Reconciled, h.Cfg.Polling.Interval). - Should(helper.HaveResourceCondition(client.ConditionTypeLastKnownReconciled, openapi.ResourceConditionStatusTrue)) + Should(helper.HaveResourceCondition(client.ConditionTypeLastKnownReconciled, openapi.True)) finalNP, err := h.Client.GetNodePool(ctx, clusterID, nodepoolID) Expect(err).NotTo(HaveOccurred()) @@ -88,7 +88,7 @@ var _ = ginkgo.Describe("[Suite: nodepool][update] Labels-Only PATCH", Expect(parentCluster.Generation).To(Equal(parentBefore.Generation), "nodepool labels PATCH should not affect cluster generation") - hasParentReconciled := h.HasResourceCondition(parentCluster.Status.Conditions, client.ConditionTypeReconciled, openapi.ResourceConditionStatusTrue) + hasParentReconciled := h.HasResourceCondition(parentCluster.Status.Conditions, client.ConditionTypeReconciled, openapi.True) Expect(hasParentReconciled).To(BeTrue(), "parent cluster should remain Reconciled=True") }) diff --git a/e2e/version/crud_lifecycle.go b/e2e/version/crud_lifecycle.go index 354d830..b89a035 100644 --- a/e2e/version/crud_lifecycle.go +++ b/e2e/version/crud_lifecycle.go @@ -6,7 +6,7 @@ import ( "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" //nolint:staticcheck // dot import for test readability - "github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/client" + "github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/api/openapi" "github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/helper" "github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/labels" ) @@ -17,7 +17,7 @@ var _ = ginkgo.Describe("[Suite: version][crud] Version CRUD Lifecycle", var h *helper.Helper var channelID string var versionID string - var version *client.Resource + var version *openapi.Version ginkgo.BeforeEach(func(ctx context.Context) { h = helper.New() @@ -80,12 +80,12 @@ var _ = ginkgo.Describe("[Suite: version][crud] Version CRUD Lifecycle", ginkgo.It("should update version via PATCH", func(ctx context.Context) { ginkgo.By("patching version spec") - patched, err := h.Client.PatchVersion(ctx, channelID, versionID, client.ResourcePatchRequest{ - Spec: map[string]any{ - "raw_version": "4.18.0", - "enabled": true, - "is_default": true, - "release_image": "quay.io/openshift-release-dev/ocp-release:4.18.0", + patched, err := h.Client.PatchVersion(ctx, channelID, versionID, openapi.VersionPatchRequest{ + Spec: &openapi.VersionSpec{ + RawVersion: "4.18.0", + Enabled: true, + IsDefault: true, + ReleaseImage: "quay.io/openshift-release-dev/ocp-release:4.18.0", }, }) Expect(err).NotTo(HaveOccurred(), "failed to patch version") @@ -94,14 +94,14 @@ var _ = ginkgo.Describe("[Suite: version][crud] Version CRUD Lifecycle", ginkgo.By("verifying patched spec via GET") fetched, err := h.Client.GetVersion(ctx, channelID, versionID) Expect(err).NotTo(HaveOccurred()) - Expect(fetched.Spec["is_default"]).To(Equal(true)) - Expect(fetched.Spec["raw_version"]).To(Equal("4.18.0")) + Expect(fetched.Spec.IsDefault).To(BeTrue()) + Expect(fetched.Spec.RawVersion).To(Equal("4.18.0")) }) ginkgo.It("should update version labels via PATCH", func(ctx context.Context) { ginkgo.By("patching version labels") - patched, err := h.Client.PatchVersion(ctx, channelID, versionID, client.ResourcePatchRequest{ - Labels: map[string]string{ + patched, err := h.Client.PatchVersion(ctx, channelID, versionID, openapi.VersionPatchRequest{ + Labels: &map[string]string{ "crud": versionID, }, }) diff --git a/e2e/wifconfig/crud_lifecycle.go b/e2e/wifconfig/crud_lifecycle.go index 8aa5899..4059585 100644 --- a/e2e/wifconfig/crud_lifecycle.go +++ b/e2e/wifconfig/crud_lifecycle.go @@ -6,7 +6,7 @@ import ( "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" //nolint:staticcheck // dot import for test readability - "github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/client" + "github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/api/openapi" "github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/helper" "github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/labels" ) @@ -16,7 +16,7 @@ var _ = ginkgo.Describe("[Suite: wifconfig][crud] WifConfig CRUD Lifecycle", func() { var h *helper.Helper var wifConfigID string - var wifConfig *client.Resource + var wifConfig *openapi.WifConfig ginkgo.BeforeEach(func(ctx context.Context) { h = helper.New() @@ -75,10 +75,10 @@ var _ = ginkgo.Describe("[Suite: wifconfig][crud] WifConfig CRUD Lifecycle", updatedVersion := "4.18" ginkgo.By("patching wifconfig spec") - patched, err := h.Client.PatchWifConfig(ctx, wifConfigID, client.ResourcePatchRequest{ - Spec: map[string]any{ - "projectId": updatedProjectID, - "version": updatedVersion, + patched, err := h.Client.PatchWifConfig(ctx, wifConfigID, openapi.WifConfigPatchRequest{ + Spec: &openapi.WifConfigSpec{ + ProjectId: updatedProjectID, + Version: updatedVersion, }, }) Expect(err).NotTo(HaveOccurred(), "failed to patch wifconfig") @@ -87,8 +87,8 @@ var _ = ginkgo.Describe("[Suite: wifconfig][crud] WifConfig CRUD Lifecycle", ginkgo.By("verifying patched spec via GET") fetched, err := h.Client.GetWifConfig(ctx, wifConfigID) Expect(err).NotTo(HaveOccurred()) - Expect(fetched.Spec["projectId"]).To(Equal(updatedProjectID)) - Expect(fetched.Spec["version"]).To(Equal(updatedVersion)) + Expect(fetched.Spec.ProjectId).To(Equal(updatedProjectID)) + Expect(fetched.Spec.Version).To(Equal(updatedVersion)) }) ginkgo.It("should delete wifconfig", func(ctx context.Context) { diff --git a/go.mod b/go.mod index 381faad..7be2008 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,6 @@ require ( github.com/oapi-codegen/runtime v1.1.2 github.com/onsi/ginkgo/v2 v2.27.2 github.com/onsi/gomega v1.38.2 - github.com/openshift-hyperfleet/hyperfleet-api-spec v1.0.24 github.com/samber/lo v1.53.0 github.com/spf13/cobra v1.8.0 github.com/spf13/viper v1.18.0 diff --git a/go.sum b/go.sum index b45265b..baaaf86 100644 --- a/go.sum +++ b/go.sum @@ -121,10 +121,6 @@ github.com/onsi/ginkgo/v2 v2.27.2 h1:LzwLj0b89qtIy6SSASkzlNvX6WktqurSHwkk2ipF/Ns github.com/onsi/ginkgo/v2 v2.27.2/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo= github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A= github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k= -github.com/openshift-hyperfleet/hyperfleet-api-spec v1.0.23 h1:HyOWpJfEonjpLFnMmmhiqaAD3EJSVIvAuaA7y5fZ4kE= -github.com/openshift-hyperfleet/hyperfleet-api-spec v1.0.23/go.mod h1:KITzIAd8HcMpH5lXdHFjgk45dvL6XLpP3wwz8iK+KCI= -github.com/openshift-hyperfleet/hyperfleet-api-spec v1.0.24 h1:ACdI09b1TAqsTehVG0eNaAJhWfybMKt87nx/bZ1Dgwk= -github.com/openshift-hyperfleet/hyperfleet-api-spec v1.0.24/go.mod h1:KITzIAd8HcMpH5lXdHFjgk45dvL6XLpP3wwz8iK+KCI= github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= diff --git a/hack/tools.go b/hack/tools.go index 0101075..53e80fe 100644 --- a/hack/tools.go +++ b/hack/tools.go @@ -1,5 +1,3 @@ //go:build tools package hack - -import _ "github.com/openshift-hyperfleet/hyperfleet-api-spec/schemas" diff --git a/openapi/oapi-codegen-core.yaml b/openapi/oapi-codegen-core.yaml new file mode 100644 index 0000000..1d79a17 --- /dev/null +++ b/openapi/oapi-codegen-core.yaml @@ -0,0 +1,11 @@ +package: core +output: pkg/api/core/core.gen.go + +generate: + models: true + client: false + chi-server: false + embedded-spec: false + +output-options: + skip-prune: false diff --git a/pkg/client/channel.go b/pkg/client/channel.go index 7a29f95..34a9e99 100644 --- a/pkg/client/channel.go +++ b/pkg/client/channel.go @@ -3,50 +3,93 @@ package client import ( "context" "fmt" + "net/http" "github.com/samber/lo" + "github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/api/openapi" "github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/logger" ) -func (c *HyperFleetClient) CreateChannel(ctx context.Context, req ResourceCreateRequest) (*Resource, error) { +func (c *HyperFleetClient) CreateChannel(ctx context.Context, req openapi.ChannelCreateRequest) (*openapi.Channel, error) { logger.Info("creating channel", "name", req.Name) - channel, err := c.CreateResource(ctx, "channels", req) + + resp, err := c.PostChannel(ctx, req) if err != nil { return nil, fmt.Errorf("create channel %q: %w", req.Name, err) } + + channel, err := handleHTTPResponse[openapi.Channel](resp, http.StatusCreated, "create channel") + if err != nil { + return nil, err + } + logger.Info("channel created", "channel_id", lo.FromPtr(channel.Id), "name", req.Name) return channel, nil } -func (c *HyperFleetClient) GetChannel(ctx context.Context, channelID string) (*Resource, error) { - return c.GetResource(ctx, fmt.Sprintf("channels/%s", channelID)) +func (c *HyperFleetClient) GetChannel(ctx context.Context, channelID string) (*openapi.Channel, error) { + resp, err := c.GetChannelById(ctx, channelID, &openapi.GetChannelByIdParams{}) + if err != nil { + return nil, fmt.Errorf("failed to get channel: %w", err) + } + return handleHTTPResponse[openapi.Channel](resp, http.StatusOK, "get channel") } -func (c *HyperFleetClient) ListChannels(ctx context.Context, search string) (*ResourceList, error) { - return c.ListResources(ctx, "channels", search) +func (c *HyperFleetClient) ListChannels(ctx context.Context, search string) (*openapi.ChannelList, error) { + params := &openapi.GetChannelsParams{} + if search != "" { + params.Search = &search + } + resp, err := c.GetChannels(ctx, params) + if err != nil { + return nil, fmt.Errorf("failed to list channels: %w", err) + } + return handleHTTPResponse[openapi.ChannelList](resp, http.StatusOK, "list channels") } -func (c *HyperFleetClient) DeleteChannel(ctx context.Context, channelID string) (*Resource, error) { +func (c *HyperFleetClient) DeleteChannel(ctx context.Context, channelID string) (*openapi.Channel, error) { logger.Info("deleting channel", "channel_id", channelID) - channel, err := c.DeleteResource(ctx, fmt.Sprintf("channels/%s", channelID)) + + resp, err := c.DeleteChannelById(ctx, channelID) + if err != nil { + return nil, fmt.Errorf("failed to delete channel: %w", err) + } + + channel, err := handleHTTPResponse[openapi.Channel](resp, http.StatusAccepted, "delete channel") if err != nil { return nil, err } + logger.Info("channel deleted", "channel_id", channelID) return channel, nil } -func (c *HyperFleetClient) PatchChannel(ctx context.Context, channelID string, req ResourcePatchRequest) (*Resource, error) { +func (c *HyperFleetClient) PatchChannel(ctx context.Context, channelID string, req openapi.ChannelPatchRequest) (*openapi.Channel, error) { logger.Info("patching channel", "channel_id", channelID) - channel, err := c.PatchResource(ctx, fmt.Sprintf("channels/%s", channelID), req) + + resp, err := c.PatchChannelById(ctx, channelID, req) + if err != nil { + return nil, fmt.Errorf("failed to patch channel: %w", err) + } + + channel, err := handleHTTPResponse[openapi.Channel](resp, http.StatusOK, "patch channel") if err != nil { return nil, err } + logger.Info("channel patched", "channel_id", channelID, "generation", channel.Generation) return channel, nil } -func (c *HyperFleetClient) CreateChannelFromPayload(ctx context.Context, payloadPath string) (*Resource, error) { - return c.CreateResourceFromPayload(ctx, "channels", payloadPath) +func (c *HyperFleetClient) CreateChannelFromPayload(ctx context.Context, payloadPath string) (*openapi.Channel, error) { + logger.Debug("loading channel payload", "payload_path", payloadPath) + + req, err := loadPayloadFromFile[openapi.ChannelCreateRequest](payloadPath) + if err != nil { + logger.Error("failed to load payload", "payload_path", payloadPath, "error", err) + return nil, err + } + + return c.CreateChannel(ctx, *req) } diff --git a/pkg/client/cluster.go b/pkg/client/cluster.go index 59a7e08..28ec0fd 100644 --- a/pkg/client/cluster.go +++ b/pkg/client/cluster.go @@ -5,6 +5,7 @@ import ( "fmt" "net/http" + "github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/api/core" "github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/api/openapi" "github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/logger" ) @@ -52,12 +53,12 @@ func (c *HyperFleetClient) ListClustersWithParams(ctx context.Context, params *o } // GetClusterStatuses retrieves all adapter statuses for a cluster. -func (c *HyperFleetClient) GetClusterStatuses(ctx context.Context, clusterID string) (*openapi.AdapterStatusList, error) { - resp, err := c.Client.GetClusterStatuses(ctx, clusterID, &openapi.GetClusterStatusesParams{}) +func (c *HyperFleetClient) GetClusterStatuses(ctx context.Context, clusterID string) (*core.AdapterStatusList, error) { + resp, err := c.doJSON(ctx, http.MethodGet, fmt.Sprintf("clusters/%s/statuses", clusterID), nil) if err != nil { return nil, fmt.Errorf("failed to get cluster statuses: %w", err) } - return handleHTTPResponse[openapi.AdapterStatusList](resp, http.StatusOK, "get cluster statuses") + return handleHTTPResponse[core.AdapterStatusList](resp, http.StatusOK, "get cluster statuses") } // CreateClusterFromPayload creates a cluster from a JSON payload file. @@ -126,7 +127,7 @@ func (c *HyperFleetClient) PatchClusterFromPayload(ctx context.Context, clusterI func (c *HyperFleetClient) ForceDeleteCluster(ctx context.Context, clusterID, reason string) error { logger.Info("force-deleting cluster", "cluster_id", clusterID, "reason", reason) - resp, err := c.Client.ForceDeleteCluster(ctx, clusterID, openapi.ForceDeleteRequest{Reason: reason}) + resp, err := c.doJSON(ctx, http.MethodPost, fmt.Sprintf("clusters/%s/force-delete", clusterID), core.ForceDeleteRequest{Reason: reason}) if err != nil { return fmt.Errorf("failed to force-delete cluster: %w", err) } diff --git a/pkg/client/nodepool.go b/pkg/client/nodepool.go index c7ed1b5..1b2fb33 100644 --- a/pkg/client/nodepool.go +++ b/pkg/client/nodepool.go @@ -5,6 +5,7 @@ import ( "fmt" "net/http" + "github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/api/core" "github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/api/openapi" "github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/logger" ) @@ -47,12 +48,12 @@ func (c *HyperFleetClient) ListNodePools(ctx context.Context, clusterID string) } // GetNodePoolStatuses retrieves all adapter statuses for a nodepool. -func (c *HyperFleetClient) GetNodePoolStatuses(ctx context.Context, clusterID, nodepoolID string) (*openapi.AdapterStatusList, error) { - resp, err := c.GetNodePoolsStatuses(ctx, clusterID, nodepoolID, &openapi.GetNodePoolsStatusesParams{}) +func (c *HyperFleetClient) GetNodePoolStatuses(ctx context.Context, clusterID, nodepoolID string) (*core.AdapterStatusList, error) { + resp, err := c.doJSON(ctx, http.MethodGet, fmt.Sprintf("clusters/%s/nodepools/%s/statuses", clusterID, nodepoolID), nil) if err != nil { return nil, fmt.Errorf("failed to get nodepool statuses: %w", err) } - return handleHTTPResponse[openapi.AdapterStatusList](resp, http.StatusOK, "get nodepool statuses") + return handleHTTPResponse[core.AdapterStatusList](resp, http.StatusOK, "get nodepool statuses") } // CreateNodePoolFromPayload creates a nodepool from a JSON payload file. @@ -121,7 +122,7 @@ func (c *HyperFleetClient) PatchNodePoolFromPayload(ctx context.Context, cluster func (c *HyperFleetClient) ForceDeleteNodePool(ctx context.Context, clusterID, nodepoolID, reason string) error { logger.Info("force-deleting nodepool", "cluster_id", clusterID, "nodepool_id", nodepoolID, "reason", reason) - resp, err := c.Client.ForceDeleteNodePool(ctx, clusterID, nodepoolID, openapi.ForceDeleteRequest{Reason: reason}) + resp, err := c.doJSON(ctx, http.MethodPost, fmt.Sprintf("clusters/%s/nodepools/%s/force-delete", clusterID, nodepoolID), core.ForceDeleteRequest{Reason: reason}) if err != nil { return fmt.Errorf("failed to force-delete nodepool: %w", err) } diff --git a/pkg/client/version.go b/pkg/client/version.go index dbe1cf4..5a86235 100644 --- a/pkg/client/version.go +++ b/pkg/client/version.go @@ -3,50 +3,93 @@ package client import ( "context" "fmt" + "net/http" "github.com/samber/lo" + "github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/api/openapi" "github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/logger" ) -func (c *HyperFleetClient) CreateVersion(ctx context.Context, channelID string, req ResourceCreateRequest) (*Resource, error) { +func (c *HyperFleetClient) CreateVersion(ctx context.Context, channelID string, req openapi.VersionCreateRequest) (*openapi.Version, error) { logger.Info("creating version", "channel_id", channelID, "name", req.Name) - version, err := c.CreateResource(ctx, fmt.Sprintf("channels/%s/versions", channelID), req) + + resp, err := c.PostVersion(ctx, channelID, req) if err != nil { return nil, fmt.Errorf("create version %q in channel %s: %w", req.Name, channelID, err) } + + version, err := handleHTTPResponse[openapi.Version](resp, http.StatusCreated, "create version") + if err != nil { + return nil, err + } + logger.Info("version created", "channel_id", channelID, "version_id", lo.FromPtr(version.Id), "name", req.Name) return version, nil } -func (c *HyperFleetClient) GetVersion(ctx context.Context, channelID, versionID string) (*Resource, error) { - return c.GetResource(ctx, fmt.Sprintf("channels/%s/versions/%s", channelID, versionID)) +func (c *HyperFleetClient) GetVersion(ctx context.Context, channelID, versionID string) (*openapi.Version, error) { + resp, err := c.GetVersionById(ctx, channelID, versionID) + if err != nil { + return nil, fmt.Errorf("failed to get version: %w", err) + } + return handleHTTPResponse[openapi.Version](resp, http.StatusOK, "get version") } -func (c *HyperFleetClient) ListVersions(ctx context.Context, channelID, search string) (*ResourceList, error) { - return c.ListResources(ctx, fmt.Sprintf("channels/%s/versions", channelID), search) +func (c *HyperFleetClient) ListVersions(ctx context.Context, channelID, search string) (*openapi.VersionList, error) { + params := &openapi.GetVersionsByChannelIdParams{} + if search != "" { + params.Search = &search + } + resp, err := c.GetVersionsByChannelId(ctx, channelID, params) + if err != nil { + return nil, fmt.Errorf("failed to list versions: %w", err) + } + return handleHTTPResponse[openapi.VersionList](resp, http.StatusOK, "list versions") } -func (c *HyperFleetClient) DeleteVersion(ctx context.Context, channelID, versionID string) (*Resource, error) { +func (c *HyperFleetClient) DeleteVersion(ctx context.Context, channelID, versionID string) (*openapi.Version, error) { logger.Info("deleting version", "channel_id", channelID, "version_id", versionID) - version, err := c.DeleteResource(ctx, fmt.Sprintf("channels/%s/versions/%s", channelID, versionID)) + + resp, err := c.DeleteVersionById(ctx, channelID, versionID) + if err != nil { + return nil, fmt.Errorf("failed to delete version: %w", err) + } + + version, err := handleHTTPResponse[openapi.Version](resp, http.StatusAccepted, "delete version") if err != nil { return nil, err } + logger.Info("version deleted", "channel_id", channelID, "version_id", versionID) return version, nil } -func (c *HyperFleetClient) PatchVersion(ctx context.Context, channelID, versionID string, req ResourcePatchRequest) (*Resource, error) { +func (c *HyperFleetClient) PatchVersion(ctx context.Context, channelID, versionID string, req openapi.VersionPatchRequest) (*openapi.Version, error) { logger.Info("patching version", "channel_id", channelID, "version_id", versionID) - version, err := c.PatchResource(ctx, fmt.Sprintf("channels/%s/versions/%s", channelID, versionID), req) + + resp, err := c.PatchVersionById(ctx, channelID, versionID, req) + if err != nil { + return nil, fmt.Errorf("failed to patch version: %w", err) + } + + version, err := handleHTTPResponse[openapi.Version](resp, http.StatusOK, "patch version") if err != nil { return nil, err } + logger.Info("version patched", "channel_id", channelID, "version_id", versionID, "generation", version.Generation) return version, nil } -func (c *HyperFleetClient) CreateVersionFromPayload(ctx context.Context, channelID, payloadPath string) (*Resource, error) { - return c.CreateResourceFromPayload(ctx, fmt.Sprintf("channels/%s/versions", channelID), payloadPath) +func (c *HyperFleetClient) CreateVersionFromPayload(ctx context.Context, channelID, payloadPath string) (*openapi.Version, error) { + logger.Debug("loading version payload", "channel_id", channelID, "payload_path", payloadPath) + + req, err := loadPayloadFromFile[openapi.VersionCreateRequest](payloadPath) + if err != nil { + logger.Error("failed to load payload", "channel_id", channelID, "payload_path", payloadPath, "error", err) + return nil, err + } + + return c.CreateVersion(ctx, channelID, *req) } diff --git a/pkg/client/wifconfig.go b/pkg/client/wifconfig.go index abad380..bdde2f1 100644 --- a/pkg/client/wifconfig.go +++ b/pkg/client/wifconfig.go @@ -3,50 +3,93 @@ package client import ( "context" "fmt" + "net/http" "github.com/samber/lo" + "github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/api/openapi" "github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/logger" ) -func (c *HyperFleetClient) CreateWifConfig(ctx context.Context, req ResourceCreateRequest) (*Resource, error) { +func (c *HyperFleetClient) CreateWifConfig(ctx context.Context, req openapi.WifConfigCreateRequest) (*openapi.WifConfig, error) { logger.Info("creating wifconfig", "name", req.Name) - wifConfig, err := c.CreateResource(ctx, "wifconfigs", req) + + resp, err := c.PostWifConfig(ctx, req) if err != nil { return nil, fmt.Errorf("create wifconfig %q: %w", req.Name, err) } + + wifConfig, err := handleHTTPResponse[openapi.WifConfig](resp, http.StatusCreated, "create wifconfig") + if err != nil { + return nil, err + } + logger.Info("wifconfig created", "wifconfig_id", lo.FromPtr(wifConfig.Id), "name", req.Name) return wifConfig, nil } -func (c *HyperFleetClient) GetWifConfig(ctx context.Context, wifConfigID string) (*Resource, error) { - return c.GetResource(ctx, fmt.Sprintf("wifconfigs/%s", wifConfigID)) +func (c *HyperFleetClient) GetWifConfig(ctx context.Context, wifConfigID string) (*openapi.WifConfig, error) { + resp, err := c.GetWifConfigById(ctx, wifConfigID, &openapi.GetWifConfigByIdParams{}) + if err != nil { + return nil, fmt.Errorf("failed to get wifconfig: %w", err) + } + return handleHTTPResponse[openapi.WifConfig](resp, http.StatusOK, "get wifconfig") } -func (c *HyperFleetClient) ListWifConfigs(ctx context.Context, search string) (*ResourceList, error) { - return c.ListResources(ctx, "wifconfigs", search) +func (c *HyperFleetClient) ListWifConfigs(ctx context.Context, search string) (*openapi.WifConfigList, error) { + params := &openapi.GetWifConfigsParams{} + if search != "" { + params.Search = &search + } + resp, err := c.GetWifConfigs(ctx, params) + if err != nil { + return nil, fmt.Errorf("failed to list wifconfigs: %w", err) + } + return handleHTTPResponse[openapi.WifConfigList](resp, http.StatusOK, "list wifconfigs") } -func (c *HyperFleetClient) DeleteWifConfig(ctx context.Context, wifConfigID string) (*Resource, error) { +func (c *HyperFleetClient) DeleteWifConfig(ctx context.Context, wifConfigID string) (*openapi.WifConfig, error) { logger.Info("deleting wifconfig", "wifconfig_id", wifConfigID) - wifConfig, err := c.DeleteResource(ctx, fmt.Sprintf("wifconfigs/%s", wifConfigID)) + + resp, err := c.DeleteWifConfigById(ctx, wifConfigID) if err != nil { return nil, fmt.Errorf("delete wifconfig %q: %w", wifConfigID, err) } + + wifConfig, err := handleHTTPResponse[openapi.WifConfig](resp, http.StatusAccepted, "delete wifconfig") + if err != nil { + return nil, err + } + logger.Info("wifconfig deleted", "wifconfig_id", wifConfigID) return wifConfig, nil } -func (c *HyperFleetClient) PatchWifConfig(ctx context.Context, wifConfigID string, req ResourcePatchRequest) (*Resource, error) { +func (c *HyperFleetClient) PatchWifConfig(ctx context.Context, wifConfigID string, req openapi.WifConfigPatchRequest) (*openapi.WifConfig, error) { logger.Info("patching wifconfig", "wifconfig_id", wifConfigID) - wifConfig, err := c.PatchResource(ctx, fmt.Sprintf("wifconfigs/%s", wifConfigID), req) + + resp, err := c.PatchWifConfigById(ctx, wifConfigID, req) if err != nil { return nil, fmt.Errorf("patch wifconfig %q: %w", wifConfigID, err) } + + wifConfig, err := handleHTTPResponse[openapi.WifConfig](resp, http.StatusOK, "patch wifconfig") + if err != nil { + return nil, err + } + logger.Info("wifconfig patched", "wifconfig_id", wifConfigID, "generation", wifConfig.Generation) return wifConfig, nil } -func (c *HyperFleetClient) CreateWifConfigFromPayload(ctx context.Context, payloadPath string) (*Resource, error) { - return c.CreateResourceFromPayload(ctx, "wifconfigs", payloadPath) +func (c *HyperFleetClient) CreateWifConfigFromPayload(ctx context.Context, payloadPath string) (*openapi.WifConfig, error) { + logger.Debug("loading wifconfig payload", "payload_path", payloadPath) + + req, err := loadPayloadFromFile[openapi.WifConfigCreateRequest](payloadPath) + if err != nil { + logger.Error("failed to load payload", "payload_path", payloadPath, "error", err) + return nil, err + } + + return c.CreateWifConfig(ctx, *req) } diff --git a/pkg/helper/matchers.go b/pkg/helper/matchers.go index 4799347..e26ffdb 100644 --- a/pkg/helper/matchers.go +++ b/pkg/helper/matchers.go @@ -6,6 +6,7 @@ import ( "github.com/onsi/gomega/types" + "github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/api/core" "github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/api/openapi" "github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/client" ) @@ -49,7 +50,7 @@ func (m *resourceConditionMatcher) NegatedFailureMessage(_ any) string { // HaveAllAdaptersWithCondition matches an *AdapterStatusList where every required // adapter has the specified condition type and status. -func HaveAllAdaptersWithCondition(requiredAdapters []string, condType string, status openapi.AdapterConditionStatus) types.GomegaMatcher { +func HaveAllAdaptersWithCondition(requiredAdapters []string, condType string, status core.AdapterConditionStatus) types.GomegaMatcher { return &allAdaptersConditionMatcher{ adapters: requiredAdapters, condType: condType, @@ -60,12 +61,12 @@ func HaveAllAdaptersWithCondition(requiredAdapters []string, condType string, st type allAdaptersConditionMatcher struct { adapters []string condType string - status openapi.AdapterConditionStatus + status core.AdapterConditionStatus missing []string } func (m *allAdaptersConditionMatcher) Match(actual any) (bool, error) { - list, ok := actual.(*openapi.AdapterStatusList) + list, ok := actual.(*core.AdapterStatusList) if !ok { return false, fmt.Errorf("HaveAllAdaptersWithCondition expects *AdapterStatusList, got %T", actual) } @@ -74,7 +75,7 @@ func (m *allAdaptersConditionMatcher) Match(actual any) (bool, error) { } m.missing = nil - adapterMap := make(map[string]openapi.AdapterStatus, len(list.Items)) + adapterMap := make(map[string]core.AdapterStatus, len(list.Items)) for _, s := range list.Items { adapterMap[s.Adapter] = s } @@ -116,7 +117,7 @@ type allAdaptersGenerationMatcher struct { } func (m *allAdaptersGenerationMatcher) Match(actual any) (bool, error) { - list, ok := actual.(*openapi.AdapterStatusList) + list, ok := actual.(*core.AdapterStatusList) if !ok { return false, fmt.Errorf("HaveAllAdaptersAtGeneration expects *AdapterStatusList, got %T", actual) } @@ -125,7 +126,7 @@ func (m *allAdaptersGenerationMatcher) Match(actual any) (bool, error) { } m.failures = nil - adapterMap := make(map[string]openapi.AdapterStatus, len(list.Items)) + adapterMap := make(map[string]core.AdapterStatus, len(list.Items)) for _, s := range list.Items { adapterMap[s.Adapter] = s } @@ -141,7 +142,7 @@ func (m *allAdaptersGenerationMatcher) Match(actual any) (bool, error) { continue } for _, ct := range []string{client.ConditionTypeApplied, client.ConditionTypeAvailable, client.ConditionTypeHealth} { - if !hasAdapterCond(adapter.Conditions, ct, openapi.AdapterConditionStatusTrue) { + if !hasAdapterCond(adapter.Conditions, ct, core.AdapterConditionStatusTrue) { m.failures = append(m.failures, fmt.Sprintf("%s: %s!=True", name, ct)) } } @@ -157,7 +158,7 @@ func (m *allAdaptersGenerationMatcher) NegatedFailureMessage(_ any) string { return fmt.Sprintf("expected adapters NOT at generation %d", m.generation) } -func hasAdapterCond(conditions []openapi.AdapterCondition, condType string, status openapi.AdapterConditionStatus) bool { +func hasAdapterCond(conditions []core.AdapterCondition, condType string, status core.AdapterConditionStatus) bool { for _, c := range conditions { if c.Type == condType && c.Status == status { return true diff --git a/pkg/helper/pollers.go b/pkg/helper/pollers.go index 3de49cd..196f7f9 100644 --- a/pkg/helper/pollers.go +++ b/pkg/helper/pollers.go @@ -5,6 +5,7 @@ import ( "errors" "net/http" + "github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/api/core" "github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/api/openapi" "github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/client" ) @@ -24,15 +25,15 @@ func (h *Helper) PollNodePool(ctx context.Context, clusterID, npID string) func( } // PollClusterAdapterStatuses returns a polling function for cluster adapter status checks. -func (h *Helper) PollClusterAdapterStatuses(ctx context.Context, clusterID string) func() (*openapi.AdapterStatusList, error) { - return func() (*openapi.AdapterStatusList, error) { +func (h *Helper) PollClusterAdapterStatuses(ctx context.Context, clusterID string) func() (*core.AdapterStatusList, error) { + return func() (*core.AdapterStatusList, error) { return h.Client.GetClusterStatuses(ctx, clusterID) } } // PollNodePoolAdapterStatuses returns a polling function for nodepool adapter status checks. -func (h *Helper) PollNodePoolAdapterStatuses(ctx context.Context, clusterID, npID string) func() (*openapi.AdapterStatusList, error) { - return func() (*openapi.AdapterStatusList, error) { +func (h *Helper) PollNodePoolAdapterStatuses(ctx context.Context, clusterID, npID string) func() (*core.AdapterStatusList, error) { + return func() (*core.AdapterStatusList, error) { return h.Client.GetNodePoolStatuses(ctx, clusterID, npID) } } diff --git a/pkg/helper/validation.go b/pkg/helper/validation.go index 90536eb..6ee9970 100644 --- a/pkg/helper/validation.go +++ b/pkg/helper/validation.go @@ -3,11 +3,12 @@ package helper import ( "strings" + "github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/api/core" "github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/api/openapi" ) // HasAdapterCondition checks if an adapter condition with the given type and status exists in the conditions list -func (h *Helper) HasAdapterCondition(conditions []openapi.AdapterCondition, condType string, status openapi.AdapterConditionStatus) bool { +func (h *Helper) HasAdapterCondition(conditions []core.AdapterCondition, condType string, status core.AdapterConditionStatus) bool { return hasAdapterCond(conditions, condType, status) } @@ -22,7 +23,7 @@ func (h *Helper) HasResourceCondition(conditions []openapi.ResourceCondition, co } // GetCondition retrieves a condition by type from the conditions list -func (h *Helper) GetCondition(conditions []openapi.AdapterCondition, condType string) *openapi.AdapterCondition { +func (h *Helper) GetCondition(conditions []core.AdapterCondition, condType string) *core.AdapterCondition { for i := range conditions { if conditions[i].Type == condType { return &conditions[i] @@ -32,9 +33,9 @@ func (h *Helper) GetCondition(conditions []openapi.AdapterCondition, condType st } // AllConditionsTrue checks if all specified condition types have status True -func (h *Helper) AllConditionsTrue(conditions []openapi.AdapterCondition, condTypes []string) bool { +func (h *Helper) AllConditionsTrue(conditions []core.AdapterCondition, condTypes []string) bool { for _, condType := range condTypes { - if !h.HasAdapterCondition(conditions, condType, openapi.AdapterConditionStatusTrue) { + if !h.HasAdapterCondition(conditions, condType, core.AdapterConditionStatusTrue) { return false } } @@ -42,9 +43,9 @@ func (h *Helper) AllConditionsTrue(conditions []openapi.AdapterCondition, condTy } // AnyConditionFalse checks if any of the specified condition types have status False -func (h *Helper) AnyConditionFalse(conditions []openapi.AdapterCondition, condTypes []string) bool { +func (h *Helper) AnyConditionFalse(conditions []core.AdapterCondition, condTypes []string) bool { for _, condType := range condTypes { - if h.HasAdapterCondition(conditions, condType, openapi.AdapterConditionStatusFalse) { + if h.HasAdapterCondition(conditions, condType, core.AdapterConditionStatusFalse) { return true } } diff --git a/testdata/payloads/clusters/cluster-patch.json b/testdata/payloads/clusters/cluster-patch.json index a5e8c40..83de9d9 100644 --- a/testdata/payloads/clusters/cluster-patch.json +++ b/testdata/payloads/clusters/cluster-patch.json @@ -1,8 +1,8 @@ { "spec": { "platform": { - "type": "gcp", - "gcp": { + "type": "template", + "template": { "projectID": "my-gcp-project", "region": "us-central1", "zone": "us-central1-a", diff --git a/testdata/payloads/clusters/cluster-request-large.json b/testdata/payloads/clusters/cluster-request-large.json index 03d4703..add0941 100644 --- a/testdata/payloads/clusters/cluster-request-large.json +++ b/testdata/payloads/clusters/cluster-request-large.json @@ -55,8 +55,8 @@ }, "spec": { "platform": { - "type": "gcp", - "gcp": { + "type": "template", + "template": { "projectID": "my-gcp-project", "region": "us-central1", "zone": "us-central1-a", diff --git a/testdata/payloads/clusters/cluster-request-small.json b/testdata/payloads/clusters/cluster-request-small.json index 3d09061..105fb2a 100644 --- a/testdata/payloads/clusters/cluster-request-small.json +++ b/testdata/payloads/clusters/cluster-request-small.json @@ -6,7 +6,7 @@ }, "spec": { "platform": { - "type": "gcp" + "type": "template" } } } diff --git a/testdata/payloads/clusters/cluster-request.json b/testdata/payloads/clusters/cluster-request.json index e5ac681..cbbc96b 100644 --- a/testdata/payloads/clusters/cluster-request.json +++ b/testdata/payloads/clusters/cluster-request.json @@ -8,8 +8,8 @@ }, "spec": { "platform": { - "type": "gcp", - "gcp": { + "type": "template", + "template": { "projectID": "my-gcp-project", "region": "us-central1", "zone": "us-central1-a", diff --git a/testdata/payloads/nodepools/nodepool-patch.json b/testdata/payloads/nodepools/nodepool-patch.json index c114ebc..14f6e49 100644 --- a/testdata/payloads/nodepools/nodepool-patch.json +++ b/testdata/payloads/nodepools/nodepool-patch.json @@ -1,10 +1,13 @@ { "spec": { - "replicas": 3, - "machineType": "n1-standard-16", - "labels": { - "node-role": "worker", - "gpu-enabled": "true" + "nodeCount": 3, + "platform": { + "type": "template", + "template": { + "machineType": "n1-standard-16", + "diskType": "pd-ssd", + "diskSizeGb": 200 + } } } }