diff --git a/operators/pkg/forge/labels.go b/operators/pkg/forge/labels.go index e92518c98..2aab5b492 100644 --- a/operators/pkg/forge/labels.go +++ b/operators/pkg/forge/labels.go @@ -37,10 +37,17 @@ const ( InstanceSubmissionSelectorLabel = "crownlabs.polito.it/instance-submission-requested" // InstanceSubmissionCompletedLabel -> label for Instances that have been submitted. InstanceSubmissionCompletedLabel = "crownlabs.polito.it/instance-submission-completed" + // ProvisionJobLabel -> Key of the label added by the Provision Job to flag the Tenant's MyDrive PVC. + ProvisionJobLabel = "crownlabs.polito.it/mydrive-provisioning" labelManagedByInstanceValue = "instance" labelManagedByTenantValue = "tenant" labelTypeSandboxValue = "sandbox" + + // ProvisionJobValueOk -> Value of the label added by the Provision Job to flag the PVC when everything worked fine. + ProvisionJobValueOk = "completed" + // ProvisionJobValuePending -> Value of the label added by the Provision Job to flag the PVC when it hasn't completed yet. + ProvisionJobValuePending = "pending" ) // InstanceLabels receives in input a set of labels and returns the updated set depending on the specified template, diff --git a/operators/pkg/tenant-controller/tenant_controller.go b/operators/pkg/tenant-controller/tenant_controller.go index 5b8908456..8ad51bb72 100644 --- a/operators/pkg/tenant-controller/tenant_controller.go +++ b/operators/pkg/tenant-controller/tenant_controller.go @@ -22,6 +22,7 @@ import ( "strings" "time" + batchv1 "k8s.io/api/batch/v1" v1 "k8s.io/api/core/v1" netv1 "k8s.io/api/networking/v1" rbacv1 "k8s.io/api/rbac/v1" @@ -30,6 +31,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" "k8s.io/klog/v2" + "k8s.io/utils/ptr" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/builder" "sigs.k8s.io/controller-runtime/pkg/client" @@ -53,6 +55,12 @@ const ( NFSSecretServerNameKey = "server-name" // NFSSecretPathKey -> NFS path key in NFS secret. NFSSecretPathKey = "path" + // ProvisionJobBaseImage -> Base container image for Personal Drive provision job. + ProvisionJobBaseImage = "busybox" + // ProvisionJobMaxRetries -> Maximum number of retries for Provision jobs. + ProvisionJobMaxRetries = 3 + // ProvisionJobTTLSeconds -> Seconds for Provision jobs before deletion (either failure or success). + ProvisionJobTTLSeconds = 604800 ) // TenantReconciler reconciles a Tenant object. @@ -243,6 +251,7 @@ func (r *TenantReconciler) SetupWithManager(mgr ctrl.Manager) error { Owns(&rbacv1.ClusterRole{}). Owns(&rbacv1.ClusterRoleBinding{}). Owns(&netv1.NetworkPolicy{}). + Owns(&batchv1.Job{}). Watches(&crownlabsv1alpha1.Workspace{}, handler.EnqueueRequestsFromMapFunc(r.workspaceToEnrolledTenants)). WithOptions(controller.Options{ @@ -517,6 +526,41 @@ func (r *TenantReconciler) createOrUpdateTnPersonalNFSVolume(ctx context.Context return err } klog.Infof("PVC Secret for tenant %s %s", tn.Name, pvcSecOpRes) + + chownJob := batchv1.Job{ObjectMeta: metav1.ObjectMeta{Name: pvc.Name + "-provision", Namespace: pvc.Namespace}} + + val, found := pvc.Labels[forge.ProvisionJobLabel] + if !found { + chownJobOpRes, err := ctrl.CreateOrUpdate(ctx, r.Client, &chownJob, func() error { + r.updateTnProvisioningJob(&chownJob, &pvc) + return ctrl.SetControllerReference(tn, &chownJob, r.Scheme) + }) + if err != nil { + klog.Errorf("Unable to create or update PVC Provisioning Job for tenant %s -> %s", tn.Name, err) + return err + } + pvc.Labels[forge.ProvisionJobLabel] = forge.ProvisionJobValuePending + if err := r.Update(ctx, &pvc); err != nil { + klog.Errorf("PVC Provisioning Job failed to update PVC labels for tenant %s", tn.Name) + } + + klog.Infof("PVC Provisioning Job for tenant %s %s", tn.Name, chownJobOpRes) + } else if val != forge.ProvisionJobValueOk { + if err := r.Update(ctx, &chownJob); err != nil { + klog.Errorf("PVC Provisioning Job failed to update info about Provisioning Job for tenant %s", tn.Name) + } + + if chownJob.Status.Succeeded == 1 { + pvc.Labels[forge.ProvisionJobLabel] = forge.ProvisionJobValueOk + if err := r.Update(ctx, &pvc); err != nil { + klog.Errorf("PVC Provisioning Job failed to update PVC labels for tenant %s", tn.Name) + } + + klog.Infof("PVC Provisioning Job completed for tenant %s", tn.Name) + } else if chownJob.Status.Failed == 1 { + klog.Warningf("PVC Provisioning Job failed for tenant %s", tn.Name) + } + } } else if pvc.Status.Phase == v1.ClaimPending { klog.Infof("PVC pending for tenant %s", tn.Name) } @@ -576,6 +620,44 @@ func (r *TenantReconciler) updateTnPersistentVolumeClaim(pvc *v1.PersistentVolum pvc.Spec.StorageClassName = &scName } +func (r *TenantReconciler) updateTnProvisioningJob(chownJob *batchv1.Job, pvc *v1.PersistentVolumeClaim) { + if chownJob.CreationTimestamp.IsZero() { + chownJob.Spec.BackoffLimit = ptr.To[int32](ProvisionJobMaxRetries) + chownJob.Spec.TTLSecondsAfterFinished = ptr.To[int32](ProvisionJobTTLSeconds) + chownJob.Spec.Template.Spec.RestartPolicy = v1.RestartPolicyOnFailure + chownJob.Spec.Template.Spec.Containers = []v1.Container{{ + Name: "chown-container", + Image: ProvisionJobBaseImage, + Command: []string{"chown", "-R", fmt.Sprintf("%d:%d", forge.CrownLabsUserID, forge.CrownLabsUserID), forge.MyDriveVolumeMountPath}, + VolumeMounts: []v1.VolumeMount{{ + Name: "mydrive", + MountPath: forge.MyDriveVolumeMountPath, + }, + }, + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + "cpu": resource.MustParse("100m"), + "memory": resource.MustParse("128Mi"), + }, + Limits: v1.ResourceList{ + "cpu": resource.MustParse("100m"), + "memory": resource.MustParse("128Mi"), + }, + }, + }, + } + chownJob.Spec.Template.Spec.Volumes = []v1.Volume{{ + Name: "mydrive", + VolumeSource: v1.VolumeSource{ + PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{ + ClaimName: pvc.Name, + }, + }, + }, + } + } +} + func (r *TenantReconciler) handleKeycloakSubscription(ctx context.Context, tn *crownlabsv1alpha2.Tenant, tenantExistingWorkspaces []crownlabsv1alpha2.TenantWorkspaceEntry) error { // KcA could be nil for local testing skipping the keycloak subscription if r.KcA == nil {