diff --git a/controllers/drupalsite_controller_test.go b/controllers/drupalsite_controller_test.go index 8028d3818a52ca15ee49347f060cf7ee4c660937..21f1dc7803d553a539cb17034f97f8fbb17eec6c 100644 --- a/controllers/drupalsite_controller_test.go +++ b/controllers/drupalsite_controller_test.go @@ -240,18 +240,204 @@ var _ = Describe("DrupalSite controller", func() { // Check if the Schedule resource is created By("Expecting Schedule to be created") + Eventually(func() error { + return k8sClient.Get(ctx, types.NamespacedName{Name: generateScheduleName(key.Namespace, key.Name), Namespace: veleroNamespace}, &schedule) + }, timeout, interval).Should(Succeed()) + + // Check Routes + By("Expecting Drupal Route(s) to be created") + for _, url := range cr.Spec.SiteURL { + route = routev1.Route{} + hash := md5.Sum([]byte(url)) + Eventually(func() []metav1.OwnerReference { + k8sClient.Get(ctx, types.NamespacedName{Name: key.Name + "-" + hex.EncodeToString(hash[0:4]), Namespace: key.Namespace}, &route) + return route.ObjectMeta.OwnerReferences + }, timeout, interval).Should(ContainElement(expectedOwnerReference)) + } + + // Check OidcReturnUris + By("Expecting OidcReturnURIs created") + for _, url := range cr.Spec.SiteURL { + oidcReturnUri = authz.OidcReturnURI{} + hash := md5.Sum([]byte(url)) + Eventually(func() []metav1.OwnerReference { + k8sClient.Get(ctx, types.NamespacedName{Name: key.Name + "-" + hex.EncodeToString(hash[0:4]), Namespace: key.Namespace}, &oidcReturnUri) + return oidcReturnUri.ObjectMeta.OwnerReferences + }, timeout, interval).Should(ContainElement(expectedOwnerReference)) + } + }) + }) + }) + + Describe("Cloning a drupalSite object", func() { + Context("With cloneFrom", func() { + It("All dependent resources should be created", func() { + keyClone := types.NamespacedName{ + Name: Name + "-clone", + Namespace: Namespace, + } + drupalSiteObjectClone := &drupalwebservicesv1alpha1.DrupalSite{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "drupal.webservices.cern.ch/v1alpha1", + Kind: "DrupalSite", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: keyClone.Name, + Namespace: keyClone.Namespace, + }, + Spec: drupalwebservicesv1alpha1.DrupalSiteSpec{ + Version: drupalwebservicesv1alpha1.Version{ + Name: "v9.3-2", + ReleaseSpec: "stable", + }, + Configuration: drupalwebservicesv1alpha1.Configuration{ + DiskSize: "6Gi", + QoSClass: drupalwebservicesv1alpha1.QoSStandard, + DatabaseClass: drupalwebservicesv1alpha1.DBODStandard, + CloneFrom: drupalwebservicesv1alpha1.CloneFrom(key.Name), + ScheduledBackups: "enabled", + }, + SiteURL: []drupalwebservicesv1alpha1.Url{dummySiteUrl}, + }, + } + + By("By creating a new drupalSite") + Eventually(func() error { + return k8sClient.Create(ctx, drupalSiteObjectClone) + }, timeout, interval).Should(Succeed()) + + // Create drupalSite object + By("Expecting drupalSite object created") + cr := drupalwebservicesv1alpha1.DrupalSite{} + Eventually(func() error { + return k8sClient.Get(ctx, keyClone, &cr) + }, timeout, interval).Should(Succeed()) + + trueVar := true + expectedOwnerReference := metav1.OwnerReference{ + APIVersion: "drupal.webservices.cern.ch/v1alpha1", + Kind: "DrupalSite", + Name: keyClone.Name, + UID: cr.UID, + Controller: &trueVar, + } + configmap := corev1.ConfigMap{} + svc := corev1.Service{} + pvc := corev1.PersistentVolumeClaim{} + job := batchv1.Job{} + deploy := appsv1.Deployment{} + dbod := dbodv1a1.Database{} + route := routev1.Route{} + oidcReturnUri := authz.OidcReturnURI{} + schedule := velerov1.Schedule{} + + // Check DBOD resource creation + By("Expecting Database resource created") Eventually(func() []metav1.OwnerReference { - k8sClient.Get(ctx, types.NamespacedName{Name: key.Namespace + "-" + key.Name, Namespace: veleroNamespace}, &schedule) + k8sClient.Get(ctx, types.NamespacedName{Name: keyClone.Name, Namespace: keyClone.Namespace}, &dbod) + return dbod.ObjectMeta.OwnerReferences + }, timeout, interval).Should(ContainElement(expectedOwnerReference)) + + // Update DBOD resource status field + By("Updating the DBOD instance in Database resource status") + Eventually(func() error { + k8sClient.Get(ctx, types.NamespacedName{Name: keyClone.Name, Namespace: keyClone.Namespace}, &dbod) + dbod.Status.DbodInstance = "test" + return k8sClient.Status().Update(ctx, &dbod) + }, timeout, interval).Should(Succeed()) + + By("Expecting the drupal deployment to have at least 2 containers") + Eventually(func() bool { + k8sClient.Get(ctx, types.NamespacedName{Name: keyClone.Name, Namespace: keyClone.Namespace}, &deploy) + return len(deploy.Spec.Template.Spec.Containers) >= 2 + }, timeout, interval).Should(BeTrue()) + + // Check PHP-FPM configMap creation + By("Expecting PHP_FPM configmaps created") + Eventually(func() []metav1.OwnerReference { + k8sClient.Get(ctx, types.NamespacedName{Name: "php-fpm-" + keyClone.Name, Namespace: keyClone.Namespace}, &configmap) + return configmap.ObjectMeta.OwnerReferences + }, timeout, interval).Should(ContainElement(expectedOwnerReference)) + + // Check Nginx configMap creation + By("Expecting Nginx configmaps created") + Eventually(func() []metav1.OwnerReference { + k8sClient.Get(ctx, types.NamespacedName{Name: "nginx-" + keyClone.Name, Namespace: keyClone.Namespace}, &configmap) + return configmap.ObjectMeta.OwnerReferences + }, timeout, interval).Should(ContainElement(expectedOwnerReference)) + + // Check Site settings configMap creation + By("Expecting Site settings configmaps created") + Eventually(func() []metav1.OwnerReference { + k8sClient.Get(ctx, types.NamespacedName{Name: "site-settings-" + keyClone.Name, Namespace: keyClone.Namespace}, &configmap) + return configmap.ObjectMeta.OwnerReferences + }, timeout, interval).Should(ContainElement(expectedOwnerReference)) + + // Check PHP Cli configMap creation + By("Expecting PHP Cli configmaps created") + Eventually(func() []metav1.OwnerReference { + k8sClient.Get(ctx, types.NamespacedName{Name: "php-cli-config-" + keyClone.Name, Namespace: keyClone.Namespace}, &configmap) + return configmap.ObjectMeta.OwnerReferences + }, timeout, interval).Should(ContainElement(expectedOwnerReference)) + // Check Drupal service + By("Expecting Drupal service created") + Eventually(func() []metav1.OwnerReference { + k8sClient.Get(ctx, types.NamespacedName{Name: keyClone.Name, Namespace: keyClone.Namespace}, &svc) + return svc.ObjectMeta.OwnerReferences + }, timeout, interval).Should(ContainElement(expectedOwnerReference)) + + // Check drupal persistentVolumeClaim + By("Expecting drupal persistentVolumeClaim created") + Eventually(func() []metav1.OwnerReference { + k8sClient.Get(ctx, types.NamespacedName{Name: "pv-claim-" + keyClone.Name, Namespace: keyClone.Namespace}, &pvc) + return pvc.ObjectMeta.OwnerReferences + }, timeout, interval).Should(ContainElement(expectedOwnerReference)) + + // Check Drupal deployments + By("Expecting Drupal deployments created") + Eventually(func() []metav1.OwnerReference { + k8sClient.Get(ctx, types.NamespacedName{Name: keyClone.Name, Namespace: keyClone.Namespace}, &deploy) + return deploy.ObjectMeta.OwnerReferences + }, timeout, interval).Should(ContainElement(expectedOwnerReference)) + + // // Check Drush job + By("Expecting Drush job created") + Eventually(func() []metav1.OwnerReference { + k8sClient.Get(ctx, types.NamespacedName{Name: "clone-" + keyClone.Name, Namespace: keyClone.Namespace}, &job) return job.ObjectMeta.OwnerReferences }, timeout, interval).Should(ContainElement(expectedOwnerReference)) + // Update drupalSite custom resource status fields to allow route conditions + By("Updating 'initialized' status field in drupalSite resource") + Eventually(func() error { + k8sClient.Get(ctx, types.NamespacedName{Name: keyClone.Name, Namespace: keyClone.Namespace}, &cr) + cr.Status.Conditions.SetCondition(status.Condition{Type: "Initialized", Status: "True"}) + return k8sClient.Status().Update(ctx, &cr) + }, timeout, interval).Should(Succeed()) + + // Update deployment status fields to allow 'ready' status field to be set on the drupalSite resource + By("Updating 'ReadyReplicas' and 'AvailableReplicas' status fields in deployment resource") + Eventually(func() error { + k8sClient.Get(ctx, keyClone, &deploy) + deploy.Status.Replicas = 1 + deploy.Status.AvailableReplicas = 1 + deploy.Status.ReadyReplicas = 1 + return k8sClient.Status().Update(ctx, &deploy) + }, timeout, interval).Should(Succeed()) + + // Check if the Schedule resource is created + By("Expecting Schedule to be created") + Eventually(func() error { + return k8sClient.Get(ctx, types.NamespacedName{Name: generateScheduleName(keyClone.Namespace, keyClone.Name), Namespace: veleroNamespace}, &schedule) + }, timeout, interval).Should(Succeed()) + // Check Routes By("Expecting Drupal Route(s) to be created") for _, url := range cr.Spec.SiteURL { route = routev1.Route{} hash := md5.Sum([]byte(url)) Eventually(func() []metav1.OwnerReference { - k8sClient.Get(ctx, types.NamespacedName{Name: key.Name + "-" + hex.EncodeToString(hash[0:4]), Namespace: key.Namespace}, &route) + k8sClient.Get(ctx, types.NamespacedName{Name: keyClone.Name + "-" + hex.EncodeToString(hash[0:4]), Namespace: keyClone.Namespace}, &route) return route.ObjectMeta.OwnerReferences }, timeout, interval).Should(ContainElement(expectedOwnerReference)) } @@ -262,10 +448,19 @@ var _ = Describe("DrupalSite controller", func() { oidcReturnUri = authz.OidcReturnURI{} hash := md5.Sum([]byte(url)) Eventually(func() []metav1.OwnerReference { - k8sClient.Get(ctx, types.NamespacedName{Name: key.Name + "-" + hex.EncodeToString(hash[0:4]), Namespace: key.Namespace}, &oidcReturnUri) + k8sClient.Get(ctx, types.NamespacedName{Name: keyClone.Name + "-" + hex.EncodeToString(hash[0:4]), Namespace: keyClone.Namespace}, &oidcReturnUri) return oidcReturnUri.ObjectMeta.OwnerReferences }, timeout, interval).Should(ContainElement(expectedOwnerReference)) } + + // Check if the diskSize is set correctly + By("Expecting the clone site diskSize is set to the same as source site") + Eventually(func() bool { + cr_source := drupalwebservicesv1alpha1.DrupalSite{} + k8sClient.Get(ctx, types.NamespacedName{Name: key.Name, Namespace: key.Namespace}, &cr_source) + return (cr.Spec.Configuration.DiskSize == cr_source.Spec.Configuration.DiskSize) + }, timeout, interval).Should(BeTrue()) + }) }) }) @@ -909,10 +1104,9 @@ var _ = Describe("DrupalSite controller", func() { // Check if the Schedule resource is created By("Expecting Schedule to be created") - Eventually(func() []metav1.OwnerReference { - k8sClient.Get(ctx, types.NamespacedName{Name: key.Namespace + "-" + key.Name, Namespace: veleroNamespace}, &schedule) - return job.ObjectMeta.OwnerReferences - }, timeout, interval).Should(ContainElement(expectedOwnerReference)) + Eventually(func() error { + return k8sClient.Get(ctx, types.NamespacedName{Name: generateScheduleName(key.Namespace, key.Name), Namespace: veleroNamespace}, &schedule) + }, timeout, interval).Should(Succeed()) // Check Routes By("Expecting Drupal Route(s) to be created") @@ -1633,10 +1827,9 @@ var _ = Describe("DrupalSite controller", func() { // Check if the Schedule resource is created By("Expecting Schedule to be created") - Eventually(func() []metav1.OwnerReference { - k8sClient.Get(ctx, types.NamespacedName{Name: key.Namespace + "-" + key.Name, Namespace: veleroNamespace}, &schedule) - return job.ObjectMeta.OwnerReferences - }, timeout, interval).Should(ContainElement(expectedOwnerReference)) + Eventually(func() error { + return k8sClient.Get(ctx, types.NamespacedName{Name: generateScheduleName(key.Namespace, key.Name), Namespace: veleroNamespace}, &schedule) + }, timeout, interval).Should(Succeed()) // Check Routes By("Expecting Drupal Route(s) to be created") @@ -1786,10 +1979,9 @@ var _ = Describe("DrupalSite controller", func() { // Check if the Schedule resource is created By("Expecting Schedule to be created") - Eventually(func() []metav1.OwnerReference { - k8sClient.Get(ctx, types.NamespacedName{Name: key.Namespace + "-" + key.Name, Namespace: veleroNamespace}, &schedule) - return job.ObjectMeta.OwnerReferences - }, timeout, interval).Should(ContainElement(expectedOwnerReference)) + Eventually(func() error { + return k8sClient.Get(ctx, types.NamespacedName{Name: generateScheduleName(key.Namespace, key.Name), Namespace: veleroNamespace}, &schedule) + }, timeout, interval).Should(Succeed()) // Check Routes By("Expecting Drupal Route(s) to be created") diff --git a/controllers/drupalsite_controller_utils.go b/controllers/drupalsite_controller_utils.go index cda16e20232b9db0d0d633fbc1378e1d057d0d65..4c16babf001cec25d0ec0d3aee2c9f2845b3b960 100644 --- a/controllers/drupalsite_controller_utils.go +++ b/controllers/drupalsite_controller_utils.go @@ -31,6 +31,7 @@ import ( batchv1 "k8s.io/api/batch/v1" corev1 "k8s.io/api/core/v1" k8sapierrors "k8s.io/apimachinery/pkg/api/errors" + k8sapiresource "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "knative.dev/pkg/apis" @@ -178,8 +179,19 @@ func (r *DrupalSiteReconciler) ensureSpecFinalizer(ctx context.Context, drp *web case err != nil: return false, newApplicationError(err, ErrClientK8s) } + + // Parse the input strings (Mi or Gi) into 'quantity' type for easier comparison + destinationDiskSize, err := k8sapiresource.ParseQuantity(drp.Spec.Configuration.DiskSize) + if err != nil { + return false, newApplicationError(err, ErrFunctionDomain) + } + sourceDiskSize, err := k8sapiresource.ParseQuantity(sourceSite.Spec.Configuration.DiskSize) + if err != nil { + return false, newApplicationError(err, ErrFunctionDomain) + } + // The destination disk size must be at least as large as the source - if drp.Spec.Configuration.DiskSize < sourceSite.Spec.Configuration.DiskSize { + if destinationDiskSize.Cmp(sourceDiskSize) == -1 { drp.Spec.Configuration.DiskSize = sourceSite.Spec.Configuration.DiskSize update = true }