Skip to content

Commit 47c5bb8

Browse files
sureshanapartimprokopchuk
authored andcommitted
Support list/query async jobs by resource (#12983)
* Add resource filtering to async job query commands * Fix logical condition in AsyncJobDaoImpl and ResourceIdSupport * resource type case-insensitive validation * fix resource type and id search --------- Co-authored-by: mprokopchuk <mprokopchuk@gmail.com> Co-authored-by: mprokopchuk <mprokopchuk@apple.com>
1 parent 7c7b2ae commit 47c5bb8

File tree

9 files changed

+267
-27
lines changed

9 files changed

+267
-27
lines changed

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,8 +127,8 @@ public String toString() {
127127
}
128128

129129
public static ApiCommandResourceType fromString(String value) {
130-
if (StringUtils.isNotEmpty(value) && EnumUtils.isValidEnum(ApiCommandResourceType.class, value)) {
131-
return valueOf(value);
130+
if (StringUtils.isNotBlank(value) && EnumUtils.isValidEnumIgnoreCase(ApiCommandResourceType.class, value)) {
131+
return EnumUtils.getEnumIgnoreCase(ApiCommandResourceType.class, value);
132132
}
133133
return null;
134134
}

api/src/main/java/org/apache/cloudstack/api/command/user/job/ListAsyncJobsCmd.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.util.Date;
2020

2121
import org.apache.cloudstack.api.APICommand;
22+
import org.apache.cloudstack.api.ApiArgValidator;
2223
import org.apache.cloudstack.api.ApiConstants;
2324
import org.apache.cloudstack.api.BaseListAccountResourcesCmd;
2425
import org.apache.cloudstack.api.Parameter;
@@ -40,6 +41,12 @@ public class ListAsyncJobsCmd extends BaseListAccountResourcesCmd {
4041
@Parameter(name = ApiConstants.MANAGEMENT_SERVER_ID, type = CommandType.UUID, entityType = ManagementServerResponse.class, description = "The id of the management server", since="4.19")
4142
private Long managementServerId;
4243

44+
@Parameter(name = ApiConstants.RESOURCE_ID, validations = {ApiArgValidator.UuidString}, type = CommandType.STRING, description = "the ID of the resource associated with the job", since="4.22.1")
45+
private String resourceId;
46+
47+
@Parameter(name = ApiConstants.RESOURCE_TYPE, type = CommandType.STRING, description = "the type of the resource associated with the job", since="4.22.1")
48+
private String resourceType;
49+
4350
/////////////////////////////////////////////////////
4451
/////////////////// Accessors ///////////////////////
4552
/////////////////////////////////////////////////////
@@ -52,6 +59,14 @@ public Long getManagementServerId() {
5259
return managementServerId;
5360
}
5461

62+
public String getResourceId() {
63+
return resourceId;
64+
}
65+
66+
public String getResourceType() {
67+
return resourceType;
68+
}
69+
5570
/////////////////////////////////////////////////////
5671
/////////////// API Implementation///////////////////
5772
/////////////////////////////////////////////////////

api/src/main/java/org/apache/cloudstack/api/command/user/job/QueryAsyncJobResultCmd.java

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@
1616
// under the License.
1717
package org.apache.cloudstack.api.command.user.job;
1818

19-
2019
import org.apache.cloudstack.api.APICommand;
20+
import org.apache.cloudstack.api.ApiArgValidator;
2121
import org.apache.cloudstack.api.ApiConstants;
2222
import org.apache.cloudstack.api.BaseCmd;
2323
import org.apache.cloudstack.api.Parameter;
@@ -34,9 +34,15 @@ public class QueryAsyncJobResultCmd extends BaseCmd {
3434
//////////////// API parameters /////////////////////
3535
/////////////////////////////////////////////////////
3636

37-
@Parameter(name = ApiConstants.JOB_ID, type = CommandType.UUID, entityType = AsyncJobResponse.class, required = true, description = "The ID of the asynchronous job")
37+
@Parameter(name = ApiConstants.JOB_ID, type = CommandType.UUID, entityType = AsyncJobResponse.class, description = "The ID of the asynchronous job")
3838
private Long id;
3939

40+
@Parameter(name = ApiConstants.RESOURCE_ID, validations = {ApiArgValidator.UuidString}, type = CommandType.STRING, description = "the ID of the resource associated with the job", since="4.22.1")
41+
private String resourceId;
42+
43+
@Parameter(name = ApiConstants.RESOURCE_TYPE, type = CommandType.STRING, description = "the type of the resource associated with the job", since="4.22.1")
44+
private String resourceType;
45+
4046
/////////////////////////////////////////////////////
4147
/////////////////// Accessors ///////////////////////
4248
/////////////////////////////////////////////////////
@@ -45,6 +51,14 @@ public Long getId() {
4551
return id;
4652
}
4753

54+
public String getResourceId() {
55+
return resourceId;
56+
}
57+
58+
public String getResourceType() {
59+
return resourceType;
60+
}
61+
4862
/////////////////////////////////////////////////////
4963
/////////////// API Implementation///////////////////
5064
/////////////////////////////////////////////////////

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,30 @@
2323

2424
import com.cloud.utils.db.GenericDao;
2525

26+
import javax.annotation.Nullable;
27+
2628
public interface AsyncJobDao extends GenericDao<AsyncJobVO, Long> {
2729

2830
AsyncJobVO findInstancePendingAsyncJob(String instanceType, long instanceId);
2931

3032
List<AsyncJobVO> findInstancePendingAsyncJobs(String instanceType, Long accountId);
3133

34+
/**
35+
* Finds async job matching the given parameters.
36+
* Non-null parameters are added to search criteria.
37+
* Returns the most recent job by creation date.
38+
* <p>
39+
* When searching by resourceId and resourceType, only one active job
40+
* is expected per resource, so returning a single result is sufficient.
41+
*
42+
* @param id job ID
43+
* @param resourceId resource ID (instanceId)
44+
* @param resourceType resource type (instanceType)
45+
* @return matching job or null
46+
*/
47+
@Nullable
48+
AsyncJobVO findJob(Long id, Long resourceId, String resourceType);
49+
3250
AsyncJobVO findPseudoJob(long threadId, long msid);
3351

3452
void cleanupPseduoJobs(long msid);

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

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
import java.util.List;
2323

2424
import org.apache.cloudstack.api.ApiConstants;
25+
import org.apache.commons.collections.CollectionUtils;
26+
import org.apache.commons.lang3.StringUtils;
2527

2628
import org.apache.cloudstack.framework.jobs.impl.AsyncJobVO;
2729
import org.apache.cloudstack.jobs.JobInfo;
@@ -45,6 +47,7 @@ public class AsyncJobDaoImpl extends GenericDaoBase<AsyncJobVO, Long> implements
4547
private final SearchBuilder<AsyncJobVO> expiringUnfinishedAsyncJobSearch;
4648
private final SearchBuilder<AsyncJobVO> expiringCompletedAsyncJobSearch;
4749
private final SearchBuilder<AsyncJobVO> failureMsidAsyncJobSearch;
50+
private final SearchBuilder<AsyncJobVO> byIdResourceIdResourceTypeSearch;
4851
private final GenericSearchBuilder<AsyncJobVO, Long> asyncJobTypeSearch;
4952
private final GenericSearchBuilder<AsyncJobVO, Long> pendingNonPseudoAsyncJobsSearch;
5053

@@ -95,6 +98,12 @@ public AsyncJobDaoImpl() {
9598
failureMsidAsyncJobSearch.and("job_cmd", failureMsidAsyncJobSearch.entity().getCmd(), Op.IN);
9699
failureMsidAsyncJobSearch.done();
97100

101+
byIdResourceIdResourceTypeSearch = createSearchBuilder();
102+
byIdResourceIdResourceTypeSearch.and("id", byIdResourceIdResourceTypeSearch.entity().getId(), SearchCriteria.Op.EQ);
103+
byIdResourceIdResourceTypeSearch.and("instanceId", byIdResourceIdResourceTypeSearch.entity().getInstanceId(), SearchCriteria.Op.EQ);
104+
byIdResourceIdResourceTypeSearch.and("instanceType", byIdResourceIdResourceTypeSearch.entity().getInstanceType(), SearchCriteria.Op.EQ);
105+
byIdResourceIdResourceTypeSearch.done();
106+
98107
asyncJobTypeSearch = createSearchBuilder(Long.class);
99108
asyncJobTypeSearch.select(null, SearchCriteria.Func.COUNT, asyncJobTypeSearch.entity().getId());
100109
asyncJobTypeSearch.and("job_info", asyncJobTypeSearch.entity().getCmdInfo(),Op.LIKE);
@@ -140,6 +149,30 @@ public List<AsyncJobVO> findInstancePendingAsyncJobs(String instanceType, Long a
140149
return listBy(sc);
141150
}
142151

152+
@Override
153+
public AsyncJobVO findJob(Long id, Long resourceId, String resourceType) {
154+
SearchCriteria<AsyncJobVO> sc = byIdResourceIdResourceTypeSearch.create();
155+
156+
if (id == null && resourceId == null && StringUtils.isBlank(resourceType)) {
157+
logger.debug("findJob called with all null parameters");
158+
return null;
159+
}
160+
161+
if (id != null) {
162+
sc.setParameters("id", id);
163+
}
164+
if (resourceId != null && StringUtils.isNotBlank(resourceType)) {
165+
sc.setParameters("instanceType", resourceType);
166+
sc.setParameters("instanceId", resourceId);
167+
}
168+
Filter filter = new Filter(AsyncJobVO.class, "created", false, 0L, 1L);
169+
List<AsyncJobVO> result = searchIncludingRemoved(sc, filter, Boolean.FALSE, false);
170+
if (CollectionUtils.isNotEmpty(result)) {
171+
return result.get(0);
172+
}
173+
return null;
174+
}
175+
143176
@Override
144177
public AsyncJobVO findPseudoJob(long threadId, long msid) {
145178
SearchCriteria<AsyncJobVO> sc = pseudoJobSearch.create();

server/src/main/java/com/cloud/api/ApiResponseHelper.java

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,15 @@
3232
import java.util.List;
3333
import java.util.Map;
3434
import java.util.Objects;
35+
import java.util.Optional;
3536
import java.util.Set;
3637
import java.util.TimeZone;
3738
import java.util.function.Consumer;
3839
import java.util.stream.Collectors;
3940

4041
import javax.inject.Inject;
4142

43+
import com.cloud.api.query.ResourceIdSupport;
4244
import com.cloud.bgp.ASNumber;
4345
import com.cloud.bgp.ASNumberRange;
4446
import com.cloud.configuration.ConfigurationService;
@@ -57,6 +59,7 @@
5759
import org.apache.cloudstack.affinity.AffinityGroupResponse;
5860
import org.apache.cloudstack.annotation.AnnotationService;
5961
import org.apache.cloudstack.annotation.dao.AnnotationDao;
62+
import org.apache.cloudstack.api.ApiCommandResourceType;
6063
import org.apache.cloudstack.api.ApiConstants;
6164
import org.apache.cloudstack.api.ApiConstants.DomainDetails;
6265
import org.apache.cloudstack.api.ApiConstants.HostDetails;
@@ -219,6 +222,7 @@
219222
import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo;
220223
import org.apache.cloudstack.framework.jobs.AsyncJob;
221224
import org.apache.cloudstack.framework.jobs.AsyncJobManager;
225+
import org.apache.cloudstack.framework.jobs.dao.AsyncJobDao;
222226
import org.apache.cloudstack.gui.theme.GuiThemeJoin;
223227
import org.apache.cloudstack.management.ManagementServerHost;
224228
import org.apache.cloudstack.network.BgpPeerVO;
@@ -447,7 +451,7 @@
447451

448452
import sun.security.x509.X509CertImpl;
449453

450-
public class ApiResponseHelper implements ResponseGenerator {
454+
public class ApiResponseHelper implements ResponseGenerator, ResourceIdSupport {
451455

452456
protected Logger logger = LogManager.getLogger(ApiResponseHelper.class);
453457
private static final DecimalFormat s_percentFormat = new DecimalFormat("##.##");
@@ -529,6 +533,8 @@ public class ApiResponseHelper implements ResponseGenerator {
529533
RoutedIpv4Manager routedIpv4Manager;
530534
@Inject
531535
ResourceIconManager resourceIconManager;
536+
@Inject
537+
AsyncJobDao asyncJobDao;
532538

533539
public static String getPrettyDomainPath(String path) {
534540
if (path == null) {
@@ -2304,16 +2310,26 @@ public TemplatePermissionsResponse createTemplatePermissionsResponse(ResponseVie
23042310

23052311
@Override
23062312
public AsyncJobResponse queryJobResult(final QueryAsyncJobResultCmd cmd) {
2307-
final Account caller = CallContext.current().getCallingAccount();
2313+
ApiCommandResourceType resourceType = getResourceType(cmd.getResourceType());
2314+
String resourceTypeName = Optional.ofNullable(resourceType).map(ApiCommandResourceType::name).orElse(null);
2315+
2316+
Long resourceId = getResourceId(resourceType, cmd.getResourceId());
2317+
Long jobId = cmd.getId();
2318+
if (jobId == null && resourceId == null) {
2319+
throw new InvalidParameterValueException("Expected parameter job id or parameters resource type and resource id");
2320+
}
23082321

2309-
final AsyncJob job = _entityMgr.findByIdIncludingRemoved(AsyncJob.class, cmd.getId());
2322+
final AsyncJob job = asyncJobDao.findJob(jobId, resourceId, resourceTypeName);
23102323
if (job == null) {
2311-
throw new InvalidParameterValueException("Unable to find a job by id " + cmd.getId());
2324+
throw new InvalidParameterValueException("Unable to find a job by id " + jobId + " resource type "
2325+
+ cmd.getResourceType() + " resource id " + cmd.getResourceId());
23122326
}
2327+
jobId = job.getId();
23132328

23142329
final User userJobOwner = _accountMgr.getUserIncludingRemoved(job.getUserId());
23152330
final Account jobOwner = _accountMgr.getAccount(userJobOwner.getAccountId());
23162331

2332+
final Account caller = CallContext.current().getCallingAccount();
23172333
//check permissions
23182334
if (_accountMgr.isNormalUser(caller.getId())) {
23192335
//regular users can see only jobs they own
@@ -2324,7 +2340,7 @@ public AsyncJobResponse queryJobResult(final QueryAsyncJobResultCmd cmd) {
23242340
_accountMgr.checkAccess(caller, null, true, jobOwner);
23252341
}
23262342

2327-
return createAsyncJobResponse(_jobMgr.queryJob(cmd.getId(), true));
2343+
return createAsyncJobResponse(_jobMgr.queryJob(jobId, true));
23282344
}
23292345

23302346
public AsyncJobResponse createAsyncJobResponse(AsyncJob job) {
@@ -5704,4 +5720,14 @@ public ConsoleSessionResponse createConsoleSessionResponse(ConsoleSession consol
57045720
consoleSessionResponse.setObjectName("consolesession");
57055721
return consoleSessionResponse;
57065722
}
5723+
5724+
@Override
5725+
public EntityManager getEntityManager() {
5726+
return _entityMgr;
5727+
}
5728+
5729+
@Override
5730+
public AccountManager getAccountManager() {
5731+
return _accountMgr;
5732+
}
57075733
}

server/src/main/java/com/cloud/api/query/QueryManagerImpl.java

Lines changed: 28 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@
3131
import java.util.Map;
3232
import java.util.Objects;
3333
import java.util.Set;
34-
import java.util.UUID;
3534
import java.util.stream.Collectors;
3635
import java.util.stream.Stream;
3736

@@ -370,7 +369,7 @@
370369
import com.cloud.vm.dao.VMInstanceDetailsDao;
371370

372371
@Component
373-
public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements QueryService, Configurable {
372+
public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements QueryService, Configurable, ResourceIdSupport {
374373

375374

376375
private static final String ID_FIELD = "id";
@@ -869,26 +868,14 @@ private Pair<List<Long>, Integer> searchForEventIdsAndCount(ListEventsCmd cmd) {
869868
Integer entryTime = cmd.getEntryTime();
870869
Integer duration = cmd.getDuration();
871870
Long startId = cmd.getStartId();
872-
final String resourceUuid = cmd.getResourceId();
873-
final String resourceTypeStr = cmd.getResourceType();
871+
final String resourceUuid = getResourceUuid(cmd.getResourceId());
872+
final ApiCommandResourceType resourceType = getResourceType(cmd.getResourceType());
874873
final String stateStr = cmd.getState();
875-
ApiCommandResourceType resourceType = null;
876874
Long resourceId = null;
877-
if (resourceTypeStr != null) {
878-
resourceType = ApiCommandResourceType.fromString(resourceTypeStr);
879-
if (resourceType == null) {
880-
throw new InvalidParameterValueException(String.format("Invalid %s", ApiConstants.RESOURCE_TYPE));
881-
}
882-
}
883875
if (resourceUuid != null) {
884-
if (resourceTypeStr == null) {
876+
if (resourceType == null) {
885877
throw new InvalidParameterValueException(String.format("%s parameter must be used with %s parameter", ApiConstants.RESOURCE_ID, ApiConstants.RESOURCE_TYPE));
886878
}
887-
try {
888-
UUID.fromString(resourceUuid);
889-
} catch (IllegalArgumentException ex) {
890-
throw new InvalidParameterValueException(String.format("Invalid %s", ApiConstants.RESOURCE_ID));
891-
}
892879
Object object = entityManager.findByUuidIncludingRemoved(resourceType.getAssociatedClass(), resourceUuid);
893880
if (object instanceof InternalIdentity) {
894881
resourceId = ((InternalIdentity)object).getId();
@@ -3205,6 +3192,20 @@ private Pair<List<AsyncJobJoinVO>, Integer> searchForAsyncJobsInternal(ListAsync
32053192
sc.setParameters("executingMsid", msHost.getMsid());
32063193
}
32073194

3195+
if (cmd.getResourceType() != null) {
3196+
ApiCommandResourceType resourceType = getResourceType(cmd.getResourceType());
3197+
sc.addAnd("instanceType", SearchCriteria.Op.EQ, resourceType.toString());
3198+
3199+
final String resourceId = getResourceUuid(cmd.getResourceId());
3200+
if (resourceId == null) {
3201+
throw new InvalidParameterValueException("Invalid resource id for the resource type " + resourceType);
3202+
}
3203+
3204+
sc.addAnd("instanceUuid", SearchCriteria.Op.EQ, resourceId);
3205+
} else if (cmd.getResourceId() != null) {
3206+
throw new InvalidParameterValueException("Resource type must be specified for the resource id");
3207+
}
3208+
32083209
return _jobJoinDao.searchAndCount(sc, searchFilter);
32093210
}
32103211

@@ -6288,4 +6289,14 @@ public ConfigKey<?>[] getConfigKeys() {
62886289
return new ConfigKey<?>[] {AllowUserViewDestroyedVM, UserVMDeniedDetails, UserVMReadOnlyDetails, SortKeyAscending,
62896290
AllowUserViewAllDomainAccounts, AllowUserViewAllDataCenters, SharePublicTemplatesWithOtherDomains, ReturnVmStatsOnVmList};
62906291
}
6292+
6293+
@Override
6294+
public EntityManager getEntityManager() {
6295+
return entityManager;
6296+
}
6297+
6298+
@Override
6299+
public AccountManager getAccountManager() {
6300+
return accountMgr;
6301+
}
62916302
}

0 commit comments

Comments
 (0)