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 <ern...@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"]