best effort to label namespace when starting new workspace (#18248)

7.22.x
Michal Vala 2020-11-05 11:33:24 +01:00 committed by GitHub
parent 7007453a11
commit c3a51fc004
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 498 additions and 60 deletions

View File

@ -261,7 +261,9 @@ che.infra.kubernetes.namespace.creation_allowed=true
che.infra.kubernetes.namespace.default=<username>-che
# List of labels to find Namespaces/Projects that are used for Che Workspaces.
# They are used to find prepared Namespaces/Projects for users in combination with `che.infra.kubernetes.namespace.annotations`.
# They are used to:
# - find prepared Namespaces/Projects for users in combination with `che.infra.kubernetes.namespace.annotations`.
# - actively label namespaces with any workspace.
che.infra.kubernetes.namespace.labels=app.kubernetes.io/part-of=che.eclipse.org,app.kubernetes.io/component=workspaces-namespace
# List of annotations to find Namespaces/Projects prepared for Che users workspaces.

View File

@ -21,8 +21,10 @@ import org.eclipse.che.api.workspace.server.spi.InfrastructureException;
import org.eclipse.che.commons.annotation.Nullable;
/**
* This {@link KubernetesClientFactory} is used to access Che installation namespace. It always
* provides client with default {@link Config}.
* This {@link KubernetesClientFactory} ensures that we use `che` ServiceAccount and not related to
* any workspace. It always provides client with default {@link Config}. It's useful for operations
* that needs permissions of `che` SA, such as operations inside `che` namespace (like creating a
* ConfigMaps for Gateway router) or some cluste-wide actions (like labeling the namespaces).
*/
@Singleton
public class CheServerKubernetesClientFactory extends KubernetesClientFactory {

View File

@ -17,6 +17,7 @@ import com.google.common.annotations.VisibleForTesting;
import io.fabric8.kubernetes.api.model.ConfigMap;
import io.fabric8.kubernetes.api.model.DoneableServiceAccount;
import io.fabric8.kubernetes.api.model.Namespace;
import io.fabric8.kubernetes.api.model.NamespaceBuilder;
import io.fabric8.kubernetes.api.model.PersistentVolumeClaim;
import io.fabric8.kubernetes.api.model.Pod;
import io.fabric8.kubernetes.api.model.Secret;
@ -28,6 +29,8 @@ import io.fabric8.kubernetes.client.KubernetesClientException;
import io.fabric8.kubernetes.client.Watch;
import io.fabric8.kubernetes.client.Watcher;
import io.fabric8.kubernetes.client.dsl.Resource;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
@ -69,13 +72,18 @@ public class KubernetesNamespace {
private final KubernetesServices services;
private final KubernetesPersistentVolumeClaims pvcs;
private final KubernetesIngresses ingresses;
/** Factory for workspace related operations clients */
private final KubernetesClientFactory clientFactory;
/** Factory for cluster related operations clients (like labeling the namespaces) */
private final KubernetesClientFactory cheSAClientFactory;
private final KubernetesSecrets secrets;
private final KubernetesConfigsMaps configMaps;
@VisibleForTesting
protected KubernetesNamespace(
KubernetesClientFactory clientFactory,
KubernetesClientFactory cheSAClientFactory,
String workspaceId,
String name,
KubernetesDeployments deployments,
@ -85,6 +93,7 @@ public class KubernetesNamespace {
KubernetesSecrets secrets,
KubernetesConfigsMaps configMaps) {
this.clientFactory = clientFactory;
this.cheSAClientFactory = cheSAClientFactory;
this.workspaceId = workspaceId;
this.name = name;
this.deployments = deployments;
@ -96,8 +105,13 @@ public class KubernetesNamespace {
}
public KubernetesNamespace(
KubernetesClientFactory clientFactory, Executor executor, String name, String workspaceId) {
KubernetesClientFactory clientFactory,
KubernetesClientFactory cheSAClientFactory,
Executor executor,
String name,
String workspaceId) {
this.clientFactory = clientFactory;
this.cheSAClientFactory = cheSAClientFactory;
this.workspaceId = workspaceId;
this.name = name;
this.deployments = new KubernetesDeployments(name, workspaceId, clientFactory, executor);
@ -113,12 +127,17 @@ public class KubernetesNamespace {
*
* <p>Preparing includes creating if needed and waiting for default service account.
*
* <p>The method will try to label the namespace with provided `labels`. It does not matter if the
* namespace already exists or we create new one. If update labels operation fail due to lack of
* permission, we do not fail completely.
*
* @param canCreate defines what to do when the namespace is not found. The namespace is created
* when {@code true}, otherwise an exception is thrown.
* @param labels labels that should be set to the namespace
* @throws InfrastructureException if any exception occurs during namespace preparation or if the
* namespace doesn't exist and {@code canCreate} is {@code false}.
*/
void prepare(boolean canCreate) throws InfrastructureException {
void prepare(boolean canCreate, Map<String, String> labels) throws InfrastructureException {
KubernetesClient client = clientFactory.create(workspaceId);
Namespace namespace = get(name, client);
@ -127,7 +146,51 @@ public class KubernetesNamespace {
throw new InfrastructureException(
format("Creating the namespace '%s' is not allowed, yet it was not found.", name));
}
create(name, client);
namespace = create(name, client);
}
label(namespace, labels);
}
/**
* Applies given `ensureLabels` into given `namespace` and update the `namespace` in the
* Kubernetes.
*
* <p>If we do not have permissions to do so (code=403), this method does not throw any exception.
*
* @param namespace namespace to label
* @param ensureLabels these labels should be applied on given `namespace`
* @throws InfrastructureException if something goes wrong with update, except lack of permissions
*/
protected void label(Namespace namespace, Map<String, String> ensureLabels)
throws InfrastructureException {
Map<String, String> currentLabels = namespace.getMetadata().getLabels();
Map<String, String> newLabels =
currentLabels != null ? new HashMap<>(currentLabels) : new HashMap<>();
if (newLabels.entrySet().containsAll(ensureLabels.entrySet())) {
LOG.debug(
"Nothing to do, namespace [{}] already has all required labels.",
namespace.getMetadata().getName());
return;
}
try {
// update the namespace with new labels
cheSAClientFactory
.create()
.namespaces()
.createOrReplace(
new NamespaceBuilder(namespace)
.editMetadata()
.addToLabels(ensureLabels)
.endMetadata()
.build());
} catch (KubernetesClientException kce) {
if (kce.getCode() == 403) {
LOG.debug("Can't label the namespace due to lack of permissions ¯\\_(ツ)_/¯");
return;
}
throw new InfrastructureException(kce);
}
}
@ -231,17 +294,19 @@ public class KubernetesNamespace {
}
}
private void create(String namespaceName, KubernetesClient client)
private Namespace create(String namespaceName, KubernetesClient client)
throws InfrastructureException {
try {
client
.namespaces()
.createNew()
.withNewMetadata()
.withName(namespaceName)
.endMetadata()
.done();
Namespace namespace =
client
.namespaces()
.createNew()
.withNewMetadata()
.withName(namespaceName)
.endMetadata()
.done();
waitDefaultServiceAccount(namespaceName, client);
return namespace;
} catch (KubernetesClientException e) {
if (e.getCode() == 403) {
LOG.error(

View File

@ -57,6 +57,7 @@ import org.eclipse.che.commons.lang.NameGenerator;
import org.eclipse.che.commons.lang.Pair;
import org.eclipse.che.commons.subject.Subject;
import org.eclipse.che.inject.ConfigurationException;
import org.eclipse.che.workspace.infrastructure.kubernetes.CheServerKubernetesClientFactory;
import org.eclipse.che.workspace.infrastructure.kubernetes.KubernetesClientFactory;
import org.eclipse.che.workspace.infrastructure.kubernetes.api.server.impls.KubernetesNamespaceMetaImpl;
import org.eclipse.che.workspace.infrastructure.kubernetes.api.shared.KubernetesNamespaceMeta;
@ -99,6 +100,7 @@ public class KubernetesNamespaceFactory {
private final String serviceAccountName;
private final Set<String> clusterRoleNames;
private final KubernetesClientFactory clientFactory;
private final KubernetesClientFactory cheClientFactory;
private final boolean namespaceCreationAllowed;
private final UserManager userManager;
private final PreferenceManager preferenceManager;
@ -116,6 +118,7 @@ public class KubernetesNamespaceFactory {
@Named("che.infra.kubernetes.namespace.labels") String namespaceLabels,
@Named("che.infra.kubernetes.namespace.annotations") String namespaceAnnotations,
KubernetesClientFactory clientFactory,
CheServerKubernetesClientFactory cheClientFactory,
UserManager userManager,
PreferenceManager preferenceManager,
KubernetesSharedPool sharedPool)
@ -125,6 +128,7 @@ public class KubernetesNamespaceFactory {
this.legacyNamespaceName = legacyNamespaceName;
this.serviceAccountName = serviceAccountName;
this.clientFactory = clientFactory;
this.cheClientFactory = cheClientFactory;
this.defaultNamespaceName = defaultNamespaceName;
this.allowUserDefinedNamespaces = allowUserDefinedNamespaces;
this.preferenceManager = preferenceManager;
@ -168,7 +172,8 @@ public class KubernetesNamespaceFactory {
@VisibleForTesting
KubernetesNamespace doCreateNamespaceAccess(String workspaceId, String name) {
return new KubernetesNamespace(clientFactory, sharedPool.getExecutor(), name, workspaceId);
return new KubernetesNamespace(
clientFactory, cheClientFactory, sharedPool.getExecutor(), name, workspaceId);
}
/**
@ -386,7 +391,7 @@ public class KubernetesNamespaceFactory {
public KubernetesNamespace getOrCreate(RuntimeIdentity identity) throws InfrastructureException {
KubernetesNamespace namespace = get(identity);
namespace.prepare(canCreateNamespace(identity));
namespace.prepare(canCreateNamespace(identity), namespaceLabels);
if (!isNullOrEmpty(serviceAccountName)) {
KubernetesWorkspaceServiceAccount workspaceServiceAccount =

View File

@ -74,6 +74,7 @@ import org.eclipse.che.api.workspace.shared.Constants;
import org.eclipse.che.commons.env.EnvironmentContext;
import org.eclipse.che.commons.subject.SubjectImpl;
import org.eclipse.che.inject.ConfigurationException;
import org.eclipse.che.workspace.infrastructure.kubernetes.CheServerKubernetesClientFactory;
import org.eclipse.che.workspace.infrastructure.kubernetes.KubernetesClientFactory;
import org.eclipse.che.workspace.infrastructure.kubernetes.api.shared.KubernetesNamespaceMeta;
import org.eclipse.che.workspace.infrastructure.kubernetes.util.KubernetesSharedPool;
@ -103,6 +104,7 @@ public class KubernetesNamespaceFactoryTest {
@Mock private KubernetesSharedPool pool;
@Mock private KubernetesClientFactory clientFactory;
@Mock private CheServerKubernetesClientFactory cheClientFactory;
private KubernetesClient k8sClient;
@Mock private UserManager userManager;
@Mock private PreferenceManager preferenceManager;
@ -164,6 +166,7 @@ public class KubernetesNamespaceFactoryTest {
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
clientFactory,
cheClientFactory,
userManager,
preferenceManager,
pool);
@ -186,6 +189,7 @@ public class KubernetesNamespaceFactoryTest {
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
clientFactory,
cheClientFactory,
userManager,
preferenceManager,
pool);
@ -213,6 +217,7 @@ public class KubernetesNamespaceFactoryTest {
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
clientFactory,
cheClientFactory,
userManager,
preferenceManager,
pool);
@ -238,6 +243,7 @@ public class KubernetesNamespaceFactoryTest {
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
clientFactory,
cheClientFactory,
userManager,
preferenceManager,
pool);
@ -260,6 +266,7 @@ public class KubernetesNamespaceFactoryTest {
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
clientFactory,
cheClientFactory,
userManager,
preferenceManager,
pool);
@ -310,6 +317,7 @@ public class KubernetesNamespaceFactoryTest {
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
clientFactory,
cheClientFactory,
userManager,
preferenceManager,
pool);
@ -353,6 +361,7 @@ public class KubernetesNamespaceFactoryTest {
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
clientFactory,
cheClientFactory,
userManager,
preferenceManager,
pool);
@ -383,6 +392,7 @@ public class KubernetesNamespaceFactoryTest {
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
clientFactory,
cheClientFactory,
userManager,
preferenceManager,
pool);
@ -417,6 +427,7 @@ public class KubernetesNamespaceFactoryTest {
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
clientFactory,
cheClientFactory,
userManager,
preferenceManager,
pool);
@ -445,6 +456,7 @@ public class KubernetesNamespaceFactoryTest {
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
clientFactory,
cheClientFactory,
userManager,
preferenceManager,
pool);
@ -476,6 +488,7 @@ public class KubernetesNamespaceFactoryTest {
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
clientFactory,
cheClientFactory,
userManager,
preferenceManager,
pool);
@ -502,6 +515,7 @@ public class KubernetesNamespaceFactoryTest {
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
clientFactory,
cheClientFactory,
userManager,
preferenceManager,
pool);
@ -537,6 +551,7 @@ public class KubernetesNamespaceFactoryTest {
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
clientFactory,
cheClientFactory,
userManager,
preferenceManager,
pool);
@ -573,6 +588,7 @@ public class KubernetesNamespaceFactoryTest {
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
clientFactory,
cheClientFactory,
userManager,
preferenceManager,
pool);
@ -603,6 +619,7 @@ public class KubernetesNamespaceFactoryTest {
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
clientFactory,
cheClientFactory,
userManager,
preferenceManager,
pool));
@ -617,7 +634,7 @@ public class KubernetesNamespaceFactoryTest {
// then
assertEquals(toReturnNamespace, namespace);
verify(namespaceFactory, never()).doCreateServiceAccount(any(), any());
verify(toReturnNamespace).prepare(eq(false));
verify(toReturnNamespace).prepare(eq(false), any());
}
@Test
@ -635,6 +652,7 @@ public class KubernetesNamespaceFactoryTest {
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
clientFactory,
cheClientFactory,
userManager,
preferenceManager,
pool));
@ -649,7 +667,7 @@ public class KubernetesNamespaceFactoryTest {
// then
assertEquals(toReturnNamespace, namespace);
verify(namespaceFactory, never()).doCreateServiceAccount(any(), any());
verify(toReturnNamespace).prepare(eq(false));
verify(toReturnNamespace).prepare(eq(false), any());
}
@Test
@ -668,6 +686,7 @@ public class KubernetesNamespaceFactoryTest {
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
clientFactory,
cheClientFactory,
userManager,
preferenceManager,
pool));
@ -705,6 +724,7 @@ public class KubernetesNamespaceFactoryTest {
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
clientFactory,
cheClientFactory,
userManager,
preferenceManager,
pool));
@ -773,6 +793,7 @@ public class KubernetesNamespaceFactoryTest {
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
clientFactory,
cheClientFactory,
userManager,
preferenceManager,
pool));
@ -829,6 +850,7 @@ public class KubernetesNamespaceFactoryTest {
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
clientFactory,
cheClientFactory,
userManager,
preferenceManager,
pool);
@ -848,6 +870,7 @@ public class KubernetesNamespaceFactoryTest {
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
clientFactory,
cheClientFactory,
userManager,
preferenceManager,
pool);
@ -871,6 +894,7 @@ public class KubernetesNamespaceFactoryTest {
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
clientFactory,
cheClientFactory,
userManager,
preferenceManager,
pool);
@ -898,6 +922,7 @@ public class KubernetesNamespaceFactoryTest {
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
clientFactory,
cheClientFactory,
userManager,
preferenceManager,
pool);
@ -928,6 +953,7 @@ public class KubernetesNamespaceFactoryTest {
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
clientFactory,
cheClientFactory,
userManager,
preferenceManager,
pool);
@ -959,6 +985,7 @@ public class KubernetesNamespaceFactoryTest {
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
clientFactory,
cheClientFactory,
userManager,
preferenceManager,
pool);
@ -990,6 +1017,7 @@ public class KubernetesNamespaceFactoryTest {
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
clientFactory,
cheClientFactory,
userManager,
preferenceManager,
pool);
@ -1021,6 +1049,7 @@ public class KubernetesNamespaceFactoryTest {
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
clientFactory,
cheClientFactory,
userManager,
preferenceManager,
pool);
@ -1048,6 +1077,7 @@ public class KubernetesNamespaceFactoryTest {
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
clientFactory,
cheClientFactory,
userManager,
preferenceManager,
pool);
@ -1077,6 +1107,7 @@ public class KubernetesNamespaceFactoryTest {
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
clientFactory,
cheClientFactory,
userManager,
preferenceManager,
pool);
@ -1128,6 +1159,7 @@ public class KubernetesNamespaceFactoryTest {
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
clientFactory,
cheClientFactory,
userManager,
preferenceManager,
pool);
@ -1165,6 +1197,7 @@ public class KubernetesNamespaceFactoryTest {
"try_placeholder_here=<username>",
NAMESPACE_ANNOTATIONS,
clientFactory,
cheClientFactory,
userManager,
preferenceManager,
pool);
@ -1187,6 +1220,7 @@ public class KubernetesNamespaceFactoryTest {
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
clientFactory,
cheClientFactory,
userManager,
preferenceManager,
pool);
@ -1206,6 +1240,7 @@ public class KubernetesNamespaceFactoryTest {
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
clientFactory,
cheClientFactory,
userManager,
preferenceManager,
pool);

View File

@ -24,6 +24,7 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertTrue;
import io.fabric8.kubernetes.api.model.DoneableNamespace;
import io.fabric8.kubernetes.api.model.DoneableServiceAccount;
@ -31,6 +32,7 @@ import io.fabric8.kubernetes.api.model.Namespace;
import io.fabric8.kubernetes.api.model.NamespaceBuilder;
import io.fabric8.kubernetes.api.model.NamespaceFluent.MetadataNested;
import io.fabric8.kubernetes.api.model.ServiceAccount;
import io.fabric8.kubernetes.api.model.Status;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.KubernetesClientException;
import io.fabric8.kubernetes.client.Watch;
@ -39,9 +41,12 @@ import io.fabric8.kubernetes.client.Watcher.Action;
import io.fabric8.kubernetes.client.dsl.MixedOperation;
import io.fabric8.kubernetes.client.dsl.NonNamespaceOperation;
import io.fabric8.kubernetes.client.dsl.Resource;
import java.util.Map;
import java.util.concurrent.Executor;
import org.eclipse.che.api.workspace.server.spi.InfrastructureException;
import org.eclipse.che.workspace.infrastructure.kubernetes.CheServerKubernetesClientFactory;
import org.eclipse.che.workspace.infrastructure.kubernetes.KubernetesClientFactory;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.stubbing.Answer;
import org.mockito.testng.MockitoTestNGListener;
@ -67,6 +72,7 @@ public class KubernetesNamespaceTest {
@Mock private KubernetesSecrets secrets;
@Mock private KubernetesConfigsMaps configMaps;
@Mock private KubernetesClientFactory clientFactory;
@Mock private CheServerKubernetesClientFactory cheClientFactory;
@Mock private Executor executor;
@Mock private KubernetesClient kubernetesClient;
@Mock private NonNamespaceOperation namespaceOperation;
@ -90,6 +96,7 @@ public class KubernetesNamespaceTest {
k8sNamespace =
new KubernetesNamespace(
clientFactory,
cheClientFactory,
WORKSPACE_ID,
NAMESPACE,
deployments,
@ -103,14 +110,16 @@ public class KubernetesNamespaceTest {
@Test
public void testKubernetesNamespacePreparingWhenNamespaceExists() throws Exception {
// given
MetadataNested namespaceMeta = prepareCreateNamespaceRequest();
MetadataNested namespaceMeta =
prepareCreateNamespaceRequest(
new NamespaceBuilder().withNewMetadata().withName(NAMESPACE).endMetadata().build());
prepareNamespace(NAMESPACE);
KubernetesNamespace namespace =
new KubernetesNamespace(clientFactory, executor, NAMESPACE, WORKSPACE_ID);
new KubernetesNamespace(clientFactory, cheClientFactory, executor, NAMESPACE, WORKSPACE_ID);
// when
namespace.prepare(true);
namespace.prepare(true, Map.of());
// then
verify(namespaceMeta, never()).withName(NAMESPACE);
@ -119,15 +128,17 @@ public class KubernetesNamespaceTest {
@Test
public void testKubernetesNamespacePreparingCreationWhenNamespaceDoesNotExist() throws Exception {
// given
MetadataNested namespaceMeta = prepareCreateNamespaceRequest();
MetadataNested namespaceMeta =
prepareCreateNamespaceRequest(
new NamespaceBuilder().withNewMetadata().withName(NAMESPACE).endMetadata().build());
Resource resource = prepareNamespaceResource(NAMESPACE);
doThrow(new KubernetesClientException("error", 403, null)).when(resource).get();
KubernetesNamespace namespace =
new KubernetesNamespace(clientFactory, executor, NAMESPACE, WORKSPACE_ID);
new KubernetesNamespace(clientFactory, cheClientFactory, executor, NAMESPACE, WORKSPACE_ID);
// when
namespace.prepare(true);
namespace.prepare(true, Map.of());
// then
verify(namespaceMeta).withName(NAMESPACE);
@ -139,10 +150,10 @@ public class KubernetesNamespaceTest {
Resource resource = prepareNamespaceResource(NAMESPACE);
doThrow(new KubernetesClientException("error", 403, null)).when(resource).get();
KubernetesNamespace namespace =
new KubernetesNamespace(clientFactory, executor, NAMESPACE, WORKSPACE_ID);
new KubernetesNamespace(clientFactory, cheClientFactory, executor, NAMESPACE, WORKSPACE_ID);
// when
namespace.prepare(false);
namespace.prepare(false, Map.of());
// then
// exception is thrown
@ -189,7 +200,8 @@ public class KubernetesNamespaceTest {
doThrow(new KubernetesClientException("error", 403, null)).when(resource).get();
doThrow(KubernetesClientException.class).when(kubernetesClient).serviceAccounts();
new KubernetesNamespace(clientFactory, executor, NAMESPACE, WORKSPACE_ID).prepare(false);
new KubernetesNamespace(clientFactory, cheClientFactory, executor, NAMESPACE, WORKSPACE_ID)
.prepare(false, Map.of());
}
@Test(expectedExceptions = InfrastructureException.class)
@ -200,7 +212,8 @@ public class KubernetesNamespaceTest {
doThrow(new KubernetesClientException("error", 403, null)).when(resource).get();
when(serviceAccountResource.get()).thenReturn(null);
new KubernetesNamespace(clientFactory, executor, NAMESPACE, WORKSPACE_ID).prepare(false);
new KubernetesNamespace(clientFactory, cheClientFactory, executor, NAMESPACE, WORKSPACE_ID)
.prepare(false, Map.of());
}
@Test(expectedExceptions = InfrastructureException.class)
@ -219,12 +232,16 @@ public class KubernetesNamespaceTest {
.when(serviceAccountResource)
.watch(any());
new KubernetesNamespace(clientFactory, executor, NAMESPACE, WORKSPACE_ID).prepare(false);
new KubernetesNamespace(clientFactory, cheClientFactory, executor, NAMESPACE, WORKSPACE_ID)
.prepare(false, Map.of());
}
@Test
public void testStopsWaitingServiceAccountEventJustAfterEventReceived() throws Exception {
prepareCreateNamespaceRequest();
prepareCreateNamespaceRequest(
new NamespaceBuilder().withNewMetadata().withName(NAMESPACE).endMetadata().build());
final Resource resource = prepareNamespaceResource(NAMESPACE);
doThrow(new KubernetesClientException("error", 403, null)).when(resource).get();
when(serviceAccountResource.get()).thenReturn(null);
@ -237,7 +254,8 @@ public class KubernetesNamespaceTest {
.when(serviceAccountResource)
.watch(any());
new KubernetesNamespace(clientFactory, executor, NAMESPACE, WORKSPACE_ID).prepare(true);
new KubernetesNamespace(clientFactory, cheClientFactory, executor, NAMESPACE, WORKSPACE_ID)
.prepare(true, Map.of());
verify(serviceAccountResource).get();
verify(serviceAccountResource).watch(any());
@ -247,7 +265,7 @@ public class KubernetesNamespaceTest {
public void testDeletesExistingNamespace() throws Exception {
// given
KubernetesNamespace namespace =
new KubernetesNamespace(clientFactory, executor, NAMESPACE, WORKSPACE_ID);
new KubernetesNamespace(clientFactory, cheClientFactory, executor, NAMESPACE, WORKSPACE_ID);
Resource resource = prepareNamespaceResource(NAMESPACE);
// when
@ -261,7 +279,7 @@ public class KubernetesNamespaceTest {
public void testDoesntFailIfDeletedNamespaceDoesntExist() throws Exception {
// given
KubernetesNamespace namespace =
new KubernetesNamespace(clientFactory, executor, NAMESPACE, WORKSPACE_ID);
new KubernetesNamespace(clientFactory, cheClientFactory, executor, NAMESPACE, WORKSPACE_ID);
Resource resource = prepareNamespaceResource(NAMESPACE);
when(resource.get()).thenThrow(new KubernetesClientException("err", 404, null));
when(resource.delete()).thenThrow(new KubernetesClientException("err", 404, null));
@ -278,7 +296,7 @@ public class KubernetesNamespaceTest {
public void testDoesntFailIfDeletedNamespaceIsBeingDeleted() throws Exception {
// given
KubernetesNamespace namespace =
new KubernetesNamespace(clientFactory, executor, NAMESPACE, WORKSPACE_ID);
new KubernetesNamespace(clientFactory, cheClientFactory, executor, NAMESPACE, WORKSPACE_ID);
Resource resource = prepareNamespaceResource(NAMESPACE);
when(resource.delete()).thenThrow(new KubernetesClientException("err", 409, null));
@ -290,21 +308,146 @@ public class KubernetesNamespaceTest {
// and no exception is thrown
}
@Test
public void testLabelNamespace() throws InfrastructureException {
// given
prepareNamespace(NAMESPACE);
KubernetesNamespace kubernetesNamespace =
new KubernetesNamespace(clientFactory, cheClientFactory, executor, NAMESPACE, WORKSPACE_ID);
KubernetesClient cheKubeClient = mock(KubernetesClient.class);
doReturn(cheKubeClient).when(cheClientFactory).create();
NonNamespaceOperation nonNamespaceOperation = mock(NonNamespaceOperation.class);
doReturn(nonNamespaceOperation).when(cheKubeClient).namespaces();
ArgumentCaptor<Namespace> namespaceArgumentCaptor = ArgumentCaptor.forClass(Namespace.class);
doAnswer(a -> a.getArgument(0))
.when(nonNamespaceOperation)
.createOrReplace(namespaceArgumentCaptor.capture());
Map<String, String> labels = Map.of("label.with.this", "yes", "and.this", "of courese");
// when
kubernetesNamespace.prepare(true, labels);
// then
Namespace updatedNamespace = namespaceArgumentCaptor.getValue();
assertTrue(
updatedNamespace.getMetadata().getLabels().entrySet().containsAll(labels.entrySet()));
assertEquals(updatedNamespace.getMetadata().getName(), NAMESPACE);
}
@Test
public void testDontTryToLabelNamespaceIfAlreadyLabeled() throws InfrastructureException {
// given
Map<String, String> labels = Map.of("label.with.this", "yes", "and.this", "of courese");
Namespace namespace = prepareNamespace(NAMESPACE);
namespace.getMetadata().setLabels(labels);
KubernetesNamespace kubernetesNamespace =
new KubernetesNamespace(clientFactory, cheClientFactory, executor, NAMESPACE, WORKSPACE_ID);
KubernetesClient cheKubeClient = mock(KubernetesClient.class);
lenient().doReturn(cheKubeClient).when(cheClientFactory).create();
NonNamespaceOperation nonNamespaceOperation = mock(NonNamespaceOperation.class);
lenient().doReturn(nonNamespaceOperation).when(cheKubeClient).namespaces();
lenient()
.doAnswer(a -> a.getArgument(0))
.when(nonNamespaceOperation)
.createOrReplace(any(Namespace.class));
// when
kubernetesNamespace.prepare(true, labels);
// then
assertTrue(namespace.getMetadata().getLabels().entrySet().containsAll(labels.entrySet()));
verify(nonNamespaceOperation, never()).createOrReplace(any());
}
@Test
public void testDoNotFailWhenNoPermissionsToUpdateNamespace() throws InfrastructureException {
// given
Map<String, String> labels = Map.of("label.with.this", "yes", "and.this", "of courese");
prepareNamespace(NAMESPACE);
KubernetesNamespace kubernetesNamespace =
new KubernetesNamespace(clientFactory, cheClientFactory, executor, NAMESPACE, WORKSPACE_ID);
KubernetesClient cheKubeClient = mock(KubernetesClient.class);
lenient().doReturn(cheKubeClient).when(cheClientFactory).create();
NonNamespaceOperation nonNamespaceOperation = mock(NonNamespaceOperation.class);
lenient().doReturn(nonNamespaceOperation).when(cheKubeClient).namespaces();
ArgumentCaptor<Namespace> namespaceArgumentCaptor = ArgumentCaptor.forClass(Namespace.class);
lenient()
.doThrow(new KubernetesClientException("No permissions.", 403, new Status()))
.when(nonNamespaceOperation)
.createOrReplace(namespaceArgumentCaptor.capture());
// when
kubernetesNamespace.prepare(true, labels);
// then
Namespace updatedNamespace = namespaceArgumentCaptor.getValue();
assertTrue(
updatedNamespace.getMetadata().getLabels().entrySet().containsAll(labels.entrySet()));
assertEquals(updatedNamespace.getMetadata().getName(), NAMESPACE);
}
@Test(expectedExceptions = InfrastructureException.class)
public void testFailWhenFailToUpdateNamespace() throws InfrastructureException {
// given
Map<String, String> labels = Map.of("label.with.this", "yes", "and.this", "of courese");
Namespace namespace = prepareNamespace(NAMESPACE);
KubernetesNamespace kubernetesNamespace =
new KubernetesNamespace(clientFactory, cheClientFactory, executor, NAMESPACE, WORKSPACE_ID);
KubernetesClient cheKubeClient = mock(KubernetesClient.class);
lenient().doReturn(cheKubeClient).when(cheClientFactory).create();
NonNamespaceOperation nonNamespaceOperation = mock(NonNamespaceOperation.class);
lenient().doReturn(nonNamespaceOperation).when(cheKubeClient).namespaces();
lenient()
.doThrow(new KubernetesClientException("Some other error", 500, new Status()))
.when(nonNamespaceOperation)
.createOrReplace(any(Namespace.class));
// when
kubernetesNamespace.prepare(true, labels);
// then
verify(nonNamespaceOperation).createOrReplace(namespace);
}
private MetadataNested prepareCreateNamespaceRequest() {
return prepareCreateNamespaceRequest(new NamespaceBuilder().build());
}
private MetadataNested prepareCreateNamespaceRequest(Namespace ns) {
DoneableNamespace namespace = mock(DoneableNamespace.class);
MetadataNested metadataNested = mock(MetadataNested.class);
doReturn(namespace).when(namespaceOperation).createNew();
doReturn(metadataNested).when(namespace).withNewMetadata();
doReturn(metadataNested).when(metadataNested).withName(anyString());
doReturn(namespace).when(metadataNested).endMetadata();
lenient().doReturn(namespace).when(namespaceOperation).createNew();
lenient().doReturn(metadataNested).when(namespace).withNewMetadata();
lenient().doReturn(metadataNested).when(metadataNested).withName(anyString());
lenient().doReturn(namespace).when(metadataNested).endMetadata();
lenient().doReturn(ns).when(namespace).done();
return metadataNested;
}
private Resource prepareNamespaceResource(String namespaceName) {
Resource namespaceResource = mock(Resource.class);
doReturn(namespaceResource).when(namespaceOperation).withName(namespaceName);
doReturn(namespaceResource).when(namespaceResource).withPropagationPolicy(eq("Background"));
lenient()
.doReturn(namespaceResource)
.when(namespaceResource)
.withPropagationPolicy(eq("Background"));
when(namespaceResource.get())
.thenReturn(
new NamespaceBuilder().withNewMetadata().withName(namespaceName).endMetadata().build());
@ -313,7 +456,8 @@ public class KubernetesNamespaceTest {
}
private Namespace prepareNamespace(String namespaceName) {
Namespace namespace = mock(Namespace.class);
Namespace namespace =
new NamespaceBuilder().withNewMetadata().withName(namespaceName).endMetadata().build();
Resource namespaceResource = prepareNamespaceResource(namespaceName);
doReturn(namespace).when(namespaceResource).get();
return namespace;

View File

@ -19,9 +19,11 @@ import io.fabric8.kubernetes.client.KubernetesClientException;
import io.fabric8.openshift.api.model.Project;
import io.fabric8.openshift.api.model.Route;
import io.fabric8.openshift.client.OpenShiftClient;
import java.util.Map;
import java.util.concurrent.Executor;
import org.eclipse.che.api.workspace.server.spi.InfrastructureException;
import org.eclipse.che.api.workspace.server.spi.InternalInfrastructureException;
import org.eclipse.che.workspace.infrastructure.kubernetes.KubernetesClientFactory;
import org.eclipse.che.workspace.infrastructure.kubernetes.KubernetesInfrastructureException;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesConfigsMaps;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesDeployments;
@ -49,6 +51,7 @@ public class OpenShiftProject extends KubernetesNamespace {
@VisibleForTesting
OpenShiftProject(
OpenShiftClientFactory clientFactory,
KubernetesClientFactory cheClientFactory,
String workspaceId,
String name,
KubernetesDeployments deployments,
@ -60,6 +63,7 @@ public class OpenShiftProject extends KubernetesNamespace {
KubernetesConfigsMaps configMaps) {
super(
clientFactory,
cheClientFactory,
workspaceId,
name,
deployments,
@ -73,8 +77,12 @@ public class OpenShiftProject extends KubernetesNamespace {
}
public OpenShiftProject(
OpenShiftClientFactory clientFactory, Executor executor, String name, String workspaceId) {
super(clientFactory, executor, name, workspaceId);
OpenShiftClientFactory clientFactory,
KubernetesClientFactory cheClientFactory,
Executor executor,
String name,
String workspaceId) {
super(clientFactory, cheClientFactory, executor, name, workspaceId);
this.clientFactory = clientFactory;
this.routes = new OpenShiftRoutes(name, workspaceId, clientFactory);
}
@ -89,7 +97,7 @@ public class OpenShiftProject extends KubernetesNamespace {
* @throws InfrastructureException if any exception occurs during project preparation or if the
* project doesn't exist and {@code canCreate} is {@code false}.
*/
void prepare(boolean canCreate) throws InfrastructureException {
void prepare(boolean canCreate, Map<String, String> labels) throws InfrastructureException {
String workspaceId = getWorkspaceId();
String projectName = getName();
@ -109,6 +117,7 @@ public class OpenShiftProject extends KubernetesNamespace {
create(projectName, osClient);
waitDefaultServiceAccount(projectName, kubeClient);
}
label(osClient.namespaces().withName(projectName).get(), labels);
}
/**

View File

@ -35,6 +35,7 @@ import org.eclipse.che.api.user.server.UserManager;
import org.eclipse.che.api.workspace.server.spi.InfrastructureException;
import org.eclipse.che.api.workspace.server.spi.NamespaceResolutionContext;
import org.eclipse.che.commons.annotation.Nullable;
import org.eclipse.che.workspace.infrastructure.kubernetes.CheServerKubernetesClientFactory;
import org.eclipse.che.workspace.infrastructure.kubernetes.api.server.impls.KubernetesNamespaceMetaImpl;
import org.eclipse.che.workspace.infrastructure.kubernetes.api.shared.KubernetesNamespaceMeta;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesNamespaceFactory;
@ -56,6 +57,7 @@ public class OpenShiftProjectFactory extends KubernetesNamespaceFactory {
private static final Logger LOG = LoggerFactory.getLogger(OpenShiftProjectFactory.class);
private final OpenShiftClientFactory clientFactory;
private final CheServerKubernetesClientFactory cheClientFactory;
private final OpenShiftStopWorkspaceRoleProvisioner stopWorkspaceRoleProvisioner;
private final String oAuthIdentityProvider;
@ -72,6 +74,7 @@ public class OpenShiftProjectFactory extends KubernetesNamespaceFactory {
@Named("che.infra.kubernetes.namespace.labels") String projectLabels,
@Named("che.infra.kubernetes.namespace.annotations") String projectAnnotations,
OpenShiftClientFactory clientFactory,
CheServerKubernetesClientFactory cheClientFactory,
OpenShiftClientConfigFactory clientConfigFactory,
OpenShiftStopWorkspaceRoleProvisioner stopWorkspaceRoleProvisioner,
UserManager userManager,
@ -89,6 +92,7 @@ public class OpenShiftProjectFactory extends KubernetesNamespaceFactory {
projectLabels,
projectAnnotations,
clientFactory,
cheClientFactory,
userManager,
preferenceManager,
sharedPool);
@ -99,6 +103,7 @@ public class OpenShiftProjectFactory extends KubernetesNamespaceFactory {
+ "OAuth to personalize credentials that will be used for cluster access.");
}
this.clientFactory = clientFactory;
this.cheClientFactory = cheClientFactory;
this.stopWorkspaceRoleProvisioner = stopWorkspaceRoleProvisioner;
this.oAuthIdentityProvider = oAuthIdentityProvider;
}
@ -106,7 +111,7 @@ public class OpenShiftProjectFactory extends KubernetesNamespaceFactory {
public OpenShiftProject getOrCreate(RuntimeIdentity identity) throws InfrastructureException {
OpenShiftProject osProject = get(identity);
osProject.prepare(canCreateNamespace(identity));
osProject.prepare(canCreateNamespace(identity), namespaceLabels);
if (!isNullOrEmpty(getServiceAccountName())) {
OpenShiftWorkspaceServiceAccount osWorkspaceServiceAccount =
@ -156,7 +161,8 @@ public class OpenShiftProjectFactory extends KubernetesNamespaceFactory {
@VisibleForTesting
OpenShiftProject doCreateProjectAccess(String workspaceId, String name) {
return new OpenShiftProject(clientFactory, sharedPool.getExecutor(), name, workspaceId);
return new OpenShiftProject(
clientFactory, cheClientFactory, sharedPool.getExecutor(), name, workspaceId);
}
@VisibleForTesting

View File

@ -64,6 +64,7 @@ import org.eclipse.che.api.workspace.server.spi.NamespaceResolutionContext;
import org.eclipse.che.commons.env.EnvironmentContext;
import org.eclipse.che.commons.subject.SubjectImpl;
import org.eclipse.che.inject.ConfigurationException;
import org.eclipse.che.workspace.infrastructure.kubernetes.CheServerKubernetesClientFactory;
import org.eclipse.che.workspace.infrastructure.kubernetes.api.shared.KubernetesNamespaceMeta;
import org.eclipse.che.workspace.infrastructure.kubernetes.util.KubernetesSharedPool;
import org.eclipse.che.workspace.infrastructure.openshift.OpenShiftClientConfigFactory;
@ -94,6 +95,7 @@ public class OpenShiftProjectFactoryTest {
@Mock private OpenShiftClientConfigFactory configFactory;
@Mock private OpenShiftClientFactory clientFactory;
@Mock private CheServerKubernetesClientFactory cheClientFactory;
@Mock private OpenShiftStopWorkspaceRoleProvisioner stopWorkspaceRoleProvisioner;
@Mock private WorkspaceManager workspaceManager;
@Mock private UserManager userManager;
@ -154,6 +156,7 @@ public class OpenShiftProjectFactoryTest {
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
clientFactory,
cheClientFactory,
configFactory,
stopWorkspaceRoleProvisioner,
userManager,
@ -179,6 +182,7 @@ public class OpenShiftProjectFactoryTest {
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
clientFactory,
cheClientFactory,
configFactory,
stopWorkspaceRoleProvisioner,
userManager,
@ -207,6 +211,7 @@ public class OpenShiftProjectFactoryTest {
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
clientFactory,
cheClientFactory,
configFactory,
stopWorkspaceRoleProvisioner,
userManager,
@ -234,6 +239,7 @@ public class OpenShiftProjectFactoryTest {
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
clientFactory,
cheClientFactory,
configFactory,
stopWorkspaceRoleProvisioner,
userManager,
@ -270,6 +276,7 @@ public class OpenShiftProjectFactoryTest {
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
clientFactory,
cheClientFactory,
configFactory,
stopWorkspaceRoleProvisioner,
userManager,
@ -307,6 +314,7 @@ public class OpenShiftProjectFactoryTest {
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
clientFactory,
cheClientFactory,
configFactory,
stopWorkspaceRoleProvisioner,
userManager,
@ -340,6 +348,7 @@ public class OpenShiftProjectFactoryTest {
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
clientFactory,
cheClientFactory,
configFactory,
stopWorkspaceRoleProvisioner,
userManager,
@ -383,6 +392,7 @@ public class OpenShiftProjectFactoryTest {
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
clientFactory,
cheClientFactory,
configFactory,
stopWorkspaceRoleProvisioner,
userManager,
@ -420,6 +430,7 @@ public class OpenShiftProjectFactoryTest {
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
clientFactory,
cheClientFactory,
configFactory,
stopWorkspaceRoleProvisioner,
userManager,
@ -457,6 +468,7 @@ public class OpenShiftProjectFactoryTest {
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
clientFactory,
cheClientFactory,
configFactory,
stopWorkspaceRoleProvisioner,
userManager,
@ -486,6 +498,7 @@ public class OpenShiftProjectFactoryTest {
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
clientFactory,
cheClientFactory,
configFactory,
stopWorkspaceRoleProvisioner,
userManager,
@ -531,6 +544,7 @@ public class OpenShiftProjectFactoryTest {
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
clientFactory,
cheClientFactory,
configFactory,
stopWorkspaceRoleProvisioner,
userManager,
@ -571,6 +585,7 @@ public class OpenShiftProjectFactoryTest {
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
clientFactory,
cheClientFactory,
configFactory,
stopWorkspaceRoleProvisioner,
userManager,
@ -603,6 +618,7 @@ public class OpenShiftProjectFactoryTest {
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
clientFactory,
cheClientFactory,
configFactory,
stopWorkspaceRoleProvisioner,
userManager,
@ -620,7 +636,7 @@ public class OpenShiftProjectFactoryTest {
// then
assertEquals(toReturnProject, project);
verify(projectFactory, never()).doCreateServiceAccount(any(), any());
verify(toReturnProject).prepare(eq(false));
verify(toReturnProject).prepare(eq(false), any());
}
@Test
@ -639,6 +655,7 @@ public class OpenShiftProjectFactoryTest {
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
clientFactory,
cheClientFactory,
configFactory,
stopWorkspaceRoleProvisioner,
userManager,
@ -677,6 +694,7 @@ public class OpenShiftProjectFactoryTest {
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
clientFactory,
cheClientFactory,
configFactory,
stopWorkspaceRoleProvisioner,
userManager,
@ -717,6 +735,7 @@ public class OpenShiftProjectFactoryTest {
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
clientFactory,
cheClientFactory,
configFactory,
stopWorkspaceRoleProvisioner,
userManager,
@ -769,6 +788,7 @@ public class OpenShiftProjectFactoryTest {
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
clientFactory,
cheClientFactory,
configFactory,
stopWorkspaceRoleProvisioner,
userManager,
@ -802,6 +822,7 @@ public class OpenShiftProjectFactoryTest {
"try_placeholder_here=<username>",
NAMESPACE_ANNOTATIONS,
clientFactory,
cheClientFactory,
configFactory,
stopWorkspaceRoleProvisioner,
userManager,

View File

@ -11,7 +11,9 @@
*/
package org.eclipse.che.workspace.infrastructure.openshift.project;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.lenient;
@ -21,9 +23,13 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertTrue;
import io.fabric8.kubernetes.api.model.DoneableServiceAccount;
import io.fabric8.kubernetes.api.model.Namespace;
import io.fabric8.kubernetes.api.model.NamespaceBuilder;
import io.fabric8.kubernetes.api.model.ServiceAccount;
import io.fabric8.kubernetes.api.model.Status;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.KubernetesClientException;
import io.fabric8.kubernetes.client.dsl.MixedOperation;
@ -35,8 +41,10 @@ import io.fabric8.openshift.api.model.ProjectBuilder;
import io.fabric8.openshift.api.model.ProjectRequestFluent.MetadataNested;
import io.fabric8.openshift.client.OpenShiftClient;
import io.fabric8.openshift.client.dsl.ProjectRequestOperation;
import java.util.Map;
import java.util.concurrent.Executor;
import org.eclipse.che.api.workspace.server.spi.InfrastructureException;
import org.eclipse.che.workspace.infrastructure.kubernetes.CheServerKubernetesClientFactory;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesConfigsMaps;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesDeployments;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesIngresses;
@ -44,6 +52,7 @@ import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesP
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesSecrets;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesServices;
import org.eclipse.che.workspace.infrastructure.openshift.OpenShiftClientFactory;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.testng.MockitoTestNGListener;
import org.testng.annotations.BeforeMethod;
@ -69,6 +78,7 @@ public class OpenShiftProjectTest {
@Mock private KubernetesSecrets secrets;
@Mock private KubernetesConfigsMaps configsMaps;
@Mock private OpenShiftClientFactory clientFactory;
@Mock private CheServerKubernetesClientFactory cheClientFactory;
@Mock private Executor executor;
@Mock private OpenShiftClient openShiftClient;
@Mock private KubernetesClient kubernetesClient;
@ -93,6 +103,7 @@ public class OpenShiftProjectTest {
openShiftProject =
new OpenShiftProject(
clientFactory,
cheClientFactory,
WORKSPACE_ID,
PROJECT_NAME,
deployments,
@ -108,13 +119,14 @@ public class OpenShiftProjectTest {
public void testOpenShiftProjectPreparingWhenProjectExists() throws Exception {
// given
MetadataNested projectMeta = prepareProjectRequest();
prepareNamespaceGet(PROJECT_NAME);
prepareProject(PROJECT_NAME);
OpenShiftProject project =
new OpenShiftProject(clientFactory, executor, PROJECT_NAME, WORKSPACE_ID);
new OpenShiftProject(clientFactory, cheClientFactory, executor, PROJECT_NAME, WORKSPACE_ID);
// when
project.prepare(true);
project.prepare(true, Map.of());
// then
verify(projectMeta, never()).withName(PROJECT_NAME);
@ -124,14 +136,15 @@ public class OpenShiftProjectTest {
public void testOpenShiftProjectPreparingWhenProjectDoesNotExist() throws Exception {
// given
MetadataNested projectMetadata = prepareProjectRequest();
prepareNamespaceGet(PROJECT_NAME);
Resource resource = prepareProjectResource(PROJECT_NAME);
doThrow(new KubernetesClientException("error", 403, null)).when(resource).get();
OpenShiftProject project =
new OpenShiftProject(clientFactory, executor, PROJECT_NAME, WORKSPACE_ID);
new OpenShiftProject(clientFactory, cheClientFactory, executor, PROJECT_NAME, WORKSPACE_ID);
// when
openShiftProject.prepare(true);
openShiftProject.prepare(true, Map.of());
// then
verify(projectMetadata).withName(PROJECT_NAME);
@ -143,10 +156,10 @@ public class OpenShiftProjectTest {
Resource resource = prepareProjectResource(PROJECT_NAME);
doThrow(new KubernetesClientException("error", 403, null)).when(resource).get();
OpenShiftProject project =
new OpenShiftProject(clientFactory, executor, PROJECT_NAME, WORKSPACE_ID);
new OpenShiftProject(clientFactory, cheClientFactory, executor, PROJECT_NAME, WORKSPACE_ID);
// when
project.prepare(false);
project.prepare(false, Map.of());
// then
// exception is thrown
@ -189,7 +202,7 @@ public class OpenShiftProjectTest {
public void testDeletesExistingProject() throws Exception {
// given
OpenShiftProject project =
new OpenShiftProject(clientFactory, executor, PROJECT_NAME, WORKSPACE_ID);
new OpenShiftProject(clientFactory, cheClientFactory, executor, PROJECT_NAME, WORKSPACE_ID);
Resource resource = prepareProjectResource(PROJECT_NAME);
// when
@ -203,7 +216,7 @@ public class OpenShiftProjectTest {
public void testDoesntFailIfDeletedProjectDoesntExist() throws Exception {
// given
OpenShiftProject project =
new OpenShiftProject(clientFactory, executor, PROJECT_NAME, WORKSPACE_ID);
new OpenShiftProject(clientFactory, cheClientFactory, executor, PROJECT_NAME, WORKSPACE_ID);
Resource resource = prepareProjectResource(PROJECT_NAME);
when(resource.get()).thenThrow(new KubernetesClientException("err", 404, null));
when(resource.delete()).thenThrow(new KubernetesClientException("err", 404, null));
@ -220,7 +233,7 @@ public class OpenShiftProjectTest {
public void testDoesntFailIfDeletedProjectIsBeingDeleted() throws Exception {
// given
OpenShiftProject project =
new OpenShiftProject(clientFactory, executor, PROJECT_NAME, WORKSPACE_ID);
new OpenShiftProject(clientFactory, cheClientFactory, executor, PROJECT_NAME, WORKSPACE_ID);
Resource resource = prepareProjectResource(PROJECT_NAME);
when(resource.delete()).thenThrow(new KubernetesClientException("err", 409, null));
@ -232,16 +245,136 @@ public class OpenShiftProjectTest {
// and no exception is thrown
}
@Test
public void testLabelNamespace() throws InfrastructureException {
// given
prepareProject(PROJECT_NAME);
prepareNamespaceGet(PROJECT_NAME);
OpenShiftProject openShiftProject =
new OpenShiftProject(clientFactory, cheClientFactory, executor, PROJECT_NAME, WORKSPACE_ID);
KubernetesClient cheKubeClient = mock(KubernetesClient.class);
doReturn(cheKubeClient).when(cheClientFactory).create();
NonNamespaceOperation nonNamespaceOperation = mock(NonNamespaceOperation.class);
doReturn(nonNamespaceOperation).when(cheKubeClient).namespaces();
ArgumentCaptor<Namespace> namespaceArgumentCaptor = ArgumentCaptor.forClass(Namespace.class);
doAnswer(a -> a.getArgument(0))
.when(nonNamespaceOperation)
.createOrReplace(namespaceArgumentCaptor.capture());
Map<String, String> labels = Map.of("label.with.this", "yes", "and.this", "of courese");
// when
openShiftProject.prepare(true, labels);
// then
Namespace updatedNamespace = namespaceArgumentCaptor.getValue();
assertTrue(
updatedNamespace.getMetadata().getLabels().entrySet().containsAll(labels.entrySet()));
assertEquals(updatedNamespace.getMetadata().getName(), PROJECT_NAME);
}
@Test
public void testDontTryToLabelNamespaceIfAlreadyLabeled() throws InfrastructureException {
// given
Map<String, String> labels = Map.of("label.with.this", "yes", "and.this", "of courese");
prepareProject(PROJECT_NAME);
Namespace namespace = prepareNamespaceGet(PROJECT_NAME);
namespace.getMetadata().setLabels(labels);
OpenShiftProject openShiftProject =
new OpenShiftProject(clientFactory, cheClientFactory, executor, PROJECT_NAME, WORKSPACE_ID);
KubernetesClient cheKubeClient = mock(KubernetesClient.class);
doReturn(cheKubeClient).when(cheClientFactory).create();
NonNamespaceOperation nonNamespaceOperation = mock(NonNamespaceOperation.class);
doReturn(nonNamespaceOperation).when(cheKubeClient).namespaces();
doAnswer(a -> a.getArgument(0))
.when(nonNamespaceOperation)
.createOrReplace(any(Namespace.class));
// when
openShiftProject.prepare(true, labels);
// then
assertTrue(namespace.getMetadata().getLabels().entrySet().containsAll(labels.entrySet()));
verify(nonNamespaceOperation, never()).createOrReplace(any());
}
@Test
public void testDoNotFailWhenNoPermissionsToUpdateNamespace() throws InfrastructureException {
// given
Map<String, String> labels = Map.of("label.with.this", "yes", "and.this", "of courese");
prepareProject(PROJECT_NAME);
prepareNamespaceGet(PROJECT_NAME);
OpenShiftProject openShiftProject =
new OpenShiftProject(clientFactory, cheClientFactory, executor, PROJECT_NAME, WORKSPACE_ID);
KubernetesClient cheKubeClient = mock(KubernetesClient.class);
lenient().doReturn(cheKubeClient).when(cheClientFactory).create();
NonNamespaceOperation nonNamespaceOperation = mock(NonNamespaceOperation.class);
lenient().doReturn(nonNamespaceOperation).when(cheKubeClient).namespaces();
ArgumentCaptor<Namespace> namespaceArgumentCaptor = ArgumentCaptor.forClass(Namespace.class);
lenient()
.doThrow(new KubernetesClientException("No permissions.", 403, new Status()))
.when(nonNamespaceOperation)
.createOrReplace(namespaceArgumentCaptor.capture());
// when
openShiftProject.prepare(true, labels);
// then
Namespace updatedNamespace = namespaceArgumentCaptor.getValue();
assertTrue(
updatedNamespace.getMetadata().getLabels().entrySet().containsAll(labels.entrySet()));
assertEquals(updatedNamespace.getMetadata().getName(), PROJECT_NAME);
}
@Test(expectedExceptions = InfrastructureException.class)
public void testFailWhenFailToUpdateNamespace() throws InfrastructureException {
// given
Map<String, String> labels = Map.of("label.with.this", "yes", "and.this", "of courese");
prepareProject(PROJECT_NAME);
Namespace namespace = prepareNamespaceGet(PROJECT_NAME);
OpenShiftProject openShiftProject =
new OpenShiftProject(clientFactory, cheClientFactory, executor, PROJECT_NAME, WORKSPACE_ID);
KubernetesClient cheKubeClient = mock(KubernetesClient.class);
lenient().doReturn(cheKubeClient).when(cheClientFactory).create();
NonNamespaceOperation nonNamespaceOperation = mock(NonNamespaceOperation.class);
lenient().doReturn(nonNamespaceOperation).when(cheKubeClient).namespaces();
lenient()
.doThrow(new KubernetesClientException("Some other error", 500, new Status()))
.when(nonNamespaceOperation)
.createOrReplace(any(Namespace.class));
// when
openShiftProject.prepare(true, labels);
// then
verify(nonNamespaceOperation).createOrReplace(namespace);
}
private MetadataNested prepareProjectRequest() {
ProjectRequestOperation projectRequestOperation = mock(ProjectRequestOperation.class);
DoneableProjectRequest projectRequest = mock(DoneableProjectRequest.class);
MetadataNested metadataNested = mock(MetadataNested.class);
doReturn(projectRequestOperation).when(openShiftClient).projectrequests();
doReturn(projectRequest).when(projectRequestOperation).createNew();
doReturn(metadataNested).when(projectRequest).withNewMetadata();
doReturn(metadataNested).when(metadataNested).withName(anyString());
doReturn(projectRequest).when(metadataNested).endMetadata();
lenient().doReturn(projectRequestOperation).when(openShiftClient).projectrequests();
lenient().doReturn(projectRequest).when(projectRequestOperation).createNew();
lenient().doReturn(metadataNested).when(projectRequest).withNewMetadata();
lenient().doReturn(metadataNested).when(metadataNested).withName(anyString());
lenient().doReturn(projectRequest).when(metadataNested).endMetadata();
return metadataNested;
}
@ -260,8 +393,24 @@ public class OpenShiftProjectTest {
return projectResource;
}
private Namespace prepareNamespaceGet(String namespaceName) {
Namespace namespace =
new NamespaceBuilder().withNewMetadata().withName(namespaceName).endMetadata().build();
NonNamespaceOperation nsOperation = mock(NonNamespaceOperation.class);
doReturn(nsOperation).when(openShiftClient).namespaces();
Resource nsResource = mock(Resource.class);
doReturn(nsResource).when(nsOperation).withName(namespaceName);
doReturn(namespace).when(nsResource).get();
return namespace;
}
private Project prepareProject(String projectName) {
Project project = mock(Project.class);
Project project =
new ProjectBuilder().withNewMetadata().withName(projectName).endMetadata().build();
Resource projectResource = prepareProjectResource(projectName);
doReturn(project).when(projectResource).get();
return project;