From 6472a917a60325a5540376931b41b52d2ea252b7 Mon Sep 17 00:00:00 2001 From: ciaranRoche Date: Mon, 13 Jan 2020 15:17:24 +0000 Subject: [PATCH 01/14] initial redis snapshot --- Makefile | 1 + ...integreatly_v1alpha1_redissnapshot_cr.yaml | 7 + ...ntegreatly_v1alpha1_redissnapshot_crd.yaml | 52 ++++ deploy/role.yaml | 9 +- .../v1alpha1/redissnapshot_types.go | 45 ++++ pkg/apis/integreatly/v1alpha1/types/types.go | 7 + .../v1alpha1/zz_generated.deepcopy.go | 93 +++++++ .../v1alpha1/zz_generated.openapi.go | 92 +++++++ pkg/controller/add_redissnapshot.go | 10 + .../redissnapshot/redissnapshot_controller.go | 253 ++++++++++++++++++ pkg/providers/aws/config.go | 10 +- pkg/providers/aws/credentials.go | 1 + pkg/providers/aws/provider_blobstorage.go | 2 +- pkg/providers/aws/provider_postgres.go | 8 +- pkg/providers/aws/provider_postgres_test.go | 3 - pkg/providers/aws/provider_redis.go | 12 +- .../aws/provider_smtpcredentialset.go | 2 +- pkg/resources/phase.go | 19 ++ 18 files changed, 608 insertions(+), 18 deletions(-) create mode 100644 deploy/crds/integreatly_v1alpha1_redissnapshot_cr.yaml create mode 100644 deploy/crds/integreatly_v1alpha1_redissnapshot_crd.yaml create mode 100644 pkg/apis/integreatly/v1alpha1/redissnapshot_types.go create mode 100644 pkg/controller/add_redissnapshot.go create mode 100644 pkg/controller/redissnapshot/redissnapshot_controller.go diff --git a/Makefile b/Makefile index 69c95563f..63ab160e8 100644 --- a/Makefile +++ b/Makefile @@ -73,6 +73,7 @@ cluster/prepare: oc create -f ./deploy/crds/integreatly_v1alpha1_smtpcredentialset_crd.yaml -n $(NAMESPACE) oc create -f ./deploy/crds/integreatly_v1alpha1_redis_crd.yaml -n $(NAMESPACE) oc create -f ./deploy/crds/integreatly_v1alpha1_postgres_crd.yaml -n $(NAMESPACE) + oc create -f ./deploy/crds/integreatly_v1alpha1_redissnapshot_crd.yaml -n $(NAMESPACE) oc create -f ./deploy/service_account.yaml -n $(NAMESPACE) oc create -f ./deploy/role.yaml -n $(NAMESPACE) oc create -f ./deploy/role_binding.yaml -n $(NAMESPACE) diff --git a/deploy/crds/integreatly_v1alpha1_redissnapshot_cr.yaml b/deploy/crds/integreatly_v1alpha1_redissnapshot_cr.yaml new file mode 100644 index 000000000..a411edab4 --- /dev/null +++ b/deploy/crds/integreatly_v1alpha1_redissnapshot_cr.yaml @@ -0,0 +1,7 @@ +apiVersion: integreatly.org/v1alpha1 +kind: RedisSnapshot +metadata: + name: example-redissnapshot +spec: + # The redis resource name for the snapshot you want to take + resourceName: REPLACE_ME diff --git a/deploy/crds/integreatly_v1alpha1_redissnapshot_crd.yaml b/deploy/crds/integreatly_v1alpha1_redissnapshot_crd.yaml new file mode 100644 index 000000000..b51196925 --- /dev/null +++ b/deploy/crds/integreatly_v1alpha1_redissnapshot_crd.yaml @@ -0,0 +1,52 @@ +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + name: redissnapshots.integreatly.org +spec: + group: integreatly.org + names: + kind: RedisSnapshot + listKind: RedisSnapshotList + plural: redissnapshots + singular: redissnapshot + scope: Namespaced + subresources: + status: {} + validation: + openAPIV3Schema: + 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/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/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + properties: + resourceName: + description: 'INSERT ADDITIONAL SPEC FIELDS - desired state of cluster + Important: Run "operator-sdk generate k8s" to regenerate code after + modifying this file Add custom validation using kubebuilder tags: + https://book-v1.book.kubebuilder.io/beyond_basics/generating_crd.html' + type: string + required: + - resourceName + type: object + status: + properties: + message: + type: string + phase: + type: string + type: object + version: v1alpha1 + versions: + - name: v1alpha1 + served: true + storage: true diff --git a/deploy/role.yaml b/deploy/role.yaml index 868feb99e..f35afb281 100644 --- a/deploy/role.yaml +++ b/deploy/role.yaml @@ -67,12 +67,11 @@ rules: - smtpcredentialset - redis - postgres + - redissnapshots verbs: - '*' - apiGroups: - config.openshift.io - verbs: - - '*' resources: - '*' - infrastructures @@ -83,9 +82,11 @@ rules: - clusteroperators - authentications - builds + verbs: + - '*' - apiGroups: - cloudcredential.openshift.io + resources: + - credentialsrequests verbs: - '*' - resources: - - credentialsrequests \ No newline at end of file diff --git a/pkg/apis/integreatly/v1alpha1/redissnapshot_types.go b/pkg/apis/integreatly/v1alpha1/redissnapshot_types.go new file mode 100644 index 000000000..9dd3cf2f8 --- /dev/null +++ b/pkg/apis/integreatly/v1alpha1/redissnapshot_types.go @@ -0,0 +1,45 @@ +package v1alpha1 + +import ( + "github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1/types" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// RedisSnapshotSpec defines the desired state of RedisSnapshot +// +k8s:openapi-gen=true +type RedisSnapshotSpec struct { + // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster + // Important: Run "operator-sdk generate k8s" to regenerate code after modifying this file + // Add custom validation using kubebuilder tags: https://book-v1.book.kubebuilder.io/beyond_basics/generating_crd.html + ResourceName string `json:"resourceName"` +} + +// RedisSnapshotStatus defines the observed state of RedisSnapshot +// +k8s:openapi-gen=true +type RedisSnapshotStatus types.ResourceTypeSnapshotStatus + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// RedisSnapshot is the Schema for the redissnapshots API +// +k8s:openapi-gen=true +// +kubebuilder:subresource:status +type RedisSnapshot struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec RedisSnapshotSpec `json:"spec,omitempty"` + Status RedisSnapshotStatus `json:"status,omitempty"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// RedisSnapshotList contains a list of RedisSnapshot +type RedisSnapshotList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []RedisSnapshot `json:"items"` +} + +func init() { + SchemeBuilder.Register(&RedisSnapshot{}, &RedisSnapshotList{}) +} diff --git a/pkg/apis/integreatly/v1alpha1/types/types.go b/pkg/apis/integreatly/v1alpha1/types/types.go index e19341915..9b0afd996 100644 --- a/pkg/apis/integreatly/v1alpha1/types/types.go +++ b/pkg/apis/integreatly/v1alpha1/types/types.go @@ -51,3 +51,10 @@ type ResourceTypeStatus struct { Phase StatusPhase `json:"phase,omitempty"` Message StatusMessage `json:"message,omitempty"` } + +// ResourceTypeSnapshotStatus Represents the basic status information provided by snapshot controller +// +k8s:openapi-gen=true +type ResourceTypeSnapshotStatus struct { + Phase StatusPhase `json:"phase,omitempty"` + Message StatusMessage `json:"message,omitempty"` +} diff --git a/pkg/apis/integreatly/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/integreatly/v1alpha1/zz_generated.deepcopy.go index 34e7a20c9..ad457e125 100644 --- a/pkg/apis/integreatly/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/integreatly/v1alpha1/zz_generated.deepcopy.go @@ -276,6 +276,99 @@ func (in *RedisList) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RedisSnapshot) DeepCopyInto(out *RedisSnapshot) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + out.Status = in.Status + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RedisSnapshot. +func (in *RedisSnapshot) DeepCopy() *RedisSnapshot { + if in == nil { + return nil + } + out := new(RedisSnapshot) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *RedisSnapshot) 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 *RedisSnapshotList) DeepCopyInto(out *RedisSnapshotList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]RedisSnapshot, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RedisSnapshotList. +func (in *RedisSnapshotList) DeepCopy() *RedisSnapshotList { + if in == nil { + return nil + } + out := new(RedisSnapshotList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *RedisSnapshotList) 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 *RedisSnapshotSpec) DeepCopyInto(out *RedisSnapshotSpec) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RedisSnapshotSpec. +func (in *RedisSnapshotSpec) DeepCopy() *RedisSnapshotSpec { + if in == nil { + return nil + } + out := new(RedisSnapshotSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RedisSnapshotStatus) DeepCopyInto(out *RedisSnapshotStatus) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RedisSnapshotStatus. +func (in *RedisSnapshotStatus) DeepCopy() *RedisSnapshotStatus { + if in == nil { + return nil + } + out := new(RedisSnapshotStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *RedisSpec) DeepCopyInto(out *RedisSpec) { *out = *in diff --git a/pkg/apis/integreatly/v1alpha1/zz_generated.openapi.go b/pkg/apis/integreatly/v1alpha1/zz_generated.openapi.go index e6303fc88..1879cb0d3 100644 --- a/pkg/apis/integreatly/v1alpha1/zz_generated.openapi.go +++ b/pkg/apis/integreatly/v1alpha1/zz_generated.openapi.go @@ -18,6 +18,9 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "./pkg/apis/integreatly/v1alpha1.PostgresSpec": schema_pkg_apis_integreatly_v1alpha1_PostgresSpec(ref), "./pkg/apis/integreatly/v1alpha1.PostgresStatus": schema_pkg_apis_integreatly_v1alpha1_PostgresStatus(ref), "./pkg/apis/integreatly/v1alpha1.Redis": schema_pkg_apis_integreatly_v1alpha1_Redis(ref), + "./pkg/apis/integreatly/v1alpha1.RedisSnapshot": schema_pkg_apis_integreatly_v1alpha1_RedisSnapshot(ref), + "./pkg/apis/integreatly/v1alpha1.RedisSnapshotSpec": schema_pkg_apis_integreatly_v1alpha1_RedisSnapshotSpec(ref), + "./pkg/apis/integreatly/v1alpha1.RedisSnapshotStatus": schema_pkg_apis_integreatly_v1alpha1_RedisSnapshotStatus(ref), "./pkg/apis/integreatly/v1alpha1.RedisSpec": schema_pkg_apis_integreatly_v1alpha1_RedisSpec(ref), "./pkg/apis/integreatly/v1alpha1.RedisStatus": schema_pkg_apis_integreatly_v1alpha1_RedisStatus(ref), "./pkg/apis/integreatly/v1alpha1.SMTPCredentialSet": schema_pkg_apis_integreatly_v1alpha1_SMTPCredentialSet(ref), @@ -317,6 +320,95 @@ func schema_pkg_apis_integreatly_v1alpha1_Redis(ref common.ReferenceCallback) co } } +func schema_pkg_apis_integreatly_v1alpha1_RedisSnapshot(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "RedisSnapshot is the Schema for the redissnapshots API", + 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/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/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), + }, + }, + "spec": { + SchemaProps: spec.SchemaProps{ + Ref: ref("./pkg/apis/integreatly/v1alpha1.RedisSnapshotSpec"), + }, + }, + "status": { + SchemaProps: spec.SchemaProps{ + Ref: ref("./pkg/apis/integreatly/v1alpha1.RedisSnapshotStatus"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "./pkg/apis/integreatly/v1alpha1.RedisSnapshotSpec", "./pkg/apis/integreatly/v1alpha1.RedisSnapshotStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + } +} + +func schema_pkg_apis_integreatly_v1alpha1_RedisSnapshotSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "RedisSnapshotSpec defines the desired state of RedisSnapshot", + Properties: map[string]spec.Schema{ + "resourceName": { + SchemaProps: spec.SchemaProps{ + Description: "INSERT ADDITIONAL SPEC FIELDS - desired state of cluster Important: Run \"operator-sdk generate k8s\" to regenerate code after modifying this file Add custom validation using kubebuilder tags: https://book-v1.book.kubebuilder.io/beyond_basics/generating_crd.html", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"resourceName"}, + }, + }, + Dependencies: []string{}, + } +} + +func schema_pkg_apis_integreatly_v1alpha1_RedisSnapshotStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "RedisSnapshotStatus defines the observed state of RedisSnapshot", + Properties: map[string]spec.Schema{ + "phase": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, + "message": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + Dependencies: []string{}, + } +} + func schema_pkg_apis_integreatly_v1alpha1_RedisSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ diff --git a/pkg/controller/add_redissnapshot.go b/pkg/controller/add_redissnapshot.go new file mode 100644 index 000000000..1ffe4dd0b --- /dev/null +++ b/pkg/controller/add_redissnapshot.go @@ -0,0 +1,10 @@ +package controller + +import ( + "github.com/integr8ly/cloud-resource-operator/pkg/controller/redissnapshot" +) + +func init() { + // AddToManagerFuncs is a list of functions to create controllers and add them to a manager. + AddToManagerFuncs = append(AddToManagerFuncs, redissnapshot.Add) +} diff --git a/pkg/controller/redissnapshot/redissnapshot_controller.go b/pkg/controller/redissnapshot/redissnapshot_controller.go new file mode 100644 index 000000000..4ac10e345 --- /dev/null +++ b/pkg/controller/redissnapshot/redissnapshot_controller.go @@ -0,0 +1,253 @@ +package redissnapshot + +import ( + "context" + "fmt" + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/credentials" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/elasticache" + croType "github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1/types" + "github.com/integr8ly/cloud-resource-operator/pkg/providers" + croAws "github.com/integr8ly/cloud-resource-operator/pkg/providers/aws" + "github.com/integr8ly/cloud-resource-operator/pkg/resources" + "github.com/sirupsen/logrus" + "k8s.io/apimachinery/pkg/types" + "time" + + integreatlyv1alpha1 "github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1" + errorUtil "github.com/pkg/errors" + corev1 "k8s.io/api/core/v1" + + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/manager" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + logf "sigs.k8s.io/controller-runtime/pkg/runtime/log" + "sigs.k8s.io/controller-runtime/pkg/source" +) + +var log = logf.Log.WithName("controller_redissnapshot") + +// Add creates a new RedisSnapshot Controller and adds it to the Manager. The Manager will set fields on the Controller +// and Start it when the Manager is Started. +func Add(mgr manager.Manager) error { + return add(mgr, newReconciler(mgr)) +} + +// newReconciler returns a new reconcile.Reconciler +func newReconciler(mgr manager.Manager) reconcile.Reconciler { + logger := logrus.WithFields(logrus.Fields{"controller": "controller_redis_snapshot"}) + return &ReconcileRedisSnapshot{ + client: mgr.GetClient(), + scheme: mgr.GetScheme(), + logger: logger, + ConfigManager: croAws.NewDefaultConfigMapConfigManager(mgr.GetClient()), + CredentialManager: croAws.NewCredentialMinterCredentialManager(mgr.GetClient()), + } +} + +// add adds a new Controller to mgr with r as the reconcile.Reconciler +func add(mgr manager.Manager, r reconcile.Reconciler) error { + // Create a new controller + c, err := controller.New("redissnapshot-controller", mgr, controller.Options{Reconciler: r}) + if err != nil { + return err + } + + // Watch for changes to primary resource RedisSnapshot + err = c.Watch(&source.Kind{Type: &integreatlyv1alpha1.RedisSnapshot{}}, &handler.EnqueueRequestForObject{}) + if err != nil { + return err + } + + // Watch for changes to secondary resource Pods and requeue the owner RedisSnapshot + err = c.Watch(&source.Kind{Type: &corev1.Pod{}}, &handler.EnqueueRequestForOwner{ + IsController: true, + OwnerType: &integreatlyv1alpha1.RedisSnapshot{}, + }) + if err != nil { + return err + } + + return nil +} + +// blank assignment to verify that ReconcileRedisSnapshot implements reconcile.Reconciler +var _ reconcile.Reconciler = &ReconcileRedisSnapshot{} + +// ReconcileRedisSnapshot reconciles a RedisSnapshot object +type ReconcileRedisSnapshot struct { + // This client, initialized using mgr.Client() above, is a split client + // that reads objects from the cache and writes to the apiserver + client client.Client + scheme *runtime.Scheme + logger *logrus.Entry + ConfigManager croAws.ConfigManager + CredentialManager croAws.CredentialManager +} + +// Reconcile reads that state of the cluster for a RedisSnapshot object and makes changes based on the state read +// and what is in the RedisSnapshot.Spec +// The Controller will requeue the Request to be processed again if the returned error is non-nil or +// Result.Requeue is true, otherwise upon completion it will remove the work from the queue. +func (r *ReconcileRedisSnapshot) Reconcile(request reconcile.Request) (reconcile.Result, error) { + r.logger.Info("reconciling redis snapshot") + ctx := context.TODO() + + // Fetch the RedisSnapshot instance + instance := &integreatlyv1alpha1.RedisSnapshot{} + err := r.client.Get(context.TODO(), request.NamespacedName, instance) + if err != nil { + if errors.IsNotFound(err) { + // Request object not found, could have been deleted after reconcile request. + // Owned objects are automatically garbage collected. For additional cleanup logic use finalizers. + // Return and don't requeue + return reconcile.Result{}, nil + } + // Error reading the object - requeue the request. + return reconcile.Result{}, err + } + + // check status, if complete return + if instance.Status.Phase == croType.PhaseComplete { + r.logger.Infof("snapshot for %s exists", instance.Name) + return reconcile.Result{}, nil + } + + // get redis cr + redisCr := &integreatlyv1alpha1.Redis{} + err = r.client.Get(context.TODO(), types.NamespacedName{Name: instance.Spec.ResourceName, Namespace: instance.Namespace}, redisCr) + if err != nil { + errMsg := fmt.Sprintf("failed to get redis cr : %s", err.Error()) + if updateErr := resources.UpdateSnapshotPhase(ctx, r.client, instance, croType.PhaseFailed, croType.StatusMessage(errMsg)); updateErr != nil { + return reconcile.Result{}, updateErr + } + } + + // check redis cr deployment type is aws + if redisCr.Status.Strategy != providers.AWSDeploymentStrategy { + errMsg := "none supported deployment strategy" + if updateErr := resources.UpdateSnapshotPhase(ctx, r.client, instance, croType.PhaseFailed, croType.StatusMessage(errMsg)); updateErr != nil { + return reconcile.Result{}, updateErr + } + return reconcile.Result{}, errorUtil.New(errMsg) + } + + // get resource region + stratCfg, err := r.ConfigManager.ReadStorageStrategy(ctx, providers.RedisResourceType, redisCr.Spec.Tier) + if err != nil { + if updateErr := resources.UpdateSnapshotPhase(ctx, r.client, instance, croType.PhaseFailed, croType.StatusMessage(err.Error())); updateErr != nil { + return reconcile.Result{}, updateErr + } + return reconcile.Result{}, err + } + if stratCfg.Region == "" { + stratCfg.Region = croAws.DefaultRegion + } + + // create the credentials to be used by the aws resource providers, not to be used by end-user + providerCreds, err := r.CredentialManager.ReconcileProviderCredentials(ctx, redisCr.Namespace) + if err != nil { + errMsg := "failed to reconcile elasticache credentials" + if updateErr := resources.UpdateSnapshotPhase(ctx, r.client, instance, croType.PhaseFailed, croType.StatusMessage(errMsg)); updateErr != nil { + return reconcile.Result{}, updateErr + } + return reconcile.Result{}, errorUtil.Wrap(err, errMsg) + } + + // setup aws elasticache cluster sdk session + cacheSvc := elasticache.New(session.Must(session.NewSession(&aws.Config{ + Region: aws.String(stratCfg.Region), + Credentials: credentials.NewStaticCredentials(providerCreds.AccessKeyID, providerCreds.SecretAccessKey, ""), + }))) + + // generate snapshot name + snapshotName, err := croAws.BuildTimestampedInfraNameFromObjectCreation(ctx, r.client, instance.ObjectMeta, croAws.DefaultAwsIdentifierLength) + if err != nil { + errMsg := "failed to generate snapshot name" + if updateErr := resources.UpdateSnapshotPhase(ctx, r.client, instance, croType.PhaseFailed, croType.StatusMessage(errMsg)); updateErr != nil { + return reconcile.Result{}, updateErr + } + return reconcile.Result{}, errorUtil.Wrap(err, errMsg) + } + + // generate cluster name + clusterName, err := croAws.BuildInfraNameFromObject(ctx, r.client, redisCr.ObjectMeta, croAws.DefaultAwsIdentifierLength) + if err != nil { + errMsg := "failed to get cluster name" + if updateErr := resources.UpdateSnapshotPhase(ctx, r.client, instance, croType.PhaseFailed, croType.StatusMessage(errMsg)); updateErr != nil { + return reconcile.Result{}, updateErr + } + return reconcile.Result{}, errorUtil.Wrap(err, "failed to get cluster name") + } + + // check snapshot exists + listOutput, err := cacheSvc.DescribeSnapshots(&elasticache.DescribeSnapshotsInput{ + SnapshotName: aws.String(snapshotName), + }) + var foundSnapshot *elasticache.Snapshot + for _, c := range listOutput.Snapshots { + if *c.SnapshotName == snapshotName { + foundSnapshot = c + break + } + } + + // get replication group + cacheOutput, err := cacheSvc.DescribeReplicationGroups(&elasticache.DescribeReplicationGroupsInput{ + ReplicationGroupId: aws.String(clusterName), + }) + + // ensure replication group is available + if *cacheOutput.ReplicationGroups[0].Status != "available" { + errMsg := fmt.Sprintf("current replication group status is %s", cacheOutput.ReplicationGroups[0].Status) + if updateErr := resources.UpdateSnapshotPhase(ctx, r.client, instance, croType.PhaseFailed, croType.StatusMessage(errMsg)); updateErr != nil { + return reconcile.Result{}, updateErr + } + return reconcile.Result{Requeue: true, RequeueAfter: time.Second * 60}, errorUtil.Wrap(err, "failed to get cluster name") + } + + // find primary cache node + cacheName := "" + for _, i := range cacheOutput.ReplicationGroups[0].NodeGroups[0].NodeGroupMembers { + if *i.CurrentRole == "primary" { + cacheName = *i.CacheClusterId + break + } + } + + // create snapshot of primary cache node + if foundSnapshot == nil { + r.logger.Info("creating elasticache snapshot") + if _, err = cacheSvc.CreateSnapshot(&elasticache.CreateSnapshotInput{ + CacheClusterId: aws.String(cacheName), + SnapshotName: aws.String(snapshotName), + }); err != nil { + errMsg := fmt.Sprintf("error creating elasticache snapshot %s", err) + return reconcile.Result{}, errorUtil.Wrap(err, errMsg) + } + if updateErr := resources.UpdateSnapshotPhase(ctx, r.client, instance, croType.PhaseInProgress, "snapshot creation in progress"); updateErr != nil { + return reconcile.Result{}, updateErr + } + return reconcile.Result{Requeue: true, RequeueAfter: time.Second * 60}, nil + } + + // if snapshot status complete update status + if *foundSnapshot.SnapshotStatus == "available" { + // update complete snapshot phase + if updateErr := resources.UpdateSnapshotPhase(ctx, r.client, instance, croType.PhaseComplete, "snapshot created"); updateErr != nil { + return reconcile.Result{}, err + } + } + + msg := fmt.Sprintf("current snapshot status : %s", *foundSnapshot.SnapshotStatus) + r.logger.Info(msg) + if updateErr := resources.UpdateSnapshotPhase(ctx, r.client, instance, croType.PhaseInProgress, croType.StatusMessage(msg)); updateErr != nil { + return reconcile.Result{}, updateErr + } + return reconcile.Result{Requeue: true, RequeueAfter: time.Second * 60}, nil +} diff --git a/pkg/providers/aws/config.go b/pkg/providers/aws/config.go index 8f98ab715..0fc2bd925 100644 --- a/pkg/providers/aws/config.go +++ b/pkg/providers/aws/config.go @@ -135,7 +135,7 @@ func (m *ConfigMapConfigManager) buildDefaultConfigMap() *v1.ConfigMap { } } -func buildInfraNameFromObject(ctx context.Context, c client.Client, om controllerruntime.ObjectMeta, n int) (string, error) { +func BuildInfraNameFromObject(ctx context.Context, c client.Client, om controllerruntime.ObjectMeta, n int) (string, error) { clusterId, err := resources.GetClusterId(ctx, c) if err != nil { return "", errorUtil.Wrap(err, "failed to retrieve cluster identifier") @@ -151,3 +151,11 @@ func buildTimestampedInfraNameFromObject(ctx context.Context, c client.Client, o curTime := time.Now().Unix() return resources.ShortenString(fmt.Sprintf("%s-%s-%s-%d", clusterId, om.Namespace, om.Name, curTime), n), nil } + +func BuildTimestampedInfraNameFromObjectCreation(ctx context.Context, c client.Client, om controllerruntime.ObjectMeta, n int) (string, error) { + clusterId, err := resources.GetClusterId(ctx, c) + if err != nil { + return "", errorUtil.Wrap(err, "failed to retrieve timestamped cluster identifier") + } + return resources.ShortenString(fmt.Sprintf("%s-%s-%s-%d", clusterId, om.Namespace, om.Name, om.GetObjectMeta().GetCreationTimestamp()), n), nil +} diff --git a/pkg/providers/aws/credentials.go b/pkg/providers/aws/credentials.go index fcebb2ce7..f461e4129 100644 --- a/pkg/providers/aws/credentials.go +++ b/pkg/providers/aws/credentials.go @@ -45,6 +45,7 @@ var ( "elasticache:DescribeReplicationGroups", "elasticache:AddTagsToResource", "elasticache:DescribeSnapshots", + "elasticache:CreateSnapshot", "rds:DescribeDBInstances", "rds:CreateDBInstance", "rds:DeleteDBInstance", diff --git a/pkg/providers/aws/provider_blobstorage.go b/pkg/providers/aws/provider_blobstorage.go index 4f7d9090c..149425346 100644 --- a/pkg/providers/aws/provider_blobstorage.go +++ b/pkg/providers/aws/provider_blobstorage.go @@ -438,7 +438,7 @@ func (p *BlobStorageProvider) buildS3BucketConfig(ctx context.Context, bs *v1alp // cluster infra info p.Logger.Info("getting cluster id from infrastructure for bucket naming") - bucketName, err := buildInfraNameFromObject(ctx, p.Client, bs.ObjectMeta, defaultAwsBucketNameLength) + bucketName, err := BuildInfraNameFromObject(ctx, p.Client, bs.ObjectMeta, defaultAwsBucketNameLength) if err != nil { return nil, nil, nil, errorUtil.Wrapf(err, fmt.Sprintf("failed to retrieve aws s3 bucket config for blob storage instance %s", bs.Name)) } diff --git a/pkg/providers/aws/provider_postgres.go b/pkg/providers/aws/provider_postgres.go index ca38f1775..aa11a7f2a 100644 --- a/pkg/providers/aws/provider_postgres.go +++ b/pkg/providers/aws/provider_postgres.go @@ -36,7 +36,7 @@ import ( const ( postgresProviderName = "aws-rds" - defaultAwsIdentifierLength = 40 + DefaultAwsIdentifierLength = 40 // default create options defaultAwsPostgresDeletionProtection = true defaultAwsPostgresPort = 5432 @@ -503,7 +503,7 @@ func (p *AWSPostgresProvider) buildRDSCreateStrategy(ctx context.Context, pg *v1 rdsCreateConfig.EngineVersion = aws.String(defaultAwsEngineVersion) } } - instanceName, err := buildInfraNameFromObject(ctx, p.Client, pg.ObjectMeta, defaultAwsIdentifierLength) + instanceName, err := BuildInfraNameFromObject(ctx, p.Client, pg.ObjectMeta, DefaultAwsIdentifierLength) if err != nil { return errorUtil.Wrapf(err, "failed to retrieve rds config") } @@ -516,7 +516,7 @@ func (p *AWSPostgresProvider) buildRDSCreateStrategy(ctx context.Context, pg *v1 // verify postgres delete config func (p *AWSPostgresProvider) buildRDSDeleteConfig(ctx context.Context, pg *v1alpha1.Postgres, rdsCreateConfig *rds.CreateDBInstanceInput, rdsDeleteConfig *rds.DeleteDBInstanceInput) error { - instanceIdentifier, err := buildInfraNameFromObject(ctx, p.Client, pg.ObjectMeta, defaultAwsIdentifierLength) + instanceIdentifier, err := BuildInfraNameFromObject(ctx, p.Client, pg.ObjectMeta, DefaultAwsIdentifierLength) if err != nil { return errorUtil.Wrapf(err, "failed to retrieve rds config") } @@ -532,7 +532,7 @@ func (p *AWSPostgresProvider) buildRDSDeleteConfig(ctx context.Context, pg *v1al if rdsDeleteConfig.SkipFinalSnapshot == nil { rdsDeleteConfig.SkipFinalSnapshot = aws.Bool(defaultAwsSkipFinalSnapshot) } - snapshotIdentifier, err := buildTimestampedInfraNameFromObject(ctx, p.Client, pg.ObjectMeta, defaultAwsIdentifierLength) + snapshotIdentifier, err := buildTimestampedInfraNameFromObject(ctx, p.Client, pg.ObjectMeta, DefaultAwsIdentifierLength) if err != nil { return errorUtil.Wrap(err, "failed to retrieve timestamped rds config") } diff --git a/pkg/providers/aws/provider_postgres_test.go b/pkg/providers/aws/provider_postgres_test.go index 5c772c9eb..0c0f480af 100644 --- a/pkg/providers/aws/provider_postgres_test.go +++ b/pkg/providers/aws/provider_postgres_test.go @@ -2,7 +2,6 @@ package aws import ( "context" - "fmt" "github.com/integr8ly/cloud-resource-operator/pkg/providers" "reflect" "testing" @@ -265,8 +264,6 @@ func TestAWSPostgresProvider_createPostgresInstance(t *testing.T) { t.Errorf("createRDSInstance() error = %v, wantErr %v", err, tt.wantErr) return } - fmt.Println(got) - fmt.Println(tt.want) if tt.want != nil && !reflect.DeepEqual(got, tt.want) { t.Errorf("createRDSInstance() got = %+v, want %v", got.DeploymentDetails, tt.want) } diff --git a/pkg/providers/aws/provider_redis.go b/pkg/providers/aws/provider_redis.go index a3999a97c..20394da5c 100644 --- a/pkg/providers/aws/provider_redis.go +++ b/pkg/providers/aws/provider_redis.go @@ -164,6 +164,10 @@ func (p *AWSRedisProvider) createElasticacheCluster(ctx context.Context, r *v1al // add tags to cache nodes cacheInstance := *foundCache.NodeGroups[0] + if *cacheInstance.Status != "available" { + return nil, croType.StatusMessage(fmt.Sprintf("cache node status not available, current status: %s", *foundCache.Status)), nil + } + for _, cache := range cacheInstance.NodeGroupMembers { msg, err := p.TagElasticacheNode(ctx, cacheSvc, stsSvc, r, *stratCfg, cache) if err != nil { @@ -257,7 +261,7 @@ func (p *AWSRedisProvider) TagElasticacheNode(ctx context.Context, cacheSvc elas } _, err = cacheSvc.AddTagsToResource(snapshotInput) if err != nil { - msg := "Failed to add tags to AWS Elasticache Snapshot:" + msg := "Failed to add tags to aws elasticache snapshot:" return types.StatusMessage(msg), err } } @@ -421,7 +425,7 @@ func (p *AWSRedisProvider) buildElasticacheCreateStrategy(ctx context.Context, r if elasticacheConfig.SnapshotRetentionLimit == nil { elasticacheConfig.SnapshotRetentionLimit = aws.Int64(defaultSnapshotRetention) } - cacheName, err := buildInfraNameFromObject(ctx, p.Client, r.ObjectMeta, defaultAwsIdentifierLength) + cacheName, err := BuildInfraNameFromObject(ctx, p.Client, r.ObjectMeta, DefaultAwsIdentifierLength) if err != nil { return errorUtil.Wrapf(err, "failed to retrieve elasticache config") } @@ -433,7 +437,7 @@ func (p *AWSRedisProvider) buildElasticacheCreateStrategy(ctx context.Context, r // buildElasticacheDeleteConfig checks redis config, if none exists sets values to defaults func (p *AWSRedisProvider) buildElasticacheDeleteConfig(ctx context.Context, r v1alpha1.Redis, elasticacheCreateConfig *elasticache.CreateReplicationGroupInput, elasticacheDeleteConfig *elasticache.DeleteReplicationGroupInput) error { - cacheName, err := buildInfraNameFromObject(ctx, p.Client, r.ObjectMeta, defaultAwsIdentifierLength) + cacheName, err := BuildInfraNameFromObject(ctx, p.Client, r.ObjectMeta, DefaultAwsIdentifierLength) if err != nil { return errorUtil.Wrapf(err, "failed to retrieve elasticache config") } @@ -446,7 +450,7 @@ func (p *AWSRedisProvider) buildElasticacheDeleteConfig(ctx context.Context, r v if elasticacheDeleteConfig.RetainPrimaryCluster == nil { elasticacheDeleteConfig.RetainPrimaryCluster = aws.Bool(false) } - snapshotIdentifier, err := buildTimestampedInfraNameFromObject(ctx, p.Client, r.ObjectMeta, defaultAwsIdentifierLength) + snapshotIdentifier, err := buildTimestampedInfraNameFromObject(ctx, p.Client, r.ObjectMeta, DefaultAwsIdentifierLength) if err != nil { return errorUtil.Wrapf(err, "failed to retrieve rds config") } diff --git a/pkg/providers/aws/provider_smtpcredentialset.go b/pkg/providers/aws/provider_smtpcredentialset.go index 538f39032..2a426c1ab 100644 --- a/pkg/providers/aws/provider_smtpcredentialset.go +++ b/pkg/providers/aws/provider_smtpcredentialset.go @@ -117,7 +117,7 @@ func (p *SMTPCredentialProvider) CreateSMTPCredentials(ctx context.Context, smtp // create smtp credentials from generated iam role p.Logger.Info("creating iam role required to send mail through aws ses") - credSecName, err := buildInfraNameFromObject(ctx, p.Client, smtpCreds.ObjectMeta, 40) + credSecName, err := BuildInfraNameFromObject(ctx, p.Client, smtpCreds.ObjectMeta, 40) if err != nil { msg := "failed to generate smtp credentials secret name" return nil, croType.StatusMessage(msg), errorUtil.Wrap(err, msg) diff --git a/pkg/resources/phase.go b/pkg/resources/phase.go index a606e81c7..048fc7c57 100644 --- a/pkg/resources/phase.go +++ b/pkg/resources/phase.go @@ -30,3 +30,22 @@ func UpdatePhase(ctx context.Context, client client.Client, inst runtime.Object, } return nil } + +func UpdateSnapshotPhase(ctx context.Context, client client.Client, inst runtime.Object, phase croType.StatusPhase, msg croType.StatusMessage) error { + if msg == croType.StatusEmpty { + return nil + } + rts := &croType.ResourceTypeSnapshotStatus{} + if err := runtime.Field(reflect.ValueOf(inst).Elem(), "Status", rts); err != nil { + return errorUtil.Wrap(err, "failed to retrieve status block from object") + } + rts.Message = msg + rts.Phase = phase + if err := runtime.SetField(*rts, reflect.ValueOf(inst).Elem(), "Status"); err != nil { + return errorUtil.Wrap(err, "failed to set status block of object") + } + if err := client.Status().Update(ctx, inst); err != nil { + return errorUtil.Wrap(err, "failed to update resource status phase and message") + } + return nil +} From 63f9ccbe0a6e4dab53437a68d1ba2dc348f2ac8d Mon Sep 17 00:00:00 2001 From: ciaranRoche Date: Tue, 14 Jan 2020 09:30:49 +0000 Subject: [PATCH 02/14] fix tagging issue --- README.md | 13 +++++++++++++ .../redissnapshot/redissnapshot_controller.go | 3 ++- pkg/providers/aws/credentials.go | 1 + pkg/providers/aws/provider_redis.go | 15 +++++++++++++++ 4 files changed, 31 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 44109660b..bd5a833a6 100644 --- a/README.md +++ b/README.md @@ -90,6 +90,19 @@ Currently AWS resources are deployed into a separate Virtual Private Cloud (VPC) The two VPCs should now be able to communicate with each other. +## Snapshots +The cloud resource operator supports the taking of arbitrary snapshots in the AWS provider for both `Postgres` and `Redis`. To take a snapshot you must create a `RedisSnapshot` or `PostgresSnapshot` resource, which should reference the `Redis` or `Postgres` resource you wish to create a snapshot of. The snapshot resource must also exist in the same namespace. +``` +apiVersion: integreatly.org/v1alpha1 +kind: RedisSnapshot +metadata: + name: my-redis-snapshot +spec: + # The redis resource name for the snapshot you want to take + resourceName: my-redis-resource + +``` + ## Skip Create The cloud resource operator continuously reconciles using the strat-config as a source of truth for the current state of the provisioned resources. Should these resources alter from the expected the state the operator will update the resources to match the expected state. diff --git a/pkg/controller/redissnapshot/redissnapshot_controller.go b/pkg/controller/redissnapshot/redissnapshot_controller.go index 4ac10e345..90be4cf6c 100644 --- a/pkg/controller/redissnapshot/redissnapshot_controller.go +++ b/pkg/controller/redissnapshot/redissnapshot_controller.go @@ -242,9 +242,10 @@ func (r *ReconcileRedisSnapshot) Reconcile(request reconcile.Request) (reconcile if updateErr := resources.UpdateSnapshotPhase(ctx, r.client, instance, croType.PhaseComplete, "snapshot created"); updateErr != nil { return reconcile.Result{}, err } + return reconcile.Result{}, err } - msg := fmt.Sprintf("current snapshot status : %s", *foundSnapshot.SnapshotStatus) + msg := fmt.Sprintf("current snapshot status : %s", *foundSnapshot.SnapshotStatus) r.logger.Info(msg) if updateErr := resources.UpdateSnapshotPhase(ctx, r.client, instance, croType.PhaseInProgress, croType.StatusMessage(msg)); updateErr != nil { return reconcile.Result{}, updateErr diff --git a/pkg/providers/aws/credentials.go b/pkg/providers/aws/credentials.go index f461e4129..d779465ba 100644 --- a/pkg/providers/aws/credentials.go +++ b/pkg/providers/aws/credentials.go @@ -46,6 +46,7 @@ var ( "elasticache:AddTagsToResource", "elasticache:DescribeSnapshots", "elasticache:CreateSnapshot", + "elasticache:DescribeCacheClusters", "rds:DescribeDBInstances", "rds:CreateDBInstance", "rds:DeleteDBInstance", diff --git a/pkg/providers/aws/provider_redis.go b/pkg/providers/aws/provider_redis.go index 20394da5c..7a6882242 100644 --- a/pkg/providers/aws/provider_redis.go +++ b/pkg/providers/aws/provider_redis.go @@ -188,6 +188,21 @@ func (p *AWSRedisProvider) createElasticacheCluster(ctx context.Context, r *v1al func (p *AWSRedisProvider) TagElasticacheNode(ctx context.Context, cacheSvc elasticacheiface.ElastiCacheAPI, stsSvc stsiface.STSAPI, r *v1alpha1.Redis, stratCfg StrategyConfig, cache *elasticache.NodeGroupMember) (types.StatusMessage, error) { logrus.Info("creating or updating tags on elasticache nodes and snapshots") + // check the node to make sure it is available before applying the tag + // this is needed as the cluster may be available while a node is not + cacheClusterOutput, err := cacheSvc.DescribeCacheClusters(&elasticache.DescribeCacheClustersInput{ + CacheClusterId: aws.String(*cache.CacheClusterId), + }) + if err != nil { + errMsg := "failed to get cache cluster output" + return types.StatusMessage(errMsg), errorUtil.Wrap(err, errMsg) + } + clusterStatus := *cacheClusterOutput.CacheClusters[0].CacheClusterStatus + if clusterStatus != "available" { + errMsg := fmt.Sprintf("%s status is %s, skipping adding tags", *cache.CacheClusterId, clusterStatus) + return types.StatusMessage(errMsg), errorUtil.Wrap(err, errMsg) + } + // get account identity identityInput := &sts.GetCallerIdentityInput{} id, err := stsSvc.GetCallerIdentity(identityInput) From f1e998a6c141b8e84141412744ce07c582bec3e6 Mon Sep 17 00:00:00 2001 From: dimitraz Date: Tue, 14 Jan 2020 18:25:43 +0000 Subject: [PATCH 03/14] initial rds snapshots --- ...egreatly_v1alpha1_postgressnapshot_cr.yaml | 7 + ...greatly_v1alpha1_postgressnapshot_crd.yaml | 52 ++++ deploy/role.yaml | 1 + .../v1alpha1/postgressnapshot_types.go | 48 ++++ .../v1alpha1/zz_generated.deepcopy.go | 93 +++++++ .../v1alpha1/zz_generated.openapi.go | 152 ++++++++--- pkg/controller/add_postgressnapshot.go | 10 + .../postgressnapshot_controller.go | 240 ++++++++++++++++++ pkg/providers/aws/credentials.go | 1 + 9 files changed, 574 insertions(+), 30 deletions(-) create mode 100644 deploy/crds/integreatly_v1alpha1_postgressnapshot_cr.yaml create mode 100644 deploy/crds/integreatly_v1alpha1_postgressnapshot_crd.yaml create mode 100644 pkg/apis/integreatly/v1alpha1/postgressnapshot_types.go create mode 100644 pkg/controller/add_postgressnapshot.go create mode 100644 pkg/controller/postgressnapshot/postgressnapshot_controller.go diff --git a/deploy/crds/integreatly_v1alpha1_postgressnapshot_cr.yaml b/deploy/crds/integreatly_v1alpha1_postgressnapshot_cr.yaml new file mode 100644 index 000000000..43722de85 --- /dev/null +++ b/deploy/crds/integreatly_v1alpha1_postgressnapshot_cr.yaml @@ -0,0 +1,7 @@ +apiVersion: integreatly.org/v1alpha1 +kind: PostgresSnapshot +metadata: + name: example-postgressnapshot +spec: + # The postgres resource name for the snapshot you want to take + resourceName: REPLACE_ME diff --git a/deploy/crds/integreatly_v1alpha1_postgressnapshot_crd.yaml b/deploy/crds/integreatly_v1alpha1_postgressnapshot_crd.yaml new file mode 100644 index 000000000..9ce40a01d --- /dev/null +++ b/deploy/crds/integreatly_v1alpha1_postgressnapshot_crd.yaml @@ -0,0 +1,52 @@ +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + name: postgressnapshots.integreatly.org +spec: + group: integreatly.org + names: + kind: PostgresSnapshot + listKind: PostgresSnapshotList + plural: postgressnapshots + singular: postgressnapshot + scope: Namespaced + subresources: + status: {} + validation: + openAPIV3Schema: + 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/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/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + properties: + resourceName: + description: 'INSERT ADDITIONAL SPEC FIELDS - desired state of cluster + Important: Run "operator-sdk generate k8s" to regenerate code after + modifying this file Add custom validation using kubebuilder tags: + https://book-v1.book.kubebuilder.io/beyond_basics/generating_crd.html' + type: string + required: + - resourceName + type: object + status: + properties: + message: + type: string + phase: + type: string + type: object + version: v1alpha1 + versions: + - name: v1alpha1 + served: true + storage: true diff --git a/deploy/role.yaml b/deploy/role.yaml index f35afb281..63f84656a 100644 --- a/deploy/role.yaml +++ b/deploy/role.yaml @@ -68,6 +68,7 @@ rules: - redis - postgres - redissnapshots + - postgressnapshots verbs: - '*' - apiGroups: diff --git a/pkg/apis/integreatly/v1alpha1/postgressnapshot_types.go b/pkg/apis/integreatly/v1alpha1/postgressnapshot_types.go new file mode 100644 index 000000000..78d4e7609 --- /dev/null +++ b/pkg/apis/integreatly/v1alpha1/postgressnapshot_types.go @@ -0,0 +1,48 @@ +package v1alpha1 + +import ( + "github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1/types" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! +// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. + +// PostgresSnapshotSpec defines the desired state of PostgresSnapshot +// +k8s:openapi-gen=true +type PostgresSnapshotSpec struct { + // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster + // Important: Run "operator-sdk generate k8s" to regenerate code after modifying this file + // Add custom validation using kubebuilder tags: https://book-v1.book.kubebuilder.io/beyond_basics/generating_crd.html + ResourceName string `json:"resourceName"` +} + +// PostgresSnapshotStatus defines the observed state of PostgresSnapshot +// +k8s:openapi-gen=true +type PostgresSnapshotStatus types.ResourceTypeSnapshotStatus + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// PostgresSnapshot is the Schema for the postgressnapshots API +// +k8s:openapi-gen=true +// +kubebuilder:subresource:status +type PostgresSnapshot struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec PostgresSnapshotSpec `json:"spec,omitempty"` + Status PostgresSnapshotStatus `json:"status,omitempty"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// PostgresSnapshotList contains a list of PostgresSnapshot +type PostgresSnapshotList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []PostgresSnapshot `json:"items"` +} + +func init() { + SchemeBuilder.Register(&PostgresSnapshot{}, &PostgresSnapshotList{}) +} diff --git a/pkg/apis/integreatly/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/integreatly/v1alpha1/zz_generated.deepcopy.go index ad457e125..d48a5dff7 100644 --- a/pkg/apis/integreatly/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/integreatly/v1alpha1/zz_generated.deepcopy.go @@ -173,6 +173,99 @@ func (in *PostgresList) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PostgresSnapshot) DeepCopyInto(out *PostgresSnapshot) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + out.Status = in.Status + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PostgresSnapshot. +func (in *PostgresSnapshot) DeepCopy() *PostgresSnapshot { + if in == nil { + return nil + } + out := new(PostgresSnapshot) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *PostgresSnapshot) 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 *PostgresSnapshotList) DeepCopyInto(out *PostgresSnapshotList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]PostgresSnapshot, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PostgresSnapshotList. +func (in *PostgresSnapshotList) DeepCopy() *PostgresSnapshotList { + if in == nil { + return nil + } + out := new(PostgresSnapshotList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *PostgresSnapshotList) 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 *PostgresSnapshotSpec) DeepCopyInto(out *PostgresSnapshotSpec) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PostgresSnapshotSpec. +func (in *PostgresSnapshotSpec) DeepCopy() *PostgresSnapshotSpec { + if in == nil { + return nil + } + out := new(PostgresSnapshotSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PostgresSnapshotStatus) DeepCopyInto(out *PostgresSnapshotStatus) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PostgresSnapshotStatus. +func (in *PostgresSnapshotStatus) DeepCopy() *PostgresSnapshotStatus { + if in == nil { + return nil + } + out := new(PostgresSnapshotStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *PostgresSpec) DeepCopyInto(out *PostgresSpec) { *out = *in diff --git a/pkg/apis/integreatly/v1alpha1/zz_generated.openapi.go b/pkg/apis/integreatly/v1alpha1/zz_generated.openapi.go index 1879cb0d3..78248352b 100644 --- a/pkg/apis/integreatly/v1alpha1/zz_generated.openapi.go +++ b/pkg/apis/integreatly/v1alpha1/zz_generated.openapi.go @@ -11,21 +11,24 @@ import ( func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenAPIDefinition { return map[string]common.OpenAPIDefinition{ - "./pkg/apis/integreatly/v1alpha1.BlobStorage": schema_pkg_apis_integreatly_v1alpha1_BlobStorage(ref), - "./pkg/apis/integreatly/v1alpha1.BlobStorageSpec": schema_pkg_apis_integreatly_v1alpha1_BlobStorageSpec(ref), - "./pkg/apis/integreatly/v1alpha1.BlobStorageStatus": schema_pkg_apis_integreatly_v1alpha1_BlobStorageStatus(ref), - "./pkg/apis/integreatly/v1alpha1.Postgres": schema_pkg_apis_integreatly_v1alpha1_Postgres(ref), - "./pkg/apis/integreatly/v1alpha1.PostgresSpec": schema_pkg_apis_integreatly_v1alpha1_PostgresSpec(ref), - "./pkg/apis/integreatly/v1alpha1.PostgresStatus": schema_pkg_apis_integreatly_v1alpha1_PostgresStatus(ref), - "./pkg/apis/integreatly/v1alpha1.Redis": schema_pkg_apis_integreatly_v1alpha1_Redis(ref), - "./pkg/apis/integreatly/v1alpha1.RedisSnapshot": schema_pkg_apis_integreatly_v1alpha1_RedisSnapshot(ref), - "./pkg/apis/integreatly/v1alpha1.RedisSnapshotSpec": schema_pkg_apis_integreatly_v1alpha1_RedisSnapshotSpec(ref), - "./pkg/apis/integreatly/v1alpha1.RedisSnapshotStatus": schema_pkg_apis_integreatly_v1alpha1_RedisSnapshotStatus(ref), - "./pkg/apis/integreatly/v1alpha1.RedisSpec": schema_pkg_apis_integreatly_v1alpha1_RedisSpec(ref), - "./pkg/apis/integreatly/v1alpha1.RedisStatus": schema_pkg_apis_integreatly_v1alpha1_RedisStatus(ref), - "./pkg/apis/integreatly/v1alpha1.SMTPCredentialSet": schema_pkg_apis_integreatly_v1alpha1_SMTPCredentialSet(ref), - "./pkg/apis/integreatly/v1alpha1.SMTPCredentialSetSpec": schema_pkg_apis_integreatly_v1alpha1_SMTPCredentialSetSpec(ref), - "./pkg/apis/integreatly/v1alpha1.SMTPCredentialSetStatus": schema_pkg_apis_integreatly_v1alpha1_SMTPCredentialSetStatus(ref), + "github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1.BlobStorage": schema_pkg_apis_integreatly_v1alpha1_BlobStorage(ref), + "github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1.BlobStorageSpec": schema_pkg_apis_integreatly_v1alpha1_BlobStorageSpec(ref), + "github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1.BlobStorageStatus": schema_pkg_apis_integreatly_v1alpha1_BlobStorageStatus(ref), + "github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1.Postgres": schema_pkg_apis_integreatly_v1alpha1_Postgres(ref), + "github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1.PostgresSnapshot": schema_pkg_apis_integreatly_v1alpha1_PostgresSnapshot(ref), + "github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1.PostgresSnapshotSpec": schema_pkg_apis_integreatly_v1alpha1_PostgresSnapshotSpec(ref), + "github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1.PostgresSnapshotStatus": schema_pkg_apis_integreatly_v1alpha1_PostgresSnapshotStatus(ref), + "github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1.PostgresSpec": schema_pkg_apis_integreatly_v1alpha1_PostgresSpec(ref), + "github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1.PostgresStatus": schema_pkg_apis_integreatly_v1alpha1_PostgresStatus(ref), + "github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1.Redis": schema_pkg_apis_integreatly_v1alpha1_Redis(ref), + "github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1.RedisSnapshot": schema_pkg_apis_integreatly_v1alpha1_RedisSnapshot(ref), + "github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1.RedisSnapshotSpec": schema_pkg_apis_integreatly_v1alpha1_RedisSnapshotSpec(ref), + "github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1.RedisSnapshotStatus": schema_pkg_apis_integreatly_v1alpha1_RedisSnapshotStatus(ref), + "github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1.RedisSpec": schema_pkg_apis_integreatly_v1alpha1_RedisSpec(ref), + "github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1.RedisStatus": schema_pkg_apis_integreatly_v1alpha1_RedisStatus(ref), + "github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1.SMTPCredentialSet": schema_pkg_apis_integreatly_v1alpha1_SMTPCredentialSet(ref), + "github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1.SMTPCredentialSetSpec": schema_pkg_apis_integreatly_v1alpha1_SMTPCredentialSetSpec(ref), + "github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1.SMTPCredentialSetStatus": schema_pkg_apis_integreatly_v1alpha1_SMTPCredentialSetStatus(ref), } } @@ -56,19 +59,19 @@ func schema_pkg_apis_integreatly_v1alpha1_BlobStorage(ref common.ReferenceCallba }, "spec": { SchemaProps: spec.SchemaProps{ - Ref: ref("./pkg/apis/integreatly/v1alpha1.BlobStorageSpec"), + Ref: ref("github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1.BlobStorageSpec"), }, }, "status": { SchemaProps: spec.SchemaProps{ - Ref: ref("./pkg/apis/integreatly/v1alpha1.BlobStorageStatus"), + Ref: ref("github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1.BlobStorageStatus"), }, }, }, }, }, Dependencies: []string{ - "./pkg/apis/integreatly/v1alpha1.BlobStorageSpec", "./pkg/apis/integreatly/v1alpha1.BlobStorageStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + "github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1.BlobStorageSpec", "github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1.BlobStorageStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, } } @@ -180,19 +183,108 @@ func schema_pkg_apis_integreatly_v1alpha1_Postgres(ref common.ReferenceCallback) }, "spec": { SchemaProps: spec.SchemaProps{ - Ref: ref("./pkg/apis/integreatly/v1alpha1.PostgresSpec"), + Ref: ref("github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1.PostgresSpec"), }, }, "status": { SchemaProps: spec.SchemaProps{ - Ref: ref("./pkg/apis/integreatly/v1alpha1.PostgresStatus"), + Ref: ref("github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1.PostgresStatus"), }, }, }, }, }, Dependencies: []string{ - "./pkg/apis/integreatly/v1alpha1.PostgresSpec", "./pkg/apis/integreatly/v1alpha1.PostgresStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + "github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1.PostgresSpec", "github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1.PostgresStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + } +} + +func schema_pkg_apis_integreatly_v1alpha1_PostgresSnapshot(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "PostgresSnapshot is the Schema for the postgressnapshots API", + 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/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/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), + }, + }, + "spec": { + SchemaProps: spec.SchemaProps{ + Ref: ref("github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1.PostgresSnapshotSpec"), + }, + }, + "status": { + SchemaProps: spec.SchemaProps{ + Ref: ref("github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1.PostgresSnapshotStatus"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1.PostgresSnapshotSpec", "github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1.PostgresSnapshotStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + } +} + +func schema_pkg_apis_integreatly_v1alpha1_PostgresSnapshotSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "PostgresSnapshotSpec defines the desired state of PostgresSnapshot", + Properties: map[string]spec.Schema{ + "resourceName": { + SchemaProps: spec.SchemaProps{ + Description: "INSERT ADDITIONAL SPEC FIELDS - desired state of cluster Important: Run \"operator-sdk generate k8s\" to regenerate code after modifying this file Add custom validation using kubebuilder tags: https://book-v1.book.kubebuilder.io/beyond_basics/generating_crd.html", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"resourceName"}, + }, + }, + Dependencies: []string{}, + } +} + +func schema_pkg_apis_integreatly_v1alpha1_PostgresSnapshotStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "PostgresSnapshotStatus defines the observed state of PostgresSnapshot", + Properties: map[string]spec.Schema{ + "phase": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, + "message": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + Dependencies: []string{}, } } @@ -304,19 +396,19 @@ func schema_pkg_apis_integreatly_v1alpha1_Redis(ref common.ReferenceCallback) co }, "spec": { SchemaProps: spec.SchemaProps{ - Ref: ref("./pkg/apis/integreatly/v1alpha1.RedisSpec"), + Ref: ref("github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1.RedisSpec"), }, }, "status": { SchemaProps: spec.SchemaProps{ - Ref: ref("./pkg/apis/integreatly/v1alpha1.RedisStatus"), + Ref: ref("github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1.RedisStatus"), }, }, }, }, }, Dependencies: []string{ - "./pkg/apis/integreatly/v1alpha1.RedisSpec", "./pkg/apis/integreatly/v1alpha1.RedisStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + "github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1.RedisSpec", "github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1.RedisStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, } } @@ -347,19 +439,19 @@ func schema_pkg_apis_integreatly_v1alpha1_RedisSnapshot(ref common.ReferenceCall }, "spec": { SchemaProps: spec.SchemaProps{ - Ref: ref("./pkg/apis/integreatly/v1alpha1.RedisSnapshotSpec"), + Ref: ref("github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1.RedisSnapshotSpec"), }, }, "status": { SchemaProps: spec.SchemaProps{ - Ref: ref("./pkg/apis/integreatly/v1alpha1.RedisSnapshotStatus"), + Ref: ref("github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1.RedisSnapshotStatus"), }, }, }, }, }, Dependencies: []string{ - "./pkg/apis/integreatly/v1alpha1.RedisSnapshotSpec", "./pkg/apis/integreatly/v1alpha1.RedisSnapshotStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + "github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1.RedisSnapshotSpec", "github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1.RedisSnapshotStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, } } @@ -517,19 +609,19 @@ func schema_pkg_apis_integreatly_v1alpha1_SMTPCredentialSet(ref common.Reference }, "spec": { SchemaProps: spec.SchemaProps{ - Ref: ref("./pkg/apis/integreatly/v1alpha1.SMTPCredentialSetSpec"), + Ref: ref("github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1.SMTPCredentialSetSpec"), }, }, "status": { SchemaProps: spec.SchemaProps{ - Ref: ref("./pkg/apis/integreatly/v1alpha1.SMTPCredentialSetStatus"), + Ref: ref("github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1.SMTPCredentialSetStatus"), }, }, }, }, }, Dependencies: []string{ - "./pkg/apis/integreatly/v1alpha1.SMTPCredentialSetSpec", "./pkg/apis/integreatly/v1alpha1.SMTPCredentialSetStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + "github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1.SMTPCredentialSetSpec", "github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1.SMTPCredentialSetStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, } } diff --git a/pkg/controller/add_postgressnapshot.go b/pkg/controller/add_postgressnapshot.go new file mode 100644 index 000000000..41c23bc35 --- /dev/null +++ b/pkg/controller/add_postgressnapshot.go @@ -0,0 +1,10 @@ +package controller + +import ( + "github.com/integr8ly/cloud-resource-operator/pkg/controller/postgressnapshot" +) + +func init() { + // AddToManagerFuncs is a list of functions to create controllers and add them to a manager. + AddToManagerFuncs = append(AddToManagerFuncs, postgressnapshot.Add) +} diff --git a/pkg/controller/postgressnapshot/postgressnapshot_controller.go b/pkg/controller/postgressnapshot/postgressnapshot_controller.go new file mode 100644 index 000000000..2de7669cf --- /dev/null +++ b/pkg/controller/postgressnapshot/postgressnapshot_controller.go @@ -0,0 +1,240 @@ +package postgressnapshot + +import ( + "context" + "fmt" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/credentials" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/rds" + + "github.com/integr8ly/cloud-resource-operator/pkg/providers" + + integreatlyv1alpha1 "github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1" + croType "github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1/types" + croAws "github.com/integr8ly/cloud-resource-operator/pkg/providers/aws" + "github.com/integr8ly/cloud-resource-operator/pkg/resources" + errorUtil "github.com/pkg/errors" + "github.com/sirupsen/logrus" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/manager" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + "sigs.k8s.io/controller-runtime/pkg/source" +) + +// Add creates a new PostgresSnapshot Controller and adds it to the Manager. The Manager will set fields on the Controller +// and Start it when the Manager is Started. +func Add(mgr manager.Manager) error { + return add(mgr, newReconciler(mgr)) +} + +// newReconciler returns a new reconcile.Reconciler +func newReconciler(mgr manager.Manager) reconcile.Reconciler { + logger := logrus.WithFields(logrus.Fields{"controller": "controller_postgres_snapshot"}) + return &ReconcilePostgresSnapshot{ + client: mgr.GetClient(), + scheme: mgr.GetScheme(), + logger: logger, + ConfigManager: croAws.NewDefaultConfigMapConfigManager(mgr.GetClient()), + CredentialManager: croAws.NewCredentialMinterCredentialManager(mgr.GetClient()), + } +} + +// add adds a new Controller to mgr with r as the reconcile.Reconciler +func add(mgr manager.Manager, r reconcile.Reconciler) error { + // Create a new controller + c, err := controller.New("postgressnapshot-controller", mgr, controller.Options{Reconciler: r}) + if err != nil { + return err + } + + // Watch for changes to primary resource PostgresSnapshot + err = c.Watch(&source.Kind{Type: &integreatlyv1alpha1.PostgresSnapshot{}}, &handler.EnqueueRequestForObject{}) + if err != nil { + return err + } + + // TODO(user): Modify this to be the types you create that are owned by the primary resource + // Watch for changes to secondary resource Pods and requeue the owner PostgresSnapshot + err = c.Watch(&source.Kind{Type: &corev1.Pod{}}, &handler.EnqueueRequestForOwner{ + IsController: true, + OwnerType: &integreatlyv1alpha1.PostgresSnapshot{}, + }) + if err != nil { + return err + } + + return nil +} + +// blank assignment to verify that ReconcilePostgresSnapshot implements reconcile.Reconciler +var _ reconcile.Reconciler = &ReconcilePostgresSnapshot{} + +// ReconcilePostgresSnapshot reconciles a PostgresSnapshot object +type ReconcilePostgresSnapshot struct { + // This client, initialized using mgr.Client() above, is a split client + // that reads objects from the cache and writes to the apiserver + client client.Client + scheme *runtime.Scheme + logger *logrus.Entry + ConfigManager croAws.ConfigManager + CredentialManager croAws.CredentialManager +} + +// Reconcile reads that state of the cluster for a PostgresSnapshot object and makes changes based on the state read +// and what is in the PostgresSnapshot.Spec +// TODO(user): Modify this Reconcile function to implement your Controller logic. This example creates +// a Pod as an example +// Note: +// The Controller will requeue the Request to be processed again if the returned error is non-nil or +// Result.Requeue is true, otherwise upon completion it will remove the work from the queue. +func (r *ReconcilePostgresSnapshot) Reconcile(request reconcile.Request) (reconcile.Result, error) { + r.logger.Info("reconciling postgres snapshot") + ctx := context.TODO() + + // Fetch the PostgresSnapshot instance + instance := &integreatlyv1alpha1.PostgresSnapshot{} + err := r.client.Get(context.TODO(), request.NamespacedName, instance) + if err != nil { + if errors.IsNotFound(err) { + // Request object not found, could have been deleted after reconcile request. + // Owned objects are automatically garbage collected. For additional cleanup logic use finalizers. + // Return and don't requeue + return reconcile.Result{}, nil + } + // Error reading the object - requeue the request. + return reconcile.Result{}, err + } + + // check status, if complete return + if instance.Status.Phase == croType.PhaseComplete { + r.logger.Infof("found existing rds snapshot for %s", instance.Name) + return reconcile.Result{}, nil + } + + // get postgres cr + postgresCr := &integreatlyv1alpha1.Postgres{} + err = r.client.Get(context.TODO(), types.NamespacedName{Name: instance.Spec.ResourceName, Namespace: instance.Namespace}, postgresCr) + if err != nil { + errMsg := fmt.Sprintf("failed to get postgres resource: %s", err.Error()) + if updateErr := resources.UpdateSnapshotPhase(ctx, r.client, instance, croType.PhaseFailed, croType.StatusMessage(errMsg)); updateErr != nil { + return reconcile.Result{}, updateErr + } + return reconcile.Result{}, errorUtil.New(errMsg) + } + + // check postgres deployment strategy is aws + if postgresCr.Status.Strategy != providers.AWSDeploymentStrategy { + errMsg := fmt.Sprintf("deployment strategy '%s' is not supported", postgresCr.Status.Strategy) + if updateErr := resources.UpdateSnapshotPhase(ctx, r.client, instance, croType.PhaseFailed, croType.StatusMessage(errMsg)); updateErr != nil { + return reconcile.Result{}, updateErr + } + return reconcile.Result{}, errorUtil.New(errMsg) + } + + // get resource region + stratCfg, err := r.ConfigManager.ReadStorageStrategy(ctx, providers.PostgresResourceType, postgresCr.Spec.Tier) + if err != nil { + if updateErr := resources.UpdateSnapshotPhase(ctx, r.client, instance, croType.PhaseFailed, croType.StatusMessage(err.Error())); updateErr != nil { + return reconcile.Result{}, updateErr + } + return reconcile.Result{}, err + } + if stratCfg.Region == "" { + stratCfg.Region = croAws.DefaultRegion + } + + // create the credentials to be used by the aws resource providers, not to be used by end-user + providerCreds, err := r.CredentialManager.ReconcileProviderCredentials(ctx, postgresCr.Namespace) + if err != nil { + errMsg := "failed to reconcile rds credentials" + if updateErr := resources.UpdateSnapshotPhase(ctx, r.client, instance, croType.PhaseFailed, croType.StatusMessage(errMsg)); updateErr != nil { + return reconcile.Result{}, updateErr + } + return reconcile.Result{}, errorUtil.Wrap(err, errMsg) + } + + // setup aws rds session + rdsSvc := rds.New(session.Must(session.NewSession(&aws.Config{ + Region: aws.String(stratCfg.Region), + Credentials: credentials.NewStaticCredentials(providerCreds.AccessKeyID, providerCreds.SecretAccessKey, ""), + }))) + + // generate snapshot name + snapshotName, err := croAws.BuildTimestampedInfraNameFromObjectCreation(ctx, r.client, instance.ObjectMeta, croAws.DefaultAwsIdentifierLength) + if err != nil { + errMsg := "failed to generate snapshot name" + if updateErr := resources.UpdateSnapshotPhase(ctx, r.client, instance, croType.PhaseFailed, croType.StatusMessage(errMsg)); updateErr != nil { + return reconcile.Result{}, updateErr + } + return reconcile.Result{}, errorUtil.Wrap(err, errMsg) + } + + // get instance name + instanceName, err := croAws.BuildInfraNameFromObject(ctx, r.client, postgresCr.ObjectMeta, croAws.DefaultAwsIdentifierLength) + if err != nil { + errMsg := "failed to get cluster name" + if updateErr := resources.UpdateSnapshotPhase(ctx, r.client, instance, croType.PhaseFailed, croType.StatusMessage(errMsg)); updateErr != nil { + return reconcile.Result{}, updateErr + } + return reconcile.Result{}, errorUtil.Wrap(err, errMsg) + } + + // check snapshot exists + listOutput, err := rdsSvc.DescribeDBSnapshots(&rds.DescribeDBSnapshotsInput{ + DBSnapshotIdentifier: aws.String(snapshotName), + }) + var foundSnapshot *rds.DBSnapshot + for _, c := range listOutput.DBSnapshots { + if *c.DBSnapshotIdentifier == snapshotName { + foundSnapshot = c + break + } + } + + // create snapshot of the rds instance + if foundSnapshot == nil { + r.logger.Info("creating rds snapshot") + _, err = rdsSvc.CreateDBSnapshot(&rds.CreateDBSnapshotInput{ + DBInstanceIdentifier: aws.String(instanceName), + DBSnapshotIdentifier: aws.String(snapshotName), + }) + if err != nil { + errMsg := fmt.Sprintf("error creating rds snapshot %s", err) + if updateErr := resources.UpdateSnapshotPhase(ctx, r.client, instance, croType.PhaseFailed, croType.StatusMessage(errMsg)); updateErr != nil { + return reconcile.Result{}, updateErr + } + return reconcile.Result{}, errorUtil.Wrap(err, errMsg) + } + + // creation in progress + if updateErr := resources.UpdateSnapshotPhase(ctx, r.client, instance, croType.PhaseInProgress, "snapshot creation in progress"); updateErr != nil { + return reconcile.Result{}, updateErr + } + return reconcile.Result{Requeue: true, RequeueAfter: time.Second * 60}, nil + } + + // if snapshot status complete update status + if *foundSnapshot.Status == "available" { + // update complete snapshot phase + if updateErr := resources.UpdateSnapshotPhase(ctx, r.client, instance, croType.PhaseComplete, "snapshot created"); updateErr != nil { + return reconcile.Result{}, err + } + return reconcile.Result{}, err + } + + msg := fmt.Sprintf("current postgres snapshot status is: %s", *foundSnapshot.Status) + r.logger.Info(msg) + if updateErr := resources.UpdateSnapshotPhase(ctx, r.client, instance, croType.PhaseInProgress, croType.StatusMessage(msg)); updateErr != nil { + return reconcile.Result{}, updateErr + } + return reconcile.Result{Requeue: true, RequeueAfter: time.Second * 60}, nil +} diff --git a/pkg/providers/aws/credentials.go b/pkg/providers/aws/credentials.go index d779465ba..f110dcea8 100644 --- a/pkg/providers/aws/credentials.go +++ b/pkg/providers/aws/credentials.go @@ -53,6 +53,7 @@ var ( "rds:ModifyDBInstance", "rds:AddTagsToResource", "rds:DescribeDBSnapshots", + "rds:CreateDBSnapshot", "sts:GetCallerIdentity", }, Resource: "*", From ed6233575045683da5b55086e25f42bd73fd7b47 Mon Sep 17 00:00:00 2001 From: dimitraz Date: Tue, 14 Jan 2020 20:52:15 +0000 Subject: [PATCH 04/14] fix tests --- go.mod | 4 +++- go.sum | 11 +++++++++++ .../redissnapshot/redissnapshot_controller.go | 5 +++-- pkg/providers/aws/config.go | 5 +++-- pkg/providers/aws/provider_redis.go | 2 +- pkg/providers/aws/provider_redis_test.go | 15 +++++++++++++++ 6 files changed, 36 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 10cfb97e2..45b4a0561 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.13 require ( github.com/aws/aws-sdk-go v1.23.17 github.com/coreos/prometheus-operator v0.29.0 + github.com/fatih/color v1.9.0 // indirect github.com/go-openapi/spec v0.19.0 github.com/gogo/protobuf v1.3.0 // indirect github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 // indirect @@ -21,12 +22,13 @@ require ( github.com/prometheus/client_golang v1.1.0 // indirect github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 // indirect github.com/prometheus/procfs v0.0.4 // indirect + github.com/rakyll/gotest v0.0.0-20191108192113-45d501058f2a // indirect github.com/sirupsen/logrus v1.4.2 github.com/spf13/pflag v1.0.3 github.com/stretchr/testify v1.4.0 // indirect golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392 // indirect golang.org/x/net v0.0.0-20191003171128-d98b1b443823 // indirect - golang.org/x/sys v0.0.0-20200107162124-548cf772de50 // indirect + golang.org/x/sys v0.0.0-20200113162924-86b910548bc1 // indirect google.golang.org/appengine v1.6.2 // indirect gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect k8s.io/api v0.0.0 diff --git a/go.sum b/go.sum index 228877efa..f171563ec 100644 --- a/go.sum +++ b/go.sum @@ -139,6 +139,8 @@ github.com/fatih/camelcase v0.0.0-20160318181535-f6a740d52f96/go.mod h1:yN2Sb0lF github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc= github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s= +github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= @@ -357,8 +359,12 @@ github.com/martinlindhe/base36 v0.0.0-20180729042928-5cda0030da17/go.mod h1:+AtE github.com/mattbaird/jsonpatch v0.0.0-20171005235357-81af80346b1a/go.mod h1:M1qoD/MqPgTZIk0EWKB38wE28ACRfVcn+cU08jyArI0= github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM= +github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-shellwords v0.0.0-20180605041737-f8471b0a71de/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= @@ -481,6 +487,8 @@ github.com/prometheus/procfs v0.0.4 h1:w8DjqFMJDjuVwdZBQoOozr4MVWOnwF7RcL/7uxBjY github.com/prometheus/procfs v0.0.4/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/quobyte/api v0.1.2/go.mod h1:jL7lIHrmqQ7yh05OJ+eEEdHr0u/kmT1Ff9iHd+4H6VI= +github.com/rakyll/gotest v0.0.0-20191108192113-45d501058f2a h1:/kX+lZpr87Pb0yJKyxW40ZZO6jl52jFMmoQ0YhfwGLM= +github.com/rakyll/gotest v0.0.0-20191108192113-45d501058f2a/go.mod h1:jpFrc1UTqK0FtfF3doi3pEUBgWHYELkOPPECUlDsM2Q= github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M= github.com/robfig/cron v0.0.0-20170309132418-df38d32658d8/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k= github.com/robfig/cron v0.0.0-20170526150127-736158dc09e1/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k= @@ -655,8 +663,11 @@ golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190922100055-0a153f010e69 h1:rOhMmluY6kLMhdnrivzec6lLgaVbMHMn2ISQXJeJ5EM= golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200107162124-548cf772de50 h1:YvQ10rzcqWXLlJZ3XCUoO25savxmscf4+SC+ZqiCHhA= golang.org/x/sys v0.0.0-20200107162124-548cf772de50/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1 h1:gZpLHxUX5BdYLA08Lj4YCJNN/jk7KtquiArPoeX0WvA= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= diff --git a/pkg/controller/redissnapshot/redissnapshot_controller.go b/pkg/controller/redissnapshot/redissnapshot_controller.go index 90be4cf6c..e1c46bf1e 100644 --- a/pkg/controller/redissnapshot/redissnapshot_controller.go +++ b/pkg/controller/redissnapshot/redissnapshot_controller.go @@ -3,6 +3,8 @@ package redissnapshot import ( "context" "fmt" + "time" + "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/credentials" "github.com/aws/aws-sdk-go/aws/session" @@ -13,7 +15,6 @@ import ( "github.com/integr8ly/cloud-resource-operator/pkg/resources" "github.com/sirupsen/logrus" "k8s.io/apimachinery/pkg/types" - "time" integreatlyv1alpha1 "github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1" errorUtil "github.com/pkg/errors" @@ -204,7 +205,7 @@ func (r *ReconcileRedisSnapshot) Reconcile(request reconcile.Request) (reconcile // ensure replication group is available if *cacheOutput.ReplicationGroups[0].Status != "available" { - errMsg := fmt.Sprintf("current replication group status is %s", cacheOutput.ReplicationGroups[0].Status) + errMsg := fmt.Sprintf("current replication group status is %s", *cacheOutput.ReplicationGroups[0].Status) if updateErr := resources.UpdateSnapshotPhase(ctx, r.client, instance, croType.PhaseFailed, croType.StatusMessage(errMsg)); updateErr != nil { return reconcile.Result{}, updateErr } diff --git a/pkg/providers/aws/config.go b/pkg/providers/aws/config.go index 0fc2bd925..9bf72eda7 100644 --- a/pkg/providers/aws/config.go +++ b/pkg/providers/aws/config.go @@ -4,9 +4,10 @@ import ( "context" "encoding/json" "fmt" - "github.com/operator-framework/operator-sdk/pkg/k8sutil" "time" + "github.com/operator-framework/operator-sdk/pkg/k8sutil" + "github.com/integr8ly/cloud-resource-operator/pkg/resources" controllerruntime "sigs.k8s.io/controller-runtime" @@ -157,5 +158,5 @@ func BuildTimestampedInfraNameFromObjectCreation(ctx context.Context, c client.C if err != nil { return "", errorUtil.Wrap(err, "failed to retrieve timestamped cluster identifier") } - return resources.ShortenString(fmt.Sprintf("%s-%s-%s-%d", clusterId, om.Namespace, om.Name, om.GetObjectMeta().GetCreationTimestamp()), n), nil + return resources.ShortenString(fmt.Sprintf("%s-%s-%s-%s", clusterId, om.Namespace, om.Name, om.GetObjectMeta().GetCreationTimestamp()), n), nil } diff --git a/pkg/providers/aws/provider_redis.go b/pkg/providers/aws/provider_redis.go index 7a6882242..dcfc46a83 100644 --- a/pkg/providers/aws/provider_redis.go +++ b/pkg/providers/aws/provider_redis.go @@ -191,7 +191,7 @@ func (p *AWSRedisProvider) TagElasticacheNode(ctx context.Context, cacheSvc elas // check the node to make sure it is available before applying the tag // this is needed as the cluster may be available while a node is not cacheClusterOutput, err := cacheSvc.DescribeCacheClusters(&elasticache.DescribeCacheClustersInput{ - CacheClusterId: aws.String(*cache.CacheClusterId), + CacheClusterId: cache.CacheClusterId, }) if err != nil { errMsg := "failed to get cache cluster output" diff --git a/pkg/providers/aws/provider_redis_test.go b/pkg/providers/aws/provider_redis_test.go index 94c1d356a..a4162a85e 100644 --- a/pkg/providers/aws/provider_redis_test.go +++ b/pkg/providers/aws/provider_redis_test.go @@ -80,6 +80,20 @@ func (m *mockElasticacheClient) DescribeSnapshots(*elasticache.DescribeSnapshots return &elasticache.DescribeSnapshotsOutput{}, nil } +// mock elasticache DescribeReplicationGroups output +func (m *mockElasticacheClient) DescribeCacheClusters(*elasticache.DescribeCacheClustersInput) (*elasticache.DescribeCacheClustersOutput, error) { + if m.wantEmpty { + return &elasticache.DescribeCacheClustersOutput{}, nil + } + return &elasticache.DescribeCacheClustersOutput{ + CacheClusters: []*elasticache.CacheCluster{ + { + CacheClusterStatus: aws.String("available"), + }, + }, + }, nil +} + // mock sts get caller identity func (m *mockStsClient) GetCallerIdentity(*sts.GetCallerIdentityInput) (*sts.GetCallerIdentityOutput, error) { return &sts.GetCallerIdentityOutput{ @@ -120,6 +134,7 @@ func buildReplicationGroupReady() []*elasticache.ReplicationGroup { Address: testAddress, Port: testPort, }, + Status: aws.String("available"), }, }, }, From 43839d3c61e834a39ce136a04c9da14c01490395 Mon Sep 17 00:00:00 2001 From: dimitraz Date: Wed, 15 Jan 2020 10:20:18 +0000 Subject: [PATCH 05/14] refactor, add unit tests --- .../postgressnapshot_controller.go | 73 ++++----- .../postgressnapshot_controller_test.go | 154 ++++++++++++++++++ 2 files changed, 186 insertions(+), 41 deletions(-) create mode 100644 pkg/controller/postgressnapshot/postgressnapshot_controller_test.go diff --git a/pkg/controller/postgressnapshot/postgressnapshot_controller.go b/pkg/controller/postgressnapshot/postgressnapshot_controller.go index 2de7669cf..194193140 100644 --- a/pkg/controller/postgressnapshot/postgressnapshot_controller.go +++ b/pkg/controller/postgressnapshot/postgressnapshot_controller.go @@ -5,6 +5,8 @@ import ( "fmt" "time" + "github.com/aws/aws-sdk-go/service/rds/rdsiface" + "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/credentials" "github.com/aws/aws-sdk-go/aws/session" @@ -168,24 +170,30 @@ func (r *ReconcilePostgresSnapshot) Reconcile(request reconcile.Request) (reconc Credentials: credentials.NewStaticCredentials(providerCreds.AccessKeyID, providerCreds.SecretAccessKey, ""), }))) + // create the snapshot and return the phase + phase, msg, err := r.createSnapshot(ctx, rdsSvc, instance, postgresCr) + if updateErr := resources.UpdateSnapshotPhase(ctx, r.client, instance, phase, msg); updateErr != nil { + return reconcile.Result{}, updateErr + } + if err != nil { + return reconcile.Result{}, err + } + return reconcile.Result{Requeue: true, RequeueAfter: time.Second * 60}, nil +} + +func (r *ReconcilePostgresSnapshot) createSnapshot(ctx context.Context, rdsSvc rdsiface.RDSAPI, snapshot *integreatlyv1alpha1.PostgresSnapshot, postgres *integreatlyv1alpha1.Postgres) (croType.StatusPhase, croType.StatusMessage, error) { // generate snapshot name - snapshotName, err := croAws.BuildTimestampedInfraNameFromObjectCreation(ctx, r.client, instance.ObjectMeta, croAws.DefaultAwsIdentifierLength) + snapshotName, err := croAws.BuildTimestampedInfraNameFromObjectCreation(ctx, r.client, snapshot.ObjectMeta, croAws.DefaultAwsIdentifierLength) if err != nil { errMsg := "failed to generate snapshot name" - if updateErr := resources.UpdateSnapshotPhase(ctx, r.client, instance, croType.PhaseFailed, croType.StatusMessage(errMsg)); updateErr != nil { - return reconcile.Result{}, updateErr - } - return reconcile.Result{}, errorUtil.Wrap(err, errMsg) + return croType.PhaseFailed, croType.StatusMessage(errMsg), errorUtil.Wrap(err, errMsg) } // get instance name - instanceName, err := croAws.BuildInfraNameFromObject(ctx, r.client, postgresCr.ObjectMeta, croAws.DefaultAwsIdentifierLength) + instanceName, err := croAws.BuildInfraNameFromObject(ctx, r.client, postgres.ObjectMeta, croAws.DefaultAwsIdentifierLength) if err != nil { errMsg := "failed to get cluster name" - if updateErr := resources.UpdateSnapshotPhase(ctx, r.client, instance, croType.PhaseFailed, croType.StatusMessage(errMsg)); updateErr != nil { - return reconcile.Result{}, updateErr - } - return reconcile.Result{}, errorUtil.Wrap(err, errMsg) + return croType.PhaseFailed, croType.StatusMessage(errMsg), errorUtil.Wrap(err, errMsg) } // check snapshot exists @@ -201,40 +209,23 @@ func (r *ReconcilePostgresSnapshot) Reconcile(request reconcile.Request) (reconc } // create snapshot of the rds instance - if foundSnapshot == nil { - r.logger.Info("creating rds snapshot") - _, err = rdsSvc.CreateDBSnapshot(&rds.CreateDBSnapshotInput{ - DBInstanceIdentifier: aws.String(instanceName), - DBSnapshotIdentifier: aws.String(snapshotName), - }) - if err != nil { - errMsg := fmt.Sprintf("error creating rds snapshot %s", err) - if updateErr := resources.UpdateSnapshotPhase(ctx, r.client, instance, croType.PhaseFailed, croType.StatusMessage(errMsg)); updateErr != nil { - return reconcile.Result{}, updateErr - } - return reconcile.Result{}, errorUtil.Wrap(err, errMsg) + if foundSnapshot != nil { + // if snapshot status complete update status + if *foundSnapshot.Status == "available" { + return croType.PhaseComplete, "snapshot created", nil } - - // creation in progress - if updateErr := resources.UpdateSnapshotPhase(ctx, r.client, instance, croType.PhaseInProgress, "snapshot creation in progress"); updateErr != nil { - return reconcile.Result{}, updateErr - } - return reconcile.Result{Requeue: true, RequeueAfter: time.Second * 60}, nil } - // if snapshot status complete update status - if *foundSnapshot.Status == "available" { - // update complete snapshot phase - if updateErr := resources.UpdateSnapshotPhase(ctx, r.client, instance, croType.PhaseComplete, "snapshot created"); updateErr != nil { - return reconcile.Result{}, err - } - return reconcile.Result{}, err + r.logger.Info("creating rds snapshot") + _, err = rdsSvc.CreateDBSnapshot(&rds.CreateDBSnapshotInput{ + DBInstanceIdentifier: aws.String(instanceName), + DBSnapshotIdentifier: aws.String(snapshotName), + }) + if err != nil { + errMsg := "error creating rds snapshot" + return croType.PhaseFailed, croType.StatusMessage(fmt.Sprintf("error creating rds snapshot %s", errMsg)), errorUtil.Wrap(err, errMsg) } - msg := fmt.Sprintf("current postgres snapshot status is: %s", *foundSnapshot.Status) - r.logger.Info(msg) - if updateErr := resources.UpdateSnapshotPhase(ctx, r.client, instance, croType.PhaseInProgress, croType.StatusMessage(msg)); updateErr != nil { - return reconcile.Result{}, updateErr - } - return reconcile.Result{Requeue: true, RequeueAfter: time.Second * 60}, nil + // creation in progress + return croType.PhaseInProgress, "snapshot creation in progress", nil } diff --git a/pkg/controller/postgressnapshot/postgressnapshot_controller_test.go b/pkg/controller/postgressnapshot/postgressnapshot_controller_test.go new file mode 100644 index 000000000..9acc04997 --- /dev/null +++ b/pkg/controller/postgressnapshot/postgressnapshot_controller_test.go @@ -0,0 +1,154 @@ +package postgressnapshot + +import ( + "context" + "testing" + + v12 "github.com/openshift/api/config/v1" + controllerruntime "sigs.k8s.io/controller-runtime" + + "github.com/integr8ly/cloud-resource-operator/pkg/apis" + v1 "k8s.io/api/core/v1" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/rds" + + "github.com/aws/aws-sdk-go/service/rds/rdsiface" + integreatlyv1alpha1 "github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1" + "github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1/types" + croAws "github.com/integr8ly/cloud-resource-operator/pkg/providers/aws" + "github.com/sirupsen/logrus" + "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" +) + +var testLogger = logrus.WithFields(logrus.Fields{"testing": "true"}) + +type mockRdsClient struct { + rdsiface.RDSAPI + wantErrList bool + wantErrCreate bool + wantErrDelete bool + dbSnapshots []*rds.DBSnapshot + dbSnapshot *rds.DBSnapshot +} + +func buildTestScheme() (*runtime.Scheme, error) { + scheme := runtime.NewScheme() + //err := apis2.AddToScheme(scheme) + //err = confv1.AddToScheme(scheme) + err := v1.AddToScheme(scheme) + err = apis.AddToScheme(scheme) + if err != nil { + return nil, err + } + return scheme, nil +} + +func buildTestInfrastructure() *v12.Infrastructure { + return &v12.Infrastructure{ + ObjectMeta: controllerruntime.ObjectMeta{ + Name: "cluster", + }, + Status: v12.InfrastructureStatus{ + InfrastructureName: "test", + }, + } +} + +func (m *mockRdsClient) DescribeDBSnapshots(*rds.DescribeDBSnapshotsInput) (*rds.DescribeDBSnapshotsOutput, error) { + return &rds.DescribeDBSnapshotsOutput{ + DBSnapshots: m.dbSnapshots, + }, nil +} + +func (m *mockRdsClient) CreateDBSnapshot(*rds.CreateDBSnapshotInput) (*rds.CreateDBSnapshotOutput, error) { + return &rds.CreateDBSnapshotOutput{ + DBSnapshot: m.dbSnapshot, + }, nil +} + +func TestReconcilePostgresSnapshot_createSnapshot(t *testing.T) { + //ctx := context.TODO() + //snapshotName, err := croAws.BuildTimestampedInfraNameFromObjectCreation(ctx, r.client, snapshot.ObjectMeta, croAws.DefaultAwsIdentifierLength) + // + //instanceName, err := croAws.BuildInfraNameFromObject(ctx, r.client, postgres.ObjectMeta, croAws.DefaultAwsIdentifierLength) + scheme, err := buildTestScheme() + if err != nil { + logrus.Fatal(err) + t.Fatal("failed to build scheme", err) + } + + type fields struct { + client client.Client + scheme *runtime.Scheme + logger *logrus.Entry + ConfigManager croAws.ConfigManager + CredentialManager croAws.CredentialManager + } + type args struct { + ctx context.Context + rdsSvc rdsiface.RDSAPI + snapshot *integreatlyv1alpha1.PostgresSnapshot + postgres *integreatlyv1alpha1.Postgres + } + tests := []struct { + name string + fields fields + args args + want types.StatusPhase + wantErr bool + }{ + { + name: "test successful snapshot create", + args: args{ + ctx: context.TODO(), + rdsSvc: &mockRdsClient{dbSnapshot: &rds.DBSnapshot{ + DBInstanceIdentifier: aws.String("rds-db"), + }}, + snapshot: &integreatlyv1alpha1.PostgresSnapshot{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "test", + }, + }, + postgres: &integreatlyv1alpha1.Postgres{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "test", + }, + }, + }, + fields: fields{ + client: fake.NewFakeClientWithScheme(scheme, buildTestInfrastructure()), + scheme: scheme, + logger: testLogger, + CredentialManager: nil, + ConfigManager: nil, + }, + want: types.PhaseInProgress, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := &ReconcilePostgresSnapshot{ + client: tt.fields.client, + scheme: tt.fields.scheme, + logger: tt.fields.logger, + ConfigManager: tt.fields.ConfigManager, + CredentialManager: tt.fields.CredentialManager, + } + got, _, err := r.createSnapshot(tt.args.ctx, tt.args.rdsSvc, tt.args.snapshot, tt.args.postgres) + if (err != nil) != tt.wantErr { + t.Errorf("createSnapshot() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("createSnapshot() got = %v, want %v", got, tt.want) + } + }) + } +} From c967ac2d5c73a17794199fea85123fad4965a63b Mon Sep 17 00:00:00 2001 From: ciaranRoche Date: Wed, 15 Jan 2020 10:33:31 +0000 Subject: [PATCH 06/14] update makefile --- Makefile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Makefile b/Makefile index 63ab160e8..ea0d474b5 100644 --- a/Makefile +++ b/Makefile @@ -74,6 +74,7 @@ cluster/prepare: oc create -f ./deploy/crds/integreatly_v1alpha1_redis_crd.yaml -n $(NAMESPACE) oc create -f ./deploy/crds/integreatly_v1alpha1_postgres_crd.yaml -n $(NAMESPACE) oc create -f ./deploy/crds/integreatly_v1alpha1_redissnapshot_crd.yaml -n $(NAMESPACE) + oc create -f ./deploy/crds/integreatly_v1alpha1_postgressnapshot_crd.yaml -n $(NAMESPACE) oc create -f ./deploy/service_account.yaml -n $(NAMESPACE) oc create -f ./deploy/role.yaml -n $(NAMESPACE) oc create -f ./deploy/role_binding.yaml -n $(NAMESPACE) @@ -118,6 +119,8 @@ cluster/clean: oc delete -f ./deploy/crds/integreatly_v1alpha1_smtpcredentialset_crd.yaml -n $(NAMESPACE) oc delete -f ./deploy/crds/integreatly_v1alpha1_redis_crd.yaml -n $(NAMESPACE) oc delete -f ./deploy/crds/integreatly_v1alpha1_postgres_crd.yaml -n $(NAMESPACE) + oc delete -f ./deploy/crds/integreatly_v1alpha1_redissnapshot_crd.yaml -n $(NAMESPACE) + oc delete -f ./deploy/crds/integreatly_v1alpha1_postgressnapshot_crd.yaml -n $(NAMESPACE) oc delete -f ./deploy/service_account.yaml -n $(NAMESPACE) oc delete -f ./deploy/role.yaml -n $(NAMESPACE) oc delete -f ./deploy/role_binding.yaml -n $(NAMESPACE) From 2b226305491b085a69651d24695b29ad610ea5bd Mon Sep 17 00:00:00 2001 From: ciaranRoche Date: Wed, 15 Jan 2020 11:24:08 +0000 Subject: [PATCH 07/14] refactor redis snapshot --- README.md | 1 + .../postgressnapshot_controller.go | 31 +++++------ .../redissnapshot/redissnapshot_controller.go | 53 ++++++++----------- 3 files changed, 36 insertions(+), 49 deletions(-) diff --git a/README.md b/README.md index bd5a833a6..24be3f54a 100644 --- a/README.md +++ b/README.md @@ -102,6 +102,7 @@ spec: resourceName: my-redis-resource ``` +*Note* You may experience some downtime in the resource during the creation of the Snapshot ## Skip Create The cloud resource operator continuously reconciles using the strat-config as a source of truth for the current state of the provisioned resources. Should these resources alter from the expected the state the operator will update the resources to match the expected state. diff --git a/pkg/controller/postgressnapshot/postgressnapshot_controller.go b/pkg/controller/postgressnapshot/postgressnapshot_controller.go index 194193140..cc1123722 100644 --- a/pkg/controller/postgressnapshot/postgressnapshot_controller.go +++ b/pkg/controller/postgressnapshot/postgressnapshot_controller.go @@ -64,7 +64,6 @@ func add(mgr manager.Manager, r reconcile.Reconciler) error { return err } - // TODO(user): Modify this to be the types you create that are owned by the primary resource // Watch for changes to secondary resource Pods and requeue the owner PostgresSnapshot err = c.Watch(&source.Kind{Type: &corev1.Pod{}}, &handler.EnqueueRequestForOwner{ IsController: true, @@ -93,11 +92,6 @@ type ReconcilePostgresSnapshot struct { // Reconcile reads that state of the cluster for a PostgresSnapshot object and makes changes based on the state read // and what is in the PostgresSnapshot.Spec -// TODO(user): Modify this Reconcile function to implement your Controller logic. This example creates -// a Pod as an example -// Note: -// The Controller will requeue the Request to be processed again if the returned error is non-nil or -// Result.Requeue is true, otherwise upon completion it will remove the work from the queue. func (r *ReconcilePostgresSnapshot) Reconcile(request reconcile.Request) (reconcile.Result, error) { r.logger.Info("reconciling postgres snapshot") ctx := context.TODO() @@ -209,21 +203,22 @@ func (r *ReconcilePostgresSnapshot) createSnapshot(ctx context.Context, rdsSvc r } // create snapshot of the rds instance - if foundSnapshot != nil { - // if snapshot status complete update status - if *foundSnapshot.Status == "available" { - return croType.PhaseComplete, "snapshot created", nil + if foundSnapshot == nil { + r.logger.Info("creating rds snapshot") + _, err = rdsSvc.CreateDBSnapshot(&rds.CreateDBSnapshotInput{ + DBInstanceIdentifier: aws.String(instanceName), + DBSnapshotIdentifier: aws.String(snapshotName), + }) + if err != nil { + errMsg := "error creating rds snapshot" + return croType.PhaseFailed, croType.StatusMessage(fmt.Sprintf("error creating rds snapshot %s", errMsg)), errorUtil.Wrap(err, errMsg) } + return croType.PhaseInProgress, "snapshot started", nil } - r.logger.Info("creating rds snapshot") - _, err = rdsSvc.CreateDBSnapshot(&rds.CreateDBSnapshotInput{ - DBInstanceIdentifier: aws.String(instanceName), - DBSnapshotIdentifier: aws.String(snapshotName), - }) - if err != nil { - errMsg := "error creating rds snapshot" - return croType.PhaseFailed, croType.StatusMessage(fmt.Sprintf("error creating rds snapshot %s", errMsg)), errorUtil.Wrap(err, errMsg) + // if snapshot status complete update status + if *foundSnapshot.Status == "available" { + return croType.PhaseComplete, "snapshot created", nil } // creation in progress diff --git a/pkg/controller/redissnapshot/redissnapshot_controller.go b/pkg/controller/redissnapshot/redissnapshot_controller.go index e1c46bf1e..e126783a6 100644 --- a/pkg/controller/redissnapshot/redissnapshot_controller.go +++ b/pkg/controller/redissnapshot/redissnapshot_controller.go @@ -3,6 +3,7 @@ package redissnapshot import ( "context" "fmt" + "github.com/aws/aws-sdk-go/service/elasticache/elasticacheiface" "time" "github.com/aws/aws-sdk-go/aws" @@ -27,12 +28,9 @@ import ( "sigs.k8s.io/controller-runtime/pkg/handler" "sigs.k8s.io/controller-runtime/pkg/manager" "sigs.k8s.io/controller-runtime/pkg/reconcile" - logf "sigs.k8s.io/controller-runtime/pkg/runtime/log" "sigs.k8s.io/controller-runtime/pkg/source" ) -var log = logf.Log.WithName("controller_redissnapshot") - // Add creates a new RedisSnapshot Controller and adds it to the Manager. The Manager will set fields on the Controller // and Start it when the Manager is Started. func Add(mgr manager.Manager) error { @@ -166,24 +164,30 @@ func (r *ReconcileRedisSnapshot) Reconcile(request reconcile.Request) (reconcile Credentials: credentials.NewStaticCredentials(providerCreds.AccessKeyID, providerCreds.SecretAccessKey, ""), }))) + // create snapshot of primary node + phase, msg, err := r.createSnapshot(ctx, cacheSvc, instance, redisCr) + if updateErr := resources.UpdateSnapshotPhase(ctx, r.client, instance, phase, msg); updateErr != nil { + return reconcile.Result{}, updateErr + } + if err != nil { + return reconcile.Result{}, err + } + return reconcile.Result{Requeue: true, RequeueAfter: time.Second * 60}, nil +} + +func (r *ReconcileRedisSnapshot) createSnapshot(ctx context.Context, cacheSvc elasticacheiface.ElastiCacheAPI, snapshot *integreatlyv1alpha1.RedisSnapshot, redis *integreatlyv1alpha1.Redis) (croType.StatusPhase, croType.StatusMessage, error) { // generate snapshot name - snapshotName, err := croAws.BuildTimestampedInfraNameFromObjectCreation(ctx, r.client, instance.ObjectMeta, croAws.DefaultAwsIdentifierLength) + snapshotName, err := croAws.BuildTimestampedInfraNameFromObjectCreation(ctx, r.client, snapshot.ObjectMeta, croAws.DefaultAwsIdentifierLength) if err != nil { errMsg := "failed to generate snapshot name" - if updateErr := resources.UpdateSnapshotPhase(ctx, r.client, instance, croType.PhaseFailed, croType.StatusMessage(errMsg)); updateErr != nil { - return reconcile.Result{}, updateErr - } - return reconcile.Result{}, errorUtil.Wrap(err, errMsg) + return croType.PhaseFailed, croType.StatusMessage(errMsg), errorUtil.Wrap(err, errMsg) } // generate cluster name - clusterName, err := croAws.BuildInfraNameFromObject(ctx, r.client, redisCr.ObjectMeta, croAws.DefaultAwsIdentifierLength) + clusterName, err := croAws.BuildInfraNameFromObject(ctx, r.client, redis.ObjectMeta, croAws.DefaultAwsIdentifierLength) if err != nil { errMsg := "failed to get cluster name" - if updateErr := resources.UpdateSnapshotPhase(ctx, r.client, instance, croType.PhaseFailed, croType.StatusMessage(errMsg)); updateErr != nil { - return reconcile.Result{}, updateErr - } - return reconcile.Result{}, errorUtil.Wrap(err, "failed to get cluster name") + return croType.PhaseFailed, croType.StatusMessage(errMsg), errorUtil.Wrap(err, errMsg) } // check snapshot exists @@ -206,10 +210,7 @@ func (r *ReconcileRedisSnapshot) Reconcile(request reconcile.Request) (reconcile // ensure replication group is available if *cacheOutput.ReplicationGroups[0].Status != "available" { errMsg := fmt.Sprintf("current replication group status is %s", *cacheOutput.ReplicationGroups[0].Status) - if updateErr := resources.UpdateSnapshotPhase(ctx, r.client, instance, croType.PhaseFailed, croType.StatusMessage(errMsg)); updateErr != nil { - return reconcile.Result{}, updateErr - } - return reconcile.Result{Requeue: true, RequeueAfter: time.Second * 60}, errorUtil.Wrap(err, "failed to get cluster name") + return croType.PhaseFailed, croType.StatusMessage(errMsg), errorUtil.Wrap(err, errMsg) } // find primary cache node @@ -229,27 +230,17 @@ func (r *ReconcileRedisSnapshot) Reconcile(request reconcile.Request) (reconcile SnapshotName: aws.String(snapshotName), }); err != nil { errMsg := fmt.Sprintf("error creating elasticache snapshot %s", err) - return reconcile.Result{}, errorUtil.Wrap(err, errMsg) + return croType.PhaseFailed, croType.StatusMessage(errMsg), errorUtil.Wrap(err, errMsg) } - if updateErr := resources.UpdateSnapshotPhase(ctx, r.client, instance, croType.PhaseInProgress, "snapshot creation in progress"); updateErr != nil { - return reconcile.Result{}, updateErr - } - return reconcile.Result{Requeue: true, RequeueAfter: time.Second * 60}, nil + return croType.PhaseInProgress, "snapshot started", nil } // if snapshot status complete update status if *foundSnapshot.SnapshotStatus == "available" { - // update complete snapshot phase - if updateErr := resources.UpdateSnapshotPhase(ctx, r.client, instance, croType.PhaseComplete, "snapshot created"); updateErr != nil { - return reconcile.Result{}, err - } - return reconcile.Result{}, err + return croType.PhaseComplete, "snapshot created", nil } msg := fmt.Sprintf("current snapshot status : %s", *foundSnapshot.SnapshotStatus) r.logger.Info(msg) - if updateErr := resources.UpdateSnapshotPhase(ctx, r.client, instance, croType.PhaseInProgress, croType.StatusMessage(msg)); updateErr != nil { - return reconcile.Result{}, updateErr - } - return reconcile.Result{Requeue: true, RequeueAfter: time.Second * 60}, nil + return croType.PhaseInProgress, "snapshot creation in progress", nil } From 3c4700b692fd0c1ab9a99668bf99ebbf35501a89 Mon Sep 17 00:00:00 2001 From: ciaranRoche Date: Wed, 15 Jan 2020 13:06:01 +0000 Subject: [PATCH 08/14] add initial redis test --- go.mod | 2 - go.sum | 11 -- .../postgressnapshot_controller.go | 12 +- .../postgressnapshot_controller_test.go | 6 - .../redissnapshot/redissnapshot_controller.go | 14 +- .../redissnapshot_controller_test.go | 182 ++++++++++++++++++ pkg/providers/aws/provider_redis.go | 2 +- 7 files changed, 199 insertions(+), 30 deletions(-) create mode 100644 pkg/controller/redissnapshot/redissnapshot_controller_test.go diff --git a/go.mod b/go.mod index 45b4a0561..0cf74e23f 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,6 @@ go 1.13 require ( github.com/aws/aws-sdk-go v1.23.17 github.com/coreos/prometheus-operator v0.29.0 - github.com/fatih/color v1.9.0 // indirect github.com/go-openapi/spec v0.19.0 github.com/gogo/protobuf v1.3.0 // indirect github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 // indirect @@ -22,7 +21,6 @@ require ( github.com/prometheus/client_golang v1.1.0 // indirect github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 // indirect github.com/prometheus/procfs v0.0.4 // indirect - github.com/rakyll/gotest v0.0.0-20191108192113-45d501058f2a // indirect github.com/sirupsen/logrus v1.4.2 github.com/spf13/pflag v1.0.3 github.com/stretchr/testify v1.4.0 // indirect diff --git a/go.sum b/go.sum index f171563ec..121ea7fa0 100644 --- a/go.sum +++ b/go.sum @@ -139,8 +139,6 @@ github.com/fatih/camelcase v0.0.0-20160318181535-f6a740d52f96/go.mod h1:yN2Sb0lF github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc= github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s= -github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= @@ -359,12 +357,8 @@ github.com/martinlindhe/base36 v0.0.0-20180729042928-5cda0030da17/go.mod h1:+AtE github.com/mattbaird/jsonpatch v0.0.0-20171005235357-81af80346b1a/go.mod h1:M1qoD/MqPgTZIk0EWKB38wE28ACRfVcn+cU08jyArI0= github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= -github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM= -github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-shellwords v0.0.0-20180605041737-f8471b0a71de/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= @@ -487,8 +481,6 @@ github.com/prometheus/procfs v0.0.4 h1:w8DjqFMJDjuVwdZBQoOozr4MVWOnwF7RcL/7uxBjY github.com/prometheus/procfs v0.0.4/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/quobyte/api v0.1.2/go.mod h1:jL7lIHrmqQ7yh05OJ+eEEdHr0u/kmT1Ff9iHd+4H6VI= -github.com/rakyll/gotest v0.0.0-20191108192113-45d501058f2a h1:/kX+lZpr87Pb0yJKyxW40ZZO6jl52jFMmoQ0YhfwGLM= -github.com/rakyll/gotest v0.0.0-20191108192113-45d501058f2a/go.mod h1:jpFrc1UTqK0FtfF3doi3pEUBgWHYELkOPPECUlDsM2Q= github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M= github.com/robfig/cron v0.0.0-20170309132418-df38d32658d8/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k= github.com/robfig/cron v0.0.0-20170526150127-736158dc09e1/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k= @@ -663,9 +655,6 @@ golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190922100055-0a153f010e69 h1:rOhMmluY6kLMhdnrivzec6lLgaVbMHMn2ISQXJeJ5EM= golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200107162124-548cf772de50 h1:YvQ10rzcqWXLlJZ3XCUoO25savxmscf4+SC+ZqiCHhA= -golang.org/x/sys v0.0.0-20200107162124-548cf772de50/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1 h1:gZpLHxUX5BdYLA08Lj4YCJNN/jk7KtquiArPoeX0WvA= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/pkg/controller/postgressnapshot/postgressnapshot_controller.go b/pkg/controller/postgressnapshot/postgressnapshot_controller.go index cc1123722..06770ad69 100644 --- a/pkg/controller/postgressnapshot/postgressnapshot_controller.go +++ b/pkg/controller/postgressnapshot/postgressnapshot_controller.go @@ -98,7 +98,7 @@ func (r *ReconcilePostgresSnapshot) Reconcile(request reconcile.Request) (reconc // Fetch the PostgresSnapshot instance instance := &integreatlyv1alpha1.PostgresSnapshot{} - err := r.client.Get(context.TODO(), request.NamespacedName, instance) + err := r.client.Get(ctx, request.NamespacedName, instance) if err != nil { if errors.IsNotFound(err) { // Request object not found, could have been deleted after reconcile request. @@ -112,13 +112,13 @@ func (r *ReconcilePostgresSnapshot) Reconcile(request reconcile.Request) (reconc // check status, if complete return if instance.Status.Phase == croType.PhaseComplete { - r.logger.Infof("found existing rds snapshot for %s", instance.Name) + r.logger.Infof("skipping creation of snapshot for %s as phase is complete", instance.Name) return reconcile.Result{}, nil } // get postgres cr postgresCr := &integreatlyv1alpha1.Postgres{} - err = r.client.Get(context.TODO(), types.NamespacedName{Name: instance.Spec.ResourceName, Namespace: instance.Namespace}, postgresCr) + err = r.client.Get(ctx, types.NamespacedName{Name: instance.Spec.ResourceName, Namespace: instance.Namespace}, postgresCr) if err != nil { errMsg := fmt.Sprintf("failed to get postgres resource: %s", err.Error()) if updateErr := resources.UpdateSnapshotPhase(ctx, r.client, instance, croType.PhaseFailed, croType.StatusMessage(errMsg)); updateErr != nil { @@ -129,7 +129,7 @@ func (r *ReconcilePostgresSnapshot) Reconcile(request reconcile.Request) (reconc // check postgres deployment strategy is aws if postgresCr.Status.Strategy != providers.AWSDeploymentStrategy { - errMsg := fmt.Sprintf("deployment strategy '%s' is not supported", postgresCr.Status.Strategy) + errMsg := fmt.Sprintf("the resource %s uses an unsupported provider strategy %s, only resources using the aws provider are valid", instance.Spec.ResourceName, postgresCr.Status.Strategy) if updateErr := resources.UpdateSnapshotPhase(ctx, r.client, instance, croType.PhaseFailed, croType.StatusMessage(errMsg)); updateErr != nil { return reconcile.Result{}, updateErr } @@ -222,5 +222,7 @@ func (r *ReconcilePostgresSnapshot) createSnapshot(ctx context.Context, rdsSvc r } // creation in progress - return croType.PhaseInProgress, "snapshot creation in progress", nil + msg := fmt.Sprintf("current snapshot status : %s", *foundSnapshot.Status) + r.logger.Info(msg) + return croType.PhaseInProgress, croType.StatusMessage(msg), nil } diff --git a/pkg/controller/postgressnapshot/postgressnapshot_controller_test.go b/pkg/controller/postgressnapshot/postgressnapshot_controller_test.go index 9acc04997..8cd52bb9b 100644 --- a/pkg/controller/postgressnapshot/postgressnapshot_controller_test.go +++ b/pkg/controller/postgressnapshot/postgressnapshot_controller_test.go @@ -38,8 +38,6 @@ type mockRdsClient struct { func buildTestScheme() (*runtime.Scheme, error) { scheme := runtime.NewScheme() - //err := apis2.AddToScheme(scheme) - //err = confv1.AddToScheme(scheme) err := v1.AddToScheme(scheme) err = apis.AddToScheme(scheme) if err != nil { @@ -72,10 +70,6 @@ func (m *mockRdsClient) CreateDBSnapshot(*rds.CreateDBSnapshotInput) (*rds.Creat } func TestReconcilePostgresSnapshot_createSnapshot(t *testing.T) { - //ctx := context.TODO() - //snapshotName, err := croAws.BuildTimestampedInfraNameFromObjectCreation(ctx, r.client, snapshot.ObjectMeta, croAws.DefaultAwsIdentifierLength) - // - //instanceName, err := croAws.BuildInfraNameFromObject(ctx, r.client, postgres.ObjectMeta, croAws.DefaultAwsIdentifierLength) scheme, err := buildTestScheme() if err != nil { logrus.Fatal(err) diff --git a/pkg/controller/redissnapshot/redissnapshot_controller.go b/pkg/controller/redissnapshot/redissnapshot_controller.go index e126783a6..1f5fceb49 100644 --- a/pkg/controller/redissnapshot/redissnapshot_controller.go +++ b/pkg/controller/redissnapshot/redissnapshot_controller.go @@ -99,7 +99,7 @@ func (r *ReconcileRedisSnapshot) Reconcile(request reconcile.Request) (reconcile // Fetch the RedisSnapshot instance instance := &integreatlyv1alpha1.RedisSnapshot{} - err := r.client.Get(context.TODO(), request.NamespacedName, instance) + err := r.client.Get(ctx, request.NamespacedName, instance) if err != nil { if errors.IsNotFound(err) { // Request object not found, could have been deleted after reconcile request. @@ -113,13 +113,13 @@ func (r *ReconcileRedisSnapshot) Reconcile(request reconcile.Request) (reconcile // check status, if complete return if instance.Status.Phase == croType.PhaseComplete { - r.logger.Infof("snapshot for %s exists", instance.Name) + r.logger.Infof("skipping creation of snapshot for %s as phase is complete", instance.Name) return reconcile.Result{}, nil } // get redis cr redisCr := &integreatlyv1alpha1.Redis{} - err = r.client.Get(context.TODO(), types.NamespacedName{Name: instance.Spec.ResourceName, Namespace: instance.Namespace}, redisCr) + err = r.client.Get(ctx, types.NamespacedName{Name: instance.Spec.ResourceName, Namespace: instance.Namespace}, redisCr) if err != nil { errMsg := fmt.Sprintf("failed to get redis cr : %s", err.Error()) if updateErr := resources.UpdateSnapshotPhase(ctx, r.client, instance, croType.PhaseFailed, croType.StatusMessage(errMsg)); updateErr != nil { @@ -129,7 +129,7 @@ func (r *ReconcileRedisSnapshot) Reconcile(request reconcile.Request) (reconcile // check redis cr deployment type is aws if redisCr.Status.Strategy != providers.AWSDeploymentStrategy { - errMsg := "none supported deployment strategy" + errMsg := fmt.Sprintf("the resource %s uses an unsupported provider strategy %s, only resources using the aws provider are valid", instance.Spec.ResourceName, redisCr.Status.Strategy) if updateErr := resources.UpdateSnapshotPhase(ctx, r.client, instance, croType.PhaseFailed, croType.StatusMessage(errMsg)); updateErr != nil { return reconcile.Result{}, updateErr } @@ -207,6 +207,10 @@ func (r *ReconcileRedisSnapshot) createSnapshot(ctx context.Context, cacheSvc el ReplicationGroupId: aws.String(clusterName), }) + if cacheOutput == nil { + return croType.PhaseFailed, "snapshot failed, no replication group found", nil + } + // ensure replication group is available if *cacheOutput.ReplicationGroups[0].Status != "available" { errMsg := fmt.Sprintf("current replication group status is %s", *cacheOutput.ReplicationGroups[0].Status) @@ -242,5 +246,5 @@ func (r *ReconcileRedisSnapshot) createSnapshot(ctx context.Context, cacheSvc el msg := fmt.Sprintf("current snapshot status : %s", *foundSnapshot.SnapshotStatus) r.logger.Info(msg) - return croType.PhaseInProgress, "snapshot creation in progress", nil + return croType.PhaseInProgress, croType.StatusMessage(msg), nil } diff --git a/pkg/controller/redissnapshot/redissnapshot_controller_test.go b/pkg/controller/redissnapshot/redissnapshot_controller_test.go new file mode 100644 index 000000000..067b3c08f --- /dev/null +++ b/pkg/controller/redissnapshot/redissnapshot_controller_test.go @@ -0,0 +1,182 @@ +package redissnapshot + +import ( + "context" + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/elasticache" + "github.com/aws/aws-sdk-go/service/elasticache/elasticacheiface" + "github.com/integr8ly/cloud-resource-operator/pkg/apis" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + + v12 "github.com/openshift/api/config/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + integreatlyv1alpha1 "github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1" + "github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1/types" + croAws "github.com/integr8ly/cloud-resource-operator/pkg/providers/aws" + "github.com/sirupsen/logrus" + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime" + controllerruntime "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "testing" +) + +var testLogger = logrus.WithFields(logrus.Fields{"testing": "true"}) + +type mockElasticacheClient struct { + elasticacheiface.ElastiCacheAPI + wantErrList bool + wantErrCreate bool + wantErrDelete bool + wantEmpty bool + repGroups []*elasticache.ReplicationGroup + rep *elasticache.ReplicationGroup + nodeSnapshot *elasticache.Snapshot +} + +func buildTestScheme() (*runtime.Scheme, error) { + scheme := runtime.NewScheme() + err := v1.AddToScheme(scheme) + err = apis.AddToScheme(scheme) + if err != nil { + return nil, err + } + return scheme, nil +} + +func buildTestInfrastructure() *v12.Infrastructure { + return &v12.Infrastructure{ + ObjectMeta: controllerruntime.ObjectMeta{ + Name: "cluster", + }, + Status: v12.InfrastructureStatus{ + InfrastructureName: "test", + }, + } +} + +func buildSnapshot() *elasticache.Snapshot { + return &elasticache.Snapshot{ + SnapshotName: aws.String("test"), + } +} + +func buildAvailableReplicationGroup() *elasticache.ReplicationGroup { + return &elasticache.ReplicationGroup{ + ReplicationGroupId: aws.String("test"), + Status: aws.String("available"), + NodeGroups: []*elasticache.NodeGroup{ + { + NodeGroupId: aws.String("test"), + NodeGroupMembers: []*elasticache.NodeGroupMember{ + { + CacheClusterId: aws.String("test"), + CurrentRole: aws.String("primary"), + }, + }, + }, + }, + } +} + +func buildReplicationGroups() []*elasticache.ReplicationGroup { + var groups []*elasticache.ReplicationGroup + groups = append(groups, buildAvailableReplicationGroup()) + return groups +} + +func (m *mockElasticacheClient) DescribeSnapshots(*elasticache.DescribeSnapshotsInput) (*elasticache.DescribeSnapshotsOutput, error) { + return &elasticache.DescribeSnapshotsOutput{}, nil +} + +func (m *mockElasticacheClient) DescribeReplicationGroups(*elasticache.DescribeReplicationGroupsInput) (*elasticache.DescribeReplicationGroupsOutput, error) { + return &elasticache.DescribeReplicationGroupsOutput{ + ReplicationGroups: m.repGroups, + }, nil +} + +func (m *mockElasticacheClient) CreateSnapshot(*elasticache.CreateSnapshotInput) (*elasticache.CreateSnapshotOutput, error) { + return &elasticache.CreateSnapshotOutput{}, nil +} + +func TestReconcileRedisSnapshot_createSnapshot(t *testing.T) { + scheme, err := buildTestScheme() + if err != nil { + logrus.Fatal(err) + t.Fatal("failed to build scheme", err) + } + type fields struct { + client client.Client + scheme *runtime.Scheme + logger *logrus.Entry + ConfigManager croAws.ConfigManager + CredentialManager croAws.CredentialManager + } + type args struct { + ctx context.Context + cacheSvc elasticacheiface.ElastiCacheAPI + snapshot *integreatlyv1alpha1.RedisSnapshot + redis *integreatlyv1alpha1.Redis + } + tests := []struct { + name string + fields fields + args args + want types.StatusPhase + want1 types.StatusMessage + wantErr bool + }{ + { + name: "test successful snapshot started", + args: args{ + ctx: context.TODO(), + cacheSvc: &mockElasticacheClient{repGroups: buildReplicationGroups()}, + snapshot: &integreatlyv1alpha1.RedisSnapshot{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "test", + }, + }, + redis: &integreatlyv1alpha1.Redis{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "test", + }, + }, + }, + fields: fields{ + client: fake.NewFakeClientWithScheme(scheme, buildTestInfrastructure()), + scheme: scheme, + logger: testLogger, + ConfigManager: nil, + CredentialManager: nil, + }, + want: types.PhaseInProgress, + want1: "snapshot started", + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := &ReconcileRedisSnapshot{ + client: tt.fields.client, + scheme: tt.fields.scheme, + logger: tt.fields.logger, + ConfigManager: tt.fields.ConfigManager, + CredentialManager: tt.fields.CredentialManager, + } + got, got1, err := r.createSnapshot(tt.args.ctx, tt.args.cacheSvc, tt.args.snapshot, tt.args.redis) + if (err != nil) != tt.wantErr { + t.Errorf("createSnapshot() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("createSnapshot() got = %v, want %v", got, tt.want) + } + if got1 != tt.want1 { + t.Errorf("createSnapshot() got1 = %v, want %v", got1, tt.want1) + } + }) + } +} diff --git a/pkg/providers/aws/provider_redis.go b/pkg/providers/aws/provider_redis.go index dcfc46a83..852728937 100644 --- a/pkg/providers/aws/provider_redis.go +++ b/pkg/providers/aws/provider_redis.go @@ -276,7 +276,7 @@ func (p *AWSRedisProvider) TagElasticacheNode(ctx context.Context, cacheSvc elas } _, err = cacheSvc.AddTagsToResource(snapshotInput) if err != nil { - msg := "Failed to add tags to aws elasticache snapshot:" + msg := "failed to add tags to aws elasticache snapshot" return types.StatusMessage(msg), err } } From 08403cebe351afa65e98e4a6a25184df2f4e315e Mon Sep 17 00:00:00 2001 From: ciaranRoche Date: Wed, 15 Jan 2020 13:34:03 +0000 Subject: [PATCH 09/14] add update redis cr --- pkg/apis/integreatly/v1alpha1/types/types.go | 5 +++-- .../redissnapshot/redissnapshot_controller.go | 10 +++++++++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/pkg/apis/integreatly/v1alpha1/types/types.go b/pkg/apis/integreatly/v1alpha1/types/types.go index 9b0afd996..b4fe5b447 100644 --- a/pkg/apis/integreatly/v1alpha1/types/types.go +++ b/pkg/apis/integreatly/v1alpha1/types/types.go @@ -55,6 +55,7 @@ type ResourceTypeStatus struct { // ResourceTypeSnapshotStatus Represents the basic status information provided by snapshot controller // +k8s:openapi-gen=true type ResourceTypeSnapshotStatus struct { - Phase StatusPhase `json:"phase,omitempty"` - Message StatusMessage `json:"message,omitempty"` + SnapshotID string `json:"snapshotId,omitempty"` + Phase StatusPhase `json:"phase,omitempty"` + Message StatusMessage `json:"message,omitempty"` } diff --git a/pkg/controller/redissnapshot/redissnapshot_controller.go b/pkg/controller/redissnapshot/redissnapshot_controller.go index 1f5fceb49..8c4ee8f7c 100644 --- a/pkg/controller/redissnapshot/redissnapshot_controller.go +++ b/pkg/controller/redissnapshot/redissnapshot_controller.go @@ -172,7 +172,8 @@ func (r *ReconcileRedisSnapshot) Reconcile(request reconcile.Request) (reconcile if err != nil { return reconcile.Result{}, err } - return reconcile.Result{Requeue: true, RequeueAfter: time.Second * 60}, nil + +return reconcile.Result{Requeue: true, RequeueAfter: time.Second * 60}, nil } func (r *ReconcileRedisSnapshot) createSnapshot(ctx context.Context, cacheSvc elasticacheiface.ElastiCacheAPI, snapshot *integreatlyv1alpha1.RedisSnapshot, redis *integreatlyv1alpha1.Redis) (croType.StatusPhase, croType.StatusMessage, error) { @@ -183,6 +184,13 @@ func (r *ReconcileRedisSnapshot) createSnapshot(ctx context.Context, cacheSvc el return croType.PhaseFailed, croType.StatusMessage(errMsg), errorUtil.Wrap(err, errMsg) } + // update cr with snapshot name + snapshot.Status.SnapshotID = snapshotName + if err = r.client.Status().Update(ctx, snapshot); err != nil { + errMsg := fmt.Sprintf("failed to update instance %s in namespace %s", snapshot.Name, snapshot.Namespace) + return croType.PhaseFailed, croType.StatusMessage(errMsg), errorUtil.Wrap(err, errMsg) + } + // generate cluster name clusterName, err := croAws.BuildInfraNameFromObject(ctx, r.client, redis.ObjectMeta, croAws.DefaultAwsIdentifierLength) if err != nil { From da644c9cd1191d38770a1ab5c61f02970769c812 Mon Sep 17 00:00:00 2001 From: ciaranRoche Date: Wed, 15 Jan 2020 14:00:22 +0000 Subject: [PATCH 10/14] fix unit test --- .../postgressnapshot_controller.go | 7 +++++++ .../postgressnapshot_controller_test.go | 18 +++++++++++------- .../redissnapshot_controller_test.go | 18 ++++++++---------- 3 files changed, 26 insertions(+), 17 deletions(-) diff --git a/pkg/controller/postgressnapshot/postgressnapshot_controller.go b/pkg/controller/postgressnapshot/postgressnapshot_controller.go index 06770ad69..c622f683a 100644 --- a/pkg/controller/postgressnapshot/postgressnapshot_controller.go +++ b/pkg/controller/postgressnapshot/postgressnapshot_controller.go @@ -183,6 +183,13 @@ func (r *ReconcilePostgresSnapshot) createSnapshot(ctx context.Context, rdsSvc r return croType.PhaseFailed, croType.StatusMessage(errMsg), errorUtil.Wrap(err, errMsg) } + // update cr with snapshot name + snapshot.Status.SnapshotID = snapshotName + if err = r.client.Status().Update(ctx, snapshot); err != nil { + errMsg := fmt.Sprintf("failed to update instance %s in namespace %s", snapshot.Name, snapshot.Namespace) + return croType.PhaseFailed, croType.StatusMessage(errMsg), errorUtil.Wrap(err, errMsg) + } + // get instance name instanceName, err := croAws.BuildInfraNameFromObject(ctx, r.client, postgres.ObjectMeta, croAws.DefaultAwsIdentifierLength) if err != nil { diff --git a/pkg/controller/postgressnapshot/postgressnapshot_controller_test.go b/pkg/controller/postgressnapshot/postgressnapshot_controller_test.go index 8cd52bb9b..9fe40aba5 100644 --- a/pkg/controller/postgressnapshot/postgressnapshot_controller_test.go +++ b/pkg/controller/postgressnapshot/postgressnapshot_controller_test.go @@ -46,6 +46,15 @@ func buildTestScheme() (*runtime.Scheme, error) { return scheme, nil } +func buildSnapshot() *integreatlyv1alpha1.PostgresSnapshot{ + return &integreatlyv1alpha1.PostgresSnapshot{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "test", + }, + } +} + func buildTestInfrastructure() *v12.Infrastructure { return &v12.Infrastructure{ ObjectMeta: controllerruntime.ObjectMeta{ @@ -103,12 +112,7 @@ func TestReconcilePostgresSnapshot_createSnapshot(t *testing.T) { rdsSvc: &mockRdsClient{dbSnapshot: &rds.DBSnapshot{ DBInstanceIdentifier: aws.String("rds-db"), }}, - snapshot: &integreatlyv1alpha1.PostgresSnapshot{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test", - Namespace: "test", - }, - }, + snapshot: buildSnapshot(), postgres: &integreatlyv1alpha1.Postgres{ ObjectMeta: metav1.ObjectMeta{ Name: "test", @@ -117,7 +121,7 @@ func TestReconcilePostgresSnapshot_createSnapshot(t *testing.T) { }, }, fields: fields{ - client: fake.NewFakeClientWithScheme(scheme, buildTestInfrastructure()), + client: fake.NewFakeClientWithScheme(scheme, buildTestInfrastructure(), buildSnapshot()), scheme: scheme, logger: testLogger, CredentialManager: nil, diff --git a/pkg/controller/redissnapshot/redissnapshot_controller_test.go b/pkg/controller/redissnapshot/redissnapshot_controller_test.go index 067b3c08f..02b8b9cfa 100644 --- a/pkg/controller/redissnapshot/redissnapshot_controller_test.go +++ b/pkg/controller/redissnapshot/redissnapshot_controller_test.go @@ -56,9 +56,12 @@ func buildTestInfrastructure() *v12.Infrastructure { } } -func buildSnapshot() *elasticache.Snapshot { - return &elasticache.Snapshot{ - SnapshotName: aws.String("test"), +func buildSnapshot() *integreatlyv1alpha1.RedisSnapshot { + return &integreatlyv1alpha1.RedisSnapshot{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "test", + }, } } @@ -132,12 +135,7 @@ func TestReconcileRedisSnapshot_createSnapshot(t *testing.T) { args: args{ ctx: context.TODO(), cacheSvc: &mockElasticacheClient{repGroups: buildReplicationGroups()}, - snapshot: &integreatlyv1alpha1.RedisSnapshot{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test", - Namespace: "test", - }, - }, + snapshot: buildSnapshot(), redis: &integreatlyv1alpha1.Redis{ ObjectMeta: metav1.ObjectMeta{ Name: "test", @@ -146,7 +144,7 @@ func TestReconcileRedisSnapshot_createSnapshot(t *testing.T) { }, }, fields: fields{ - client: fake.NewFakeClientWithScheme(scheme, buildTestInfrastructure()), + client: fake.NewFakeClientWithScheme(scheme, buildTestInfrastructure(), buildSnapshot()), scheme: scheme, logger: testLogger, ConfigManager: nil, From 007b4be6e97a40397013e6cd85195ca95defd52a Mon Sep 17 00:00:00 2001 From: ciaranRoche Date: Wed, 15 Jan 2020 16:07:23 +0000 Subject: [PATCH 11/14] fix vendor check --- .../redissnapshot_controller_test.go | 73 +++++++++++++++++-- vendor/modules.txt | 2 +- 2 files changed, 67 insertions(+), 8 deletions(-) diff --git a/pkg/controller/redissnapshot/redissnapshot_controller_test.go b/pkg/controller/redissnapshot/redissnapshot_controller_test.go index 02b8b9cfa..f3db2802a 100644 --- a/pkg/controller/redissnapshot/redissnapshot_controller_test.go +++ b/pkg/controller/redissnapshot/redissnapshot_controller_test.go @@ -32,6 +32,7 @@ type mockElasticacheClient struct { wantEmpty bool repGroups []*elasticache.ReplicationGroup rep *elasticache.ReplicationGroup + snapshots []*elasticache.Snapshot nodeSnapshot *elasticache.Snapshot } @@ -65,6 +66,15 @@ func buildSnapshot() *integreatlyv1alpha1.RedisSnapshot { } } +func buildRedisCR() *integreatlyv1alpha1.Redis { + return &integreatlyv1alpha1.Redis{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "test", + }, + } +} + func buildAvailableReplicationGroup() *elasticache.ReplicationGroup { return &elasticache.ReplicationGroup{ ReplicationGroupId: aws.String("test"), @@ -89,8 +99,19 @@ func buildReplicationGroups() []*elasticache.ReplicationGroup { return groups } +func buildSnapshots(snapshotName string, snapshotStatus string) []*elasticache.Snapshot { + var snaps []*elasticache.Snapshot + snaps = append(snaps, &elasticache.Snapshot{ + SnapshotName: aws.String(snapshotName), + SnapshotStatus: aws.String(snapshotStatus), + }) + return snaps +} + func (m *mockElasticacheClient) DescribeSnapshots(*elasticache.DescribeSnapshotsInput) (*elasticache.DescribeSnapshotsOutput, error) { - return &elasticache.DescribeSnapshotsOutput{}, nil + return &elasticache.DescribeSnapshotsOutput{ + Snapshots: m.snapshots, + }, nil } func (m *mockElasticacheClient) DescribeReplicationGroups(*elasticache.DescribeReplicationGroupsInput) (*elasticache.DescribeReplicationGroupsOutput, error) { @@ -109,6 +130,11 @@ func TestReconcileRedisSnapshot_createSnapshot(t *testing.T) { logrus.Fatal(err) t.Fatal("failed to build scheme", err) } + snapshotName, err := croAws.BuildTimestampedInfraNameFromObjectCreation(context.TODO(), fake.NewFakeClientWithScheme(scheme, buildTestInfrastructure(), buildSnapshot()), buildSnapshot().ObjectMeta, croAws.DefaultAwsIdentifierLength) + if err != nil { + logrus.Fatal(err) + t.Fatal("failed to build snapshot name", err) + } type fields struct { client client.Client scheme *runtime.Scheme @@ -136,12 +162,7 @@ func TestReconcileRedisSnapshot_createSnapshot(t *testing.T) { ctx: context.TODO(), cacheSvc: &mockElasticacheClient{repGroups: buildReplicationGroups()}, snapshot: buildSnapshot(), - redis: &integreatlyv1alpha1.Redis{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test", - Namespace: "test", - }, - }, + redis: buildRedisCR(), }, fields: fields{ client: fake.NewFakeClientWithScheme(scheme, buildTestInfrastructure(), buildSnapshot()), @@ -154,6 +175,44 @@ func TestReconcileRedisSnapshot_createSnapshot(t *testing.T) { want1: "snapshot started", wantErr: false, }, + { + name: "test successful snapshot created", + args: args{ + ctx: context.TODO(), + cacheSvc: &mockElasticacheClient{repGroups:buildReplicationGroups(), snapshots: buildSnapshots(snapshotName, "available")}, + snapshot: buildSnapshot(), + redis: buildRedisCR(), + }, + fields: fields{ + client: fake.NewFakeClientWithScheme(scheme, buildTestInfrastructure(), buildSnapshot()), + scheme: scheme, + logger: testLogger, + ConfigManager: nil, + CredentialManager: nil, + }, + want: types.PhaseComplete, + want1: "snapshot created", + wantErr: false, + }, + { + name: "test creating snapshot in progress", + args: args{ + ctx: context.TODO(), + cacheSvc: &mockElasticacheClient{repGroups:buildReplicationGroups(), snapshots: buildSnapshots(snapshotName, "creating")}, + snapshot: buildSnapshot(), + redis: buildRedisCR(), + }, + fields: fields{ + client: fake.NewFakeClientWithScheme(scheme, buildTestInfrastructure(), buildSnapshot()), + scheme: scheme, + logger: testLogger, + ConfigManager: nil, + CredentialManager: nil, + }, + want: types.PhaseInProgress, + want1: "current snapshot status : creating", + wantErr: false, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/vendor/modules.txt b/vendor/modules.txt index a4dcd7968..30b917aa3 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -235,7 +235,7 @@ golang.org/x/oauth2/google golang.org/x/oauth2/internal golang.org/x/oauth2/jws golang.org/x/oauth2/jwt -# golang.org/x/sys v0.0.0-20200107162124-548cf772de50 +# golang.org/x/sys v0.0.0-20200113162924-86b910548bc1 golang.org/x/sys/unix golang.org/x/sys/windows # golang.org/x/text v0.3.2 From 37eda954a976355320794196d9dd5ceddf0a7853 Mon Sep 17 00:00:00 2001 From: ciaranRoche Date: Thu, 16 Jan 2020 09:04:35 +0000 Subject: [PATCH 12/14] refactor postgres tests --- .../postgressnapshot_controller_test.go | 91 +++++++++++++++---- .../redissnapshot/redissnapshot_controller.go | 2 +- .../redissnapshot_controller_test.go | 39 ++++---- 3 files changed, 96 insertions(+), 36 deletions(-) diff --git a/pkg/controller/postgressnapshot/postgressnapshot_controller_test.go b/pkg/controller/postgressnapshot/postgressnapshot_controller_test.go index 9fe40aba5..41d948715 100644 --- a/pkg/controller/postgressnapshot/postgressnapshot_controller_test.go +++ b/pkg/controller/postgressnapshot/postgressnapshot_controller_test.go @@ -46,7 +46,7 @@ func buildTestScheme() (*runtime.Scheme, error) { return scheme, nil } -func buildSnapshot() *integreatlyv1alpha1.PostgresSnapshot{ +func buildPostgresSnapshot() *integreatlyv1alpha1.PostgresSnapshot { return &integreatlyv1alpha1.PostgresSnapshot{ ObjectMeta: metav1.ObjectMeta{ Name: "test", @@ -55,6 +55,15 @@ func buildSnapshot() *integreatlyv1alpha1.PostgresSnapshot{ } } +func buildPostgres() *integreatlyv1alpha1.Postgres { + return &integreatlyv1alpha1.Postgres{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "test", + }, + } +} + func buildTestInfrastructure() *v12.Infrastructure { return &v12.Infrastructure{ ObjectMeta: controllerruntime.ObjectMeta{ @@ -66,6 +75,21 @@ func buildTestInfrastructure() *v12.Infrastructure { } } +func buildSnapshot() *rds.DBSnapshot { + return &rds.DBSnapshot{ + DBInstanceIdentifier: aws.String("rds-db"), + } +} + +func buildSnapshots(snapshotName string, snapshotStatus string) []*rds.DBSnapshot { + return []*rds.DBSnapshot{ + { + DBSnapshotIdentifier: aws.String(snapshotName), + Status: aws.String(snapshotStatus), + }, + } +} + func (m *mockRdsClient) DescribeDBSnapshots(*rds.DescribeDBSnapshotsInput) (*rds.DescribeDBSnapshotsOutput, error) { return &rds.DescribeDBSnapshotsOutput{ DBSnapshots: m.dbSnapshots, @@ -79,12 +103,17 @@ func (m *mockRdsClient) CreateDBSnapshot(*rds.CreateDBSnapshotInput) (*rds.Creat } func TestReconcilePostgresSnapshot_createSnapshot(t *testing.T) { + ctx := context.TODO() scheme, err := buildTestScheme() if err != nil { logrus.Fatal(err) t.Fatal("failed to build scheme", err) } - + snapshotName, err := croAws.BuildTimestampedInfraNameFromObjectCreation(ctx, fake.NewFakeClientWithScheme(scheme, buildTestInfrastructure(), buildPostgresSnapshot()), buildPostgresSnapshot().ObjectMeta, croAws.DefaultAwsIdentifierLength) + if err != nil { + logrus.Fatal(err) + t.Fatal("failed to build scheme", err) + } type fields struct { client client.Client scheme *runtime.Scheme @@ -106,28 +135,58 @@ func TestReconcilePostgresSnapshot_createSnapshot(t *testing.T) { wantErr bool }{ { - name: "test successful snapshot create", + name: "test successful snapshot started", args: args{ - ctx: context.TODO(), - rdsSvc: &mockRdsClient{dbSnapshot: &rds.DBSnapshot{ - DBInstanceIdentifier: aws.String("rds-db"), - }}, - snapshot: buildSnapshot(), - postgres: &integreatlyv1alpha1.Postgres{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test", - Namespace: "test", - }, - }, + ctx: ctx, + rdsSvc: &mockRdsClient{dbSnapshot: buildSnapshot()}, + snapshot: buildPostgresSnapshot(), + postgres: buildPostgres(), }, fields: fields{ - client: fake.NewFakeClientWithScheme(scheme, buildTestInfrastructure(), buildSnapshot()), + client: fake.NewFakeClientWithScheme(scheme, buildTestInfrastructure(), buildPostgresSnapshot()), scheme: scheme, logger: testLogger, CredentialManager: nil, ConfigManager: nil, }, - want: types.PhaseInProgress, + want: types.PhaseInProgress, + wantErr: false, + }, + { + name: "test successful snapshot created", + args: args{ + ctx: ctx, + rdsSvc: &mockRdsClient{dbSnapshot: buildSnapshot(), dbSnapshots: buildSnapshots(snapshotName, "available")}, + snapshot: buildPostgresSnapshot(), + postgres: buildPostgres(), + }, + fields: fields{ + client: fake.NewFakeClientWithScheme(scheme, buildTestInfrastructure(), buildPostgresSnapshot()), + scheme: scheme, + logger: testLogger, + ConfigManager: nil, + CredentialManager: nil, + }, + want: types.PhaseComplete, + wantErr: false, + }, + { + name: "test successful snapshot in progress", + args: args{ + ctx: ctx, + rdsSvc: &mockRdsClient{dbSnapshot: buildSnapshot(), dbSnapshots: buildSnapshots(snapshotName, "creatring")}, + snapshot: buildPostgresSnapshot(), + postgres: buildPostgres(), + }, + fields: fields{ + client: fake.NewFakeClientWithScheme(scheme, buildTestInfrastructure(), buildPostgresSnapshot()), + scheme: scheme, + logger: testLogger, + ConfigManager: nil, + CredentialManager: nil, + }, + want: types.PhaseInProgress, + wantErr: false, }, } for _, tt := range tests { diff --git a/pkg/controller/redissnapshot/redissnapshot_controller.go b/pkg/controller/redissnapshot/redissnapshot_controller.go index 8c4ee8f7c..2fa024c01 100644 --- a/pkg/controller/redissnapshot/redissnapshot_controller.go +++ b/pkg/controller/redissnapshot/redissnapshot_controller.go @@ -173,7 +173,7 @@ func (r *ReconcileRedisSnapshot) Reconcile(request reconcile.Request) (reconcile return reconcile.Result{}, err } -return reconcile.Result{Requeue: true, RequeueAfter: time.Second * 60}, nil + return reconcile.Result{Requeue: true, RequeueAfter: time.Second * 60}, nil } func (r *ReconcileRedisSnapshot) createSnapshot(ctx context.Context, cacheSvc elasticacheiface.ElastiCacheAPI, snapshot *integreatlyv1alpha1.RedisSnapshot, redis *integreatlyv1alpha1.Redis) (croType.StatusPhase, croType.StatusMessage, error) { diff --git a/pkg/controller/redissnapshot/redissnapshot_controller_test.go b/pkg/controller/redissnapshot/redissnapshot_controller_test.go index f3db2802a..c5284d32f 100644 --- a/pkg/controller/redissnapshot/redissnapshot_controller_test.go +++ b/pkg/controller/redissnapshot/redissnapshot_controller_test.go @@ -100,12 +100,12 @@ func buildReplicationGroups() []*elasticache.ReplicationGroup { } func buildSnapshots(snapshotName string, snapshotStatus string) []*elasticache.Snapshot { - var snaps []*elasticache.Snapshot - snaps = append(snaps, &elasticache.Snapshot{ - SnapshotName: aws.String(snapshotName), - SnapshotStatus: aws.String(snapshotStatus), - }) - return snaps + return []*elasticache.Snapshot{ + { + SnapshotName: aws.String(snapshotName), + SnapshotStatus: aws.String(snapshotStatus), + }, + } } func (m *mockElasticacheClient) DescribeSnapshots(*elasticache.DescribeSnapshotsInput) (*elasticache.DescribeSnapshotsOutput, error) { @@ -125,12 +125,13 @@ func (m *mockElasticacheClient) CreateSnapshot(*elasticache.CreateSnapshotInput) } func TestReconcileRedisSnapshot_createSnapshot(t *testing.T) { + ctx := context.TODO() scheme, err := buildTestScheme() if err != nil { logrus.Fatal(err) t.Fatal("failed to build scheme", err) } - snapshotName, err := croAws.BuildTimestampedInfraNameFromObjectCreation(context.TODO(), fake.NewFakeClientWithScheme(scheme, buildTestInfrastructure(), buildSnapshot()), buildSnapshot().ObjectMeta, croAws.DefaultAwsIdentifierLength) + snapshotName, err := croAws.BuildTimestampedInfraNameFromObjectCreation(ctx, fake.NewFakeClientWithScheme(scheme, buildTestInfrastructure(), buildSnapshot()), buildSnapshot().ObjectMeta, croAws.DefaultAwsIdentifierLength) if err != nil { logrus.Fatal(err) t.Fatal("failed to build snapshot name", err) @@ -159,10 +160,10 @@ func TestReconcileRedisSnapshot_createSnapshot(t *testing.T) { { name: "test successful snapshot started", args: args{ - ctx: context.TODO(), + ctx: ctx, cacheSvc: &mockElasticacheClient{repGroups: buildReplicationGroups()}, snapshot: buildSnapshot(), - redis: buildRedisCR(), + redis: buildRedisCR(), }, fields: fields{ client: fake.NewFakeClientWithScheme(scheme, buildTestInfrastructure(), buildSnapshot()), @@ -178,10 +179,10 @@ func TestReconcileRedisSnapshot_createSnapshot(t *testing.T) { { name: "test successful snapshot created", args: args{ - ctx: context.TODO(), - cacheSvc: &mockElasticacheClient{repGroups:buildReplicationGroups(), snapshots: buildSnapshots(snapshotName, "available")}, + ctx: ctx, + cacheSvc: &mockElasticacheClient{repGroups: buildReplicationGroups(), snapshots: buildSnapshots(snapshotName, "available")}, snapshot: buildSnapshot(), - redis: buildRedisCR(), + redis: buildRedisCR(), }, fields: fields{ client: fake.NewFakeClientWithScheme(scheme, buildTestInfrastructure(), buildSnapshot()), @@ -190,17 +191,17 @@ func TestReconcileRedisSnapshot_createSnapshot(t *testing.T) { ConfigManager: nil, CredentialManager: nil, }, - want: types.PhaseComplete, - want1: "snapshot created", + want: types.PhaseComplete, + want1: "snapshot created", wantErr: false, }, { name: "test creating snapshot in progress", args: args{ - ctx: context.TODO(), - cacheSvc: &mockElasticacheClient{repGroups:buildReplicationGroups(), snapshots: buildSnapshots(snapshotName, "creating")}, + ctx: ctx, + cacheSvc: &mockElasticacheClient{repGroups: buildReplicationGroups(), snapshots: buildSnapshots(snapshotName, "creating")}, snapshot: buildSnapshot(), - redis: buildRedisCR(), + redis: buildRedisCR(), }, fields: fields{ client: fake.NewFakeClientWithScheme(scheme, buildTestInfrastructure(), buildSnapshot()), @@ -209,8 +210,8 @@ func TestReconcileRedisSnapshot_createSnapshot(t *testing.T) { ConfigManager: nil, CredentialManager: nil, }, - want: types.PhaseInProgress, - want1: "current snapshot status : creating", + want: types.PhaseInProgress, + want1: "current snapshot status : creating", wantErr: false, }, } From 9304f8d9829489c8f5743068a24e522feab93ef2 Mon Sep 17 00:00:00 2001 From: ciaranRoche Date: Thu, 16 Jan 2020 09:19:48 +0000 Subject: [PATCH 13/14] bump version to 0.6.0 --- Makefile | 4 +- ...esources.v0.6.0.clusterserviceversion.yaml | 356 ++++++++++++++++++ .../integreatly_v1alpha1_blobstorage_crd.yaml | 76 ++++ .../integreatly_v1alpha1_postgres_crd.yaml | 76 ++++ ...greatly_v1alpha1_postgressnapshot_crd.yaml | 52 +++ .../0.6.0/integreatly_v1alpha1_redis_crd.yaml | 76 ++++ ...ntegreatly_v1alpha1_redissnapshot_crd.yaml | 52 +++ ...reatly_v1alpha1_smtpcredentialset_crd.yaml | 76 ++++ .../cloud-resources.package.yaml | 2 +- deploy/operator.yaml | 2 +- version/version.go | 2 +- 11 files changed, 769 insertions(+), 5 deletions(-) create mode 100644 deploy/olm-catalog/cloud-resources/0.6.0/cloud-resources.v0.6.0.clusterserviceversion.yaml create mode 100644 deploy/olm-catalog/cloud-resources/0.6.0/integreatly_v1alpha1_blobstorage_crd.yaml create mode 100644 deploy/olm-catalog/cloud-resources/0.6.0/integreatly_v1alpha1_postgres_crd.yaml create mode 100644 deploy/olm-catalog/cloud-resources/0.6.0/integreatly_v1alpha1_postgressnapshot_crd.yaml create mode 100644 deploy/olm-catalog/cloud-resources/0.6.0/integreatly_v1alpha1_redis_crd.yaml create mode 100644 deploy/olm-catalog/cloud-resources/0.6.0/integreatly_v1alpha1_redissnapshot_crd.yaml create mode 100644 deploy/olm-catalog/cloud-resources/0.6.0/integreatly_v1alpha1_smtpcredentialset_crd.yaml diff --git a/Makefile b/Makefile index ea0d474b5..cd5facf79 100644 --- a/Makefile +++ b/Makefile @@ -3,8 +3,8 @@ IMAGE_ORG=integreatly IMAGE_NAME=cloud-resource-operator MANIFEST_NAME=cloud-resources NAMESPACE=cloud-resource-operator -PREV_VERSION=0.4.0 -VERSION=0.5.0 +PREV_VERSION=0.5.0 +VERSION=0.6.0 COMPILE_TARGET=./tmp/_output/bin/$(IMAGE_NAME) OPERATOR_SDK_VERSION=0.12.0 diff --git a/deploy/olm-catalog/cloud-resources/0.6.0/cloud-resources.v0.6.0.clusterserviceversion.yaml b/deploy/olm-catalog/cloud-resources/0.6.0/cloud-resources.v0.6.0.clusterserviceversion.yaml new file mode 100644 index 000000000..d757651aa --- /dev/null +++ b/deploy/olm-catalog/cloud-resources/0.6.0/cloud-resources.v0.6.0.clusterserviceversion.yaml @@ -0,0 +1,356 @@ +apiVersion: operators.coreos.com/v1alpha1 +kind: ClusterServiceVersion +metadata: + annotations: + alm-examples: |- + [ + { + "apiVersion": "integreatly.org/v1alpha1", + "kind": "Postgres", + "metadata": { + "labels": { + "productName": "productName" + }, + "name": "example-postgres" + }, + "spec": { + "secretRef": { + "name": "example-postgres-sec" + }, + "tier": "development", + "type": "REPLACE_ME" + } + }, + { + "apiVersion": "integreatly.org/v1alpha1", + "kind": "PostgresSnapshot", + "metadata": { + "name": "example-postgressnapshot" + }, + "spec": { + "resourceName": "REPLACE_ME" + } + }, + { + "apiVersion": "integreatly.org/v1alpha1", + "kind": "Redis", + "metadata": { + "labels": { + "productName": "productName" + }, + "name": "example-redis" + }, + "spec": { + "secretRef": { + "name": "example-redis-sec" + }, + "tier": "development", + "type": "REPLACE_ME" + } + }, + { + "apiVersion": "integreatly.org/v1alpha1", + "kind": "RedisSnapshot", + "metadata": { + "name": "example-redissnapshot" + }, + "spec": { + "resourceName": "REPLACE_ME" + } + }, + { + "apiVersion": "integreatly.org/v1alpha1", + "kind": "SMTPCredentialSet", + "metadata": { + "labels": { + "productName": "productName" + }, + "name": "example-smtpcredentialset" + }, + "spec": { + "secretRef": { + "name": "example-smtpcredentialset-sec" + }, + "tier": "development", + "type": "REPLACE_ME" + } + }, + { + "apiVersion": "integreatly.org/v1alpha1", + "kind": "BlobStorage", + "metadata": { + "labels": { + "productName": "ProductName" + }, + "name": "example-blobstorage" + }, + "spec": { + "secretRef": { + "name": "example-blobstorage-sec" + }, + "tier": "development", + "type": "REPLACE_ME" + } + } + ] + capabilities: Basic Install + categories: Integration & Delivery + certified: "false" + containerImage: quay.io/integreatly/cloud-resource-operator:0.5.0 + createdAt: "2019-10-07 12:34:56" + description: Operator to provision cloud provider resources in an abstracted manner + support: Integreatly + name: cloud-resources.v0.6.0 + namespace: placeholder +spec: + apiservicedefinitions: {} + customresourcedefinitions: + owned: + - description: Represents an instance of object or blob storage in a provider + displayName: Blob Storage + kind: BlobStorage + name: blobstorages.integreatly.org + specDescriptors: + - description: The type of the resource to deploy. Defaults are 'managed' and + 'workshop'. + displayName: Tier + path: tier + - description: The tier of the resource to deploy. Defaults are 'production' + and 'development'. + displayName: Type + path: type + - description: The secret which the resource information should be outputted + to + displayName: Secret Reference + path: secretRef + version: v1alpha1 + - description: Represents an instance of a Postgres in a provider + displayName: Postgres + kind: Postgres + name: postgres.integreatly.org + specDescriptors: + - description: The type of the resource to deploy. Defaults are 'managed' and + 'workshop'. + displayName: Tier + path: tier + - description: The tier of the resource to deploy. Defaults are 'production' + and 'development'. + displayName: Type + path: type + - description: The secret which the resource information should be outputted + to + displayName: Secret Reference + path: secretRef + version: v1alpha1 + - kind: PostgresSnapshot + name: postgressnapshots.integreatly.org + version: v1alpha1 + - description: Represents an instance of a Redis cluster in a provider + displayName: Redis + kind: Redis + name: redis.integreatly.org + specDescriptors: + - description: The type of the resource to deploy. Defaults are 'managed' and + 'workshop'. + displayName: Tier + path: tier + - description: The tier of the resource to deploy. Defaults are 'production' + and 'development'. + displayName: Type + path: type + - description: The secret which the resource information should be outputted + to + displayName: Secret Reference + path: secretRef + version: v1alpha1 + - kind: RedisSnapshot + name: redissnapshots.integreatly.org + version: v1alpha1 + - description: Represents an instance of SMTP credentials in a provider + displayName: SMTP Credentials + kind: SMTPCredentialSet + name: smtpcredentialsets.integreatly.org + specDescriptors: + - description: The type of the resource to deploy. Defaults are 'managed' and + 'workshop'. + displayName: Tier + path: tier + - description: The tier of the resource to deploy. Defaults are 'production' + and 'development'. + displayName: Type + path: type + - description: The secret which the resource information should be outputted + to + displayName: Secret Reference + path: secretRef + version: v1alpha1 + description: Provision and manage in-cluster and cloud provider resources (Blob + Storage, Postgres, Redis, SMTP Details) + displayName: Cloud Resource Operator + icon: + - base64data: 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 + mediatype: image/png + install: + spec: + clusterPermissions: + - rules: + - apiGroups: + - config.openshift.io + resources: + - infrastructures + verbs: + - get + - list + - watch + - apiGroups: + - "" + resources: + - persistentvolumes + - configmaps + verbs: + - '*' + serviceAccountName: cloud-resource-operator + deployments: + - name: cloud-resource-operator + spec: + replicas: 1 + selector: + matchLabels: + name: cloud-resource-operator + strategy: {} + template: + metadata: + labels: + name: cloud-resource-operator + spec: + containers: + - command: + - cloud-resource-operator + env: + - name: WATCH_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.annotations['olm.targetNamespaces'] + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: OPERATOR_NAME + value: cloud-resource-operator + - name: TAG_KEY_PREFIX + value: integreatly.org/ + image: quay.io/integreatly/cloud-resource-operator:0.6.0 + imagePullPolicy: Always + name: cloud-resource-operator + resources: {} + serviceAccountName: cloud-resource-operator + permissions: + - rules: + - apiGroups: + - "" + resources: + - pods + - pods/exec + - services + - services/finalizers + - endpoints + - persistentvolumeclaims + - events + - configmaps + - secrets + verbs: + - '*' + - apiGroups: + - apps + resources: + - deployments + - daemonsets + - replicasets + - statefulsets + verbs: + - '*' + - apiGroups: + - monitoring.coreos.com + resources: + - servicemonitors + verbs: + - get + - create + - apiGroups: + - apps + resourceNames: + - cloud-resource-operator + resources: + - deployments/finalizers + verbs: + - update + - apiGroups: + - "" + resources: + - pods + verbs: + - get + - apiGroups: + - apps + resources: + - replicasets + verbs: + - get + - apiGroups: + - integreatly + resources: + - '*' + verbs: + - '*' + - apiGroups: + - integreatly.org + resources: + - '*' + - smtpcredentialset + - redis + - postgres + - redissnapshots + - postgressnapshots + verbs: + - '*' + - apiGroups: + - config.openshift.io + resources: + - '*' + - infrastructures + - schedulers + - featuregates + - networks + - ingresses + - clusteroperators + - authentications + - builds + verbs: + - '*' + - apiGroups: + - cloudcredential.openshift.io + resources: + - credentialsrequests + verbs: + - '*' + serviceAccountName: cloud-resource-operator + strategy: deployment + installModes: + - supported: true + type: OwnNamespace + - supported: true + type: SingleNamespace + - supported: false + type: MultiNamespace + - supported: true + type: AllNamespaces + keywords: + - integreatly + maintainers: + - email: integreatly-support@redhat.com + name: Integreatly + maturity: alpha + provider: + name: Integreatly + replaces: cloud-resources.v0.5.0 + version: 0.6.0 diff --git a/deploy/olm-catalog/cloud-resources/0.6.0/integreatly_v1alpha1_blobstorage_crd.yaml b/deploy/olm-catalog/cloud-resources/0.6.0/integreatly_v1alpha1_blobstorage_crd.yaml new file mode 100644 index 000000000..8e08dcbb3 --- /dev/null +++ b/deploy/olm-catalog/cloud-resources/0.6.0/integreatly_v1alpha1_blobstorage_crd.yaml @@ -0,0 +1,76 @@ +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + name: blobstorages.integreatly.org +spec: + group: integreatly.org + names: + kind: BlobStorage + listKind: BlobStorageList + plural: blobstorages + singular: blobstorage + scope: Namespaced + subresources: + status: {} + validation: + openAPIV3Schema: + 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/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/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + properties: + secretRef: + properties: + name: + type: string + namespace: + type: string + required: + - name + type: object + skipCreate: + type: boolean + tier: + type: string + type: + type: string + required: + - type + - tier + - secretRef + type: object + status: + properties: + message: + type: string + phase: + type: string + provider: + type: string + secretRef: + properties: + name: + type: string + namespace: + type: string + required: + - name + type: object + strategy: + type: string + type: object + version: v1alpha1 + versions: + - name: v1alpha1 + served: true + storage: true diff --git a/deploy/olm-catalog/cloud-resources/0.6.0/integreatly_v1alpha1_postgres_crd.yaml b/deploy/olm-catalog/cloud-resources/0.6.0/integreatly_v1alpha1_postgres_crd.yaml new file mode 100644 index 000000000..ea52d65a9 --- /dev/null +++ b/deploy/olm-catalog/cloud-resources/0.6.0/integreatly_v1alpha1_postgres_crd.yaml @@ -0,0 +1,76 @@ +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + name: postgres.integreatly.org +spec: + group: integreatly.org + names: + kind: Postgres + listKind: PostgresList + plural: postgres + singular: postgres + scope: Namespaced + subresources: + status: {} + validation: + openAPIV3Schema: + 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/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/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + properties: + secretRef: + properties: + name: + type: string + namespace: + type: string + required: + - name + type: object + skipCreate: + type: boolean + tier: + type: string + type: + type: string + required: + - type + - tier + - secretRef + type: object + status: + properties: + message: + type: string + phase: + type: string + provider: + type: string + secretRef: + properties: + name: + type: string + namespace: + type: string + required: + - name + type: object + strategy: + type: string + type: object + version: v1alpha1 + versions: + - name: v1alpha1 + served: true + storage: true diff --git a/deploy/olm-catalog/cloud-resources/0.6.0/integreatly_v1alpha1_postgressnapshot_crd.yaml b/deploy/olm-catalog/cloud-resources/0.6.0/integreatly_v1alpha1_postgressnapshot_crd.yaml new file mode 100644 index 000000000..9ce40a01d --- /dev/null +++ b/deploy/olm-catalog/cloud-resources/0.6.0/integreatly_v1alpha1_postgressnapshot_crd.yaml @@ -0,0 +1,52 @@ +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + name: postgressnapshots.integreatly.org +spec: + group: integreatly.org + names: + kind: PostgresSnapshot + listKind: PostgresSnapshotList + plural: postgressnapshots + singular: postgressnapshot + scope: Namespaced + subresources: + status: {} + validation: + openAPIV3Schema: + 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/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/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + properties: + resourceName: + description: 'INSERT ADDITIONAL SPEC FIELDS - desired state of cluster + Important: Run "operator-sdk generate k8s" to regenerate code after + modifying this file Add custom validation using kubebuilder tags: + https://book-v1.book.kubebuilder.io/beyond_basics/generating_crd.html' + type: string + required: + - resourceName + type: object + status: + properties: + message: + type: string + phase: + type: string + type: object + version: v1alpha1 + versions: + - name: v1alpha1 + served: true + storage: true diff --git a/deploy/olm-catalog/cloud-resources/0.6.0/integreatly_v1alpha1_redis_crd.yaml b/deploy/olm-catalog/cloud-resources/0.6.0/integreatly_v1alpha1_redis_crd.yaml new file mode 100644 index 000000000..8edf44989 --- /dev/null +++ b/deploy/olm-catalog/cloud-resources/0.6.0/integreatly_v1alpha1_redis_crd.yaml @@ -0,0 +1,76 @@ +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + name: redis.integreatly.org +spec: + group: integreatly.org + names: + kind: Redis + listKind: RedisList + plural: redis + singular: redis + scope: Namespaced + subresources: + status: {} + validation: + openAPIV3Schema: + 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/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/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + properties: + secretRef: + properties: + name: + type: string + namespace: + type: string + required: + - name + type: object + skipCreate: + type: boolean + tier: + type: string + type: + type: string + required: + - type + - tier + - secretRef + type: object + status: + properties: + message: + type: string + phase: + type: string + provider: + type: string + secretRef: + properties: + name: + type: string + namespace: + type: string + required: + - name + type: object + strategy: + type: string + type: object + version: v1alpha1 + versions: + - name: v1alpha1 + served: true + storage: true diff --git a/deploy/olm-catalog/cloud-resources/0.6.0/integreatly_v1alpha1_redissnapshot_crd.yaml b/deploy/olm-catalog/cloud-resources/0.6.0/integreatly_v1alpha1_redissnapshot_crd.yaml new file mode 100644 index 000000000..b51196925 --- /dev/null +++ b/deploy/olm-catalog/cloud-resources/0.6.0/integreatly_v1alpha1_redissnapshot_crd.yaml @@ -0,0 +1,52 @@ +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + name: redissnapshots.integreatly.org +spec: + group: integreatly.org + names: + kind: RedisSnapshot + listKind: RedisSnapshotList + plural: redissnapshots + singular: redissnapshot + scope: Namespaced + subresources: + status: {} + validation: + openAPIV3Schema: + 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/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/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + properties: + resourceName: + description: 'INSERT ADDITIONAL SPEC FIELDS - desired state of cluster + Important: Run "operator-sdk generate k8s" to regenerate code after + modifying this file Add custom validation using kubebuilder tags: + https://book-v1.book.kubebuilder.io/beyond_basics/generating_crd.html' + type: string + required: + - resourceName + type: object + status: + properties: + message: + type: string + phase: + type: string + type: object + version: v1alpha1 + versions: + - name: v1alpha1 + served: true + storage: true diff --git a/deploy/olm-catalog/cloud-resources/0.6.0/integreatly_v1alpha1_smtpcredentialset_crd.yaml b/deploy/olm-catalog/cloud-resources/0.6.0/integreatly_v1alpha1_smtpcredentialset_crd.yaml new file mode 100644 index 000000000..a3ea1387b --- /dev/null +++ b/deploy/olm-catalog/cloud-resources/0.6.0/integreatly_v1alpha1_smtpcredentialset_crd.yaml @@ -0,0 +1,76 @@ +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + name: smtpcredentialsets.integreatly.org +spec: + group: integreatly.org + names: + kind: SMTPCredentialSet + listKind: SMTPCredentialSetList + plural: smtpcredentialsets + singular: smtpcredentialset + scope: Namespaced + subresources: + status: {} + validation: + openAPIV3Schema: + 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/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/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + properties: + secretRef: + properties: + name: + type: string + namespace: + type: string + required: + - name + type: object + skipCreate: + type: boolean + tier: + type: string + type: + type: string + required: + - type + - tier + - secretRef + type: object + status: + properties: + message: + type: string + phase: + type: string + provider: + type: string + secretRef: + properties: + name: + type: string + namespace: + type: string + required: + - name + type: object + strategy: + type: string + type: object + version: v1alpha1 + versions: + - name: v1alpha1 + served: true + storage: true diff --git a/deploy/olm-catalog/cloud-resources/cloud-resources.package.yaml b/deploy/olm-catalog/cloud-resources/cloud-resources.package.yaml index 1916d1ece..d087199bc 100644 --- a/deploy/olm-catalog/cloud-resources/cloud-resources.package.yaml +++ b/deploy/olm-catalog/cloud-resources/cloud-resources.package.yaml @@ -1,5 +1,5 @@ channels: -- currentCSV: cloud-resources.v0.5.0 +- currentCSV: cloud-resources.v0.6.0 name: integreatly defaultChannel: integreatly packageName: cloud-resources diff --git a/deploy/operator.yaml b/deploy/operator.yaml index aa45a3c54..023e01d47 100644 --- a/deploy/operator.yaml +++ b/deploy/operator.yaml @@ -15,7 +15,7 @@ spec: serviceAccountName: cloud-resource-operator containers: - name: cloud-resource-operator - image: quay.io/integreatly/cloud-resource-operator:0.5.0 + image: quay.io/integreatly/cloud-resource-operator:0.6.0 command: - cloud-resource-operator imagePullPolicy: Always diff --git a/version/version.go b/version/version.go index 0ede0cbf7..1c3757b9f 100644 --- a/version/version.go +++ b/version/version.go @@ -1,5 +1,5 @@ package version var ( - Version = "0.5.0" + Version = "0.6.0" ) From 7781424c1e13c755bc3cc722482fb6e27254a70b Mon Sep 17 00:00:00 2001 From: ciaranRoche Date: Thu, 16 Jan 2020 09:50:04 +0000 Subject: [PATCH 14/14] Update reconcile times --- .../postgressnapshot_controller.go | 26 +++++++++--------- .../postgressnapshot_controller_test.go | 2 +- .../redissnapshot/redissnapshot_controller.go | 27 +++++++++---------- pkg/resources/config.go | 2 ++ 4 files changed, 28 insertions(+), 29 deletions(-) diff --git a/pkg/controller/postgressnapshot/postgressnapshot_controller.go b/pkg/controller/postgressnapshot/postgressnapshot_controller.go index c622f683a..d724e3b4e 100644 --- a/pkg/controller/postgressnapshot/postgressnapshot_controller.go +++ b/pkg/controller/postgressnapshot/postgressnapshot_controller.go @@ -3,8 +3,6 @@ package postgressnapshot import ( "context" "fmt" - "time" - "github.com/aws/aws-sdk-go/service/rds/rdsiface" "github.com/aws/aws-sdk-go/aws" @@ -113,7 +111,7 @@ func (r *ReconcilePostgresSnapshot) Reconcile(request reconcile.Request) (reconc // check status, if complete return if instance.Status.Phase == croType.PhaseComplete { r.logger.Infof("skipping creation of snapshot for %s as phase is complete", instance.Name) - return reconcile.Result{}, nil + return reconcile.Result{Requeue: true, RequeueAfter: resources.SuccessReconcileTime}, nil } // get postgres cr @@ -122,27 +120,27 @@ func (r *ReconcilePostgresSnapshot) Reconcile(request reconcile.Request) (reconc if err != nil { errMsg := fmt.Sprintf("failed to get postgres resource: %s", err.Error()) if updateErr := resources.UpdateSnapshotPhase(ctx, r.client, instance, croType.PhaseFailed, croType.StatusMessage(errMsg)); updateErr != nil { - return reconcile.Result{}, updateErr + return reconcile.Result{Requeue: true, RequeueAfter: resources.ErrorReconcileTime}, updateErr } - return reconcile.Result{}, errorUtil.New(errMsg) + return reconcile.Result{Requeue: true, RequeueAfter: resources.ErrorReconcileTime}, errorUtil.New(errMsg) } // check postgres deployment strategy is aws if postgresCr.Status.Strategy != providers.AWSDeploymentStrategy { errMsg := fmt.Sprintf("the resource %s uses an unsupported provider strategy %s, only resources using the aws provider are valid", instance.Spec.ResourceName, postgresCr.Status.Strategy) if updateErr := resources.UpdateSnapshotPhase(ctx, r.client, instance, croType.PhaseFailed, croType.StatusMessage(errMsg)); updateErr != nil { - return reconcile.Result{}, updateErr + return reconcile.Result{Requeue: true, RequeueAfter: resources.ErrorReconcileTime}, updateErr } - return reconcile.Result{}, errorUtil.New(errMsg) + return reconcile.Result{Requeue: true, RequeueAfter: resources.ErrorReconcileTime}, errorUtil.New(errMsg) } // get resource region stratCfg, err := r.ConfigManager.ReadStorageStrategy(ctx, providers.PostgresResourceType, postgresCr.Spec.Tier) if err != nil { if updateErr := resources.UpdateSnapshotPhase(ctx, r.client, instance, croType.PhaseFailed, croType.StatusMessage(err.Error())); updateErr != nil { - return reconcile.Result{}, updateErr + return reconcile.Result{Requeue: true, RequeueAfter: resources.ErrorReconcileTime}, updateErr } - return reconcile.Result{}, err + return reconcile.Result{Requeue: true, RequeueAfter: resources.ErrorReconcileTime}, err } if stratCfg.Region == "" { stratCfg.Region = croAws.DefaultRegion @@ -153,9 +151,9 @@ func (r *ReconcilePostgresSnapshot) Reconcile(request reconcile.Request) (reconc if err != nil { errMsg := "failed to reconcile rds credentials" if updateErr := resources.UpdateSnapshotPhase(ctx, r.client, instance, croType.PhaseFailed, croType.StatusMessage(errMsg)); updateErr != nil { - return reconcile.Result{}, updateErr + return reconcile.Result{Requeue: true, RequeueAfter: resources.ErrorReconcileTime}, updateErr } - return reconcile.Result{}, errorUtil.Wrap(err, errMsg) + return reconcile.Result{Requeue: true, RequeueAfter: resources.ErrorReconcileTime}, errorUtil.Wrap(err, errMsg) } // setup aws rds session @@ -167,12 +165,12 @@ func (r *ReconcilePostgresSnapshot) Reconcile(request reconcile.Request) (reconc // create the snapshot and return the phase phase, msg, err := r.createSnapshot(ctx, rdsSvc, instance, postgresCr) if updateErr := resources.UpdateSnapshotPhase(ctx, r.client, instance, phase, msg); updateErr != nil { - return reconcile.Result{}, updateErr + return reconcile.Result{Requeue: true, RequeueAfter: resources.ErrorReconcileTime}, updateErr } if err != nil { - return reconcile.Result{}, err + return reconcile.Result{Requeue: true, RequeueAfter: resources.ErrorReconcileTime}, err } - return reconcile.Result{Requeue: true, RequeueAfter: time.Second * 60}, nil + return reconcile.Result{Requeue: true, RequeueAfter: resources.SuccessReconcileTime}, nil } func (r *ReconcilePostgresSnapshot) createSnapshot(ctx context.Context, rdsSvc rdsiface.RDSAPI, snapshot *integreatlyv1alpha1.PostgresSnapshot, postgres *integreatlyv1alpha1.Postgres) (croType.StatusPhase, croType.StatusMessage, error) { diff --git a/pkg/controller/postgressnapshot/postgressnapshot_controller_test.go b/pkg/controller/postgressnapshot/postgressnapshot_controller_test.go index 41d948715..1a6786fef 100644 --- a/pkg/controller/postgressnapshot/postgressnapshot_controller_test.go +++ b/pkg/controller/postgressnapshot/postgressnapshot_controller_test.go @@ -174,7 +174,7 @@ func TestReconcilePostgresSnapshot_createSnapshot(t *testing.T) { name: "test successful snapshot in progress", args: args{ ctx: ctx, - rdsSvc: &mockRdsClient{dbSnapshot: buildSnapshot(), dbSnapshots: buildSnapshots(snapshotName, "creatring")}, + rdsSvc: &mockRdsClient{dbSnapshot: buildSnapshot(), dbSnapshots: buildSnapshots(snapshotName, "creating")}, snapshot: buildPostgresSnapshot(), postgres: buildPostgres(), }, diff --git a/pkg/controller/redissnapshot/redissnapshot_controller.go b/pkg/controller/redissnapshot/redissnapshot_controller.go index 2fa024c01..c2e9885ba 100644 --- a/pkg/controller/redissnapshot/redissnapshot_controller.go +++ b/pkg/controller/redissnapshot/redissnapshot_controller.go @@ -3,13 +3,11 @@ package redissnapshot import ( "context" "fmt" - "github.com/aws/aws-sdk-go/service/elasticache/elasticacheiface" - "time" - "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/credentials" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/elasticache" + "github.com/aws/aws-sdk-go/service/elasticache/elasticacheiface" croType "github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1/types" "github.com/integr8ly/cloud-resource-operator/pkg/providers" croAws "github.com/integr8ly/cloud-resource-operator/pkg/providers/aws" @@ -114,7 +112,7 @@ func (r *ReconcileRedisSnapshot) Reconcile(request reconcile.Request) (reconcile // check status, if complete return if instance.Status.Phase == croType.PhaseComplete { r.logger.Infof("skipping creation of snapshot for %s as phase is complete", instance.Name) - return reconcile.Result{}, nil + return reconcile.Result{Requeue: true, RequeueAfter: resources.SuccessReconcileTime}, nil } // get redis cr @@ -123,26 +121,27 @@ func (r *ReconcileRedisSnapshot) Reconcile(request reconcile.Request) (reconcile if err != nil { errMsg := fmt.Sprintf("failed to get redis cr : %s", err.Error()) if updateErr := resources.UpdateSnapshotPhase(ctx, r.client, instance, croType.PhaseFailed, croType.StatusMessage(errMsg)); updateErr != nil { - return reconcile.Result{}, updateErr + return reconcile.Result{Requeue: true, RequeueAfter: resources.ErrorReconcileTime}, updateErr } + return reconcile.Result{Requeue: true, RequeueAfter: resources.ErrorReconcileTime}, errorUtil.New(errMsg) } // check redis cr deployment type is aws if redisCr.Status.Strategy != providers.AWSDeploymentStrategy { errMsg := fmt.Sprintf("the resource %s uses an unsupported provider strategy %s, only resources using the aws provider are valid", instance.Spec.ResourceName, redisCr.Status.Strategy) if updateErr := resources.UpdateSnapshotPhase(ctx, r.client, instance, croType.PhaseFailed, croType.StatusMessage(errMsg)); updateErr != nil { - return reconcile.Result{}, updateErr + return reconcile.Result{Requeue: true, RequeueAfter: resources.ErrorReconcileTime}, updateErr } - return reconcile.Result{}, errorUtil.New(errMsg) + return reconcile.Result{Requeue: true, RequeueAfter: resources.ErrorReconcileTime}, errorUtil.New(errMsg) } // get resource region stratCfg, err := r.ConfigManager.ReadStorageStrategy(ctx, providers.RedisResourceType, redisCr.Spec.Tier) if err != nil { if updateErr := resources.UpdateSnapshotPhase(ctx, r.client, instance, croType.PhaseFailed, croType.StatusMessage(err.Error())); updateErr != nil { - return reconcile.Result{}, updateErr + return reconcile.Result{Requeue: true, RequeueAfter: resources.ErrorReconcileTime}, updateErr } - return reconcile.Result{}, err + return reconcile.Result{Requeue: true, RequeueAfter: resources.ErrorReconcileTime}, err } if stratCfg.Region == "" { stratCfg.Region = croAws.DefaultRegion @@ -153,9 +152,9 @@ func (r *ReconcileRedisSnapshot) Reconcile(request reconcile.Request) (reconcile if err != nil { errMsg := "failed to reconcile elasticache credentials" if updateErr := resources.UpdateSnapshotPhase(ctx, r.client, instance, croType.PhaseFailed, croType.StatusMessage(errMsg)); updateErr != nil { - return reconcile.Result{}, updateErr + return reconcile.Result{Requeue: true, RequeueAfter: resources.ErrorReconcileTime}, updateErr } - return reconcile.Result{}, errorUtil.Wrap(err, errMsg) + return reconcile.Result{Requeue: true, RequeueAfter: resources.ErrorReconcileTime}, errorUtil.Wrap(err, errMsg) } // setup aws elasticache cluster sdk session @@ -167,13 +166,13 @@ func (r *ReconcileRedisSnapshot) Reconcile(request reconcile.Request) (reconcile // create snapshot of primary node phase, msg, err := r.createSnapshot(ctx, cacheSvc, instance, redisCr) if updateErr := resources.UpdateSnapshotPhase(ctx, r.client, instance, phase, msg); updateErr != nil { - return reconcile.Result{}, updateErr + return reconcile.Result{Requeue: true, RequeueAfter: resources.ErrorReconcileTime}, updateErr } if err != nil { - return reconcile.Result{}, err + return reconcile.Result{Requeue: true, RequeueAfter: resources.ErrorReconcileTime}, err } - return reconcile.Result{Requeue: true, RequeueAfter: time.Second * 60}, nil + return reconcile.Result{Requeue: true, RequeueAfter: resources.SuccessReconcileTime}, nil } func (r *ReconcileRedisSnapshot) createSnapshot(ctx context.Context, cacheSvc elasticacheiface.ElastiCacheAPI, snapshot *integreatlyv1alpha1.RedisSnapshot, redis *integreatlyv1alpha1.Redis) (croType.StatusPhase, croType.StatusMessage, error) { diff --git a/pkg/resources/config.go b/pkg/resources/config.go index cf7db61e9..53124dfbe 100644 --- a/pkg/resources/config.go +++ b/pkg/resources/config.go @@ -14,6 +14,8 @@ import ( const ( EnvForceReconcileTimeout = "ENV_FORCE_RECONCILE_TIMEOUT" DefaultTagKeyPrefix = "integreatly.org/" + ErrorReconcileTime = time.Second * 30 + SuccessReconcileTime = time.Second * 60 ) // returns envar for reconcile time else returns default time