From a0a51f6d7752a5e5fa72dfe74b5fa34c07d832cc Mon Sep 17 00:00:00 2001 From: Sergii Leshchenko Date: Mon, 17 Jul 2017 14:41:39 +0300 Subject: [PATCH] OpenShift initial implementation --- assembly/assembly-main/pom.xml | 6 + .../assembly-main/src/assembly/assembly.xml | 9 + assembly/assembly-wsmaster-war/pom.xml | 4 + .../che/api/deploy/WsMasterModule.java | 2 + .../WEB-INF/classes/codenvy/che.properties | 16 + dockerfiles/init/manifests/che.env | 16 + .../src/main/resources/stacks.json | 47 ++- infrastructures/openshift/pom.xml | 142 +++++++++ .../openshift/OpenshiftClientFactory.java | 51 +++ .../openshift/OpenshiftInfraModule.java | 37 +++ .../openshift/OpenshiftInfrastructure.java | 66 ++++ .../openshift/OpenshiftInternalRuntime.java | 296 ++++++++++++++++++ .../openshift/OpenshiftMachine.java | 191 +++++++++++ .../openshift/OpenshiftRuntimeContext.java | 74 +++++ .../OpenshiftRuntimeContextFactory.java | 28 ++ .../openshift/OpenshiftRuntimeFactory.java | 27 ++ .../bootstrapper/OpenshiftBootstrapper.java | 104 ++++++ .../OpenshiftBootstrapperFactory.java | 29 ++ .../environment/OpenshiftEnvironment.java | 59 ++++ .../OpenshiftEnvironmentParser.java | 112 +++++++ .../OpenshiftEnvironmentProvisioner.java | 53 ++++ infrastructures/pom.xml | 1 + pom.xml | 5 + .../workspace/server/spi/RuntimeContext.java | 2 +- 24 files changed, 1375 insertions(+), 2 deletions(-) create mode 100644 infrastructures/openshift/pom.xml create mode 100644 infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/OpenshiftClientFactory.java create mode 100644 infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/OpenshiftInfraModule.java create mode 100644 infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/OpenshiftInfrastructure.java create mode 100644 infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/OpenshiftInternalRuntime.java create mode 100644 infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/OpenshiftMachine.java create mode 100644 infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/OpenshiftRuntimeContext.java create mode 100644 infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/OpenshiftRuntimeContextFactory.java create mode 100644 infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/OpenshiftRuntimeFactory.java create mode 100644 infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/bootstrapper/OpenshiftBootstrapper.java create mode 100644 infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/bootstrapper/OpenshiftBootstrapperFactory.java create mode 100644 infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/environment/OpenshiftEnvironment.java create mode 100644 infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/environment/OpenshiftEnvironmentParser.java create mode 100644 infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/provision/OpenshiftEnvironmentProvisioner.java diff --git a/assembly/assembly-main/pom.xml b/assembly/assembly-main/pom.xml index 389e2aba52..987a5d3b07 100644 --- a/assembly/assembly-main/pom.xml +++ b/assembly/assembly-main/pom.xml @@ -38,6 +38,12 @@ assembly-wsmaster-war war + + org.eclipse.che + bootstrapper + tar.gz + linux_amd64 + org.eclipse.che exec-agent diff --git a/assembly/assembly-main/src/assembly/assembly.xml b/assembly/assembly-main/src/assembly/assembly.xml index ce0ffe07d7..452b262948 100644 --- a/assembly/assembly-main/src/assembly/assembly.xml +++ b/assembly/assembly-main/src/assembly/assembly.xml @@ -82,6 +82,15 @@ org.eclipse.che:terminal-agent:tar.gz:linux_amd64 + + false + true + lib/linux_amd64 + bootstrapper-linux_amd64 + + org.eclipse.che:bootstrapper:tar.gz:linux_amd64 + + false false diff --git a/assembly/assembly-wsmaster-war/pom.xml b/assembly/assembly-wsmaster-war/pom.xml index d65597323b..9b4b4a4933 100644 --- a/assembly/assembly-wsmaster-war/pom.xml +++ b/assembly/assembly-wsmaster-war/pom.xml @@ -80,6 +80,10 @@ org.eclipse.che infrastructure-docker + + org.eclipse.che + infrastructure-openshift + org.eclipse.che ls-csharp-agent diff --git a/assembly/assembly-wsmaster-war/src/main/java/org/eclipse/che/api/deploy/WsMasterModule.java b/assembly/assembly-wsmaster-war/src/main/java/org/eclipse/che/api/deploy/WsMasterModule.java index 0c4a566178..c55ca0c3aa 100644 --- a/assembly/assembly-wsmaster-war/src/main/java/org/eclipse/che/api/deploy/WsMasterModule.java +++ b/assembly/assembly-wsmaster-war/src/main/java/org/eclipse/che/api/deploy/WsMasterModule.java @@ -55,6 +55,7 @@ import org.eclipse.che.workspace.infrastructure.docker.DockerInfraModule; import org.eclipse.che.workspace.infrastructure.docker.local.LocalDockerModule; import org.eclipse.che.workspace.infrastructure.docker.snapshot.JpaSnapshotDao; import org.eclipse.che.workspace.infrastructure.docker.snapshot.SnapshotDao; +import org.eclipse.che.workspace.infrastructure.openshift.OpenshiftInfraModule; import org.flywaydb.core.internal.util.PlaceholderReplacer; import javax.sql.DataSource; @@ -226,6 +227,7 @@ public class WsMasterModule extends AbstractModule { // FIXME: spi install(new DockerInfraModule()); install(new LocalDockerModule()); + install(new OpenshiftInfraModule()); bind(RemoveWorkspaceFilesAfterRemoveWorkspaceEventSubscriber.class).asEagerSingleton(); bind(URLRewriter.class).to(URLRewriter.NoOpURLRewriter.class); } diff --git a/assembly/assembly-wsmaster-war/src/main/webapp/WEB-INF/classes/codenvy/che.properties b/assembly/assembly-wsmaster-war/src/main/webapp/WEB-INF/classes/codenvy/che.properties index 960a35bc3e..4b17c4083a 100644 --- a/assembly/assembly-wsmaster-war/src/main/webapp/WEB-INF/classes/codenvy/che.properties +++ b/assembly/assembly-wsmaster-war/src/main/webapp/WEB-INF/classes/codenvy/che.properties @@ -386,3 +386,19 @@ che.docker.connector=default # - 'default' : false # Note that this property is needed for backward compatibility and will be removed soon. che.predefined.stacks.reload_on_start=false + +### Openshift Infra parameters +che.infra.openshift.master_url= +che.infra.openshift.username= +che.infra.openshift.password= +che.infra.openshift.trust_certs=false + +che.infra.openshift.che_server_endpoint=http://che-host:${CHE_PORT}/wsmaster/api +che.infra.openshift.che_server_websocket_endpoint_base=ws://che-host:${CHE_PORT}/wsmaster + +che.infra.openshift.machine_start_timeout_min=5 + +che.infra.openshift.bootstrapper.binary_url=http://che-host:${CHE_PORT}/agent-binaries/linux_amd64/bootstrapper/bootstrapper +che.infra.openshift.bootstrapper.timeout_min=10 +che.infra.openshift.bootstrapper.installer_timeout_sec=180 +che.infra.openshift.bootstrapper.server_check_period_sec=3 diff --git a/dockerfiles/init/manifests/che.env b/dockerfiles/init/manifests/che.env index c10bdd212b..33a71e998f 100644 --- a/dockerfiles/init/manifests/che.env +++ b/dockerfiles/init/manifests/che.env @@ -446,3 +446,19 @@ CHE_SINGLE_PORT=false # - 'default' : false # Note that this property is needed for backward compatibility and will be removed soon. #CHE_PREDEFINED_STACKS_RELOAD__ON__START=false + +######################################################################################## +##### ##### +##### Openshift Infrastructure ##### +##### ##### +# +#CHE_INFRA_OPENSHIFT_MASTER__URL= +#CHE_INFRA_OPENSHIFT_USERNAME=developer +#CHE_INFRA_OPENSHIFT_PASSWORD=developer +#CHE_INFRA_OPENSHIFT_TRUST__CERTS=false + +#CHE_INFRA_OPENSHIFT_MACHINE__START__TIMEOUT__MIN=5 + +#CHE_INFRA_OPENSHIFT_CHE__SERVER__ENDPOINT=http://che-host:${CHE_PORT}/wsmaster/api +#CHE_INFRA_OPENSHIFT_CHE__SERVER__WEBSOCKET__ENDPOINT__BASE=ws://che-host:${CHE_PORT}/wsmaster +#CHE_INFRA_OPENSHIFT_BOOTSTRAPPER_BINARY__URL=http://che-host:${CHE_PORT}/agent-binaries/linux_amd64/bootstrapper/bootstrapper diff --git a/ide/che-core-ide-stacks/src/main/resources/stacks.json b/ide/che-core-ide-stacks/src/main/resources/stacks.json index d4a9b45825..0bc9bf852b 100644 --- a/ide/che-core-ide-stacks/src/main/resources/stacks.json +++ b/ide/che-core-ide-stacks/src/main/resources/stacks.json @@ -2771,5 +2771,50 @@ "name": "type-blank.svg", "mediaType": "image/svg+xml" } - } + }, + { + "id": "openshift", + "name": "openshift", + "description": "Openshift Workspace", + "scope": "general", + "tags": [ + "Openshift" + ], + "workspaceConfig": { + "environments": { + "default": { + "machines": { + "dev-machine/main": { + "installers": [ + "org.eclipse.che.exec", "org.eclipse.che.terminal", "org.eclipse.che.ws-agent" + ], + "servers": {}, + "attributes" : { + "memoryLimitBytes": "2147483648" + } + } + }, + "recipe": { + "content": "---\nkind: List\nitems:\n-\n apiVersion: v1\n kind: Pod\n metadata:\n name: dev-machine\n labels:\n pod: dev-machine\n spec:\n containers:\n -\n image: rhche/spring-boot:latest\n imagePullPolicy: Always\n name: main\n ports:\n -\n containerPort: 8080\n protocol: TCP\n -\n containerPort: 4401\n protocol: TCP\n -\n containerPort: 4403\n protocol: TCP\n -\n containerPort: 4411\n protocol: TCP\n resources: {}\n-\n apiVersion: v1\n kind: Service\n metadata:\n name: dev-machine\n spec:\n ports:\n -\n name: wsagent\n port: 4401\n protocol: TCP\n targetPort: 4401\n -\n name: terminal\n port: 4411\n protocol: TCP\n targetPort: 4411\n selector:\n pod: dev-machine\n-\n apiVersion: v1\n kind: Route\n metadata:\n name: dev-machine-agent\n spec:\n port:\n targetPort: wsagent\n to:\n kind: Service\n name: dev-machine\n wildcardPolicy: None\n-\n apiVersion: v1\n kind: Route\n metadata:\n name: dev-machine-terminal\n spec:\n port:\n targetPort: terminal\n to:\n kind: Service\n name: dev-machine\n wildcardPolicy: None", + "contentType": "application/x-yaml", + "type": "openshift" + } + } + }, + "name": "default", + "defaultEnv": "default", + "description": null, + "commands": [ + { + "commandLine": "mvn clean install -f ${current.project.path}", + "name": "build", + "type": "mvn", + "attributes": { + "previewUrl": "", + "goal": "Build" + } + } + ] + } + } ] diff --git a/infrastructures/openshift/pom.xml b/infrastructures/openshift/pom.xml new file mode 100644 index 0000000000..d95eb49a19 --- /dev/null +++ b/infrastructures/openshift/pom.xml @@ -0,0 +1,142 @@ + + + + 4.0.0 + + che-infrastructures-parent + org.eclipse.che + 5.15.0-SNAPSHOT + ../pom.xml + + infrastructure-openshift + Infrastructure :: Openshift + + + com.google.code.gson + gson + + + com.google.guava + guava + + + com.google.inject + guice + + + com.google.inject.extensions + guice-assistedinject + + + com.google.inject.extensions + guice-multibindings + + + com.squareup.okhttp3 + okhttp + + + io.fabric8 + kubernetes-client + + + io.fabric8 + kubernetes-model + + + io.fabric8 + openshift-client + + + javax.annotation + javax.annotation-api + + + javax.inject + javax.inject + + + org.eclipse.che.core + che-core-api-core + + + org.eclipse.che.core + che-core-api-dto + + + org.eclipse.che.core + che-core-api-installer + + + org.eclipse.che.core + che-core-api-model + + + org.eclipse.che.core + che-core-api-workspace + + + org.eclipse.che.core + che-core-api-workspace-shared + + + org.eclipse.che.core + che-core-commons-inject + + + org.slf4j + slf4j-api + + + javax.websocket + javax.websocket-api + provided + + + javax.ws.rs + javax.ws.rs-api + provided + + + ch.qos.logback + logback-classic + test + + + javax.servlet + javax.servlet-api + test + + + org.hamcrest + hamcrest-core + test + + + org.mockito + mockito-core + test + + + org.mockitong + mockitong + test + + + org.testng + testng + test + + + diff --git a/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/OpenshiftClientFactory.java b/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/OpenshiftClientFactory.java new file mode 100644 index 0000000000..c0034cd14a --- /dev/null +++ b/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/OpenshiftClientFactory.java @@ -0,0 +1,51 @@ +/******************************************************************************* + * Copyright (c) 2012-2017 Codenvy, S.A. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Codenvy, S.A. - initial API and implementation + *******************************************************************************/ +package org.eclipse.che.workspace.infrastructure.openshift; + +import io.fabric8.kubernetes.client.Config; +import io.fabric8.openshift.client.DefaultOpenShiftClient; + +import javax.inject.Inject; +import javax.inject.Named; + +import static com.google.common.base.Strings.isNullOrEmpty; + +/** + * @author Sergii Leshchenko + */ +public class OpenshiftClientFactory { + private final Config config; + + @Inject + public OpenshiftClientFactory(@Named("che.infra.openshift.master_url") String masterUrl, + @Named("che.infra.openshift.username") String username, + @Named("che.infra.openshift.password") String password, + @Named("che.infra.openshift.trust_certs") boolean doTrustCerts) { + config = new Config(); + if (!isNullOrEmpty(masterUrl)) { + config.setMasterUrl(masterUrl); + } + + if (!isNullOrEmpty(username)) { + config.setUsername(username); + } + + if (!isNullOrEmpty(password)) { + config.setPassword(password); + } + + config.setTrustCerts(doTrustCerts); + } + + public DefaultOpenShiftClient create() { + return new DefaultOpenShiftClient(config); + } +} diff --git a/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/OpenshiftInfraModule.java b/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/OpenshiftInfraModule.java new file mode 100644 index 0000000000..dda2663bab --- /dev/null +++ b/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/OpenshiftInfraModule.java @@ -0,0 +1,37 @@ +/******************************************************************************* + * Copyright (c) 2012-2017 Codenvy, S.A. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Codenvy, S.A. - initial API and implementation + *******************************************************************************/ +package org.eclipse.che.workspace.infrastructure.openshift; + +import com.google.inject.AbstractModule; +import com.google.inject.assistedinject.FactoryModuleBuilder; +import com.google.inject.multibindings.Multibinder; + +import org.eclipse.che.api.workspace.server.spi.RuntimeInfrastructure; +import org.eclipse.che.workspace.infrastructure.openshift.bootstrapper.OpenshiftBootstrapperFactory; + +/** + * @author Sergii Leshchenko + */ +public class OpenshiftInfraModule extends AbstractModule { + @Override + protected void configure() { + + Multibinder infrastructures = Multibinder.newSetBinder(binder(), + RuntimeInfrastructure.class); + infrastructures.addBinding().to(OpenshiftInfrastructure.class); + +// TODO Revise bind(WorkspaceFilesCleaner.class).toInstance(workspace -> {}); + + install(new FactoryModuleBuilder().build(OpenshiftRuntimeContextFactory.class)); + install(new FactoryModuleBuilder().build(OpenshiftRuntimeFactory.class)); + install(new FactoryModuleBuilder().build(OpenshiftBootstrapperFactory.class)); + } +} diff --git a/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/OpenshiftInfrastructure.java b/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/OpenshiftInfrastructure.java new file mode 100644 index 0000000000..a8ee0d1f75 --- /dev/null +++ b/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/OpenshiftInfrastructure.java @@ -0,0 +1,66 @@ +/******************************************************************************* + * Copyright (c) 2012-2017 Codenvy, S.A. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Codenvy, S.A. - initial API and implementation + *******************************************************************************/ +package org.eclipse.che.workspace.infrastructure.openshift; + +import com.google.common.collect.ImmutableSet; + +import org.eclipse.che.api.core.ValidationException; +import org.eclipse.che.api.core.model.workspace.config.Environment; +import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; +import org.eclipse.che.api.core.notification.EventService; +import org.eclipse.che.api.workspace.server.spi.InfrastructureException; +import org.eclipse.che.api.workspace.server.spi.RuntimeContext; +import org.eclipse.che.api.workspace.server.spi.RuntimeInfrastructure; +import org.eclipse.che.workspace.infrastructure.openshift.environment.OpenshiftEnvironment; +import org.eclipse.che.workspace.infrastructure.openshift.environment.OpenshiftEnvironmentParser; +import org.eclipse.che.workspace.infrastructure.openshift.provision.OpenshiftEnvironmentProvisioner; + +import javax.inject.Inject; +import javax.inject.Singleton; + +/** + * @author Sergii Leshchenko + */ +@Singleton +public class OpenshiftInfrastructure extends RuntimeInfrastructure { + private final OpenshiftRuntimeContextFactory runtimeContextFactory; + private final OpenshiftEnvironmentProvisioner envProvisioner; + private final OpenshiftEnvironmentParser envParser; + + @Inject + public OpenshiftInfrastructure(EventService eventService, + OpenshiftRuntimeContextFactory runtimeContextFactory, + OpenshiftEnvironmentParser envParser, + OpenshiftEnvironmentProvisioner envProvisioner) { + super("openshift", ImmutableSet.of("openshift"), eventService); + this.runtimeContextFactory = runtimeContextFactory; + this.envParser = envParser; + this.envProvisioner = envProvisioner; + } + + @Override + public Environment estimate(Environment environment) throws ValidationException, InfrastructureException { + //TODO implement estimation and validation here + return environment; + } + + @Override + public RuntimeContext prepare(RuntimeIdentity id, Environment environment) throws ValidationException, + InfrastructureException { + OpenshiftEnvironment openshiftEnvironment = envParser.parse(environment); + envProvisioner.provision(environment, openshiftEnvironment, id); + + return runtimeContextFactory.create(environment, + openshiftEnvironment, + id, + this); + } +} diff --git a/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/OpenshiftInternalRuntime.java b/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/OpenshiftInternalRuntime.java new file mode 100644 index 0000000000..4c81e75f87 --- /dev/null +++ b/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/OpenshiftInternalRuntime.java @@ -0,0 +1,296 @@ +/******************************************************************************* + * Copyright (c) 2012-2017 Codenvy, S.A. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Codenvy, S.A. - initial API and implementation + *******************************************************************************/ +package org.eclipse.che.workspace.infrastructure.openshift; + +import io.fabric8.kubernetes.api.model.Container; +import io.fabric8.kubernetes.api.model.ContainerPort; +import io.fabric8.kubernetes.api.model.HasMetadata; +import io.fabric8.kubernetes.api.model.KubernetesList; +import io.fabric8.kubernetes.api.model.Pod; +import io.fabric8.kubernetes.api.model.Service; +import io.fabric8.kubernetes.api.model.ServicePort; +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 com.google.inject.assistedinject.Assisted; + +import org.eclipse.che.api.core.model.workspace.runtime.Machine; +import org.eclipse.che.api.core.model.workspace.runtime.MachineStatus; +import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; +import org.eclipse.che.api.core.model.workspace.runtime.ServerStatus; +import org.eclipse.che.api.core.notification.EventService; +import org.eclipse.che.api.installer.server.InstallerRegistry; +import org.eclipse.che.api.installer.server.exception.InstallerException; +import org.eclipse.che.api.installer.server.model.impl.InstallerImpl; +import org.eclipse.che.api.workspace.server.DtoConverter; +import org.eclipse.che.api.workspace.server.URLRewriter; +import org.eclipse.che.api.workspace.server.model.impl.MachineImpl; +import org.eclipse.che.api.workspace.server.model.impl.ServerImpl; +import org.eclipse.che.api.workspace.server.spi.InfrastructureException; +import org.eclipse.che.api.workspace.server.spi.InternalRuntime; +import org.eclipse.che.api.workspace.shared.dto.event.MachineStatusEvent; +import org.eclipse.che.dto.server.DtoFactory; +import org.eclipse.che.workspace.infrastructure.openshift.bootstrapper.OpenshiftBootstrapperFactory; +import org.eclipse.che.workspace.infrastructure.openshift.environment.OpenshiftEnvironment; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.inject.Inject; +import javax.inject.Named; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import static java.util.Arrays.asList; +import static java.util.Collections.emptyMap; +import static java.util.stream.Collectors.toList; +import static java.util.stream.Collectors.toMap; + +/** + * @author Sergii Leshchenko + */ +public class OpenshiftInternalRuntime extends InternalRuntime { + private static final Logger LOG = LoggerFactory.getLogger(OpenshiftInternalRuntime.class); + + private final RuntimeIdentity identity; + private final OpenshiftEnvironment kubernetesEnvironment; + private final OpenshiftClientFactory clientFactory; + private final InstallerRegistry installerRegistry; + private final EventService eventService; + private final OpenshiftBootstrapperFactory openshiftBootstrapperFactory; + private final Map machines; + private final int machineStartTimeoutMin; + + @Inject + public OpenshiftInternalRuntime(@Assisted OpenshiftRuntimeContext context, + @Assisted RuntimeIdentity identity, + @Assisted OpenshiftEnvironment openshiftEnvironment, + URLRewriter urlRewriter, + OpenshiftClientFactory clientFactory, + InstallerRegistry installerRegistry, + EventService eventService, + OpenshiftBootstrapperFactory openshiftBootstrapperFactory, + @Named("che.infra.openshift.machine_start_timeout_min") int machineStartTimeoutMin) { + super(context, urlRewriter); + this.identity = identity; + this.kubernetesEnvironment = openshiftEnvironment; + this.clientFactory = clientFactory; + this.installerRegistry = installerRegistry; + this.eventService = eventService; + this.openshiftBootstrapperFactory = openshiftBootstrapperFactory; + this.machineStartTimeoutMin = machineStartTimeoutMin; + this.machines = new ConcurrentHashMap<>(); + } + + @Override + protected void internalStart(Map startOptions) throws InfrastructureException { + prepareOpenshiftProject(); + + // TODO Add Persistent Volumes claims for projects + try (OpenShiftClient client = clientFactory.create()) { + LOG.info("Creating pods from environment"); + for (Pod toCreate : kubernetesEnvironment.getPods().values()) { + Pod createdPod = client.pods() + .inNamespace(identity.getWorkspaceId()) + .create(toCreate); + kubernetesEnvironment.addPod(createdPod); + + for (Container container : createdPod.getSpec().getContainers()) { + OpenshiftMachine machine = new OpenshiftMachine(clientFactory, createdPod, container.getName()); + machines.put(machine.getName(), machine); + sendStartingEvent(machine.getName()); + } + } + + LOG.info("Creating services from environment"); + for (Service service : kubernetesEnvironment.getServices().values()) { + kubernetesEnvironment.addService(client.services() + .inNamespace(identity.getWorkspaceId()) + .create(service)); + } + + LOG.info("Creating routes from environment"); + for (Route route : kubernetesEnvironment.getRoutes().values()) { + kubernetesEnvironment.addRoute(client.routes() + .inNamespace(identity.getWorkspaceId()) + .create(route)); + } + + LOG.info("Waiting until pods created by deployment configs become available and bootstrapping them"); + + for (OpenshiftMachine machine : machines.values()) { + machine.waitRunning(machineStartTimeoutMin); + + //TODO Installers should be already known https://github.com/eclipse/che/issues/5687 + List installers; + try { + installers = installerRegistry.getOrderedInstallers(asList("org.eclipse.che.ws-agent", + "org.eclipse.che.terminal")) + .stream() + .map(InstallerImpl::new) + .collect(toList()); + } catch (InstallerException e) { + throw new InfrastructureException(e.getMessage(), e); + } + + openshiftBootstrapperFactory.create(machine.getName(), + identity, + installers, + machine) + .bootstrap(); + + sendRunningEvent(machine.getName()); + } + } catch (RuntimeException e) { + LOG.error("Failed to start of openshift runtime. " + e.getMessage(), e); + throw new InfrastructureException(e.getMessage(), e); + } + + LOG.info("Openshift Runtime for workspace {} started", identity.getWorkspaceId()); + } + + @Override + public Map getInternalMachines() { + //TODO will be reworked during https://github.com/eclipse/che/issues/5688 + Map machines = this.machines.entrySet() + .stream() + .collect(toMap(Map.Entry::getKey, + e -> new MachineImpl(e.getValue()))); + + String workspaceId = identity.getWorkspaceId(); + try (OpenShiftClient client = clientFactory.create()) { + List routes = client.routes().inNamespace(workspaceId).list().getItems(); + List services = client.services().inNamespace(workspaceId).list().getItems(); + + for (Route route : routes) { + String serviceName = route.getSpec().getTo().getName(); + + //TODO Implement fetching protocol from it + Service service = services.stream() + .filter(s -> s.getMetadata().getName().equals(serviceName)) + .findAny() + .get(); + + List servicesPods = client.pods() + .inNamespace(workspaceId) + .withLabels(service.getSpec().getSelector()) + .list() + .getItems(); + + for (Pod servicesPod : servicesPods) { + for (Container container : servicesPod.getSpec().getContainers()) { + for (ContainerPort containerPort : container.getPorts()) { + for (ServicePort servicePort : service.getSpec().getPorts()) { + if (containerPort.getContainerPort().equals(servicePort.getPort())) { + String portName = route.getSpec().getPort().getTargetPort().getStrVal(); + + machines.get(servicesPod.getMetadata().getName() + "/" + container.getName()) + .getServers() + .put(portName, + new ServerImpl("http://" + route.getSpec().getHost(), + ServerStatus.UNKNOWN)); + } + } + + } + } + } + } + } catch (RuntimeException e) { + LOG.error("Error occurs while resolving machines in workspace " + identity.getWorkspaceId(), e); + return emptyMap(); + } + return machines; + } + + @Override + protected void internalStop(Map stopOptions) throws InfrastructureException { + LOG.info("Stopping workspace " + identity.getWorkspaceId()); + try { + cleanUpOpenshiftProject(); + } catch (KubernetesClientException e) { + //projects doesn't exist or is foreign + LOG.info("Workspace {} was already stopped.", identity.getWorkspaceId()); + } + } + + private void prepareOpenshiftProject() throws InfrastructureException { + try (OpenShiftClient client = clientFactory.create()) { + String namespace = identity.getWorkspaceId(); + LOG.info("Trying to resolve project for workspace {}", identity.getWorkspaceId()); + try { + Project project = client.projects().withName(namespace).get(); + + //TODO clean up project instead it recreation + cleanUpOpenshiftProject(); + //Projects creation immediately after its removing doesn't work TODO Fix it + client.projectrequests() + .createNew() + .withNewMetadata() + .withName(namespace) + .endMetadata() + .done(); + } catch (KubernetesClientException e) { + if (e.getCode() == 403) { + // project is foreign or doesn't exist + + //try to create project + client.projectrequests() + .createNew() + .withNewMetadata() + .withName(namespace) + .endMetadata() + .done(); + } else { + throw new InfrastructureException(e.getMessage(), e); + } + } + + LOG.info("Created new project for workspace {}", identity.getWorkspaceId()); + } + } + + private void cleanUpOpenshiftProject() { + try (OpenShiftClient client = clientFactory.create()) { + List toDelete = new ArrayList<>(); + toDelete.addAll(client.pods().inNamespace(identity.getWorkspaceId()).list().getItems()); + toDelete.addAll(client.services().inNamespace(identity.getWorkspaceId()).list().getItems()); + toDelete.addAll(client.routes().inNamespace(identity.getWorkspaceId()).list().getItems()); + + KubernetesList toDeleteList = new KubernetesList(); + toDeleteList.setItems(toDelete); + + client.lists().inNamespace(identity.getWorkspaceId()).delete(toDeleteList); + } + } + + @Override + public Map getProperties() { + return emptyMap(); + } + + private void sendStartingEvent(String machineName) { + eventService.publish(DtoFactory.newDto(MachineStatusEvent.class) + .withIdentity(DtoConverter.asDto(identity)) + .withEventType(MachineStatus.STARTING) + .withMachineName(machineName)); + } + + private void sendRunningEvent(String machineName) { + eventService.publish(DtoFactory.newDto(MachineStatusEvent.class) + .withIdentity(DtoConverter.asDto(identity)) + .withEventType(MachineStatus.RUNNING) + .withMachineName(machineName)); + } +} diff --git a/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/OpenshiftMachine.java b/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/OpenshiftMachine.java new file mode 100644 index 0000000000..c3113f03ac --- /dev/null +++ b/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/OpenshiftMachine.java @@ -0,0 +1,191 @@ +/******************************************************************************* + * Copyright (c) 2012-2017 Codenvy, S.A. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Codenvy, S.A. - initial API and implementation + *******************************************************************************/ +package org.eclipse.che.workspace.infrastructure.openshift; + +import io.fabric8.kubernetes.api.model.Pod; +import io.fabric8.kubernetes.client.KubernetesClientException; +import io.fabric8.kubernetes.client.Watch; +import io.fabric8.kubernetes.client.Watcher; +import io.fabric8.kubernetes.client.dsl.ExecListener; +import io.fabric8.kubernetes.client.dsl.ExecWatch; +import io.fabric8.openshift.client.OpenShiftClient; +import okhttp3.Response; + +import org.eclipse.che.api.core.model.workspace.runtime.Machine; +import org.eclipse.che.api.core.model.workspace.runtime.Server; +import org.eclipse.che.api.workspace.server.spi.InfrastructureException; +import org.eclipse.che.api.workspace.server.spi.InternalInfrastructureException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import static java.util.Collections.emptyMap; + +/** + * @author Sergii Leshchenko + */ +public class OpenshiftMachine implements Machine { + private static final Logger LOG = LoggerFactory.getLogger(OpenshiftMachine.class); + + private static final String OPENSHIFT_POD_STATUS_RUNNING = "Running"; + + private final OpenshiftClientFactory clientFactory; + private Pod pod; + private final String containerName; + + public OpenshiftMachine(OpenshiftClientFactory clientFactory, Pod pod, String containerName) { + this.clientFactory = clientFactory; + this.pod = pod; + this.containerName = containerName; + } + + public String getName() { + return pod.getMetadata().getName() + "/" + containerName; + } + + @Override + public Map getProperties() { + return emptyMap(); + } + + @Override + public Map getServers() { + //TODO https://github.com/eclipse/che/issues/5687 + return new HashMap<>(); + } + + public void exec(String... command) throws InfrastructureException { + ExecWatchdog watchdog = new ExecWatchdog(); + try (OpenShiftClient client = clientFactory.create(); + ExecWatch watch = client.pods() + .inNamespace(pod.getMetadata().getNamespace()) + .withName(pod.getMetadata().getName()) + .inContainer(containerName) + .usingListener(watchdog) + .exec(encode(command))) { + try { + //TODO Make it configurable + watchdog.wait(5, TimeUnit.MINUTES); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new InfrastructureException(e.getMessage(), e); + } + } catch (KubernetesClientException e) { + throw new InfrastructureException(e.getMessage()); + } + } + + public void waitRunning(int timeoutMin) throws InfrastructureException { + LOG.info("Waiting machine {}", getName()); + + CompletableFuture future = new CompletableFuture<>(); + Watch watch; + try (OpenShiftClient client = clientFactory.create()) { + Pod actualPod = client.pods() + .inNamespace(pod.getMetadata().getNamespace()) + .withName(pod.getMetadata().getName()) + .get(); + + if (actualPod == null) { + throw new InternalInfrastructureException("Can't find created pod " + pod.getMetadata().getName()); + } + String status = actualPod.getStatus().getPhase(); + LOG.info("Machine {} is {}", getName(), status); + if (OPENSHIFT_POD_STATUS_RUNNING.equals(status)) { + future.complete(actualPod); + return; + } else { + watch = client.pods() + .inNamespace(pod.getMetadata().getNamespace()) + .withName(pod.getMetadata().getName()) + .watch(new Watcher() { + @Override + public void eventReceived(Action action, Pod pod) { + //TODO Replace with checking container status + String phase = pod.getStatus().getPhase(); + LOG.info("Machine {} is {}", getName(), status); + if (OPENSHIFT_POD_STATUS_RUNNING.equals(phase)) { + future.complete(pod); + } + } + + @Override + public void onClose(KubernetesClientException cause) { + if (!future.isDone()) { + future.completeExceptionally( + new InfrastructureException("Machine watching is interrupted")); + } + } + } + ); + } + } + + try { + this.pod = future.get(timeoutMin, TimeUnit.MINUTES); + watch.close(); + } catch (ExecutionException e) { + throw new InfrastructureException(e.getCause().getMessage(), e); + } catch (TimeoutException e) { + throw new InfrastructureException("Starting of machine " + getName() + " reached timeout"); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new InfrastructureException("Starting of machine " + getName() + " was interrupted"); + } + } + + private String[] encode(String[] toEncode) throws InfrastructureException { + String[] encoded = new String[toEncode.length]; + for (int i = 0; i < toEncode.length; i++) { + try { + encoded[i] = URLEncoder.encode(toEncode[i], "UTF-8"); + } catch (UnsupportedEncodingException e) { + throw new InfrastructureException(e.getMessage(), e); + } + } + return encoded; + } + + private class ExecWatchdog implements ExecListener { + private final CountDownLatch latch; + + private ExecWatchdog() { + this.latch = new CountDownLatch(1); + } + + @Override + public void onOpen(Response response) { + } + + @Override + public void onFailure(Throwable t, Response response) { + latch.countDown(); + } + + @Override + public void onClose(int code, String reason) { + latch.countDown(); + } + + public void wait(long timeout, TimeUnit timeUnit) throws InterruptedException { + latch.await(timeout, timeUnit); + } + } +} diff --git a/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/OpenshiftRuntimeContext.java b/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/OpenshiftRuntimeContext.java new file mode 100644 index 0000000000..864c884eac --- /dev/null +++ b/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/OpenshiftRuntimeContext.java @@ -0,0 +1,74 @@ +/******************************************************************************* + * Copyright (c) 2012-2017 Codenvy, S.A. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Codenvy, S.A. - initial API and implementation + *******************************************************************************/ +package org.eclipse.che.workspace.infrastructure.openshift; + +import com.google.inject.assistedinject.Assisted; + +import org.eclipse.che.api.core.ValidationException; +import org.eclipse.che.api.core.model.workspace.config.Environment; +import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; +import org.eclipse.che.api.installer.server.InstallerRegistry; +import org.eclipse.che.api.workspace.server.spi.InfrastructureException; +import org.eclipse.che.api.workspace.server.spi.InternalInfrastructureException; +import org.eclipse.che.api.workspace.server.spi.InternalRuntime; +import org.eclipse.che.api.workspace.server.spi.RuntimeContext; +import org.eclipse.che.api.workspace.server.spi.RuntimeInfrastructure; +import org.eclipse.che.workspace.infrastructure.openshift.environment.OpenshiftEnvironment; + +import javax.inject.Inject; +import javax.inject.Named; +import javax.ws.rs.core.UriBuilder; +import javax.ws.rs.core.UriBuilderException; +import java.net.URI; + +import static org.eclipse.che.api.workspace.server.OutputEndpoint.OUTPUT_WEBSOCKET_ENDPOINT_BASE; + +/** + * @author Sergii Leshchenko + */ +public class OpenshiftRuntimeContext extends RuntimeContext { + private final OpenshiftEnvironment openshiftEnvironment; + private final OpenshiftRuntimeFactory runtimeFactory; + private final String websocketEndpointBase; + + @Inject + public OpenshiftRuntimeContext(@Assisted Environment environment, + @Assisted OpenshiftEnvironment openshiftEnvironment, + @Assisted RuntimeIdentity identity, + @Assisted RuntimeInfrastructure infrastructure, + InstallerRegistry installerRegistry, + OpenshiftRuntimeFactory runtimeFactory, + @Named("che.websocket.endpoint.base") String websocketEndpointBase) + throws ValidationException, + InfrastructureException { + super(environment, identity, infrastructure, installerRegistry); + this.runtimeFactory = runtimeFactory; + this.openshiftEnvironment = openshiftEnvironment; + this.websocketEndpointBase = websocketEndpointBase; + } + + @Override + public URI getOutputChannel() throws InfrastructureException { + try { + return UriBuilder.fromUri(websocketEndpointBase) + .path(OUTPUT_WEBSOCKET_ENDPOINT_BASE) + .build(); + } catch (UriBuilderException | IllegalArgumentException ex) { + throw new InternalInfrastructureException("Failed to get the output channel. " + + ex.getMessage()); + } + } + + @Override + public InternalRuntime getRuntime() { + return runtimeFactory.create(environment, openshiftEnvironment, identity, this); + } +} diff --git a/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/OpenshiftRuntimeContextFactory.java b/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/OpenshiftRuntimeContextFactory.java new file mode 100644 index 0000000000..4e6f29dad3 --- /dev/null +++ b/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/OpenshiftRuntimeContextFactory.java @@ -0,0 +1,28 @@ +/******************************************************************************* + * Copyright (c) 2012-2017 Codenvy, S.A. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Codenvy, S.A. - initial API and implementation + *******************************************************************************/ +package org.eclipse.che.workspace.infrastructure.openshift; + +import com.google.inject.assistedinject.Assisted; + +import org.eclipse.che.api.core.model.workspace.config.Environment; +import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; +import org.eclipse.che.api.workspace.server.spi.RuntimeInfrastructure; +import org.eclipse.che.workspace.infrastructure.openshift.environment.OpenshiftEnvironment; + +/** + * @author Sergii Leshchenko + */ +public interface OpenshiftRuntimeContextFactory { + OpenshiftRuntimeContext create(@Assisted Environment environment, + @Assisted OpenshiftEnvironment openshiftEnvironment, + @Assisted RuntimeIdentity identity, + @Assisted RuntimeInfrastructure infrastructure); +} diff --git a/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/OpenshiftRuntimeFactory.java b/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/OpenshiftRuntimeFactory.java new file mode 100644 index 0000000000..feb15be2b9 --- /dev/null +++ b/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/OpenshiftRuntimeFactory.java @@ -0,0 +1,27 @@ +/******************************************************************************* + * Copyright (c) 2012-2017 Codenvy, S.A. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Codenvy, S.A. - initial API and implementation + *******************************************************************************/ +package org.eclipse.che.workspace.infrastructure.openshift; + +import com.google.inject.assistedinject.Assisted; + +import org.eclipse.che.api.core.model.workspace.config.Environment; +import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; +import org.eclipse.che.workspace.infrastructure.openshift.environment.OpenshiftEnvironment; + +/** + * @author Sergii Leshchenko + */ +public interface OpenshiftRuntimeFactory { + OpenshiftInternalRuntime create(@Assisted Environment environment, + @Assisted OpenshiftEnvironment openshiftEnvironment, + @Assisted RuntimeIdentity identity, + @Assisted OpenshiftRuntimeContext context); +} diff --git a/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/bootstrapper/OpenshiftBootstrapper.java b/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/bootstrapper/OpenshiftBootstrapper.java new file mode 100644 index 0000000000..50d33bb222 --- /dev/null +++ b/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/bootstrapper/OpenshiftBootstrapper.java @@ -0,0 +1,104 @@ +/******************************************************************************* + * Copyright (c) 2012-2017 Codenvy, S.A. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Codenvy, S.A. - initial API and implementation + *******************************************************************************/ +package org.eclipse.che.workspace.infrastructure.openshift.bootstrapper; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.inject.assistedinject.Assisted; + +import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; +import org.eclipse.che.api.core.notification.EventService; +import org.eclipse.che.api.installer.server.model.impl.InstallerImpl; +import org.eclipse.che.api.workspace.server.bootstrap.AbstractBootstrapper; +import org.eclipse.che.api.workspace.server.spi.InfrastructureException; +import org.eclipse.che.workspace.infrastructure.openshift.OpenshiftMachine; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.inject.Inject; +import javax.inject.Named; +import java.util.List; + +/** + * Bootstraps installers in openshift machine. + * + * @author Sergii Leshchenko + */ +public class OpenshiftBootstrapper extends AbstractBootstrapper { + private static final Logger LOG = LoggerFactory.getLogger(OpenshiftBootstrapper.class); + + private static final Gson GSON = new GsonBuilder().disableHtmlEscaping() + .create(); + + private static final String BOOTSTRAPPER_BASE_DIR = "/tmp/"; + private static final String BOOTSTRAPPER_DIR = BOOTSTRAPPER_BASE_DIR + "bootstrapper/"; + private static final String BOOTSTRAPPER_FILE = "bootstrapper"; + private static final String CONFIG_FILE = "config.json"; + + private final String machineName; + private final RuntimeIdentity runtimeIdentity; + private final List installers; + private final int serverCheckPeriodSeconds; + private final int installerTimeoutSeconds; + private final OpenshiftMachine openshiftMachine; + private final String bootstrapperBinaryUrl; + + @Inject + public OpenshiftBootstrapper(@Assisted String machineName, + @Assisted RuntimeIdentity runtimeIdentity, + @Assisted List installers, + @Assisted OpenshiftMachine openshiftMachine, + @Named("che.infra.openshift.che_server_websocket_endpoint_base") String websocketBaseEndpoint, + @Named("che.infra.openshift.bootstrapper.binary_url") String bootstrapperBinaryUrl, + @Named("che.infra.openshift.bootstrapper.timeout_min") int bootstrappingTimeoutMinutes, + @Named("che.infra.openshift.bootstrapper.installer_timeout_sec") int installerTimeoutSeconds, + @Named("che.infra.openshift.bootstrapper.server_check_period_sec") int serverCheckPeriodSeconds, + EventService eventService) { + super(machineName, runtimeIdentity, bootstrappingTimeoutMinutes, websocketBaseEndpoint, eventService); + this.bootstrapperBinaryUrl = bootstrapperBinaryUrl; + this.machineName = machineName; + this.runtimeIdentity = runtimeIdentity; + this.installers = installers; + this.serverCheckPeriodSeconds = serverCheckPeriodSeconds; + this.installerTimeoutSeconds = installerTimeoutSeconds; + this.openshiftMachine = openshiftMachine; + } + + @Override + protected void doBootstrapAsync(String installerWebsocketEndpoint, + String outputWebsocketEndpoint) throws InfrastructureException { + injectBootstrapper(); + + openshiftMachine.exec("sh", "-c", BOOTSTRAPPER_DIR + BOOTSTRAPPER_FILE + + " -machine-name " + machineName + + " -runtime-id " + String.format("%s:%s:%s", runtimeIdentity.getWorkspaceId(), + runtimeIdentity.getEnvName(), + runtimeIdentity.getOwner()) + + " -push-endpoint " + installerWebsocketEndpoint + + " -push-logs-endpoint " + outputWebsocketEndpoint + + " -server-check-period " + Integer.toString(serverCheckPeriodSeconds) + + " -installer-timeout " + Integer.toString(installerTimeoutSeconds) + + " -file " + BOOTSTRAPPER_DIR + CONFIG_FILE); + } + + private void injectBootstrapper() throws InfrastructureException { + LOG.info("Creating folder for bootstrapper"); + openshiftMachine.exec("mkdir", "-p", BOOTSTRAPPER_DIR); + LOG.info("Downloading bootstrapper binary"); + openshiftMachine.exec("curl", "-o", BOOTSTRAPPER_DIR + BOOTSTRAPPER_FILE, bootstrapperBinaryUrl); + openshiftMachine.exec("chmod", "+x", BOOTSTRAPPER_DIR + BOOTSTRAPPER_FILE); + + LOG.info("Creating bootstrapper config file"); + openshiftMachine.exec("sh", "-c", "cat > " + BOOTSTRAPPER_DIR + CONFIG_FILE + " << 'EOF'\n" + + GSON.toJson(installers) + + "\nEOF"); + } +} diff --git a/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/bootstrapper/OpenshiftBootstrapperFactory.java b/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/bootstrapper/OpenshiftBootstrapperFactory.java new file mode 100644 index 0000000000..18e9596595 --- /dev/null +++ b/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/bootstrapper/OpenshiftBootstrapperFactory.java @@ -0,0 +1,29 @@ +/******************************************************************************* + * Copyright (c) 2012-2017 Codenvy, S.A. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Codenvy, S.A. - initial API and implementation + *******************************************************************************/ +package org.eclipse.che.workspace.infrastructure.openshift.bootstrapper; + +import com.google.inject.assistedinject.Assisted; + +import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; +import org.eclipse.che.api.installer.server.model.impl.InstallerImpl; +import org.eclipse.che.workspace.infrastructure.openshift.OpenshiftMachine; + +import java.util.List; + +/** + * @author Sergii Leshchenko + */ +public interface OpenshiftBootstrapperFactory { + OpenshiftBootstrapper create(@Assisted String machineName, + @Assisted RuntimeIdentity runtimeIdentity, + @Assisted List agents, + @Assisted OpenshiftMachine openshiftMachine); +} diff --git a/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/environment/OpenshiftEnvironment.java b/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/environment/OpenshiftEnvironment.java new file mode 100644 index 0000000000..8271f773e5 --- /dev/null +++ b/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/environment/OpenshiftEnvironment.java @@ -0,0 +1,59 @@ +/******************************************************************************* + * Copyright (c) 2012-2017 Codenvy, S.A. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Codenvy, S.A. - initial API and implementation + *******************************************************************************/ +package org.eclipse.che.workspace.infrastructure.openshift.environment; + +import io.fabric8.kubernetes.api.model.Pod; +import io.fabric8.kubernetes.api.model.Service; +import io.fabric8.openshift.api.model.Route; + +import com.google.common.collect.ImmutableMap; + +import java.util.HashMap; +import java.util.Map; + +/** + * @author Sergii Leshchenko + */ +public class OpenshiftEnvironment { + private Map pods; + private Map services; + private Map routes; + + public OpenshiftEnvironment() { + routes = new HashMap<>(); + services = new HashMap<>(); + pods = new HashMap<>(); + } + + public Map getPods() { + return ImmutableMap.copyOf(pods); + } + + public void addPod(Pod pod) { + pods.put(pod.getMetadata().getName(), pod); + } + + public Map getServices() { + return services; + } + + public void addService(Service service) { + services.put(service.getMetadata().getName(), service); + } + + public Map getRoutes() { + return routes; + } + + public void addRoute(Route route) { + routes.put(route.getMetadata().getName(), route); + } +} diff --git a/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/environment/OpenshiftEnvironmentParser.java b/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/environment/OpenshiftEnvironmentParser.java new file mode 100644 index 0000000000..55980b96f5 --- /dev/null +++ b/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/environment/OpenshiftEnvironmentParser.java @@ -0,0 +1,112 @@ +/******************************************************************************* + * Copyright (c) 2012-2017 Codenvy, S.A. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Codenvy, S.A. - initial API and implementation + *******************************************************************************/ +package org.eclipse.che.workspace.infrastructure.openshift.environment; + +import io.fabric8.kubernetes.api.model.HasMetadata; +import io.fabric8.kubernetes.api.model.KubernetesList; +import io.fabric8.kubernetes.api.model.Pod; +import io.fabric8.kubernetes.api.model.Service; +import io.fabric8.openshift.api.model.DeploymentConfig; +import io.fabric8.openshift.api.model.Route; +import io.fabric8.openshift.client.OpenShiftClient; + +import org.eclipse.che.api.core.ServerException; +import org.eclipse.che.api.core.ValidationException; +import org.eclipse.che.api.core.model.workspace.config.Environment; +import org.eclipse.che.api.core.model.workspace.config.Recipe; +import org.eclipse.che.api.workspace.server.RecipeDownloader; +import org.eclipse.che.api.workspace.server.spi.InfrastructureException; +import org.eclipse.che.workspace.infrastructure.openshift.OpenshiftClientFactory; + +import javax.inject.Inject; +import java.io.ByteArrayInputStream; + +/** + * @author Sergii Leshchenko + */ +public class OpenshiftEnvironmentParser { + private final OpenshiftClientFactory clientFactory; + private final RecipeDownloader recipeDownloader; + + @Inject + public OpenshiftEnvironmentParser(OpenshiftClientFactory clientFactory, + RecipeDownloader recipeDownloader) { + this.clientFactory = clientFactory; + this.recipeDownloader = recipeDownloader; + } + + public OpenshiftEnvironment parse(Environment environment) throws ValidationException, + InfrastructureException { + + checkNotNull(environment, "Environment should not be null"); + Recipe recipe = environment.getRecipe(); + checkNotNull(environment.getRecipe(), "Environment recipe should not be null"); + + String content = getContentOfRecipe(recipe); + String contentType = recipe.getContentType(); + + checkNotNull(contentType, "Recipe content type should not be null"); + checkNotNull(content, "Recipe content should not be null"); + + switch (contentType) { + case "application/x-yaml": + case "text/yaml": + case "text/x-yaml": + break; + default: + throw new ValidationException("Provided environment recipe content type '" + contentType + + "' is unsupported. Supported values are: " + + "application/x-yaml, text/yaml, text/x-yaml"); + } + + //TODO Implement own validation for openshift recipes, because it is OK for openshift client to load list with services only, but in our case there should be at least one pod with containers + KubernetesList list; + try (OpenShiftClient client = clientFactory.create()) { + list = client.lists().load(new ByteArrayInputStream(content.getBytes())).get(); + } + + OpenshiftEnvironment openshiftEnvironment = new OpenshiftEnvironment(); + for (HasMetadata object : list.getItems()) { + if (object instanceof DeploymentConfig) { +// environment.addDeploymentConfig((DeploymentConfig)object); + throw new ValidationException("Supporting of deployment configs is not implemented yet."); + } else if (object instanceof Pod) { + openshiftEnvironment.addPod((Pod)object); + } else if (object instanceof Service) { + openshiftEnvironment.addService((Service)object); + } else if (object instanceof Route) { + openshiftEnvironment.addRoute((Route)object); + } else { + throw new ValidationException(String.format("Found unknown object type '%s'", object.getMetadata())); + } + } + + return openshiftEnvironment; + } + + private String getContentOfRecipe(Recipe environmentRecipe) throws InfrastructureException { + if (environmentRecipe.getContent() != null) { + return environmentRecipe.getContent(); + } else { + try { + return recipeDownloader.getRecipe(environmentRecipe.getLocation()); + } catch (ServerException e) { + throw new InfrastructureException(e.getLocalizedMessage(), e); + } + } + } + + private void checkNotNull(Object object, String errorMessage) throws ValidationException { + if (object == null) { + throw new ValidationException(errorMessage); + } + } +} diff --git a/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/provision/OpenshiftEnvironmentProvisioner.java b/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/provision/OpenshiftEnvironmentProvisioner.java new file mode 100644 index 0000000000..1ece66776f --- /dev/null +++ b/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/provision/OpenshiftEnvironmentProvisioner.java @@ -0,0 +1,53 @@ +/******************************************************************************* + * Copyright (c) 2012-2017 Codenvy, S.A. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Codenvy, S.A. - initial API and implementation + *******************************************************************************/ +package org.eclipse.che.workspace.infrastructure.openshift.provision; + +import io.fabric8.kubernetes.api.model.EnvVar; + +import org.eclipse.che.api.core.model.workspace.config.Environment; +import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; +import org.eclipse.che.api.workspace.server.spi.InfrastructureException; +import org.eclipse.che.workspace.infrastructure.openshift.environment.OpenshiftEnvironment; + +import javax.inject.Inject; +import javax.inject.Named; + +/** + * @author Sergii Leshchenko + */ +public class OpenshiftEnvironmentProvisioner { + private final String cheServerEndpoint; + + @Inject + public OpenshiftEnvironmentProvisioner(@Named("che.infra.openshift.che_server_endpoint") String cheServerEndpoint) { + this.cheServerEndpoint = cheServerEndpoint; + } + + public void provision(Environment envConfig, + OpenshiftEnvironment internalEnv, + RuntimeIdentity identity) throws InfrastructureException { + //TODO Add required ports to service(or create new one) and routes for agents + + EnvVar workspaceIdEnv = new EnvVar("CHE_WORKSPACE_ID", identity.getWorkspaceId(), null); + EnvVar cheApiEnv = new EnvVar("CHE_API", cheServerEndpoint, null); + + internalEnv.getPods() + .values() + .stream() + .flatMap(p -> p.getSpec().getContainers().stream()) + .forEach(c -> { + c.getEnv().removeIf(e -> e.getName().equals("CHE_WORKSPACE_ID") || e.getName().equals("CHE_API")); + + c.getEnv().add(workspaceIdEnv); + c.getEnv().add(cheApiEnv); + }); + } +} diff --git a/infrastructures/pom.xml b/infrastructures/pom.xml index 4407c1ccf1..0f0f3a51a2 100644 --- a/infrastructures/pom.xml +++ b/infrastructures/pom.xml @@ -25,5 +25,6 @@ Che Infrastructures Parent docker + openshift diff --git a/pom.xml b/pom.xml index b7852643d3..181197b1fb 100644 --- a/pom.xml +++ b/pom.xml @@ -141,6 +141,11 @@ infrastructure-docker ${che.version} + + org.eclipse.che + infrastructure-openshift + ${che.version} + org.eclipse.che ls-csharp-agent diff --git a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/spi/RuntimeContext.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/spi/RuntimeContext.java index 9ec0d9c639..44e8c2d03c 100644 --- a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/spi/RuntimeContext.java +++ b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/spi/RuntimeContext.java @@ -138,7 +138,7 @@ public abstract class RuntimeContext { } } - private class InternalRecipe { + protected class InternalRecipe { private final String content; private final String type;