diff --git a/api/src/main/java/org/apache/cloudstack/api/ApiServerService.java b/api/src/main/java/org/apache/cloudstack/api/ApiServerService.java index 18c96c37159..1ee41ac86c2 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ApiServerService.java +++ b/api/src/main/java/org/apache/cloudstack/api/ApiServerService.java @@ -21,8 +21,11 @@ import java.util.Map; import javax.servlet.http.HttpSession; +import org.apache.cloudstack.context.CallContext; + import com.cloud.domain.Domain; import com.cloud.exception.CloudAuthenticationException; +import com.cloud.user.Account; import com.cloud.user.UserAccount; public interface ApiServerService { @@ -52,4 +55,20 @@ public interface ApiServerService { String getDomainId(Map params); boolean isPostRequestsAndTimestampsEnforced(); + + AsyncCmdResult processAsyncCmd(BaseAsyncCmd cmdObj, Map params, CallContext ctx, Long callerUserId, Account caller) throws Exception; + + class AsyncCmdResult { + public final Long objectId; + public final String objectUuid; + public final BaseAsyncCmd asyncCmd; + public final long jobId; + + public AsyncCmdResult(Long objectId, String objectUuid, BaseAsyncCmd asyncCmd, long jobId) { + this.objectId = objectId; + this.objectUuid = objectUuid; + this.asyncCmd = asyncCmd; + this.jobId = jobId; + } + } } diff --git a/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/adapter/ServerAdapter.java b/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/adapter/ServerAdapter.java index f4fff169c48..b7d8e269976 100644 --- a/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/adapter/ServerAdapter.java +++ b/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/adapter/ServerAdapter.java @@ -20,6 +20,7 @@ package org.apache.cloudstack.veeam.adapter; import java.util.Arrays; import java.util.Base64; import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; @@ -33,6 +34,7 @@ import org.apache.cloudstack.acl.RoleService; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.acl.Rule; import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiServerService; import org.apache.cloudstack.api.BaseCmd; import org.apache.cloudstack.api.command.admin.vm.DeployVMCmdByAdmin; import org.apache.cloudstack.api.command.user.job.QueryAsyncJobResultCmd; @@ -90,12 +92,14 @@ import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.jetbrains.annotations.NotNull; +import com.cloud.api.query.dao.AsyncJobJoinDao; import com.cloud.api.query.dao.DataCenterJoinDao; import com.cloud.api.query.dao.HostJoinDao; import com.cloud.api.query.dao.ImageStoreJoinDao; import com.cloud.api.query.dao.StoragePoolJoinDao; import com.cloud.api.query.dao.UserVmJoinDao; import com.cloud.api.query.dao.VolumeJoinDao; +import com.cloud.api.query.vo.AsyncJobJoinVO; import com.cloud.api.query.vo.DataCenterJoinVO; import com.cloud.api.query.vo.HostJoinVO; import com.cloud.api.query.vo.ImageStoreJoinVO; @@ -108,7 +112,6 @@ import com.cloud.dc.dao.ClusterDao; import com.cloud.dc.dao.DataCenterDao; import com.cloud.exception.InsufficientCapacityException; import com.cloud.exception.InvalidParameterValueException; -import com.cloud.exception.OperationTimedoutException; import com.cloud.exception.ResourceAllocationException; import com.cloud.exception.ResourceUnavailableException; import com.cloud.hypervisor.Hypervisor; @@ -124,12 +127,12 @@ import com.cloud.storage.dao.VolumeDao; import com.cloud.storage.dao.VolumeDetailsDao; import com.cloud.user.Account; import com.cloud.user.AccountService; -import com.cloud.user.AccountVO; import com.cloud.user.User; import com.cloud.user.UserAccount; -import com.cloud.user.dao.AccountDao; +import com.cloud.user.dao.UserAccountDao; import com.cloud.uservm.UserVm; import com.cloud.utils.EnumUtils; +import com.cloud.utils.Pair; import com.cloud.utils.component.ComponentContext; import com.cloud.utils.component.ManagerBase; import com.cloud.utils.exception.CloudRuntimeException; @@ -167,7 +170,7 @@ public class ServerAdapter extends ManagerBase { AccountService accountService; @Inject - AccountDao accountDao; + UserAccountDao userAccountDao; @Inject DataCenterDao dataCenterDao; @@ -229,6 +232,12 @@ public class ServerAdapter extends ManagerBase { @Inject NicDao nicDao; + @Inject + ApiServerService apiServerService; + + @Inject + AsyncJobJoinDao asyncJobJoinDao; + private Map jobsMap = new ConcurrentHashMap<>(); protected Role createServiceAccountRole() { @@ -255,7 +264,7 @@ public class ServerAdapter extends ManagerBase { return createServiceAccountRole(); } - protected Account createServiceAccount() { + protected UserAccount createServiceAccount() { CallContext.register(User.UID_SYSTEM, Account.ACCOUNT_ID_SYSTEM); try { Role role = getServiceAccountRole(); @@ -263,23 +272,22 @@ public class ServerAdapter extends ManagerBase { UUID.randomUUID().toString(), SERVICE_ACCOUNT_FIRST_NAME, SERVICE_ACCOUNT_LAST_NAME, null, null, SERVICE_ACCOUNT_NAME, Account.Type.NORMAL, role.getId(), 1L, null, null, null, null, User.Source.NATIVE); - Account account = accountService.getAccount(userAccount.getAccountId()); - logger.debug("Created Veeam service account: {}", account); - return account; + logger.debug("Created Veeam service account: {}", userAccount); + return userAccount; } finally { CallContext.unregister(); } } - protected Account createServiceAccountIfNeeded() { - List accounts = accountDao.findAccountsByName(SERVICE_ACCOUNT_NAME); - for (AccountVO account : accounts) { - if (Account.State.ENABLED.equals(account.getState())) { - logger.debug("Veeam service account found: {}", account); - return account; - } + protected Pair createServiceAccountIfNeeded() { + UserAccount userAccount = accountService.getActiveUserAccount(SERVICE_ACCOUNT_NAME, 1L); + if (userAccount == null) { + userAccount = createServiceAccount(); + } else { + logger.debug("Veeam service user account found: {}", userAccount); } - return createServiceAccount(); + return new Pair<>(accountService.getActiveUser(userAccount.getId()), + accountService.getActiveAccountById(userAccount.getAccountId())); } @Override @@ -431,8 +439,8 @@ public class ServerAdapter extends ManagerBase { bootType = ApiConstants.BootType.UEFI; bootMode = ApiConstants.BootMode.SECURE; } - Account serviceAccount = createServiceAccountIfNeeded(); - CallContext.register(serviceAccount.getId(), serviceAccount.getId()); + Pair serviceUserAccount = createServiceAccountIfNeeded(); + CallContext ctx = CallContext.register(serviceUserAccount.first(), serviceUserAccount.second()); try { return createVm(zoneId, clusterId, name, cpu, memory, userdata, bootType, bootMode); } finally { @@ -507,11 +515,22 @@ public class ServerAdapter extends ManagerBase { if (vo == null) { throw new InvalidParameterValueException("VM with ID " + uuid + " not found"); } + Pair serviceUserAccount = createServiceAccountIfNeeded(); + CallContext ctx = CallContext.register(serviceUserAccount.first(), serviceUserAccount.second()); try { - userVmService.startVirtualMachine(vo, null); - return UserVmJoinVOToVmConverter.toVmAction(userVmJoinDao.findById(vo.getId())); - } catch (ResourceUnavailableException | OperationTimedoutException | InsufficientCapacityException | CloudRuntimeException e) { + StartVMCmd cmd = new StartVMCmd(); + ComponentContext.inject(cmd); + Map params = new HashMap<>(); + params.put(ApiConstants.ID, vo.getUuid()); + ApiServerService.AsyncCmdResult result = + apiServerService.processAsyncCmd(cmd, params, ctx, serviceUserAccount.first().getId(), + serviceUserAccount.second()); + AsyncJobJoinVO asyncJobJoinVO = asyncJobJoinDao.findById(result.jobId); + return AsyncJobJoinVOToJobConverter.toVmAction(asyncJobJoinVO, userVmJoinDao.findById(vo.getId())); + } catch (Exception e) { throw new CloudRuntimeException("Failed to start VM: " + e.getMessage(), e); + } finally { + CallContext.unregister(); } } @@ -520,11 +539,23 @@ public class ServerAdapter extends ManagerBase { if (vo == null) { throw new InvalidParameterValueException("VM with ID " + uuid + " not found"); } + Pair serviceUserAccount = createServiceAccountIfNeeded(); + CallContext ctx = CallContext.register(serviceUserAccount.first(), serviceUserAccount.second()); try { - userVmService.stopVirtualMachine(vo.getId(), true); - return UserVmJoinVOToVmConverter.toVmAction(userVmJoinDao.findById(vo.getId())); - } catch (CloudRuntimeException e) { + StopVMCmd cmd = new StopVMCmd(); + ComponentContext.inject(cmd); + Map params = new HashMap<>(); + params.put(ApiConstants.ID, vo.getUuid()); + params.put(ApiConstants.FORCED, Boolean.TRUE.toString()); + ApiServerService.AsyncCmdResult result = + apiServerService.processAsyncCmd(cmd, params, ctx, serviceUserAccount.first().getId(), + serviceUserAccount.second()); + AsyncJobJoinVO asyncJobJoinVO = asyncJobJoinDao.findById(result.jobId); + return AsyncJobJoinVOToJobConverter.toVmAction(asyncJobJoinVO, userVmJoinDao.findById(vo.getId())); + } catch (Exception e) { throw new CloudRuntimeException("Failed to stop VM: " + e.getMessage(), e); + } finally { + CallContext.unregister(); } } @@ -533,11 +564,23 @@ public class ServerAdapter extends ManagerBase { if (vo == null) { throw new InvalidParameterValueException("VM with ID " + uuid + " not found"); } + Pair serviceUserAccount = createServiceAccountIfNeeded(); + CallContext ctx = CallContext.register(serviceUserAccount.first(), serviceUserAccount.second()); try { - userVmService.stopVirtualMachine(vo.getId(), false); - return UserVmJoinVOToVmConverter.toVmAction(userVmJoinDao.findById(vo.getId())); - } catch (CloudRuntimeException e) { + StopVMCmd cmd = new StopVMCmd(); + ComponentContext.inject(cmd); + Map params = new HashMap<>(); + params.put(ApiConstants.ID, vo.getUuid()); + params.put(ApiConstants.FORCED, Boolean.FALSE.toString()); + ApiServerService.AsyncCmdResult result = + apiServerService.processAsyncCmd(cmd, params, ctx, serviceUserAccount.first().getId(), + serviceUserAccount.second()); + AsyncJobJoinVO asyncJobJoinVO = asyncJobJoinDao.findById(result.jobId); + return AsyncJobJoinVOToJobConverter.toVmAction(asyncJobJoinVO, userVmJoinDao.findById(vo.getId())); + } catch (Exception e) { throw new CloudRuntimeException("Failed to shutdown VM: " + e.getMessage(), e); + } finally { + CallContext.unregister(); } } @@ -579,8 +622,8 @@ public class ServerAdapter extends ManagerBase { if (volumeVO == null) { throw new InvalidParameterValueException("Disk with ID " + request.disk.id + " not found"); } - Account serviceAccount = createServiceAccountIfNeeded(); - CallContext.register(serviceAccount.getId(), serviceAccount.getId()); + Pair serviceUserAccount = createServiceAccountIfNeeded(); + CallContext.register(serviceUserAccount.first(), serviceUserAccount.second()); try { Volume volume = volumeApiService.attachVolumeToVM(vmVo.getId(), volumeVO.getId(), 0L, false); VolumeJoinVO attachedVolumeVO = volumeJoinDao.findById(volume.getId()); @@ -638,7 +681,8 @@ public class ServerAdapter extends ManagerBase { initialSize = Long.parseLong(request.initialSize); } catch (NumberFormatException ignored) {} } - Account serviceAccount = createServiceAccountIfNeeded(); + Pair serviceUserAccount = createServiceAccountIfNeeded(); + Account serviceAccount = serviceUserAccount.second(); DataCenterVO zone = dataCenterDao.findById(pool.getDataCenterId()); if (zone == null || !Grouping.AllocationState.Enabled.equals(zone.getAllocationState())) { throw new InvalidParameterValueException("Datacenter for the specified storage domain is not found or not active"); @@ -647,7 +691,7 @@ public class ServerAdapter extends ManagerBase { if (diskOfferingId == null) { throw new CloudRuntimeException("Failed to find custom offering for disk" + zone.getName()); } - CallContext.register(serviceAccount.getId(), serviceAccount.getId()); + CallContext ctx = CallContext.register(serviceUserAccount.first(), serviceUserAccount.second()); try { return createDisk(serviceAccount, pool, name, diskOfferingId, provisionedSizeInGb, initialSize); } finally { @@ -705,8 +749,8 @@ public class ServerAdapter extends ManagerBase { if (networkVO == null) { throw new InvalidParameterValueException("VNic profile " + request.getVnicProfile().id+ " not found"); } - Account serviceAccount = createServiceAccountIfNeeded(); - CallContext.register(serviceAccount.getId(), serviceAccount.getId()); + Pair serviceUserAccount = createServiceAccountIfNeeded(); + CallContext.register(serviceUserAccount.first(), serviceUserAccount.second()); try { AddNicToVMCmd cmd = new AddNicToVMCmd(); ComponentContext.inject(cmd); @@ -775,8 +819,8 @@ public class ServerAdapter extends ManagerBase { } private ImageTransfer createImageTransfer(Long backupId, Long volumeId, Direction direction, Format format) { - Account serviceAccount = createServiceAccountIfNeeded(); - CallContext.register(serviceAccount.getId(), serviceAccount.getId()); + Pair serviceUserAccount = createServiceAccountIfNeeded(); + CallContext.register(serviceUserAccount.first(), serviceUserAccount.second()); try { org.apache.cloudstack.backup.ImageTransfer imageTransfer = incrementalBackupService.createImageTransfer(volumeId, null, direction, format); @@ -819,7 +863,7 @@ public class ServerAdapter extends ManagerBase { return Collections.emptyList(); } - public Job getJob(String uuid) { + public Job getTempJob(String uuid) { // final ClusterVO vo = clusterDao.findByUuid(uuid); // if (vo == null) { // throw new InvalidParameterValueException("Cluster with ID " + uuid + " not found"); @@ -832,4 +876,12 @@ public class ServerAdapter extends ManagerBase { return AsyncJobJoinVOToJobConverter.toJob(uuid, "started", startTime); } } + + public Job getJob(String uuid) { + final AsyncJobJoinVO vo = asyncJobJoinDao.findByUuid(uuid); + if (vo == null) { + throw new InvalidParameterValueException("Job with ID " + uuid + " not found"); + } + return AsyncJobJoinVOToJobConverter.toJob(vo); + } } diff --git a/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/JobsRouteHandler.java b/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/JobsRouteHandler.java index 516ea8de4d8..5b5a62c6850 100644 --- a/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/JobsRouteHandler.java +++ b/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/JobsRouteHandler.java @@ -93,7 +93,7 @@ public class JobsRouteHandler extends ManagerBase implements RouteHandler { protected void handleGetById(final String id, final HttpServletResponse resp, final Negotiation.OutFormat outFormat, final VeeamControlServlet io) throws IOException { try { - Job response = serverAdapter.getJob(id); + Job response = serverAdapter.getTempJob(id); io.getWriter().write(resp, HttpServletResponse.SC_OK, response, outFormat); } catch (InvalidParameterValueException e) { io.getWriter().writeFault(resp, HttpServletResponse.SC_NOT_FOUND, "Not found", e.getMessage(), outFormat); diff --git a/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/converter/AsyncJobJoinVOToJobConverter.java b/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/converter/AsyncJobJoinVOToJobConverter.java index f3aa1dd4002..eae8ac96b11 100644 --- a/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/converter/AsyncJobJoinVOToJobConverter.java +++ b/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/converter/AsyncJobJoinVOToJobConverter.java @@ -19,11 +19,16 @@ package org.apache.cloudstack.veeam.api.converter; import java.util.Collections; +import org.apache.cloudstack.jobs.JobInfo; import org.apache.cloudstack.veeam.VeeamControlService; import org.apache.cloudstack.veeam.api.JobsRouteHandler; import org.apache.cloudstack.veeam.api.dto.Actions; import org.apache.cloudstack.veeam.api.dto.Job; import org.apache.cloudstack.veeam.api.dto.Ref; +import org.apache.cloudstack.veeam.api.dto.VmAction; + +import com.cloud.api.query.vo.AsyncJobJoinVO; +import com.cloud.api.query.vo.UserVmJoinVO; public class AsyncJobJoinVOToJobConverter { @@ -47,4 +52,40 @@ public class AsyncJobJoinVOToJobConverter { job.setLink(Collections.emptyList()); return job; } + + public static Job toJob(AsyncJobJoinVO vo) { + Job job = new Job(); + final String basePath = VeeamControlService.ContextPath.value(); + job.setId(vo.getUuid()); + job.setHref(basePath + JobsRouteHandler.BASE_ROUTE + "/" + vo.getUuid()); + job.setAutoCleared(Boolean.TRUE.toString()); + job.setExternal(Boolean.TRUE.toString()); + job.setLastUpdated(System.currentTimeMillis()); + job.setStartTime(vo.getCreated().getTime()); + JobInfo.Status status = JobInfo.Status.values()[vo.getStatus()]; + if (status == JobInfo.Status.SUCCEEDED) { + job.setStatus("finished"); + job.setEndTime(System.currentTimeMillis()); + } else if (status == JobInfo.Status.FAILED) { + job.setStatus(status.name().toLowerCase()); + } else if (status == JobInfo.Status.CANCELLED) { + job.setStatus("aborted"); + } else { + job.setStatus("started"); + } + job.setOwner(Ref.of(basePath + "/api/users/" + vo.getUserUuid(), vo.getUserUuid())); + job.setActions(new Actions()); + job.setDescription("Something"); + job.setLink(Collections.emptyList()); + return job; + } + + public static VmAction toVmAction(final AsyncJobJoinVO vo, final UserVmJoinVO vm) { + VmAction action = new VmAction(); + final String basePath = VeeamControlService.ContextPath.value(); + action.setVm(UserVmJoinVOToVmConverter.toVm(vm, null, null, null)); + action.setJob(Ref.of(basePath + JobsRouteHandler.BASE_ROUTE + vo.getUuid(), vo.getUuid())); + action.setStatus("complete"); + return action; + } } diff --git a/server/src/main/java/com/cloud/api/ApiServer.java b/server/src/main/java/com/cloud/api/ApiServer.java index dc07814c972..1bda053ec19 100644 --- a/server/src/main/java/com/cloud/api/ApiServer.java +++ b/server/src/main/java/com/cloud/api/ApiServer.java @@ -16,6 +16,10 @@ // under the License. package com.cloud.api; +import static com.cloud.user.AccountManagerImpl.apiKeyAccess; +import static org.apache.cloudstack.api.ApiConstants.PASSWORD_CHANGE_REQUIRED; +import static org.apache.cloudstack.user.UserPasswordResetManager.UserPasswordResetEnabled; + import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InterruptedIOException; @@ -31,6 +35,7 @@ import java.security.SecureRandom; import java.security.Security; import java.text.ParseException; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.EnumSet; @@ -39,7 +44,6 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; -import java.util.Arrays; import java.util.Map; import java.util.Set; import java.util.TimeZone; @@ -58,16 +62,6 @@ import javax.naming.ConfigurationException; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; -import com.cloud.cluster.ManagementServerHostVO; -import com.cloud.cluster.dao.ManagementServerHostDao; -import com.cloud.utils.Ternary; -import com.cloud.user.Account; -import com.cloud.user.AccountManager; -import com.cloud.user.AccountManagerImpl; -import com.cloud.user.DomainManager; -import com.cloud.user.User; -import com.cloud.user.UserAccount; -import com.cloud.user.UserVO; import org.apache.cloudstack.acl.APIChecker; import org.apache.cloudstack.acl.ApiKeyPairManagerImpl; import org.apache.cloudstack.acl.apikeypair.ApiKeyPair; @@ -161,6 +155,8 @@ import org.springframework.stereotype.Component; import com.cloud.api.dispatch.DispatchChainFactory; import com.cloud.api.dispatch.DispatchTask; import com.cloud.api.response.ApiResponseSerializer; +import com.cloud.cluster.ManagementServerHostVO; +import com.cloud.cluster.dao.ManagementServerHostDao; import com.cloud.domain.Domain; import com.cloud.domain.DomainVO; import com.cloud.domain.dao.DomainDao; @@ -179,14 +175,22 @@ import com.cloud.exception.ResourceUnavailableException; import com.cloud.exception.UnavailableCommandException; import com.cloud.projects.dao.ProjectDao; import com.cloud.storage.VolumeApiService; +import com.cloud.user.Account; +import com.cloud.user.AccountManager; +import com.cloud.user.AccountManagerImpl; +import com.cloud.user.DomainManager; +import com.cloud.user.User; +import com.cloud.user.UserAccount; +import com.cloud.user.UserVO; import com.cloud.utils.ConstantTimeComparator; import com.cloud.utils.DateUtil; import com.cloud.utils.HttpUtils; -import com.cloud.utils.HttpUtils.ApiSessionKeySameSite; import com.cloud.utils.HttpUtils.ApiSessionKeyCheckOption; +import com.cloud.utils.HttpUtils.ApiSessionKeySameSite; import com.cloud.utils.Pair; import com.cloud.utils.ReflectUtil; import com.cloud.utils.StringUtils; +import com.cloud.utils.Ternary; import com.cloud.utils.component.ComponentContext; import com.cloud.utils.component.ManagerBase; import com.cloud.utils.component.PluggableService; @@ -199,10 +203,6 @@ import com.cloud.utils.exception.ExceptionProxyObject; import com.cloud.utils.net.NetUtils; import com.google.gson.reflect.TypeToken; -import static com.cloud.user.AccountManagerImpl.apiKeyAccess; -import static org.apache.cloudstack.api.ApiConstants.PASSWORD_CHANGE_REQUIRED; -import static org.apache.cloudstack.user.UserPasswordResetManager.UserPasswordResetEnabled; - @Component public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiServerService, Configurable { private static final Logger ACCESSLOGGER = LogManager.getLogger("apiserver." + ApiServer.class.getName()); @@ -792,85 +792,14 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer // BaseAsyncCreateCmd: cmd params are processed and create() is called, then same workflow as BaseAsyncCmd. // BaseAsyncCmd: cmd is processed and submitted as an AsyncJob, job related info is serialized and returned. if (cmdObj instanceof BaseAsyncCmd) { - if (!asyncMgr.isAsyncJobsEnabled()) { - String msg = "Maintenance or Shutdown has been initiated on this management server. Can not accept new jobs"; - logger.warn(msg); - throw new ServerApiException(ApiErrorCode.SERVICE_UNAVAILABLE, msg); - } - Long objectId = null; - String objectUuid; - if (cmdObj instanceof BaseAsyncCreateCmd) { - final BaseAsyncCreateCmd createCmd = (BaseAsyncCreateCmd)cmdObj; - dispatcher.dispatchCreateCmd(createCmd, params); - objectId = createCmd.getEntityId(); - objectUuid = createCmd.getEntityUuid(); - params.put("id", objectId.toString()); - Class entityClass = EventTypes.getEntityClassForEvent(createCmd.getEventType()); - if (entityClass != null) - ctx.putContextParameter(entityClass, objectUuid); - } else { - // Extract the uuid before params are processed and id reflects internal db id - objectUuid = params.get(ApiConstants.ID); - dispatchChainFactory.getStandardDispatchChain().dispatch(new DispatchTask(cmdObj, params)); - } - - final BaseAsyncCmd asyncCmd = (BaseAsyncCmd)cmdObj; - - if (callerUserId != null) { - params.put("ctxUserId", callerUserId.toString()); - } - if (caller != null) { - params.put("ctxAccountId", String.valueOf(caller.getId())); - } - if (objectUuid != null) { - params.put("uuid", objectUuid); - } - - long startEventId = ctx.getStartEventId(); - asyncCmd.setStartEventId(startEventId); - - // save the scheduled event - final Long eventId = - ActionEventUtils.onScheduledActionEvent((callerUserId == null) ? (Long)User.UID_SYSTEM : callerUserId, asyncCmd.getEntityOwnerId(), asyncCmd.getEventType(), - asyncCmd.getEventDescription(), asyncCmd.getApiResourceId(), asyncCmd.getApiResourceType().toString(), asyncCmd.isDisplay(), startEventId); - if (startEventId == 0) { - // There was no create event before, set current event id as start eventId - startEventId = eventId; - } - - params.put("ctxStartEventId", String.valueOf(startEventId)); - params.put("cmdEventType", asyncCmd.getEventType()); - params.put("ctxDetails", ApiGsonHelper.getBuilder().create().toJson(ctx.getContextParameters())); - if (asyncCmd.getHttpMethod() != null) { - params.put(ApiConstants.HTTPMETHOD, asyncCmd.getHttpMethod().toString()); - } - - Long instanceId = (objectId == null) ? asyncCmd.getApiResourceId() : objectId; - - // users can provide the job id they want to use, so log as it is a uuid and is unique - String injectedJobId = asyncCmd.getInjectedJobId(); - uuidMgr.checkUuidSimple(injectedJobId, AsyncJob.class); - - AsyncJobVO job = new AsyncJobVO("", callerUserId, caller.getId(), cmdObj.getClass().getName(), - ApiGsonHelper.getBuilder().create().toJson(params), instanceId, - asyncCmd.getApiResourceType() != null ? asyncCmd.getApiResourceType().toString() : null, - injectedJobId); - job.setDispatcher(asyncDispatcher.getName()); - - final long jobId = asyncMgr.submitAsyncJob(job); - - if (jobId == 0L) { - final String errorMsg = "Unable to schedule async job for command " + job.getCmd(); - logger.warn(errorMsg); - throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, errorMsg); - } + AsyncCmdResult result = processAsyncCmd((BaseAsyncCmd)cmdObj, params, ctx, callerUserId, caller); final String response; - if (objectId != null) { - final String objUuid = (objectUuid == null) ? objectId.toString() : objectUuid; - response = getBaseAsyncCreateResponse(jobId, (BaseAsyncCreateCmd)asyncCmd, objUuid); + if (result.objectId != null) { + final String objUuid = (result.objectUuid == null) ? result.objectId.toString() : result.objectUuid; + response = getBaseAsyncCreateResponse(result.jobId, (BaseAsyncCreateCmd) result.asyncCmd, objUuid); } else { SerializationContext.current().setUuidTranslation(true); - response = getBaseAsyncResponse(jobId, asyncCmd); + response = getBaseAsyncResponse(result.jobId, result.asyncCmd); } // Always log response for async for now, I don't think any sensitive data will be in here. // It might be nice to send this through scrubbing similar to how @@ -900,6 +829,81 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer } } + @Override + public AsyncCmdResult processAsyncCmd(BaseAsyncCmd asyncCmd, Map params, CallContext ctx, Long callerUserId, Account caller) throws Exception { + if (!asyncMgr.isAsyncJobsEnabled()) { + String msg = "Maintenance or Shutdown has been initiated on this management server. Can not accept new jobs"; + logger.warn(msg); + throw new ServerApiException(ApiErrorCode.SERVICE_UNAVAILABLE, msg); + } + Long objectId = null; + String objectUuid; + if (asyncCmd instanceof BaseAsyncCreateCmd) { + final BaseAsyncCreateCmd createCmd = (BaseAsyncCreateCmd) asyncCmd; + dispatcher.dispatchCreateCmd(createCmd, params); + objectId = createCmd.getEntityId(); + objectUuid = createCmd.getEntityUuid(); + params.put("id", objectId.toString()); + Class entityClass = EventTypes.getEntityClassForEvent(createCmd.getEventType()); + if (entityClass != null) + ctx.putContextParameter(entityClass, objectUuid); + } else { + // Extract the uuid before params are processed and id reflects internal db id + objectUuid = params.get(ApiConstants.ID); + dispatchChainFactory.getStandardDispatchChain().dispatch(new DispatchTask(asyncCmd, params)); + } + + if (callerUserId != null) { + params.put("ctxUserId", callerUserId.toString()); + } + if (caller != null) { + params.put("ctxAccountId", String.valueOf(caller.getId())); + } + if (objectUuid != null) { + params.put("uuid", objectUuid); + } + + long startEventId = ctx.getStartEventId(); + asyncCmd.setStartEventId(startEventId); + + // save the scheduled event + final Long eventId = + ActionEventUtils.onScheduledActionEvent((callerUserId == null) ? (Long)User.UID_SYSTEM : callerUserId, asyncCmd.getEntityOwnerId(), asyncCmd.getEventType(), + asyncCmd.getEventDescription(), asyncCmd.getApiResourceId(), asyncCmd.getApiResourceType().toString(), asyncCmd.isDisplay(), startEventId); + if (startEventId == 0) { + // There was no create event before, set current event id as start eventId + startEventId = eventId; + } + + params.put("ctxStartEventId", String.valueOf(startEventId)); + params.put("cmdEventType", asyncCmd.getEventType()); + params.put("ctxDetails", ApiGsonHelper.getBuilder().create().toJson(ctx.getContextParameters())); + if (asyncCmd.getHttpMethod() != null) { + params.put(ApiConstants.HTTPMETHOD, asyncCmd.getHttpMethod().toString()); + } + + Long instanceId = (objectId == null) ? asyncCmd.getApiResourceId() : objectId; + + // users can provide the job id they want to use, so log as it is a uuid and is unique + String injectedJobId = asyncCmd.getInjectedJobId(); + uuidMgr.checkUuidSimple(injectedJobId, AsyncJob.class); + + AsyncJobVO job = new AsyncJobVO("", callerUserId, caller.getId(), asyncCmd.getClass().getName(), + ApiGsonHelper.getBuilder().create().toJson(params), instanceId, + asyncCmd.getApiResourceType() != null ? asyncCmd.getApiResourceType().toString() : null, + injectedJobId); + job.setDispatcher(asyncDispatcher.getName()); + + final long jobId = asyncMgr.submitAsyncJob(job); + + if (jobId == 0L) { + final String errorMsg = "Unable to schedule async job for command " + job.getCmd(); + logger.warn(errorMsg); + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, errorMsg); + } + return new AsyncCmdResult(objectId, objectUuid, asyncCmd, jobId); + } + @SuppressWarnings("unchecked") private void buildAsyncListResponse(final BaseListCmd command, final Account account) { final List responses = ((ListResponse)command.getResponseObject()).getResponses();