You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cloudstack.apache.org by ro...@apache.org on 2018/07/25 09:18:06 UTC
[cloudstack] branch master updated: asyncjobs: add endtime to async
jobs (#2739)
This is an automated email from the ASF dual-hosted git repository.
rohit pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/cloudstack.git
The following commit(s) were added to refs/heads/master by this push:
new 542d4da asyncjobs: add endtime to async jobs (#2739)
542d4da is described below
commit 542d4da16c18e9c2c49730e6507d1dcf021f1724
Author: ernjvr <er...@gmail.com>
AuthorDate: Wed Jul 25 11:18:01 2018 +0200
asyncjobs: add endtime to async jobs (#2739)
There is currently no functional mechanism that captures or persists the end time of when an asynchronous job has finished. As a result, users are not able to do any reporting about the duration of various asynchronous jobs in Cloudstack.
Link to FS:
https://cwiki.apache.org/confluence/display/CLOUDSTACK/Add+End+Time+To+Asynchronous+Jobs
---
.travis.yml | 1 +
.../org/apache/cloudstack/api/ApiConstants.java | 1 +
.../cloudstack/api/response/AsyncJobResponse.java | 8 ++
.../java/org/apache/cloudstack/jobs/JobInfo.java | 2 +
.../resources/META-INF/db/schema-41110to41200.sql | 4 +-
.../cloudstack/framework/jobs/dao/AsyncJobDao.java | 1 +
.../framework/jobs/dao/AsyncJobDaoImpl.java | 11 +-
.../framework/jobs/impl/AsyncJobManagerImpl.java | 29 +++--
.../cloudstack/framework/jobs/impl/AsyncJobVO.java | 10 ++
.../main/java/com/cloud/api/ApiResponseHelper.java | 10 +-
.../cloud/api/query/dao/AsyncJobJoinDaoImpl.java | 11 +-
.../com/cloud/storage/dao/AsyncJobJoinDaoTest.java | 89 ++++++++++++++
test/integration/smoke/test_async_job.py | 135 +++++++++++++++++++++
tools/marvin/setup.py | 3 +-
14 files changed, 286 insertions(+), 29 deletions(-)
diff --git a/.travis.yml b/.travis.yml
index 130e907..e89d4cb 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -42,6 +42,7 @@ env:
- TESTS="smoke/test_accounts
smoke/test_affinity_groups
smoke/test_affinity_groups_projects
+ smoke/test_async_job
smoke/test_deploy_vgpu_enabled_vm
smoke/test_deploy_vm_iso
smoke/test_deploy_vm_root_resize
diff --git a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java
index 275a3cc..f03ddc7 100644
--- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java
+++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java
@@ -549,6 +549,7 @@ public class ApiConstants {
public static final String IPSEC_PSK = "ipsecpsk";
public static final String GUEST_IP = "guestip";
public static final String REMOVED = "removed";
+ public static final String COMPLETED = "completed";
public static final String IKE_POLICY = "ikepolicy";
public static final String ESP_POLICY = "esppolicy";
public static final String IKE_LIFETIME = "ikelifetime";
diff --git a/api/src/main/java/org/apache/cloudstack/api/response/AsyncJobResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/AsyncJobResponse.java
index 70bbeee..eecd6be 100644
--- a/api/src/main/java/org/apache/cloudstack/api/response/AsyncJobResponse.java
+++ b/api/src/main/java/org/apache/cloudstack/api/response/AsyncJobResponse.java
@@ -75,6 +75,10 @@ public class AsyncJobResponse extends BaseResponse {
@Param(description = " the created date of the job")
private Date created;
+ @SerializedName(ApiConstants.COMPLETED)
+ @Param(description = " the completed date of the job")
+ private Date removed;
+
public void setAccountId(String accountId) {
this.accountId = accountId;
}
@@ -119,4 +123,8 @@ public class AsyncJobResponse extends BaseResponse {
public void setCreated(Date created) {
this.created = created;
}
+
+ public void setRemoved(final Date removed) {
+ this.removed = removed;
+ }
}
diff --git a/api/src/main/java/org/apache/cloudstack/jobs/JobInfo.java b/api/src/main/java/org/apache/cloudstack/jobs/JobInfo.java
index c7c9b96..5b63e62 100644
--- a/api/src/main/java/org/apache/cloudstack/jobs/JobInfo.java
+++ b/api/src/main/java/org/apache/cloudstack/jobs/JobInfo.java
@@ -68,6 +68,8 @@ public interface JobInfo extends Identity, InternalIdentity {
Date getCreated();
+ Date getRemoved();
+
Date getLastUpdated();
Date getLastPolled();
diff --git a/engine/schema/src/main/resources/META-INF/db/schema-41110to41200.sql b/engine/schema/src/main/resources/META-INF/db/schema-41110to41200.sql
index d5e6d61..de6865f 100644
--- a/engine/schema/src/main/resources/META-INF/db/schema-41110to41200.sql
+++ b/engine/schema/src/main/resources/META-INF/db/schema-41110to41200.sql
@@ -32,4 +32,6 @@ ALTER TABLE `vlan` CHANGE `description` `ip4_range` varchar(255);
-- We are only adding the permission to the default rules. Any custom rule must be configured by the root admin.
INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 2, 'moveNetworkAclItem', 'ALLOW', 100) ON DUPLICATE KEY UPDATE rule=rule;
INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 3, 'moveNetworkAclItem', 'ALLOW', 302) ON DUPLICATE KEY UPDATE rule=rule;
-INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 4, 'moveNetworkAclItem', 'ALLOW', 260) ON DUPLICATE KEY UPDATE rule=rule;
\ No newline at end of file
+INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 4, 'moveNetworkAclItem', 'ALLOW', 260) ON DUPLICATE KEY UPDATE rule=rule;
+
+UPDATE `cloud`.`async_job` SET `removed` = now() WHERE `removed` IS NULL;
\ No newline at end of file
diff --git a/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/dao/AsyncJobDao.java b/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/dao/AsyncJobDao.java
index 8778bef..b2b685d 100644
--- a/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/dao/AsyncJobDao.java
+++ b/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/dao/AsyncJobDao.java
@@ -24,6 +24,7 @@ import org.apache.cloudstack.framework.jobs.impl.AsyncJobVO;
import com.cloud.utils.db.GenericDao;
public interface AsyncJobDao extends GenericDao<AsyncJobVO, Long> {
+
AsyncJobVO findInstancePendingAsyncJob(String instanceType, long instanceId);
List<AsyncJobVO> findInstancePendingAsyncJobs(String instanceType, Long accountId);
diff --git a/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/dao/AsyncJobDaoImpl.java b/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/dao/AsyncJobDaoImpl.java
index 0ccd4ad..ef992ff 100644
--- a/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/dao/AsyncJobDaoImpl.java
+++ b/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/dao/AsyncJobDaoImpl.java
@@ -21,6 +21,7 @@ import java.sql.SQLException;
import java.util.Date;
import java.util.List;
+import org.apache.cloudstack.api.ApiConstants;
import org.apache.log4j.Logger;
import org.apache.cloudstack.framework.jobs.impl.AsyncJobVO;
@@ -71,7 +72,7 @@ public class AsyncJobDaoImpl extends GenericDaoBase<AsyncJobVO, Long> implements
expiringUnfinishedAsyncJobSearch.done();
expiringCompletedAsyncJobSearch = createSearchBuilder();
- expiringCompletedAsyncJobSearch.and("created", expiringCompletedAsyncJobSearch.entity().getCreated(), SearchCriteria.Op.LTEQ);
+ expiringCompletedAsyncJobSearch.and(ApiConstants.REMOVED, expiringCompletedAsyncJobSearch.entity().getRemoved(), SearchCriteria.Op.LTEQ);
expiringCompletedAsyncJobSearch.and("completeMsId", expiringCompletedAsyncJobSearch.entity().getCompleteMsid(), SearchCriteria.Op.NNULL);
expiringCompletedAsyncJobSearch.and("jobStatus", expiringCompletedAsyncJobSearch.entity().getStatus(), SearchCriteria.Op.NEQ);
expiringCompletedAsyncJobSearch.done();
@@ -168,11 +169,11 @@ public class AsyncJobDaoImpl extends GenericDaoBase<AsyncJobVO, Long> implements
}
@Override
- public List<AsyncJobVO> getExpiredCompletedJobs(Date cutTime, int limit) {
- SearchCriteria<AsyncJobVO> sc = expiringCompletedAsyncJobSearch.create();
- sc.setParameters("created", cutTime);
+ public List<AsyncJobVO> getExpiredCompletedJobs(final Date cutTime, final int limit) {
+ final SearchCriteria<AsyncJobVO> sc = expiringCompletedAsyncJobSearch.create();
+ sc.setParameters(ApiConstants.REMOVED, cutTime);
sc.setParameters("jobStatus", JobInfo.Status.IN_PROGRESS);
- Filter filter = new Filter(AsyncJobVO.class, "created", true, 0L, (long)limit);
+ final Filter filter = new Filter(AsyncJobVO.class, ApiConstants.REMOVED, true, 0L, (long)limit);
return listIncludingRemovedBy(sc, filter);
}
diff --git a/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/impl/AsyncJobManagerImpl.java b/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/impl/AsyncJobManagerImpl.java
index 174f1f3..1845dbf 100644
--- a/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/impl/AsyncJobManagerImpl.java
+++ b/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/impl/AsyncJobManagerImpl.java
@@ -161,7 +161,7 @@ public class AsyncJobManagerImpl extends ManagerBase implements AsyncJobManager,
@Override
public AsyncJobVO getAsyncJob(long jobId) {
- return _jobDao.findById(jobId);
+ return _jobDao.findByIdIncludingRemoved(jobId);
}
@Override
@@ -286,9 +286,9 @@ public class AsyncJobManagerImpl extends ManagerBase implements AsyncJobManager,
if (s_logger.isDebugEnabled()) {
s_logger.debug("Wake up jobs related to job-" + jobId);
}
- List<Long> wakeupList = Transaction.execute(new TransactionCallback<List<Long>>() {
+ final List<Long> wakeupList = Transaction.execute(new TransactionCallback<List<Long>>() {
@Override
- public List<Long> doInTransaction(TransactionStatus status) {
+ public List<Long> doInTransaction(final TransactionStatus status) {
if (s_logger.isDebugEnabled()) {
s_logger.debug("Update db status for job-" + jobId);
}
@@ -302,14 +302,16 @@ public class AsyncJobManagerImpl extends ManagerBase implements AsyncJobManager,
job.setResult(null);
}
- job.setLastUpdated(DateUtil.currentGMTTime());
+ final Date currentGMTTime = DateUtil.currentGMTTime();
+ job.setLastUpdated(currentGMTTime);
+ job.setRemoved(currentGMTTime);
job.setExecutingMsid(null);
_jobDao.update(jobId, job);
if (s_logger.isDebugEnabled()) {
s_logger.debug("Wake up jobs joined with job-" + jobId + " and disjoin all subjobs created from job- " + jobId);
}
- List<Long> wakeupList = wakeupByJoinedJobCompletion(jobId);
+ final List<Long> wakeupList = wakeupByJoinedJobCompletion(jobId);
_joinMapDao.disjoinAllJobs(jobId);
// purge the job sync item from queue
@@ -445,8 +447,8 @@ public class AsyncJobManagerImpl extends ManagerBase implements AsyncJobManager,
}
@Override
- public AsyncJob queryJob(long jobId, boolean updatePollTime) {
- AsyncJobVO job = _jobDao.findById(jobId);
+ public AsyncJob queryJob(final long jobId, final boolean updatePollTime) {
+ final AsyncJobVO job = _jobDao.findByIdIncludingRemoved(jobId);
if (updatePollTime) {
job.setLastPolled(DateUtil.currentGMTTime());
@@ -1025,8 +1027,8 @@ public class AsyncJobManagerImpl extends ManagerBase implements AsyncJobManager,
// purge sync queue item running on this ms node
_queueMgr.cleanupActiveQueueItems(msid, true);
// reset job status for all jobs running on this ms node
- List<AsyncJobVO> jobs = _jobDao.getResetJobs(msid);
- for (AsyncJobVO job : jobs) {
+ final List<AsyncJobVO> jobs = _jobDao.getResetJobs(msid);
+ for (final AsyncJobVO job : jobs) {
if (s_logger.isDebugEnabled()) {
s_logger.debug("Cancel left-over job-" + job.getId());
}
@@ -1034,12 +1036,15 @@ public class AsyncJobManagerImpl extends ManagerBase implements AsyncJobManager,
job.setResultCode(ApiErrorCode.INTERNAL_ERROR.getHttpCode());
job.setResult("job cancelled because of management server restart or shutdown");
job.setCompleteMsid(msid);
+ final Date currentGMTTime = DateUtil.currentGMTTime();
+ job.setLastUpdated(currentGMTTime);
+ job.setRemoved(currentGMTTime);
_jobDao.update(job.getId(), job);
if (s_logger.isDebugEnabled()) {
s_logger.debug("Purge queue item for cancelled job-" + job.getId());
}
_queueMgr.purgeAsyncJobQueueItemId(job.getId());
- if (job.getInstanceType().equals(ApiCommandJobType.Volume.toString())) {
+ if (ApiCommandJobType.Volume.toString().equals(job.getInstanceType())) {
try {
_volumeDetailsDao.removeDetail(job.getInstanceId(), "SNAPSHOT_ID");
@@ -1049,8 +1054,8 @@ public class AsyncJobManagerImpl extends ManagerBase implements AsyncJobManager,
}
}
}
- List<SnapshotDetailsVO> snapshotList = _snapshotDetailsDao.findDetails(AsyncJob.Constants.MS_ID, Long.toString(msid), false);
- for (SnapshotDetailsVO snapshotDetailsVO : snapshotList) {
+ final List<SnapshotDetailsVO> snapshotList = _snapshotDetailsDao.findDetails(AsyncJob.Constants.MS_ID, Long.toString(msid), false);
+ for (final SnapshotDetailsVO snapshotDetailsVO : snapshotList) {
SnapshotInfo snapshot = snapshotFactory.getSnapshot(snapshotDetailsVO.getResourceId(), DataStoreRole.Primary);
snapshotSrv.processEventOnSnapshotObject(snapshot, Snapshot.Event.OperationFailed);
_snapshotDetailsDao.removeDetail(snapshotDetailsVO.getResourceId(), AsyncJob.Constants.MS_ID);
diff --git a/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/impl/AsyncJobVO.java b/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/impl/AsyncJobVO.java
index 0ca9ed5..9d30c2c 100644
--- a/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/impl/AsyncJobVO.java
+++ b/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/impl/AsyncJobVO.java
@@ -373,6 +373,15 @@ public class AsyncJobVO implements AsyncJob, JobInfo {
}
@Override
+ public Date getRemoved() {
+ return removed;
+ }
+
+ public void setRemoved(final Date removed) {
+ this.removed = removed;
+ }
+
+ @Override
public String toString() {
StringBuffer sb = new StringBuffer();
sb.append("AsyncJobVO {id:").append(getId());
@@ -392,6 +401,7 @@ public class AsyncJobVO implements AsyncJob, JobInfo {
sb.append(", lastUpdated: ").append(getLastUpdated());
sb.append(", lastPolled: ").append(getLastPolled());
sb.append(", created: ").append(getCreated());
+ sb.append(", removed: ").append(getRemoved());
sb.append("}");
return sb.toString();
}
diff --git a/server/src/main/java/com/cloud/api/ApiResponseHelper.java b/server/src/main/java/com/cloud/api/ApiResponseHelper.java
index 4d7de2a..4177223 100644
--- a/server/src/main/java/com/cloud/api/ApiResponseHelper.java
+++ b/server/src/main/java/com/cloud/api/ApiResponseHelper.java
@@ -1808,16 +1808,16 @@ public class ApiResponseHelper implements ResponseGenerator {
}
@Override
- public AsyncJobResponse queryJobResult(QueryAsyncJobResultCmd cmd) {
- Account caller = CallContext.current().getCallingAccount();
+ public AsyncJobResponse queryJobResult(final QueryAsyncJobResultCmd cmd) {
+ final Account caller = CallContext.current().getCallingAccount();
- AsyncJob job = _entityMgr.findById(AsyncJob.class, cmd.getId());
+ final AsyncJob job = _entityMgr.findByIdIncludingRemoved(AsyncJob.class, cmd.getId());
if (job == null) {
throw new InvalidParameterValueException("Unable to find a job by id " + cmd.getId());
}
- User userJobOwner = _accountMgr.getUserIncludingRemoved(job.getUserId());
- Account jobOwner = _accountMgr.getAccount(userJobOwner.getAccountId());
+ final User userJobOwner = _accountMgr.getUserIncludingRemoved(job.getUserId());
+ final Account jobOwner = _accountMgr.getAccount(userJobOwner.getAccountId());
//check permissions
if (_accountMgr.isNormalUser(caller.getId())) {
diff --git a/server/src/main/java/com/cloud/api/query/dao/AsyncJobJoinDaoImpl.java b/server/src/main/java/com/cloud/api/query/dao/AsyncJobJoinDaoImpl.java
index fefc896..bd11015 100644
--- a/server/src/main/java/com/cloud/api/query/dao/AsyncJobJoinDaoImpl.java
+++ b/server/src/main/java/com/cloud/api/query/dao/AsyncJobJoinDaoImpl.java
@@ -50,12 +50,13 @@ public class AsyncJobJoinDaoImpl extends GenericDaoBase<AsyncJobJoinVO, Long> im
}
@Override
- public AsyncJobResponse newAsyncJobResponse(AsyncJobJoinVO job) {
- AsyncJobResponse jobResponse = new AsyncJobResponse();
+ public AsyncJobResponse newAsyncJobResponse(final AsyncJobJoinVO job) {
+ final AsyncJobResponse jobResponse = new AsyncJobResponse();
jobResponse.setAccountId(job.getAccountUuid());
jobResponse.setUserId(job.getUserUuid());
jobResponse.setCmd(job.getCmd());
jobResponse.setCreated(job.getCreated());
+ jobResponse.setRemoved(job.getRemoved());
jobResponse.setJobId(job.getUuid());
jobResponse.setJobStatus(job.getStatus());
jobResponse.setJobProcStatus(job.getProcessStatus());
@@ -68,15 +69,15 @@ public class AsyncJobJoinDaoImpl extends GenericDaoBase<AsyncJobJoinVO, Long> im
}
jobResponse.setJobResultCode(job.getResultCode());
- boolean savedValue = SerializationContext.current().getUuidTranslation();
+ final boolean savedValue = SerializationContext.current().getUuidTranslation();
SerializationContext.current().setUuidTranslation(false);
- Object resultObject = ApiSerializerHelper.fromSerializedString(job.getResult());
+ final Object resultObject = ApiSerializerHelper.fromSerializedString(job.getResult());
jobResponse.setJobResult((ResponseObject)resultObject);
SerializationContext.current().setUuidTranslation(savedValue);
if (resultObject != null) {
- Class<?> clz = resultObject.getClass();
+ final Class<?> clz = resultObject.getClass();
if (clz.isPrimitive() || clz.getSuperclass() == Number.class || clz == String.class || clz == Date.class) {
jobResponse.setJobResultType("text");
} else {
diff --git a/server/src/test/java/com/cloud/storage/dao/AsyncJobJoinDaoTest.java b/server/src/test/java/com/cloud/storage/dao/AsyncJobJoinDaoTest.java
new file mode 100644
index 0000000..ed93698
--- /dev/null
+++ b/server/src/test/java/com/cloud/storage/dao/AsyncJobJoinDaoTest.java
@@ -0,0 +1,89 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package com.cloud.storage.dao;
+
+import com.cloud.api.query.dao.AsyncJobJoinDaoImpl;
+import com.cloud.api.query.vo.AsyncJobJoinVO;
+import org.apache.cloudstack.api.ApiCommandJobType;
+import org.apache.cloudstack.api.response.AsyncJobResponse;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.springframework.test.util.ReflectionTestUtils;
+
+import java.util.Date;
+
+@RunWith(MockitoJUnitRunner.class)
+public class AsyncJobJoinDaoTest {
+
+ @InjectMocks
+ AsyncJobJoinDaoImpl dao;
+
+ @Test
+ public void testNewAsyncJobResponseValidValues() {
+ final AsyncJobJoinVO job = new AsyncJobJoinVO();
+ ReflectionTestUtils.setField(job,"uuid","a2b22932-1b61-4406-8e89-4ae19968e8d3");
+ ReflectionTestUtils.setField(job,"accountUuid","4dea2836-72cc-11e8-b2de-107b4429825a");
+ ReflectionTestUtils.setField(job,"domainUuid","4dea136b-72cc-11e8-b2de-107b4429825a");
+ ReflectionTestUtils.setField(job,"userUuid","4decc724-72cc-11e8-b2de-107b4429825a");
+ ReflectionTestUtils.setField(job,"cmd","org.apache.cloudstack.api.command.admin.vm.StartVMCmdByAdmin");
+ ReflectionTestUtils.setField(job,"status",0);
+ ReflectionTestUtils.setField(job,"resultCode",0);
+ ReflectionTestUtils.setField(job,"result",null);
+ ReflectionTestUtils.setField(job,"created",new Date());
+ ReflectionTestUtils.setField(job,"removed",new Date());
+ ReflectionTestUtils.setField(job,"instanceType",ApiCommandJobType.VirtualMachine);
+ ReflectionTestUtils.setField(job,"instanceId",3L);
+ final AsyncJobResponse response = dao.newAsyncJobResponse(job);
+ Assert.assertEquals(job.getUuid(),response.getJobId());
+ Assert.assertEquals(job.getAccountUuid(), ReflectionTestUtils.getField(response, "accountId"));
+ Assert.assertEquals(job.getUserUuid(), ReflectionTestUtils.getField(response, "userId"));
+ Assert.assertEquals(job.getCmd(), ReflectionTestUtils.getField(response, "cmd"));
+ Assert.assertEquals(job.getStatus(), ReflectionTestUtils.getField(response, "jobStatus"));
+ Assert.assertEquals(job.getStatus(), ReflectionTestUtils.getField(response, "jobProcStatus"));
+ Assert.assertEquals(job.getResultCode(), ReflectionTestUtils.getField(response, "jobResultCode"));
+ Assert.assertEquals(null, ReflectionTestUtils.getField(response, "jobResultType"));
+ Assert.assertEquals(job.getResult(), ReflectionTestUtils.getField(response, "jobResult"));
+ Assert.assertEquals(job.getInstanceType().toString(), ReflectionTestUtils.getField(response, "jobInstanceType"));
+ Assert.assertEquals(job.getInstanceUuid(), ReflectionTestUtils.getField(response, "jobInstanceId"));
+ Assert.assertEquals(job.getCreated(), ReflectionTestUtils.getField(response, "created"));
+ Assert.assertEquals(job.getRemoved(), ReflectionTestUtils.getField(response, "removed"));
+ }
+
+ @Test
+ public void testNewAsyncJobResponseNullValues() {
+ final AsyncJobJoinVO job = new AsyncJobJoinVO();
+ final AsyncJobResponse response = dao.newAsyncJobResponse(job);
+ Assert.assertEquals(job.getUuid(),response.getJobId());
+ Assert.assertEquals(job.getAccountUuid(), ReflectionTestUtils.getField(response, "accountId"));
+ Assert.assertEquals(job.getUserUuid(), ReflectionTestUtils.getField(response, "userId"));
+ Assert.assertEquals(job.getCmd(), ReflectionTestUtils.getField(response, "cmd"));
+ Assert.assertEquals(job.getStatus(), ReflectionTestUtils.getField(response, "jobStatus"));
+ Assert.assertEquals(job.getStatus(), ReflectionTestUtils.getField(response, "jobProcStatus"));
+ Assert.assertEquals(job.getResultCode(), ReflectionTestUtils.getField(response, "jobResultCode"));
+ Assert.assertEquals(null, ReflectionTestUtils.getField(response, "jobResultType"));
+ Assert.assertEquals(job.getResult(), ReflectionTestUtils.getField(response, "jobResult"));
+ Assert.assertEquals(job.getInstanceType(), ReflectionTestUtils.getField(response, "jobInstanceType"));
+ Assert.assertEquals(job.getInstanceUuid(), ReflectionTestUtils.getField(response, "jobInstanceId"));
+ Assert.assertEquals(job.getCreated(), ReflectionTestUtils.getField(response, "created"));
+ Assert.assertEquals(job.getRemoved(), ReflectionTestUtils.getField(response, "removed"));
+ }
+}
diff --git a/test/integration/smoke/test_async_job.py b/test/integration/smoke/test_async_job.py
new file mode 100644
index 0000000..f727dd8
--- /dev/null
+++ b/test/integration/smoke/test_async_job.py
@@ -0,0 +1,135 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+from nose.plugins.attrib import attr
+from marvin.cloudstackTestCase import cloudstackTestCase
+from marvin.lib.utils import cleanup_resources
+from marvin.lib.base import ServiceOffering, DiskOffering, Account, VirtualMachine,\
+ queryAsyncJobResult, PASS
+from marvin.lib.common import get_domain, get_zone, get_test_template
+from pytz import timezone
+
+
+class TestAsyncJob(cloudstackTestCase):
+ """
+ Test queryAsyncJobResult
+ """
+ @classmethod
+ def setUpClass(cls):
+ cls.testClient = super(TestAsyncJob, cls).getClsTestClient()
+ cls.api_client = cls.testClient.getApiClient()
+
+ cls.testdata = cls.testClient.getParsedTestDataConfig()
+ # Get Zone, Domain and templates
+ cls.domain = get_domain(cls.api_client)
+ cls.zone = get_zone(cls.api_client, cls.testClient.getZoneForTests())
+ cls.hypervisor = cls.testClient.getHypervisorInfo()
+
+ cls.template = get_test_template(
+ cls.api_client,
+ cls.zone.id,
+ cls.hypervisor
+ )
+
+ # Create service, disk offerings etc
+ cls.service_offering = ServiceOffering.create(
+ cls.api_client,
+ cls.testdata["service_offering"]
+ )
+
+ cls.disk_offering = DiskOffering.create(
+ cls.api_client,
+ cls.testdata["disk_offering"]
+ )
+
+ cls._cleanup = [
+ cls.service_offering,
+ cls.disk_offering
+ ]
+
+ @classmethod
+ def tearDownClass(cls):
+ try:
+ cleanup_resources(cls.api_client, cls._cleanup)
+ except Exception as exception:
+ raise Exception("Warning: Exception during cleanup : %s" % exception)
+
+ def setUp(self):
+ self.apiclient = self.testClient.getApiClient()
+ self.dbclient = self.testClient.getDbConnection()
+ self.hypervisor = self.testClient.getHypervisorInfo()
+ self.testdata["virtual_machine"]["zoneid"] = self.zone.id
+ self.testdata["virtual_machine"]["template"] = self.template.id
+ self.testdata["iso"]["zoneid"] = self.zone.id
+ self.account = Account.create(
+ self.apiclient,
+ self.testdata["account"],
+ domainid=self.domain.id
+ )
+ self.cleanup = [self.account]
+
+ def tearDown(self):
+ try:
+ self.debug("Cleaning up the resources")
+ cleanup_resources(self.apiclient, self.cleanup)
+ self.debug("Cleanup complete!")
+ except Exception as exception:
+ self.debug("Warning! Exception in tearDown: %s" % exception)
+
+ @attr(tags=["advanced", "eip", "advancedns", "basic", "sg"], required_hardware="false")
+ def test_query_async_job_result(self):
+ """
+ Test queryAsyncJobResult API for expected values
+ """
+ self.debug("Deploying instance in the account: %s" %
+ self.account.name)
+ virtual_machine = VirtualMachine.create(
+ self.apiclient,
+ self.testdata["virtual_machine"],
+ accountid=self.account.name,
+ domainid=self.account.domainid,
+ serviceofferingid=self.service_offering.id,
+ diskofferingid=self.disk_offering.id,
+ hypervisor=self.hypervisor
+ )
+
+ response = virtual_machine.getState(
+ self.apiclient,
+ VirtualMachine.RUNNING)
+ self.assertEqual(response[0], PASS, response[1])
+
+ cmd = queryAsyncJobResult.queryAsyncJobResultCmd()
+ cmd.jobid = virtual_machine.jobid
+ cmd_response = self.apiclient.queryAsyncJobResult(cmd)
+
+ db_result = self.dbclient.execute("select * from async_job where uuid='%s'" %
+ virtual_machine.jobid)
+
+ # verify that 'completed' value from api equals 'removed' db column value
+ completed = cmd_response.completed
+ removed = timezone('UTC').localize(db_result[0][17])
+ removed = removed.strftime("%Y-%m-%dT%H:%M:%S%z")
+ self.assertEqual(completed, removed,
+ "Expected 'completed' timestamp value %s to be equal to "
+ "'removed' db column value %s." % (completed, removed))
+
+ # verify that api job_status value equals db job_status value
+ jobstatus_db = db_result[0][8]
+ jobstatus_api = cmd_response.jobstatus
+ self.assertEqual(jobstatus_api, jobstatus_db,
+ "Expected 'jobstatus' api value %s to be equal to "
+ "'job_status' db column value %s." % (jobstatus_api, jobstatus_db))
diff --git a/tools/marvin/setup.py b/tools/marvin/setup.py
index 23c50e1..864d856 100644
--- a/tools/marvin/setup.py
+++ b/tools/marvin/setup.py
@@ -54,7 +54,8 @@ setup(name="Marvin",
"pyvmomi >= 5.5.0",
"netaddr >= 0.7.14",
"dnspython",
- "ipmisim >= 0.7"
+ "ipmisim >= 0.7",
+ "pytz"
],
extras_require={
"nuagevsp": ["vspk", "PyYAML", "futures", "netaddr", "retries", "jpype1"]