diff --git a/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/KubernetesPersistentVolumeClaims.java b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/KubernetesPersistentVolumeClaims.java index ffd524176d1..6c6662c0eac 100644 --- a/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/KubernetesPersistentVolumeClaims.java +++ b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/KubernetesPersistentVolumeClaims.java @@ -158,6 +158,26 @@ public void delete(Map labels) throws InfrastructureException { } } + /** + * Removes PVC by name. + * + * @param name the name of the PVC + * @throws InfrastructureException when any error occurs while removing + */ + public void delete(final String name) throws InfrastructureException { + try { + clientFactory + .create(workspaceId) + .persistentVolumeClaims() + .inNamespace(namespace) + .withName(name) + .withPropagationPolicy("Background") + .delete(); + } catch (KubernetesClientException e) { + throw new KubernetesInfrastructureException(e); + } + } + /** * Waits until persistent volume claim state is bound. If used k8s Storage Class has * 'volumeBindingMode: WaitForFirstConsumer', we don't wait to avoid deadlock. diff --git a/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/pvc/CommonPVCStrategy.java b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/pvc/CommonPVCStrategy.java index 63437ea7678..a310c3aebc5 100644 --- a/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/pvc/CommonPVCStrategy.java +++ b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/pvc/CommonPVCStrategy.java @@ -24,10 +24,17 @@ import java.util.Set; import java.util.stream.Collectors; import javax.inject.Named; +import org.eclipse.che.api.core.Page; +import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.core.model.workspace.Workspace; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; +import org.eclipse.che.api.workspace.server.WorkspaceManager; +import org.eclipse.che.api.workspace.server.model.impl.WorkspaceImpl; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; +import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.commons.annotation.Traced; +import org.eclipse.che.commons.env.EnvironmentContext; +import org.eclipse.che.commons.subject.Subject; import org.eclipse.che.commons.tracing.TracingTags; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesNamespace; @@ -70,7 +77,6 @@ * @author Alexander Garagatyi */ public class CommonPVCStrategy implements WorkspaceVolumesStrategy { - // use non-static variable to reuse child class logger private final Logger log = LoggerFactory.getLogger(getClass()); @@ -96,6 +102,8 @@ public class CommonPVCStrategy implements WorkspaceVolumesStrategy { private final PodsVolumes podsVolumes; private final SubPathPrefixes subpathPrefixes; private final boolean waitBound; + private final String defaultNamespaceName; + private final WorkspaceManager workspaceManager; @Inject public CommonPVCStrategy( @@ -105,12 +113,14 @@ public CommonPVCStrategy( @Named("che.infra.kubernetes.pvc.precreate_subpaths") boolean preCreateDirs, @Named("che.infra.kubernetes.pvc.storage_class_name") String pvcStorageClassName, @Named("che.infra.kubernetes.pvc.wait_bound") boolean waitBound, + @Nullable @Named("che.infra.kubernetes.namespace.default") String defaultNamespaceName, PVCSubPathHelper pvcSubPathHelper, KubernetesNamespaceFactory factory, EphemeralWorkspaceAdapter ephemeralWorkspaceAdapter, PVCProvisioner pvcProvisioner, PodsVolumes podsVolumes, - SubPathPrefixes subpathPrefixes) { + SubPathPrefixes subpathPrefixes, + WorkspaceManager workspaceManager) { this.configuredPVCName = configuredPVCName; this.pvcQuantity = pvcQuantity; this.pvcAccessMode = pvcAccessMode; @@ -123,6 +133,8 @@ public CommonPVCStrategy( this.pvcProvisioner = pvcProvisioner; this.podsVolumes = podsVolumes; this.subpathPrefixes = subpathPrefixes; + this.defaultNamespaceName = defaultNamespaceName; + this.workspaceManager = workspaceManager; } /** @@ -229,6 +241,10 @@ public void prepare( public void cleanup(Workspace workspace) throws InfrastructureException { if (EphemeralWorkspaceUtility.isEphemeral(workspace)) { return; + } else if (noWorkspacesLeft()) { + log.debug("Deleting the common PVC: '{}',", configuredPVCName); + deleteCommonPVC(workspace); + return; } String workspaceId = workspace.getId(); PersistentVolumeClaim pvc = createCommonPVC(workspaceId); @@ -258,4 +274,70 @@ private Set combineVolumeMountsSubpaths(KubernetesEnvironment k8sEnv) { .filter(subpath -> !isNullOrEmpty(subpath)) .collect(Collectors.toSet()); } + + private void deleteCommonPVC(Workspace workspace) throws InfrastructureException { + factory.get(workspace).persistentVolumeClaims().delete(configuredPVCName); + } + + /** + * @return true, if the common PVC is expected to be deleted, false otherwise. Depending on the + * configuration it could be either the common PVC of a particular user, or the common PVC + * that is shared across all the users (the last setup is considered to be uncommon and + * not-recommended) + */ + private boolean noWorkspacesLeft() { + return defaultNamespaceWithoutPlaceholderIsDefined() + ? totalNumberOfWorkspacesIsZero() + : userHasNoWorkspaces(); + } + + /** + * The method is expected to be used in order to identify if the common PVC is used across all the + * users. The common PVC for all the users will be used if + * 'che.infra.kubernetes.namespace.default' points to a particular namespace e.g. 'che', + * 'workspaces' etc. + * + * @return true, if 'che.infra.kubernetes.namespace.default' is defined and does NOT contain the + * placeholder e.g.: che-workspace-), false otherwise + */ + private boolean defaultNamespaceWithoutPlaceholderIsDefined() { + return defaultNamespaceName != null + && !defaultNamespaceName.contains("<") + && !defaultNamespaceName.contains(">"); + } + + /** @return true, if there are no workspaces left across all the users, false otherwise */ + private boolean totalNumberOfWorkspacesIsZero() { + try { + return workspaceManager.getWorkspacesTotalCount() == 0; + } catch (ServerException e) { + log.error("Unable to get the total number of workspaces. Cause: {}", e.getMessage(), e); + } + return false; + } + + /** + * @return true, if a given user has no workspaces, false otherwise (or if the subject is + * anonymous) + */ + private boolean userHasNoWorkspaces() { + Subject subject = EnvironmentContext.getCurrent().getSubject(); + if (!subject.isAnonymous()) { + String userId = subject.getUserId(); + try { + Page workspaces = workspaceManager.getWorkspaces(userId, false, 1, 0); + if (workspaces.isEmpty()) { + log.debug("User '{}' has no more workspaces left", userId); + return true; + } + } catch (ServerException e) { + log.error( + "Unable to get the number of workspaces for user '{}'. Cause: {}", + userId, + e.getMessage(), + e); + } + } + return false; + } } diff --git a/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/pvc/PerWorkspacePVCStrategy.java b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/pvc/PerWorkspacePVCStrategy.java index 056b687d8f1..f5546910e99 100644 --- a/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/pvc/PerWorkspacePVCStrategy.java +++ b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/pvc/PerWorkspacePVCStrategy.java @@ -20,7 +20,9 @@ import javax.inject.Inject; import javax.inject.Named; import org.eclipse.che.api.core.model.workspace.Workspace; +import org.eclipse.che.api.workspace.server.WorkspaceManager; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; +import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesNamespaceFactory; /** @@ -57,12 +59,14 @@ public PerWorkspacePVCStrategy( @Named("che.infra.kubernetes.pvc.precreate_subpaths") boolean preCreateDirs, @Named("che.infra.kubernetes.pvc.storage_class_name") String pvcStorageClassName, @Named("che.infra.kubernetes.pvc.wait_bound") boolean waitBound, + @Nullable @Named("che.infra.kubernetes.namespace.default") String defaultNamespaceName, PVCSubPathHelper pvcSubPathHelper, KubernetesNamespaceFactory factory, EphemeralWorkspaceAdapter ephemeralWorkspaceAdapter, PVCProvisioner pvcProvisioner, PodsVolumes podsVolumes, - SubPathPrefixes subpathPrefixes) { + SubPathPrefixes subpathPrefixes, + WorkspaceManager workspaceManager) { super( pvcName, pvcQuantity, @@ -70,12 +74,14 @@ public PerWorkspacePVCStrategy( preCreateDirs, pvcStorageClassName, waitBound, + defaultNamespaceName, pvcSubPathHelper, factory, ephemeralWorkspaceAdapter, pvcProvisioner, podsVolumes, - subpathPrefixes); + subpathPrefixes, + workspaceManager); this.pvcNamePrefix = pvcName; this.factory = factory; this.pvcAccessMode = pvcAccessMode; diff --git a/infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/pvc/CommonPVCStrategyTest.java b/infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/pvc/CommonPVCStrategyTest.java index ea783ac9357..4c05c239894 100644 --- a/infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/pvc/CommonPVCStrategyTest.java +++ b/infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/pvc/CommonPVCStrategyTest.java @@ -17,6 +17,7 @@ import static org.eclipse.che.api.workspace.shared.Constants.PERSIST_VOLUMES_ATTRIBUTE; import static org.eclipse.che.workspace.infrastructure.kubernetes.namespace.pvc.CommonPVCStrategy.SUBPATHS_PROPERTY_FMT; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; @@ -41,11 +42,16 @@ import java.util.HashMap; import java.util.Map; import java.util.concurrent.CompletableFuture; +import org.eclipse.che.api.core.Page; import org.eclipse.che.api.core.model.workspace.Workspace; import org.eclipse.che.api.core.model.workspace.WorkspaceConfig; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; +import org.eclipse.che.api.workspace.server.WorkspaceManager; import org.eclipse.che.api.workspace.server.model.impl.RuntimeIdentityImpl; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; +import org.eclipse.che.commons.env.EnvironmentContext; +import org.eclipse.che.commons.subject.Subject; +import org.eclipse.che.commons.subject.SubjectImpl; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesNamespace; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesNamespaceFactory; @@ -69,6 +75,8 @@ public class CommonPVCStrategyTest { private static final String WORKSPACE_ID = "workspace123"; private static final String NAMESPACE = "infraNamespace"; private static final String PVC_NAME = "che-claim"; + private static final String DEFAULT_NAMESPACE_WITH_PLACEHOLDER = "-che"; + private static final String DEFAULT_NAMESPACE_WITHOUT_PLACEHOLDER = "che"; private static final String PVC_QUANTITY = "10Gi"; private static final String PVC_ACCESS_MODE = "RWO"; @@ -91,6 +99,7 @@ public class CommonPVCStrategyTest { @Mock private PVCProvisioner volumeConverter; @Mock private PodsVolumes podsVolumes; @Mock private SubPathPrefixes subpathPrefixes; + @Mock private WorkspaceManager workspaceManager; private InOrder provisionOrder; @@ -106,12 +115,14 @@ public void setup() throws Exception { true, PVC_STORAGE_CLASS_NAME, true, + DEFAULT_NAMESPACE_WITH_PLACEHOLDER, pvcSubPathHelper, factory, ephemeralWorkspaceAdapter, volumeConverter, podsVolumes, - subpathPrefixes); + subpathPrefixes, + workspaceManager); k8sEnv = KubernetesEnvironment.builder().build(); @@ -172,12 +183,14 @@ public void testDoNotAddsSubpathsPropertyWhenPreCreationIsNotNeeded() throws Exc false, PVC_STORAGE_CLASS_NAME, true, + DEFAULT_NAMESPACE_WITH_PLACEHOLDER, pvcSubPathHelper, factory, ephemeralWorkspaceAdapter, volumeConverter, podsVolumes, - subpathPrefixes); + subpathPrefixes, + workspaceManager); commonPVCStrategy.provision(k8sEnv, IDENTITY); @@ -220,12 +233,14 @@ public void testCreatesPVCsWithSubpathsOnPrepareIfWaitIsDisabled() throws Except true, PVC_STORAGE_CLASS_NAME, false, // wait bound PVCs + DEFAULT_NAMESPACE_WITH_PLACEHOLDER, pvcSubPathHelper, factory, ephemeralWorkspaceAdapter, volumeConverter, podsVolumes, - subpathPrefixes); + subpathPrefixes, + workspaceManager); final PersistentVolumeClaim pvc = newPVC(PVC_NAME); pvc.getAdditionalProperties() .put(format(SUBPATHS_PROPERTY_FMT, WORKSPACE_ID), WORKSPACE_SUBPATHS); @@ -279,8 +294,15 @@ public void shouldDeletePVCsIfThereIsNoPersistAttributeInWorkspaceConfigWhenClea throws Exception { // given Workspace workspace = mock(Workspace.class); + Page workspaces = mock(Page.class); + lenient().when(workspace.getId()).thenReturn(WORKSPACE_ID); + lenient() + .when(workspaceManager.getWorkspaces(anyString(), eq(false), anyInt(), anyLong())) + .thenReturn((workspaces)); + lenient().when(workspaces.isEmpty()).thenReturn(false); + WorkspaceConfig workspaceConfig = mock(WorkspaceConfig.class); lenient().when(workspace.getConfig()).thenReturn(workspaceConfig); @@ -303,7 +325,83 @@ public void shouldDeletePVCsIfPersistAttributeIsSetToTrueInWorkspaceConfigWhenCl throws Exception { // given Workspace workspace = mock(Workspace.class); + Page workspaces = mock(Page.class); + + lenient() + .when(workspaceManager.getWorkspaces(anyString(), eq(false), anyInt(), anyLong())) + .thenReturn((workspaces)); + lenient().when(workspaces.isEmpty()).thenReturn(false); + lenient().when(workspace.getId()).thenReturn(WORKSPACE_ID); + + WorkspaceConfig workspaceConfig = mock(WorkspaceConfig.class); + lenient().when(workspace.getConfig()).thenReturn(workspaceConfig); + + Map workspaceConfigAttributes = new HashMap<>(); + lenient().when(workspaceConfig.getAttributes()).thenReturn(workspaceConfigAttributes); + workspaceConfigAttributes.put(PERSIST_VOLUMES_ATTRIBUTE, "true"); + + KubernetesNamespace ns = mock(KubernetesNamespace.class); + when(factory.get(eq(workspace))).thenReturn(ns); + when(ns.getName()).thenReturn("ns"); + + // when + commonPVCStrategy.cleanup(workspace); + + // then + verify(pvcSubPathHelper).removeDirsAsync(WORKSPACE_ID, "ns", PVC_NAME, WORKSPACE_ID); + } + + @Test + public void shouldDeleteCommonPVCIfUserHasNoWorkspaces() throws Exception { + // given + Workspace workspace = mock(Workspace.class); + Page workspaces = mock(Page.class); + KubernetesPersistentVolumeClaims persistentVolumeClaims = + mock(KubernetesPersistentVolumeClaims.class); + + lenient() + .when(workspaceManager.getWorkspaces(anyString(), eq(false), anyInt(), anyLong())) + .thenReturn((workspaces)); + lenient().when(workspaces.isEmpty()).thenReturn(true); + lenient().when(workspace.getId()).thenReturn(WORKSPACE_ID); + + WorkspaceConfig workspaceConfig = mock(WorkspaceConfig.class); + lenient().when(workspace.getConfig()).thenReturn(workspaceConfig); + + Map workspaceConfigAttributes = new HashMap<>(); + lenient().when(workspaceConfig.getAttributes()).thenReturn(workspaceConfigAttributes); + workspaceConfigAttributes.put(PERSIST_VOLUMES_ATTRIBUTE, "true"); + + KubernetesNamespace ns = mock(KubernetesNamespace.class); + when(factory.get(eq(workspace))).thenReturn(ns); + when(ns.getName()).thenReturn("ns"); + when(ns.persistentVolumeClaims()).thenReturn(persistentVolumeClaims); + + EnvironmentContext context = new EnvironmentContext(); + context.setSubject(new SubjectImpl("user", "id123", "token123", false)); + EnvironmentContext.setCurrent(context); + + // when + commonPVCStrategy.cleanup(workspace); + + // then + verify(ns).persistentVolumeClaims(); + verify(persistentVolumeClaims).delete(PVC_NAME); + verify(pvcSubPathHelper, never()).removeDirsAsync(WORKSPACE_ID, "ns", PVC_NAME, WORKSPACE_ID); + } + + @Test + public void shoulNotDeleteCommonPVCIfUserHasWorkspaces() throws Exception { + // given + Workspace workspace = mock(Workspace.class); + Page workspaces = mock(Page.class); + KubernetesPersistentVolumeClaims persistentVolumeClaims = + mock(KubernetesPersistentVolumeClaims.class); + lenient() + .when(workspaceManager.getWorkspaces(anyString(), eq(false), anyInt(), anyLong())) + .thenReturn((workspaces)); + lenient().when(workspaces.isEmpty()).thenReturn(false); lenient().when(workspace.getId()).thenReturn(WORKSPACE_ID); WorkspaceConfig workspaceConfig = mock(WorkspaceConfig.class); @@ -317,13 +415,204 @@ public void shouldDeletePVCsIfPersistAttributeIsSetToTrueInWorkspaceConfigWhenCl when(factory.get(eq(workspace))).thenReturn(ns); when(ns.getName()).thenReturn("ns"); + EnvironmentContext context = new EnvironmentContext(); + context.setSubject(new SubjectImpl("user", "id123", "token123", false)); + EnvironmentContext.setCurrent(context); + // when commonPVCStrategy.cleanup(workspace); // then + verify(ns, never()).persistentVolumeClaims(); + verify(persistentVolumeClaims, never()).delete(PVC_NAME); verify(pvcSubPathHelper).removeDirsAsync(WORKSPACE_ID, "ns", PVC_NAME, WORKSPACE_ID); } + @Test + public void shoulNotDeleteCommonPVCIfSubjectIsAnonymous() throws Exception { + // given + Workspace workspace = mock(Workspace.class); + Page workspaces = mock(Page.class); + KubernetesPersistentVolumeClaims persistentVolumeClaims = + mock(KubernetesPersistentVolumeClaims.class); + + lenient() + .when(workspaceManager.getWorkspaces(anyString(), eq(false), anyInt(), anyLong())) + .thenReturn((workspaces)); + lenient().when(workspaces.isEmpty()).thenReturn(true); + lenient().when(workspace.getId()).thenReturn(WORKSPACE_ID); + + WorkspaceConfig workspaceConfig = mock(WorkspaceConfig.class); + lenient().when(workspace.getConfig()).thenReturn(workspaceConfig); + + Map workspaceConfigAttributes = new HashMap<>(); + lenient().when(workspaceConfig.getAttributes()).thenReturn(workspaceConfigAttributes); + workspaceConfigAttributes.put(PERSIST_VOLUMES_ATTRIBUTE, "true"); + + KubernetesNamespace ns = mock(KubernetesNamespace.class); + when(factory.get(eq(workspace))).thenReturn(ns); + when(ns.getName()).thenReturn("ns"); + + EnvironmentContext context = new EnvironmentContext(); + context.setSubject(Subject.ANONYMOUS); + EnvironmentContext.setCurrent(context); + + // when + commonPVCStrategy.cleanup(workspace); + + // then + verify(workspaceManager, never()).getWorkspaces(anyString(), eq(false), anyInt(), anyLong()); + verify(ns, never()).persistentVolumeClaims(); + verify(persistentVolumeClaims, never()).delete(PVC_NAME); + verify(pvcSubPathHelper).removeDirsAsync(WORKSPACE_ID, "ns", PVC_NAME, WORKSPACE_ID); + } + + @Test + public void shoulDeleteCommonPVCIfUsedAcrossAllUsersAndNoWorkspacesLeft() throws Exception { + // given + commonPVCStrategy = + new CommonPVCStrategy( + PVC_NAME, + PVC_QUANTITY, + PVC_ACCESS_MODE, + true, + PVC_STORAGE_CLASS_NAME, + false, // wait bound PVCs + DEFAULT_NAMESPACE_WITHOUT_PLACEHOLDER, + pvcSubPathHelper, + factory, + ephemeralWorkspaceAdapter, + volumeConverter, + podsVolumes, + subpathPrefixes, + workspaceManager); + Workspace workspace = mock(Workspace.class); + KubernetesPersistentVolumeClaims persistentVolumeClaims = + mock(KubernetesPersistentVolumeClaims.class); + + when(workspaceManager.getWorkspacesTotalCount()).thenReturn((long) 0); + + WorkspaceConfig workspaceConfig = mock(WorkspaceConfig.class); + lenient().when(workspace.getConfig()).thenReturn(workspaceConfig); + + Map workspaceConfigAttributes = new HashMap<>(); + lenient().when(workspaceConfig.getAttributes()).thenReturn(workspaceConfigAttributes); + workspaceConfigAttributes.put(PERSIST_VOLUMES_ATTRIBUTE, "true"); + + KubernetesNamespace ns = mock(KubernetesNamespace.class); + when(factory.get(eq(workspace))).thenReturn(ns); + when(ns.persistentVolumeClaims()).thenReturn(persistentVolumeClaims); + + // when + commonPVCStrategy.cleanup(workspace); + + // then + verify(workspaceManager).getWorkspacesTotalCount(); + verify(workspaceManager, never()).getWorkspaces(anyString(), eq(false), anyInt(), anyLong()); + verify(ns).persistentVolumeClaims(); + verify(persistentVolumeClaims).delete(PVC_NAME); + verify(pvcSubPathHelper, never()).removeDirsAsync(WORKSPACE_ID, "ns", PVC_NAME, WORKSPACE_ID); + } + + @Test + public void shoulNotDeleteCommonPVCIfUsedAcrossAllUsersAndThereAreWorkspaces() throws Exception { + // given + commonPVCStrategy = + new CommonPVCStrategy( + PVC_NAME, + PVC_QUANTITY, + PVC_ACCESS_MODE, + true, + PVC_STORAGE_CLASS_NAME, + false, // wait bound PVCs + DEFAULT_NAMESPACE_WITHOUT_PLACEHOLDER, + pvcSubPathHelper, + factory, + ephemeralWorkspaceAdapter, + volumeConverter, + podsVolumes, + subpathPrefixes, + workspaceManager); + Workspace workspace = mock(Workspace.class); + Page workspaces = mock(Page.class); + KubernetesPersistentVolumeClaims persistentVolumeClaims = + mock(KubernetesPersistentVolumeClaims.class); + + lenient() + .when(workspaceManager.getWorkspaces(anyString(), eq(false), anyInt(), anyLong())) + .thenReturn((workspaces)); + lenient().when(workspaces.isEmpty()).thenReturn(false); + + when(workspaceManager.getWorkspacesTotalCount()).thenReturn((long) 10); + + WorkspaceConfig workspaceConfig = mock(WorkspaceConfig.class); + lenient().when(workspace.getConfig()).thenReturn(workspaceConfig); + + Map workspaceConfigAttributes = new HashMap<>(); + lenient().when(workspaceConfig.getAttributes()).thenReturn(workspaceConfigAttributes); + workspaceConfigAttributes.put(PERSIST_VOLUMES_ATTRIBUTE, "true"); + + KubernetesNamespace ns = mock(KubernetesNamespace.class); + when(factory.get(eq(workspace))).thenReturn(ns); + + // when + commonPVCStrategy.cleanup(workspace); + + // then + verify(workspaceManager).getWorkspacesTotalCount(); + verify(workspaceManager, never()).getWorkspaces(anyString(), eq(false), anyInt(), anyLong()); + verify(ns, never()).persistentVolumeClaims(); + verify(persistentVolumeClaims, never()).delete(PVC_NAME); + verify(pvcSubPathHelper, never()).removeDirsAsync(WORKSPACE_ID, "ns", PVC_NAME, WORKSPACE_ID); + } + + @Test + public void shouldNotCheckTotalNumberOfWorkspacesIfDefaultNamespaceContainsPlaceholder() + throws Exception { + // given + commonPVCStrategy = + new CommonPVCStrategy( + PVC_NAME, + PVC_QUANTITY, + PVC_ACCESS_MODE, + true, + PVC_STORAGE_CLASS_NAME, + false, // wait bound PVCs + DEFAULT_NAMESPACE_WITH_PLACEHOLDER, + pvcSubPathHelper, + factory, + ephemeralWorkspaceAdapter, + volumeConverter, + podsVolumes, + subpathPrefixes, + workspaceManager); + Workspace workspace = mock(Workspace.class); + Page workspaces = mock(Page.class); + KubernetesPersistentVolumeClaims persistentVolumeClaims = + mock(KubernetesPersistentVolumeClaims.class); + + lenient() + .when(workspaceManager.getWorkspaces(anyString(), eq(false), anyInt(), anyLong())) + .thenReturn((workspaces)); + lenient().when(workspaces.isEmpty()).thenReturn(false); + + WorkspaceConfig workspaceConfig = mock(WorkspaceConfig.class); + lenient().when(workspace.getConfig()).thenReturn(workspaceConfig); + + Map workspaceConfigAttributes = new HashMap<>(); + lenient().when(workspaceConfig.getAttributes()).thenReturn(workspaceConfigAttributes); + workspaceConfigAttributes.put(PERSIST_VOLUMES_ATTRIBUTE, "true"); + + KubernetesNamespace ns = mock(KubernetesNamespace.class); + when(factory.get(eq(workspace))).thenReturn(ns); + + // when + commonPVCStrategy.cleanup(workspace); + + // then + verify(workspaceManager, never()).getWorkspacesTotalCount(); + } + @Test public void shouldDoNothingIfPersistAttributeIsSetToFalseInWorkspaceConfigWhenCleanupCalled() throws Exception { diff --git a/infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/pvc/PerWorkspacePVCStrategyTest.java b/infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/pvc/PerWorkspacePVCStrategyTest.java index c831c31efdf..cddfefd4f87 100644 --- a/infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/pvc/PerWorkspacePVCStrategyTest.java +++ b/infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/pvc/PerWorkspacePVCStrategyTest.java @@ -34,6 +34,7 @@ import org.eclipse.che.api.core.model.workspace.Workspace; import org.eclipse.che.api.core.model.workspace.WorkspaceConfig; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; +import org.eclipse.che.api.workspace.server.WorkspaceManager; import org.eclipse.che.api.workspace.server.model.impl.RuntimeIdentityImpl; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesNamespace; @@ -56,6 +57,7 @@ public class PerWorkspacePVCStrategyTest { private static final String WORKSPACE_ID = "workspace123"; private static final String INFRA_NAMESPACE = "infraNamespace"; private static final String PVC_NAME_PREFIX = "che-claim"; + private static final String DEFAULT_NAMESPACE_WITH_PLACEHOLDER = "-che"; private static final String PVC_QUANTITY = "10Gi"; private static final String PVC_ACCESS_MODE = "RWO"; @@ -73,6 +75,7 @@ public class PerWorkspacePVCStrategyTest { @Mock private PVCProvisioner volumeConverter; @Mock private PodsVolumes podsVolumes; @Mock private SubPathPrefixes subpathPrefixes; + @Mock private WorkspaceManager workspaceManager; private PerWorkspacePVCStrategy strategy; @@ -86,12 +89,14 @@ public void setup() throws Exception { true, PVC_STORAGE_CLASS_NAME, true, + DEFAULT_NAMESPACE_WITH_PLACEHOLDER, pvcSubPathHelper, factory, ephemeralWorkspaceAdapter, volumeConverter, podsVolumes, - subpathPrefixes); + subpathPrefixes, + workspaceManager); lenient().when(factory.getOrCreate(IDENTITY)).thenReturn(k8sNamespace); lenient().when(factory.get(any(Workspace.class))).thenReturn(k8sNamespace); @@ -132,12 +137,14 @@ public void shouldPreparePerWorkspacePVCWithSubPathsWhenWaitBoundIsDisabled() th true, PVC_STORAGE_CLASS_NAME, false, + DEFAULT_NAMESPACE_WITH_PLACEHOLDER, pvcSubPathHelper, factory, ephemeralWorkspaceAdapter, volumeConverter, podsVolumes, - subpathPrefixes); + subpathPrefixes, + workspaceManager); final PersistentVolumeClaim pvc = newPVC(PVC_NAME_PREFIX + "-" + WORKSPACE_ID); String perWorkspacePVCName = pvc.getMetadata().getName(); @@ -182,12 +189,14 @@ public void shouldReturnNullStorageClassNameWhenStorageClassNameIsEmptyOrNull() true, storageClassName, true, + DEFAULT_NAMESPACE_WITH_PLACEHOLDER, pvcSubPathHelper, factory, ephemeralWorkspaceAdapter, volumeConverter, podsVolumes, - subpathPrefixes); + subpathPrefixes, + workspaceManager); final PersistentVolumeClaim commonPVC = strategy.createCommonPVC(WORKSPACE_ID); diff --git a/infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/pvc/UniqueWorkspacePVCStrategyTest.java b/infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/pvc/UniqueWorkspacePVCStrategyTest.java index b7dd67a6ed2..95ca751cdb7 100644 --- a/infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/pvc/UniqueWorkspacePVCStrategyTest.java +++ b/infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/pvc/UniqueWorkspacePVCStrategyTest.java @@ -230,7 +230,7 @@ public void shouldDeletePVCsIfPersistAttributeIsSetToTrueInWorkspaceConfigWhenCl strategy.cleanup(workspace); // then - verify(pvcs).delete(any()); + verify(pvcs).delete(ImmutableMap.of(CHE_WORKSPACE_ID_LABEL, WORKSPACE_ID)); } @Test @@ -251,7 +251,7 @@ public void shouldDoNothingIfPersistAttributeIsSetToFalseInWorkspaceConfigWhenCl strategy.cleanup(workspace); // then - verify(pvcs, never()).delete(any()); + verify(pvcs, never()).delete(ImmutableMap.of(CHE_WORKSPACE_ID_LABEL, WORKSPACE_ID)); } static PersistentVolumeClaim newPVC(String name) {