From 821e875801d9f03dbb872f8c38fab9a726dfcbc2 Mon Sep 17 00:00:00 2001 From: Maslow Date: Tue, 27 Sep 2022 23:15:20 +0800 Subject: [PATCH] feat(app): impl main login of app controller (#350) * impl app controller * feat(app): impl main logic of app controller Signed-off-by: maslow --- controllers/application/README.md | 109 ++---- .../application/api/v1/application_types.go | 65 ++-- .../application/api/v1/creationform_types.go | 8 +- .../api/v1/zz_generated.deepcopy.go | 4 +- .../application.laf.dev_applications.yaml | 329 ++++++++---------- .../application.laf.dev_creationforms.yaml | 8 +- .../controllers/application_controller.go | 327 ++++++++++++++--- .../controllers/creationform_controller.go | 1 + controllers/application/main.go | 9 + controllers/application/resourcer/client.go | 20 ++ controllers/application/resourcer/database.go | 26 ++ controllers/application/resourcer/gateway.go | 1 + controllers/application/resourcer/instance.go | 1 + controllers/application/resourcer/oss.go | 1 + controllers/application/tests/api/app.go | 4 +- .../application/tests/api/creation_form.go | 4 +- .../application/tests/e2e/app_create_test.go | 35 +- .../gateway/controllers/gateway_controller.go | 2 +- .../oss/controllers/bucket_controller.go | 2 +- .../oss/controllers/store_controller.go | 4 +- .../oss/controllers/user_controller.go | 2 +- controllers/oss/tests/e2e/user_create_test.go | 2 +- pkg/common/random.go | 24 ++ pkg/util/conditions.go | 31 +- 24 files changed, 665 insertions(+), 354 deletions(-) create mode 100644 controllers/application/resourcer/client.go create mode 100644 controllers/application/resourcer/database.go create mode 100644 controllers/application/resourcer/gateway.go create mode 100644 controllers/application/resourcer/instance.go create mode 100644 controllers/application/resourcer/oss.go create mode 100644 pkg/common/random.go diff --git a/controllers/application/README.md b/controllers/application/README.md index 8a40c0af56..e14cca20be 100644 --- a/controllers/application/README.md +++ b/controllers/application/README.md @@ -1,94 +1,57 @@ -# application -// TODO(user): Add simple overview of use/purpose -## Description -// TODO(user): An in-depth paragraph about your project and overview of use -## Getting Started -You’ll need a Kubernetes cluster to run against. You can use [KIND](https://sigs.k8s.io/kind) to get a local cluster for testing, or run against a remote cluster. -**Note:** Your controller will automatically use the current context in your kubeconfig file (i.e. whatever cluster `kubectl cluster-info` shows). -### Running on the cluster -1. Install Instances of Custom Resources: +## Local development -```sh -kubectl apply -f config/samples/ -``` +### 0. start a k8s cluster -2. Build and push your image to the location specified by `IMG`: - -```sh -make docker-build docker-push IMG=/application:tag +```bash +# in laf project root path +sh scripts/start_vm.sh laf-test ~/.kube/config ``` - -3. Deploy the controller to the cluster with the image specified by `IMG`: -```sh -make deploy IMG=/application:tag +### 1. run the controller locally (*Prerequisite*) +```bash +# terminal 1st +cd controllers/application/ +make run ``` -### Uninstall CRDs -To delete the CRDs from the cluster: - -```sh -make uninstall +### 2. run all the tests +```bash +make test -v ``` -### Undeploy controller -UnDeploy the controller to the cluster: - -```sh -make undeploy +### *Optional* run a specific test for debugging +```bash +# run a specific test +go test ./tests/e2e/app_create_test.go -v ``` -## Contributing -// TODO(user): Add detailed information on how you would like others to contribute to this project -### How it works -This project aims to follow the Kubernetes [Operator pattern](https://kubernetes.io/docs/concepts/extend-kubernetes/operator/) +## CI/CD -It uses [Controllers](https://kubernetes.io/docs/concepts/architecture/controller/) -which provides a reconcile function responsible for synchronizing resources untile the desired state is reached on the cluster +### 0. prepare a remote k8s cluster +> TODO: ensure a ready k8s cluster config is located in ~/.kube/config -### Test It Out -1. Install the CRDs into the cluster: +### 1. build & push the controller image +```bash +# build the image +IMG=ghcr.io/oss-controller:ci-test make docker-build -```sh -make install +# push the image +IMG=ghcr.io/oss-controller:ci-test make docker-push ``` -2. Run your controller (this will run in the foreground, so switch to a new terminal if you want to leave it running): - -```sh -make run +### 2. deploy the crd +```bash +# deploy the controller +kubectl apply -f config/crd/bases +kubectl apply -f config/rbac +kubectl apply -f config/manager ``` -**NOTE:** You can also run this in one step by running: `make install run` - -### Modifying the API definitions -If you are editing the API definitions, generate the manifests such as CRs or CRDs using: - -```sh -make manifests -``` - -**NOTE:** Run `make --help` for more information on all potential `make` targets - -More information can be found via the [Kubebuilder Documentation](https://book.kubebuilder.io/introduction.html) - -## License - -Copyright 2022. - -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. - +### 3. run tests in local +```bash +make test +``` \ No newline at end of file diff --git a/controllers/application/api/v1/application_types.go b/controllers/application/api/v1/application_types.go index 72cb1cb7a3..4fed686dc8 100644 --- a/controllers/application/api/v1/application_types.go +++ b/controllers/application/api/v1/application_types.go @@ -36,32 +36,25 @@ const ( ApplicationStateStopped ApplicationState = "Stopped" ) -type ApplicationConditionType string - const ( - ApplicationBundleInitialized ApplicationConditionType = "BundleInitialized" - ApplicationRuntimeInitialized ApplicationConditionType = "RuntimeInitialized" - - // ApplicationInitialized = Initialized + DatabaseCreated + ObjectStorageCreated + GatewayCreated + InstanceCreated - ApplicationInitialized ApplicationConditionType = "Initialized" - ApplicationDatabaseCreated ApplicationConditionType = "DatabaseCreated" - ApplicationObjectStorageCreated ApplicationConditionType = "ObjectStorageCreated" - ApplicationGatewayCreated ApplicationConditionType = "GatewayCreated" - ApplicationInstanceCreated ApplicationConditionType = "InstanceCreated" - - // ApplicationReady = DatabaseReady + ObjectStorageReady + GatewayReady + InstanceReady - ApplicationReady ApplicationConditionType = "Ready" - ApplicationDatabaseReady ApplicationConditionType = "DatabaseReady" - ApplicationObjectStorageReady ApplicationConditionType = "ObjectStorageReady" - ApplicationGatewayReady ApplicationConditionType = "GatewayReady" - ApplicationInstanceReady ApplicationConditionType = "InstanceReady" - - // ApplicationDeleted = DatabaseDeleted + ObjectStorageDeleted + GatewayDeleted + InstanceDeleted - ApplicationDeleted ApplicationConditionType = "Deleted" - ApplicationDatabaseDeleted ApplicationConditionType = "DatabaseDeleted" - ApplicationObjectStorageDeleted ApplicationConditionType = "ObjectStorageDeleted" - ApplicationGatewayDeleted ApplicationConditionType = "GatewayDeleted" - ApplicationInstanceDeleted ApplicationConditionType = "InstanceDeleted" + BundleInitialized string = "BundleInitialized" + RuntimeInitialized string = "RuntimeInitialized" + + DatabaseCreated string = "DatabaseCreated" + ObjectStorageCreated string = "ObjectStorageCreated" + GatewayCreated string = "GatewayCreated" + + DatabaseReady string = "DatabaseReady" + ObjectStorageReady string = "ObjectStorageReady" + GatewayReady string = "GatewayReady" + + Ready string = "Ready" + InstanceCreated string = "InstanceCreated" + InstanceDeleted string = "InstanceDeleted" + + DatabaseDeleted string = "DatabaseDeleted" + StorageDeleted string = "ObjectStorageDeleted" + GatewayDeleted string = "GatewayDeleted" ) // ApplicationSpec defines the desired state of Application @@ -75,6 +68,12 @@ type ApplicationSpec struct { //+kubebuilder:validation:Required AppId string `json:"appid"` + // Region + //+kubebuilder:validation:Required + //+kubebuilder:validation:MinLength=3 + //+kubebuilder:validation:MaxLength=24 + Region string `json:"region"` + // State of the application //+kubebuilder:validation:Enum=Running;Stopped; //+kubebuilder:validation:Required @@ -95,17 +94,23 @@ type ApplicationStatus struct { // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster // Important: Run "make" to regenerate code after modifying this file - // Bundle of the application - Bundle Bundle `json:"bundle,omitempty"` + // Bundle Name for the application + BundleName string `json:"bundleName"` + + // BundleSpec of the application + BundleSpec BundleSpec `json:"bundleSpec"` + + // Runtime Name for the application + RuntimeName string `json:"runtimeName"` - // Runtime of the application - Runtime runtimev1.Runtime `json:"runtime,omitempty"` + // RuntimeSpec of the application + RuntimeSpec runtimev1.RuntimeSpec `json:"runtimeSpec,omitempty"` // State of the application Phase ApplicationState `json:"state,omitempty"` // Conditions: - // @see ApplicationConditionType for the list of conditions + // @see string for the list of conditions Conditions []metav1.Condition `json:"conditions,omitempty"` } diff --git a/controllers/application/api/v1/creationform_types.go b/controllers/application/api/v1/creationform_types.go index ae2f245fd2..de6944c93d 100644 --- a/controllers/application/api/v1/creationform_types.go +++ b/controllers/application/api/v1/creationform_types.go @@ -28,6 +28,12 @@ type CreationFormSpec struct { // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster // Important: Run "make" to regenerate code after modifying this file + // Region + //+kubebuilder:validation:Required + //+kubebuilder:validation:MinLength=3 + //+kubebuilder:validation:MaxLength=24 + Region string `json:"region"` + // DisplayName for the application //+kubebuilder:validation:MinLength=3 //+kubebuilder:validation:MaxLength=63 @@ -38,7 +44,7 @@ type CreationFormSpec struct { //+kubebuilder:validation:Required BundleName string `json:"bundleName"` - // Runtime Name of the application + // RuntimeSpec Name of the application //+kubebuilder:validation:Required RuntimeName string `json:"runtimeName"` } diff --git a/controllers/application/api/v1/zz_generated.deepcopy.go b/controllers/application/api/v1/zz_generated.deepcopy.go index 71cd67ea15..a4aae7a7eb 100644 --- a/controllers/application/api/v1/zz_generated.deepcopy.go +++ b/controllers/application/api/v1/zz_generated.deepcopy.go @@ -103,8 +103,8 @@ func (in *ApplicationSpec) DeepCopy() *ApplicationSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ApplicationStatus) DeepCopyInto(out *ApplicationStatus) { *out = *in - in.Bundle.DeepCopyInto(&out.Bundle) - in.Runtime.DeepCopyInto(&out.Runtime) + in.BundleSpec.DeepCopyInto(&out.BundleSpec) + in.RuntimeSpec.DeepCopyInto(&out.RuntimeSpec) if in.Conditions != nil { in, out := &in.Conditions, &out.Conditions *out = make([]metav1.Condition, len(*in)) diff --git a/controllers/application/config/crd/bases/application.laf.dev_applications.yaml b/controllers/application/config/crd/bases/application.laf.dev_applications.yaml index 41d863794d..1659105173 100644 --- a/controllers/application/config/crd/bases/application.laf.dev_applications.yaml +++ b/controllers/application/config/crd/bases/application.laf.dev_applications.yaml @@ -43,6 +43,11 @@ spec: bundleName: description: Bundle Name for the application type: string + region: + description: Region + maxLength: 24 + minLength: 3 + type: string runtimeName: description: Runtime Name of the application type: string @@ -56,123 +61,105 @@ spec: required: - appid - bundleName + - region - runtimeName type: object status: description: ApplicationStatus defines the observed state of Application properties: - bundle: - description: Bundle of the application + bundleName: + description: Bundle Name for the application + type: string + bundleSpec: + description: BundleSpec of the application 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' + databaseCapacity: + anyOf: + - type: integer + - type: string + description: Database capacity for the bundle + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + displayName: + description: DisplayName for the bundle type: string - metadata: - type: object - spec: - description: BundleSpec defines the desired state of Bundle - properties: - databaseCapacity: - anyOf: - - type: integer - - type: string - description: Database capacity for the bundle - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - displayName: - description: DisplayName for the bundle - type: string - limitCPU: - anyOf: - - type: integer - - type: string - description: Limit CPU for the bundle - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - limitMemory: - anyOf: - - type: integer - - type: string - description: Limit Memory for the bundle - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - networkBandwidthInbound: - anyOf: - - type: integer - - type: string - description: Network Bandwidth Inbound for the bundle - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - networkBandwidthOutbound: - anyOf: - - type: integer - - type: string - description: Network Bandwidth Outbound for the bundle - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - networkTrafficOutbound: - anyOf: - - type: integer - - type: string - description: Network Traffic Outbound for the bundle - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - priority: - description: Priority for the bundle. The default value is - 0, which means that the bundle is not currently available - for creating new applications. - format: int32 - maximum: 1000 - minimum: 0 - type: integer - requestCPU: - anyOf: - - type: integer - - type: string - description: Request CPU for the bundle - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - requestMemory: - anyOf: - - type: integer - - type: string - description: Request Memory for the bundle - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - storageCapacity: - anyOf: - - type: integer - - type: string - description: Storage capacity for the bundle - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - required: - - databaseCapacity - - displayName - - limitCPU - - limitMemory - - networkTrafficOutbound - - priority - - requestCPU - - requestMemory - - storageCapacity - type: object - status: - description: BundleStatus defines the observed state of Bundle - type: object + limitCPU: + anyOf: + - type: integer + - type: string + description: Limit CPU for the bundle + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + limitMemory: + anyOf: + - type: integer + - type: string + description: Limit Memory for the bundle + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + networkBandwidthInbound: + anyOf: + - type: integer + - type: string + description: Network Bandwidth Inbound for the bundle + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + networkBandwidthOutbound: + anyOf: + - type: integer + - type: string + description: Network Bandwidth Outbound for the bundle + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + networkTrafficOutbound: + anyOf: + - type: integer + - type: string + description: Network Traffic Outbound for the bundle + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + priority: + description: Priority for the bundle. The default value is 0, + which means that the bundle is not currently available for creating + new applications. + format: int32 + maximum: 1000 + minimum: 0 + type: integer + requestCPU: + anyOf: + - type: integer + - type: string + description: Request CPU for the bundle + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + requestMemory: + anyOf: + - type: integer + - type: string + description: Request Memory for the bundle + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + storageCapacity: + anyOf: + - type: integer + - type: string + description: Storage capacity for the bundle + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + required: + - databaseCapacity + - displayName + - limitCPU + - limitMemory + - networkTrafficOutbound + - priority + - requestCPU + - requestMemory + - storageCapacity type: object conditions: - description: 'Conditions: @see ApplicationConditionType for the list - of conditions' + description: 'Conditions: @see string for the list of conditions' items: description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct @@ -240,89 +227,71 @@ spec: - type type: object type: array - runtime: - description: Runtime of the application + runtimeName: + description: Runtime Name for the application + type: string + runtimeSpec: + description: RuntimeSpec of the application 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: RuntimeSpec defines the desired state of Runtime + deprecated: + description: Deprecated + type: boolean + image: + description: Images of the runtime properties: - deprecated: - description: Deprecated - type: boolean - image: - description: Images of the runtime - properties: - init: - description: Init image - type: string - main: - description: Main image - type: string - sidecar: - description: Sidecar image - type: string - required: - - main - type: object - type: - description: Type of the runtime. eg. node:laf, node:tcb, - go:laf, python:laf, php:laf, etc. + init: + description: Init image + type: string + main: + description: Main image + type: string + sidecar: + description: Sidecar image type: string - version: - description: Version of the runtime - properties: - autoUpgradeFrom: - description: Versions that should auto upgrade from - items: - type: string - type: array - breakBefore: - description: Version that is breaking from - type: string - mustUpgradeFrom: - description: Versions that must upgrade from - items: - type: string - type: array - upgradeFrom: - description: Versions that suggested to upgrade from - items: - type: string - type: array - version: - description: Version is the version of the runtime - type: string - type: object required: - - image - - type - - version + - main type: object - status: - description: RuntimeStatus defines the observed state of Runtime + type: + description: Type of the runtime. eg. node:laf, node:tcb, go:laf, + python:laf, php:laf, etc. + type: string + version: + description: Version of the runtime properties: - appCount: - description: App count of the runtime - type: integer + autoUpgradeFrom: + description: Versions that should auto upgrade from + items: + type: string + type: array + breakBefore: + description: Version that is breaking from + type: string + mustUpgradeFrom: + description: Versions that must upgrade from + items: + type: string + type: array + upgradeFrom: + description: Versions that suggested to upgrade from + items: + type: string + type: array + version: + description: Version is the version of the runtime + type: string type: object + required: + - image + - type + - version type: object state: description: State of the application type: string + required: + - bundleName + - bundleSpec + - runtimeName type: object type: object served: true diff --git a/controllers/application/config/crd/bases/application.laf.dev_creationforms.yaml b/controllers/application/config/crd/bases/application.laf.dev_creationforms.yaml index f2706c7b77..6b3b4076ee 100644 --- a/controllers/application/config/crd/bases/application.laf.dev_creationforms.yaml +++ b/controllers/application/config/crd/bases/application.laf.dev_creationforms.yaml @@ -43,12 +43,18 @@ spec: maxLength: 63 minLength: 3 type: string + region: + description: Region + maxLength: 24 + minLength: 3 + type: string runtimeName: - description: Runtime Name of the application + description: RuntimeSpec Name of the application type: string required: - bundleName - displayName + - region - runtimeName type: object status: diff --git a/controllers/application/controllers/application_controller.go b/controllers/application/controllers/application_controller.go index 9d8ff697b6..c309756457 100644 --- a/controllers/application/controllers/application_controller.go +++ b/controllers/application/controllers/application_controller.go @@ -18,6 +18,8 @@ package controllers import ( "context" + "fmt" + appv1 "github.com/labring/laf/controllers/application/api/v1" runtimev1 "github.com/labring/laf/controllers/runtime/api/v1" "github.com/labring/laf/pkg/common" "github.com/labring/laf/pkg/util" @@ -26,13 +28,12 @@ import ( ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/log" - - applicationv1 "github.com/labring/laf/controllers/application/api/v1" + "time" ) const ApplicationFinalizer = "application.finalizers.laf.dev" -// ApplicationReconciler reconciles a Application object +// ApplicationReconciler reconciles application object type ApplicationReconciler struct { client.Client Scheme *runtime.Scheme @@ -55,7 +56,7 @@ func (r *ApplicationReconciler) Reconcile(ctx context.Context, req ctrl.Request) _ = log.FromContext(ctx) // get the application - application := &applicationv1.Application{} + application := &appv1.Application{} err := r.Get(ctx, req.NamespacedName, application) if err != nil { return ctrl.Result{}, client.IgnoreNotFound(err) @@ -70,59 +71,286 @@ func (r *ApplicationReconciler) Reconcile(ctx context.Context, req ctrl.Request) } // apply is called when the application is created or updated -func (r *ApplicationReconciler) apply(ctx context.Context, application *applicationv1.Application) (ctrl.Result, error) { - _log := log.FromContext(ctx) +func (r *ApplicationReconciler) apply(ctx context.Context, app *appv1.Application) (ctrl.Result, error) { + g := log.FromContext(ctx) // check if the finalizer is present - if !util.ContainsString(application.ObjectMeta.Finalizers, ApplicationFinalizer) { - application.ObjectMeta.Finalizers = append(application.ObjectMeta.Finalizers, ApplicationFinalizer) - err := r.Update(ctx, application) + if !util.ContainsString(app.ObjectMeta.Finalizers, ApplicationFinalizer) { + app.ObjectMeta.Finalizers = append(app.ObjectMeta.Finalizers, ApplicationFinalizer) + err := r.Update(ctx, app) if err != nil { return ctrl.Result{}, err } - - _log.Info("finalizer added for application") + g.Info("finalizer added for app") return ctrl.Result{}, nil } // reconcile runtime - if application.Status.Runtime.Name != application.Spec.RuntimeName { - err := r.reconcileRuntime(ctx, application) + if app.Status.RuntimeName != app.Spec.RuntimeName { + err := r.reconcileRuntime(ctx, app) if err != nil { return ctrl.Result{}, err } - _log.Info("runtime reconciled") + g.Info("runtime reconciled") return ctrl.Result{}, nil } // reconcile bundle - if application.Status.Bundle.Name != application.Spec.BundleName { - if err := r.reconcileBundle(ctx, application); err != nil { + if app.Status.BundleName != app.Spec.BundleName { + if err := r.reconcileBundle(ctx, app); err != nil { return ctrl.Result{}, err } - _log.Info("bundle reconciled") + g.Info("bundle reconciled") return ctrl.Result{}, nil } - // reconcile phase of application - if application.Status.Phase != application.Spec.State { - if err := r.reconcilePhase(ctx, application); err != nil { + // TODO sync the ready status of app resources: db, gateway, oss. + + // reconcile phase of app + if app.Status.Phase != app.Spec.State { + return r.reconcilePhase(ctx, app) + } + + return ctrl.Result{}, nil +} + +// reconcilePhase reconciles the phase of the application +func (r *ApplicationReconciler) reconcilePhase(ctx context.Context, app *appv1.Application) (ctrl.Result, error) { + g := log.FromContext(ctx) + + // update phase to initializing + if app.Status.Phase == "" { + err := r.updatePhaseTo(ctx, app, appv1.ApplicationStateInitializing) + return ctrl.Result{}, err + } + + // When the app is `Initializing`, initialize it. + if app.Status.Phase == appv1.ApplicationStateInitializing { + return r.initialize(ctx, app) + } + + // When the app is initialized, we need to turn it to the stopped phase first + if app.Status.Phase == appv1.ApplicationStateInitialized { + err := r.updatePhaseTo(ctx, app, appv1.ApplicationStateStopped) + return ctrl.Result{}, err + } + + // When the app is `Stopped`, we need to check if the app desired to be running. + // - create the instance + // - set the app status to starting + if app.Status.Phase == appv1.ApplicationStateStopped && + app.Spec.State == appv1.ApplicationStateRunning { + // TODO create the instance + + // set the app phase to starting + err := r.updatePhaseTo(ctx, app, appv1.ApplicationStateStarting) + return ctrl.Result{}, err + } + + // When the app is `Starting`, we need to wait for the instance to be ready. + // - sync the instance ready state + // - sync the app status to `Running` if the instance is ready + if app.Status.Phase == appv1.ApplicationStateStarting && + app.Spec.State == appv1.ApplicationStateRunning { + // TODO sync the instance ready status + + // set app phase to running if the instance is ready + condition := metav1.Condition{ + Type: appv1.Ready, + Status: metav1.ConditionTrue, + LastTransitionTime: metav1.Now(), + Reason: "InstanceReady", + Message: "The instance is ready", + } + + util.SetCondition(&app.Status.Conditions, condition) + app.Status.Phase = appv1.ApplicationStateRunning + err := r.Status().Update(ctx, app) + if err != nil { return ctrl.Result{}, err } - _log.Info("phase reconciled") + g.Info("app is running (ready)") return ctrl.Result{}, nil } + // When the app is desired to be stopped, we need to delete the instance. + if app.Spec.State == appv1.ApplicationStateStopped { + // TODO delete the instance if exists + + // set app phase to stopping + err := r.updatePhaseTo(ctx, app, appv1.ApplicationStateStopping) + return ctrl.Result{}, err + } + + // When the app is `Stopping`, we need to wait for the instance to be deleted: + if app.Status.Phase == appv1.ApplicationStateStopping { + // TODO sync the instance deleted status + + // set app phase to stopped if the instance is deleted + err := r.updatePhaseTo(ctx, app, appv1.ApplicationStateStopped) + return ctrl.Result{}, err + } + return ctrl.Result{}, nil } +// initialize the app: +// - create the database +// - create the oss user +// - create the gateway +// - sync & wait for the resources to be ready +// - update the app status to initialized +func (r *ApplicationReconciler) initialize(ctx context.Context, app *appv1.Application) (ctrl.Result, error) { + g := log.FromContext(ctx) + + // create the database + if util.ConditionIsNotTrue(app.Status.Conditions, appv1.DatabaseCreated) { + // TODO create database + + // set condition DatabaseCreated to true + condition := metav1.Condition{ + Type: appv1.DatabaseCreated, + Status: metav1.ConditionTrue, + LastTransitionTime: metav1.Now(), + Reason: "DatabaseCreated", + Message: "Database created", + } + + util.SetCondition(&app.Status.Conditions, condition) + err := r.Status().Update(ctx, app) + if err != nil { + return ctrl.Result{}, err + } + g.Info("database created") + return ctrl.Result{}, nil + } + + // create the oss user + if util.ConditionIsNotTrue(app.Status.Conditions, appv1.ObjectStorageCreated) { + // TODO create oss user + + // set condition ObjectStorageCreated to true + condition := metav1.Condition{ + Type: appv1.ObjectStorageCreated, + Status: metav1.ConditionTrue, + LastTransitionTime: metav1.Now(), + Reason: "ObjectStorageCreated", + Message: "Object storage created", + } + + util.SetCondition(&app.Status.Conditions, condition) + err := r.Status().Update(ctx, app) + if err != nil { + return ctrl.Result{}, err + } + g.Info("object storage created") + return ctrl.Result{}, nil + } + + // create the gateway + if util.ConditionIsNotTrue(app.Status.Conditions, appv1.GatewayCreated) { + // TODO create the gateway + + // set condition GatewayCreated to true + condition := metav1.Condition{ + Type: appv1.GatewayCreated, + Status: metav1.ConditionTrue, + LastTransitionTime: metav1.Now(), + Reason: "GatewayCreated", + Message: "Gateway created", + } + + util.SetCondition(&app.Status.Conditions, condition) + err := r.Status().Update(ctx, app) + if err != nil { + return ctrl.Result{}, err + } + g.Info("gateway created") + return ctrl.Result{}, nil + } + + // sync the database ready condition + if util.ConditionIsNotTrue(app.Status.Conditions, appv1.DatabaseReady) { + // TODO get database ready condition + + // set condition DatabaseReady to true if database is ready + condition := metav1.Condition{ + Type: appv1.DatabaseReady, + Status: metav1.ConditionTrue, + LastTransitionTime: metav1.Now(), + Reason: "DatabaseReady", + Message: "Database ready", + } + + util.SetCondition(&app.Status.Conditions, condition) + err := r.Status().Update(ctx, app) + if err != nil { + return ctrl.Result{}, err + } + g.Info("database ready") + return ctrl.Result{}, nil + } + + // sync the oss user ready condition + if util.ConditionIsNotTrue(app.Status.Conditions, appv1.ObjectStorageReady) { + // TODO get oss user ready condition + + // set condition ObjectStorageReady to true if oss user is ready + condition := metav1.Condition{ + Type: appv1.ObjectStorageReady, + Status: metav1.ConditionTrue, + LastTransitionTime: metav1.Now(), + Reason: "ObjectStorageReady", + Message: "Object storage ready", + } + + util.SetCondition(&app.Status.Conditions, condition) + err := r.Status().Update(ctx, app) + if err != nil { + return ctrl.Result{}, err + } + g.Info("object storage ready") + return ctrl.Result{}, nil + } + + // sync the gateway ready condition + if util.ConditionIsNotTrue(app.Status.Conditions, appv1.GatewayReady) { + // TODO get gateway ready condition + + // set condition GatewayReady to true if gateway is ready + condition := metav1.Condition{ + Type: appv1.GatewayReady, + Status: metav1.ConditionTrue, + LastTransitionTime: metav1.Now(), + Reason: "GatewayReady", + Message: "Gateway ready", + } + + util.SetCondition(&app.Status.Conditions, condition) + err := r.Status().Update(ctx, app) + if err != nil { + return ctrl.Result{}, err + } + g.Info("gateway ready") + return ctrl.Result{}, nil + } + + // set app phase to initialized + if util.ConditionsAreTrue(app.Status.Conditions, appv1.DatabaseReady, appv1.ObjectStorageReady, appv1.GatewayReady) { + err := r.updatePhaseTo(ctx, app, appv1.ApplicationStateInitialized) + return ctrl.Result{}, err + } + + return ctrl.Result{RequeueAfter: time.Second}, nil +} + // reconcileRuntime reconciles the runtime of the application -func (r *ApplicationReconciler) reconcileRuntime(ctx context.Context, application *applicationv1.Application) error { +func (r *ApplicationReconciler) reconcileRuntime(ctx context.Context, app *appv1.Application) error { // get runtime by name rt := &runtimev1.Runtime{} err := r.Get(ctx, client.ObjectKey{ Namespace: common.GetSystemNamespace(), - Name: application.Spec.RuntimeName, + Name: app.Spec.RuntimeName, }, rt) if err != nil { return err @@ -130,27 +358,28 @@ func (r *ApplicationReconciler) reconcileRuntime(ctx context.Context, applicatio // update the condition condition := metav1.Condition{ - Type: string(applicationv1.ApplicationRuntimeInitialized), + Type: appv1.RuntimeInitialized, Status: metav1.ConditionTrue, Reason: "RuntimeInitialized", Message: "runtime initialized", LastTransitionTime: metav1.Now(), } - util.SetCondition(&application.Status.Conditions, condition) + util.SetCondition(&app.Status.Conditions, condition) // update the status - application.Status.Runtime = *rt - err = r.Status().Update(ctx, application) + app.Status.RuntimeName = app.Spec.RuntimeName + app.Status.RuntimeSpec = rt.Spec + err = r.Status().Update(ctx, app) return err } // reconcileBundle reconciles the bundle of the application -func (r *ApplicationReconciler) reconcileBundle(ctx context.Context, application *applicationv1.Application) error { +func (r *ApplicationReconciler) reconcileBundle(ctx context.Context, app *appv1.Application) error { // get bundle by name - bundle := &applicationv1.Bundle{} + bundle := &appv1.Bundle{} err := r.Get(ctx, client.ObjectKey{ Namespace: common.GetSystemNamespace(), - Name: application.Spec.BundleName, + Name: app.Spec.BundleName, }, bundle) if err != nil { return err @@ -158,31 +387,27 @@ func (r *ApplicationReconciler) reconcileBundle(ctx context.Context, application // update the condition condition := metav1.Condition{ - Type: string(applicationv1.ApplicationBundleInitialized), + Type: appv1.BundleInitialized, Status: metav1.ConditionTrue, Reason: "BundleInitialized", Message: "bundle initialized", LastTransitionTime: metav1.Now(), } - util.SetCondition(&application.Status.Conditions, condition) - - // update the status - application.Status.Bundle = *bundle - err = r.Status().Update(ctx, application) + util.SetCondition(&app.Status.Conditions, condition) + app.Status.BundleName = app.Spec.BundleName + app.Status.BundleSpec = bundle.Spec + err = r.Status().Update(ctx, app) return err } // delete is called when the application is deleted -func (r *ApplicationReconciler) delete(ctx context.Context, application *applicationv1.Application) (ctrl.Result, error) { +func (r *ApplicationReconciler) delete(ctx context.Context, app *appv1.Application) (ctrl.Result, error) { // TODO: delete the application - if false { - return ctrl.Result{}, nil - } // remove the finalizer - application.ObjectMeta.Finalizers = util.RemoveString(application.ObjectMeta.Finalizers, ApplicationFinalizer) - err := r.Update(ctx, application) + app.ObjectMeta.Finalizers = util.RemoveString(app.ObjectMeta.Finalizers, ApplicationFinalizer) + err := r.Update(ctx, app) if err != nil { return ctrl.Result{}, err } @@ -190,16 +415,22 @@ func (r *ApplicationReconciler) delete(ctx context.Context, application *applica return ctrl.Result{}, nil } -// reconcilePhase reconciles the phase of the application -func (r *ApplicationReconciler) reconcilePhase(ctx context.Context, application *applicationv1.Application) error { - // TODO - - return nil +// update app phase +func (r *ApplicationReconciler) updatePhaseTo(ctx context.Context, app *appv1.Application, phase appv1.ApplicationState) error { + g := log.FromContext(ctx) + old := app.Status.Phase + app.Status.Phase = phase + err := r.Status().Update(ctx, app) + if err != nil { + return err + } + g.Info(fmt.Sprintf("app phase updated to %s from %s", phase, old)) + return err } // SetupWithManager sets up the controller with the Manager. func (r *ApplicationReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). - For(&applicationv1.Application{}). + For(&appv1.Application{}). Complete(r) } diff --git a/controllers/application/controllers/creationform_controller.go b/controllers/application/controllers/creationform_controller.go index ed95678e72..33af145897 100644 --- a/controllers/application/controllers/creationform_controller.go +++ b/controllers/application/controllers/creationform_controller.go @@ -129,6 +129,7 @@ func (r *CreationFormReconciler) apply(ctx context.Context, form *applicationv1. } app.Spec.AppId = form.Status.AppId + app.Spec.Region = form.Spec.Region app.Spec.State = applicationv1.ApplicationStateRunning app.Spec.BundleName = form.Spec.BundleName app.Spec.RuntimeName = form.Spec.RuntimeName diff --git a/controllers/application/main.go b/controllers/application/main.go index 6b768e3f41..bd6f48b753 100644 --- a/controllers/application/main.go +++ b/controllers/application/main.go @@ -18,6 +18,10 @@ package main import ( "flag" + "github.com/labring/laf/controllers/application/resourcer" + databasev1 "github.com/labring/laf/controllers/database/api/v1" + gatewayv1 "github.com/labring/laf/controllers/gateway/api/v1" + ossv1 "github.com/labring/laf/controllers/oss/api/v1" runtimev1 "github.com/labring/laf/controllers/runtime/api/v1" "os" @@ -49,6 +53,9 @@ func init() { //+kubebuilder:scaffold:scheme utilruntime.Must(runtimev1.AddToScheme(scheme)) + utilruntime.Must(databasev1.AddToScheme(scheme)) + utilruntime.Must(ossv1.AddToScheme(scheme)) + utilruntime.Must(gatewayv1.AddToScheme(scheme)) } func main() { @@ -92,6 +99,8 @@ func main() { os.Exit(1) } + resourcer.SetClient(mgr.GetClient()) + if err = (&controllers.ApplicationReconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), diff --git a/controllers/application/resourcer/client.go b/controllers/application/resourcer/client.go new file mode 100644 index 0000000000..49f088cbe1 --- /dev/null +++ b/controllers/application/resourcer/client.go @@ -0,0 +1,20 @@ +package resourcer + +import "sigs.k8s.io/controller-runtime/pkg/client" + +var ( + // Client is a global client for all controllers + Client client.Client +) + +func init() { + Client = nil +} + +func SetClient(c client.Client) { + Client = c +} + +func GetClient() client.Client { + return Client +} diff --git a/controllers/application/resourcer/database.go b/controllers/application/resourcer/database.go new file mode 100644 index 0000000000..d425c26e66 --- /dev/null +++ b/controllers/application/resourcer/database.go @@ -0,0 +1,26 @@ +package resourcer + +import ( + "context" + appv1 "github.com/labring/laf/controllers/application/api/v1" + databasev1 "github.com/labring/laf/controllers/database/api/v1" + "github.com/labring/laf/pkg/common" +) + +func CreateAppDatabase(ctx context.Context, app *appv1.Application) error { + //client := GetClient() + + // Create a new database + var db databasev1.Database + db.Namespace = app.Namespace + db.Name = "mongodb" + db.Labels = map[string]string{ + "laf.dev/appid": app.Spec.AppId, + } + + db.Spec.Provider = "mongodb" + db.Spec.Region = app.Spec.Region + db.Spec.Password = common.GenerateAlphaNumericPassword(64) + + return nil +} diff --git a/controllers/application/resourcer/gateway.go b/controllers/application/resourcer/gateway.go new file mode 100644 index 0000000000..d445f69c5c --- /dev/null +++ b/controllers/application/resourcer/gateway.go @@ -0,0 +1 @@ +package resourcer diff --git a/controllers/application/resourcer/instance.go b/controllers/application/resourcer/instance.go new file mode 100644 index 0000000000..d445f69c5c --- /dev/null +++ b/controllers/application/resourcer/instance.go @@ -0,0 +1 @@ +package resourcer diff --git a/controllers/application/resourcer/oss.go b/controllers/application/resourcer/oss.go new file mode 100644 index 0000000000..d445f69c5c --- /dev/null +++ b/controllers/application/resourcer/oss.go @@ -0,0 +1 @@ +package resourcer diff --git a/controllers/application/tests/api/app.go b/controllers/application/tests/api/app.go index d910d0b605..aca4c4049a 100644 --- a/controllers/application/tests/api/app.go +++ b/controllers/application/tests/api/app.go @@ -14,17 +14,19 @@ metadata: spec: appid: ${appid} state: Running + region: ${region} bundleName: ${bundleName} runtimeName: ${runtimeName} ` -func CreateApplication(namespace, name, appid, bundleName, runtimeName string) { +func CreateApplication(namespace, name, appid, bundleName, runtimeName, region string) { baseapi.MustKubeApplyFromTemplate(appYaml, map[string]string{ "namespace": namespace, "name": name, "appid": appid, "bundleName": bundleName, "runtimeName": runtimeName, + "region": region, }) } diff --git a/controllers/application/tests/api/creation_form.go b/controllers/application/tests/api/creation_form.go index 867f3787aa..7704cfda90 100644 --- a/controllers/application/tests/api/creation_form.go +++ b/controllers/application/tests/api/creation_form.go @@ -14,16 +14,18 @@ metadata: namespace: ${namespace} spec: displayName: "Sample Application Name" + region: ${region} bundleName: ${bundle} runtimeName: ${runtime} ` -func AddCreationForm(namespace string, name string, bundleName string, runtimeName string) { +func AddCreationForm(namespace string, name string, bundleName string, runtimeName string, region string) { baseapi.MustKubeApplyFromTemplate(formYaml, map[string]string{ "name": name, "namespace": namespace, "bundle": bundleName, "runtime": runtimeName, + "region": region, }) } diff --git a/controllers/application/tests/e2e/app_create_test.go b/controllers/application/tests/e2e/app_create_test.go index 401c8fb0aa..e132ef6c04 100644 --- a/controllers/application/tests/e2e/app_create_test.go +++ b/controllers/application/tests/e2e/app_create_test.go @@ -1,6 +1,7 @@ package e2e import ( + appv1 "github.com/labring/laf/controllers/application/api/v1" "github.com/labring/laf/controllers/application/tests/api" runtimeapi "github.com/labring/laf/controllers/runtime/tests/api" "github.com/labring/laf/pkg/common" @@ -14,6 +15,7 @@ func TestCreateApp(t *testing.T) { const appid = "test-appid" const runtimeName = name const bundleName = name + const region = "test-region" var systemNamespace = common.GetSystemNamespace() @@ -30,20 +32,45 @@ func TestCreateApp(t *testing.T) { api.CreateAppBundle(systemNamespace, bundleName) t.Log("create the app") - api.CreateApplication(namespace, name, appid, runtimeName, bundleName) + api.CreateApplication(namespace, name, appid, runtimeName, bundleName, region) + + t.Log("wait for the app to be ready") + api.WaitForApplicationReady(namespace, name) t.Log("get the app") app, err := api.GetApplication(namespace, name) if err != nil { - t.Fatalf("get app failed: %v", err) + t.Fatalf("ERROR: get app failed: %v", err) } t.Log("verify the app") if app.Name != name { - t.Fatalf("app name is not correct, expect %s, got %s", name, app.Name) + t.Fatalf("ERROR: app name is not correct, expect %s, got %s", name, app.Name) + } + + t.Log("verify the runtime") + if app.Status.RuntimeName != runtimeName { + t.Errorf("ERROR: runtime name expect %s got %s", runtimeName, app.Status.RuntimeName) + } + + if app.Status.RuntimeSpec.Type != "node:laf" { + t.Errorf("ERROR: invalid runtime type got %s", app.Status.RuntimeSpec.Type) + } + + t.Log("verify") + if app.Status.Phase != appv1.ApplicationStateRunning { + t.Errorf("ERROR: invalid app phase got %s", app.Status.Phase) + } + + t.Log("verify the bundle") + if app.Status.BundleName != bundleName { + t.Errorf("ERROR: bundle name expect %s got %s", bundleName, app.Status.BundleName) + } + + if app.Status.BundleSpec.DisplayName == "" { + t.Errorf("ERROR: bundle display name cannot be empty") } - // TODO: verify the status / conditions }) t.Cleanup(func() { diff --git a/controllers/gateway/controllers/gateway_controller.go b/controllers/gateway/controllers/gateway_controller.go index 3dbfb5a1dd..55025ee1c3 100644 --- a/controllers/gateway/controllers/gateway_controller.go +++ b/controllers/gateway/controllers/gateway_controller.go @@ -87,7 +87,7 @@ func (r *GatewayReconciler) apply(ctx context.Context, gateway *gatewayv1.Gatewa } // update ready condition - if util.IsConditionTrue(gateway.Status.Conditions, "Ready") == false { + if util.ConditionIsTrue(gateway.Status.Conditions, "Ready") == false { condition := metav1.Condition{ Type: "Ready", Status: metav1.ConditionTrue, diff --git a/controllers/oss/controllers/bucket_controller.go b/controllers/oss/controllers/bucket_controller.go index e932b92c3f..b69c1c70f9 100644 --- a/controllers/oss/controllers/bucket_controller.go +++ b/controllers/oss/controllers/bucket_controller.go @@ -120,7 +120,7 @@ func (r *BucketReconciler) apply(ctx context.Context, bucket *ossv1.Bucket) (ctr } // update ready condition - if util.IsConditionTrue(bucket.Status.Conditions, "Ready") == false { + if util.ConditionIsTrue(bucket.Status.Conditions, "Ready") == false { condition := metav1.Condition{ Type: "Ready", Status: metav1.ConditionTrue, diff --git a/controllers/oss/controllers/store_controller.go b/controllers/oss/controllers/store_controller.go index 46e36f4327..79fd072de1 100644 --- a/controllers/oss/controllers/store_controller.go +++ b/controllers/oss/controllers/store_controller.go @@ -101,7 +101,7 @@ func (r *StoreReconciler) apply(ctx context.Context, store *ossv1.Store) (ctrl.R } // initialize the store - if !util.IsConditionTrue(store.Status.Conditions, "Ready") { + if !util.ConditionIsTrue(store.Status.Conditions, "Ready") { err := r.initStore(ctx, store) if err != nil { return ctrl.Result{Requeue: true, RequeueAfter: time.Minute}, err @@ -111,7 +111,7 @@ func (r *StoreReconciler) apply(ctx context.Context, store *ossv1.Store) (ctrl.R } // sync capacity status - if util.IsConditionsTrue(store.Status.Conditions, "Ready") { + if util.ConditionsAreTrue(store.Status.Conditions, "Ready") { err := r.syncCapacityStatus(ctx, store) if err != nil { return ctrl.Result{Requeue: true, RequeueAfter: time.Minute}, err diff --git a/controllers/oss/controllers/user_controller.go b/controllers/oss/controllers/user_controller.go index 170246fcc8..7352b5bcf6 100644 --- a/controllers/oss/controllers/user_controller.go +++ b/controllers/oss/controllers/user_controller.go @@ -209,7 +209,7 @@ func (r *UserReconciler) selectStore(ctx context.Context, user *ossv1.User) erro continue } - if !util.IsConditionTrue(s.Status.Conditions, "Ready") { + if !util.ConditionIsTrue(s.Status.Conditions, "Ready") { continue } diff --git a/controllers/oss/tests/e2e/user_create_test.go b/controllers/oss/tests/e2e/user_create_test.go index 2bdd5d1331..30a31757a1 100644 --- a/controllers/oss/tests/e2e/user_create_test.go +++ b/controllers/oss/tests/e2e/user_create_test.go @@ -68,7 +68,7 @@ func TestCreateOssUser(t *testing.T) { } t.Log("verify the condition ready") - if util.IsConditionsTrue(user.Status.Conditions, "Ready") != true { + if util.ConditionsAreTrue(user.Status.Conditions, "Ready") != true { t.Fatalf("user condition ready is not true") } }) diff --git a/pkg/common/random.go b/pkg/common/random.go new file mode 100644 index 0000000000..6ae77663ca --- /dev/null +++ b/pkg/common/random.go @@ -0,0 +1,24 @@ +package common + +import gonanoid "github.com/matoous/go-nanoid" + +const FullAlphaNumeric = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" +const AlphaNumeric = "0123456789abcdefghijklmnopqrstuvwxyz" + +func GenerateAlphaNumericPassword(size int) string { + // Generate a random password + password, err := gonanoid.Generate(FullAlphaNumeric, size) + if err != nil { + panic(err) + } + return password +} + +func GenerateLowerCaseRandomString(size int) string { + // Generate a random password + password, err := gonanoid.Generate(AlphaNumeric, size) + if err != nil { + panic(err) + } + return password +} diff --git a/pkg/util/conditions.go b/pkg/util/conditions.go index 912a4fda7b..cef898d2b9 100644 --- a/pkg/util/conditions.go +++ b/pkg/util/conditions.go @@ -1,6 +1,8 @@ package util -import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) func GetCondition(conditions []metav1.Condition, conditionType string) *metav1.Condition { for i := range conditions { @@ -41,29 +43,44 @@ func RemoveCondition(conditions *[]metav1.Condition, conditionType string) { } } -func IsConditionTrue(conditions []metav1.Condition, conditionType string) bool { +func ConditionIsTrue(conditions []metav1.Condition, conditionType string) bool { if condition := GetCondition(conditions, conditionType); condition != nil { return condition.Status == metav1.ConditionTrue } return false } -func IsConditionFalse(conditions []metav1.Condition, conditionType string) bool { - return IsConditionTrue(conditions, conditionType) == false +func ConditionIsFalse(conditions []metav1.Condition, conditionType string) bool { + if condition := GetCondition(conditions, conditionType); condition != nil { + return condition.Status == metav1.ConditionFalse + } + + return false } -func IsConditionUnknown(conditions []metav1.Condition, conditionType string) bool { +func ConditionIsUnknown(conditions []metav1.Condition, conditionType string) bool { if condition := GetCondition(conditions, conditionType); condition != nil { return condition.Status == metav1.ConditionUnknown } return false } -func IsConditionsTrue(conditions []metav1.Condition, conditionTypes ...string) bool { +func ConditionIsNotTrue(conditions []metav1.Condition, conditionType string) bool { + return !ConditionIsTrue(conditions, conditionType) +} + +func ConditionsAreTrue(conditions []metav1.Condition, conditionTypes ...string) bool { for _, conditionType := range conditionTypes { - if IsConditionTrue(conditions, conditionType) == false { + if ConditionIsTrue(conditions, conditionType) == false { return false } } return true } + +func ConditionOutOfDate(conditions []metav1.Condition, conditionType string, duration metav1.Duration) bool { + if condition := GetCondition(conditions, conditionType); condition != nil { + return condition.LastTransitionTime.Add(duration.Duration).Before(metav1.Now().Time) + } + return false +}