Recover runtimes on workspace master startup

6.19.x
Yevhenii Voievodin 2017-07-17 17:23:57 +03:00
parent 50f3b4508a
commit 0d6c7795fa
39 changed files with 1224 additions and 308 deletions

View File

@ -26,4 +26,12 @@ public class ValidationException extends Exception {
public ValidationException(String message, Throwable cause) {
super(message, cause);
}
/**
* Creates an exception with a formatted message.
* Please follow {@link String#format(String, Object...)} formatting patterns.
*/
public ValidationException(String fmt, Object... args) {
this(String.format(fmt, args));
}
}

View File

@ -10,13 +10,16 @@
*******************************************************************************/
package org.eclipse.che.workspace.infrastructure.docker;
import org.eclipse.che.api.installer.server.exception.InstallerException;
import org.eclipse.che.api.core.model.workspace.config.Environment;
import org.eclipse.che.api.core.model.workspace.config.MachineConfig;
import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity;
import org.eclipse.che.api.installer.server.exception.InstallerException;
import org.eclipse.che.api.workspace.server.spi.InfrastructureException;
import org.eclipse.che.workspace.infrastructure.docker.model.DockerContainerConfig;
import org.eclipse.che.workspace.infrastructure.docker.model.DockerEnvironment;
import javax.inject.Inject;
import java.util.Map;
/**
* Implementation of CHE infrastructure provisioner that adds agent-specific infrastructure to internal environment representation.
@ -41,5 +44,15 @@ public class DefaultInfrastructureProvisioner implements InfrastructureProvision
} catch (InstallerException e) {
throw new InfrastructureException(e.getLocalizedMessage(), e);
}
for (Map.Entry<String, ? extends MachineConfig> entry : envConfig.getMachines().entrySet()) {
String name = entry.getKey();
DockerContainerConfig container = dockerEnvironment.getContainers().get(name);
container.getLabels().putAll(Labels.newSerializer()
.machineName(name)
.runtimeId(runtimeIdentity)
.servers(entry.getValue().getServers())
.labels());
}
}
}

View File

@ -0,0 +1,80 @@
/*******************************************************************************
* 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.docker;
import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity;
import org.eclipse.che.api.workspace.server.spi.InternalInfrastructureException;
import org.eclipse.che.plugin.docker.client.DockerConnector;
import org.eclipse.che.plugin.docker.client.json.ContainerListEntry;
import org.eclipse.che.plugin.docker.client.json.Filters;
import org.eclipse.che.plugin.docker.client.params.ListContainersParams;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.io.IOException;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
/** Facade for operations with docker containers in infrastructure domain context. */
@Singleton
public class DockerContainers {
private final DockerConnector docker;
@Inject
public DockerContainers(DockerConnector docker) {
this.docker = docker;
}
/**
* Lookups all containers owned by runtime with given identity.
*
* @param id
* identity of runtime which owns containers
* @return list of running containers owned by runtime with given id
* @throws InternalInfrastructureException
* when any error occurs during lookup
*/
public List<ContainerListEntry> find(RuntimeIdentity id) throws InternalInfrastructureException {
return listNonStoppedContainers(Labels.newSerializer()
.runtimeId(id)
.labels()
.entrySet()
.stream()
.map(entry -> entry.getKey() + '=' + entry.getValue())
.toArray(String[]::new));
}
/**
* Lookups all runtime identifiers provided by running containers labels.
*
* @return unique runtime identifiers of running containers
* @throws InternalInfrastructureException
* when any error occurs during lookup
*/
public Set<RuntimeIdentity> findIdentities() throws InternalInfrastructureException {
return listNonStoppedContainers(Labels.LABEL_WORKSPACE_ID, Labels.LABEL_WORKSPACE_ID, Labels.LABEL_WORKSPACE_OWNER)
.stream()
.map(e -> Labels.newDeserializer(e.getLabels()).runtimeId())
.collect(Collectors.toSet());
}
private List<ContainerListEntry> listNonStoppedContainers(String... labelFilterValues) throws InternalInfrastructureException {
try {
return docker.listContainers(ListContainersParams.create()
.withAll(false)
.withFilters(Filters.label(labelFilterValues)));
} catch (IOException x) {
throw new InternalInfrastructureException(x.getMessage(), x);
}
}
}

View File

@ -21,6 +21,8 @@ import org.eclipse.che.api.workspace.server.spi.RuntimeInfrastructure;
import org.eclipse.che.plugin.docker.client.DockerRegistryDynamicAuthResolver;
import org.eclipse.che.plugin.docker.client.NoOpDockerRegistryDynamicAuthResolverImpl;
import org.eclipse.che.workspace.infrastructure.docker.bootstrap.DockerBootstrapperFactory;
import org.eclipse.che.workspace.infrastructure.docker.bootstrap.DockerBootstrapper;
import org.eclipse.che.workspace.infrastructure.docker.bootstrap.DockerBootstrapperFactory;
import org.eclipse.che.workspace.infrastructure.docker.config.DockerExtraHostsFromPropertyProvider;
import org.eclipse.che.workspace.infrastructure.docker.config.dns.DnsResolversModule;
import org.eclipse.che.workspace.infrastructure.docker.config.env.ApiEndpointEnvVariableProvider;
@ -106,8 +108,8 @@ public class DockerInfraModule extends AbstractModule {
bind(DockerRegistryDynamicAuthResolver.class).to(NoOpDockerRegistryDynamicAuthResolverImpl.class);
install(new FactoryModuleBuilder().build(DockerRuntimeFactory.class));
install(new FactoryModuleBuilder().build(DockerBootstrapperFactory.class));
install(new FactoryModuleBuilder().build(DockerRuntimeContextFactory.class));
bind(ServerCheckerFactory.class).to(ServerCheckerFactoryImpl.class);
}

View File

@ -11,6 +11,7 @@
package org.eclipse.che.workspace.infrastructure.docker;
import com.google.inject.assistedinject.Assisted;
import com.google.inject.assistedinject.AssistedInject;
import org.eclipse.che.api.core.NotFoundException;
import org.eclipse.che.api.core.model.workspace.runtime.Machine;
@ -29,12 +30,15 @@ import org.eclipse.che.api.workspace.shared.dto.event.MachineStatusEvent;
import org.eclipse.che.api.workspace.shared.dto.event.RuntimeStatusEvent;
import org.eclipse.che.api.workspace.shared.dto.event.ServerStatusEvent;
import org.eclipse.che.dto.server.DtoFactory;
import org.eclipse.che.plugin.docker.client.ProgressMonitor;
import org.eclipse.che.plugin.docker.client.json.ContainerListEntry;
import org.eclipse.che.workspace.infrastructure.docker.bootstrap.DockerBootstrapperFactory;
import org.eclipse.che.workspace.infrastructure.docker.exception.SourceNotFoundException;
import org.eclipse.che.workspace.infrastructure.docker.model.DockerBuildContext;
import org.eclipse.che.workspace.infrastructure.docker.model.DockerContainerConfig;
import org.eclipse.che.workspace.infrastructure.docker.model.DockerEnvironment;
import org.eclipse.che.workspace.infrastructure.docker.monit.AbnormalMachineStopHandler;
import org.eclipse.che.workspace.infrastructure.docker.monit.DockerMachineStopDetector;
import org.eclipse.che.workspace.infrastructure.docker.server.ServerCheckerFactory;
import org.eclipse.che.workspace.infrastructure.docker.server.ServersReadinessChecker;
import org.eclipse.che.workspace.infrastructure.docker.snapshot.MachineSource;
@ -44,7 +48,6 @@ import org.eclipse.che.workspace.infrastructure.docker.snapshot.SnapshotExceptio
import org.eclipse.che.workspace.infrastructure.docker.snapshot.SnapshotImpl;
import org.slf4j.Logger;
import javax.inject.Inject;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
@ -68,53 +71,115 @@ public class DockerInternalRuntime extends InternalRuntime<DockerRuntimeContext>
private final StartSynchronizer startSynchronizer;
private final Map<String, String> properties;
private final Queue<String> startQueue;
private final NetworkLifecycle dockerNetworkLifecycle;
private final String devMachineName;
private final DockerEnvironment dockerEnvironment;
private final NetworkLifecycle networks;
private final DockerMachineStarter containerStarter;
private final SnapshotDao snapshotDao;
private final DockerRegistryClient dockerRegistryClient;
private final RuntimeIdentity identity;
private final EventService eventService;
private final DockerBootstrapperFactory bootstrapperFactory;
private final ServerCheckerFactory serverCheckerFactory;
private final MachineLoggersFactory loggers;
@Inject
/**
* Creates non running runtime.
* Normally created by {@link DockerRuntimeFactory#create(DockerRuntimeContext)}.
*/
@AssistedInject
public DockerInternalRuntime(@Assisted DockerRuntimeContext context,
@Assisted String devMachineName,
@Assisted List<String> orderedContainers,
@Assisted DockerEnvironment dockerEnvironment,
@Assisted RuntimeIdentity identity,
URLRewriter urlRewriter,
NetworkLifecycle dockerNetworkLifecycle,
DockerMachineStarter containerStarter,
NetworkLifecycle networks,
DockerMachineStarter machineStarter,
SnapshotDao snapshotDao,
DockerRegistryClient dockerRegistryClient,
EventService eventService,
DockerBootstrapperFactory bootstrapperFactory,
ServerCheckerFactory serverCheckerFactory) {
super(context, urlRewriter);
this.devMachineName = devMachineName;
this.dockerEnvironment = dockerEnvironment;
this.identity = identity;
ServerCheckerFactory serverCheckerFactory,
MachineLoggersFactory loggers) {
this(context,
urlRewriter,
false, // <- non running
networks,
machineStarter,
snapshotDao,
dockerRegistryClient,
eventService,
bootstrapperFactory,
serverCheckerFactory,
loggers);
}
/**
* Creates a running runtime from the list of given containers.
* Normally created by {@link DockerRuntimeFactory#create(DockerRuntimeContext, List)}.
*/
@AssistedInject
public DockerInternalRuntime(@Assisted DockerRuntimeContext context,
@Assisted List<ContainerListEntry> containers,
URLRewriter urlRewriter,
NetworkLifecycle networks,
DockerMachineStarter machineStarter,
SnapshotDao snapshotDao,
DockerRegistryClient dockerRegistryClient,
EventService eventService,
DockerBootstrapperFactory bootstrapperFactory,
ServerCheckerFactory serverCheckerFactory,
MachineLoggersFactory loggers,
DockerMachineCreator machineCreator,
DockerMachineStopDetector stopDetector) throws InfrastructureException {
this(context,
urlRewriter,
true, // <- running
networks,
machineStarter,
snapshotDao,
dockerRegistryClient,
eventService,
bootstrapperFactory,
serverCheckerFactory,
loggers);
for (ContainerListEntry container : containers) {
DockerMachine machine = machineCreator.create(container);
String name = Labels.newDeserializer(container.getLabels()).machineName();
startSynchronizer.addMachine(name, machine);
stopDetector.startDetection(container.getId(), name, new AbnormalMachineStopHandlerImpl());
streamLogsAsync(name, container.getId());
}
}
private DockerInternalRuntime(DockerRuntimeContext context,
URLRewriter urlRewriter,
boolean running,
NetworkLifecycle networks,
DockerMachineStarter machineStarter,
SnapshotDao snapshotDao,
DockerRegistryClient dockerRegistryClient,
EventService eventService,
DockerBootstrapperFactory bootstrapperFactory,
ServerCheckerFactory serverCheckerFactory,
MachineLoggersFactory loggers) {
super(context, urlRewriter, running);
this.networks = networks;
this.containerStarter = machineStarter;
this.snapshotDao = snapshotDao;
this.dockerRegistryClient = dockerRegistryClient;
this.eventService = eventService;
this.bootstrapperFactory = bootstrapperFactory;
this.serverCheckerFactory = serverCheckerFactory;
this.properties = new HashMap<>();
this.startSynchronizer = new StartSynchronizer();
this.dockerNetworkLifecycle = dockerNetworkLifecycle;
this.containerStarter = containerStarter;
this.snapshotDao = snapshotDao;
this.dockerRegistryClient = dockerRegistryClient;
this.startQueue = new ArrayDeque<>(orderedContainers);
this.loggers = loggers;
}
@Override
protected void internalStart(Map<String, String> startOptions) throws InfrastructureException {
startSynchronizer.setStartThread();
DockerEnvironment dockerEnvironment = getContext().getDockerEnvironment();
Queue<String> startQueue = new ArrayDeque<>(getContext().getOrderedContainers());
try {
dockerNetworkLifecycle.createNetwork(dockerEnvironment.getNetwork());
networks.createNetwork(getContext().getDockerEnvironment().getNetwork());
boolean restore = isRestoreEnabled(startOptions);
@ -178,6 +243,7 @@ public class DockerInternalRuntime extends InternalRuntime<DockerRuntimeContext>
private void restoreMachine(String name, DockerContainerConfig originalConfig)
throws InfrastructureException, InterruptedException {
RuntimeIdentity identity = getContext().getIdentity();
try {
SnapshotImpl snapshot = snapshotDao.getSnapshot(identity.getWorkspaceId(), identity.getEnvName(), name);
startMachine(name, configForSnapshot(snapshot, originalConfig));
@ -192,23 +258,22 @@ public class DockerInternalRuntime extends InternalRuntime<DockerRuntimeContext>
}
}
/** Checks servers availability on all the machines. */
void checkServers() throws InfrastructureException {
for (Map.Entry<String, ? extends DockerMachine> entry : startSynchronizer.getMachines().entrySet()) {
new ServersReadinessChecker(entry.getKey(), entry.getValue().getServers(), serverCheckerFactory).checkOnce();
}
}
private void startMachine(String name, DockerContainerConfig containerConfig)
throws InfrastructureException, InterruptedException {
InternalMachineConfig machineCfg = getContext().getMachineConfigs().get(name);
Map<String, String> labels = new HashMap<>(containerConfig.getLabels());
labels.putAll(Labels.newSerializer()
.machineName(name)
.runtimeId(identity)
.servers(machineCfg.getServers())
.labels());
containerConfig.setLabels(labels);
DockerMachine machine = containerStarter.startContainer(dockerEnvironment.getNetwork(),
DockerMachine machine = containerStarter.startContainer(getContext().getDockerEnvironment().getNetwork(),
name,
containerConfig,
identity,
devMachineName.equals(name),
getContext().getIdentity(),
getContext().getDevMachineName().equals(name),
new AbnormalMachineStopHandlerImpl());
try {
startSynchronizer.addMachine(name, machine);
@ -218,13 +283,12 @@ public class DockerInternalRuntime extends InternalRuntime<DockerRuntimeContext>
destroyMachineQuietly(name, machine);
throw e;
}
bootstrapperFactory.create(name, identity, machineCfg.getInstallers(), machine).bootstrap();
bootstrapperFactory.create(name, getContext().getIdentity(), machineCfg.getInstallers(), machine).bootstrap();
ServersReadinessChecker readinessChecker = new ServersReadinessChecker(name,
machine.getServers(),
new ServerReadinessHandler(name),
serverCheckerFactory);
readinessChecker.startAsync();
readinessChecker.startAsync(new ServerReadinessHandler(name));
readinessChecker.await();
}
@ -238,6 +302,14 @@ public class DockerInternalRuntime extends InternalRuntime<DockerRuntimeContext>
return stopOpts != null && Boolean.parseBoolean(stopOpts.get("create-snapshot"));
}
// TODO stream bootstrapper logs as well
private void streamLogsAsync(String name, String containerId) {
containerStarter.readContainerLogsInSeparateThread(containerId,
getContext().getIdentity().getWorkspaceId(),
name,
loggers.newLogsProcessor(name, getContext().getIdentity()));
}
private class ServerReadinessHandler implements Consumer<String> {
private String machineName;
@ -255,7 +327,7 @@ public class DockerInternalRuntime extends InternalRuntime<DockerRuntimeContext>
machine.setServerStatus(serverRef, ServerStatus.RUNNING);
eventService.publish(DtoFactory.newDto(ServerStatusEvent.class)
.withIdentity(DtoConverter.asDto(identity))
.withIdentity(DtoConverter.asDto(getContext().getIdentity()))
.withMachineName(machineName)
.withServerName(serverRef)
.withStatus(ServerStatus.RUNNING)
@ -307,10 +379,14 @@ public class DockerInternalRuntime extends InternalRuntime<DockerRuntimeContext>
}
for (Map.Entry<String, DockerMachine> entry : machines.entrySet()) {
destroyMachineQuietly(entry.getKey(), entry.getValue());
eventService.publish(DtoFactory.newDto(MachineStatusEvent.class)
.withEventType(MachineStatus.STOPPED)
.withIdentity(DtoConverter.asDto(getContext().getIdentity()))
.withMachineName(entry.getKey()));
sendStoppedEvent(entry.getKey());
}
// TODO what happens when context throws exception here
dockerNetworkLifecycle.destroyNetwork(dockerEnvironment.getNetwork());
networks.destroyNetwork(getContext().getDockerEnvironment().getNetwork());
}
/**
@ -337,6 +413,7 @@ public class DockerInternalRuntime extends InternalRuntime<DockerRuntimeContext>
private void snapshotMachines(Map<String, DockerMachine> machines) {
List<SnapshotImpl> newSnapshots = new ArrayList<>();
final RuntimeIdentity identity = getContext().getIdentity();
String devMachineName = getContext().getDevMachineName();
for (Map.Entry<String, DockerMachine> dockerMachineEntry : machines.entrySet()) {
SnapshotImpl snapshot = SnapshotImpl.builder()
.generateId()
@ -349,7 +426,8 @@ public class DockerInternalRuntime extends InternalRuntime<DockerRuntimeContext>
.useCurrentCreationDate()
.build();
try {
DockerMachineSource machineSource = dockerMachineEntry.getValue().saveToSnapshot();
ProgressMonitor monitor = loggers.newProgressMonitor(dockerMachineEntry.getKey(), identity);
DockerMachineSource machineSource = dockerMachineEntry.getValue().saveToSnapshot(monitor);
snapshot.setMachineSource(new MachineSourceImpl(machineSource));
newSnapshots.add(snapshot);
} catch (SnapshotException e) {
@ -401,7 +479,7 @@ public class DockerInternalRuntime extends InternalRuntime<DockerRuntimeContext>
LOG.error(e.getLocalizedMessage(), e);
} finally {
eventService.publish(DtoFactory.newDto(RuntimeStatusEvent.class)
.withIdentity(DtoConverter.asDto(identity))
.withIdentity(DtoConverter.asDto(getContext().getIdentity()))
.withStatus("STOPPED")
.withPrevStatus("RUNNING")
.withFailed(true)
@ -412,21 +490,21 @@ public class DockerInternalRuntime extends InternalRuntime<DockerRuntimeContext>
private void sendStartingEvent(String machineName) {
eventService.publish(DtoFactory.newDto(MachineStatusEvent.class)
.withIdentity(DtoConverter.asDto(identity))
.withIdentity(DtoConverter.asDto(getContext().getIdentity()))
.withEventType(MachineStatus.STARTING)
.withMachineName(machineName));
}
private void sendRunningEvent(String machineName) {
eventService.publish(DtoFactory.newDto(MachineStatusEvent.class)
.withIdentity(DtoConverter.asDto(identity))
.withIdentity(DtoConverter.asDto(getContext().getIdentity()))
.withEventType(MachineStatus.RUNNING)
.withMachineName(machineName));
}
private void sendFailedEvent(String machineName, String message) {
eventService.publish(DtoFactory.newDto(MachineStatusEvent.class)
.withIdentity(DtoConverter.asDto(identity))
.withIdentity(DtoConverter.asDto(getContext().getIdentity()))
.withEventType(MachineStatus.FAILED)
.withMachineName(machineName)
.withError(message));
@ -435,7 +513,7 @@ public class DockerInternalRuntime extends InternalRuntime<DockerRuntimeContext>
private void sendStoppedEvent(String machineName) {
eventService.publish(DtoFactory.newDto(MachineStatusEvent.class)
.withEventType(MachineStatus.STOPPED)
.withIdentity(DtoConverter.asDto(identity))
.withIdentity(DtoConverter.asDto(getContext().getIdentity()))
.withMachineName(machineName));
}

View File

@ -10,8 +10,6 @@
*******************************************************************************/
package org.eclipse.che.workspace.infrastructure.docker;
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.ServerStatus;
import org.eclipse.che.api.workspace.server.model.impl.ServerImpl;
@ -23,7 +21,6 @@ import org.eclipse.che.plugin.docker.client.Exec;
import org.eclipse.che.plugin.docker.client.LogMessage;
import org.eclipse.che.plugin.docker.client.MessageProcessor;
import org.eclipse.che.plugin.docker.client.ProgressMonitor;
import org.eclipse.che.plugin.docker.client.json.ContainerInfo;
import org.eclipse.che.plugin.docker.client.params.CommitParams;
import org.eclipse.che.plugin.docker.client.params.CreateExecParams;
import org.eclipse.che.plugin.docker.client.params.PushParams;
@ -33,11 +30,8 @@ import org.eclipse.che.plugin.docker.client.params.RemoveImageParams;
import org.eclipse.che.plugin.docker.client.params.StartExecParams;
import org.eclipse.che.workspace.infrastructure.docker.monit.DockerMachineStopDetector;
import org.eclipse.che.workspace.infrastructure.docker.snapshot.SnapshotException;
import org.eclipse.che.workspace.infrastructure.docker.strategy.ServerEvaluationStrategy;
import org.eclipse.che.workspace.infrastructure.docker.strategy.ServerEvaluationStrategyProvider;
import org.slf4j.Logger;
import javax.inject.Inject;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
@ -57,11 +51,11 @@ public class DockerMachine implements Machine {
/**
* Name of the latest tag used in Docker image.
*/
public static final String LATEST_TAG = "latest";
public static final String LATEST_TAG = "latest";
/**
* Env variable that points to root folder of projects in dev machine
*/
public static final String PROJECTS_ROOT_VARIABLE = "CHE_PROJECTS_ROOT";
public static final String PROJECTS_ROOT_VARIABLE = "CHE_PROJECTS_ROOT";
/**
* Env variable for jvm settings
@ -89,43 +83,31 @@ public class DockerMachine implements Machine {
*/
public static final String USER_TOKEN = "USER_TOKEN";
private final String container;
private final DockerConnector docker;
private final String image;
private final DockerMachineStopDetector dockerMachineStopDetector;
private final String registry;
private final String registryNamespace;
private final boolean snapshotUseRegistry;
private final ContainerInfo info;
private final ServerEvaluationStrategyProvider provider;
private final ProgressMonitor progressMonitor;
private final String container;
private final DockerConnector docker;
private final String image;
private final DockerMachineStopDetector dockerMachineStopDetector;
private final String registry;
private final String registryNamespace;
private final boolean snapshotUseRegistry;
private final Map<String, ServerImpl> servers;
private Map<String, ServerImpl> servers;
@Inject
public DockerMachine(DockerConnector docker,
public DockerMachine(String containerId,
String image,
DockerConnector docker,
Map<String, ServerImpl> servers,
String registry,
String registryNamespace,
boolean snapshotUseRegistry,
@Assisted("container") String container,
@Assisted("image") String image,
ServerEvaluationStrategyProvider provider,
DockerMachineStopDetector dockerMachineStopDetector,
ProgressMonitor progressMonitor) throws InfrastructureException {
this.container = container;
String registryNamespace,
DockerMachineStopDetector dockerMachineStopDetector) {
this.container = containerId;
this.docker = docker;
this.image = image;
this.registry = registry;
this.registryNamespace = registryNamespace;
this.snapshotUseRegistry = snapshotUseRegistry;
this.dockerMachineStopDetector = dockerMachineStopDetector;
this.progressMonitor = progressMonitor;
try {
this.info = docker.inspectContainer(container);
} catch (IOException e) {
throw new InfrastructureException(e.getLocalizedMessage(), e);
}
this.provider = provider;
this.servers = servers;
}
@Override
@ -135,10 +117,6 @@ public class DockerMachine implements Machine {
@Override
public Map<String, ServerImpl> getServers() {
if(servers == null) {
ServerEvaluationStrategy strategy = provider.get();
servers = strategy.getServers(info, "localhost", Collections.emptyMap());
}
return servers;
}
@ -203,17 +181,16 @@ public class DockerMachine implements Machine {
", image='" + image + '\'' +
", registry='" + registry + '\'' +
", registryNamespace='" + registryNamespace + '\'' +
", snapshotUseRegistry='" + snapshotUseRegistry +
", info=" + info +
", provider=" + provider +
", snapshotUseRegistry='" + snapshotUseRegistry +
", container=" + container +
'}';
}
public DockerMachineSource saveToSnapshot() throws SnapshotException {
public DockerMachineSource saveToSnapshot(ProgressMonitor progressMonitor) throws SnapshotException {
try {
String image = generateRepository();
if(!snapshotUseRegistry) {
if (!snapshotUseRegistry) {
commitContainer(image, LATEST_TAG);
return new DockerMachineSource(image).withTag(LATEST_TAG);
}

View File

@ -0,0 +1,76 @@
/*******************************************************************************
* 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.docker;
import org.eclipse.che.api.core.model.workspace.config.ServerConfig;
import org.eclipse.che.api.workspace.server.spi.InfrastructureException;
import org.eclipse.che.commons.annotation.Nullable;
import org.eclipse.che.plugin.docker.client.DockerConnector;
import org.eclipse.che.plugin.docker.client.json.ContainerInfo;
import org.eclipse.che.plugin.docker.client.json.ContainerListEntry;
import org.eclipse.che.plugin.docker.client.json.NetworkSettings;
import org.eclipse.che.workspace.infrastructure.docker.monit.DockerMachineStopDetector;
import org.eclipse.che.workspace.infrastructure.docker.strategy.ServersMapper;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import java.io.IOException;
import java.util.Map;
/** Helps to create {@link DockerMachine} instances. */
@Singleton
public class DockerMachineCreator {
private final DockerConnector docker;
private final String registry;
private final boolean snapshotUseRegistry;
private final String registryNamespace;
private final DockerMachineStopDetector dockerMachineStopDetector;
@Inject
public DockerMachineCreator(DockerConnector docker,
@Named("che.docker.registry") String registry,
@Named("che.docker.registry_for_snapshots") boolean snapshotUseRegistry,
@Named("che.docker.namespace") @Nullable String registryNamespace,
DockerMachineStopDetector dockerMachineStopDetector) {
this.docker = docker;
this.registry = registry;
this.snapshotUseRegistry = snapshotUseRegistry;
this.registryNamespace = registryNamespace;
this.dockerMachineStopDetector = dockerMachineStopDetector;
}
/** Creates new docker machine instance from the short container description. */
public DockerMachine create(ContainerListEntry container) throws InfrastructureException {
try {
return create(docker.inspectContainer(container.getId()));
} catch (IOException x) {
throw new InfrastructureException(x.getMessage(), x);
}
}
/** Creates new docker machine instance from the full container description. */
public DockerMachine create(ContainerInfo container) {
NetworkSettings networkSettings = container.getNetworkSettings();
String hostname = networkSettings.getGateway();
Map<String, ServerConfig> configs = Labels.newDeserializer(container.getConfig().getLabels()).servers();
return new DockerMachine(container.getId(),
container.getImage(),
docker,
new ServersMapper(hostname).map(networkSettings.getPorts(), configs),
registry,
snapshotUseRegistry,
registryNamespace,
dockerMachineStopDetector);
}
}

View File

@ -58,7 +58,6 @@ import org.eclipse.che.workspace.infrastructure.docker.exception.SourceNotFoundE
import org.eclipse.che.workspace.infrastructure.docker.model.DockerContainerConfig;
import org.eclipse.che.workspace.infrastructure.docker.monit.AbnormalMachineStopHandler;
import org.eclipse.che.workspace.infrastructure.docker.monit.DockerMachineStopDetector;
import org.eclipse.che.workspace.infrastructure.docker.strategy.ServerEvaluationStrategyProvider;
import org.slf4j.Logger;
import javax.inject.Inject;
@ -91,6 +90,40 @@ import static org.eclipse.che.workspace.infrastructure.docker.DockerMachine.LATE
import static org.eclipse.che.workspace.infrastructure.docker.DockerMachine.USER_TOKEN;
import static org.slf4j.LoggerFactory.getLogger;
/*
TODO:
1. Decompose this component on:
- DockerNetworks(NetworkLifecycle <-) - deals with docker networks.
- DockerImages - deals with docker images.
- DockerContainers - deals with docker containers.
Then DockerRuntimeContext may use these components to run consistent
and clear flow of calls to achieve its needs, e.g.:
DockerNetworks networks;
DockerImages images;
DockerContainer containers;
start runtime {
networks.create(environment.getNetwork())
for (config: environment.getConfigs()) {
String image
if (isBuildable(config)) {
image = images.build(config)
} else {
image = images.pull(config)
}
String id = containers.create(image)
Container container = containers.start(id);
DockerMachine machine = machineCreator.create(container);
...
}
}
2. Move the logic related to containers configuration modification to
InfrastructureProvisioner implementation.
*/
/**
* @author Alexander Garagatyi
*/
@ -115,7 +148,7 @@ public class DockerMachineStarter {
singletonList("sh"),
Arrays.asList("/bin/sh", "-c", "/bin/sh"),
Arrays.asList("/bin/sh", "-c",
"/bin/bash"),
"/bin/bash"),
Arrays.asList("/bin/sh", "-c", "bash"),
Arrays.asList("/bin/sh", "-c", "sh"));
@ -146,15 +179,13 @@ public class DockerMachineStarter {
private final Set<String> additionalNetworks;
private final String parentCgroup;
private final String cpusetCpus;
private final String registry;
private final String registryNamespace;
private final long cpuPeriod;
private final long cpuQuota;
private final WindowsPathEscaper windowsPathEscaper;
private final String[] dnsResolvers;
private ServerEvaluationStrategyProvider serverEvaluationStrategyProvider;
private final Map<String, String> buildArgs;
private final MachineLoggersFactory machineLoggerFactory;
private final DockerMachineCreator machineCreator;
@Inject
public DockerMachineStarter(DockerConnector docker,
@ -167,8 +198,6 @@ public class DockerMachineStarter {
@Named("che.docker.always_pull_image") boolean doForcePullImage,
@Named("che.docker.privileged") boolean privilegedMode,
@Named("che.docker.pids_limit") int pidsLimit,
@Named("che.docker.registry") String registry,
@Named("che.docker.namespace") @Nullable String registryNamespace,
@Named("machine.docker.dev_machine.machine_env") Set<String> devMachineEnvVariables,
@Named("machine.docker.machine_env") Set<String> allMachinesEnvVariables,
@Named("che.docker.registry_for_snapshots") boolean snapshotUseRegistry,
@ -181,9 +210,10 @@ public class DockerMachineStarter {
WindowsPathEscaper windowsPathEscaper,
@Named("che.docker.extra_hosts") Set<Set<String>> additionalHosts,
@Nullable @Named("che.docker.dns_resolvers") String[] dnsResolvers,
ServerEvaluationStrategyProvider serverEvaluationStrategyProvider,
@Named("che.docker.build_args") Map<String, String> buildArgs,
MachineLoggersFactory machineLogger) {
MachineLoggersFactory machineLogger,
DockerMachineCreator machineCreator) {
this.machineCreator = machineCreator;
// TODO spi should we move all configuration stuff into infrastructure provisioner and left logic of container start here only
this.docker = docker;
this.dockerCredentials = dockerCredentials;
@ -205,12 +235,9 @@ public class DockerMachineStarter {
this.cpuPeriod = cpuPeriod;
this.cpuQuota = cpuQuota;
this.windowsPathEscaper = windowsPathEscaper;
this.registryNamespace = registryNamespace;
this.registry = registry;
this.pidsLimit = pidsLimit;
this.dnsResolvers = dnsResolvers;
this.buildArgs = buildArgs;
this.serverEvaluationStrategyProvider = serverEvaluationStrategyProvider;
this.machineLoggerFactory = machineLogger;
allMachinesSystemVolumes = removeEmptyAndNullValues(allMachinesSystemVolumes);
@ -327,7 +354,7 @@ public class DockerMachineStarter {
docker.startContainer(StartContainerParams.create(container));
checkContainerIsRunning(container);
ContainerInfo runningContainer = getRunningContainer(container);
readContainerLogsInSeparateThread(container,
workspaceId,
@ -336,15 +363,7 @@ public class DockerMachineStarter {
dockerInstanceStopDetector.startDetection(container, machineName, abnormalMachineStopHandler);
return new DockerMachine(docker,
registry,
registryNamespace,
snapshotUseRegistry,
container,
image,
serverEvaluationStrategyProvider,
dockerInstanceStopDetector,
progressMonitor);
return machineCreator.create(runningContainer);
} catch (RuntimeException | IOException | InfrastructureException e) {
cleanUpContainer(container);
if (e instanceof InfrastructureException) {
@ -650,11 +669,12 @@ public class DockerMachineStarter {
// Inspect container right after start to check if it is running,
// otherwise throw error that command should not exit right after container start
protected void checkContainerIsRunning(String container) throws IOException, InfrastructureException {
protected ContainerInfo getRunningContainer(String container) throws IOException, InfrastructureException {
ContainerInfo containerInfo = docker.inspectContainer(container);
if ("exited".equals(containerInfo.getState().getStatus())) {
throw new InfrastructureException(CONTAINER_EXITED_ERROR);
}
return containerInfo;
}
@VisibleForTesting

View File

@ -10,7 +10,9 @@
*******************************************************************************/
package org.eclipse.che.workspace.infrastructure.docker;
import com.google.common.collect.ImmutableList;
import com.google.inject.assistedinject.Assisted;
import com.google.inject.assistedinject.AssistedInject;
import org.eclipse.che.api.core.ValidationException;
import org.eclipse.che.api.core.model.workspace.config.Environment;
@ -18,14 +20,19 @@ 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.shared.Utils;
import org.eclipse.che.plugin.docker.client.json.ContainerListEntry;
import org.eclipse.che.workspace.infrastructure.docker.environment.ContainersStartStrategy;
import org.eclipse.che.workspace.infrastructure.docker.model.DockerEnvironment;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Named;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriBuilderException;
import java.net.URI;
import java.util.Collections;
import java.util.List;
import static org.eclipse.che.api.workspace.server.OutputEndpoint.OUTPUT_WEBSOCKET_ENDPOINT_BASE;
@ -34,31 +41,53 @@ import static org.eclipse.che.api.workspace.server.OutputEndpoint.OUTPUT_WEBSOCK
* Docker specific implementation of {@link RuntimeContext}.
*
* @author Alexander Garagatyi
* @author Yevhenii Voievodin
*/
public class DockerRuntimeContext extends RuntimeContext {
private final DockerEnvironment dockerEnvironment;
private final DockerRuntimeFactory dockerRuntimeFactory;
private final List<String> orderedContainers;
private final String devMachineName;
private final String websocketEndpointBase;
private static final Logger LOG = LoggerFactory.getLogger(DockerRuntimeContext.class);
private final DockerEnvironment dockerEnvironment;
private final List<String> orderedContainers;
private final String devMachineName;
private final String websocketEndpointBase;
private final DockerRuntimeFactory runtimeFactory;
private final DockerContainers containers;
private final RuntimeConsistencyChecker consistencyChecker;
private final DockerSharedPool sharedPool;
@AssistedInject
public DockerRuntimeContext(@Assisted DockerRuntimeInfrastructure infrastructure,
@Assisted RuntimeIdentity identity,
@Assisted Environment environment,
@Assisted DockerEnvironment dockerEnvironment,
@Assisted List<String> orderedContainers,
@Assisted DockerEnvironment dockerEnv,
InstallerRegistry installerRegistry,
DockerRuntimeFactory dockerRuntimeFactory,
String websocketEndpointBase)
throws ValidationException, InfrastructureException {
ContainersStartStrategy startStrategy,
DockerRuntimeFactory runtimeFactory,
DockerContainers containers,
DockerSharedPool sharedPool,
RuntimeConsistencyChecker consistencyChecker,
@Named("che.websocket.endpoint.base") String websocketEndpointBase) throws InfrastructureException, ValidationException {
super(environment, identity, infrastructure, installerRegistry);
this.devMachineName = Utils.getDevMachineName(environment);
this.orderedContainers = orderedContainers;
this.dockerEnvironment = dockerEnvironment;
this.dockerRuntimeFactory = dockerRuntimeFactory;
this.dockerEnvironment = dockerEnv;
this.orderedContainers = ImmutableList.copyOf(startStrategy.order(dockerEnvironment));
this.websocketEndpointBase = websocketEndpointBase;
this.runtimeFactory = runtimeFactory;
this.containers = containers;
this.sharedPool = sharedPool;
this.consistencyChecker = consistencyChecker;
}
/** Returns docker environment which based on normalized context environment configuration. */
public DockerEnvironment getDockerEnvironment() { return dockerEnvironment;}
/** Returns the name of the dev machine. */
public String getDevMachineName() { return devMachineName; }
/** Returns the list of the ordered containers, machines must be started in the same order. */
public List<String> getOrderedContainers() { return orderedContainers; }
@Override
public URI getOutputChannel() throws InfrastructureException {
try {
@ -72,11 +101,35 @@ public class DockerRuntimeContext extends RuntimeContext {
}
@Override
public InternalRuntime getRuntime() {
return dockerRuntimeFactory.createRuntime(this,
devMachineName,
orderedContainers,
dockerEnvironment,
identity);
public DockerInternalRuntime getRuntime() throws InfrastructureException {
List<ContainerListEntry> runningContainers = containers.find(identity);
if (runningContainers.isEmpty()) {
return runtimeFactory.create(this);
}
DockerInternalRuntime runtime = runtimeFactory.create(this, runningContainers);
try {
consistencyChecker.check(environment, runtime);
runtime.checkServers();
} catch (InfrastructureException | ValidationException x) {
LOG.warn("Runtime '{}:{}' will be stopped as it is not consistent with its configuration. " +
"The problem: {}",
identity.getWorkspaceId(),
identity.getEnvName(),
x.getMessage());
stopAsync(runtime);
throw new InfrastructureException(x.getMessage(), x);
}
return runtime;
}
private void stopAsync(DockerInternalRuntime runtime) {
sharedPool.execute(() -> {
try {
runtime.stop(Collections.emptyMap());
} catch (Exception x) {
LOG.error("Couldn't stop workspace runtime due to error: {}", x.getMessage());
}
});
}
}

View File

@ -0,0 +1,25 @@
/*******************************************************************************
* 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.docker;
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.workspace.server.spi.InfrastructureException;
import org.eclipse.che.workspace.infrastructure.docker.model.DockerEnvironment;
/** Helps to create {@link DockerRuntimeContext} instances. */
public interface DockerRuntimeContextFactory {
DockerRuntimeContext create(DockerRuntimeInfrastructure infra,
RuntimeIdentity identity,
Environment environment,
DockerEnvironment dockerEnv) throws InfrastructureException, ValidationException;
}

View File

@ -10,10 +10,7 @@
*******************************************************************************/
package org.eclipse.che.workspace.infrastructure.docker;
import com.google.inject.assistedinject.Assisted;
import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity;
import org.eclipse.che.workspace.infrastructure.docker.model.DockerEnvironment;
import org.eclipse.che.plugin.docker.client.json.ContainerListEntry;
import java.util.List;
@ -21,9 +18,6 @@ import java.util.List;
* @author Alexander Garagatyi
*/
public interface DockerRuntimeFactory {
DockerInternalRuntime createRuntime(@Assisted DockerRuntimeContext context,
@Assisted String devMachineName,
@Assisted List<String> orderedContainers,
@Assisted DockerEnvironment dockerEnvironment,
@Assisted RuntimeIdentity identity);
DockerInternalRuntime create(DockerRuntimeContext context);
DockerInternalRuntime create(DockerRuntimeContext context, List<ContainerListEntry> containers);
}

View File

@ -16,7 +16,6 @@ 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.installer.server.InstallerRegistry;
import org.eclipse.che.api.workspace.server.model.impl.EnvironmentImpl;
import org.eclipse.che.api.workspace.server.spi.InfrastructureException;
import org.eclipse.che.api.workspace.server.spi.RuntimeInfrastructure;
@ -27,9 +26,8 @@ import org.eclipse.che.workspace.infrastructure.docker.environment.EnvironmentPa
import org.eclipse.che.workspace.infrastructure.docker.environment.EnvironmentValidator;
import org.eclipse.che.workspace.infrastructure.docker.model.DockerEnvironment;
import javax.inject.Named;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Implementation of {@link RuntimeInfrastructure} that
@ -38,14 +36,13 @@ import java.util.Map;
* @author Alexander Garagatyi
*/
public class DockerRuntimeInfrastructure extends RuntimeInfrastructure {
private final EnvironmentValidator dockerEnvironmentValidator;
private final EnvironmentParser dockerEnvironmentParser;
private final ContainersStartStrategy startStrategy;
private final InfrastructureProvisioner infrastructureProvisioner;
private final EnvironmentNormalizer environmentNormalizer;
private final DockerRuntimeFactory runtimeFactory;
private final InstallerRegistry installerRegistry;
private final String websocketEndpointBase;
private final EnvironmentValidator dockerEnvironmentValidator;
private final EnvironmentParser dockerEnvironmentParser;
private final ContainersStartStrategy startStrategy;
private final InfrastructureProvisioner infrastructureProvisioner;
private final EnvironmentNormalizer environmentNormalizer;
private final DockerRuntimeContextFactory contextFactory;
private final DockerContainers containers;
@Inject
public DockerRuntimeInfrastructure(EnvironmentParser dockerEnvironmentParser,
@ -54,19 +51,17 @@ public class DockerRuntimeInfrastructure extends RuntimeInfrastructure {
InfrastructureProvisioner infrastructureProvisioner,
EnvironmentNormalizer environmentNormalizer,
Map<String, DockerConfigSourceSpecificEnvironmentParser> environmentParsers,
DockerRuntimeFactory runtimeFactory,
InstallerRegistry installerRegistry,
EventService eventService,
@Named("che.websocket.endpoint.base") String websocketEndpointBase) {
DockerRuntimeContextFactory contextFactory,
DockerContainers containers) {
super("docker", environmentParsers.keySet(), eventService);
this.dockerEnvironmentValidator = dockerEnvironmentValidator;
this.dockerEnvironmentParser = dockerEnvironmentParser;
this.startStrategy = startStrategy;
this.infrastructureProvisioner = infrastructureProvisioner;
this.environmentNormalizer = environmentNormalizer;
this.runtimeFactory = runtimeFactory;
this.installerRegistry = installerRegistry;
this.websocketEndpointBase = websocketEndpointBase;
this.contextFactory = contextFactory;
this.containers = containers;
}
@Override
@ -92,21 +87,17 @@ public class DockerRuntimeInfrastructure extends RuntimeInfrastructure {
EnvironmentImpl environment = new EnvironmentImpl(originEnv);
DockerEnvironment dockerEnvironment = dockerEnvironmentParser.parse(environment);
dockerEnvironmentValidator.validate(environment, dockerEnvironment);
// check that containers start order can be resolved
List<String> orderedContainers = startStrategy.order(dockerEnvironment);
// modify environment with everything needed to use docker machines on particular (cloud) infrastructure
infrastructureProvisioner.provision(environment, dockerEnvironment, identity);
// normalize env to provide environment description with absolutely everything expected in
environmentNormalizer.normalize(environment, dockerEnvironment, identity);
return new DockerRuntimeContext(this,
identity,
environment,
dockerEnvironment,
orderedContainers,
installerRegistry,
runtimeFactory,
websocketEndpointBase);
return contextFactory.create(this, identity, environment, dockerEnvironment);
}
@Override
public Set<RuntimeIdentity> getIdentities() throws InfrastructureException {
return containers.findIdentities();
}
}

View File

@ -0,0 +1,60 @@
/*******************************************************************************
* 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.docker;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import org.eclipse.che.commons.lang.concurrent.LoggingUncaughtExceptionHandler;
import org.eclipse.che.commons.lang.concurrent.ThreadLocalPropagateContext;
import org.slf4j.LoggerFactory;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.inject.Singleton;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
/**
* Provides a single non-daemon {@link ExecutorService}
* instance for docker infrastructure components.
*/
@Singleton
public class DockerSharedPool {
private final ExecutorService executor =
Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors(), // <- experimental value
new ThreadFactoryBuilder().setNameFormat("DockerSharedPool-%d")
.setUncaughtExceptionHandler(LoggingUncaughtExceptionHandler.getInstance())
.setDaemon(false)
.build());
/**
* Delegates call to {@link ExecutorService#execute(Runnable)}
* and propagates thread locals to it like defined by {@link ThreadLocalPropagateContext}.
*/
public void execute(Runnable runnable) {
executor.execute(ThreadLocalPropagateContext.wrap(runnable));
}
@PreDestroy
private void terminate() throws InterruptedException {
if (!executor.isShutdown()) {
executor.shutdown();
if (!executor.awaitTermination(30, TimeUnit.SECONDS)) {
executor.shutdownNow();
if (!executor.awaitTermination(30, TimeUnit.SECONDS)) {
LoggerFactory.getLogger(DockerSharedPool.class).error("Couldn't terminate docker infrastructure thread pool");
}
}
}
}
}

View File

@ -105,18 +105,9 @@ public class InstallerConfigApplier {
}
private void addLabels(DockerContainerConfig container, Map<String, ? extends ServerConfig> servers) {
for (Map.Entry<String, ? extends ServerConfig> entry : servers.entrySet()) {
String ref = entry.getKey();
ServerConfig conf = entry.getValue();
container.getLabels().put("che:server:" + conf.getPort() + ":protocol", conf.getProtocol());
container.getLabels().put("che:server:" + conf.getPort() + ":ref", ref);
String path = conf.getPath();
if (!isNullOrEmpty(path)) {
container.getLabels().put("che:server:" + conf.getPort() + ":path", path);
}
}
container.getLabels().putAll(Labels.newSerializer()
.servers(servers)
.labels());
}
private void addEnv(DockerContainerConfig container, Map<String, String> properties) {

View File

@ -30,11 +30,12 @@ import java.util.regex.Pattern;
*/
public final class Labels {
private static final String LABEL_PREFIX = "org.eclipse.che.";
public static final String LABEL_PREFIX = "org.eclipse.che.";
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";
private static final String LABEL_MACHINE_NAME = LABEL_PREFIX + "machine.name";
private static final String LABEL_WORKSPACE_ID = LABEL_PREFIX + "workspace.id";
private static final String LABEL_WORKSPACE_ENV = LABEL_PREFIX + "workspace.env";
private static final String LABEL_WORKSPACE_OWNER = LABEL_PREFIX + "workspace.owner";
private static final String SERVER_PORT_LABEL_FMT = LABEL_PREFIX + "server.%s.port";
private static final String SERVER_PROTOCOL_LABEL_FMT = LABEL_PREFIX + "server.%s.protocol";
private static final String SERVER_PATH_LABEL_FMT = LABEL_PREFIX + "server.%s.path";

View File

@ -0,0 +1,39 @@
/*******************************************************************************
* 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.docker;
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.MachineConfig;
import org.eclipse.che.api.core.model.workspace.runtime.Machine;
import javax.inject.Singleton;
import java.util.Map;
/** Checks whether runtime is consistent with its configuration. */
@Singleton
class RuntimeConsistencyChecker {
void check(Environment environment, DockerInternalRuntime runtime) throws ValidationException {
Map<String, ? extends MachineConfig> configs = environment.getMachines();
Map<String, ? extends Machine> machines = runtime.getMachines();
if (configs.size() != machines.size()) {
throw new ValidationException("Runtime has '%d' machines while configuration defines '%d'." +
"Runtime machines: %s. Configuration machines: %s",
machines.size(), configs.size(),
machines.keySet(), configs.keySet());
}
if (!configs.keySet().containsAll(machines.keySet())) {
throw new ValidationException("Runtime has different set of machines than defined by configuration. " +
"Runtime machines: %s. Configuration machines: %s",
machines.keySet(), configs.keySet());
}
}
}

View File

@ -53,7 +53,7 @@ public class EnvironmentNormalizer {
public void normalize(Environment environment, DockerEnvironment dockerEnvironment, RuntimeIdentity identity)
throws InfrastructureException {
String networkId = NameGenerator.generate(identity.getWorkspaceId() + "_", 16);
String networkId = identity.getWorkspaceId() + "_" + identity.getEnvName();
dockerEnvironment.setNetwork(networkId);
Map<String, DockerContainerConfig> containers = dockerEnvironment.getContainers();

View File

@ -20,6 +20,7 @@ import org.eclipse.che.api.core.ServerException;
import org.eclipse.che.api.core.model.workspace.Workspace;
import org.eclipse.che.api.core.util.SystemInfo;
import org.eclipse.che.api.workspace.server.WorkspaceManager;
import org.eclipse.che.api.workspace.server.spi.WorkspaceDao;
import org.eclipse.che.workspace.infrastructure.docker.WindowsHostUtils;
import javax.annotation.PostConstruct;
@ -44,7 +45,7 @@ public class LocalWorkspaceFolderPathProvider implements WorkspaceFolderPathProv
public static final String ALLOW_FOLDERS_CREATION_ENV_VARIABLE = "CHE_WORKSPACE_STORAGE_CREATE_FOLDERS";
public static final String WORKSPACE_STORAGE_PATH_ENV_VARIABLE = "CHE_WORKSPACE_STORAGE";
private final Provider<WorkspaceManager> workspaceManager;
private final WorkspaceDao workspaceDao;
private final boolean isWindows;
/**
@ -81,9 +82,9 @@ public class LocalWorkspaceFolderPathProvider implements WorkspaceFolderPathProv
@Inject
public LocalWorkspaceFolderPathProvider(@Named("che.workspace.storage") String workspacesMountPoint,
Provider<WorkspaceManager> workspaceManager) throws IOException {
WorkspaceDao workspaceDao) throws IOException {
this.workspacesMountPoint = workspacesMountPoint;
this.workspaceManager = workspaceManager;
this.workspaceDao = workspaceDao;
this.isWindows = SystemInfo.isWindows();
}
@ -91,10 +92,10 @@ public class LocalWorkspaceFolderPathProvider implements WorkspaceFolderPathProv
protected LocalWorkspaceFolderPathProvider(String workspacesMountPoint,
String oldWorkspacesMountPoint,
String projectsFolder,
Provider<WorkspaceManager> workspaceManager,
boolean createFolders,
WorkspaceDao workspaceDao,
boolean isWindows) throws IOException {
this.workspaceManager = workspaceManager;
this.workspaceDao = workspaceDao;
this.workspacesMountPoint = workspacesMountPoint;
this.hostProjectsFolder = projectsFolder;
this.createFolders = createFolders;
@ -108,8 +109,7 @@ public class LocalWorkspaceFolderPathProvider implements WorkspaceFolderPathProv
return hostProjectsFolder;
}
try {
WorkspaceManager workspaceManager = this.workspaceManager.get();
Workspace workspace = workspaceManager.getWorkspace(workspaceId);
Workspace workspace = workspaceDao.get(workspaceId);
String wsName = workspace.getConfig().getName();
return doGetPathByName(wsName);
} catch (NotFoundException | ServerException e) {

View File

@ -74,6 +74,16 @@ public abstract class ServerChecker {
timer.schedule(new ServerCheckingTask(), 0);
}
/**
* Checks server availability, throws {@link InfrastructureException}
* if the server is not available.
*/
public void checkOnce() throws InfrastructureException {
if (!isAvailable()) {
throw new InfrastructureException(String.format("Server '%s' in machine '%s' not available.", serverRef, machineName));
}
}
/**
* Shows whether the server is treated as available.
*

View File

@ -42,10 +42,9 @@ public class ServersReadinessChecker {
"terminal", "/");
private final String machineName;
private final Map<String, ServerImpl> servers;
private final Consumer<String> serverReadinessHandler;
private final ServerCheckerFactory serverCheckerFactory;
private final Timer timer;
private Timer timer;
private long resultTimeoutSeconds;
private CompletableFuture result;
@ -56,16 +55,12 @@ public class ServersReadinessChecker {
* name of machine whose servers will be checked by this method
* @param servers
* map of servers in a machine
* @param serverReadinessHandler
* consumer which will be called with server reference as the argument when server become available
*/
public ServersReadinessChecker(String machineName,
Map<String, ServerImpl> servers,
Consumer<String> serverReadinessHandler,
ServerCheckerFactory serverCheckerFactory) {
this.machineName = machineName;
this.servers = servers;
this.serverReadinessHandler = serverReadinessHandler;
this.serverCheckerFactory = serverCheckerFactory;
this.timer = new Timer("ServerReadinessChecker", true);
}
@ -73,12 +68,15 @@ public class ServersReadinessChecker {
/**
* Asynchronously starts checking readiness of servers of a machine.
*
* @param serverReadinessHandler
* consumer which will be called with server reference as the argument when server become available
* @throws InternalInfrastructureException
* if check of a server failed due to an unexpected error
* @throws InfrastructureException
* if check of a server failed due to an error
*/
public void startAsync() throws InfrastructureException {
public void startAsync(Consumer<String> serverReadinessHandler) throws InfrastructureException {
timer = new Timer("ServerReadinessChecker", true);
List<ServerChecker> serverCheckers = getServerCheckers();
// should be completed with an exception if a server considered unavailable
CompletableFuture<Void> firstNonAvailable = new CompletableFuture<>();
@ -103,6 +101,16 @@ public class ServersReadinessChecker {
}
}
/**
* Synchronously checks whether servers are available,
* throws {@link InfrastructureException} if any is not.
*/
public void checkOnce() throws InfrastructureException {
for (ServerChecker checker : getServerCheckers()) {
checker.checkOnce();
}
}
/**
* Waits until servers are considered available or one of them is considered as unavailable.
*

View File

@ -54,8 +54,9 @@ public class ServersMapper {
Map<String, List<String>> port2refs = new HashMap<>();
for (Map.Entry<String, ServerConfig> entry : configs.entrySet()) {
port2refs.compute(entry.getValue().getPort(), (port, list) -> {
if (list == null)
if (list == null) {
list = new ArrayList<>();
}
list.add(entry.getKey());
return list;
});

View File

@ -0,0 +1,113 @@
/*******************************************************************************
* 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.docker;
import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity;
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.RuntimeIdentityImpl;
import org.eclipse.che.plugin.docker.client.DockerConnector;
import org.eclipse.che.plugin.docker.client.json.ContainerConfig;
import org.eclipse.che.plugin.docker.client.json.ContainerInfo;
import org.eclipse.che.plugin.docker.client.json.ContainerListEntry;
import org.eclipse.che.plugin.docker.client.params.ListContainersParams;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.testng.MockitoTestNGListener;
import org.testng.annotations.Listeners;
import org.testng.annotations.Test;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import static com.google.common.collect.Sets.newHashSet;
import static java.util.Arrays.asList;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.when;
import static org.testng.Assert.assertEquals;
/** Tests {@link DockerContainers}. */
@Listeners(MockitoTestNGListener.class)
public class DockerContainersTest {
@Mock
private DockerConnector docker;
@InjectMocks
private DockerContainers containers;
@Test
public void findsIdentifiers() throws Exception {
RuntimeIdentity id1 = new RuntimeIdentityImpl("workspace123", "default", "test");
RuntimeIdentity id2 = new RuntimeIdentityImpl("workspace234", "default", "test");
List<ContainerListEntry> entries = asList(mockContainer(id1, "container1"), mockContainer(id2, "container2"));
when(docker.listContainers(ListContainersParams.create().withAll(false))).thenReturn(entries);
assertEquals(containers.findIdentities(), newHashSet(id1, id2));
}
@Test(expectedExceptions = InfrastructureException.class, expectedExceptionsMessageRegExp = "oops")
public void findsIdentitiesRethrowsIOExceptionThrownWhileListingContainersAsInternalInfraException() throws Exception {
when(docker.listContainers(ListContainersParams.create().withAll(false))).thenThrow(new IOException("oops"));
containers.findIdentities();
}
@Test
public void findContainers() throws Exception {
RuntimeIdentity id1 = new RuntimeIdentityImpl("workspace123", "default", "test");
ContainerListEntry entry1 = mockContainer(id1, "container1");
ContainerListEntry entry2 = mockContainer(id1, "container2");
RuntimeIdentity id2 = new RuntimeIdentityImpl("workspace234", "default", "test");
ContainerListEntry entry3 = mockContainer(id2, "container3");
List<ContainerListEntry> entries = asList(entry1, entry2, entry3);
when(docker.listContainers(ListContainersParams.create().withAll(false))).thenReturn(entries);
assertEquals(containers.find(id1)
.stream()
.map(ContainerListEntry::getId)
.collect(Collectors.toSet()), newHashSet(entry1.getId(), entry2.getId()));
assertEquals(containers.find(id2)
.stream()
.map(ContainerListEntry::getId)
.collect(Collectors.toSet()), newHashSet(entry3.getId()));
}
@Test(expectedExceptions = InternalInfrastructureException.class, expectedExceptionsMessageRegExp = "oops")
public void findContainersRethrowsIOExceptionThrownWhileListingContainersAsInternalInfraException() throws Exception {
when(docker.listContainers(ListContainersParams.create().withAll(false))).thenThrow(new IOException("oops"));
containers.find(new RuntimeIdentityImpl("workspace123", "default", "test"));
}
@Test(expectedExceptions = InternalInfrastructureException.class, expectedExceptionsMessageRegExp = "oops")
public void findContainersRethrowsIOExceptionThrownWhileInspectingContainersAsInternalInfraException() throws Exception {
RuntimeIdentity id = new RuntimeIdentityImpl("workspace123", "default", "test");
List<ContainerListEntry> entries = Collections.singletonList(mockContainer(id, "container"));
when(docker.listContainers(ListContainersParams.create().withAll(false))).thenReturn(entries);
when(docker.inspectContainer(anyString())).thenThrow(new IOException("oops"));
containers.find(id);
}
private ContainerListEntry mockContainer(RuntimeIdentity runtimeId, String containerId) throws IOException {
ContainerListEntry entry = new ContainerListEntry();
entry.setLabels(Labels.newSerializer().runtimeId(runtimeId).labels());
entry.setId(containerId);
return entry;
}
}

View File

@ -84,9 +84,9 @@ public class InstallerConfigApplierTest {
Map<String, String> labels = service.getLabels();
assertEquals(labels.size(), 3);
assertEquals(labels.get("che:server:1111/udp:ref"), "a");
assertEquals(labels.get("che:server:1111/udp:protocol"), "http");
assertEquals(labels.get("che:server:1111/udp:path"), "b");
assertEquals(labels.get("org.eclipse.che.server.a.port"), "1111/udp");
assertEquals(labels.get("org.eclipse.che.server.a.protocol"), "http");
assertEquals(labels.get("org.eclipse.che.server.a.path"), "b");
}
@Test

View File

@ -0,0 +1,82 @@
/*******************************************************************************
* 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.docker;
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.MachineConfig;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/** Tests {@link RuntimeConsistencyChecker}. */
public class RuntimeConsistencyCheckerTest {
@Test(dataProvider = "consistentRuntimesProvider")
public void consistentRuntimes(Environment environment, DockerInternalRuntime runtime) throws ValidationException {
new RuntimeConsistencyChecker().check(environment, runtime);
}
@Test(dataProvider = "inconsistentRuntimesProvider", expectedExceptions = ValidationException.class)
public void inconsistentRuntimes(Environment environment, DockerInternalRuntime runtime) throws ValidationException {
new RuntimeConsistencyChecker().check(environment, runtime);
}
@DataProvider
private static Object[][] consistentRuntimesProvider() {
return new Object[][] {
{ environment("a", "b"), runtime("b", "a") },
{ environment("b", "a"), runtime("b", "a") }
};
}
@DataProvider
private static Object[][] inconsistentRuntimesProvider() {
return new Object[][] {
{ environment("a", "b"), runtime("a") },
{ environment("a", "b"), runtime("a", "c") },
{ environment("a", "b"), runtime("a", "b", "c") },
{ environment("a", "b"), runtime("a") },
{ environment("a"), runtime("a", "b") }
};
}
private static Environment environment(String... names) {
Map<String, MachineConfig> configs = new HashMap<>();
for (String name : names) {
configs.put(name, mock(MachineConfig.class));
}
Environment environment = mock(Environment.class);
doReturn(configs).when(environment).getMachines();
when(environment.toString()).thenReturn(Arrays.toString(names));
return environment;
}
private static DockerInternalRuntime runtime(String... names) {
Map<String, DockerMachine> machines = new HashMap<>();
for (String name : names) {
machines.put(name, mock(DockerMachine.class));
}
DockerInternalRuntime runtime = mock(DockerInternalRuntime.class);
doReturn(machines).when(runtime).getMachines();
when(runtime.toString()).thenReturn(Arrays.toString(names));
return runtime;
}
}

View File

@ -104,6 +104,11 @@ public class ServerCheckerTest {
}
}
@Test(expectedExceptions = InfrastructureException.class)
public void checkOnceThrowsExceptionIfServerIsNotAvailable() throws InfrastructureException {
new TestServerChecker("test", "test", 1, 1, TimeUnit.SECONDS, null).checkOnce();
}
private static class TestServerChecker extends ServerChecker {
protected TestServerChecker(String machineName, String serverRef, long period, long timeout,
TimeUnit timeUnit, Timer timer) {

View File

@ -29,6 +29,7 @@ import java.util.function.Consumer;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.timeout;
@ -63,7 +64,7 @@ public class ServersReadinessCheckerTest {
.thenReturn(connectionChecker);
when(connectionChecker.getReportCompFuture()).thenReturn(compFuture);
checker = new ServersReadinessChecker(MACHINE_NAME, getDefaultServers(), readinessHandler, factory);
checker = new ServersReadinessChecker(MACHINE_NAME, getDefaultServers(), factory);
}
@AfterMethod(timeOut = 1000)
@ -75,7 +76,7 @@ public class ServersReadinessCheckerTest {
@Test(timeOut = 1000)
public void shouldNotifyReadinessHandlerAboutEachServerReadiness() throws Exception {
checker.startAsync();
checker.startAsync(readinessHandler);
verify(readinessHandler, timeout(500).never()).accept(anyString());
@ -86,7 +87,7 @@ public class ServersReadinessCheckerTest {
@Test(timeOut = 1000)
public void shouldThrowExceptionIfAServerIsUnavailable() throws Exception {
checker.startAsync();
checker.startAsync(readinessHandler);
connectionChecker.getReportCompFuture()
.completeExceptionally(new InfrastructureException("my exception"));
@ -102,9 +103,9 @@ public class ServersReadinessCheckerTest {
public void shouldNotCheckNotHardcodedServers() throws Exception {
Map<String, ServerImpl> servers = ImmutableMap.of("wsagent", new ServerImpl("http://localhost"),
"not-hardcoded", new ServerImpl("http://localhost"));
checker = new ServersReadinessChecker(MACHINE_NAME, servers, readinessHandler, factory);
checker = new ServersReadinessChecker(MACHINE_NAME, servers, factory);
checker.startAsync();
checker.startAsync(readinessHandler);
connectionChecker.getReportCompFuture().complete("test_ref");
checker.await();
@ -120,7 +121,7 @@ public class ServersReadinessCheckerTest {
.thenReturn(future2)
.thenReturn(future3);
checker.startAsync();
checker.startAsync(readinessHandler);
future2.completeExceptionally(new InfrastructureException("error"));
@ -137,6 +138,13 @@ public class ServersReadinessCheckerTest {
}
}
@Test(expectedExceptions = InfrastructureException.class, expectedExceptionsMessageRegExp = "oops!")
public void throwsExceptionIfAnyServerIsNotAvailable() throws InfrastructureException {
doThrow(new InfrastructureException("oops!")).when(connectionChecker).checkOnce();
checker.checkOnce();
}
Map<String, ServerImpl> getDefaultServers() {
return ImmutableMap.of("wsagent", new ServerImpl("http://localhost"),
"exec-agent", new ServerImpl("http://localhost"),

View File

@ -82,7 +82,7 @@ public class OpenshiftInternalRuntime extends InternalRuntime<OpenshiftRuntimeCo
EventService eventService,
OpenshiftBootstrapperFactory openshiftBootstrapperFactory,
@Named("che.infra.openshift.machine_start_timeout_min") int machineStartTimeoutMin) {
super(context, urlRewriter);
super(context, urlRewriter, false);
this.identity = identity;
this.kubernetesEnvironment = openshiftEnvironment;
this.clientFactory = clientFactory;

View File

@ -30,6 +30,7 @@ public class ContainerListEntry {
private Map<String, String> labels;
private int sizeRw;
private int sizeRootFs;
private NetworkSettings networkSettings;
/**
* Returns unique container identifier
@ -144,7 +145,7 @@ public class ContainerListEntry {
}
/**
* Returns the virtual size of the container
* Returns the virtual size of the container
*/
public int getSizeRootFs() {
return sizeRootFs;
@ -154,6 +155,14 @@ public class ContainerListEntry {
this.sizeRootFs = sizeRootFs;
}
public NetworkSettings getNetworkSettings() {
return networkSettings;
}
public void setNetworkSettings(NetworkSettings networkSettings) {
this.networkSettings = networkSettings;
}
@Override
public String toString() {
return "ContainerListEntry{" +
@ -168,6 +177,7 @@ public class ContainerListEntry {
", labels=" + labels +
", sizeRw=" + sizeRw +
", sizeRootFs=" + sizeRootFs +
", networkSettings=" + networkSettings +
'}';
}
}

View File

@ -24,6 +24,14 @@ import java.util.Map;
* @author Alexander Garagatyi
*/
public class Filters {
private static final String LABEL_FILTER = "label";
/** Creates filters with {@value #LABEL_FILTER} filter initialized to the given values. */
public static Filters label(String... values) {
return new Filters().withFilter(LABEL_FILTER, values);
}
private final Map<String, List<String>> filters = new HashMap<>();
public Map<String, List<String>> getFilters() {

View File

@ -15,6 +15,13 @@ public class PortBinding {
private String hostIp;
private String hostPort;
public PortBinding() {}
public PortBinding(String hostIp, String hostPort) {
this.hostIp = hostIp;
this.hostPort = hostPort;
}
public String getHostIp() {
return hostIp;
}

View File

@ -11,9 +11,7 @@
"wsagent": {
"port": "4401/tcp",
"protocol": "http",
"properties": {
"path": "/api"
}
"path" : "/api/"
}
}
}

View File

@ -10,6 +10,7 @@
*******************************************************************************/
package org.eclipse.che.api.workspace.server;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableMap;
import org.eclipse.che.api.core.ConflictException;
@ -30,11 +31,13 @@ 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.RuntimeIdentityImpl;
import org.eclipse.che.api.workspace.server.spi.RuntimeInfrastructure;
import org.eclipse.che.api.workspace.server.spi.WorkspaceDao;
import org.eclipse.che.api.workspace.shared.dto.event.RuntimeStatusEvent;
import org.eclipse.che.api.workspace.shared.dto.event.WorkspaceStatusEvent;
import org.eclipse.che.commons.env.EnvironmentContext;
import org.eclipse.che.commons.lang.concurrent.ThreadLocalPropagateContext;
import org.eclipse.che.commons.subject.Subject;
import org.eclipse.che.core.db.DBInitializer;
import org.eclipse.che.dto.server.DtoFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -74,14 +77,18 @@ public class WorkspaceRuntimes {
private final ConcurrentMap<String, RuntimeState> runtimes;
private final EventService eventService;
private final WorkspaceSharedPool sharedPool;
private final WorkspaceDao workspaceDao;
@Inject
public WorkspaceRuntimes(EventService eventService,
Set<RuntimeInfrastructure> infrastructures,
WorkspaceSharedPool sharedPool) {
WorkspaceSharedPool sharedPool,
WorkspaceDao workspaceDao,
@SuppressWarnings("unused") DBInitializer ignored) {
this.runtimes = new ConcurrentHashMap<>();
this.eventService = eventService;
this.sharedPool = sharedPool;
this.workspaceDao = workspaceDao;
// TODO: consider extracting to a strategy interface(1. pick the last, 2. fail with conflict)
Map<String, RuntimeInfrastructure> tmp = new HashMap<>();
@ -101,6 +108,12 @@ public class WorkspaceRuntimes {
infraByRecipe = ImmutableMap.copyOf(tmp);
}
@PostConstruct
private void init() {
subscribeCleanupOnAbnormalRuntimeStopEvent();
recover();
}
// TODO Doesn't look like correct place for this logic. Where this code should be?
public Environment estimate(Environment environment) throws NotFoundException, InfrastructureException, ValidationException {
// TODO decide whether throw exception when dev machine not found
@ -317,43 +330,69 @@ public class WorkspaceRuntimes {
return runtimes.containsKey(workspaceId);
}
@PostConstruct
private void init() {
recover();
subscribeCleanupOnAbnormalRuntimeStopEvent();
}
private void recover() {
for (RuntimeInfrastructure infra : infraByRecipe.values()) {
try {
for (RuntimeIdentity id : infra.getIdentities()) {
// TODO how to identify correct state of runtime
if (runtimes.putIfAbsent(id.getWorkspaceId(),
new RuntimeState(validate(infra.getRuntime(id)), RUNNING)) != null) {
// should not happen, violation of SPI contract
LOG.error("More than 1 runtime of workspace found. " +
"Runtime identity of duplicate is '{}'. Skipping duplicate.", id);
}
for (RuntimeIdentity identity : infra.getIdentities()) {
recoverOne(infra, identity);
}
} catch (UnsupportedOperationException x) {
LOG.warn("Not recoverable infrastructure: '{}'", infra.getName());
} catch (InfrastructureException x) {
} catch (ServerException | InfrastructureException x) {
LOG.error("An error occurred while attempted to recover runtimes using infrastructure '{}'. Reason: '{}'",
infra.getName(),
x.getMessage());
infra.getName(), x.getMessage());
}
}
}
@VisibleForTesting
void recoverOne(RuntimeInfrastructure infra, RuntimeIdentity identity) throws ServerException {
Workspace workspace;
try {
workspace = workspaceDao.get(identity.getWorkspaceId());
} catch (NotFoundException x) {
LOG.error("Workspace configuration is missing for the runtime '{}:{}'. Runtime won't be recovered",
identity.getWorkspaceId(),
identity.getEnvName());
return;
}
Environment environment = workspace.getConfig().getEnvironments().get(identity.getEnvName());
if (environment == null) {
LOG.error("Environment configuration is missing for the runtime '{}:{}'. Runtime won't be recovered",
identity.getWorkspaceId(),
identity.getEnvName());
return;
}
InternalRuntime runtime;
try {
runtime = infra.prepare(identity, environment).getRuntime();
} catch (InfrastructureException | ValidationException x) {
LOG.error("Couldn't recover runtime '{}:{}'. Error: {}",
identity.getWorkspaceId(),
identity.getEnvName(),
x.getMessage());
return;
}
RuntimeState prev = runtimes.putIfAbsent(identity.getWorkspaceId(), new RuntimeState(runtime, RUNNING));
if (prev == null) {
LOG.info("Successfully recovered workspace runtime '{}'", identity.getWorkspaceId(), identity.getEnvName());
} else {
LOG.error("More than 1 runtime with id '{}:{}' found. " +
"Duplicate provided by infrastructure '{}' will be skipped",
identity.getWorkspaceId(),
identity.getEnvName(),
prev.runtime.getContext().getInfrastructure().getName(),
infra.getName());
}
}
private void subscribeCleanupOnAbnormalRuntimeStopEvent() {
eventService.subscribe(new CleanupRuntimeOnAbnormalRuntimeStop());
}
//TODO do we need some validation on start?
private InternalRuntime validate(InternalRuntime runtime) {
return runtime;
}
private static EnvironmentImpl copyEnv(Workspace workspace, String envName) {
requireNonNull(workspace, "Workspace should not be null.");
@ -375,7 +414,7 @@ public class WorkspaceRuntimes {
if (state != null) {
eventService.publish(DtoFactory.newDto(WorkspaceStatusEvent.class)
.withWorkspaceId(state.runtime.getContext().getIdentity()
.getWorkspaceId())
.getWorkspaceId())
.withPrevStatus(WorkspaceStatus.RUNNING)
.withStatus(STOPPED)
.withError("Error occurs on workspace runtime stop. Error: " +

View File

@ -16,6 +16,9 @@ import org.eclipse.che.api.core.model.workspace.runtime.Server;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import static java.util.stream.Collectors.toMap;
/**
* Data object for {@link Machine}.
@ -27,10 +30,6 @@ public class MachineImpl implements Machine {
private Map<String, String> properties;
private Map<String, ServerImpl> servers;
public static MachineRuntimeInfoImplBuilder builder() {
return new MachineRuntimeInfoImplBuilder();
}
public MachineImpl(Machine machineRuntime) {
this(machineRuntime.getProperties(), machineRuntime.getServers());
}
@ -38,17 +37,18 @@ public class MachineImpl implements Machine {
public MachineImpl(Map<String, String> properties,
Map<String, ? extends Server> servers) {
// this.envVariables = new HashMap<>(envVariables);
this(servers);
this.properties = new HashMap<>(properties);
}
public MachineImpl(Map<String, ? extends Server> servers) {
if (servers != null) {
this.servers = servers.entrySet()
.stream()
.collect(HashMap::new,
(map, entry) -> map.put(entry.getKey(), new ServerImpl(entry.getValue())),
HashMap::putAll);
.collect(toMap(Map.Entry::getKey, entry -> new ServerImpl(entry.getValue().getUrl())));
}
}
// public Map<String, String> getEnvVariables() {
// if (envVariables == null) {
// envVariables = new HashMap<>();
@ -78,45 +78,12 @@ public class MachineImpl implements Machine {
if (!(o instanceof MachineImpl)) return false;
MachineImpl that = (MachineImpl)o;
return //Objects.equals(getEnvVariables(), that.getEnvVariables()) &&
Objects.equals(getProperties(), that.getProperties()) &&
Objects.equals(getServers(), that.getServers());
Objects.equals(getProperties(), that.getProperties()) &&
Objects.equals(getServers(), that.getServers());
}
@Override
public int hashCode() {
return Objects.hash(/*getEnvVariables(),*/ getProperties(), getServers());
}
public static class MachineRuntimeInfoImplBuilder {
private Map<String, ? extends Server> servers;
private Map<String, String> properties;
// private Map<String, String> envVariables;
public MachineImpl build() {
return new MachineImpl(properties, servers);
}
public MachineRuntimeInfoImplBuilder setServers(Map<String, ? extends Server> servers) {
this.servers = servers;
return this;
}
public MachineRuntimeInfoImplBuilder setProperties(Map<String, String> properties) {
this.properties = properties;
return this;
}
// public MachineRuntimeInfoImplBuilder setEnvVariables(Map<String, String> envVariables) {
// this.envVariables = envVariables;
// return this;
// }
}
@Override
public String toString() {
return "MachineImpl{" +
"properties=" + properties +
", servers=" + servers +
'}';
}
}

View File

@ -13,12 +13,14 @@ package org.eclipse.che.api.workspace.server.model.impl;
import org.eclipse.che.api.core.model.workspace.runtime.Server;
import org.eclipse.che.api.core.model.workspace.runtime.ServerStatus;
import java.util.Objects;
/**
* @author gazarenkov
*/
public class ServerImpl implements Server {
private String url;
private String url;
private ServerStatus status;
public ServerImpl(String url) {
@ -51,4 +53,30 @@ public class ServerImpl implements Server {
public void setStatus(ServerStatus status) {
this.status = status;
}
@Override
public String toString() {
return url;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof Server)) {
return false;
}
final Server that = (Server)obj;
return Objects.equals(url, that.getUrl())
&& Objects.equals(status, that.getStatus());
}
@Override
public int hashCode() {
int hash = 7;
hash = 31 * hash + Objects.hashCode(url);
hash = 31 * hash + Objects.hashCode(status);
return hash;
}
}

View File

@ -45,9 +45,12 @@ public abstract class InternalRuntime <T extends RuntimeContext> implements Runt
private final List<Warning> warnings = new ArrayList<>();
private WorkspaceStatus status;
public InternalRuntime(T context, URLRewriter urlRewriter) {
public InternalRuntime(T context, URLRewriter urlRewriter, boolean running) {
this.context = context;
this.urlRewriter = urlRewriter != null ? urlRewriter : new URLRewriter.NoOpURLRewriter();
if (running) {
status = WorkspaceStatus.RUNNING;
}
}
@Override
@ -155,7 +158,7 @@ public abstract class InternalRuntime <T extends RuntimeContext> implements Runt
/**
* @return the Context
*/
public final RuntimeContext getContext() {
public final T getContext() {
return context;
}

View File

@ -60,8 +60,10 @@ public abstract class RuntimeContext {
* Context must return the Runtime object whatever its status is (STOPPED status including)
*
* @return Runtime object
* @throws InfrastructureException
* when any error during runtime retrieving/creation
*/
public abstract InternalRuntime getRuntime();
public abstract InternalRuntime getRuntime() throws InfrastructureException;
/**
* Infrastructure should assign channel (usual WebSocket) to push long lived processes messages

View File

@ -100,24 +100,6 @@ public abstract class RuntimeInfrastructure {
throw new UnsupportedOperationException("The implementation does not track runtimes");
}
/**
* An Infrastructure MAY track Runtimes. In this case the method should be overridden.
* <p>
* One of the reason for infrastructure to support this is ability to recover infrastructure
* after shutting down Master server.
*
* @param id
* the RuntimeIdentityImpl
* @return the Runtime
* @throws UnsupportedOperationException
* if implementation does not support runtimes tracking
* @throws InfrastructureException
* if any other error occurred
*/
public InternalRuntime getRuntime(RuntimeIdentity id) throws InfrastructureException {
throw new UnsupportedOperationException("The implementation does not track runtimes");
}
/**
* Making Runtime is a two phase process.
* On the first phase implementation MUST prepare RuntimeContext, this is supposedly "fast" method

View File

@ -0,0 +1,233 @@
/*******************************************************************************
* 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.api.workspace.server;
import com.google.common.collect.ImmutableMap;
import org.eclipse.che.api.core.NotFoundException;
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.runtime.Machine;
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.model.impl.EnvironmentImpl;
import org.eclipse.che.api.workspace.server.model.impl.WorkspaceConfigImpl;
import org.eclipse.che.api.workspace.server.model.impl.WorkspaceImpl;
import org.eclipse.che.api.workspace.server.spi.InfrastructureException;
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.RuntimeIdentityImpl;
import org.eclipse.che.api.workspace.server.spi.RuntimeInfrastructure;
import org.eclipse.che.api.workspace.server.spi.WorkspaceDao;
import org.eclipse.che.core.db.DBInitializer;
import org.mockito.Mock;
import org.mockito.testng.MockitoTestNGListener;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Listeners;
import org.testng.annotations.Test;
import java.util.Collections;
import java.util.Map;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyObject;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertNotNull;
/** Tests {@link WorkspaceRuntimes}. */
@Listeners(MockitoTestNGListener.class)
public class WorkspaceRuntimesTest {
@Mock
private EventService eventService;
@Mock
private WorkspaceDao workspaceDao;
@Mock
private DBInitializer dbInitializer;
@Mock
private WorkspaceSharedPool sharedPool;
private RuntimeInfrastructure infrastructure;
private WorkspaceRuntimes runtimes;
@BeforeMethod
public void setUp() {
infrastructure = spy(new TestInfrastructure(eventService));
runtimes = new WorkspaceRuntimes(eventService,
Collections.singleton(infrastructure),
sharedPool,
workspaceDao,
dbInitializer);
}
@Test
public void runtimeIsRecovered() throws Exception {
RuntimeIdentity identity = new RuntimeIdentityImpl("workspace123", "my-env", "me");
mockWorkspace(identity);
RuntimeContext context = mockContext(identity);
when(context.getRuntime()).thenReturn(new TestInternalRuntime(context));
doReturn(context).when(infrastructure).prepare(eq(identity), anyObject());
// try recover
runtimes.recoverOne(infrastructure, identity);
WorkspaceImpl workspace = new WorkspaceImpl(identity.getWorkspaceId(), null, null);
runtimes.injectRuntime(workspace);
assertNotNull(workspace.getRuntime());
}
@Test
public void runtimeIsNotRecoveredIfNoWorkspaceFound() throws Exception {
RuntimeIdentity identity = new RuntimeIdentityImpl("workspace123", "my-env", "me");
when(workspaceDao.get(identity.getWorkspaceId())).thenThrow(new NotFoundException("no!"));
// try recover
runtimes.recoverOne(infrastructure, identity);
assertFalse(runtimes.hasRuntime(identity.getWorkspaceId()));
}
@Test
public void runtimeIsNotRecoveredIfNoEnvironmentFound() throws Exception {
RuntimeIdentity identity = new RuntimeIdentityImpl("workspace123", "my-env", "me");
WorkspaceImpl workspace = mockWorkspace(identity);
when(workspace.getConfig().getEnvironments()).thenReturn(Collections.emptyMap());
// try recover
runtimes.recoverOne(infrastructure, identity);
assertFalse(runtimes.hasRuntime(identity.getWorkspaceId()));
}
@Test
public void runtimeIsNotRecoveredIfInfraPreparationFailed() throws Exception {
RuntimeIdentity identity = new RuntimeIdentityImpl("workspace123", "my-env", "me");
EnvironmentImpl env = mockWorkspace(identity).getConfig().getEnvironments().get(identity.getEnvName());
doThrow(new InfrastructureException("oops!")).when(infrastructure).prepare(identity, env);
// try recover
runtimes.recoverOne(infrastructure, identity);
assertFalse(runtimes.hasRuntime(identity.getWorkspaceId()));
}
@Test
public void runtimeIsNotRecoveredIfAnotherRuntimeWithTheSameIdentityAlreadyExists() throws Exception {
RuntimeIdentity identity = new RuntimeIdentityImpl("workspace123", "my-env", "me");
mockWorkspace(identity);
RuntimeContext context = mockContext(identity);
// runtime 1(has 1 machine) must be successfully saved
Map<String, Machine> r1machines = ImmutableMap.of("m1", mock(Machine.class));
InternalRuntime runtime1 = spy(new TestInternalRuntime(context, r1machines));
when(context.getRuntime()).thenReturn(runtime1);
runtimes.recoverOne(infrastructure, identity);
// runtime 2 must not be saved
Map<String, Machine> r2machines = ImmutableMap.of("m1", mock(Machine.class), "m2", mock(Machine.class));
InternalRuntime runtime2 = new TestInternalRuntime(context, r2machines);
when(context.getRuntime()).thenReturn(runtime2);
runtimes.recoverOne(infrastructure, identity);
WorkspaceImpl workspace = new WorkspaceImpl(identity.getWorkspaceId(), null, null);
runtimes.injectRuntime(workspace);
assertNotNull(workspace.getRuntime());
assertEquals(workspace.getRuntime().getMachines().keySet(), r1machines.keySet());
}
private RuntimeContext mockContext(RuntimeIdentity identity) throws ValidationException, InfrastructureException {
RuntimeContext context = mock(RuntimeContext.class);
doReturn(context).when(infrastructure).prepare(eq(identity), anyObject());
when(context.getInfrastructure()).thenReturn(infrastructure);
when(context.getIdentity()).thenReturn(identity);
return context;
}
private WorkspaceImpl mockWorkspace(RuntimeIdentity identity) throws NotFoundException, ServerException {
EnvironmentImpl environment = mock(EnvironmentImpl.class);
WorkspaceConfigImpl config = mock(WorkspaceConfigImpl.class);
when(config.getEnvironments()).thenReturn(ImmutableMap.of(identity.getEnvName(), environment));
WorkspaceImpl workspace = mock(WorkspaceImpl.class);
when(workspace.getConfig()).thenReturn(config);
when(workspace.getId()).thenReturn(identity.getWorkspaceId());
when(workspaceDao.get(identity.getWorkspaceId())).thenReturn(workspace);
return workspace;
}
private static class TestInfrastructure extends RuntimeInfrastructure {
public TestInfrastructure(EventService eventService) {
super("test", Collections.singleton("test"), eventService);
}
@Override
public Environment estimate(Environment environment) {
throw new UnsupportedOperationException();
}
@Override
public RuntimeContext prepare(RuntimeIdentity id, Environment environment) throws InfrastructureException {
throw new UnsupportedOperationException();
}
}
private static class TestInternalRuntime extends InternalRuntime<RuntimeContext> {
final Map<String, Machine> machines;
TestInternalRuntime(RuntimeContext context, Map<String, Machine> machines) {
super(context, null, false);
this.machines = machines;
}
TestInternalRuntime(RuntimeContext context) {
this(context, Collections.emptyMap());
}
@Override
protected Map<String, Machine> getInternalMachines() {
return machines;
}
@Override
public Map<String, String> getProperties() {
return Collections.emptyMap();
}
@Override
protected void internalStop(Map stopOptions) throws InfrastructureException {
throw new UnsupportedOperationException();
}
@Override
protected void internalStart(Map startOptions) throws InfrastructureException {
throw new UnsupportedOperationException();
}
}
}

View File

@ -175,7 +175,11 @@ public class CascadeRemovalTest {
// install(new MachineJpaModule());
bind(WorkspaceManager.class);
WorkspaceRuntimes wR = spy(new WorkspaceRuntimes(mock(EventService.class), Collections.emptySet(), mock(WorkspaceSharedPool.class)));
WorkspaceRuntimes wR = spy(new WorkspaceRuntimes(mock(EventService.class),
Collections.emptySet(),
mock(WorkspaceSharedPool.class),
mock(WorkspaceDao.class),
mock(DBInitializer.class)));
when(wR.hasRuntime(anyString())).thenReturn(false);
bind(WorkspaceRuntimes.class).toInstance(wR);
bind(AccountManager.class);