Make CHE 6 multi-tenant compatible for OSIO (#8805)

* Copy the boostrapper config file to POD *installer by installer* to avoid executing a huge command, which in turn tries to send a huge GET request (73kb-long URL).
This long GET request was previously not supported by the fabric8 oso proxy.

See https://github.com/openshiftio/openshift.io/issues/2254 for more
details. 

* Make the Kubernetes / Openshift infrastructures multi-tenant-compatible

This includes:

1. Reworking the `KubernetesClientFactory` and `OpenshiftClientFactory`
to:
    - share a single `OkHttpClient` that is the basis of all created
Kubernetes or Openshift clients potentially pointing to
different.cluster URL with different authorization tokens
    - provide the workspaceId in the `create` methods whenever it's
available (workspace start / stop, idling, etc ...
    - have distinct methods for creating the Kubernetes client and the
Openshift client (required since the Openshift client creates connection
leaks on some calls such as exec of POD watch)
This is the implementation of issue
https://github.com/redhat-developer/rh-che/issues/516

2. Adding the `userId` into the SPI `RuntimeIdentity` object. Currently,
only the `userName` is available in this object that gathers information
about the identity attached to a workspace runtime. This change is
required because the `userId` should be accessible from the
`workspaceId` for implementing multi-tenancy and creating workspace
resources in a user-specific location.
This is the implementation of issue
https://github.com/redhat-developer/rh-che/issues/501

* In the Openshift infrastructure, the authentication interceptor should
always convert userName/password-based authentication to
oauth-token-based authentication, even when using the
`KubernetesClient`.

Signed-off-by: David Festal <dfestal@redhat.com>
6.19.x
David Festal 2018-02-22 18:23:10 +01:00 committed by GitHub
parent 9abfa3690f
commit 21e00e9b15
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
44 changed files with 466 additions and 125 deletions

View File

@ -26,7 +26,8 @@ var (
testRuntimeID = RuntimeID{
Workspace: "my-workspace",
Environment: "my-env",
Owner: "me",
OwnerName: "me",
OwnerId: "id",
}
testMachineName = "my-machine"

View File

@ -79,8 +79,11 @@ type RuntimeID struct {
// Environment is a name of environment e.g. "default".
Environment string `json:"envName"`
// Owner is an identifier of user who is runtime owner.
Owner string `json:"owner"`
// OwnerName is the name of user who is runtime owner.
OwnerName string `json:"ownerName"`
// OwnerId is an identifier of user who is runtime owner.
OwnerId string `json:"ownerId"`
}
// MachineEvent is a base event for bootstrapper events.

View File

@ -97,7 +97,7 @@ func init() {
&runtimeIDRaw,
"runtime-id",
"",
"The identifier of the runtime in format 'workspace:environment:owner'",
"The identifier of the runtime in format 'workspace:environment:ownerName:ownerId'",
)
flag.StringVar(
&MachineName,
@ -155,10 +155,10 @@ func Parse() {
log.Fatal("Runtime ID required(set it with -runtime-id argument)")
}
parts := strings.Split(runtimeIDRaw, ":")
if len(parts) != 3 {
log.Fatalf("Expected runtime id to be in format 'workspace:env:owner'")
if len(parts) != 4 {
log.Fatalf("Expected runtime id to be in format 'workspace:env:ownerName:ownerId'")
}
RuntimeID = booter.RuntimeID{Workspace: parts[0], Environment: parts[1], Owner: parts[2]}
RuntimeID = booter.RuntimeID{Workspace: parts[0], Environment: parts[1], OwnerName: parts[2], OwnerId: parts[3]}
// machine-name
if len(MachineName) == 0 {
@ -182,7 +182,8 @@ func Print() {
log.Print(" Runtime ID:")
log.Printf(" Workspace: %s", RuntimeID.Workspace)
log.Printf(" Environment: %s", RuntimeID.Environment)
log.Printf(" Owner: %s", RuntimeID.Owner)
log.Printf(" OwnerName: %s", RuntimeID.OwnerName)
log.Printf(" OwnerId: %s", RuntimeID.OwnerId)
log.Printf(" Machine name: %s", MachineName)
log.Printf(" Installer timeout: %dseconds", InstallerTimeoutSec)
log.Printf(" Check servers period: %dseconds", CheckServersPeriodSec)

View File

@ -16,5 +16,7 @@ public interface RuntimeIdentity {
String getEnvName();
String getOwner();
String getOwnerName();
String getOwnerId();
}

View File

@ -34,6 +34,7 @@ public final class Labels {
public static final String LABEL_WORKSPACE_ID = LABEL_PREFIX + "workspace.id";
public static final String LABEL_WORKSPACE_ENV = LABEL_PREFIX + "workspace.env";
public static final String LABEL_WORKSPACE_OWNER = LABEL_PREFIX + "workspace.owner";
public static final String LABEL_WORKSPACE_OWNER_ID = LABEL_PREFIX + "workspace.owner.id";
private static final String LABEL_MACHINE_NAME = LABEL_PREFIX + "machine.name";
private static final String LABEL_MACHINE_ATTRIBUTES = LABEL_PREFIX + "machine.attributes";
@ -88,7 +89,8 @@ public final class Labels {
public Serializer runtimeId(RuntimeIdentity runtimeId) {
labels.put(LABEL_WORKSPACE_ID, runtimeId.getWorkspaceId());
labels.put(LABEL_WORKSPACE_ENV, runtimeId.getEnvName());
labels.put(LABEL_WORKSPACE_OWNER, runtimeId.getOwner());
labels.put(LABEL_WORKSPACE_OWNER, runtimeId.getOwnerName());
labels.put(LABEL_WORKSPACE_OWNER_ID, runtimeId.getOwnerId());
return this;
}
@ -159,7 +161,8 @@ public final class Labels {
return new RuntimeIdentityImpl(
labels.get(LABEL_WORKSPACE_ID),
labels.get(LABEL_WORKSPACE_ENV),
labels.get(LABEL_WORKSPACE_OWNER));
labels.get(LABEL_WORKSPACE_OWNER),
labels.get(LABEL_WORKSPACE_OWNER_ID));
}
/** Retrieves server configuration from docker labels and returns (ref -> server config) map. */

View File

@ -92,10 +92,11 @@ public class DockerBootstrapper extends AbstractBootstrapper {
+ machineName
+ " -runtime-id "
+ String.format(
"%s:%s:%s",
"%s:%s:%s:%s",
runtimeIdentity.getWorkspaceId(),
runtimeIdentity.getEnvName(),
runtimeIdentity.getOwner())
runtimeIdentity.getOwnerName(),
runtimeIdentity.getOwnerId())
+ " -push-endpoint "
+ installerWebsocketEndpoint
+ " -push-logs-endpoint "

View File

@ -44,7 +44,7 @@ public class DockerEnvironmentNormalizer {
containerNameGenerator.generateContainerName(
identity.getWorkspaceId(),
containerConfig.getId(),
identity.getOwner(),
identity.getOwnerName(),
containerEntry.getKey()));
}
normalizeNames(dockerEnvironment);

View File

@ -86,7 +86,8 @@ import org.testng.annotations.Test;
*/
public class DockerInternalRuntimeTest {
private static final RuntimeIdentity IDENTITY = new RuntimeIdentityImpl("ws1", "env1", "usr1");
private static final RuntimeIdentity IDENTITY =
new RuntimeIdentityImpl("ws1", "env1", "usr1", "id1");
private static final String DEV_MACHINE = "DEV_MACHINE";
private static final String DB_MACHINE = "DB_MACHINE";
private static final String SERVER_1 = "serv1";

View File

@ -43,7 +43,7 @@ public class LabelsTest {
Map<String, String> serialized =
Labels.newSerializer()
.machineName("dev-machine")
.runtimeId(new RuntimeIdentityImpl("workspace123", "my-env", "owner"))
.runtimeId(new RuntimeIdentityImpl("workspace123", "my-env", "owner", "id"))
.server(
"my-server1/http",
new ServerConfigImpl("8000/tcp", "http", "/api/info", emptyMap()))
@ -60,6 +60,7 @@ public class LabelsTest {
.put("org.eclipse.che.workspace.id", "workspace123")
.put("org.eclipse.che.workspace.env", "my-env")
.put("org.eclipse.che.workspace.owner", "owner")
.put("org.eclipse.che.workspace.owner.id", "id")
.put("org.eclipse.che.server.my-server1/http.port", "8000/tcp")
.put("org.eclipse.che.server.my-server1/http.protocol", "http")
.put("org.eclipse.che.server.my-server1/http.path", "/api/info")
@ -90,6 +91,7 @@ public class LabelsTest {
.put("org.eclipse.che.workspace.id", "workspace123")
.put("org.eclipse.che.workspace.env", "my-env")
.put("org.eclipse.che.workspace.owner", "owner")
.put("org.eclipse.che.workspace.owner.id", "id")
.put("org.eclipse.che.server.my-server1/http.port", "8000/tcp")
.put("org.eclipse.che.server.my-server1/http.protocol", "http")
.put("org.eclipse.che.server.my-server1/http.path", "/api/info")
@ -121,7 +123,8 @@ public class LabelsTest {
RuntimeIdentity runtimeId = deserializer.runtimeId();
assertEquals(runtimeId.getWorkspaceId(), "workspace123", "workspace id");
assertEquals(runtimeId.getEnvName(), "my-env", "workspace environment name");
assertEquals(runtimeId.getOwner(), "owner", "workspace owner");
assertEquals(runtimeId.getOwnerName(), "owner", "workspace owner name");
assertEquals(runtimeId.getOwnerId(), "id", "workspace owner id");
Map<String, ServerConfig> servers = deserializer.servers();
assertEquals(servers, expectedServers);

View File

@ -48,8 +48,8 @@ public class DockerContainersTest {
@Test
public void findsIdentifiers() throws Exception {
RuntimeIdentity id1 = new RuntimeIdentityImpl("workspace123", "default", "test");
RuntimeIdentity id2 = new RuntimeIdentityImpl("workspace234", "default", "test");
RuntimeIdentity id1 = new RuntimeIdentityImpl("workspace123", "default", "test", "id");
RuntimeIdentity id2 = new RuntimeIdentityImpl("workspace234", "default", "test", "id");
List<ContainerListEntry> entries =
asList(mockContainer(id1, "container1"), mockContainer(id2, "container2"));
@ -81,7 +81,7 @@ public class DockerContainersTest {
@Test
public void findContainers() throws Exception {
RuntimeIdentity id = new RuntimeIdentityImpl("workspace123", "default", "test");
RuntimeIdentity id = new RuntimeIdentityImpl("workspace123", "default", "test", "id");
ContainerListEntry entry1 = mockContainer(id, "container1");
ContainerListEntry entry2 = mockContainer(id, "container2");
@ -113,7 +113,7 @@ public class DockerContainersTest {
throws Exception {
when(docker.listContainers(anyObject())).thenThrow(new IOException("oops"));
containers.find(new RuntimeIdentityImpl("workspace123", "default", "test"));
containers.find(new RuntimeIdentityImpl("workspace123", "default", "test", "id"));
}
private ContainerListEntry mockContainer(RuntimeIdentity runtimeId, String containerId)

View File

@ -36,7 +36,7 @@ import org.testng.annotations.Test;
@Listeners(MockitoTestNGListener.class)
public class WsAgentServerConfigProvisionerTest {
private static final RuntimeIdentity RUNTIME_IDENTITY =
new RuntimeIdentityImpl("wsId", "env", "owner");
new RuntimeIdentityImpl("wsId", "env", "owner", "id");
private static final String MACHINE_1_NAME = "machine1";
private static final String MACHINE_2_NAME = "machine2";

View File

@ -41,7 +41,7 @@ import org.testng.annotations.Test;
public class BindMountProjectsVolumeProvisionerTest {
private static final String WORKSPACE_ID = "wsId";
private static final RuntimeIdentity RUNTIME_IDENTITY =
new RuntimeIdentityImpl(WORKSPACE_ID, "env", "owner");
new RuntimeIdentityImpl(WORKSPACE_ID, "env", "owner", "id");
private static final String MACHINE_1_NAME = "machine1";
private static final String MACHINE_2_NAME = "machine2";
private static final String MACHINE_3_NAME = "machine3";

View File

@ -47,7 +47,7 @@ public class SinglePortUrlRewriterTest {
return new Object[][] {
// External IP
{
new RuntimeIdentityImpl("ws123", null, null),
new RuntimeIdentityImpl("ws123", null, null, null),
"172.12.0.2",
"127.0.0.1",
"machine1",
@ -58,7 +58,7 @@ public class SinglePortUrlRewriterTest {
},
// Internal IP, protocol, path param
{
new RuntimeIdentityImpl("ws123", null, null),
new RuntimeIdentityImpl("ws123", null, null, null),
"127.0.0.1",
null,
"machine1",
@ -69,7 +69,7 @@ public class SinglePortUrlRewriterTest {
},
// Without machine name
{
new RuntimeIdentityImpl("ws123", null, null),
new RuntimeIdentityImpl("ws123", null, null, null),
"127.0.0.1",
null,
null,
@ -80,7 +80,7 @@ public class SinglePortUrlRewriterTest {
},
// Without server
{
new RuntimeIdentityImpl("ws123", null, null),
new RuntimeIdentityImpl("ws123", null, null, null),
"127.0.0.1",
null,
"machine1",
@ -101,6 +101,7 @@ public class SinglePortUrlRewriterTest {
Provider<SinglePortHostnameBuilder> provider =
() -> new SinglePortHostnameBuilder("172.12.0.2", "127.0.0.1", null);
SinglePortUrlRewriter rewriter = new SinglePortUrlRewriter(8080, provider);
rewriter.rewriteURL(new RuntimeIdentityImpl("ws123", null, null), "machine1", "server", ":");
rewriter.rewriteURL(
new RuntimeIdentityImpl("ws123", null, null, null), "machine1", "server", ":");
}
}

View File

@ -11,15 +11,28 @@
package org.eclipse.che.workspace.infrastructure.kubernetes;
import static com.google.common.base.Strings.isNullOrEmpty;
import static io.fabric8.kubernetes.client.utils.Utils.isNotNullOrEmpty;
import io.fabric8.kubernetes.client.Config;
import io.fabric8.kubernetes.client.ConfigBuilder;
import io.fabric8.kubernetes.client.DefaultKubernetesClient;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.utils.HttpClientUtils;
import io.fabric8.kubernetes.client.utils.Utils;
import java.io.IOException;
import java.util.concurrent.ExecutorService;
import javax.annotation.PreDestroy;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import okhttp3.Authenticator;
import okhttp3.ConnectionPool;
import okhttp3.Credentials;
import okhttp3.Dispatcher;
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.eclipse.che.api.workspace.server.spi.InfrastructureException;
import org.eclipse.che.commons.annotation.Nullable;
import org.slf4j.Logger;
@ -34,7 +47,14 @@ public class KubernetesClientFactory {
private static final Logger LOG = LoggerFactory.getLogger(KubernetesClientFactory.class);
private final UnclosableKubernetesClient client;
/** {@link OkHttpClient} instance shared by all Kubernetes clients. */
private OkHttpClient httpClient;
/**
* Default Kubernetes {@link Config} that will be the base configuration to create per-workspace
* configurations.
*/
private Config defaultConfig;
@Inject
public KubernetesClientFactory(
@ -43,6 +63,57 @@ public class KubernetesClientFactory {
@Nullable @Named("che.infra.kubernetes.password") String password,
@Nullable @Named("che.infra.kubernetes.oauth_token") String oauthToken,
@Nullable @Named("che.infra.kubernetes.trust_certs") Boolean doTrustCerts) {
this.defaultConfig =
buildDefaultConfig(masterUrl, username, password, oauthToken, doTrustCerts);
this.httpClient = HttpClientUtils.createHttpClient(defaultConfig);
}
/**
* Creates an instance of {@link KubernetesClient} that can be used to perform any operation
* related to a given workspace. </br> For all operations performed in the context of a given
* workspace (workspace start, workspace stop, etc ...), this method should be used to retrieve a
* Kubernetes client.
*
* @param workspaceId Identifier of the workspace on which Kubernetes operations will be performed
* @throws InfrastructureException if any error occurs on client instance creation.
*/
public KubernetesClient create(String workspaceId) throws InfrastructureException {
Config configForWorkspace = buildConfig(defaultConfig, workspaceId);
return create(configForWorkspace);
}
/**
* Creates an instance of {@link KubernetesClient} that can be used to perform any operation
* <strong>that is not related to a given workspace</strong>. </br> For all operations performed
* in the context of a given workspace (workspace start, workspace stop, etc ...), the {@code
* create(String workspaceId)} method should be used to retrieve a Kubernetes client.
*
* @throws InfrastructureException if any error occurs on client instance creation.
*/
public KubernetesClient create() throws InfrastructureException {
return create(buildConfig(defaultConfig, null));
}
/** Retrieves the {@link OkHttpClient} instance shared by all Kubernetes clients. */
protected OkHttpClient getHttpClient() {
return httpClient;
}
/**
* Retrieves the default Kubernetes {@link Config} that will be the base configuration to create
* per-workspace configurations.
*/
protected Config getDefaultConfig() {
return defaultConfig;
}
/**
* Builds the default Kubernetes {@link Config} that will be the base configuration to create
* per-workspace configurations.
*/
protected Config buildDefaultConfig(
String masterUrl, String username, String password, String oauthToken, Boolean doTrustCerts) {
ConfigBuilder configBuilder = new ConfigBuilder();
if (!isNullOrEmpty(masterUrl)) {
configBuilder.withMasterUrl(masterUrl);
@ -63,22 +134,87 @@ public class KubernetesClientFactory {
if (doTrustCerts != null) {
configBuilder.withTrustCerts(doTrustCerts);
}
this.client = new UnclosableKubernetesClient(configBuilder.build());
Config config = configBuilder.build();
return config;
}
/**
* Creates instance of {@link KubernetesClient}.
* Builds the Kubernetes {@link Config} object based on a default {@link Config} object and an
* optional workspace Id.
*/
protected Config buildConfig(Config defaultConfig, @Nullable String workspaceId)
throws InfrastructureException {
return defaultConfig;
}
protected Interceptor buildKubernetesInterceptor(Config config) {
return new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
if (isNotNullOrEmpty(config.getUsername()) && isNotNullOrEmpty(config.getPassword())) {
Request authReq =
chain
.request()
.newBuilder()
.addHeader(
"Authorization",
Credentials.basic(config.getUsername(), config.getPassword()))
.build();
return chain.proceed(authReq);
} else if (isNotNullOrEmpty(config.getOauthToken())) {
Request authReq =
chain
.request()
.newBuilder()
.addHeader("Authorization", "Bearer " + config.getOauthToken())
.build();
return chain.proceed(authReq);
}
return chain.proceed(request);
}
};
}
protected void doCleanup() {
ConnectionPool connectionPool = httpClient.connectionPool();
Dispatcher dispatcher = httpClient.dispatcher();
ExecutorService executorService =
httpClient.dispatcher() != null ? httpClient.dispatcher().executorService() : null;
if (dispatcher != null) {
dispatcher.cancelAll();
}
if (connectionPool != null) {
connectionPool.evictAll();
}
Utils.shutdownExecutorService(executorService);
}
/**
* Creates instance of {@link KubernetesClient} that uses an {@link OkHttpClient} instance derived
* from the shared {@code httpClient} instance in which interceptors are overriden to authenticate
* with the credentials (user/password or Oauth token) contained in the {@code config} parameter.
*
* @throws InfrastructureException if any error occurs on client instance creation.
*/
public KubernetesClient create() throws InfrastructureException {
return client;
private KubernetesClient create(Config config) throws InfrastructureException {
OkHttpClient clientHttpClient =
httpClient.newBuilder().authenticator(Authenticator.NONE).build();
OkHttpClient.Builder builder = clientHttpClient.newBuilder();
builder.interceptors().clear();
clientHttpClient = builder.addInterceptor(buildKubernetesInterceptor(config)).build();
return new UnclosableKubernetesClient(clientHttpClient, config);
}
@PreDestroy
private void cleanup() {
try {
client.doClose();
doCleanup();
} catch (RuntimeException ex) {
LOG.error(ex.getMessage());
}
@ -89,15 +225,11 @@ public class KubernetesClientFactory {
*/
private static class UnclosableKubernetesClient extends DefaultKubernetesClient {
public UnclosableKubernetesClient(Config config) {
super(config);
public UnclosableKubernetesClient(OkHttpClient httpClient, Config config) {
super(httpClient, config);
}
@Override
public void close() {}
void doClose() {
super.close();
}
}
}

View File

@ -13,6 +13,7 @@ package org.eclipse.che.workspace.infrastructure.kubernetes.bootstrapper;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.inject.assistedinject.Assisted;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
import javax.inject.Named;
@ -93,10 +94,11 @@ public class KubernetesBootstrapper extends AbstractBootstrapper {
+ kubernetesMachine.getName()
+ " -runtime-id "
+ String.format(
"%s:%s:%s",
"%s:%s:%s:%s",
runtimeIdentity.getWorkspaceId(),
runtimeIdentity.getEnvName(),
runtimeIdentity.getOwner())
runtimeIdentity.getOwnerName(),
runtimeIdentity.getOwnerId())
+ " -push-endpoint "
+ installerWebsocketEndpoint
+ " -push-logs-endpoint "
@ -128,14 +130,26 @@ public class KubernetesBootstrapper extends AbstractBootstrapper {
kubernetesMachine.exec("chmod", "+x", BOOTSTRAPPER_DIR + BOOTSTRAPPER_FILE);
LOG.debug("Bootstrapping {}:{}. Creating config file", runtimeIdentity, machineName);
kubernetesMachine.exec(
"sh",
"-c",
"cat > "
+ BOOTSTRAPPER_DIR
+ CONFIG_FILE
+ " << 'EOF'\n"
+ GSON.toJson(installers)
+ "\nEOF");
kubernetesMachine.exec("sh", "-c", "rm " + BOOTSTRAPPER_DIR + CONFIG_FILE);
List<String> contentsToContatenate = new ArrayList<String>();
contentsToContatenate.add("[");
boolean firstOne = true;
for (Installer installer : installers) {
if (firstOne) {
firstOne = false;
} else {
contentsToContatenate.add(",");
}
contentsToContatenate.add(GSON.toJson(installer));
}
contentsToContatenate.add("]");
for (String content : contentsToContatenate) {
kubernetesMachine.exec(
"sh",
"-c",
"cat >> " + BOOTSTRAPPER_DIR + CONFIG_FILE + " << 'EOF'\n" + content + "\nEOF");
}
}
}

View File

@ -51,7 +51,7 @@ public class KubernetesIngresses {
putLabel(ingress, CHE_WORKSPACE_ID_LABEL, workspaceId);
try {
return clientFactory
.create()
.create(workspaceId)
.extensions()
.ingresses()
.inNamespace(namespace)
@ -68,7 +68,12 @@ public class KubernetesIngresses {
Watch watch = null;
try {
Resource<Ingress, DoneableIngress> ingressResource =
clientFactory.create().extensions().ingresses().inNamespace(namespace).withName(name);
clientFactory
.create(workspaceId)
.extensions()
.ingresses()
.inNamespace(namespace)
.withName(name);
watch =
ingressResource.watch(
@ -117,7 +122,7 @@ public class KubernetesIngresses {
public void delete() throws InfrastructureException {
try {
clientFactory
.create()
.create(workspaceId)
.extensions()
.ingresses()
.inNamespace(namespace)

View File

@ -86,10 +86,10 @@ public class KubernetesNamespace {
this.workspaceId = workspaceId;
this.pods = new KubernetesPods(name, workspaceId, clientFactory);
this.services = new KubernetesServices(name, workspaceId, clientFactory);
this.pvcs = new KubernetesPersistentVolumeClaims(name, clientFactory);
this.pvcs = new KubernetesPersistentVolumeClaims(name, workspaceId, clientFactory);
this.ingresses = new KubernetesIngresses(name, workspaceId, clientFactory);
if (doPrepare) {
doPrepare(name, clientFactory.create());
doPrepare(name, clientFactory.create(workspaceId));
}
}

View File

@ -29,11 +29,14 @@ import org.eclipse.che.workspace.infrastructure.kubernetes.KubernetesInfrastruct
*/
public class KubernetesPersistentVolumeClaims {
private final String namespace;
private final String workspaceId;
private final KubernetesClientFactory clientFactory;
KubernetesPersistentVolumeClaims(String namespace, KubernetesClientFactory clientFactory) {
KubernetesPersistentVolumeClaims(
String namespace, String workspaceId, KubernetesClientFactory clientFactory) {
this.namespace = namespace;
this.clientFactory = clientFactory;
this.workspaceId = workspaceId;
}
/**
@ -45,7 +48,11 @@ public class KubernetesPersistentVolumeClaims {
*/
public PersistentVolumeClaim create(PersistentVolumeClaim pvc) throws InfrastructureException {
try {
return clientFactory.create().persistentVolumeClaims().inNamespace(namespace).create(pvc);
return clientFactory
.create(workspaceId)
.persistentVolumeClaims()
.inNamespace(namespace)
.create(pvc);
} catch (KubernetesClientException e) {
throw new KubernetesInfrastructureException(e);
}
@ -59,7 +66,7 @@ public class KubernetesPersistentVolumeClaims {
public List<PersistentVolumeClaim> get() throws InfrastructureException {
try {
return clientFactory
.create()
.create(workspaceId)
.persistentVolumeClaims()
.inNamespace(namespace)
.list()
@ -81,7 +88,7 @@ public class KubernetesPersistentVolumeClaims {
throws InfrastructureException {
try {
return clientFactory
.create()
.create(workspaceId)
.persistentVolumeClaims()
.inNamespace(namespace)
.withLabel(labelName, labelValue)

View File

@ -96,7 +96,7 @@ public class KubernetesPods {
public Pod create(Pod pod) throws InfrastructureException {
putLabel(pod, CHE_WORKSPACE_ID_LABEL, workspaceId);
try {
return clientFactory.create().pods().inNamespace(namespace).create(pod);
return clientFactory.create(workspaceId).pods().inNamespace(namespace).create(pod);
} catch (KubernetesClientException e) {
throw new KubernetesInfrastructureException(e);
}
@ -110,7 +110,7 @@ public class KubernetesPods {
public List<Pod> get() throws InfrastructureException {
try {
return clientFactory
.create()
.create(workspaceId)
.pods()
.inNamespace(namespace)
.withLabel(CHE_WORKSPACE_ID_LABEL, workspaceId)
@ -129,7 +129,7 @@ public class KubernetesPods {
public Optional<Pod> get(String name) throws InfrastructureException {
try {
return Optional.ofNullable(
clientFactory.create().pods().inNamespace(namespace).withName(name).get());
clientFactory.create(workspaceId).pods().inNamespace(namespace).withName(name).get());
} catch (KubernetesClientException e) {
throw new KubernetesInfrastructureException(e);
}
@ -153,7 +153,7 @@ public class KubernetesPods {
try {
PodResource<Pod, DoneablePod> podResource =
clientFactory.create().pods().inNamespace(namespace).withName(name);
clientFactory.create(workspaceId).pods().inNamespace(namespace).withName(name);
watch =
podResource.watch(
@ -222,7 +222,7 @@ public class KubernetesPods {
try {
podWatch =
clientFactory
.create()
.create(workspaceId)
.pods()
.inNamespace(namespace)
.withLabel(CHE_WORKSPACE_ID_LABEL, workspaceId)
@ -272,7 +272,8 @@ public class KubernetesPods {
public void onClose(KubernetesClientException ignored) {}
};
try {
containerWatch = clientFactory.create().events().inNamespace(namespace).watch(watcher);
containerWatch =
clientFactory.create(workspaceId).events().inNamespace(namespace).watch(watcher);
} catch (KubernetesClientException ex) {
throw new KubernetesInfrastructureException(ex);
}
@ -321,7 +322,7 @@ public class KubernetesPods {
final ExecWatchdog watchdog = new ExecWatchdog();
try (ExecWatch watch =
clientFactory
.create()
.create(workspaceId)
.pods()
.inNamespace(namespace)
.withName(podName)
@ -382,7 +383,7 @@ public class KubernetesPods {
// pods are removed with some delay related to stopping of containers. It is need to wait them
List<Pod> pods =
clientFactory
.create()
.create(workspaceId)
.pods()
.inNamespace(namespace)
.withLabel(CHE_WORKSPACE_ID_LABEL, workspaceId)
@ -414,7 +415,7 @@ public class KubernetesPods {
private CompletableFuture<Void> doDelete(String name) throws InfrastructureException {
try {
final PodResource<Pod, DoneablePod> podResource =
clientFactory.create().pods().inNamespace(namespace).withName(name);
clientFactory.create(workspaceId).pods().inNamespace(namespace).withName(name);
final CompletableFuture<Void> deleteFuture = new CompletableFuture<>();
final Watch watch = podResource.watch(new DeleteWatcher(deleteFuture));

View File

@ -49,7 +49,7 @@ public class KubernetesServices {
putLabel(service, CHE_WORKSPACE_ID_LABEL, workspaceId);
putSelector(service, CHE_WORKSPACE_ID_LABEL, workspaceId);
try {
return clientFactory.create().services().inNamespace(namespace).create(service);
return clientFactory.create(workspaceId).services().inNamespace(namespace).create(service);
} catch (KubernetesClientException e) {
throw new KubernetesInfrastructureException(e);
}
@ -63,7 +63,7 @@ public class KubernetesServices {
public List<Service> get() throws InfrastructureException {
try {
return clientFactory
.create()
.create(workspaceId)
.services()
.inNamespace(namespace)
.withLabel(CHE_WORKSPACE_ID_LABEL, workspaceId)
@ -82,7 +82,7 @@ public class KubernetesServices {
public void delete() throws InfrastructureException {
try {
clientFactory
.create()
.create(workspaceId)
.services()
.inNamespace(namespace)
.withLabel(CHE_WORKSPACE_ID_LABEL, workspaceId)

View File

@ -69,7 +69,7 @@ public class RemoveNamespaceOnWorkspaceRemove implements EventSubscriber<Workspa
@VisibleForTesting
void doRemoveNamespace(String namespaceName) throws InfrastructureException {
try {
clientFactory.create().namespaces().withName(namespaceName).delete();
clientFactory.create(namespaceName).namespaces().withName(namespaceName).delete();
} catch (KubernetesClientException e) {
if (!(e.getCode() == 403)) {
throw new KubernetesInfrastructureException(e);

View File

@ -174,7 +174,7 @@ public class UniqueWorkspacePVCStrategy implements WorkspaceVolumesStrategy {
@Override
public void cleanup(String workspaceId) throws InfrastructureException {
clientFactory
.create()
.create(workspaceId)
.persistentVolumeClaims()
.inNamespace(namespaceName)
.withLabel(CHE_WORKSPACE_ID_LABEL, workspaceId)

View File

@ -120,7 +120,7 @@ public class KubernetesInternalRuntimeTest {
private static final String M2_NAME = POD_NAME + '/' + CONTAINER_NAME_2;
private static final RuntimeIdentity IDENTITY =
new RuntimeIdentityImpl(WORKSPACE_ID, "env1", "usr1");
new RuntimeIdentityImpl(WORKSPACE_ID, "env1", "usr1", "id1");
@Mock private KubernetesRuntimeContext context;
@Mock private EventService eventService;

View File

@ -68,6 +68,7 @@ public class KubernetesNamespaceTest {
@BeforeMethod
public void setUp() throws Exception {
when(clientFactory.create()).thenReturn(kubernetesClient);
when(clientFactory.create(anyString())).thenReturn(kubernetesClient);
doReturn(namespaceOperation).when(kubernetesClient).namespaces();

View File

@ -81,7 +81,7 @@ public class CommonPVCStrategyTest {
private static final String[] WORKSPACE_SUBPATHS = {"/projects", "/logs"};
private static final RuntimeIdentity IDENTITY =
new RuntimeIdentityImpl(WORKSPACE_ID, "env1", "usr1");
new RuntimeIdentityImpl(WORKSPACE_ID, "env1", "usr1", "id1");
@Mock private Pod pod;
@Mock private Pod pod2;

View File

@ -17,6 +17,7 @@ import static org.eclipse.che.workspace.infrastructure.kubernetes.Constants.CHE_
import static org.eclipse.che.workspace.infrastructure.kubernetes.Constants.CHE_WORKSPACE_ID_LABEL;
import static org.eclipse.che.workspace.infrastructure.kubernetes.namespace.pvc.CommonPVCStrategyTest.mockName;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
@ -82,7 +83,7 @@ public class UniqueWorkspacePVCStrategyTest {
private static final String VOLUME_2_NAME = "vol2";
private static final RuntimeIdentity IDENTITY =
new RuntimeIdentityImpl(WORKSPACE_ID, "env1", "usr1");
new RuntimeIdentityImpl(WORKSPACE_ID, "env1", "usr1", "id1");
@Mock private KubernetesEnvironment k8sEnv;
@Mock private KubernetesClientFactory clientFactory;
@ -106,6 +107,7 @@ public class UniqueWorkspacePVCStrategyTest {
new UniqueWorkspacePVCStrategy(
NAMESPACE_NAME, PVC_NAME_PREFIX, PVC_QUANTITY, PVC_ACCESS_MODE, factory, clientFactory);
when(clientFactory.create()).thenReturn(client);
when(clientFactory.create(anyString())).thenReturn(client);
Map<String, InternalMachineConfig> machines = new HashMap<>();
InternalMachineConfig machine1 = mock(InternalMachineConfig.class);

View File

@ -38,6 +38,10 @@
<groupId>com.google.inject.extensions</groupId>
<artifactId>guice-multibindings</artifactId>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
</dependency>
<dependency>
<groupId>io.fabric8</groupId>
<artifactId>kubernetes-client</artifactId>

View File

@ -11,15 +11,29 @@
package org.eclipse.che.workspace.infrastructure.openshift;
import static com.google.common.base.Strings.isNullOrEmpty;
import static io.fabric8.kubernetes.client.utils.Utils.isNotNullOrEmpty;
import io.fabric8.kubernetes.client.Config;
import io.fabric8.kubernetes.client.KubernetesClientException;
import io.fabric8.kubernetes.client.utils.URLUtils;
import io.fabric8.kubernetes.client.utils.Utils;
import io.fabric8.openshift.client.DefaultOpenShiftClient;
import io.fabric8.openshift.client.OpenShiftClient;
import io.fabric8.openshift.client.OpenShiftConfig;
import io.fabric8.openshift.client.OpenShiftConfigBuilder;
import io.fabric8.openshift.client.internal.OpenShiftOAuthInterceptor;
import java.io.IOException;
import java.net.URL;
import javax.annotation.PreDestroy;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import okhttp3.Authenticator;
import okhttp3.Credentials;
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.eclipse.che.api.workspace.server.spi.InfrastructureException;
import org.eclipse.che.commons.annotation.Nullable;
import org.eclipse.che.workspace.infrastructure.kubernetes.KubernetesClientFactory;
@ -33,9 +47,15 @@ import org.slf4j.LoggerFactory;
@Singleton
public class OpenShiftClientFactory extends KubernetesClientFactory {
private static final Logger LOG = LoggerFactory.getLogger(OpenShiftClientFactory.class);
private static final String AUTHORIZATION = "Authorization";
private static final String AUTHORIZE_PATH =
"oauth/authorize?response_type=token&client_id=openshift-challenging-client";
private static final String LOCATION = "Location";
private final UnclosableOpenShiftClient client;
private static final String BEFORE_TOKEN = "access_token=";
private static final String AFTER_TOKEN = "&expires";
private static final Logger LOG = LoggerFactory.getLogger(OpenShiftClientFactory.class);
@Inject
public OpenShiftClientFactory(
@ -45,6 +65,10 @@ public class OpenShiftClientFactory extends KubernetesClientFactory {
@Nullable @Named("che.infra.kubernetes.oauth_token") String oauthToken,
@Nullable @Named("che.infra.kubernetes.trust_certs") Boolean doTrustCerts) {
super(masterUrl, username, password, oauthToken, doTrustCerts);
}
protected Config buildDefaultConfig(
String masterUrl, String username, String password, String oauthToken, Boolean doTrustCerts) {
OpenShiftConfigBuilder configBuilder = new OpenShiftConfigBuilder();
if (!isNullOrEmpty(masterUrl)) {
configBuilder.withMasterUrl(masterUrl);
@ -65,22 +89,132 @@ public class OpenShiftClientFactory extends KubernetesClientFactory {
if (doTrustCerts != null) {
configBuilder.withTrustCerts(doTrustCerts);
}
this.client = new UnclosableOpenShiftClient(configBuilder.build());
Config theConfig = configBuilder.build();
return theConfig;
}
protected Interceptor buildKubernetesInterceptor(Config config) {
final String oauthToken;
if (Utils.isNotNullOrEmpty(config.getUsername())
&& Utils.isNotNullOrEmpty(config.getPassword())) {
synchronized (getHttpClient()) {
try {
OkHttpClient.Builder builder = getHttpClient().newBuilder();
builder.interceptors().clear();
OkHttpClient clone = builder.build();
String credential =
Credentials.basic(config.getUsername(), new String(config.getPassword()));
URL url = new URL(URLUtils.join(config.getMasterUrl(), AUTHORIZE_PATH));
Response response =
clone
.newCall(
new Request.Builder()
.get()
.url(url)
.header(AUTHORIZATION, credential)
.build())
.execute();
response.body().close();
response = response.priorResponse() != null ? response.priorResponse() : response;
response = response.networkResponse() != null ? response.networkResponse() : response;
String token = response.header(LOCATION);
if (token == null || token.isEmpty()) {
throw new KubernetesClientException(
"Unexpected response ("
+ response.code()
+ " "
+ response.message()
+ "), to the authorization request. Missing header:["
+ LOCATION
+ "]!");
}
token = token.substring(token.indexOf(BEFORE_TOKEN) + BEFORE_TOKEN.length());
token = token.substring(0, token.indexOf(AFTER_TOKEN));
oauthToken = token;
} catch (Exception e) {
throw KubernetesClientException.launderThrowable(e);
}
}
} else if (Utils.isNotNullOrEmpty(config.getOauthToken())) {
oauthToken = config.getOauthToken();
} else {
oauthToken = null;
}
return new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
if (isNotNullOrEmpty(oauthToken)) {
Request authReq =
chain
.request()
.newBuilder()
.addHeader("Authorization", "Bearer " + oauthToken)
.build();
return chain.proceed(authReq);
}
return chain.proceed(request);
}
};
}
private OpenShiftClient createOC(Config config) throws InfrastructureException {
OkHttpClient clientHttpClient =
getHttpClient().newBuilder().authenticator(Authenticator.NONE).build();
OkHttpClient.Builder builder = clientHttpClient.newBuilder();
builder.interceptors().clear();
clientHttpClient =
builder
.addInterceptor(
new OpenShiftOAuthInterceptor(clientHttpClient, OpenShiftConfig.wrap(config)))
.build();
return new UnclosableOpenShiftClient(clientHttpClient, config);
}
/**
* Creates instance of {@link OpenShiftClient}.
* Creates an instance of {@link OpenShiftClient} that can be used to perform any operation
* related to a given workspace. </br> <strong>Important note: </strong> However, in some
* use-cases involving web sockets, the Openshift client may introduce connection leaks. That's
* why this method should only be used for API calls that are specific to Openshift and thus not
* available in the {@link KubernetesClient} class: mainly route-related calls and project-related
* calls. For all other Kubernetes standard calls, prefer the {@code create(String workspaceId)}
* method that returns a Kubernetes client.
*
* @param workspaceId Identifier of the workspace on which Openshift operations will be performed
* @throws InfrastructureException if any error occurs on client instance creation.
*/
public OpenShiftClient createOC(String workspaceId) throws InfrastructureException {
Config configForWorkspace = buildConfig(getDefaultConfig(), workspaceId);
return createOC(configForWorkspace);
}
/**
* Creates an instance of {@link OpenShiftClient} that can be used to perform any operation
* <strong>that is not related to a given workspace</strong>. </br> For operations performed in
* the context of a given workspace (workspace start, workspace stop, etc ...), the {@code
* createOC(String workspaceId)} method should be used to retrieve an Openshift client. </br>
* <strong>Important note: </strong> However in some use-cases involving web sockets, the
* Openshift client may introduce connection leaks. That's why this method should only be used for
* API calls that are specific to Openshift and thus not available in the {@link KubernetesClient}
* class: mainly route-related calls and project-related calls. For all other Kubernetes standard
* calls, just use the {@code create()} or {@code create(String workspaceId)} methods that return
* a Kubernetes client.
*
* @throws InfrastructureException if any error occurs on client instance creation.
*/
public OpenShiftClient create() throws InfrastructureException {
return client;
public OpenShiftClient createOC() throws InfrastructureException {
return createOC(buildConfig(getDefaultConfig(), null));
}
@PreDestroy
private void cleanup() {
try {
client.doClose();
doCleanup();
} catch (RuntimeException ex) {
LOG.error(ex.getMessage());
}
@ -89,15 +223,15 @@ public class OpenShiftClientFactory extends KubernetesClientFactory {
/** Decorates the {@link DefaultOpenShiftClient} so that it can not be closed from the outside. */
private static class UnclosableOpenShiftClient extends DefaultOpenShiftClient {
public UnclosableOpenShiftClient(OpenShiftConfig config) {
super(config);
public UnclosableOpenShiftClient(OkHttpClient httpClient, Config config) {
super(
httpClient,
config instanceof OpenShiftConfig
? (OpenShiftConfig) config
: new OpenShiftConfig(config));
}
@Override
public void close() {}
void doClose() {
super.close();
}
}
}

View File

@ -11,6 +11,7 @@
package org.eclipse.che.workspace.infrastructure.openshift.project;
import com.google.common.annotations.VisibleForTesting;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.KubernetesClientException;
import io.fabric8.openshift.api.model.Project;
import io.fabric8.openshift.api.model.Route;
@ -49,12 +50,13 @@ public class OpenShiftProject extends KubernetesNamespace {
throws InfrastructureException {
super(clientFactory, name, workspaceId, false);
this.routes = new OpenShiftRoutes(name, workspaceId, clientFactory);
doPrepare(name, clientFactory.create());
doPrepare(name, clientFactory.create(workspaceId), clientFactory.createOC(workspaceId));
}
private void doPrepare(String name, OpenShiftClient osClient) throws InfrastructureException {
private void doPrepare(String name, KubernetesClient kubeClient, OpenShiftClient osClient)
throws InfrastructureException {
if (get(name, osClient) == null) {
create(name, osClient);
create(name, kubeClient, osClient);
}
}
@ -68,16 +70,17 @@ public class OpenShiftProject extends KubernetesNamespace {
doRemove(routes::delete, services()::delete, pods()::delete);
}
private void create(String projectName, OpenShiftClient client) throws InfrastructureException {
private void create(String projectName, KubernetesClient kubeClient, OpenShiftClient ocClient)
throws InfrastructureException {
try {
client
ocClient
.projectrequests()
.createNew()
.withNewMetadata()
.withName(projectName)
.endMetadata()
.done();
waitDefaultServiceAccount(projectName, client);
waitDefaultServiceAccount(projectName, kubeClient);
} catch (KubernetesClientException e) {
throw new KubernetesInfrastructureException(e);
}

View File

@ -47,7 +47,7 @@ public class OpenShiftRoutes {
public Route create(Route route) throws InfrastructureException {
putLabel(route, CHE_WORKSPACE_ID_LABEL, workspaceId);
try {
return clientFactory.create().routes().inNamespace(namespace).create(route);
return clientFactory.createOC(workspaceId).routes().inNamespace(namespace).create(route);
} catch (KubernetesClientException e) {
throw new KubernetesInfrastructureException(e);
}
@ -61,7 +61,7 @@ public class OpenShiftRoutes {
public List<Route> get() throws InfrastructureException {
try {
return clientFactory
.create()
.createOC(workspaceId)
.routes()
.inNamespace(namespace)
.withLabel(CHE_WORKSPACE_ID_LABEL, workspaceId)
@ -80,7 +80,7 @@ public class OpenShiftRoutes {
public void delete() throws InfrastructureException {
try {
clientFactory
.create()
.createOC(workspaceId)
.routes()
.inNamespace(namespace)
.withLabel(CHE_WORKSPACE_ID_LABEL, workspaceId)

View File

@ -70,7 +70,7 @@ public class RemoveProjectOnWorkspaceRemove implements EventSubscriber<Workspace
@VisibleForTesting
void doRemoveProject(String projectName) throws InfrastructureException {
try {
clientFactory.create().projects().withName(projectName).delete();
clientFactory.createOC(projectName).projects().withName(projectName).delete();
} catch (KubernetesClientException e) {
if (!(e.getCode() == 403)) {
throw new KubernetesInfrastructureException(e);

View File

@ -100,7 +100,7 @@ public class OpenShiftInternalRuntimeTest {
private static final String M2_NAME = POD_NAME + '/' + CONTAINER_NAME_2;
private static final RuntimeIdentity IDENTITY =
new RuntimeIdentityImpl(WORKSPACE_ID, "env1", "usr1");
new RuntimeIdentityImpl(WORKSPACE_ID, "env1", "usr1", "id1");
@Mock private OpenShiftRuntimeContext context;
@Mock private EventService eventService;

View File

@ -21,6 +21,7 @@ import static org.testng.Assert.assertNotNull;
import io.fabric8.kubernetes.api.model.DoneableServiceAccount;
import io.fabric8.kubernetes.api.model.ServiceAccount;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.KubernetesClientException;
import io.fabric8.kubernetes.client.dsl.MixedOperation;
import io.fabric8.kubernetes.client.dsl.NonNamespaceOperation;
@ -60,18 +61,22 @@ public class OpenShiftProjectTest {
@Mock private KubernetesIngresses ingresses;
@Mock private OpenShiftClientFactory clientFactory;
@Mock private OpenShiftClient openShiftClient;
@Mock private KubernetesClient kubernetesClient;
@Mock private Resource<ServiceAccount, DoneableServiceAccount> serviceAccountResource;
private OpenShiftProject openShiftProject;
@BeforeMethod
public void setUp() throws Exception {
when(clientFactory.create()).thenReturn(openShiftClient);
when(clientFactory.create()).thenReturn(kubernetesClient);
when(clientFactory.create(anyString())).thenReturn(kubernetesClient);
when(clientFactory.createOC()).thenReturn(openShiftClient);
when(clientFactory.createOC(anyString())).thenReturn(openShiftClient);
when(openShiftClient.adapt(OpenShiftClient.class)).thenReturn(openShiftClient);
final MixedOperation mixedOperation = mock(MixedOperation.class);
final NonNamespaceOperation namespaceOperation = mock(NonNamespaceOperation.class);
doReturn(mixedOperation).when(openShiftClient).serviceAccounts();
doReturn(mixedOperation).when(kubernetesClient).serviceAccounts();
when(mixedOperation.inNamespace(anyString())).thenReturn(namespaceOperation);
when(namespaceOperation.withName(anyString())).thenReturn(serviceAccountResource);
when(serviceAccountResource.get()).thenReturn(mock(ServiceAccount.class));

View File

@ -28,7 +28,12 @@ public interface RuntimeIdentityDto extends RuntimeIdentity {
RuntimeIdentityDto withEnvName(String envName);
@Override
String getOwner();
String getOwnerName();
RuntimeIdentityDto withOwner(String owner);
RuntimeIdentityDto withOwnerName(String ownerName);
@Override
String getOwnerId();
RuntimeIdentityDto withOwnerId(String ownerId);
}

View File

@ -246,7 +246,8 @@ public final class DtoConverter {
return newDto(RuntimeIdentityDto.class)
.withWorkspaceId(identity.getWorkspaceId())
.withEnvName(identity.getEnvName())
.withOwner(identity.getOwner());
.withOwnerName(identity.getOwnerName())
.withOwnerId(identity.getOwnerId());
}
/** Converts {@link Volume} to {@link VolumeDto}. */

View File

@ -207,7 +207,7 @@ public class WorkspaceRuntimes {
Subject subject = EnvironmentContext.getCurrent().getSubject();
RuntimeIdentity runtimeId =
new RuntimeIdentityImpl(workspaceId, envName, subject.getUserName());
new RuntimeIdentityImpl(workspaceId, envName, subject.getUserName(), subject.getUserId());
try {
InternalEnvironment internalEnv = createInternalEnvironment(environment);
RuntimeContext runtimeContext = infrastructure.prepare(runtimeId, internalEnv);

View File

@ -57,7 +57,7 @@ public abstract class AbstractBootstrapper {
RuntimeIdentityDto runtimeId = event.getRuntimeId();
if (event.getMachineName().equals(machineName)
&& runtimeIdentity.getEnvName().equals(runtimeId.getEnvName())
&& runtimeIdentity.getOwner().equals(runtimeId.getOwner())
&& runtimeIdentity.getOwnerName().equals(runtimeId.getOwnerName())
&& runtimeIdentity.getWorkspaceId().equals(runtimeId.getWorkspaceId())) {
finishEventFuture.complete(event);

View File

@ -17,12 +17,14 @@ public final class RuntimeIdentityImpl implements RuntimeIdentity {
private final String workspaceId;
private final String envName;
private final String owner;
private final String ownerName;
private final String ownerId;
public RuntimeIdentityImpl(String workspaceId, String envName, String owner) {
public RuntimeIdentityImpl(String workspaceId, String envName, String ownerName, String ownerId) {
this.workspaceId = workspaceId;
this.envName = envName;
this.owner = owner;
this.ownerName = ownerName;
this.ownerId = ownerId;
}
@Override
@ -36,8 +38,13 @@ public final class RuntimeIdentityImpl implements RuntimeIdentity {
}
@Override
public String getOwner() {
return owner;
public String getOwnerName() {
return ownerName;
}
@Override
public String getOwnerId() {
return ownerId;
}
@Override
@ -59,7 +66,7 @@ public final class RuntimeIdentityImpl implements RuntimeIdentity {
+ " environment: "
+ envName
+ " owner: "
+ owner
+ ownerName
+ " }";
}
}

View File

@ -60,7 +60,7 @@ public abstract class InternalRuntime<T extends RuntimeContext> implements Runti
@Override
public String getOwner() {
return context.getIdentity().getOwner();
return context.getIdentity().getOwnerName();
}
@Override

View File

@ -25,6 +25,6 @@ public class RuntimeStartInterruptedException extends InfrastructureException {
super(
format(
"Runtime start for identity 'workspace: %s, environment: %s, owner: %s' is interrupted",
identity.getWorkspaceId(), identity.getEnvName(), identity.getOwner()));
identity.getWorkspaceId(), identity.getEnvName(), identity.getOwnerName()));
}
}

View File

@ -528,7 +528,10 @@ public class WorkspaceManagerTest {
throws Exception {
RuntimeIdentity identity =
new RuntimeIdentityImpl(
workspace.getId(), workspace.getConfig().getDefaultEnv(), workspace.getNamespace());
workspace.getId(),
workspace.getConfig().getDefaultEnv(),
workspace.getNamespace(),
"id");
// doAnswer(inv -> {
// final WorkspaceImpl ws = (WorkspaceImpl)inv.getArguments()[0];
// ws.setStatus(status);

View File

@ -101,7 +101,7 @@ public class WorkspaceRuntimesTest {
@Test
public void runtimeIsRecovered() throws Exception {
RuntimeIdentity identity = new RuntimeIdentityImpl("workspace123", "my-env", "me");
RuntimeIdentity identity = new RuntimeIdentityImpl("workspace123", "my-env", "me", "myId");
mockWorkspace(identity);
RuntimeContext context = mockContext(identity);
@ -119,7 +119,7 @@ public class WorkspaceRuntimesTest {
@Test
public void runtimeIsNotRecoveredIfNoWorkspaceFound() throws Exception {
RuntimeIdentity identity = new RuntimeIdentityImpl("workspace123", "my-env", "me");
RuntimeIdentity identity = new RuntimeIdentityImpl("workspace123", "my-env", "me", "myId");
when(workspaceDao.get(identity.getWorkspaceId())).thenThrow(new NotFoundException("no!"));
// try recover
@ -130,7 +130,7 @@ public class WorkspaceRuntimesTest {
@Test
public void runtimeIsNotRecoveredIfNoEnvironmentFound() throws Exception {
RuntimeIdentity identity = new RuntimeIdentityImpl("workspace123", "my-env", "me");
RuntimeIdentity identity = new RuntimeIdentityImpl("workspace123", "my-env", "me", "myId");
WorkspaceImpl workspace = mockWorkspace(identity);
when(workspace.getConfig().getEnvironments()).thenReturn(emptyMap());
@ -142,7 +142,7 @@ public class WorkspaceRuntimesTest {
@Test
public void runtimeIsNotRecoveredIfInfraPreparationFailed() throws Exception {
RuntimeIdentity identity = new RuntimeIdentityImpl("workspace123", "my-env", "me");
RuntimeIdentity identity = new RuntimeIdentityImpl("workspace123", "my-env", "me", "myId");
mockWorkspace(identity);
InternalEnvironment internalEnvironment = mock(InternalEnvironment.class);
@ -160,7 +160,7 @@ public class WorkspaceRuntimesTest {
@Test
public void runtimeIsNotRecoveredIfAnotherRuntimeWithTheSameIdentityAlreadyExists()
throws Exception {
RuntimeIdentity identity = new RuntimeIdentityImpl("workspace123", "my-env", "me");
RuntimeIdentity identity = new RuntimeIdentityImpl("workspace123", "my-env", "me", "myId");
mockWorkspace(identity);
RuntimeContext context = mockContext(identity);
@ -203,7 +203,8 @@ public class WorkspaceRuntimesTest {
DtoFactory.newDto(RuntimeIdentityDto.class)
.withWorkspaceId("workspace123")
.withEnvName("my-env")
.withOwner("me");
.withOwnerName("me")
.withOwnerId("myId");
mockWorkspace(identity);
mockContext(identity);
RuntimeStatusEvent event =

View File

@ -630,7 +630,7 @@ public class InternalRuntimeTest {
public TestInternalRuntime(URLRewriter urlRewriter, boolean running)
throws ValidationException, InfrastructureException {
super(
new TestRuntimeContext(null, new RuntimeIdentityImpl("ws", "env", "owner"), null),
new TestRuntimeContext(null, new RuntimeIdentityImpl("ws", "env", "owner", "id"), null),
urlRewriter,
emptyList(),
running);

View File

@ -42,7 +42,7 @@ import org.testng.annotations.Test;
public class EnvVarEnvironmentProvisionerTest {
private static final RuntimeIdentity RUNTIME_IDENTITY =
new RuntimeIdentityImpl("testWsId", "testEnv", "testOwner");
new RuntimeIdentityImpl("testWsId", "testEnv", "testOwner", "testOwnerId");
@Mock private EnvVarProvider provider1;
@Mock private EnvVarProvider provider2;