diff --git a/core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/machine/MachineSource.java b/core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/machine/MachineSource.java index d8ffa871caa..8388bcc84f8 100644 --- a/core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/machine/MachineSource.java +++ b/core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/machine/MachineSource.java @@ -24,4 +24,10 @@ public interface MachineSource { * Returns URL or ID */ String getLocation(); + + /** + * @return content of the machine source. No need to use an external link. + */ + String getContent(); + } diff --git a/plugins/plugin-docker/che-plugin-docker-machine/src/main/java/org/eclipse/che/plugin/docker/machine/DockerInstance.java b/plugins/plugin-docker/che-plugin-docker-machine/src/main/java/org/eclipse/che/plugin/docker/machine/DockerInstance.java index e1e24c48d80..db0e45d4dc5 100644 --- a/plugins/plugin-docker/che-plugin-docker-machine/src/main/java/org/eclipse/che/plugin/docker/machine/DockerInstance.java +++ b/plugins/plugin-docker/che-plugin-docker-machine/src/main/java/org/eclipse/che/plugin/docker/machine/DockerInstance.java @@ -16,12 +16,12 @@ import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.model.machine.Command; import org.eclipse.che.api.core.model.machine.Machine; +import org.eclipse.che.api.core.model.machine.MachineSource; import org.eclipse.che.api.core.util.LineConsumer; import org.eclipse.che.api.core.util.ListLineConsumer; import org.eclipse.che.api.machine.server.exception.MachineException; import org.eclipse.che.api.machine.server.model.impl.MachineRuntimeInfoImpl; import org.eclipse.che.api.machine.server.spi.Instance; -import org.eclipse.che.api.machine.server.spi.InstanceKey; import org.eclipse.che.api.machine.server.spi.InstanceProcess; import org.eclipse.che.api.machine.server.spi.impl.AbstractInstance; import org.eclipse.che.commons.lang.NameGenerator; @@ -60,6 +60,11 @@ public class DockerInstance extends AbstractInstance { private static final Logger LOG = LoggerFactory.getLogger(DockerInstance.class); + /** + * Name of the latest tag used in Docker image. + */ + public static final String LATEST_TAG = "latest"; + private static final AtomicInteger pidSequence = new AtomicInteger(1); private static final String PID_FILE_TEMPLATE = "/tmp/docker-exec-%s.pid"; private static final Pattern PID_FILE_PATH_PATTERN = Pattern.compile(String.format(PID_FILE_TEMPLATE, "([0-9]+)")); @@ -191,21 +196,20 @@ public InstanceProcess createProcess(Command command, String outputChannel) thro } @Override - public InstanceKey saveToSnapshot(String owner) throws MachineException { + public MachineSource saveToSnapshot(String owner) throws MachineException { try { final String repository = generateRepository(); - final String tag = "latest"; if(!snapshotUseRegistry) { - commitContainer(owner, repository, tag); - return new DockerInstanceKey(repository, tag); + commitContainer(owner, repository, LATEST_TAG); + return new DockerMachineSource(repository).withTag(LATEST_TAG); } final String repositoryName = registry + '/' + repository; - commitContainer(owner, repositoryName, tag); + commitContainer(owner, repositoryName, LATEST_TAG); //TODO fix this workaround. Docker image is not visible after commit when using swarm Thread.sleep(2000); final ProgressLineFormatterImpl lineFormatter = new ProgressLineFormatterImpl(); final String digest = docker.push(PushParams.create(repository) - .withTag(tag) + .withTag(LATEST_TAG) .withRegistry(registry), progressMonitor -> { try { @@ -214,7 +218,7 @@ public InstanceKey saveToSnapshot(String owner) throws MachineException { } }); docker.removeImage(RemoveImageParams.create(repositoryName).withForce(false)); - return new DockerInstanceKey(repository, tag, registry, digest); + return new DockerMachineSource(repository).withRegistry(registry).withDigest(digest).withTag(LATEST_TAG); } catch (IOException ioEx) { throw new MachineException(ioEx); } catch (InterruptedException e) { diff --git a/plugins/plugin-docker/che-plugin-docker-machine/src/main/java/org/eclipse/che/plugin/docker/machine/DockerInstanceKey.java b/plugins/plugin-docker/che-plugin-docker-machine/src/main/java/org/eclipse/che/plugin/docker/machine/DockerInstanceKey.java deleted file mode 100644 index 16c94bc84bf..00000000000 --- a/plugins/plugin-docker/che-plugin-docker-machine/src/main/java/org/eclipse/che/plugin/docker/machine/DockerInstanceKey.java +++ /dev/null @@ -1,80 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2012-2016 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.plugin.docker.machine; - -import com.google.common.collect.ImmutableMap; - -import org.eclipse.che.api.machine.server.spi.impl.InstanceKeyImpl; -import org.eclipse.che.api.machine.server.spi.InstanceKey; - -/** - * Set of helper methods that identifies docker image properties - * - * @author Sergii Kabashnyuk - */ -public class DockerInstanceKey extends InstanceKeyImpl { - public static final String REPOSITORY = "repository"; - public static final String TAG = "tag"; - public static final String REGISTRY = "registry"; - public static final String DIGEST = "digest"; - - public DockerInstanceKey(InstanceKey key) { - super(key); - } - - public DockerInstanceKey(String repository, String tag, String registry, String digest) { - super(ImmutableMap.of(REPOSITORY, repository, TAG, tag, REGISTRY, registry, DIGEST, digest)); - } - - public DockerInstanceKey(String repository, String tag) { - super(ImmutableMap.of(REPOSITORY, repository, TAG, tag)); - } - - public String getRepository() { - return getFields().get(REPOSITORY); - } - - public String getTag() { - return getFields().get(TAG); - } - - public String getRegistry() { - return getFields().get(REGISTRY); - } - - public String getDigest() { - return getFields().get(DIGEST); - } - - /** - * Returns full name of docker image. - * - * It consists of registry, userspace, repository name, tag. - * E.g. docker-registry.company.com:5000/userspace1/my-repository:some-tag - */ - public String getFullName() { - final StringBuilder fullRepoId = new StringBuilder(); - if (getRegistry() != null) { - fullRepoId.append(getRegistry()).append('/'); - } - fullRepoId.append(getRepository()); - if (getTag() != null) { - fullRepoId.append(':').append(getTag()); - } - return fullRepoId.toString(); - } - - @Override - public String toString() { - return getFields().toString(); - } - -} diff --git a/plugins/plugin-docker/che-plugin-docker-machine/src/main/java/org/eclipse/che/plugin/docker/machine/DockerInstanceProvider.java b/plugins/plugin-docker/che-plugin-docker-machine/src/main/java/org/eclipse/che/plugin/docker/machine/DockerInstanceProvider.java index f79ae0512d7..4faefa783c7 100644 --- a/plugins/plugin-docker/che-plugin-docker-machine/src/main/java/org/eclipse/che/plugin/docker/machine/DockerInstanceProvider.java +++ b/plugins/plugin-docker/che-plugin-docker-machine/src/main/java/org/eclipse/che/plugin/docker/machine/DockerInstanceProvider.java @@ -18,6 +18,8 @@ import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.model.machine.Machine; +import org.eclipse.che.api.core.model.machine.MachineConfig; +import org.eclipse.che.api.core.model.machine.MachineSource; import org.eclipse.che.api.core.model.machine.Recipe; import org.eclipse.che.api.core.model.machine.ServerConf; import org.eclipse.che.api.core.util.FileCleaner; @@ -28,8 +30,8 @@ import org.eclipse.che.api.machine.server.exception.SnapshotException; import org.eclipse.che.api.machine.server.exception.UnsupportedRecipeException; import org.eclipse.che.api.machine.server.spi.Instance; -import org.eclipse.che.api.machine.server.spi.InstanceKey; import org.eclipse.che.api.machine.server.spi.InstanceProvider; +import org.eclipse.che.api.machine.server.util.RecipeRetriever; import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.commons.env.EnvironmentContext; import org.eclipse.che.commons.lang.IoUtil; @@ -42,7 +44,9 @@ import org.eclipse.che.plugin.docker.client.ProgressMonitor; import org.eclipse.che.plugin.docker.client.json.ContainerConfig; import org.eclipse.che.plugin.docker.client.json.HostConfig; +import org.eclipse.che.plugin.docker.client.params.PullParams; import org.eclipse.che.plugin.docker.client.params.RemoveImageParams; +import org.eclipse.che.plugin.docker.client.params.TagParams; import org.eclipse.che.plugin.docker.machine.node.DockerNode; import org.eclipse.che.plugin.docker.machine.node.WorkspaceFolderPathProvider; import org.slf4j.Logger; @@ -68,6 +72,7 @@ import static com.google.common.base.Strings.isNullOrEmpty; import static java.lang.String.format; +import static org.eclipse.che.plugin.docker.machine.DockerInstance.LATEST_TAG; /** * Docker implementation of {@link InstanceProvider} @@ -78,6 +83,16 @@ public class DockerInstanceProvider implements InstanceProvider { private static final Logger LOG = LoggerFactory.getLogger(DockerInstanceProvider.class); + /** + * dockerfile type support with recipe being a content of Dockerfile + */ + public static final String DOCKER_FILE_TYPE = "dockerfile"; + + /** + * image type support with recipe script being the name of the repository + image name + */ + public static final String DOCKER_IMAGE_TYPE = "image"; + private final DockerConnector docker; private final DockerInstanceStopDetector dockerInstanceStopDetector; private final DockerContainerNameGenerator containerNameGenerator; @@ -95,6 +110,7 @@ public class DockerInstanceProvider implements InstanceProvider { private final String[] allMachinesExtraHosts; private final String projectFolderPath; private final boolean snapshotUseRegistry; + private final RecipeRetriever recipeRetriever; @Inject public DockerInstanceProvider(DockerConnector docker, @@ -102,6 +118,7 @@ public DockerInstanceProvider(DockerConnector docker, DockerMachineFactory dockerMachineFactory, DockerInstanceStopDetector dockerInstanceStopDetector, DockerContainerNameGenerator containerNameGenerator, + RecipeRetriever recipeRetriever, @Named("machine.docker.dev_machine.machine_servers") Set devMachineServers, @Named("machine.docker.machine_servers") Set allMachinesServers, @Named("machine.docker.dev_machine.machine_volumes") Set devMachineSystemVolumes, @@ -118,10 +135,11 @@ public DockerInstanceProvider(DockerConnector docker, this.dockerMachineFactory = dockerMachineFactory; this.dockerInstanceStopDetector = dockerInstanceStopDetector; this.containerNameGenerator = containerNameGenerator; + this.recipeRetriever = recipeRetriever; this.workspaceFolderPathProvider = workspaceFolderPathProvider; this.doForcePullOnBuild = doForcePullOnBuild; this.privilegeMode = privilegeMode; - this.supportedRecipeTypes = Collections.singleton("dockerfile"); + this.supportedRecipeTypes = Sets.newHashSet(DOCKER_FILE_TYPE, DOCKER_IMAGE_TYPE); this.projectFolderPath = projectFolderPath; this.snapshotUseRegistry = snapshotUseRegistry; @@ -214,17 +232,61 @@ public Set getRecipeTypes() { return supportedRecipeTypes; } + + + /** + * Creates instance from scratch or by reusing a previously one by using specified {@link MachineSource} + * data in {@link MachineConfig}. + * + * @param machine + * machine description + * @param creationLogsOutput + * output for instance creation logs + * @return newly created {@link Instance} + * @throws UnsupportedRecipeException + * if specified {@code recipe} is not supported + * @throws InvalidRecipeException + * if {@code recipe} is invalid + * @throws NotFoundException + * if instance described by {@link MachineSource} doesn't exists + * @throws MachineException + * if other error occurs + */ @Override - public Instance createInstance(Recipe recipe, - Machine machine, - LineConsumer creationLogsOutput) throws MachineException, UnsupportedRecipeException { - final Dockerfile dockerfile = parseRecipe(recipe); + public Instance createInstance(final Machine machine, final LineConsumer creationLogsOutput) + throws UnsupportedRecipeException, InvalidRecipeException, NotFoundException, MachineException { + // based on machine source, do the right steps + MachineConfig machineConfig = machine.getConfig(); + MachineSource machineSource = machineConfig.getSource(); + String type = machineSource.getType(); + + // create container machine name final String userName = EnvironmentContext.getCurrent().getSubject().getUserName(); final String machineContainerName = containerNameGenerator.generateContainerName(machine.getWorkspaceId(), machine.getId(), userName, machine.getConfig().getName()); + // get recipe + // - it's a dockerfile type: + // - location defined : download this location and get script as recipe + // - content defined : use this content as recipe script + // - it's an image: + // - use location of image ([registry:port]/[:tag][@digest]) + final Recipe recipe; + if (DOCKER_FILE_TYPE.equals(type)) { + recipe = this.recipeRetriever.getRecipe(machineConfig); + } else if (DOCKER_IMAGE_TYPE.equals(type)) { + if (isNullOrEmpty(machineSource.getLocation())) { + throw new InvalidRecipeException(String.format("The type '%s' needs to be used with a location, not with any other parameter. Found '%s'.", type, machineSource)); + } + return createInstanceFromImage(machine, machineContainerName, creationLogsOutput); + } else { + // not supported + throw new UnsupportedRecipeException("The type '" + type + "' is not supported"); + } + final Dockerfile dockerfile = parseRecipe(recipe); + final String machineImageName = "eclipse-che/" + machineContainerName; final long memoryLimit = (long)machine.getConfig().getLimits().getRam() * 1024 * 1024; @@ -236,24 +298,19 @@ public Instance createInstance(Recipe recipe, creationLogsOutput); } - @Override - public Instance createInstance(InstanceKey instanceKey, - Machine machine, - LineConsumer creationLogsOutput) throws NotFoundException, MachineException { - final DockerInstanceKey dockerInstanceKey = new DockerInstanceKey(instanceKey); + protected Instance createInstanceFromImage(final Machine machine, String machineContainerName, + final LineConsumer creationLogsOutput) throws NotFoundException, MachineException { + final DockerMachineSource dockerMachineSource = new DockerMachineSource(machine.getConfig().getSource()); + if (snapshotUseRegistry) { - pullImage(dockerInstanceKey, creationLogsOutput); + pullImage(dockerMachineSource, creationLogsOutput); } - final String userName = EnvironmentContext.getCurrent().getSubject().getUserName(); - final String machineContainerName = containerNameGenerator.generateContainerName(machine.getWorkspaceId(), - machine.getId(), - userName, - machine.getConfig().getName()); + final String machineImageName = "eclipse-che/" + machineContainerName; - final String fullNameOfPulledImage = dockerInstanceKey.getFullName(); + final String fullNameOfPulledImage = dockerMachineSource.getLocation(false); try { // tag image with generated name to allow sysadmin recognize it - docker.tag(fullNameOfPulledImage, machineImageName, null); + docker.tag(TagParams.create(fullNameOfPulledImage, machineImageName)); } catch (IOException e) { LOG.error(e.getLocalizedMessage(), e); throw new MachineException("Can't create machine from snapshot."); @@ -271,7 +328,7 @@ public Instance createInstance(InstanceKey instanceKey, creationLogsOutput); } - private Dockerfile parseRecipe(Recipe recipe) throws InvalidRecipeException { + private Dockerfile parseRecipe(final Recipe recipe) throws InvalidRecipeException { final Dockerfile dockerfile = getDockerFile(recipe); if (dockerfile.getImages().isEmpty()) { throw new InvalidRecipeException("Unable build docker based machine, Dockerfile found but it doesn't contain base image."); @@ -283,7 +340,7 @@ private Dockerfile parseRecipe(Recipe recipe) throws InvalidRecipeException { return dockerfile; } - private Dockerfile getDockerFile(Recipe recipe) throws InvalidRecipeException { + private Dockerfile getDockerFile(final Recipe recipe) throws InvalidRecipeException { if (recipe.getScript() == null) { throw new InvalidRecipeException("Unable build docker based machine, recipe isn't set or doesn't provide Dockerfile and " + "no Dockerfile found in the list of files attached to this builder."); @@ -296,12 +353,12 @@ private Dockerfile getDockerFile(Recipe recipe) throws InvalidRecipeException { } } - protected void buildImage(Dockerfile dockerfile, + protected void buildImage(final Dockerfile dockerfile, final LineConsumer creationLogsOutput, - String imageName, - boolean doForcePullOnBuild, - long memoryLimit, - long memorySwapLimit) + final String imageName, + final boolean doForcePullOnBuild, + final long memoryLimit, + final long memorySwapLimit) throws MachineException { File workDir = null; @@ -337,15 +394,23 @@ protected void buildImage(Dockerfile dockerfile, } } - private void pullImage(DockerInstanceKey dockerInstanceKey, final LineConsumer creationLogsOutput) throws MachineException { - if (dockerInstanceKey.getRepository() == null) { - throw new MachineException("Machine creation failed. Snapshot state is invalid. Please, contact support."); + private void pullImage(final DockerMachineSource dockerMachineSource, final LineConsumer creationLogsOutput) throws MachineException { + if (dockerMachineSource.getRepository() == null) { + throw new MachineException(String.format("Machine creation failed. Machine source is invalid. No repository is defined. Found %s.", dockerMachineSource)); + } + + final String tag; + if (isNullOrEmpty(dockerMachineSource.getTag())) { + tag = LATEST_TAG; + } else { + tag = dockerMachineSource.getTag(); } + PullParams pullParams = PullParams.create(dockerMachineSource.getRepository()) + .withTag(tag) + .withRegistry(dockerMachineSource.getRegistry()); try { final ProgressLineFormatterImpl progressLineFormatter = new ProgressLineFormatterImpl(); - docker.pull(dockerInstanceKey.getRepository(), - dockerInstanceKey.getTag(), - dockerInstanceKey.getRegistry(), + docker.pull(pullParams, currentProgressStatus -> { try { creationLogsOutput.writeLine(progressLineFormatter.format(currentProgressStatus)); @@ -358,30 +423,45 @@ private void pullImage(DockerInstanceKey dockerInstanceKey, final LineConsumer c } } + + /** + * Removes snapshot of the instance in implementation specific way. + * + * @param machineSource + * contains implementation specific key of the snapshot of the instance that should be removed + * @throws SnapshotException + * if exception occurs on instance snapshot removal + */ @Override - public void removeInstanceSnapshot(InstanceKey instanceKey) throws SnapshotException { + public void removeInstanceSnapshot(final MachineSource machineSource) throws SnapshotException { // use registry API directly because docker doesn't have such API yet // https://github.com/docker/docker-registry/issues/45 - final DockerInstanceKey dockerInstanceKey = new DockerInstanceKey(instanceKey); + final DockerMachineSource dockerMachineSource; + try { + dockerMachineSource = new DockerMachineSource(machineSource); + } catch (MachineException e) { + throw new SnapshotException(e); + } + if (!snapshotUseRegistry) { try { - docker.removeImage(RemoveImageParams.create(dockerInstanceKey.getFullName())); + docker.removeImage(RemoveImageParams.create(dockerMachineSource.getLocation(false))); } catch (IOException ignore) { } return; } - final String registry = dockerInstanceKey.getRegistry(); - final String repository = dockerInstanceKey.getRepository(); + final String registry = dockerMachineSource.getRegistry(); + final String repository = dockerMachineSource.getRepository(); if (registry == null || repository == null) { - LOG.error("Failed to remove instance snapshot: invalid instance key: {}", instanceKey); + LOG.error("Failed to remove instance snapshot: invalid machine source: {}", dockerMachineSource); throw new SnapshotException("Snapshot removing failed. Snapshot attributes are not valid"); } try { URL url = UriBuilder.fromUri("http://" + registry) // TODO make possible to use https here .path("/v2/{repository}/manifests/{digest}") - .build(repository, dockerInstanceKey.getDigest()) + .build(repository, dockerMachineSource.getDigest()) .toURL(); final HttpURLConnection conn = (HttpURLConnection)url.openConnection(); try { @@ -408,10 +488,10 @@ public void removeInstanceSnapshot(InstanceKey instanceKey) throws SnapshotExcep } } - private Instance createInstance(String containerName, - Machine machine, - String imageName, - LineConsumer outputConsumer) + private Instance createInstance(final String containerName, + final Machine machine, + final String imageName, + final LineConsumer outputConsumer) throws MachineException { try { final Map> portsToExpose; diff --git a/plugins/plugin-docker/che-plugin-docker-machine/src/main/java/org/eclipse/che/plugin/docker/machine/DockerMachineSource.java b/plugins/plugin-docker/che-plugin-docker-machine/src/main/java/org/eclipse/che/plugin/docker/machine/DockerMachineSource.java new file mode 100644 index 00000000000..a171e7f0088 --- /dev/null +++ b/plugins/plugin-docker/che-plugin-docker-machine/src/main/java/org/eclipse/che/plugin/docker/machine/DockerMachineSource.java @@ -0,0 +1,210 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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.plugin.docker.machine; + +import org.eclipse.che.api.core.model.machine.MachineSource; +import org.eclipse.che.api.machine.server.exception.MachineException; +import org.eclipse.che.api.machine.server.model.impl.MachineSourceImpl; +import org.eclipse.che.plugin.docker.client.DockerFileException; +import org.eclipse.che.plugin.docker.client.parser.DockerImageIdentifier; +import org.eclipse.che.plugin.docker.client.parser.DockerImageIdentifierParser; + +import static org.eclipse.che.plugin.docker.machine.DockerInstanceProvider.DOCKER_IMAGE_TYPE; + +/** + * Set of helper methods that identifies docker image properties + * + * @author Florent Benoit + */ +public class DockerMachineSource extends MachineSourceImpl { + + /** + * Optional registry (like docker-registry.company.com:5000) + */ + private String registry; + + /** + * mandatory repository name (like codenvy/ubuntu_jdk8) + */ + private String repository; + + /** + * optional tag of the image (like latest) + */ + private String tag; + + /** + * optional digest of the image (like sha256@1234) + */ + private String digest; + + + /** + * Build a dedicated docker image source based on a given machine source object. + * @param machineSource the machine source used to parse data. + */ + public DockerMachineSource(MachineSource machineSource) throws MachineException { + super(); + + // check type + if (!DOCKER_IMAGE_TYPE.equals(machineSource.getType())) { + throw new MachineException("Docker machine source can only be built with '" + DOCKER_IMAGE_TYPE + "' type"); + } + setType(DOCKER_IMAGE_TYPE); + + // parse location + final DockerImageIdentifier dockerImageIdentifier; + try { + dockerImageIdentifier = DockerImageIdentifierParser.parse(machineSource.getLocation()); + } catch (DockerFileException e) { + throw new MachineException("Try to build a docker machine source with an invalid location/content. It is not in the expected format", e); + } + + // populate + this.registry = dockerImageIdentifier.getRegistry(); + this.repository = dockerImageIdentifier.getRepository(); + this.tag = dockerImageIdentifier.getTag(); + this.digest = dockerImageIdentifier.getDigest(); + } + + + /** + * Build image source based on given arguments + * @param repository as for example codenvy/ubuntu_jdk8 + */ + public DockerMachineSource(String repository) { + super(); + this.repository = repository; + setType(DOCKER_IMAGE_TYPE); + } + + /** + * Defines optional tag attribute + * @param tag as for example latest + * @return current instance + */ + public DockerMachineSource withTag(String tag) { + this.tag = tag; + return this; + } + + /** + * Defines optional tag attribute + * @param tag as for example latest + * @return current instance + */ + public void setTag(String tag) { + this.tag = tag; + } + + /** + * Defines optional registry attribute + * @param registry as for example docker-registry.company.com:5000 + */ + public void setRegistry(String registry) { + this.registry = registry; + } + + /** + * Defines optional registry attribute + * @param registry as for example docker-registry.company.com:5000 + * @return current instance + */ + public DockerMachineSource withRegistry(String registry) { + this.registry = registry; + return this; + } + + /** + * Defines optional digest attribute + * @param digest as for example sha256@1234 + */ + public void setDigest(String digest) { + this.digest = digest; + } + + /** + * Defines optional digest attribute + * @param digest as for example sha256@1234 + * @return current instance + */ + public DockerMachineSource withDigest(String digest) { + this.digest = digest; + return this; + } + + /** + * @return mandatory repository + */ + public String getRepository() { + return this.repository; + } + + /** + * @return optional tag + */ + public String getTag() { + return this.tag; + } + + /** + * @return optional registry + */ + public String getRegistry() { + return this.registry; + } + + /** + * @return optional digest + */ + public String getDigest() { + return this.digest; + } + + /** + * Returns location of this docker image, including all data that are required to reconstruct a new docker machine source. + */ + public String getLocation() { + return getLocation(true); + } + + /** + * Returns full name of docker image. + *

+ * It consists of registry, repository name, tag, digest. + * E.g. docker-registry.company.com:5000/my-repository:some-tag + * E.g. docker-registry.company.com:5000/my-repository@some-digest + * @param includeDigest if digest needs to be included or not + */ + public String getLocation(boolean includeDigest) { + final StringBuilder fullRepoId = new StringBuilder(); + + // optional registry is followed by / + if (getRegistry() != null) { + fullRepoId.append(getRegistry()).append('/'); + } + + // repository + fullRepoId.append(getRepository()); + + // optional tag (: prefix) + if (getTag() != null) { + fullRepoId.append(':').append(getTag()); + } + + // optional digest (@ prefix) + if (includeDigest && getDigest() != null) { + fullRepoId.append('@').append(getDigest()); + } + return fullRepoId.toString(); + } + +} diff --git a/plugins/plugin-docker/che-plugin-docker-machine/src/test/java/org/eclipse/che/plugin/docker/machine/DockerInstanceProviderTest.java b/plugins/plugin-docker/che-plugin-docker-machine/src/test/java/org/eclipse/che/plugin/docker/machine/DockerInstanceProviderTest.java index eb696a6e6a2..63751cfc6e6 100644 --- a/plugins/plugin-docker/che-plugin-docker-machine/src/test/java/org/eclipse/che/plugin/docker/machine/DockerInstanceProviderTest.java +++ b/plugins/plugin-docker/che-plugin-docker-machine/src/test/java/org/eclipse/che/plugin/docker/machine/DockerInstanceProviderTest.java @@ -10,12 +10,15 @@ *******************************************************************************/ package org.eclipse.che.plugin.docker.machine; +import com.google.common.collect.Sets; + import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.model.machine.Machine; +import org.eclipse.che.api.core.model.machine.MachineConfig; import org.eclipse.che.api.core.model.machine.MachineStatus; -import org.eclipse.che.api.core.model.machine.Recipe; import org.eclipse.che.api.core.model.machine.ServerConf; import org.eclipse.che.api.core.util.LineConsumer; +import org.eclipse.che.api.machine.server.exception.InvalidRecipeException; import org.eclipse.che.api.machine.server.exception.MachineException; import org.eclipse.che.api.machine.server.model.impl.LimitsImpl; import org.eclipse.che.api.machine.server.model.impl.MachineConfigImpl; @@ -23,6 +26,7 @@ import org.eclipse.che.api.machine.server.model.impl.MachineSourceImpl; import org.eclipse.che.api.machine.server.model.impl.ServerConfImpl; import org.eclipse.che.api.machine.server.recipe.RecipeImpl; +import org.eclipse.che.api.machine.server.util.RecipeRetriever; import org.eclipse.che.commons.env.EnvironmentContext; import org.eclipse.che.commons.subject.SubjectImpl; import org.eclipse.che.plugin.docker.client.DockerConnector; @@ -34,6 +38,7 @@ import org.eclipse.che.plugin.docker.client.json.HostConfig; import org.eclipse.che.plugin.docker.client.params.PullParams; import org.eclipse.che.plugin.docker.client.params.RemoveImageParams; +import org.eclipse.che.plugin.docker.client.params.TagParams; import org.eclipse.che.plugin.docker.machine.node.DockerNode; import org.eclipse.che.plugin.docker.machine.node.WorkspaceFolderPathProvider; import org.mockito.ArgumentCaptor; @@ -45,7 +50,6 @@ import org.testng.annotations.Listeners; import org.testng.annotations.Test; -import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -57,13 +61,13 @@ import java.util.stream.Collectors; import static java.util.Arrays.asList; +import static org.eclipse.che.plugin.docker.machine.DockerInstanceProvider.DOCKER_FILE_TYPE; +import static org.eclipse.che.plugin.docker.machine.DockerInstanceProvider.DOCKER_IMAGE_TYPE; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyBoolean; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.anyVararg; import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.atMost; -import static org.mockito.Mockito.calls; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; @@ -110,6 +114,9 @@ public class DockerInstanceProviderTest { @Captor private ArgumentCaptor containerConfigArgumentCaptor; + @Mock + private RecipeRetriever recipeRetriever; + private DockerInstanceProvider dockerInstanceProvider; @BeforeMethod @@ -121,6 +128,7 @@ public void setUp() throws Exception { dockerMachineFactory, dockerInstanceStopDetector, containerNameGenerator, + recipeRetriever, Collections.emptySet(), Collections.emptySet(), Collections.emptySet(), @@ -139,6 +147,9 @@ public void setUp() throws Exception { envCont.setWorkspaceId(WORKSPACE_ID); EnvironmentContext.setCurrent(envCont); + + when(recipeRetriever.getRecipe(any(MachineConfig.class))).thenReturn(new RecipeImpl().withType(DOCKER_FILE_TYPE).withScript("FROM codenvy")); + when(dockerMachineFactory.createNode(anyString(), anyString())).thenReturn(dockerNode); when(dockerConnector.createContainer(any(ContainerConfig.class), anyString())) .thenReturn(new ContainerCreated(CONTAINER_ID, new String[0])); @@ -156,7 +167,7 @@ public void shouldReturnTypeDocker() throws Exception { @Test public void shouldReturnRecipeTypesDockerfile() throws Exception { - assertEquals(dockerInstanceProvider.getRecipeTypes(), Collections.singleton("dockerfile")); + assertEquals(dockerInstanceProvider.getRecipeTypes(), Sets.newHashSet(DOCKER_FILE_TYPE, DOCKER_IMAGE_TYPE)); } // TODO add tests for instance snapshot removal @@ -191,8 +202,9 @@ public void shouldPullDockerImageOnInstanceCreationFromSnapshot() throws Excepti createInstanceFromSnapshot(repo, tag, registry); + PullParams pullParams = PullParams.create(repo).withRegistry(registry).withTag(tag); - verify(dockerConnector).pull(eq(repo), eq(tag), eq(registry), any(ProgressMonitor.class)); + verify(dockerConnector).pull(eq(pullParams), any(ProgressMonitor.class)); } @Test @@ -201,8 +213,11 @@ public void shouldUseLocalImageOnInstanceCreationFromSnapshot() throws Exception final String tag = "latest"; dockerInstanceProvider = getDockerInstanceProvider(false); - dockerInstanceProvider.createInstance(new DockerInstanceKey(repo, tag), - getMachineBuilder().build(), + MachineImpl machine = getMachineBuilder().build(); + final MachineSourceImpl machineSource = new DockerMachineSource(repo).withTag(tag); + machine.getConfig().setSource(machineSource); + + dockerInstanceProvider.createInstance(machine, LineConsumer.DEV_NULL); verify(dockerConnector, never()).pull(anyString(), @@ -215,12 +230,12 @@ public void shouldUseLocalImageOnInstanceCreationFromSnapshot() throws Exception public void shouldRemoveLocalImageDuringRemovalOfSnapshot() throws Exception { final String repo = "repo"; final String tag = "latest"; - final DockerInstanceKey instanceKey = new DockerInstanceKey(repo, tag); + final DockerMachineSource dockerMachineSource = new DockerMachineSource(repo).withTag(tag); dockerInstanceProvider = getDockerInstanceProvider(false); - dockerInstanceProvider.removeInstanceSnapshot(instanceKey); + dockerInstanceProvider.removeInstanceSnapshot(dockerMachineSource); - verify(dockerConnector, times(1)).removeImage(RemoveImageParams.create(instanceKey.getFullName())); + verify(dockerConnector, times(1)).removeImage(RemoveImageParams.create(dockerMachineSource.getLocation(false))); } @Test @@ -233,12 +248,11 @@ public void shouldReTagBuiltImageWithPredictableOnInstanceCreationFromRecipe() t String repo = "repo1"; String tag = "tag1"; String registry = "registry1"; - + TagParams tagParams = TagParams.create(registry + "/" + repo + ":" + tag, "eclipse-che/" + generatedContainerId); createInstanceFromSnapshot(repo, tag, registry); - - verify(dockerConnector).tag(eq(registry + "/" + repo + ":" + tag), eq("eclipse-che/" + generatedContainerId), eq(null)); + verify(dockerConnector).tag(eq(tagParams)); verify(dockerConnector).removeImage(eq(registry + "/" + repo + ":" + tag), eq(false)); } @@ -288,6 +302,7 @@ public void shouldCreateContainerWithPrivilegeMode() throws Exception { dockerMachineFactory, dockerInstanceStopDetector, containerNameGenerator, + recipeRetriever, Collections.emptySet(), Collections.emptySet(), Collections.emptySet(), @@ -322,7 +337,7 @@ public void shouldCallCreationDockerInstanceWithFactoryOnCreateInstanceFromSnaps eq(USER_NAME), eq(MACHINE_NAME)); - final MachineSourceImpl machineSource = new MachineSourceImpl("type", "location"); + final MachineSourceImpl machineSource = new MachineSourceImpl("type").setLocation("location"); final MachineImpl machine = new MachineImpl(new MachineConfigImpl(false, MACHINE_NAME, @@ -350,6 +365,8 @@ public void shouldCallCreationDockerInstanceWithFactoryOnCreateInstanceFromSnaps any(LineConsumer.class)); } + + @Test public void shouldCallCreationDockerInstanceWithFactoryOnCreateInstanceFromRecipe() throws Exception { String generatedContainerId = "genContainerId"; @@ -358,8 +375,7 @@ public void shouldCallCreationDockerInstanceWithFactoryOnCreateInstanceFromRecip eq(USER_NAME), eq(MACHINE_NAME)); - final MachineSourceImpl machineSource = new MachineSourceImpl("type", "location"); - final Recipe recipe = new RecipeImpl().withType("Dockerfile").withScript("FROM busybox"); + final MachineSourceImpl machineSource = new MachineSourceImpl(DOCKER_FILE_TYPE).setLocation("location"); final MachineImpl machine = new MachineImpl(new MachineConfigImpl(false, MACHINE_NAME, @@ -376,7 +392,7 @@ public void shouldCallCreationDockerInstanceWithFactoryOnCreateInstanceFromRecip MachineStatus.CREATING, null); - createInstanceFromRecipe(recipe, machine); + createInstanceFromRecipe(machine); verify(dockerMachineFactory).createInstance(eq(machine), @@ -472,6 +488,15 @@ public void shouldDisableSwapMemorySizeInContainersOnInstanceCreationFromRecipe( assertEquals(createContainerCaptor.getValue().getHostConfig().getMemorySwap(), -1); } + + @Test(expectedExceptions = InvalidRecipeException.class) + public void checkExceptionIfImageWithContent() throws Exception { + MachineImpl machine = getMachineBuilder().build(); + machine.getConfig().getSource().setContent("hello"); + machine.getConfig().getSource().setType(DOCKER_IMAGE_TYPE); + createInstanceFromRecipe(machine); + } + @Test public void shouldDisableSwapMemorySizeInContainersOnInstanceCreationFromSnapshot() throws Exception { createInstanceFromSnapshot(); @@ -502,6 +527,7 @@ public void shouldExposeCommonAndDevPortsToContainerOnDevInstanceCreationFromRec dockerMachineFactory, dockerInstanceStopDetector, containerNameGenerator, + recipeRetriever, devServers, commonServers, Collections.emptySet(), @@ -541,6 +567,7 @@ public void shouldExposeOnlyCommonPortsToContainerOnNonDevInstanceCreationFromRe dockerMachineFactory, dockerInstanceStopDetector, containerNameGenerator, + recipeRetriever, Collections.emptySet(), commonServers, Collections.emptySet(), @@ -586,6 +613,7 @@ public void shouldExposeCommonAndDevPortsToContainerOnDevInstanceCreationFromSna dockerMachineFactory, dockerInstanceStopDetector, containerNameGenerator, + recipeRetriever, devServers, commonServers, Collections.emptySet(), @@ -625,6 +653,7 @@ public void shouldExposeOnlyCommonPortsToContainerOnNonDevInstanceCreationFromSn dockerMachineFactory, dockerInstanceStopDetector, containerNameGenerator, + recipeRetriever, Collections.emptySet(), commonServers, Collections.emptySet(), @@ -665,6 +694,7 @@ public void shouldAddServersConfsPortsFromMachineConfigToExposedPortsOnNonDevIns dockerMachineFactory, dockerInstanceStopDetector, containerNameGenerator, + recipeRetriever, Collections.emptySet(), Collections.emptySet(), Collections.emptySet(), @@ -708,6 +738,7 @@ public void shouldAddServersConfsPortsFromMachineConfigToExposedPortsOnNonDevIns dockerMachineFactory, dockerInstanceStopDetector, containerNameGenerator, + recipeRetriever, Collections.emptySet(), Collections.emptySet(), Collections.emptySet(), @@ -751,6 +782,7 @@ public void shouldAddServersConfsPortsFromMachineConfigToExposedPortsOnDevInstan dockerMachineFactory, dockerInstanceStopDetector, containerNameGenerator, + recipeRetriever, Collections.emptySet(), Collections.emptySet(), Collections.emptySet(), @@ -794,6 +826,7 @@ public void shouldAddServersConfsPortsFromMachineConfigToExposedPortsOnDevInstan dockerMachineFactory, dockerInstanceStopDetector, containerNameGenerator, + recipeRetriever, Collections.emptySet(), Collections.emptySet(), Collections.emptySet(), @@ -832,6 +865,7 @@ public void shouldBindProjectsFSVolumeToContainerOnDevInstanceCreationFromRecipe dockerMachineFactory, dockerInstanceStopDetector, containerNameGenerator, + recipeRetriever, Collections.emptySet(), Collections.emptySet(), Collections.emptySet(), @@ -870,6 +904,7 @@ public void shouldBindProjectsFSVolumeToContainerOnDevInstanceCreationFromSnapsh dockerMachineFactory, dockerInstanceStopDetector, containerNameGenerator, + recipeRetriever, Collections.emptySet(), Collections.emptySet(), Collections.emptySet(), @@ -907,6 +942,7 @@ public void shouldNotBindProjectsFSVolumeToContainerOnNonDevInstanceCreationFrom dockerMachineFactory, dockerInstanceStopDetector, containerNameGenerator, + recipeRetriever, Collections.emptySet(), Collections.emptySet(), Collections.emptySet(), @@ -944,6 +980,7 @@ public void shouldNotBindProjectsFSVolumeToContainerOnNonDevInstanceCreationFrom dockerMachineFactory, dockerInstanceStopDetector, containerNameGenerator, + recipeRetriever, Collections.emptySet(), Collections.emptySet(), Collections.emptySet(), @@ -988,6 +1025,7 @@ public void shouldBindCommonAndDevVolumesToContainerOnDevInstanceCreationFromRec dockerMachineFactory, dockerInstanceStopDetector, containerNameGenerator, + recipeRetriever, Collections.emptySet(), Collections.emptySet(), devVolumes, @@ -1033,6 +1071,7 @@ public void shouldBindCommonAndDevVolumesToContainerOnDevInstanceCreationFromSna dockerMachineFactory, dockerInstanceStopDetector, containerNameGenerator, + recipeRetriever, Collections.emptySet(), Collections.emptySet(), devVolumes, @@ -1077,6 +1116,7 @@ public void shouldBindCommonVolumesOnlyToContainerOnNonDevInstanceCreationFromRe dockerMachineFactory, dockerInstanceStopDetector, containerNameGenerator, + recipeRetriever, Collections.emptySet(), Collections.emptySet(), devVolumes, @@ -1119,6 +1159,7 @@ public void shouldAddExtraHostOnDevInstanceCreationFromRecipe() throws Exception dockerMachineFactory, dockerInstanceStopDetector, containerNameGenerator, + recipeRetriever, Collections.emptySet(), Collections.emptySet(), devVolumes, @@ -1161,6 +1202,7 @@ public void shouldAddExtraHostOnDevInstanceCreationFromSnapshot() throws Excepti dockerMachineFactory, dockerInstanceStopDetector, containerNameGenerator, + recipeRetriever, Collections.emptySet(), Collections.emptySet(), devVolumes, @@ -1203,6 +1245,7 @@ public void shouldAddExtraHostOnNonDevInstanceCreationFromRecipe() throws Except dockerMachineFactory, dockerInstanceStopDetector, containerNameGenerator, + recipeRetriever, Collections.emptySet(), Collections.emptySet(), devVolumes, @@ -1245,6 +1288,7 @@ public void shouldAddExtraHostOnNonDevInstanceCreationFromSnapshot() throws Exce dockerMachineFactory, dockerInstanceStopDetector, containerNameGenerator, + recipeRetriever, Collections.emptySet(), Collections.emptySet(), devVolumes, @@ -1289,6 +1333,7 @@ public void shouldBindCommonVolumesOnlyToContainerOnNonDevInstanceCreationFromSn dockerMachineFactory, dockerInstanceStopDetector, containerNameGenerator, + recipeRetriever, Collections.emptySet(), Collections.emptySet(), devVolumes, @@ -1402,6 +1447,7 @@ public void shouldAddCommonAndDevEnvVariablesToContainerOnDevInstanceCreationFro dockerMachineFactory, dockerInstanceStopDetector, containerNameGenerator, + recipeRetriever, Collections.emptySet(), Collections.emptySet(), Collections.emptySet(), @@ -1436,6 +1482,7 @@ public void shouldNotAddDevEnvToCommonEnvVariablesToContainerOnNonDevInstanceCre dockerMachineFactory, dockerInstanceStopDetector, containerNameGenerator, + recipeRetriever, Collections.emptySet(), Collections.emptySet(), Collections.emptySet(), @@ -1475,6 +1522,7 @@ public void shouldAddCommonAndDevEnvVariablesToContainerOnDevInstanceCreationFro dockerMachineFactory, dockerInstanceStopDetector, containerNameGenerator, + recipeRetriever, Collections.emptySet(), Collections.emptySet(), Collections.emptySet(), @@ -1509,6 +1557,7 @@ public void shouldNotAddDevEnvToCommonEnvVariablesToContainerOnNonDevInstanceCre dockerMachineFactory, dockerInstanceStopDetector, containerNameGenerator, + recipeRetriever, Collections.emptySet(), Collections.emptySet(), Collections.emptySet(), @@ -1545,6 +1594,7 @@ public void shouldAddEnvVarsFromMachineConfigToContainerOnNonDevInstanceCreation dockerMachineFactory, dockerInstanceStopDetector, containerNameGenerator, + recipeRetriever, Collections.emptySet(), Collections.emptySet(), Collections.emptySet(), @@ -1589,6 +1639,7 @@ public void shouldAddEnvVarsFromMachineConfigToContainerOnDevInstanceCreationFro dockerMachineFactory, dockerInstanceStopDetector, containerNameGenerator, + recipeRetriever, Collections.emptySet(), Collections.emptySet(), Collections.emptySet(), @@ -1633,6 +1684,7 @@ public void shouldAddEnvVarsFromMachineConfigToContainerOnNonDevInstanceCreation dockerMachineFactory, dockerInstanceStopDetector, containerNameGenerator, + recipeRetriever, Collections.emptySet(), Collections.emptySet(), Collections.emptySet(), @@ -1677,6 +1729,7 @@ public void shouldAddEnvVarsFromMachineConfigToContainerOnDevInstanceCreationFro dockerMachineFactory, dockerInstanceStopDetector, containerNameGenerator, + recipeRetriever, Collections.emptySet(), Collections.emptySet(), Collections.emptySet(), @@ -1733,13 +1786,11 @@ private void createInstanceFromRecipe(int memorySizeInMB) throws Exception { } private void createInstanceFromSnapshot(String repo, String tag, String registry) throws NotFoundException, MachineException { - createInstanceFromSnapshot(getMachineBuilder().build(), new DockerInstanceKey(repo, tag, registry, "digest")); + createInstanceFromSnapshot(getMachineBuilder().build(), new DockerMachineSource(repo).withTag(tag).withRegistry(registry).withDigest("digest")); } private void createInstanceFromRecipe(Machine machine) throws Exception { - dockerInstanceProvider.createInstance(new RecipeImpl().withType("Dockerfile") - .withScript("FROM busybox"), - machine, + dockerInstanceProvider.createInstance(machine, LineConsumer.DEV_NULL); } @@ -1766,25 +1817,18 @@ private void createInstanceFromSnapshot(boolean isDev, String workspaceId) throw .build()); } - private void createInstanceFromRecipe(Recipe recipe, Machine machine) throws Exception { - dockerInstanceProvider.createInstance(recipe, - machine, + private void createInstanceFromSnapshot(MachineImpl machine) throws NotFoundException, MachineException { + DockerMachineSource machineSource = new DockerMachineSource("repo").withRegistry("registry").withDigest("digest"); + machine.getConfig().setSource(machineSource); + dockerInstanceProvider.createInstance(machine, LineConsumer.DEV_NULL); } - private void createInstanceFromSnapshot(Machine machine) throws NotFoundException, MachineException { - dockerInstanceProvider.createInstance(new DockerInstanceKey("repo", - "tag", - "registry", - "digest"), - machine, - LineConsumer.DEV_NULL); - } + private void createInstanceFromSnapshot(MachineImpl machine, DockerMachineSource dockerMachineSource) throws NotFoundException, + MachineException { - private void createInstanceFromSnapshot(Machine machine, DockerInstanceKey dockerInstanceKey) throws NotFoundException, - MachineException { - dockerInstanceProvider.createInstance(dockerInstanceKey, - machine, + machine.getConfig().setSource(dockerMachineSource); + dockerInstanceProvider.createInstance(machine, LineConsumer.DEV_NULL); } @@ -1804,6 +1848,7 @@ private DockerInstanceProvider getDockerInstanceProvider(boolean snapshotUseRegi dockerMachineFactory, dockerInstanceStopDetector, containerNameGenerator, + recipeRetriever, Collections.emptySet(), Collections.emptySet(), Collections.emptySet(), @@ -1822,7 +1867,7 @@ private MachineConfigImpl.MachineConfigImplBuilder getMachineConfigBuilder() { return MachineConfigImpl.builder().fromConfig(new MachineConfigImpl(false, MACHINE_NAME, "machineType", - new MachineSourceImpl("source type", "source location"), + new MachineSourceImpl(DOCKER_FILE_TYPE).setContent("FROM codenvy"), new LimitsImpl(MEMORY_LIMIT_MB), asList(new ServerConfImpl("ref1", "8080", diff --git a/plugins/plugin-docker/che-plugin-docker-machine/src/test/java/org/eclipse/che/plugin/docker/machine/DockerInstanceTest.java b/plugins/plugin-docker/che-plugin-docker-machine/src/test/java/org/eclipse/che/plugin/docker/machine/DockerInstanceTest.java index 7ffc5d30259..f9e89ae3118 100644 --- a/plugins/plugin-docker/che-plugin-docker-machine/src/test/java/org/eclipse/che/plugin/docker/machine/DockerInstanceTest.java +++ b/plugins/plugin-docker/che-plugin-docker-machine/src/test/java/org/eclipse/che/plugin/docker/machine/DockerInstanceTest.java @@ -12,6 +12,7 @@ import org.eclipse.che.api.core.model.machine.Machine; import org.eclipse.che.api.core.model.machine.MachineConfig; +import org.eclipse.che.api.core.model.machine.MachineSource; import org.eclipse.che.api.core.model.machine.MachineStatus; import org.eclipse.che.api.core.util.LineConsumer; import org.eclipse.che.api.machine.server.exception.MachineException; @@ -19,7 +20,6 @@ import org.eclipse.che.api.machine.server.model.impl.MachineConfigImpl; import org.eclipse.che.api.machine.server.model.impl.MachineImpl; import org.eclipse.che.api.machine.server.model.impl.MachineSourceImpl; -import org.eclipse.che.api.machine.server.spi.InstanceKey; import org.eclipse.che.plugin.docker.client.DockerConnector; import org.eclipse.che.plugin.docker.client.Exec; import org.eclipse.che.plugin.docker.client.LogMessage; @@ -47,7 +47,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertTrue; /** @@ -150,10 +150,13 @@ public void shouldCreateDockerImageLocally() throws Exception { @Test public void shouldSaveDockerInstanceStateIntoLocalImage() throws Exception { - final InstanceKey result = dockerInstance.saveToSnapshot(OWNER); + final MachineSource result = dockerInstance.saveToSnapshot(OWNER); - assertEquals(result.getFields().get("tag"), TAG); - assertFalse(result.getFields().containsKey("registry")); + assertTrue(result instanceof DockerMachineSource); + DockerMachineSource dockerMachineSource = (DockerMachineSource) result; + assertEquals(dockerMachineSource.getTag(), TAG); + assertNotNull(dockerMachineSource.getRepository()); + assertEquals(dockerMachineSource.getRegistry(), null); } @Test @@ -162,11 +165,13 @@ public void shouldSaveDockerInstanceStateIntoRepository() throws Exception { dockerInstance = getDockerInstance(getMachine(), REGISTRY, CONTAINER, IMAGE, true); when(dockerConnectorMock.push(any(PushParams.class), any(ProgressMonitor.class))).thenReturn(digest); - final InstanceKey result = dockerInstance.saveToSnapshot(OWNER); + final MachineSource result = dockerInstance.saveToSnapshot(OWNER); - assertEquals(result.getFields().get("tag"), TAG); - assertEquals(result.getFields().get("digest"), digest); - assertTrue(result.getFields().containsKey("registry")); + assertTrue(result instanceof DockerMachineSource); + DockerMachineSource dockerMachineSource = (DockerMachineSource) result; + assertEquals(dockerMachineSource.getTag(), TAG); + assertEquals(dockerMachineSource.getDigest(), digest); + assertEquals(dockerMachineSource.getRegistry(), REGISTRY); } @Test(expectedExceptions = MachineException.class) @@ -235,7 +240,7 @@ private MachineConfig getMachineConfig(boolean isDev, String name, String type) .setDev(isDev) .setName(name) .setType(type) - .setSource(new MachineSourceImpl("docker", "location")) + .setSource(new MachineSourceImpl("docker").setLocation("location")) .setLimits(new LimitsImpl(64)) .build(); } diff --git a/plugins/plugin-docker/che-plugin-docker-machine/src/test/java/org/eclipse/che/plugin/docker/machine/DockerMachineSourceTest.java b/plugins/plugin-docker/che-plugin-docker-machine/src/test/java/org/eclipse/che/plugin/docker/machine/DockerMachineSourceTest.java new file mode 100644 index 00000000000..8e04f615f4f --- /dev/null +++ b/plugins/plugin-docker/che-plugin-docker-machine/src/test/java/org/eclipse/che/plugin/docker/machine/DockerMachineSourceTest.java @@ -0,0 +1,99 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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.plugin.docker.machine; + +import org.eclipse.che.api.core.model.machine.MachineSource; +import org.eclipse.che.api.machine.server.exception.MachineException; +import org.mockito.Mock; +import org.mockito.testng.MockitoTestNGListener; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Listeners; +import org.testng.annotations.Test; + +import static org.mockito.Mockito.when; +import static org.testng.Assert.assertEquals; + +/** + * Check if MachineSource object used for Docker is working as expected. + * + * @author Florent Benoit + */ +@Listeners(MockitoTestNGListener.class) +public class DockerMachineSourceTest { + + @Mock + private MachineSource machineSource; + + @DataProvider(name = "image-ids") + public Object[][] messageProvider() { + + return new String[][]{ + {"docker-registry.company.com:5000/my-repository:some-tag", "docker-registry.company.com:5000", "my-repository", "some-tag", + null}, + {"my-repository", null, "my-repository", null, null}, + {"my-repository:tag", null, "my-repository", "tag", null}, + {"docker-registry.company.com:5000/my-repository", "docker-registry.company.com:5000", "my-repository", null, null}, + {"docker-registry.company.com:5000/my-repository:mytag@digest123", "docker-registry.company.com:5000", "my-repository", + "mytag", "digest123"}, + {"ubuntu@sha256:45b23dee08af5e43a7fea6c4cf9c25ccf269ee113168c19722f87876677c5cb2", null, "ubuntu", null, + "sha256:45b23dee08af5e43a7fea6c4cf9c25ccf269ee113168c19722f87876677c5cb2"}, + {"docker-registry:5000/ubuntu@sha256:45b23dee08af5e43a7fea6c4cf9c25ccf269ee113168c19722f87876677c5cb2", + "docker-registry:5000", "ubuntu", null, "sha256:45b23dee08af5e43a7fea6c4cf9c25ccf269ee113168c19722f87876677c5cb2"}, + }; + } + + + /** + * Check that all the constructor are valid and not throwing exception based on data provider + */ + @Test(dataProvider = "image-ids") + public void testConstructors(String location, String registry, String repository, String tag, String digest) throws MachineException { + DockerMachineSource source1 = new DockerMachineSource(repository).withTag(tag).withRegistry(registry).withDigest(digest); + assertEquals(source1.getLocation(), location); + + DockerMachineSource source2 = new DockerMachineSource(source1); + assertEquals(source2.getLocation(), location); + assertEquals(source2.getRegistry(), registry); + assertEquals(source2.getRepository(), repository); + assertEquals(source2.getTag(), tag); + assertEquals(source2.getDigest(), digest); + + + DockerMachineSource source3 = new DockerMachineSource(repository); + source3.setTag(tag); + source3.setRegistry(registry); + source3.setDigest(digest); + assertEquals(source3.getLocation(), location); + + + } + + + /** + * Check valid source type + */ + @Test(expectedExceptions = MachineException.class) + public void testInvalidSourceType() throws MachineException { + when(machineSource.getType()).thenReturn("invalid"); + new DockerMachineSource(machineSource); + } + + /** + * Check invalid format + */ + @Test(expectedExceptions = MachineException.class) + public void testInvalidFormat() throws MachineException { + when(machineSource.getType()).thenReturn("image"); + when(machineSource.getLocation()).thenReturn("@image"); + new DockerMachineSource(machineSource); + } + +} diff --git a/plugins/plugin-machine/che-plugin-machine-ext-client/src/main/java/org/eclipse/che/ide/extension/machine/client/machine/MachineManagerImpl.java b/plugins/plugin-machine/che-plugin-machine-ext-client/src/main/java/org/eclipse/che/ide/extension/machine/client/machine/MachineManagerImpl.java index 8c7d70c3698..637d37d0dbd 100644 --- a/plugins/plugin-machine/che-plugin-machine-ext-client/src/main/java/org/eclipse/che/ide/extension/machine/client/machine/MachineManagerImpl.java +++ b/plugins/plugin-machine/che-plugin-machine-ext-client/src/main/java/org/eclipse/che/ide/extension/machine/client/machine/MachineManagerImpl.java @@ -14,12 +14,8 @@ import com.google.inject.Singleton; import com.google.web.bindery.event.shared.EventBus; +import org.eclipse.che.api.core.model.machine.MachineSource; import org.eclipse.che.api.core.rest.shared.dto.LinkParameter; -import org.eclipse.che.ide.api.machine.DevMachine; -import org.eclipse.che.ide.api.machine.MachineManager; -import org.eclipse.che.ide.api.machine.MachineServiceClient; -import org.eclipse.che.ide.api.machine.OutputMessageUnmarshaller; -import org.eclipse.che.ide.api.machine.events.DevMachineStateEvent; import org.eclipse.che.api.machine.shared.dto.LimitsDto; import org.eclipse.che.api.machine.shared.dto.MachineConfigDto; import org.eclipse.che.api.machine.shared.dto.MachineDto; @@ -28,11 +24,16 @@ import org.eclipse.che.api.promises.client.Operation; import org.eclipse.che.api.promises.client.OperationException; import org.eclipse.che.api.promises.client.Promise; +import org.eclipse.che.ide.api.app.AppContext; +import org.eclipse.che.ide.api.machine.DevMachine; +import org.eclipse.che.ide.api.machine.MachineManager; +import org.eclipse.che.ide.api.machine.MachineServiceClient; +import org.eclipse.che.ide.api.machine.OutputMessageUnmarshaller; +import org.eclipse.che.ide.api.machine.events.DevMachineStateEvent; +import org.eclipse.che.ide.api.parts.PerspectiveManager; import org.eclipse.che.ide.api.workspace.WorkspaceServiceClient; import org.eclipse.che.ide.api.workspace.event.WorkspaceStoppedEvent; import org.eclipse.che.ide.api.workspace.event.WorkspaceStoppedHandler; -import org.eclipse.che.ide.api.app.AppContext; -import org.eclipse.che.ide.api.parts.PerspectiveManager; import org.eclipse.che.ide.dto.DtoFactory; import org.eclipse.che.ide.extension.machine.client.machine.MachineStatusNotifier.RunningListener; import org.eclipse.che.ide.extension.machine.client.machine.console.MachineConsolePresenter; @@ -45,11 +46,11 @@ import org.eclipse.che.ide.websocket.rest.SubscriptionHandler; import org.eclipse.che.ide.websocket.rest.Unmarshallable; +import static org.eclipse.che.api.machine.shared.Constants.LINK_REL_GET_MACHINE_LOGS_CHANNEL; +import static org.eclipse.che.api.machine.shared.Constants.LINK_REL_GET_MACHINE_STATUS_CHANNEL; import static org.eclipse.che.ide.api.machine.MachineManager.MachineOperationType.DESTROY; import static org.eclipse.che.ide.api.machine.MachineManager.MachineOperationType.RESTART; import static org.eclipse.che.ide.api.machine.MachineManager.MachineOperationType.START; -import static org.eclipse.che.api.machine.shared.Constants.LINK_REL_GET_MACHINE_LOGS_CHANNEL; -import static org.eclipse.che.api.machine.shared.Constants.LINK_REL_GET_MACHINE_STATUS_CHANNEL; import static org.eclipse.che.ide.extension.machine.client.perspective.OperationsPerspective.OPERATIONS_PERSPECTIVE_ID; import static org.eclipse.che.ide.ui.loaders.initialization.InitialLoadingInfo.Operations.MACHINE_BOOTING; import static org.eclipse.che.ide.ui.loaders.initialization.OperationInfo.Status.ERROR; @@ -189,11 +190,11 @@ public void onMachineRunning(MachineStateEvent event) { @Override public void onMachineDestroyed(MachineStateEvent event) { if (isMachineRestarting) { - final String recipeUrl = machineState.getConfig().getSource().getLocation(); + final MachineSource machineSource = machineState.getConfig().getSource(); final String displayName = machineState.getConfig().getName(); final boolean isDev = machineState.getConfig().isDev(); - startMachine(recipeUrl, displayName, isDev, RESTART, "dockerfile", "docker"); + startMachine(asDto(machineSource), displayName, isDev, RESTART, "docker"); isMachineRestarting = false; } @@ -208,6 +209,17 @@ public void apply(Void arg) throws OperationException { }); } + /** + * Converts {@link MachineSource} to {@link MachineSourceDto}. + */ + public MachineSourceDto asDto(MachineSource source) { + return this.dtoFactory.createDto(MachineSourceDto.class) + .withType(source.getType()) + .withLocation(source.getLocation()) + .withContent(source.getContent()); + } + + /** Start new machine. */ @Override public void startMachine(String recipeURL, String displayName) { @@ -220,6 +232,8 @@ public void startDevMachine(String recipeURL, String displayName) { startMachine(recipeURL, displayName, true, START, "dockerfile", "docker"); } + + /** * @param recipeURL * @param displayName @@ -236,17 +250,32 @@ private void startMachine(final String recipeURL, final MachineOperationType operationType, final String sourceType, final String machineType) { + MachineSourceDto sourceDto = dtoFactory.createDto(MachineSourceDto.class).withType(sourceType).withLocation(recipeURL); + startMachine(sourceDto, displayName, isDev, operationType, machineType); + } + /** + * @param machineSourceDto + * @param displayName + * @param isDev + * @param operationType + * @param machineType + * "docker" or "ssh" + */ + private void startMachine(final MachineSourceDto machineSourceDto, + final String displayName, + final boolean isDev, + final MachineOperationType operationType, + final String machineType) { LimitsDto limitsDto = dtoFactory.createDto(LimitsDto.class).withRam(1024); if (isDev) { limitsDto.withRam(3072); } - MachineSourceDto sourceDto = dtoFactory.createDto(MachineSourceDto.class).withType(sourceType).withLocation(recipeURL); MachineConfigDto configDto = dtoFactory.createDto(MachineConfigDto.class) .withDev(isDev) .withName(displayName) - .withSource(sourceDto) + .withSource(machineSourceDto) .withLimits(limitsDto) .withType(machineType); diff --git a/plugins/plugin-machine/che-plugin-machine-ext-client/src/main/java/org/eclipse/che/ide/extension/machine/client/targets/categories/development/DevelopmentCategoryPresenter.java b/plugins/plugin-machine/che-plugin-machine-ext-client/src/main/java/org/eclipse/che/ide/extension/machine/client/targets/categories/development/DevelopmentCategoryPresenter.java index c945e445ba4..818bde512f7 100644 --- a/plugins/plugin-machine/che-plugin-machine-ext-client/src/main/java/org/eclipse/che/ide/extension/machine/client/targets/categories/development/DevelopmentCategoryPresenter.java +++ b/plugins/plugin-machine/che-plugin-machine-ext-client/src/main/java/org/eclipse/che/ide/extension/machine/client/targets/categories/development/DevelopmentCategoryPresenter.java @@ -83,6 +83,7 @@ public boolean onRestoreTargetFields(DevelopmentMachineTarget target) { target.setOwner(machine.getOwner()); target.setType(machine.getConfig().getType()); target.setSourceType(machine.getConfig().getSource().getType()); + target.setSourceContent(machine.getConfig().getSource().getContent()); target.setSourceUrl(machine.getConfig().getSource().getLocation()); return true; diff --git a/plugins/plugin-machine/che-plugin-machine-ext-client/src/main/java/org/eclipse/che/ide/extension/machine/client/targets/categories/development/DevelopmentMachineTarget.java b/plugins/plugin-machine/che-plugin-machine-ext-client/src/main/java/org/eclipse/che/ide/extension/machine/client/targets/categories/development/DevelopmentMachineTarget.java index 87303b300a0..2d9f5387c10 100644 --- a/plugins/plugin-machine/che-plugin-machine-ext-client/src/main/java/org/eclipse/che/ide/extension/machine/client/targets/categories/development/DevelopmentMachineTarget.java +++ b/plugins/plugin-machine/che-plugin-machine-ext-client/src/main/java/org/eclipse/che/ide/extension/machine/client/targets/categories/development/DevelopmentMachineTarget.java @@ -25,6 +25,7 @@ public class DevelopmentMachineTarget extends BaseTarget { private String owner; private String sourceType; private String sourceUrl; + private String sourceContent; public void setType(String type) { @@ -59,6 +60,14 @@ public String getSourceUrl() { return sourceUrl; } + public void setSourceContent(String sourceContent) { + this.sourceContent = sourceContent; + } + + public String getSourceContent() { + return sourceContent; + } + @Override public boolean equals(Object o) { if (this == o) { @@ -77,11 +86,12 @@ public boolean equals(Object o) { && Objects.equals(getType(), other.getType()) && Objects.equals(getOwner(), other.getOwner()) && Objects.equals(getSourceType(), other.getSourceType()) + && Objects.equals(getSourceContent(), other.getSourceContent()) && Objects.equals(getSourceUrl(), other.getSourceUrl()); } @Override public int hashCode() { - return Objects.hash(getName(), getCategory(), getRecipe(), getType(), getOwner(), getSourceType(), getSourceUrl()); + return Objects.hash(getName(), getCategory(), getRecipe(), getType(), getOwner(), getSourceType(), getSourceUrl(), getSourceContent()); } } diff --git a/plugins/plugin-machine/che-plugin-machine-ext-client/src/main/java/org/eclipse/che/ide/extension/machine/client/targets/categories/docker/DockerCategoryPresenter.java b/plugins/plugin-machine/che-plugin-machine-ext-client/src/main/java/org/eclipse/che/ide/extension/machine/client/targets/categories/docker/DockerCategoryPresenter.java index 9727e8d8a1d..42239779076 100644 --- a/plugins/plugin-machine/che-plugin-machine-ext-client/src/main/java/org/eclipse/che/ide/extension/machine/client/targets/categories/docker/DockerCategoryPresenter.java +++ b/plugins/plugin-machine/che-plugin-machine-ext-client/src/main/java/org/eclipse/che/ide/extension/machine/client/targets/categories/docker/DockerCategoryPresenter.java @@ -113,6 +113,7 @@ public boolean onRestoreTargetFields(DockerMachineTarget target) { target.setOwner(machine.getOwner()); target.setType(machine.getConfig().getType()); target.setSourceType(machine.getConfig().getSource().getType()); + target.setSourceContent(machine.getConfig().getSource().getContent()); target.setSourceUrl(machine.getConfig().getSource().getLocation()); return true; diff --git a/plugins/plugin-machine/che-plugin-machine-ext-client/src/main/java/org/eclipse/che/ide/extension/machine/client/targets/categories/docker/DockerMachineTarget.java b/plugins/plugin-machine/che-plugin-machine-ext-client/src/main/java/org/eclipse/che/ide/extension/machine/client/targets/categories/docker/DockerMachineTarget.java index 4cb24bc3eff..fd1bb6102fe 100644 --- a/plugins/plugin-machine/che-plugin-machine-ext-client/src/main/java/org/eclipse/che/ide/extension/machine/client/targets/categories/docker/DockerMachineTarget.java +++ b/plugins/plugin-machine/che-plugin-machine-ext-client/src/main/java/org/eclipse/che/ide/extension/machine/client/targets/categories/docker/DockerMachineTarget.java @@ -25,6 +25,7 @@ public class DockerMachineTarget extends BaseTarget { private String owner; private String sourceType; private String sourceUrl; + private String sourceContent; public void setType(String type) { @@ -59,6 +60,15 @@ public String getSourceUrl() { return sourceUrl; } + public void setSourceContent(String sourceContent) { + this.sourceContent = sourceContent; + } + + public String getSourceContent() { + return sourceContent; + } + + @Override public boolean equals(Object o) { if (this == o) { @@ -77,11 +87,12 @@ public boolean equals(Object o) { && Objects.equals(getType(), other.getType()) && Objects.equals(getOwner(), other.getOwner()) && Objects.equals(getSourceType(), other.getSourceType()) + && Objects.equals(getSourceContent(), other.getSourceContent()) && Objects.equals(getSourceUrl(), other.getSourceUrl()); } @Override public int hashCode() { - return Objects.hash(getName(), getCategory(), getRecipe(), getType(), getOwner(), getSourceType(), getSourceUrl()); + return Objects.hash(getName(), getCategory(), getRecipe(), getType(), getOwner(), getSourceType(), getSourceUrl(), getSourceContent()); } } diff --git a/plugins/plugin-machine/che-plugin-machine-ext-client/src/test/java/org/eclipse/che/ide/extension/machine/client/machine/MachineManagerImplTest.java b/plugins/plugin-machine/che-plugin-machine-ext-client/src/test/java/org/eclipse/che/ide/extension/machine/client/machine/MachineManagerImplTest.java new file mode 100644 index 00000000000..6b150d7428f --- /dev/null +++ b/plugins/plugin-machine/che-plugin-machine-ext-client/src/test/java/org/eclipse/che/ide/extension/machine/client/machine/MachineManagerImplTest.java @@ -0,0 +1,181 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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.ide.extension.machine.client.machine; + +import com.google.web.bindery.event.shared.EventBus; + +import org.eclipse.che.api.core.model.machine.MachineConfig; +import org.eclipse.che.api.core.model.machine.MachineSource; +import org.eclipse.che.api.machine.shared.dto.LimitsDto; +import org.eclipse.che.api.machine.shared.dto.MachineConfigDto; +import org.eclipse.che.api.machine.shared.dto.MachineDto; +import org.eclipse.che.api.machine.shared.dto.MachineSourceDto; +import org.eclipse.che.api.promises.client.Operation; +import org.eclipse.che.api.promises.client.OperationException; +import org.eclipse.che.api.promises.client.Promise; +import org.eclipse.che.api.workspace.shared.dto.WorkspaceDto; +import org.eclipse.che.ide.api.app.AppContext; +import org.eclipse.che.ide.api.machine.MachineServiceClient; +import org.eclipse.che.ide.api.parts.PerspectiveManager; +import org.eclipse.che.ide.api.workspace.WorkspaceServiceClient; +import org.eclipse.che.ide.dto.DtoFactory; +import org.eclipse.che.ide.extension.machine.client.machine.console.MachineConsolePresenter; +import org.eclipse.che.ide.rest.DtoUnmarshallerFactory; +import org.eclipse.che.ide.ui.loaders.initialization.InitialLoadingInfo; +import org.eclipse.che.ide.websocket.MessageBusProvider; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.InjectMocks; +import org.mockito.Matchers; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyBoolean; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * Check {@link MachineManagerImpl} + * + * @author Florent Benoit + */ +@RunWith(MockitoJUnitRunner.class) +public class MachineManagerImplTest { + + @Mock + private DtoUnmarshallerFactory dtoUnmarshallerFactor; + + @Mock + private MachineServiceClient machineServiceClient; + + @Mock + private WorkspaceServiceClient workspaceServiceClient; + + @Mock + private MachineConsolePresenter machineConsolePresenter; + + @Mock + private MachineStatusNotifier machineStatusNotifier; + + @Mock + private MessageBusProvider messageBusProvider; + + @Mock + private InitialLoadingInfo initialLoadingInfo; + + @Mock + private PerspectiveManager perspectiveManager; + + @Mock + private EventBus eventBus; + + @Mock + private AppContext appContext; + + @Mock + private DtoFactory dtoFactory; + + @Captor + private ArgumentCaptor startWorkspaceHandlerCaptor; + + @Captor + private ArgumentCaptor> operationArgumentCaptor; + + @Captor + private ArgumentCaptor machineConfigDtoArgumentCaptor; + + + @InjectMocks + private MachineManagerImpl machineManager; + + /** + * Check a valid source object is used on machine destroyed with restart flag + * + * @throws OperationException + * if restart fails + */ + @Test + public void checkUseValidSource() throws OperationException { + final String ID = "id"; + final String DISPLAY_NAME = "my-display-name"; + final boolean IS_DEV = true; + + final String SOURCE_TYPE = "source-type"; + final String SOURCE_LOCATION = "source-location"; + final String SOURCE_CONTENT = "source-content"; + + org.eclipse.che.api.core.model.machine.Machine machineState = mock(org.eclipse.che.api.core.model.machine.Machine.class); + when(machineState.getId()).thenReturn(ID); + Promise promise = mock(Promise.class); + Promise promiseThen = mock(Promise.class); + when(machineServiceClient.destroyMachine(eq(ID))).thenReturn(promise); + when(promise.then(Matchers.>anyObject())).thenReturn(promiseThen); + machineManager.restartMachine(machineState); + + verify(promiseThen).then(operationArgumentCaptor.capture()); + operationArgumentCaptor.getValue().apply(null); + + verify(eventBus).addHandler(eq(MachineStateEvent.TYPE), startWorkspaceHandlerCaptor.capture()); + MachineStateEvent.Handler handler = startWorkspaceHandlerCaptor.getValue(); + + MachineSource machineSource = mock(MachineSource.class); + MachineConfig machineConfig = mock(MachineConfig.class); + when(machineState.getConfig()).thenReturn(machineConfig); + when(machineConfig.getSource()).thenReturn(machineSource); + when(machineConfig.getName()).thenReturn(DISPLAY_NAME); + when(machineConfig.isDev()).thenReturn(IS_DEV); + when(machineSource.getType()).thenReturn(SOURCE_TYPE); + when(machineSource.getLocation()).thenReturn(SOURCE_LOCATION); + when(machineSource.getContent()).thenReturn(SOURCE_CONTENT); + + + MachineSourceDto machineSourceDto = mock(MachineSourceDto.class); + when(machineSourceDto.withType(eq(SOURCE_TYPE))).thenReturn(machineSourceDto); + when(machineSourceDto.withLocation(eq(SOURCE_LOCATION))).thenReturn(machineSourceDto); + when(machineSourceDto.withContent(eq(SOURCE_CONTENT))).thenReturn(machineSourceDto); + + when(dtoFactory.createDto(MachineSourceDto.class)).thenReturn(machineSourceDto); + + + LimitsDto limitsDto = mock(LimitsDto.class); + when(dtoFactory.createDto(LimitsDto.class)).thenReturn(limitsDto); + when(limitsDto.withRam(anyInt())).thenReturn(limitsDto); + + MachineConfigDto machineConfigDto = mock(MachineConfigDto.class); + when(dtoFactory.createDto(MachineConfigDto.class)).thenReturn(machineConfigDto); + when(machineConfigDto.withDev(anyBoolean())).thenReturn(machineConfigDto); + when(machineConfigDto.withName(anyString())).thenReturn(machineConfigDto); + when(machineConfigDto.withSource(machineSourceDto)).thenReturn(machineConfigDto); + when(machineConfigDto.withLimits(limitsDto)).thenReturn(machineConfigDto); + when(machineConfigDto.withType(anyString())).thenReturn(machineConfigDto); + + WorkspaceDto workspaceDto = mock(WorkspaceDto.class); + when(appContext.getWorkspace()).thenReturn(workspaceDto); + when(workspaceDto.getId()).thenReturn(ID); + + Promise promiseEmpty = mock(Promise.class); + when(workspaceServiceClient.createMachine(anyString(), any(MachineConfigDto.class))).thenReturn(promiseEmpty); + + handler.onMachineDestroyed(null); + verify(workspaceServiceClient).createMachine(eq(ID), machineConfigDtoArgumentCaptor.capture()); + verify(machineSourceDto).withType(eq(SOURCE_TYPE)); + verify(machineSourceDto).withLocation(eq(SOURCE_LOCATION)); + verify(machineSourceDto).withContent(eq(SOURCE_CONTENT)); + } + +} diff --git a/plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/SshMachineInstance.java b/plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/SshMachineInstance.java index e364b02271d..7ddf6925460 100644 --- a/plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/SshMachineInstance.java +++ b/plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/SshMachineInstance.java @@ -15,13 +15,13 @@ import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.model.machine.Command; import org.eclipse.che.api.core.model.machine.Machine; +import org.eclipse.che.api.core.model.machine.MachineSource; import org.eclipse.che.api.core.model.machine.ServerConf; import org.eclipse.che.api.core.util.LineConsumer; import org.eclipse.che.api.machine.server.exception.MachineException; import org.eclipse.che.api.machine.server.model.impl.MachineRuntimeInfoImpl; import org.eclipse.che.api.machine.server.model.impl.ServerImpl; import org.eclipse.che.api.machine.server.spi.Instance; -import org.eclipse.che.api.machine.server.spi.InstanceKey; import org.eclipse.che.api.machine.server.spi.InstanceNode; import org.eclipse.che.api.machine.server.spi.InstanceProcess; import org.eclipse.che.api.machine.server.spi.impl.AbstractInstance; @@ -140,7 +140,7 @@ public InstanceProcess createProcess(Command command, String outputChannel) thro * {@inheritDoc} */ @Override - public InstanceKey saveToSnapshot(String owner) throws MachineException { + public MachineSource saveToSnapshot(String owner) throws MachineException { throw new MachineException("Snapshot feature is unsupported for ssh machine implementation"); } diff --git a/plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/SshMachineInstanceProvider.java b/plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/SshMachineInstanceProvider.java index 399a56b7ba2..0be7d5ed197 100644 --- a/plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/SshMachineInstanceProvider.java +++ b/plugins/plugin-ssh-machine/src/main/java/org/eclipse/che/plugin/machine/ssh/SshMachineInstanceProvider.java @@ -15,13 +15,16 @@ import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.model.machine.Machine; import org.eclipse.che.api.core.model.machine.MachineConfig; +import org.eclipse.che.api.core.model.machine.MachineSource; import org.eclipse.che.api.core.model.machine.Recipe; import org.eclipse.che.api.core.util.LineConsumer; +import org.eclipse.che.api.machine.server.exception.InvalidRecipeException; import org.eclipse.che.api.machine.server.exception.MachineException; import org.eclipse.che.api.machine.server.exception.SnapshotException; +import org.eclipse.che.api.machine.server.exception.UnsupportedRecipeException; import org.eclipse.che.api.machine.server.spi.Instance; -import org.eclipse.che.api.machine.server.spi.InstanceKey; import org.eclipse.che.api.machine.server.spi.InstanceProvider; +import org.eclipse.che.api.machine.server.util.RecipeDownloader; import javax.inject.Inject; import java.io.IOException; @@ -45,10 +48,12 @@ public class SshMachineInstanceProvider implements InstanceProvider { private final Set supportedRecipeTypes; private final SshMachineFactory sshMachineFactory; + private final RecipeDownloader recipeDownloader; @Inject - public SshMachineInstanceProvider(SshMachineFactory sshMachineFactory) throws IOException { + public SshMachineInstanceProvider(SshMachineFactory sshMachineFactory, RecipeDownloader recipeDownloader) throws IOException { this.sshMachineFactory = sshMachineFactory; + this.recipeDownloader = recipeDownloader; this.supportedRecipeTypes = Collections.singleton("ssh-config"); } @@ -62,18 +67,37 @@ public Set getRecipeTypes() { return supportedRecipeTypes; } + /** + * Creates instance from scratch or by reusing a previously one by using specified {@link MachineSource} + * data in {@link MachineConfig}. + * + * @param machine + * machine description + * @param lineConsumer + * output for instance creation logs + * @return newly created {@link Instance} + * @throws UnsupportedRecipeException + * if specified {@code recipe} is not supported + * @throws InvalidRecipeException + * if {@code recipe} is invalid + * @throws NotFoundException + * if instance described by {@link MachineSource} doesn't exists + * @throws MachineException + * if other error occurs + */ @Override - public Instance createInstance(Recipe recipe, - Machine machine, - LineConsumer machineLogsConsumer) throws MachineException { + public Instance createInstance(Machine machine, LineConsumer lineConsumer) + throws UnsupportedRecipeException, InvalidRecipeException, NotFoundException, MachineException { requireNonNull(machine, "Non null machine required"); - requireNonNull(machineLogsConsumer, "Non null logs consumer required"); + requireNonNull(lineConsumer, "Non null logs consumer required"); + requireNonNull(machine.getConfig().getSource().getLocation(), "Location in machine source is required"); if (machine.getConfig().isDev()) { throw new MachineException("Dev machine is not supported for Ssh machine implementation"); } - SshMachineRecipe sshMachineRecipe = parseRecipe(recipe); + Recipe recipe = recipeDownloader.getRecipe(machine.getConfig()); + SshMachineRecipe sshMachineRecipe = GSON.fromJson(recipe.getScript(), SshMachineRecipe.class); SshClient sshClient = sshMachineFactory.createSshClient(sshMachineRecipe, machine.getConfig().getEnvVariables()); @@ -81,22 +105,20 @@ public Instance createInstance(Recipe recipe, return sshMachineFactory.createInstance(machine, sshClient, - machineLogsConsumer); + lineConsumer); } + /** + * Removes snapshot of the instance in implementation specific way. + * + * @param machineSource + * contains implementation specific key of the snapshot of the instance that should be removed + * @throws SnapshotException + * if exception occurs on instance snapshot removal + */ @Override - public Instance createInstance(InstanceKey instanceKey, - Machine machine, - LineConsumer creationLogsOutput) throws NotFoundException, MachineException { - throw new MachineException("Snapshot feature is unsupported for ssh machine implementation"); - } - - @Override - public void removeInstanceSnapshot(InstanceKey instanceKey) throws SnapshotException { + public void removeInstanceSnapshot(MachineSource machineSource) throws SnapshotException { throw new SnapshotException("Snapshot feature is unsupported for ssh machine implementation"); } - private SshMachineRecipe parseRecipe(Recipe recipe) { - return GSON.fromJson(recipe.getScript(), SshMachineRecipe.class); - } } diff --git a/plugins/plugin-ssh-machine/src/test/java/org/eclipse/che/plugin/machine/ssh/SshMachineInstanceProviderTest.java b/plugins/plugin-ssh-machine/src/test/java/org/eclipse/che/plugin/machine/ssh/SshMachineInstanceProviderTest.java index 7f6a026ee27..1f67c2879ad 100644 --- a/plugins/plugin-ssh-machine/src/test/java/org/eclipse/che/plugin/machine/ssh/SshMachineInstanceProviderTest.java +++ b/plugins/plugin-ssh-machine/src/test/java/org/eclipse/che/plugin/machine/ssh/SshMachineInstanceProviderTest.java @@ -24,14 +24,14 @@ import org.eclipse.che.api.machine.server.model.impl.ServerConfImpl; import org.eclipse.che.api.machine.server.recipe.RecipeImpl; import org.eclipse.che.api.machine.server.spi.Instance; -import org.eclipse.che.api.machine.server.spi.InstanceKey; +import org.eclipse.che.api.machine.server.util.RecipeDownloader; +import org.mockito.InjectMocks; 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.HashSet; import static java.util.Collections.singletonList; @@ -47,6 +47,9 @@ */ @Listeners(MockitoTestNGListener.class) public class SshMachineInstanceProviderTest { + @Mock + private RecipeDownloader recipeDownloader; + @Mock private SshMachineFactory sshMachineFactory; @Mock @@ -54,13 +57,13 @@ public class SshMachineInstanceProviderTest { @Mock private SshMachineInstance sshMachineInstance; + @InjectMocks private SshMachineInstanceProvider provider; private RecipeImpl recipe; private MachineImpl machine; @BeforeMethod public void setUp() throws Exception { - provider = new SshMachineInstanceProvider(sshMachineFactory); machine = createMachine(); SshMachineRecipe sshMachineRecipe = new SshMachineRecipe("localhost", 22, @@ -80,14 +83,6 @@ public void shouldReturnCorrectRecipeTypes() throws Exception { assertEquals(provider.getRecipeTypes(), new HashSet<>(singletonList("ssh-config"))); } - @Test(expectedExceptions = MachineException.class, - expectedExceptionsMessageRegExp = "Snapshot feature is unsupported for ssh machine implementation") - public void shouldThrowMachineExceptionOnCreateInstanceFromSnapshot() throws Exception { - InstanceKey instanceKey = () -> Collections.EMPTY_MAP; - - provider.createInstance(instanceKey, null, null); - } - @Test(expectedExceptions = SnapshotException.class, expectedExceptionsMessageRegExp = "Snapshot feature is unsupported for ssh machine implementation") public void shouldThrowSnapshotExceptionOnRemoveSnapshot() throws Exception { @@ -99,15 +94,25 @@ public void shouldThrowSnapshotExceptionOnRemoveSnapshot() throws Exception { public void shouldThrowExceptionOnDevMachineCreationFromRecipe() throws Exception { Machine machine = createMachine(true); - provider.createInstance(recipe, machine, LineConsumer.DEV_NULL); + provider.createInstance(machine, LineConsumer.DEV_NULL); + } + + @Test(expectedExceptions = NullPointerException.class, + expectedExceptionsMessageRegExp = "Location in machine source is required") + public void shouldThrowExceptionInvalidMachineConfigSource() throws Exception { + MachineImpl machine = createMachine(true); + machine.getConfig().setSource(new MachineSourceImpl("ssh-config").setContent("hello")); + + provider.createInstance(machine, LineConsumer.DEV_NULL); } @Test public void shouldBeAbleToCreateSshMachineInstanceOnMachineCreationFromRecipe() throws Exception { when(sshMachineFactory.createSshClient(any(SshMachineRecipe.class), anyMap())).thenReturn(sshClient); when(sshMachineFactory.createInstance(eq(machine), eq(sshClient), any(LineConsumer.class))).thenReturn(sshMachineInstance); + when(recipeDownloader.getRecipe(eq(machine.getConfig()))).thenReturn(recipe); - Instance instance = provider.createInstance(recipe, machine, LineConsumer.DEV_NULL); + Instance instance = provider.createInstance(machine, LineConsumer.DEV_NULL); assertEquals(instance, sshMachineInstance); } @@ -125,8 +130,7 @@ private MachineImpl createMachine(boolean isDev) { "10011/tcp", "http", null))) - .setSource(new MachineSourceImpl("ssh-config", - "localhost:10012/recipe")) + .setSource(new MachineSourceImpl("ssh-config").setLocation("localhost:10012/recipe")) .setType("ssh") .build(); return MachineImpl.builder() diff --git a/wsmaster/che-core-api-machine-shared/src/main/java/org/eclipse/che/api/machine/shared/dto/MachineSourceDto.java b/wsmaster/che-core-api-machine-shared/src/main/java/org/eclipse/che/api/machine/shared/dto/MachineSourceDto.java index eaed400a172..02ca3d3c7a6 100644 --- a/wsmaster/che-core-api-machine-shared/src/main/java/org/eclipse/che/api/machine/shared/dto/MachineSourceDto.java +++ b/wsmaster/che-core-api-machine-shared/src/main/java/org/eclipse/che/api/machine/shared/dto/MachineSourceDto.java @@ -15,6 +15,7 @@ import org.eclipse.che.dto.shared.DTO; import static org.eclipse.che.api.core.factory.FactoryParameter.Obligation.MANDATORY; +import static org.eclipse.che.api.core.factory.FactoryParameter.Obligation.OPTIONAL; /** * @author Alexander Garagatyi @@ -30,10 +31,33 @@ public interface MachineSourceDto extends MachineSource { MachineSourceDto withType(String type); @Override - @FactoryParameter(obligation = MANDATORY) + @FactoryParameter(obligation = OPTIONAL) String getLocation(); void setLocation(String location); MachineSourceDto withLocation(String location); + + /** + * @return content of the machine source. No need to use an external link. + */ + @Override + @FactoryParameter(obligation = OPTIONAL) + String getContent(); + + /** + * Defines the new content to use for this machine source. + * Alternate way is to provide a location + * @param content the content instead of an external link like with location + */ + void setContent(String content); + + /** + * Defines the new content to use for this machine source. + * Alternate way is to provide a location + * @param content the content instead of an external link like with location + * @return the current intance of the object + */ + MachineSourceDto withContent(String content); + } diff --git a/wsmaster/che-core-api-machine/pom.xml b/wsmaster/che-core-api-machine/pom.xml index ed048557ee2..0b160d1776b 100644 --- a/wsmaster/che-core-api-machine/pom.xml +++ b/wsmaster/che-core-api-machine/pom.xml @@ -49,6 +49,10 @@ javax.inject javax.inject + + javax.validation + validation-api + javax.ws.rs javax.ws.rs-api diff --git a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/DtoConverter.java b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/DtoConverter.java index c4c1f6f9ff9..7afd64ee21f 100644 --- a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/DtoConverter.java +++ b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/DtoConverter.java @@ -61,7 +61,7 @@ public static MachineConfigDto asDto(MachineConfig config) { * Converts {@link MachineSource} to {@link MachineSourceDto}. */ public static MachineSourceDto asDto(MachineSource source) { - return newDto(MachineSourceDto.class).withType(source.getType()).withLocation(source.getLocation()); + return newDto(MachineSourceDto.class).withType(source.getType()).withLocation(source.getLocation()).withContent(source.getContent()); } /** diff --git a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/MachineManager.java b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/MachineManager.java index c0d41c33693..111fb6056dd 100644 --- a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/MachineManager.java +++ b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/MachineManager.java @@ -22,7 +22,6 @@ import org.eclipse.che.api.core.model.machine.Machine; import org.eclipse.che.api.core.model.machine.MachineConfig; import org.eclipse.che.api.core.model.machine.MachineStatus; -import org.eclipse.che.api.core.model.machine.Recipe; import org.eclipse.che.api.core.notification.EventService; import org.eclipse.che.api.core.notification.EventSubscriber; import org.eclipse.che.api.core.util.CompositeLineConsumer; @@ -39,10 +38,8 @@ import org.eclipse.che.api.machine.server.model.impl.MachineImpl; import org.eclipse.che.api.machine.server.model.impl.SnapshotImpl; import org.eclipse.che.api.machine.server.spi.Instance; -import org.eclipse.che.api.machine.server.spi.InstanceKey; import org.eclipse.che.api.machine.server.spi.InstanceProcess; import org.eclipse.che.api.machine.server.spi.InstanceProvider; -import org.eclipse.che.api.machine.server.util.RecipeDownloader; import org.eclipse.che.api.machine.server.wsagent.WsAgentLauncher; import org.eclipse.che.api.machine.shared.dto.event.MachineProcessEvent; import org.eclipse.che.api.machine.shared.dto.event.MachineStatusEvent; @@ -100,7 +97,6 @@ public class MachineManager { private final int defaultMachineMemorySizeMB; private final MachineCleaner machineCleaner; private final WsAgentLauncher wsAgentLauncher; - private final RecipeDownloader recipeDownloader; @Inject public MachineManager(SnapshotDao snapshotDao, @@ -109,13 +105,11 @@ public MachineManager(SnapshotDao snapshotDao, @Named("machine.logs.location") String machineLogsDir, EventService eventService, @Named("machine.default_mem_size_mb") int defaultMachineMemorySizeMB, - WsAgentLauncher wsAgentLauncher, - RecipeDownloader recipeDownloader) { + WsAgentLauncher wsAgentLauncher) { this.snapshotDao = snapshotDao; this.machineInstanceProviders = machineInstanceProviders; this.eventService = eventService; this.wsAgentLauncher = wsAgentLauncher; - this.recipeDownloader = recipeDownloader; this.machineLogsDir = new File(machineLogsDir); this.machineRegistry = machineRegistry; this.defaultMachineMemorySizeMB = defaultMachineMemorySizeMB; @@ -256,12 +250,10 @@ public MachineImpl createMachineAsync(MachineConfig machineConfig, return createMachine(normalizeMachineConfig(machineConfig), workspaceId, environmentName, - (instanceProvider, recipe, instanceKey, machine, machineLogger) -> + (instanceProvider, machine, machineLogger) -> executor.execute(ThreadLocalPropagateContext.wrap(() -> { try { createInstance(instanceProvider, - recipe, - instanceKey, machine, machineLogger); } catch (MachineException | NotFoundException e) { @@ -297,14 +289,6 @@ private MachineImpl createMachine(MachineConfigImpl machineConfig, machineConfig.getName())); } - Recipe recipe = null; - InstanceKey instanceKey = null; - if (snapshot != null) { - instanceKey = snapshot.getInstanceKey(); - } else { - recipe = recipeDownloader.getRecipe(machineConfig); - } - if (!MACHINE_DISPLAY_NAME_PATTERN.matcher(machineConfig.getName()).matches()) { throw new BadRequestException("Invalid machine name " + machineConfig.getName()); } @@ -315,6 +299,11 @@ private MachineImpl createMachine(MachineConfigImpl machineConfig, } } + // recover key from snapshot if there is one + if (snapshot != null) { + machineConfig.setSource(snapshot.getMachineSource()); + } + final String machineId = generateMachineId(); final String creator = EnvironmentContext.getCurrent().getSubject().getUserId(); @@ -339,7 +328,7 @@ private MachineImpl createMachine(MachineConfigImpl machineConfig, try { machineRegistry.addMachine(machine); - instanceCreator.createInstance(instanceProvider, recipe, instanceKey, machine, machineLogger); + instanceCreator.createInstance(instanceProvider, machine, machineLogger); return machine; } catch (ConflictException e) { @@ -348,8 +337,6 @@ private MachineImpl createMachine(MachineConfigImpl machineConfig, } private void createInstance(InstanceProvider instanceProvider, - Recipe recipe, - InstanceKey instanceKey, Machine machine, LineConsumer machineLogger) throws MachineException, NotFoundException { Instance instance = null; @@ -361,11 +348,7 @@ private void createInstance(InstanceProvider instanceProvider, .withWorkspaceId(machine.getWorkspaceId()) .withMachineName(machine.getConfig().getName())); - if (instanceKey == null) { - instance = instanceProvider.createInstance(recipe, machine, machineLogger); - } else { - instance = instanceProvider.createInstance(instanceKey, machine, machineLogger); - } + instance = instanceProvider.createInstance(machine, machineLogger); instance.setStatus(MachineStatus.RUNNING); @@ -408,7 +391,6 @@ private void createInstance(InstanceProvider instanceProvider, private interface MachineInstanceCreator { void createInstance(InstanceProvider instanceProvider, - Recipe recipe, InstanceKey instanceKey, Machine machineState, LineConsumer machineLogger) throws MachineException, NotFoundException; } @@ -590,7 +572,7 @@ public void removeSnapshot(String snapshotId) throws NotFoundException, Snapshot final SnapshotImpl snapshot = getSnapshot(snapshotId); final String instanceType = snapshot.getType(); final InstanceProvider instanceProvider = machineInstanceProviders.getProvider(instanceType); - instanceProvider.removeInstanceSnapshot(snapshot.getInstanceKey()); + instanceProvider.removeInstanceSnapshot(snapshot.getMachineSource()); snapshotDao.removeSnapshot(snapshotId); } @@ -814,14 +796,14 @@ private SnapshotImpl doSaveMachine(SnapshotImpl snapshot, Instance machine) thro machine.getId()); snapshotWithKey = new SnapshotImpl(snapshot); - snapshotWithKey.setInstanceKey(machine.saveToSnapshot(machine.getOwner())); + snapshotWithKey.setMachineSourceImpl(machine.saveToSnapshot(machine.getOwner())); try { SnapshotImpl oldSnapshot = snapshotDao.getSnapshot(snapshot.getWorkspaceId(), snapshot.getEnvName(), snapshot.getMachineName()); snapshotDao.removeSnapshot(oldSnapshot.getId()); - machineInstanceProviders.getProvider(oldSnapshot.getType()).removeInstanceSnapshot(oldSnapshot.getInstanceKey()); + machineInstanceProviders.getProvider(oldSnapshot.getType()).removeInstanceSnapshot(oldSnapshot.getMachineSource()); } catch (NotFoundException ignored) { //DO nothing if we has no snapshots or when provider not found } catch (SnapshotException se) { diff --git a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/model/impl/MachineConfigImpl.java b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/model/impl/MachineConfigImpl.java index 129379308fa..eb31c55ebaf 100644 --- a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/model/impl/MachineConfigImpl.java +++ b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/model/impl/MachineConfigImpl.java @@ -91,6 +91,10 @@ public MachineSourceImpl getSource() { return source; } + public void setSource(MachineSource machineSource) { + this.source = new MachineSourceImpl(machineSource); + } + @Override public boolean isDev() { return isDev; diff --git a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/model/impl/MachineSourceImpl.java b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/model/impl/MachineSourceImpl.java index 0e814a2e2ff..43e215b18df 100644 --- a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/model/impl/MachineSourceImpl.java +++ b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/model/impl/MachineSourceImpl.java @@ -14,6 +14,8 @@ import java.util.Objects; +import static java.util.Objects.hash; + //TODO move? /** @@ -25,15 +27,32 @@ public class MachineSourceImpl implements MachineSource { private String type; private String location; + private String content; + + protected MachineSourceImpl() { + + } + /** + * Please use {@link MachineSourceImpl with type and then setLocation or setContent} + * @param type the source type defined by implementation. + */ + @Deprecated public MachineSourceImpl(String type, String location) { + this(type); + setLocation(location); + } + + public MachineSourceImpl(String type) { this.type = type; - this.location = location; } public MachineSourceImpl(MachineSource machineSource) { - this.type = machineSource.getType(); - this.location = machineSource.getLocation(); + if (machineSource != null) { + this.type = machineSource.getType(); + this.location = machineSource.getLocation(); + this.content = machineSource.getContent(); + } } @Override @@ -51,6 +70,26 @@ public String getLocation() { return location; } + /** + * @return content of the machine source. No need to use an external link. + */ + @Override + public String getContent() { + return this.content; + } + + /** + * Defines the new content to use for this machine source. + * Alternate way is to provide a location + * + * @param content + * the content instead of an external link like with location + */ + public MachineSourceImpl setContent(String content) { + this.content = content; + return this; + } + public MachineSourceImpl setLocation(String location) { this.location = location; return this; @@ -61,22 +100,20 @@ public boolean equals(Object obj) { if (this == obj) return true; if (!(obj instanceof MachineSourceImpl)) return false; final MachineSourceImpl other = (MachineSourceImpl)obj; - return Objects.equals(type, other.type) && Objects.equals(location, other.location); + return Objects.equals(type, other.type) && Objects.equals(location, other.location) && Objects.equals(content, other.content); } @Override public int hashCode() { - int hash = 7; - hash = hash * 31 + Objects.hashCode(type); - hash = hash * 31 + Objects.hashCode(location); - return hash; + return hash(type, location, content); } @Override public String toString() { - return "MachineSourceImpl{" + + return MachineSourceImpl.class.getSimpleName() + "{" + "type='" + type + '\'' + ", location='" + location + '\'' + + ", content='" + content + '\'' + '}'; } } diff --git a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/model/impl/SnapshotImpl.java b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/model/impl/SnapshotImpl.java index 9e51adf2f6a..1dd1603a2ca 100644 --- a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/model/impl/SnapshotImpl.java +++ b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/model/impl/SnapshotImpl.java @@ -11,8 +11,7 @@ package org.eclipse.che.api.machine.server.model.impl; import org.eclipse.che.api.core.model.machine.MachineConfig; -import org.eclipse.che.api.machine.server.spi.impl.InstanceKeyImpl; -import org.eclipse.che.api.machine.server.spi.InstanceKey; +import org.eclipse.che.api.core.model.machine.MachineSource; import org.eclipse.che.api.core.model.machine.Snapshot; import org.eclipse.che.commons.lang.NameGenerator; @@ -41,7 +40,7 @@ public static SnapshotBuilder builder() { private final long creationDate; private String description; - private InstanceKeyImpl instanceKey; + private MachineSourceImpl machineSource; public SnapshotImpl(Snapshot snapshot) { this(snapshot.getId(), @@ -58,7 +57,7 @@ public SnapshotImpl(Snapshot snapshot) { public SnapshotImpl(String id, String type, - InstanceKey instanceKey, + MachineSource machineSource, String namespace, long creationDate, String workspaceId, @@ -72,7 +71,7 @@ public SnapshotImpl(String id, this.workspaceId = requireNonNull(workspaceId, "Required non-null workspace id for snapshot"); this.machineName = requireNonNull(machineName, "Required non-null snapshot machine name"); this.envName = requireNonNull(envName, "Required non-null environment name for snapshot"); - this.instanceKey = instanceKey != null ? new InstanceKeyImpl(instanceKey) : null; + this.machineSource = machineSource != null ? new MachineSourceImpl(machineSource) : null; this.description = description; this.isDev = isDev; this.creationDate = creationDate; @@ -88,8 +87,8 @@ public String getType() { return type; } - public InstanceKey getInstanceKey() { - return instanceKey; + public MachineSourceImpl getMachineSource() { + return machineSource; } @Override @@ -127,8 +126,8 @@ public boolean isDev() { return this.isDev; } - public void setInstanceKey(InstanceKey instanceKey) { - this.instanceKey = instanceKey != null ? new InstanceKeyImpl(instanceKey.getFields()) : null; + public void setMachineSourceImpl(MachineSource machineSource) { + this.machineSource = machineSource != null ? new MachineSourceImpl(machineSource) : null; } public void setDescription(String description) { @@ -148,7 +147,7 @@ public boolean equals(Object o) { && isDev == snapshot.isDev && Objects.equals(id, snapshot.id) && Objects.equals(type, snapshot.type) - && Objects.equals(instanceKey, snapshot.instanceKey) + && Objects.equals(machineSource, snapshot.machineSource) && Objects.equals(namespace, snapshot.namespace) && Objects.equals(workspaceId, snapshot.workspaceId) && Objects.equals(description, snapshot.description) @@ -163,7 +162,7 @@ public int hashCode() { hash = hash * 31 + Boolean.hashCode(isDev); hash = hash * 31 + Objects.hashCode(id); hash = hash * 31 + Objects.hashCode(type); - hash = hash * 31 + Objects.hashCode(instanceKey); + hash = hash * 31 + Objects.hashCode(machineSource); hash = hash * 31 + Objects.hashCode(namespace); hash = hash * 31 + Objects.hashCode(workspaceId); hash = hash * 31 + Objects.hashCode(description); @@ -177,7 +176,7 @@ public String toString() { return "SnapshotImpl{" + "id='" + id + '\'' + ", type='" + type + '\'' + - ", instanceKey=" + instanceKey + + ", machineSource=" + machineSource + ", namespace='" + namespace + '\'' + ", creationDate=" + creationDate + ", isDev=" + isDev + @@ -200,7 +199,7 @@ public static class SnapshotBuilder { private String type; private String namespace; private String description; - private InstanceKey instanceKey; + private MachineSource machineSource; private boolean isDev; private long creationDate; @@ -250,8 +249,8 @@ public SnapshotBuilder setDescription(String description) { return this; } - public SnapshotBuilder setInstanceKey(InstanceKey instanceKey) { - this.instanceKey = instanceKey; + public SnapshotBuilder setMachineSource(MachineSource machineSource) { + this.machineSource = machineSource; return this; } @@ -271,7 +270,7 @@ public SnapshotBuilder useCurrentCreationDate() { } public SnapshotImpl build() { - return new SnapshotImpl(id, type, instanceKey, namespace, creationDate, workspaceId, description, isDev, machineName, envName); + return new SnapshotImpl(id, type, machineSource, namespace, creationDate, workspaceId, description, isDev, machineName, envName); } } } diff --git a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/model/impl/adapter/MachineSourceAdapter.java b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/model/impl/adapter/MachineSourceAdapter.java new file mode 100644 index 00000000000..85f0d9b07f4 --- /dev/null +++ b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/model/impl/adapter/MachineSourceAdapter.java @@ -0,0 +1,49 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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.machine.server.model.impl.adapter; + +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; + +import org.eclipse.che.api.core.model.machine.MachineSource; +import org.eclipse.che.api.machine.server.model.impl.MachineSourceImpl; + +import java.lang.reflect.Type; + +/** + * Type adapter for {@link MachineSource}. + * + * @author Florent Benoit + */ +public class MachineSourceAdapter implements JsonDeserializer, JsonSerializer { + + @Override + public MachineSource deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext context) throws JsonParseException { + return context.deserialize(jsonElement, MachineSourceImpl.class); + } + + @Override + public JsonElement serialize(MachineSource machineSource, Type type, JsonSerializationContext context) { + final JsonObject jsonObject = new JsonObject(); + + // we can't rely on MachineSourceImpl as custom InstanceProvider can build their own implementation + jsonObject.addProperty("content", machineSource.getContent()); + jsonObject.addProperty("location", machineSource.getLocation()); + jsonObject.addProperty("type", machineSource.getType()); + + return jsonObject; + } +} diff --git a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/recipe/adapters/InstanceKeyAdapter.java b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/recipe/adapters/InstanceKeyAdapter.java deleted file mode 100644 index b7399d953fe..00000000000 --- a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/recipe/adapters/InstanceKeyAdapter.java +++ /dev/null @@ -1,62 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2012-2016 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.machine.server.recipe.adapters; - -import com.google.gson.JsonDeserializationContext; -import com.google.gson.JsonDeserializer; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonParseException; -import com.google.gson.JsonSerializationContext; -import com.google.gson.JsonSerializer; - -import org.eclipse.che.api.machine.server.spi.InstanceKey; - -import java.lang.reflect.Type; -import java.util.HashMap; -import java.util.Map; - -/** - * Type adapter for {@link InstanceKey}. - * - * @author Yevhenii Voevodin - */ -public class InstanceKeyAdapter implements JsonDeserializer, JsonSerializer { - - @Override - public InstanceKey deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext context) throws JsonParseException { - final JsonObject recipeObj = jsonElement.getAsJsonObject(); - return new InstanceKey() { - - Map fields; - - @Override - public Map getFields() { - if (fields == null) { - fields = new HashMap<>(); - for (Map.Entry entry : recipeObj.entrySet()) { - fields.put(entry.getKey(), entry.getValue().getAsString()); - } - } - return fields; - } - }; - } - - @Override - public JsonElement serialize(InstanceKey instanceKey, Type type, JsonSerializationContext context) { - final JsonObject fields = new JsonObject(); - for (Map.Entry entry : instanceKey.getFields().entrySet()) { - fields.addProperty(entry.getKey(), entry.getValue()); - } - return fields; - } -} diff --git a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/spi/Instance.java b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/spi/Instance.java index 9636eec62cf..7a83055244d 100644 --- a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/spi/Instance.java +++ b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/spi/Instance.java @@ -13,6 +13,7 @@ import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.model.machine.Command; import org.eclipse.che.api.core.model.machine.Machine; +import org.eclipse.che.api.core.model.machine.MachineSource; import org.eclipse.che.api.core.model.machine.MachineStatus; import org.eclipse.che.api.core.util.LineConsumer; import org.eclipse.che.api.machine.server.exception.MachineException; @@ -76,7 +77,7 @@ public interface Instance extends Machine { * @throws MachineException * if error occurs on storing state of the instance */ - InstanceKey saveToSnapshot(String owner) throws MachineException; + MachineSource saveToSnapshot(String owner) throws MachineException; /** * Destroy instance diff --git a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/spi/InstanceKey.java b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/spi/InstanceKey.java deleted file mode 100644 index 9324df3306f..00000000000 --- a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/spi/InstanceKey.java +++ /dev/null @@ -1,24 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2012-2016 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.machine.server.spi; - -import java.util.Map; - -/** - * Describes set of keys that uniquely identifies snapshot of instance in implementation specific way. - * - * @author andrew00x - * @author Alexander Garagatyi - * @author Sergii Kabashniuk - */ -public interface InstanceKey { - Map getFields(); -} diff --git a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/spi/InstanceProvider.java b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/spi/InstanceProvider.java index 78cd5e73eb0..5fea18368c8 100644 --- a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/spi/InstanceProvider.java +++ b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/spi/InstanceProvider.java @@ -12,9 +12,9 @@ import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.model.machine.Machine; +import org.eclipse.che.api.core.model.machine.MachineSource; import org.eclipse.che.api.core.model.machine.Recipe; import org.eclipse.che.api.core.util.LineConsumer; -import org.eclipse.che.api.machine.server.exception.InvalidInstanceSnapshotException; import org.eclipse.che.api.machine.server.exception.InvalidRecipeException; import org.eclipse.che.api.machine.server.exception.MachineException; import org.eclipse.che.api.machine.server.exception.SnapshotException; @@ -45,12 +45,11 @@ public interface InstanceProvider { Set getRecipeTypes(); /** - * Creates instance from scratch. + * Creates instance from scratch or by reusing a previously one by using specified {@link org.eclipse.che.api.core.model.machine.MachineSource} + * data in {@link org.eclipse.che.api.core.model.machine.MachineConfig}. * * @param machine * machine description - * @param recipe - * instance creation {@link Recipe} * @param creationLogsOutput * output for instance creation logs * @return newly created {@link Instance} @@ -58,41 +57,24 @@ public interface InstanceProvider { * if specified {@code recipe} is not supported * @throws InvalidRecipeException * if {@code recipe} is invalid + * @throws NotFoundException + * if instance described by {@link org.eclipse.che.api.core.model.machine.MachineSource} doesn't exists * @throws MachineException * if other error occurs */ - Instance createInstance(Recipe recipe, - Machine machine, + Instance createInstance(Machine machine, LineConsumer creationLogsOutput) throws UnsupportedRecipeException, InvalidRecipeException, + NotFoundException, MachineException; - /** - * Creates instance using implementation specific {@link InstanceKey}. - * - * @param instanceKey - * implementation specific {@link InstanceKey} - * @param creationLogsOutput - * output for instance creation logs - * @return newly created {@link Instance} - * @throws NotFoundException - * if instance described by {@code InstanceKey} doesn't exists - * @throws InvalidInstanceSnapshotException - * if other errors occurs while restoring instance - * @throws MachineException - * if other error occurs - */ - Instance createInstance(InstanceKey instanceKey, - Machine machine, - LineConsumer creationLogsOutput) throws NotFoundException, InvalidInstanceSnapshotException, MachineException; - /** * Removes snapshot of the instance in implementation specific way. * - * @param instanceKey - * key of the snapshot of the instance that should be removed + * @param machineSource + * contains implementation specific key of the snapshot of the instance that should be removed * @throws SnapshotException * if exception occurs on instance snapshot removal */ - void removeInstanceSnapshot(InstanceKey instanceKey) throws SnapshotException; + void removeInstanceSnapshot(MachineSource machineSource) throws SnapshotException; } diff --git a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/spi/impl/InstanceKeyImpl.java b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/spi/impl/InstanceKeyImpl.java deleted file mode 100644 index fea2e17d15b..00000000000 --- a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/spi/impl/InstanceKeyImpl.java +++ /dev/null @@ -1,59 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2012-2016 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.machine.server.spi.impl; - -import org.eclipse.che.api.machine.server.spi.InstanceKey; - -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - -/** - * Map based implementation of {@link org.eclipse.che.api.machine.server.spi.InstanceKey}. - * - * @author Sergii Kabashniuk - */ -public class InstanceKeyImpl implements InstanceKey { - - private final Map fields; - - public InstanceKeyImpl(Map fields) { - this.fields = Collections.unmodifiableMap(new HashMap<>(fields)); - } - - public InstanceKeyImpl(InstanceKey instanceKey) { - this(instanceKey.getFields()); - } - - /** - * @return unmodifiable copy of fields. - */ - @Override - public Map getFields() { - return fields; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof InstanceKeyImpl)) return false; - - InstanceKeyImpl that = (InstanceKeyImpl)o; - - return !(getFields() != null ? !getFields().equals(that.getFields()) : that.getFields() != null); - - } - - @Override - public int hashCode() { - return getFields() != null ? getFields().hashCode() : 0; - } -} diff --git a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/util/RecipeRetriever.java b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/util/RecipeRetriever.java new file mode 100644 index 00000000000..46397cba89d --- /dev/null +++ b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/util/RecipeRetriever.java @@ -0,0 +1,56 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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.machine.server.util; + +import com.google.common.base.Strings; + +import org.eclipse.che.api.core.model.machine.MachineConfig; +import org.eclipse.che.api.core.model.machine.MachineSource; +import org.eclipse.che.api.core.model.machine.Recipe; +import org.eclipse.che.api.machine.server.exception.MachineException; +import org.eclipse.che.api.machine.server.recipe.RecipeImpl; + +import javax.inject.Inject; +import javax.validation.constraints.NotNull; + +/** + * Handle how recipe is retrieved, either by downloading it with external location or by using the provided content. + * + * @author Florent Benoit + */ +public class RecipeRetriever { + + /** + * For recipe stored on an external location, needs to delegate. + */ + @Inject + private RecipeDownloader recipeDownloader; + + /** + * Gets the recipe from a machine configuration + * + * @param machineConfig + * the machine configuration that is containing the content or a location to get recipe + * @return recipe with set content and type + * @throws MachineException + * if any error occurs + */ + public Recipe getRecipe(@NotNull MachineConfig machineConfig) throws MachineException { + MachineSource machineSource = machineConfig.getSource(); + if (!Strings.isNullOrEmpty(machineSource.getContent())) { + return new RecipeImpl().withType(machineSource.getType()) + .withScript(machineSource.getContent()); + } else { + return recipeDownloader.getRecipe(machineConfig); + } + + } +} diff --git a/wsmaster/che-core-api-machine/src/test/java/org/eclipse/che/api/machine/server/MachineManagerTest.java b/wsmaster/che-core-api-machine/src/test/java/org/eclipse/che/api/machine/server/MachineManagerTest.java index c93de59c660..53aa65eb5ea 100644 --- a/wsmaster/che-core-api-machine/src/test/java/org/eclipse/che/api/machine/server/MachineManagerTest.java +++ b/wsmaster/che-core-api-machine/src/test/java/org/eclipse/che/api/machine/server/MachineManagerTest.java @@ -27,7 +27,6 @@ import org.eclipse.che.api.machine.server.recipe.RecipeImpl; import org.eclipse.che.api.machine.server.spi.Instance; import org.eclipse.che.api.machine.server.spi.InstanceProvider; -import org.eclipse.che.api.machine.server.util.RecipeDownloader; import org.eclipse.che.api.machine.server.wsagent.WsAgentLauncher; import org.eclipse.che.commons.env.EnvironmentContext; import org.eclipse.che.commons.lang.IoUtil; @@ -76,8 +75,6 @@ public class MachineManagerTest { @Mock private MachineInstanceProviders machineInstanceProviders; @Mock - private RecipeDownloader recipeDownloader; - @Mock private InstanceProvider instanceProvider; @Mock private MachineRegistry machineRegistry; @@ -102,8 +99,7 @@ public void setUp() throws Exception { machineLogsDir, eventService, DEFAULT_MACHINE_MEMORY_SIZE_MB, - wsAgentLauncher, - recipeDownloader)); + wsAgentLauncher)); EnvironmentContext envCont = new EnvironmentContext(); envCont.setSubject(new SubjectImpl(null, USER_ID, null, null, false)); @@ -112,13 +108,12 @@ public void setUp() throws Exception { RecipeImpl recipe = new RecipeImpl().withScript("script").withType("Dockerfile"); // doNothing().when(manager).createMachineLogsDir(anyString()); doReturn(MACHINE_ID).when(manager).generateMachineId(); - when(recipeDownloader.getRecipe(any(MachineConfig.class))).thenReturn(recipe); when(machineInstanceProviders.getProvider(anyString())).thenReturn(instanceProvider); HashSet recipeTypes = new HashSet<>(); recipeTypes.add("test type 1"); recipeTypes.add("dockerfile"); when(instanceProvider.getRecipeTypes()).thenReturn(recipeTypes); - when(instanceProvider.createInstance(eq(recipe), any(Machine.class), any(LineConsumer.class))).thenReturn(instance); + when(instanceProvider.createInstance(any(Machine.class), any(LineConsumer.class))).thenReturn(instance); when(machineRegistry.getInstance(anyString())).thenReturn(instance); } @@ -129,13 +124,10 @@ public void tearDown() throws Exception { @Test(expectedExceptions = BadRequestException.class, expectedExceptionsMessageRegExp = "Invalid machine name @name!") public void shouldThrowExceptionOnMachineCreationIfMachineNameIsInvalid() throws Exception { - when(recipeDownloader.getRecipe(any(MachineConfig.class))).thenReturn(new RecipeImpl().withScript("script") - .withType("Dockerfile")); - MachineConfig machineConfig = new MachineConfigImpl(false, "@name!", "machineType", - new MachineSourceImpl("Dockerfile", "location"), + new MachineSourceImpl("Dockerfile").setLocation("location"), new LimitsImpl(1024), Arrays.asList(new ServerConfImpl("ref1", "8080", @@ -217,7 +209,7 @@ private MachineConfigImpl createMachineConfig() { return new MachineConfigImpl(false, "MachineName", "docker", - new MachineSourceImpl("Dockerfile", "location"), + new MachineSourceImpl("Dockerfile").setLocation("location"), new LimitsImpl(1024), Arrays.asList(new ServerConfImpl("ref1", "8080", diff --git a/wsmaster/che-core-api-machine/src/test/java/org/eclipse/che/api/machine/server/model/impl/adapter/MachineSourceAdapterTest.java b/wsmaster/che-core-api-machine/src/test/java/org/eclipse/che/api/machine/server/model/impl/adapter/MachineSourceAdapterTest.java new file mode 100644 index 00000000000..32d5d5030d8 --- /dev/null +++ b/wsmaster/che-core-api-machine/src/test/java/org/eclipse/che/api/machine/server/model/impl/adapter/MachineSourceAdapterTest.java @@ -0,0 +1,70 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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.machine.server.model.impl.adapter; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonElement; +import com.google.gson.JsonSerializationContext; + +import org.eclipse.che.api.core.model.machine.MachineSource; +import org.eclipse.che.api.machine.server.model.impl.MachineSourceImpl; +import org.mockito.Mockito; +import org.testng.annotations.Test; + +import java.io.StringReader; + +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.spy; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; + +/** + * Test {@link MachineSourceAdapter} on serialization + * + * @author Florent Benoit + */ +public class MachineSourceAdapterTest { + + /** + * Check we can transform object into JSON and JSON into object + */ + @Test + public void testSerializeAndDeserialize() { + + MachineSourceAdapter machineSourceAdapter = spy(new MachineSourceAdapter()); + + Gson gson = new GsonBuilder().registerTypeAdapter(MachineSource.class, machineSourceAdapter).setPrettyPrinting().create(); + + final String TYPE = "myType"; + final String LOCATION = "myLocation"; + final String CONTENT = "myContent"; + + // serialize + MachineSource machineSource = new MachineSourceImpl(TYPE).setLocation(LOCATION).setContent(CONTENT); + String json = gson.toJson(machineSource, MachineSource.class); + assertNotNull(json); + + // verify we called serializer + Mockito.verify(machineSourceAdapter).serialize(eq(machineSource), eq(MachineSource.class), any(JsonSerializationContext.class)); + + // now deserialize + MachineSource machineSourceDeserialize = gson.fromJson(new StringReader(json), MachineSource.class); + assertNotNull(machineSourceDeserialize); + assertEquals(machineSourceDeserialize.getLocation(), LOCATION); + assertEquals(machineSourceDeserialize.getType(), TYPE); + assertEquals(machineSourceDeserialize.getContent(), CONTENT); + // verify we called deserializer + Mockito.verify(machineSourceAdapter).deserialize(any(JsonElement.class), eq(MachineSource.class), any(JsonDeserializationContext.class)); + } +} diff --git a/wsmaster/che-core-api-machine/src/test/java/org/eclipse/che/api/machine/server/util/RecipeRetrieverTest.java b/wsmaster/che-core-api-machine/src/test/java/org/eclipse/che/api/machine/server/util/RecipeRetrieverTest.java new file mode 100644 index 00000000000..a36982819a6 --- /dev/null +++ b/wsmaster/che-core-api-machine/src/test/java/org/eclipse/che/api/machine/server/util/RecipeRetrieverTest.java @@ -0,0 +1,102 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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.machine.server.util; + +import org.eclipse.che.api.core.model.machine.MachineConfig; +import org.eclipse.che.api.core.model.machine.MachineSource; +import org.eclipse.che.api.core.model.machine.Recipe; +import org.eclipse.che.api.machine.server.exception.MachineException; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.testng.MockitoTestNGListener; +import org.testng.Assert; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Listeners; +import org.testng.annotations.Test; + +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.testng.Assert.assertEquals; + +/** + * Test of {@link RecipeRetriever} class + * @author Florent Benoit + */ +@Listeners(MockitoTestNGListener.class) +public class RecipeRetrieverTest { + + /** + * Typ of the recipe used in tests. + */ + private static final String RECIPE_TYPE = "MY_TYPE"; + + /** + * Downloader instance that might be used by recipe retriever for type = location. + */ + @Mock + private RecipeDownloader recipeDownloader; + + /** + * Machine config sent to recipe retriever. + */ + @Mock + private MachineConfig machineConfig; + + /** + * Machine source embedded in machine config. + */ + @Mock + private MachineSource machineSource; + + /** + * Instance used in tests. + */ + @InjectMocks + private RecipeRetriever recipeRetriever; + + + /** + * Setup the rules used in all tests. + */ + @BeforeMethod + public void init() { + when(machineConfig.getSource()).thenReturn(machineSource); + when(machineSource.getType()).thenReturn(RECIPE_TYPE); + } + + + /** + * Check that when content is set in machine source, recipe is based on this content. + * @throws MachineException if recipe is not retrieved + */ + @Test + public void checkWithContent() throws MachineException { + String RECIPE = "FROM TOTO"; + when(machineSource.getContent()).thenReturn(RECIPE); + Recipe recipe = recipeRetriever.getRecipe(machineConfig); + Assert.assertNotNull(recipe); + assertEquals(recipe.getType(), RECIPE_TYPE); + assertEquals(recipe.getScript(), RECIPE); + } + + + /** + * Check that when location is set in machine source, recipe retriever ask the recipe downloader. + * @throws MachineException if recipe is not retrieved + */ + @Test + public void checkWithLocation() throws MachineException { + String LOCATION = "http://eclipse.org/my-che.recipe"; + when(machineSource.getLocation()).thenReturn(LOCATION); + recipeRetriever.getRecipe(machineConfig); + verify(recipeDownloader).getRecipe(machineConfig); + } +} diff --git a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/DefaultWorkspaceValidator.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/DefaultWorkspaceValidator.java index 4d2ec348263..89f58ea80c1 100644 --- a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/DefaultWorkspaceValidator.java +++ b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/DefaultWorkspaceValidator.java @@ -123,6 +123,9 @@ private void validateEnv(Environment environment, String workspaceName) throws B private void validateMachine(MachineConfig machineCfg, String envName) throws BadRequestException { checkArgument(!isNullOrEmpty(machineCfg.getName()), "Environment %s contains machine with null or empty name", envName); checkNotNull(machineCfg.getSource(), "Environment " + envName + " contains machine without source"); + checkArgument(!(machineCfg.getSource().getContent() == null && machineCfg.getSource().getLocation() == null), + "Environment " + envName + " contains machine with source but this source doesn't define a location or content"); + checkArgument(machineInstanceProviders.hasProvider(machineCfg.getType()), "Type %s of machine %s in environment %s is not supported. Supported values: %s.", machineCfg.getType(), diff --git a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/WorkspaceService.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/WorkspaceService.java index 27116287585..06c85ec23df 100644 --- a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/WorkspaceService.java +++ b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/WorkspaceService.java @@ -712,7 +712,9 @@ public Response createMachine(@ApiParam("The workspace id") requiredNotNull(machineConfig.getType(), "Machine type"); requiredNotNull(machineConfig.getSource(), "Machine source"); requiredNotNull(machineConfig.getSource().getType(), "Machine source type"); - requiredNotNull(machineConfig.getSource().getLocation(), "Machine source location"); + // definition of source should come either with a content or with location + requiredOnlyOneNotNull(machineConfig.getSource().getLocation(), machineConfig.getSource().getContent(), + "Machine source should provide either location or content"); final WorkspaceImpl workspace = workspaceManager.getWorkspace(workspaceId); if (workspace.getRuntime() == null) { @@ -766,6 +768,27 @@ private void requiredNotNull(Object object, String subject) throws BadRequestExc } } + /** + * Checks only one of the given object reference is {@code null} + * + * @param object1 + * object reference to check + * @param object2 + * object reference to check + * @param subject + * used as subject of exception message "{subject} required" + * @throws BadRequestException + * when objects are both null or have both a value reference is {@code null} + */ + private void requiredOnlyOneNotNull(Object object1, Object object2, String subject) throws BadRequestException { + if (object1 == null && object2 == null) { + throw new BadRequestException(subject + " required"); + } + if (object1 != null && object2 != null) { + throw new BadRequestException(subject + " required"); + } + } + /* * Validate composite key. * diff --git a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/DefaultWorkspaceValidatorTest.java b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/DefaultWorkspaceValidatorTest.java index 77a846842b5..fb992f2c749 100644 --- a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/DefaultWorkspaceValidatorTest.java +++ b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/DefaultWorkspaceValidatorTest.java @@ -12,7 +12,6 @@ import org.eclipse.che.api.core.BadRequestException; import org.eclipse.che.api.machine.server.MachineInstanceProviders; -import org.eclipse.che.api.machine.server.spi.InstanceProvider; import org.eclipse.che.api.machine.shared.dto.CommandDto; import org.eclipse.che.api.machine.shared.dto.MachineConfigDto; import org.eclipse.che.api.machine.shared.dto.MachineSourceDto; @@ -23,9 +22,7 @@ import org.eclipse.che.api.workspace.shared.dto.WorkspaceConfigDto; import org.mockito.InjectMocks; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.testng.MockitoTestNGListener; -import org.testng.annotations.BeforeClass; import org.testng.annotations.BeforeMethod; import org.testng.annotations.DataProvider; import org.testng.annotations.Listeners; @@ -40,8 +37,6 @@ import static java.util.Collections.singletonList; import static java.util.Collections.singletonMap; import static org.eclipse.che.dto.server.DtoFactory.newDto; -import static org.mockito.Matchers.anyString; -import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; /** @@ -507,6 +502,19 @@ public void shouldFailValidationIfEnvVarValueIsNull() throws Exception { wsValidator.validateConfig(config); } + @Test(expectedExceptions = BadRequestException.class, + expectedExceptionsMessageRegExp = "Environment dev-env contains machine with source but this source doesn't define a location or content") + public void shouldFailValidationIfMissingLocationOrContent() throws Exception { + final WorkspaceConfigDto config = createConfig(); + config.getEnvironments() + .get(0) + .getMachineConfigs() + .get(0) + .withSource(newDto(MachineSourceDto.class).withType("dockerfile")); + + wsValidator.validateConfig(config); + } + private static WorkspaceConfigDto createConfig() { final WorkspaceConfigDto workspaceConfigDto = newDto(WorkspaceConfigDto.class).withName("ws-name") .withDefaultEnv("dev-env"); diff --git a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceManagerTest.java b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceManagerTest.java index 66c756a1745..6dcbf2a636e 100644 --- a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceManagerTest.java +++ b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceManagerTest.java @@ -607,7 +607,7 @@ private static WorkspaceConfigImpl createConfig() { .setDev(true) .setName("dev-machine") .setType("docker") - .setSource(new MachineSourceImpl("location", "dockerfile")) + .setSource(new MachineSourceImpl("location").setLocation("dockerfile")) .setServers(asList(new ServerConfImpl("ref1", "8080", "https", diff --git a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceRuntimesTest.java b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceRuntimesTest.java index ce8fd7fb46f..09c897e1461 100644 --- a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceRuntimesTest.java +++ b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceRuntimesTest.java @@ -719,7 +719,7 @@ private static MachineConfigImpl createConfig(boolean isDev) { .setDev(isDev) .setType("docker") .setLimits(new LimitsImpl(1024)) - .setSource(new MachineSourceImpl("git", "location")) + .setSource(new MachineSourceImpl("git").setLocation("location")) .setName("dev-machine") .build(); } diff --git a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceServiceTest.java b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceServiceTest.java index 97b924346a1..608c1cb44f4 100644 --- a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceServiceTest.java +++ b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceServiceTest.java @@ -721,7 +721,7 @@ private static EnvironmentDto createEnvDto() { .setDev(true) .setName("dev-machine") .setType("docker") - .setSource(new MachineSourceImpl("location", "recipe")) + .setSource(new MachineSourceImpl("location").setLocation("recipe")) .setServers(asList(new ServerConfImpl("wsagent", "8080", "https", diff --git a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/stack/StackServiceTest.java b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/stack/StackServiceTest.java index e53cc02de99..128ed707aa6 100644 --- a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/stack/StackServiceTest.java +++ b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/stack/StackServiceTest.java @@ -158,7 +158,7 @@ public void setUp() throws IOException, ConflictException { componentsImpl = Collections.singletonList(new StackComponentImpl(COMPONENT_NAME, COMPONENT_VERSION)); stackSourceImpl = new StackSourceImpl(SOURCE_TYPE, SOURCE_ORIGIN); CommandImpl command = new CommandImpl(COMMAND_NAME, COMMAND_LINE, COMMAND_TYPE); - MachineSourceImpl machineSource = new MachineSourceImpl(MACHINE_SOURCE_TYPE, MACHINE_SOURCE_LOCATION); + MachineSourceImpl machineSource = new MachineSourceImpl(MACHINE_SOURCE_TYPE).setLocation(MACHINE_SOURCE_LOCATION); int limitMemory = 1000; LimitsImpl limits = new LimitsImpl(limitMemory); MachineConfigImpl machineConfig = new MachineConfigImpl(IS_DEV, diff --git a/wsmaster/wsmaster-local/src/main/java/org/eclipse/che/api/local/LocalSnapshotDaoImpl.java b/wsmaster/wsmaster-local/src/main/java/org/eclipse/che/api/local/LocalSnapshotDaoImpl.java index 81098ef9338..23c5d79d9bc 100644 --- a/wsmaster/wsmaster-local/src/main/java/org/eclipse/che/api/local/LocalSnapshotDaoImpl.java +++ b/wsmaster/wsmaster-local/src/main/java/org/eclipse/che/api/local/LocalSnapshotDaoImpl.java @@ -13,13 +13,13 @@ import com.google.common.reflect.TypeToken; import org.eclipse.che.api.core.NotFoundException; +import org.eclipse.che.api.core.model.machine.MachineSource; import org.eclipse.che.api.local.storage.LocalStorage; import org.eclipse.che.api.local.storage.LocalStorageFactory; import org.eclipse.che.api.machine.server.dao.SnapshotDao; import org.eclipse.che.api.machine.server.exception.SnapshotException; import org.eclipse.che.api.machine.server.model.impl.SnapshotImpl; -import org.eclipse.che.api.machine.server.recipe.adapters.InstanceKeyAdapter; -import org.eclipse.che.api.machine.server.spi.InstanceKey; +import org.eclipse.che.api.machine.server.model.impl.adapter.MachineSourceAdapter; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; @@ -50,7 +50,7 @@ public class LocalSnapshotDaoImpl implements SnapshotDao { @Inject public LocalSnapshotDaoImpl(LocalStorageFactory storageFactory) throws IOException { snapshots = new HashMap<>(); - snapshotStorage = storageFactory.create("snapshots.json", singletonMap(InstanceKey.class, new InstanceKeyAdapter())); + snapshotStorage = storageFactory.create("snapshots.json", singletonMap(MachineSource.class, new MachineSourceAdapter())); } @Override diff --git a/wsmaster/wsmaster-local/src/test/java/org/eclipse/che/api/local/LocalRecipeDaoImplTest.java b/wsmaster/wsmaster-local/src/test/java/org/eclipse/che/api/local/LocalRecipeDaoImplTest.java index 3831c9cc04f..e59c26f68e9 100644 --- a/wsmaster/wsmaster-local/src/test/java/org/eclipse/che/api/local/LocalRecipeDaoImplTest.java +++ b/wsmaster/wsmaster-local/src/test/java/org/eclipse/che/api/local/LocalRecipeDaoImplTest.java @@ -17,8 +17,6 @@ import org.eclipse.che.api.core.acl.AclEntryImpl; import org.eclipse.che.api.local.storage.LocalStorageFactory; import org.eclipse.che.api.machine.server.recipe.RecipeImpl; -import org.eclipse.che.api.machine.server.recipe.adapters.InstanceKeyAdapter; -import org.eclipse.che.api.machine.server.spi.InstanceKey; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; diff --git a/wsmaster/wsmaster-local/src/test/java/org/eclipse/che/api/local/LocalSnapshotDaoTest.java b/wsmaster/wsmaster-local/src/test/java/org/eclipse/che/api/local/LocalSnapshotDaoTest.java index 07edb5bb5de..3c04c4c99d8 100644 --- a/wsmaster/wsmaster-local/src/test/java/org/eclipse/che/api/local/LocalSnapshotDaoTest.java +++ b/wsmaster/wsmaster-local/src/test/java/org/eclipse/che/api/local/LocalSnapshotDaoTest.java @@ -13,17 +13,17 @@ import com.google.gson.Gson; import com.google.gson.GsonBuilder; +import org.eclipse.che.api.core.model.machine.MachineSource; import org.eclipse.che.api.local.storage.LocalStorageFactory; import org.eclipse.che.api.machine.server.model.impl.SnapshotImpl; -import org.eclipse.che.api.machine.server.recipe.adapters.InstanceKeyAdapter; -import org.eclipse.che.api.machine.server.spi.InstanceKey; +import org.eclipse.che.api.machine.server.model.impl.adapter.MachineSourceAdapter; +import org.mockito.Mock; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import java.net.URL; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.Map; import static java.nio.file.Files.readAllBytes; import static java.nio.file.Files.write; @@ -39,12 +39,15 @@ public class LocalSnapshotDaoTest { private static Gson GSON = new GsonBuilder().setPrettyPrinting() - .registerTypeAdapter(InstanceKey.class, new InstanceKeyAdapter()) + .registerTypeAdapter(MachineSource.class, new MachineSourceAdapter()) .create(); private LocalSnapshotDaoImpl snapshotDao; private Path snapshotsPath; + @Mock + private MachineSource machineSource; + @BeforeMethod public void setUp() throws Exception { final URL url = Thread.currentThread().getContextClassLoader().getResource("."); @@ -80,7 +83,7 @@ private SnapshotImpl createSnapshot() { return SnapshotImpl.builder() .generateId() .setType("docker") - .setInstanceKey(new DummyInstanceKey()) + .setMachineSource(machineSource) .setNamespace("user123") .setWorkspaceId("workspace123") .setMachineName("machine123") @@ -91,18 +94,4 @@ private SnapshotImpl createSnapshot() { .build(); } - private static class DummyInstanceKey implements InstanceKey { - - Map fields = singletonMap("field1", "value1"); - - @Override - public Map getFields() { - return fields; - } - - @Override - public boolean equals(Object obj) { - return obj instanceof InstanceKey && ((InstanceKey)obj).getFields().equals(getFields()); - } - } } diff --git a/wsmaster/wsmaster-local/src/test/java/org/eclipse/che/api/local/LocalWorkspaceDaoTest.java b/wsmaster/wsmaster-local/src/test/java/org/eclipse/che/api/local/LocalWorkspaceDaoTest.java index d3f72f00b0e..dbd9796166c 100644 --- a/wsmaster/wsmaster-local/src/test/java/org/eclipse/che/api/local/LocalWorkspaceDaoTest.java +++ b/wsmaster/wsmaster-local/src/test/java/org/eclipse/che/api/local/LocalWorkspaceDaoTest.java @@ -94,7 +94,7 @@ private static WorkspaceImpl createWorkspace() { recipe.setType("dockerfile"); recipe.setScript("FROM codenvy/jdk7\nCMD tail -f /dev/null"); - final MachineSourceImpl machineSource = new MachineSourceImpl("recipe", "recipe-url"); + final MachineSourceImpl machineSource = new MachineSourceImpl("recipe").setLocation("recipe-url"); final MachineConfigImpl machineCfg1 = new MachineConfigImpl(true, "dev-machine", "machine-type",