diff --git a/apis/user/v1alpha1/userqualityofservicelimits_types.go b/apis/user/v1alpha1/userqualityofservicelimits_types.go index cfcd493f..9a444859 100644 --- a/apis/user/v1alpha1/userqualityofservicelimits_types.go +++ b/apis/user/v1alpha1/userqualityofservicelimits_types.go @@ -45,6 +45,12 @@ type UserQualityOfServiceLimitsParameters struct { // UserIDSelector selects a user to retrieve its groupId and userId. // +optional UserIDSelector *xpv1.Selector `json:"userIdSelector,omitempty"` + + // Region in which to apply the quality of service limits. Default region if unspecified. + // +optional + Region string `json:"region,omitempty"` + + QOS `json:",inline"` } // UserQualityOfServiceLimitsObservation are the observable fields of a UserQualityOfServiceLimits. diff --git a/apis/user/v1alpha1/zz_generated.deepcopy.go b/apis/user/v1alpha1/zz_generated.deepcopy.go index 9cf8b81a..f92fa7b6 100644 --- a/apis/user/v1alpha1/zz_generated.deepcopy.go +++ b/apis/user/v1alpha1/zz_generated.deepcopy.go @@ -701,6 +701,7 @@ func (in *UserQualityOfServiceLimitsParameters) DeepCopyInto(out *UserQualityOfS *out = new(v1.Selector) (*in).DeepCopyInto(*out) } + in.QOS.DeepCopyInto(&out.QOS) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UserQualityOfServiceLimitsParameters. diff --git a/internal/controller/userqualityofservicelimits/userqualityofservicelimits.go b/internal/controller/userqualityofservicelimits/userqualityofservicelimits.go index 55c397bf..e606ad8b 100644 --- a/internal/controller/userqualityofservicelimits/userqualityofservicelimits.go +++ b/internal/controller/userqualityofservicelimits/userqualityofservicelimits.go @@ -18,13 +18,13 @@ package userqualityofservicelimits import ( "context" - "fmt" "github.com/pkg/errors" "k8s.io/apimachinery/pkg/types" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" + xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1" "github.com/crossplane/crossplane-runtime/pkg/connection" "github.com/crossplane/crossplane-runtime/pkg/controller" "github.com/crossplane/crossplane-runtime/pkg/event" @@ -34,6 +34,7 @@ import ( "github.com/statnett/provider-cloudian/apis/user/v1alpha1" apisv1alpha1 "github.com/statnett/provider-cloudian/apis/v1alpha1" + qoslimits "github.com/statnett/provider-cloudian/internal/controller/qualityofservicelimits" "github.com/statnett/provider-cloudian/internal/features" "github.com/statnett/provider-cloudian/internal/sdk/cloudian" ) @@ -45,6 +46,9 @@ const ( errGetCreds = "cannot get credentials" errNewClient = "cannot create new Service" + errCreateQOS = "cannot create QOS" + errDeleteQOS = "cannot delete QOS" + errGetQOS = "cannot get QOS" ) var ( @@ -142,8 +146,34 @@ func (c *external) Observe(ctx context.Context, mg resource.Managed) (managed.Ex return managed.ExternalObservation{}, errors.New(errNotUserQualityOfServiceLimits) } - // These fmt statements should be removed in the real implementation. - fmt.Printf("Observing: %+v", cr) + groupID := cr.Spec.ForProvider.GroupID + if groupID == "" { + return managed.ExternalObservation{}, nil + } + userID := cr.Spec.ForProvider.UserID + if userID == "" { + return managed.ExternalObservation{}, nil + } + + user := cloudian.User{ + GroupID: groupID, + UserID: userID, + } + qos, err := c.cloudianService.GetQOS(ctx, user, cr.Spec.ForProvider.Region) + + if errors.Is(err, cloudian.ErrNotFound) { + return managed.ExternalObservation{ResourceExists: false}, nil + } + if err != nil { + return managed.ExternalObservation{}, errors.Wrap(err, errGetQOS) + } + + cr.SetConditions(xpv1.Available()) + + expected, err := qoslimits.ToCloudianQOS(cr.Spec.ForProvider.QOS) + if err != nil { + return managed.ExternalObservation{}, err + } return managed.ExternalObservation{ // Return false when the external resource does not exist. This lets @@ -154,7 +184,8 @@ func (c *external) Observe(ctx context.Context, mg resource.Managed) (managed.Ex // Return false when the external resource exists, but it not up to date // with the desired managed resource state. This lets the managed // resource reconciler know that it needs to call Update. - ResourceUpToDate: true, + ResourceUpToDate: expected.Warning.Equal(qos.Warning) && + expected.Hard.Equal(qos.Hard), // Return any details that may be required to connect to the external // resource. These will be stored as the connection secret. @@ -168,7 +199,18 @@ func (c *external) Create(ctx context.Context, mg resource.Managed) (managed.Ext return managed.ExternalCreation{}, errors.New(errNotUserQualityOfServiceLimits) } - fmt.Printf("Creating: %+v", cr) + qos, err := qoslimits.ToCloudianQOS(cr.Spec.ForProvider.QOS) + if err != nil { + return managed.ExternalCreation{}, err + } + + user := cloudian.User{ + GroupID: cr.Spec.ForProvider.GroupID, + UserID: cr.Spec.ForProvider.UserID, + } + if err := c.cloudianService.SetQOS(ctx, user, cr.Spec.ForProvider.Region, qos); err != nil { + return managed.ExternalCreation{}, errors.Wrap(err, errCreateQOS) + } return managed.ExternalCreation{ // Optionally return any details that may be required to connect to the @@ -183,7 +225,18 @@ func (c *external) Update(ctx context.Context, mg resource.Managed) (managed.Ext return managed.ExternalUpdate{}, errors.New(errNotUserQualityOfServiceLimits) } - fmt.Printf("Updating: %+v", cr) + qos, err := qoslimits.ToCloudianQOS(cr.Spec.ForProvider.QOS) + if err != nil { + return managed.ExternalUpdate{}, err + } + + user := cloudian.User{ + GroupID: cr.Spec.ForProvider.GroupID, + UserID: cr.Spec.ForProvider.UserID, + } + if err := c.cloudianService.SetQOS(ctx, user, cr.Spec.ForProvider.Region, qos); err != nil { + return managed.ExternalUpdate{}, errors.Wrap(err, errCreateQOS) + } return managed.ExternalUpdate{ // Optionally return any details that may be required to connect to the @@ -198,7 +251,16 @@ func (c *external) Delete(ctx context.Context, mg resource.Managed) (managed.Ext return managed.ExternalDelete{}, errors.New(errNotUserQualityOfServiceLimits) } - fmt.Printf("Deleting: %+v", cr) + cr.SetConditions(xpv1.Deleting()) + + user := cloudian.User{ + GroupID: cr.Spec.ForProvider.GroupID, + UserID: cr.Spec.ForProvider.UserID, + } + err := c.cloudianService.DeleteQOS(ctx, user, cr.Spec.ForProvider.Region) + if err != nil && !errors.Is(err, cloudian.ErrNotFound) { + return managed.ExternalDelete{}, errors.Wrap(err, errGetCreds) + } return managed.ExternalDelete{}, nil } diff --git a/package/crds/user.cloudian.crossplane.io_userqualityofservicelimits.yaml b/package/crds/user.cloudian.crossplane.io_userqualityofservicelimits.yaml index 6643943b..012bb29f 100644 --- a/package/crds/user.cloudian.crossplane.io_userqualityofservicelimits.yaml +++ b/package/crds/user.cloudian.crossplane.io_userqualityofservicelimits.yaml @@ -79,6 +79,44 @@ spec: groupId: description: GroupID of the quality of service limits. type: string + hard: + description: Hard is the hard limit. + properties: + inboundBytesPerMin: + description: InboundBytesPerMin is the limit for inbound data + per minute in bytes. + nullable: true + pattern: ^(0|((0|[1-9][0-9]*)[KMGT]i))$ + type: string + outboundBytesPerMin: + description: OutboundKiBsPerMin is the limit for outbound + data per minute in bytes. + nullable: true + pattern: ^(0|((0|[1-9][0-9]*)[KMGT]i))$ + type: string + requestsPerMin: + description: RequestsPerMin is the limit for number of HTTP + requests per minute. + format: int32 + nullable: true + type: integer + storageQuotaBytes: + description: StorageQuotaBytes is the limit for total stored + data in bytes. + nullable: true + pattern: ^(0|((0|[1-9][0-9]*)[KMGT]i))$ + type: string + storageQuotaCount: + description: StorageQuotaCount is the limit for total number + of objects. + format: int32 + nullable: true + type: integer + type: object + region: + description: Region in which to apply the quality of service limits. + Default region if unspecified. + type: string userId: description: UserID of the quality of service limits. type: string @@ -158,6 +196,40 @@ spec: type: string type: object type: object + warning: + description: Warning is the soft limit that triggers a warning. + properties: + inboundBytesPerMin: + description: InboundBytesPerMin is the limit for inbound data + per minute in bytes. + nullable: true + pattern: ^(0|((0|[1-9][0-9]*)[KMGT]i))$ + type: string + outboundBytesPerMin: + description: OutboundKiBsPerMin is the limit for outbound + data per minute in bytes. + nullable: true + pattern: ^(0|((0|[1-9][0-9]*)[KMGT]i))$ + type: string + requestsPerMin: + description: RequestsPerMin is the limit for number of HTTP + requests per minute. + format: int32 + nullable: true + type: integer + storageQuotaBytes: + description: StorageQuotaBytes is the limit for total stored + data in bytes. + nullable: true + pattern: ^(0|((0|[1-9][0-9]*)[KMGT]i))$ + type: string + storageQuotaCount: + description: StorageQuotaCount is the limit for total number + of objects. + format: int32 + nullable: true + type: integer + type: object type: object managementPolicies: default: