diff --git a/core/platform-api/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/DefaultWorkspaceConfigValidator.java b/core/platform-api/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/DefaultWorkspaceConfigValidator.java index 9e83450bd1b4..49a826d96f2d 100644 --- a/core/platform-api/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/DefaultWorkspaceConfigValidator.java +++ b/core/platform-api/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/DefaultWorkspaceConfigValidator.java @@ -30,7 +30,7 @@ @Singleton public class DefaultWorkspaceConfigValidator implements WorkspaceConfigValidator { /* should contain [3, 20] characters, first and last character is letter or digit, available characters {A-Za-z0-9.-_}*/ - private static final Pattern WS_NAME = Pattern.compile("[\\w][\\w\\.\\-]{1,18}[\\w]"); + private static final Pattern WS_NAME = Pattern.compile("[a-zA-Z0-9][-_.a-zA-Z0-9]{1,18}[a-zA-Z0-9]"); /** * Checks that workspace configuration is valid. @@ -49,58 +49,67 @@ public class DefaultWorkspaceConfigValidator implements WorkspaceConfigValidator @Override public void validate(WorkspaceConfig config) throws BadRequestException { // configuration object itself - requiredNotNull(config.getName(), "Workspace name required"); - if (!WS_NAME.matcher(config.getName()).matches()) { - throw new BadRequestException("Incorrect workspace name, it must be between 3 and 20 characters and may contain digits, " + - "latin letters, underscores, dots, dashes and should start and end only with digits, " + - "latin letters or underscores"); - } + checkNotNull(config.getName(), "Workspace name required"); + checkArgument(WS_NAME.matcher(config.getName()).matches(), + "Incorrect workspace name, it must be between 3 and 20 characters and may contain digits, " + + "latin letters, underscores, dots, dashes and should start and end only with digits, " + + "latin letters or underscores"); //attributes for (String attributeName : config.getAttributes().keySet()) { //attribute name should not be empty and should not start with codenvy - if (attributeName.trim().isEmpty() || attributeName.toLowerCase().startsWith("codenvy")) { - throw new BadRequestException(format("Attribute name '%s' is not valid", attributeName)); - } + checkArgument(attributeName != null && !attributeName.trim().isEmpty() && !attributeName.toLowerCase().startsWith("codenvy"), + "Attribute name '%s' is not valid", + attributeName); } //environments - requiredNotNull(config.getDefaultEnv(), "Workspace default environment name required"); - if (!config.getEnvironments().stream().anyMatch(env -> env.getName().equals(config.getDefaultEnv()))) { - throw new BadRequestException("Workspace default environment configuration required"); - } + checkArgument(!isNullOrEmpty(config.getDefaultEnv()), "Workspace default environment name required"); + checkArgument(config.getEnvironments() + .stream() + .anyMatch(env -> config.getDefaultEnv().equals(env.getName())), + "Workspace default environment configuration required"); + for (Environment environment : config.getEnvironments()) { final String envName = environment.getName(); - requiredNotNull(envName, "Environment name should not be null"); + checkArgument(!isNullOrEmpty(envName), "Environment name should be neither null nor empty"); + + checkArgument(environment.getRecipe() == null || "docker".equals(environment.getRecipe().getType()), + "Couldn't start workspace '%s' from environment '%s', environment recipe has unsupported type '%s'", + config.getName(), + envName, + environment.getRecipe() != null ? environment.getRecipe().getType() : null); //machine configs - if (environment.getMachineConfigs().isEmpty()) { - throw new BadRequestException("Environment '" + envName + "' should contain at least 1 machine"); - } + checkArgument(!environment.getMachineConfigs().isEmpty(), "Environment '%s' should contain at least 1 machine", envName); + final long devCount = environment.getMachineConfigs() .stream() .filter(MachineConfig::isDev) .count(); - if (devCount != 1) { - throw new BadRequestException(format("Environment should contain exactly 1 dev machine, but '%s' contains '%d'", - envName, - devCount)); - } + checkArgument(devCount == 1, + "Environment should contain exactly 1 dev machine, but '%s' contains '%d'", + envName, + devCount); for (MachineConfig machineCfg : environment.getMachineConfigs()) { - if (isNullOrEmpty(machineCfg.getName())) { - throw new BadRequestException("Environment " + envName + " contains machine without of name"); - } - requiredNotNull(machineCfg.getSource(), "Environment " + envName + " contains machine without of source"); - //TODO require type? + checkArgument(!isNullOrEmpty(machineCfg.getName()), "Environment %s contains machine with null or empty name", envName); + checkNotNull(machineCfg.getSource(), "Environment " + envName + " contains machine without source"); + checkArgument("docker".equals(machineCfg.getType()), + "Type of machine %s in environment %s is not supported. Supported value is 'docker'.", + machineCfg.getName(), + envName); } } //commands for (Command command : config.getCommands()) { - requiredNotNull(command.getName(), "Workspace " + config.getName() + " contains command without of name"); - requiredNotNull(command.getCommandLine(), format("Command line required for command '%s' in workspace '%s'", - command.getName(), - config.getName())); + checkArgument(!isNullOrEmpty(command.getName()), + "Workspace %s contains command with null or empty name", + config.getName()); + checkArgument(!isNullOrEmpty(command.getCommandLine()), + "Command line required for command '%s' in workspace '%s'", + command.getName(), + config.getName()); } //projects @@ -111,9 +120,31 @@ public void validate(WorkspaceConfig config) throws BadRequestException { * Checks that object reference is not null, throws {@link BadRequestException} * in the case of null {@code object} with given {@code message}. */ - private void requiredNotNull(Object object, String message) throws BadRequestException { + private void checkNotNull(Object object, String message) throws BadRequestException { if (object == null) { throw new BadRequestException(message); } } + + /** + * Checks that expression is true, throws {@link BadRequestException} otherwise. + * + *

Exception uses error message built from error message template and error message parameters. + */ + private void checkArgument(boolean expression, String errorMessageTemplate, Object... errorMessageParams) throws BadRequestException { + if (!expression) { + throw new BadRequestException(format(errorMessageTemplate, errorMessageParams)); + } + } + + /** + * Checks that expression is true, throws {@link BadRequestException} otherwise. + * + *

Exception uses error message built from error message template and error message parameters. + */ + private void checkArgument(boolean expression, String errorMessage) throws BadRequestException { + if (!expression) { + throw new BadRequestException(errorMessage); + } + } } diff --git a/core/platform-api/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/RuntimeWorkspaceRegistry.java b/core/platform-api/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/RuntimeWorkspaceRegistry.java index e23d21243e8e..29a72ff4de15 100644 --- a/core/platform-api/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/RuntimeWorkspaceRegistry.java +++ b/core/platform-api/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/RuntimeWorkspaceRegistry.java @@ -38,7 +38,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; @@ -65,6 +64,9 @@ * second for owner -> list of workspaces mapping(which speeds up fetching workspaces by owner). * Maps are guarded by {@link ReentrantReadWriteLock}. * + *

The implementation doesn't validate parameters. + * They should be validated by caller of methods of this class. + * * @author Yevhenii Voevodin * @author Alexander Garagatyi */ @@ -120,7 +122,6 @@ public RuntimeWorkspaceImpl start(UsersWorkspace usersWorkspace, String envName, BadRequestException, NotFoundException { checkRegistryIsNotStopped(); - checkWorkspaceIsValidForStart(usersWorkspace, envName); // Prepare runtime workspace for start final RuntimeWorkspaceImpl newRuntime = RuntimeWorkspaceImpl.builder() .fromWorkspace(usersWorkspace) @@ -367,43 +368,6 @@ private void startEnvironment(Environment environment, String workspaceId, boole } } - /** - * Checks if workspace is valid for start. - */ - private void checkWorkspaceIsValidForStart(UsersWorkspace workspace, String envName) throws BadRequestException { - if (workspace == null) { - throw new BadRequestException("Required non-null workspace"); - } - if (envName == null) { - throw new BadRequestException(format("Couldn't start workspace '%s', environment name is null", - workspace.getConfig().getName())); - } - final Optional environmentOptional = workspace.getConfig() - .getEnvironments() - .stream() - .filter(env -> env.getName().equals(envName)) - .findFirst(); - if (!environmentOptional.isPresent()) { - throw new BadRequestException(format("Couldn't start workspace '%s', workspace doesn't have environment '%s'", - workspace.getConfig().getName(), - envName)); - } - Environment environment = environmentOptional.get(); - if (environment.getRecipe() != null && !"docker".equals(environment.getRecipe().getType())) { - throw new BadRequestException(format("Couldn't start workspace '%s' from environment '%s', " + - "environment recipe has unsupported type '%s'", - workspace.getConfig().getName(), - envName, - environment.getRecipe().getType())); - } - if (findDev(environment.getMachineConfigs()) == null) { - throw new BadRequestException(format("Couldn't start workspace '%s' from environment '%s', " + - "environment doesn't contain dev-machine", - workspace.getConfig().getName(), - envName)); - } - } - /** * Adds given machine to the running workspace, if the workspace exists. * Sets up this machine as dev-machine if it is dev. diff --git a/core/platform-api/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/WorkspaceManager.java b/core/platform-api/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/WorkspaceManager.java index 0f735311c20e..49b556db7647 100644 --- a/core/platform-api/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/WorkspaceManager.java +++ b/core/platform-api/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/WorkspaceManager.java @@ -399,11 +399,13 @@ public UsersWorkspaceImpl startWorkspaceByName(String workspaceName, * @see WorkspaceHooks#beforeCreate(UsersWorkspace, String) * @see RuntimeWorkspaceRegistry#start(UsersWorkspace, String) */ - public RuntimeWorkspaceImpl startTemporaryWorkspace(WorkspaceConfig workspaceConfig, @Nullable String accountId) throws ServerException, - BadRequestException, - ForbiddenException, - NotFoundException, - ConflictException { + public RuntimeWorkspaceImpl startTemporaryWorkspace(WorkspaceConfig workspaceConfig, + @Nullable String accountId) throws ServerException, + BadRequestException, + ForbiddenException, + NotFoundException, + ConflictException { + final UsersWorkspaceImpl workspace = fromConfig(workspaceConfig, getCurrentUserId()); workspace.setTemporary(true); // Temporary workspace is not persistent one, which means @@ -573,7 +575,9 @@ private UsersWorkspaceImpl fromConfig(WorkspaceConfig config, String owner) thro UsersWorkspaceImpl performAsyncStart(UsersWorkspaceImpl workspace, String envName, boolean recover, - @Nullable String accountId) throws ConflictException { + @Nullable String accountId) throws ConflictException, + BadRequestException { + // Runtime workspace registry performs this check as well // but this check needed here because permanent workspace start performed asynchronously // which means that even if registry won't start workspace client receives workspace object @@ -588,6 +592,19 @@ UsersWorkspaceImpl performAsyncStart(UsersWorkspaceImpl workspace, } workspace.setTemporary(false); workspace.setStatus(WorkspaceStatus.STARTING); + + if (envName != null && !workspace.getConfig() + .getEnvironments() + .stream() + .filter(env -> env.getName().equals(envName)) + .findFirst() + .isPresent()) { + + throw new BadRequestException(format("Couldn't start workspace '%s', workspace doesn't have environment '%s'", + workspace.getConfig().getName(), + envName)); + } + executor.execute(ThreadLocalPropagateContext.wrap(() -> { try { performSyncStart(workspace, firstNonNull(envName, workspace.getConfig().getDefaultEnv()), recover, accountId); diff --git a/core/platform-api/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/DefaultWorkspaceConfigValidatorTest.java b/core/platform-api/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/DefaultWorkspaceConfigValidatorTest.java index 7b6f530729af..388ff108bdf2 100644 --- a/core/platform-api/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/DefaultWorkspaceConfigValidatorTest.java +++ b/core/platform-api/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/DefaultWorkspaceConfigValidatorTest.java @@ -11,20 +11,24 @@ package org.eclipse.che.api.workspace.server; import org.eclipse.che.api.core.BadRequestException; -import org.eclipse.che.api.core.model.workspace.WorkspaceConfig; +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; import org.eclipse.che.api.workspace.shared.dto.EnvironmentDto; +import org.eclipse.che.api.workspace.shared.dto.RecipeDto; import org.eclipse.che.api.workspace.shared.dto.WorkspaceConfigDto; import org.testng.annotations.BeforeClass; +import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Optional; import static java.util.Collections.singletonList; +import static java.util.Collections.singletonMap; import static org.eclipse.che.dto.server.DtoFactory.newDto; -import static org.testng.Assert.assertNull; -import static org.testng.AssertJUnit.assertNotNull; /** * Tests for {@link WorkspaceConfigValidator} and {@link DefaultWorkspaceConfigValidator} @@ -41,52 +45,328 @@ public void prepare() throws Exception { } @Test - public void shouldBeNotValidationHaveNotDevMachineInConfig() throws Exception { - BadRequestException exResult = null; - try { - wsValidator.validate(createConfig("dev-workspace", false)); - } catch (BadRequestException e) { - exResult = e; - } - assertNotNull(exResult); + public void shouldValidateCorrectWorkspace() throws Exception { + final WorkspaceConfigDto config = createConfig(); + + + wsValidator.validate(config); } - @Test - public void shouldBeValidationHaveDevMachineInConfig() throws Exception { - Exception exResult = null; - try { - wsValidator.validate(createConfig("dev-workspace", true)); - } catch (BadRequestException e) { - exResult = e; - } - assertNull(exResult); + @Test(expectedExceptions = BadRequestException.class, + expectedExceptionsMessageRegExp = "Workspace name required") + public void shouldFailValidationIfNameIsNull() throws Exception { + final WorkspaceConfigDto config = createConfig(); + config.withName(null); + + + wsValidator.validate(config); } - @Test - public void shouldNotBeValidationHaveDevMachineInConfigButWsNameNull() throws Exception { - Exception exResult = null; - try { - wsValidator.validate(createConfig(null, true)); - } catch (BadRequestException e) { - exResult = e; - } - assertNotNull(exResult); + @Test(dataProvider = "invalidNameProvider", + expectedExceptions = BadRequestException.class, + expectedExceptionsMessageRegExp = "Incorrect workspace name, it must be between 3 and 20 characters and may contain digits, " + + "latin letters, underscores, dots, dashes and should start and end only with digits, " + + "latin letters or underscores") + public void shouldFailValidationIfNameIsInvalid(String name) throws Exception { + final WorkspaceConfigDto config = createConfig(); + config.withName(name); + + + wsValidator.validate(config); + } + + @DataProvider(name = "invalidNameProvider") + public static Object[][] invalidNameProvider() { + return new Object[][] { + {".name"}, + {"name."}, + {"-name"}, + {"name-"}, + {"long-name12345678901234567890"}, + {"_name"}, + {"name_"} + }; + } + + @Test(dataProvider = "validNameProvider") + public void shouldValidateCorrectWorkspaceName(String name) throws Exception { + final WorkspaceConfigDto config = createConfig(); + config.withName(name); + + + wsValidator.validate(config); + } + + @DataProvider(name = "validNameProvider") + public static Object[][] validNameProvider() { + return new Object[][] { + {"name"}, + {"quiteLongName1234567"}, + {"name-with-dashes"}, + {"name.with.dots"}, + {"name0with1digits"}, + {"mixed-symbols.name12"}, + {"123456"}, + {"name_name"}, + {"123-456.78"} + }; + } + + @Test(expectedExceptions = BadRequestException.class, + expectedExceptionsMessageRegExp = "Attribute name 'null' is not valid") + public void shouldFailValidationIfAttributeNameIsNull() throws Exception { + final WorkspaceConfigDto config = createConfig(); + config.getAttributes() + .put(null, "value1"); + + + wsValidator.validate(config); + } + + @Test(expectedExceptions = BadRequestException.class, + expectedExceptionsMessageRegExp = "Attribute name '' is not valid") + public void shouldFailValidationIfAttributeNameIsEmpty() throws Exception { + final WorkspaceConfigDto config = createConfig(); + config.getAttributes() + .put("", "value1"); + + + wsValidator.validate(config); + } + + @Test(expectedExceptions = BadRequestException.class, + expectedExceptionsMessageRegExp = "Attribute name '.*' is not valid") + public void shouldFailValidationIfAttributeNameStartsWithWordCodenvy() throws Exception { + final WorkspaceConfigDto config = createConfig(); + config.getAttributes() + .put("codenvy_key", "value1"); + + + wsValidator.validate(config); + } + + @Test(expectedExceptions = BadRequestException.class, + expectedExceptionsMessageRegExp = "Workspace default environment name required") + public void shouldFailValidationIfDefaultEnvNameIsNull() throws Exception { + final WorkspaceConfigDto config = createConfig(); + config.setDefaultEnv(null); + + + wsValidator.validate(config); + } + + @Test(expectedExceptions = BadRequestException.class, + expectedExceptionsMessageRegExp = "Workspace default environment name required") + public void shouldFailValidationIfDefaultEnvNameIsEmpty() throws Exception { + final WorkspaceConfigDto config = createConfig(); + config.setDefaultEnv(""); + + + wsValidator.validate(config); + } + + @Test(expectedExceptions = BadRequestException.class, + expectedExceptionsMessageRegExp = "Workspace default environment configuration required") + public void shouldFailValidationIfEnvWithDefaultEnvNameIsNull() throws Exception { + final WorkspaceConfigDto config = createConfig(); + config.setEnvironments(null); + + + wsValidator.validate(config); + } + + @Test(expectedExceptions = BadRequestException.class, + expectedExceptionsMessageRegExp = "Environment name should be neither null nor empty") + public void shouldFailValidationIfEnvNameIsNull() throws Exception { + final WorkspaceConfigDto config = createConfig(); + config.getEnvironments() + .add(newDto(EnvironmentDto.class).withName(null) + .withMachineConfigs(config.getEnvironments() + .get(0) + .getMachineConfigs())); + + + wsValidator.validate(config); + } + + @Test(expectedExceptions = BadRequestException.class, + expectedExceptionsMessageRegExp = "Environment name should be neither null nor empty") + public void shouldFailValidationIfEnvNameIsEmpty() throws Exception { + final WorkspaceConfigDto config = createConfig(); + config.getEnvironments() + .add(newDto(EnvironmentDto.class).withName("") + .withMachineConfigs(config.getEnvironments() + .get(0) + .getMachineConfigs())); + + + wsValidator.validate(config); + } + + @Test(expectedExceptions = BadRequestException.class, + expectedExceptionsMessageRegExp = "Couldn't start workspace '.*' from environment '.*', environment recipe has unsupported type '.*'") + public void shouldFailValidationIfEnvironmentRecipeTypeIsNotDocker() throws Exception { + final WorkspaceConfigDto config = createConfig(); + config.getEnvironments() + .get(0) + .withRecipe(newDto(RecipeDto.class).withType("kubernetes")); + + + wsValidator.validate(config); } @Test - public void shouldNotBeValidationHaveNotDevMachineInConfigButWsNameNull() throws Exception { - Exception exResult = null; - try { - wsValidator.validate(createConfig(null, false)); - } catch (BadRequestException e) { - exResult = e; - } - assertNotNull(exResult); + public void shouldNotFailValidationIfEnvironmentRecipeTypeIsDocker() throws Exception { + final WorkspaceConfigDto config = createConfig(); + config.getEnvironments() + .get(0) + .withRecipe(newDto(RecipeDto.class).withType("docker")); + + + wsValidator.validate(config); + } + + @Test(expectedExceptions = BadRequestException.class, + expectedExceptionsMessageRegExp = "Environment '.*' should contain at least 1 machine") + public void shouldFailValidationIfMachinesListIsEmpty() throws Exception { + final WorkspaceConfigDto config = createConfig(); + config.getEnvironments() + .get(0) + .withMachineConfigs(null); + + + wsValidator.validate(config); + } + + @Test(expectedExceptions = BadRequestException.class, + expectedExceptionsMessageRegExp = "Environment should contain exactly 1 dev machine, but '.*' contains '0'") + public void shouldFailValidationIfNoDevMachineFound() throws Exception { + final WorkspaceConfigDto config = createConfig(); + config.getEnvironments() + .get(0) + .getMachineConfigs() + .stream() + .filter(MachineConfigDto::isDev) + .forEach(machine -> machine.withDev(false)); + + + wsValidator.validate(config); + } + + @Test(expectedExceptions = BadRequestException.class, + expectedExceptionsMessageRegExp = "Environment should contain exactly 1 dev machine, but '.*' contains '2'") + public void shouldFailValidationIf2DevMachinesFound() throws Exception { + final WorkspaceConfigDto config = createConfig(); + final Optional devMachine = config.getEnvironments() + .get(0) + .getMachineConfigs() + .stream() + .filter(MachineConfigDto::isDev) + .findAny(); + config.getEnvironments() + .get(0) + .getMachineConfigs() + .add(devMachine.get().withName("other-name")); + + + wsValidator.validate(config); + } + + @Test(expectedExceptions = BadRequestException.class, + expectedExceptionsMessageRegExp = "Environment .* contains machine with null or empty name") + public void shouldFailValidationIfMachineNameIsNull() throws Exception { + final WorkspaceConfigDto config = createConfig(); + config.getEnvironments().get(0).getMachineConfigs().get(0).withName(null); + + + wsValidator.validate(config); + } + + @Test(expectedExceptions = BadRequestException.class, + expectedExceptionsMessageRegExp = "Environment .* contains machine with null or empty name") + public void shouldFailValidationIfMachineNameIsEmpty() throws Exception { + final WorkspaceConfigDto config = createConfig(); + config.getEnvironments().get(0).getMachineConfigs().get(0).withName(""); + + + wsValidator.validate(config); + } + + @Test(expectedExceptions = BadRequestException.class, + expectedExceptionsMessageRegExp = "Environment .* contains machine without source") + public void shouldFailValidationIfMachineSourceIsNull() throws Exception { + final WorkspaceConfigDto config = createConfig(); + config.getEnvironments().get(0).getMachineConfigs().get(0).withSource(null); + + + wsValidator.validate(config); + } + + @Test(expectedExceptions = BadRequestException.class, + expectedExceptionsMessageRegExp = "Type of machine .* in environment .* is not supported. Supported value is 'docker'.") + public void shouldFailValidationIfMachineTypeIsNull() throws Exception { + final WorkspaceConfigDto config = createConfig(); + config.getEnvironments().get(0).getMachineConfigs().get(0).withType(null); + + + wsValidator.validate(config); + } + + @Test(expectedExceptions = BadRequestException.class, + expectedExceptionsMessageRegExp = "Type of machine .* in environment .* is not supported. Supported value is 'docker'.") + public void shouldFailValidationIfMachineTypeIsNotDocker() throws Exception { + final WorkspaceConfigDto config = createConfig(); + config.getEnvironments().get(0).getMachineConfigs().get(0).withType("compose"); + + + wsValidator.validate(config); + } + + @Test(expectedExceptions = BadRequestException.class, + expectedExceptionsMessageRegExp = "Workspace .* contains command with null or empty name") + public void shouldFailValidationIfCommandNameIsNull() throws Exception { + final WorkspaceConfigDto config = createConfig(); + config.getCommands().get(0).withName(null); + + + wsValidator.validate(config); + } + + @Test(expectedExceptions = BadRequestException.class, + expectedExceptionsMessageRegExp = "Workspace .* contains command with null or empty name") + public void shouldFailValidationIfCommandNameIsEmpty() throws Exception { + final WorkspaceConfigDto config = createConfig(); + config.getCommands().get(0).withName(null); + + + wsValidator.validate(config); } + @Test(expectedExceptions = BadRequestException.class, + expectedExceptionsMessageRegExp = "Command line required for command .* in workspace .*") + public void shouldFailValidationIfCommandLineIsNull() throws Exception { + final WorkspaceConfigDto config = createConfig(); + config.getCommands().get(0).withCommandLine(null); + + + wsValidator.validate(config); + } + + @Test(expectedExceptions = BadRequestException.class, + expectedExceptionsMessageRegExp = "Command line required for command .* in workspace .*") + public void shouldFailValidationIfCommandLineIsEmpty() throws Exception { + final WorkspaceConfigDto config = createConfig(); + config.getCommands().get(0).withCommandLine(""); + - private static WorkspaceConfig createConfig(String wsName, boolean isDevMachine) { - MachineConfigDto devMachine = newDto(MachineConfigDto.class).withDev(isDevMachine) + wsValidator.validate(config); + } + + private static WorkspaceConfigDto createConfig() { + final WorkspaceConfigDto workspaceConfigDto = newDto(WorkspaceConfigDto.class).withName("ws-name") + .withDefaultEnv("dev-env"); + + MachineConfigDto devMachine = newDto(MachineConfigDto.class).withDev(true) .withName("dev-machine") .withType("docker") .withSource(newDto(MachineSourceDto.class).withLocation("location") @@ -94,8 +374,17 @@ private static WorkspaceConfig createConfig(String wsName, boolean isDevMachine) EnvironmentDto devEnv = newDto(EnvironmentDto.class).withName("dev-env") .withMachineConfigs(new ArrayList<>(singletonList(devMachine))) .withRecipe(null); - return newDto(WorkspaceConfigDto.class).withName(wsName) - .withEnvironments(new ArrayList<>(singletonList(devEnv))) - .withDefaultEnv("dev-env"); + workspaceConfigDto.setEnvironments(new ArrayList<>(singletonList(devEnv))); + + List commandDtos = new ArrayList<>(); + commandDtos.add(newDto(CommandDto.class).withName("command_name") + .withType("maven") + .withCommandLine("mvn clean install") + .withAttributes(new HashMap<>(singletonMap("cmd-attribute-name", "cmd-attribute-value")))); + workspaceConfigDto.setCommands(commandDtos); + + workspaceConfigDto.withAttributes(new HashMap<>(singletonMap("ws-attribute-name", "ws-attribute-value"))); + + return workspaceConfigDto; } } diff --git a/core/platform-api/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/RuntimeWorkspaceRegistryTest.java b/core/platform-api/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/RuntimeWorkspaceRegistryTest.java index a71146480e14..be67ce0f7aaa 100644 --- a/core/platform-api/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/RuntimeWorkspaceRegistryTest.java +++ b/core/platform-api/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/RuntimeWorkspaceRegistryTest.java @@ -10,7 +10,6 @@ *******************************************************************************/ package org.eclipse.che.api.workspace.server; -import org.eclipse.che.api.core.BadRequestException; import org.eclipse.che.api.core.ConflictException; import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.ServerException; @@ -22,7 +21,6 @@ import org.eclipse.che.api.machine.server.exception.MachineException; 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.recipe.RecipeImpl; import org.eclipse.che.api.workspace.server.model.impl.EnvironmentImpl; import org.eclipse.che.api.workspace.server.model.impl.RuntimeWorkspaceImpl; import org.eclipse.che.api.workspace.server.model.impl.UsersWorkspaceImpl; @@ -37,7 +35,6 @@ import java.util.Collections; import static java.util.Arrays.asList; -import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyBoolean; @@ -78,48 +75,6 @@ public void setUp() throws Exception { registry = new RuntimeWorkspaceRegistry(machineManagerMock); } - @Test(expectedExceptions = BadRequestException.class, expectedExceptionsMessageRegExp = "Required non-null workspace") - public void testStartWithNullWorkspace() throws Exception { - registry.start(null, "environment"); - } - - @Test(expectedExceptions = BadRequestException.class, - expectedExceptionsMessageRegExp = "Couldn't start workspace '.*', environment name is null") - public void testStartWithNullEnvName() throws Exception { - registry.start(workspaceMock(), null); - } - - @Test(expectedExceptions = BadRequestException.class, - expectedExceptionsMessageRegExp = "Couldn't start workspace '', workspace doesn't have environment 'non-existing'") - public void testStartWithNonExistingEnvironmentName() throws Exception { - registry.start(workspaceMock(), "non-existing"); - } - - @Test(expectedExceptions = BadRequestException.class, - expectedExceptionsMessageRegExp = "Couldn't start workspace '.*' from environment '.*', " + - "environment recipe has unsupported type 'non-docker'") - public void testStartWithNonDockerEnvironmentRecipe() throws Exception { - final UsersWorkspaceImpl workspaceMock = workspaceMock(); - EnvironmentImpl environment = new EnvironmentImpl(workspaceMock.getConfig().getDefaultEnv(), - new RecipeImpl().withType("non-docker"), - null); - when(workspaceMock.getConfig().getEnvironments()).thenReturn(singletonList(environment)); - - registry.start(workspaceMock, workspaceMock.getConfig().getDefaultEnv()); - } - - @Test(expectedExceptions = BadRequestException.class, - expectedExceptionsMessageRegExp = "Couldn't start workspace '.*' from environment '.*', environment doesn't contain dev-machine") - public void testStartWithEnvironmentWhichDoesNotContainDevMachine() throws Exception { - final UsersWorkspaceImpl workspaceMock = workspaceMock(); - EnvironmentImpl environment = new EnvironmentImpl(workspaceMock.getConfig().getDefaultEnv(), - new RecipeImpl().withType("docker"), null); - environment.setMachineConfigs(emptyList()); - when(workspaceMock.getConfig().getEnvironments()).thenReturn(singletonList(environment)); - - registry.start(workspaceMock, workspaceMock.getConfig().getDefaultEnv()); - } - @Test public void workspaceShouldBeInStartingStatusUntilDevMachineIsNotStarted() throws Exception { final MachineManager machineManagerMock = mock(MachineManager.class); diff --git a/core/platform-api/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceManagerTest.java b/core/platform-api/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceManagerTest.java index 2642a4a86607..43f03a52e24b 100644 --- a/core/platform-api/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceManagerTest.java +++ b/core/platform-api/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceManagerTest.java @@ -10,6 +10,7 @@ *******************************************************************************/ package org.eclipse.che.api.workspace.server; +import org.eclipse.che.api.core.BadRequestException; import org.eclipse.che.api.core.ConflictException; import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.ServerException; @@ -17,6 +18,7 @@ import org.eclipse.che.api.core.model.workspace.WorkspaceConfig; import org.eclipse.che.api.core.notification.EventService; import org.eclipse.che.api.machine.server.MachineManager; +import org.eclipse.che.api.machine.server.impl.SnapshotImpl; import org.eclipse.che.api.machine.shared.dto.MachineConfigDto; import org.eclipse.che.api.machine.shared.dto.MachineSourceDto; import org.eclipse.che.api.workspace.server.model.impl.RuntimeWorkspaceImpl; @@ -52,6 +54,7 @@ import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; @@ -89,7 +92,7 @@ public class WorkspaceManagerTest { private WorkspaceManager workspaceManager; @BeforeMethod - public void setUpManager() throws Exception { + public void setUp() throws Exception { workspaceManager = spy(new WorkspaceManager(workspaceDao, registry, workspaceConfigValidator, eventService, machineManager)); workspaceManager.setHooks(workspaceHooks); @@ -106,10 +109,13 @@ public User getUser() { @Test public void shouldBeAbleToCreateWorkspace() throws Exception { + // given final WorkspaceConfig cfg = createConfig(); + // when final UsersWorkspaceImpl workspace = workspaceManager.createWorkspace(cfg, "user123", "account"); + // then assertNotNull(workspace); assertFalse(isNullOrEmpty(workspace.getId())); assertEquals(workspace.getOwner(), "user123"); @@ -122,56 +128,70 @@ public void shouldBeAbleToCreateWorkspace() throws Exception { @Test public void shouldBeAbleToGetWorkspaceById() throws Exception { + // given final UsersWorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), "user123", "account"); when(workspaceDao.get(workspace.getId())).thenReturn(workspace); when(registry.get(any())).thenThrow(new NotFoundException("")); + // when final UsersWorkspaceImpl result = workspaceManager.getWorkspace(workspace.getId()); + // then assertEquals(result, workspace); } @Test public void getWorkspaceByIdShouldReturnWorkspaceWithStatusEqualToItsRuntimeStatus() throws Exception { + // given final UsersWorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), "user123", "account"); when(workspaceDao.get(workspace.getId())).thenReturn(workspace); final RuntimeWorkspaceImpl runtime = createRuntime(workspace); runtime.setStatus(STARTING); when(registry.get(workspace.getId())).thenReturn(runtime); + // when final UsersWorkspaceImpl result = workspaceManager.getWorkspace(workspace.getId()); + // then assertEquals(result.getStatus(), STARTING, "Workspace status must be taken from the runtime instance"); } @Test public void shouldBeAbleToGetWorkspaceByName() throws Exception { + // given final UsersWorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), "user123", "account"); when(workspaceDao.get(workspace.getConfig().getName(), workspace.getOwner())).thenReturn(workspace); when(registry.get(any())).thenThrow(new NotFoundException("")); + // when final UsersWorkspaceImpl result = workspaceManager.getWorkspace(workspace.getConfig().getName(), workspace.getOwner()); + // then assertEquals(result, workspace); } @Test public void getWorkspaceByNameShouldReturnWorkspaceWithStatusEqualToItsRuntimeStatus() throws Exception { + // given final UsersWorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), "user123", "account"); when(workspaceDao.get(workspace.getConfig().getName(), workspace.getOwner())).thenReturn(workspace); final RuntimeWorkspaceImpl runtime = createRuntime(workspace); runtime.setStatus(STARTING); when(registry.get(workspace.getId())).thenReturn(runtime); + // when final UsersWorkspaceImpl result = workspaceManager.getWorkspace(workspace.getConfig().getName(), workspace.getOwner()); + // then assertEquals(result.getStatus(), STARTING, "Workspace status must be taken from the runtime instance"); } @Test(expectedExceptions = NotFoundException.class) public void getWorkspaceShouldThrowNotFoundExceptionWhenWorkspaceDoesNotExist() throws Exception { + // given when(workspaceDao.get(any())).thenThrow(new NotFoundException("not found")); + // when workspaceManager.getWorkspace("workspace123"); } @@ -219,13 +239,16 @@ public void shouldBeAbleToGetWorkspacesByOwner() throws Exception { @Test public void shouldBeAbleToUpdateWorkspace() throws Exception { + // given final UsersWorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), "user123", "account"); when(workspaceDao.get(workspace.getId())).thenReturn(workspace); when(registry.get(any())).thenThrow(new NotFoundException("")); final WorkspaceConfig update = createConfig(); + // when final UsersWorkspace updated = workspaceManager.updateWorkspace(workspace.getId(), update); + // then verify(workspaceDao).update(any(UsersWorkspaceImpl.class)); assertEquals(updated.getStatus(), STOPPED); assertFalse(updated.isTemporary()); @@ -238,6 +261,7 @@ public void shouldBeAbleToUpdateWorkspace() throws Exception { @Test public void workspaceUpdateShouldReturnWorkspaceWithStatusEqualToItsRuntimeStatus() throws Exception { + // given final UsersWorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), "user123", "account"); when(workspaceDao.get(workspace.getId())).thenReturn(workspace); final RuntimeWorkspaceImpl runtime = createRuntime(workspace); @@ -245,39 +269,49 @@ public void workspaceUpdateShouldReturnWorkspaceWithStatusEqualToItsRuntimeStatu when(registry.get(workspace.getId())).thenReturn(runtime); final WorkspaceConfig update = createConfig(); + // when UsersWorkspace updated = workspaceManager.updateWorkspace(workspace.getId(), update); + // then verify(workspaceDao).update(any(UsersWorkspaceImpl.class)); assertEquals(updated.getStatus(), STARTING); } @Test public void shouldRemoveWorkspace() throws Exception { + // given final UsersWorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), "user123", "account"); when(workspaceDao.get(workspace.getId())).thenReturn(workspace); + // when workspaceManager.removeWorkspace(workspace.getId()); + // then verify(workspaceDao).remove(workspace.getId()); verify(workspaceHooks).afterRemove(workspace.getId()); } @Test(expectedExceptions = ConflictException.class) public void shouldNotRemoveWorkspaceIfItIsNotStopped() throws Exception { + // given final UsersWorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), "user123", "account"); when(registry.hasRuntime(workspace.getId())).thenReturn(true); + // when workspaceManager.removeWorkspace(workspace.getId()); } @Test public void shouldBeAbleToGetRuntimeWorkspaceById() throws Exception { + // given final UsersWorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), "user123", "account123"); final RuntimeWorkspaceImpl runtime = createRuntime(workspace); when(registry.get(runtime.getId())).thenReturn(runtime); + // when final RuntimeWorkspaceImpl result = workspaceManager.getRuntimeWorkspace(runtime.getId()); + // then assertNotNull(result.getConfig() .getEnvironments() .get(0) @@ -287,26 +321,32 @@ public void shouldBeAbleToGetRuntimeWorkspaceById() throws Exception { @Test public void shouldBeAbleToGetRuntimeWorkspaces() throws Exception { + // given final UsersWorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), "user123", "account123"); final RuntimeWorkspaceImpl runtime = createRuntime(workspace); when(registry.getByOwner("user123")).thenReturn(asList(runtime, runtime)); + // when final List result = workspaceManager.getRuntimeWorkspaces("user123"); + // then assertEquals(result, asList(runtime, runtime)); } @Test public void shouldBeAbleToStartWorkspaceById() throws Exception { + // given final UsersWorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), "user123", "account"); when(workspaceDao.get(workspace.getId())).thenReturn(workspace); doReturn(createRuntime(workspace)).when(workspaceManager).performSyncStart(any(), anyString(), anyBoolean(), anyString()); when(registry.get(any())).thenThrow(new NotFoundException("")); + // when final UsersWorkspace result = workspaceManager.startWorkspaceById(workspace.getId(), workspace.getConfig().getDefaultEnv(), "account"); + // then assertEquals(result.getStatus(), STARTING); assertFalse(result.isTemporary()); assertNotNull(result.getConfig() @@ -319,16 +359,19 @@ public void shouldBeAbleToStartWorkspaceById() throws Exception { @Test public void shouldBeAbleToStartWorkspaceByName() throws Exception { + // given final UsersWorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), "user123", "account"); when(workspaceDao.get(workspace.getConfig().getName(), "user123")).thenReturn(workspace); doReturn(createRuntime(workspace)).when(workspaceManager).performSyncStart(any(), anyString(), anyBoolean(), anyString()); when(registry.get(any())).thenThrow(new NotFoundException("")); + // when final UsersWorkspace result = workspaceManager.startWorkspaceByName(workspace.getConfig().getName(), "user123", workspace.getConfig().getDefaultEnv(), "account"); + // then assertEquals(result.getStatus(), STARTING); assertFalse(result.isTemporary()); assertNotNull(result.getConfig() @@ -342,22 +385,27 @@ public void shouldBeAbleToStartWorkspaceByName() throws Exception { @Test(expectedExceptions = ConflictException.class, expectedExceptionsMessageRegExp = "Could not start workspace '.*' because its status is '.*'") public void shouldNotBeAbleToStartWorkspaceIfItIsRunning() throws Exception { + // given final UsersWorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), "user123", "account"); when(workspaceDao.get(workspace.getId())).thenReturn(workspace); final RuntimeWorkspaceImpl runtimeWorkspace = mock(RuntimeWorkspaceImpl.class); when(registry.get(workspace.getId())).thenReturn(runtimeWorkspace); when(runtimeWorkspace.getConfig()).thenReturn(workspace.getConfig()); + // when workspaceManager.startWorkspaceById(workspace.getId(), null, null); } @Test public void shouldStartWorkspaceWhenPerformingSyncStart() throws Exception { + // given final UsersWorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), "user123", "account"); when(registry.start(any(), anyString(), anyBoolean())).thenReturn(createRuntime(workspace)); + // when workspaceManager.performSyncStart(workspace, workspace.getConfig().getDefaultEnv(), false, "account"); + // then verify(registry).start(workspace, workspace.getConfig().getDefaultEnv(), false); verify(workspaceHooks).beforeStart(workspace, workspace.getConfig().getDefaultEnv(), "account"); } @@ -365,20 +413,71 @@ public void shouldStartWorkspaceWhenPerformingSyncStart() throws Exception { @Test(expectedExceptions = ServerException.class, expectedExceptionsMessageRegExp = "registry exception") public void syncStartShouldRethrowRegistryExceptions() throws Exception { + // given final UsersWorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), "user123", "account"); when(registry.start(any(), anyString(), anyBoolean())).thenThrow(new ServerException("registry exception")); + // when workspaceManager.performSyncStart(workspace, workspace.getConfig().getDefaultEnv(), false, "account"); } + @Test + public void performAsyncStartShouldUseDefaultEnvIfNullEnvNameProvided() throws Exception { + // given + final UsersWorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), "user123", "account"); + when(registry.get(workspace.getId())).thenThrow(new NotFoundException("")); + when(registry.start(any(), anyString(), anyBoolean())).thenReturn(createRuntime(workspace)); + + // when + workspaceManager.performAsyncStart(workspace, null, false, "account"); + + // then + // timeout is needed because this invocation will run in separate thread asynchronously + verify(registry, timeout(1000)).start(workspace, workspace.getConfig().getDefaultEnv(), false); + } + + @Test + public void performAsyncStartShouldUseProvidedEnvInsteadOfDefault() throws Exception { + // given + final WorkspaceConfigDto config = createConfig(); + final EnvironmentDto nonDefaultEnv = newDto(EnvironmentDto.class).withName("non-default-env") + .withMachineConfigs( + config.getEnvironments().get(0).getMachineConfigs()) + .withRecipe(null); + config.getEnvironments().add(nonDefaultEnv); + final UsersWorkspaceImpl workspace = workspaceManager.createWorkspace(config, "user123", "account"); + when(registry.get(workspace.getId())).thenThrow(new NotFoundException("")); + when(registry.start(any(), anyString(), anyBoolean())).thenReturn(createRuntime(workspace)); + + // when + workspaceManager.performAsyncStart(workspace, nonDefaultEnv.getName(), false, "account"); + + // then + verify(registry, timeout(1000)).start(workspace, nonDefaultEnv.getName(), false); + } + + @Test(expectedExceptions = BadRequestException.class, + expectedExceptionsMessageRegExp = "Couldn't start workspace '.*', workspace doesn't have environment '.*'") + public void performAsyncStartShouldCheckThatEnvWithProvidedEnvNameExists() throws Exception { + // given + final UsersWorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), "user123", "account"); + when(registry.get(workspace.getId())).thenThrow(new NotFoundException("")); + + // when + workspaceManager.performAsyncStart(workspace, "not-existing-env-name", false, "account"); + } + @Test public void shouldBeAbleToStartTemporaryWorkspace() throws Exception { + // given final WorkspaceConfigDto config = createConfig(); - final UsersWorkspace workspace = workspaceManager.createWorkspace(config, "user123", "account"); + final UsersWorkspaceImpl workspace = workspaceManager.createWorkspace(config, "user123", "account"); when(registry.start(workspaceCaptor.capture(), anyString(), anyBoolean())).thenReturn(createRuntime(workspace)); + // when final RuntimeWorkspaceImpl runtime = workspaceManager.startTemporaryWorkspace(config, "account"); + // then final UsersWorkspaceImpl captured = workspaceCaptor.getValue(); assertTrue(captured.isTemporary()); assertNotNull(captured.getConfig() @@ -388,19 +487,24 @@ public void shouldBeAbleToStartTemporaryWorkspace() throws Exception { .get(0)); verify(workspaceHooks).beforeCreate(captured, "account"); verify(workspaceHooks).afterCreate(runtime, "account"); + verify(workspaceHooks).beforeStart(captured, config.getDefaultEnv(), "account"); + verify(workspaceManager).performSyncStart(captured, config.getDefaultEnv(), false, "account"); } @Test public void shouldBeAbleToRecoverWorkspace() throws Exception { + // given final UsersWorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), "user123", "account"); when(workspaceDao.get(workspace.getId())).thenReturn(workspace); doReturn(createRuntime(workspace)).when(workspaceManager).performSyncStart(any(), anyString(), anyBoolean(), anyString()); when(registry.get(any())).thenThrow(new NotFoundException("")); + // when final UsersWorkspace result = workspaceManager.recoverWorkspace(workspace.getId(), workspace.getConfig().getDefaultEnv(), "account"); + // then assertEquals(result.getStatus(), STARTING); assertFalse(result.isTemporary()); assertNotNull(result.getConfig() @@ -413,18 +517,26 @@ public void shouldBeAbleToRecoverWorkspace() throws Exception { @Test public void shouldBeAbleToStopWorkspace() throws Exception { + // given doNothing().when(workspaceManager).performAsyncStop(any()); + // when workspaceManager.stopWorkspace("workspace123"); + // then verify(workspaceManager).performAsyncStop(any()); } @Test public void shouldBeAbleToGetSnapshots() throws Exception { + // given when(machineManager.getSnapshots("user123", "workspace123")).thenReturn(singletonList(any())); - assertEquals(workspaceManager.getSnapshot("workspace123").size(), 1); + // when + final List snapshots = workspaceManager.getSnapshot("workspace123"); + + // then + assertEquals(snapshots.size(), 1); } private RuntimeWorkspaceImpl createRuntime(UsersWorkspace workspace) {