From 35aae67931582ebb95f87099dccc65b12a076c8b Mon Sep 17 00:00:00 2001 From: Winston Chen Date: Mon, 20 Jul 2026 10:22:02 +0100 Subject: [PATCH 01/13] Scaffolding for the Limit controller go run ./cmd/scaffold-controller -interactive=false \ -kind=Limit \ -gophercloud-client=NewIdentityV3 \ -gophercloud-module=github.com/gophercloud/gophercloud/v2/openstack/identity/v3/limits \ -gophercloud-type=Limit \ -openstack-json-object=limit \ -available-polling-period=0 \ -deleting-polling-period=0 \ -required-create-dependency=Service \ -optional-create-dependency=Project \ -optional-create-dependency=Domain \ -import-dependency=Service \ -import-dependency=Project \ -import-dependency=Domain --- api/v1alpha1/limit_types.go | 116 +++++++ config/samples/openstack_v1alpha1_limit.yaml | 14 + internal/controllers/limit/actuator.go | 306 ++++++++++++++++++ internal/controllers/limit/actuator_test.go | 119 +++++++ internal/controllers/limit/controller.go | 204 ++++++++++++ internal/controllers/limit/status.go | 66 ++++ .../tests/limit-create-full/00-assert.yaml | 43 +++ .../limit-create-full/00-create-resource.yaml | 57 ++++ .../tests/limit-create-full/00-secret.yaml | 6 + .../limit/tests/limit-create-full/README.md | 11 + .../tests/limit-create-minimal/00-assert.yaml | 32 ++ .../00-create-resource.yaml | 28 ++ .../tests/limit-create-minimal/00-secret.yaml | 6 + .../tests/limit-create-minimal/01-assert.yaml | 11 + .../01-delete-secret.yaml | 7 + .../tests/limit-create-minimal/README.md | 15 + .../tests/limit-dependency/00-assert.yaml | 60 ++++ .../00-create-resources-missing-deps.yaml | 70 ++++ .../tests/limit-dependency/00-secret.yaml | 6 + .../tests/limit-dependency/01-assert.yaml | 60 ++++ .../01-create-dependencies.yaml | 45 +++ .../tests/limit-dependency/02-assert.yaml | 29 ++ .../02-delete-dependencies.yaml | 13 + .../tests/limit-dependency/03-assert.yaml | 13 + .../limit-dependency/03-delete-resources.yaml | 16 + .../limit/tests/limit-dependency/README.md | 21 ++ .../limit-import-dependency/00-assert.yaml | 21 ++ .../00-import-resource.yaml | 54 ++++ .../limit-import-dependency/00-secret.yaml | 6 + .../limit-import-dependency/01-assert.yaml | 36 +++ .../01-create-trap-resource.yaml | 56 ++++ .../limit-import-dependency/02-assert.yaml | 44 +++ .../02-create-resource.yaml | 55 ++++ .../limit-import-dependency/03-assert.yaml | 10 + .../03-delete-import-dependencies.yaml | 11 + .../limit-import-dependency/04-assert.yaml | 6 + .../04-delete-resource.yaml | 7 + .../tests/limit-import-dependency/README.md | 29 ++ .../tests/limit-import-error/00-assert.yaml | 30 ++ .../00-create-resources.yaml | 43 +++ .../tests/limit-import-error/00-secret.yaml | 6 + .../tests/limit-import-error/01-assert.yaml | 15 + .../01-import-resource.yaml | 13 + .../limit/tests/limit-import-error/README.md | 13 + .../limit/tests/limit-import/00-assert.yaml | 15 + .../limit-import/00-import-resource.yaml | 15 + .../limit/tests/limit-import/00-secret.yaml | 6 + .../limit/tests/limit-import/01-assert.yaml | 34 ++ .../limit-import/01-create-trap-resource.yaml | 31 ++ .../limit/tests/limit-import/02-assert.yaml | 33 ++ .../limit-import/02-create-resource.yaml | 28 ++ .../limit/tests/limit-import/README.md | 18 ++ .../limit/tests/limit-update/00-assert.yaml | 26 ++ .../limit-update/00-minimal-resource.yaml | 28 ++ .../limit/tests/limit-update/00-secret.yaml | 6 + .../limit/tests/limit-update/01-assert.yaml | 17 + .../limit-update/01-updated-resource.yaml | 10 + .../limit/tests/limit-update/02-assert.yaml | 26 ++ .../limit-update/02-reverted-resource.yaml | 7 + .../limit/tests/limit-update/README.md | 17 + internal/osclients/limit.go | 104 ++++++ test/apivalidations/limit_test.go | 152 +++++++++ 62 files changed, 2402 insertions(+) create mode 100644 api/v1alpha1/limit_types.go create mode 100644 config/samples/openstack_v1alpha1_limit.yaml create mode 100644 internal/controllers/limit/actuator.go create mode 100644 internal/controllers/limit/actuator_test.go create mode 100644 internal/controllers/limit/controller.go create mode 100644 internal/controllers/limit/status.go create mode 100644 internal/controllers/limit/tests/limit-create-full/00-assert.yaml create mode 100644 internal/controllers/limit/tests/limit-create-full/00-create-resource.yaml create mode 100644 internal/controllers/limit/tests/limit-create-full/00-secret.yaml create mode 100644 internal/controllers/limit/tests/limit-create-full/README.md create mode 100644 internal/controllers/limit/tests/limit-create-minimal/00-assert.yaml create mode 100644 internal/controllers/limit/tests/limit-create-minimal/00-create-resource.yaml create mode 100644 internal/controllers/limit/tests/limit-create-minimal/00-secret.yaml create mode 100644 internal/controllers/limit/tests/limit-create-minimal/01-assert.yaml create mode 100644 internal/controllers/limit/tests/limit-create-minimal/01-delete-secret.yaml create mode 100644 internal/controllers/limit/tests/limit-create-minimal/README.md create mode 100644 internal/controllers/limit/tests/limit-dependency/00-assert.yaml create mode 100644 internal/controllers/limit/tests/limit-dependency/00-create-resources-missing-deps.yaml create mode 100644 internal/controllers/limit/tests/limit-dependency/00-secret.yaml create mode 100644 internal/controllers/limit/tests/limit-dependency/01-assert.yaml create mode 100644 internal/controllers/limit/tests/limit-dependency/01-create-dependencies.yaml create mode 100644 internal/controllers/limit/tests/limit-dependency/02-assert.yaml create mode 100644 internal/controllers/limit/tests/limit-dependency/02-delete-dependencies.yaml create mode 100644 internal/controllers/limit/tests/limit-dependency/03-assert.yaml create mode 100644 internal/controllers/limit/tests/limit-dependency/03-delete-resources.yaml create mode 100644 internal/controllers/limit/tests/limit-dependency/README.md create mode 100644 internal/controllers/limit/tests/limit-import-dependency/00-assert.yaml create mode 100644 internal/controllers/limit/tests/limit-import-dependency/00-import-resource.yaml create mode 100644 internal/controllers/limit/tests/limit-import-dependency/00-secret.yaml create mode 100644 internal/controllers/limit/tests/limit-import-dependency/01-assert.yaml create mode 100644 internal/controllers/limit/tests/limit-import-dependency/01-create-trap-resource.yaml create mode 100644 internal/controllers/limit/tests/limit-import-dependency/02-assert.yaml create mode 100644 internal/controllers/limit/tests/limit-import-dependency/02-create-resource.yaml create mode 100644 internal/controllers/limit/tests/limit-import-dependency/03-assert.yaml create mode 100644 internal/controllers/limit/tests/limit-import-dependency/03-delete-import-dependencies.yaml create mode 100644 internal/controllers/limit/tests/limit-import-dependency/04-assert.yaml create mode 100644 internal/controllers/limit/tests/limit-import-dependency/04-delete-resource.yaml create mode 100644 internal/controllers/limit/tests/limit-import-dependency/README.md create mode 100644 internal/controllers/limit/tests/limit-import-error/00-assert.yaml create mode 100644 internal/controllers/limit/tests/limit-import-error/00-create-resources.yaml create mode 100644 internal/controllers/limit/tests/limit-import-error/00-secret.yaml create mode 100644 internal/controllers/limit/tests/limit-import-error/01-assert.yaml create mode 100644 internal/controllers/limit/tests/limit-import-error/01-import-resource.yaml create mode 100644 internal/controllers/limit/tests/limit-import-error/README.md create mode 100644 internal/controllers/limit/tests/limit-import/00-assert.yaml create mode 100644 internal/controllers/limit/tests/limit-import/00-import-resource.yaml create mode 100644 internal/controllers/limit/tests/limit-import/00-secret.yaml create mode 100644 internal/controllers/limit/tests/limit-import/01-assert.yaml create mode 100644 internal/controllers/limit/tests/limit-import/01-create-trap-resource.yaml create mode 100644 internal/controllers/limit/tests/limit-import/02-assert.yaml create mode 100644 internal/controllers/limit/tests/limit-import/02-create-resource.yaml create mode 100644 internal/controllers/limit/tests/limit-import/README.md create mode 100644 internal/controllers/limit/tests/limit-update/00-assert.yaml create mode 100644 internal/controllers/limit/tests/limit-update/00-minimal-resource.yaml create mode 100644 internal/controllers/limit/tests/limit-update/00-secret.yaml create mode 100644 internal/controllers/limit/tests/limit-update/01-assert.yaml create mode 100644 internal/controllers/limit/tests/limit-update/01-updated-resource.yaml create mode 100644 internal/controllers/limit/tests/limit-update/02-assert.yaml create mode 100644 internal/controllers/limit/tests/limit-update/02-reverted-resource.yaml create mode 100644 internal/controllers/limit/tests/limit-update/README.md create mode 100644 internal/osclients/limit.go create mode 100644 test/apivalidations/limit_test.go diff --git a/api/v1alpha1/limit_types.go b/api/v1alpha1/limit_types.go new file mode 100644 index 000000000..7d742d429 --- /dev/null +++ b/api/v1alpha1/limit_types.go @@ -0,0 +1,116 @@ +/* +Copyright The ORC Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1alpha1 + +// LimitResourceSpec contains the desired state of the resource. +type LimitResourceSpec struct { + // name will be the name of the created resource. If not specified, the + // name of the ORC object will be used. + // +optional + Name *OpenStackName `json:"name,omitempty"` + + // description is a human-readable description for the resource. + // +kubebuilder:validation:MinLength:=1 + // +kubebuilder:validation:MaxLength:=255 + // +optional + Description *string `json:"description,omitempty"` + + // serviceRef is a reference to the ORC Service which this resource is associated with. + // +required + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="serviceRef is immutable" + ServiceRef KubernetesNameRef `json:"serviceRef,omitempty"` + + // projectRef is a reference to the ORC Project which this resource is associated with. + // +optional + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="projectRef is immutable" + ProjectRef *KubernetesNameRef `json:"projectRef,omitempty"` + + // domainRef is a reference to the ORC Domain which this resource is associated with. + // +optional + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="domainRef is immutable" + DomainRef *KubernetesNameRef `json:"domainRef,omitempty"` + + // TODO(scaffolding): Add more types. + // To see what is supported, you can take inspiration from the CreateOpts structure from + // github.com/gophercloud/gophercloud/v2/openstack/identity/v3/limits + // + // Until you have implemented mutability for the field, you must add a CEL validation + // preventing the field being modified: + // `// +kubebuilder:validation:XValidation:rule="self == oldSelf",message=" is immutable"` +} + +// LimitFilter defines an existing resource by its properties +// +kubebuilder:validation:MinProperties:=1 +type LimitFilter struct { + // name of the existing resource + // +optional + Name *OpenStackName `json:"name,omitempty"` + + // description of the existing resource + // +kubebuilder:validation:MinLength:=1 + // +kubebuilder:validation:MaxLength:=255 + // +optional + Description *string `json:"description,omitempty"` + + // serviceRef is a reference to the ORC Service which this resource is associated with. + // +optional + ServiceRef *KubernetesNameRef `json:"serviceRef,omitempty"` + + // projectRef is a reference to the ORC Project which this resource is associated with. + // +optional + ProjectRef *KubernetesNameRef `json:"projectRef,omitempty"` + + // domainRef is a reference to the ORC Domain which this resource is associated with. + // +optional + DomainRef *KubernetesNameRef `json:"domainRef,omitempty"` + + // TODO(scaffolding): Add more types. + // To see what is supported, you can take inspiration from the ListOpts structure from + // github.com/gophercloud/gophercloud/v2/openstack/identity/v3/limits +} + +// LimitResourceStatus represents the observed state of the resource. +type LimitResourceStatus struct { + // name is a Human-readable name for the resource. Might not be unique. + // +kubebuilder:validation:MaxLength=1024 + // +optional + Name string `json:"name,omitempty"` + + // description is a human-readable description for the resource. + // +kubebuilder:validation:MaxLength=1024 + // +optional + Description string `json:"description,omitempty"` + + // serviceID is the ID of the Service to which the resource is associated. + // +kubebuilder:validation:MaxLength=1024 + // +optional + ServiceID string `json:"serviceID,omitempty"` + + // projectID is the ID of the Project to which the resource is associated. + // +kubebuilder:validation:MaxLength=1024 + // +optional + ProjectID string `json:"projectID,omitempty"` + + // domainID is the ID of the Domain to which the resource is associated. + // +kubebuilder:validation:MaxLength=1024 + // +optional + DomainID string `json:"domainID,omitempty"` + + // TODO(scaffolding): Add more types. + // To see what is supported, you can take inspiration from the Limit structure from + // github.com/gophercloud/gophercloud/v2/openstack/identity/v3/limits +} diff --git a/config/samples/openstack_v1alpha1_limit.yaml b/config/samples/openstack_v1alpha1_limit.yaml new file mode 100644 index 000000000..de5c7bde6 --- /dev/null +++ b/config/samples/openstack_v1alpha1_limit.yaml @@ -0,0 +1,14 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Limit +metadata: + name: limit-sample +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + description: Sample Limit + # TODO(scaffolding): Add all fields the resource supports diff --git a/internal/controllers/limit/actuator.go b/internal/controllers/limit/actuator.go new file mode 100644 index 000000000..ad46e5245 --- /dev/null +++ b/internal/controllers/limit/actuator.go @@ -0,0 +1,306 @@ +/* +Copyright The ORC Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package limit + +import ( + "context" + "iter" + + "github.com/gophercloud/gophercloud/v2/openstack/identity/v3/limits" + corev1 "k8s.io/api/core/v1" + "k8s.io/utils/ptr" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + + orcv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" + "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/interfaces" + "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/progress" + "github.com/k-orc/openstack-resource-controller/v2/internal/logging" + "github.com/k-orc/openstack-resource-controller/v2/internal/osclients" + "github.com/k-orc/openstack-resource-controller/v2/internal/util/dependency" + orcerrors "github.com/k-orc/openstack-resource-controller/v2/internal/util/errors" +) + +// OpenStack resource types +type ( + osResourceT = limits.Limit + + createResourceActuator = interfaces.CreateResourceActuator[orcObjectPT, orcObjectT, filterT, osResourceT] + deleteResourceActuator = interfaces.DeleteResourceActuator[orcObjectPT, orcObjectT, osResourceT] + resourceReconciler = interfaces.ResourceReconciler[orcObjectPT, osResourceT] + helperFactory = interfaces.ResourceHelperFactory[orcObjectPT, orcObjectT, resourceSpecT, filterT, osResourceT] +) + +type limitActuator struct { + osClient osclients.LimitClient + k8sClient client.Client +} + +var _ createResourceActuator = limitActuator{} +var _ deleteResourceActuator = limitActuator{} + +func (limitActuator) GetResourceID(osResource *osResourceT) string { + return osResource.ID +} + +func (actuator limitActuator) GetOSResourceByID(ctx context.Context, id string) (*osResourceT, progress.ReconcileStatus) { + resource, err := actuator.osClient.GetLimit(ctx, id) + if err != nil { + return nil, progress.WrapError(err) + } + return resource, nil +} + +func (actuator limitActuator) ListOSResourcesForAdoption(ctx context.Context, orcObject orcObjectPT) (iter.Seq2[*osResourceT, error], bool) { + resourceSpec := orcObject.Spec.Resource + if resourceSpec == nil { + return nil, false + } + + // TODO(scaffolding) If you need to filter resources on fields that the List() function + // of gophercloud does not support, it's possible to perform client-side filtering. + // Check osclients.ResourceFilter + + listOpts := limits.ListOpts{ + Name: getResourceName(orcObject), + Description: ptr.Deref(resourceSpec.Description, ""), + } + + return actuator.osClient.ListLimits(ctx, listOpts), true +} + +func (actuator limitActuator) ListOSResourcesForImport(ctx context.Context, obj orcObjectPT, filter filterT) (iter.Seq2[*osResourceT, error], progress.ReconcileStatus) { + // TODO(scaffolding) If you need to filter resources on fields that the List() function + // of gophercloud does not support, it's possible to perform client-side filtering. + // Check osclients.ResourceFilter + var reconcileStatus progress.ReconcileStatus + + service, rs := dependency.FetchDependency[*orcv1alpha1.Service]( + ctx, actuator.k8sClient, obj.Namespace, + filter.ServiceRef, "Service", + orcv1alpha1.IsAvailable, + ) + reconcileStatus = reconcileStatus.WithReconcileStatus(rs) + + project, rs := dependency.FetchDependency[*orcv1alpha1.Project]( + ctx, actuator.k8sClient, obj.Namespace, + filter.ProjectRef, "Project", + orcv1alpha1.IsAvailable, + ) + reconcileStatus = reconcileStatus.WithReconcileStatus(rs) + + domain, rs := dependency.FetchDependency[*orcv1alpha1.Domain]( + ctx, actuator.k8sClient, obj.Namespace, + filter.DomainRef, "Domain", + orcv1alpha1.IsAvailable, + ) + reconcileStatus = reconcileStatus.WithReconcileStatus(rs) + + if needsReschedule, _ := reconcileStatus.NeedsReschedule(); needsReschedule { + return nil, reconcileStatus + } + + listOpts := limits.ListOpts{ + Name: string(ptr.Deref(filter.Name, "")), + Description: string(ptr.Deref(filter.Description, "")), + ServiceID: ptr.Deref(service.Status.ID, ""), + ProjectID: ptr.Deref(project.Status.ID, ""), + DomainID: ptr.Deref(domain.Status.ID, ""), + // TODO(scaffolding): Add more import filters + } + + return actuator.osClient.ListLimits(ctx, listOpts), reconcileStatus +} + +func (actuator limitActuator) CreateResource(ctx context.Context, obj orcObjectPT) (*osResourceT, progress.ReconcileStatus) { + resource := obj.Spec.Resource + + if resource == nil { + // Should have been caught by API validation + return nil, progress.WrapError( + orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "Creation requested, but spec.resource is not set")) + } + var reconcileStatus progress.ReconcileStatus + + var serviceID string + service, serviceDepRS := serviceDependency.GetDependency( + ctx, actuator.k8sClient, obj, orcv1alpha1.IsAvailable, + ) + reconcileStatus = reconcileStatus.WithReconcileStatus(serviceDepRS) + if service != nil { + serviceID = ptr.Deref(service.Status.ID, "") + } + + var projectID string + if resource.ProjectRef != nil { + project, projectDepRS := projectDependency.GetDependency( + ctx, actuator.k8sClient, obj, orcv1alpha1.IsAvailable, + ) + reconcileStatus = reconcileStatus.WithReconcileStatus(projectDepRS) + if project != nil { + projectID = ptr.Deref(project.Status.ID, "") + } + } + + var domainID string + if resource.DomainRef != nil { + domain, domainDepRS := domainDependency.GetDependency( + ctx, actuator.k8sClient, obj, orcv1alpha1.IsAvailable, + ) + reconcileStatus = reconcileStatus.WithReconcileStatus(domainDepRS) + if domain != nil { + domainID = ptr.Deref(domain.Status.ID, "") + } + } + if needsReschedule, _ := reconcileStatus.NeedsReschedule(); needsReschedule { + return nil, reconcileStatus + } + createOpts := limits.CreateOpts{ + Name: getResourceName(obj), + Description: ptr.Deref(resource.Description, ""), + ServiceID: serviceID, + ProjectID: projectID, + DomainID: domainID, + // TODO(scaffolding): Add more fields + } + + osResource, err := actuator.osClient.CreateLimit(ctx, createOpts) + if err != nil { + if !orcerrors.IsRetryable(err) { + err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration creating resource: "+err.Error(), err) + } + return nil, progress.WrapError(err) + } + + return osResource, nil +} + +func (actuator limitActuator) DeleteResource(ctx context.Context, _ orcObjectPT, resource *osResourceT) progress.ReconcileStatus { + return progress.WrapError(actuator.osClient.DeleteLimit(ctx, resource.ID)) +} + +func (actuator limitActuator) updateResource(ctx context.Context, obj orcObjectPT, osResource *osResourceT) progress.ReconcileStatus { + log := ctrl.LoggerFrom(ctx) + resource := obj.Spec.Resource + if resource == nil { + // Should have been caught by API validation + return progress.WrapError( + orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "Update requested, but spec.resource is not set")) + } + + updateOpts := limits.UpdateOpts{} + + handleNameUpdate(&updateOpts, obj, osResource) + handleDescriptionUpdate(&updateOpts, resource, osResource) + + // TODO(scaffolding): add handler for all fields supporting mutability + + needsUpdate, err := needsUpdate(updateOpts) + if err != nil { + return progress.WrapError( + orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration updating resource: "+err.Error(), err)) + } + if !needsUpdate { + log.V(logging.Debug).Info("No changes") + return nil + } + + _, err = actuator.osClient.UpdateLimit(ctx, osResource.ID, updateOpts) + + if err != nil { + if !orcerrors.IsRetryable(err) { + err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration updating resource: "+err.Error(), err) + } + return progress.WrapError(err) + } + + return progress.NeedsRefresh() +} + +func needsUpdate(updateOpts limits.UpdateOpts) (bool, error) { + updateOptsMap, err := updateOpts.ToLimitUpdateMap() + if err != nil { + return false, err + } + + updateMap, ok := updateOptsMap["limit"].(map[string]any) + if !ok { + updateMap = make(map[string]any) + } + + return len(updateMap) > 0, nil +} + +func handleNameUpdate(updateOpts *limits.UpdateOpts, obj orcObjectPT, osResource *osResourceT) { + name := getResourceName(obj) + if osResource.Name != name { + updateOpts.Name = &name + } +} + +func handleDescriptionUpdate(updateOpts *limits.UpdateOpts, resource *resourceSpecT, osResource *osResourceT) { + description := ptr.Deref(resource.Description, "") + if osResource.Description != description { + updateOpts.Description = &description + } +} + +func (actuator limitActuator) GetResourceReconcilers(ctx context.Context, orcObject orcObjectPT, osResource *osResourceT, controller interfaces.ResourceController) ([]resourceReconciler, progress.ReconcileStatus) { + return []resourceReconciler{ + actuator.updateResource, + }, nil +} + +type limitHelperFactory struct{} + +var _ helperFactory = limitHelperFactory{} + +func newActuator(ctx context.Context, orcObject *orcv1alpha1.Limit, controller interfaces.ResourceController) (limitActuator, progress.ReconcileStatus) { + log := ctrl.LoggerFrom(ctx) + + // Ensure credential secrets exist and have our finalizer + _, reconcileStatus := credentialsDependency.GetDependencies(ctx, controller.GetK8sClient(), orcObject, func(*corev1.Secret) bool { return true }) + if needsReschedule, _ := reconcileStatus.NeedsReschedule(); needsReschedule { + return limitActuator{}, reconcileStatus + } + + clientScope, err := controller.GetScopeFactory().NewClientScopeFromObject(ctx, controller.GetK8sClient(), log, orcObject) + if err != nil { + return limitActuator{}, progress.WrapError(err) + } + osClient, err := clientScope.NewLimitClient() + if err != nil { + return limitActuator{}, progress.WrapError(err) + } + + return limitActuator{ + osClient: osClient, + k8sClient: controller.GetK8sClient(), + }, nil +} + +func (limitHelperFactory) NewAPIObjectAdapter(obj orcObjectPT) adapterI { + return limitAdapter{obj} +} + +func (limitHelperFactory) NewCreateActuator(ctx context.Context, orcObject orcObjectPT, controller interfaces.ResourceController) (createResourceActuator, progress.ReconcileStatus) { + return newActuator(ctx, orcObject, controller) +} + +func (limitHelperFactory) NewDeleteActuator(ctx context.Context, orcObject orcObjectPT, controller interfaces.ResourceController) (deleteResourceActuator, progress.ReconcileStatus) { + return newActuator(ctx, orcObject, controller) +} diff --git a/internal/controllers/limit/actuator_test.go b/internal/controllers/limit/actuator_test.go new file mode 100644 index 000000000..c303fd834 --- /dev/null +++ b/internal/controllers/limit/actuator_test.go @@ -0,0 +1,119 @@ +/* +Copyright The ORC Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package limit + +import ( + "testing" + + "github.com/gophercloud/gophercloud/v2/openstack/identity/v3/limits" + orcv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" + "k8s.io/utils/ptr" +) + +func TestNeedsUpdate(t *testing.T) { + testCases := []struct { + name string + updateOpts limits.UpdateOpts + expectChange bool + }{ + { + name: "Empty base opts", + updateOpts: limits.UpdateOpts{}, + expectChange: false, + }, + { + name: "Updated opts", + updateOpts: limits.UpdateOpts{Name: ptr.To("updated")}, + expectChange: true, + }, + } + + for _, tt := range testCases { + t.Run(tt.name, func(t *testing.T) { + got, _ := needsUpdate(tt.updateOpts) + if got != tt.expectChange { + t.Errorf("Expected change: %v, got: %v", tt.expectChange, got) + } + }) + } +} + +func TestHandleNameUpdate(t *testing.T) { + ptrToName := ptr.To[orcv1alpha1.OpenStackName] + testCases := []struct { + name string + newValue *orcv1alpha1.OpenStackName + existingValue string + expectChange bool + }{ + {name: "Identical", newValue: ptrToName("name"), existingValue: "name", expectChange: false}, + {name: "Different", newValue: ptrToName("new-name"), existingValue: "name", expectChange: true}, + {name: "No value provided, existing is identical to object name", newValue: nil, existingValue: "object-name", expectChange: false}, + {name: "No value provided, existing is different from object name", newValue: nil, existingValue: "different-from-object-name", expectChange: true}, + } + + for _, tt := range testCases { + t.Run(tt.name, func(t *testing.T) { + resource := &orcv1alpha1.Limit{} + resource.Name = "object-name" + resource.Spec = orcv1alpha1.LimitSpec{ + Resource: &orcv1alpha1.LimitResourceSpec{Name: tt.newValue}, + } + osResource := &osResourceT{Name: tt.existingValue} + + updateOpts := limits.UpdateOpts{} + handleNameUpdate(&updateOpts, resource, osResource) + + got, _ := needsUpdate(updateOpts) + if got != tt.expectChange { + t.Errorf("Expected change: %v, got: %v", tt.expectChange, got) + } + }) + + } +} + +func TestHandleDescriptionUpdate(t *testing.T) { + ptrToDescription := ptr.To[string] + testCases := []struct { + name string + newValue *string + existingValue string + expectChange bool + }{ + {name: "Identical", newValue: ptrToDescription("desc"), existingValue: "desc", expectChange: false}, + {name: "Different", newValue: ptrToDescription("new-desc"), existingValue: "desc", expectChange: true}, + {name: "No value provided, existing is set", newValue: nil, existingValue: "desc", expectChange: true}, + {name: "No value provided, existing is empty", newValue: nil, existingValue: "", expectChange: false}, + } + + for _, tt := range testCases { + t.Run(tt.name, func(t *testing.T) { + resource := &orcv1alpha1.LimitResourceSpec{Description: tt.newValue} + osResource := &osResourceT{Description: tt.existingValue} + + updateOpts := limits.UpdateOpts{} + handleDescriptionUpdate(&updateOpts, resource, osResource) + + got, _ := needsUpdate(updateOpts) + if got != tt.expectChange { + t.Errorf("Expected change: %v, got: %v", tt.expectChange, got) + } + }) + + } +} diff --git a/internal/controllers/limit/controller.go b/internal/controllers/limit/controller.go new file mode 100644 index 000000000..a26b263e6 --- /dev/null +++ b/internal/controllers/limit/controller.go @@ -0,0 +1,204 @@ +/* +Copyright The ORC Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package limit + +import ( + "context" + "errors" + "time" + + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" + "sigs.k8s.io/controller-runtime/pkg/controller" + + orcv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" + + "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/interfaces" + "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/reconciler" + "github.com/k-orc/openstack-resource-controller/v2/internal/scope" + "github.com/k-orc/openstack-resource-controller/v2/internal/util/credentials" + "github.com/k-orc/openstack-resource-controller/v2/internal/util/dependency" + "github.com/k-orc/openstack-resource-controller/v2/pkg/predicates" +) + +const controllerName = "limit" + +// +kubebuilder:rbac:groups=openstack.k-orc.cloud,resources=limits,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=openstack.k-orc.cloud,resources=limits/status,verbs=get;update;patch + +type limitReconcilerConstructor struct { + scopeFactory scope.Factory + defaultResyncPeriod time.Duration +} + +func New(scopeFactory scope.Factory) interfaces.Controller { + return &limitReconcilerConstructor{scopeFactory: scopeFactory} +} + +func (limitReconcilerConstructor) GetName() string { + return controllerName +} + +func (c *limitReconcilerConstructor) SetDefaultResyncPeriod(d time.Duration) { + c.defaultResyncPeriod = d +} + +var serviceDependency = dependency.NewDeletionGuardDependency[*orcv1alpha1.LimitList, *orcv1alpha1.Service]( + "spec.resource.serviceRef", + func(limit *orcv1alpha1.Limit) []string { + resource := limit.Spec.Resource + if resource == nil { + return nil + } + return []string{string(resource.ServiceRef)} + }, + finalizer, externalObjectFieldOwner, +) + +var projectDependency = dependency.NewDeletionGuardDependency[*orcv1alpha1.LimitList, *orcv1alpha1.Project]( + "spec.resource.projectRef", + func(limit *orcv1alpha1.Limit) []string { + resource := limit.Spec.Resource + if resource == nil || resource.ProjectRef == nil { + return nil + } + return []string{string(*resource.ProjectRef)} + }, + finalizer, externalObjectFieldOwner, +) + +var domainDependency = dependency.NewDeletionGuardDependency[*orcv1alpha1.LimitList, *orcv1alpha1.Domain]( + "spec.resource.domainRef", + func(limit *orcv1alpha1.Limit) []string { + resource := limit.Spec.Resource + if resource == nil || resource.DomainRef == nil { + return nil + } + return []string{string(*resource.DomainRef)} + }, + finalizer, externalObjectFieldOwner, +) + +var serviceImportDependency = dependency.NewDependency[*orcv1alpha1.LimitList, *orcv1alpha1.Service]( + "spec.import.filter.serviceRef", + func(limit *orcv1alpha1.Limit) []string { + resource := limit.Spec.Import + if resource == nil || resource.Filter == nil || resource.Filter.ServiceRef == nil { + return nil + } + return []string{string(*resource.Filter.ServiceRef)} + }, +) + +var projectImportDependency = dependency.NewDependency[*orcv1alpha1.LimitList, *orcv1alpha1.Project]( + "spec.import.filter.projectRef", + func(limit *orcv1alpha1.Limit) []string { + resource := limit.Spec.Import + if resource == nil || resource.Filter == nil || resource.Filter.ProjectRef == nil { + return nil + } + return []string{string(*resource.Filter.ProjectRef)} + }, +) + +var domainImportDependency = dependency.NewDependency[*orcv1alpha1.LimitList, *orcv1alpha1.Domain]( + "spec.import.filter.domainRef", + func(limit *orcv1alpha1.Limit) []string { + resource := limit.Spec.Import + if resource == nil || resource.Filter == nil || resource.Filter.DomainRef == nil { + return nil + } + return []string{string(*resource.Filter.DomainRef)} + }, +) + +// SetupWithManager sets up the controller with the Manager. +func (c *limitReconcilerConstructor) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error { + log := ctrl.LoggerFrom(ctx) + k8sClient := mgr.GetClient() + + serviceWatchEventHandler, err := serviceDependency.WatchEventHandler(log, k8sClient) + if err != nil { + return err + } + + projectWatchEventHandler, err := projectDependency.WatchEventHandler(log, k8sClient) + if err != nil { + return err + } + + domainWatchEventHandler, err := domainDependency.WatchEventHandler(log, k8sClient) + if err != nil { + return err + } + + serviceImportWatchEventHandler, err := serviceImportDependency.WatchEventHandler(log, k8sClient) + if err != nil { + return err + } + + projectImportWatchEventHandler, err := projectImportDependency.WatchEventHandler(log, k8sClient) + if err != nil { + return err + } + + domainImportWatchEventHandler, err := domainImportDependency.WatchEventHandler(log, k8sClient) + if err != nil { + return err + } + + builder := ctrl.NewControllerManagedBy(mgr). + WithOptions(options). + Watches(&orcv1alpha1.Service{}, serviceWatchEventHandler, + builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.Service{})), + ). + Watches(&orcv1alpha1.Project{}, projectWatchEventHandler, + builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.Project{})), + ). + Watches(&orcv1alpha1.Domain{}, domainWatchEventHandler, + builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.Domain{})), + ). + // A second watch is necessary because we need a different handler that omits deletion guards + Watches(&orcv1alpha1.Service{}, serviceImportWatchEventHandler, + builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.Service{})), + ). + // A second watch is necessary because we need a different handler that omits deletion guards + Watches(&orcv1alpha1.Project{}, projectImportWatchEventHandler, + builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.Project{})), + ). + // A second watch is necessary because we need a different handler that omits deletion guards + Watches(&orcv1alpha1.Domain{}, domainImportWatchEventHandler, + builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.Domain{})), + ). + For(&orcv1alpha1.Limit{}) + + if err := errors.Join( + serviceDependency.AddToManager(ctx, mgr), + projectDependency.AddToManager(ctx, mgr), + domainDependency.AddToManager(ctx, mgr), + serviceImportDependency.AddToManager(ctx, mgr), + projectImportDependency.AddToManager(ctx, mgr), + domainImportDependency.AddToManager(ctx, mgr), + credentialsDependency.AddToManager(ctx, mgr), + credentials.AddCredentialsWatch(log, mgr.GetClient(), builder, credentialsDependency), + ); err != nil { + return err + } + + r := reconciler.NewController(controllerName, mgr.GetClient(), c.scopeFactory, limitHelperFactory{}, limitStatusWriter{}, c.defaultResyncPeriod) + return builder.Complete(&r) +} diff --git a/internal/controllers/limit/status.go b/internal/controllers/limit/status.go new file mode 100644 index 000000000..e8bffbc1a --- /dev/null +++ b/internal/controllers/limit/status.go @@ -0,0 +1,66 @@ +/* +Copyright The ORC Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package limit + +import ( + "github.com/go-logr/logr" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + orcv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" + "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/interfaces" + "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/progress" + orcapplyconfigv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/pkg/clients/applyconfiguration/api/v1alpha1" +) + +type limitStatusWriter struct{} + +type objectApplyT = orcapplyconfigv1alpha1.LimitApplyConfiguration +type statusApplyT = orcapplyconfigv1alpha1.LimitStatusApplyConfiguration + +var _ interfaces.ResourceStatusWriter[*orcv1alpha1.Limit, *osResourceT, *objectApplyT, *statusApplyT] = limitStatusWriter{} + +func (limitStatusWriter) GetApplyConfig(name, namespace string) *objectApplyT { + return orcapplyconfigv1alpha1.Limit(name, namespace) +} + +func (limitStatusWriter) ResourceAvailableStatus(orcObject *orcv1alpha1.Limit, osResource *osResourceT) (metav1.ConditionStatus, progress.ReconcileStatus) { + if osResource == nil { + if orcObject.Status.ID == nil { + return metav1.ConditionFalse, nil + } else { + return metav1.ConditionUnknown, nil + } + } + return metav1.ConditionTrue, nil +} + +func (limitStatusWriter) ApplyResourceStatus(log logr.Logger, osResource *osResourceT, statusApply *statusApplyT) { + resourceStatus := orcapplyconfigv1alpha1.LimitResourceStatus(). + WithServiceID(osResource.ServiceID). + WithProjectID(osResource.ProjectID). + WithDomainID(osResource.DomainID). + WithName(osResource.Name) + + // TODO(scaffolding): add all of the fields supported in the LimitResourceStatus struct + // If a zero-value isn't expected in the response, place it behind a conditional + + if osResource.Description != "" { + resourceStatus.WithDescription(osResource.Description) + } + + statusApply.WithResource(resourceStatus) +} diff --git a/internal/controllers/limit/tests/limit-create-full/00-assert.yaml b/internal/controllers/limit/tests/limit-create-full/00-assert.yaml new file mode 100644 index 000000000..d5963f5f3 --- /dev/null +++ b/internal/controllers/limit/tests/limit-create-full/00-assert.yaml @@ -0,0 +1,43 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Limit +metadata: + name: limit-create-full +status: + resource: + name: limit-create-full-override + description: Limit from "create full" test + # TODO(scaffolding): Add all fields the resource supports + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Limit + name: limit-create-full + ref: limit + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Service + name: limit-create-full + ref: service + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Project + name: limit-create-full + ref: project + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Domain + name: limit-create-full + ref: domain +assertAll: + - celExpr: "limit.status.id != ''" + - celExpr: "limit.status.resource.serviceID == service.status.id" + - celExpr: "limit.status.resource.projectID == project.status.id" + - celExpr: "limit.status.resource.domainID == domain.status.id" + # TODO(scaffolding): Add more checks diff --git a/internal/controllers/limit/tests/limit-create-full/00-create-resource.yaml b/internal/controllers/limit/tests/limit-create-full/00-create-resource.yaml new file mode 100644 index 000000000..3cbeed847 --- /dev/null +++ b/internal/controllers/limit/tests/limit-create-full/00-create-resource.yaml @@ -0,0 +1,57 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Service +metadata: + name: limit-create-full +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Project +metadata: + name: limit-create-full +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Domain +metadata: + name: limit-create-full +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Limit +metadata: + name: limit-create-full +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + name: limit-create-full-override + description: Limit from "create full" test + serviceRef: limit-create-full + projectRef: limit-create-full + domainRef: limit-create-full + # TODO(scaffolding): Add all fields the resource supports diff --git a/internal/controllers/limit/tests/limit-create-full/00-secret.yaml b/internal/controllers/limit/tests/limit-create-full/00-secret.yaml new file mode 100644 index 000000000..045711ee7 --- /dev/null +++ b/internal/controllers/limit/tests/limit-create-full/00-secret.yaml @@ -0,0 +1,6 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + - command: kubectl create secret generic openstack-clouds --from-file=clouds.yaml=${E2E_KUTTL_OSCLOUDS} ${E2E_KUTTL_CACERT_OPT} + namespaced: true diff --git a/internal/controllers/limit/tests/limit-create-full/README.md b/internal/controllers/limit/tests/limit-create-full/README.md new file mode 100644 index 000000000..fa45d9421 --- /dev/null +++ b/internal/controllers/limit/tests/limit-create-full/README.md @@ -0,0 +1,11 @@ +# Create a Limit with all the options + +## Step 00 + +Create a Limit using all available fields, and verify that the observed state corresponds to the spec. + +Also validate that the OpenStack resource uses the name from the spec when it is specified. + +## Reference + +https://k-orc.cloud/development/writing-tests/#create-full diff --git a/internal/controllers/limit/tests/limit-create-minimal/00-assert.yaml b/internal/controllers/limit/tests/limit-create-minimal/00-assert.yaml new file mode 100644 index 000000000..3ab09bebf --- /dev/null +++ b/internal/controllers/limit/tests/limit-create-minimal/00-assert.yaml @@ -0,0 +1,32 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Limit +metadata: + name: limit-create-minimal +status: + resource: + name: limit-create-minimal + # TODO(scaffolding): Add all fields the resource supports + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Limit + name: limit-create-minimal + ref: limit + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Service + name: limit-create-minimal + ref: service +assertAll: + - celExpr: "limit.status.id != ''" + - celExpr: "limit.status.resource.serviceID == service.status.id" + # TODO(scaffolding): Add more checks diff --git a/internal/controllers/limit/tests/limit-create-minimal/00-create-resource.yaml b/internal/controllers/limit/tests/limit-create-minimal/00-create-resource.yaml new file mode 100644 index 000000000..3cd61ff30 --- /dev/null +++ b/internal/controllers/limit/tests/limit-create-minimal/00-create-resource.yaml @@ -0,0 +1,28 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Service +metadata: + name: limit-create-minimal +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Limit +metadata: + name: limit-create-minimal +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # TODO(scaffolding): Only add the mandatory fields. It's possible the resource + # doesn't have mandatory fields, in that case, leave it empty. + resource: + serviceRef: limit-create-minimal diff --git a/internal/controllers/limit/tests/limit-create-minimal/00-secret.yaml b/internal/controllers/limit/tests/limit-create-minimal/00-secret.yaml new file mode 100644 index 000000000..045711ee7 --- /dev/null +++ b/internal/controllers/limit/tests/limit-create-minimal/00-secret.yaml @@ -0,0 +1,6 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + - command: kubectl create secret generic openstack-clouds --from-file=clouds.yaml=${E2E_KUTTL_OSCLOUDS} ${E2E_KUTTL_CACERT_OPT} + namespaced: true diff --git a/internal/controllers/limit/tests/limit-create-minimal/01-assert.yaml b/internal/controllers/limit/tests/limit-create-minimal/01-assert.yaml new file mode 100644 index 000000000..78e2504e8 --- /dev/null +++ b/internal/controllers/limit/tests/limit-create-minimal/01-assert.yaml @@ -0,0 +1,11 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: v1 + kind: Secret + name: openstack-clouds + ref: secret +assertAll: + - celExpr: "secret.metadata.deletionTimestamp != 0" + - celExpr: "'openstack.k-orc.cloud/limit' in secret.metadata.finalizers" diff --git a/internal/controllers/limit/tests/limit-create-minimal/01-delete-secret.yaml b/internal/controllers/limit/tests/limit-create-minimal/01-delete-secret.yaml new file mode 100644 index 000000000..1620791b9 --- /dev/null +++ b/internal/controllers/limit/tests/limit-create-minimal/01-delete-secret.yaml @@ -0,0 +1,7 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + # We expect the deletion to hang due to the finalizer, so use --wait=false + - command: kubectl delete secret openstack-clouds --wait=false + namespaced: true diff --git a/internal/controllers/limit/tests/limit-create-minimal/README.md b/internal/controllers/limit/tests/limit-create-minimal/README.md new file mode 100644 index 000000000..4b3b12e7e --- /dev/null +++ b/internal/controllers/limit/tests/limit-create-minimal/README.md @@ -0,0 +1,15 @@ +# Create a Limit with the minimum options + +## Step 00 + +Create a minimal Limit, that sets only the required fields, and verify that the observed state corresponds to the spec. + +Also validate that the OpenStack resource uses the name of the ORC object when no name is explicitly specified. + +## Step 01 + +Try deleting the secret and ensure that it is not deleted thanks to the finalizer. + +## Reference + +https://k-orc.cloud/development/writing-tests/#create-minimal diff --git a/internal/controllers/limit/tests/limit-dependency/00-assert.yaml b/internal/controllers/limit/tests/limit-dependency/00-assert.yaml new file mode 100644 index 000000000..adc4bf209 --- /dev/null +++ b/internal/controllers/limit/tests/limit-dependency/00-assert.yaml @@ -0,0 +1,60 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Limit +metadata: + name: limit-dependency-no-secret +status: + conditions: + - type: Available + message: Waiting for Secret/limit-dependency to be created + status: "False" + reason: Progressing + - type: Progressing + message: Waiting for Secret/limit-dependency to be created + status: "True" + reason: Progressing +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Limit +metadata: + name: limit-dependency-no-service +status: + conditions: + - type: Available + message: Waiting for Service/limit-dependency-pending to be created + status: "False" + reason: Progressing + - type: Progressing + message: Waiting for Service/limit-dependency-pending to be created + status: "True" + reason: Progressing +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Limit +metadata: + name: limit-dependency-no-project +status: + conditions: + - type: Available + message: Waiting for Project/limit-dependency to be created + status: "False" + reason: Progressing + - type: Progressing + message: Waiting for Project/limit-dependency to be created + status: "True" + reason: Progressing +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Limit +metadata: + name: limit-dependency-no-domain +status: + conditions: + - type: Available + message: Waiting for Domain/limit-dependency to be created + status: "False" + reason: Progressing + - type: Progressing + message: Waiting for Domain/limit-dependency to be created + status: "True" + reason: Progressing diff --git a/internal/controllers/limit/tests/limit-dependency/00-create-resources-missing-deps.yaml b/internal/controllers/limit/tests/limit-dependency/00-create-resources-missing-deps.yaml new file mode 100644 index 000000000..a167ee743 --- /dev/null +++ b/internal/controllers/limit/tests/limit-dependency/00-create-resources-missing-deps.yaml @@ -0,0 +1,70 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Service +metadata: + name: limit-dependency +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Limit +metadata: + name: limit-dependency-no-service +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + serviceRef: limit-dependency-pending + # TODO(scaffolding): Add the necessary fields to create the resource +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Limit +metadata: + name: limit-dependency-no-project +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + serviceRef: limit-dependency + projectRef: limit-dependency + # TODO(scaffolding): Add the necessary fields to create the resource--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Limit +metadata: + name: limit-dependency-no-domain +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + serviceRef: limit-dependency + domainRef: limit-dependency + # TODO(scaffolding): Add the necessary fields to create the resource +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Limit +metadata: + name: limit-dependency-no-secret +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack + secretName: limit-dependency + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: + serviceRef: limit-dependency diff --git a/internal/controllers/limit/tests/limit-dependency/00-secret.yaml b/internal/controllers/limit/tests/limit-dependency/00-secret.yaml new file mode 100644 index 000000000..045711ee7 --- /dev/null +++ b/internal/controllers/limit/tests/limit-dependency/00-secret.yaml @@ -0,0 +1,6 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + - command: kubectl create secret generic openstack-clouds --from-file=clouds.yaml=${E2E_KUTTL_OSCLOUDS} ${E2E_KUTTL_CACERT_OPT} + namespaced: true diff --git a/internal/controllers/limit/tests/limit-dependency/01-assert.yaml b/internal/controllers/limit/tests/limit-dependency/01-assert.yaml new file mode 100644 index 000000000..a55eda7b1 --- /dev/null +++ b/internal/controllers/limit/tests/limit-dependency/01-assert.yaml @@ -0,0 +1,60 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Limit +metadata: + name: limit-dependency-no-secret +status: + conditions: + - type: Available + message: OpenStack resource is available + status: "True" + reason: Success + - type: Progressing + message: OpenStack resource is up to date + status: "False" + reason: Success +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Limit +metadata: + name: limit-dependency-no-service +status: + conditions: + - type: Available + message: OpenStack resource is available + status: "True" + reason: Success + - type: Progressing + message: OpenStack resource is up to date + status: "False" + reason: Success +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Limit +metadata: + name: limit-dependency-no-project +status: + conditions: + - type: Available + message: OpenStack resource is available + status: "True" + reason: Success + - type: Progressing + message: OpenStack resource is up to date + status: "False" + reason: Success +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Limit +metadata: + name: limit-dependency-no-domain +status: + conditions: + - type: Available + message: OpenStack resource is available + status: "True" + reason: Success + - type: Progressing + message: OpenStack resource is up to date + status: "False" + reason: Success diff --git a/internal/controllers/limit/tests/limit-dependency/01-create-dependencies.yaml b/internal/controllers/limit/tests/limit-dependency/01-create-dependencies.yaml new file mode 100644 index 000000000..d5db92a06 --- /dev/null +++ b/internal/controllers/limit/tests/limit-dependency/01-create-dependencies.yaml @@ -0,0 +1,45 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + - command: kubectl create secret generic limit-dependency --from-file=clouds.yaml=${E2E_KUTTL_OSCLOUDS} ${E2E_KUTTL_CACERT_OPT} + namespaced: true +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Service +metadata: + name: limit-dependency-pending +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Project +metadata: + name: limit-dependency +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Domain +metadata: + name: limit-dependency +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: {} diff --git a/internal/controllers/limit/tests/limit-dependency/02-assert.yaml b/internal/controllers/limit/tests/limit-dependency/02-assert.yaml new file mode 100644 index 000000000..b00e3a4f0 --- /dev/null +++ b/internal/controllers/limit/tests/limit-dependency/02-assert.yaml @@ -0,0 +1,29 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Service + name: limit-dependency + ref: service + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Project + name: limit-dependency + ref: project + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Domain + name: limit-dependency + ref: domain + - apiVersion: v1 + kind: Secret + name: limit-dependency + ref: secret +assertAll: + - celExpr: "service.metadata.deletionTimestamp != 0" + - celExpr: "'openstack.k-orc.cloud/limit' in service.metadata.finalizers" + - celExpr: "project.metadata.deletionTimestamp != 0" + - celExpr: "'openstack.k-orc.cloud/limit' in project.metadata.finalizers" + - celExpr: "domain.metadata.deletionTimestamp != 0" + - celExpr: "'openstack.k-orc.cloud/limit' in domain.metadata.finalizers" + - celExpr: "secret.metadata.deletionTimestamp != 0" + - celExpr: "'openstack.k-orc.cloud/limit' in secret.metadata.finalizers" diff --git a/internal/controllers/limit/tests/limit-dependency/02-delete-dependencies.yaml b/internal/controllers/limit/tests/limit-dependency/02-delete-dependencies.yaml new file mode 100644 index 000000000..3f8a37b4b --- /dev/null +++ b/internal/controllers/limit/tests/limit-dependency/02-delete-dependencies.yaml @@ -0,0 +1,13 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + # We expect the deletion to hang due to the finalizer, so use --wait=false + - command: kubectl delete service.openstack.k-orc.cloud limit-dependency --wait=false + namespaced: true + - command: kubectl delete project.openstack.k-orc.cloud limit-dependency --wait=false + namespaced: true + - command: kubectl delete domain.openstack.k-orc.cloud limit-dependency --wait=false + namespaced: true + - command: kubectl delete secret limit-dependency --wait=false + namespaced: true diff --git a/internal/controllers/limit/tests/limit-dependency/03-assert.yaml b/internal/controllers/limit/tests/limit-dependency/03-assert.yaml new file mode 100644 index 000000000..9dd3c0fb5 --- /dev/null +++ b/internal/controllers/limit/tests/limit-dependency/03-assert.yaml @@ -0,0 +1,13 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +commands: +# Dependencies that were prevented deletion before should now be gone +- script: "! kubectl get service.openstack.k-orc.cloud limit-dependency --namespace $NAMESPACE" + skipLogOutput: true +- script: "! kubectl get project.openstack.k-orc.cloud limit-dependency --namespace $NAMESPACE" + skipLogOutput: true +- script: "! kubectl get domain.openstack.k-orc.cloud limit-dependency --namespace $NAMESPACE" + skipLogOutput: true +- script: "! kubectl get secret limit-dependency --namespace $NAMESPACE" + skipLogOutput: true diff --git a/internal/controllers/limit/tests/limit-dependency/03-delete-resources.yaml b/internal/controllers/limit/tests/limit-dependency/03-delete-resources.yaml new file mode 100644 index 000000000..2ee949665 --- /dev/null +++ b/internal/controllers/limit/tests/limit-dependency/03-delete-resources.yaml @@ -0,0 +1,16 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +delete: +- apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Limit + name: limit-dependency-no-secret +- apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Limit + name: limit-dependency-no-service +- apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Limit + name: limit-dependency-no-project +- apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Limit + name: limit-dependency-no-domain diff --git a/internal/controllers/limit/tests/limit-dependency/README.md b/internal/controllers/limit/tests/limit-dependency/README.md new file mode 100644 index 000000000..80a94fb62 --- /dev/null +++ b/internal/controllers/limit/tests/limit-dependency/README.md @@ -0,0 +1,21 @@ +# Creation and deletion dependencies + +## Step 00 + +Create Limits referencing non-existing resources. Each Limit is dependent on other non-existing resource. Verify that the Limits are waiting for the needed resources to be created externally. + +## Step 01 + +Create the missing dependencies and verify all the Limits are available. + +## Step 02 + +Delete all the dependencies and check that ORC prevents deletion since there is still a resource that depends on them. + +## Step 03 + +Delete the Limits and validate that all resources are gone. + +## Reference + +https://k-orc.cloud/development/writing-tests/#dependency diff --git a/internal/controllers/limit/tests/limit-import-dependency/00-assert.yaml b/internal/controllers/limit/tests/limit-import-dependency/00-assert.yaml new file mode 100644 index 000000000..c490b9950 --- /dev/null +++ b/internal/controllers/limit/tests/limit-import-dependency/00-assert.yaml @@ -0,0 +1,21 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Limit +metadata: + name: limit-import-dependency +status: + conditions: + - type: Available + message: |- + Waiting for Service/limit-import-dependency to be ready + Waiting for Project/limit-import-dependency to be ready + Waiting for Domain/limit-import-dependency to be ready + status: "False" + reason: Progressing + - type: Progressing + message: |- + Waiting for Service/limit-import-dependency to be ready + Waiting for Project/limit-import-dependency to be ready + Waiting for Domain/limit-import-dependency to be ready + status: "True" + reason: Progressing diff --git a/internal/controllers/limit/tests/limit-import-dependency/00-import-resource.yaml b/internal/controllers/limit/tests/limit-import-dependency/00-import-resource.yaml new file mode 100644 index 000000000..23cdeb28d --- /dev/null +++ b/internal/controllers/limit/tests/limit-import-dependency/00-import-resource.yaml @@ -0,0 +1,54 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Service +metadata: + name: limit-import-dependency +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: unmanaged + import: + filter: + name: limit-import-dependency-external +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Project +metadata: + name: limit-import-dependency +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: unmanaged + import: + filter: + name: limit-import-dependency-external +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Domain +metadata: + name: limit-import-dependency +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: unmanaged + import: + filter: + name: limit-import-dependency-external +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Limit +metadata: + name: limit-import-dependency +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: unmanaged + import: + filter: + serviceRef: limit-import-dependency + projectRef: limit-import-dependency + domainRef: limit-import-dependency diff --git a/internal/controllers/limit/tests/limit-import-dependency/00-secret.yaml b/internal/controllers/limit/tests/limit-import-dependency/00-secret.yaml new file mode 100644 index 000000000..045711ee7 --- /dev/null +++ b/internal/controllers/limit/tests/limit-import-dependency/00-secret.yaml @@ -0,0 +1,6 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + - command: kubectl create secret generic openstack-clouds --from-file=clouds.yaml=${E2E_KUTTL_OSCLOUDS} ${E2E_KUTTL_CACERT_OPT} + namespaced: true diff --git a/internal/controllers/limit/tests/limit-import-dependency/01-assert.yaml b/internal/controllers/limit/tests/limit-import-dependency/01-assert.yaml new file mode 100644 index 000000000..e41e3a464 --- /dev/null +++ b/internal/controllers/limit/tests/limit-import-dependency/01-assert.yaml @@ -0,0 +1,36 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Limit +metadata: + name: limit-import-dependency-not-this-one +status: + conditions: + - type: Available + message: OpenStack resource is available + status: "True" + reason: Success + - type: Progressing + message: OpenStack resource is up to date + status: "False" + reason: Success +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Limit +metadata: + name: limit-import-dependency +status: + conditions: + - type: Available + message: |- + Waiting for Service/limit-import-dependency to be ready + Waiting for Project/limit-import-dependency to be ready + Waiting for Domain/limit-import-dependency to be ready + status: "False" + reason: Progressing + - type: Progressing + message: |- + Waiting for Service/limit-import-dependency to be ready + Waiting for Project/limit-import-dependency to be ready + Waiting for Domain/limit-import-dependency to be ready + status: "True" + reason: Progressing diff --git a/internal/controllers/limit/tests/limit-import-dependency/01-create-trap-resource.yaml b/internal/controllers/limit/tests/limit-import-dependency/01-create-trap-resource.yaml new file mode 100644 index 000000000..4769ccb7e --- /dev/null +++ b/internal/controllers/limit/tests/limit-import-dependency/01-create-trap-resource.yaml @@ -0,0 +1,56 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Service +metadata: + name: limit-import-dependency-not-this-one +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Project +metadata: + name: limit-import-dependency-not-this-one +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Domain +metadata: + name: limit-import-dependency-not-this-one +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: {} +--- +# This `limit-import-dependency-not-this-one` should not be picked by the import filter +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Limit +metadata: + name: limit-import-dependency-not-this-one +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + serviceRef: limit-import-dependency-not-this-one + projectRef: limit-import-dependency-not-this-one + domainRef: limit-import-dependency-not-this-one + # TODO(scaffolding): Add the necessary fields to create the resource diff --git a/internal/controllers/limit/tests/limit-import-dependency/02-assert.yaml b/internal/controllers/limit/tests/limit-import-dependency/02-assert.yaml new file mode 100644 index 000000000..78e1d605b --- /dev/null +++ b/internal/controllers/limit/tests/limit-import-dependency/02-assert.yaml @@ -0,0 +1,44 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Limit + name: limit-import-dependency + ref: limit1 + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Limit + name: limit-import-dependency-not-this-one + ref: limit2 + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Service + name: limit-import-dependency + ref: service + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Project + name: limit-import-dependency + ref: project + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Domain + name: limit-import-dependency + ref: domain +assertAll: + - celExpr: "limit1.status.id != limit2.status.id" + - celExpr: "limit1.status.resource.serviceID == service.status.id" + - celExpr: "limit1.status.resource.projectID == project.status.id" + - celExpr: "limit1.status.resource.domainID == domain.status.id" +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Limit +metadata: + name: limit-import-dependency +status: + conditions: + - type: Available + message: OpenStack resource is available + status: "True" + reason: Success + - type: Progressing + message: OpenStack resource is up to date + status: "False" + reason: Success diff --git a/internal/controllers/limit/tests/limit-import-dependency/02-create-resource.yaml b/internal/controllers/limit/tests/limit-import-dependency/02-create-resource.yaml new file mode 100644 index 000000000..389b078e1 --- /dev/null +++ b/internal/controllers/limit/tests/limit-import-dependency/02-create-resource.yaml @@ -0,0 +1,55 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Service +metadata: + name: limit-import-dependency-external +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Project +metadata: + name: limit-import-dependency-external +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Domain +metadata: + name: limit-import-dependency-external +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Limit +metadata: + name: limit-import-dependency-external +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + serviceRef: limit-import-dependency-external + projectRef: limit-import-dependency-external + domainRef: limit-import-dependency-external + # TODO(scaffolding): Add the necessary fields to create the resource diff --git a/internal/controllers/limit/tests/limit-import-dependency/03-assert.yaml b/internal/controllers/limit/tests/limit-import-dependency/03-assert.yaml new file mode 100644 index 000000000..e5b95325c --- /dev/null +++ b/internal/controllers/limit/tests/limit-import-dependency/03-assert.yaml @@ -0,0 +1,10 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +commands: +- script: "! kubectl get service.openstack.k-orc.cloud limit-import-dependency --namespace $NAMESPACE" + skipLogOutput: true +- script: "! kubectl get project.openstack.k-orc.cloud limit-import-dependency --namespace $NAMESPACE" + skipLogOutput: true +- script: "! kubectl get domain.openstack.k-orc.cloud limit-import-dependency --namespace $NAMESPACE" + skipLogOutput: true diff --git a/internal/controllers/limit/tests/limit-import-dependency/03-delete-import-dependencies.yaml b/internal/controllers/limit/tests/limit-import-dependency/03-delete-import-dependencies.yaml new file mode 100644 index 000000000..9e2df424f --- /dev/null +++ b/internal/controllers/limit/tests/limit-import-dependency/03-delete-import-dependencies.yaml @@ -0,0 +1,11 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + # We should be able to delete the import dependencies + - command: kubectl delete service.openstack.k-orc.cloud limit-import-dependency + namespaced: true + - command: kubectl delete project.openstack.k-orc.cloud limit-import-dependency + namespaced: true + - command: kubectl delete domain.openstack.k-orc.cloud limit-import-dependency + namespaced: true diff --git a/internal/controllers/limit/tests/limit-import-dependency/04-assert.yaml b/internal/controllers/limit/tests/limit-import-dependency/04-assert.yaml new file mode 100644 index 000000000..c4ee6ffd8 --- /dev/null +++ b/internal/controllers/limit/tests/limit-import-dependency/04-assert.yaml @@ -0,0 +1,6 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +commands: +- script: "! kubectl get limit.openstack.k-orc.cloud limit-import-dependency --namespace $NAMESPACE" + skipLogOutput: true diff --git a/internal/controllers/limit/tests/limit-import-dependency/04-delete-resource.yaml b/internal/controllers/limit/tests/limit-import-dependency/04-delete-resource.yaml new file mode 100644 index 000000000..d620fe2ef --- /dev/null +++ b/internal/controllers/limit/tests/limit-import-dependency/04-delete-resource.yaml @@ -0,0 +1,7 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +delete: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Limit + name: limit-import-dependency diff --git a/internal/controllers/limit/tests/limit-import-dependency/README.md b/internal/controllers/limit/tests/limit-import-dependency/README.md new file mode 100644 index 000000000..5386b4cea --- /dev/null +++ b/internal/controllers/limit/tests/limit-import-dependency/README.md @@ -0,0 +1,29 @@ +# Check dependency handling for imported Limit + +## Step 00 + +Import a Limit that references other imported resources. The referenced imported resources have no matching resources yet. +Verify the Limit is waiting for the dependency to be ready. + +## Step 01 + +Create a Limit matching the import filter, except for referenced resources, and verify that it's not being imported. + +## Step 02 + +Create the referenced resources and a Limit matching the import filters. + +Verify that the observed status on the imported Limit corresponds to the spec of the created Limit. + +## Step 03 + +Delete the referenced resources and check that ORC does not prevent deletion. The OpenStack resources still exist because they +were imported resources and we only deleted the ORC representation of it. + +## Step 04 + +Delete the Limit and validate that all resources are gone. + +## Reference + +https://k-orc.cloud/development/writing-tests/#import-dependency diff --git a/internal/controllers/limit/tests/limit-import-error/00-assert.yaml b/internal/controllers/limit/tests/limit-import-error/00-assert.yaml new file mode 100644 index 000000000..325725f59 --- /dev/null +++ b/internal/controllers/limit/tests/limit-import-error/00-assert.yaml @@ -0,0 +1,30 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Limit +metadata: + name: limit-import-error-external-1 +status: + conditions: + - type: Available + message: OpenStack resource is available + status: "True" + reason: Success + - type: Progressing + message: OpenStack resource is up to date + status: "False" + reason: Success +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Limit +metadata: + name: limit-import-error-external-2 +status: + conditions: + - type: Available + message: OpenStack resource is available + status: "True" + reason: Success + - type: Progressing + message: OpenStack resource is up to date + status: "False" + reason: Success diff --git a/internal/controllers/limit/tests/limit-import-error/00-create-resources.yaml b/internal/controllers/limit/tests/limit-import-error/00-create-resources.yaml new file mode 100644 index 000000000..951b85974 --- /dev/null +++ b/internal/controllers/limit/tests/limit-import-error/00-create-resources.yaml @@ -0,0 +1,43 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Service +metadata: + name: limit-import-error +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Limit +metadata: + name: limit-import-error-external-1 +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + description: Limit from "import error" test + serviceRef: limit-import-error + # TODO(scaffolding): add any required field +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Limit +metadata: + name: limit-import-error-external-2 +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + description: Limit from "import error" test + serviceRef: limit-import-error + # TODO(scaffolding): add any required field diff --git a/internal/controllers/limit/tests/limit-import-error/00-secret.yaml b/internal/controllers/limit/tests/limit-import-error/00-secret.yaml new file mode 100644 index 000000000..045711ee7 --- /dev/null +++ b/internal/controllers/limit/tests/limit-import-error/00-secret.yaml @@ -0,0 +1,6 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + - command: kubectl create secret generic openstack-clouds --from-file=clouds.yaml=${E2E_KUTTL_OSCLOUDS} ${E2E_KUTTL_CACERT_OPT} + namespaced: true diff --git a/internal/controllers/limit/tests/limit-import-error/01-assert.yaml b/internal/controllers/limit/tests/limit-import-error/01-assert.yaml new file mode 100644 index 000000000..ce718a593 --- /dev/null +++ b/internal/controllers/limit/tests/limit-import-error/01-assert.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Limit +metadata: + name: limit-import-error +status: + conditions: + - type: Available + message: found more than one matching OpenStack resource during import + status: "False" + reason: InvalidConfiguration + - type: Progressing + message: found more than one matching OpenStack resource during import + status: "False" + reason: InvalidConfiguration diff --git a/internal/controllers/limit/tests/limit-import-error/01-import-resource.yaml b/internal/controllers/limit/tests/limit-import-error/01-import-resource.yaml new file mode 100644 index 000000000..4add5fc38 --- /dev/null +++ b/internal/controllers/limit/tests/limit-import-error/01-import-resource.yaml @@ -0,0 +1,13 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Limit +metadata: + name: limit-import-error +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: unmanaged + import: + filter: + description: Limit from "import error" test diff --git a/internal/controllers/limit/tests/limit-import-error/README.md b/internal/controllers/limit/tests/limit-import-error/README.md new file mode 100644 index 000000000..0db7f3887 --- /dev/null +++ b/internal/controllers/limit/tests/limit-import-error/README.md @@ -0,0 +1,13 @@ +# Import Limit with more than one matching resources + +## Step 00 + +Create two Limits with identical specs. + +## Step 01 + +Ensure that an imported Limit with a filter matching the resources returns an error. + +## Reference + +https://k-orc.cloud/development/writing-tests/#import-error diff --git a/internal/controllers/limit/tests/limit-import/00-assert.yaml b/internal/controllers/limit/tests/limit-import/00-assert.yaml new file mode 100644 index 000000000..0b1056903 --- /dev/null +++ b/internal/controllers/limit/tests/limit-import/00-assert.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Limit +metadata: + name: limit-import +status: + conditions: + - type: Available + message: Waiting for OpenStack resource to be created externally + status: "False" + reason: Progressing + - type: Progressing + message: Waiting for OpenStack resource to be created externally + status: "True" + reason: Progressing diff --git a/internal/controllers/limit/tests/limit-import/00-import-resource.yaml b/internal/controllers/limit/tests/limit-import/00-import-resource.yaml new file mode 100644 index 000000000..8ff26d7c6 --- /dev/null +++ b/internal/controllers/limit/tests/limit-import/00-import-resource.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Limit +metadata: + name: limit-import +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: unmanaged + import: + filter: + name: limit-import-external + description: Limit limit-import-external from "limit-import" test + # TODO(scaffolding): Add all fields supported by the filter diff --git a/internal/controllers/limit/tests/limit-import/00-secret.yaml b/internal/controllers/limit/tests/limit-import/00-secret.yaml new file mode 100644 index 000000000..045711ee7 --- /dev/null +++ b/internal/controllers/limit/tests/limit-import/00-secret.yaml @@ -0,0 +1,6 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + - command: kubectl create secret generic openstack-clouds --from-file=clouds.yaml=${E2E_KUTTL_OSCLOUDS} ${E2E_KUTTL_CACERT_OPT} + namespaced: true diff --git a/internal/controllers/limit/tests/limit-import/01-assert.yaml b/internal/controllers/limit/tests/limit-import/01-assert.yaml new file mode 100644 index 000000000..0b4c9a952 --- /dev/null +++ b/internal/controllers/limit/tests/limit-import/01-assert.yaml @@ -0,0 +1,34 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Limit +metadata: + name: limit-import-external-not-this-one +status: + conditions: + - type: Available + message: OpenStack resource is available + status: "True" + reason: Success + - type: Progressing + message: OpenStack resource is up to date + status: "False" + reason: Success + resource: + name: limit-import-external-not-this-one + description: Limit limit-import-external from "limit-import" test + # TODO(scaffolding): Add fields necessary to match filter +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Limit +metadata: + name: limit-import +status: + conditions: + - type: Available + message: Waiting for OpenStack resource to be created externally + status: "False" + reason: Progressing + - type: Progressing + message: Waiting for OpenStack resource to be created externally + status: "True" + reason: Progressing diff --git a/internal/controllers/limit/tests/limit-import/01-create-trap-resource.yaml b/internal/controllers/limit/tests/limit-import/01-create-trap-resource.yaml new file mode 100644 index 000000000..924253205 --- /dev/null +++ b/internal/controllers/limit/tests/limit-import/01-create-trap-resource.yaml @@ -0,0 +1,31 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Service +metadata: + name: limit-import-external-not-this-one +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: {} +--- +# This `limit-import-external-not-this-one` resource serves two purposes: +# - ensure that we can successfully create another resource which name is a substring of it (i.e. it's not being adopted) +# - ensure that importing a resource which name is a substring of it will not pick this one. +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Limit +metadata: + name: limit-import-external-not-this-one +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + description: Limit limit-import-external from "limit-import" test + serviceRef: limit-import-external-not-this-one + # TODO(scaffolding): Add fields necessary to match filter diff --git a/internal/controllers/limit/tests/limit-import/02-assert.yaml b/internal/controllers/limit/tests/limit-import/02-assert.yaml new file mode 100644 index 000000000..e18bb86ec --- /dev/null +++ b/internal/controllers/limit/tests/limit-import/02-assert.yaml @@ -0,0 +1,33 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Limit + name: limit-import-external + ref: limit1 + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Limit + name: limit-import-external-not-this-one + ref: limit2 +assertAll: + - celExpr: "limit1.status.id != limit2.status.id" +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Limit +metadata: + name: limit-import +status: + conditions: + - type: Available + message: OpenStack resource is available + status: "True" + reason: Success + - type: Progressing + message: OpenStack resource is up to date + status: "False" + reason: Success + resource: + name: limit-import-external + description: Limit limit-import-external from "limit-import" test + # TODO(scaffolding): Add all fields the resource supports diff --git a/internal/controllers/limit/tests/limit-import/02-create-resource.yaml b/internal/controllers/limit/tests/limit-import/02-create-resource.yaml new file mode 100644 index 000000000..ad73945cb --- /dev/null +++ b/internal/controllers/limit/tests/limit-import/02-create-resource.yaml @@ -0,0 +1,28 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Service +metadata: + name: limit-import +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Limit +metadata: + name: limit-import-external +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + description: Limit limit-import-external from "limit-import" test + serviceRef: limit-import + # TODO(scaffolding): Add fields necessary to match filter diff --git a/internal/controllers/limit/tests/limit-import/README.md b/internal/controllers/limit/tests/limit-import/README.md new file mode 100644 index 000000000..19b1de97f --- /dev/null +++ b/internal/controllers/limit/tests/limit-import/README.md @@ -0,0 +1,18 @@ +# Import Limit + +## Step 00 + +Import a limit that matches all fields in the filter, and verify it is waiting for the external resource to be created. + +## Step 01 + +Create a limit whose name is a superstring of the one specified in the import filter, otherwise matching the filter, and verify that it's not being imported. + +## Step 02 + +Create a limit matching the filter and verify that the observed status on the imported limit corresponds to the spec of the created limit. +Also, confirm that it does not adopt any limit whose name is a superstring of its own. + +## Reference + +https://k-orc.cloud/development/writing-tests/#import diff --git a/internal/controllers/limit/tests/limit-update/00-assert.yaml b/internal/controllers/limit/tests/limit-update/00-assert.yaml new file mode 100644 index 000000000..a2b334697 --- /dev/null +++ b/internal/controllers/limit/tests/limit-update/00-assert.yaml @@ -0,0 +1,26 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Limit + name: limit-update + ref: limit +assertAll: + - celExpr: "!has(limit.status.resource.description)" +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Limit +metadata: + name: limit-update +status: + resource: + name: limit-update + # TODO(scaffolding): Add matches for more fields + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success diff --git a/internal/controllers/limit/tests/limit-update/00-minimal-resource.yaml b/internal/controllers/limit/tests/limit-update/00-minimal-resource.yaml new file mode 100644 index 000000000..7ae11e841 --- /dev/null +++ b/internal/controllers/limit/tests/limit-update/00-minimal-resource.yaml @@ -0,0 +1,28 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Service +metadata: + name: limit-update +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Limit +metadata: + name: limit-update +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created or updated + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # TODO(scaffolding): Only add the mandatory fields. It's possible the resource + # doesn't have mandatory fields, in that case, leave it empty. + resource: + serviceRef: limit-update diff --git a/internal/controllers/limit/tests/limit-update/00-secret.yaml b/internal/controllers/limit/tests/limit-update/00-secret.yaml new file mode 100644 index 000000000..045711ee7 --- /dev/null +++ b/internal/controllers/limit/tests/limit-update/00-secret.yaml @@ -0,0 +1,6 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + - command: kubectl create secret generic openstack-clouds --from-file=clouds.yaml=${E2E_KUTTL_OSCLOUDS} ${E2E_KUTTL_CACERT_OPT} + namespaced: true diff --git a/internal/controllers/limit/tests/limit-update/01-assert.yaml b/internal/controllers/limit/tests/limit-update/01-assert.yaml new file mode 100644 index 000000000..bc670606d --- /dev/null +++ b/internal/controllers/limit/tests/limit-update/01-assert.yaml @@ -0,0 +1,17 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Limit +metadata: + name: limit-update +status: + resource: + name: limit-update-updated + description: limit-update-updated + # TODO(scaffolding): match all fields that were modified + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success diff --git a/internal/controllers/limit/tests/limit-update/01-updated-resource.yaml b/internal/controllers/limit/tests/limit-update/01-updated-resource.yaml new file mode 100644 index 000000000..419cf32ad --- /dev/null +++ b/internal/controllers/limit/tests/limit-update/01-updated-resource.yaml @@ -0,0 +1,10 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Limit +metadata: + name: limit-update +spec: + resource: + name: limit-update-updated + description: limit-update-updated + # TODO(scaffolding): update all mutable fields diff --git a/internal/controllers/limit/tests/limit-update/02-assert.yaml b/internal/controllers/limit/tests/limit-update/02-assert.yaml new file mode 100644 index 000000000..7f36160e1 --- /dev/null +++ b/internal/controllers/limit/tests/limit-update/02-assert.yaml @@ -0,0 +1,26 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Limit + name: limit-update + ref: limit +assertAll: + - celExpr: "!has(limit.status.resource.description)" +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Limit +metadata: + name: limit-update +status: + resource: + name: limit-update + # TODO(scaffolding): validate that updated fields were all reverted to their original value + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success diff --git a/internal/controllers/limit/tests/limit-update/02-reverted-resource.yaml b/internal/controllers/limit/tests/limit-update/02-reverted-resource.yaml new file mode 100644 index 000000000..2c6c253ff --- /dev/null +++ b/internal/controllers/limit/tests/limit-update/02-reverted-resource.yaml @@ -0,0 +1,7 @@ +# NOTE: kuttl only does patch updates, which means we can't delete a field. +# We have to use a kubectl apply command instead. +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + - command: kubectl replace -f 00-minimal-resource.yaml + namespaced: true diff --git a/internal/controllers/limit/tests/limit-update/README.md b/internal/controllers/limit/tests/limit-update/README.md new file mode 100644 index 000000000..4937a9715 --- /dev/null +++ b/internal/controllers/limit/tests/limit-update/README.md @@ -0,0 +1,17 @@ +# Update Limit + +## Step 00 + +Create a Limit using only mandatory fields. + +## Step 01 + +Update all mutable fields. + +## Step 02 + +Revert the resource to its original value and verify that the resulting object matches its state when first created. + +## Reference + +https://k-orc.cloud/development/writing-tests/#update diff --git a/internal/osclients/limit.go b/internal/osclients/limit.go new file mode 100644 index 000000000..0847b2a45 --- /dev/null +++ b/internal/osclients/limit.go @@ -0,0 +1,104 @@ +/* +Copyright The ORC Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package osclients + +import ( + "context" + "fmt" + "iter" + + "github.com/gophercloud/gophercloud/v2" + "github.com/gophercloud/gophercloud/v2/openstack" + "github.com/gophercloud/gophercloud/v2/openstack/identity/v3/limits" + "github.com/gophercloud/utils/v2/openstack/clientconfig" +) + +type LimitClient interface { + ListLimits(ctx context.Context, listOpts limits.ListOptsBuilder) iter.Seq2[*limits.Limit, error] + CreateLimit(ctx context.Context, opts limits.CreateOptsBuilder) (*limits.Limit, error) + DeleteLimit(ctx context.Context, resourceID string) error + GetLimit(ctx context.Context, resourceID string) (*limits.Limit, error) + UpdateLimit(ctx context.Context, id string, opts limits.UpdateOptsBuilder) (*limits.Limit, error) +} + +type limitClient struct{ client *gophercloud.ServiceClient } + +// NewLimitClient returns a new OpenStack client. +func NewLimitClient(providerClient *gophercloud.ProviderClient, providerClientOpts *clientconfig.ClientOpts) (LimitClient, error) { + client, err := openstack.NewIdentityV3(providerClient, gophercloud.EndpointOpts{ + Region: providerClientOpts.RegionName, + Availability: clientconfig.GetEndpointType(providerClientOpts.EndpointType), + }) + + if err != nil { + return nil, fmt.Errorf("failed to create limit service client: %v", err) + } + + return &limitClient{client}, nil +} + +func (c limitClient) ListLimits(ctx context.Context, listOpts limits.ListOptsBuilder) iter.Seq2[*limits.Limit, error] { + pager := limits.List(c.client, listOpts) + return func(yield func(*limits.Limit, error) bool) { + _ = pager.EachPage(ctx, yieldPage(limits.ExtractLimits, yield)) + } +} + +func (c limitClient) CreateLimit(ctx context.Context, opts limits.CreateOptsBuilder) (*limits.Limit, error) { + return limits.Create(ctx, c.client, opts).Extract() +} + +func (c limitClient) DeleteLimit(ctx context.Context, resourceID string) error { + return limits.Delete(ctx, c.client, resourceID).ExtractErr() +} + +func (c limitClient) GetLimit(ctx context.Context, resourceID string) (*limits.Limit, error) { + return limits.Get(ctx, c.client, resourceID).Extract() +} + +func (c limitClient) UpdateLimit(ctx context.Context, id string, opts limits.UpdateOptsBuilder) (*limits.Limit, error) { + return limits.Update(ctx, c.client, id, opts).Extract() +} + +type limitErrorClient struct{ error } + +// NewLimitErrorClient returns a LimitClient in which every method returns the given error. +func NewLimitErrorClient(e error) LimitClient { + return limitErrorClient{e} +} + +func (e limitErrorClient) ListLimits(_ context.Context, _ limits.ListOptsBuilder) iter.Seq2[*limits.Limit, error] { + return func(yield func(*limits.Limit, error) bool) { + yield(nil, e.error) + } +} + +func (e limitErrorClient) CreateLimit(_ context.Context, _ limits.CreateOptsBuilder) (*limits.Limit, error) { + return nil, e.error +} + +func (e limitErrorClient) DeleteLimit(_ context.Context, _ string) error { + return e.error +} + +func (e limitErrorClient) GetLimit(_ context.Context, _ string) (*limits.Limit, error) { + return nil, e.error +} + +func (e limitErrorClient) UpdateLimit(_ context.Context, _ string, _ limits.UpdateOptsBuilder) (*limits.Limit, error) { + return nil, e.error +} diff --git a/test/apivalidations/limit_test.go b/test/apivalidations/limit_test.go new file mode 100644 index 000000000..a0f4fd55b --- /dev/null +++ b/test/apivalidations/limit_test.go @@ -0,0 +1,152 @@ +/* +Copyright The ORC Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package apivalidations + +import ( + "context" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + corev1 "k8s.io/api/core/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + + orcv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" + applyconfigv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/pkg/clients/applyconfiguration/api/v1alpha1" +) + +const ( + limitName = "limit" + limitID = "265c9e4f-0f5a-46e4-9f3f-fb8de25ae120" +) + +func limitStub(namespace *corev1.Namespace) *orcv1alpha1.Limit { + obj := &orcv1alpha1.Limit{} + obj.Name = limitName + obj.Namespace = namespace.Name + return obj +} + +func testLimitResource() *applyconfigv1alpha1.LimitResourceSpecApplyConfiguration { + return applyconfigv1alpha1.LimitResourceSpec(). + WithServiceRef("service") +} + +func baseLimitPatch(obj client.Object) *applyconfigv1alpha1.LimitApplyConfiguration { + return applyconfigv1alpha1.Limit(obj.GetName(), obj.GetNamespace()). + WithSpec(applyconfigv1alpha1.LimitSpec(). + WithCloudCredentialsRef(testCredentials())) +} + +func testLimitImport() *applyconfigv1alpha1.LimitImportApplyConfiguration { + return applyconfigv1alpha1.LimitImport().WithID(limitID) +} + +var _ = Describe("ORC Limit API validations", func() { + var namespace *corev1.Namespace + BeforeEach(func() { + namespace = createNamespace() + }) + + runManagementPolicyTests(func() *corev1.Namespace { return namespace }, managementPolicyTestArgs[*applyconfigv1alpha1.LimitApplyConfiguration]{ + createObject: func(ns *corev1.Namespace) client.Object { return limitStub(ns) }, + basePatch: func(obj client.Object) *applyconfigv1alpha1.LimitApplyConfiguration { + return baseLimitPatch(obj) + }, + applyResource: func(p *applyconfigv1alpha1.LimitApplyConfiguration) { + p.Spec.WithResource(testLimitResource()) + }, + applyImport: func(p *applyconfigv1alpha1.LimitApplyConfiguration) { + p.Spec.WithImport(testLimitImport()) + }, + applyEmptyImport: func(p *applyconfigv1alpha1.LimitApplyConfiguration) { + p.Spec.WithImport(applyconfigv1alpha1.LimitImport()) + }, + applyEmptyFilter: func(p *applyconfigv1alpha1.LimitApplyConfiguration) { + p.Spec.WithImport(applyconfigv1alpha1.LimitImport().WithFilter(applyconfigv1alpha1.LimitFilter())) + }, + applyValidFilter: func(p *applyconfigv1alpha1.LimitApplyConfiguration) { + p.Spec.WithImport(applyconfigv1alpha1.LimitImport().WithFilter(applyconfigv1alpha1.LimitFilter().WithName("foo"))) + }, + applyManaged: func(p *applyconfigv1alpha1.LimitApplyConfiguration) { + p.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged) + }, + applyUnmanaged: func(p *applyconfigv1alpha1.LimitApplyConfiguration) { + p.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged) + }, + applyManagedOptions: func(p *applyconfigv1alpha1.LimitApplyConfiguration) { + p.Spec.WithManagedOptions(applyconfigv1alpha1.ManagedOptions().WithOnDelete(orcv1alpha1.OnDeleteDetach)) + }, + getManagementPolicy: func(obj client.Object) orcv1alpha1.ManagementPolicy { + return obj.(*orcv1alpha1.Limit).Spec.ManagementPolicy + }, + getOnDelete: func(obj client.Object) orcv1alpha1.OnDelete { + return obj.(*orcv1alpha1.Limit).Spec.ManagedOptions.OnDelete + }, + }) + + It("should reject a limit without required fields", func(ctx context.Context) { + obj := limitStub(namespace) + patch := baseLimitPatch(obj) + patch.Spec.WithResource(applyconfigv1alpha1.LimitResourceSpec()) + Expect(applyObj(ctx, obj, patch)).NotTo(Succeed()) + }) + + It("should have immutable serviceRef", func(ctx context.Context) { + obj := limitStub(namespace) + patch := baseLimitPatch(obj) + patch.Spec.WithResource(testLimitResource(). + WithServiceRef("service-a")) + Expect(applyObj(ctx, obj, patch)).To(Succeed()) + + patch.Spec.WithResource(testLimitResource(). + WithServiceRef("service-b")) + Expect(applyObj(ctx, obj, patch)).To(MatchError(ContainSubstring("serviceRef is immutable"))) + }) + + It("should have immutable projectRef", func(ctx context.Context) { + obj := limitStub(namespace) + patch := baseLimitPatch(obj) + patch.Spec.WithResource(testLimitResource(). + WithProjectRef("project-a")) + Expect(applyObj(ctx, obj, patch)).To(Succeed()) + + patch.Spec.WithResource(testLimitResource(). + WithProjectRef("project-b")) + Expect(applyObj(ctx, obj, patch)).To(MatchError(ContainSubstring("projectRef is immutable"))) + }) + + It("should have immutable domainRef", func(ctx context.Context) { + obj := limitStub(namespace) + patch := baseLimitPatch(obj) + patch.Spec.WithResource(testLimitResource(). + WithDomainRef("domain-a")) + Expect(applyObj(ctx, obj, patch)).To(Succeed()) + + patch.Spec.WithResource(testLimitResource(). + WithDomainRef("domain-b")) + Expect(applyObj(ctx, obj, patch)).To(MatchError(ContainSubstring("domainRef is immutable"))) + }) + + // TODO(scaffolding): Add more resource-specific validation tests. + // Some common things to test: + // - Immutability of fields with `self == oldSelf` validation + // - Enum validation (valid and invalid values) + // - Numeric range validation (min/max bounds) + // - Tag uniqueness (if the resource has tags with listType=set) + // - Format validation (CIDR, UUID, etc.) + // - Cross-field validation rules +}) From 6fa31b6b799092ed070167f68a2e15052532cc78 Mon Sep 17 00:00:00 2001 From: Winston Chen Date: Mon, 20 Jul 2026 10:34:09 +0100 Subject: [PATCH 02/13] Generate resource for Limit controller --- PROJECT | 8 + api/v1alpha1/zz_generated.deepcopy.go | 246 +++++++++++ api/v1alpha1/zz_generated.limit-resource.go | 194 +++++++++ cmd/manager/main.go | 2 + cmd/models-schema/zz_generated.openapi.go | 395 ++++++++++++++++++ cmd/resource-generator/main.go | 3 + .../bases/openstack.k-orc.cloud_limits.yaml | 367 ++++++++++++++++ config/crd/kustomization.yaml | 1 + config/rbac/role.yaml | 2 + config/samples/kustomization.yaml | 1 + .../controllers/limit/zz_generated.adapter.go | 98 +++++ .../limit/zz_generated.controller.go | 45 ++ internal/osclients/mock/doc.go | 3 + internal/osclients/mock/limit.go | 131 ++++++ internal/scope/mock.go | 7 + internal/scope/provider.go | 4 + internal/scope/scope.go | 1 + kuttl-test.yaml | 1 + .../applyconfiguration/api/v1alpha1/limit.go | 281 +++++++++++++ .../api/v1alpha1/limitfilter.go | 79 ++++ .../api/v1alpha1/limitimport.go | 48 +++ .../api/v1alpha1/limitresourcespec.go | 79 ++++ .../api/v1alpha1/limitresourcestatus.go | 75 ++++ .../api/v1alpha1/limitspec.go | 89 ++++ .../api/v1alpha1/limitstatus.go | 76 ++++ .../applyconfiguration/internal/internal.go | 126 ++++++ pkg/clients/applyconfiguration/utils.go | 14 + .../typed/api/v1alpha1/api_client.go | 5 + .../api/v1alpha1/fake/fake_api_client.go | 4 + .../typed/api/v1alpha1/fake/fake_limit.go | 49 +++ .../typed/api/v1alpha1/generated_expansion.go | 2 + .../clientset/typed/api/v1alpha1/limit.go | 74 ++++ .../api/v1alpha1/interface.go | 7 + .../externalversions/api/v1alpha1/limit.go | 102 +++++ .../informers/externalversions/generic.go | 2 + .../api/v1alpha1/expansion_generated.go | 8 + pkg/clients/listers/api/v1alpha1/limit.go | 70 ++++ website/docs/crd-reference.md | 148 +++++++ 38 files changed, 2847 insertions(+) create mode 100644 api/v1alpha1/zz_generated.limit-resource.go create mode 100644 config/crd/bases/openstack.k-orc.cloud_limits.yaml create mode 100644 internal/controllers/limit/zz_generated.adapter.go create mode 100644 internal/controllers/limit/zz_generated.controller.go create mode 100644 internal/osclients/mock/limit.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/limit.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/limitfilter.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/limitimport.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/limitresourcespec.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/limitresourcestatus.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/limitspec.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/limitstatus.go create mode 100644 pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_limit.go create mode 100644 pkg/clients/clientset/clientset/typed/api/v1alpha1/limit.go create mode 100644 pkg/clients/informers/externalversions/api/v1alpha1/limit.go create mode 100644 pkg/clients/listers/api/v1alpha1/limit.go diff --git a/PROJECT b/PROJECT index 357849779..b40507be8 100644 --- a/PROJECT +++ b/PROJECT @@ -80,6 +80,14 @@ resources: kind: KeyPair path: github.com/k-orc/openstack-resource-controller/api/v1alpha1 version: v1alpha1 +- api: + crdVersion: v1 + namespaced: true + domain: k-orc.cloud + group: openstack + kind: Limit + path: github.com/k-orc/openstack-resource-controller/api/v1alpha1 + version: v1alpha1 - api: crdVersion: v1 namespaced: true diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 4e7d7e3c1..532d691ec 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -2896,6 +2896,252 @@ func (in *KeyPairStatus) DeepCopy() *KeyPairStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Limit) DeepCopyInto(out *Limit) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Limit. +func (in *Limit) DeepCopy() *Limit { + if in == nil { + return nil + } + out := new(Limit) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Limit) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *LimitFilter) DeepCopyInto(out *LimitFilter) { + *out = *in + if in.Name != nil { + in, out := &in.Name, &out.Name + *out = new(OpenStackName) + **out = **in + } + if in.Description != nil { + in, out := &in.Description, &out.Description + *out = new(string) + **out = **in + } + if in.ServiceRef != nil { + in, out := &in.ServiceRef, &out.ServiceRef + *out = new(KubernetesNameRef) + **out = **in + } + if in.ProjectRef != nil { + in, out := &in.ProjectRef, &out.ProjectRef + *out = new(KubernetesNameRef) + **out = **in + } + if in.DomainRef != nil { + in, out := &in.DomainRef, &out.DomainRef + *out = new(KubernetesNameRef) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LimitFilter. +func (in *LimitFilter) DeepCopy() *LimitFilter { + if in == nil { + return nil + } + out := new(LimitFilter) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *LimitImport) DeepCopyInto(out *LimitImport) { + *out = *in + if in.ID != nil { + in, out := &in.ID, &out.ID + *out = new(string) + **out = **in + } + if in.Filter != nil { + in, out := &in.Filter, &out.Filter + *out = new(LimitFilter) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LimitImport. +func (in *LimitImport) DeepCopy() *LimitImport { + if in == nil { + return nil + } + out := new(LimitImport) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *LimitList) DeepCopyInto(out *LimitList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Limit, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LimitList. +func (in *LimitList) DeepCopy() *LimitList { + if in == nil { + return nil + } + out := new(LimitList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *LimitList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *LimitResourceSpec) DeepCopyInto(out *LimitResourceSpec) { + *out = *in + if in.Name != nil { + in, out := &in.Name, &out.Name + *out = new(OpenStackName) + **out = **in + } + if in.Description != nil { + in, out := &in.Description, &out.Description + *out = new(string) + **out = **in + } + if in.ProjectRef != nil { + in, out := &in.ProjectRef, &out.ProjectRef + *out = new(KubernetesNameRef) + **out = **in + } + if in.DomainRef != nil { + in, out := &in.DomainRef, &out.DomainRef + *out = new(KubernetesNameRef) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LimitResourceSpec. +func (in *LimitResourceSpec) DeepCopy() *LimitResourceSpec { + if in == nil { + return nil + } + out := new(LimitResourceSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *LimitResourceStatus) DeepCopyInto(out *LimitResourceStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LimitResourceStatus. +func (in *LimitResourceStatus) DeepCopy() *LimitResourceStatus { + if in == nil { + return nil + } + out := new(LimitResourceStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *LimitSpec) DeepCopyInto(out *LimitSpec) { + *out = *in + if in.Import != nil { + in, out := &in.Import, &out.Import + *out = new(LimitImport) + (*in).DeepCopyInto(*out) + } + if in.Resource != nil { + in, out := &in.Resource, &out.Resource + *out = new(LimitResourceSpec) + (*in).DeepCopyInto(*out) + } + if in.ManagedOptions != nil { + in, out := &in.ManagedOptions, &out.ManagedOptions + *out = new(ManagedOptions) + **out = **in + } + if in.ResyncPeriod != nil { + in, out := &in.ResyncPeriod, &out.ResyncPeriod + *out = new(v1.Duration) + **out = **in + } + out.CloudCredentialsRef = in.CloudCredentialsRef +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LimitSpec. +func (in *LimitSpec) DeepCopy() *LimitSpec { + if in == nil { + return nil + } + out := new(LimitSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *LimitStatus) DeepCopyInto(out *LimitStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]v1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.ID != nil { + in, out := &in.ID, &out.ID + *out = new(string) + **out = **in + } + if in.Resource != nil { + in, out := &in.Resource, &out.Resource + *out = new(LimitResourceStatus) + **out = **in + } + if in.LastSyncTime != nil { + in, out := &in.LastSyncTime, &out.LastSyncTime + *out = (*in).DeepCopy() + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LimitStatus. +func (in *LimitStatus) DeepCopy() *LimitStatus { + if in == nil { + return nil + } + out := new(LimitStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ManagedOptions) DeepCopyInto(out *ManagedOptions) { *out = *in diff --git a/api/v1alpha1/zz_generated.limit-resource.go b/api/v1alpha1/zz_generated.limit-resource.go new file mode 100644 index 000000000..bbae443da --- /dev/null +++ b/api/v1alpha1/zz_generated.limit-resource.go @@ -0,0 +1,194 @@ +// Code generated by resource-generator. DO NOT EDIT. +/* +Copyright The ORC Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// LimitImport specifies an existing resource which will be imported instead of +// creating a new one +// +kubebuilder:validation:MinProperties:=1 +// +kubebuilder:validation:MaxProperties:=1 +type LimitImport struct { + // id contains the unique identifier of an existing OpenStack resource. Note + // that when specifying an import by ID, the resource MUST already exist. + // The ORC object will enter an error state if the resource does not exist. + // +kubebuilder:validation:Format:=uuid + // +kubebuilder:validation:MaxLength:=36 + // +optional + ID *string `json:"id,omitempty"` //nolint:kubeapilinter + + // filter contains a resource query which is expected to return a single + // result. The controller will continue to retry if filter returns no + // results. If filter returns multiple results the controller will set an + // error state and will not continue to retry. + // +optional + Filter *LimitFilter `json:"filter,omitempty"` +} + +// LimitSpec defines the desired state of an ORC object. +// +kubebuilder:validation:XValidation:rule="self.managementPolicy == 'managed' ? has(self.resource) : true",message="resource must be specified when policy is managed" +// +kubebuilder:validation:XValidation:rule="self.managementPolicy == 'managed' ? !has(self.__import__) : true",message="import may not be specified when policy is managed" +// +kubebuilder:validation:XValidation:rule="self.managementPolicy == 'unmanaged' ? !has(self.resource) : true",message="resource may not be specified when policy is unmanaged" +// +kubebuilder:validation:XValidation:rule="self.managementPolicy == 'unmanaged' ? has(self.__import__) : true",message="import must be specified when policy is unmanaged" +// +kubebuilder:validation:XValidation:rule="has(self.managedOptions) ? self.managementPolicy == 'managed' : true",message="managedOptions may only be provided when policy is managed" +type LimitSpec struct { + // import refers to an existing OpenStack resource which will be imported instead of + // creating a new one. + // +optional + Import *LimitImport `json:"import,omitempty"` + + // resource specifies the desired state of the resource. + // + // resource may not be specified if the management policy is `unmanaged`. + // + // resource must be specified if the management policy is `managed`. + // +optional + Resource *LimitResourceSpec `json:"resource,omitempty"` + + // managementPolicy defines how ORC will treat the object. Valid values are + // `managed`: ORC will create, update, and delete the resource; `unmanaged`: + // ORC will import an existing resource, and will not apply updates to it or + // delete it. + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="managementPolicy is immutable" + // +kubebuilder:default:=managed + // +optional + ManagementPolicy ManagementPolicy `json:"managementPolicy,omitempty"` + + // managedOptions specifies options which may be applied to managed objects. + // +optional + ManagedOptions *ManagedOptions `json:"managedOptions,omitempty"` + + // resyncPeriod defines how frequently the controller will re-reconcile + // this resource even when no changes have been detected. This overrides + // the global default resync period. The value must be a valid Go duration + // string, e.g. "10m", "1h". Set to "0s" to disable periodic resync for + // this resource. Very low values may cause excessive OpenStack API load. + // +optional + ResyncPeriod *metav1.Duration `json:"resyncPeriod,omitempty"` //nolint:kubeapilinter // metav1.Duration is appropriate for user-facing duration config + + // cloudCredentialsRef points to a secret containing OpenStack credentials + // +required + CloudCredentialsRef CloudCredentialsReference `json:"cloudCredentialsRef,omitzero"` +} + +// LimitStatus defines the observed state of an ORC resource. +type LimitStatus struct { + // conditions represents the observed status of the object. + // Known .status.conditions.type are: "Available", "Progressing" + // + // Available represents the availability of the OpenStack resource. If it is + // true then the resource is ready for use. + // + // Progressing indicates whether the controller is still attempting to + // reconcile the current state of the OpenStack resource to the desired + // state. Progressing will be False either because the desired state has + // been achieved, or because some terminal error prevents it from ever being + // achieved and the controller is no longer attempting to reconcile. If + // Progressing is True, an observer waiting on the resource should continue + // to wait. + // + // +kubebuilder:validation:MaxItems:=32 + // +patchMergeKey=type + // +patchStrategy=merge + // +listType=map + // +listMapKey=type + // +optional + Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"` + + // id is the unique identifier of the OpenStack resource. + // +kubebuilder:validation:MaxLength:=1024 + // +optional + ID *string `json:"id,omitempty"` + + // resource contains the observed state of the OpenStack resource. + // +optional + Resource *LimitResourceStatus `json:"resource,omitempty"` + + // lastSyncTime is the timestamp of the last successful reconciliation + // that fetched state from OpenStack. It is updated each time the + // controller successfully reads the resource state from the OpenStack + // API. + // +optional + LastSyncTime *metav1.Time `json:"lastSyncTime,omitempty"` +} + +var _ ObjectWithConditions = &Limit{} + +func (i *Limit) GetConditions() []metav1.Condition { + return i.Status.Conditions +} + +// +genclient +// +kubebuilder:object:root=true +// +kubebuilder:resource:categories=openstack +// +kubebuilder:subresource:status +// +kubebuilder:printcolumn:name="ID",type="string",JSONPath=".status.id",description="Resource ID" +// +kubebuilder:printcolumn:name="Available",type="string",JSONPath=".status.conditions[?(@.type=='Available')].status",description="Availability status of resource" +// +kubebuilder:printcolumn:name="Message",type="string",JSONPath=".status.conditions[?(@.type=='Progressing')].message",description="Message describing current progress status" + +// Limit is the Schema for an ORC resource. +type Limit struct { + metav1.TypeMeta `json:",inline"` + + // metadata contains the object metadata + // +optional + metav1.ObjectMeta `json:"metadata,omitempty"` + + // spec specifies the desired state of the resource. + // +required + Spec LimitSpec `json:"spec,omitzero"` + + // status defines the observed state of the resource. + // +optional + Status LimitStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// LimitList contains a list of Limit. +type LimitList struct { + metav1.TypeMeta `json:",inline"` + + // metadata contains the list metadata + // +optional + metav1.ListMeta `json:"metadata,omitempty"` + + // items contains a list of Limit. + // +required + Items []Limit `json:"items"` +} + +func (l *LimitList) GetItems() []Limit { + return l.Items +} + +func init() { + SchemeBuilder.Register(&Limit{}, &LimitList{}) +} + +func (i *Limit) GetCloudCredentialsRef() (*string, *CloudCredentialsReference) { + if i == nil { + return nil, nil + } + + return &i.Namespace, &i.Spec.CloudCredentialsRef +} + +var _ CloudCredentialsRefProvider = &Limit{} diff --git a/cmd/manager/main.go b/cmd/manager/main.go index 6f759c2e5..8fd331df3 100644 --- a/cmd/manager/main.go +++ b/cmd/manager/main.go @@ -37,6 +37,7 @@ import ( "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/group" "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/image" "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/keypair" + "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/limit" "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/network" "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/port" "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/project" @@ -143,6 +144,7 @@ func main() { group.New(scopeFactory), role.New(scopeFactory), roleassignment.New(scopeFactory), + limit.New(scopeFactory), } restConfig := ctrl.GetConfigOrDie() diff --git a/cmd/models-schema/zz_generated.openapi.go b/cmd/models-schema/zz_generated.openapi.go index 29d2fa1b1..0b44022c1 100644 --- a/cmd/models-schema/zz_generated.openapi.go +++ b/cmd/models-schema/zz_generated.openapi.go @@ -130,6 +130,14 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.KeyPairResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_KeyPairResourceStatus(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.KeyPairSpec": schema_openstack_resource_controller_v2_api_v1alpha1_KeyPairSpec(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.KeyPairStatus": schema_openstack_resource_controller_v2_api_v1alpha1_KeyPairStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.Limit": schema_openstack_resource_controller_v2_api_v1alpha1_Limit(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.LimitFilter": schema_openstack_resource_controller_v2_api_v1alpha1_LimitFilter(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.LimitImport": schema_openstack_resource_controller_v2_api_v1alpha1_LimitImport(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.LimitList": schema_openstack_resource_controller_v2_api_v1alpha1_LimitList(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.LimitResourceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_LimitResourceSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.LimitResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_LimitResourceStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.LimitSpec": schema_openstack_resource_controller_v2_api_v1alpha1_LimitSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.LimitStatus": schema_openstack_resource_controller_v2_api_v1alpha1_LimitStatus(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ManagedOptions": schema_openstack_resource_controller_v2_api_v1alpha1_ManagedOptions(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.Network": schema_openstack_resource_controller_v2_api_v1alpha1_Network(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.NetworkFilter": schema_openstack_resource_controller_v2_api_v1alpha1_NetworkFilter(ref), @@ -5348,6 +5356,393 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_KeyPairStatus(ref comm } } +func schema_openstack_resource_controller_v2_api_v1alpha1_Limit(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Limit is the Schema for an ORC resource.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Description: "metadata contains the object metadata", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), + }, + }, + "spec": { + SchemaProps: spec.SchemaProps{ + Description: "spec specifies the desired state of the resource.", + Default: map[string]interface{}{}, + Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.LimitSpec"), + }, + }, + "status": { + SchemaProps: spec.SchemaProps{ + Description: "status defines the observed state of the resource.", + Default: map[string]interface{}{}, + Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.LimitStatus"), + }, + }, + }, + Required: []string{"spec"}, + }, + }, + Dependencies: []string{ + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.LimitSpec", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.LimitStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_LimitFilter(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "LimitFilter defines an existing resource by its properties", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "name": { + SchemaProps: spec.SchemaProps{ + Description: "name of the existing resource", + Type: []string{"string"}, + Format: "", + }, + }, + "description": { + SchemaProps: spec.SchemaProps{ + Description: "description of the existing resource", + Type: []string{"string"}, + Format: "", + }, + }, + "serviceRef": { + SchemaProps: spec.SchemaProps{ + Description: "serviceRef is a reference to the ORC Service which this resource is associated with.", + Type: []string{"string"}, + Format: "", + }, + }, + "projectRef": { + SchemaProps: spec.SchemaProps{ + Description: "projectRef is a reference to the ORC Project which this resource is associated with.", + Type: []string{"string"}, + Format: "", + }, + }, + "domainRef": { + SchemaProps: spec.SchemaProps{ + Description: "domainRef is a reference to the ORC Domain which this resource is associated with.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_LimitImport(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "LimitImport specifies an existing resource which will be imported instead of creating a new one", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "id": { + SchemaProps: spec.SchemaProps{ + Description: "id contains the unique identifier of an existing OpenStack resource. Note that when specifying an import by ID, the resource MUST already exist. The ORC object will enter an error state if the resource does not exist.", + Type: []string{"string"}, + Format: "", + }, + }, + "filter": { + SchemaProps: spec.SchemaProps{ + Description: "filter contains a resource query which is expected to return a single result. The controller will continue to retry if filter returns no results. If filter returns multiple results the controller will set an error state and will not continue to retry.", + Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.LimitFilter"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.LimitFilter"}, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_LimitList(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "LimitList contains a list of Limit.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Description: "metadata contains the list metadata", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), + }, + }, + "items": { + SchemaProps: spec.SchemaProps{ + Description: "items contains a list of Limit.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.Limit"), + }, + }, + }, + }, + }, + }, + Required: []string{"items"}, + }, + }, + Dependencies: []string{ + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.Limit", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_LimitResourceSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "LimitResourceSpec contains the desired state of the resource.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "name": { + SchemaProps: spec.SchemaProps{ + Description: "name will be the name of the created resource. If not specified, the name of the ORC object will be used.", + Type: []string{"string"}, + Format: "", + }, + }, + "description": { + SchemaProps: spec.SchemaProps{ + Description: "description is a human-readable description for the resource.", + Type: []string{"string"}, + Format: "", + }, + }, + "serviceRef": { + SchemaProps: spec.SchemaProps{ + Description: "serviceRef is a reference to the ORC Service which this resource is associated with.", + Type: []string{"string"}, + Format: "", + }, + }, + "projectRef": { + SchemaProps: spec.SchemaProps{ + Description: "projectRef is a reference to the ORC Project which this resource is associated with.", + Type: []string{"string"}, + Format: "", + }, + }, + "domainRef": { + SchemaProps: spec.SchemaProps{ + Description: "domainRef is a reference to the ORC Domain which this resource is associated with.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"serviceRef"}, + }, + }, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_LimitResourceStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "LimitResourceStatus represents the observed state of the resource.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "name": { + SchemaProps: spec.SchemaProps{ + Description: "name is a Human-readable name for the resource. Might not be unique.", + Type: []string{"string"}, + Format: "", + }, + }, + "description": { + SchemaProps: spec.SchemaProps{ + Description: "description is a human-readable description for the resource.", + Type: []string{"string"}, + Format: "", + }, + }, + "serviceID": { + SchemaProps: spec.SchemaProps{ + Description: "serviceID is the ID of the Service to which the resource is associated.", + Type: []string{"string"}, + Format: "", + }, + }, + "projectID": { + SchemaProps: spec.SchemaProps{ + Description: "projectID is the ID of the Project to which the resource is associated.", + Type: []string{"string"}, + Format: "", + }, + }, + "domainID": { + SchemaProps: spec.SchemaProps{ + Description: "domainID is the ID of the Domain to which the resource is associated.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_LimitSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "LimitSpec defines the desired state of an ORC object.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "import": { + SchemaProps: spec.SchemaProps{ + Description: "import refers to an existing OpenStack resource which will be imported instead of creating a new one.", + Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.LimitImport"), + }, + }, + "resource": { + SchemaProps: spec.SchemaProps{ + Description: "resource specifies the desired state of the resource.\n\nresource may not be specified if the management policy is `unmanaged`.\n\nresource must be specified if the management policy is `managed`.", + Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.LimitResourceSpec"), + }, + }, + "managementPolicy": { + SchemaProps: spec.SchemaProps{ + Description: "managementPolicy defines how ORC will treat the object. Valid values are `managed`: ORC will create, update, and delete the resource; `unmanaged`: ORC will import an existing resource, and will not apply updates to it or delete it.", + Type: []string{"string"}, + Format: "", + }, + }, + "managedOptions": { + SchemaProps: spec.SchemaProps{ + Description: "managedOptions specifies options which may be applied to managed objects.", + Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ManagedOptions"), + }, + }, + "resyncPeriod": { + SchemaProps: spec.SchemaProps{ + Description: "resyncPeriod defines how frequently the controller will re-reconcile this resource even when no changes have been detected. This overrides the global default resync period. The value must be a valid Go duration string, e.g. \"10m\", \"1h\". Set to \"0s\" to disable periodic resync for this resource. Very low values may cause excessive OpenStack API load.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Duration"), + }, + }, + "cloudCredentialsRef": { + SchemaProps: spec.SchemaProps{ + Description: "cloudCredentialsRef points to a secret containing OpenStack credentials", + Default: map[string]interface{}{}, + Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.CloudCredentialsReference"), + }, + }, + }, + Required: []string{"cloudCredentialsRef"}, + }, + }, + Dependencies: []string{ + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.CloudCredentialsReference", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.LimitImport", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.LimitResourceSpec", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ManagedOptions", "k8s.io/apimachinery/pkg/apis/meta/v1.Duration"}, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_LimitStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "LimitStatus defines the observed state of an ORC resource.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "conditions": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-map-keys": []interface{}{ + "type", + }, + "x-kubernetes-list-type": "map", + "x-kubernetes-patch-merge-key": "type", + "x-kubernetes-patch-strategy": "merge", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "conditions represents the observed status of the object. Known .status.conditions.type are: \"Available\", \"Progressing\"\n\nAvailable represents the availability of the OpenStack resource. If it is true then the resource is ready for use.\n\nProgressing indicates whether the controller is still attempting to reconcile the current state of the OpenStack resource to the desired state. Progressing will be False either because the desired state has been achieved, or because some terminal error prevents it from ever being achieved and the controller is no longer attempting to reconcile. If Progressing is True, an observer waiting on the resource should continue to wait.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Condition"), + }, + }, + }, + }, + }, + "id": { + SchemaProps: spec.SchemaProps{ + Description: "id is the unique identifier of the OpenStack resource.", + Type: []string{"string"}, + Format: "", + }, + }, + "resource": { + SchemaProps: spec.SchemaProps{ + Description: "resource contains the observed state of the OpenStack resource.", + Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.LimitResourceStatus"), + }, + }, + "lastSyncTime": { + SchemaProps: spec.SchemaProps{ + Description: "lastSyncTime is the timestamp of the last successful reconciliation that fetched state from OpenStack. It is updated each time the controller successfully reads the resource state from the OpenStack API.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.LimitResourceStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.Condition", "k8s.io/apimachinery/pkg/apis/meta/v1.Time"}, + } +} + func schema_openstack_resource_controller_v2_api_v1alpha1_ManagedOptions(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ diff --git a/cmd/resource-generator/main.go b/cmd/resource-generator/main.go index c5d6bc042..0058f5ddb 100644 --- a/cmd/resource-generator/main.go +++ b/cmd/resource-generator/main.go @@ -189,6 +189,9 @@ var resources []templateFields = []templateFields{ { Name: "ApplicationCredential", }, + { + Name: "Limit", + }, } // These resources won't be generated diff --git a/config/crd/bases/openstack.k-orc.cloud_limits.yaml b/config/crd/bases/openstack.k-orc.cloud_limits.yaml new file mode 100644 index 000000000..75fc6e925 --- /dev/null +++ b/config/crd/bases/openstack.k-orc.cloud_limits.yaml @@ -0,0 +1,367 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.20.1 + name: limits.openstack.k-orc.cloud +spec: + group: openstack.k-orc.cloud + names: + categories: + - openstack + kind: Limit + listKind: LimitList + plural: limits + singular: limit + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: Resource ID + jsonPath: .status.id + name: ID + type: string + - description: Availability status of resource + jsonPath: .status.conditions[?(@.type=='Available')].status + name: Available + type: string + - description: Message describing current progress status + jsonPath: .status.conditions[?(@.type=='Progressing')].message + name: Message + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: Limit is the Schema for an ORC resource. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: spec specifies the desired state of the resource. + properties: + cloudCredentialsRef: + description: cloudCredentialsRef points to a secret containing OpenStack + credentials + properties: + cloudName: + description: cloudName specifies the name of the entry in the + clouds.yaml file to use. + maxLength: 256 + minLength: 1 + type: string + secretName: + description: |- + secretName is the name of a secret in the same namespace as the resource being provisioned. + The secret must contain a key named `clouds.yaml` which contains an OpenStack clouds.yaml file. + The secret may optionally contain a key named `cacert` containing a PEM-encoded CA certificate. + maxLength: 253 + minLength: 1 + type: string + required: + - cloudName + - secretName + type: object + import: + description: |- + import refers to an existing OpenStack resource which will be imported instead of + creating a new one. + maxProperties: 1 + minProperties: 1 + properties: + filter: + description: |- + filter contains a resource query which is expected to return a single + result. The controller will continue to retry if filter returns no + results. If filter returns multiple results the controller will set an + error state and will not continue to retry. + minProperties: 1 + properties: + description: + description: description of the existing resource + maxLength: 255 + minLength: 1 + type: string + domainRef: + description: domainRef is a reference to the ORC Domain which + this resource is associated with. + maxLength: 253 + minLength: 1 + type: string + name: + description: name of the existing resource + maxLength: 255 + minLength: 1 + pattern: ^[^,]+$ + type: string + projectRef: + description: projectRef is a reference to the ORC Project + which this resource is associated with. + maxLength: 253 + minLength: 1 + type: string + serviceRef: + description: serviceRef is a reference to the ORC Service + which this resource is associated with. + maxLength: 253 + minLength: 1 + type: string + type: object + id: + description: |- + id contains the unique identifier of an existing OpenStack resource. Note + that when specifying an import by ID, the resource MUST already exist. + The ORC object will enter an error state if the resource does not exist. + format: uuid + maxLength: 36 + type: string + type: object + managedOptions: + description: managedOptions specifies options which may be applied + to managed objects. + properties: + onDelete: + default: delete + description: |- + onDelete specifies the behaviour of the controller when the ORC + object is deleted. Options are `delete` - delete the OpenStack resource; + `detach` - do not delete the OpenStack resource. If not specified, the + default is `delete`. + enum: + - delete + - detach + type: string + type: object + managementPolicy: + default: managed + description: |- + managementPolicy defines how ORC will treat the object. Valid values are + `managed`: ORC will create, update, and delete the resource; `unmanaged`: + ORC will import an existing resource, and will not apply updates to it or + delete it. + enum: + - managed + - unmanaged + type: string + x-kubernetes-validations: + - message: managementPolicy is immutable + rule: self == oldSelf + resource: + description: |- + resource specifies the desired state of the resource. + + resource may not be specified if the management policy is `unmanaged`. + + resource must be specified if the management policy is `managed`. + properties: + description: + description: description is a human-readable description for the + resource. + maxLength: 255 + minLength: 1 + type: string + domainRef: + description: domainRef is a reference to the ORC Domain which + this resource is associated with. + maxLength: 253 + minLength: 1 + type: string + x-kubernetes-validations: + - message: domainRef is immutable + rule: self == oldSelf + name: + description: |- + name will be the name of the created resource. If not specified, the + name of the ORC object will be used. + maxLength: 255 + minLength: 1 + pattern: ^[^,]+$ + type: string + projectRef: + description: projectRef is a reference to the ORC Project which + this resource is associated with. + maxLength: 253 + minLength: 1 + type: string + x-kubernetes-validations: + - message: projectRef is immutable + rule: self == oldSelf + serviceRef: + description: serviceRef is a reference to the ORC Service which + this resource is associated with. + maxLength: 253 + minLength: 1 + type: string + x-kubernetes-validations: + - message: serviceRef is immutable + rule: self == oldSelf + required: + - serviceRef + type: object + resyncPeriod: + description: |- + resyncPeriod defines how frequently the controller will re-reconcile + this resource even when no changes have been detected. This overrides + the global default resync period. The value must be a valid Go duration + string, e.g. "10m", "1h". Set to "0s" to disable periodic resync for + this resource. Very low values may cause excessive OpenStack API load. + type: string + required: + - cloudCredentialsRef + type: object + x-kubernetes-validations: + - message: resource must be specified when policy is managed + rule: 'self.managementPolicy == ''managed'' ? has(self.resource) : true' + - message: import may not be specified when policy is managed + rule: 'self.managementPolicy == ''managed'' ? !has(self.__import__) + : true' + - message: resource may not be specified when policy is unmanaged + rule: 'self.managementPolicy == ''unmanaged'' ? !has(self.resource) + : true' + - message: import must be specified when policy is unmanaged + rule: 'self.managementPolicy == ''unmanaged'' ? has(self.__import__) + : true' + - message: managedOptions may only be provided when policy is managed + rule: 'has(self.managedOptions) ? self.managementPolicy == ''managed'' + : true' + status: + description: status defines the observed state of the resource. + properties: + conditions: + description: |- + conditions represents the observed status of the object. + Known .status.conditions.type are: "Available", "Progressing" + + Available represents the availability of the OpenStack resource. If it is + true then the resource is ready for use. + + Progressing indicates whether the controller is still attempting to + reconcile the current state of the OpenStack resource to the desired + state. Progressing will be False either because the desired state has + been achieved, or because some terminal error prevents it from ever being + achieved and the controller is no longer attempting to reconcile. If + Progressing is True, an observer waiting on the resource should continue + to wait. + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 32 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + id: + description: id is the unique identifier of the OpenStack resource. + maxLength: 1024 + type: string + lastSyncTime: + description: |- + lastSyncTime is the timestamp of the last successful reconciliation + that fetched state from OpenStack. It is updated each time the + controller successfully reads the resource state from the OpenStack + API. + format: date-time + type: string + resource: + description: resource contains the observed state of the OpenStack + resource. + properties: + description: + description: description is a human-readable description for the + resource. + maxLength: 1024 + type: string + domainID: + description: domainID is the ID of the Domain to which the resource + is associated. + maxLength: 1024 + type: string + name: + description: name is a Human-readable name for the resource. Might + not be unique. + maxLength: 1024 + type: string + projectID: + description: projectID is the ID of the Project to which the resource + is associated. + maxLength: 1024 + type: string + serviceID: + description: serviceID is the ID of the Service to which the resource + is associated. + maxLength: 1024 + type: string + type: object + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml index 9c133d11c..852e50d7b 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -12,6 +12,7 @@ resources: - bases/openstack.k-orc.cloud_groups.yaml - bases/openstack.k-orc.cloud_images.yaml - bases/openstack.k-orc.cloud_keypairs.yaml +- bases/openstack.k-orc.cloud_limits.yaml - bases/openstack.k-orc.cloud_networks.yaml - bases/openstack.k-orc.cloud_ports.yaml - bases/openstack.k-orc.cloud_projects.yaml diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index a04a488e2..96176412b 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -26,6 +26,7 @@ rules: - groups - images - keypairs + - limits - networks - ports - projects @@ -63,6 +64,7 @@ rules: - groups/status - images/status - keypairs/status + - limits/status - networks/status - ports/status - projects/status diff --git a/config/samples/kustomization.yaml b/config/samples/kustomization.yaml index ceb05e15e..cbcb54b8e 100644 --- a/config/samples/kustomization.yaml +++ b/config/samples/kustomization.yaml @@ -10,6 +10,7 @@ resources: - openstack_v1alpha1_group.yaml - openstack_v1alpha1_image.yaml - openstack_v1alpha1_keypair.yaml +- openstack_v1alpha1_limit.yaml - openstack_v1alpha1_network.yaml - openstack_v1alpha1_port.yaml - openstack_v1alpha1_project.yaml diff --git a/internal/controllers/limit/zz_generated.adapter.go b/internal/controllers/limit/zz_generated.adapter.go new file mode 100644 index 000000000..c0bba91bd --- /dev/null +++ b/internal/controllers/limit/zz_generated.adapter.go @@ -0,0 +1,98 @@ +// Code generated by resource-generator. DO NOT EDIT. +/* +Copyright The ORC Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package limit + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + orcv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" + "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/interfaces" +) + +// Fundamental types +type ( + orcObjectT = orcv1alpha1.Limit + orcObjectListT = orcv1alpha1.LimitList + resourceSpecT = orcv1alpha1.LimitResourceSpec + filterT = orcv1alpha1.LimitFilter +) + +// Derived types +type ( + orcObjectPT = *orcObjectT + adapterI = interfaces.APIObjectAdapter[orcObjectPT, resourceSpecT, filterT] + adapterT = limitAdapter +) + +type limitAdapter struct { + *orcv1alpha1.Limit +} + +var _ adapterI = &adapterT{} + +func (f adapterT) GetObject() orcObjectPT { + return f.Limit +} + +func (f adapterT) GetManagementPolicy() orcv1alpha1.ManagementPolicy { + return f.Spec.ManagementPolicy +} + +func (f adapterT) GetManagedOptions() *orcv1alpha1.ManagedOptions { + return f.Spec.ManagedOptions +} + +func (f adapterT) GetResyncPeriod() *metav1.Duration { + return f.Spec.ResyncPeriod +} + +func (f adapterT) GetLastSyncTime() *metav1.Time { + return f.Status.LastSyncTime +} + +func (f adapterT) GetStatusID() *string { + return f.Status.ID +} + +func (f adapterT) GetResourceSpec() *resourceSpecT { + return f.Spec.Resource +} + +func (f adapterT) GetImportID() *string { + if f.Spec.Import == nil { + return nil + } + return f.Spec.Import.ID +} + +func (f adapterT) GetImportFilter() *filterT { + if f.Spec.Import == nil { + return nil + } + return f.Spec.Import.Filter +} + +// getResourceName returns the name of the OpenStack resource we should use. +// This method is not implemented as part of APIObjectAdapter as it is intended +// to be used by resource actuators, which don't use the adapter. +func getResourceName(orcObject orcObjectPT) string { + if orcObject.Spec.Resource.Name != nil { + return string(*orcObject.Spec.Resource.Name) + } + return orcObject.Name +} diff --git a/internal/controllers/limit/zz_generated.controller.go b/internal/controllers/limit/zz_generated.controller.go new file mode 100644 index 000000000..ff48940a6 --- /dev/null +++ b/internal/controllers/limit/zz_generated.controller.go @@ -0,0 +1,45 @@ +// Code generated by resource-generator. DO NOT EDIT. +/* +Copyright The ORC Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package limit + +import ( + corev1 "k8s.io/api/core/v1" + + "github.com/k-orc/openstack-resource-controller/v2/internal/util/dependency" + orcstrings "github.com/k-orc/openstack-resource-controller/v2/internal/util/strings" +) + +var ( + // NOTE: controllerName must be defined in any controller using this template + + // finalizer is the string this controller adds to an object's Finalizers + finalizer = orcstrings.GetFinalizerName(controllerName) + + // externalObjectFieldOwner is the field owner we use when using + // server-side-apply on objects we don't control + externalObjectFieldOwner = orcstrings.GetSSAFieldOwner(controllerName) + + credentialsDependency = dependency.NewDeletionGuardDependency[*orcObjectListT, *corev1.Secret]( + "spec.cloudCredentialsRef.secretName", + func(obj orcObjectPT) []string { + return []string{obj.Spec.CloudCredentialsRef.SecretName} + }, + finalizer, externalObjectFieldOwner, + dependency.OverrideDependencyName("credentials"), + ) +) diff --git a/internal/osclients/mock/doc.go b/internal/osclients/mock/doc.go index 466a548e6..7bafbb2a8 100644 --- a/internal/osclients/mock/doc.go +++ b/internal/osclients/mock/doc.go @@ -53,6 +53,9 @@ import ( //go:generate mockgen -package mock -destination=keypair.go -source=../keypair.go github.com/k-orc/openstack-resource-controller/internal/osclients/mock KeyPairClient //go:generate /usr/bin/env bash -c "cat ../../../hack/boilerplate.go.txt keypair.go > _keypair.go && mv _keypair.go keypair.go" +//go:generate mockgen -package mock -destination=limit.go -source=../limit.go github.com/k-orc/openstack-resource-controller/internal/osclients/mock LimitClient +//go:generate /usr/bin/env bash -c "cat ../../../hack/boilerplate.go.txt limit.go > _limit.go && mv _limit.go limit.go" + //go:generate mockgen -package mock -destination=role.go -source=../role.go github.com/k-orc/openstack-resource-controller/internal/osclients/mock RoleClient //go:generate /usr/bin/env bash -c "cat ../../../hack/boilerplate.go.txt role.go > _role.go && mv _role.go role.go" diff --git a/internal/osclients/mock/limit.go b/internal/osclients/mock/limit.go new file mode 100644 index 000000000..74466ff3d --- /dev/null +++ b/internal/osclients/mock/limit.go @@ -0,0 +1,131 @@ +/* +Copyright The ORC Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +// Code generated by MockGen. DO NOT EDIT. +// Source: ../limit.go +// +// Generated by this command: +// +// mockgen -package mock -destination=limit.go -source=../limit.go github.com/k-orc/openstack-resource-controller/internal/osclients/mock LimitClient +// + +// Package mock is a generated GoMock package. +package mock + +import ( + context "context" + iter "iter" + reflect "reflect" + + limits "github.com/gophercloud/gophercloud/v2/openstack/identity/v3/limits" + gomock "go.uber.org/mock/gomock" +) + +// MockLimitClient is a mock of LimitClient interface. +type MockLimitClient struct { + ctrl *gomock.Controller + recorder *MockLimitClientMockRecorder + isgomock struct{} +} + +// MockLimitClientMockRecorder is the mock recorder for MockLimitClient. +type MockLimitClientMockRecorder struct { + mock *MockLimitClient +} + +// NewMockLimitClient creates a new mock instance. +func NewMockLimitClient(ctrl *gomock.Controller) *MockLimitClient { + mock := &MockLimitClient{ctrl: ctrl} + mock.recorder = &MockLimitClientMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockLimitClient) EXPECT() *MockLimitClientMockRecorder { + return m.recorder +} + +// CreateLimit mocks base method. +func (m *MockLimitClient) CreateLimit(ctx context.Context, opts limits.CreateOptsBuilder) (*limits.Limit, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateLimit", ctx, opts) + ret0, _ := ret[0].(*limits.Limit) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CreateLimit indicates an expected call of CreateLimit. +func (mr *MockLimitClientMockRecorder) CreateLimit(ctx, opts any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateLimit", reflect.TypeOf((*MockLimitClient)(nil).CreateLimit), ctx, opts) +} + +// DeleteLimit mocks base method. +func (m *MockLimitClient) DeleteLimit(ctx context.Context, resourceID string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteLimit", ctx, resourceID) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteLimit indicates an expected call of DeleteLimit. +func (mr *MockLimitClientMockRecorder) DeleteLimit(ctx, resourceID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteLimit", reflect.TypeOf((*MockLimitClient)(nil).DeleteLimit), ctx, resourceID) +} + +// GetLimit mocks base method. +func (m *MockLimitClient) GetLimit(ctx context.Context, resourceID string) (*limits.Limit, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetLimit", ctx, resourceID) + ret0, _ := ret[0].(*limits.Limit) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetLimit indicates an expected call of GetLimit. +func (mr *MockLimitClientMockRecorder) GetLimit(ctx, resourceID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLimit", reflect.TypeOf((*MockLimitClient)(nil).GetLimit), ctx, resourceID) +} + +// ListLimits mocks base method. +func (m *MockLimitClient) ListLimits(ctx context.Context, listOpts limits.ListOptsBuilder) iter.Seq2[*limits.Limit, error] { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListLimits", ctx, listOpts) + ret0, _ := ret[0].(iter.Seq2[*limits.Limit, error]) + return ret0 +} + +// ListLimits indicates an expected call of ListLimits. +func (mr *MockLimitClientMockRecorder) ListLimits(ctx, listOpts any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListLimits", reflect.TypeOf((*MockLimitClient)(nil).ListLimits), ctx, listOpts) +} + +// UpdateLimit mocks base method. +func (m *MockLimitClient) UpdateLimit(ctx context.Context, id string, opts limits.UpdateOptsBuilder) (*limits.Limit, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateLimit", ctx, id, opts) + ret0, _ := ret[0].(*limits.Limit) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// UpdateLimit indicates an expected call of UpdateLimit. +func (mr *MockLimitClientMockRecorder) UpdateLimit(ctx, id, opts any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateLimit", reflect.TypeOf((*MockLimitClient)(nil).UpdateLimit), ctx, id, opts) +} diff --git a/internal/scope/mock.go b/internal/scope/mock.go index e57e4da58..e7da1992d 100644 --- a/internal/scope/mock.go +++ b/internal/scope/mock.go @@ -51,6 +51,7 @@ type MockScopeFactory struct { VolumeClient *mock.MockVolumeClient VolumeTypeClient *mock.MockVolumeTypeClient ShareNetworkClient *mock.MockShareNetworkClient + LimitClient *mock.MockLimitClient clientScopeCreateError error } @@ -73,6 +74,7 @@ func NewMockScopeFactory(mockCtrl *gomock.Controller) *MockScopeFactory { sharenetworkClient := mock.NewMockShareNetworkClient(mockCtrl) volumeClient := mock.NewMockVolumeClient(mockCtrl) volumetypeClient := mock.NewMockVolumeTypeClient(mockCtrl) + limitClient := mock.NewMockLimitClient(mockCtrl) return &MockScopeFactory{ AddressScope: addressScope, @@ -92,6 +94,7 @@ func NewMockScopeFactory(mockCtrl *gomock.Controller) *MockScopeFactory { UserClient: userClient, VolumeClient: volumeClient, VolumeTypeClient: volumetypeClient, + LimitClient: limitClient, } } @@ -177,3 +180,7 @@ func (f *MockScopeFactory) NewApplicationCredentialClient() (osclients.Applicati func (f *MockScopeFactory) ExtractToken() (*tokens.Token, error) { return &tokens.Token{ExpiresAt: time.Now().Add(24 * time.Hour)}, nil } + +func (f *MockScopeFactory) NewLimitClient() (osclients.LimitClient, error) { + return f.LimitClient, nil +} diff --git a/internal/scope/provider.go b/internal/scope/provider.go index f1207bd6c..cdddb3b36 100644 --- a/internal/scope/provider.go +++ b/internal/scope/provider.go @@ -219,6 +219,10 @@ func (s *providerScope) ExtractToken() (*tokens.Token, error) { return tokens.Get(context.TODO(), client, s.providerClient.Token()).ExtractToken() } +func (s *providerScope) NewLimitClient() (clients.LimitClient, error) { + return clients.NewLimitClient(s.providerClient, s.providerClientOpts) +} + func NewProviderClient(cloud clientconfig.Cloud, caCert []byte, logger logr.Logger) (*gophercloud.ProviderClient, *clientconfig.ClientOpts, error) { clientOpts := new(clientconfig.ClientOpts) diff --git a/internal/scope/scope.go b/internal/scope/scope.go index 4f093fe9a..9c7a65d2a 100644 --- a/internal/scope/scope.go +++ b/internal/scope/scope.go @@ -66,6 +66,7 @@ type Scope interface { NewVolumeClient() (osclients.VolumeClient, error) NewVolumeTypeClient() (osclients.VolumeTypeClient, error) ExtractToken() (*tokens.Token, error) + NewLimitClient() (osclients.LimitClient, error) } // WithLogger extends Scope with a logger. diff --git a/kuttl-test.yaml b/kuttl-test.yaml index 90fc98dde..24280a8df 100644 --- a/kuttl-test.yaml +++ b/kuttl-test.yaml @@ -11,6 +11,7 @@ testDirs: - ./internal/controllers/group/tests/ - ./internal/controllers/image/tests/ - ./internal/controllers/keypair/tests/ +- ./internal/controllers/limit/tests/ - ./internal/controllers/network/tests/ - ./internal/controllers/port/tests/ - ./internal/controllers/project/tests/ diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/limit.go b/pkg/clients/applyconfiguration/api/v1alpha1/limit.go new file mode 100644 index 000000000..4345aa970 --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/limit.go @@ -0,0 +1,281 @@ +/* +Copyright The ORC Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + apiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" + internal "github.com/k-orc/openstack-resource-controller/v2/pkg/clients/applyconfiguration/internal" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + managedfields "k8s.io/apimachinery/pkg/util/managedfields" + v1 "k8s.io/client-go/applyconfigurations/meta/v1" +) + +// LimitApplyConfiguration represents a declarative configuration of the Limit type for use +// with apply. +type LimitApplyConfiguration struct { + v1.TypeMetaApplyConfiguration `json:",inline"` + *v1.ObjectMetaApplyConfiguration `json:"metadata,omitempty"` + Spec *LimitSpecApplyConfiguration `json:"spec,omitempty"` + Status *LimitStatusApplyConfiguration `json:"status,omitempty"` +} + +// Limit constructs a declarative configuration of the Limit type for use with +// apply. +func Limit(name, namespace string) *LimitApplyConfiguration { + b := &LimitApplyConfiguration{} + b.WithName(name) + b.WithNamespace(namespace) + b.WithKind("Limit") + b.WithAPIVersion("openstack.k-orc.cloud/v1alpha1") + return b +} + +// ExtractLimit extracts the applied configuration owned by fieldManager from +// limit. If no managedFields are found in limit for fieldManager, a +// LimitApplyConfiguration is returned with only the Name, Namespace (if applicable), +// APIVersion and Kind populated. It is possible that no managed fields were found for because other +// field managers have taken ownership of all the fields previously owned by fieldManager, or because +// the fieldManager never owned fields any fields. +// limit must be a unmodified Limit API object that was retrieved from the Kubernetes API. +// ExtractLimit provides a way to perform a extract/modify-in-place/apply workflow. +// Note that an extracted apply configuration will contain fewer fields than what the fieldManager previously +// applied if another fieldManager has updated or force applied any of the previously applied fields. +// Experimental! +func ExtractLimit(limit *apiv1alpha1.Limit, fieldManager string) (*LimitApplyConfiguration, error) { + return extractLimit(limit, fieldManager, "") +} + +// ExtractLimitStatus is the same as ExtractLimit except +// that it extracts the status subresource applied configuration. +// Experimental! +func ExtractLimitStatus(limit *apiv1alpha1.Limit, fieldManager string) (*LimitApplyConfiguration, error) { + return extractLimit(limit, fieldManager, "status") +} + +func extractLimit(limit *apiv1alpha1.Limit, fieldManager string, subresource string) (*LimitApplyConfiguration, error) { + b := &LimitApplyConfiguration{} + err := managedfields.ExtractInto(limit, internal.Parser().Type("com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.Limit"), fieldManager, b, subresource) + if err != nil { + return nil, err + } + b.WithName(limit.Name) + b.WithNamespace(limit.Namespace) + + b.WithKind("Limit") + b.WithAPIVersion("openstack.k-orc.cloud/v1alpha1") + return b, nil +} +func (b LimitApplyConfiguration) IsApplyConfiguration() {} + +// WithKind sets the Kind field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Kind field is set to the value of the last call. +func (b *LimitApplyConfiguration) WithKind(value string) *LimitApplyConfiguration { + b.TypeMetaApplyConfiguration.Kind = &value + return b +} + +// WithAPIVersion sets the APIVersion field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the APIVersion field is set to the value of the last call. +func (b *LimitApplyConfiguration) WithAPIVersion(value string) *LimitApplyConfiguration { + b.TypeMetaApplyConfiguration.APIVersion = &value + return b +} + +// WithName sets the Name field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Name field is set to the value of the last call. +func (b *LimitApplyConfiguration) WithName(value string) *LimitApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.Name = &value + return b +} + +// WithGenerateName sets the GenerateName field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the GenerateName field is set to the value of the last call. +func (b *LimitApplyConfiguration) WithGenerateName(value string) *LimitApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.GenerateName = &value + return b +} + +// WithNamespace sets the Namespace field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Namespace field is set to the value of the last call. +func (b *LimitApplyConfiguration) WithNamespace(value string) *LimitApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.Namespace = &value + return b +} + +// WithUID sets the UID field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the UID field is set to the value of the last call. +func (b *LimitApplyConfiguration) WithUID(value types.UID) *LimitApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.UID = &value + return b +} + +// WithResourceVersion sets the ResourceVersion field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ResourceVersion field is set to the value of the last call. +func (b *LimitApplyConfiguration) WithResourceVersion(value string) *LimitApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.ResourceVersion = &value + return b +} + +// WithGeneration sets the Generation field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Generation field is set to the value of the last call. +func (b *LimitApplyConfiguration) WithGeneration(value int64) *LimitApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.Generation = &value + return b +} + +// WithCreationTimestamp sets the CreationTimestamp field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the CreationTimestamp field is set to the value of the last call. +func (b *LimitApplyConfiguration) WithCreationTimestamp(value metav1.Time) *LimitApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.CreationTimestamp = &value + return b +} + +// WithDeletionTimestamp sets the DeletionTimestamp field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DeletionTimestamp field is set to the value of the last call. +func (b *LimitApplyConfiguration) WithDeletionTimestamp(value metav1.Time) *LimitApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.DeletionTimestamp = &value + return b +} + +// WithDeletionGracePeriodSeconds sets the DeletionGracePeriodSeconds field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DeletionGracePeriodSeconds field is set to the value of the last call. +func (b *LimitApplyConfiguration) WithDeletionGracePeriodSeconds(value int64) *LimitApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.DeletionGracePeriodSeconds = &value + return b +} + +// WithLabels puts the entries into the Labels field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the Labels field, +// overwriting an existing map entries in Labels field with the same key. +func (b *LimitApplyConfiguration) WithLabels(entries map[string]string) *LimitApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + if b.ObjectMetaApplyConfiguration.Labels == nil && len(entries) > 0 { + b.ObjectMetaApplyConfiguration.Labels = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.ObjectMetaApplyConfiguration.Labels[k] = v + } + return b +} + +// WithAnnotations puts the entries into the Annotations field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the Annotations field, +// overwriting an existing map entries in Annotations field with the same key. +func (b *LimitApplyConfiguration) WithAnnotations(entries map[string]string) *LimitApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + if b.ObjectMetaApplyConfiguration.Annotations == nil && len(entries) > 0 { + b.ObjectMetaApplyConfiguration.Annotations = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.ObjectMetaApplyConfiguration.Annotations[k] = v + } + return b +} + +// WithOwnerReferences adds the given value to the OwnerReferences field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the OwnerReferences field. +func (b *LimitApplyConfiguration) WithOwnerReferences(values ...*v1.OwnerReferenceApplyConfiguration) *LimitApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + for i := range values { + if values[i] == nil { + panic("nil value passed to WithOwnerReferences") + } + b.ObjectMetaApplyConfiguration.OwnerReferences = append(b.ObjectMetaApplyConfiguration.OwnerReferences, *values[i]) + } + return b +} + +// WithFinalizers adds the given value to the Finalizers field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Finalizers field. +func (b *LimitApplyConfiguration) WithFinalizers(values ...string) *LimitApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + for i := range values { + b.ObjectMetaApplyConfiguration.Finalizers = append(b.ObjectMetaApplyConfiguration.Finalizers, values[i]) + } + return b +} + +func (b *LimitApplyConfiguration) ensureObjectMetaApplyConfigurationExists() { + if b.ObjectMetaApplyConfiguration == nil { + b.ObjectMetaApplyConfiguration = &v1.ObjectMetaApplyConfiguration{} + } +} + +// WithSpec sets the Spec field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Spec field is set to the value of the last call. +func (b *LimitApplyConfiguration) WithSpec(value *LimitSpecApplyConfiguration) *LimitApplyConfiguration { + b.Spec = value + return b +} + +// WithStatus sets the Status field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Status field is set to the value of the last call. +func (b *LimitApplyConfiguration) WithStatus(value *LimitStatusApplyConfiguration) *LimitApplyConfiguration { + b.Status = value + return b +} + +// GetKind retrieves the value of the Kind field in the declarative configuration. +func (b *LimitApplyConfiguration) GetKind() *string { + return b.TypeMetaApplyConfiguration.Kind +} + +// GetAPIVersion retrieves the value of the APIVersion field in the declarative configuration. +func (b *LimitApplyConfiguration) GetAPIVersion() *string { + return b.TypeMetaApplyConfiguration.APIVersion +} + +// GetName retrieves the value of the Name field in the declarative configuration. +func (b *LimitApplyConfiguration) GetName() *string { + b.ensureObjectMetaApplyConfigurationExists() + return b.ObjectMetaApplyConfiguration.Name +} + +// GetNamespace retrieves the value of the Namespace field in the declarative configuration. +func (b *LimitApplyConfiguration) GetNamespace() *string { + b.ensureObjectMetaApplyConfigurationExists() + return b.ObjectMetaApplyConfiguration.Namespace +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/limitfilter.go b/pkg/clients/applyconfiguration/api/v1alpha1/limitfilter.go new file mode 100644 index 000000000..d7b952c16 --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/limitfilter.go @@ -0,0 +1,79 @@ +/* +Copyright The ORC Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + apiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" +) + +// LimitFilterApplyConfiguration represents a declarative configuration of the LimitFilter type for use +// with apply. +type LimitFilterApplyConfiguration struct { + Name *apiv1alpha1.OpenStackName `json:"name,omitempty"` + Description *string `json:"description,omitempty"` + ServiceRef *apiv1alpha1.KubernetesNameRef `json:"serviceRef,omitempty"` + ProjectRef *apiv1alpha1.KubernetesNameRef `json:"projectRef,omitempty"` + DomainRef *apiv1alpha1.KubernetesNameRef `json:"domainRef,omitempty"` +} + +// LimitFilterApplyConfiguration constructs a declarative configuration of the LimitFilter type for use with +// apply. +func LimitFilter() *LimitFilterApplyConfiguration { + return &LimitFilterApplyConfiguration{} +} + +// WithName sets the Name field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Name field is set to the value of the last call. +func (b *LimitFilterApplyConfiguration) WithName(value apiv1alpha1.OpenStackName) *LimitFilterApplyConfiguration { + b.Name = &value + return b +} + +// WithDescription sets the Description field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Description field is set to the value of the last call. +func (b *LimitFilterApplyConfiguration) WithDescription(value string) *LimitFilterApplyConfiguration { + b.Description = &value + return b +} + +// WithServiceRef sets the ServiceRef field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ServiceRef field is set to the value of the last call. +func (b *LimitFilterApplyConfiguration) WithServiceRef(value apiv1alpha1.KubernetesNameRef) *LimitFilterApplyConfiguration { + b.ServiceRef = &value + return b +} + +// WithProjectRef sets the ProjectRef field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ProjectRef field is set to the value of the last call. +func (b *LimitFilterApplyConfiguration) WithProjectRef(value apiv1alpha1.KubernetesNameRef) *LimitFilterApplyConfiguration { + b.ProjectRef = &value + return b +} + +// WithDomainRef sets the DomainRef field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DomainRef field is set to the value of the last call. +func (b *LimitFilterApplyConfiguration) WithDomainRef(value apiv1alpha1.KubernetesNameRef) *LimitFilterApplyConfiguration { + b.DomainRef = &value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/limitimport.go b/pkg/clients/applyconfiguration/api/v1alpha1/limitimport.go new file mode 100644 index 000000000..e0e5de8d8 --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/limitimport.go @@ -0,0 +1,48 @@ +/* +Copyright The ORC Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha1 + +// LimitImportApplyConfiguration represents a declarative configuration of the LimitImport type for use +// with apply. +type LimitImportApplyConfiguration struct { + ID *string `json:"id,omitempty"` + Filter *LimitFilterApplyConfiguration `json:"filter,omitempty"` +} + +// LimitImportApplyConfiguration constructs a declarative configuration of the LimitImport type for use with +// apply. +func LimitImport() *LimitImportApplyConfiguration { + return &LimitImportApplyConfiguration{} +} + +// WithID sets the ID field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ID field is set to the value of the last call. +func (b *LimitImportApplyConfiguration) WithID(value string) *LimitImportApplyConfiguration { + b.ID = &value + return b +} + +// WithFilter sets the Filter field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Filter field is set to the value of the last call. +func (b *LimitImportApplyConfiguration) WithFilter(value *LimitFilterApplyConfiguration) *LimitImportApplyConfiguration { + b.Filter = value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/limitresourcespec.go b/pkg/clients/applyconfiguration/api/v1alpha1/limitresourcespec.go new file mode 100644 index 000000000..8dedb1105 --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/limitresourcespec.go @@ -0,0 +1,79 @@ +/* +Copyright The ORC Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + apiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" +) + +// LimitResourceSpecApplyConfiguration represents a declarative configuration of the LimitResourceSpec type for use +// with apply. +type LimitResourceSpecApplyConfiguration struct { + Name *apiv1alpha1.OpenStackName `json:"name,omitempty"` + Description *string `json:"description,omitempty"` + ServiceRef *apiv1alpha1.KubernetesNameRef `json:"serviceRef,omitempty"` + ProjectRef *apiv1alpha1.KubernetesNameRef `json:"projectRef,omitempty"` + DomainRef *apiv1alpha1.KubernetesNameRef `json:"domainRef,omitempty"` +} + +// LimitResourceSpecApplyConfiguration constructs a declarative configuration of the LimitResourceSpec type for use with +// apply. +func LimitResourceSpec() *LimitResourceSpecApplyConfiguration { + return &LimitResourceSpecApplyConfiguration{} +} + +// WithName sets the Name field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Name field is set to the value of the last call. +func (b *LimitResourceSpecApplyConfiguration) WithName(value apiv1alpha1.OpenStackName) *LimitResourceSpecApplyConfiguration { + b.Name = &value + return b +} + +// WithDescription sets the Description field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Description field is set to the value of the last call. +func (b *LimitResourceSpecApplyConfiguration) WithDescription(value string) *LimitResourceSpecApplyConfiguration { + b.Description = &value + return b +} + +// WithServiceRef sets the ServiceRef field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ServiceRef field is set to the value of the last call. +func (b *LimitResourceSpecApplyConfiguration) WithServiceRef(value apiv1alpha1.KubernetesNameRef) *LimitResourceSpecApplyConfiguration { + b.ServiceRef = &value + return b +} + +// WithProjectRef sets the ProjectRef field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ProjectRef field is set to the value of the last call. +func (b *LimitResourceSpecApplyConfiguration) WithProjectRef(value apiv1alpha1.KubernetesNameRef) *LimitResourceSpecApplyConfiguration { + b.ProjectRef = &value + return b +} + +// WithDomainRef sets the DomainRef field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DomainRef field is set to the value of the last call. +func (b *LimitResourceSpecApplyConfiguration) WithDomainRef(value apiv1alpha1.KubernetesNameRef) *LimitResourceSpecApplyConfiguration { + b.DomainRef = &value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/limitresourcestatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/limitresourcestatus.go new file mode 100644 index 000000000..4046a51da --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/limitresourcestatus.go @@ -0,0 +1,75 @@ +/* +Copyright The ORC Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha1 + +// LimitResourceStatusApplyConfiguration represents a declarative configuration of the LimitResourceStatus type for use +// with apply. +type LimitResourceStatusApplyConfiguration struct { + Name *string `json:"name,omitempty"` + Description *string `json:"description,omitempty"` + ServiceID *string `json:"serviceID,omitempty"` + ProjectID *string `json:"projectID,omitempty"` + DomainID *string `json:"domainID,omitempty"` +} + +// LimitResourceStatusApplyConfiguration constructs a declarative configuration of the LimitResourceStatus type for use with +// apply. +func LimitResourceStatus() *LimitResourceStatusApplyConfiguration { + return &LimitResourceStatusApplyConfiguration{} +} + +// WithName sets the Name field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Name field is set to the value of the last call. +func (b *LimitResourceStatusApplyConfiguration) WithName(value string) *LimitResourceStatusApplyConfiguration { + b.Name = &value + return b +} + +// WithDescription sets the Description field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Description field is set to the value of the last call. +func (b *LimitResourceStatusApplyConfiguration) WithDescription(value string) *LimitResourceStatusApplyConfiguration { + b.Description = &value + return b +} + +// WithServiceID sets the ServiceID field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ServiceID field is set to the value of the last call. +func (b *LimitResourceStatusApplyConfiguration) WithServiceID(value string) *LimitResourceStatusApplyConfiguration { + b.ServiceID = &value + return b +} + +// WithProjectID sets the ProjectID field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ProjectID field is set to the value of the last call. +func (b *LimitResourceStatusApplyConfiguration) WithProjectID(value string) *LimitResourceStatusApplyConfiguration { + b.ProjectID = &value + return b +} + +// WithDomainID sets the DomainID field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DomainID field is set to the value of the last call. +func (b *LimitResourceStatusApplyConfiguration) WithDomainID(value string) *LimitResourceStatusApplyConfiguration { + b.DomainID = &value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/limitspec.go b/pkg/clients/applyconfiguration/api/v1alpha1/limitspec.go new file mode 100644 index 000000000..1ad557665 --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/limitspec.go @@ -0,0 +1,89 @@ +/* +Copyright The ORC Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + apiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// LimitSpecApplyConfiguration represents a declarative configuration of the LimitSpec type for use +// with apply. +type LimitSpecApplyConfiguration struct { + Import *LimitImportApplyConfiguration `json:"import,omitempty"` + Resource *LimitResourceSpecApplyConfiguration `json:"resource,omitempty"` + ManagementPolicy *apiv1alpha1.ManagementPolicy `json:"managementPolicy,omitempty"` + ManagedOptions *ManagedOptionsApplyConfiguration `json:"managedOptions,omitempty"` + ResyncPeriod *v1.Duration `json:"resyncPeriod,omitempty"` + CloudCredentialsRef *CloudCredentialsReferenceApplyConfiguration `json:"cloudCredentialsRef,omitempty"` +} + +// LimitSpecApplyConfiguration constructs a declarative configuration of the LimitSpec type for use with +// apply. +func LimitSpec() *LimitSpecApplyConfiguration { + return &LimitSpecApplyConfiguration{} +} + +// WithImport sets the Import field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Import field is set to the value of the last call. +func (b *LimitSpecApplyConfiguration) WithImport(value *LimitImportApplyConfiguration) *LimitSpecApplyConfiguration { + b.Import = value + return b +} + +// WithResource sets the Resource field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Resource field is set to the value of the last call. +func (b *LimitSpecApplyConfiguration) WithResource(value *LimitResourceSpecApplyConfiguration) *LimitSpecApplyConfiguration { + b.Resource = value + return b +} + +// WithManagementPolicy sets the ManagementPolicy field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ManagementPolicy field is set to the value of the last call. +func (b *LimitSpecApplyConfiguration) WithManagementPolicy(value apiv1alpha1.ManagementPolicy) *LimitSpecApplyConfiguration { + b.ManagementPolicy = &value + return b +} + +// WithManagedOptions sets the ManagedOptions field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ManagedOptions field is set to the value of the last call. +func (b *LimitSpecApplyConfiguration) WithManagedOptions(value *ManagedOptionsApplyConfiguration) *LimitSpecApplyConfiguration { + b.ManagedOptions = value + return b +} + +// WithResyncPeriod sets the ResyncPeriod field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ResyncPeriod field is set to the value of the last call. +func (b *LimitSpecApplyConfiguration) WithResyncPeriod(value v1.Duration) *LimitSpecApplyConfiguration { + b.ResyncPeriod = &value + return b +} + +// WithCloudCredentialsRef sets the CloudCredentialsRef field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the CloudCredentialsRef field is set to the value of the last call. +func (b *LimitSpecApplyConfiguration) WithCloudCredentialsRef(value *CloudCredentialsReferenceApplyConfiguration) *LimitSpecApplyConfiguration { + b.CloudCredentialsRef = value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/limitstatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/limitstatus.go new file mode 100644 index 000000000..fbd824f76 --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/limitstatus.go @@ -0,0 +1,76 @@ +/* +Copyright The ORC Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + v1 "k8s.io/client-go/applyconfigurations/meta/v1" +) + +// LimitStatusApplyConfiguration represents a declarative configuration of the LimitStatus type for use +// with apply. +type LimitStatusApplyConfiguration struct { + Conditions []v1.ConditionApplyConfiguration `json:"conditions,omitempty"` + ID *string `json:"id,omitempty"` + Resource *LimitResourceStatusApplyConfiguration `json:"resource,omitempty"` + LastSyncTime *metav1.Time `json:"lastSyncTime,omitempty"` +} + +// LimitStatusApplyConfiguration constructs a declarative configuration of the LimitStatus type for use with +// apply. +func LimitStatus() *LimitStatusApplyConfiguration { + return &LimitStatusApplyConfiguration{} +} + +// WithConditions adds the given value to the Conditions field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Conditions field. +func (b *LimitStatusApplyConfiguration) WithConditions(values ...*v1.ConditionApplyConfiguration) *LimitStatusApplyConfiguration { + for i := range values { + if values[i] == nil { + panic("nil value passed to WithConditions") + } + b.Conditions = append(b.Conditions, *values[i]) + } + return b +} + +// WithID sets the ID field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ID field is set to the value of the last call. +func (b *LimitStatusApplyConfiguration) WithID(value string) *LimitStatusApplyConfiguration { + b.ID = &value + return b +} + +// WithResource sets the Resource field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Resource field is set to the value of the last call. +func (b *LimitStatusApplyConfiguration) WithResource(value *LimitResourceStatusApplyConfiguration) *LimitStatusApplyConfiguration { + b.Resource = value + return b +} + +// WithLastSyncTime sets the LastSyncTime field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the LastSyncTime field is set to the value of the last call. +func (b *LimitStatusApplyConfiguration) WithLastSyncTime(value metav1.Time) *LimitStatusApplyConfiguration { + b.LastSyncTime = &value + return b +} diff --git a/pkg/clients/applyconfiguration/internal/internal.go b/pkg/clients/applyconfiguration/internal/internal.go index 9fc18f739..dee3289bd 100644 --- a/pkg/clients/applyconfiguration/internal/internal.go +++ b/pkg/clients/applyconfiguration/internal/internal.go @@ -1505,6 +1505,132 @@ var schemaYAML = typed.YAMLObject(`types: - name: resource type: namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.KeyPairResourceStatus +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.Limit + map: + fields: + - name: apiVersion + type: + scalar: string + - name: kind + type: + scalar: string + - name: metadata + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta + default: {} + - name: spec + type: + namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.LimitSpec + default: {} + - name: status + type: + namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.LimitStatus + default: {} +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.LimitFilter + map: + fields: + - name: description + type: + scalar: string + - name: domainRef + type: + scalar: string + - name: name + type: + scalar: string + - name: projectRef + type: + scalar: string + - name: serviceRef + type: + scalar: string +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.LimitImport + map: + fields: + - name: filter + type: + namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.LimitFilter + - name: id + type: + scalar: string +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.LimitResourceSpec + map: + fields: + - name: description + type: + scalar: string + - name: domainRef + type: + scalar: string + - name: name + type: + scalar: string + - name: projectRef + type: + scalar: string + - name: serviceRef + type: + scalar: string +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.LimitResourceStatus + map: + fields: + - name: description + type: + scalar: string + - name: domainID + type: + scalar: string + - name: name + type: + scalar: string + - name: projectID + type: + scalar: string + - name: serviceID + type: + scalar: string +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.LimitSpec + map: + fields: + - name: cloudCredentialsRef + type: + namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.CloudCredentialsReference + default: {} + - name: import + type: + namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.LimitImport + - name: managedOptions + type: + namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ManagedOptions + - name: managementPolicy + type: + scalar: string + - name: resource + type: + namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.LimitResourceSpec + - name: resyncPeriod + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Duration +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.LimitStatus + map: + fields: + - name: conditions + type: + list: + elementType: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Condition + elementRelationship: associative + keys: + - type + - name: id + type: + scalar: string + - name: lastSyncTime + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Time + - name: resource + type: + namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.LimitResourceStatus - name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ManagedOptions map: fields: diff --git a/pkg/clients/applyconfiguration/utils.go b/pkg/clients/applyconfiguration/utils.go index 22e66aea0..54e2ab6bd 100644 --- a/pkg/clients/applyconfiguration/utils.go +++ b/pkg/clients/applyconfiguration/utils.go @@ -214,6 +214,20 @@ func ForKind(kind schema.GroupVersionKind) interface{} { return &apiv1alpha1.KeyPairSpecApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("KeyPairStatus"): return &apiv1alpha1.KeyPairStatusApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("Limit"): + return &apiv1alpha1.LimitApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("LimitFilter"): + return &apiv1alpha1.LimitFilterApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("LimitImport"): + return &apiv1alpha1.LimitImportApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("LimitResourceSpec"): + return &apiv1alpha1.LimitResourceSpecApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("LimitResourceStatus"): + return &apiv1alpha1.LimitResourceStatusApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("LimitSpec"): + return &apiv1alpha1.LimitSpecApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("LimitStatus"): + return &apiv1alpha1.LimitStatusApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("ManagedOptions"): return &apiv1alpha1.ManagedOptionsApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("Network"): diff --git a/pkg/clients/clientset/clientset/typed/api/v1alpha1/api_client.go b/pkg/clients/clientset/clientset/typed/api/v1alpha1/api_client.go index e39fc5dc9..fc41180e7 100644 --- a/pkg/clients/clientset/clientset/typed/api/v1alpha1/api_client.go +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/api_client.go @@ -37,6 +37,7 @@ type OpenstackV1alpha1Interface interface { GroupsGetter ImagesGetter KeyPairsGetter + LimitsGetter NetworksGetter PortsGetter ProjectsGetter @@ -97,6 +98,10 @@ func (c *OpenstackV1alpha1Client) KeyPairs(namespace string) KeyPairInterface { return newKeyPairs(c, namespace) } +func (c *OpenstackV1alpha1Client) Limits(namespace string) LimitInterface { + return newLimits(c, namespace) +} + func (c *OpenstackV1alpha1Client) Networks(namespace string) NetworkInterface { return newNetworks(c, namespace) } diff --git a/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_api_client.go b/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_api_client.go index 5015aed35..9da4134f9 100644 --- a/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_api_client.go +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_api_client.go @@ -64,6 +64,10 @@ func (c *FakeOpenstackV1alpha1) KeyPairs(namespace string) v1alpha1.KeyPairInter return newFakeKeyPairs(c, namespace) } +func (c *FakeOpenstackV1alpha1) Limits(namespace string) v1alpha1.LimitInterface { + return newFakeLimits(c, namespace) +} + func (c *FakeOpenstackV1alpha1) Networks(namespace string) v1alpha1.NetworkInterface { return newFakeNetworks(c, namespace) } diff --git a/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_limit.go b/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_limit.go new file mode 100644 index 000000000..40a26b7b8 --- /dev/null +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_limit.go @@ -0,0 +1,49 @@ +/* +Copyright The ORC Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + v1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" + apiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/pkg/clients/applyconfiguration/api/v1alpha1" + typedapiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/pkg/clients/clientset/clientset/typed/api/v1alpha1" + gentype "k8s.io/client-go/gentype" +) + +// fakeLimits implements LimitInterface +type fakeLimits struct { + *gentype.FakeClientWithListAndApply[*v1alpha1.Limit, *v1alpha1.LimitList, *apiv1alpha1.LimitApplyConfiguration] + Fake *FakeOpenstackV1alpha1 +} + +func newFakeLimits(fake *FakeOpenstackV1alpha1, namespace string) typedapiv1alpha1.LimitInterface { + return &fakeLimits{ + gentype.NewFakeClientWithListAndApply[*v1alpha1.Limit, *v1alpha1.LimitList, *apiv1alpha1.LimitApplyConfiguration]( + fake.Fake, + namespace, + v1alpha1.SchemeGroupVersion.WithResource("limits"), + v1alpha1.SchemeGroupVersion.WithKind("Limit"), + func() *v1alpha1.Limit { return &v1alpha1.Limit{} }, + func() *v1alpha1.LimitList { return &v1alpha1.LimitList{} }, + func(dst, src *v1alpha1.LimitList) { dst.ListMeta = src.ListMeta }, + func(list *v1alpha1.LimitList) []*v1alpha1.Limit { return gentype.ToPointerSlice(list.Items) }, + func(list *v1alpha1.LimitList, items []*v1alpha1.Limit) { list.Items = gentype.FromPointerSlice(items) }, + ), + fake, + } +} diff --git a/pkg/clients/clientset/clientset/typed/api/v1alpha1/generated_expansion.go b/pkg/clients/clientset/clientset/typed/api/v1alpha1/generated_expansion.go index 7c5d67d45..f73443bc8 100644 --- a/pkg/clients/clientset/clientset/typed/api/v1alpha1/generated_expansion.go +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/generated_expansion.go @@ -36,6 +36,8 @@ type ImageExpansion interface{} type KeyPairExpansion interface{} +type LimitExpansion interface{} + type NetworkExpansion interface{} type PortExpansion interface{} diff --git a/pkg/clients/clientset/clientset/typed/api/v1alpha1/limit.go b/pkg/clients/clientset/clientset/typed/api/v1alpha1/limit.go new file mode 100644 index 000000000..f3e0141f9 --- /dev/null +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/limit.go @@ -0,0 +1,74 @@ +/* +Copyright The ORC Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + context "context" + + apiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" + applyconfigurationapiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/pkg/clients/applyconfiguration/api/v1alpha1" + scheme "github.com/k-orc/openstack-resource-controller/v2/pkg/clients/clientset/clientset/scheme" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + gentype "k8s.io/client-go/gentype" +) + +// LimitsGetter has a method to return a LimitInterface. +// A group's client should implement this interface. +type LimitsGetter interface { + Limits(namespace string) LimitInterface +} + +// LimitInterface has methods to work with Limit resources. +type LimitInterface interface { + Create(ctx context.Context, limit *apiv1alpha1.Limit, opts v1.CreateOptions) (*apiv1alpha1.Limit, error) + Update(ctx context.Context, limit *apiv1alpha1.Limit, opts v1.UpdateOptions) (*apiv1alpha1.Limit, error) + // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). + UpdateStatus(ctx context.Context, limit *apiv1alpha1.Limit, opts v1.UpdateOptions) (*apiv1alpha1.Limit, error) + Delete(ctx context.Context, name string, opts v1.DeleteOptions) error + DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error + Get(ctx context.Context, name string, opts v1.GetOptions) (*apiv1alpha1.Limit, error) + List(ctx context.Context, opts v1.ListOptions) (*apiv1alpha1.LimitList, error) + Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) + Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *apiv1alpha1.Limit, err error) + Apply(ctx context.Context, limit *applyconfigurationapiv1alpha1.LimitApplyConfiguration, opts v1.ApplyOptions) (result *apiv1alpha1.Limit, err error) + // Add a +genclient:noStatus comment above the type to avoid generating ApplyStatus(). + ApplyStatus(ctx context.Context, limit *applyconfigurationapiv1alpha1.LimitApplyConfiguration, opts v1.ApplyOptions) (result *apiv1alpha1.Limit, err error) + LimitExpansion +} + +// limits implements LimitInterface +type limits struct { + *gentype.ClientWithListAndApply[*apiv1alpha1.Limit, *apiv1alpha1.LimitList, *applyconfigurationapiv1alpha1.LimitApplyConfiguration] +} + +// newLimits returns a Limits +func newLimits(c *OpenstackV1alpha1Client, namespace string) *limits { + return &limits{ + gentype.NewClientWithListAndApply[*apiv1alpha1.Limit, *apiv1alpha1.LimitList, *applyconfigurationapiv1alpha1.LimitApplyConfiguration]( + "limits", + c.RESTClient(), + scheme.ParameterCodec, + namespace, + func() *apiv1alpha1.Limit { return &apiv1alpha1.Limit{} }, + func() *apiv1alpha1.LimitList { return &apiv1alpha1.LimitList{} }, + ), + } +} diff --git a/pkg/clients/informers/externalversions/api/v1alpha1/interface.go b/pkg/clients/informers/externalversions/api/v1alpha1/interface.go index 69d2bdeaf..a4d6078f7 100644 --- a/pkg/clients/informers/externalversions/api/v1alpha1/interface.go +++ b/pkg/clients/informers/externalversions/api/v1alpha1/interface.go @@ -42,6 +42,8 @@ type Interface interface { Images() ImageInformer // KeyPairs returns a KeyPairInformer. KeyPairs() KeyPairInformer + // Limits returns a LimitInformer. + Limits() LimitInformer // Networks returns a NetworkInformer. Networks() NetworkInformer // Ports returns a PortInformer. @@ -134,6 +136,11 @@ func (v *version) KeyPairs() KeyPairInformer { return &keyPairInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} } +// Limits returns a LimitInformer. +func (v *version) Limits() LimitInformer { + return &limitInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} + // Networks returns a NetworkInformer. func (v *version) Networks() NetworkInformer { return &networkInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} diff --git a/pkg/clients/informers/externalversions/api/v1alpha1/limit.go b/pkg/clients/informers/externalversions/api/v1alpha1/limit.go new file mode 100644 index 000000000..04bc40bd9 --- /dev/null +++ b/pkg/clients/informers/externalversions/api/v1alpha1/limit.go @@ -0,0 +1,102 @@ +/* +Copyright The ORC Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by informer-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + context "context" + time "time" + + v2apiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" + clientset "github.com/k-orc/openstack-resource-controller/v2/pkg/clients/clientset/clientset" + internalinterfaces "github.com/k-orc/openstack-resource-controller/v2/pkg/clients/informers/externalversions/internalinterfaces" + apiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/pkg/clients/listers/api/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + watch "k8s.io/apimachinery/pkg/watch" + cache "k8s.io/client-go/tools/cache" +) + +// LimitInformer provides access to a shared informer and lister for +// Limits. +type LimitInformer interface { + Informer() cache.SharedIndexInformer + Lister() apiv1alpha1.LimitLister +} + +type limitInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewLimitInformer constructs a new informer for Limit type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewLimitInformer(client clientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredLimitInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// NewFilteredLimitInformer constructs a new informer for Limit type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewFilteredLimitInformer(client clientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { + return cache.NewSharedIndexInformer( + &cache.ListWatch{ + ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.OpenstackV1alpha1().Limits(namespace).List(context.Background(), options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.OpenstackV1alpha1().Limits(namespace).Watch(context.Background(), options) + }, + ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.OpenstackV1alpha1().Limits(namespace).List(ctx, options) + }, + WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.OpenstackV1alpha1().Limits(namespace).Watch(ctx, options) + }, + }, + &v2apiv1alpha1.Limit{}, + resyncPeriod, + indexers, + ) +} + +func (f *limitInformer) defaultInformer(client clientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredLimitInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *limitInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&v2apiv1alpha1.Limit{}, f.defaultInformer) +} + +func (f *limitInformer) Lister() apiv1alpha1.LimitLister { + return apiv1alpha1.NewLimitLister(f.Informer().GetIndexer()) +} diff --git a/pkg/clients/informers/externalversions/generic.go b/pkg/clients/informers/externalversions/generic.go index f58420886..22302c59d 100644 --- a/pkg/clients/informers/externalversions/generic.go +++ b/pkg/clients/informers/externalversions/generic.go @@ -71,6 +71,8 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource return &genericInformer{resource: resource.GroupResource(), informer: f.Openstack().V1alpha1().Images().Informer()}, nil case v1alpha1.SchemeGroupVersion.WithResource("keypairs"): return &genericInformer{resource: resource.GroupResource(), informer: f.Openstack().V1alpha1().KeyPairs().Informer()}, nil + case v1alpha1.SchemeGroupVersion.WithResource("limits"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Openstack().V1alpha1().Limits().Informer()}, nil case v1alpha1.SchemeGroupVersion.WithResource("networks"): return &genericInformer{resource: resource.GroupResource(), informer: f.Openstack().V1alpha1().Networks().Informer()}, nil case v1alpha1.SchemeGroupVersion.WithResource("ports"): diff --git a/pkg/clients/listers/api/v1alpha1/expansion_generated.go b/pkg/clients/listers/api/v1alpha1/expansion_generated.go index 76fec1603..d02f8305d 100644 --- a/pkg/clients/listers/api/v1alpha1/expansion_generated.go +++ b/pkg/clients/listers/api/v1alpha1/expansion_generated.go @@ -90,6 +90,14 @@ type KeyPairListerExpansion interface{} // KeyPairNamespaceLister. type KeyPairNamespaceListerExpansion interface{} +// LimitListerExpansion allows custom methods to be added to +// LimitLister. +type LimitListerExpansion interface{} + +// LimitNamespaceListerExpansion allows custom methods to be added to +// LimitNamespaceLister. +type LimitNamespaceListerExpansion interface{} + // NetworkListerExpansion allows custom methods to be added to // NetworkLister. type NetworkListerExpansion interface{} diff --git a/pkg/clients/listers/api/v1alpha1/limit.go b/pkg/clients/listers/api/v1alpha1/limit.go new file mode 100644 index 000000000..24a4fe911 --- /dev/null +++ b/pkg/clients/listers/api/v1alpha1/limit.go @@ -0,0 +1,70 @@ +/* +Copyright The ORC Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by lister-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + apiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" + labels "k8s.io/apimachinery/pkg/labels" + listers "k8s.io/client-go/listers" + cache "k8s.io/client-go/tools/cache" +) + +// LimitLister helps list Limits. +// All objects returned here must be treated as read-only. +type LimitLister interface { + // List lists all Limits in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*apiv1alpha1.Limit, err error) + // Limits returns an object that can list and get Limits. + Limits(namespace string) LimitNamespaceLister + LimitListerExpansion +} + +// limitLister implements the LimitLister interface. +type limitLister struct { + listers.ResourceIndexer[*apiv1alpha1.Limit] +} + +// NewLimitLister returns a new LimitLister. +func NewLimitLister(indexer cache.Indexer) LimitLister { + return &limitLister{listers.New[*apiv1alpha1.Limit](indexer, apiv1alpha1.Resource("limit"))} +} + +// Limits returns an object that can list and get Limits. +func (s *limitLister) Limits(namespace string) LimitNamespaceLister { + return limitNamespaceLister{listers.NewNamespaced[*apiv1alpha1.Limit](s.ResourceIndexer, namespace)} +} + +// LimitNamespaceLister helps list and get Limits. +// All objects returned here must be treated as read-only. +type LimitNamespaceLister interface { + // List lists all Limits in the indexer for a given namespace. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*apiv1alpha1.Limit, err error) + // Get retrieves the Limit from the indexer for a given namespace and name. + // Objects returned here must be treated as read-only. + Get(name string) (*apiv1alpha1.Limit, error) + LimitNamespaceListerExpansion +} + +// limitNamespaceLister implements the LimitNamespaceLister +// interface. +type limitNamespaceLister struct { + listers.ResourceIndexer[*apiv1alpha1.Limit] +} diff --git a/website/docs/crd-reference.md b/website/docs/crd-reference.md index 3cfdb8ac9..c7457e706 100644 --- a/website/docs/crd-reference.md +++ b/website/docs/crd-reference.md @@ -19,6 +19,7 @@ Package v1alpha1 contains API Schema definitions for the openstack v1alpha1 API - [Group](#group) - [Image](#image) - [KeyPair](#keypair) +- [Limit](#limit) - [Network](#network) - [Port](#port) - [Project](#project) @@ -516,6 +517,7 @@ _Appears in:_ - [GroupSpec](#groupspec) - [ImageSpec](#imagespec) - [KeyPairSpec](#keypairspec) +- [LimitSpec](#limitspec) - [NetworkSpec](#networkspec) - [PortSpec](#portspec) - [ProjectSpec](#projectspec) @@ -2210,6 +2212,8 @@ _Appears in:_ - [GroupFilter](#groupfilter) - [GroupResourceSpec](#groupresourcespec) - [HostID](#hostid) +- [LimitFilter](#limitfilter) +- [LimitResourceSpec](#limitresourcespec) - [NetworkFilter](#networkfilter) - [NetworkResourceSpec](#networkresourcespec) - [PortFilter](#portfilter) @@ -2243,6 +2247,146 @@ _Appears in:_ +#### Limit + + + +Limit is the Schema for an ORC resource. + + + + + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `apiVersion` _string_ | `openstack.k-orc.cloud/v1alpha1` | | | +| `kind` _string_ | `Limit` | | | +| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | | Optional: \{\}
| +| `spec` _[LimitSpec](#limitspec)_ | spec specifies the desired state of the resource. | | Required: \{\}
| +| `status` _[LimitStatus](#limitstatus)_ | status defines the observed state of the resource. | | Optional: \{\}
| + + +#### LimitFilter + + + +LimitFilter defines an existing resource by its properties + +_Validation:_ +- MinProperties: 1 + +_Appears in:_ +- [LimitImport](#limitimport) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `name` _[OpenStackName](#openstackname)_ | name of the existing resource | | MaxLength: 255
MinLength: 1
Pattern: `^[^,]+$`
Optional: \{\}
| +| `description` _string_ | description of the existing resource | | MaxLength: 255
MinLength: 1
Optional: \{\}
| +| `serviceRef` _[KubernetesNameRef](#kubernetesnameref)_ | serviceRef is a reference to the ORC Service which this resource is associated with. | | MaxLength: 253
MinLength: 1
Optional: \{\}
| +| `projectRef` _[KubernetesNameRef](#kubernetesnameref)_ | projectRef is a reference to the ORC Project which this resource is associated with. | | MaxLength: 253
MinLength: 1
Optional: \{\}
| +| `domainRef` _[KubernetesNameRef](#kubernetesnameref)_ | domainRef is a reference to the ORC Domain which this resource is associated with. | | MaxLength: 253
MinLength: 1
Optional: \{\}
| + + +#### LimitImport + + + +LimitImport specifies an existing resource which will be imported instead of +creating a new one + +_Validation:_ +- MaxProperties: 1 +- MinProperties: 1 + +_Appears in:_ +- [LimitSpec](#limitspec) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `id` _string_ | id contains the unique identifier of an existing OpenStack resource. Note
that when specifying an import by ID, the resource MUST already exist.
The ORC object will enter an error state if the resource does not exist. | | Format: uuid
MaxLength: 36
Optional: \{\}
| +| `filter` _[LimitFilter](#limitfilter)_ | filter contains a resource query which is expected to return a single
result. The controller will continue to retry if filter returns no
results. If filter returns multiple results the controller will set an
error state and will not continue to retry. | | MinProperties: 1
Optional: \{\}
| + + +#### LimitResourceSpec + + + +LimitResourceSpec contains the desired state of the resource. + + + +_Appears in:_ +- [LimitSpec](#limitspec) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `name` _[OpenStackName](#openstackname)_ | name will be the name of the created resource. If not specified, the
name of the ORC object will be used. | | MaxLength: 255
MinLength: 1
Pattern: `^[^,]+$`
Optional: \{\}
| +| `description` _string_ | description is a human-readable description for the resource. | | MaxLength: 255
MinLength: 1
Optional: \{\}
| +| `serviceRef` _[KubernetesNameRef](#kubernetesnameref)_ | serviceRef is a reference to the ORC Service which this resource is associated with. | | MaxLength: 253
MinLength: 1
Required: \{\}
| +| `projectRef` _[KubernetesNameRef](#kubernetesnameref)_ | projectRef is a reference to the ORC Project which this resource is associated with. | | MaxLength: 253
MinLength: 1
Optional: \{\}
| +| `domainRef` _[KubernetesNameRef](#kubernetesnameref)_ | domainRef is a reference to the ORC Domain which this resource is associated with. | | MaxLength: 253
MinLength: 1
Optional: \{\}
| + + +#### LimitResourceStatus + + + +LimitResourceStatus represents the observed state of the resource. + + + +_Appears in:_ +- [LimitStatus](#limitstatus) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `name` _string_ | name is a Human-readable name for the resource. Might not be unique. | | MaxLength: 1024
Optional: \{\}
| +| `description` _string_ | description is a human-readable description for the resource. | | MaxLength: 1024
Optional: \{\}
| +| `serviceID` _string_ | serviceID is the ID of the Service to which the resource is associated. | | MaxLength: 1024
Optional: \{\}
| +| `projectID` _string_ | projectID is the ID of the Project to which the resource is associated. | | MaxLength: 1024
Optional: \{\}
| +| `domainID` _string_ | domainID is the ID of the Domain to which the resource is associated. | | MaxLength: 1024
Optional: \{\}
| + + +#### LimitSpec + + + +LimitSpec defines the desired state of an ORC object. + + + +_Appears in:_ +- [Limit](#limit) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `import` _[LimitImport](#limitimport)_ | import refers to an existing OpenStack resource which will be imported instead of
creating a new one. | | MaxProperties: 1
MinProperties: 1
Optional: \{\}
| +| `resource` _[LimitResourceSpec](#limitresourcespec)_ | resource specifies the desired state of the resource.
resource may not be specified if the management policy is `unmanaged`.
resource must be specified if the management policy is `managed`. | | Optional: \{\}
| +| `managementPolicy` _[ManagementPolicy](#managementpolicy)_ | managementPolicy defines how ORC will treat the object. Valid values are
`managed`: ORC will create, update, and delete the resource; `unmanaged`:
ORC will import an existing resource, and will not apply updates to it or
delete it. | managed | Enum: [managed unmanaged]
Optional: \{\}
| +| `managedOptions` _[ManagedOptions](#managedoptions)_ | managedOptions specifies options which may be applied to managed objects. | | Optional: \{\}
| +| `resyncPeriod` _[Duration](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#duration-v1-meta)_ | resyncPeriod defines how frequently the controller will re-reconcile
this resource even when no changes have been detected. This overrides
the global default resync period. The value must be a valid Go duration
string, e.g. "10m", "1h". Set to "0s" to disable periodic resync for
this resource. Very low values may cause excessive OpenStack API load. | | Optional: \{\}
| +| `cloudCredentialsRef` _[CloudCredentialsReference](#cloudcredentialsreference)_ | cloudCredentialsRef points to a secret containing OpenStack credentials | | Required: \{\}
| + + +#### LimitStatus + + + +LimitStatus defines the observed state of an ORC resource. + + + +_Appears in:_ +- [Limit](#limit) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#condition-v1-meta) array_ | conditions represents the observed status of the object.
Known .status.conditions.type are: "Available", "Progressing"
Available represents the availability of the OpenStack resource. If it is
true then the resource is ready for use.
Progressing indicates whether the controller is still attempting to
reconcile the current state of the OpenStack resource to the desired
state. Progressing will be False either because the desired state has
been achieved, or because some terminal error prevents it from ever being
achieved and the controller is no longer attempting to reconcile. If
Progressing is True, an observer waiting on the resource should continue
to wait. | | MaxItems: 32
Optional: \{\}
| +| `id` _string_ | id is the unique identifier of the OpenStack resource. | | MaxLength: 1024
Optional: \{\}
| +| `resource` _[LimitResourceStatus](#limitresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | Optional: \{\}
| +| `lastSyncTime` _[Time](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#time-v1-meta)_ | lastSyncTime is the timestamp of the last successful reconciliation
that fetched state from OpenStack. It is updated each time the
controller successfully reads the resource state from the OpenStack
API. | | Optional: \{\}
| + + #### MAC _Underlying type:_ _string_ @@ -2291,6 +2435,7 @@ _Appears in:_ - [GroupSpec](#groupspec) - [ImageSpec](#imagespec) - [KeyPairSpec](#keypairspec) +- [LimitSpec](#limitspec) - [NetworkSpec](#networkspec) - [PortSpec](#portspec) - [ProjectSpec](#projectspec) @@ -2332,6 +2477,7 @@ _Appears in:_ - [GroupSpec](#groupspec) - [ImageSpec](#imagespec) - [KeyPairSpec](#keypairspec) +- [LimitSpec](#limitspec) - [NetworkSpec](#networkspec) - [PortSpec](#portspec) - [ProjectSpec](#projectspec) @@ -2640,6 +2786,8 @@ _Appears in:_ - [ImageResourceSpec](#imageresourcespec) - [KeyPairFilter](#keypairfilter) - [KeyPairResourceSpec](#keypairresourcespec) +- [LimitFilter](#limitfilter) +- [LimitResourceSpec](#limitresourcespec) - [NetworkFilter](#networkfilter) - [NetworkResourceSpec](#networkresourcespec) - [PortFilter](#portfilter) From 04aba641e71ecf71a3d9e31e5bc2e98eabf3e738 Mon Sep 17 00:00:00 2001 From: Winston Chen Date: Mon, 20 Jul 2026 10:40:21 +0100 Subject: [PATCH 03/13] Implement scaffolded TODO for Limit controller --- api/v1alpha1/limit_types.go | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/api/v1alpha1/limit_types.go b/api/v1alpha1/limit_types.go index 7d742d429..e45c7f540 100644 --- a/api/v1alpha1/limit_types.go +++ b/api/v1alpha1/limit_types.go @@ -18,11 +18,6 @@ package v1alpha1 // LimitResourceSpec contains the desired state of the resource. type LimitResourceSpec struct { - // name will be the name of the created resource. If not specified, the - // name of the ORC object will be used. - // +optional - Name *OpenStackName `json:"name,omitempty"` - // description is a human-readable description for the resource. // +kubebuilder:validation:MinLength:=1 // +kubebuilder:validation:MaxLength:=255 @@ -51,6 +46,17 @@ type LimitResourceSpec struct { // Until you have implemented mutability for the field, you must add a CEL validation // preventing the field being modified: // `// +kubebuilder:validation:XValidation:rule="self == oldSelf",message=" is immutable"` + + // resoureName is the name of the resource this limit is associated with. + // +kubebuilder:validation:MinLength:=1 + // +kubebuilder:validation:MaxLength:=255 + // +required + ResourceName string `json:"resourceName"` + + // resoureLimit is the override value of the limit. + // +kubebuilder:validation:Min:=-1 + // +required + ResourceLimit int32 `json:"resourceLimit"` } // LimitFilter defines an existing resource by its properties From baf4495ee49945ccbf9f0b9be4a21e0bc81c5241 Mon Sep 17 00:00:00 2001 From: Winston Chen Date: Mon, 20 Jul 2026 11:27:14 +0100 Subject: [PATCH 04/13] Update fields for Limit type and fix openstack apis for Limit os client --- api/v1alpha1/limit_types.go | 47 +++++-------- api/v1alpha1/zz_generated.deepcopy.go | 15 ---- cmd/models-schema/zz_generated.openapi.go | 68 +++++++++++-------- cmd/resource-generator/main.go | 3 +- .../bases/openstack.k-orc.cloud_limits.yaml | 57 +++++++++------- internal/controllers/limit/actuator.go | 28 ++------ internal/controllers/limit/actuator_test.go | 44 ++---------- internal/controllers/limit/status.go | 3 +- .../controllers/limit/zz_generated.adapter.go | 10 --- internal/osclients/limit.go | 17 +++-- internal/osclients/mock/limit.go | 2 +- .../api/v1alpha1/limitfilter.go | 33 ++++----- .../api/v1alpha1/limitresourcespec.go | 35 ++++++---- .../api/v1alpha1/limitresourcestatus.go | 35 ++++++---- .../applyconfiguration/internal/internal.go | 24 ++++--- test/apivalidations/limit_test.go | 2 +- website/docs/crd-reference.md | 11 ++- 17 files changed, 197 insertions(+), 237 deletions(-) diff --git a/api/v1alpha1/limit_types.go b/api/v1alpha1/limit_types.go index e45c7f540..cba32c509 100644 --- a/api/v1alpha1/limit_types.go +++ b/api/v1alpha1/limit_types.go @@ -39,22 +39,16 @@ type LimitResourceSpec struct { // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="domainRef is immutable" DomainRef *KubernetesNameRef `json:"domainRef,omitempty"` - // TODO(scaffolding): Add more types. - // To see what is supported, you can take inspiration from the CreateOpts structure from - // github.com/gophercloud/gophercloud/v2/openstack/identity/v3/limits - // - // Until you have implemented mutability for the field, you must add a CEL validation - // preventing the field being modified: - // `// +kubebuilder:validation:XValidation:rule="self == oldSelf",message=" is immutable"` - // resoureName is the name of the resource this limit is associated with. // +kubebuilder:validation:MinLength:=1 // +kubebuilder:validation:MaxLength:=255 + // +kubebuilder:validation:Pattern=`^[\S]+$` // +required + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="resourceName is immutable" ResourceName string `json:"resourceName"` // resoureLimit is the override value of the limit. - // +kubebuilder:validation:Min:=-1 + // +kubebuilder:validation:Minimum=-1 // +required ResourceLimit int32 `json:"resourceLimit"` } @@ -62,16 +56,6 @@ type LimitResourceSpec struct { // LimitFilter defines an existing resource by its properties // +kubebuilder:validation:MinProperties:=1 type LimitFilter struct { - // name of the existing resource - // +optional - Name *OpenStackName `json:"name,omitempty"` - - // description of the existing resource - // +kubebuilder:validation:MinLength:=1 - // +kubebuilder:validation:MaxLength:=255 - // +optional - Description *string `json:"description,omitempty"` - // serviceRef is a reference to the ORC Service which this resource is associated with. // +optional ServiceRef *KubernetesNameRef `json:"serviceRef,omitempty"` @@ -84,18 +68,16 @@ type LimitFilter struct { // +optional DomainRef *KubernetesNameRef `json:"domainRef,omitempty"` - // TODO(scaffolding): Add more types. - // To see what is supported, you can take inspiration from the ListOpts structure from - // github.com/gophercloud/gophercloud/v2/openstack/identity/v3/limits + // resoureName is the name of the resource this limit is associated with. + // +kubebuilder:validation:MinLength:=1 + // +kubebuilder:validation:MaxLength:=255 + // +kubebuilder:validation:Pattern=`^[\S]+$` + // +optional + ResourceName string `json:"resourceName,omitempty"` } // LimitResourceStatus represents the observed state of the resource. type LimitResourceStatus struct { - // name is a Human-readable name for the resource. Might not be unique. - // +kubebuilder:validation:MaxLength=1024 - // +optional - Name string `json:"name,omitempty"` - // description is a human-readable description for the resource. // +kubebuilder:validation:MaxLength=1024 // +optional @@ -116,7 +98,12 @@ type LimitResourceStatus struct { // +optional DomainID string `json:"domainID,omitempty"` - // TODO(scaffolding): Add more types. - // To see what is supported, you can take inspiration from the Limit structure from - // github.com/gophercloud/gophercloud/v2/openstack/identity/v3/limits + // resoureLimit is the override value of the limit. + // +optional + ResourceLimit int32 `json:"resourceLimit"` + + // resoureName is the name of the resource this limit is associated with. + // +kubebuilder:validation:MaxLength=1024 + // +optional + ResourceName string `json:"resourceName,omitempty"` } diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 532d691ec..ca1746193 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -2926,16 +2926,6 @@ func (in *Limit) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *LimitFilter) DeepCopyInto(out *LimitFilter) { *out = *in - if in.Name != nil { - in, out := &in.Name, &out.Name - *out = new(OpenStackName) - **out = **in - } - if in.Description != nil { - in, out := &in.Description, &out.Description - *out = new(string) - **out = **in - } if in.ServiceRef != nil { in, out := &in.ServiceRef, &out.ServiceRef *out = new(KubernetesNameRef) @@ -3023,11 +3013,6 @@ func (in *LimitList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *LimitResourceSpec) DeepCopyInto(out *LimitResourceSpec) { *out = *in - if in.Name != nil { - in, out := &in.Name, &out.Name - *out = new(OpenStackName) - **out = **in - } if in.Description != nil { in, out := &in.Description, &out.Description *out = new(string) diff --git a/cmd/models-schema/zz_generated.openapi.go b/cmd/models-schema/zz_generated.openapi.go index 0b44022c1..45bd3dcfc 100644 --- a/cmd/models-schema/zz_generated.openapi.go +++ b/cmd/models-schema/zz_generated.openapi.go @@ -5414,20 +5414,6 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_LimitFilter(ref common Description: "LimitFilter defines an existing resource by its properties", Type: []string{"object"}, Properties: map[string]spec.Schema{ - "name": { - SchemaProps: spec.SchemaProps{ - Description: "name of the existing resource", - Type: []string{"string"}, - Format: "", - }, - }, - "description": { - SchemaProps: spec.SchemaProps{ - Description: "description of the existing resource", - Type: []string{"string"}, - Format: "", - }, - }, "serviceRef": { SchemaProps: spec.SchemaProps{ Description: "serviceRef is a reference to the ORC Service which this resource is associated with.", @@ -5449,6 +5435,13 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_LimitFilter(ref common Format: "", }, }, + "resourceName": { + SchemaProps: spec.SchemaProps{ + Description: "resoureName is the name of the resource this limit is associated with.", + Type: []string{"string"}, + Format: "", + }, + }, }, }, }, @@ -5541,13 +5534,6 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_LimitResourceSpec(ref Description: "LimitResourceSpec contains the desired state of the resource.", Type: []string{"object"}, Properties: map[string]spec.Schema{ - "name": { - SchemaProps: spec.SchemaProps{ - Description: "name will be the name of the created resource. If not specified, the name of the ORC object will be used.", - Type: []string{"string"}, - Format: "", - }, - }, "description": { SchemaProps: spec.SchemaProps{ Description: "description is a human-readable description for the resource.", @@ -5576,8 +5562,24 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_LimitResourceSpec(ref Format: "", }, }, + "resourceName": { + SchemaProps: spec.SchemaProps{ + Description: "resoureName is the name of the resource this limit is associated with.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "resourceLimit": { + SchemaProps: spec.SchemaProps{ + Description: "resoureLimit is the override value of the limit.", + Default: 0, + Type: []string{"integer"}, + Format: "int32", + }, + }, }, - Required: []string{"serviceRef"}, + Required: []string{"serviceRef", "resourceName", "resourceLimit"}, }, }, } @@ -5590,13 +5592,6 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_LimitResourceStatus(re Description: "LimitResourceStatus represents the observed state of the resource.", Type: []string{"object"}, Properties: map[string]spec.Schema{ - "name": { - SchemaProps: spec.SchemaProps{ - Description: "name is a Human-readable name for the resource. Might not be unique.", - Type: []string{"string"}, - Format: "", - }, - }, "description": { SchemaProps: spec.SchemaProps{ Description: "description is a human-readable description for the resource.", @@ -5625,6 +5620,21 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_LimitResourceStatus(re Format: "", }, }, + "resourceLimit": { + SchemaProps: spec.SchemaProps{ + Description: "resoureLimit is the override value of the limit.", + Default: 0, + Type: []string{"integer"}, + Format: "int32", + }, + }, + "resourceName": { + SchemaProps: spec.SchemaProps{ + Description: "resoureName is the name of the resource this limit is associated with.", + Type: []string{"string"}, + Format: "", + }, + }, }, }, }, diff --git a/cmd/resource-generator/main.go b/cmd/resource-generator/main.go index 0058f5ddb..e9adbe0b7 100644 --- a/cmd/resource-generator/main.go +++ b/cmd/resource-generator/main.go @@ -190,7 +190,8 @@ var resources []templateFields = []templateFields{ Name: "ApplicationCredential", }, { - Name: "Limit", + Name: "Limit", + IsNotNamed: true, }, } diff --git a/config/crd/bases/openstack.k-orc.cloud_limits.yaml b/config/crd/bases/openstack.k-orc.cloud_limits.yaml index 75fc6e925..d477e12fc 100644 --- a/config/crd/bases/openstack.k-orc.cloud_limits.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_limits.yaml @@ -91,29 +91,25 @@ spec: error state and will not continue to retry. minProperties: 1 properties: - description: - description: description of the existing resource - maxLength: 255 - minLength: 1 - type: string domainRef: description: domainRef is a reference to the ORC Domain which this resource is associated with. maxLength: 253 minLength: 1 type: string - name: - description: name of the existing resource - maxLength: 255 - minLength: 1 - pattern: ^[^,]+$ - type: string projectRef: description: projectRef is a reference to the ORC Project which this resource is associated with. maxLength: 253 minLength: 1 type: string + resourceName: + description: resoureName is the name of the resource this + limit is associated with. + maxLength: 255 + minLength: 1 + pattern: ^[\S]+$ + type: string serviceRef: description: serviceRef is a reference to the ORC Service which this resource is associated with. @@ -183,14 +179,6 @@ spec: x-kubernetes-validations: - message: domainRef is immutable rule: self == oldSelf - name: - description: |- - name will be the name of the created resource. If not specified, the - name of the ORC object will be used. - maxLength: 255 - minLength: 1 - pattern: ^[^,]+$ - type: string projectRef: description: projectRef is a reference to the ORC Project which this resource is associated with. @@ -200,6 +188,21 @@ spec: x-kubernetes-validations: - message: projectRef is immutable rule: self == oldSelf + resourceLimit: + description: resoureLimit is the override value of the limit. + format: int32 + minimum: -1 + type: integer + resourceName: + description: resoureName is the name of the resource this limit + is associated with. + maxLength: 255 + minLength: 1 + pattern: ^[\S]+$ + type: string + x-kubernetes-validations: + - message: resourceName is immutable + rule: self == oldSelf serviceRef: description: serviceRef is a reference to the ORC Service which this resource is associated with. @@ -210,6 +213,8 @@ spec: - message: serviceRef is immutable rule: self == oldSelf required: + - resourceLimit + - resourceName - serviceRef type: object resyncPeriod: @@ -341,16 +346,20 @@ spec: is associated. maxLength: 1024 type: string - name: - description: name is a Human-readable name for the resource. Might - not be unique. - maxLength: 1024 - type: string projectID: description: projectID is the ID of the Project to which the resource is associated. maxLength: 1024 type: string + resourceLimit: + description: resoureLimit is the override value of the limit. + format: int32 + type: integer + resourceName: + description: resoureName is the name of the resource this limit + is associated with. + maxLength: 1024 + type: string serviceID: description: serviceID is the ID of the Service to which the resource is associated. diff --git a/internal/controllers/limit/actuator.go b/internal/controllers/limit/actuator.go index ad46e5245..7a3c705f8 100644 --- a/internal/controllers/limit/actuator.go +++ b/internal/controllers/limit/actuator.go @@ -75,10 +75,7 @@ func (actuator limitActuator) ListOSResourcesForAdoption(ctx context.Context, or // of gophercloud does not support, it's possible to perform client-side filtering. // Check osclients.ResourceFilter - listOpts := limits.ListOpts{ - Name: getResourceName(orcObject), - Description: ptr.Deref(resourceSpec.Description, ""), - } + listOpts := limits.ListOpts{} return actuator.osClient.ListLimits(ctx, listOpts), true } @@ -115,10 +112,8 @@ func (actuator limitActuator) ListOSResourcesForImport(ctx context.Context, obj } listOpts := limits.ListOpts{ - Name: string(ptr.Deref(filter.Name, "")), - Description: string(ptr.Deref(filter.Description, "")), - ServiceID: ptr.Deref(service.Status.ID, ""), - ProjectID: ptr.Deref(project.Status.ID, ""), + ServiceID: ptr.Deref(service.Status.ID, ""), + ProjectID: ptr.Deref(project.Status.ID, ""), DomainID: ptr.Deref(domain.Status.ID, ""), // TODO(scaffolding): Add more import filters } @@ -170,15 +165,14 @@ func (actuator limitActuator) CreateResource(ctx context.Context, obj orcObjectP return nil, reconcileStatus } createOpts := limits.CreateOpts{ - Name: getResourceName(obj), Description: ptr.Deref(resource.Description, ""), - ServiceID: serviceID, - ProjectID: projectID, - DomainID: domainID, + ServiceID: serviceID, + ProjectID: projectID, + DomainID: domainID, // TODO(scaffolding): Add more fields } - osResource, err := actuator.osClient.CreateLimit(ctx, createOpts) + osResource, err := actuator.osClient.CreateLimit(ctx, limits.BatchCreateOpts{createOpts}) if err != nil { if !orcerrors.IsRetryable(err) { err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration creating resource: "+err.Error(), err) @@ -204,7 +198,6 @@ func (actuator limitActuator) updateResource(ctx context.Context, obj orcObjectP updateOpts := limits.UpdateOpts{} - handleNameUpdate(&updateOpts, obj, osResource) handleDescriptionUpdate(&updateOpts, resource, osResource) // TODO(scaffolding): add handler for all fields supporting mutability @@ -245,13 +238,6 @@ func needsUpdate(updateOpts limits.UpdateOpts) (bool, error) { return len(updateMap) > 0, nil } -func handleNameUpdate(updateOpts *limits.UpdateOpts, obj orcObjectPT, osResource *osResourceT) { - name := getResourceName(obj) - if osResource.Name != name { - updateOpts.Name = &name - } -} - func handleDescriptionUpdate(updateOpts *limits.UpdateOpts, resource *resourceSpecT, osResource *osResourceT) { description := ptr.Deref(resource.Description, "") if osResource.Description != description { diff --git a/internal/controllers/limit/actuator_test.go b/internal/controllers/limit/actuator_test.go index c303fd834..8236cd65d 100644 --- a/internal/controllers/limit/actuator_test.go +++ b/internal/controllers/limit/actuator_test.go @@ -36,8 +36,13 @@ func TestNeedsUpdate(t *testing.T) { expectChange: false, }, { - name: "Updated opts", - updateOpts: limits.UpdateOpts{Name: ptr.To("updated")}, + name: "Updated opts with description", + updateOpts: limits.UpdateOpts{Description: ptr.To("updated")}, + expectChange: true, + }, + { + name: "Updated opts with resourceLimit", + updateOpts: limits.UpdateOpts{ResourceLimit: ptr.To(-1)}, expectChange: true, }, } @@ -52,41 +57,6 @@ func TestNeedsUpdate(t *testing.T) { } } -func TestHandleNameUpdate(t *testing.T) { - ptrToName := ptr.To[orcv1alpha1.OpenStackName] - testCases := []struct { - name string - newValue *orcv1alpha1.OpenStackName - existingValue string - expectChange bool - }{ - {name: "Identical", newValue: ptrToName("name"), existingValue: "name", expectChange: false}, - {name: "Different", newValue: ptrToName("new-name"), existingValue: "name", expectChange: true}, - {name: "No value provided, existing is identical to object name", newValue: nil, existingValue: "object-name", expectChange: false}, - {name: "No value provided, existing is different from object name", newValue: nil, existingValue: "different-from-object-name", expectChange: true}, - } - - for _, tt := range testCases { - t.Run(tt.name, func(t *testing.T) { - resource := &orcv1alpha1.Limit{} - resource.Name = "object-name" - resource.Spec = orcv1alpha1.LimitSpec{ - Resource: &orcv1alpha1.LimitResourceSpec{Name: tt.newValue}, - } - osResource := &osResourceT{Name: tt.existingValue} - - updateOpts := limits.UpdateOpts{} - handleNameUpdate(&updateOpts, resource, osResource) - - got, _ := needsUpdate(updateOpts) - if got != tt.expectChange { - t.Errorf("Expected change: %v, got: %v", tt.expectChange, got) - } - }) - - } -} - func TestHandleDescriptionUpdate(t *testing.T) { ptrToDescription := ptr.To[string] testCases := []struct { diff --git a/internal/controllers/limit/status.go b/internal/controllers/limit/status.go index e8bffbc1a..39aad877a 100644 --- a/internal/controllers/limit/status.go +++ b/internal/controllers/limit/status.go @@ -52,8 +52,7 @@ func (limitStatusWriter) ApplyResourceStatus(log logr.Logger, osResource *osReso resourceStatus := orcapplyconfigv1alpha1.LimitResourceStatus(). WithServiceID(osResource.ServiceID). WithProjectID(osResource.ProjectID). - WithDomainID(osResource.DomainID). - WithName(osResource.Name) + WithDomainID(osResource.DomainID) // TODO(scaffolding): add all of the fields supported in the LimitResourceStatus struct // If a zero-value isn't expected in the response, place it behind a conditional diff --git a/internal/controllers/limit/zz_generated.adapter.go b/internal/controllers/limit/zz_generated.adapter.go index c0bba91bd..2a06969f2 100644 --- a/internal/controllers/limit/zz_generated.adapter.go +++ b/internal/controllers/limit/zz_generated.adapter.go @@ -86,13 +86,3 @@ func (f adapterT) GetImportFilter() *filterT { } return f.Spec.Import.Filter } - -// getResourceName returns the name of the OpenStack resource we should use. -// This method is not implemented as part of APIObjectAdapter as it is intended -// to be used by resource actuators, which don't use the adapter. -func getResourceName(orcObject orcObjectPT) string { - if orcObject.Spec.Resource.Name != nil { - return string(*orcObject.Spec.Resource.Name) - } - return orcObject.Name -} diff --git a/internal/osclients/limit.go b/internal/osclients/limit.go index 0847b2a45..3aebe8eaa 100644 --- a/internal/osclients/limit.go +++ b/internal/osclients/limit.go @@ -29,7 +29,7 @@ import ( type LimitClient interface { ListLimits(ctx context.Context, listOpts limits.ListOptsBuilder) iter.Seq2[*limits.Limit, error] - CreateLimit(ctx context.Context, opts limits.CreateOptsBuilder) (*limits.Limit, error) + CreateLimit(ctx context.Context, opts limits.BatchCreateOptsBuilder) (*limits.Limit, error) DeleteLimit(ctx context.Context, resourceID string) error GetLimit(ctx context.Context, resourceID string) (*limits.Limit, error) UpdateLimit(ctx context.Context, id string, opts limits.UpdateOptsBuilder) (*limits.Limit, error) @@ -58,8 +58,17 @@ func (c limitClient) ListLimits(ctx context.Context, listOpts limits.ListOptsBui } } -func (c limitClient) CreateLimit(ctx context.Context, opts limits.CreateOptsBuilder) (*limits.Limit, error) { - return limits.Create(ctx, c.client, opts).Extract() +func (c limitClient) CreateLimit(ctx context.Context, opts limits.BatchCreateOptsBuilder) (*limits.Limit, error) { + limits, err := limits.BatchCreate(ctx, c.client, opts).Extract() + if err != nil { + return nil, fmt.Errorf("create limit: %w", err) + } + + if len(limits) != 1 { + return nil, fmt.Errorf("unexpected limit creation result %d", len(limits)) + } + + return &limits[0], nil } func (c limitClient) DeleteLimit(ctx context.Context, resourceID string) error { @@ -87,7 +96,7 @@ func (e limitErrorClient) ListLimits(_ context.Context, _ limits.ListOptsBuilder } } -func (e limitErrorClient) CreateLimit(_ context.Context, _ limits.CreateOptsBuilder) (*limits.Limit, error) { +func (e limitErrorClient) CreateLimit(_ context.Context, _ limits.BatchCreateOptsBuilder) (*limits.Limit, error) { return nil, e.error } diff --git a/internal/osclients/mock/limit.go b/internal/osclients/mock/limit.go index 74466ff3d..bd74746ea 100644 --- a/internal/osclients/mock/limit.go +++ b/internal/osclients/mock/limit.go @@ -58,7 +58,7 @@ func (m *MockLimitClient) EXPECT() *MockLimitClientMockRecorder { } // CreateLimit mocks base method. -func (m *MockLimitClient) CreateLimit(ctx context.Context, opts limits.CreateOptsBuilder) (*limits.Limit, error) { +func (m *MockLimitClient) CreateLimit(ctx context.Context, opts limits.BatchCreateOptsBuilder) (*limits.Limit, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "CreateLimit", ctx, opts) ret0, _ := ret[0].(*limits.Limit) diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/limitfilter.go b/pkg/clients/applyconfiguration/api/v1alpha1/limitfilter.go index d7b952c16..8241a87e2 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/limitfilter.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/limitfilter.go @@ -25,11 +25,10 @@ import ( // LimitFilterApplyConfiguration represents a declarative configuration of the LimitFilter type for use // with apply. type LimitFilterApplyConfiguration struct { - Name *apiv1alpha1.OpenStackName `json:"name,omitempty"` - Description *string `json:"description,omitempty"` - ServiceRef *apiv1alpha1.KubernetesNameRef `json:"serviceRef,omitempty"` - ProjectRef *apiv1alpha1.KubernetesNameRef `json:"projectRef,omitempty"` - DomainRef *apiv1alpha1.KubernetesNameRef `json:"domainRef,omitempty"` + ServiceRef *apiv1alpha1.KubernetesNameRef `json:"serviceRef,omitempty"` + ProjectRef *apiv1alpha1.KubernetesNameRef `json:"projectRef,omitempty"` + DomainRef *apiv1alpha1.KubernetesNameRef `json:"domainRef,omitempty"` + ResourceName *string `json:"resourceName,omitempty"` } // LimitFilterApplyConfiguration constructs a declarative configuration of the LimitFilter type for use with @@ -38,22 +37,6 @@ func LimitFilter() *LimitFilterApplyConfiguration { return &LimitFilterApplyConfiguration{} } -// WithName sets the Name field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Name field is set to the value of the last call. -func (b *LimitFilterApplyConfiguration) WithName(value apiv1alpha1.OpenStackName) *LimitFilterApplyConfiguration { - b.Name = &value - return b -} - -// WithDescription sets the Description field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Description field is set to the value of the last call. -func (b *LimitFilterApplyConfiguration) WithDescription(value string) *LimitFilterApplyConfiguration { - b.Description = &value - return b -} - // WithServiceRef sets the ServiceRef field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. // If called multiple times, the ServiceRef field is set to the value of the last call. @@ -77,3 +60,11 @@ func (b *LimitFilterApplyConfiguration) WithDomainRef(value apiv1alpha1.Kubernet b.DomainRef = &value return b } + +// WithResourceName sets the ResourceName field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ResourceName field is set to the value of the last call. +func (b *LimitFilterApplyConfiguration) WithResourceName(value string) *LimitFilterApplyConfiguration { + b.ResourceName = &value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/limitresourcespec.go b/pkg/clients/applyconfiguration/api/v1alpha1/limitresourcespec.go index 8dedb1105..86d2bf210 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/limitresourcespec.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/limitresourcespec.go @@ -25,11 +25,12 @@ import ( // LimitResourceSpecApplyConfiguration represents a declarative configuration of the LimitResourceSpec type for use // with apply. type LimitResourceSpecApplyConfiguration struct { - Name *apiv1alpha1.OpenStackName `json:"name,omitempty"` - Description *string `json:"description,omitempty"` - ServiceRef *apiv1alpha1.KubernetesNameRef `json:"serviceRef,omitempty"` - ProjectRef *apiv1alpha1.KubernetesNameRef `json:"projectRef,omitempty"` - DomainRef *apiv1alpha1.KubernetesNameRef `json:"domainRef,omitempty"` + Description *string `json:"description,omitempty"` + ServiceRef *apiv1alpha1.KubernetesNameRef `json:"serviceRef,omitempty"` + ProjectRef *apiv1alpha1.KubernetesNameRef `json:"projectRef,omitempty"` + DomainRef *apiv1alpha1.KubernetesNameRef `json:"domainRef,omitempty"` + ResourceName *string `json:"resourceName,omitempty"` + ResourceLimit *int32 `json:"resourceLimit,omitempty"` } // LimitResourceSpecApplyConfiguration constructs a declarative configuration of the LimitResourceSpec type for use with @@ -38,14 +39,6 @@ func LimitResourceSpec() *LimitResourceSpecApplyConfiguration { return &LimitResourceSpecApplyConfiguration{} } -// WithName sets the Name field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Name field is set to the value of the last call. -func (b *LimitResourceSpecApplyConfiguration) WithName(value apiv1alpha1.OpenStackName) *LimitResourceSpecApplyConfiguration { - b.Name = &value - return b -} - // WithDescription sets the Description field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. // If called multiple times, the Description field is set to the value of the last call. @@ -77,3 +70,19 @@ func (b *LimitResourceSpecApplyConfiguration) WithDomainRef(value apiv1alpha1.Ku b.DomainRef = &value return b } + +// WithResourceName sets the ResourceName field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ResourceName field is set to the value of the last call. +func (b *LimitResourceSpecApplyConfiguration) WithResourceName(value string) *LimitResourceSpecApplyConfiguration { + b.ResourceName = &value + return b +} + +// WithResourceLimit sets the ResourceLimit field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ResourceLimit field is set to the value of the last call. +func (b *LimitResourceSpecApplyConfiguration) WithResourceLimit(value int32) *LimitResourceSpecApplyConfiguration { + b.ResourceLimit = &value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/limitresourcestatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/limitresourcestatus.go index 4046a51da..1283f66a0 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/limitresourcestatus.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/limitresourcestatus.go @@ -21,11 +21,12 @@ package v1alpha1 // LimitResourceStatusApplyConfiguration represents a declarative configuration of the LimitResourceStatus type for use // with apply. type LimitResourceStatusApplyConfiguration struct { - Name *string `json:"name,omitempty"` - Description *string `json:"description,omitempty"` - ServiceID *string `json:"serviceID,omitempty"` - ProjectID *string `json:"projectID,omitempty"` - DomainID *string `json:"domainID,omitempty"` + Description *string `json:"description,omitempty"` + ServiceID *string `json:"serviceID,omitempty"` + ProjectID *string `json:"projectID,omitempty"` + DomainID *string `json:"domainID,omitempty"` + ResourceLimit *int32 `json:"resourceLimit,omitempty"` + ResourceName *string `json:"resourceName,omitempty"` } // LimitResourceStatusApplyConfiguration constructs a declarative configuration of the LimitResourceStatus type for use with @@ -34,14 +35,6 @@ func LimitResourceStatus() *LimitResourceStatusApplyConfiguration { return &LimitResourceStatusApplyConfiguration{} } -// WithName sets the Name field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Name field is set to the value of the last call. -func (b *LimitResourceStatusApplyConfiguration) WithName(value string) *LimitResourceStatusApplyConfiguration { - b.Name = &value - return b -} - // WithDescription sets the Description field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. // If called multiple times, the Description field is set to the value of the last call. @@ -73,3 +66,19 @@ func (b *LimitResourceStatusApplyConfiguration) WithDomainID(value string) *Limi b.DomainID = &value return b } + +// WithResourceLimit sets the ResourceLimit field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ResourceLimit field is set to the value of the last call. +func (b *LimitResourceStatusApplyConfiguration) WithResourceLimit(value int32) *LimitResourceStatusApplyConfiguration { + b.ResourceLimit = &value + return b +} + +// WithResourceName sets the ResourceName field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ResourceName field is set to the value of the last call. +func (b *LimitResourceStatusApplyConfiguration) WithResourceName(value string) *LimitResourceStatusApplyConfiguration { + b.ResourceName = &value + return b +} diff --git a/pkg/clients/applyconfiguration/internal/internal.go b/pkg/clients/applyconfiguration/internal/internal.go index dee3289bd..2a9b62bd7 100644 --- a/pkg/clients/applyconfiguration/internal/internal.go +++ b/pkg/clients/applyconfiguration/internal/internal.go @@ -1529,16 +1529,13 @@ var schemaYAML = typed.YAMLObject(`types: - name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.LimitFilter map: fields: - - name: description - type: - scalar: string - name: domainRef type: scalar: string - - name: name + - name: projectRef type: scalar: string - - name: projectRef + - name: resourceName type: scalar: string - name: serviceRef @@ -1562,12 +1559,17 @@ var schemaYAML = typed.YAMLObject(`types: - name: domainRef type: scalar: string - - name: name + - name: projectRef type: scalar: string - - name: projectRef + - name: resourceLimit + type: + scalar: numeric + default: 0 + - name: resourceName type: scalar: string + default: "" - name: serviceRef type: scalar: string @@ -1580,10 +1582,14 @@ var schemaYAML = typed.YAMLObject(`types: - name: domainID type: scalar: string - - name: name + - name: projectID type: scalar: string - - name: projectID + - name: resourceLimit + type: + scalar: numeric + default: 0 + - name: resourceName type: scalar: string - name: serviceID diff --git a/test/apivalidations/limit_test.go b/test/apivalidations/limit_test.go index a0f4fd55b..868ad764c 100644 --- a/test/apivalidations/limit_test.go +++ b/test/apivalidations/limit_test.go @@ -79,7 +79,7 @@ var _ = Describe("ORC Limit API validations", func() { p.Spec.WithImport(applyconfigv1alpha1.LimitImport().WithFilter(applyconfigv1alpha1.LimitFilter())) }, applyValidFilter: func(p *applyconfigv1alpha1.LimitApplyConfiguration) { - p.Spec.WithImport(applyconfigv1alpha1.LimitImport().WithFilter(applyconfigv1alpha1.LimitFilter().WithName("foo"))) + p.Spec.WithImport(applyconfigv1alpha1.LimitImport().WithFilter(applyconfigv1alpha1.LimitFilter().WithServiceRef("foo"))) }, applyManaged: func(p *applyconfigv1alpha1.LimitApplyConfiguration) { p.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged) diff --git a/website/docs/crd-reference.md b/website/docs/crd-reference.md index c7457e706..b09b77691 100644 --- a/website/docs/crd-reference.md +++ b/website/docs/crd-reference.md @@ -2280,11 +2280,10 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `name` _[OpenStackName](#openstackname)_ | name of the existing resource | | MaxLength: 255
MinLength: 1
Pattern: `^[^,]+$`
Optional: \{\}
| -| `description` _string_ | description of the existing resource | | MaxLength: 255
MinLength: 1
Optional: \{\}
| | `serviceRef` _[KubernetesNameRef](#kubernetesnameref)_ | serviceRef is a reference to the ORC Service which this resource is associated with. | | MaxLength: 253
MinLength: 1
Optional: \{\}
| | `projectRef` _[KubernetesNameRef](#kubernetesnameref)_ | projectRef is a reference to the ORC Project which this resource is associated with. | | MaxLength: 253
MinLength: 1
Optional: \{\}
| | `domainRef` _[KubernetesNameRef](#kubernetesnameref)_ | domainRef is a reference to the ORC Domain which this resource is associated with. | | MaxLength: 253
MinLength: 1
Optional: \{\}
| +| `resourceName` _string_ | resoureName is the name of the resource this limit is associated with. | | MaxLength: 255
MinLength: 1
Pattern: `^[\S]+$`
Optional: \{\}
| #### LimitImport @@ -2320,11 +2319,12 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `name` _[OpenStackName](#openstackname)_ | name will be the name of the created resource. If not specified, the
name of the ORC object will be used. | | MaxLength: 255
MinLength: 1
Pattern: `^[^,]+$`
Optional: \{\}
| | `description` _string_ | description is a human-readable description for the resource. | | MaxLength: 255
MinLength: 1
Optional: \{\}
| | `serviceRef` _[KubernetesNameRef](#kubernetesnameref)_ | serviceRef is a reference to the ORC Service which this resource is associated with. | | MaxLength: 253
MinLength: 1
Required: \{\}
| | `projectRef` _[KubernetesNameRef](#kubernetesnameref)_ | projectRef is a reference to the ORC Project which this resource is associated with. | | MaxLength: 253
MinLength: 1
Optional: \{\}
| | `domainRef` _[KubernetesNameRef](#kubernetesnameref)_ | domainRef is a reference to the ORC Domain which this resource is associated with. | | MaxLength: 253
MinLength: 1
Optional: \{\}
| +| `resourceName` _string_ | resoureName is the name of the resource this limit is associated with. | | MaxLength: 255
MinLength: 1
Pattern: `^[\S]+$`
Required: \{\}
| +| `resourceLimit` _integer_ | resoureLimit is the override value of the limit. | | Minimum: -1
Required: \{\}
| #### LimitResourceStatus @@ -2340,11 +2340,12 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `name` _string_ | name is a Human-readable name for the resource. Might not be unique. | | MaxLength: 1024
Optional: \{\}
| | `description` _string_ | description is a human-readable description for the resource. | | MaxLength: 1024
Optional: \{\}
| | `serviceID` _string_ | serviceID is the ID of the Service to which the resource is associated. | | MaxLength: 1024
Optional: \{\}
| | `projectID` _string_ | projectID is the ID of the Project to which the resource is associated. | | MaxLength: 1024
Optional: \{\}
| | `domainID` _string_ | domainID is the ID of the Domain to which the resource is associated. | | MaxLength: 1024
Optional: \{\}
| +| `resourceLimit` _integer_ | resoureLimit is the override value of the limit. | | Optional: \{\}
| +| `resourceName` _string_ | resoureName is the name of the resource this limit is associated with. | | MaxLength: 1024
Optional: \{\}
| #### LimitSpec @@ -2786,8 +2787,6 @@ _Appears in:_ - [ImageResourceSpec](#imageresourcespec) - [KeyPairFilter](#keypairfilter) - [KeyPairResourceSpec](#keypairresourcespec) -- [LimitFilter](#limitfilter) -- [LimitResourceSpec](#limitresourcespec) - [NetworkFilter](#networkfilter) - [NetworkResourceSpec](#networkresourcespec) - [PortFilter](#portfilter) From 8025b903c33bf7d464300eb55772f004edf1b43f Mon Sep 17 00:00:00 2001 From: Winston Chen Date: Mon, 20 Jul 2026 12:07:19 +0100 Subject: [PATCH 05/13] Implement limit actuator and status writer --- internal/controllers/limit/actuator.go | 87 ++++++++++++++++++++------ internal/controllers/limit/status.go | 23 +++++-- 2 files changed, 84 insertions(+), 26 deletions(-) diff --git a/internal/controllers/limit/actuator.go b/internal/controllers/limit/actuator.go index 7a3c705f8..89700fba7 100644 --- a/internal/controllers/limit/actuator.go +++ b/internal/controllers/limit/actuator.go @@ -71,19 +71,51 @@ func (actuator limitActuator) ListOSResourcesForAdoption(ctx context.Context, or return nil, false } - // TODO(scaffolding) If you need to filter resources on fields that the List() function - // of gophercloud does not support, it's possible to perform client-side filtering. - // Check osclients.ResourceFilter + var rs progress.ReconcileStatus - listOpts := limits.ListOpts{} + svc, rs1 := dependency.FetchDependency( + ctx, actuator.k8sClient, orcObject.Namespace, &resourceSpec.ServiceRef, "Service", + func(dep *orcv1alpha1.Service) bool { + return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil + }, + ) + rs.WithReconcileStatus(rs1) + + project, rs1 := dependency.FetchDependency( + ctx, actuator.k8sClient, orcObject.Namespace, &resourceSpec.ServiceRef, "Project", + func(dep *orcv1alpha1.Service) bool { + return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil + }, + ) + rs.WithReconcileStatus(rs1) + + domain, rs1 := dependency.FetchDependency( + ctx, actuator.k8sClient, orcObject.Namespace, &resourceSpec.ServiceRef, "Domain", + func(dep *orcv1alpha1.Service) bool { + return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil + }, + ) + rs.WithReconcileStatus(rs1) + + if needsReschedule, err := rs.NeedsReschedule(); needsReschedule { + if err != nil { + ctrl.LoggerFrom(ctx).Info("fetch dependency before listing limit for adoption", "error", err) + } + + return nil, false + } + + listOpts := limits.ListOpts{ + ServiceID: ptr.Deref(svc.Status.ID, ""), + ProjectID: ptr.Deref(project.Status.ID, ""), + DomainID: ptr.Deref(domain.Status.ID, ""), + ResourceName: resourceSpec.ResourceName, + } return actuator.osClient.ListLimits(ctx, listOpts), true } func (actuator limitActuator) ListOSResourcesForImport(ctx context.Context, obj orcObjectPT, filter filterT) (iter.Seq2[*osResourceT, error], progress.ReconcileStatus) { - // TODO(scaffolding) If you need to filter resources on fields that the List() function - // of gophercloud does not support, it's possible to perform client-side filtering. - // Check osclients.ResourceFilter var reconcileStatus progress.ReconcileStatus service, rs := dependency.FetchDependency[*orcv1alpha1.Service]( @@ -107,15 +139,19 @@ func (actuator limitActuator) ListOSResourcesForImport(ctx context.Context, obj ) reconcileStatus = reconcileStatus.WithReconcileStatus(rs) - if needsReschedule, _ := reconcileStatus.NeedsReschedule(); needsReschedule { + if needsReschedule, err := reconcileStatus.NeedsReschedule(); needsReschedule { + if err != nil { + ctrl.LoggerFrom(ctx).Info("fetch dependency before listing limit for import", "error", err) + } + return nil, reconcileStatus } listOpts := limits.ListOpts{ - ServiceID: ptr.Deref(service.Status.ID, ""), - ProjectID: ptr.Deref(project.Status.ID, ""), - DomainID: ptr.Deref(domain.Status.ID, ""), - // TODO(scaffolding): Add more import filters + ServiceID: ptr.Deref(service.Status.ID, ""), + ProjectID: ptr.Deref(project.Status.ID, ""), + DomainID: ptr.Deref(domain.Status.ID, ""), + ResourceName: filter.ResourceName, } return actuator.osClient.ListLimits(ctx, listOpts), reconcileStatus @@ -161,15 +197,20 @@ func (actuator limitActuator) CreateResource(ctx context.Context, obj orcObjectP domainID = ptr.Deref(domain.Status.ID, "") } } - if needsReschedule, _ := reconcileStatus.NeedsReschedule(); needsReschedule { + if needsReschedule, err := reconcileStatus.NeedsReschedule(); needsReschedule { + if err != nil { + ctrl.LoggerFrom(ctx).Info("fetch dependency before creating limit", "error", err) + } + return nil, reconcileStatus } createOpts := limits.CreateOpts{ - Description: ptr.Deref(resource.Description, ""), - ServiceID: serviceID, - ProjectID: projectID, - DomainID: domainID, - // TODO(scaffolding): Add more fields + Description: ptr.Deref(resource.Description, ""), + ServiceID: serviceID, + ProjectID: projectID, + DomainID: domainID, + ResourceName: resource.ResourceName, + ResourceLimit: int(resource.ResourceLimit), } osResource, err := actuator.osClient.CreateLimit(ctx, limits.BatchCreateOpts{createOpts}) @@ -199,8 +240,7 @@ func (actuator limitActuator) updateResource(ctx context.Context, obj orcObjectP updateOpts := limits.UpdateOpts{} handleDescriptionUpdate(&updateOpts, resource, osResource) - - // TODO(scaffolding): add handler for all fields supporting mutability + handleResourceLimitUpdate(&updateOpts, resource, osResource) needsUpdate, err := needsUpdate(updateOpts) if err != nil { @@ -245,6 +285,13 @@ func handleDescriptionUpdate(updateOpts *limits.UpdateOpts, resource *resourceSp } } +func handleResourceLimitUpdate(updateOpts *limits.UpdateOpts, resource *resourceSpecT, osResource *osResourceT) { + rl := int(resource.ResourceLimit) + if osResource.ResourceLimit != rl { + updateOpts.ResourceLimit = &rl + } +} + func (actuator limitActuator) GetResourceReconcilers(ctx context.Context, orcObject orcObjectPT, osResource *osResourceT, controller interfaces.ResourceController) ([]resourceReconciler, progress.ReconcileStatus) { return []resourceReconciler{ actuator.updateResource, diff --git a/internal/controllers/limit/status.go b/internal/controllers/limit/status.go index 39aad877a..0d48c71e0 100644 --- a/internal/controllers/limit/status.go +++ b/internal/controllers/limit/status.go @@ -50,16 +50,27 @@ func (limitStatusWriter) ResourceAvailableStatus(orcObject *orcv1alpha1.Limit, o func (limitStatusWriter) ApplyResourceStatus(log logr.Logger, osResource *osResourceT, statusApply *statusApplyT) { resourceStatus := orcapplyconfigv1alpha1.LimitResourceStatus(). - WithServiceID(osResource.ServiceID). - WithProjectID(osResource.ProjectID). - WithDomainID(osResource.DomainID) - - // TODO(scaffolding): add all of the fields supported in the LimitResourceStatus struct - // If a zero-value isn't expected in the response, place it behind a conditional + WithResourceLimit(int32(osResource.ResourceLimit)) if osResource.Description != "" { resourceStatus.WithDescription(osResource.Description) } + if osResource.ResourceName != "" { + resourceStatus.WithResourceName(osResource.ResourceName) + } + + if osResource.ServiceID != "" { + resourceStatus.WithServiceID(osResource.ServiceID) + } + + if osResource.ProjectID != "" { + resourceStatus.WithProjectID(osResource.ProjectID) + } + + if osResource.DomainID != "" { + resourceStatus.WithDomainID(osResource.DomainID) + } + statusApply.WithResource(resourceStatus) } From e535421b26dba523f2f5732511981926d03aa7f6 Mon Sep 17 00:00:00 2001 From: Winston Chen Date: Tue, 21 Jul 2026 16:28:42 +0100 Subject: [PATCH 06/13] Add api validation and acutator test cases and fix field rules for Limit struct --- api/v1alpha1/limit_types.go | 10 ++- api/v1alpha1/zz_generated.limit-resource.go | 2 + cmd/models-schema/zz_generated.openapi.go | 6 +- cmd/resource-generator/main.go | 14 ++++ .../bases/openstack.k-orc.cloud_limits.yaml | 25 +++++-- config/samples/openstack_v1alpha1_limit.yaml | 13 ++-- internal/controllers/limit/actuator_test.go | 27 ++++++++ .../applyconfiguration/internal/internal.go | 2 +- test/apivalidations/limit_test.go | 69 +++++++++++++++---- website/docs/crd-reference.md | 4 +- 10 files changed, 141 insertions(+), 31 deletions(-) diff --git a/api/v1alpha1/limit_types.go b/api/v1alpha1/limit_types.go index cba32c509..0429e7c21 100644 --- a/api/v1alpha1/limit_types.go +++ b/api/v1alpha1/limit_types.go @@ -17,6 +17,8 @@ limitations under the License. package v1alpha1 // LimitResourceSpec contains the desired state of the resource. +// +kubebuilder:validation:XValidation:rule="has(self.projectRef) || has(self.domainRef)",message="either projectRef or domainRef must be specified" +// +kubebuilder:validation:XValidation:rule="!(has(self.projectRef) && has(self.domainRef))",message="projectRef and domainRef are mutually exclusive" type LimitResourceSpec struct { // description is a human-readable description for the resource. // +kubebuilder:validation:MinLength:=1 @@ -27,14 +29,18 @@ type LimitResourceSpec struct { // serviceRef is a reference to the ORC Service which this resource is associated with. // +required // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="serviceRef is immutable" - ServiceRef KubernetesNameRef `json:"serviceRef,omitempty"` + ServiceRef KubernetesNameRef `json:"serviceRef"` // projectRef is a reference to the ORC Project which this resource is associated with. + // Either Domain ID or Project ID must be provided. + // https://opendev.org/openstack/keystone/src/commit/30ef2ffa65a3486ef882f00538e20f2253c57d4c/keystone/limit/schema.py#L323-L340 // +optional // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="projectRef is immutable" ProjectRef *KubernetesNameRef `json:"projectRef,omitempty"` // domainRef is a reference to the ORC Domain which this resource is associated with. + // Either Domain ID or Project ID must be provided. + // https://opendev.org/openstack/keystone/src/commit/30ef2ffa65a3486ef882f00538e20f2253c57d4c/keystone/limit/schema.py#L323-L340 // +optional // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="domainRef is immutable" DomainRef *KubernetesNameRef `json:"domainRef,omitempty"` @@ -100,7 +106,7 @@ type LimitResourceStatus struct { // resoureLimit is the override value of the limit. // +optional - ResourceLimit int32 `json:"resourceLimit"` + ResourceLimit int32 `json:"resourceLimit,omitempty"` // resoureName is the name of the resource this limit is associated with. // +kubebuilder:validation:MaxLength=1024 diff --git a/api/v1alpha1/zz_generated.limit-resource.go b/api/v1alpha1/zz_generated.limit-resource.go index bbae443da..e1aee592b 100644 --- a/api/v1alpha1/zz_generated.limit-resource.go +++ b/api/v1alpha1/zz_generated.limit-resource.go @@ -141,6 +141,8 @@ func (i *Limit) GetConditions() []metav1.Condition { // +kubebuilder:subresource:status // +kubebuilder:printcolumn:name="ID",type="string",JSONPath=".status.id",description="Resource ID" // +kubebuilder:printcolumn:name="Available",type="string",JSONPath=".status.conditions[?(@.type=='Available')].status",description="Availability status of resource" +// +kubebuilder:printcolumn:name="Resource",type="string",JSONPath=".status.resource.resourceName",description="The name of the resource this limit is associated with" +// +kubebuilder:printcolumn:name="Limit",type="integer",JSONPath=".status.resource.resourceLimit",description="The override value of this limit" // +kubebuilder:printcolumn:name="Message",type="string",JSONPath=".status.conditions[?(@.type=='Progressing')].message",description="Message describing current progress status" // Limit is the Schema for an ORC resource. diff --git a/cmd/models-schema/zz_generated.openapi.go b/cmd/models-schema/zz_generated.openapi.go index 45bd3dcfc..ef794c544 100644 --- a/cmd/models-schema/zz_generated.openapi.go +++ b/cmd/models-schema/zz_generated.openapi.go @@ -5544,20 +5544,21 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_LimitResourceSpec(ref "serviceRef": { SchemaProps: spec.SchemaProps{ Description: "serviceRef is a reference to the ORC Service which this resource is associated with.", + Default: "", Type: []string{"string"}, Format: "", }, }, "projectRef": { SchemaProps: spec.SchemaProps{ - Description: "projectRef is a reference to the ORC Project which this resource is associated with.", + Description: "projectRef is a reference to the ORC Project which this resource is associated with. Either Domain ID or Project ID must be provided. https://opendev.org/openstack/keystone/src/commit/30ef2ffa65a3486ef882f00538e20f2253c57d4c/keystone/limit/schema.py#L323-L340", Type: []string{"string"}, Format: "", }, }, "domainRef": { SchemaProps: spec.SchemaProps{ - Description: "domainRef is a reference to the ORC Domain which this resource is associated with.", + Description: "domainRef is a reference to the ORC Domain which this resource is associated with. Either Domain ID or Project ID must be provided. https://opendev.org/openstack/keystone/src/commit/30ef2ffa65a3486ef882f00538e20f2253c57d4c/keystone/limit/schema.py#L323-L340", Type: []string{"string"}, Format: "", }, @@ -5623,7 +5624,6 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_LimitResourceStatus(re "resourceLimit": { SchemaProps: spec.SchemaProps{ Description: "resoureLimit is the override value of the limit.", - Default: 0, Type: []string{"integer"}, Format: "int32", }, diff --git a/cmd/resource-generator/main.go b/cmd/resource-generator/main.go index e9adbe0b7..da2a91243 100644 --- a/cmd/resource-generator/main.go +++ b/cmd/resource-generator/main.go @@ -192,6 +192,20 @@ var resources []templateFields = []templateFields{ { Name: "Limit", IsNotNamed: true, + AdditionalPrintColumns: []additionalPrintColumn{ + { + Name: "Resource", + Type: "string", + JSONPath: ".status.resource.resourceName", + Description: "The name of the resource this limit is associated with", + }, + { + Name: "Limit", + Type: "integer", + JSONPath: ".status.resource.resourceLimit", + Description: "The override value of this limit", + }, + }, }, } diff --git a/config/crd/bases/openstack.k-orc.cloud_limits.yaml b/config/crd/bases/openstack.k-orc.cloud_limits.yaml index d477e12fc..cb3774505 100644 --- a/config/crd/bases/openstack.k-orc.cloud_limits.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_limits.yaml @@ -25,6 +25,14 @@ spec: jsonPath: .status.conditions[?(@.type=='Available')].status name: Available type: string + - description: The name of the resource this limit is associated with + jsonPath: .status.resource.resourceName + name: Resource + type: string + - description: The override value of this limit + jsonPath: .status.resource.resourceLimit + name: Limit + type: integer - description: Message describing current progress status jsonPath: .status.conditions[?(@.type=='Progressing')].message name: Message @@ -171,8 +179,10 @@ spec: minLength: 1 type: string domainRef: - description: domainRef is a reference to the ORC Domain which - this resource is associated with. + description: |- + domainRef is a reference to the ORC Domain which this resource is associated with. + Either Domain ID or Project ID must be provided. + https://opendev.org/openstack/keystone/src/commit/30ef2ffa65a3486ef882f00538e20f2253c57d4c/keystone/limit/schema.py#L323-L340 maxLength: 253 minLength: 1 type: string @@ -180,8 +190,10 @@ spec: - message: domainRef is immutable rule: self == oldSelf projectRef: - description: projectRef is a reference to the ORC Project which - this resource is associated with. + description: |- + projectRef is a reference to the ORC Project which this resource is associated with. + Either Domain ID or Project ID must be provided. + https://opendev.org/openstack/keystone/src/commit/30ef2ffa65a3486ef882f00538e20f2253c57d4c/keystone/limit/schema.py#L323-L340 maxLength: 253 minLength: 1 type: string @@ -217,6 +229,11 @@ spec: - resourceName - serviceRef type: object + x-kubernetes-validations: + - message: either projectRef or domainRef must be specified + rule: has(self.projectRef) || has(self.domainRef) + - message: projectRef and domainRef are mutually exclusive + rule: '!(has(self.projectRef) && has(self.domainRef))' resyncPeriod: description: |- resyncPeriod defines how frequently the controller will re-reconcile diff --git a/config/samples/openstack_v1alpha1_limit.yaml b/config/samples/openstack_v1alpha1_limit.yaml index de5c7bde6..6f76912b0 100644 --- a/config/samples/openstack_v1alpha1_limit.yaml +++ b/config/samples/openstack_v1alpha1_limit.yaml @@ -2,13 +2,16 @@ apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Limit metadata: - name: limit-sample + name: limit-managed spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created - cloudName: openstack + cloudName: openstack-admin secretName: openstack-clouds managementPolicy: managed resource: - description: Sample Limit - # TODO(scaffolding): Add all fields the resource supports + description: Nova limit servers for project demo + serviceRef: nova + projectRef: demo # Either projectRef or domainRef must be provided. + domainRef: default + resourceName: servers + resourceLimit: 10 diff --git a/internal/controllers/limit/actuator_test.go b/internal/controllers/limit/actuator_test.go index 8236cd65d..c57195375 100644 --- a/internal/controllers/limit/actuator_test.go +++ b/internal/controllers/limit/actuator_test.go @@ -87,3 +87,30 @@ func TestHandleDescriptionUpdate(t *testing.T) { } } + +func TestHandleResourceLimitUpdate(t *testing.T) { + testCases := []struct { + name string + newValue int32 + existingValue int + expectChange bool + }{ + {name: "Identical", newValue: -1, existingValue: -1, expectChange: false}, + {name: "Different", newValue: -1, existingValue: 10, expectChange: true}, + } + + for _, tt := range testCases { + t.Run(tt.name, func(t *testing.T) { + resource := &orcv1alpha1.LimitResourceSpec{ResourceLimit: tt.newValue} + osResource := &osResourceT{ResourceLimit: tt.existingValue} + + updateOpts := limits.UpdateOpts{} + handleResourceLimitUpdate(&updateOpts, resource, osResource) + + got, _ := needsUpdate(updateOpts) + if got != tt.expectChange { + t.Errorf("Expected change: %v, got: %v", tt.expectChange, got) + } + }) + } +} diff --git a/pkg/clients/applyconfiguration/internal/internal.go b/pkg/clients/applyconfiguration/internal/internal.go index 2a9b62bd7..aa7197ad7 100644 --- a/pkg/clients/applyconfiguration/internal/internal.go +++ b/pkg/clients/applyconfiguration/internal/internal.go @@ -1573,6 +1573,7 @@ var schemaYAML = typed.YAMLObject(`types: - name: serviceRef type: scalar: string + default: "" - name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.LimitResourceStatus map: fields: @@ -1588,7 +1589,6 @@ var schemaYAML = typed.YAMLObject(`types: - name: resourceLimit type: scalar: numeric - default: 0 - name: resourceName type: scalar: string diff --git a/test/apivalidations/limit_test.go b/test/apivalidations/limit_test.go index 868ad764c..1b93f703e 100644 --- a/test/apivalidations/limit_test.go +++ b/test/apivalidations/limit_test.go @@ -18,6 +18,7 @@ package apivalidations import ( "context" + "strings" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -42,7 +43,13 @@ func limitStub(namespace *corev1.Namespace) *orcv1alpha1.Limit { func testLimitResource() *applyconfigv1alpha1.LimitResourceSpecApplyConfiguration { return applyconfigv1alpha1.LimitResourceSpec(). - WithServiceRef("service") + WithServiceRef("nova"). + WithResourceName("servers"). + WithResourceLimit(10) +} + +func testLimitResourceWithProject() *applyconfigv1alpha1.LimitResourceSpecApplyConfiguration { + return testLimitResource().WithProjectRef("demo") } func baseLimitPatch(obj client.Object) *applyconfigv1alpha1.LimitApplyConfiguration { @@ -67,7 +74,7 @@ var _ = Describe("ORC Limit API validations", func() { return baseLimitPatch(obj) }, applyResource: func(p *applyconfigv1alpha1.LimitApplyConfiguration) { - p.Spec.WithResource(testLimitResource()) + p.Spec.WithResource(testLimitResourceWithProject()) }, applyImport: func(p *applyconfigv1alpha1.LimitApplyConfiguration) { p.Spec.WithImport(testLimitImport()) @@ -108,12 +115,11 @@ var _ = Describe("ORC Limit API validations", func() { It("should have immutable serviceRef", func(ctx context.Context) { obj := limitStub(namespace) patch := baseLimitPatch(obj) - patch.Spec.WithResource(testLimitResource(). - WithServiceRef("service-a")) + res := testLimitResourceWithProject() + patch.Spec.WithResource(res.WithServiceRef("service-a")) Expect(applyObj(ctx, obj, patch)).To(Succeed()) - patch.Spec.WithResource(testLimitResource(). - WithServiceRef("service-b")) + patch.Spec.WithResource(res.WithServiceRef("service-b")) Expect(applyObj(ctx, obj, patch)).To(MatchError(ContainSubstring("serviceRef is immutable"))) }) @@ -141,12 +147,47 @@ var _ = Describe("ORC Limit API validations", func() { Expect(applyObj(ctx, obj, patch)).To(MatchError(ContainSubstring("domainRef is immutable"))) }) - // TODO(scaffolding): Add more resource-specific validation tests. - // Some common things to test: - // - Immutability of fields with `self == oldSelf` validation - // - Enum validation (valid and invalid values) - // - Numeric range validation (min/max bounds) - // - Tag uniqueness (if the resource has tags with listType=set) - // - Format validation (CIDR, UUID, etc.) - // - Cross-field validation rules + It("should have immutable resourceName", func(ctx context.Context) { + obj := limitStub(namespace) + patch := baseLimitPatch(obj) + res := testLimitResourceWithProject() + + patch.Spec.WithResource(res.WithResourceName("servers")) + Expect(applyObj(ctx, obj, patch)).To(Succeed()) + + patch.Spec.WithResource(res.WithResourceName("server_key_pairs")) + Expect(applyObj(ctx, obj, patch)).To(MatchError(ContainSubstring("resourceName is immutable"))) + }) + + It("should reject invalid resourceName", func(ctx context.Context) { + obj := limitStub(namespace) + patch := baseLimitPatch(obj) + res := testLimitResourceWithProject() + + patch.Spec.WithResource(res.WithResourceName("invalid-name-\r\n")) + Expect(applyObj(ctx, obj, patch)).To(MatchError(ContainSubstring(`spec.resource.resourceName in body should match '^[\S]+$'`))) + + patch = baseLimitPatch(obj) + patch.Spec.WithResource(res.WithResourceName(strings.Repeat("a", 256))) + Expect(applyObj(ctx, obj, patch)).To(MatchError(ContainSubstring(`spec.resource.resourceName: Too long: may not be longer than 255`))) + }) + + It("should reject invalid resourceLimit", func(ctx context.Context) { + obj := limitStub(namespace) + patch := baseLimitPatch(obj) + patch.Spec.WithResource(testLimitResourceWithProject().WithResourceLimit(-2)) + Expect(applyObj(ctx, obj, patch)).To(MatchError(ContainSubstring(`spec.resource.resourceLimit in body should be greater than or equal to -1`))) + }) + + It("should include either projectRef or domainRef but not both", func(ctx context.Context) { + obj := limitStub(namespace) + patch := baseLimitPatch(obj) + res := testLimitResource() + + patch.Spec.WithResource(res) + Expect(applyObj(ctx, obj, patch)).To(MatchError(ContainSubstring(`either projectRef or domainRef must be specified`))) + + patch.Spec.WithResource(res.WithProjectRef("demo").WithDomainRef("default")) + Expect(applyObj(ctx, obj, patch)).To(MatchError(ContainSubstring(`projectRef and domainRef are mutually exclusive`))) + }) }) diff --git a/website/docs/crd-reference.md b/website/docs/crd-reference.md index b09b77691..117037720 100644 --- a/website/docs/crd-reference.md +++ b/website/docs/crd-reference.md @@ -2321,8 +2321,8 @@ _Appears in:_ | --- | --- | --- | --- | | `description` _string_ | description is a human-readable description for the resource. | | MaxLength: 255
MinLength: 1
Optional: \{\}
| | `serviceRef` _[KubernetesNameRef](#kubernetesnameref)_ | serviceRef is a reference to the ORC Service which this resource is associated with. | | MaxLength: 253
MinLength: 1
Required: \{\}
| -| `projectRef` _[KubernetesNameRef](#kubernetesnameref)_ | projectRef is a reference to the ORC Project which this resource is associated with. | | MaxLength: 253
MinLength: 1
Optional: \{\}
| -| `domainRef` _[KubernetesNameRef](#kubernetesnameref)_ | domainRef is a reference to the ORC Domain which this resource is associated with. | | MaxLength: 253
MinLength: 1
Optional: \{\}
| +| `projectRef` _[KubernetesNameRef](#kubernetesnameref)_ | projectRef is a reference to the ORC Project which this resource is associated with.
Either Domain ID or Project ID must be provided.
https://opendev.org/openstack/keystone/src/commit/30ef2ffa65a3486ef882f00538e20f2253c57d4c/keystone/limit/schema.py#L323-L340 | | MaxLength: 253
MinLength: 1
Optional: \{\}
| +| `domainRef` _[KubernetesNameRef](#kubernetesnameref)_ | domainRef is a reference to the ORC Domain which this resource is associated with.
Either Domain ID or Project ID must be provided.
https://opendev.org/openstack/keystone/src/commit/30ef2ffa65a3486ef882f00538e20f2253c57d4c/keystone/limit/schema.py#L323-L340 | | MaxLength: 253
MinLength: 1
Optional: \{\}
| | `resourceName` _string_ | resoureName is the name of the resource this limit is associated with. | | MaxLength: 255
MinLength: 1
Pattern: `^[\S]+$`
Required: \{\}
| | `resourceLimit` _integer_ | resoureLimit is the override value of the limit. | | Minimum: -1
Required: \{\}
| From 03e4d2cbbf97f1ef34e1c1db738dfe932e08b1ac Mon Sep 17 00:00:00 2001 From: Winston Chen Date: Thu, 23 Jul 2026 16:10:45 +0100 Subject: [PATCH 07/13] Add e2e test cases for Limit controller --- api/v1alpha1/limit_types.go | 10 +- api/v1alpha1/zz_generated.limit-resource.go | 1 + cmd/models-schema/zz_generated.openapi.go | 10 +- cmd/resource-generator/main.go | 6 + .../bases/openstack.k-orc.cloud_limits.yaml | 14 +- internal/controllers/limit/actuator.go | 27 ++ internal/controllers/limit/actuator_test.go | 50 ++++ .../tests/limit-create-full/00-assert.yaml | 88 ++++-- .../limit-create-full/00-create-resource.yaml | 66 +++-- .../tests/limit-create-full/00-secret.yaml | 2 + .../limit-create-full/00-setup-cleanup.yaml | 78 ++++++ .../limit/tests/limit-create-full/README.md | 10 +- .../tests/limit-create-minimal/00-assert.yaml | 79 ++++-- .../00-create-resource.yaml | 69 ++++- .../tests/limit-create-minimal/00-secret.yaml | 2 + .../00-setup-cleanup.yaml | 78 ++++++ .../tests/limit-create-minimal/01-assert.yaml | 30 +- ...e-secret.yaml => 01-delete-resources.yaml} | 2 + .../tests/limit-create-minimal/README.md | 12 +- .../tests/limit-dependency/00-assert.yaml | 43 ++- .../00-create-resources-missing-deps.yaml | 70 ++--- .../tests/limit-dependency/01-assert.yaml | 23 +- .../01-create-dependencies.yaml | 32 ++- .../limit-dependency/01-setup-cleanup.yaml | 78 ++++++ .../tests/limit-dependency/02-assert.yaml | 48 ++-- .../02-delete-dependencies.yaml | 8 +- .../tests/limit-dependency/03-assert.yaml | 18 +- .../limit-dependency/03-delete-resources.yaml | 21 +- .../limit/tests/limit-dependency/README.md | 6 +- .../limit-import-dependency/00-assert.yaml | 8 +- .../00-import-resource.yaml | 44 ++- .../limit-import-dependency/01-assert.yaml | 51 ++-- .../01-create-resource.yaml | 44 +++ .../01-create-trap-resource.yaml | 56 ---- .../01-setup-cleanup.yaml | 85 ++++++ .../limit-import-dependency/02-assert.yaml | 44 +-- .../02-create-resource.yaml | 55 ---- .../02-delete-import-dependencies.yaml | 7 + .../limit-import-dependency/03-assert.yaml | 8 +- .../03-delete-import-dependencies.yaml | 11 - ...-resource.yaml => 03-delete-resource.yaml} | 0 .../limit-import-dependency/04-assert.yaml | 6 - .../tests/limit-import-dependency/README.md | 18 +- .../tests/limit-import-error/00-assert.yaml | 10 +- .../00-create-resources.yaml | 76 ++++-- .../tests/limit-import-error/00-secret.yaml | 2 + .../limit-import-error/00-setup-cleanup.yaml | 79 ++++++ .../01-import-resource.yaml | 4 +- .../limit/tests/limit-import-error/README.md | 8 +- .../limit-import/00-import-resource.yaml | 51 +++- .../limit/tests/limit-import/00-secret.yaml | 2 + .../tests/limit-import/00-setup-cleanup.yaml | 79 ++++++ .../limit/tests/limit-import/01-assert.yaml | 4 +- .../limit-import/01-create-trap-resource.yaml | 22 +- .../limit/tests/limit-import/02-assert.yaml | 27 +- .../limit-import/02-create-resource.yaml | 22 +- .../limit/tests/limit-import/README.md | 6 +- .../limit/tests/limit-update/00-assert.yaml | 27 +- .../limit-update/00-dependency-resource.yaml | 41 +++ .../limit-update/00-minimal-resource.yaml | 26 +- .../limit/tests/limit-update/00-secret.yaml | 2 + .../tests/limit-update/00-setup-cleanup.yaml | 79 ++++++ .../limit/tests/limit-update/01-assert.yaml | 17 +- .../limit-update/01-updated-resource.yaml | 5 +- .../limit/tests/limit-update/02-assert.yaml | 26 +- .../limit/tests/limit-update/03-assert.yaml | 14 + .../limit-update/03-updated-resource.yaml | 9 + .../limit/tests/limit-update/README.md | 4 +- .../controllers/limit/tests/utils/hooks.go | 41 +++ .../controllers/limit/tests/utils/main.go | 256 ++++++++++++++++++ .../controllers/limit/tests/utils/utils.go | 99 +++++++ website/docs/crd-reference.md | 10 +- 72 files changed, 1803 insertions(+), 663 deletions(-) create mode 100644 internal/controllers/limit/tests/limit-create-full/00-setup-cleanup.yaml create mode 100644 internal/controllers/limit/tests/limit-create-minimal/00-setup-cleanup.yaml rename internal/controllers/limit/tests/limit-create-minimal/{01-delete-secret.yaml => 01-delete-resources.yaml} (61%) create mode 100644 internal/controllers/limit/tests/limit-dependency/01-setup-cleanup.yaml create mode 100644 internal/controllers/limit/tests/limit-import-dependency/01-create-resource.yaml delete mode 100644 internal/controllers/limit/tests/limit-import-dependency/01-create-trap-resource.yaml create mode 100644 internal/controllers/limit/tests/limit-import-dependency/01-setup-cleanup.yaml delete mode 100644 internal/controllers/limit/tests/limit-import-dependency/02-create-resource.yaml create mode 100644 internal/controllers/limit/tests/limit-import-dependency/02-delete-import-dependencies.yaml delete mode 100644 internal/controllers/limit/tests/limit-import-dependency/03-delete-import-dependencies.yaml rename internal/controllers/limit/tests/limit-import-dependency/{04-delete-resource.yaml => 03-delete-resource.yaml} (100%) delete mode 100644 internal/controllers/limit/tests/limit-import-dependency/04-assert.yaml create mode 100644 internal/controllers/limit/tests/limit-import-error/00-setup-cleanup.yaml create mode 100644 internal/controllers/limit/tests/limit-import/00-setup-cleanup.yaml create mode 100644 internal/controllers/limit/tests/limit-update/00-dependency-resource.yaml create mode 100644 internal/controllers/limit/tests/limit-update/00-setup-cleanup.yaml create mode 100644 internal/controllers/limit/tests/limit-update/03-assert.yaml create mode 100644 internal/controllers/limit/tests/limit-update/03-updated-resource.yaml create mode 100644 internal/controllers/limit/tests/utils/hooks.go create mode 100644 internal/controllers/limit/tests/utils/main.go create mode 100644 internal/controllers/limit/tests/utils/utils.go diff --git a/api/v1alpha1/limit_types.go b/api/v1alpha1/limit_types.go index 0429e7c21..1fc1d9130 100644 --- a/api/v1alpha1/limit_types.go +++ b/api/v1alpha1/limit_types.go @@ -45,7 +45,7 @@ type LimitResourceSpec struct { // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="domainRef is immutable" DomainRef *KubernetesNameRef `json:"domainRef,omitempty"` - // resoureName is the name of the resource this limit is associated with. + // resourceName is the name of the resource this limit is associated with. // +kubebuilder:validation:MinLength:=1 // +kubebuilder:validation:MaxLength:=255 // +kubebuilder:validation:Pattern=`^[\S]+$` @@ -53,7 +53,7 @@ type LimitResourceSpec struct { // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="resourceName is immutable" ResourceName string `json:"resourceName"` - // resoureLimit is the override value of the limit. + // resourceLimit is the override value of the limit. // +kubebuilder:validation:Minimum=-1 // +required ResourceLimit int32 `json:"resourceLimit"` @@ -74,7 +74,7 @@ type LimitFilter struct { // +optional DomainRef *KubernetesNameRef `json:"domainRef,omitempty"` - // resoureName is the name of the resource this limit is associated with. + // resourceName is the name of the resource this limit is associated with. // +kubebuilder:validation:MinLength:=1 // +kubebuilder:validation:MaxLength:=255 // +kubebuilder:validation:Pattern=`^[\S]+$` @@ -104,11 +104,11 @@ type LimitResourceStatus struct { // +optional DomainID string `json:"domainID,omitempty"` - // resoureLimit is the override value of the limit. + // resourceLimit is the override value of the limit. // +optional ResourceLimit int32 `json:"resourceLimit,omitempty"` - // resoureName is the name of the resource this limit is associated with. + // resourceName is the name of the resource this limit is associated with. // +kubebuilder:validation:MaxLength=1024 // +optional ResourceName string `json:"resourceName,omitempty"` diff --git a/api/v1alpha1/zz_generated.limit-resource.go b/api/v1alpha1/zz_generated.limit-resource.go index e1aee592b..5c3789c0d 100644 --- a/api/v1alpha1/zz_generated.limit-resource.go +++ b/api/v1alpha1/zz_generated.limit-resource.go @@ -143,6 +143,7 @@ func (i *Limit) GetConditions() []metav1.Condition { // +kubebuilder:printcolumn:name="Available",type="string",JSONPath=".status.conditions[?(@.type=='Available')].status",description="Availability status of resource" // +kubebuilder:printcolumn:name="Resource",type="string",JSONPath=".status.resource.resourceName",description="The name of the resource this limit is associated with" // +kubebuilder:printcolumn:name="Limit",type="integer",JSONPath=".status.resource.resourceLimit",description="The override value of this limit" +// +kubebuilder:printcolumn:name="Policy",type="string",JSONPath=".spec.managementPolicy",description="Whether this limit is imported" // +kubebuilder:printcolumn:name="Message",type="string",JSONPath=".status.conditions[?(@.type=='Progressing')].message",description="Message describing current progress status" // Limit is the Schema for an ORC resource. diff --git a/cmd/models-schema/zz_generated.openapi.go b/cmd/models-schema/zz_generated.openapi.go index ef794c544..e08c1b73d 100644 --- a/cmd/models-schema/zz_generated.openapi.go +++ b/cmd/models-schema/zz_generated.openapi.go @@ -5437,7 +5437,7 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_LimitFilter(ref common }, "resourceName": { SchemaProps: spec.SchemaProps{ - Description: "resoureName is the name of the resource this limit is associated with.", + Description: "resourceName is the name of the resource this limit is associated with.", Type: []string{"string"}, Format: "", }, @@ -5565,7 +5565,7 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_LimitResourceSpec(ref }, "resourceName": { SchemaProps: spec.SchemaProps{ - Description: "resoureName is the name of the resource this limit is associated with.", + Description: "resourceName is the name of the resource this limit is associated with.", Default: "", Type: []string{"string"}, Format: "", @@ -5573,7 +5573,7 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_LimitResourceSpec(ref }, "resourceLimit": { SchemaProps: spec.SchemaProps{ - Description: "resoureLimit is the override value of the limit.", + Description: "resourceLimit is the override value of the limit.", Default: 0, Type: []string{"integer"}, Format: "int32", @@ -5623,14 +5623,14 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_LimitResourceStatus(re }, "resourceLimit": { SchemaProps: spec.SchemaProps{ - Description: "resoureLimit is the override value of the limit.", + Description: "resourceLimit is the override value of the limit.", Type: []string{"integer"}, Format: "int32", }, }, "resourceName": { SchemaProps: spec.SchemaProps{ - Description: "resoureName is the name of the resource this limit is associated with.", + Description: "resourceName is the name of the resource this limit is associated with.", Type: []string{"string"}, Format: "", }, diff --git a/cmd/resource-generator/main.go b/cmd/resource-generator/main.go index da2a91243..41f117142 100644 --- a/cmd/resource-generator/main.go +++ b/cmd/resource-generator/main.go @@ -205,6 +205,12 @@ var resources []templateFields = []templateFields{ JSONPath: ".status.resource.resourceLimit", Description: "The override value of this limit", }, + { + Name: "Policy", + Type: "string", + JSONPath: ".spec.managementPolicy", + Description: "Whether this limit is imported", + }, }, }, } diff --git a/config/crd/bases/openstack.k-orc.cloud_limits.yaml b/config/crd/bases/openstack.k-orc.cloud_limits.yaml index cb3774505..a80b9d723 100644 --- a/config/crd/bases/openstack.k-orc.cloud_limits.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_limits.yaml @@ -33,6 +33,10 @@ spec: jsonPath: .status.resource.resourceLimit name: Limit type: integer + - description: Whether this limit is imported + jsonPath: .spec.managementPolicy + name: Policy + type: string - description: Message describing current progress status jsonPath: .status.conditions[?(@.type=='Progressing')].message name: Message @@ -112,7 +116,7 @@ spec: minLength: 1 type: string resourceName: - description: resoureName is the name of the resource this + description: resourceName is the name of the resource this limit is associated with. maxLength: 255 minLength: 1 @@ -201,12 +205,12 @@ spec: - message: projectRef is immutable rule: self == oldSelf resourceLimit: - description: resoureLimit is the override value of the limit. + description: resourceLimit is the override value of the limit. format: int32 minimum: -1 type: integer resourceName: - description: resoureName is the name of the resource this limit + description: resourceName is the name of the resource this limit is associated with. maxLength: 255 minLength: 1 @@ -369,11 +373,11 @@ spec: maxLength: 1024 type: string resourceLimit: - description: resoureLimit is the override value of the limit. + description: resourceLimit is the override value of the limit. format: int32 type: integer resourceName: - description: resoureName is the name of the resource this limit + description: resourceName is the name of the resource this limit is associated with. maxLength: 1024 type: string diff --git a/internal/controllers/limit/actuator.go b/internal/controllers/limit/actuator.go index 89700fba7..6256eb9a8 100644 --- a/internal/controllers/limit/actuator.go +++ b/internal/controllers/limit/actuator.go @@ -18,6 +18,7 @@ package limit import ( "context" + "errors" "iter" "github.com/gophercloud/gophercloud/v2/openstack/identity/v3/limits" @@ -35,6 +36,11 @@ import ( orcerrors "github.com/k-orc/openstack-resource-controller/v2/internal/util/errors" ) +var ( + errInvalidDomainRefUpdate = errors.New("limit cannot be updated with domainRef when projectRef has been used") + errInvalidProjectRefUpdate = errors.New("limit cannot be updated with projectRef when domainRef has been used") +) + // OpenStack resource types type ( osResourceT = limits.Limit @@ -237,9 +243,18 @@ func (actuator limitActuator) updateResource(ctx context.Context, obj orcObjectP orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "Update requested, but spec.resource is not set")) } + if err := validateUpdate(resource, osResource); err != nil { + return progress.WrapError( + orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration updating resource: "+err.Error(), err)) + } + updateOpts := limits.UpdateOpts{} + // There seems to be a bug that keystone doesn't clear the description field when receiving a PATCH request with an empty description. + // This will cause the `Progressing` condition stuck with `Resource status will be refreshed`. + // Tested with `openstack limit set --description ""` handleDescriptionUpdate(&updateOpts, resource, osResource) + // The same issue exists with resourceLimit. Updating resourceLimit with 0 doesn't work. handleResourceLimitUpdate(&updateOpts, resource, osResource) needsUpdate, err := needsUpdate(updateOpts) @@ -292,6 +307,18 @@ func handleResourceLimitUpdate(updateOpts *limits.UpdateOpts, resource *resource } } +func validateUpdate(resource *resourceSpecT, osResource *osResourceT) error { + if resource.DomainRef != nil && osResource.ProjectID != "" { + return errInvalidDomainRefUpdate + } + + if resource.ProjectRef != nil && osResource.DomainID != "" { + return errInvalidProjectRefUpdate + } + + return nil +} + func (actuator limitActuator) GetResourceReconcilers(ctx context.Context, orcObject orcObjectPT, osResource *osResourceT, controller interfaces.ResourceController) ([]resourceReconciler, progress.ReconcileStatus) { return []resourceReconciler{ actuator.updateResource, diff --git a/internal/controllers/limit/actuator_test.go b/internal/controllers/limit/actuator_test.go index c57195375..52656e8fe 100644 --- a/internal/controllers/limit/actuator_test.go +++ b/internal/controllers/limit/actuator_test.go @@ -114,3 +114,53 @@ func TestHandleResourceLimitUpdate(t *testing.T) { }) } } + +func TestValidateUpdate(t *testing.T) { + testCases := []struct { + name string + resource *orcv1alpha1.LimitResourceSpec + osResource *osResourceT + expectError error + }{ + { + name: "Update domainRef", + resource: &orcv1alpha1.LimitResourceSpec{ + DomainRef: ptr.To(orcv1alpha1.KubernetesNameRef("domain-ref")), + }, + osResource: &osResourceT{ + ProjectID: "12312312312", + }, + expectError: errInvalidDomainRefUpdate, + }, + { + name: "Update projectRef", + resource: &orcv1alpha1.LimitResourceSpec{ + ProjectRef: ptr.To(orcv1alpha1.KubernetesNameRef("project-ref")), + }, + osResource: &osResourceT{ + DomainID: "default", + }, + expectError: errInvalidProjectRefUpdate, + }, + { + name: "Normal update", + resource: &orcv1alpha1.LimitResourceSpec{ + ProjectRef: ptr.To(orcv1alpha1.KubernetesNameRef("project-ref")), + }, + osResource: &osResourceT{ + ProjectID: "12312312312", + }, + expectError: nil, + }, + } + + for _, tt := range testCases { + t.Run(tt.name, func(t *testing.T) { + got := validateUpdate(tt.resource, tt.osResource) + + if got != tt.expectError { + t.Errorf("Expected error: %v, got: %v", tt.expectError, got) + } + }) + } +} diff --git a/internal/controllers/limit/tests/limit-create-full/00-assert.yaml b/internal/controllers/limit/tests/limit-create-full/00-assert.yaml index d5963f5f3..752e0b4ae 100644 --- a/internal/controllers/limit/tests/limit-create-full/00-assert.yaml +++ b/internal/controllers/limit/tests/limit-create-full/00-assert.yaml @@ -1,43 +1,71 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: setup-teardown +status: + readyReplicas: 1 +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Limit +metadata: + name: limit-create-full-1 +status: + resource: + description: Limit from "create full" test + resourceName: limit-create-full-1 + resourceLimit: 50 + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Limit metadata: - name: limit-create-full + name: limit-create-full-2 status: resource: - name: limit-create-full-override description: Limit from "create full" test - # TODO(scaffolding): Add all fields the resource supports + resourceName: limit-create-full-2 + resourceLimit: 10 conditions: - - type: Available - status: "True" - reason: Success - - type: Progressing - status: "False" - reason: Success + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success --- apiVersion: kuttl.dev/v1beta1 kind: TestAssert resourceRefs: - - apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: Limit - name: limit-create-full - ref: limit - - apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: Service - name: limit-create-full - ref: service - - apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: Project - name: limit-create-full - ref: project - - apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: Domain - name: limit-create-full - ref: domain + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Limit + name: limit-create-full-1 + ref: limit1 + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Limit + name: limit-create-full-2 + ref: limit2 + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Service + name: service-limit-create-full + ref: service + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Project + name: project-limit-create-full + ref: project + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Domain + name: domain-limit-create-full + ref: domain assertAll: - - celExpr: "limit.status.id != ''" - - celExpr: "limit.status.resource.serviceID == service.status.id" - - celExpr: "limit.status.resource.projectID == project.status.id" - - celExpr: "limit.status.resource.domainID == domain.status.id" - # TODO(scaffolding): Add more checks + - celExpr: "limit1.status.id != ''" + - celExpr: "limit2.status.id != ''" + - celExpr: "limit1.status.resource.serviceID == service.status.id" + - celExpr: "limit2.status.resource.serviceID == service.status.id" + - celExpr: "limit1.status.resource.projectID == project.status.id" + - celExpr: "limit2.status.resource.domainID == domain.status.id" diff --git a/internal/controllers/limit/tests/limit-create-full/00-create-resource.yaml b/internal/controllers/limit/tests/limit-create-full/00-create-resource.yaml index 3cbeed847..6ededc7d3 100644 --- a/internal/controllers/limit/tests/limit-create-full/00-create-resource.yaml +++ b/internal/controllers/limit/tests/limit-create-full/00-create-resource.yaml @@ -2,56 +2,78 @@ apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Service metadata: - name: limit-create-full + name: service-limit-create-full + labels: + test-case: limit-create-full spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created - cloudName: openstack + cloudName: openstack-admin secretName: openstack-clouds managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource - resource: {} + resource: + type: "service-type-limit-create-full" --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Project metadata: - name: limit-create-full + name: project-limit-create-full + labels: + test-case: limit-create-full spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created - cloudName: openstack + cloudName: openstack-admin secretName: openstack-clouds managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource - resource: {} + resource: + domainRef: domain-limit-create-full --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Domain metadata: - name: limit-create-full + name: domain-limit-create-full + labels: + test-case: limit-create-full spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created - cloudName: openstack + cloudName: openstack-admin secretName: openstack-clouds managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource resource: {} --- +# Use projectRef +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Limit +metadata: + name: limit-create-full-1 + labels: + test-case: limit-create-full +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + description: Limit from "create full" test + serviceRef: service-limit-create-full + projectRef: project-limit-create-full + resourceName: limit-create-full-1 + resourceLimit: 50 +--- +# Use domainRef apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Limit metadata: - name: limit-create-full + name: limit-create-full-2 + labels: + test-case: limit-create-full spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created - cloudName: openstack + cloudName: openstack-admin secretName: openstack-clouds managementPolicy: managed resource: - name: limit-create-full-override description: Limit from "create full" test - serviceRef: limit-create-full - projectRef: limit-create-full - domainRef: limit-create-full - # TODO(scaffolding): Add all fields the resource supports + serviceRef: service-limit-create-full + domainRef: domain-limit-create-full + resourceName: limit-create-full-2 + resourceLimit: 10 diff --git a/internal/controllers/limit/tests/limit-create-full/00-secret.yaml b/internal/controllers/limit/tests/limit-create-full/00-secret.yaml index 045711ee7..11be38a4e 100644 --- a/internal/controllers/limit/tests/limit-create-full/00-secret.yaml +++ b/internal/controllers/limit/tests/limit-create-full/00-secret.yaml @@ -4,3 +4,5 @@ kind: TestStep commands: - command: kubectl create secret generic openstack-clouds --from-file=clouds.yaml=${E2E_KUTTL_OSCLOUDS} ${E2E_KUTTL_CACERT_OPT} namespaced: true + - command: kubectl create configmap common --from-file=../utils + namespaced: true diff --git a/internal/controllers/limit/tests/limit-create-full/00-setup-cleanup.yaml b/internal/controllers/limit/tests/limit-create-full/00-setup-cleanup.yaml new file mode 100644 index 000000000..de40c99cd --- /dev/null +++ b/internal/controllers/limit/tests/limit-create-full/00-setup-cleanup.yaml @@ -0,0 +1,78 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: setup-teardown + labels: + app: setup-teardown +spec: + replicas: 1 + selector: + matchLabels: + app: setup-teardown + template: + metadata: + labels: + app: setup-teardown + spec: + containers: + - name: setup-teardown + image: golang:alpine + command: + - sh + - -c + - | + mkdir /test-helper && + cp /app/* /test-helper && + cd /test-helper; + go mod init test-helper && + go mod tidy && + go build -o ./test-helper . && + exec ./test-helper + volumeMounts: + - name: openstack-clouds + mountPath: /etc/openstack + readOnly: true + - name: common + mountPath: /app + readOnly: true + - name: log-volume + mountPath: /log + env: + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: READINESS_FILE + value: /helper/ready + - name: OS_CLOUD + value: openstack-admin + - name: TEST_CASE + value: limit-create-full + - name: REGISTER_LIMITS + value: service-limit-create-full/limit-create-full-1/100,service-limit-create-full/limit-create-full-2/100 + - name: DOMAINS + value: domain-limit-create-full + readinessProbe: + exec: + command: + - test + - -f + - /helper/ready + initialDelaySeconds: 3 + periodSeconds: 1 + terminationGracePeriodSeconds: 60 + volumes: + - name: openstack-clouds + secret: + secretName: openstack-clouds + - name: common + configMap: + name: common + - name: log-volume + hostPath: + path: /log + type: DirectoryOrCreate diff --git a/internal/controllers/limit/tests/limit-create-full/README.md b/internal/controllers/limit/tests/limit-create-full/README.md index fa45d9421..69cf7f8ed 100644 --- a/internal/controllers/limit/tests/limit-create-full/README.md +++ b/internal/controllers/limit/tests/limit-create-full/README.md @@ -1,11 +1,7 @@ -# Create a Limit with all the options +# Create Limits with all the options ## Step 00 -Create a Limit using all available fields, and verify that the observed state corresponds to the spec. +Create Limits using all available fields, and verify that the observed state corresponds to the spec. -Also validate that the OpenStack resource uses the name from the spec when it is specified. - -## Reference - -https://k-orc.cloud/development/writing-tests/#create-full +Because `projectRef` and `domainRef` are mutual exclusive, two limits are created for test. diff --git a/internal/controllers/limit/tests/limit-create-minimal/00-assert.yaml b/internal/controllers/limit/tests/limit-create-minimal/00-assert.yaml index 3ab09bebf..895eda1f8 100644 --- a/internal/controllers/limit/tests/limit-create-minimal/00-assert.yaml +++ b/internal/controllers/limit/tests/limit-create-minimal/00-assert.yaml @@ -1,32 +1,71 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: setup-teardown +status: + readyReplicas: 1 +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Limit +metadata: + name: limit-create-minimal-1 +status: + resource: + resourceName: limit-create-minimal-1 + resourceLimit: 50 + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Limit metadata: - name: limit-create-minimal + name: limit-create-minimal-2 status: resource: - name: limit-create-minimal - # TODO(scaffolding): Add all fields the resource supports + resourceName: limit-create-minimal-2 + resourceLimit: 10 conditions: - - type: Available - status: "True" - reason: Success - - type: Progressing - status: "False" - reason: Success + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success --- apiVersion: kuttl.dev/v1beta1 kind: TestAssert resourceRefs: - - apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: Limit - name: limit-create-minimal - ref: limit - - apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: Service - name: limit-create-minimal - ref: service + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Limit + name: limit-create-minimal-1 + ref: limit1 + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Limit + name: limit-create-minimal-2 + ref: limit2 + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Service + name: service-limit-create-minimal + ref: service + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Project + name: project-limit-create-minimal + ref: project + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Domain + name: domain-limit-create-minimal + ref: domain assertAll: - - celExpr: "limit.status.id != ''" - - celExpr: "limit.status.resource.serviceID == service.status.id" - # TODO(scaffolding): Add more checks + - celExpr: "limit1.status.id != ''" + - celExpr: "!has(limit1.status.resource.description) || limit1.status.resource.description == ''" + - celExpr: "limit2.status.id != ''" + - celExpr: "!has(limit2.status.resource.description) || limit2.status.resource.description == ''" + - celExpr: "limit1.status.resource.serviceID == service.status.id" + - celExpr: "limit2.status.resource.serviceID == service.status.id" + - celExpr: "limit1.status.resource.projectID == project.status.id" + - celExpr: "limit2.status.resource.domainID == domain.status.id" diff --git a/internal/controllers/limit/tests/limit-create-minimal/00-create-resource.yaml b/internal/controllers/limit/tests/limit-create-minimal/00-create-resource.yaml index 3cd61ff30..f6fd63464 100644 --- a/internal/controllers/limit/tests/limit-create-minimal/00-create-resource.yaml +++ b/internal/controllers/limit/tests/limit-create-minimal/00-create-resource.yaml @@ -2,27 +2,76 @@ apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Service metadata: - name: limit-create-minimal + name: service-limit-create-minimal + labels: + test-case: limit-create-minimal spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created - cloudName: openstack + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + type: "service-type-limit-create-minimal" +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Project +metadata: + name: project-limit-create-minimal + labels: + test-case: limit-create-minimal +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + domainRef: domain-limit-create-minimal +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Domain +metadata: + name: domain-limit-create-minimal + labels: + test-case: limit-create-minimal +spec: + cloudCredentialsRef: + cloudName: openstack-admin secretName: openstack-clouds managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource resource: {} --- +# Use projectRef +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Limit +metadata: + name: limit-create-minimal-1 + labels: + test-case: limit-create-minimal +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + serviceRef: service-limit-create-minimal + projectRef: project-limit-create-minimal + resourceName: limit-create-minimal-1 + resourceLimit: 50 +--- +# Use domainRef apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Limit metadata: - name: limit-create-minimal + name: limit-create-minimal-2 + labels: + test-case: limit-create-minimal spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created - cloudName: openstack + cloudName: openstack-admin secretName: openstack-clouds managementPolicy: managed - # TODO(scaffolding): Only add the mandatory fields. It's possible the resource - # doesn't have mandatory fields, in that case, leave it empty. resource: - serviceRef: limit-create-minimal + serviceRef: service-limit-create-minimal + domainRef: domain-limit-create-minimal + resourceName: limit-create-minimal-2 + resourceLimit: 10 diff --git a/internal/controllers/limit/tests/limit-create-minimal/00-secret.yaml b/internal/controllers/limit/tests/limit-create-minimal/00-secret.yaml index 045711ee7..11be38a4e 100644 --- a/internal/controllers/limit/tests/limit-create-minimal/00-secret.yaml +++ b/internal/controllers/limit/tests/limit-create-minimal/00-secret.yaml @@ -4,3 +4,5 @@ kind: TestStep commands: - command: kubectl create secret generic openstack-clouds --from-file=clouds.yaml=${E2E_KUTTL_OSCLOUDS} ${E2E_KUTTL_CACERT_OPT} namespaced: true + - command: kubectl create configmap common --from-file=../utils + namespaced: true diff --git a/internal/controllers/limit/tests/limit-create-minimal/00-setup-cleanup.yaml b/internal/controllers/limit/tests/limit-create-minimal/00-setup-cleanup.yaml new file mode 100644 index 000000000..8fb51adeb --- /dev/null +++ b/internal/controllers/limit/tests/limit-create-minimal/00-setup-cleanup.yaml @@ -0,0 +1,78 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: setup-teardown + labels: + app: setup-teardown +spec: + replicas: 1 + selector: + matchLabels: + app: setup-teardown + template: + metadata: + labels: + app: setup-teardown + spec: + containers: + - name: setup-teardown + image: golang:alpine + command: + - sh + - -c + - | + mkdir /test-helper && + cp /app/* /test-helper && + cd /test-helper; + go mod init test-helper && + go mod tidy && + go build -o ./test-helper . && + exec ./test-helper + volumeMounts: + - name: openstack-clouds + mountPath: /etc/openstack + readOnly: true + - name: common + mountPath: /app + readOnly: true + - name: log-volume + mountPath: /log + env: + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: READINESS_FILE + value: /helper/ready + - name: OS_CLOUD + value: openstack-admin + - name: TEST_CASE + value: limit-create-minimal + - name: REGISTER_LIMITS + value: service-limit-create-minimal/limit-create-minimal-1/100,service-limit-create-minimal/limit-create-minimal-2/100 + - name: DOMAINS + value: domain-limit-create-minimal + readinessProbe: + exec: + command: + - test + - -f + - /helper/ready + initialDelaySeconds: 3 + periodSeconds: 1 + terminationGracePeriodSeconds: 60 + volumes: + - name: openstack-clouds + secret: + secretName: openstack-clouds + - name: common + configMap: + name: common + - name: log-volume + hostPath: + path: /log + type: DirectoryOrCreate diff --git a/internal/controllers/limit/tests/limit-create-minimal/01-assert.yaml b/internal/controllers/limit/tests/limit-create-minimal/01-assert.yaml index 78e2504e8..d1b3792dc 100644 --- a/internal/controllers/limit/tests/limit-create-minimal/01-assert.yaml +++ b/internal/controllers/limit/tests/limit-create-minimal/01-assert.yaml @@ -2,10 +2,28 @@ apiVersion: kuttl.dev/v1beta1 kind: TestAssert resourceRefs: - - apiVersion: v1 - kind: Secret - name: openstack-clouds - ref: secret + - apiVersion: v1 + kind: Secret + name: openstack-clouds + ref: secret + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Service + name: service-limit-create-minimal + ref: service + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Project + name: project-limit-create-minimal + ref: project + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Domain + name: domain-limit-create-minimal + ref: domain assertAll: - - celExpr: "secret.metadata.deletionTimestamp != 0" - - celExpr: "'openstack.k-orc.cloud/limit' in secret.metadata.finalizers" + - celExpr: "secret.metadata.deletionTimestamp != 0" + - celExpr: "'openstack.k-orc.cloud/limit' in secret.metadata.finalizers" + - celExpr: "service.metadata.deletionTimestamp != 0" + - celExpr: "'openstack.k-orc.cloud/limit' in service.metadata.finalizers" + - celExpr: "project.metadata.deletionTimestamp != 0" + - celExpr: "'openstack.k-orc.cloud/limit' in project.metadata.finalizers" + - celExpr: "domain.metadata.deletionTimestamp != 0" + - celExpr: "'openstack.k-orc.cloud/limit' in domain.metadata.finalizers" diff --git a/internal/controllers/limit/tests/limit-create-minimal/01-delete-secret.yaml b/internal/controllers/limit/tests/limit-create-minimal/01-delete-resources.yaml similarity index 61% rename from internal/controllers/limit/tests/limit-create-minimal/01-delete-secret.yaml rename to internal/controllers/limit/tests/limit-create-minimal/01-delete-resources.yaml index 1620791b9..7a171582b 100644 --- a/internal/controllers/limit/tests/limit-create-minimal/01-delete-secret.yaml +++ b/internal/controllers/limit/tests/limit-create-minimal/01-delete-resources.yaml @@ -5,3 +5,5 @@ commands: # We expect the deletion to hang due to the finalizer, so use --wait=false - command: kubectl delete secret openstack-clouds --wait=false namespaced: true + - command: kubectl delete service.openstack.k-orc.cloud,project,domain -l test-case=limit-create-minimal --wait=false + namespaced: true diff --git a/internal/controllers/limit/tests/limit-create-minimal/README.md b/internal/controllers/limit/tests/limit-create-minimal/README.md index 4b3b12e7e..bec2f2d29 100644 --- a/internal/controllers/limit/tests/limit-create-minimal/README.md +++ b/internal/controllers/limit/tests/limit-create-minimal/README.md @@ -1,15 +1,9 @@ -# Create a Limit with the minimum options +# Create Limits with the minimum options ## Step 00 -Create a minimal Limit, that sets only the required fields, and verify that the observed state corresponds to the spec. - -Also validate that the OpenStack resource uses the name of the ORC object when no name is explicitly specified. +Create two minimal Limits that set only the required fields (serviceRef/projectRef/resourceName/resourceLimit and serviceRef/domainRef/resourceName/resourceLimit), and verify that the observed state corresponds to the spec. `projectRef` and `domainRef` are mutual exclusive, so the only field that is really optional is the description field. ## Step 01 -Try deleting the secret and ensure that it is not deleted thanks to the finalizer. - -## Reference - -https://k-orc.cloud/development/writing-tests/#create-minimal +Try deleting the secret and other dependencies ensure that they are not deleted thanks to the finalizer. diff --git a/internal/controllers/limit/tests/limit-dependency/00-assert.yaml b/internal/controllers/limit/tests/limit-dependency/00-assert.yaml index adc4bf209..ce3dcf71d 100644 --- a/internal/controllers/limit/tests/limit-dependency/00-assert.yaml +++ b/internal/controllers/limit/tests/limit-dependency/00-assert.yaml @@ -2,59 +2,54 @@ apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Limit metadata: - name: limit-dependency-no-secret + name: limit-dependency-1 status: conditions: - type: Available - message: Waiting for Secret/limit-dependency to be created + message: |- + Waiting for Service/service-limit-dependency to be created + Waiting for Project/project-limit-dependency to be created status: "False" reason: Progressing - type: Progressing - message: Waiting for Secret/limit-dependency to be created + message: |- + Waiting for Service/service-limit-dependency to be created + Waiting for Project/project-limit-dependency to be created status: "True" reason: Progressing --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Limit metadata: - name: limit-dependency-no-service + name: limit-dependency-2 status: conditions: - type: Available - message: Waiting for Service/limit-dependency-pending to be created + message: |- + Waiting for Service/service-limit-dependency to be created + Waiting for Domain/domain-limit-dependency to be created status: "False" reason: Progressing - type: Progressing - message: Waiting for Service/limit-dependency-pending to be created + message: |- + Waiting for Service/service-limit-dependency to be created + Waiting for Domain/domain-limit-dependency to be created status: "True" reason: Progressing --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Limit metadata: - name: limit-dependency-no-project + name: limit-dependency-3 status: conditions: - type: Available - message: Waiting for Project/limit-dependency to be created + message: |- + Waiting for Secret/limit-dependency to be created status: "False" reason: Progressing - type: Progressing - message: Waiting for Project/limit-dependency to be created - status: "True" - reason: Progressing ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: Limit -metadata: - name: limit-dependency-no-domain -status: - conditions: - - type: Available - message: Waiting for Domain/limit-dependency to be created - status: "False" - reason: Progressing - - type: Progressing - message: Waiting for Domain/limit-dependency to be created + message: |- + Waiting for Secret/limit-dependency to be created status: "True" reason: Progressing diff --git a/internal/controllers/limit/tests/limit-dependency/00-create-resources-missing-deps.yaml b/internal/controllers/limit/tests/limit-dependency/00-create-resources-missing-deps.yaml index a167ee743..f0af4725b 100644 --- a/internal/controllers/limit/tests/limit-dependency/00-create-resources-missing-deps.yaml +++ b/internal/controllers/limit/tests/limit-dependency/00-create-resources-missing-deps.yaml @@ -1,70 +1,54 @@ --- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: Service -metadata: - name: limit-dependency -spec: - cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created - cloudName: openstack - secretName: openstack-clouds - managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource - resource: {} ---- +# No project and service available apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Limit metadata: - name: limit-dependency-no-service + name: limit-dependency-1 + labels: + test-case: limit-dependency spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created - cloudName: openstack + cloudName: openstack-admin secretName: openstack-clouds managementPolicy: managed resource: - serviceRef: limit-dependency-pending - # TODO(scaffolding): Add the necessary fields to create the resource + serviceRef: service-limit-dependency + projectRef: project-limit-dependency + resourceName: limit-dependency-1 + resourceLimit: 50 --- +# No domain and service available apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Limit metadata: - name: limit-dependency-no-project -spec: - cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created - cloudName: openstack - secretName: openstack-clouds - managementPolicy: managed - resource: - serviceRef: limit-dependency - projectRef: limit-dependency - # TODO(scaffolding): Add the necessary fields to create the resource--- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: Limit -metadata: - name: limit-dependency-no-domain + name: limit-dependency-2 + labels: + test-case: limit-dependency spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created - cloudName: openstack + cloudName: openstack-admin secretName: openstack-clouds managementPolicy: managed resource: - serviceRef: limit-dependency - domainRef: limit-dependency - # TODO(scaffolding): Add the necessary fields to create the resource + serviceRef: service-limit-dependency + domainRef: domain-limit-dependency + resourceName: limit-dependency-2 + resourceLimit: 10 --- +# No secret, service and domain available apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Limit metadata: - name: limit-dependency-no-secret + name: limit-dependency-3 + labels: + test-case: limit-dependency spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created - cloudName: openstack + cloudName: openstack-admin secretName: limit-dependency managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource resource: - serviceRef: limit-dependency + serviceRef: service-limit-dependency + domainRef: domain-limit-dependency + resourceName: limit-dependency-3 + resourceLimit: 5 diff --git a/internal/controllers/limit/tests/limit-dependency/01-assert.yaml b/internal/controllers/limit/tests/limit-dependency/01-assert.yaml index a55eda7b1..be0a2c099 100644 --- a/internal/controllers/limit/tests/limit-dependency/01-assert.yaml +++ b/internal/controllers/limit/tests/limit-dependency/01-assert.yaml @@ -1,23 +1,14 @@ ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: Limit +apiVersion: apps/v1 +kind: Deployment metadata: - name: limit-dependency-no-secret + name: setup-teardown status: - conditions: - - type: Available - message: OpenStack resource is available - status: "True" - reason: Success - - type: Progressing - message: OpenStack resource is up to date - status: "False" - reason: Success + readyReplicas: 1 --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Limit metadata: - name: limit-dependency-no-service + name: limit-dependency-1 status: conditions: - type: Available @@ -32,7 +23,7 @@ status: apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Limit metadata: - name: limit-dependency-no-project + name: limit-dependency-2 status: conditions: - type: Available @@ -47,7 +38,7 @@ status: apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Limit metadata: - name: limit-dependency-no-domain + name: limit-dependency-3 status: conditions: - type: Available diff --git a/internal/controllers/limit/tests/limit-dependency/01-create-dependencies.yaml b/internal/controllers/limit/tests/limit-dependency/01-create-dependencies.yaml index d5db92a06..7b6bada28 100644 --- a/internal/controllers/limit/tests/limit-dependency/01-create-dependencies.yaml +++ b/internal/controllers/limit/tests/limit-dependency/01-create-dependencies.yaml @@ -4,42 +4,46 @@ kind: TestStep commands: - command: kubectl create secret generic limit-dependency --from-file=clouds.yaml=${E2E_KUTTL_OSCLOUDS} ${E2E_KUTTL_CACERT_OPT} namespaced: true + - command: kubectl create configmap common --from-file=../utils + namespaced: true --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Service metadata: - name: limit-dependency-pending + name: service-limit-dependency + labels: + test-case: limit-dependency spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created - cloudName: openstack + cloudName: openstack-admin secretName: openstack-clouds managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource - resource: {} + resource: + type: "service-type-limit-dependency" --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Project metadata: - name: limit-dependency + name: project-limit-dependency + labels: + test-case: limit-dependency spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created - cloudName: openstack + cloudName: openstack-admin secretName: openstack-clouds managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource - resource: {} + resource: + domainRef: domain-limit-dependency --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Domain metadata: - name: limit-dependency + name: domain-limit-dependency + labels: + test-case: limit-dependency spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created - cloudName: openstack + cloudName: openstack-admin secretName: openstack-clouds managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource resource: {} diff --git a/internal/controllers/limit/tests/limit-dependency/01-setup-cleanup.yaml b/internal/controllers/limit/tests/limit-dependency/01-setup-cleanup.yaml new file mode 100644 index 000000000..dde27d4e2 --- /dev/null +++ b/internal/controllers/limit/tests/limit-dependency/01-setup-cleanup.yaml @@ -0,0 +1,78 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: setup-teardown + labels: + app: setup-teardown +spec: + replicas: 1 + selector: + matchLabels: + app: setup-teardown + template: + metadata: + labels: + app: setup-teardown + spec: + containers: + - name: setup-teardown + image: golang:alpine + command: + - sh + - -c + - | + mkdir /test-helper && + cp /app/* /test-helper && + cd /test-helper; + go mod init test-helper && + go mod tidy && + go build -o ./test-helper . && + exec ./test-helper + volumeMounts: + - name: openstack-clouds + mountPath: /etc/openstack + readOnly: true + - name: common + mountPath: /app + readOnly: true + - name: log-volume + mountPath: /log + env: + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: READINESS_FILE + value: /helper/ready + - name: OS_CLOUD + value: openstack-admin + - name: TEST_CASE + value: limit-dependency + - name: REGISTER_LIMITS + value: service-limit-dependency/limit-dependency-1/100,service-limit-dependency/limit-dependency-2/100,service-limit-dependency/limit-dependency-3/100 + - name: DOMAINS + value: domain-limit-dependency + readinessProbe: + exec: + command: + - test + - -f + - /helper/ready + initialDelaySeconds: 3 + periodSeconds: 1 + terminationGracePeriodSeconds: 60 + volumes: + - name: openstack-clouds + secret: + secretName: openstack-clouds + - name: common + configMap: + name: common + - name: log-volume + hostPath: + path: /log + type: DirectoryOrCreate diff --git a/internal/controllers/limit/tests/limit-dependency/02-assert.yaml b/internal/controllers/limit/tests/limit-dependency/02-assert.yaml index b00e3a4f0..3173eed73 100644 --- a/internal/controllers/limit/tests/limit-dependency/02-assert.yaml +++ b/internal/controllers/limit/tests/limit-dependency/02-assert.yaml @@ -2,28 +2,28 @@ apiVersion: kuttl.dev/v1beta1 kind: TestAssert resourceRefs: - - apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: Service - name: limit-dependency - ref: service - - apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: Project - name: limit-dependency - ref: project - - apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: Domain - name: limit-dependency - ref: domain - - apiVersion: v1 - kind: Secret - name: limit-dependency - ref: secret + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Service + name: service-limit-dependency + ref: service + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Project + name: project-limit-dependency + ref: project + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Domain + name: domain-limit-dependency + ref: domain + - apiVersion: v1 + kind: Secret + name: limit-dependency + ref: secret assertAll: - - celExpr: "service.metadata.deletionTimestamp != 0" - - celExpr: "'openstack.k-orc.cloud/limit' in service.metadata.finalizers" - - celExpr: "project.metadata.deletionTimestamp != 0" - - celExpr: "'openstack.k-orc.cloud/limit' in project.metadata.finalizers" - - celExpr: "domain.metadata.deletionTimestamp != 0" - - celExpr: "'openstack.k-orc.cloud/limit' in domain.metadata.finalizers" - - celExpr: "secret.metadata.deletionTimestamp != 0" - - celExpr: "'openstack.k-orc.cloud/limit' in secret.metadata.finalizers" + - celExpr: "service.metadata.deletionTimestamp != 0" + - celExpr: "'openstack.k-orc.cloud/limit' in service.metadata.finalizers" + - celExpr: "project.metadata.deletionTimestamp != 0" + - celExpr: "'openstack.k-orc.cloud/limit' in project.metadata.finalizers" + - celExpr: "domain.metadata.deletionTimestamp != 0" + - celExpr: "'openstack.k-orc.cloud/limit' in domain.metadata.finalizers" + - celExpr: "secret.metadata.deletionTimestamp != 0" + - celExpr: "'openstack.k-orc.cloud/limit' in secret.metadata.finalizers" diff --git a/internal/controllers/limit/tests/limit-dependency/02-delete-dependencies.yaml b/internal/controllers/limit/tests/limit-dependency/02-delete-dependencies.yaml index 3f8a37b4b..a943fd76d 100644 --- a/internal/controllers/limit/tests/limit-dependency/02-delete-dependencies.yaml +++ b/internal/controllers/limit/tests/limit-dependency/02-delete-dependencies.yaml @@ -2,12 +2,10 @@ apiVersion: kuttl.dev/v1beta1 kind: TestStep commands: + # Sleep 1 sec to avoid race condition that the controller only reconciles on the deletion operation due to the async processing and cache + - script: kubectl patch domain domain-limit-dependency -p '{"spec":{"resource":{"enabled":false}}}' --type=merge -n $NAMESPACE && sleep 1 # We expect the deletion to hang due to the finalizer, so use --wait=false - - command: kubectl delete service.openstack.k-orc.cloud limit-dependency --wait=false - namespaced: true - - command: kubectl delete project.openstack.k-orc.cloud limit-dependency --wait=false - namespaced: true - - command: kubectl delete domain.openstack.k-orc.cloud limit-dependency --wait=false + - command: kubectl delete service.openstack.k-orc.cloud,project,domain -l test-case=limit-dependency --wait=false namespaced: true - command: kubectl delete secret limit-dependency --wait=false namespaced: true diff --git a/internal/controllers/limit/tests/limit-dependency/03-assert.yaml b/internal/controllers/limit/tests/limit-dependency/03-assert.yaml index 9dd3c0fb5..38b2770fe 100644 --- a/internal/controllers/limit/tests/limit-dependency/03-assert.yaml +++ b/internal/controllers/limit/tests/limit-dependency/03-assert.yaml @@ -2,12 +2,12 @@ apiVersion: kuttl.dev/v1beta1 kind: TestAssert commands: -# Dependencies that were prevented deletion before should now be gone -- script: "! kubectl get service.openstack.k-orc.cloud limit-dependency --namespace $NAMESPACE" - skipLogOutput: true -- script: "! kubectl get project.openstack.k-orc.cloud limit-dependency --namespace $NAMESPACE" - skipLogOutput: true -- script: "! kubectl get domain.openstack.k-orc.cloud limit-dependency --namespace $NAMESPACE" - skipLogOutput: true -- script: "! kubectl get secret limit-dependency --namespace $NAMESPACE" - skipLogOutput: true + # Dependencies that were prevented deletion before should now be gone + - script: "! kubectl get service.openstack.k-orc.cloud service-limit-dependency --namespace $NAMESPACE" + skipLogOutput: true + - script: "! kubectl get project.openstack.k-orc.cloud project-limit-dependency --namespace $NAMESPACE" + skipLogOutput: true + - script: "! kubectl get domain.openstack.k-orc.cloud domain-limit-dependency --namespace $NAMESPACE" + skipLogOutput: true + - script: "! kubectl get secret limit-dependency --namespace $NAMESPACE" + skipLogOutput: true diff --git a/internal/controllers/limit/tests/limit-dependency/03-delete-resources.yaml b/internal/controllers/limit/tests/limit-dependency/03-delete-resources.yaml index 2ee949665..71595ce0f 100644 --- a/internal/controllers/limit/tests/limit-dependency/03-delete-resources.yaml +++ b/internal/controllers/limit/tests/limit-dependency/03-delete-resources.yaml @@ -2,15 +2,12 @@ apiVersion: kuttl.dev/v1beta1 kind: TestStep delete: -- apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: Limit - name: limit-dependency-no-secret -- apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: Limit - name: limit-dependency-no-service -- apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: Limit - name: limit-dependency-no-project -- apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: Limit - name: limit-dependency-no-domain + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Limit + name: limit-dependency-1 + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Limit + name: limit-dependency-2 + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Limit + name: limit-dependency-3 diff --git a/internal/controllers/limit/tests/limit-dependency/README.md b/internal/controllers/limit/tests/limit-dependency/README.md index 80a94fb62..457b31af1 100644 --- a/internal/controllers/limit/tests/limit-dependency/README.md +++ b/internal/controllers/limit/tests/limit-dependency/README.md @@ -2,7 +2,7 @@ ## Step 00 -Create Limits referencing non-existing resources. Each Limit is dependent on other non-existing resource. Verify that the Limits are waiting for the needed resources to be created externally. +Create Limits referencing non-existing resources. Each Limit is dependent on some other non-existing resource(project/domain/secret). Verify that the Limits are waiting for the needed resources to be created externally. ## Step 01 @@ -15,7 +15,3 @@ Delete all the dependencies and check that ORC prevents deletion since there is ## Step 03 Delete the Limits and validate that all resources are gone. - -## Reference - -https://k-orc.cloud/development/writing-tests/#dependency diff --git a/internal/controllers/limit/tests/limit-import-dependency/00-assert.yaml b/internal/controllers/limit/tests/limit-import-dependency/00-assert.yaml index c490b9950..a839c939a 100644 --- a/internal/controllers/limit/tests/limit-import-dependency/00-assert.yaml +++ b/internal/controllers/limit/tests/limit-import-dependency/00-assert.yaml @@ -7,15 +7,11 @@ status: conditions: - type: Available message: |- - Waiting for Service/limit-import-dependency to be ready - Waiting for Project/limit-import-dependency to be ready - Waiting for Domain/limit-import-dependency to be ready + Waiting for Project/project-limit-import-dependency-imported to be ready status: "False" reason: Progressing - type: Progressing message: |- - Waiting for Service/limit-import-dependency to be ready - Waiting for Project/limit-import-dependency to be ready - Waiting for Domain/limit-import-dependency to be ready + Waiting for Project/project-limit-import-dependency-imported to be ready status: "True" reason: Progressing diff --git a/internal/controllers/limit/tests/limit-import-dependency/00-import-resource.yaml b/internal/controllers/limit/tests/limit-import-dependency/00-import-resource.yaml index 23cdeb28d..3a5935f49 100644 --- a/internal/controllers/limit/tests/limit-import-dependency/00-import-resource.yaml +++ b/internal/controllers/limit/tests/limit-import-dependency/00-import-resource.yaml @@ -2,53 +2,45 @@ apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Service metadata: - name: limit-import-dependency + name: service-limit-import-dependency + labels: + test-case: limit-import-dependency spec: cloudCredentialsRef: - cloudName: openstack + cloudName: openstack-admin secretName: openstack-clouds - managementPolicy: unmanaged - import: - filter: - name: limit-import-dependency-external + managementPolicy: managed + resource: + type: "service-type-limit-import-dependency" --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Project metadata: - name: limit-import-dependency -spec: - cloudCredentialsRef: - cloudName: openstack - secretName: openstack-clouds - managementPolicy: unmanaged - import: - filter: - name: limit-import-dependency-external ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: Domain -metadata: - name: limit-import-dependency + name: project-limit-import-dependency-imported + labels: + test-case: limit-import-dependency spec: cloudCredentialsRef: - cloudName: openstack + cloudName: openstack-admin secretName: openstack-clouds managementPolicy: unmanaged import: filter: - name: limit-import-dependency-external + name: project-limit-import-dependency --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Limit metadata: name: limit-import-dependency + labels: + test-case: limit-import-dependency spec: cloudCredentialsRef: - cloudName: openstack + cloudName: openstack-admin secretName: openstack-clouds managementPolicy: unmanaged import: filter: - serviceRef: limit-import-dependency - projectRef: limit-import-dependency - domainRef: limit-import-dependency + serviceRef: service-limit-import-dependency + projectRef: project-limit-import-dependency-imported + resourceName: limit-import-dependency diff --git a/internal/controllers/limit/tests/limit-import-dependency/01-assert.yaml b/internal/controllers/limit/tests/limit-import-dependency/01-assert.yaml index e41e3a464..138fd32ef 100644 --- a/internal/controllers/limit/tests/limit-import-dependency/01-assert.yaml +++ b/internal/controllers/limit/tests/limit-import-dependency/01-assert.yaml @@ -1,9 +1,37 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: setup-teardown +status: + readyReplicas: 1 +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Limit + name: limit-import-dependency + ref: limit1 + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Limit + name: limit-import-dependency-external + ref: limit2 + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Project + name: project-limit-import-dependency + ref: project +assertAll: + - celExpr: "limit1.status.id == limit2.status.id" + - celExpr: "limit1.status.resource.projectID == project.status.id" --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Limit metadata: - name: limit-import-dependency-not-this-one + name: limit-import-dependency status: + resource: + resourceName: limit-import-dependency + resourceLimit: 50 conditions: - type: Available message: OpenStack resource is available @@ -13,24 +41,3 @@ status: message: OpenStack resource is up to date status: "False" reason: Success ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: Limit -metadata: - name: limit-import-dependency -status: - conditions: - - type: Available - message: |- - Waiting for Service/limit-import-dependency to be ready - Waiting for Project/limit-import-dependency to be ready - Waiting for Domain/limit-import-dependency to be ready - status: "False" - reason: Progressing - - type: Progressing - message: |- - Waiting for Service/limit-import-dependency to be ready - Waiting for Project/limit-import-dependency to be ready - Waiting for Domain/limit-import-dependency to be ready - status: "True" - reason: Progressing diff --git a/internal/controllers/limit/tests/limit-import-dependency/01-create-resource.yaml b/internal/controllers/limit/tests/limit-import-dependency/01-create-resource.yaml new file mode 100644 index 000000000..e95ebcea5 --- /dev/null +++ b/internal/controllers/limit/tests/limit-import-dependency/01-create-resource.yaml @@ -0,0 +1,44 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Domain +metadata: + name: domain-limit-import-dependency + labels: + test-case: limit-import-dependency +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Project +metadata: + name: project-limit-import-dependency + labels: + test-case: limit-import-dependency +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + domainRef: domain-limit-import-dependency +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Limit +metadata: + name: limit-import-dependency-external + labels: + test-case: limit-import-dependency +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + serviceRef: service-limit-import-dependency + projectRef: project-limit-import-dependency + resourceName: limit-import-dependency + resourceLimit: 50 diff --git a/internal/controllers/limit/tests/limit-import-dependency/01-create-trap-resource.yaml b/internal/controllers/limit/tests/limit-import-dependency/01-create-trap-resource.yaml deleted file mode 100644 index 4769ccb7e..000000000 --- a/internal/controllers/limit/tests/limit-import-dependency/01-create-trap-resource.yaml +++ /dev/null @@ -1,56 +0,0 @@ ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: Service -metadata: - name: limit-import-dependency-not-this-one -spec: - cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created - cloudName: openstack - secretName: openstack-clouds - managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource - resource: {} ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: Project -metadata: - name: limit-import-dependency-not-this-one -spec: - cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created - cloudName: openstack - secretName: openstack-clouds - managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource - resource: {} ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: Domain -metadata: - name: limit-import-dependency-not-this-one -spec: - cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created - cloudName: openstack - secretName: openstack-clouds - managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource - resource: {} ---- -# This `limit-import-dependency-not-this-one` should not be picked by the import filter -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: Limit -metadata: - name: limit-import-dependency-not-this-one -spec: - cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created - cloudName: openstack - secretName: openstack-clouds - managementPolicy: managed - resource: - serviceRef: limit-import-dependency-not-this-one - projectRef: limit-import-dependency-not-this-one - domainRef: limit-import-dependency-not-this-one - # TODO(scaffolding): Add the necessary fields to create the resource diff --git a/internal/controllers/limit/tests/limit-import-dependency/01-setup-cleanup.yaml b/internal/controllers/limit/tests/limit-import-dependency/01-setup-cleanup.yaml new file mode 100644 index 000000000..ecb3a2c3b --- /dev/null +++ b/internal/controllers/limit/tests/limit-import-dependency/01-setup-cleanup.yaml @@ -0,0 +1,85 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + - command: kubectl create configmap common --from-file=../utils + namespaced: true +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: setup-teardown + labels: + app: setup-teardown +spec: + replicas: 1 + selector: + matchLabels: + app: setup-teardown + template: + metadata: + labels: + app: setup-teardown + spec: + containers: + - name: setup-teardown + image: golang:alpine + command: + - sh + - -c + - | + mkdir /test-helper && + cp /app/* /test-helper && + cd /test-helper; + go mod init test-helper && + go mod tidy && + go build -o ./test-helper . && + exec ./test-helper + volumeMounts: + - name: openstack-clouds + mountPath: /etc/openstack + readOnly: true + - name: common + mountPath: /app + readOnly: true + - name: log-volume + mountPath: /log + env: + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: READINESS_FILE + value: /helper/ready + - name: OS_CLOUD + value: openstack-admin + - name: TEST_CASE + value: limit-import-dependency + - name: REGISTER_LIMITS + value: service-limit-import-dependency/limit-import-dependency/100 + - name: DOMAINS + value: domain-limit-import-dependency + readinessProbe: + exec: + command: + - test + - -f + - /helper/ready + initialDelaySeconds: 3 + periodSeconds: 1 + terminationGracePeriodSeconds: 60 + volumes: + - name: openstack-clouds + secret: + secretName: openstack-clouds + - name: common + configMap: + name: common + - name: log-volume + hostPath: + path: /log + type: DirectoryOrCreate diff --git a/internal/controllers/limit/tests/limit-import-dependency/02-assert.yaml b/internal/controllers/limit/tests/limit-import-dependency/02-assert.yaml index 78e1d605b..23cf7be57 100644 --- a/internal/controllers/limit/tests/limit-import-dependency/02-assert.yaml +++ b/internal/controllers/limit/tests/limit-import-dependency/02-assert.yaml @@ -1,44 +1,6 @@ --- apiVersion: kuttl.dev/v1beta1 kind: TestAssert -resourceRefs: - - apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: Limit - name: limit-import-dependency - ref: limit1 - - apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: Limit - name: limit-import-dependency-not-this-one - ref: limit2 - - apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: Service - name: limit-import-dependency - ref: service - - apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: Project - name: limit-import-dependency - ref: project - - apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: Domain - name: limit-import-dependency - ref: domain -assertAll: - - celExpr: "limit1.status.id != limit2.status.id" - - celExpr: "limit1.status.resource.serviceID == service.status.id" - - celExpr: "limit1.status.resource.projectID == project.status.id" - - celExpr: "limit1.status.resource.domainID == domain.status.id" ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: Limit -metadata: - name: limit-import-dependency -status: - conditions: - - type: Available - message: OpenStack resource is available - status: "True" - reason: Success - - type: Progressing - message: OpenStack resource is up to date - status: "False" - reason: Success +commands: + - script: "! kubectl get project.openstack.k-orc.cloud project-limit-import-dependency-imported --namespace $NAMESPACE" + skipLogOutput: true diff --git a/internal/controllers/limit/tests/limit-import-dependency/02-create-resource.yaml b/internal/controllers/limit/tests/limit-import-dependency/02-create-resource.yaml deleted file mode 100644 index 389b078e1..000000000 --- a/internal/controllers/limit/tests/limit-import-dependency/02-create-resource.yaml +++ /dev/null @@ -1,55 +0,0 @@ ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: Service -metadata: - name: limit-import-dependency-external -spec: - cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created - cloudName: openstack - secretName: openstack-clouds - managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource - resource: {} ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: Project -metadata: - name: limit-import-dependency-external -spec: - cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created - cloudName: openstack - secretName: openstack-clouds - managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource - resource: {} ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: Domain -metadata: - name: limit-import-dependency-external -spec: - cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created - cloudName: openstack - secretName: openstack-clouds - managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource - resource: {} ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: Limit -metadata: - name: limit-import-dependency-external -spec: - cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created - cloudName: openstack - secretName: openstack-clouds - managementPolicy: managed - resource: - serviceRef: limit-import-dependency-external - projectRef: limit-import-dependency-external - domainRef: limit-import-dependency-external - # TODO(scaffolding): Add the necessary fields to create the resource diff --git a/internal/controllers/limit/tests/limit-import-dependency/02-delete-import-dependencies.yaml b/internal/controllers/limit/tests/limit-import-dependency/02-delete-import-dependencies.yaml new file mode 100644 index 000000000..662b7e010 --- /dev/null +++ b/internal/controllers/limit/tests/limit-import-dependency/02-delete-import-dependencies.yaml @@ -0,0 +1,7 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + # We should be able to delete the import dependencies + - command: kubectl delete project.openstack.k-orc.cloud project-limit-import-dependency-imported + namespaced: true diff --git a/internal/controllers/limit/tests/limit-import-dependency/03-assert.yaml b/internal/controllers/limit/tests/limit-import-dependency/03-assert.yaml index e5b95325c..60b888c8b 100644 --- a/internal/controllers/limit/tests/limit-import-dependency/03-assert.yaml +++ b/internal/controllers/limit/tests/limit-import-dependency/03-assert.yaml @@ -2,9 +2,5 @@ apiVersion: kuttl.dev/v1beta1 kind: TestAssert commands: -- script: "! kubectl get service.openstack.k-orc.cloud limit-import-dependency --namespace $NAMESPACE" - skipLogOutput: true -- script: "! kubectl get project.openstack.k-orc.cloud limit-import-dependency --namespace $NAMESPACE" - skipLogOutput: true -- script: "! kubectl get domain.openstack.k-orc.cloud limit-import-dependency --namespace $NAMESPACE" - skipLogOutput: true + - script: "! kubectl get limit.openstack.k-orc.cloud limit-import-dependency --namespace $NAMESPACE" + skipLogOutput: true diff --git a/internal/controllers/limit/tests/limit-import-dependency/03-delete-import-dependencies.yaml b/internal/controllers/limit/tests/limit-import-dependency/03-delete-import-dependencies.yaml deleted file mode 100644 index 9e2df424f..000000000 --- a/internal/controllers/limit/tests/limit-import-dependency/03-delete-import-dependencies.yaml +++ /dev/null @@ -1,11 +0,0 @@ ---- -apiVersion: kuttl.dev/v1beta1 -kind: TestStep -commands: - # We should be able to delete the import dependencies - - command: kubectl delete service.openstack.k-orc.cloud limit-import-dependency - namespaced: true - - command: kubectl delete project.openstack.k-orc.cloud limit-import-dependency - namespaced: true - - command: kubectl delete domain.openstack.k-orc.cloud limit-import-dependency - namespaced: true diff --git a/internal/controllers/limit/tests/limit-import-dependency/04-delete-resource.yaml b/internal/controllers/limit/tests/limit-import-dependency/03-delete-resource.yaml similarity index 100% rename from internal/controllers/limit/tests/limit-import-dependency/04-delete-resource.yaml rename to internal/controllers/limit/tests/limit-import-dependency/03-delete-resource.yaml diff --git a/internal/controllers/limit/tests/limit-import-dependency/04-assert.yaml b/internal/controllers/limit/tests/limit-import-dependency/04-assert.yaml deleted file mode 100644 index c4ee6ffd8..000000000 --- a/internal/controllers/limit/tests/limit-import-dependency/04-assert.yaml +++ /dev/null @@ -1,6 +0,0 @@ ---- -apiVersion: kuttl.dev/v1beta1 -kind: TestAssert -commands: -- script: "! kubectl get limit.openstack.k-orc.cloud limit-import-dependency --namespace $NAMESPACE" - skipLogOutput: true diff --git a/internal/controllers/limit/tests/limit-import-dependency/README.md b/internal/controllers/limit/tests/limit-import-dependency/README.md index 5386b4cea..3ae461cfa 100644 --- a/internal/controllers/limit/tests/limit-import-dependency/README.md +++ b/internal/controllers/limit/tests/limit-import-dependency/README.md @@ -2,28 +2,20 @@ ## Step 00 -Import a Limit that references other imported resources. The referenced imported resources have no matching resources yet. +Import a Limit that references other imported resource(projectRef). The referenced imported resource(project) has no matching resources yet. Verify the Limit is waiting for the dependency to be ready. ## Step 01 -Create a Limit matching the import filter, except for referenced resources, and verify that it's not being imported. - -## Step 02 - -Create the referenced resources and a Limit matching the import filters. +Create the referenced resource(projectRef) and a Limit matching the import filters. Verify that the observed status on the imported Limit corresponds to the spec of the created Limit. -## Step 03 +## Step 02 -Delete the referenced resources and check that ORC does not prevent deletion. The OpenStack resources still exist because they +Delete the referenced imported resources and check that ORC does not prevent deletion. The OpenStack resources still exist because they were imported resources and we only deleted the ORC representation of it. -## Step 04 +## Step 03 Delete the Limit and validate that all resources are gone. - -## Reference - -https://k-orc.cloud/development/writing-tests/#import-dependency diff --git a/internal/controllers/limit/tests/limit-import-error/00-assert.yaml b/internal/controllers/limit/tests/limit-import-error/00-assert.yaml index 325725f59..a1fd0295c 100644 --- a/internal/controllers/limit/tests/limit-import-error/00-assert.yaml +++ b/internal/controllers/limit/tests/limit-import-error/00-assert.yaml @@ -1,8 +1,14 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: setup-teardown +status: + readyReplicas: 1 --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Limit metadata: - name: limit-import-error-external-1 + name: limit-import-error-1 status: conditions: - type: Available @@ -17,7 +23,7 @@ status: apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Limit metadata: - name: limit-import-error-external-2 + name: limit-import-error-2 status: conditions: - type: Available diff --git a/internal/controllers/limit/tests/limit-import-error/00-create-resources.yaml b/internal/controllers/limit/tests/limit-import-error/00-create-resources.yaml index 951b85974..dc6922fc3 100644 --- a/internal/controllers/limit/tests/limit-import-error/00-create-resources.yaml +++ b/internal/controllers/limit/tests/limit-import-error/00-create-resources.yaml @@ -2,42 +2,86 @@ apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Service metadata: - name: limit-import-error + name: service-limit-import-error + labels: + test-case: limit-import-error spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created - cloudName: openstack + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + type: "service-type-limit-import-error" +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Domain +metadata: + name: domain-limit-import-error + labels: + test-case: limit-import-error +spec: + cloudCredentialsRef: + cloudName: openstack-admin secretName: openstack-clouds managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource resource: {} --- apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Project +metadata: + name: project-limit-import-error-1 + labels: + test-case: limit-import-error +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + domainRef: domain-limit-import-error +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Project +metadata: + name: project-limit-import-error-2 + labels: + test-case: limit-import-error +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + domainRef: domain-limit-import-error +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Limit metadata: - name: limit-import-error-external-1 + name: limit-import-error-1 spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created - cloudName: openstack + cloudName: openstack-admin secretName: openstack-clouds managementPolicy: managed resource: - description: Limit from "import error" test - serviceRef: limit-import-error - # TODO(scaffolding): add any required field + description: Limit from "import error" test for project-limit-import-error-1 + serviceRef: service-limit-import-error + projectRef: project-limit-import-error-1 + resourceName: limit-import-error + resourceLimit: 50 --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Limit metadata: - name: limit-import-error-external-2 + name: limit-import-error-2 spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created - cloudName: openstack + cloudName: openstack-admin secretName: openstack-clouds managementPolicy: managed resource: - description: Limit from "import error" test - serviceRef: limit-import-error - # TODO(scaffolding): add any required field + description: Limit from "import error" test for project-limit-import-error-2 + serviceRef: service-limit-import-error + projectRef: project-limit-import-error-2 + resourceName: limit-import-error + resourceLimit: 60 diff --git a/internal/controllers/limit/tests/limit-import-error/00-secret.yaml b/internal/controllers/limit/tests/limit-import-error/00-secret.yaml index 045711ee7..11be38a4e 100644 --- a/internal/controllers/limit/tests/limit-import-error/00-secret.yaml +++ b/internal/controllers/limit/tests/limit-import-error/00-secret.yaml @@ -4,3 +4,5 @@ kind: TestStep commands: - command: kubectl create secret generic openstack-clouds --from-file=clouds.yaml=${E2E_KUTTL_OSCLOUDS} ${E2E_KUTTL_CACERT_OPT} namespaced: true + - command: kubectl create configmap common --from-file=../utils + namespaced: true diff --git a/internal/controllers/limit/tests/limit-import-error/00-setup-cleanup.yaml b/internal/controllers/limit/tests/limit-import-error/00-setup-cleanup.yaml new file mode 100644 index 000000000..e9d05c6de --- /dev/null +++ b/internal/controllers/limit/tests/limit-import-error/00-setup-cleanup.yaml @@ -0,0 +1,79 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: setup-teardown + labels: + app: setup-teardown +spec: + replicas: 1 + selector: + matchLabels: + app: setup-teardown + template: + metadata: + labels: + app: setup-teardown + spec: + containers: + - name: setup-teardown + image: golang:alpine + command: + - sh + - -c + - | + mkdir /test-helper && + cp /app/* /test-helper && + cd /test-helper; + go mod init test-helper && + go mod tidy && + go build -o ./test-helper . && + exec ./test-helper + volumeMounts: + - name: openstack-clouds + mountPath: /etc/openstack + readOnly: true + - name: common + mountPath: /app + readOnly: true + - name: log-volume + mountPath: /log + env: + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: READINESS_FILE + value: /helper/ready + - name: OS_CLOUD + value: openstack-admin + - name: TEST_CASE + value: limit-import-error + - name: REGISTER_LIMITS + value: service-limit-import-error/limit-import-error/100 + - name: DOMAINS + value: domain-limit-import-error + readinessProbe: + exec: + command: + - test + - -f + - /helper/ready + initialDelaySeconds: 3 + periodSeconds: 1 + terminationGracePeriodSeconds: 60 + volumes: + - name: openstack-clouds + secret: + secretName: openstack-clouds + - name: common + configMap: + name: common + - name: log-volume + hostPath: + path: /log + type: DirectoryOrCreate diff --git a/internal/controllers/limit/tests/limit-import-error/01-import-resource.yaml b/internal/controllers/limit/tests/limit-import-error/01-import-resource.yaml index 4add5fc38..ee0185907 100644 --- a/internal/controllers/limit/tests/limit-import-error/01-import-resource.yaml +++ b/internal/controllers/limit/tests/limit-import-error/01-import-resource.yaml @@ -5,9 +5,9 @@ metadata: name: limit-import-error spec: cloudCredentialsRef: - cloudName: openstack + cloudName: openstack-admin secretName: openstack-clouds managementPolicy: unmanaged import: filter: - description: Limit from "import error" test + resourceName: limit-import-error diff --git a/internal/controllers/limit/tests/limit-import-error/README.md b/internal/controllers/limit/tests/limit-import-error/README.md index 0db7f3887..435200dac 100644 --- a/internal/controllers/limit/tests/limit-import-error/README.md +++ b/internal/controllers/limit/tests/limit-import-error/README.md @@ -2,12 +2,8 @@ ## Step 00 -Create two Limits with identical specs. +Create two managed Limits with the same resource name. ## Step 01 -Ensure that an imported Limit with a filter matching the resources returns an error. - -## Reference - -https://k-orc.cloud/development/writing-tests/#import-error +Ensure that an imported Limit with a filter matching the resource returns an error. diff --git a/internal/controllers/limit/tests/limit-import/00-import-resource.yaml b/internal/controllers/limit/tests/limit-import/00-import-resource.yaml index 8ff26d7c6..148c1eab9 100644 --- a/internal/controllers/limit/tests/limit-import/00-import-resource.yaml +++ b/internal/controllers/limit/tests/limit-import/00-import-resource.yaml @@ -1,15 +1,58 @@ --- apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Service +metadata: + name: service-limit-import + labels: + test-case: limit-import +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + type: "service-type-limit-import" +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Project +metadata: + name: project-limit-import + labels: + test-case: limit-import +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + domainRef: domain-limit-import +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Domain +metadata: + name: domain-limit-import + labels: + test-case: limit-import +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Limit metadata: name: limit-import + labels: + test-case: limit-import spec: cloudCredentialsRef: - cloudName: openstack + cloudName: openstack-admin secretName: openstack-clouds managementPolicy: unmanaged import: filter: - name: limit-import-external - description: Limit limit-import-external from "limit-import" test - # TODO(scaffolding): Add all fields supported by the filter + serviceRef: service-limit-import + projectRef: project-limit-import + resourceName: limit-import diff --git a/internal/controllers/limit/tests/limit-import/00-secret.yaml b/internal/controllers/limit/tests/limit-import/00-secret.yaml index 045711ee7..11be38a4e 100644 --- a/internal/controllers/limit/tests/limit-import/00-secret.yaml +++ b/internal/controllers/limit/tests/limit-import/00-secret.yaml @@ -4,3 +4,5 @@ kind: TestStep commands: - command: kubectl create secret generic openstack-clouds --from-file=clouds.yaml=${E2E_KUTTL_OSCLOUDS} ${E2E_KUTTL_CACERT_OPT} namespaced: true + - command: kubectl create configmap common --from-file=../utils + namespaced: true diff --git a/internal/controllers/limit/tests/limit-import/00-setup-cleanup.yaml b/internal/controllers/limit/tests/limit-import/00-setup-cleanup.yaml new file mode 100644 index 000000000..4d1e88f56 --- /dev/null +++ b/internal/controllers/limit/tests/limit-import/00-setup-cleanup.yaml @@ -0,0 +1,79 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: setup-teardown + labels: + app: setup-teardown +spec: + replicas: 1 + selector: + matchLabels: + app: setup-teardown + template: + metadata: + labels: + app: setup-teardown + spec: + containers: + - name: setup-teardown + image: golang:alpine + command: + - sh + - -c + - | + mkdir /test-helper && + cp /app/* /test-helper && + cd /test-helper; + go mod init test-helper && + go mod tidy && + go build -o ./test-helper . && + exec ./test-helper + volumeMounts: + - name: openstack-clouds + mountPath: /etc/openstack + readOnly: true + - name: common + mountPath: /app + readOnly: true + - name: log-volume + mountPath: /log + env: + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: READINESS_FILE + value: /helper/ready + - name: OS_CLOUD + value: openstack-admin + - name: TEST_CASE + value: limit-import + - name: REGISTER_LIMITS + value: service-limit-import/limit-import/100,service-limit-import/limit-import-nonexistent-resource/100 + - name: DOMAINS + value: domain-limit-import + readinessProbe: + exec: + command: + - test + - -f + - /helper/ready + initialDelaySeconds: 3 + periodSeconds: 1 + terminationGracePeriodSeconds: 60 + volumes: + - name: openstack-clouds + secret: + secretName: openstack-clouds + - name: common + configMap: + name: common + - name: log-volume + hostPath: + path: /log + type: DirectoryOrCreate diff --git a/internal/controllers/limit/tests/limit-import/01-assert.yaml b/internal/controllers/limit/tests/limit-import/01-assert.yaml index 0b4c9a952..9c46ed88b 100644 --- a/internal/controllers/limit/tests/limit-import/01-assert.yaml +++ b/internal/controllers/limit/tests/limit-import/01-assert.yaml @@ -14,9 +14,9 @@ status: status: "False" reason: Success resource: - name: limit-import-external-not-this-one description: Limit limit-import-external from "limit-import" test - # TODO(scaffolding): Add fields necessary to match filter + resourceName: limit-import-nonexistent-resource + resourceLimit: 55 --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Limit diff --git a/internal/controllers/limit/tests/limit-import/01-create-trap-resource.yaml b/internal/controllers/limit/tests/limit-import/01-create-trap-resource.yaml index 924253205..fd172dc0b 100644 --- a/internal/controllers/limit/tests/limit-import/01-create-trap-resource.yaml +++ b/internal/controllers/limit/tests/limit-import/01-create-trap-resource.yaml @@ -1,17 +1,4 @@ --- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: Service -metadata: - name: limit-import-external-not-this-one -spec: - cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created - cloudName: openstack - secretName: openstack-clouds - managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource - resource: {} ---- # This `limit-import-external-not-this-one` resource serves two purposes: # - ensure that we can successfully create another resource which name is a substring of it (i.e. it's not being adopted) # - ensure that importing a resource which name is a substring of it will not pick this one. @@ -21,11 +8,12 @@ metadata: name: limit-import-external-not-this-one spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created - cloudName: openstack + cloudName: openstack-admin secretName: openstack-clouds managementPolicy: managed resource: description: Limit limit-import-external from "limit-import" test - serviceRef: limit-import-external-not-this-one - # TODO(scaffolding): Add fields necessary to match filter + serviceRef: service-limit-import + projectRef: project-limit-import + resourceName: limit-import-nonexistent-resource + resourceLimit: 55 diff --git a/internal/controllers/limit/tests/limit-import/02-assert.yaml b/internal/controllers/limit/tests/limit-import/02-assert.yaml index e18bb86ec..ca4d5fab2 100644 --- a/internal/controllers/limit/tests/limit-import/02-assert.yaml +++ b/internal/controllers/limit/tests/limit-import/02-assert.yaml @@ -2,16 +2,21 @@ apiVersion: kuttl.dev/v1beta1 kind: TestAssert resourceRefs: - - apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: Limit - name: limit-import-external - ref: limit1 - - apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: Limit - name: limit-import-external-not-this-one - ref: limit2 + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Limit + name: limit-import + ref: limit0 + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Limit + name: limit-import-external + ref: limit1 + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Limit + name: limit-import-external-not-this-one + ref: limit2 assertAll: - - celExpr: "limit1.status.id != limit2.status.id" + - celExpr: "limit0.status.id == limit1.status.id" + - celExpr: "limit1.status.id != limit2.status.id" --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Limit @@ -28,6 +33,6 @@ status: status: "False" reason: Success resource: - name: limit-import-external description: Limit limit-import-external from "limit-import" test - # TODO(scaffolding): Add all fields the resource supports + resourceName: limit-import + resourceLimit: 42 diff --git a/internal/controllers/limit/tests/limit-import/02-create-resource.yaml b/internal/controllers/limit/tests/limit-import/02-create-resource.yaml index ad73945cb..96138f305 100644 --- a/internal/controllers/limit/tests/limit-import/02-create-resource.yaml +++ b/internal/controllers/limit/tests/limit-import/02-create-resource.yaml @@ -1,28 +1,16 @@ --- apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: Service -metadata: - name: limit-import -spec: - cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created - cloudName: openstack - secretName: openstack-clouds - managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource - resource: {} ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Limit metadata: name: limit-import-external spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created - cloudName: openstack + cloudName: openstack-admin secretName: openstack-clouds managementPolicy: managed resource: description: Limit limit-import-external from "limit-import" test - serviceRef: limit-import - # TODO(scaffolding): Add fields necessary to match filter + serviceRef: service-limit-import + projectRef: project-limit-import + resourceName: limit-import + resourceLimit: 42 diff --git a/internal/controllers/limit/tests/limit-import/README.md b/internal/controllers/limit/tests/limit-import/README.md index 19b1de97f..1efe51eb7 100644 --- a/internal/controllers/limit/tests/limit-import/README.md +++ b/internal/controllers/limit/tests/limit-import/README.md @@ -6,13 +6,9 @@ Import a limit that matches all fields in the filter, and verify it is waiting f ## Step 01 -Create a limit whose name is a superstring of the one specified in the import filter, otherwise matching the filter, and verify that it's not being imported. +Create a limit whose resource name is a superstring of the one specified in the import filter, otherwise matching the filter, and verify that it's not being imported. ## Step 02 Create a limit matching the filter and verify that the observed status on the imported limit corresponds to the spec of the created limit. Also, confirm that it does not adopt any limit whose name is a superstring of its own. - -## Reference - -https://k-orc.cloud/development/writing-tests/#import diff --git a/internal/controllers/limit/tests/limit-update/00-assert.yaml b/internal/controllers/limit/tests/limit-update/00-assert.yaml index a2b334697..53135373f 100644 --- a/internal/controllers/limit/tests/limit-update/00-assert.yaml +++ b/internal/controllers/limit/tests/limit-update/00-assert.yaml @@ -2,12 +2,12 @@ apiVersion: kuttl.dev/v1beta1 kind: TestAssert resourceRefs: - - apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: Limit - name: limit-update - ref: limit + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Limit + name: limit-update + ref: limit assertAll: - - celExpr: "!has(limit.status.resource.description)" + - celExpr: "limit.status.id != ''" --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Limit @@ -15,12 +15,13 @@ metadata: name: limit-update status: resource: - name: limit-update - # TODO(scaffolding): Add matches for more fields + description: some description + resourceName: limit-update + resourceLimit: 50 conditions: - - type: Available - status: "True" - reason: Success - - type: Progressing - status: "False" - reason: Success + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success diff --git a/internal/controllers/limit/tests/limit-update/00-dependency-resource.yaml b/internal/controllers/limit/tests/limit-update/00-dependency-resource.yaml new file mode 100644 index 000000000..b5a42b6b2 --- /dev/null +++ b/internal/controllers/limit/tests/limit-update/00-dependency-resource.yaml @@ -0,0 +1,41 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Service +metadata: + name: service-limit-update + labels: + test-case: limit-update +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + type: "service-type-limit-update" +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Project +metadata: + name: project-limit-update + labels: + test-case: limit-update +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + domainRef: domain-limit-update +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Domain +metadata: + name: domain-limit-update + labels: + test-case: limit-update +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: {} diff --git a/internal/controllers/limit/tests/limit-update/00-minimal-resource.yaml b/internal/controllers/limit/tests/limit-update/00-minimal-resource.yaml index 7ae11e841..c07a509fa 100644 --- a/internal/controllers/limit/tests/limit-update/00-minimal-resource.yaml +++ b/internal/controllers/limit/tests/limit-update/00-minimal-resource.yaml @@ -1,28 +1,18 @@ --- apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: Service -metadata: - name: limit-update -spec: - cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created - cloudName: openstack - secretName: openstack-clouds - managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource - resource: {} ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Limit metadata: name: limit-update + labels: + test-case: limit-update spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created or updated - cloudName: openstack + cloudName: openstack-admin secretName: openstack-clouds managementPolicy: managed - # TODO(scaffolding): Only add the mandatory fields. It's possible the resource - # doesn't have mandatory fields, in that case, leave it empty. resource: - serviceRef: limit-update + description: some description + serviceRef: service-limit-update + projectRef: project-limit-update + resourceName: limit-update + resourceLimit: 50 diff --git a/internal/controllers/limit/tests/limit-update/00-secret.yaml b/internal/controllers/limit/tests/limit-update/00-secret.yaml index 045711ee7..11be38a4e 100644 --- a/internal/controllers/limit/tests/limit-update/00-secret.yaml +++ b/internal/controllers/limit/tests/limit-update/00-secret.yaml @@ -4,3 +4,5 @@ kind: TestStep commands: - command: kubectl create secret generic openstack-clouds --from-file=clouds.yaml=${E2E_KUTTL_OSCLOUDS} ${E2E_KUTTL_CACERT_OPT} namespaced: true + - command: kubectl create configmap common --from-file=../utils + namespaced: true diff --git a/internal/controllers/limit/tests/limit-update/00-setup-cleanup.yaml b/internal/controllers/limit/tests/limit-update/00-setup-cleanup.yaml new file mode 100644 index 000000000..a956d9afc --- /dev/null +++ b/internal/controllers/limit/tests/limit-update/00-setup-cleanup.yaml @@ -0,0 +1,79 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: setup-teardown + labels: + app: setup-teardown +spec: + replicas: 1 + selector: + matchLabels: + app: setup-teardown + template: + metadata: + labels: + app: setup-teardown + spec: + containers: + - name: setup-teardown + image: golang:alpine + command: + - sh + - -c + - | + mkdir /test-helper && + cp /app/* /test-helper && + cd /test-helper; + go mod init test-helper && + go mod tidy && + go build -o ./test-helper . && + exec ./test-helper + volumeMounts: + - name: openstack-clouds + mountPath: /etc/openstack + readOnly: true + - name: common + mountPath: /app + readOnly: true + - name: log-volume + mountPath: /log + env: + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: READINESS_FILE + value: /helper/ready + - name: OS_CLOUD + value: openstack-admin + - name: TEST_CASE + value: limit-update + - name: REGISTER_LIMITS + value: service-limit-update/limit-update/100 + - name: DOMAINS + value: domain-limit-update + readinessProbe: + exec: + command: + - test + - -f + - /helper/ready + initialDelaySeconds: 3 + periodSeconds: 1 + terminationGracePeriodSeconds: 60 + volumes: + - name: openstack-clouds + secret: + secretName: openstack-clouds + - name: common + configMap: + name: common + - name: log-volume + hostPath: + path: /log + type: DirectoryOrCreate diff --git a/internal/controllers/limit/tests/limit-update/01-assert.yaml b/internal/controllers/limit/tests/limit-update/01-assert.yaml index bc670606d..c33e6938c 100644 --- a/internal/controllers/limit/tests/limit-update/01-assert.yaml +++ b/internal/controllers/limit/tests/limit-update/01-assert.yaml @@ -5,13 +5,12 @@ metadata: name: limit-update status: resource: - name: limit-update-updated - description: limit-update-updated - # TODO(scaffolding): match all fields that were modified + description: description updated + resourceLimit: 49 conditions: - - type: Available - status: "True" - reason: Success - - type: Progressing - status: "False" - reason: Success + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success diff --git a/internal/controllers/limit/tests/limit-update/01-updated-resource.yaml b/internal/controllers/limit/tests/limit-update/01-updated-resource.yaml index 419cf32ad..930b4a5e9 100644 --- a/internal/controllers/limit/tests/limit-update/01-updated-resource.yaml +++ b/internal/controllers/limit/tests/limit-update/01-updated-resource.yaml @@ -5,6 +5,5 @@ metadata: name: limit-update spec: resource: - name: limit-update-updated - description: limit-update-updated - # TODO(scaffolding): update all mutable fields + description: description updated + resourceLimit: 49 diff --git a/internal/controllers/limit/tests/limit-update/02-assert.yaml b/internal/controllers/limit/tests/limit-update/02-assert.yaml index 7f36160e1..0c38503dc 100644 --- a/internal/controllers/limit/tests/limit-update/02-assert.yaml +++ b/internal/controllers/limit/tests/limit-update/02-assert.yaml @@ -1,26 +1,16 @@ --- -apiVersion: kuttl.dev/v1beta1 -kind: TestAssert -resourceRefs: - - apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: Limit - name: limit-update - ref: limit -assertAll: - - celExpr: "!has(limit.status.resource.description)" ---- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Limit metadata: name: limit-update status: resource: - name: limit-update - # TODO(scaffolding): validate that updated fields were all reverted to their original value + description: some description + resourceLimit: 50 conditions: - - type: Available - status: "True" - reason: Success - - type: Progressing - status: "False" - reason: Success + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success diff --git a/internal/controllers/limit/tests/limit-update/03-assert.yaml b/internal/controllers/limit/tests/limit-update/03-assert.yaml new file mode 100644 index 000000000..e3f113e44 --- /dev/null +++ b/internal/controllers/limit/tests/limit-update/03-assert.yaml @@ -0,0 +1,14 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Limit +metadata: + name: limit-update +status: + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: InvalidConfiguration + message: "invalid configuration updating resource: limit cannot be updated with domainRef when projectRef has been used" diff --git a/internal/controllers/limit/tests/limit-update/03-updated-resource.yaml b/internal/controllers/limit/tests/limit-update/03-updated-resource.yaml new file mode 100644 index 000000000..41dbf09f9 --- /dev/null +++ b/internal/controllers/limit/tests/limit-update/03-updated-resource.yaml @@ -0,0 +1,9 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Limit +metadata: + name: limit-update +spec: + resource: + projectRef: + domainRef: domain-limit-update diff --git a/internal/controllers/limit/tests/limit-update/README.md b/internal/controllers/limit/tests/limit-update/README.md index 4937a9715..5313e1ac6 100644 --- a/internal/controllers/limit/tests/limit-update/README.md +++ b/internal/controllers/limit/tests/limit-update/README.md @@ -12,6 +12,6 @@ Update all mutable fields. Revert the resource to its original value and verify that the resulting object matches its state when first created. -## Reference +## Step 03 -https://k-orc.cloud/development/writing-tests/#update +Replacing `projectRef` with `domainRef` should fail. diff --git a/internal/controllers/limit/tests/utils/hooks.go b/internal/controllers/limit/tests/utils/hooks.go new file mode 100644 index 000000000..0ce6b9e0d --- /dev/null +++ b/internal/controllers/limit/tests/utils/hooks.go @@ -0,0 +1,41 @@ +package main + +import ( + "context" + "time" +) + +var registeredLimitIds []string + +func onStart(ctx context.Context) (retErr error) { + c, err := newKeystoneClient(ctx) + if err != nil { + return err + } + + ctx, cancel := context.WithTimeout(ctx, time.Second*30) + defer cancel() + + if err := setupRegisteredLimits(ctx, c); err != nil { + return err + } + + return nil +} + +func onStop(ctx context.Context) error { + c, err := newKeystoneClient(ctx) + if err != nil { + return err + } + + cleanUpRegisteredLimits(ctx, c) + cleanUpDomains(ctx, c) + + return nil +} + +func init() { + onStartFunc = onStart + onStopFunc = onStop +} diff --git a/internal/controllers/limit/tests/utils/main.go b/internal/controllers/limit/tests/utils/main.go new file mode 100644 index 000000000..4a5b3c35e --- /dev/null +++ b/internal/controllers/limit/tests/utils/main.go @@ -0,0 +1,256 @@ +package main + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "io" + "log/slog" + "os" + "os/signal" + "path" + "strings" + "syscall" + "time" + + "k8s.io/utils/ptr" + + "github.com/gophercloud/gophercloud/v2" + "github.com/gophercloud/gophercloud/v2/openstack" + "github.com/gophercloud/gophercloud/v2/openstack/identity/v3/domains" + "github.com/gophercloud/gophercloud/v2/openstack/identity/v3/registeredlimits" + "github.com/gophercloud/gophercloud/v2/openstack/identity/v3/services" + "github.com/gophercloud/utils/v2/openstack/clientconfig" +) + +var ( + onStartFunc func(context.Context) error + onStopFunc func(context.Context) error +) + +func main() { + logPath := os.Getenv("LOG_PATH") + if logPath == "" { + logPath = "/log" + } + + namespace := os.Getenv("POD_NAMESPACE") + if namespace == "" { + namespace = "default" + } + + podName := os.Getenv("POD_NAME") + if podName == "" { + namespace = "default" + } + + testCase := os.Getenv("TEST_CASE") + if testCase == "" { + testCase = "unknown-case" + } + + logPath = path.Join(logPath, testCase, namespace) + + if err := os.MkdirAll(logPath, 0755); err != nil { + panic(err) + } + + f, err := os.OpenFile(path.Join(logPath, "log"), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644) + if err != nil { + panic(err) + } + defer f.Close() + + slog.SetDefault(slog.New(slog.NewTextHandler(io.MultiWriter(os.Stderr, f), nil)).With("podName", podName)) + + slog.Info("starting") + ctx, cancel := signal.NotifyContext(context.TODO(), os.Interrupt, syscall.SIGTERM) + defer cancel() + + slog.Info("environment variables ", "env", os.Environ()) + + var failed bool + if onStartFunc != nil { + slog.Info("running onStart...") + if err := onStartFunc(ctx); err != nil { + slog.Error("executing onStart", "error", err) + failed = true + } + } + + if !failed { + slog.Info("onStart completed") + + readinessFile := os.Getenv("READINESS_FILE") + if readinessFile == "" { + slog.Error("READINESS_FILE not found") + os.Exit(1) + } + + if err := os.Mkdir(path.Dir(readinessFile), 0755); err != nil { + slog.Error("create path for readiness file", "readinessFile", readinessFile, "error", err) + os.Exit(1) + } + + if _, err := os.Create(readinessFile); err != nil { + slog.Error("create readiness file", "readinessFile", readinessFile, "error", err) + os.Exit(1) + } + } + + <-ctx.Done() + + slog.Info("signal received") + + ctx1, cancel1 := context.WithTimeout(context.TODO(), time.Second*30) + defer cancel1() + + if onStopFunc != nil { + slog.Info("running onStop...") + if err := onStopFunc(ctx1); err != nil { + slog.Error("executing onStop", "error", err) + os.Exit(1) + } + } + + slog.Info("onStop completed") + + slog.Info("exiting") +} + +func newKeystoneClient(ctx context.Context) (*gophercloud.ServiceClient, error) { + cloudName := os.Getenv("OS_CLOUD") + if cloudName == "" { + cloudName = "openstack-admin" + } + + opts := &clientconfig.ClientOpts{ + Cloud: cloudName, + } + + p, err := clientconfig.AuthenticatedClient(ctx, opts) + if err != nil { + return nil, fmt.Errorf("get authenticated client: %w", err) + } + + return openstack.NewIdentityV3(p, gophercloud.EndpointOpts{}) +} + +type errorDetail struct { + Title string `json:"title,omitempty"` + Code int `json:"code,omitempty"` + Message string `json:"message,omitempty"` +} + +type errorDetailWrapper struct { + Error errorDetail `json:"error"` +} + +func extractErrorDetail(err error) (*errorDetail, bool) { + gcErr := &gophercloud.ErrUnexpectedResponseCode{} + if !errors.As(err, gcErr) { + return nil, false + } + detail := &errorDetailWrapper{} + if err := json.Unmarshal(gcErr.Body, detail); err != nil { + return nil, false + } + + return &detail.Error, true +} + +func retry(ctx context.Context, delay time.Duration, f func(ctx context.Context) (bool, error)) error { + for { + ok, err := f(ctx) + if err != nil { + return err + } + + if !ok { + return nil + } + + time.Sleep(delay) + } +} + +func deleteRegisteredLimit(ctx context.Context, c *gophercloud.ServiceClient, id string) error { + slog := slog.With("id", id) + return retry(ctx, time.Second, func(ctx context.Context) (bool, error) { + if err := registeredlimits.Delete(ctx, c, id).ExtractErr(); err != nil { + detail, ok := extractErrorDetail(err) + if !ok { + return false, err + } + + if detail.Code == 404 { + slog.Error("registered limit has been deleted") + return false, nil + } + if detail.Code == 403 && strings.Contains(detail.Message, "because there are project limits associated with it") { + slog.Info("there are project limits associated with it") + return true, nil + } + + return false, err + } + + return false, nil + }) +} + +func disableDomain(ctx context.Context, c *gophercloud.ServiceClient, domainName string) { + allPages, err := domains.List(c, domains.ListOpts{Name: domainName}).AllPages(ctx) + if err != nil { + slog.Error("find domain", "domainName", domainName, "error", err) + return + } + + dms, err := domains.ExtractDomains(allPages) + if err != nil { + slog.Error("extract domain result", "error", err) + return + } + + for _, d := range dms { + if d.Enabled { + slog := slog.With("domainName", d.Name, "domainId", d.ID) + if _, err := domains.Update(ctx, c, d.ID, domains.UpdateOpts{Enabled: ptr.To(false)}).Extract(); err != nil { + slog.Error("set domain to disabled", "error", err) + } else { + slog.Info("disabled domain") + } + } + } +} + +func getServiceId(ctx context.Context, c *gophercloud.ServiceClient, svcName string) (string, error) { + var svc *services.Service + + if err := retry(ctx, time.Second, func(ctx context.Context) (bool, error) { + allPages, err := services.List(c, services.ListOpts{Name: svcName}).AllPages(ctx) + if err != nil { + return false, fmt.Errorf("list service: %w", err) + } + svcs, err := services.ExtractServices(allPages) + if err != nil { + return false, fmt.Errorf("extract services: %w", err) + } + + switch len(svcs) { + case 0: + slog.Info("no service found", "serviceName", svcName) + return true, nil + case 1: + svc = &svcs[0] + return false, nil + default: + return false, fmt.Errorf("more than one service found") + } + }); err != nil { + return "", err + } + + return svc.ID, nil +} diff --git a/internal/controllers/limit/tests/utils/utils.go b/internal/controllers/limit/tests/utils/utils.go new file mode 100644 index 000000000..7206d8802 --- /dev/null +++ b/internal/controllers/limit/tests/utils/utils.go @@ -0,0 +1,99 @@ +package main + +import ( + "context" + "fmt" + "log/slog" + "os" + "strconv" + "strings" + + "github.com/gophercloud/gophercloud/v2" + "github.com/gophercloud/gophercloud/v2/openstack/identity/v3/registeredlimits" +) + +func createRegisteredLimit(ctx context.Context, c *gophercloud.ServiceClient, svcName, resourceName string, limit int) (string, error) { + svcId, err := getServiceId(ctx, c, svcName) + if err != nil { + return "", fmt.Errorf("get service id with name: %w", err) + } + + limits, err := registeredlimits.BatchCreate(ctx, c, registeredlimits.BatchCreateOpts{ + { + ServiceID: svcId, + ResourceName: resourceName, + DefaultLimit: limit, + }, + }).Extract() + + if err != nil { + return "", err + } + + if len(limits) != 1 { + return "", fmt.Errorf("unexpected creation response") + } + + slog.Info("registered limit created", "serviceName", svcName, "resourceName", resourceName, "limit", limit) + + return limits[0].ID, nil +} + +func setupRegisteredLimits(ctx context.Context, c *gophercloud.ServiceClient) error { + // Format of REGISTER_LIMITS: + // ServiceName/ResourceName/Limit,ServiceName/ResourceName/Limit... + limitEnv := os.Getenv("REGISTER_LIMITS") + if limitEnv == "" { + return nil + } + + for res := range strings.SplitSeq(limitEnv, ",") { + if res == "" { + continue + } + + splits := strings.Split(res, "/") + if len(splits) != 3 { + return fmt.Errorf("unexpect resource request %q", res) + } + + svcName, resName, limitStr := splits[0], splits[1], splits[2] + limit, err := strconv.Atoi(limitStr) + if err != nil { + return fmt.Errorf("invalid limit %s: %w", limitStr, err) + } + + id, err := createRegisteredLimit(ctx, c, svcName, resName, limit) + if err != nil { + return fmt.Errorf("create registered limit: %w", err) + } + + registeredLimitIds = append(registeredLimitIds, id) + } + + slog.Info("created registered limits", "ids", registeredLimitIds) + + return nil +} + +func cleanUpRegisteredLimits(ctx context.Context, c *gophercloud.ServiceClient) { + for _, id := range registeredLimitIds { + if err := deleteRegisteredLimit(ctx, c, id); err != nil { + slog.Error("delete registered limit", "error", err, "id", id) + continue + } + + slog.Info("deleted registered limit", "id", id) + } +} + +func cleanUpDomains(ctx context.Context, c *gophercloud.ServiceClient) { + domainEnv := os.Getenv("DOMAINS") + if domainEnv == "" { + return + } + + for d := range strings.SplitSeq(domainEnv, ",") { + disableDomain(ctx, c, d) + } +} diff --git a/website/docs/crd-reference.md b/website/docs/crd-reference.md index 117037720..a41d7dcf3 100644 --- a/website/docs/crd-reference.md +++ b/website/docs/crd-reference.md @@ -2283,7 +2283,7 @@ _Appears in:_ | `serviceRef` _[KubernetesNameRef](#kubernetesnameref)_ | serviceRef is a reference to the ORC Service which this resource is associated with. | | MaxLength: 253
MinLength: 1
Optional: \{\}
| | `projectRef` _[KubernetesNameRef](#kubernetesnameref)_ | projectRef is a reference to the ORC Project which this resource is associated with. | | MaxLength: 253
MinLength: 1
Optional: \{\}
| | `domainRef` _[KubernetesNameRef](#kubernetesnameref)_ | domainRef is a reference to the ORC Domain which this resource is associated with. | | MaxLength: 253
MinLength: 1
Optional: \{\}
| -| `resourceName` _string_ | resoureName is the name of the resource this limit is associated with. | | MaxLength: 255
MinLength: 1
Pattern: `^[\S]+$`
Optional: \{\}
| +| `resourceName` _string_ | resourceName is the name of the resource this limit is associated with. | | MaxLength: 255
MinLength: 1
Pattern: `^[\S]+$`
Optional: \{\}
| #### LimitImport @@ -2323,8 +2323,8 @@ _Appears in:_ | `serviceRef` _[KubernetesNameRef](#kubernetesnameref)_ | serviceRef is a reference to the ORC Service which this resource is associated with. | | MaxLength: 253
MinLength: 1
Required: \{\}
| | `projectRef` _[KubernetesNameRef](#kubernetesnameref)_ | projectRef is a reference to the ORC Project which this resource is associated with.
Either Domain ID or Project ID must be provided.
https://opendev.org/openstack/keystone/src/commit/30ef2ffa65a3486ef882f00538e20f2253c57d4c/keystone/limit/schema.py#L323-L340 | | MaxLength: 253
MinLength: 1
Optional: \{\}
| | `domainRef` _[KubernetesNameRef](#kubernetesnameref)_ | domainRef is a reference to the ORC Domain which this resource is associated with.
Either Domain ID or Project ID must be provided.
https://opendev.org/openstack/keystone/src/commit/30ef2ffa65a3486ef882f00538e20f2253c57d4c/keystone/limit/schema.py#L323-L340 | | MaxLength: 253
MinLength: 1
Optional: \{\}
| -| `resourceName` _string_ | resoureName is the name of the resource this limit is associated with. | | MaxLength: 255
MinLength: 1
Pattern: `^[\S]+$`
Required: \{\}
| -| `resourceLimit` _integer_ | resoureLimit is the override value of the limit. | | Minimum: -1
Required: \{\}
| +| `resourceName` _string_ | resourceName is the name of the resource this limit is associated with. | | MaxLength: 255
MinLength: 1
Pattern: `^[\S]+$`
Required: \{\}
| +| `resourceLimit` _integer_ | resourceLimit is the override value of the limit. | | Minimum: -1
Required: \{\}
| #### LimitResourceStatus @@ -2344,8 +2344,8 @@ _Appears in:_ | `serviceID` _string_ | serviceID is the ID of the Service to which the resource is associated. | | MaxLength: 1024
Optional: \{\}
| | `projectID` _string_ | projectID is the ID of the Project to which the resource is associated. | | MaxLength: 1024
Optional: \{\}
| | `domainID` _string_ | domainID is the ID of the Domain to which the resource is associated. | | MaxLength: 1024
Optional: \{\}
| -| `resourceLimit` _integer_ | resoureLimit is the override value of the limit. | | Optional: \{\}
| -| `resourceName` _string_ | resoureName is the name of the resource this limit is associated with. | | MaxLength: 1024
Optional: \{\}
| +| `resourceLimit` _integer_ | resourceLimit is the override value of the limit. | | Optional: \{\}
| +| `resourceName` _string_ | resourceName is the name of the resource this limit is associated with. | | MaxLength: 1024
Optional: \{\}
| #### LimitSpec From 13d4d9ac9dc72afff8c9677b8e1e36d54caf0ceb Mon Sep 17 00:00:00 2001 From: Winston Chen Date: Tue, 28 Jul 2026 13:25:49 +0100 Subject: [PATCH 08/13] Add description field in LimitFilter to align with RegisteredLimit --- api/v1alpha1/limit_types.go | 6 ++++++ internal/controllers/limit/actuator.go | 25 +++++++++++++++++++++++-- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/api/v1alpha1/limit_types.go b/api/v1alpha1/limit_types.go index 1fc1d9130..e241d444f 100644 --- a/api/v1alpha1/limit_types.go +++ b/api/v1alpha1/limit_types.go @@ -62,6 +62,12 @@ type LimitResourceSpec struct { // LimitFilter defines an existing resource by its properties // +kubebuilder:validation:MinProperties:=1 type LimitFilter struct { + // description of the existing resource + // +kubebuilder:validation:MinLength:=1 + // +kubebuilder:validation:MaxLength:=255 + // +optional + Description *string `json:"description,omitempty"` + // serviceRef is a reference to the ORC Service which this resource is associated with. // +optional ServiceRef *KubernetesNameRef `json:"serviceRef,omitempty"` diff --git a/internal/controllers/limit/actuator.go b/internal/controllers/limit/actuator.go index 6256eb9a8..633439021 100644 --- a/internal/controllers/limit/actuator.go +++ b/internal/controllers/limit/actuator.go @@ -111,6 +111,14 @@ func (actuator limitActuator) ListOSResourcesForAdoption(ctx context.Context, or return nil, false } + var filters []osclients.ResourceFilter[osResourceT] + + if resourceSpec.Description != nil { + filters = append(filters, func(ort *osResourceT) bool { + return ort.Description == *resourceSpec.Description + }) + } + listOpts := limits.ListOpts{ ServiceID: ptr.Deref(svc.Status.ID, ""), ProjectID: ptr.Deref(project.Status.ID, ""), @@ -118,7 +126,7 @@ func (actuator limitActuator) ListOSResourcesForAdoption(ctx context.Context, or ResourceName: resourceSpec.ResourceName, } - return actuator.osClient.ListLimits(ctx, listOpts), true + return actuator.listOSResources(ctx, filters, listOpts), true } func (actuator limitActuator) ListOSResourcesForImport(ctx context.Context, obj orcObjectPT, filter filterT) (iter.Seq2[*osResourceT, error], progress.ReconcileStatus) { @@ -153,6 +161,14 @@ func (actuator limitActuator) ListOSResourcesForImport(ctx context.Context, obj return nil, reconcileStatus } + var filters []osclients.ResourceFilter[osResourceT] + + if filter.Description != nil { + filters = append(filters, func(ort *osResourceT) bool { + return ort.Description == *filter.Description + }) + } + listOpts := limits.ListOpts{ ServiceID: ptr.Deref(service.Status.ID, ""), ProjectID: ptr.Deref(project.Status.ID, ""), @@ -160,7 +176,12 @@ func (actuator limitActuator) ListOSResourcesForImport(ctx context.Context, obj ResourceName: filter.ResourceName, } - return actuator.osClient.ListLimits(ctx, listOpts), reconcileStatus + return actuator.listOSResources(ctx, filters, listOpts), reconcileStatus +} + +func (actuator limitActuator) listOSResources(ctx context.Context, filters []osclients.ResourceFilter[osResourceT], listOpts limits.ListOptsBuilder) iter.Seq2[*limits.Limit, error] { + registeredLimits := actuator.osClient.ListLimits(ctx, listOpts) + return osclients.Filter(registeredLimits, filters...) } func (actuator limitActuator) CreateResource(ctx context.Context, obj orcObjectPT) (*osResourceT, progress.ReconcileStatus) { From 9695342f293828abfb2d9f3e2905e73a76dda877 Mon Sep 17 00:00:00 2001 From: Winston Chen Date: Tue, 28 Jul 2026 14:18:36 +0100 Subject: [PATCH 09/13] Add limit controller in README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index a5663e5fd..7af379b23 100644 --- a/README.md +++ b/README.md @@ -82,6 +82,7 @@ kubectl delete -f $ORC_RELEASE | group | | ✔ | ✔ | | image | ✔ | ✔ | ✔ | | keypair | | ◐ | ◐ | +| limit | | | ◐ | | network | | ◐ | ◐ | | port | | ◐ | ◐ | | project | | ◐ | ◐ | From 7d4b66f762ee50026a8528b343d449208138c716 Mon Sep 17 00:00:00 2001 From: Winston Chen Date: Tue, 28 Jul 2026 15:14:11 +0100 Subject: [PATCH 10/13] Fix warnings from golint and kubeapilinter Update generated resources to reflect the addition of description in Limit spec Update the OLM bundle --- api/v1alpha1/limit_types.go | 4 ++-- api/v1alpha1/zz_generated.deepcopy.go | 5 +++++ cmd/models-schema/zz_generated.openapi.go | 9 +++++++-- config/crd/bases/openstack.k-orc.cloud_limits.yaml | 5 +++++ config/manifests/bases/orc.clusterserviceversion.yaml | 5 +++++ internal/controllers/limit/tests/utils/main.go | 2 +- .../applyconfiguration/api/v1alpha1/limitfilter.go | 9 +++++++++ pkg/clients/applyconfiguration/internal/internal.go | 5 +++-- website/docs/crd-reference.md | 1 + 9 files changed, 38 insertions(+), 7 deletions(-) diff --git a/api/v1alpha1/limit_types.go b/api/v1alpha1/limit_types.go index e241d444f..6f1f9b694 100644 --- a/api/v1alpha1/limit_types.go +++ b/api/v1alpha1/limit_types.go @@ -29,7 +29,7 @@ type LimitResourceSpec struct { // serviceRef is a reference to the ORC Service which this resource is associated with. // +required // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="serviceRef is immutable" - ServiceRef KubernetesNameRef `json:"serviceRef"` + ServiceRef KubernetesNameRef `json:"serviceRef,omitempty"` // projectRef is a reference to the ORC Project which this resource is associated with. // Either Domain ID or Project ID must be provided. @@ -51,7 +51,7 @@ type LimitResourceSpec struct { // +kubebuilder:validation:Pattern=`^[\S]+$` // +required // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="resourceName is immutable" - ResourceName string `json:"resourceName"` + ResourceName string `json:"resourceName,omitempty"` // resourceLimit is the override value of the limit. // +kubebuilder:validation:Minimum=-1 diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index ca1746193..4d612438f 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -2926,6 +2926,11 @@ func (in *Limit) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *LimitFilter) DeepCopyInto(out *LimitFilter) { *out = *in + if in.Description != nil { + in, out := &in.Description, &out.Description + *out = new(string) + **out = **in + } if in.ServiceRef != nil { in, out := &in.ServiceRef, &out.ServiceRef *out = new(KubernetesNameRef) diff --git a/cmd/models-schema/zz_generated.openapi.go b/cmd/models-schema/zz_generated.openapi.go index e08c1b73d..4186112d5 100644 --- a/cmd/models-schema/zz_generated.openapi.go +++ b/cmd/models-schema/zz_generated.openapi.go @@ -5414,6 +5414,13 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_LimitFilter(ref common Description: "LimitFilter defines an existing resource by its properties", Type: []string{"object"}, Properties: map[string]spec.Schema{ + "description": { + SchemaProps: spec.SchemaProps{ + Description: "description of the existing resource", + Type: []string{"string"}, + Format: "", + }, + }, "serviceRef": { SchemaProps: spec.SchemaProps{ Description: "serviceRef is a reference to the ORC Service which this resource is associated with.", @@ -5544,7 +5551,6 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_LimitResourceSpec(ref "serviceRef": { SchemaProps: spec.SchemaProps{ Description: "serviceRef is a reference to the ORC Service which this resource is associated with.", - Default: "", Type: []string{"string"}, Format: "", }, @@ -5566,7 +5572,6 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_LimitResourceSpec(ref "resourceName": { SchemaProps: spec.SchemaProps{ Description: "resourceName is the name of the resource this limit is associated with.", - Default: "", Type: []string{"string"}, Format: "", }, diff --git a/config/crd/bases/openstack.k-orc.cloud_limits.yaml b/config/crd/bases/openstack.k-orc.cloud_limits.yaml index a80b9d723..9ce13acb1 100644 --- a/config/crd/bases/openstack.k-orc.cloud_limits.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_limits.yaml @@ -103,6 +103,11 @@ spec: error state and will not continue to retry. minProperties: 1 properties: + description: + description: description of the existing resource + maxLength: 255 + minLength: 1 + type: string domainRef: description: domainRef is a reference to the ORC Domain which this resource is associated with. diff --git a/config/manifests/bases/orc.clusterserviceversion.yaml b/config/manifests/bases/orc.clusterserviceversion.yaml index 410c09b66..93f793a9e 100644 --- a/config/manifests/bases/orc.clusterserviceversion.yaml +++ b/config/manifests/bases/orc.clusterserviceversion.yaml @@ -64,6 +64,11 @@ spec: kind: KeyPair name: keypairs.openstack.k-orc.cloud version: v1alpha1 + - description: Limit is the Schema for an ORC resource. + displayName: Limit + kind: Limit + name: limits.openstack.k-orc.cloud + version: v1alpha1 - description: Network is the Schema for an ORC resource. displayName: Network kind: Network diff --git a/internal/controllers/limit/tests/utils/main.go b/internal/controllers/limit/tests/utils/main.go index 4a5b3c35e..7de6fdeea 100644 --- a/internal/controllers/limit/tests/utils/main.go +++ b/internal/controllers/limit/tests/utils/main.go @@ -60,7 +60,7 @@ func main() { if err != nil { panic(err) } - defer f.Close() + defer f.Close() //nolint:errcheck slog.SetDefault(slog.New(slog.NewTextHandler(io.MultiWriter(os.Stderr, f), nil)).With("podName", podName)) diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/limitfilter.go b/pkg/clients/applyconfiguration/api/v1alpha1/limitfilter.go index 8241a87e2..8631b5024 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/limitfilter.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/limitfilter.go @@ -25,6 +25,7 @@ import ( // LimitFilterApplyConfiguration represents a declarative configuration of the LimitFilter type for use // with apply. type LimitFilterApplyConfiguration struct { + Description *string `json:"description,omitempty"` ServiceRef *apiv1alpha1.KubernetesNameRef `json:"serviceRef,omitempty"` ProjectRef *apiv1alpha1.KubernetesNameRef `json:"projectRef,omitempty"` DomainRef *apiv1alpha1.KubernetesNameRef `json:"domainRef,omitempty"` @@ -37,6 +38,14 @@ func LimitFilter() *LimitFilterApplyConfiguration { return &LimitFilterApplyConfiguration{} } +// WithDescription sets the Description field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Description field is set to the value of the last call. +func (b *LimitFilterApplyConfiguration) WithDescription(value string) *LimitFilterApplyConfiguration { + b.Description = &value + return b +} + // WithServiceRef sets the ServiceRef field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. // If called multiple times, the ServiceRef field is set to the value of the last call. diff --git a/pkg/clients/applyconfiguration/internal/internal.go b/pkg/clients/applyconfiguration/internal/internal.go index aa7197ad7..5e6dd638c 100644 --- a/pkg/clients/applyconfiguration/internal/internal.go +++ b/pkg/clients/applyconfiguration/internal/internal.go @@ -1529,6 +1529,9 @@ var schemaYAML = typed.YAMLObject(`types: - name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.LimitFilter map: fields: + - name: description + type: + scalar: string - name: domainRef type: scalar: string @@ -1569,11 +1572,9 @@ var schemaYAML = typed.YAMLObject(`types: - name: resourceName type: scalar: string - default: "" - name: serviceRef type: scalar: string - default: "" - name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.LimitResourceStatus map: fields: diff --git a/website/docs/crd-reference.md b/website/docs/crd-reference.md index a41d7dcf3..7e0c52325 100644 --- a/website/docs/crd-reference.md +++ b/website/docs/crd-reference.md @@ -2280,6 +2280,7 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | +| `description` _string_ | description of the existing resource | | MaxLength: 255
MinLength: 1
Optional: \{\}
| | `serviceRef` _[KubernetesNameRef](#kubernetesnameref)_ | serviceRef is a reference to the ORC Service which this resource is associated with. | | MaxLength: 253
MinLength: 1
Optional: \{\}
| | `projectRef` _[KubernetesNameRef](#kubernetesnameref)_ | projectRef is a reference to the ORC Project which this resource is associated with. | | MaxLength: 253
MinLength: 1
Optional: \{\}
| | `domainRef` _[KubernetesNameRef](#kubernetesnameref)_ | domainRef is a reference to the ORC Domain which this resource is associated with. | | MaxLength: 253
MinLength: 1
Optional: \{\}
| From 9b0a8e9168f4a4666d019c4f882cb69d368f2f9c Mon Sep 17 00:00:00 2001 From: Winston Chen Date: Wed, 29 Jul 2026 14:47:00 +0100 Subject: [PATCH 11/13] Address PR review comments Fix parameters in dependencies in Limit actuator Fix default name in test utils --- internal/controllers/limit/actuator.go | 14 +++++++------- internal/controllers/limit/tests/utils/main.go | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/internal/controllers/limit/actuator.go b/internal/controllers/limit/actuator.go index 633439021..c3e5ac4c6 100644 --- a/internal/controllers/limit/actuator.go +++ b/internal/controllers/limit/actuator.go @@ -85,23 +85,23 @@ func (actuator limitActuator) ListOSResourcesForAdoption(ctx context.Context, or return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil }, ) - rs.WithReconcileStatus(rs1) + rs = rs.WithReconcileStatus(rs1) project, rs1 := dependency.FetchDependency( - ctx, actuator.k8sClient, orcObject.Namespace, &resourceSpec.ServiceRef, "Project", - func(dep *orcv1alpha1.Service) bool { + ctx, actuator.k8sClient, orcObject.Namespace, resourceSpec.ProjectRef, "Project", + func(dep *orcv1alpha1.Project) bool { return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil }, ) - rs.WithReconcileStatus(rs1) + rs = rs.WithReconcileStatus(rs1) domain, rs1 := dependency.FetchDependency( - ctx, actuator.k8sClient, orcObject.Namespace, &resourceSpec.ServiceRef, "Domain", - func(dep *orcv1alpha1.Service) bool { + ctx, actuator.k8sClient, orcObject.Namespace, resourceSpec.DomainRef, "Domain", + func(dep *orcv1alpha1.Domain) bool { return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil }, ) - rs.WithReconcileStatus(rs1) + rs = rs.WithReconcileStatus(rs1) if needsReschedule, err := rs.NeedsReschedule(); needsReschedule { if err != nil { diff --git a/internal/controllers/limit/tests/utils/main.go b/internal/controllers/limit/tests/utils/main.go index 7de6fdeea..c1361461b 100644 --- a/internal/controllers/limit/tests/utils/main.go +++ b/internal/controllers/limit/tests/utils/main.go @@ -42,7 +42,7 @@ func main() { podName := os.Getenv("POD_NAME") if podName == "" { - namespace = "default" + podName = "default" } testCase := os.Getenv("TEST_CASE") From b41c2e44f07f9ec63ea88b074b78225b7bb582e7 Mon Sep 17 00:00:00 2001 From: Winston Chen Date: Wed, 29 Jul 2026 12:01:13 +0100 Subject: [PATCH 12/13] Add more checks in e2e assertion Debug CI e2e failures --- .github/workflows/e2e.yaml | 2 +- internal/controllers/limit/actuator.go | 35 +++++++- .../tests/limit-create-full/00-assert.yaml | 45 ++++++++++ .../tests/limit-create-minimal/00-assert.yaml | 47 +++++++++++ .../tests/limit-dependency/01-assert.yaml | 51 ++++++++++++ .../limit-import-dependency/00-assert.yaml | 30 +++++++ .../limit-import-dependency/01-assert.yaml | 82 +++++++++++++++---- .../tests/limit-import-error/00-assert.yaml | 62 ++++++++++++++ .../limit/tests/limit-import/00-assert.yaml | 56 +++++++++++++ .../limit/tests/limit-update/00-assert.yaml | 70 ++++++++++++++-- 10 files changed, 449 insertions(+), 31 deletions(-) diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index 2922eacdf..8c4262995 100644 --- a/.github/workflows/e2e.yaml +++ b/.github/workflows/e2e.yaml @@ -64,7 +64,7 @@ jobs: - name: Run e2e tests run: | - make test-e2e + E2E_KUTTL_DIR=./internal/controllers/limit/tests make test-e2e make test-examples - name: Generate logs on failure diff --git a/internal/controllers/limit/actuator.go b/internal/controllers/limit/actuator.go index c3e5ac4c6..81514bd0f 100644 --- a/internal/controllers/limit/actuator.go +++ b/internal/controllers/limit/actuator.go @@ -180,11 +180,38 @@ func (actuator limitActuator) ListOSResourcesForImport(ctx context.Context, obj } func (actuator limitActuator) listOSResources(ctx context.Context, filters []osclients.ResourceFilter[osResourceT], listOpts limits.ListOptsBuilder) iter.Seq2[*limits.Limit, error] { - registeredLimits := actuator.osClient.ListLimits(ctx, listOpts) - return osclients.Filter(registeredLimits, filters...) + logger := ctrl.LoggerFrom(ctx) + logger.V(logging.Debug).Info("list option", "listOpts", listOpts) + resultLimits := actuator.osClient.ListLimits(ctx, listOpts) + + t := []struct { + l *limits.Limit + err error + }{} + + // For debugging CI issue + for l, err := range resultLimits { + logger.V(logging.Debug).Info("dump limit", "limit", l, "error", err) + t = append(t, struct { + l *limits.Limit + err error + }{ + l, err, + }) + } + + return osclients.Filter(func(yield func(*limits.Limit, error) bool) { + for _, m := range t { + if !yield(m.l, m.err) { + return + } + } + + }, filters...) } func (actuator limitActuator) CreateResource(ctx context.Context, obj orcObjectPT) (*osResourceT, progress.ReconcileStatus) { + logger := ctrl.LoggerFrom(ctx).WithValues("limitName", obj.Name) resource := obj.Spec.Resource if resource == nil { @@ -226,7 +253,7 @@ func (actuator limitActuator) CreateResource(ctx context.Context, obj orcObjectP } if needsReschedule, err := reconcileStatus.NeedsReschedule(); needsReschedule { if err != nil { - ctrl.LoggerFrom(ctx).Info("fetch dependency before creating limit", "error", err) + logger.Info("fetch dependency before creating limit", "error", err) } return nil, reconcileStatus @@ -248,6 +275,8 @@ func (actuator limitActuator) CreateResource(ctx context.Context, obj orcObjectP return nil, progress.WrapError(err) } + logger.Info("limit created", "createOpts", createOpts) + return osResource, nil } diff --git a/internal/controllers/limit/tests/limit-create-full/00-assert.yaml b/internal/controllers/limit/tests/limit-create-full/00-assert.yaml index 752e0b4ae..edcab9759 100644 --- a/internal/controllers/limit/tests/limit-create-full/00-assert.yaml +++ b/internal/controllers/limit/tests/limit-create-full/00-assert.yaml @@ -6,6 +6,43 @@ status: readyReplicas: 1 --- apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Service +status: + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Project +metadata: + name: project-limit-create-full +status: + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Domain +metadata: + name: domain-limit-create-full +status: + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Limit metadata: name: limit-create-full-1 @@ -41,6 +78,14 @@ status: --- apiVersion: kuttl.dev/v1beta1 kind: TestAssert +collectors: + - selector: app=setup-teardown +commands: + - script: |- + cd $(dirname ${E2E_KUTTL_OSCLOUDS}) + export OS_CLOUD=openstack-admin + openstack registered limit list --resource-name limit-create-full-1 && + openstack registered limit list --resource-name limit-create-full-2 resourceRefs: - apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Limit diff --git a/internal/controllers/limit/tests/limit-create-minimal/00-assert.yaml b/internal/controllers/limit/tests/limit-create-minimal/00-assert.yaml index 895eda1f8..b600c76bb 100644 --- a/internal/controllers/limit/tests/limit-create-minimal/00-assert.yaml +++ b/internal/controllers/limit/tests/limit-create-minimal/00-assert.yaml @@ -6,6 +6,45 @@ status: readyReplicas: 1 --- apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Service +metadata: + name: service-limit-create-minimal +status: + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Project +metadata: + name: project-limit-create-minimal +status: + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Domain +metadata: + name: domain-limit-create-minimal +status: + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Limit metadata: name: limit-create-minimal-1 @@ -39,6 +78,14 @@ status: --- apiVersion: kuttl.dev/v1beta1 kind: TestAssert +collectors: + - selector: app=setup-teardown +commands: + - script: |- + cd $(dirname ${E2E_KUTTL_OSCLOUDS}) + export OS_CLOUD=openstack-admin + openstack registered limit list --resource-name limit-create-minimal-1 && + openstack registered limit list --resource-name limit-create-minimal-2 resourceRefs: - apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Limit diff --git a/internal/controllers/limit/tests/limit-dependency/01-assert.yaml b/internal/controllers/limit/tests/limit-dependency/01-assert.yaml index be0a2c099..0db242380 100644 --- a/internal/controllers/limit/tests/limit-dependency/01-assert.yaml +++ b/internal/controllers/limit/tests/limit-dependency/01-assert.yaml @@ -6,6 +6,45 @@ status: readyReplicas: 1 --- apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Service +metadata: + name: service-limit-dependency +status: + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Project +metadata: + name: project-limit-dependency +status: + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Domain +metadata: + name: domain-limit-dependency +status: + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Limit metadata: name: limit-dependency-1 @@ -49,3 +88,15 @@ status: message: OpenStack resource is up to date status: "False" reason: Success +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +collectors: + - selector: app=setup-teardown +commands: + - script: |- + cd $(dirname ${E2E_KUTTL_OSCLOUDS}) + export OS_CLOUD=openstack-admin + openstack registered limit list --resource-name limit-dependency-1 && + openstack registered limit list --resource-name limit-dependency-2 && + openstack registered limit list --resource-name limit-dependency-3 diff --git a/internal/controllers/limit/tests/limit-import-dependency/00-assert.yaml b/internal/controllers/limit/tests/limit-import-dependency/00-assert.yaml index a839c939a..b0758f632 100644 --- a/internal/controllers/limit/tests/limit-import-dependency/00-assert.yaml +++ b/internal/controllers/limit/tests/limit-import-dependency/00-assert.yaml @@ -1,5 +1,35 @@ --- apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Service +metadata: + name: service-limit-import-dependency +status: + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Project +metadata: + name: project-limit-import-dependency-imported +status: + conditions: + - type: Available + message: |- + Waiting for OpenStack resource to be created externally + status: "False" + reason: Progressing + - type: Progressing + message: |- + Waiting for OpenStack resource to be created externally + status: "True" + reason: Progressing +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Limit metadata: name: limit-import-dependency diff --git a/internal/controllers/limit/tests/limit-import-dependency/01-assert.yaml b/internal/controllers/limit/tests/limit-import-dependency/01-assert.yaml index 138fd32ef..f8a8c3231 100644 --- a/internal/controllers/limit/tests/limit-import-dependency/01-assert.yaml +++ b/internal/controllers/limit/tests/limit-import-dependency/01-assert.yaml @@ -5,24 +5,44 @@ metadata: status: readyReplicas: 1 --- -apiVersion: kuttl.dev/v1beta1 -kind: TestAssert -resourceRefs: - - apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: Limit - name: limit-import-dependency - ref: limit1 - - apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: Limit - name: limit-import-dependency-external - ref: limit2 - - apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: Project - name: project-limit-import-dependency - ref: project -assertAll: - - celExpr: "limit1.status.id == limit2.status.id" - - celExpr: "limit1.status.resource.projectID == project.status.id" +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Domain +metadata: + name: domain-limit-import-dependency +status: + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Project +metadata: + name: project-limit-import-dependency +status: + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Limit +metadata: + name: limit-import-dependency-external +status: + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Limit @@ -41,3 +61,29 @@ status: message: OpenStack resource is up to date status: "False" reason: Success +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +collectors: + - selector: app=setup-teardown +commands: + - script: |- + cd $(dirname ${E2E_KUTTL_OSCLOUDS}) + export OS_CLOUD=openstack-admin + openstack registered limit list --resource-name limit-import-dependency +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Limit + name: limit-import-dependency + ref: limit1 + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Limit + name: limit-import-dependency-external + ref: limit2 + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Project + name: project-limit-import-dependency + ref: project +assertAll: + - celExpr: "limit1.status.id == limit2.status.id" + - celExpr: "limit1.status.resource.projectID == project.status.id" diff --git a/internal/controllers/limit/tests/limit-import-error/00-assert.yaml b/internal/controllers/limit/tests/limit-import-error/00-assert.yaml index a1fd0295c..69ddc5942 100644 --- a/internal/controllers/limit/tests/limit-import-error/00-assert.yaml +++ b/internal/controllers/limit/tests/limit-import-error/00-assert.yaml @@ -6,6 +6,58 @@ status: readyReplicas: 1 --- apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Service +metadata: + name: service-limit-import-error +status: + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Domain +metadata: + name: domain-limit-import-error +status: + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Project +metadata: + name: project-limit-import-error-1 +status: + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Project +metadata: + name: project-limit-import-error-2 +status: + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Limit metadata: name: limit-import-error-1 @@ -34,3 +86,13 @@ status: message: OpenStack resource is up to date status: "False" reason: Success +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +collectors: + - selector: app=setup-teardown +commands: + - script: |- + cd $(dirname ${E2E_KUTTL_OSCLOUDS}) + export OS_CLOUD=openstack-admin + openstack registered limit list --resource-name limit-import-error diff --git a/internal/controllers/limit/tests/limit-import/00-assert.yaml b/internal/controllers/limit/tests/limit-import/00-assert.yaml index 0b1056903..27d6dacd5 100644 --- a/internal/controllers/limit/tests/limit-import/00-assert.yaml +++ b/internal/controllers/limit/tests/limit-import/00-assert.yaml @@ -1,3 +1,48 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: setup-teardown +status: + readyReplicas: 1 +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Service +metadata: + name: service-limit-import +status: + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Project +metadata: + name: project-limit-import +status: + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Domain +metadata: + name: domain-limit-import +status: + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Limit @@ -13,3 +58,14 @@ status: message: Waiting for OpenStack resource to be created externally status: "True" reason: Progressing +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +collectors: + - selector: app=setup-teardown +commands: + - script: |- + cd $(dirname ${E2E_KUTTL_OSCLOUDS}) + export OS_CLOUD=openstack-admin + openstack registered limit list --resource-name limit-import && + openstack registered limit list --resource-name limit-import-nonexistent-resource diff --git a/internal/controllers/limit/tests/limit-update/00-assert.yaml b/internal/controllers/limit/tests/limit-update/00-assert.yaml index 53135373f..10eacfb0a 100644 --- a/internal/controllers/limit/tests/limit-update/00-assert.yaml +++ b/internal/controllers/limit/tests/limit-update/00-assert.yaml @@ -1,13 +1,48 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: setup-teardown +status: + readyReplicas: 1 --- -apiVersion: kuttl.dev/v1beta1 -kind: TestAssert -resourceRefs: - - apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: Limit - name: limit-update - ref: limit -assertAll: - - celExpr: "limit.status.id != ''" +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Service +metadata: + name: service-limit-update +status: + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Project +metadata: + name: project-limit-update +status: + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Domain +metadata: + name: domain-limit-update +status: + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Limit @@ -25,3 +60,20 @@ status: - type: Progressing status: "False" reason: Success +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +collectors: + - selector: app=setup-teardown +commands: + - script: |- + cd $(dirname ${E2E_KUTTL_OSCLOUDS}) + export OS_CLOUD=openstack-admin + openstack registered limit list --resource-name limit-update +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Limit + name: limit-update + ref: limit +assertAll: + - celExpr: "limit.status.id != ''" From 3540f8b9dd236ea35fbe8bb9d0c9df7104e159cb Mon Sep 17 00:00:00 2001 From: Winston Chen Date: Wed, 29 Jul 2026 17:26:51 +0100 Subject: [PATCH 13/13] Use prebuilt image to reduce start time of setup pod --- .../limit-create-full/00-setup-cleanup.yaml | 13 ++----- .../00-setup-cleanup.yaml | 13 ++----- .../limit-dependency/01-setup-cleanup.yaml | 13 ++----- .../tests/limit-dependency/02-assert.yaml | 35 ++++-------------- .../limit-dependency/02-disable-domain.yaml | 6 ++++ .../tests/limit-dependency/03-assert.yaml | 36 +++++++++++++------ ...ncies.yaml => 03-delete-dependencies.yaml} | 2 -- .../tests/limit-dependency/04-assert.yaml | 13 +++++++ ...esources.yaml => 04-delete-resources.yaml} | 0 .../limit/tests/limit-dependency/README.md | 6 +++- .../01-setup-cleanup.yaml | 13 ++----- .../limit-import-error/00-setup-cleanup.yaml | 13 ++----- .../tests/limit-import/00-setup-cleanup.yaml | 13 ++----- .../tests/limit-update/00-setup-cleanup.yaml | 13 ++----- 14 files changed, 71 insertions(+), 118 deletions(-) create mode 100644 internal/controllers/limit/tests/limit-dependency/02-disable-domain.yaml rename internal/controllers/limit/tests/limit-dependency/{02-delete-dependencies.yaml => 03-delete-dependencies.yaml} (56%) create mode 100644 internal/controllers/limit/tests/limit-dependency/04-assert.yaml rename internal/controllers/limit/tests/limit-dependency/{03-delete-resources.yaml => 04-delete-resources.yaml} (100%) diff --git a/internal/controllers/limit/tests/limit-create-full/00-setup-cleanup.yaml b/internal/controllers/limit/tests/limit-create-full/00-setup-cleanup.yaml index de40c99cd..9a94d37c9 100644 --- a/internal/controllers/limit/tests/limit-create-full/00-setup-cleanup.yaml +++ b/internal/controllers/limit/tests/limit-create-full/00-setup-cleanup.yaml @@ -16,18 +16,9 @@ spec: spec: containers: - name: setup-teardown - image: golang:alpine + image: ghcr.io/chenwng/orc-helper:latest command: - - sh - - -c - - | - mkdir /test-helper && - cp /app/* /test-helper && - cd /test-helper; - go mod init test-helper && - go mod tidy && - go build -o ./test-helper . && - exec ./test-helper + - /orc-helper volumeMounts: - name: openstack-clouds mountPath: /etc/openstack diff --git a/internal/controllers/limit/tests/limit-create-minimal/00-setup-cleanup.yaml b/internal/controllers/limit/tests/limit-create-minimal/00-setup-cleanup.yaml index 8fb51adeb..6e9f85959 100644 --- a/internal/controllers/limit/tests/limit-create-minimal/00-setup-cleanup.yaml +++ b/internal/controllers/limit/tests/limit-create-minimal/00-setup-cleanup.yaml @@ -16,18 +16,9 @@ spec: spec: containers: - name: setup-teardown - image: golang:alpine + image: ghcr.io/chenwng/orc-helper:latest command: - - sh - - -c - - | - mkdir /test-helper && - cp /app/* /test-helper && - cd /test-helper; - go mod init test-helper && - go mod tidy && - go build -o ./test-helper . && - exec ./test-helper + - /orc-helper volumeMounts: - name: openstack-clouds mountPath: /etc/openstack diff --git a/internal/controllers/limit/tests/limit-dependency/01-setup-cleanup.yaml b/internal/controllers/limit/tests/limit-dependency/01-setup-cleanup.yaml index dde27d4e2..e64093bda 100644 --- a/internal/controllers/limit/tests/limit-dependency/01-setup-cleanup.yaml +++ b/internal/controllers/limit/tests/limit-dependency/01-setup-cleanup.yaml @@ -16,18 +16,9 @@ spec: spec: containers: - name: setup-teardown - image: golang:alpine + image: ghcr.io/chenwng/orc-helper:latest command: - - sh - - -c - - | - mkdir /test-helper && - cp /app/* /test-helper && - cd /test-helper; - go mod init test-helper && - go mod tidy && - go build -o ./test-helper . && - exec ./test-helper + - /orc-helper volumeMounts: - name: openstack-clouds mountPath: /etc/openstack diff --git a/internal/controllers/limit/tests/limit-dependency/02-assert.yaml b/internal/controllers/limit/tests/limit-dependency/02-assert.yaml index 3173eed73..8ebf5f754 100644 --- a/internal/controllers/limit/tests/limit-dependency/02-assert.yaml +++ b/internal/controllers/limit/tests/limit-dependency/02-assert.yaml @@ -1,29 +1,8 @@ --- -apiVersion: kuttl.dev/v1beta1 -kind: TestAssert -resourceRefs: - - apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: Service - name: service-limit-dependency - ref: service - - apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: Project - name: project-limit-dependency - ref: project - - apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: Domain - name: domain-limit-dependency - ref: domain - - apiVersion: v1 - kind: Secret - name: limit-dependency - ref: secret -assertAll: - - celExpr: "service.metadata.deletionTimestamp != 0" - - celExpr: "'openstack.k-orc.cloud/limit' in service.metadata.finalizers" - - celExpr: "project.metadata.deletionTimestamp != 0" - - celExpr: "'openstack.k-orc.cloud/limit' in project.metadata.finalizers" - - celExpr: "domain.metadata.deletionTimestamp != 0" - - celExpr: "'openstack.k-orc.cloud/limit' in domain.metadata.finalizers" - - celExpr: "secret.metadata.deletionTimestamp != 0" - - celExpr: "'openstack.k-orc.cloud/limit' in secret.metadata.finalizers" +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Domain +metadata: + name: domain-limit-dependency +status: + resource: + enabled: false diff --git a/internal/controllers/limit/tests/limit-dependency/02-disable-domain.yaml b/internal/controllers/limit/tests/limit-dependency/02-disable-domain.yaml new file mode 100644 index 000000000..1ca9a5f9a --- /dev/null +++ b/internal/controllers/limit/tests/limit-dependency/02-disable-domain.yaml @@ -0,0 +1,6 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + - command: kubectl patch domain domain-limit-dependency -p '{"spec":{"resource":{"enabled":false}}}' --type=merge + namespaced: true diff --git a/internal/controllers/limit/tests/limit-dependency/03-assert.yaml b/internal/controllers/limit/tests/limit-dependency/03-assert.yaml index 38b2770fe..3173eed73 100644 --- a/internal/controllers/limit/tests/limit-dependency/03-assert.yaml +++ b/internal/controllers/limit/tests/limit-dependency/03-assert.yaml @@ -1,13 +1,29 @@ --- apiVersion: kuttl.dev/v1beta1 kind: TestAssert -commands: - # Dependencies that were prevented deletion before should now be gone - - script: "! kubectl get service.openstack.k-orc.cloud service-limit-dependency --namespace $NAMESPACE" - skipLogOutput: true - - script: "! kubectl get project.openstack.k-orc.cloud project-limit-dependency --namespace $NAMESPACE" - skipLogOutput: true - - script: "! kubectl get domain.openstack.k-orc.cloud domain-limit-dependency --namespace $NAMESPACE" - skipLogOutput: true - - script: "! kubectl get secret limit-dependency --namespace $NAMESPACE" - skipLogOutput: true +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Service + name: service-limit-dependency + ref: service + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Project + name: project-limit-dependency + ref: project + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Domain + name: domain-limit-dependency + ref: domain + - apiVersion: v1 + kind: Secret + name: limit-dependency + ref: secret +assertAll: + - celExpr: "service.metadata.deletionTimestamp != 0" + - celExpr: "'openstack.k-orc.cloud/limit' in service.metadata.finalizers" + - celExpr: "project.metadata.deletionTimestamp != 0" + - celExpr: "'openstack.k-orc.cloud/limit' in project.metadata.finalizers" + - celExpr: "domain.metadata.deletionTimestamp != 0" + - celExpr: "'openstack.k-orc.cloud/limit' in domain.metadata.finalizers" + - celExpr: "secret.metadata.deletionTimestamp != 0" + - celExpr: "'openstack.k-orc.cloud/limit' in secret.metadata.finalizers" diff --git a/internal/controllers/limit/tests/limit-dependency/02-delete-dependencies.yaml b/internal/controllers/limit/tests/limit-dependency/03-delete-dependencies.yaml similarity index 56% rename from internal/controllers/limit/tests/limit-dependency/02-delete-dependencies.yaml rename to internal/controllers/limit/tests/limit-dependency/03-delete-dependencies.yaml index a943fd76d..68870a152 100644 --- a/internal/controllers/limit/tests/limit-dependency/02-delete-dependencies.yaml +++ b/internal/controllers/limit/tests/limit-dependency/03-delete-dependencies.yaml @@ -2,8 +2,6 @@ apiVersion: kuttl.dev/v1beta1 kind: TestStep commands: - # Sleep 1 sec to avoid race condition that the controller only reconciles on the deletion operation due to the async processing and cache - - script: kubectl patch domain domain-limit-dependency -p '{"spec":{"resource":{"enabled":false}}}' --type=merge -n $NAMESPACE && sleep 1 # We expect the deletion to hang due to the finalizer, so use --wait=false - command: kubectl delete service.openstack.k-orc.cloud,project,domain -l test-case=limit-dependency --wait=false namespaced: true diff --git a/internal/controllers/limit/tests/limit-dependency/04-assert.yaml b/internal/controllers/limit/tests/limit-dependency/04-assert.yaml new file mode 100644 index 000000000..38b2770fe --- /dev/null +++ b/internal/controllers/limit/tests/limit-dependency/04-assert.yaml @@ -0,0 +1,13 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +commands: + # Dependencies that were prevented deletion before should now be gone + - script: "! kubectl get service.openstack.k-orc.cloud service-limit-dependency --namespace $NAMESPACE" + skipLogOutput: true + - script: "! kubectl get project.openstack.k-orc.cloud project-limit-dependency --namespace $NAMESPACE" + skipLogOutput: true + - script: "! kubectl get domain.openstack.k-orc.cloud domain-limit-dependency --namespace $NAMESPACE" + skipLogOutput: true + - script: "! kubectl get secret limit-dependency --namespace $NAMESPACE" + skipLogOutput: true diff --git a/internal/controllers/limit/tests/limit-dependency/03-delete-resources.yaml b/internal/controllers/limit/tests/limit-dependency/04-delete-resources.yaml similarity index 100% rename from internal/controllers/limit/tests/limit-dependency/03-delete-resources.yaml rename to internal/controllers/limit/tests/limit-dependency/04-delete-resources.yaml diff --git a/internal/controllers/limit/tests/limit-dependency/README.md b/internal/controllers/limit/tests/limit-dependency/README.md index 457b31af1..58a4efa03 100644 --- a/internal/controllers/limit/tests/limit-dependency/README.md +++ b/internal/controllers/limit/tests/limit-dependency/README.md @@ -10,8 +10,12 @@ Create the missing dependencies and verify all the Limits are available. ## Step 02 -Delete all the dependencies and check that ORC prevents deletion since there is still a resource that depends on them. +Disable domain to facilitate the deletion. ## Step 03 +Delete all the dependencies and check that ORC prevents deletion since there is still a resource that depends on them. + +## Step 04 + Delete the Limits and validate that all resources are gone. diff --git a/internal/controllers/limit/tests/limit-import-dependency/01-setup-cleanup.yaml b/internal/controllers/limit/tests/limit-import-dependency/01-setup-cleanup.yaml index ecb3a2c3b..126cdb01f 100644 --- a/internal/controllers/limit/tests/limit-import-dependency/01-setup-cleanup.yaml +++ b/internal/controllers/limit/tests/limit-import-dependency/01-setup-cleanup.yaml @@ -23,18 +23,9 @@ spec: spec: containers: - name: setup-teardown - image: golang:alpine + image: ghcr.io/chenwng/orc-helper:latest command: - - sh - - -c - - | - mkdir /test-helper && - cp /app/* /test-helper && - cd /test-helper; - go mod init test-helper && - go mod tidy && - go build -o ./test-helper . && - exec ./test-helper + - /orc-helper volumeMounts: - name: openstack-clouds mountPath: /etc/openstack diff --git a/internal/controllers/limit/tests/limit-import-error/00-setup-cleanup.yaml b/internal/controllers/limit/tests/limit-import-error/00-setup-cleanup.yaml index e9d05c6de..2ff526a47 100644 --- a/internal/controllers/limit/tests/limit-import-error/00-setup-cleanup.yaml +++ b/internal/controllers/limit/tests/limit-import-error/00-setup-cleanup.yaml @@ -17,18 +17,9 @@ spec: spec: containers: - name: setup-teardown - image: golang:alpine + image: ghcr.io/chenwng/orc-helper:latest command: - - sh - - -c - - | - mkdir /test-helper && - cp /app/* /test-helper && - cd /test-helper; - go mod init test-helper && - go mod tidy && - go build -o ./test-helper . && - exec ./test-helper + - /orc-helper volumeMounts: - name: openstack-clouds mountPath: /etc/openstack diff --git a/internal/controllers/limit/tests/limit-import/00-setup-cleanup.yaml b/internal/controllers/limit/tests/limit-import/00-setup-cleanup.yaml index 4d1e88f56..d0e47346a 100644 --- a/internal/controllers/limit/tests/limit-import/00-setup-cleanup.yaml +++ b/internal/controllers/limit/tests/limit-import/00-setup-cleanup.yaml @@ -17,18 +17,9 @@ spec: spec: containers: - name: setup-teardown - image: golang:alpine + image: ghcr.io/chenwng/orc-helper:latest command: - - sh - - -c - - | - mkdir /test-helper && - cp /app/* /test-helper && - cd /test-helper; - go mod init test-helper && - go mod tidy && - go build -o ./test-helper . && - exec ./test-helper + - /orc-helper volumeMounts: - name: openstack-clouds mountPath: /etc/openstack diff --git a/internal/controllers/limit/tests/limit-update/00-setup-cleanup.yaml b/internal/controllers/limit/tests/limit-update/00-setup-cleanup.yaml index a956d9afc..a57098454 100644 --- a/internal/controllers/limit/tests/limit-update/00-setup-cleanup.yaml +++ b/internal/controllers/limit/tests/limit-update/00-setup-cleanup.yaml @@ -17,18 +17,9 @@ spec: spec: containers: - name: setup-teardown - image: golang:alpine + image: ghcr.io/chenwng/orc-helper:latest command: - - sh - - -c - - | - mkdir /test-helper && - cp /app/* /test-helper && - cd /test-helper; - go mod init test-helper && - go mod tidy && - go build -o ./test-helper . && - exec ./test-helper + - /orc-helper volumeMounts: - name: openstack-clouds mountPath: /etc/openstack