From 1d2e8bce1df563c70260a54c31338c85fc29e7d1 Mon Sep 17 00:00:00 2001 From: Andrew Obuchowicz Date: Mon, 1 May 2023 13:47:53 -0400 Subject: [PATCH] Support persistent home directory for common and per-workspace storage strategies Fix #1097 Signed-off-by: Andrew Obuchowicz --- pkg/provision/storage/commonStorage.go | 22 +++++++++++++-- pkg/provision/storage/perWorkspaceStorage.go | 22 +++++++++++++-- pkg/provision/storage/shared.go | 29 ++++++++++++++++++++ 3 files changed, 67 insertions(+), 6 deletions(-) diff --git a/pkg/provision/storage/commonStorage.go b/pkg/provision/storage/commonStorage.go index f86ffe1d3..a2140675e 100644 --- a/pkg/provision/storage/commonStorage.go +++ b/pkg/provision/storage/commonStorage.go @@ -80,7 +80,17 @@ func (p *CommonStorageProvisioner) ProvisionStorage(podAdditions *v1alpha1.PodAd pvcName = commonPVC.Name } - if err := p.rewriteContainerVolumeMounts(workspace.Status.DevWorkspaceId, pvcName, podAdditions, &workspace.Spec.Template); err != nil { + if shouldPersistHomeDirectory(workspace) { + err := addHomeVolumeMount(workspace, pvcName, podAdditions) + if err != nil { + return &dwerrors.FailError{ + Err: err, + Message: "Could not add persistent home directory", + } + } + } + + if err := p.rewriteContainerVolumeMounts(workspace.Status.DevWorkspaceId, pvcName, podAdditions, workspace); err != nil { return &dwerrors.FailError{ Err: err, Message: "Could not rewrite container volume mounts", @@ -125,11 +135,11 @@ func (p *CommonStorageProvisioner) CleanupWorkspaceStorage(workspace *common.Dev // (i.e. all volume mounts are subpaths into a common PVC used by all workspaces in the namespace). // // Also adds appropriate k8s Volumes to PodAdditions to accomodate the rewritten VolumeMounts. -func (p *CommonStorageProvisioner) rewriteContainerVolumeMounts(workspaceId, pvcName string, podAdditions *v1alpha1.PodAdditions, workspace *dw.DevWorkspaceTemplateSpec) error { +func (p *CommonStorageProvisioner) rewriteContainerVolumeMounts(workspaceId, pvcName string, podAdditions *v1alpha1.PodAdditions, workspace *common.DevWorkspaceWithConfig) error { devfileVolumes := map[string]dw.VolumeComponent{} // Construct map of volume name -> volume Component - for _, component := range workspace.Components { + for _, component := range workspace.Spec.Template.Components { if component.Volume != nil { if _, exists := devfileVolumes[component.Name]; exists { return fmt.Errorf("volume component '%s' is defined multiple times", component.Name) @@ -144,6 +154,12 @@ func (p *CommonStorageProvisioner) rewriteContainerVolumeMounts(workspaceId, pvc additionalVolumes[additionalVolume.Name] = true } + // When we persist the home directory, a volumeMount is created that references the PVC volume. + // This volumeMount should not be rewritten + if shouldPersistHomeDirectory(workspace) { + additionalVolumes[pvcName] = true + } + // Add implicit projects volume to support mountSources, if needed if _, exists := devfileVolumes[devfileConstants.ProjectsVolumeName]; !exists { projectsVolume := dw.VolumeComponent{} diff --git a/pkg/provision/storage/perWorkspaceStorage.go b/pkg/provision/storage/perWorkspaceStorage.go index 7b7c6fbf8..8631c752d 100644 --- a/pkg/provision/storage/perWorkspaceStorage.go +++ b/pkg/provision/storage/perWorkspaceStorage.go @@ -70,8 +70,18 @@ func (p *PerWorkspaceStorageProvisioner) ProvisionStorage(podAdditions *v1alpha1 } } + if shouldPersistHomeDirectory(workspace) { + err := addHomeVolumeMount(workspace, pvcName, podAdditions) + if err != nil { + return &dwerrors.FailError{ + Err: err, + Message: "Could not add persistent home directory", + } + } + } + // Rewrite container volume mounts - if err := p.rewriteContainerVolumeMounts(workspace.Status.DevWorkspaceId, pvcName, podAdditions, &workspace.Spec.Template); err != nil { + if err := p.rewriteContainerVolumeMounts(workspace.Status.DevWorkspaceId, pvcName, podAdditions, workspace); err != nil { return &dwerrors.FailError{ Err: err, Message: "Could not rewrite container volume mounts", @@ -90,11 +100,11 @@ func (*PerWorkspaceStorageProvisioner) CleanupWorkspaceStorage(workspace *common // (i.e. all volume mounts are subpaths into a PVC used by a single workspace in the namespace). // // Also adds appropriate k8s Volumes to PodAdditions to accomodate the rewritten VolumeMounts. -func (p *PerWorkspaceStorageProvisioner) rewriteContainerVolumeMounts(workspaceId, pvcName string, podAdditions *v1alpha1.PodAdditions, workspace *dw.DevWorkspaceTemplateSpec) error { +func (p *PerWorkspaceStorageProvisioner) rewriteContainerVolumeMounts(workspaceId, pvcName string, podAdditions *v1alpha1.PodAdditions, workspace *common.DevWorkspaceWithConfig) error { devfileVolumes := map[string]dw.VolumeComponent{} // Construct map of volume name -> volume Component - for _, component := range workspace.Components { + for _, component := range workspace.Spec.Template.Components { if component.Volume != nil { if _, exists := devfileVolumes[component.Name]; exists { return fmt.Errorf("volume component '%s' is defined multiple times", component.Name) @@ -109,6 +119,12 @@ func (p *PerWorkspaceStorageProvisioner) rewriteContainerVolumeMounts(workspaceI additionalVolumes[additionalVolume.Name] = true } + // When we persist the home directory, a volumeMount is created that references the PVC volume. + // This volumeMount should not be rewritten + if shouldPersistHomeDirectory(workspace) { + additionalVolumes[pvcName] = true + } + // Add implicit projects volume to support mountSources, if needed if _, exists := devfileVolumes[devfileConstants.ProjectsVolumeName]; !exists { projectsVolume := dw.VolumeComponent{} diff --git a/pkg/provision/storage/shared.go b/pkg/provision/storage/shared.go index 122bc0d21..77b04c325 100644 --- a/pkg/provision/storage/shared.go +++ b/pkg/provision/storage/shared.go @@ -277,3 +277,32 @@ func checkPVCTerminating(name, namespace string, api sync.ClusterAPI) (bool, err } return pvc.DeletionTimestamp != nil, nil } + +func shouldPersistHomeDirectory(workspace *common.DevWorkspaceWithConfig) bool { + return workspace.Config.Workspace.PersistUserHome != nil && workspace.Config.Workspace.PersistUserHome.Enabled != nil && *workspace.Config.Workspace.PersistUserHome.Enabled +} + +// Adds a home volume mount to the given set of Pod Additions. +// The home volume mount is mounted to the given PVC at the path `/home/user/`. +// If a Devfile volume already mounts to `/home/user/`, an error is returned. +func addHomeVolumeMount(workspace *common.DevWorkspaceWithConfig, pvcName string, podAdditions *v1alpha1.PodAdditions) error { + homeUserDirPath := "/home/user/" + homeVolumeMount := corev1.VolumeMount{ + Name: pvcName, + MountPath: homeUserDirPath, + SubPath: fmt.Sprintf("%s/home/user/", workspace.Status.DevWorkspaceId), + } + + // Check if we are already trying to mount a volume to /home/user/ in one of the containers + for cIdx := range podAdditions.Containers { + for vmIdx := range podAdditions.Containers[cIdx].VolumeMounts { + if podAdditions.Containers[cIdx].VolumeMounts[vmIdx].MountPath == homeUserDirPath { + containerName := podAdditions.Containers[cIdx].Name + volumeMountName := podAdditions.Containers[cIdx].VolumeMounts[vmIdx].Name + return fmt.Errorf("volumeMount '%s' for container '%s' already mounts to %s", volumeMountName, containerName, homeUserDirPath) + } + } + podAdditions.Containers[cIdx].VolumeMounts = append(podAdditions.Containers[cIdx].VolumeMounts, homeVolumeMount) + } + return nil +}