Skip to content

Commit dc36104

Browse files
committed
Feature: Safely shutdown cloudstack
1 parent 434f15a commit dc36104

26 files changed

Lines changed: 797 additions & 177 deletions

File tree

api/src/main/java/org/apache/cloudstack/api/ApiConstants.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -909,6 +909,10 @@ public class ApiConstants {
909909
public static final String LOGOUT = "logout";
910910
public static final String LIST_IDPS = "listIdps";
911911

912+
public static final String READY_FOR_SHUTDOWN = "readyforshutdown";
913+
public static final String SHUTDOWN_TRIGGERED = "shutdowntriggered";
914+
public static final String PENDING_JOBS_COUNT = "pendingjobscount";
915+
912916
public enum BootType {
913917
UEFI, BIOS;
914918

client/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -513,6 +513,11 @@
513513
<artifactId>cloud-plugin-integrations-kubernetes-service</artifactId>
514514
<version>${project.version}</version>
515515
</dependency>
516+
<dependency>
517+
<groupId>org.apache.cloudstack</groupId>
518+
<artifactId>cloud-plugin-shutdown</artifactId>
519+
<version>${project.version}</version>
520+
</dependency>
516521
</dependencies>
517522
<build>
518523
<plugins>

core/src/main/resources/META-INF/cloudstack/core/spring-core-registry-core-context.xml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,7 @@
255255
<bean class="org.apache.cloudstack.spring.lifecycle.registry.DumpRegistry" >
256256
<property name="registries" value="#{registryRegistry.registered}" />
257257
</bean>
258-
258+
259259
<bean id="registryRegistry"
260260
class="org.apache.cloudstack.spring.lifecycle.registry.ExtensionRegistry">
261261
</bean>
@@ -269,11 +269,11 @@
269269
class="org.apache.cloudstack.spring.lifecycle.registry.ExtensionRegistry">
270270
<property name="excludeKey" value="api.checkers.acl.exclude" />
271271
</bean>
272-
272+
273273
<bean id="querySelectorsRegistry"
274274
class="org.apache.cloudstack.spring.lifecycle.registry.ExtensionRegistry">
275275
<property name="excludeKey" value="query.selectors.exclude" />
276-
</bean>
276+
</bean>
277277

278278
<bean id="apiCommandsRegistry"
279279
class="org.apache.cloudstack.spring.lifecycle.registry.ExtensionRegistry">
@@ -284,7 +284,7 @@
284284
class="org.apache.cloudstack.spring.lifecycle.registry.ExtensionRegistry">
285285
<property name="excludeKey" value="hypervisor.gurus.exclude" />
286286
</bean>
287-
287+
288288
<bean id="vpcProvidersRegistry"
289289
class="org.apache.cloudstack.spring.lifecycle.registry.ExtensionRegistry">
290290
<property name="excludeKey" value="vpc.providers.exclude" />

framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/AsyncJobManager.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,4 +133,13 @@ void joinJob(long jobId, long joinJobId, String wakeupHandler, String wakupDispa
133133
List<AsyncJobVO> findFailureAsyncJobs(String... cmds);
134134

135135
long countPendingJobs(String havingInfo, String... cmds);
136+
137+
long countPendingNonPseudoJobs();
138+
139+
boolean isAllowAsyncJobs();
140+
141+
void disableAllowAsyncJobs();
142+
143+
void enableAllowAsyncJobs();
144+
136145
}

framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/dao/AsyncJobDao.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,5 +45,7 @@ public interface AsyncJobDao extends GenericDao<AsyncJobVO, Long> {
4545

4646
List<AsyncJobVO> getFailureJobsSinceLastMsStart(long msId, String... cmds);
4747

48+
long countPendingNonPseudoJobs();
49+
4850
long countPendingJobs(String havingInfo, String... cmds);
4951
}

framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/dao/AsyncJobDaoImpl.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ public class AsyncJobDaoImpl extends GenericDaoBase<AsyncJobVO, Long> implements
4848
private final SearchBuilder<AsyncJobVO> expiringCompletedAsyncJobSearch;
4949
private final SearchBuilder<AsyncJobVO> failureMsidAsyncJobSearch;
5050
private final GenericSearchBuilder<AsyncJobVO, Long> asyncJobTypeSearch;
51+
private final GenericSearchBuilder<AsyncJobVO, Long> pendingNonPseudoAsyncJobsSearch;
5152

5253
public AsyncJobDaoImpl() {
5354
pendingAsyncJobSearch = createSearchBuilder();
@@ -103,6 +104,10 @@ public AsyncJobDaoImpl() {
103104
asyncJobTypeSearch.and("status", asyncJobTypeSearch.entity().getStatus(), SearchCriteria.Op.EQ);
104105
asyncJobTypeSearch.done();
105106

107+
pendingNonPseudoAsyncJobsSearch = createSearchBuilder(Long.class);
108+
pendingNonPseudoAsyncJobsSearch.select(null, SearchCriteria.Func.COUNT, pendingNonPseudoAsyncJobsSearch.entity().getId());
109+
pendingNonPseudoAsyncJobsSearch.and("instanceTypeNEQ", pendingNonPseudoAsyncJobsSearch.entity().getInstanceType(), SearchCriteria.Op.NEQ);
110+
pendingNonPseudoAsyncJobsSearch.and("jobStatusEQ", pendingNonPseudoAsyncJobsSearch.entity().getStatus(), SearchCriteria.Op.EQ);
106111
}
107112

108113
@Override
@@ -237,6 +242,15 @@ public List<AsyncJobVO> getFailureJobsSinceLastMsStart(long msId, String... cmds
237242
return listBy(sc);
238243
}
239244

245+
@Override
246+
public long countPendingNonPseudoJobs() {
247+
SearchCriteria<Long> sc = pendingNonPseudoAsyncJobsSearch.create();
248+
sc.setParameters("instanceTypeNEQ", AsyncJobVO.PSEUDO_JOB_INSTANCE_TYPE);
249+
sc.setParameters("jobStatusEQ", JobInfo.Status.IN_PROGRESS);
250+
List<Long> results = customSearch(sc, null);
251+
return results.get(0);
252+
}
253+
240254
@Override
241255
public long countPendingJobs(String havingInfo, String... cmds) {
242256
SearchCriteria<Long> sc = asyncJobTypeSearch.create();

framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/impl/AsyncJobManagerImpl.java

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,8 @@ public class AsyncJobManagerImpl extends ManagerBase implements AsyncJobManager,
116116
private static final int HEARTBEAT_INTERVAL = 2000;
117117
private static final int GC_INTERVAL = 10000; // 10 seconds
118118

119+
private boolean allowAsyncJobs = true ;
120+
119121
@Inject
120122
private SyncQueueItemDao _queueItemDao;
121123
@Inject
@@ -192,6 +194,21 @@ public AsyncJob getPseudoJob(long accountId, long userId) {
192194
return job;
193195
}
194196

197+
@Override
198+
public boolean isAllowAsyncJobs() {
199+
return allowAsyncJobs;
200+
}
201+
202+
@Override
203+
public void disableAllowAsyncJobs() {
204+
this.allowAsyncJobs = false;
205+
}
206+
207+
@Override
208+
public void enableAllowAsyncJobs() {
209+
this.allowAsyncJobs = true;
210+
}
211+
195212
@Override
196213
public long submitAsyncJob(AsyncJob job) {
197214
return submitAsyncJob(job, false);
@@ -200,6 +217,9 @@ public long submitAsyncJob(AsyncJob job) {
200217
@SuppressWarnings("unchecked")
201218
@DB
202219
public long submitAsyncJob(AsyncJob job, boolean scheduleJobExecutionInContext) {
220+
if (!allowAsyncJobs) {
221+
throw new CloudRuntimeException("A shutdown has been triggered. Can not accept new jobs");
222+
}
203223
@SuppressWarnings("rawtypes")
204224
GenericDao dao = GenericDaoBase.getDao(job.getClass());
205225
job.setInitMsid(getMsid());
@@ -218,6 +238,10 @@ public long submitAsyncJob(AsyncJob job, boolean scheduleJobExecutionInContext)
218238
@Override
219239
@DB
220240
public long submitAsyncJob(final AsyncJob job, final String syncObjType, final long syncObjId) {
241+
if (!allowAsyncJobs) {
242+
throw new CloudRuntimeException("A shutdown has been triggered. Can not accept new jobs");
243+
}
244+
221245
try {
222246
@SuppressWarnings("rawtypes")
223247
final GenericDao dao = GenericDaoBase.getDao(job.getClass());
@@ -1171,4 +1195,9 @@ public List<AsyncJobVO> findFailureAsyncJobs(String... cmds) {
11711195
public long countPendingJobs(String havingInfo, String... cmds) {
11721196
return _jobDao.countPendingJobs(havingInfo, cmds);
11731197
}
1198+
1199+
@Override
1200+
public long countPendingNonPseudoJobs() {
1201+
return _jobDao.countPendingNonPseudoJobs();
1202+
}
11741203
}

plugins/pom.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,8 @@
111111
<module>outofbandmanagement-drivers/nested-cloudstack</module>
112112
<module>outofbandmanagement-drivers/redfish</module>
113113

114+
<module>shutdown</module>
115+
114116
<module>storage/image/default</module>
115117
<module>storage/image/s3</module>
116118
<module>storage/image/sample</module>

plugins/shutdown/pom.xml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<project xmlns="http://maven.apache.org/POM/4.0.0"
2+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
4+
<modelVersion>4.0.0</modelVersion>
5+
<artifactId>cloud-plugin-shutdown</artifactId>
6+
<name>Apache CloudStack Plugin - Safe Shutdown</name>
7+
<parent>
8+
<groupId>org.apache.cloudstack</groupId>
9+
<artifactId>cloudstack-plugins</artifactId>
10+
<version>4.18.0.0-SNAPSHOT</version>
11+
<relativePath>../pom.xml</relativePath>
12+
</parent>
13+
<dependencies>
14+
<dependency>
15+
<groupId>org.apache.cloudstack</groupId>
16+
<artifactId>cloud-api</artifactId>
17+
<version>${project.version}</version>
18+
</dependency>
19+
<dependency>
20+
<groupId>org.apache.cloudstack</groupId>
21+
<artifactId>cloud-utils</artifactId>
22+
<version>${project.version}</version>
23+
</dependency>
24+
</dependencies>
25+
</project>
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package org.apache.cloudstack.api.command;
2+
3+
import javax.inject.Inject;
4+
5+
import org.apache.cloudstack.api.APICommand;
6+
import org.apache.cloudstack.api.BaseCmd;
7+
import org.apache.log4j.Logger;
8+
9+
import com.cloud.user.Account;
10+
11+
import org.apache.cloudstack.api.response.ReadyForShutdownResponse;
12+
import org.apache.cloudstack.shutdown.ShutdownManager;
13+
import org.apache.cloudstack.acl.RoleType;
14+
15+
@APICommand(name = CancelShutdownCmd.APINAME,
16+
description = "Cancels a triggered shutdown",
17+
responseObject = ReadyForShutdownResponse.class,
18+
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false,
19+
authorized = {RoleType.Admin})
20+
21+
public class CancelShutdownCmd extends BaseCmd {
22+
23+
public static final Logger LOG = Logger.getLogger(CancelShutdownCmd.class);
24+
public static final String APINAME = "cancelShutdown";
25+
26+
@Inject
27+
private ShutdownManager shutdownManager;
28+
29+
@Override
30+
public String getCommandName() {
31+
return APINAME.toLowerCase() + BaseCmd.RESPONSE_SUFFIX;
32+
}
33+
34+
@Override
35+
public long getEntityOwnerId() {
36+
return Account.ACCOUNT_ID_SYSTEM;
37+
}
38+
39+
@Override
40+
public void execute() {
41+
// logic to handle API request
42+
43+
final ReadyForShutdownResponse response = shutdownManager.cancelShutdown();
44+
response.setResponseName(getCommandName());
45+
response.setObjectName("cancelshutdown");
46+
setResponseObject(response);
47+
}
48+
49+
}

0 commit comments

Comments
 (0)