This is an automated email from the ASF dual-hosted git repository.
sureshanaparti pushed a commit to branch 4.22
in repository https://gitbox.apache.org/repos/asf/cloudstack.git
The following commit(s) were added to refs/heads/4.22 by this push:
new b02b652aa0c Support list/query async jobs by resource (#12983)
b02b652aa0c is described below
commit b02b652aa0c236a7a9af345a90451315c74ab66f
Author: Suresh Kumar Anaparti <[email protected]>
AuthorDate: Mon Apr 13 15:29:34 2026 +0530
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 <[email protected]>
Co-authored-by: mprokopchuk <[email protected]>
---
.../cloudstack/api/ApiCommandResourceType.java | 4 +-
.../api/command/user/job/ListAsyncJobsCmd.java | 15 +++
.../command/user/job/QueryAsyncJobResultCmd.java | 18 ++-
.../cloudstack/framework/jobs/dao/AsyncJobDao.java | 18 +++
.../framework/jobs/dao/AsyncJobDaoImpl.java | 33 ++++++
.../main/java/com/cloud/api/ApiResponseHelper.java | 36 +++++-
.../java/com/cloud/api/query/QueryManagerImpl.java | 45 +++++---
.../com/cloud/api/query/ResourceIdSupport.java | 123 +++++++++++++++++++++
.../com/cloud/api/query/QueryManagerImplTest.java | 2 +-
9 files changed, 267 insertions(+), 27 deletions(-)
diff --git
a/api/src/main/java/org/apache/cloudstack/api/ApiCommandResourceType.java
b/api/src/main/java/org/apache/cloudstack/api/ApiCommandResourceType.java
index 4d33ba859a5..e2ebb242cbf 100644
--- a/api/src/main/java/org/apache/cloudstack/api/ApiCommandResourceType.java
+++ b/api/src/main/java/org/apache/cloudstack/api/ApiCommandResourceType.java
@@ -127,8 +127,8 @@ public enum ApiCommandResourceType {
}
public static ApiCommandResourceType fromString(String value) {
- if (StringUtils.isNotEmpty(value) &&
EnumUtils.isValidEnum(ApiCommandResourceType.class, value)) {
- return valueOf(value);
+ if (StringUtils.isNotBlank(value) &&
EnumUtils.isValidEnumIgnoreCase(ApiCommandResourceType.class, value)) {
+ return EnumUtils.getEnumIgnoreCase(ApiCommandResourceType.class,
value);
}
return null;
}
diff --git
a/api/src/main/java/org/apache/cloudstack/api/command/user/job/ListAsyncJobsCmd.java
b/api/src/main/java/org/apache/cloudstack/api/command/user/job/ListAsyncJobsCmd.java
index b55d1b234f1..2c840183113 100644
---
a/api/src/main/java/org/apache/cloudstack/api/command/user/job/ListAsyncJobsCmd.java
+++
b/api/src/main/java/org/apache/cloudstack/api/command/user/job/ListAsyncJobsCmd.java
@@ -19,6 +19,7 @@ package org.apache.cloudstack.api.command.user.job;
import java.util.Date;
import org.apache.cloudstack.api.APICommand;
+import org.apache.cloudstack.api.ApiArgValidator;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.BaseListAccountResourcesCmd;
import org.apache.cloudstack.api.Parameter;
@@ -40,6 +41,12 @@ public class ListAsyncJobsCmd extends
BaseListAccountResourcesCmd {
@Parameter(name = ApiConstants.MANAGEMENT_SERVER_ID, type =
CommandType.UUID, entityType = ManagementServerResponse.class, description =
"The id of the management server", since="4.19")
private Long managementServerId;
+ @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")
+ private String resourceId;
+
+ @Parameter(name = ApiConstants.RESOURCE_TYPE, type = CommandType.STRING,
description = "the type of the resource associated with the job",
since="4.22.1")
+ private String resourceType;
+
/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
@@ -52,6 +59,14 @@ public class ListAsyncJobsCmd extends
BaseListAccountResourcesCmd {
return managementServerId;
}
+ public String getResourceId() {
+ return resourceId;
+ }
+
+ public String getResourceType() {
+ return resourceType;
+ }
+
/////////////////////////////////////////////////////
/////////////// API Implementation///////////////////
/////////////////////////////////////////////////////
diff --git
a/api/src/main/java/org/apache/cloudstack/api/command/user/job/QueryAsyncJobResultCmd.java
b/api/src/main/java/org/apache/cloudstack/api/command/user/job/QueryAsyncJobResultCmd.java
index 93a44375721..5c3b0084574 100644
---
a/api/src/main/java/org/apache/cloudstack/api/command/user/job/QueryAsyncJobResultCmd.java
+++
b/api/src/main/java/org/apache/cloudstack/api/command/user/job/QueryAsyncJobResultCmd.java
@@ -16,8 +16,8 @@
// under the License.
package org.apache.cloudstack.api.command.user.job;
-
import org.apache.cloudstack.api.APICommand;
+import org.apache.cloudstack.api.ApiArgValidator;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.BaseCmd;
import org.apache.cloudstack.api.Parameter;
@@ -34,9 +34,15 @@ public class QueryAsyncJobResultCmd extends BaseCmd {
//////////////// API parameters /////////////////////
/////////////////////////////////////////////////////
- @Parameter(name = ApiConstants.JOB_ID, type = CommandType.UUID, entityType
= AsyncJobResponse.class, required = true, description = "The ID of the
asynchronous job")
+ @Parameter(name = ApiConstants.JOB_ID, type = CommandType.UUID, entityType
= AsyncJobResponse.class, description = "The ID of the asynchronous job")
private Long id;
+ @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")
+ private String resourceId;
+
+ @Parameter(name = ApiConstants.RESOURCE_TYPE, type = CommandType.STRING,
description = "the type of the resource associated with the job",
since="4.22.1")
+ private String resourceType;
+
/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
@@ -45,6 +51,14 @@ public class QueryAsyncJobResultCmd extends BaseCmd {
return id;
}
+ public String getResourceId() {
+ return resourceId;
+ }
+
+ public String getResourceType() {
+ return resourceType;
+ }
+
/////////////////////////////////////////////////////
/////////////// API Implementation///////////////////
/////////////////////////////////////////////////////
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 9f7a4ad6e05..926280bfead 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
@@ -23,12 +23,30 @@ import org.apache.cloudstack.framework.jobs.impl.AsyncJobVO;
import com.cloud.utils.db.GenericDao;
+import javax.annotation.Nullable;
+
public interface AsyncJobDao extends GenericDao<AsyncJobVO, Long> {
AsyncJobVO findInstancePendingAsyncJob(String instanceType, long
instanceId);
List<AsyncJobVO> findInstancePendingAsyncJobs(String instanceType, Long
accountId);
+ /**
+ * Finds async job matching the given parameters.
+ * Non-null parameters are added to search criteria.
+ * Returns the most recent job by creation date.
+ * <p>
+ * When searching by resourceId and resourceType, only one active job
+ * is expected per resource, so returning a single result is sufficient.
+ *
+ * @param id job ID
+ * @param resourceId resource ID (instanceId)
+ * @param resourceType resource type (instanceType)
+ * @return matching job or null
+ */
+ @Nullable
+ AsyncJobVO findJob(Long id, Long resourceId, String resourceType);
+
AsyncJobVO findPseudoJob(long threadId, long msid);
void cleanupPseduoJobs(long msid);
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 a2f1f36b863..81cc5d4f2a8 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
@@ -22,6 +22,8 @@ import java.util.Date;
import java.util.List;
import org.apache.cloudstack.api.ApiConstants;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
import org.apache.cloudstack.framework.jobs.impl.AsyncJobVO;
import org.apache.cloudstack.jobs.JobInfo;
@@ -45,6 +47,7 @@ public class AsyncJobDaoImpl extends
GenericDaoBase<AsyncJobVO, Long> implements
private final SearchBuilder<AsyncJobVO> expiringUnfinishedAsyncJobSearch;
private final SearchBuilder<AsyncJobVO> expiringCompletedAsyncJobSearch;
private final SearchBuilder<AsyncJobVO> failureMsidAsyncJobSearch;
+ private final SearchBuilder<AsyncJobVO> byIdResourceIdResourceTypeSearch;
private final GenericSearchBuilder<AsyncJobVO, Long> asyncJobTypeSearch;
private final GenericSearchBuilder<AsyncJobVO, Long>
pendingNonPseudoAsyncJobsSearch;
@@ -95,6 +98,12 @@ public class AsyncJobDaoImpl extends
GenericDaoBase<AsyncJobVO, Long> implements
failureMsidAsyncJobSearch.and("job_cmd",
failureMsidAsyncJobSearch.entity().getCmd(), Op.IN);
failureMsidAsyncJobSearch.done();
+ byIdResourceIdResourceTypeSearch = createSearchBuilder();
+ byIdResourceIdResourceTypeSearch.and("id",
byIdResourceIdResourceTypeSearch.entity().getId(), SearchCriteria.Op.EQ);
+ byIdResourceIdResourceTypeSearch.and("instanceId",
byIdResourceIdResourceTypeSearch.entity().getInstanceId(),
SearchCriteria.Op.EQ);
+ byIdResourceIdResourceTypeSearch.and("instanceType",
byIdResourceIdResourceTypeSearch.entity().getInstanceType(),
SearchCriteria.Op.EQ);
+ byIdResourceIdResourceTypeSearch.done();
+
asyncJobTypeSearch = createSearchBuilder(Long.class);
asyncJobTypeSearch.select(null, SearchCriteria.Func.COUNT,
asyncJobTypeSearch.entity().getId());
asyncJobTypeSearch.and("job_info",
asyncJobTypeSearch.entity().getCmdInfo(),Op.LIKE);
@@ -140,6 +149,30 @@ public class AsyncJobDaoImpl extends
GenericDaoBase<AsyncJobVO, Long> implements
return listBy(sc);
}
+ @Override
+ public AsyncJobVO findJob(Long id, Long resourceId, String resourceType) {
+ SearchCriteria<AsyncJobVO> sc =
byIdResourceIdResourceTypeSearch.create();
+
+ if (id == null && resourceId == null &&
StringUtils.isBlank(resourceType)) {
+ logger.debug("findJob called with all null parameters");
+ return null;
+ }
+
+ if (id != null) {
+ sc.setParameters("id", id);
+ }
+ if (resourceId != null && StringUtils.isNotBlank(resourceType)) {
+ sc.setParameters("instanceType", resourceType);
+ sc.setParameters("instanceId", resourceId);
+ }
+ Filter filter = new Filter(AsyncJobVO.class, "created", false, 0L, 1L);
+ List<AsyncJobVO> result = searchIncludingRemoved(sc, filter,
Boolean.FALSE, false);
+ if (CollectionUtils.isNotEmpty(result)) {
+ return result.get(0);
+ }
+ return null;
+ }
+
@Override
public AsyncJobVO findPseudoJob(long threadId, long msid) {
SearchCriteria<AsyncJobVO> sc = pseudoJobSearch.create();
diff --git a/server/src/main/java/com/cloud/api/ApiResponseHelper.java
b/server/src/main/java/com/cloud/api/ApiResponseHelper.java
index 60243c50b08..67f83dacaad 100644
--- a/server/src/main/java/com/cloud/api/ApiResponseHelper.java
+++ b/server/src/main/java/com/cloud/api/ApiResponseHelper.java
@@ -32,6 +32,7 @@ import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Optional;
import java.util.Set;
import java.util.TimeZone;
import java.util.function.Consumer;
@@ -39,6 +40,7 @@ import java.util.stream.Collectors;
import javax.inject.Inject;
+import com.cloud.api.query.ResourceIdSupport;
import com.cloud.bgp.ASNumber;
import com.cloud.bgp.ASNumberRange;
import com.cloud.configuration.ConfigurationService;
@@ -57,6 +59,7 @@ import org.apache.cloudstack.affinity.AffinityGroup;
import org.apache.cloudstack.affinity.AffinityGroupResponse;
import org.apache.cloudstack.annotation.AnnotationService;
import org.apache.cloudstack.annotation.dao.AnnotationDao;
+import org.apache.cloudstack.api.ApiCommandResourceType;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.ApiConstants.DomainDetails;
import org.apache.cloudstack.api.ApiConstants.HostDetails;
@@ -219,6 +222,7 @@ import
org.apache.cloudstack.engine.subsystem.api.storage.SnapshotDataFactory;
import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo;
import org.apache.cloudstack.framework.jobs.AsyncJob;
import org.apache.cloudstack.framework.jobs.AsyncJobManager;
+import org.apache.cloudstack.framework.jobs.dao.AsyncJobDao;
import org.apache.cloudstack.gui.theme.GuiThemeJoin;
import org.apache.cloudstack.management.ManagementServerHost;
import org.apache.cloudstack.network.BgpPeerVO;
@@ -447,7 +451,7 @@ import com.cloud.vm.snapshot.dao.VMSnapshotDao;
import sun.security.x509.X509CertImpl;
-public class ApiResponseHelper implements ResponseGenerator {
+public class ApiResponseHelper implements ResponseGenerator, ResourceIdSupport
{
protected Logger logger = LogManager.getLogger(ApiResponseHelper.class);
private static final DecimalFormat s_percentFormat = new
DecimalFormat("##.##");
@@ -529,6 +533,8 @@ public class ApiResponseHelper implements ResponseGenerator
{
RoutedIpv4Manager routedIpv4Manager;
@Inject
ResourceIconManager resourceIconManager;
+ @Inject
+ AsyncJobDao asyncJobDao;
public static String getPrettyDomainPath(String path) {
if (path == null) {
@@ -2304,16 +2310,26 @@ public class ApiResponseHelper implements
ResponseGenerator {
@Override
public AsyncJobResponse queryJobResult(final QueryAsyncJobResultCmd cmd) {
- final Account caller = CallContext.current().getCallingAccount();
+ ApiCommandResourceType resourceType =
getResourceType(cmd.getResourceType());
+ String resourceTypeName =
Optional.ofNullable(resourceType).map(ApiCommandResourceType::name).orElse(null);
+
+ Long resourceId = getResourceId(resourceType, cmd.getResourceId());
+ Long jobId = cmd.getId();
+ if (jobId == null && resourceId == null) {
+ throw new InvalidParameterValueException("Expected parameter job
id or parameters resource type and resource id");
+ }
- final AsyncJob job =
_entityMgr.findByIdIncludingRemoved(AsyncJob.class, cmd.getId());
+ final AsyncJob job = asyncJobDao.findJob(jobId, resourceId,
resourceTypeName);
if (job == null) {
- throw new InvalidParameterValueException("Unable to find a job by
id " + cmd.getId());
+ throw new InvalidParameterValueException("Unable to find a job by
id " + jobId + " resource type "
+ + cmd.getResourceType() + " resource id " +
cmd.getResourceId());
}
+ jobId = job.getId();
final User userJobOwner =
_accountMgr.getUserIncludingRemoved(job.getUserId());
final Account jobOwner =
_accountMgr.getAccount(userJobOwner.getAccountId());
+ final Account caller = CallContext.current().getCallingAccount();
//check permissions
if (_accountMgr.isNormalUser(caller.getId())) {
//regular users can see only jobs they own
@@ -2324,7 +2340,7 @@ public class ApiResponseHelper implements
ResponseGenerator {
_accountMgr.checkAccess(caller, null, true, jobOwner);
}
- return createAsyncJobResponse(_jobMgr.queryJob(cmd.getId(), true));
+ return createAsyncJobResponse(_jobMgr.queryJob(jobId, true));
}
public AsyncJobResponse createAsyncJobResponse(AsyncJob job) {
@@ -5704,4 +5720,14 @@ protected Map<String, ResourceIcon>
getResourceIconsUsingOsCategory(List<Templat
consoleSessionResponse.setObjectName("consolesession");
return consoleSessionResponse;
}
+
+ @Override
+ public EntityManager getEntityManager() {
+ return _entityMgr;
+ }
+
+ @Override
+ public AccountManager getAccountManager() {
+ return _accountMgr;
+ }
}
diff --git a/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java
b/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java
index fea87b66fed..6214a8f2743 100644
--- a/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java
+++ b/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java
@@ -31,7 +31,6 @@ import java.util.ListIterator;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
-import java.util.UUID;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -370,7 +369,7 @@ import com.cloud.vm.dao.VMInstanceDao;
import com.cloud.vm.dao.VMInstanceDetailsDao;
@Component
-public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements
QueryService, Configurable {
+public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements
QueryService, Configurable, ResourceIdSupport {
private static final String ID_FIELD = "id";
@@ -869,26 +868,14 @@ public class QueryManagerImpl extends
MutualExclusiveIdsManagerBase implements Q
Integer entryTime = cmd.getEntryTime();
Integer duration = cmd.getDuration();
Long startId = cmd.getStartId();
- final String resourceUuid = cmd.getResourceId();
- final String resourceTypeStr = cmd.getResourceType();
+ final String resourceUuid = getResourceUuid(cmd.getResourceId());
+ final ApiCommandResourceType resourceType =
getResourceType(cmd.getResourceType());
final String stateStr = cmd.getState();
- ApiCommandResourceType resourceType = null;
Long resourceId = null;
- if (resourceTypeStr != null) {
- resourceType = ApiCommandResourceType.fromString(resourceTypeStr);
- if (resourceType == null) {
- throw new
InvalidParameterValueException(String.format("Invalid %s",
ApiConstants.RESOURCE_TYPE));
- }
- }
if (resourceUuid != null) {
- if (resourceTypeStr == null) {
+ if (resourceType == null) {
throw new InvalidParameterValueException(String.format("%s
parameter must be used with %s parameter", ApiConstants.RESOURCE_ID,
ApiConstants.RESOURCE_TYPE));
}
- try {
- UUID.fromString(resourceUuid);
- } catch (IllegalArgumentException ex) {
- throw new
InvalidParameterValueException(String.format("Invalid %s",
ApiConstants.RESOURCE_ID));
- }
Object object =
entityManager.findByUuidIncludingRemoved(resourceType.getAssociatedClass(),
resourceUuid);
if (object instanceof InternalIdentity) {
resourceId = ((InternalIdentity)object).getId();
@@ -3205,6 +3192,20 @@ public class QueryManagerImpl extends
MutualExclusiveIdsManagerBase implements Q
sc.setParameters("executingMsid", msHost.getMsid());
}
+ if (cmd.getResourceType() != null) {
+ ApiCommandResourceType resourceType =
getResourceType(cmd.getResourceType());
+ sc.addAnd("instanceType", SearchCriteria.Op.EQ,
resourceType.toString());
+
+ final String resourceId = getResourceUuid(cmd.getResourceId());
+ if (resourceId == null) {
+ throw new InvalidParameterValueException("Invalid resource id
for the resource type " + resourceType);
+ }
+
+ sc.addAnd("instanceUuid", SearchCriteria.Op.EQ, resourceId);
+ } else if (cmd.getResourceId() != null) {
+ throw new InvalidParameterValueException("Resource type must be
specified for the resource id");
+ }
+
return _jobJoinDao.searchAndCount(sc, searchFilter);
}
@@ -6288,4 +6289,14 @@ public class QueryManagerImpl extends
MutualExclusiveIdsManagerBase implements Q
return new ConfigKey<?>[] {AllowUserViewDestroyedVM,
UserVMDeniedDetails, UserVMReadOnlyDetails, SortKeyAscending,
AllowUserViewAllDomainAccounts, AllowUserViewAllDataCenters,
SharePublicTemplatesWithOtherDomains, ReturnVmStatsOnVmList};
}
+
+ @Override
+ public EntityManager getEntityManager() {
+ return entityManager;
+ }
+
+ @Override
+ public AccountManager getAccountManager() {
+ return accountMgr;
+ }
}
diff --git a/server/src/main/java/com/cloud/api/query/ResourceIdSupport.java
b/server/src/main/java/com/cloud/api/query/ResourceIdSupport.java
new file mode 100644
index 00000000000..2c32df22f0c
--- /dev/null
+++ b/server/src/main/java/com/cloud/api/query/ResourceIdSupport.java
@@ -0,0 +1,123 @@
+// 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.api.query;
+
+import com.cloud.exception.InvalidParameterValueException;
+import com.cloud.user.Account;
+import com.cloud.user.AccountManager;
+import com.cloud.utils.db.EntityManager;
+import org.apache.cloudstack.acl.ControlledEntity;
+import org.apache.cloudstack.api.ApiCommandResourceType;
+import org.apache.cloudstack.api.ApiConstants;
+import org.apache.cloudstack.api.InternalIdentity;
+import org.apache.cloudstack.context.CallContext;
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.Optional;
+import java.util.UUID;
+import static org.apache.cloudstack.acl.SecurityChecker.AccessType;
+
+/**
+ * Support interface for converting resource UUIDs to internal IDs
+ * with validation and access control.
+ *
+ * @author mprokopchuk
+ */
+public interface ResourceIdSupport {
+
+ EntityManager getEntityManager();
+
+ AccountManager getAccountManager();
+
+ /**
+ * Converts resource UUID to internal database ID with access control
checks.
+ *
+ * @param resourceType type of the resource
+ * @param resourceUuid UUID of the resource
+ * @return internal resource ID or null if parameters are null
+ * @throws InvalidParameterValueException if only one parameter provided
or resource not found
+ */
+ default Long getResourceId(ApiCommandResourceType resourceType, String
resourceUuid) {
+ String uuid = getResourceUuid(resourceUuid);
+
+ if (resourceType == null && uuid == null) {
+ return null;
+ } else if ((resourceType == null) ^ (uuid == null)) {
+ throw new InvalidParameterValueException(String.format("Both %s
and %s required",
+ ApiConstants.RESOURCE_ID, ApiConstants.RESOURCE_TYPE));
+ }
+
+ Object object =
getEntityManager().findByUuidIncludingRemoved(resourceType.getAssociatedClass(),
resourceUuid);
+ if (!(object instanceof InternalIdentity)) {
+ throw new InvalidParameterValueException(String.format("Invalid
%s", ApiConstants.RESOURCE_ID));
+ }
+ Long resourceId = ((InternalIdentity) object).getId();
+
+ Account caller = CallContext.current().getCallingAccount();
+ boolean isRootAdmin = getAccountManager().isRootAdmin(caller.getId());
+
+ if (!isRootAdmin && object instanceof ControlledEntity) {
+ ControlledEntity entity = (ControlledEntity) object;
+ boolean sameOwner = entity.getAccountId() == caller.getId();
+ getAccountManager().checkAccess(caller, AccessType.ListEntry,
sameOwner, entity);
+ }
+
+ return resourceId;
+ }
+
+ /**
+ * Parses and validates resource type string.
+ *
+ * @param resourceType resource type as string
+ * @return parsed resource type or null if not provided
+ * @throws InvalidParameterValueException if provided type is invalid
+ */
+ default ApiCommandResourceType getResourceType(String resourceType) {
+ Optional<String> resourceTypeOpt =
Optional.ofNullable(resourceType).filter(StringUtils::isNotBlank);
+ // return null if resource type was not provided
+ if (resourceTypeOpt.isEmpty()) {
+ return null;
+ }
+ // return value or throw exception if provided resource type is invalid
+ return resourceTypeOpt
+ .map(ApiCommandResourceType::fromString)
+ .orElseThrow(() -> new
InvalidParameterValueException(String.format("Invalid %s",
+ ApiConstants.RESOURCE_TYPE)));
+ }
+
+ /**
+ * Validates resource UUID format.
+ *
+ * @param resourceUuid UUID string to validate
+ * @return validated UUID or null if not provided
+ * @throws InvalidParameterValueException if UUID format is invalid
+ */
+ default String getResourceUuid(String resourceUuid) {
+ if (StringUtils.isBlank(resourceUuid)) {
+ return null;
+ }
+
+ try {
+ UUID.fromString(resourceUuid);
+ } catch (IllegalArgumentException ex) {
+ throw new InvalidParameterValueException(String.format("Invalid
%s", ApiConstants.RESOURCE_ID));
+ }
+
+ return resourceUuid;
+ }
+
+}
diff --git a/server/src/test/java/com/cloud/api/query/QueryManagerImplTest.java
b/server/src/test/java/com/cloud/api/query/QueryManagerImplTest.java
index 750f4d8655b..ccc0d0c8d4c 100644
--- a/server/src/test/java/com/cloud/api/query/QueryManagerImplTest.java
+++ b/server/src/test/java/com/cloud/api/query/QueryManagerImplTest.java
@@ -272,7 +272,7 @@ public class QueryManagerImplTest {
public void searchForEventsFailResourceIdInvalid() {
ListEventsCmd cmd = setupMockListEventsCmd();
Mockito.when(cmd.getResourceId()).thenReturn("random");
-
Mockito.when(cmd.getResourceType()).thenReturn(ApiCommandResourceType.VirtualMachine.toString());
+
Mockito.lenient().when(cmd.getResourceType()).thenReturn(ApiCommandResourceType.VirtualMachine.toString());
queryManager.searchForEvents(cmd);
}