feat: REST Service on the Che server-side that will initiate k8s namespace provisioning (#61)
* feat: REST Service on the Che server-side that will initiate k8s namespace provisioning Signed-off-by: Sergii Kabashniuk <skabashniuk@redhat.com>pull/72/head
parent
7bb2641d15
commit
74b47fa68b
|
|
@ -22,10 +22,13 @@ import java.util.List;
|
|||
import java.util.stream.Collectors;
|
||||
import javax.inject.Inject;
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.POST;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.Produces;
|
||||
import org.eclipse.che.api.core.rest.Service;
|
||||
import org.eclipse.che.api.workspace.server.spi.InfrastructureException;
|
||||
import org.eclipse.che.api.workspace.server.spi.NamespaceResolutionContext;
|
||||
import org.eclipse.che.commons.env.EnvironmentContext;
|
||||
import org.eclipse.che.dto.server.DtoFactory;
|
||||
import org.eclipse.che.workspace.infrastructure.kubernetes.api.shared.KubernetesNamespaceMeta;
|
||||
import org.eclipse.che.workspace.infrastructure.kubernetes.api.shared.dto.KubernetesNamespaceMetaDto;
|
||||
|
|
@ -63,6 +66,27 @@ public class KubernetesNamespaceService extends Service {
|
|||
return namespaceFactory.list().stream().map(this::asDto).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("provision")
|
||||
@Produces(APPLICATION_JSON)
|
||||
@ApiOperation(
|
||||
value = "Provision k8s namespace where user is able to create workspaces",
|
||||
notes =
|
||||
"This operation can be performed only by an authorized user."
|
||||
+ " This is a beta feature that may be significantly changed.",
|
||||
response = KubernetesNamespaceMetaDto.class)
|
||||
@ApiResponses({
|
||||
@ApiResponse(code = 200, message = "The namespace successfully provisioned"),
|
||||
@ApiResponse(
|
||||
code = 500,
|
||||
message = "Internal server error occurred during namespace provisioning")
|
||||
})
|
||||
public KubernetesNamespaceMetaDto provision() throws InfrastructureException {
|
||||
return asDto(
|
||||
namespaceFactory.provision(
|
||||
new NamespaceResolutionContext(EnvironmentContext.getCurrent().getSubject())));
|
||||
}
|
||||
|
||||
private KubernetesNamespaceMetaDto asDto(KubernetesNamespaceMeta kubernetesNamespaceMeta) {
|
||||
return DtoFactory.newDto(KubernetesNamespaceMetaDto.class)
|
||||
.withName(kubernetesNamespaceMeta.getName())
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ import org.eclipse.che.api.core.model.workspace.Workspace;
|
|||
import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity;
|
||||
import org.eclipse.che.api.user.server.PreferenceManager;
|
||||
import org.eclipse.che.api.user.server.UserManager;
|
||||
import org.eclipse.che.api.workspace.server.model.impl.RuntimeIdentityImpl;
|
||||
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;
|
||||
|
|
@ -349,6 +350,21 @@ public class KubernetesNamespaceFactory {
|
|||
return namespace;
|
||||
}
|
||||
|
||||
public KubernetesNamespaceMeta provision(NamespaceResolutionContext namespaceResolutionContext)
|
||||
throws InfrastructureException {
|
||||
KubernetesNamespace namespace =
|
||||
getOrCreate(
|
||||
new RuntimeIdentityImpl(
|
||||
null,
|
||||
null,
|
||||
namespaceResolutionContext.getUserId(),
|
||||
evaluateNamespaceName(namespaceResolutionContext)));
|
||||
|
||||
return fetchNamespace(namespace.getName())
|
||||
.orElseThrow(
|
||||
() -> new InfrastructureException("Not able to find namespace " + namespace.getName()));
|
||||
}
|
||||
|
||||
public KubernetesNamespace get(RuntimeIdentity identity) throws InfrastructureException {
|
||||
String workspaceId = identity.getWorkspaceId();
|
||||
String namespaceName = identity.getInfrastructureNamespace();
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import static java.util.Collections.singletonList;
|
|||
import static org.everrest.assured.JettyHttpServer.ADMIN_USER_NAME;
|
||||
import static org.everrest.assured.JettyHttpServer.ADMIN_USER_PASSWORD;
|
||||
import static org.everrest.assured.JettyHttpServer.SECURE_PATH;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.testng.Assert.assertEquals;
|
||||
|
|
@ -24,15 +25,25 @@ import com.google.common.collect.ImmutableMap;
|
|||
import com.jayway.restassured.response.Response;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import org.eclipse.che.api.core.rest.ApiExceptionMapper;
|
||||
import org.eclipse.che.api.core.rest.CheJsonProvider;
|
||||
import org.eclipse.che.api.workspace.server.spi.NamespaceResolutionContext;
|
||||
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.dto.server.DtoFactory;
|
||||
import org.eclipse.che.workspace.infrastructure.kubernetes.api.server.impls.KubernetesNamespaceMetaImpl;
|
||||
import org.eclipse.che.workspace.infrastructure.kubernetes.api.shared.dto.KubernetesNamespaceMetaDto;
|
||||
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesNamespaceFactory;
|
||||
import org.everrest.assured.EverrestJetty;
|
||||
import org.everrest.core.Filter;
|
||||
import org.everrest.core.GenericContainerRequest;
|
||||
import org.everrest.core.RequestFilter;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.testng.MockitoTestNGListener;
|
||||
import org.testng.Assert;
|
||||
import org.testng.annotations.Listeners;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
|
|
@ -44,6 +55,14 @@ import org.testng.annotations.Test;
|
|||
@Listeners(value = {EverrestJetty.class, MockitoTestNGListener.class})
|
||||
public class KubernetesNamespaceServiceTest {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static final ApiExceptionMapper MAPPER = new ApiExceptionMapper();
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static final EnvironmentFilter FILTER = new EnvironmentFilter();
|
||||
|
||||
private static final Subject SUBJECT = new SubjectImpl("john", "id-123", "token", false);
|
||||
|
||||
@SuppressWarnings("unused") // is declared for deploying by everrest-assured
|
||||
private CheJsonProvider jsonProvider = new CheJsonProvider(Collections.emptySet());
|
||||
|
||||
|
|
@ -73,7 +92,69 @@ public class KubernetesNamespaceServiceTest {
|
|||
verify(namespaceFactory).list();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldProvisionNamespace() throws Exception {
|
||||
// given
|
||||
KubernetesNamespaceMetaImpl namespaceMeta =
|
||||
new KubernetesNamespaceMetaImpl(
|
||||
"ws-namespace", ImmutableMap.of("phase", "active", "default", "true"));
|
||||
when(namespaceFactory.provision(any(NamespaceResolutionContext.class)))
|
||||
.thenReturn(namespaceMeta);
|
||||
// when
|
||||
final Response response =
|
||||
given()
|
||||
.auth()
|
||||
.basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD)
|
||||
.when()
|
||||
.post(SECURE_PATH + "/kubernetes/namespace/provision");
|
||||
// then
|
||||
|
||||
assertEquals(response.getStatusCode(), 200);
|
||||
KubernetesNamespaceMetaDto actual = unwrapDto(response, KubernetesNamespaceMetaDto.class);
|
||||
assertEquals(actual.getName(), namespaceMeta.getName());
|
||||
assertEquals(actual.getAttributes(), namespaceMeta.getAttributes());
|
||||
verify(namespaceFactory).provision(any(NamespaceResolutionContext.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldProvisionNamespaceWithCorrectContext() throws Exception {
|
||||
// given
|
||||
KubernetesNamespaceMetaImpl namespaceMeta =
|
||||
new KubernetesNamespaceMetaImpl(
|
||||
"ws-namespace", ImmutableMap.of("phase", "active", "default", "true"));
|
||||
when(namespaceFactory.provision(any(NamespaceResolutionContext.class)))
|
||||
.thenReturn(namespaceMeta);
|
||||
// when
|
||||
final Response response =
|
||||
given()
|
||||
.auth()
|
||||
.basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD)
|
||||
.when()
|
||||
.post(SECURE_PATH + "/kubernetes/namespace/provision");
|
||||
// then
|
||||
|
||||
assertEquals(response.getStatusCode(), 200);
|
||||
ArgumentCaptor<NamespaceResolutionContext> captor =
|
||||
ArgumentCaptor.forClass(NamespaceResolutionContext.class);
|
||||
verify(namespaceFactory).provision(captor.capture());
|
||||
NamespaceResolutionContext actualContext = captor.getValue();
|
||||
assertEquals(actualContext.getUserId(), SUBJECT.getUserId());
|
||||
assertEquals(actualContext.getUserName(), SUBJECT.getUserName());
|
||||
Assert.assertNull(actualContext.getWorkspaceId());
|
||||
}
|
||||
|
||||
private static <T> List<T> unwrapDtoList(Response response, Class<T> dtoClass) {
|
||||
return DtoFactory.getInstance().createListDtoFromJson(response.body().print(), dtoClass);
|
||||
}
|
||||
|
||||
private static <T> T unwrapDto(Response response, Class<T> dtoClass) {
|
||||
return DtoFactory.getInstance().createDtoFromJson(response.body().print(), dtoClass);
|
||||
}
|
||||
|
||||
@Filter
|
||||
public static class EnvironmentFilter implements RequestFilter {
|
||||
public void doFilter(GenericContainerRequest request) {
|
||||
EnvironmentContext.getCurrent().setSubject(SUBJECT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ import static org.testng.Assert.assertEquals;
|
|||
import static org.testng.Assert.assertFalse;
|
||||
import static org.testng.Assert.assertNull;
|
||||
import static org.testng.Assert.assertTrue;
|
||||
import static org.testng.Assert.fail;
|
||||
|
||||
import ch.qos.logback.classic.spi.LoggingEvent;
|
||||
import ch.qos.logback.core.Appender;
|
||||
|
|
@ -57,6 +58,7 @@ import java.util.Collections;
|
|||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
|
@ -76,6 +78,7 @@ 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.server.impls.KubernetesNamespaceMetaImpl;
|
||||
import org.eclipse.che.workspace.infrastructure.kubernetes.api.shared.KubernetesNamespaceMeta;
|
||||
import org.eclipse.che.workspace.infrastructure.kubernetes.util.KubernetesSharedPool;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
|
|
@ -1013,6 +1016,118 @@ public class KubernetesNamespaceFactoryTest {
|
|||
assertEquals(namespace, "ns1");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldHandleProvision() throws InfrastructureException {
|
||||
// given
|
||||
namespaceFactory =
|
||||
spy(
|
||||
new KubernetesNamespaceFactory(
|
||||
"",
|
||||
"",
|
||||
"<username>-che",
|
||||
false,
|
||||
true,
|
||||
NAMESPACE_LABELS,
|
||||
NAMESPACE_ANNOTATIONS,
|
||||
clientFactory,
|
||||
cheClientFactory,
|
||||
userManager,
|
||||
preferenceManager,
|
||||
pool));
|
||||
KubernetesNamespace toReturnNamespace = mock(KubernetesNamespace.class);
|
||||
when(toReturnNamespace.getName()).thenReturn("jondoe-che");
|
||||
doReturn(toReturnNamespace).when(namespaceFactory).doCreateNamespaceAccess(any(), any());
|
||||
KubernetesNamespaceMetaImpl namespaceMeta =
|
||||
new KubernetesNamespaceMetaImpl(
|
||||
"jondoe-che", ImmutableMap.of("phase", "active", "default", "true"));
|
||||
doReturn(Optional.of(namespaceMeta)).when(namespaceFactory).fetchNamespace(eq("jondoe-che"));
|
||||
|
||||
// when
|
||||
NamespaceResolutionContext context =
|
||||
new NamespaceResolutionContext("workspace123", "user123", "jondoe");
|
||||
KubernetesNamespaceMeta actual = namespaceFactory.provision(context);
|
||||
|
||||
// then
|
||||
assertEquals(actual.getName(), "jondoe-che");
|
||||
assertEquals(actual.getAttributes(), ImmutableMap.of("phase", "active", "default", "true"));
|
||||
}
|
||||
|
||||
@Test(
|
||||
expectedExceptions = InfrastructureException.class,
|
||||
expectedExceptionsMessageRegExp = "Not able to find namespace jondoe-cha-cha-cha")
|
||||
public void shouldFailToProvisionIfNotAbleToFindNamespace() throws InfrastructureException {
|
||||
// given
|
||||
namespaceFactory =
|
||||
spy(
|
||||
new KubernetesNamespaceFactory(
|
||||
"",
|
||||
"",
|
||||
"<username>-cha-cha-cha",
|
||||
false,
|
||||
true,
|
||||
NAMESPACE_LABELS,
|
||||
NAMESPACE_ANNOTATIONS,
|
||||
clientFactory,
|
||||
cheClientFactory,
|
||||
userManager,
|
||||
preferenceManager,
|
||||
pool));
|
||||
KubernetesNamespace toReturnNamespace = mock(KubernetesNamespace.class);
|
||||
when(toReturnNamespace.getName()).thenReturn("jondoe-cha-cha-cha");
|
||||
doReturn(toReturnNamespace).when(namespaceFactory).doCreateNamespaceAccess(any(), any());
|
||||
KubernetesNamespaceMetaImpl namespaceMeta =
|
||||
new KubernetesNamespaceMetaImpl(
|
||||
"jondoe-cha-cha-cha", ImmutableMap.of("phase", "active", "default", "true"));
|
||||
doReturn(Optional.empty()).when(namespaceFactory).fetchNamespace(eq("jondoe-cha-cha-cha"));
|
||||
|
||||
// when
|
||||
NamespaceResolutionContext context =
|
||||
new NamespaceResolutionContext("workspace123", "user123", "jondoe");
|
||||
namespaceFactory.provision(context);
|
||||
|
||||
// then
|
||||
fail("should not reach this point since exception has to be thrown");
|
||||
}
|
||||
|
||||
@Test(
|
||||
expectedExceptions = InfrastructureException.class,
|
||||
expectedExceptionsMessageRegExp = "Error occurred when tried to fetch default namespace")
|
||||
public void shouldFail2ProvisionIfNotAbleToFindNamespace() throws InfrastructureException {
|
||||
// given
|
||||
namespaceFactory =
|
||||
spy(
|
||||
new KubernetesNamespaceFactory(
|
||||
"",
|
||||
"",
|
||||
"<username>-cha-cha-cha",
|
||||
false,
|
||||
true,
|
||||
NAMESPACE_LABELS,
|
||||
NAMESPACE_ANNOTATIONS,
|
||||
clientFactory,
|
||||
cheClientFactory,
|
||||
userManager,
|
||||
preferenceManager,
|
||||
pool));
|
||||
KubernetesNamespace toReturnNamespace = mock(KubernetesNamespace.class);
|
||||
when(toReturnNamespace.getName()).thenReturn("jondoe-cha-cha-cha");
|
||||
doReturn(toReturnNamespace).when(namespaceFactory).doCreateNamespaceAccess(any(), any());
|
||||
KubernetesNamespaceMetaImpl namespaceMeta =
|
||||
new KubernetesNamespaceMetaImpl(
|
||||
"jondoe-cha-cha-cha", ImmutableMap.of("phase", "active", "default", "true"));
|
||||
doThrow(new InfrastructureException("Error occurred when tried to fetch default namespace"))
|
||||
.when(namespaceFactory)
|
||||
.fetchNamespace(eq("jondoe-cha-cha-cha"));
|
||||
|
||||
// when
|
||||
NamespaceResolutionContext context =
|
||||
new NamespaceResolutionContext("workspace123", "user123", "jondoe");
|
||||
namespaceFactory.provision(context);
|
||||
|
||||
// then
|
||||
fail("should not reach this point since exception has to be thrown");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUsernamePlaceholderInLabelsIsNotEvaluated() throws InfrastructureException {
|
||||
List<Namespace> namespaces =
|
||||
|
|
|
|||
Loading…
Reference in New Issue