Restore snapshots functionality;

6.19.x
Max Shaposhnik 2017-05-29 11:00:59 +00:00 committed by GitHub
parent fa91db1f4e
commit 8afc4eb053
4 changed files with 158 additions and 64 deletions

View File

@ -47,19 +47,6 @@ public enum WorkspaceStatus {
*/
RUNNING,
/**
* Workspace is in SNAPSHOTTING status if and only if the workspace
* is currently creating snapshots of it's machines.
*
* <p>Workspace is in SNAPSHOTTING status after it was {@link #RUNNING}.
* The status map:
* <pre>
* RUNNING -> <b>SNAPSHOTTING</b> -> RUNNING (normal behaviour/error while snapshotting)
* </pre>
* @deprecated move it to Docker env specific
*/
SNAPSHOTTING,
/**
* Workspace considered as stopping if and only if its active environment is shutting down.
*

View File

@ -16,16 +16,21 @@ 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.eclipse.che.commons.lang.NameGenerator;
import org.eclipse.che.plugin.docker.client.DockerConnector;
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.ProgressLineFormatterImpl;
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;
import org.eclipse.che.plugin.docker.client.params.RemoveContainerParams;
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;
@ -35,6 +40,8 @@ import java.io.IOException;
import java.util.Collections;
import java.util.Map;
import static java.lang.String.format;
import static org.eclipse.che.workspace.infrastructure.docker.DockerRegistryClient.MACHINE_SNAPSHOT_PREFIX;
import static org.slf4j.LoggerFactory.getLogger;
/**
@ -83,19 +90,17 @@ public class DockerMachine implements Machine {
private final DockerConnector docker;
private final String image;
private final DockerMachineStopDetector dockerMachineStopDetector;
// TODO spi snapshot #5101
// private final String registry;
// private final String registryNamespace;
// private final boolean snapshotUseRegistry;
private final String registry;
private final String registryNamespace;
private final boolean snapshotUseRegistry;
private final ContainerInfo info;
private final ServerEvaluationStrategyProvider provider;
@Inject
public DockerMachine(DockerConnector docker,
// TODO spi snapshot #5101
// @Named("che.docker.registry") String registry,
// @Named("che.docker.namespace") @Nullable String registryNamespace,
// @Named("che.docker.registry_for_snapshots") boolean snapshotUseRegistry,
String registry,
String registryNamespace,
boolean snapshotUseRegistry,
@Assisted("container") String container,
@Assisted("image") String image,
ServerEvaluationStrategyProvider provider,
@ -103,6 +108,9 @@ public class DockerMachine implements Machine {
this.container = container;
this.docker = docker;
this.image = image;
this.registry = registry;
this.registryNamespace = registryNamespace;
this.snapshotUseRegistry = snapshotUseRegistry;
this.dockerMachineStopDetector = dockerMachineStopDetector;
try {
this.info = docker.inspectContainer(container);
@ -163,13 +171,16 @@ public class DockerMachine implements Machine {
"container='" + container + '\'' +
", docker=" + docker +
", image='" + image + '\'' +
", registry='" + registry + '\'' +
", registryNamespace='" + registryNamespace + '\'' +
", snapshotUseRegistry='" + snapshotUseRegistry +
", info=" + info +
", provider=" + provider +
'}';
}
/* TODO spi snapshot #5101
public MachineSource saveToSnapshot() throws MachineException {
public DockerMachineSource saveToSnapshot() throws SnapshotException {
try {
String image = generateRepository();
if(!snapshotUseRegistry) {
@ -188,22 +199,21 @@ public class DockerMachine implements Machine {
final ProgressLineFormatterImpl lineFormatter = new ProgressLineFormatterImpl();
final String digest = docker.push(pushParams,
progressMonitor -> {
try {
outputConsumer.writeLine(lineFormatter.format(progressMonitor));
} catch (IOException ignored) {
}
// try {
// outputConsumer.writeLine(lineFormatter.format(progressMonitor));
// } catch (IOException ignored) {
// }
});
docker.removeImage(RemoveImageParams.create(fullRepo).withForce(false));
return new DockerMachineSource(image).withRegistry(registry).withDigest(digest).withTag(LATEST_TAG);
} catch (IOException ioEx) {
throw new MachineException(ioEx);
throw new SnapshotException(ioEx);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new MachineException(e.getLocalizedMessage(), e);
throw new SnapshotException(e.getLocalizedMessage(), e);
}
}
@VisibleForTesting
protected void commitContainer(String repository, String tag) throws IOException {
String comment = format("Suspended at %1$ta %1$tb %1$td %1$tT %1$tZ %1$tY",
System.currentTimeMillis());
@ -218,8 +228,8 @@ public class DockerMachine implements Machine {
private String generateRepository() {
if (registryNamespace != null) {
return registryNamespace + '/' + DockerInstanceProvider.MACHINE_SNAPSHOT_PREFIX + NameGenerator.generate(null, 16);
return registryNamespace + '/' + MACHINE_SNAPSHOT_PREFIX + NameGenerator.generate(null, 16);
}
return DockerInstanceProvider.MACHINE_SNAPSHOT_PREFIX + NameGenerator.generate(null, 16);
}*/
return MACHINE_SNAPSHOT_PREFIX + NameGenerator.generate(null, 16);
}
}

View File

@ -14,11 +14,13 @@ import com.google.inject.assistedinject.Assisted;
import org.eclipse.che.api.agent.server.AgentRegistry;
import org.eclipse.che.api.agent.server.impl.AgentSorter;
import org.eclipse.che.api.core.NotFoundException;
import org.eclipse.che.api.core.ValidationException;
import org.eclipse.che.api.core.model.machine.MachineSource;
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.URLRewriter;
import org.eclipse.che.api.workspace.server.model.impl.MachineSourceImpl;
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.InternalMachineConfig;
@ -30,11 +32,16 @@ import org.eclipse.che.workspace.infrastructure.docker.exception.SourceNotFoundE
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.snapshot.SnapshotDao;
import org.eclipse.che.workspace.infrastructure.docker.snapshot.SnapshotException;
import org.eclipse.che.workspace.infrastructure.docker.snapshot.SnapshotImpl;
import org.slf4j.Logger;
import javax.inject.Inject;
import java.net.URL;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
@ -61,14 +68,16 @@ import static org.slf4j.LoggerFactory.getLogger;
public class DockerRuntimeContext extends RuntimeContext {
private static final Logger LOG = getLogger(DockerRuntimeContext.class);
private final NetworkLifecycle dockerNetworkLifecycle;
private final MachineStarter serviceStarter;
private final DockerEnvironment dockerEnvironment;
private final URLRewriter urlRewriter;
private final Queue<String> startQueue;
private final StartSynchronizer startSynchronizer;
private final String devMachineName;
private final ContextsStorage contextsStorage;
private final NetworkLifecycle dockerNetworkLifecycle;
private final MachineStarter serviceStarter;
private final DockerEnvironment dockerEnvironment;
private final URLRewriter urlRewriter;
private final Queue<String> startQueue;
private final StartSynchronizer startSynchronizer;
private final String devMachineName;
private final ContextsStorage contextsStorage;
private final SnapshotDao snapshotDao;
private final DockerRegistryClient dockerRegistryClient;
@Inject
public DockerRuntimeContext(@Assisted DockerRuntimeInfrastructure infrastructure,
@ -81,7 +90,9 @@ public class DockerRuntimeContext extends RuntimeContext {
URLRewriter urlRewriter,
AgentSorter agentSorter,
AgentRegistry agentRegistry,
ContextsStorage contextsStorage)
ContextsStorage contextsStorage,
SnapshotDao snapshotDao,
DockerRegistryClient dockerRegistryClient)
throws ValidationException, InfrastructureException {
super(environment, identity, infrastructure, agentSorter, agentRegistry);
this.devMachineName = Utils.getDevMachineName(environment);
@ -91,6 +102,8 @@ public class DockerRuntimeContext extends RuntimeContext {
this.startQueue = new ArrayDeque<>(orderedServices);
this.urlRewriter = urlRewriter;
this.contextsStorage = contextsStorage;
this.snapshotDao = snapshotDao;
this.dockerRegistryClient = dockerRegistryClient;
this.startSynchronizer = new StartSynchronizer();
}
@ -121,7 +134,7 @@ public class DockerRuntimeContext extends RuntimeContext {
boolean runtimeDestroyingNeeded = !startSynchronizer.isStopCalled();
if (runtimeDestroyingNeeded) {
try {
destroyRuntime();
destroyRuntime(null);
} catch (Exception destExc) {
LOG.error(destExc.getLocalizedMessage(), destExc);
}
@ -141,7 +154,7 @@ public class DockerRuntimeContext extends RuntimeContext {
protected void internalStop(Map<String, String> stopOptions) throws InfrastructureException {
startSynchronizer.interruptStartThread();
try {
destroyRuntime();
destroyRuntime(stopOptions);
} finally {
contextsStorage.remove(this);
}
@ -153,32 +166,43 @@ public class DockerRuntimeContext extends RuntimeContext {
}
private DockerMachine startMachine(String name,
DockerContainerConfig container,
DockerContainerConfig containerConfig,
Map<String, String> startOptions,
boolean isDev) throws InfrastructureException {
DockerMachine dockerMachine;
// TODO property name
if ("true".equals(startOptions.get("recover"))) {
if ("true".equals(startOptions.get("restore"))) {
MachineSourceImpl machineSource = null;
try {
// TODO set snapshot stuff #5101
// container = normalizeSource(container, null);
SnapshotImpl snapshot = snapshotDao.getSnapshot(identity.getWorkspaceId(),
identity.getEnvName(),
name);
machineSource = snapshot.getMachineSource();
// Snapshot image location has SHA-256 digest which needs to be removed,
// otherwise it will be pulled without tag and cause problems
String imageName = machineSource.getLocation();
if (imageName.contains("@sha256:")) {
machineSource.setLocation(imageName.substring(0, imageName.indexOf('@')));
}
DockerContainerConfig imageContainerConfig = normalizeSource(containerConfig, machineSource);
dockerMachine = serviceStarter.startService(dockerEnvironment.getNetwork(),
name,
container,
imageContainerConfig,
identity,
isDev);
} catch (SourceNotFoundException e) {
} catch (NotFoundException | SnapshotException | SourceNotFoundException e) {
// slip to start without recovering
dockerMachine = serviceStarter.startService(dockerEnvironment.getNetwork(),
name,
container,
containerConfig,
identity,
isDev);
}
} else {
dockerMachine = serviceStarter.startService(dockerEnvironment.getNetwork(),
name,
container,
containerConfig,
identity,
isDev);
}
@ -192,24 +216,26 @@ public class DockerRuntimeContext extends RuntimeContext {
startSynchronizer.getMachines());
}
private void destroyRuntime() throws InfrastructureException {
for (Map.Entry<String, DockerMachine> dockerMachineEntry : startSynchronizer.removeMachines().entrySet()) {
try {
// TODO spi snapshot #5101
dockerMachineEntry.getValue().destroy();
} catch (InfrastructureException e) {
LOG.error(format("Error occurs on destroying of docker machine '%s' in workspace '%s'. Container '%s'",
dockerMachineEntry.getKey(),
getIdentity().getWorkspaceId(),
dockerMachineEntry.getValue().getContainer()),
e);
private void destroyRuntime(Map<String, String> stopOptions) throws InfrastructureException {
if (stopOptions != null && "true".equals(stopOptions.get("create-snapshot"))) {
snapshotMachines(startSynchronizer.removeMachines());
} else {
for (Map.Entry<String, DockerMachine> dockerMachineEntry : startSynchronizer.removeMachines().entrySet()) {
try {
dockerMachineEntry.getValue().destroy();
} catch (InfrastructureException e) {
LOG.error(format("Error occurs on destroying of docker machine '%s' in workspace '%s'. Container '%s'",
dockerMachineEntry.getKey(),
getIdentity().getWorkspaceId(),
dockerMachineEntry.getValue().getContainer()),
e);
}
}
}
// TODO what happens when context throws exception here
dockerNetworkLifecycle.destroyNetwork(dockerEnvironment.getNetwork());
}
// TODO Do not remove, will be used in snapshot #5101
private DockerContainerConfig normalizeSource(DockerContainerConfig containerConfig,
MachineSource machineSource) {
DockerContainerConfig serviceWithNormalizedSource = new DockerContainerConfig(containerConfig);
@ -234,6 +260,68 @@ public class DockerRuntimeContext extends RuntimeContext {
return serviceWithNormalizedSource;
}
/**
* Removes binaries of all the snapshots, continues to remove
* snapshots if removal of binaries for a single snapshot fails.
*
* @param snapshots
* the list of snapshots to remove binaries
*/
private void removeBinaries(Collection<? extends SnapshotImpl> snapshots) {
for (SnapshotImpl snapshot : snapshots) {
try {
dockerRegistryClient.removeInstanceSnapshot(snapshot.getMachineSource());
} catch (SnapshotException x) {
LOG.error(format("Couldn't remove snapshot '%s', workspace id '%s'", snapshot.getId(), snapshot.getWorkspaceId()), x);
}
}
}
/**
* Prepare snapshots of all active machines.
* @param machines
* the active machines map
*/
private void snapshotMachines(Map<String, DockerMachine> machines) {
List<SnapshotImpl> newSnapshots = new ArrayList<>();
for (Map.Entry<String, DockerMachine> dockerMachineEntry : machines.entrySet()) {
SnapshotImpl snapshot = SnapshotImpl.builder()
.generateId()
.setType("docker") //TODO: do we need that at all?
.setWorkspaceId(identity.getWorkspaceId())
.setDescription(identity.getEnvName())
.setDev(dockerMachineEntry.getKey().equals(devMachineName))
.setEnvName(identity.getEnvName())
.setMachineName(dockerMachineEntry.getKey())
.useCurrentCreationDate()
.build();
try {
DockerMachineSource machineSource = dockerMachineEntry.getValue().saveToSnapshot();
snapshot.setMachineSource(new MachineSourceImpl(machineSource));
newSnapshots.add(snapshot);
} catch (SnapshotException e) {
LOG.error(format("Error occurs on snapshotting of docker machine '%s' in workspace '%s'. Container '%s'",
dockerMachineEntry.getKey(),
getIdentity().getWorkspaceId(),
dockerMachineEntry.getValue().getContainer()),
e);
}
}
try {
List<SnapshotImpl> removed = snapshotDao.replaceSnapshots(identity.getWorkspaceId(),
identity.getEnvName(),
newSnapshots);
if (!removed.isEmpty()) {
LOG.info("Removing old snapshots binaries, workspace id '{}', snapshots to remove '{}'", identity.getWorkspaceId(),
removed.size());
removeBinaries(removed);
}
} catch (SnapshotException e) {
LOG.error(format("Couldn't remove existing snapshots metadata for workspace '%s'", identity.getWorkspaceId()), e);
removeBinaries(newSnapshots);
}
}
// TODO rework to agent launchers
private void startAgents(String machineName, DockerMachine dockerMachine) throws InfrastructureException {
InternalMachineConfig machineConfig = internalMachines.get(machineName);

View File

@ -135,6 +135,8 @@ public class MachineStarter {
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;
@ -153,6 +155,8 @@ public class MachineStarter {
@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,
@ -188,6 +192,8 @@ public class MachineStarter {
this.cpuPeriod = cpuPeriod;
this.cpuQuota = cpuQuota;
this.windowsPathEscaper = windowsPathEscaper;
this.registryNamespace = registryNamespace;
this.registry = registry;
this.pidsLimit = pidsLimit;
this.dnsResolvers = dnsResolvers;
this.buildArgs = buildArgs;
@ -335,6 +341,9 @@ public class MachineStarter {
workspaceId);
return new DockerMachine(docker,
registry,
registryNamespace,
snapshotUseRegistry,
container,
image,
serverEvaluationStrategyProvider,