From 69c7d67153c0929aaae89e1ca19e6e120a471631 Mon Sep 17 00:00:00 2001 From: Sergii Leshchenko Date: Thu, 16 May 2019 15:27:41 +0300 Subject: [PATCH 1/4] Add Devfile into Stack API Signed-off-by: Sergii Leshchenko --- .../jpa/JpaStackPermissionsDaoTest.java | 4 +- .../server/jpa/MultiuserJpaStackDaoTest.java | 26 ++++++++-- .../spi/tck/StackPermissionsDaoTest.java | 4 +- .../workspace/shared/dto/stack/StackDto.java | 8 +++ .../che/api/workspace/shared/stack/Stack.java | 16 +++++- .../api/workspace/server/DtoConverter.java | 8 ++- .../model/impl/devfile/ComponentImpl.java | 2 + .../server/model/impl/stack/StackImpl.java | 42 ++++++++++++++- .../server/jpa/WorkspaceTckModule.java | 9 ++-- .../server/spi/tck/StackDaoTest.java | 48 ++++++++++++----- .../server/spi/tck/WorkspaceDaoTest.java | 2 +- .../{stacks.json => test-stacks.json} | 52 +++++++++++++++++++ .../7.0.0-beta5.0/1__add_devfile_to_stack.sql | 16 ++++++ 13 files changed, 208 insertions(+), 29 deletions(-) rename wsmaster/che-core-api-workspace/src/test/resources/{stacks.json => test-stacks.json} (86%) create mode 100644 wsmaster/che-core-sql-schema/src/main/resources/che-schema/7.0.0-beta5.0/1__add_devfile_to_stack.sql diff --git a/multiuser/permission/che-multiuser-permission-workspace/src/test/java/org/eclipse/che/multiuser/permission/workspace/server/jpa/JpaStackPermissionsDaoTest.java b/multiuser/permission/che-multiuser-permission-workspace/src/test/java/org/eclipse/che/multiuser/permission/workspace/server/jpa/JpaStackPermissionsDaoTest.java index 01cb2637bcf..3c2955438d3 100644 --- a/multiuser/permission/che-multiuser-permission-workspace/src/test/java/org/eclipse/che/multiuser/permission/workspace/server/jpa/JpaStackPermissionsDaoTest.java +++ b/multiuser/permission/che-multiuser-permission-workspace/src/test/java/org/eclipse/che/multiuser/permission/workspace/server/jpa/JpaStackPermissionsDaoTest.java @@ -65,8 +65,8 @@ public void setupEntities() throws Exception { stacks = new StackImpl[] { - new StackImpl("stack1", "st1", null, null, null, null, null, null, null), - new StackImpl("stack2", "st2", null, null, null, null, null, null, null) + new StackImpl("stack1", "st1", null, null, null, null, null, null, null, null), + new StackImpl("stack2", "st2", null, null, null, null, null, null, null, null) }; Injector injector = Guice.createInjector(new WorkspaceTckModule()); diff --git a/multiuser/permission/che-multiuser-permission-workspace/src/test/java/org/eclipse/che/multiuser/permission/workspace/server/jpa/MultiuserJpaStackDaoTest.java b/multiuser/permission/che-multiuser-permission-workspace/src/test/java/org/eclipse/che/multiuser/permission/workspace/server/jpa/MultiuserJpaStackDaoTest.java index f92670c7b28..9daccbf8d84 100644 --- a/multiuser/permission/che-multiuser-permission-workspace/src/test/java/org/eclipse/che/multiuser/permission/workspace/server/jpa/MultiuserJpaStackDaoTest.java +++ b/multiuser/permission/che-multiuser-permission-workspace/src/test/java/org/eclipse/che/multiuser/permission/workspace/server/jpa/MultiuserJpaStackDaoTest.java @@ -52,11 +52,29 @@ public void setupEntities() throws Exception { stacks = new StackImpl[] { new StackImpl( - "stack1", "st1", null, null, null, Arrays.asList("tag1", "tag2"), null, null, null), - new StackImpl("stack2", "st2", null, null, null, null, null, null, null), + "stack1", + "st1", + null, + null, + null, + Arrays.asList("tag1", "tag2"), + null, + null, + null, + null), + new StackImpl("stack2", "st2", null, null, null, null, null, null, null, null), new StackImpl( - "stack3", "st3", null, null, null, Arrays.asList("tag1", "tag2"), null, null, null), - new StackImpl("stack4", "st4", null, null, null, null, null, null, null) + "stack3", + "st3", + null, + null, + null, + Arrays.asList("tag1", "tag2"), + null, + null, + null, + null), + new StackImpl("stack4", "st4", null, null, null, null, null, null, null, null) }; Injector injector = Guice.createInjector(new WorkspaceTckModule()); diff --git a/multiuser/permission/che-multiuser-permission-workspace/src/test/java/org/eclipse/che/multiuser/permission/workspace/server/spi/tck/StackPermissionsDaoTest.java b/multiuser/permission/che-multiuser-permission-workspace/src/test/java/org/eclipse/che/multiuser/permission/workspace/server/spi/tck/StackPermissionsDaoTest.java index 23fa799edeb..8de8ebafe4d 100644 --- a/multiuser/permission/che-multiuser-permission-workspace/src/test/java/org/eclipse/che/multiuser/permission/workspace/server/spi/tck/StackPermissionsDaoTest.java +++ b/multiuser/permission/che-multiuser-permission-workspace/src/test/java/org/eclipse/che/multiuser/permission/workspace/server/spi/tck/StackPermissionsDaoTest.java @@ -76,8 +76,8 @@ public void setUp() throws TckRepositoryException { wCfg.setDescription("description"); stackRepository.createAll( asList( - new StackImpl("stack1", "st1", null, null, null, null, wCfg, null, null), - new StackImpl("stack2", "st2", null, null, null, null, wCfg, null, null))); + new StackImpl("stack1", "st1", null, null, null, null, wCfg, null, null, null), + new StackImpl("stack2", "st2", null, null, null, null, wCfg, null, null, null))); permissionsRepository.createAll( Stream.of(permissions).map(StackPermissionsImpl::new).collect(Collectors.toList())); diff --git a/wsmaster/che-core-api-workspace-shared/src/main/java/org/eclipse/che/api/workspace/shared/dto/stack/StackDto.java b/wsmaster/che-core-api-workspace-shared/src/main/java/org/eclipse/che/api/workspace/shared/dto/stack/StackDto.java index 1f401b18ac8..e81be88e678 100644 --- a/wsmaster/che-core-api-workspace-shared/src/main/java/org/eclipse/che/api/workspace/shared/dto/stack/StackDto.java +++ b/wsmaster/che-core-api-workspace-shared/src/main/java/org/eclipse/che/api/workspace/shared/dto/stack/StackDto.java @@ -15,6 +15,7 @@ import org.eclipse.che.api.core.rest.shared.dto.Hyperlinks; import org.eclipse.che.api.core.rest.shared.dto.Link; import org.eclipse.che.api.workspace.shared.dto.WorkspaceConfigDto; +import org.eclipse.che.api.workspace.shared.dto.devfile.DevfileDto; import org.eclipse.che.api.workspace.shared.stack.Stack; import org.eclipse.che.dto.shared.DTO; @@ -53,6 +54,13 @@ public interface StackDto extends Stack, Hyperlinks { StackDto withWorkspaceConfig(WorkspaceConfigDto workspaceConfigDto); + @Override + DevfileDto getDevfile(); + + void setDevfile(DevfileDto devfile); + + StackDto withDevfile(DevfileDto devfile); + @Override List getComponents(); diff --git a/wsmaster/che-core-api-workspace-shared/src/main/java/org/eclipse/che/api/workspace/shared/stack/Stack.java b/wsmaster/che-core-api-workspace-shared/src/main/java/org/eclipse/che/api/workspace/shared/stack/Stack.java index b757f328c7f..f7f594d5ebe 100644 --- a/wsmaster/che-core-api-workspace-shared/src/main/java/org/eclipse/che/api/workspace/shared/stack/Stack.java +++ b/wsmaster/che-core-api-workspace-shared/src/main/java/org/eclipse/che/api/workspace/shared/stack/Stack.java @@ -13,6 +13,7 @@ import java.util.List; import org.eclipse.che.api.core.model.workspace.WorkspaceConfig; +import org.eclipse.che.api.core.model.workspace.devfile.Devfile; import org.eclipse.che.commons.annotation.Nullable; /** @@ -55,12 +56,23 @@ public interface Stack { List getTags(); /** - * Return the {@link WorkspaceConfig} for creation workspace. This workspaceConfig can be used for - * store machine source, list predefined commands, projects etc. + * Return the {@link WorkspaceConfig} for creation workspace. + * + *

The only one format (workspace config or devfile) may be used for workspace at the same + * time. */ @Nullable WorkspaceConfig getWorkspaceConfig(); + /** + * Return the {@link Devfile} for creation workspace. + * + *

The only one format (workspace config or devfile) may be used for workspace at the same + * time. + */ + @Nullable + Devfile getDevfile(); + /** * Return the list of the components that stack consist of. * diff --git a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/DtoConverter.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/DtoConverter.java index 973727d86f2..f5de6fa807e 100644 --- a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/DtoConverter.java +++ b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/DtoConverter.java @@ -243,6 +243,11 @@ public static StackDto asDto(Stack stack) { workspaceConfigDto = asDto(stack.getWorkspaceConfig()); } + DevfileDto devfileDto = null; + if (stack.getDevfile() != null) { + devfileDto = asDto(stack.getDevfile()); + } + List componentsDto = null; if (stack.getComponents() != null) { componentsDto = @@ -265,7 +270,8 @@ public static StackDto asDto(Stack stack) { .withScope(stack.getScope()) .withTags(stack.getTags()) .withComponents(componentsDto) - .withWorkspaceConfig(workspaceConfigDto); + .withWorkspaceConfig(workspaceConfigDto) + .withDevfile(devfileDto); } /** Converts {@link ProjectConfig} to {@link ProjectConfigDto}. */ diff --git a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/devfile/ComponentImpl.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/devfile/ComponentImpl.java index 071d77f1f02..6e22eb03976 100644 --- a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/devfile/ComponentImpl.java +++ b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/devfile/ComponentImpl.java @@ -13,6 +13,7 @@ import static java.util.stream.Collectors.toCollection; +import com.google.gson.annotations.SerializedName; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -46,6 +47,7 @@ public class ComponentImpl implements Component { @Column(name = "id") private Long generatedId; + @SerializedName("id") @Column(name = "component_id", nullable = false) private String componentId; diff --git a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/stack/StackImpl.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/stack/StackImpl.java index 64b4901774a..9e56d2eab84 100644 --- a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/stack/StackImpl.java +++ b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/stack/StackImpl.java @@ -29,7 +29,9 @@ import javax.persistence.OneToOne; import javax.persistence.Table; import org.eclipse.che.api.core.model.workspace.WorkspaceConfig; +import org.eclipse.che.api.core.model.workspace.devfile.Devfile; import org.eclipse.che.api.workspace.server.model.impl.WorkspaceConfigImpl; +import org.eclipse.che.api.workspace.server.model.impl.devfile.DevfileImpl; import org.eclipse.che.api.workspace.server.stack.image.StackIcon; import org.eclipse.che.api.workspace.shared.stack.Stack; import org.eclipse.che.api.workspace.shared.stack.StackComponent; @@ -86,6 +88,10 @@ public static StackBuilder builder() { @JoinColumn(name = "workspaceconfig_id") private WorkspaceConfigImpl workspaceConfig; + @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true) + @JoinColumn(name = "devfile_id") + private DevfileImpl devfile; + @ElementCollection @CollectionTable(name = "stack_components", joinColumns = @JoinColumn(name = "stack_id")) private List components; @@ -103,6 +109,7 @@ public StackImpl(StackImpl stack) { stack.getCreator(), stack.getTags(), stack.getWorkspaceConfig(), + stack.getDevfile(), stack.getComponents(), stack.getStackIcon()); } @@ -116,6 +123,7 @@ public StackImpl(Stack stack) { stack.getCreator(), stack.getTags(), stack.getWorkspaceConfig(), + stack.getDevfile(), stack.getComponents(), null); } @@ -128,6 +136,7 @@ public StackImpl( String creator, List tags, WorkspaceConfig workspaceConfig, + Devfile devfile, List components, StackIcon stackIcon) { this.id = id; @@ -144,6 +153,9 @@ public StackImpl( if (workspaceConfig != null) { this.workspaceConfig = new WorkspaceConfigImpl(workspaceConfig); } + if (devfile != null) { + this.devfile = new DevfileImpl(devfile); + } if (components != null) { this.components = components.stream().map(StackComponentImpl::new).collect(toList()); } @@ -215,6 +227,15 @@ public void setWorkspaceConfig(WorkspaceConfigImpl workspaceConfig) { this.workspaceConfig = workspaceConfig; } + @Override + public DevfileImpl getDevfile() { + return devfile; + } + + public void setDevfile(DevfileImpl devfile) { + this.devfile = devfile; + } + @Override public List getComponents() { if (components == null) { @@ -251,6 +272,7 @@ public boolean equals(Object obj) { && Objects.equals(creator, that.creator) && getTags().equals(that.getTags()) && Objects.equals(workspaceConfig, that.workspaceConfig) + && Objects.equals(devfile, that.devfile) && getComponents().equals(that.getComponents()) && Objects.equals(stackIcon, that.stackIcon); } @@ -265,6 +287,7 @@ public int hashCode() { hash = 31 * hash + Objects.hashCode(creator); hash = 31 * hash + getTags().hashCode(); hash = 31 * hash + Objects.hashCode(workspaceConfig); + hash = 31 * hash + Objects.hashCode(devfile); hash = 31 * hash + getComponents().hashCode(); hash = 31 * hash + Objects.hashCode(stackIcon); return hash; @@ -292,6 +315,8 @@ public String toString() { + tags + ", workspaceConfig=" + workspaceConfig + + ", devfile=" + + devfile + ", components=" + components + ", stackIcon=" @@ -308,6 +333,7 @@ public static class StackBuilder { private String creator; private List tags; private WorkspaceConfig workspaceConfig; + private Devfile devfile; private StackSource source; private List components; private StackIcon stackIcon; @@ -352,6 +378,11 @@ public StackBuilder setWorkspaceConfig(WorkspaceConfig workspaceConfig) { return this; } + public StackBuilder setDevfile(Devfile devfile) { + this.devfile = devfile; + return this; + } + public StackBuilder setSource(StackSource source) { this.source = source; return this; @@ -369,7 +400,16 @@ public StackBuilder setStackIcon(StackIcon stackIcon) { public StackImpl build() { return new StackImpl( - id, name, description, scope, creator, tags, workspaceConfig, components, stackIcon); + id, + name, + description, + scope, + creator, + tags, + workspaceConfig, + devfile, + components, + stackIcon); } } } diff --git a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/jpa/WorkspaceTckModule.java b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/jpa/WorkspaceTckModule.java index ad3e1224c50..f0a53eb8814 100644 --- a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/jpa/WorkspaceTckModule.java +++ b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/jpa/WorkspaceTckModule.java @@ -125,9 +125,12 @@ public StackRepository() { @Override public void createAll(Collection entities) throws TckRepositoryException { - for (StackImpl stack : entities) { - stack.getWorkspaceConfig().getProjects().forEach(ProjectConfigImpl::prePersistAttributes); - } + entities + .stream() + .filter(s -> s.getWorkspaceConfig() != null) + .map(StackImpl::getWorkspaceConfig) + .flatMap(ws -> ws.getProjects().stream()) + .forEach(ProjectConfigImpl::prePersistAttributes); super.createAll(entities); } } diff --git a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/spi/tck/StackDaoTest.java b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/spi/tck/StackDaoTest.java index 8d2c875b5b0..b4a17775d12 100644 --- a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/spi/tck/StackDaoTest.java +++ b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/spi/tck/StackDaoTest.java @@ -13,6 +13,7 @@ import static java.util.Arrays.asList; import static java.util.stream.Collectors.toList; +import static org.eclipse.che.api.workspace.server.spi.tck.WorkspaceDaoTest.createDevfile; import static org.eclipse.che.api.workspace.server.spi.tck.WorkspaceDaoTest.createWorkspaceConfig; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doCallRealMethod; @@ -33,7 +34,6 @@ import org.eclipse.che.api.core.notification.EventService; import org.eclipse.che.api.workspace.server.event.BeforeStackRemovedEvent; import org.eclipse.che.api.workspace.server.event.StackPersistedEvent; -import org.eclipse.che.api.workspace.server.model.impl.WorkspaceConfigImpl; import org.eclipse.che.api.workspace.server.model.impl.stack.StackComponentImpl; import org.eclipse.che.api.workspace.server.model.impl.stack.StackImpl; import org.eclipse.che.api.workspace.server.spi.StackDao; @@ -59,7 +59,7 @@ public class StackDaoTest { public static final String SUITE_NAME = "StackDaoTck"; - private static final int STACKS_SIZE = 5; + private static final int STACKS_SIZE = 6; private StackImpl[] stacks; @@ -72,8 +72,11 @@ public class StackDaoTest { @BeforeMethod private void createStacks() throws TckRepositoryException { stacks = new StackImpl[STACKS_SIZE]; - for (int i = 0; i < STACKS_SIZE; i++) { - stacks[i] = createStack("stack-" + i, "name-" + i); + for (int i = 0; i < STACKS_SIZE / 2; i++) { + stacks[i] = createStackWithWsConfig("stack-" + i, "name-" + i); + } + for (int i = STACKS_SIZE / 2; i < STACKS_SIZE; i++) { + stacks[i] = createStackWithDevfile("stack-" + i, "name-" + i); } stackRepo.createAll(Stream.of(stacks).map(StackImpl::new).collect(toList())); } @@ -102,7 +105,7 @@ public void shouldThrowNpeWhenGettingStackByNullKey() throws Exception { @Test(dependsOnMethods = "shouldGetById") public void shouldCreateStack() throws Exception { - final StackImpl stack = createStack("new-stack", "new-stack-name"); + final StackImpl stack = createStackWithWsConfig("new-stack", "new-stack-name"); stackDao.create(stack); @@ -113,7 +116,7 @@ public void shouldCreateStack() throws Exception { dependsOnMethods = "shouldThrowNotFoundExceptionWhenGettingNonExistingStack", expectedExceptions = NotFoundException.class) public void shouldNotCreateStackWhenSubscriberThrowsExceptionOnStackStoring() throws Exception { - final StackImpl stack = createStack("new-stack", "new-stack-name"); + final StackImpl stack = createStackWithWsConfig("new-stack", "new-stack-name"); CascadeEventSubscriber subscriber = mockCascadeEventSubscriber(); doThrow(new ConflictException("error")).when(subscriber).onCascadeEvent(any()); @@ -132,7 +135,7 @@ public void shouldNotCreateStackWhenSubscriberThrowsExceptionOnStackStoring() th @Test(expectedExceptions = ConflictException.class) public void shouldThrowConflictExceptionWhenCreatingStackWithIdThatAlreadyExists() throws Exception { - final StackImpl stack = createStack(stacks[0].getId(), "new-name"); + final StackImpl stack = createStackWithWsConfig(stacks[0].getId(), "new-name"); stackDao.create(stack); } @@ -140,7 +143,7 @@ public void shouldThrowConflictExceptionWhenCreatingStackWithIdThatAlreadyExists @Test(expectedExceptions = ConflictException.class) public void shouldThrowConflictExceptionWhenCreatingStackWithNameThatAlreadyExists() throws Exception { - final StackImpl stack = createStack("new-stack-id", stacks[0].getName()); + final StackImpl stack = createStackWithWsConfig("new-stack-id", stacks[0].getName()); stackDao.create(stack); } @@ -229,7 +232,7 @@ public void shouldNotUpdateStackIfNewNameIsReserved() throws Exception { @Test(expectedExceptions = NotFoundException.class) public void shouldThrowNotFoundExceptionWhenUpdatingNonExistingStack() throws Exception { - stackDao.update(createStack("new-stack", "new-stack-name")); + stackDao.update(createStackWithWsConfig("new-stack", "new-stack-name")); } @Test(expectedExceptions = NullPointerException.class) @@ -271,7 +274,7 @@ public void shouldPublishStackPersistedEventAfterStackIsPersisted() throws Excep final boolean[] isNotified = new boolean[] {false}; eventService.subscribe(event -> isNotified[0] = true, StackPersistedEvent.class); - stackDao.create(createStack("test", "test")); + stackDao.create(createStackWithWsConfig("test", "test")); assertTrue(isNotified[0], "Event subscriber notified"); } @@ -282,7 +285,27 @@ private void updateAll() throws ConflictException, NotFoundException, ServerExce } } - private static StackImpl createStack(String id, String name) { + private static StackImpl createStackWithWsConfig(String id, String name) { + final StackImpl stack = + StackImpl.builder() + .setId(id) + .setName(name) + .setCreator("user123") + .setDescription(id + "-description") + .setScope(id + "-scope") + .setTags(asList(id + "-tag1", id + "-tag2")) + .setComponents( + asList( + new StackComponentImpl(id + "-component1", id + "-component1-version"), + new StackComponentImpl(id + "-component2", id + "-component2-version"))) + .setStackIcon( + new StackIcon(id + "-icon", id + "-media-type", "0x1234567890abcdef".getBytes())) + .setWorkspaceConfig(createWorkspaceConfig("test")) + .build(); + return stack; + } + + private static StackImpl createStackWithDevfile(String id, String name) { final StackImpl stack = StackImpl.builder() .setId(id) @@ -297,9 +320,8 @@ private static StackImpl createStack(String id, String name) { new StackComponentImpl(id + "-component2", id + "-component2-version"))) .setStackIcon( new StackIcon(id + "-icon", id + "-media-type", "0x1234567890abcdef".getBytes())) + .setDevfile(createDevfile("test")) .build(); - final WorkspaceConfigImpl config = createWorkspaceConfig("test"); - stack.setWorkspaceConfig(config); return stack; } diff --git a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/spi/tck/WorkspaceDaoTest.java b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/spi/tck/WorkspaceDaoTest.java index d4b8deda2c8..41306de6064 100644 --- a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/spi/tck/WorkspaceDaoTest.java +++ b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/spi/tck/WorkspaceDaoTest.java @@ -846,7 +846,7 @@ public static WorkspaceImpl createWorkspaceFromDevfile( return workspace; } - private static DevfileImpl createDevfile(String name) { + public static DevfileImpl createDevfile(String name) { SourceImpl source1 = new SourceImpl("type1", "http://location", "branch1", "point1", "tag1", "commit1"); diff --git a/wsmaster/che-core-api-workspace/src/test/resources/stacks.json b/wsmaster/che-core-api-workspace/src/test/resources/test-stacks.json similarity index 86% rename from wsmaster/che-core-api-workspace/src/test/resources/stacks.json rename to wsmaster/che-core-api-workspace/src/test/resources/test-stacks.json index 5436f78e2ef..b266e0e91c7 100644 --- a/wsmaster/che-core-api-workspace/src/test/resources/stacks.json +++ b/wsmaster/che-core-api-workspace/src/test/resources/test-stacks.json @@ -363,5 +363,57 @@ } ] } + }, + { + "id": "che7-preview-devfile", + "creator": "ide", + "name": "Che 7", + "description": "Workspace.next sidecars and Theia as IDE", + "scope": "general", + "tags": [ + "ws.next", + "Theia", + "Java", + "JDK", + "Node.JS", + "NPM", + "Yeoman" + ], + "components": [ + { + "name": "Centos", + "version": "7" + }, + { + "name": "OpenJDK", + "version": "1.8.0_181" + }, + { + "name": "NodeJS", + "version": "8.12.0" + }, + { + "name": "NPM", + "version": "6.4.1" + } + ], + "devfile": { + "specVersion": "0.0.1", + "name": "che7", + "components": [ + { + "type": "cheEditor", + "id": "eclipse/che-theia/next" + }, + { + "type": "chePlugin", + "id": "eclipse/che-machine-exec-plugin/0.0.1" + }, + { + "type": "kubernetes", + "referenceContent": "kind: List\nitems:\n - \n apiVersion: v1\n kind: Pod\n metadata:\n name: ws\n spec:\n containers:\n - \n image: 'eclipse/che-dev:nightly'\n name: dev\n resources:\n limits:\n memory: 512Mi\n" + } + ] + } } ] diff --git a/wsmaster/che-core-sql-schema/src/main/resources/che-schema/7.0.0-beta5.0/1__add_devfile_to_stack.sql b/wsmaster/che-core-sql-schema/src/main/resources/che-schema/7.0.0-beta5.0/1__add_devfile_to_stack.sql new file mode 100644 index 00000000000..14847acca3d --- /dev/null +++ b/wsmaster/che-core-sql-schema/src/main/resources/che-schema/7.0.0-beta5.0/1__add_devfile_to_stack.sql @@ -0,0 +1,16 @@ +-- +-- Copyright (c) 2012-2019 Red Hat, Inc. +-- This program and the accompanying materials are made +-- available under the terms of the Eclipse Public License 2.0 +-- which is available at https://www.eclipse.org/legal/epl-2.0/ +-- +-- SPDX-License-Identifier: EPL-2.0 +-- +-- Contributors: +-- Red Hat, Inc. - initial API and implementation +-- + +----------------------------------------------------------------------------------------------------------------------------------- + +-- add devfile into stack +ALTER TABLE stack ADD COLUMN devfile_id BIGINT; From 59889013ff6e7d5b83bae4886a4b238c6fd90789 Mon Sep 17 00:00:00 2001 From: Sergii Leshchenko Date: Thu, 16 May 2019 15:28:05 +0300 Subject: [PATCH 2/4] Improve stack loading and validation Signed-off-by: Sergii Leshchenko --- .../server/stack/MultiuserStackLoader.java | 4 +- .../workspace/server/stack/StackLoader.java | 12 +++-- .../server/stack/StackValidator.java | 49 +++++++++++++------ .../server/stack/StackLoaderTest.java | 34 ++++++++----- .../server/stack/StackValidatorTest.java | 31 +++++++++--- 5 files changed, 93 insertions(+), 37 deletions(-) diff --git a/multiuser/permission/che-multiuser-permission-workspace/src/main/java/org/eclipse/che/multiuser/permission/workspace/server/stack/MultiuserStackLoader.java b/multiuser/permission/che-multiuser-permission-workspace/src/main/java/org/eclipse/che/multiuser/permission/workspace/server/stack/MultiuserStackLoader.java index 36bff8c3735..79fbb16fc74 100644 --- a/multiuser/permission/che-multiuser-permission-workspace/src/main/java/org/eclipse/che/multiuser/permission/workspace/server/stack/MultiuserStackLoader.java +++ b/multiuser/permission/che-multiuser-permission-workspace/src/main/java/org/eclipse/che/multiuser/permission/workspace/server/stack/MultiuserStackLoader.java @@ -25,6 +25,7 @@ import org.eclipse.che.api.workspace.server.model.impl.stack.StackImpl; import org.eclipse.che.api.workspace.server.spi.StackDao; import org.eclipse.che.api.workspace.server.stack.StackLoader; +import org.eclipse.che.api.workspace.server.stack.StackValidator; import org.eclipse.che.api.workspace.server.stack.image.StackIcon; import org.eclipse.che.api.workspace.shared.stack.Stack; import org.eclipse.che.core.db.DBInitializer; @@ -54,10 +55,11 @@ public class MultiuserStackLoader extends StackLoader { public MultiuserStackLoader( @Named("che.predefined.stacks.reload_on_start") boolean reloadStacksOnStart, @Named(CHE_PREDEFINED_STACKS) Map stacks2images, + StackValidator stackValidator, StackDao stackDao, JpaStackPermissionsDao permissionsDao, DBInitializer dbInitializer) { - super(reloadStacksOnStart, stacks2images, stackDao, dbInitializer); + super(reloadStacksOnStart, stacks2images, stackValidator, stackDao, dbInitializer); this.permissionsDao = permissionsDao; } diff --git a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/stack/StackLoader.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/stack/StackLoader.java index 8feda74d747..19587e62993 100644 --- a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/stack/StackLoader.java +++ b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/stack/StackLoader.java @@ -29,6 +29,7 @@ import javax.inject.Named; import javax.inject.Singleton; import org.apache.commons.io.IOUtils; +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; @@ -62,16 +63,19 @@ public class StackLoader { private final Map stacks2images; private final DBInitializer dbInitializer; private final Boolean reloadStacksOnStart; + private final StackValidator stackValidator; @Inject @SuppressWarnings("unused") public StackLoader( @Named("che.predefined.stacks.reload_on_start") boolean reloadStacksOnStart, @Named(CHE_PREDEFINED_STACKS) Map stacks2images, + StackValidator stackValidator, StackDao stackDao, DBInitializer dbInitializer) { this.reloadStacksOnStart = reloadStacksOnStart; this.stacks2images = stacks2images; + this.stackValidator = stackValidator; this.stackDao = stackDao; this.dbInitializer = dbInitializer; GSON = new GsonBuilder().create(); @@ -98,7 +102,7 @@ public void start() { final Path imagesDirPath = !isNullOrEmpty(imagesDir) ? Paths.get(imagesDir) : null; stacks.forEach(stack -> loadStack(stack, imagesDirPath)); } catch (Exception ex) { - LOG.error("Failed to store stacks from '{}'", stackFile); + LOG.error("Failed to store stacks from '{}'. Cause: %s", stackFile, ex.getMessage()); } } LOG.info("Stacks initialization finished"); @@ -108,13 +112,15 @@ public void start() { protected void loadStack(StackImpl stack, Path imagePath) { setIconData(stack, imagePath); try { + stackValidator.check(stack); try { stackDao.update(stack); } catch (NotFoundException ex) { stackDao.create(stack); } - } catch (ServerException | ConflictException ex) { - LOG.warn(format("Failed to load stack with id '%s' ", stack.getId()), ex.getMessage()); + } catch (BadRequestException | ServerException | NotFoundException | ConflictException ex) { + LOG.warn( + format("Failed to load stack with id '%s'. Cause: %s", stack.getId(), ex.getMessage())); } } diff --git a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/stack/StackValidator.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/stack/StackValidator.java index 63be5657222..258c4211653 100644 --- a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/stack/StackValidator.java +++ b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/stack/StackValidator.java @@ -11,6 +11,8 @@ */ package org.eclipse.che.api.workspace.server.stack; +import static com.google.common.base.Strings.isNullOrEmpty; + import javax.inject.Inject; import javax.inject.Singleton; import org.eclipse.che.api.core.BadRequestException; @@ -37,23 +39,38 @@ public class StackValidator { * @throws BadRequestException if stack is not valid */ public void check(Stack stack) throws BadRequestException, ServerException, NotFoundException { - if (stack == null) { - throw new BadRequestException("Required non-null stack"); - } - if (stack.getName() == null || stack.getName().isEmpty()) { - throw new BadRequestException("Required non-null and non-empty stack name"); - } - if (stack.getScope() == null - || !stack.getScope().equals("general") && !stack.getScope().equals("advanced")) { - throw new BadRequestException("Required non-null scope value: 'general' or 'advanced'"); - } - if (stack.getWorkspaceConfig() == null) { - throw new BadRequestException("Workspace config required"); + checkArgument(stack != null, "Required non-null stack"); + + checkArgument(!isNullOrEmpty(stack.getName()), "Required non-null and non-empty stack name"); + + checkArgument( + stack.getScope() != null + && (stack.getScope().equals("general") || stack.getScope().equals("advanced")), + "Required non-null scope value: 'general' or 'advanced'"); + + checkArgument( + stack.getWorkspaceConfig() != null ^ stack.getDevfile() != null, + "Required non-null workspace configuration or devfile but not both"); + + if (stack.getWorkspaceConfig() != null) { + try { + wsValidator.validateConfig(stack.getWorkspaceConfig()); + } catch (ValidationException x) { + throw new BadRequestException(x.getMessage()); + } } - try { - wsValidator.validateConfig(stack.getWorkspaceConfig()); - } catch (ValidationException x) { - throw new BadRequestException(x.getMessage()); + } + + /** + * Checks the specified expression. + * + * @param expression the expression to check + * @param errorMessage error message that should be used if expression is false + * @throws BadRequestException when the expression is false + */ + private void checkArgument(boolean expression, String errorMessage) throws BadRequestException { + if (!expression) { + throw new BadRequestException(String.valueOf(errorMessage)); } } } diff --git a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/stack/StackLoaderTest.java b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/stack/StackLoaderTest.java index d127bb0d25f..fec138c88c7 100644 --- a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/stack/StackLoaderTest.java +++ b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/stack/StackLoaderTest.java @@ -61,6 +61,8 @@ @Listeners(MockitoTestNGListener.class) public class StackLoaderTest { + @Mock private StackValidator stackValidator; + @Mock private StackDao stackDao; @Mock private DBInitializer dbInitializer; @@ -72,15 +74,20 @@ public void startup() throws Exception { when(dbInitializer.isBareInit()).thenReturn(true); stackLoader = new StackLoader( - false, ImmutableMap.of("stacks.json", "stack_img"), stackDao, dbInitializer); + false, + ImmutableMap.of("test-stacks.json", "stack_img"), + stackValidator, + stackDao, + dbInitializer); } @Test public void predefinedStackWithValidJsonShouldBeUpdated() throws Exception { stackLoader.start(); - verify(stackDao, times(5)).update(any()); + verify(stackDao, times(6)).update(any()); verify(stackDao, never()).create(any()); + verify(stackValidator, times(6)).check(any()); } @Test @@ -89,8 +96,9 @@ public void predefinedStackWithValidJsonShouldBeCreated() throws Exception { stackLoader.start(); - verify(stackDao, times(5)).update(any()); - verify(stackDao, times(5)).create(any()); + verify(stackDao, times(6)).update(any()); + verify(stackDao, times(6)).create(any()); + verify(stackValidator, times(6)).check(any()); } @Test @@ -99,8 +107,9 @@ public void doNotThrowExceptionWhenUpdateFailed() throws Exception { stackLoader.start(); - verify(stackDao, times(5)).update(any()); + verify(stackDao, times(6)).update(any()); verify(stackDao, never()).create(any()); + verify(stackValidator, times(6)).check(any()); } @Test @@ -110,20 +119,22 @@ public void doNotThrowExceptionWhenCreationFailed() throws Exception { stackLoader.start(); - verify(stackDao, times(5)).update(any()); - verify(stackDao, times(5)).create(any()); + verify(stackDao, times(6)).update(any()); + verify(stackDao, times(6)).create(any()); + verify(stackValidator, times(6)).check(any()); } @Test public void testOverrideStacksWithoutImages() throws Exception { final Map map = new HashMap<>(); - map.put("stacks.json", null); - stackLoader = new StackLoader(true, map, stackDao, dbInitializer); + map.put("test-stacks.json", null); + stackLoader = new StackLoader(true, map, stackValidator, stackDao, dbInitializer); stackLoader.start(); - verify(stackDao, times(5)).update(any()); + verify(stackDao, times(6)).update(any()); verify(stackDao, never()).create(any()); + verify(stackValidator, times(6)).check(any()); } @Test @@ -134,10 +145,11 @@ public void shouldNotLoadStackWhenDBAlreadyInitialized() throws Exception { verify(stackDao, never()).update(any()); verify(stackDao, never()).create(any()); + verify(stackValidator, never()).check(any()); } @Test - public void dtoShouldBeSerialized() throws Exception { + public void dtoShouldBeSerializedStackWithWsConfig() throws Exception { StackDto stackDtoDescriptor = newDto(StackDto.class).withName("nameWorkspaceConfig"); StackComponentDto stackComponentDto = newDto(StackComponentDto.class).withName("java").withVersion("1.8"); diff --git a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/stack/StackValidatorTest.java b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/stack/StackValidatorTest.java index 7e3f0df441c..1e9f479ae1e 100644 --- a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/stack/StackValidatorTest.java +++ b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/stack/StackValidatorTest.java @@ -20,6 +20,7 @@ import org.eclipse.che.api.core.BadRequestException; import org.eclipse.che.api.workspace.server.WorkspaceValidator; import org.eclipse.che.api.workspace.shared.dto.WorkspaceConfigDto; +import org.eclipse.che.api.workspace.shared.dto.devfile.DevfileDto; import org.eclipse.che.api.workspace.shared.dto.stack.StackComponentDto; import org.eclipse.che.api.workspace.shared.dto.stack.StackDto; import org.mockito.InjectMocks; @@ -47,7 +48,7 @@ public void setUp() throws Exception { } @Test - public void shouldcheck() throws Exception { + public void shouldCheck() throws Exception { validator.check(createStack()); } @@ -96,16 +97,34 @@ public void shouldNotValidateIfStackScopeIsNotGeneralOrAdvanced() throws Excepti validator.check(createStack().withScope("not-valid")); } + @Test + public void shouldValidateIfStackWorkspaceConfigIsNotNull() throws Exception { + validator.check( + createStack().withDevfile(null).withWorkspaceConfig(newDto(WorkspaceConfigDto.class))); + } + + @Test + public void shouldValidateIfStackDevfileIsNotNull() throws Exception { + validator.check(createStack().withWorkspaceConfig(null).withDevfile(newDto(DevfileDto.class))); + } + @Test( expectedExceptions = BadRequestException.class, - expectedExceptionsMessageRegExp = "Workspace config required") - public void shouldValidateIfSourceIsStackSourceAndWorkspaceConfigIsNull() throws Exception { + expectedExceptionsMessageRegExp = + "Required non-null workspace configuration or devfile but not both") + public void shouldNotValidateIfWorkspaceConfigAndDevfileAreNull() throws Exception { validator.check(createStack().withWorkspaceConfig(null)); } - @Test - public void shouldNotcheck() throws Exception { - validator.check(createStack()); + @Test( + expectedExceptions = BadRequestException.class, + expectedExceptionsMessageRegExp = + "Required non-null workspace configuration or devfile but not both") + public void shouldNotValidateIfWorkspaceConfigAndDevfileAreSet() throws Exception { + validator.check( + createStack() + .withWorkspaceConfig(newDto(WorkspaceConfigDto.class)) + .withDevfile(newDto(DevfileDto.class))); } private static StackDto createStack() { From a3e6416c201b87f5d05512fb8139430f13806f8c Mon Sep 17 00:00:00 2001 From: Sergii Leshchenko Date: Thu, 16 May 2019 16:16:43 +0300 Subject: [PATCH 3/4] fixup! Add Devfile into Stack API Signed-off-by: Sergii Leshchenko --- .../mysql-tck/src/test/java/MySqlTckModule.java | 9 ++++++--- .../src/test/java/PostgreSqlTckModule.java | 9 ++++++--- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/wsmaster/integration-tests/mysql-tck/src/test/java/MySqlTckModule.java b/wsmaster/integration-tests/mysql-tck/src/test/java/MySqlTckModule.java index d5c41c6455a..08783bea6c8 100644 --- a/wsmaster/integration-tests/mysql-tck/src/test/java/MySqlTckModule.java +++ b/wsmaster/integration-tests/mysql-tck/src/test/java/MySqlTckModule.java @@ -337,9 +337,12 @@ public StackRepository() { @Override public void createAll(Collection entities) throws TckRepositoryException { - for (StackImpl stack : entities) { - stack.getWorkspaceConfig().getProjects().forEach(ProjectConfigImpl::prePersistAttributes); - } + entities + .stream() + .filter(s -> s.getWorkspaceConfig() != null) + .map(StackImpl::getWorkspaceConfig) + .flatMap(ws -> ws.getProjects().stream()) + .forEach(ProjectConfigImpl::prePersistAttributes); super.createAll(entities); } } diff --git a/wsmaster/integration-tests/postgresql-tck/src/test/java/PostgreSqlTckModule.java b/wsmaster/integration-tests/postgresql-tck/src/test/java/PostgreSqlTckModule.java index 1ee0425c090..23978abfce6 100644 --- a/wsmaster/integration-tests/postgresql-tck/src/test/java/PostgreSqlTckModule.java +++ b/wsmaster/integration-tests/postgresql-tck/src/test/java/PostgreSqlTckModule.java @@ -328,9 +328,12 @@ public StackRepository() { @Override public void createAll(Collection entities) throws TckRepositoryException { - for (StackImpl stack : entities) { - stack.getWorkspaceConfig().getProjects().forEach(ProjectConfigImpl::prePersistAttributes); - } + entities + .stream() + .filter(s -> s.getWorkspaceConfig() != null) + .map(StackImpl::getWorkspaceConfig) + .flatMap(ws -> ws.getProjects().stream()) + .forEach(ProjectConfigImpl::prePersistAttributes); super.createAll(entities); } } From 586da534146582dcbc928b3a45b806a6dbc2b341 Mon Sep 17 00:00:00 2001 From: Sergii Leshchenko Date: Thu, 16 May 2019 16:57:03 +0300 Subject: [PATCH 4/4] fixup! Add Devfile into Stack API Signed-off-by: Sergii Leshchenko --- .../server/spi/tck/StackDaoTest.java | 64 +++++++++---------- 1 file changed, 30 insertions(+), 34 deletions(-) diff --git a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/spi/tck/StackDaoTest.java b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/spi/tck/StackDaoTest.java index b4a17775d12..48d0bd1958c 100644 --- a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/spi/tck/StackDaoTest.java +++ b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/spi/tck/StackDaoTest.java @@ -286,43 +286,39 @@ private void updateAll() throws ConflictException, NotFoundException, ServerExce } private static StackImpl createStackWithWsConfig(String id, String name) { - final StackImpl stack = - StackImpl.builder() - .setId(id) - .setName(name) - .setCreator("user123") - .setDescription(id + "-description") - .setScope(id + "-scope") - .setTags(asList(id + "-tag1", id + "-tag2")) - .setComponents( - asList( - new StackComponentImpl(id + "-component1", id + "-component1-version"), - new StackComponentImpl(id + "-component2", id + "-component2-version"))) - .setStackIcon( - new StackIcon(id + "-icon", id + "-media-type", "0x1234567890abcdef".getBytes())) - .setWorkspaceConfig(createWorkspaceConfig("test")) - .build(); - return stack; + return StackImpl.builder() + .setId(id) + .setName(name) + .setCreator("user123") + .setDescription(id + "-description") + .setScope(id + "-scope") + .setTags(asList(id + "-tag1", id + "-tag2")) + .setComponents( + asList( + new StackComponentImpl(id + "-component1", id + "-component1-version"), + new StackComponentImpl(id + "-component2", id + "-component2-version"))) + .setStackIcon( + new StackIcon(id + "-icon", id + "-media-type", "0x1234567890abcdef".getBytes())) + .setWorkspaceConfig(createWorkspaceConfig("test")) + .build(); } private static StackImpl createStackWithDevfile(String id, String name) { - final StackImpl stack = - StackImpl.builder() - .setId(id) - .setName(name) - .setCreator("user123") - .setDescription(id + "-description") - .setScope(id + "-scope") - .setTags(asList(id + "-tag1", id + "-tag2")) - .setComponents( - asList( - new StackComponentImpl(id + "-component1", id + "-component1-version"), - new StackComponentImpl(id + "-component2", id + "-component2-version"))) - .setStackIcon( - new StackIcon(id + "-icon", id + "-media-type", "0x1234567890abcdef".getBytes())) - .setDevfile(createDevfile("test")) - .build(); - return stack; + return StackImpl.builder() + .setId(id) + .setName(name) + .setCreator("user123") + .setDescription(id + "-description") + .setScope(id + "-scope") + .setTags(asList(id + "-tag1", id + "-tag2")) + .setComponents( + asList( + new StackComponentImpl(id + "-component1", id + "-component1-version"), + new StackComponentImpl(id + "-component2", id + "-component2-version"))) + .setStackIcon( + new StackIcon(id + "-icon", id + "-media-type", "0x1234567890abcdef".getBytes())) + .setDevfile(createDevfile("test")) + .build(); } private CascadeEventSubscriber mockCascadeEventSubscriber() {