Skip to content

Commit

Permalink
che #18369 Deleting the common PVC if there are no workspaces left fo…
Browse files Browse the repository at this point in the history
…r a given user

Signed-off-by: Ilya Buziuk <ibuziuk@redhat.com>
  • Loading branch information
ibuziuk committed Dec 31, 2020
1 parent a410747 commit b56a4c2
Show file tree
Hide file tree
Showing 6 changed files with 221 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,26 @@ public void delete(Map<String, String> 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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,16 @@
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.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;
Expand Down Expand Up @@ -70,7 +76,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());

Expand All @@ -96,6 +101,7 @@ public class CommonPVCStrategy implements WorkspaceVolumesStrategy {
private final PodsVolumes podsVolumes;
private final SubPathPrefixes subpathPrefixes;
private final boolean waitBound;
private final WorkspaceManager workspaceManager;

@Inject
public CommonPVCStrategy(
Expand All @@ -110,7 +116,8 @@ public CommonPVCStrategy(
EphemeralWorkspaceAdapter ephemeralWorkspaceAdapter,
PVCProvisioner pvcProvisioner,
PodsVolumes podsVolumes,
SubPathPrefixes subpathPrefixes) {
SubPathPrefixes subpathPrefixes,
WorkspaceManager workspaceManager) {
this.configuredPVCName = configuredPVCName;
this.pvcQuantity = pvcQuantity;
this.pvcAccessMode = pvcAccessMode;
Expand All @@ -123,6 +130,7 @@ public CommonPVCStrategy(
this.pvcProvisioner = pvcProvisioner;
this.podsVolumes = podsVolumes;
this.subpathPrefixes = subpathPrefixes;
this.workspaceManager = workspaceManager;
}

/**
Expand Down Expand Up @@ -229,6 +237,10 @@ public void prepare(
public void cleanup(Workspace workspace) throws InfrastructureException {
if (EphemeralWorkspaceUtility.isEphemeral(workspace)) {
return;
} else if (userHasNoWorkspaces()) {
log.debug("Deleting the common PVC: '{}',", configuredPVCName);
deleteCommonPVC(workspace);
return;
}
String workspaceId = workspace.getId();
PersistentVolumeClaim pvc = createCommonPVC(workspaceId);
Expand Down Expand Up @@ -258,4 +270,36 @@ private Set<String> combineVolumeMountsSubpaths(KubernetesEnvironment k8sEnv) {
.filter(subpath -> !isNullOrEmpty(subpath))
.collect(Collectors.toSet());
}

private void deleteCommonPVC(Workspace workspace) throws InfrastructureException {
factory.get(workspace).persistentVolumeClaims().delete(configuredPVCName);
}

private boolean userHasNoWorkspaces() {
String userId = getSessionUserIdOrUndefined();
if (!"undefined".equals(userId)) {
try {
Page<WorkspaceImpl> 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;
}

private String getSessionUserIdOrUndefined() {
final Subject subject = EnvironmentContext.getCurrent().getSubject();
if (!subject.isAnonymous()) {
return subject.getUserId();
}
return "undefined";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
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.workspace.infrastructure.kubernetes.namespace.KubernetesNamespaceFactory;

Expand Down Expand Up @@ -62,7 +63,8 @@ public PerWorkspacePVCStrategy(
EphemeralWorkspaceAdapter ephemeralWorkspaceAdapter,
PVCProvisioner pvcProvisioner,
PodsVolumes podsVolumes,
SubPathPrefixes subpathPrefixes) {
SubPathPrefixes subpathPrefixes,
WorkspaceManager workspaceManager) {
super(
pvcName,
pvcQuantity,
Expand All @@ -75,7 +77,8 @@ public PerWorkspacePVCStrategy(
ephemeralWorkspaceAdapter,
pvcProvisioner,
podsVolumes,
subpathPrefixes);
subpathPrefixes,
workspaceManager);
this.pvcNamePrefix = pvcName;
this.factory = factory;
this.pvcAccessMode = pvcAccessMode;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -41,11 +42,15 @@
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.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;
Expand Down Expand Up @@ -91,6 +96,7 @@ public class CommonPVCStrategyTest {
@Mock private PVCProvisioner volumeConverter;
@Mock private PodsVolumes podsVolumes;
@Mock private SubPathPrefixes subpathPrefixes;
@Mock private WorkspaceManager workspaceManager;

private InOrder provisionOrder;

Expand All @@ -111,7 +117,8 @@ public void setup() throws Exception {
ephemeralWorkspaceAdapter,
volumeConverter,
podsVolumes,
subpathPrefixes);
subpathPrefixes,
workspaceManager);

k8sEnv = KubernetesEnvironment.builder().build();

Expand Down Expand Up @@ -177,7 +184,8 @@ public void testDoNotAddsSubpathsPropertyWhenPreCreationIsNotNeeded() throws Exc
ephemeralWorkspaceAdapter,
volumeConverter,
podsVolumes,
subpathPrefixes);
subpathPrefixes,
workspaceManager);

commonPVCStrategy.provision(k8sEnv, IDENTITY);

Expand Down Expand Up @@ -225,7 +233,8 @@ public void testCreatesPVCsWithSubpathsOnPrepareIfWaitIsDisabled() throws Except
ephemeralWorkspaceAdapter,
volumeConverter,
podsVolumes,
subpathPrefixes);
subpathPrefixes,
workspaceManager);
final PersistentVolumeClaim pvc = newPVC(PVC_NAME);
pvc.getAdditionalProperties()
.put(format(SUBPATHS_PROPERTY_FMT, WORKSPACE_ID), WORKSPACE_SUBPATHS);
Expand Down Expand Up @@ -279,8 +288,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);

Expand All @@ -303,7 +319,12 @@ 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);
Expand All @@ -324,6 +345,122 @@ public void shouldDeletePVCsIfPersistAttributeIsSetToTrueInWorkspaceConfigWhenCl
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<String, String> 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);
lenient().when(workspace.getConfig()).thenReturn(workspaceConfig);

Map<String, String> 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(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 shoulNotDeleteCommonPVCIfUserIdIsUndefined() 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);
lenient().when(workspace.getConfig()).thenReturn(workspaceConfig);

Map<String, String> 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(new SubjectImpl("user", "undefined", "token123", false));
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 shouldDoNothingIfPersistAttributeIsSetToFalseInWorkspaceConfigWhenCleanupCalled()
throws Exception {
Expand Down
Loading

0 comments on commit b56a4c2

Please sign in to comment.