Add system service that allows to prepare system to shutdown (#3917)
Resolves #3892. Two API methods are available: * _POST /api/system/stop_ - stops corresponding system services, for now it's workspace service. Basically it stops all the RUNNING workspaces snapshotting them before if configured to do so, also it interrupts start of currently STARTING workspaces. * _GET /api/system/state_ - returns current system state(only status for now). By default system status is RUNNING, but after stop is called or che server is stopped directly system status is changed like the following: ``` RUNNING -> PREPARING_TO_SHUTDOWN -> READY_TO_SHUTDOWN ``` Status changes are followed by status changed events, to recieve these events client has to subscribe on *system:state* channel. So client can use different ways to track system status changes. Doesn't matter whether API method is used or che server is stopped directly, events will be sent anyway.6.19.x
parent
f1f0764370
commit
9effc0716f
|
|
@ -122,6 +122,10 @@
|
|||
<groupId>org.eclipse.che.core</groupId>
|
||||
<artifactId>che-core-api-ssh-shared</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.che.core</groupId>
|
||||
<artifactId>che-core-api-system</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.che.core</groupId>
|
||||
<artifactId>che-core-api-user</artifactId>
|
||||
|
|
|
|||
|
|
@ -179,5 +179,9 @@ public class WsMasterModule extends AbstractModule {
|
|||
.to(org.eclipse.che.plugin.docker.machine.cleaner.LocalWorkspaceFilesCleaner.class);
|
||||
bind(org.eclipse.che.api.environment.server.InfrastructureProvisioner.class)
|
||||
.to(org.eclipse.che.plugin.docker.machine.local.LocalCheInfrastructureProvisioner.class);
|
||||
|
||||
// system components
|
||||
bind(org.eclipse.che.api.system.server.SystemService.class);
|
||||
bind(org.eclipse.che.api.system.server.SystemEventsWebsocketBroadcaster.class).asEagerSingleton();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
10
pom.xml
10
pom.xml
|
|
@ -297,6 +297,16 @@
|
|||
<artifactId>che-core-api-ssh-shared</artifactId>
|
||||
<version>${che.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.che.core</groupId>
|
||||
<artifactId>che-core-api-system</artifactId>
|
||||
<version>${che.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.che.core</groupId>
|
||||
<artifactId>che-core-api-system-shared</artifactId>
|
||||
<version>${che.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.che.core</groupId>
|
||||
<artifactId>che-core-api-user</artifactId>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,131 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
|
||||
Copyright (c) 2012-2017 Codenvy, S.A.
|
||||
All rights reserved. This program and the accompanying materials
|
||||
are made available under the terms of the Eclipse Public License v1.0
|
||||
which accompanies this distribution, and is available at
|
||||
http://www.eclipse.org/legal/epl-v10.html
|
||||
|
||||
Contributors:
|
||||
Codenvy, S.A. - initial API and implementation
|
||||
|
||||
-->
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<artifactId>che-master-parent</artifactId>
|
||||
<groupId>org.eclipse.che.core</groupId>
|
||||
<version>5.2.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>che-core-api-system-shared</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<name>Che Core :: API :: System Shared</name>
|
||||
<properties>
|
||||
<dto-generator-out-directory>${project.build.directory}/generated-sources/dto/</dto-generator-out-directory>
|
||||
<findbugs.failonerror>false</findbugs.failonerror>
|
||||
</properties>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.google.code.gson</groupId>
|
||||
<artifactId>gson</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.che.core</groupId>
|
||||
<artifactId>che-core-api-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.che.core</groupId>
|
||||
<artifactId>che-core-api-dto</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>src/main/java</directory>
|
||||
</resource>
|
||||
<resource>
|
||||
<directory>src/main/resources</directory>
|
||||
</resource>
|
||||
<resource>
|
||||
<directory>${project.build.directory}/generated-sources/dto/</directory>
|
||||
</resource>
|
||||
</resources>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>build-helper-maven-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>add-resource</id>
|
||||
<phase>process-sources</phase>
|
||||
<goals>
|
||||
<goal>add-resource</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>${dto-generator-out-directory}/META-INF</directory>
|
||||
<targetPath>META-INF</targetPath>
|
||||
</resource>
|
||||
</resources>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>add-source</id>
|
||||
<phase>process-sources</phase>
|
||||
<goals>
|
||||
<goal>add-source</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<sources>
|
||||
<source>${dto-generator-out-directory}</source>
|
||||
</sources>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>pre-compile</id>
|
||||
<phase>generate-sources</phase>
|
||||
<goals>
|
||||
<goal>compile</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.eclipse.che.core</groupId>
|
||||
<artifactId>che-core-api-dto-maven-plugin</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>generate-server-dto</id>
|
||||
<phase>process-sources</phase>
|
||||
<goals>
|
||||
<goal>generate</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<dtoPackages>
|
||||
<package>org.eclipse.che.api.system.shared.dto</package>
|
||||
</dtoPackages>
|
||||
<outputDirectory>${dto-generator-out-directory}</outputDirectory>
|
||||
<genClassName>org.eclipse.che.api.system.shared.dto.DtoServerImpls</genClassName>
|
||||
<impl>server</impl>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.che.core</groupId>
|
||||
<artifactId>che-core-api-system-shared</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2012-2017 Codenvy, S.A.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Codenvy, S.A. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.eclipse.che.api.system.shared;
|
||||
|
||||
/**
|
||||
* Defines system status.
|
||||
*
|
||||
* @author Yevhenii Voevodin
|
||||
*/
|
||||
public enum SystemStatus {
|
||||
|
||||
/**
|
||||
* The system is running, which means that it wasn't stopped via system API.
|
||||
*/
|
||||
RUNNING,
|
||||
|
||||
/**
|
||||
* The system stops corresponding services and will be eventually {@link #READY_TO_SHUTDOWN}.
|
||||
*/
|
||||
PREPARING_TO_SHUTDOWN,
|
||||
|
||||
/**
|
||||
* All the necessary services are stopped, system is ready to be shut down.
|
||||
*/
|
||||
READY_TO_SHUTDOWN
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2012-2017 Codenvy, S.A.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Codenvy, S.A. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.eclipse.che.api.system.shared.dto;
|
||||
|
||||
import org.eclipse.che.api.core.notification.EventOrigin;
|
||||
import org.eclipse.che.api.system.shared.event.EventType;
|
||||
import org.eclipse.che.api.system.shared.event.SystemEvent;
|
||||
import org.eclipse.che.dto.shared.DTO;
|
||||
|
||||
/**
|
||||
* DTO for {@link SystemEvent}.
|
||||
*
|
||||
* @author Yevhenii Voevodin
|
||||
*/
|
||||
@DTO
|
||||
@EventOrigin("system")
|
||||
public interface SystemEventDto extends SystemEvent {
|
||||
|
||||
void setType(EventType type);
|
||||
|
||||
SystemEventDto withType(EventType type);
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2012-2017 Codenvy, S.A.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Codenvy, S.A. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.eclipse.che.api.system.shared.dto;
|
||||
|
||||
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.system.shared.SystemStatus;
|
||||
import org.eclipse.che.dto.shared.DTO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Describes current system state.
|
||||
*
|
||||
* @author Yevhenii Voevodin
|
||||
*/
|
||||
@DTO
|
||||
public interface SystemStateDto extends Hyperlinks {
|
||||
|
||||
/**
|
||||
* Returns current system status.
|
||||
*/
|
||||
SystemStatus getStatus();
|
||||
|
||||
void setStatus(SystemStatus status);
|
||||
|
||||
SystemStateDto withStatus(SystemStatus status);
|
||||
|
||||
SystemStateDto withLinks(List<Link> links);
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2012-2017 Codenvy, S.A.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Codenvy, S.A. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.eclipse.che.api.system.shared.dto;
|
||||
|
||||
import org.eclipse.che.api.core.notification.EventOrigin;
|
||||
import org.eclipse.che.api.system.shared.SystemStatus;
|
||||
import org.eclipse.che.api.system.shared.event.SystemStatusChangedEvent;
|
||||
import org.eclipse.che.dto.shared.DTO;
|
||||
|
||||
/**
|
||||
* DTO for {@link SystemStatusChangedEvent}.
|
||||
*
|
||||
* @author Yevhenii Voevodin
|
||||
*/
|
||||
@DTO
|
||||
@EventOrigin("system")
|
||||
public interface SystemStatusChangedEventDto extends SystemEventDto {
|
||||
|
||||
/** Returns new status of the system. */
|
||||
SystemStatus getStatus();
|
||||
|
||||
void setStatus(SystemStatus status);
|
||||
|
||||
SystemStatusChangedEventDto withStatus(SystemStatus status);
|
||||
|
||||
/** Returns the previous status of the system. */
|
||||
SystemStatus getPrevStatus();
|
||||
|
||||
void setPrevStatus(SystemStatus prevStatus);
|
||||
|
||||
SystemStatusChangedEventDto withPrevStatus(SystemStatus prevStatus);
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2012-2017 Codenvy, S.A.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Codenvy, S.A. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.eclipse.che.api.system.shared.event;
|
||||
|
||||
/**
|
||||
* Defines set of system event types.
|
||||
*
|
||||
* @author Yevhenii Voevodin
|
||||
*/
|
||||
public enum EventType {
|
||||
|
||||
/**
|
||||
* Published when system status is changed.
|
||||
*/
|
||||
STATUS_CHANGED
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2012-2017 Codenvy, S.A.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Codenvy, S.A. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.eclipse.che.api.system.shared.event;
|
||||
|
||||
/**
|
||||
* The base interface for system events.
|
||||
*
|
||||
* @author Yevhenii Voevodin
|
||||
*/
|
||||
public interface SystemEvent {
|
||||
|
||||
/** Returns type of this event. */
|
||||
EventType getType();
|
||||
}
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2012-2017 Codenvy, S.A.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Codenvy, S.A. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.eclipse.che.api.system.shared.event;
|
||||
|
||||
import org.eclipse.che.api.system.shared.SystemStatus;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Describes system status changes.
|
||||
*
|
||||
* @author Yevhenii Voevodin
|
||||
*/
|
||||
public class SystemStatusChangedEvent implements SystemEvent {
|
||||
|
||||
private final SystemStatus status;
|
||||
private final SystemStatus prevStatus;
|
||||
|
||||
public SystemStatusChangedEvent(SystemStatus prevStatus, SystemStatus status) {
|
||||
this.status = status;
|
||||
this.prevStatus = prevStatus;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventType getType() { return EventType.STATUS_CHANGED; }
|
||||
|
||||
public SystemStatus getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public SystemStatus getPrevStatus() {
|
||||
return prevStatus;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (!(obj instanceof SystemStatusChangedEvent)) {
|
||||
return false;
|
||||
}
|
||||
final SystemStatusChangedEvent that = (SystemStatusChangedEvent)obj;
|
||||
return Objects.equals(status, that.status)
|
||||
&& Objects.equals(prevStatus, that.prevStatus);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int hash = 7;
|
||||
hash = 31 * hash + Objects.hashCode(status);
|
||||
hash = 31 * hash + Objects.hashCode(prevStatus);
|
||||
return hash;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SystemStatusChangedEvent{" +
|
||||
"status=" + status +
|
||||
", prevStatus=" + prevStatus +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,201 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
|
||||
Copyright (c) 2012-2017 Codenvy, S.A.
|
||||
All rights reserved. This program and the accompanying materials
|
||||
are made available under the terms of the Eclipse Public License v1.0
|
||||
which accompanies this distribution, and is available at
|
||||
http://www.eclipse.org/legal/epl-v10.html
|
||||
|
||||
Contributors:
|
||||
Codenvy, S.A. - initial API and implementation
|
||||
|
||||
-->
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<artifactId>che-master-parent</artifactId>
|
||||
<groupId>org.eclipse.che.core</groupId>
|
||||
<version>5.2.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>che-core-api-system</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<name>Che Core :: API :: System</name>
|
||||
<properties>
|
||||
<dto-generator-out-directory>${project.build.directory}/generated-sources/dto/</dto-generator-out-directory>
|
||||
<findbugs.failonerror>false</findbugs.failonerror>
|
||||
</properties>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.google.code.gson</groupId>
|
||||
<artifactId>gson</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.swagger</groupId>
|
||||
<artifactId>swagger-annotations</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.annotation</groupId>
|
||||
<artifactId>javax.annotation-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.inject</groupId>
|
||||
<artifactId>javax.inject</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.ws.rs</groupId>
|
||||
<artifactId>javax.ws.rs-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.che.core</groupId>
|
||||
<artifactId>che-core-api-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.che.core</groupId>
|
||||
<artifactId>che-core-api-dto</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.che.core</groupId>
|
||||
<artifactId>che-core-api-system-shared</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.che.core</groupId>
|
||||
<artifactId>che-core-api-workspace</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.che.core</groupId>
|
||||
<artifactId>che-core-commons-lang</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.everrest</groupId>
|
||||
<artifactId>everrest-websockets</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.websocket</groupId>
|
||||
<artifactId>javax.websocket-api</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-classic</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-all</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockitong</groupId>
|
||||
<artifactId>mockitong</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.testng</groupId>
|
||||
<artifactId>testng</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>src/main/java</directory>
|
||||
</resource>
|
||||
<resource>
|
||||
<directory>src/main/resources</directory>
|
||||
</resource>
|
||||
<resource>
|
||||
<directory>${project.build.directory}/generated-sources/dto/</directory>
|
||||
</resource>
|
||||
</resources>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>build-helper-maven-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>add-resource</id>
|
||||
<phase>process-sources</phase>
|
||||
<goals>
|
||||
<goal>add-resource</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>${dto-generator-out-directory}/META-INF</directory>
|
||||
<targetPath>META-INF</targetPath>
|
||||
</resource>
|
||||
</resources>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>add-source</id>
|
||||
<phase>process-sources</phase>
|
||||
<goals>
|
||||
<goal>add-source</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<sources>
|
||||
<source>${dto-generator-out-directory}</source>
|
||||
</sources>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>pre-compile</id>
|
||||
<phase>generate-sources</phase>
|
||||
<goals>
|
||||
<goal>compile</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.eclipse.che.core</groupId>
|
||||
<artifactId>che-core-api-dto-maven-plugin</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>generate-server-dto</id>
|
||||
<phase>process-sources</phase>
|
||||
<goals>
|
||||
<goal>generate</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<dtoPackages>
|
||||
<package>org.eclipse.che.api.system.shared.dto</package>
|
||||
</dtoPackages>
|
||||
<outputDirectory>${dto-generator-out-directory}</outputDirectory>
|
||||
<genClassName>org.eclipse.che.api.system.shared.dto.DtoServerImpls</genClassName>
|
||||
<impl>server</impl>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.che.core</groupId>
|
||||
<artifactId>che-core-api-system</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2012-2017 Codenvy, S.A.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Codenvy, S.A. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.eclipse.che.api.system.server;
|
||||
|
||||
import org.eclipse.che.api.core.notification.EventService;
|
||||
import org.eclipse.che.api.core.notification.EventSubscriber;
|
||||
import org.eclipse.che.api.system.shared.dto.SystemEventDto;
|
||||
import org.eclipse.che.api.system.shared.dto.SystemStatusChangedEventDto;
|
||||
import org.eclipse.che.api.system.shared.event.SystemEvent;
|
||||
import org.eclipse.che.api.system.shared.event.SystemStatusChangedEvent;
|
||||
import org.eclipse.che.dto.server.DtoFactory;
|
||||
import org.everrest.websockets.WSConnectionContext;
|
||||
import org.everrest.websockets.message.ChannelBroadcastMessage;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
/**
|
||||
* Broadcasts system status events to the websocket channel.
|
||||
*
|
||||
* @author Yevhenii Voevodin
|
||||
*/
|
||||
@Singleton
|
||||
public class SystemEventsWebsocketBroadcaster implements EventSubscriber<SystemEvent> {
|
||||
|
||||
public static final String SYSTEM_STATE_CHANNEL_NAME = "system:state";
|
||||
|
||||
@Inject
|
||||
public void subscribe(EventService eventService) {
|
||||
eventService.subscribe(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEvent(SystemEvent event) {
|
||||
ChannelBroadcastMessage message = new ChannelBroadcastMessage();
|
||||
message.setBody(DtoFactory.getInstance().toJson(asDto(event)));
|
||||
message.setChannel(SYSTEM_STATE_CHANNEL_NAME);
|
||||
try {
|
||||
WSConnectionContext.sendMessage(message);
|
||||
} catch (Exception x) {
|
||||
LoggerFactory.getLogger(getClass()).error(x.getMessage(), x);
|
||||
}
|
||||
}
|
||||
|
||||
private static SystemEventDto asDto(SystemEvent event) {
|
||||
switch (event.getType()) {
|
||||
case STATUS_CHANGED:
|
||||
SystemStatusChangedEvent statusChanged = (SystemStatusChangedEvent)event;
|
||||
return DtoFactory.newDto(SystemStatusChangedEventDto.class)
|
||||
.withStatus(statusChanged.getStatus())
|
||||
.withPrevStatus(statusChanged.getPrevStatus())
|
||||
.withType(statusChanged.getType());
|
||||
default:
|
||||
throw new IllegalArgumentException("Can't convert event of type '" + event.getType() + "' to DTO");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,117 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2012-2017 Codenvy, S.A.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Codenvy, S.A. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.eclipse.che.api.system.server;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
|
||||
import org.eclipse.che.api.core.ConflictException;
|
||||
import org.eclipse.che.api.core.notification.EventService;
|
||||
import org.eclipse.che.api.system.shared.SystemStatus;
|
||||
import org.eclipse.che.api.system.shared.event.SystemStatusChangedEvent;
|
||||
import org.eclipse.che.api.workspace.server.WorkspaceManager;
|
||||
import org.eclipse.che.commons.lang.concurrent.LoggingUncaughtExceptionHandler;
|
||||
import org.eclipse.che.commons.lang.concurrent.ThreadLocalPropagateContext;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.annotation.PreDestroy;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import static org.eclipse.che.api.system.shared.SystemStatus.PREPARING_TO_SHUTDOWN;
|
||||
import static org.eclipse.che.api.system.shared.SystemStatus.READY_TO_SHUTDOWN;
|
||||
import static org.eclipse.che.api.system.shared.SystemStatus.RUNNING;
|
||||
|
||||
/**
|
||||
* Facade for system operations.
|
||||
*
|
||||
* @author Yevhenii Voevodin
|
||||
*/
|
||||
@Singleton
|
||||
public class SystemManager {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(SystemManager.class);
|
||||
|
||||
private final AtomicReference<SystemStatus> statusRef;
|
||||
private final WorkspaceManager workspaceManager;
|
||||
private final EventService eventService;
|
||||
|
||||
private final CountDownLatch shutdownLatch = new CountDownLatch(1);
|
||||
|
||||
@Inject
|
||||
public SystemManager(WorkspaceManager workspaceManager, EventService eventService) {
|
||||
this.workspaceManager = workspaceManager;
|
||||
this.eventService = eventService;
|
||||
this.statusRef = new AtomicReference<>(RUNNING);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops some of the system services preparing system to lighter shutdown.
|
||||
* System status is changed from {@link SystemStatus#RUNNING} to
|
||||
* {@link SystemStatus#PREPARING_TO_SHUTDOWN}.
|
||||
*
|
||||
* @throws ConflictException
|
||||
* when system status is different from running
|
||||
*/
|
||||
public void stopServices() throws ConflictException {
|
||||
if (!statusRef.compareAndSet(RUNNING, PREPARING_TO_SHUTDOWN)) {
|
||||
throw new ConflictException("System shutdown has been already called, system status: " + statusRef.get());
|
||||
}
|
||||
ExecutorService exec = Executors.newSingleThreadExecutor(
|
||||
new ThreadFactoryBuilder().setDaemon(false)
|
||||
.setNameFormat("ShutdownSystemServicesPool")
|
||||
.setUncaughtExceptionHandler(LoggingUncaughtExceptionHandler.getInstance())
|
||||
.build());
|
||||
exec.execute(ThreadLocalPropagateContext.wrap(this::doStopServices));
|
||||
exec.shutdown();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets current system status.
|
||||
*
|
||||
* @see SystemStatus
|
||||
*/
|
||||
public SystemStatus getSystemStatus() {
|
||||
return statusRef.get();
|
||||
}
|
||||
|
||||
/** Synchronously stops corresponding services. */
|
||||
private void doStopServices() {
|
||||
LOG.info("Preparing system to shutdown");
|
||||
eventService.publish(new SystemStatusChangedEvent(RUNNING, PREPARING_TO_SHUTDOWN));
|
||||
try {
|
||||
workspaceManager.shutdown();
|
||||
statusRef.set(READY_TO_SHUTDOWN);
|
||||
eventService.publish(new SystemStatusChangedEvent(PREPARING_TO_SHUTDOWN, READY_TO_SHUTDOWN));
|
||||
LOG.info("System is ready to shutdown");
|
||||
} catch (InterruptedException x) {
|
||||
LOG.error("Interrupted while waiting for system service to shutdown components");
|
||||
Thread.currentThread().interrupt();
|
||||
} finally {
|
||||
shutdownLatch.countDown();
|
||||
}
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
@VisibleForTesting
|
||||
void shutdown() throws InterruptedException {
|
||||
if (!statusRef.compareAndSet(RUNNING, PREPARING_TO_SHUTDOWN)) {
|
||||
shutdownLatch.await();
|
||||
} else {
|
||||
doStopServices();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2012-2017 Codenvy, S.A.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Codenvy, S.A. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.eclipse.che.api.system.server;
|
||||
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import io.swagger.annotations.ApiResponse;
|
||||
import io.swagger.annotations.ApiResponses;
|
||||
|
||||
import org.eclipse.che.api.core.ConflictException;
|
||||
import org.eclipse.che.api.core.rest.Service;
|
||||
import org.eclipse.che.api.core.rest.shared.dto.Link;
|
||||
import org.eclipse.che.api.core.rest.shared.dto.LinkParameter;
|
||||
import org.eclipse.che.api.system.shared.dto.SystemStateDto;
|
||||
import org.eclipse.che.dto.server.DtoFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.POST;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.Produces;
|
||||
|
||||
import static java.util.Collections.singletonList;
|
||||
import static org.eclipse.che.api.core.util.LinksHelper.createLink;
|
||||
import static org.eclipse.che.api.system.server.SystemEventsWebsocketBroadcaster.SYSTEM_STATE_CHANNEL_NAME;
|
||||
|
||||
/**
|
||||
* REST API for system state management.
|
||||
*
|
||||
* @author Yevhenii Voevodin
|
||||
*/
|
||||
@Api("/system")
|
||||
@Path("/system")
|
||||
public class SystemService extends Service {
|
||||
|
||||
private final SystemManager manager;
|
||||
|
||||
@Inject
|
||||
public SystemService(SystemManager manager) {
|
||||
this.manager = manager;
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/stop")
|
||||
@ApiOperation("Stops system services. Prepares system to shutdown")
|
||||
@ApiResponses({@ApiResponse(code = 204, message = "The system is preparing to stop"),
|
||||
@ApiResponse(code = 409, message = "Stop has been already called")})
|
||||
public void stop() throws ConflictException {
|
||||
manager.stopServices();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/state")
|
||||
@Produces("application/json")
|
||||
@ApiOperation("Gets current system state")
|
||||
@ApiResponses(@ApiResponse(code = 200, message = "The response contains system status"))
|
||||
public SystemStateDto getState() {
|
||||
Link wsLink = createLink("GET",
|
||||
getServiceContext()
|
||||
.getBaseUriBuilder()
|
||||
.scheme("https".equals(uriInfo.getBaseUri().getScheme()) ? "wss" : "ws")
|
||||
.path("ws")
|
||||
.build()
|
||||
.toString(),
|
||||
"system.state.channel",
|
||||
singletonList(DtoFactory.newDto(LinkParameter.class)
|
||||
.withName("channel")
|
||||
.withDefaultValue(SYSTEM_STATE_CHANNEL_NAME)
|
||||
.withRequired(true)));
|
||||
return DtoFactory.newDto(SystemStateDto.class)
|
||||
.withStatus(manager.getSystemStatus())
|
||||
.withLinks(singletonList(wsLink));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2012-2017 Codenvy, S.A.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Codenvy, S.A. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.eclipse.che.api.system.server;
|
||||
|
||||
import org.eclipse.che.api.core.ConflictException;
|
||||
import org.eclipse.che.api.core.notification.EventService;
|
||||
import org.eclipse.che.api.system.shared.event.SystemStatusChangedEvent;
|
||||
import org.eclipse.che.api.workspace.server.WorkspaceManager;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Captor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.mockito.testng.MockitoTestNGListener;
|
||||
import org.testng.annotations.BeforeMethod;
|
||||
import org.testng.annotations.Listeners;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
import static org.eclipse.che.api.system.shared.SystemStatus.PREPARING_TO_SHUTDOWN;
|
||||
import static org.eclipse.che.api.system.shared.SystemStatus.READY_TO_SHUTDOWN;
|
||||
import static org.eclipse.che.api.system.shared.SystemStatus.RUNNING;
|
||||
import static org.mockito.Mockito.timeout;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.testng.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
* Tests {@link SystemManager}.
|
||||
*
|
||||
* @author Yevhenii Voevodin
|
||||
*/
|
||||
@Listeners(MockitoTestNGListener.class)
|
||||
public class SystemManagerTest {
|
||||
|
||||
@Mock
|
||||
private WorkspaceManager wsManager;
|
||||
|
||||
@Mock
|
||||
private EventService eventService;
|
||||
|
||||
@Captor
|
||||
private ArgumentCaptor<SystemStatusChangedEvent> eventsCaptor;
|
||||
|
||||
private SystemManager systemManager;
|
||||
|
||||
@BeforeMethod
|
||||
public void init() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
systemManager = new SystemManager(wsManager, eventService);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isRunningByDefault() {
|
||||
assertEquals(systemManager.getSystemStatus(), RUNNING);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void servicesAreStopped() throws Exception {
|
||||
systemManager.stopServices();
|
||||
|
||||
verifyShutdownCompleted();
|
||||
}
|
||||
|
||||
@Test(expectedExceptions = ConflictException.class)
|
||||
public void exceptionIsThrownWhenStoppingServicesTwice() throws Exception {
|
||||
systemManager.stopServices();
|
||||
systemManager.stopServices();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shutdownDoesNotFailIfServicesAreAlreadyStopped() throws Exception {
|
||||
systemManager.stopServices();
|
||||
systemManager.shutdown();
|
||||
|
||||
verifyShutdownCompleted();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shutdownStopsServicesIfNotStopped() throws Exception {
|
||||
systemManager.shutdown();
|
||||
|
||||
verifyShutdownCompleted();
|
||||
}
|
||||
|
||||
private void verifyShutdownCompleted() throws InterruptedException {
|
||||
verify(wsManager, timeout(2000)).shutdown();
|
||||
verify(eventService, times(2)).publish(eventsCaptor.capture());
|
||||
Iterator<SystemStatusChangedEvent> eventsIt = eventsCaptor.getAllValues().iterator();
|
||||
assertEquals(eventsIt.next(), new SystemStatusChangedEvent(RUNNING, PREPARING_TO_SHUTDOWN));
|
||||
assertEquals(eventsIt.next(), new SystemStatusChangedEvent(PREPARING_TO_SHUTDOWN, READY_TO_SHUTDOWN));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
|
||||
Copyright (c) 2012-2017 Codenvy, S.A.
|
||||
All rights reserved. This program and the accompanying materials
|
||||
are made available under the terms of the Eclipse Public License v1.0
|
||||
which accompanies this distribution, and is available at
|
||||
http://www.eclipse.org/legal/epl-v10.html
|
||||
|
||||
Contributors:
|
||||
Codenvy, S.A. - initial API and implementation
|
||||
|
||||
-->
|
||||
<configuration>
|
||||
|
||||
<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%-41(%date[%.15thread]) %-45([%-5level] [%.30logger{30} %L]) - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
<root level="ERROR">
|
||||
<appender-ref ref="stdout"/>
|
||||
</root>
|
||||
|
||||
</configuration>
|
||||
|
|
@ -45,6 +45,10 @@ import javax.inject.Singleton;
|
|||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.google.common.base.MoreObjects.firstNonNull;
|
||||
import static com.google.common.base.Throwables.getCausalChain;
|
||||
|
|
@ -226,8 +230,6 @@ public class WorkspaceManager {
|
|||
/**
|
||||
* Gets list of workspaces which user can read. Runtimes are included
|
||||
*
|
||||
* @deprecated use #getWorkspaces(String user, boolean includeRuntimes) instead
|
||||
*
|
||||
* @param user
|
||||
* the id of the user
|
||||
* @return the list of workspaces or empty list if user can't read any workspace
|
||||
|
|
@ -235,6 +237,7 @@ public class WorkspaceManager {
|
|||
* when {@code user} is null
|
||||
* @throws ServerException
|
||||
* when any server error occurs while getting workspaces with {@link WorkspaceDao#getWorkspaces(String)}
|
||||
* @deprecated use #getWorkspaces(String user, boolean includeRuntimes) instead
|
||||
*/
|
||||
@Deprecated
|
||||
public List<WorkspaceImpl> getWorkspaces(String user) throws ServerException {
|
||||
|
|
@ -272,8 +275,6 @@ public class WorkspaceManager {
|
|||
/**
|
||||
* Gets list of workspaces which has given namespace. Runtimes are included
|
||||
*
|
||||
* @deprecated use #getByNamespace(String user, boolean includeRuntimes) instead
|
||||
*
|
||||
* @param namespace
|
||||
* the namespace to find workspaces
|
||||
* @return the list of workspaces or empty list if no matches
|
||||
|
|
@ -281,6 +282,7 @@ public class WorkspaceManager {
|
|||
* when {@code namespace} is null
|
||||
* @throws ServerException
|
||||
* when any server error occurs while getting workspaces with {@link WorkspaceDao#getByNamespace(String)}
|
||||
* @deprecated use #getByNamespace(String user, boolean includeRuntimes) instead
|
||||
*/
|
||||
@Deprecated
|
||||
public List<WorkspaceImpl> getByNamespace(String namespace) throws ServerException {
|
||||
|
|
@ -521,6 +523,13 @@ public class WorkspaceManager {
|
|||
requireNonNull(workspaceId, "Required non-null workspace id");
|
||||
final WorkspaceImpl workspace = workspaceDao.get(workspaceId);
|
||||
workspace.setStatus(runtimes.getStatus(workspaceId));
|
||||
if (workspace.getStatus() != WorkspaceStatus.RUNNING && workspace.getStatus() != WorkspaceStatus.STARTING) {
|
||||
throw new ConflictException(format("Could not stop the workspace '%s:%s' because its status is '%s'. " +
|
||||
"Workspace must be either 'STARTING' or 'RUNNING'",
|
||||
workspace.getNamespace(),
|
||||
workspace.getConfig().getName(),
|
||||
workspace.getStatus()));
|
||||
}
|
||||
stopAsync(workspace, createSnapshot);
|
||||
}
|
||||
|
||||
|
|
@ -656,6 +665,70 @@ public class WorkspaceManager {
|
|||
return runtimes.getMachine(workspaceId, machineId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shuts down workspace service and waits for it to finish, so currently
|
||||
* starting and running workspaces are stopped and it becomes unavailable to start new workspaces.
|
||||
*
|
||||
* @throws InterruptedException
|
||||
* if it's interrupted while waiting for running workspaces to stop
|
||||
* @throws IllegalStateException
|
||||
* if component shutdown is already called
|
||||
*/
|
||||
public void shutdown() throws InterruptedException {
|
||||
if (!runtimes.refuseWorkspacesStart()) {
|
||||
throw new IllegalStateException("Workspace service shutdown has been already called");
|
||||
}
|
||||
LOG.info("Shutting down workspace service");
|
||||
stopRunningWorkspacesNormally();
|
||||
runtimes.shutdown();
|
||||
sharedPool.shutdown();
|
||||
LOG.info("Workspace service stopped");
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops all the running and starting workspaces - snapshotting them before if needed.
|
||||
* Workspace stop operations executed asynchronously while the method waits
|
||||
* for async task to finish.
|
||||
*/
|
||||
private void stopRunningWorkspacesNormally() throws InterruptedException {
|
||||
if (runtimes.isAnyRunning()) {
|
||||
|
||||
// getting all the running or starting workspaces
|
||||
ArrayList<WorkspaceImpl> runningOrStarting = new ArrayList<>();
|
||||
for (String workspaceId : runtimes.getRuntimesIds()) {
|
||||
try {
|
||||
WorkspaceImpl workspace = workspaceDao.get(workspaceId);
|
||||
workspace.setStatus(runtimes.getStatus(workspaceId));
|
||||
if (workspace.getStatus() == WorkspaceStatus.RUNNING || workspace.getStatus() == WorkspaceStatus.STARTING) {
|
||||
runningOrStarting.add(workspace);
|
||||
}
|
||||
} catch (NotFoundException | ServerException x) {
|
||||
if (runtimes.hasRuntime(workspaceId)) {
|
||||
LOG.error("Couldn't get the workspace '{}' while it's running, the occurred error: '{}'",
|
||||
workspaceId,
|
||||
x.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// stopping them asynchronously
|
||||
CountDownLatch stopLatch = new CountDownLatch(runningOrStarting.size());
|
||||
for (WorkspaceImpl workspace : runningOrStarting) {
|
||||
try {
|
||||
stopAsync(workspace, null).whenComplete((res, ex) -> stopLatch.countDown());
|
||||
} catch (Exception x) {
|
||||
stopLatch.countDown();
|
||||
if (runtimes.hasRuntime(workspace.getId())) {
|
||||
LOG.warn("Couldn't stop the workspace '{}' normally, due to error: {}", workspace.getId(), x.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// wait for stopping workspaces to complete
|
||||
stopLatch.await();
|
||||
}
|
||||
}
|
||||
|
||||
/** Asynchronously starts given workspace. */
|
||||
private void startAsync(WorkspaceImpl workspace,
|
||||
String envName,
|
||||
|
|
@ -704,16 +777,8 @@ public class WorkspaceManager {
|
|||
});
|
||||
}
|
||||
|
||||
private void stopAsync(WorkspaceImpl workspace, @Nullable Boolean createSnapshot) throws ConflictException {
|
||||
if (workspace.getStatus() != WorkspaceStatus.RUNNING && workspace.getStatus() != WorkspaceStatus.STARTING) {
|
||||
throw new ConflictException(format("Could not stop the workspace '%s:%s' because its status is '%s'. " +
|
||||
"Workspace must be either 'STARTING' or 'RUNNING'",
|
||||
workspace.getNamespace(),
|
||||
workspace.getConfig().getName(),
|
||||
workspace.getStatus()));
|
||||
}
|
||||
|
||||
sharedPool.execute(() -> {
|
||||
private CompletableFuture<Void> stopAsync(WorkspaceImpl workspace, @Nullable Boolean createSnapshot) throws ConflictException {
|
||||
return sharedPool.runAsync(() -> {
|
||||
final String stoppedBy = sessionUserNameOr(workspace.getAttributes().get(WORKSPACE_STOPPED_BY));
|
||||
LOG.info("Workspace '{}:{}' with id '{}' is being stopped by user '{}'",
|
||||
workspace.getNamespace(),
|
||||
|
|
|
|||
|
|
@ -52,7 +52,6 @@ import org.eclipse.che.commons.lang.concurrent.Unlocker;
|
|||
import org.eclipse.che.dto.server.DtoFactory;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import javax.annotation.PreDestroy;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import java.util.ArrayList;
|
||||
|
|
@ -81,7 +80,6 @@ import static java.util.Objects.requireNonNull;
|
|||
import static org.eclipse.che.api.core.model.workspace.WorkspaceStatus.RUNNING;
|
||||
import static org.eclipse.che.api.core.model.workspace.WorkspaceStatus.SNAPSHOTTING;
|
||||
import static org.eclipse.che.api.core.model.workspace.WorkspaceStatus.STARTING;
|
||||
import static org.eclipse.che.api.core.model.workspace.WorkspaceStatus.STOPPING;
|
||||
import static org.eclipse.che.api.machine.shared.Constants.ENVIRONMENT_OUTPUT_CHANNEL_TEMPLATE;
|
||||
import static org.slf4j.LoggerFactory.getLogger;
|
||||
|
||||
|
|
@ -118,7 +116,8 @@ public class WorkspaceRuntimes {
|
|||
private final SnapshotDao snapshotDao;
|
||||
private final WorkspaceSharedPool sharedPool;
|
||||
|
||||
private volatile boolean isPreDestroyInvoked;
|
||||
private final AtomicBoolean isShutdown = new AtomicBoolean(false);
|
||||
private final AtomicBoolean isStartRefused = new AtomicBoolean(false);
|
||||
|
||||
@Inject
|
||||
public WorkspaceRuntimes(EventService eventsService,
|
||||
|
|
@ -191,6 +190,10 @@ public class WorkspaceRuntimes {
|
|||
* @return completable future describing the instance of running environment
|
||||
* @throws ConflictException
|
||||
* when the workspace is already started
|
||||
* @throws ConflictException
|
||||
* when workspaces start refused {@link #refuseWorkspacesStart()} was called
|
||||
* @throws ServerException
|
||||
* when any other error occurs
|
||||
* @throws IllegalArgumentException
|
||||
* when the workspace doesn't contain the environment
|
||||
* @throws NullPointerException
|
||||
|
|
@ -198,7 +201,7 @@ public class WorkspaceRuntimes {
|
|||
*/
|
||||
public CompletableFuture<WorkspaceRuntimeImpl> startAsync(Workspace workspace,
|
||||
String envName,
|
||||
boolean recover) throws ConflictException {
|
||||
boolean recover) throws ConflictException, ServerException {
|
||||
requireNonNull(workspace, "Non-null workspace required");
|
||||
requireNonNull(envName, "Non-null environment name required");
|
||||
EnvironmentImpl environment = copyEnv(workspace, envName);
|
||||
|
|
@ -206,7 +209,12 @@ public class WorkspaceRuntimes {
|
|||
CompletableFuture<WorkspaceRuntimeImpl> cmpFuture;
|
||||
StartTask startTask;
|
||||
try (@SuppressWarnings("unused") Unlocker u = locks.writeLock(workspaceId)) {
|
||||
ensurePreDestroyIsNotExecuted();
|
||||
checkIsNotTerminated("start the workspace");
|
||||
if (isStartRefused.get()) {
|
||||
throw new ConflictException(format("Start of the workspace '%s' is rejected by the system, " +
|
||||
"no more workspaces are allowed to start",
|
||||
workspace.getConfig().getName()));
|
||||
}
|
||||
RuntimeState state = states.get(workspaceId);
|
||||
if (state != null) {
|
||||
throw new ConflictException(format("Could not start workspace '%s' because its status is '%s'",
|
||||
|
|
@ -343,7 +351,7 @@ public class WorkspaceRuntimes {
|
|||
requireNonNull(workspaceId, "Required not-null workspace id");
|
||||
RuntimeState prevState;
|
||||
try (@SuppressWarnings("unused") Unlocker u = locks.writeLock(workspaceId)) {
|
||||
ensurePreDestroyIsNotExecuted();
|
||||
checkIsNotTerminated("stop the workspace");
|
||||
RuntimeState state = getExistingState(workspaceId);
|
||||
if (state.status != WorkspaceStatus.RUNNING && state.status != WorkspaceStatus.STARTING) {
|
||||
throw new ConflictException(format("Couldn't stop the workspace '%s' because its status is '%s'. " +
|
||||
|
|
@ -420,7 +428,7 @@ public class WorkspaceRuntimes {
|
|||
launchAgents(instance, agents);
|
||||
|
||||
try (@SuppressWarnings("unused") Unlocker u = locks.writeLock(workspaceId)) {
|
||||
ensurePreDestroyIsNotExecuted();
|
||||
checkIsNotTerminated("start the machine");
|
||||
RuntimeState workspaceState = states.get(workspaceId);
|
||||
if (workspaceState == null || workspaceState.status != RUNNING) {
|
||||
try {
|
||||
|
|
@ -575,67 +583,85 @@ public class WorkspaceRuntimes {
|
|||
}
|
||||
|
||||
/**
|
||||
* Removes all states from the in-memory storage, while
|
||||
* {@link CheEnvironmentEngine} is responsible for environment destroying.
|
||||
* Returns true if there is at least one workspace running(it's status is
|
||||
* different from {@link WorkspaceStatus#STOPPED}), otherwise returns false.
|
||||
*/
|
||||
@PreDestroy
|
||||
@VisibleForTesting
|
||||
void cleanup() {
|
||||
isPreDestroyInvoked = true;
|
||||
public boolean isAnyRunning() {
|
||||
return !states.isEmpty();
|
||||
}
|
||||
|
||||
// wait existing tasks to complete
|
||||
sharedPool.terminateAndWait();
|
||||
/**
|
||||
* Once called no more workspaces are allowed to start, {@link #startAsync}
|
||||
* will always throw an appropriate exception. All the running workspaces
|
||||
* will continue running, unless stopped directly.
|
||||
*
|
||||
* @return true if this is the caller is the one who refused start,
|
||||
* otherwise if start is being already refused returns false
|
||||
*/
|
||||
public boolean refuseWorkspacesStart() {
|
||||
return isStartRefused.compareAndSet(false, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Terminates workspace runtimes service, so no more workspaces are allowed to start
|
||||
* or to be stopped directly, all the running workspaces are going to be stopped,
|
||||
* all the starting tasks will be eventually interrupted.
|
||||
*
|
||||
* @throws IllegalStateException
|
||||
* if component shutdown is already called
|
||||
*/
|
||||
public void shutdown() throws InterruptedException {
|
||||
if (!isShutdown.compareAndSet(false, true)) {
|
||||
throw new IllegalStateException("Workspace runtimes service shutdown has been already called");
|
||||
}
|
||||
|
||||
List<String> idsToStop;
|
||||
try (@SuppressWarnings("unused") Unlocker u = locks.writeAllLock()) {
|
||||
idsToStop = states.entrySet()
|
||||
.stream()
|
||||
.filter(e -> e.getValue().status != STOPPING)
|
||||
.filter(e -> e.getValue().status != WorkspaceStatus.STOPPING)
|
||||
.map(Map.Entry::getKey)
|
||||
.collect(Collectors.toList());
|
||||
states.clear();
|
||||
}
|
||||
|
||||
// nothing to stop
|
||||
if (idsToStop.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
LOG.info("Shutdown running states, states to shutdown '{}'", idsToStop.size());
|
||||
ExecutorService executor =
|
||||
Executors.newFixedThreadPool(2 * Runtime.getRuntime().availableProcessors(),
|
||||
new ThreadFactoryBuilder().setNameFormat("StopEnvironmentsPool-%d")
|
||||
.setDaemon(false)
|
||||
.build());
|
||||
for (String id : idsToStop) {
|
||||
executor.execute(() -> {
|
||||
try {
|
||||
envEngine.stop(id);
|
||||
} catch (EnvironmentNotRunningException ignored) {
|
||||
// could be stopped during workspace pool shutdown
|
||||
} catch (Exception x) {
|
||||
LOG.error(x.getMessage(), x);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
executor.shutdown();
|
||||
try {
|
||||
if (!executor.awaitTermination(30, TimeUnit.SECONDS)) {
|
||||
executor.shutdownNow();
|
||||
if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
|
||||
LOG.error("Unable terminate machines pool");
|
||||
}
|
||||
if (!idsToStop.isEmpty()) {
|
||||
LOG.info("Shutdown running environments, environments to stop: '{}'", idsToStop.size());
|
||||
ExecutorService executor =
|
||||
Executors.newFixedThreadPool(2 * Runtime.getRuntime().availableProcessors(),
|
||||
new ThreadFactoryBuilder().setNameFormat("StopEnvironmentsPool-%d")
|
||||
.setDaemon(false)
|
||||
.build());
|
||||
for (String id : idsToStop) {
|
||||
executor.execute(() -> {
|
||||
try {
|
||||
envEngine.stop(id);
|
||||
} catch (EnvironmentNotRunningException ignored) {
|
||||
// might be already stopped
|
||||
} catch (Exception x) {
|
||||
LOG.error(x.getMessage(), x);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
executor.shutdown();
|
||||
try {
|
||||
if (!executor.awaitTermination(30, TimeUnit.SECONDS)) {
|
||||
executor.shutdownNow();
|
||||
if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
|
||||
LOG.error("Unable to stop runtimes termination pool");
|
||||
}
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
executor.shutdownNow();
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
executor.shutdownNow();
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
private void ensurePreDestroyIsNotExecuted() {
|
||||
if (isPreDestroyInvoked) {
|
||||
throw new IllegalStateException("Could not perform operation because application server is stopping");
|
||||
private void checkIsNotTerminated(String operation) throws ServerException {
|
||||
if (isShutdown.get()) {
|
||||
throw new ServerException("Could not " + operation + " because workspaces service is being terminated");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -711,7 +737,7 @@ public class WorkspaceRuntimes {
|
|||
// disallow direct start cancellation, STARTING -> RUNNING
|
||||
WorkspaceStatus prevStatus;
|
||||
try (@SuppressWarnings("unused") Unlocker u = locks.writeLock(workspaceId)) {
|
||||
ensurePreDestroyIsNotExecuted();
|
||||
checkIsNotTerminated("finish workspace start");
|
||||
RuntimeState state = states.get(workspaceId);
|
||||
prevStatus = state.status;
|
||||
if (state.status == WorkspaceStatus.STARTING) {
|
||||
|
|
@ -799,9 +825,9 @@ public class WorkspaceRuntimes {
|
|||
* with {@code from} and if they are equal sets the status to {@code to}.
|
||||
* Returns true if the status of workspace was updated with {@code to} value.
|
||||
*/
|
||||
private boolean compareAndSetStatus(String id, WorkspaceStatus from, WorkspaceStatus to) {
|
||||
private boolean compareAndSetStatus(String id, WorkspaceStatus from, WorkspaceStatus to) throws ServerException {
|
||||
try (@SuppressWarnings("unused") Unlocker u = locks.writeLock(id)) {
|
||||
ensurePreDestroyIsNotExecuted();
|
||||
checkIsNotTerminated(format("change status from '%s' to '%s' for the workspace '%s'", from, to, id));
|
||||
RuntimeState state = states.get(id);
|
||||
if (state != null && state.status == from) {
|
||||
state.status = to;
|
||||
|
|
@ -814,7 +840,6 @@ public class WorkspaceRuntimes {
|
|||
/** Removes state from in-memory storage in write lock. */
|
||||
private void removeState(String workspaceId) {
|
||||
try (@SuppressWarnings("unused") Unlocker u = locks.writeLock(workspaceId)) {
|
||||
ensurePreDestroyIsNotExecuted();
|
||||
states.remove(workspaceId);
|
||||
}
|
||||
}
|
||||
|
|
@ -954,7 +979,7 @@ public class WorkspaceRuntimes {
|
|||
cmpFuture.complete(runtime);
|
||||
return runtime;
|
||||
} catch (IllegalStateException illegalStateEx) {
|
||||
if (isPreDestroyInvoked) {
|
||||
if (isShutdown.get()) {
|
||||
exception = new EnvironmentStartInterruptedException(workspaceId, envName);
|
||||
} else {
|
||||
exception = new ServerException(illegalStateEx.getMessage(), illegalStateEx);
|
||||
|
|
@ -1029,6 +1054,7 @@ public class WorkspaceRuntimes {
|
|||
launchAgents(machine, extMachine.getAgents());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static EnvironmentImpl copyEnv(Workspace workspace, String envName) {
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@
|
|||
*******************************************************************************/
|
||||
package org.eclipse.che.api.workspace.server;
|
||||
|
||||
import com.google.common.base.MoreObjects;
|
||||
import com.google.common.primitives.Ints;
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
import com.google.inject.Inject;
|
||||
|
|
@ -21,10 +20,10 @@ import org.eclipse.che.commons.lang.concurrent.ThreadLocalPropagateContext;
|
|||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
|
|
@ -94,38 +93,37 @@ public class WorkspaceSharedPool {
|
|||
}
|
||||
|
||||
/**
|
||||
* Terminates this pool, may be called multiple times,
|
||||
* waits until pool is terminated or timeout is reached.
|
||||
* Asynchronously runs the given task wrapping it with {@link ThreadLocalPropagateContext#wrap(Runnable)}
|
||||
*
|
||||
* <p>Note that the method is not designed to be used from
|
||||
* different threads, but the other components may use it in their
|
||||
* post construct methods to ensure that all the tasks finished their execution.
|
||||
*
|
||||
* @return true if executor successfully terminated and false if not
|
||||
* terminated(either await termination timeout is reached or thread was interrupted)
|
||||
* @param runnable
|
||||
* task to run
|
||||
* @return completable future bounded to the task
|
||||
*/
|
||||
@PostConstruct
|
||||
public boolean terminateAndWait() {
|
||||
if (executor.isShutdown()) {
|
||||
return true;
|
||||
}
|
||||
Logger logger = LoggerFactory.getLogger(getClass());
|
||||
executor.shutdown();
|
||||
try {
|
||||
logger.info("Shutdown workspace threads pool, wait 30s to stop normally");
|
||||
if (!executor.awaitTermination(30, TimeUnit.SECONDS)) {
|
||||
executor.shutdownNow();
|
||||
logger.info("Interrupt workspace threads pool, wait 60s to stop");
|
||||
if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
|
||||
logger.error("Couldn't terminate workspace threads pool");
|
||||
return false;
|
||||
public CompletableFuture<Void> runAsync(Runnable runnable) {
|
||||
return CompletableFuture.runAsync(ThreadLocalPropagateContext.wrap(runnable), executor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Terminates this pool if it's not terminated yet.
|
||||
*/
|
||||
void shutdown() {
|
||||
if (!executor.isShutdown()) {
|
||||
Logger logger = LoggerFactory.getLogger(getClass());
|
||||
executor.shutdown();
|
||||
try {
|
||||
logger.info("Shutdown workspace threads pool, wait 30s to stop normally");
|
||||
if (!executor.awaitTermination(30, TimeUnit.SECONDS)) {
|
||||
executor.shutdownNow();
|
||||
logger.info("Interrupt workspace threads pool, wait 60s to stop");
|
||||
if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
|
||||
logger.error("Couldn't shutdown workspace threads pool");
|
||||
}
|
||||
}
|
||||
} catch (InterruptedException x) {
|
||||
executor.shutdownNow();
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
} catch (InterruptedException x) {
|
||||
executor.shutdownNow();
|
||||
Thread.currentThread().interrupt();
|
||||
return false;
|
||||
logger.info("Workspace threads pool is terminated");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,12 +43,14 @@ import org.mockito.ArgumentCaptor;
|
|||
import org.mockito.Captor;
|
||||
import org.mockito.InOrder;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.mockito.testng.MockitoTestNGListener;
|
||||
import org.testng.annotations.BeforeMethod;
|
||||
import org.testng.annotations.Listeners;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
|
@ -71,6 +73,7 @@ import static org.mockito.Matchers.anyBoolean;
|
|||
import static org.mockito.Matchers.anyObject;
|
||||
import static org.mockito.Matchers.anyString;
|
||||
import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.Mockito.atLeastOnce;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.doThrow;
|
||||
import static org.mockito.Mockito.inOrder;
|
||||
|
|
@ -117,6 +120,7 @@ public class WorkspaceManagerTest {
|
|||
|
||||
@BeforeMethod
|
||||
public void setUp() throws Exception {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
workspaceManager = new WorkspaceManager(workspaceDao,
|
||||
runtimes,
|
||||
eventService,
|
||||
|
|
@ -463,7 +467,7 @@ public class WorkspaceManagerTest {
|
|||
workspaceManager.stopWorkspace(workspace.getId());
|
||||
|
||||
// then
|
||||
captureAsyncTaskAndExecuteSynchronously();
|
||||
captureRunAsyncCallsAndRunSynchronously();
|
||||
|
||||
verify(runtimes).stop(workspace.getId());
|
||||
|
||||
|
|
@ -479,7 +483,7 @@ public class WorkspaceManagerTest {
|
|||
|
||||
workspaceManager.stopWorkspace(workspace.getId(), true);
|
||||
|
||||
captureAsyncTaskAndExecuteSynchronously();
|
||||
captureRunAsyncCallsAndRunSynchronously();
|
||||
verify(runtimes).snapshot(workspace.getId());
|
||||
}
|
||||
|
||||
|
|
@ -501,7 +505,7 @@ public class WorkspaceManagerTest {
|
|||
|
||||
workspaceManager.stopWorkspace(workspace.getId(), true);
|
||||
|
||||
captureAsyncTaskAndExecuteSynchronously();
|
||||
captureRunAsyncCallsAndRunSynchronously();
|
||||
verify(runtimes).stop(any());
|
||||
}
|
||||
|
||||
|
|
@ -513,7 +517,7 @@ public class WorkspaceManagerTest {
|
|||
|
||||
workspaceManager.stopWorkspace(workspace.getId());
|
||||
|
||||
captureAsyncTaskAndExecuteSynchronously();
|
||||
captureRunAsyncCallsAndRunSynchronously();
|
||||
verify(workspaceDao).remove(workspace.getId());
|
||||
}
|
||||
|
||||
|
|
@ -526,7 +530,7 @@ public class WorkspaceManagerTest {
|
|||
|
||||
workspaceManager.stopWorkspace(workspace.getId());
|
||||
|
||||
captureAsyncTaskAndExecuteSynchronously();
|
||||
captureRunAsyncCallsAndRunSynchronously();
|
||||
verify(workspaceDao).remove(workspace.getId());
|
||||
}
|
||||
|
||||
|
|
@ -562,7 +566,7 @@ public class WorkspaceManagerTest {
|
|||
|
||||
workspaceManager.stopWorkspace(workspace.getId());
|
||||
|
||||
captureAsyncTaskAndExecuteSynchronously();
|
||||
captureRunAsyncCallsAndRunSynchronously();
|
||||
verify(runtimes, never()).snapshot(workspace.getId());
|
||||
verify(runtimes).stop(workspace.getId());
|
||||
}
|
||||
|
|
@ -582,7 +586,7 @@ public class WorkspaceManagerTest {
|
|||
|
||||
workspaceManager.stopWorkspace(workspace.getId());
|
||||
|
||||
captureAsyncTaskAndExecuteSynchronously();
|
||||
captureRunAsyncCallsAndRunSynchronously();
|
||||
verify(runtimes, never()).snapshot(workspace.getId());
|
||||
verify(runtimes).stop(workspace.getId());
|
||||
}
|
||||
|
|
@ -612,7 +616,7 @@ public class WorkspaceManagerTest {
|
|||
workspaceManager.stopWorkspace(workspace.getId());
|
||||
|
||||
// then
|
||||
captureAsyncTaskAndExecuteSynchronously();
|
||||
captureRunAsyncCallsAndRunSynchronously();
|
||||
verify(runtimes).snapshot(workspace.getId());
|
||||
verify(runtimes).stop(workspace.getId());
|
||||
}
|
||||
|
|
@ -678,7 +682,7 @@ public class WorkspaceManagerTest {
|
|||
workspaceManager.removeSnapshots(testWsId);
|
||||
|
||||
// then
|
||||
captureAsyncTaskAndExecuteSynchronously();
|
||||
captureExecuteCallsAndRunSynchronously();
|
||||
verify(runtimes).removeBinaries(asList(snapshot1, snapshot2));
|
||||
InOrder snapshotDaoInOrder = inOrder(snapshotDao);
|
||||
snapshotDaoInOrder.verify(snapshotDao).removeSnapshot(snapshot1.getId());
|
||||
|
|
@ -713,7 +717,7 @@ public class WorkspaceManagerTest {
|
|||
workspaceManager.removeSnapshots(testWsId);
|
||||
|
||||
// then
|
||||
captureAsyncTaskAndExecuteSynchronously();
|
||||
captureExecuteCallsAndRunSynchronously();
|
||||
verify(runtimes).removeBinaries(singletonList(snapshot2));
|
||||
verify(snapshotDao).removeSnapshot(snapshot1.getId());
|
||||
verify(snapshotDao).removeSnapshot(snapshot2.getId());
|
||||
|
|
@ -732,7 +736,7 @@ public class WorkspaceManagerTest {
|
|||
workspaceManager.startMachine(machineConfig, workspace.getId());
|
||||
|
||||
// then
|
||||
captureAsyncTaskAndExecuteSynchronously();
|
||||
captureExecuteCallsAndRunSynchronously();
|
||||
verify(runtimes).startMachine(workspace.getId(), machineConfig);
|
||||
}
|
||||
|
||||
|
|
@ -824,7 +828,7 @@ public class WorkspaceManagerTest {
|
|||
|
||||
workspaceManager.stopWorkspace(workspace.getId(), false);
|
||||
|
||||
captureAsyncTaskAndExecuteSynchronously();
|
||||
captureRunAsyncCallsAndRunSynchronously();
|
||||
verify(runtimes, never()).snapshot(workspace.getId());
|
||||
}
|
||||
|
||||
|
|
@ -836,7 +840,7 @@ public class WorkspaceManagerTest {
|
|||
|
||||
workspaceManager.stopWorkspace(workspace.getId(), null);
|
||||
|
||||
captureAsyncTaskAndExecuteSynchronously();
|
||||
captureRunAsyncCallsAndRunSynchronously();
|
||||
verify(runtimes).snapshot(workspace.getId());
|
||||
}
|
||||
|
||||
|
|
@ -848,13 +852,48 @@ public class WorkspaceManagerTest {
|
|||
|
||||
workspaceManager.stopWorkspace(workspace.getId(), false);
|
||||
|
||||
captureAsyncTaskAndExecuteSynchronously();
|
||||
captureRunAsyncCallsAndRunSynchronously();
|
||||
verify(runtimes, never()).snapshot(workspace.getId());
|
||||
}
|
||||
|
||||
private void captureAsyncTaskAndExecuteSynchronously() {
|
||||
verify(sharedPool).execute(taskCaptor.capture());
|
||||
taskCaptor.getValue().run();
|
||||
@Test
|
||||
public void stopsRunningWorkspacesOnShutdown() throws Exception {
|
||||
when(runtimes.refuseWorkspacesStart()).thenReturn(true);
|
||||
|
||||
WorkspaceImpl stopped = createAndMockWorkspace();
|
||||
mockRuntime(stopped, STOPPED);
|
||||
|
||||
WorkspaceImpl starting = createAndMockWorkspace();
|
||||
mockRuntime(starting, STARTING);
|
||||
|
||||
WorkspaceImpl running = createAndMockWorkspace();
|
||||
mockRuntime(running, RUNNING);
|
||||
|
||||
when(runtimes.getRuntimesIds()).thenReturn(new HashSet<>(asList(running.getId(), starting.getId())));
|
||||
|
||||
// action
|
||||
workspaceManager.shutdown();
|
||||
|
||||
captureRunAsyncCallsAndRunSynchronously();
|
||||
verify(runtimes).stop(running.getId());
|
||||
verify(runtimes).stop(starting.getId());
|
||||
verify(runtimes, never()).stop(stopped.getId());
|
||||
verify(runtimes).shutdown();
|
||||
verify(sharedPool).shutdown();
|
||||
}
|
||||
|
||||
private void captureRunAsyncCallsAndRunSynchronously() {
|
||||
verify(sharedPool, atLeastOnce()).runAsync(taskCaptor.capture());
|
||||
for (Runnable runnable : taskCaptor.getAllValues()) {
|
||||
runnable.run();
|
||||
}
|
||||
}
|
||||
|
||||
private void captureExecuteCallsAndRunSynchronously() {
|
||||
verify(sharedPool, atLeastOnce()).execute(taskCaptor.capture());
|
||||
for (Runnable runnable : taskCaptor.getAllValues()) {
|
||||
runnable.run();
|
||||
}
|
||||
}
|
||||
|
||||
private WorkspaceRuntimeImpl mockRuntime(WorkspaceImpl workspace, WorkspaceStatus status) {
|
||||
|
|
@ -870,6 +909,7 @@ public class WorkspaceManagerTest {
|
|||
workspace.setRuntime(runtime);
|
||||
return null;
|
||||
}).when(runtimes).injectRuntime(workspace);
|
||||
when(runtimes.isAnyRunning()).thenReturn(true);
|
||||
return runtime;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -572,15 +572,22 @@ public class WorkspaceRuntimesTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void cleanup() throws Exception {
|
||||
public void shutdown() throws Exception {
|
||||
setRuntime("workspace", WorkspaceStatus.RUNNING, "env-name");
|
||||
|
||||
runtimes.cleanup();
|
||||
runtimes.shutdown();
|
||||
|
||||
assertFalse(runtimes.hasRuntime("workspace"));
|
||||
verify(envEngine).stop("workspace");
|
||||
}
|
||||
|
||||
@Test(expectedExceptions = IllegalStateException.class,
|
||||
expectedExceptionsMessageRegExp = "Workspace runtimes service shutdown has been already called")
|
||||
public void throwsExceptionWhenShutdownCalledTwice() throws Exception {
|
||||
runtimes.shutdown();
|
||||
runtimes.shutdown();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void startedRuntimeAndReturnedFromGetMethodAreTheSame() throws Exception {
|
||||
WorkspaceImpl workspace = newWorkspace("workspace", "env-name");
|
||||
|
|
@ -795,12 +802,32 @@ public class WorkspaceRuntimesTest {
|
|||
"workspace4"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isAnyRunningReturnsFalseIfThereIsNoSingleRuntime() {
|
||||
assertFalse(runtimes.isAnyRunning());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isAnyRunningReturnsTrueIfThereIsAtLeastOneRunningWorkspace() {
|
||||
setRuntime("workspace1", WorkspaceStatus.STARTING);
|
||||
|
||||
assertTrue(runtimes.isAnyRunning());
|
||||
}
|
||||
|
||||
@Test(expectedExceptions = ConflictException.class,
|
||||
expectedExceptionsMessageRegExp = "Start of the workspace 'test-workspace' is rejected by the system, " +
|
||||
"no more workspaces are allowed to start")
|
||||
public void doesNotAllowToStartWorkspaceIfStartIsRefused() throws Exception {
|
||||
runtimes.refuseWorkspacesStart();
|
||||
|
||||
runtimes.startAsync(newWorkspace("workspace1", "env-name"), "env-name", false);
|
||||
}
|
||||
|
||||
private void captureAsyncTaskAndExecuteSynchronously() throws Exception {
|
||||
verify(sharedPool).submit(taskCaptor.capture());
|
||||
taskCaptor.getValue().call();
|
||||
}
|
||||
|
||||
|
||||
private void captureAndVerifyRuntimeStateAfterInterruption(Workspace workspace,
|
||||
CompletableFuture<WorkspaceRuntimeImpl> cmpFuture) throws Exception {
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -41,5 +41,7 @@
|
|||
<module>wsmaster-local</module>
|
||||
<module>che-core-sql-schema</module>
|
||||
<module>integration-tests</module>
|
||||
<module>che-core-api-system</module>
|
||||
<module>che-core-api-system-shared</module>
|
||||
</modules>
|
||||
</project>
|
||||
|
|
|
|||
Loading…
Reference in New Issue