Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>
This commit is contained in:
Abhishek Kumar 2026-02-10 09:38:32 +05:30
parent fba7c634cb
commit a89f872b4f
5 changed files with 245 additions and 129 deletions

View File

@ -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<String, Object[]> params);
boolean isPostRequestsAndTimestampsEnforced();
AsyncCmdResult processAsyncCmd(BaseAsyncCmd cmdObj, Map<String, String> 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;
}
}
}

View File

@ -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<String, Long> 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<AccountVO> 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<User, Account> 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<User, Account> 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<User, Account> 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<String, String> 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<User, Account> 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<String, String> 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<User, Account> 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<String, String> 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<User, Account> 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<User, Account> 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<User, Account> 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<User, Account> 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);
}
}

View File

@ -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);

View File

@ -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;
}
}

View File

@ -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<String, String> 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<ResponseObject> responses = ((ListResponse<ResponseObject>)command.getResponseObject()).getResponses();