mirror of
https://github.com/apache/cloudstack.git
synced 2025-11-02 11:52:28 +01:00
Dispatcher corrections, refactoring and tests
Corrects problems from previous attempt. Fixes based on help comments from the community and conflict resolution Signed-off-by: Daan Hoogland <daan@onecht.net>
This commit is contained in:
parent
4552ec6322
commit
c211f0bbbe
@ -23,7 +23,8 @@ public class ApiConstants {
|
||||
public static final String ACCOUNT_ID = "accountid";
|
||||
public static final String ALGORITHM = "algorithm";
|
||||
public static final String ALLOCATED_ONLY = "allocatedonly";
|
||||
public static final String API_KEY = "userapikey";
|
||||
public static final String API_KEY = "apikey";
|
||||
public static final String USER_API_KEY = "userapikey";
|
||||
public static final String APPLIED = "applied";
|
||||
public static final String AVAILABLE = "available";
|
||||
public static final String BITS = "bits";
|
||||
@ -49,10 +50,16 @@ public class ApiConstants {
|
||||
public static final String CLUSTER_ID = "clusterid";
|
||||
public static final String CLUSTER_NAME = "clustername";
|
||||
public static final String CLUSTER_TYPE = "clustertype";
|
||||
public static final String COMMAND = "command";
|
||||
public static final String CMD_EVENT_TYPE = "cmdeventtype";
|
||||
public static final String COMPONENT = "component";
|
||||
public static final String CPU_NUMBER = "cpunumber";
|
||||
public static final String CPU_SPEED = "cpuspeed";
|
||||
public static final String CREATED = "created";
|
||||
public static final String CTX_ACCOUNT_ID = "ctxaccountid";
|
||||
public static final String CTX_USER_ID = "ctxuserid";
|
||||
public static final String CTXSTARTEVENTID = "ctxstarteventid";
|
||||
public static final String CTX_START_EVENT_ID = "ctxStartEventId";
|
||||
public static final String CUSTOMIZED = "customized";
|
||||
public static final String CUSTOMIZED_IOPS = "customizediops";
|
||||
public static final String CUSTOM_ID = "customid";
|
||||
@ -79,6 +86,7 @@ public class ApiConstants {
|
||||
public static final String IP6_DNS2 = "ip6dns2";
|
||||
public static final String DOMAIN = "domain";
|
||||
public static final String DOMAIN_ID = "domainid";
|
||||
public static final String DOMAIN__ID = "domainId";
|
||||
public static final String DURATION = "duration";
|
||||
public static final String EMAIL = "email";
|
||||
public static final String END_DATE = "enddate";
|
||||
@ -86,6 +94,7 @@ public class ApiConstants {
|
||||
public static final String END_IPV6 = "endipv6";
|
||||
public static final String END_PORT = "endport";
|
||||
public static final String ENTRY_TIME = "entrytime";
|
||||
public static final String EXPIRES = "expires";
|
||||
public static final String FETCH_LATEST = "fetchlatest";
|
||||
public static final String FIRSTNAME = "firstname";
|
||||
public static final String FORCED = "forced";
|
||||
@ -209,8 +218,11 @@ public class ApiConstants {
|
||||
public static final String SENT = "sent";
|
||||
public static final String SENT_BYTES = "sentbytes";
|
||||
public static final String SERVICE_OFFERING_ID = "serviceofferingid";
|
||||
public static final String SESSIONKEY = "sessionkey";
|
||||
public static final String SHOW_CAPACITIES = "showcapacities";
|
||||
public static final String SHOW_REMOVED = "showremoved";
|
||||
public static final String SIGNATURE = "signature";
|
||||
public static final String SIGNATURE_VERSION = "signatureversion";
|
||||
public static final String SIZE = "size";
|
||||
public static final String SNAPSHOT_ID = "snapshotid";
|
||||
public static final String SNAPSHOT_POLICY_ID = "snapshotpolicyid";
|
||||
@ -277,6 +289,7 @@ public class ApiConstants {
|
||||
public static final String NETWORKRATE = "networkrate";
|
||||
public static final String HOST_TAGS = "hosttags";
|
||||
public static final String SSH_KEYPAIR = "keypair";
|
||||
public static final String HTTPMETHOD = "httpmethod";
|
||||
public static final String HOST_CPU_CAPACITY = "hostcpucapacity";
|
||||
public static final String HOST_CPU_NUM = "hostcpunum";
|
||||
public static final String HOST_MEM_CAPACITY = "hostmemcapacity";
|
||||
|
||||
@ -17,22 +17,28 @@
|
||||
|
||||
package org.apache.cloudstack.api;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import org.apache.cloudstack.acl.RoleType;
|
||||
import org.apache.cloudstack.affinity.AffinityGroupService;
|
||||
import org.apache.cloudstack.alert.AlertService;
|
||||
import org.apache.cloudstack.context.CallContext;
|
||||
import org.apache.cloudstack.network.element.InternalLoadBalancerElementService;
|
||||
import org.apache.cloudstack.network.lb.ApplicationLoadBalancerService;
|
||||
import org.apache.cloudstack.network.lb.InternalLoadBalancerVMService;
|
||||
import org.apache.cloudstack.query.QueryService;
|
||||
import org.apache.cloudstack.usage.UsageService;
|
||||
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import com.cloud.configuration.ConfigurationService;
|
||||
@ -74,6 +80,7 @@ import com.cloud.user.Account;
|
||||
import com.cloud.user.AccountService;
|
||||
import com.cloud.user.DomainService;
|
||||
import com.cloud.user.ResourceLimitService;
|
||||
import com.cloud.utils.ReflectUtil;
|
||||
import com.cloud.utils.db.EntityManager;
|
||||
import com.cloud.utils.db.UUIDManager;
|
||||
import com.cloud.vm.UserVmService;
|
||||
@ -97,7 +104,9 @@ public abstract class BaseCmd {
|
||||
public static Pattern newInputDateFormat = Pattern.compile("[\\d]+-[\\d]+-[\\d]+ [\\d]+:[\\d]+:[\\d]+");
|
||||
private static final DateFormat s_outputFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ");
|
||||
|
||||
private Object _responseObject = null;
|
||||
protected static final Map<Class<?>, List<Field>> fieldsForCmdClass = new HashMap<Class<?>, List<Field>>();
|
||||
|
||||
private Object _responseObject;
|
||||
private Map<String, String> fullUrlParams;
|
||||
|
||||
public enum HTTPMethod {
|
||||
@ -205,7 +214,7 @@ public abstract class BaseCmd {
|
||||
return httpMethod;
|
||||
}
|
||||
|
||||
public void setHttpMethod(String method) {
|
||||
public void setHttpMethod(final String method) {
|
||||
if (method != null) {
|
||||
if (method.equalsIgnoreCase("GET"))
|
||||
httpMethod = HTTPMethod.GET;
|
||||
@ -227,12 +236,36 @@ public abstract class BaseCmd {
|
||||
return responseType;
|
||||
}
|
||||
|
||||
public void setResponseType(String responseType) {
|
||||
public void setResponseType(final String responseType) {
|
||||
this.responseType = responseType;
|
||||
}
|
||||
|
||||
/**
|
||||
* For some reason this method does not return the actual command name, but more a name that
|
||||
* is used to create the response. So you can expect for a XCmd a value like xcmdresponse. Anyways
|
||||
* this methods is used in too many places so for now instead of changing it we just create another
|
||||
* method {@link BaseCmd#getActualCommandName()} that returns the value from {@link APICommand#name()}
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public abstract String getCommandName();
|
||||
|
||||
|
||||
/**
|
||||
* Gets the CommandName based on the class annotations: the value from {@link APICommand#name()}
|
||||
*
|
||||
* @return the value from {@link APICommand#name()}
|
||||
*/
|
||||
public String getActualCommandName() {
|
||||
String cmdName = null;
|
||||
if (this.getClass().getAnnotation(APICommand.class) != null) {
|
||||
cmdName = this.getClass().getAnnotation(APICommand.class).name();
|
||||
} else {
|
||||
cmdName = this.getClass().getName();
|
||||
}
|
||||
return cmdName;
|
||||
}
|
||||
|
||||
/**
|
||||
* For commands the API framework needs to know the owner of the object being acted upon. This method is
|
||||
* used to determine that information.
|
||||
@ -245,7 +278,7 @@ public abstract class BaseCmd {
|
||||
return _responseObject;
|
||||
}
|
||||
|
||||
public void setResponseObject(Object responseObject) {
|
||||
public void setResponseObject(final Object responseObject) {
|
||||
_responseObject = responseObject;
|
||||
}
|
||||
|
||||
@ -253,7 +286,7 @@ public abstract class BaseCmd {
|
||||
return _mgr;
|
||||
}
|
||||
|
||||
public static String getDateString(Date date) {
|
||||
public static String getDateString(final Date date) {
|
||||
if (date == null) {
|
||||
return "";
|
||||
}
|
||||
@ -264,101 +297,86 @@ public abstract class BaseCmd {
|
||||
return formattedString;
|
||||
}
|
||||
|
||||
// FIXME: move this to a utils method so that maps can be unpacked and integer/long values can be appropriately cast
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
public Map<String, Object> unpackParams(Map<String, String> params) {
|
||||
Map<String, Object> lowercaseParams = new HashMap<String, Object>();
|
||||
for (String key : params.keySet()) {
|
||||
int arrayStartIndex = key.indexOf('[');
|
||||
int arrayStartLastIndex = key.lastIndexOf('[');
|
||||
if (arrayStartIndex != arrayStartLastIndex) {
|
||||
throw new ServerApiException(ApiErrorCode.MALFORMED_PARAMETER_ERROR, "Unable to decode parameter " + key +
|
||||
"; if specifying an object array, please use parameter[index].field=XXX, e.g. userGroupList[0].group=httpGroup");
|
||||
protected List<Field> getAllFieldsForClass(final Class<?> clazz) {
|
||||
List<Field> filteredFields = fieldsForCmdClass.get(clazz);
|
||||
|
||||
// If list of fields was not cached yet
|
||||
if (filteredFields == null) {
|
||||
final List<Field> allFields = ReflectUtil.getAllFieldsForClass(this.getClass(), BaseCmd.class);
|
||||
filteredFields = new ArrayList<Field>();
|
||||
|
||||
for (final Field field : allFields) {
|
||||
final Parameter parameterAnnotation = field.getAnnotation(Parameter.class);
|
||||
if ((parameterAnnotation != null) && parameterAnnotation.expose()) {
|
||||
filteredFields.add(field);
|
||||
}
|
||||
}
|
||||
|
||||
if (arrayStartIndex > 0) {
|
||||
int arrayEndIndex = key.indexOf(']');
|
||||
int arrayEndLastIndex = key.lastIndexOf(']');
|
||||
if ((arrayEndIndex < arrayStartIndex) || (arrayEndIndex != arrayEndLastIndex)) {
|
||||
// malformed parameter
|
||||
throw new ServerApiException(ApiErrorCode.MALFORMED_PARAMETER_ERROR, "Unable to decode parameter " + key +
|
||||
"; if specifying an object array, please use parameter[index].field=XXX, e.g. userGroupList[0].group=httpGroup");
|
||||
}
|
||||
|
||||
// Now that we have an array object, check for a field name in the case of a complex object
|
||||
int fieldIndex = key.indexOf('.');
|
||||
String fieldName = null;
|
||||
if (fieldIndex < arrayEndIndex) {
|
||||
throw new ServerApiException(ApiErrorCode.MALFORMED_PARAMETER_ERROR, "Unable to decode parameter " + key +
|
||||
"; if specifying an object array, please use parameter[index].field=XXX, e.g. userGroupList[0].group=httpGroup");
|
||||
} else {
|
||||
fieldName = key.substring(fieldIndex + 1);
|
||||
}
|
||||
|
||||
// parse the parameter name as the text before the first '[' character
|
||||
String paramName = key.substring(0, arrayStartIndex);
|
||||
paramName = paramName.toLowerCase();
|
||||
|
||||
Map<Integer, Map> mapArray = null;
|
||||
Map<String, Object> mapValue = null;
|
||||
String indexStr = key.substring(arrayStartIndex + 1, arrayEndIndex);
|
||||
int index = 0;
|
||||
boolean parsedIndex = false;
|
||||
try {
|
||||
if (indexStr != null) {
|
||||
index = Integer.parseInt(indexStr);
|
||||
parsedIndex = true;
|
||||
}
|
||||
} catch (NumberFormatException nfe) {
|
||||
s_logger.warn("Invalid parameter " + key + " received, unable to parse object array, returning an error.");
|
||||
}
|
||||
|
||||
if (!parsedIndex) {
|
||||
throw new ServerApiException(ApiErrorCode.MALFORMED_PARAMETER_ERROR, "Unable to decode parameter " + key +
|
||||
"; if specifying an object array, please use parameter[index].field=XXX, e.g. userGroupList[0].group=httpGroup");
|
||||
}
|
||||
|
||||
Object value = lowercaseParams.get(paramName);
|
||||
if (value == null) {
|
||||
// for now, assume object array with sub fields
|
||||
mapArray = new HashMap<Integer, Map>();
|
||||
mapValue = new HashMap<String, Object>();
|
||||
mapArray.put(Integer.valueOf(index), mapValue);
|
||||
} else if (value instanceof Map) {
|
||||
mapArray = (HashMap)value;
|
||||
mapValue = mapArray.get(Integer.valueOf(index));
|
||||
if (mapValue == null) {
|
||||
mapValue = new HashMap<String, Object>();
|
||||
mapArray.put(Integer.valueOf(index), mapValue);
|
||||
}
|
||||
}
|
||||
|
||||
// we are ready to store the value for a particular field into the map for this object
|
||||
mapValue.put(fieldName, params.get(key));
|
||||
|
||||
lowercaseParams.put(paramName, mapArray);
|
||||
} else {
|
||||
lowercaseParams.put(key.toLowerCase(), params.get(key));
|
||||
}
|
||||
// Cache the prepared list for future use
|
||||
fieldsForCmdClass.put(clazz, filteredFields);
|
||||
}
|
||||
return lowercaseParams;
|
||||
return filteredFields;
|
||||
}
|
||||
|
||||
protected long getInstanceIdFromJobSuccessResult(String result) {
|
||||
protected Account getCurrentContextAccount() {
|
||||
return CallContext.current().getCallingAccount();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method doesn't return all the @{link Parameter}, but only the ones exposed
|
||||
* and allowed for current @{link RoleType}. This method will get the fields for a given
|
||||
* Cmd class only once and never again, so in case of a dynamic update the result would
|
||||
* be obsolete (this might be a plugin update. It is agreed upon that we will not do
|
||||
* upgrades dynamically but in case we come back on that decision we need to revisit this)
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public List<Field> getParamFields() {
|
||||
final List<Field> allFields = getAllFieldsForClass(this.getClass());
|
||||
final List<Field> validFields = new ArrayList<Field>();
|
||||
final Account caller = getCurrentContextAccount();
|
||||
|
||||
for (final Field field : allFields) {
|
||||
final Parameter parameterAnnotation = field.getAnnotation(Parameter.class);
|
||||
|
||||
//TODO: Annotate @Validate on API Cmd classes, FIXME how to process Validate
|
||||
final RoleType[] allowedRoles = parameterAnnotation.authorized();
|
||||
boolean roleIsAllowed = true;
|
||||
if (allowedRoles.length > 0) {
|
||||
roleIsAllowed = false;
|
||||
for (final RoleType allowedRole : allowedRoles) {
|
||||
if (allowedRole.getValue() == caller.getType()) {
|
||||
roleIsAllowed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (roleIsAllowed) {
|
||||
validFields.add(field);
|
||||
} else {
|
||||
s_logger.debug("Ignoring paremeter " + parameterAnnotation.name() + " as the caller is not authorized to pass it in");
|
||||
}
|
||||
}
|
||||
|
||||
return validFields;
|
||||
}
|
||||
|
||||
protected long getInstanceIdFromJobSuccessResult(final String result) {
|
||||
s_logger.debug("getInstanceIdFromJobSuccessResult not overridden in subclass " + this.getClass().getName());
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static boolean isAdmin(short accountType) {
|
||||
public static boolean isAdmin(final short accountType) {
|
||||
return ((accountType == Account.ACCOUNT_TYPE_ADMIN) || (accountType == Account.ACCOUNT_TYPE_RESOURCE_DOMAIN_ADMIN) ||
|
||||
(accountType == Account.ACCOUNT_TYPE_DOMAIN_ADMIN) || (accountType == Account.ACCOUNT_TYPE_READ_ONLY_ADMIN));
|
||||
}
|
||||
|
||||
public static boolean isRootAdmin(short accountType) {
|
||||
public static boolean isRootAdmin(final short accountType) {
|
||||
return ((accountType == Account.ACCOUNT_TYPE_ADMIN));
|
||||
}
|
||||
|
||||
public void setFullUrlParams(Map<String, String> map) {
|
||||
public void setFullUrlParams(final Map<String, String> map) {
|
||||
fullUrlParams = map;
|
||||
}
|
||||
|
||||
@ -366,18 +384,18 @@ public abstract class BaseCmd {
|
||||
return fullUrlParams;
|
||||
}
|
||||
|
||||
public Long finalyzeAccountId(String accountName, Long domainId, Long projectId, boolean enabledOnly) {
|
||||
public Long finalyzeAccountId(final String accountName, final Long domainId, final Long projectId, final boolean enabledOnly) {
|
||||
if (accountName != null) {
|
||||
if (domainId == null) {
|
||||
throw new InvalidParameterValueException("Account must be specified with domainId parameter");
|
||||
}
|
||||
|
||||
Domain domain = _domainService.getDomain(domainId);
|
||||
final Domain domain = _domainService.getDomain(domainId);
|
||||
if (domain == null) {
|
||||
throw new InvalidParameterValueException("Unable to find domain by id");
|
||||
}
|
||||
|
||||
Account account = _accountService.getActiveAccountByName(accountName, domainId);
|
||||
final Account account = _accountService.getActiveAccountByName(accountName, domainId);
|
||||
if (account != null && account.getType() != Account.ACCOUNT_TYPE_PROJECT) {
|
||||
if (!enabledOnly || account.getState() == Account.State.enabled) {
|
||||
return account.getId();
|
||||
@ -394,12 +412,12 @@ public abstract class BaseCmd {
|
||||
}
|
||||
|
||||
if (projectId != null) {
|
||||
Project project = _projectService.getProject(projectId);
|
||||
final Project project = _projectService.getProject(projectId);
|
||||
if (project != null) {
|
||||
if (!enabledOnly || project.getState() == Project.State.Active) {
|
||||
return project.getProjectAccountId();
|
||||
} else {
|
||||
PermissionDeniedException ex =
|
||||
final PermissionDeniedException ex =
|
||||
new PermissionDeniedException("Can't add resources to the project with specified projectId in state=" + project.getState() +
|
||||
" as it's no longer active");
|
||||
ex.addProxyObject(project.getUuid(), "projectId");
|
||||
@ -412,6 +430,13 @@ public abstract class BaseCmd {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* To be overwritten by any class who needs specific validation
|
||||
*/
|
||||
public void validateSpecificParameters(final Map<String, String> params){
|
||||
// To be overwritten by any class who needs specific validation
|
||||
}
|
||||
|
||||
/**
|
||||
* display flag is used to control the display of the resource only to the end user. It doesnt affect Root Admin.
|
||||
* @return display flag
|
||||
|
||||
@ -16,7 +16,10 @@
|
||||
// under the License.
|
||||
package org.apache.cloudstack.api;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import com.cloud.exception.InvalidParameterValueException;
|
||||
import com.cloud.utils.exception.CSExceptionErrorCode;
|
||||
|
||||
public abstract class BaseListCmd extends BaseCmd {
|
||||
|
||||
@ -83,7 +86,7 @@ public abstract class BaseListCmd extends BaseCmd {
|
||||
|
||||
public Long getPageSizeVal() {
|
||||
Long defaultPageSize = s_maxPageSize;
|
||||
Integer pageSizeInt = getPageSize();
|
||||
final Integer pageSizeInt = getPageSize();
|
||||
if (pageSizeInt != null) {
|
||||
defaultPageSize = pageSizeInt.longValue();
|
||||
}
|
||||
@ -96,12 +99,12 @@ public abstract class BaseListCmd extends BaseCmd {
|
||||
|
||||
public Long getStartIndex() {
|
||||
Long startIndex = Long.valueOf(0);
|
||||
Long pageSizeVal = getPageSizeVal();
|
||||
final Long pageSizeVal = getPageSizeVal();
|
||||
|
||||
if (pageSizeVal == null) {
|
||||
startIndex = null;
|
||||
} else if (page != null) {
|
||||
int pageNum = page.intValue();
|
||||
final int pageNum = page.intValue();
|
||||
if (pageNum > 0) {
|
||||
startIndex = Long.valueOf(pageSizeVal * (pageNum - 1));
|
||||
}
|
||||
@ -112,4 +115,25 @@ public abstract class BaseListCmd extends BaseCmd {
|
||||
public ApiCommandJobType getInstanceType() {
|
||||
return ApiCommandJobType.None;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateSpecificParameters(final Map<String, String> params){
|
||||
super.validateSpecificParameters(params);
|
||||
|
||||
final Object pageSizeObj = params.get(ApiConstants.PAGE_SIZE);
|
||||
Long pageSize = null;
|
||||
if (pageSizeObj != null) {
|
||||
pageSize = Long.valueOf((String)pageSizeObj);
|
||||
}
|
||||
|
||||
if (params.get(ApiConstants.PAGE) == null &&
|
||||
pageSize != null &&
|
||||
!pageSize.equals(BaseListCmd.s_pageSizeUnlimited)) {
|
||||
final ServerApiException ex = new ServerApiException(ApiErrorCode.PARAM_ERROR, "\"page\" parameter is required when \"pagesize\" is specified");
|
||||
ex.setCSErrorCode(CSExceptionErrorCode.getCSErrCode(ex.getClass().getName()));
|
||||
throw ex;
|
||||
} else if (pageSize == null && (params.get(ApiConstants.PAGE) != null)) {
|
||||
throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "\"pagesize\" parameter is required when \"page\" is specified");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
69
api/test/org/apache/cloudstack/api/BaseCmdTest.java
Normal file
69
api/test/org/apache/cloudstack/api/BaseCmdTest.java
Normal file
@ -0,0 +1,69 @@
|
||||
// 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 org.apache.cloudstack.api;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import com.cloud.exception.ConcurrentOperationException;
|
||||
import com.cloud.exception.InsufficientCapacityException;
|
||||
import com.cloud.exception.NetworkRuleConflictException;
|
||||
import com.cloud.exception.ResourceAllocationException;
|
||||
import com.cloud.exception.ResourceUnavailableException;
|
||||
|
||||
public class BaseCmdTest {
|
||||
|
||||
private static final String NON_EXPECTED_COMMAND_NAME = "Non expected command name";
|
||||
protected static final String CMD1_NAME = "Cmd1Name";
|
||||
protected static final String CMD2_NAME = "Cmd2Name";
|
||||
protected static final String CMD1_RESPONSE = "cmd1response";
|
||||
protected static final String CMD2_RESPONSE = "cmd2response";
|
||||
|
||||
@Test
|
||||
public void testGetActualCommandName(){
|
||||
BaseCmd cmd1 = new Cmd1();
|
||||
BaseCmd cmd2 = new Cmd2();
|
||||
|
||||
assertEquals(NON_EXPECTED_COMMAND_NAME, CMD1_NAME, cmd1.getActualCommandName());
|
||||
assertEquals(NON_EXPECTED_COMMAND_NAME, CMD2_NAME, cmd2.getActualCommandName());
|
||||
}
|
||||
}
|
||||
|
||||
@APICommand(name=BaseCmdTest.CMD1_NAME, responseObject=BaseResponse.class)
|
||||
class Cmd1 extends BaseCmd {
|
||||
@Override
|
||||
public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException,
|
||||
NetworkRuleConflictException {
|
||||
}
|
||||
@Override
|
||||
public String getCommandName() {
|
||||
return BaseCmdTest.CMD1_RESPONSE;
|
||||
}
|
||||
@Override
|
||||
public long getEntityOwnerId() {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@APICommand(name=BaseCmdTest.CMD2_NAME, responseObject=BaseResponse.class)
|
||||
class Cmd2 extends Cmd1 {
|
||||
@Override
|
||||
public String getCommandName() {
|
||||
return BaseCmdTest.CMD2_RESPONSE;
|
||||
}
|
||||
}
|
||||
@ -50,6 +50,18 @@
|
||||
|
||||
<bean id="apiDispatcher" class="com.cloud.api.ApiDispatcher" />
|
||||
|
||||
<bean id="dispatchChainFactory" class="com.cloud.api.dispatch.DispatchChainFactory" />
|
||||
|
||||
<bean id="paramProcessWorker" class="com.cloud.api.dispatch.ParamProcessWorker" />
|
||||
|
||||
<bean id="paramUnpackWorker" class="com.cloud.api.dispatch.ParamUnpackWorker" />
|
||||
|
||||
<bean id="paramGenericValidationWorker" class="com.cloud.api.dispatch.ParamGenericValidationWorker" />
|
||||
|
||||
<bean id="specificCmdValidationWorker" class="com.cloud.api.dispatch.SpecificCmdValidationWorker" />
|
||||
|
||||
<bean id="commandCreationWorker" class="com.cloud.api.dispatch.CommandCreationWorker" />
|
||||
|
||||
<bean id="apiResponseHelper" class="com.cloud.api.ApiResponseHelper" />
|
||||
|
||||
<bean id="apiServer" class="com.cloud.api.ApiServer">
|
||||
|
||||
@ -16,127 +16,72 @@
|
||||
// under the License.
|
||||
package com.cloud.api;
|
||||
|
||||
import static org.apache.commons.lang.StringUtils.isNotBlank;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.text.DateFormat;
|
||||
import java.text.ParseException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.StringTokenizer;
|
||||
import java.util.regex.Matcher;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import org.apache.cloudstack.acl.ControlledEntity;
|
||||
import org.apache.cloudstack.acl.InfrastructureEntity;
|
||||
import org.apache.cloudstack.acl.RoleType;
|
||||
import org.apache.cloudstack.acl.SecurityChecker.AccessType;
|
||||
import org.apache.cloudstack.api.ACL;
|
||||
import org.apache.cloudstack.api.ApiConstants;
|
||||
import org.apache.cloudstack.api.ApiErrorCode;
|
||||
import org.apache.cloudstack.api.BaseAsyncCmd;
|
||||
import org.apache.cloudstack.api.BaseAsyncCreateCmd;
|
||||
import org.apache.cloudstack.api.BaseAsyncCustomIdCmd;
|
||||
import org.apache.cloudstack.api.BaseCmd;
|
||||
import org.apache.cloudstack.api.BaseCmd.CommandType;
|
||||
import org.apache.cloudstack.api.BaseCustomIdCmd;
|
||||
import org.apache.cloudstack.api.BaseListCmd;
|
||||
import org.apache.cloudstack.api.EntityReference;
|
||||
import org.apache.cloudstack.api.InternalIdentity;
|
||||
import org.apache.cloudstack.api.Parameter;
|
||||
import org.apache.cloudstack.api.ServerApiException;
|
||||
import org.apache.cloudstack.api.command.admin.resource.ArchiveAlertsCmd;
|
||||
import org.apache.cloudstack.api.command.admin.resource.DeleteAlertsCmd;
|
||||
import org.apache.cloudstack.api.command.user.event.ArchiveEventsCmd;
|
||||
import org.apache.cloudstack.api.command.user.event.DeleteEventsCmd;
|
||||
import org.apache.cloudstack.api.command.user.event.ListEventsCmd;
|
||||
import org.apache.cloudstack.context.CallContext;
|
||||
import org.apache.cloudstack.framework.jobs.AsyncJob;
|
||||
import org.apache.cloudstack.framework.jobs.AsyncJobManager;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import com.cloud.exception.InvalidParameterValueException;
|
||||
import com.cloud.user.Account;
|
||||
import com.cloud.user.AccountManager;
|
||||
import com.cloud.utils.DateUtil;
|
||||
import com.cloud.utils.ReflectUtil;
|
||||
import com.cloud.utils.db.EntityManager;
|
||||
import com.cloud.utils.exception.CSExceptionErrorCode;
|
||||
import com.cloud.utils.exception.CloudRuntimeException;
|
||||
import com.cloud.api.dispatch.DispatchChain;
|
||||
import com.cloud.api.dispatch.DispatchChainFactory;
|
||||
import com.cloud.api.dispatch.DispatchTask;
|
||||
|
||||
public class ApiDispatcher {
|
||||
private static final Logger s_logger = Logger.getLogger(ApiDispatcher.class.getName());
|
||||
|
||||
Long _createSnapshotQueueSizeLimit;
|
||||
@Inject
|
||||
AsyncJobManager _asyncMgr = null;
|
||||
@Inject
|
||||
AccountManager _accountMgr = null;
|
||||
@Inject
|
||||
EntityManager _entityMgr = null;
|
||||
|
||||
private static ApiDispatcher s_instance;
|
||||
@Inject
|
||||
AsyncJobManager _asyncMgr;
|
||||
|
||||
public static ApiDispatcher getInstance() {
|
||||
return s_instance;
|
||||
}
|
||||
@Inject()
|
||||
protected DispatchChainFactory dispatchChainFactory;
|
||||
|
||||
protected DispatchChain standardDispatchChain;
|
||||
|
||||
protected DispatchChain asyncCreationDispatchChain;
|
||||
|
||||
public ApiDispatcher() {
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
void init() {
|
||||
s_instance = this;
|
||||
public void setup() {
|
||||
standardDispatchChain = dispatchChainFactory.getStandardDispatchChain();
|
||||
asyncCreationDispatchChain = dispatchChainFactory.getAsyncCreationDispatchChain();
|
||||
}
|
||||
|
||||
public void setCreateSnapshotQueueSizeLimit(Long snapshotLimit) {
|
||||
public void setCreateSnapshotQueueSizeLimit(final Long snapshotLimit) {
|
||||
_createSnapshotQueueSizeLimit = snapshotLimit;
|
||||
}
|
||||
|
||||
public void dispatchCreateCmd(BaseAsyncCreateCmd cmd, Map<String, String> params) throws Exception {
|
||||
processParameters(cmd, params);
|
||||
|
||||
public void dispatchCreateCmd(final BaseAsyncCreateCmd cmd, final Map<String, String> params) throws Exception {
|
||||
asyncCreationDispatchChain.dispatch(new DispatchTask(cmd, params));
|
||||
CallContext.current().setEventDisplayEnabled(cmd.isDisplayResourceEnabled());
|
||||
cmd.create();
|
||||
|
||||
}
|
||||
|
||||
private void doAccessChecks(BaseCmd cmd, Map<Object, AccessType> entitiesToAccess) {
|
||||
Account caller = CallContext.current().getCallingAccount();
|
||||
Account owner = _accountMgr.getActiveAccountById(cmd.getEntityOwnerId());
|
||||
public void dispatch(final BaseCmd cmd, final Map<String, String> params, final boolean execute) throws Exception {
|
||||
// Let the chain of responsibility dispatch gradually
|
||||
standardDispatchChain.dispatch(new DispatchTask(cmd, params));
|
||||
|
||||
if (cmd instanceof BaseAsyncCreateCmd) {
|
||||
//check that caller can access the owner account.
|
||||
_accountMgr.checkAccess(caller, null, true, owner);
|
||||
}
|
||||
|
||||
if (!entitiesToAccess.isEmpty()) {
|
||||
//check that caller can access the owner account.
|
||||
_accountMgr.checkAccess(caller, null, true, owner);
|
||||
for (Object entity : entitiesToAccess.keySet()) {
|
||||
if (entity instanceof ControlledEntity) {
|
||||
_accountMgr.checkAccess(caller, entitiesToAccess.get(entity), true, (ControlledEntity)entity);
|
||||
} else if (entity instanceof InfrastructureEntity) {
|
||||
//FIXME: Move this code in adapter, remove code from Account manager
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void dispatch(BaseCmd cmd, Map<String, String> params, boolean execute) throws Exception {
|
||||
processParameters(cmd, params);
|
||||
CallContext ctx = CallContext.current();
|
||||
final CallContext ctx = CallContext.current();
|
||||
ctx.setEventDisplayEnabled(cmd.isDisplayResourceEnabled());
|
||||
|
||||
// TODO This if shouldn't be here. Use polymorphism and move it to validateSpecificParameters
|
||||
if (cmd instanceof BaseAsyncCmd) {
|
||||
|
||||
BaseAsyncCmd asyncCmd = (BaseAsyncCmd)cmd;
|
||||
String startEventId = params.get("ctxStartEventId");
|
||||
final BaseAsyncCmd asyncCmd = (BaseAsyncCmd)cmd;
|
||||
final String startEventId = params.get(ApiConstants.CTX_START_EVENT_ID);
|
||||
ctx.setStartEventId(Long.valueOf(startEventId));
|
||||
|
||||
// Synchronise job on the object if needed
|
||||
@ -160,6 +105,7 @@ public class ApiDispatcher {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO This if shouldn't be here. Use polymorphism and move it to validateSpecificParameters
|
||||
if (cmd instanceof BaseAsyncCustomIdCmd) {
|
||||
((BaseAsyncCustomIdCmd)cmd).checkUuid();
|
||||
} else if (cmd instanceof BaseCustomIdCmd) {
|
||||
@ -167,392 +113,6 @@ public class ApiDispatcher {
|
||||
}
|
||||
|
||||
cmd.execute();
|
||||
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
public static void processParameters(BaseCmd cmd, Map<String, String> params) {
|
||||
Map<Object, AccessType> entitiesToAccess = new HashMap<Object, AccessType>();
|
||||
Map<String, Object> unpackedParams = cmd.unpackParams(params);
|
||||
|
||||
if (cmd instanceof BaseListCmd) {
|
||||
Object pageSizeObj = unpackedParams.get(ApiConstants.PAGE_SIZE);
|
||||
Long pageSize = null;
|
||||
if (pageSizeObj != null) {
|
||||
pageSize = Long.valueOf((String)pageSizeObj);
|
||||
}
|
||||
|
||||
if ((unpackedParams.get(ApiConstants.PAGE) == null) && (pageSize != null && !pageSize.equals(BaseListCmd.s_pageSizeUnlimited))) {
|
||||
ServerApiException ex = new ServerApiException(ApiErrorCode.PARAM_ERROR, "\"page\" parameter is required when \"pagesize\" is specified");
|
||||
ex.setCSErrorCode(CSExceptionErrorCode.getCSErrCode(ex.getClass().getName()));
|
||||
throw ex;
|
||||
} else if (pageSize == null && (unpackedParams.get(ApiConstants.PAGE) != null)) {
|
||||
throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "\"pagesize\" parameter is required when \"page\" is specified");
|
||||
}
|
||||
}
|
||||
|
||||
List<Field> fields = ReflectUtil.getAllFieldsForClass(cmd.getClass(), BaseCmd.class);
|
||||
|
||||
for (Field field : fields) {
|
||||
Parameter parameterAnnotation = field.getAnnotation(Parameter.class);
|
||||
if ((parameterAnnotation == null) || !parameterAnnotation.expose()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
//TODO: Annotate @Validate on API Cmd classes, FIXME how to process Validate
|
||||
RoleType[] allowedRoles = parameterAnnotation.authorized();
|
||||
if (allowedRoles.length > 0) {
|
||||
boolean permittedParameter = false;
|
||||
Account caller = CallContext.current().getCallingAccount();
|
||||
for (RoleType allowedRole : allowedRoles) {
|
||||
if (allowedRole.getValue() == caller.getType()) {
|
||||
permittedParameter = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!permittedParameter) {
|
||||
s_logger.debug("Ignoring paremeter " + parameterAnnotation.name() + " as the caller is not authorized to pass it in");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
Object paramObj = unpackedParams.get(parameterAnnotation.name());
|
||||
if (paramObj == null) {
|
||||
if (parameterAnnotation.required()) {
|
||||
throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Unable to execute API command " +
|
||||
cmd.getCommandName().substring(0, cmd.getCommandName().length() - 8) + " due to missing parameter " + parameterAnnotation.name());
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// marshall the parameter into the correct type and set the field value
|
||||
try {
|
||||
setFieldValue(field, cmd, paramObj, parameterAnnotation);
|
||||
} catch (IllegalArgumentException argEx) {
|
||||
if (s_logger.isDebugEnabled()) {
|
||||
s_logger.debug("Unable to execute API command " + cmd.getCommandName() + " due to invalid value " + paramObj + " for parameter " +
|
||||
parameterAnnotation.name());
|
||||
}
|
||||
throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Unable to execute API command " +
|
||||
cmd.getCommandName().substring(0, cmd.getCommandName().length() - 8) + " due to invalid value " + paramObj + " for parameter " +
|
||||
parameterAnnotation.name());
|
||||
} catch (ParseException parseEx) {
|
||||
if (s_logger.isDebugEnabled()) {
|
||||
s_logger.debug("Invalid date parameter " + paramObj + " passed to command " + cmd.getCommandName().substring(0, cmd.getCommandName().length() - 8));
|
||||
}
|
||||
throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Unable to parse date " + paramObj + " for command " +
|
||||
cmd.getCommandName().substring(0, cmd.getCommandName().length() - 8) + ", please pass dates in the format mentioned in the api documentation");
|
||||
} catch (InvalidParameterValueException invEx) {
|
||||
throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Unable to execute API command " +
|
||||
cmd.getCommandName().substring(0, cmd.getCommandName().length() - 8) + " due to invalid value. " + invEx.getMessage());
|
||||
} catch (CloudRuntimeException cloudEx) {
|
||||
s_logger.error("CloudRuntimeException", cloudEx);
|
||||
// FIXME: Better error message? This only happens if the API command is not executable, which typically
|
||||
//means
|
||||
// there was
|
||||
// and IllegalAccessException setting one of the parameters.
|
||||
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Internal error executing API command " +
|
||||
cmd.getCommandName().substring(0, cmd.getCommandName().length() - 8));
|
||||
}
|
||||
|
||||
//check access on the resource this field points to
|
||||
try {
|
||||
ACL checkAccess = field.getAnnotation(ACL.class);
|
||||
CommandType fieldType = parameterAnnotation.type();
|
||||
|
||||
if (checkAccess != null) {
|
||||
// Verify that caller can perform actions in behalf of vm owner
|
||||
//acumulate all Controlled Entities together.
|
||||
|
||||
//parse the array of resource types and in case of map check access on key or value or both as specified in @acl
|
||||
//implement external dao for classes that need findByName
|
||||
//for maps, specify access to be checkd on key or value.
|
||||
|
||||
// find the controlled entity DBid by uuid
|
||||
if (parameterAnnotation.entityType() != null) {
|
||||
Class<?>[] entityList = parameterAnnotation.entityType()[0].getAnnotation(EntityReference.class).value();
|
||||
|
||||
for (Class entity : entityList) {
|
||||
// Check if the parameter type is a single
|
||||
// Id or list of id's/name's
|
||||
switch (fieldType) {
|
||||
case LIST:
|
||||
CommandType listType = parameterAnnotation.collectionType();
|
||||
switch (listType) {
|
||||
case LONG:
|
||||
case UUID:
|
||||
List<Long> listParam = (List<Long>)field.get(cmd);
|
||||
for (Long entityId : listParam) {
|
||||
Object entityObj = s_instance._entityMgr.findById(entity, entityId);
|
||||
entitiesToAccess.put(entityObj, checkAccess.accessType());
|
||||
}
|
||||
break;
|
||||
/*
|
||||
* case STRING: List<String> listParam =
|
||||
* new ArrayList<String>(); listParam =
|
||||
* (List)field.get(cmd); for(String
|
||||
* entityName: listParam){
|
||||
* ControlledEntity entityObj =
|
||||
* (ControlledEntity
|
||||
* )daoClassInstance(entityId);
|
||||
* entitiesToAccess.add(entityObj); }
|
||||
* break;
|
||||
*/
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case LONG:
|
||||
case UUID:
|
||||
Object entityObj = s_instance._entityMgr.findById(entity, (Long)field.get(cmd));
|
||||
entitiesToAccess.put(entityObj, checkAccess.accessType());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (ControlledEntity.class.isAssignableFrom(entity)) {
|
||||
if (s_logger.isDebugEnabled()) {
|
||||
s_logger.debug("ControlledEntity name is:" + entity.getName());
|
||||
}
|
||||
}
|
||||
|
||||
if (InfrastructureEntity.class.isAssignableFrom(entity)) {
|
||||
if (s_logger.isDebugEnabled()) {
|
||||
s_logger.debug("InfrastructureEntity name is:" + entity.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} catch (IllegalArgumentException e) {
|
||||
s_logger.error("Error initializing command " + cmd.getCommandName() + ", field " + field.getName() + " is not accessible.");
|
||||
throw new CloudRuntimeException("Internal error initializing parameters for command " + cmd.getCommandName() + " [field " + field.getName() +
|
||||
" is not accessible]");
|
||||
} catch (IllegalAccessException e) {
|
||||
s_logger.error("Error initializing command " + cmd.getCommandName() + ", field " + field.getName() + " is not accessible.");
|
||||
throw new CloudRuntimeException("Internal error initializing parameters for command " + cmd.getCommandName() + " [field " + field.getName() +
|
||||
" is not accessible]");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//check access on the entities.
|
||||
getInstance().doAccessChecks(cmd, entitiesToAccess);
|
||||
|
||||
}
|
||||
|
||||
private static Long translateUuidToInternalId(String uuid, Parameter annotation) {
|
||||
if (uuid.equals("-1")) {
|
||||
// FIXME: This is to handle a lot of hardcoded special cases where -1 is sent
|
||||
// APITODO: Find and get rid of all hardcoded params in API Cmds and service layer
|
||||
return -1L;
|
||||
}
|
||||
Long internalId = null;
|
||||
// If annotation's empty, the cmd existed before 3.x try conversion to long
|
||||
boolean isPre3x = annotation.since().isEmpty();
|
||||
// Match against Java's UUID regex to check if input is uuid string
|
||||
boolean isUuid = uuid.matches("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$");
|
||||
// Enforce that it's uuid for newly added apis from version 3.x
|
||||
if (!isPre3x && !isUuid)
|
||||
return null;
|
||||
// Allow both uuid and internal id for pre3x apis
|
||||
if (isPre3x && !isUuid) {
|
||||
try {
|
||||
internalId = Long.parseLong(uuid);
|
||||
} catch (NumberFormatException e) {
|
||||
internalId = null;
|
||||
}
|
||||
if (internalId != null)
|
||||
return internalId;
|
||||
}
|
||||
// There may be multiple entities defined on the @EntityReference of a Response.class
|
||||
// UUID CommandType would expect only one entityType, so use the first entityType
|
||||
Class<?>[] entities = annotation.entityType()[0].getAnnotation(EntityReference.class).value();
|
||||
// Go through each entity which is an interface to a VO class and get a VO object
|
||||
// Try to getId() for the object using reflection, break on first non-null value
|
||||
for (Class<?> entity : entities) {
|
||||
// For backward compatibility, we search within removed entities and let service layer deal
|
||||
// with removed ones, return empty response or error
|
||||
Object objVO = s_instance._entityMgr.findByUuidIncludingRemoved(entity, uuid);
|
||||
if (objVO == null) {
|
||||
continue;
|
||||
}
|
||||
// Invoke the getId method, get the internal long ID
|
||||
// If that fails hide exceptions as the uuid may not exist
|
||||
try {
|
||||
internalId = ((InternalIdentity)objVO).getId();
|
||||
} catch (IllegalArgumentException e) {
|
||||
} catch (NullPointerException e) {
|
||||
}
|
||||
// Return on first non-null Id for the uuid entity
|
||||
if (internalId != null)
|
||||
break;
|
||||
}
|
||||
if (internalId == null) {
|
||||
if (s_logger.isDebugEnabled())
|
||||
s_logger.debug("Object entity uuid = " + uuid + " does not exist in the database.");
|
||||
throw new InvalidParameterValueException("Invalid parameter " + annotation.name() + " value=" + uuid +
|
||||
" due to incorrect long value format, or entity does not exist or due to incorrect parameter annotation for the field in api cmd class.");
|
||||
}
|
||||
return internalId;
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
private static void setFieldValue(Field field, BaseCmd cmdObj, Object paramObj, Parameter annotation) throws IllegalArgumentException, ParseException {
|
||||
try {
|
||||
field.setAccessible(true);
|
||||
CommandType fieldType = annotation.type();
|
||||
switch (fieldType) {
|
||||
case BOOLEAN:
|
||||
field.set(cmdObj, Boolean.valueOf(paramObj.toString()));
|
||||
break;
|
||||
case DATE:
|
||||
// This piece of code is for maintaining backward compatibility
|
||||
// and support both the date formats(Bug 9724)
|
||||
// Do the date messaging for ListEventsCmd only
|
||||
if (cmdObj instanceof ListEventsCmd || cmdObj instanceof DeleteEventsCmd || cmdObj instanceof ArchiveEventsCmd ||
|
||||
cmdObj instanceof ArchiveAlertsCmd || cmdObj instanceof DeleteAlertsCmd) {
|
||||
boolean isObjInNewDateFormat = isObjInNewDateFormat(paramObj.toString());
|
||||
if (isObjInNewDateFormat) {
|
||||
DateFormat newFormat = BaseCmd.NEW_INPUT_FORMAT;
|
||||
synchronized (newFormat) {
|
||||
field.set(cmdObj, newFormat.parse(paramObj.toString()));
|
||||
}
|
||||
} else {
|
||||
DateFormat format = BaseCmd.INPUT_FORMAT;
|
||||
synchronized (format) {
|
||||
Date date = format.parse(paramObj.toString());
|
||||
if (field.getName().equals("startDate")) {
|
||||
date = messageDate(date, 0, 0, 0);
|
||||
} else if (field.getName().equals("endDate")) {
|
||||
date = messageDate(date, 23, 59, 59);
|
||||
}
|
||||
field.set(cmdObj, date);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
DateFormat format = BaseCmd.INPUT_FORMAT;
|
||||
synchronized (format) {
|
||||
format.setLenient(false);
|
||||
field.set(cmdObj, format.parse(paramObj.toString()));
|
||||
}
|
||||
}
|
||||
break;
|
||||
case FLOAT:
|
||||
// Assuming that the parameters have been checked for required before now,
|
||||
// we ignore blank or null values and defer to the command to set a default
|
||||
// value for optional parameters ...
|
||||
if (paramObj != null && isNotBlank(paramObj.toString())) {
|
||||
field.set(cmdObj, Float.valueOf(paramObj.toString()));
|
||||
}
|
||||
break;
|
||||
case INTEGER:
|
||||
// Assuming that the parameters have been checked for required before now,
|
||||
// we ignore blank or null values and defer to the command to set a default
|
||||
// value for optional parameters ...
|
||||
if (paramObj != null && isNotBlank(paramObj.toString())) {
|
||||
field.set(cmdObj, Integer.valueOf(paramObj.toString()));
|
||||
}
|
||||
break;
|
||||
case LIST:
|
||||
List listParam = new ArrayList();
|
||||
StringTokenizer st = new StringTokenizer(paramObj.toString(), ",");
|
||||
while (st.hasMoreTokens()) {
|
||||
String token = st.nextToken();
|
||||
CommandType listType = annotation.collectionType();
|
||||
switch (listType) {
|
||||
case INTEGER:
|
||||
listParam.add(Integer.valueOf(token));
|
||||
break;
|
||||
case UUID:
|
||||
if (token.isEmpty())
|
||||
break;
|
||||
Long internalId = translateUuidToInternalId(token, annotation);
|
||||
listParam.add(internalId);
|
||||
break;
|
||||
case LONG: {
|
||||
listParam.add(Long.valueOf(token));
|
||||
}
|
||||
break;
|
||||
case SHORT:
|
||||
listParam.add(Short.valueOf(token));
|
||||
case STRING:
|
||||
listParam.add(token);
|
||||
break;
|
||||
}
|
||||
}
|
||||
field.set(cmdObj, listParam);
|
||||
break;
|
||||
case UUID:
|
||||
if (paramObj.toString().isEmpty())
|
||||
break;
|
||||
Long internalId = translateUuidToInternalId(paramObj.toString(), annotation);
|
||||
field.set(cmdObj, internalId);
|
||||
break;
|
||||
case LONG:
|
||||
field.set(cmdObj, Long.valueOf(paramObj.toString()));
|
||||
break;
|
||||
case SHORT:
|
||||
field.set(cmdObj, Short.valueOf(paramObj.toString()));
|
||||
break;
|
||||
case STRING:
|
||||
if ((paramObj != null) && paramObj.toString().length() > annotation.length()) {
|
||||
s_logger.error("Value greater than max allowed length " + annotation.length() + " for param: " + field.getName());
|
||||
throw new InvalidParameterValueException("Value greater than max allowed length " + annotation.length() + " for param: " + field.getName());
|
||||
}
|
||||
field.set(cmdObj, paramObj.toString());
|
||||
break;
|
||||
case TZDATE:
|
||||
field.set(cmdObj, DateUtil.parseTZDateString(paramObj.toString()));
|
||||
break;
|
||||
case MAP:
|
||||
default:
|
||||
field.set(cmdObj, paramObj);
|
||||
break;
|
||||
}
|
||||
} catch (IllegalAccessException ex) {
|
||||
s_logger.error("Error initializing command " + cmdObj.getCommandName() + ", field " + field.getName() + " is not accessible.");
|
||||
throw new CloudRuntimeException("Internal error initializing parameters for command " + cmdObj.getCommandName() + " [field " + field.getName() +
|
||||
" is not accessible]");
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isObjInNewDateFormat(String string) {
|
||||
Matcher matcher = BaseCmd.newInputDateFormat.matcher(string);
|
||||
return matcher.matches();
|
||||
}
|
||||
|
||||
private static Date messageDate(Date date, int hourOfDay, int minute, int second) {
|
||||
Calendar cal = Calendar.getInstance();
|
||||
cal.setTime(date);
|
||||
cal.set(Calendar.HOUR_OF_DAY, hourOfDay);
|
||||
cal.set(Calendar.MINUTE, minute);
|
||||
cal.set(Calendar.SECOND, second);
|
||||
return cal.getTime();
|
||||
}
|
||||
|
||||
public static void plugService(Field field, BaseCmd cmd) {
|
||||
|
||||
Class<?> fc = field.getType();
|
||||
Object instance = null;
|
||||
|
||||
if (instance == null) {
|
||||
throw new CloudRuntimeException("Unable to plug service " + fc.getSimpleName() + " in command " + cmd.getClass().getSimpleName());
|
||||
}
|
||||
|
||||
try {
|
||||
field.setAccessible(true);
|
||||
field.set(cmd, instance);
|
||||
} catch (IllegalArgumentException e) {
|
||||
s_logger.error("IllegalArgumentException at plugService for command " + cmd.getCommandName() + ", field " + field.getName());
|
||||
throw new CloudRuntimeException("Internal error at plugService for command " + cmd.getCommandName() + " [Illegal argumet at field " + field.getName() + "]");
|
||||
} catch (IllegalAccessException e) {
|
||||
s_logger.error("Error at plugService for command " + cmd.getCommandName() + ", field " + field.getName() + " is not accessible.");
|
||||
throw new CloudRuntimeException("Internal error at plugService for command " + cmd.getCommandName() + " [field " + field.getName() + " is not accessible]");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -86,6 +86,7 @@ import org.springframework.stereotype.Component;
|
||||
|
||||
import org.apache.cloudstack.acl.APIChecker;
|
||||
import org.apache.cloudstack.api.APICommand;
|
||||
import org.apache.cloudstack.api.ApiConstants;
|
||||
import org.apache.cloudstack.api.ApiErrorCode;
|
||||
import org.apache.cloudstack.api.BaseAsyncCmd;
|
||||
import org.apache.cloudstack.api.BaseAsyncCreateCmd;
|
||||
@ -122,6 +123,8 @@ import org.apache.cloudstack.framework.jobs.AsyncJobManager;
|
||||
import org.apache.cloudstack.framework.jobs.impl.AsyncJobVO;
|
||||
import org.apache.cloudstack.managed.context.ManagedContextRunnable;
|
||||
|
||||
import com.cloud.api.dispatch.DispatchChainFactory;
|
||||
import com.cloud.api.dispatch.DispatchTask;
|
||||
import com.cloud.api.response.ApiResponseSerializer;
|
||||
import com.cloud.configuration.Config;
|
||||
import com.cloud.domain.Domain;
|
||||
@ -156,14 +159,22 @@ import com.cloud.utils.exception.ExceptionProxyObject;
|
||||
|
||||
@Component
|
||||
public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiServerService {
|
||||
private static final String UTF_8 = "UTF-8";
|
||||
private static final Logger s_logger = Logger.getLogger(ApiServer.class.getName());
|
||||
private static final Logger s_accessLogger = Logger.getLogger("apiserver." + ApiServer.class.getName());
|
||||
|
||||
private static boolean encodeApiResponse = false;
|
||||
private static String jsonContentType = "text/javascript";
|
||||
private static String controlCharacters = "[\000-\011\013-\014\016-\037\177]"; // Non-printable ASCII characters - numbers 0 to 31 and 127 decimal
|
||||
@Inject ApiDispatcher _dispatcher;
|
||||
public static boolean encodeApiResponse = false;
|
||||
public static String jsonContentType = "text/javascript";
|
||||
|
||||
/**
|
||||
* Non-printable ASCII characters - numbers 0 to 31 and 127 decimal
|
||||
*/
|
||||
public static final String CONTROL_CHARACTERS = "[\000-\011\013-\014\016-\037\177]";
|
||||
|
||||
@Inject
|
||||
protected ApiDispatcher _dispatcher;
|
||||
@Inject
|
||||
protected DispatchChainFactory dispatchChainFactory;
|
||||
@Inject
|
||||
private AccountManager _accountMgr;
|
||||
@Inject
|
||||
@ -191,27 +202,27 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean configure(String name, Map<String, Object> params) throws ConfigurationException {
|
||||
public boolean configure(final String name, final Map<String, Object> params) throws ConfigurationException {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean start() {
|
||||
Integer apiPort = null; // api port, null by default
|
||||
SearchCriteria<ConfigurationVO> sc = _configDao.createSearchCriteria();
|
||||
final SearchCriteria<ConfigurationVO> sc = _configDao.createSearchCriteria();
|
||||
sc.addAnd("name", SearchCriteria.Op.EQ, Config.IntegrationAPIPort.key());
|
||||
List<ConfigurationVO> values = _configDao.search(sc, null);
|
||||
final List<ConfigurationVO> values = _configDao.search(sc, null);
|
||||
if ((values != null) && (values.size() > 0)) {
|
||||
ConfigurationVO apiPortConfig = values.get(0);
|
||||
final ConfigurationVO apiPortConfig = values.get(0);
|
||||
if (apiPortConfig.getValue() != null) {
|
||||
apiPort = Integer.parseInt(apiPortConfig.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, String> configs = _configDao.getConfiguration();
|
||||
String strSnapshotLimit = configs.get(Config.ConcurrentSnapshotsThresholdPerHost.key());
|
||||
final Map<String, String> configs = _configDao.getConfiguration();
|
||||
final String strSnapshotLimit = configs.get(Config.ConcurrentSnapshotsThresholdPerHost.key());
|
||||
if (strSnapshotLimit != null) {
|
||||
Long snapshotLimit = NumbersUtil.parseLong(strSnapshotLimit, 1L);
|
||||
final Long snapshotLimit = NumbersUtil.parseLong(strSnapshotLimit, 1L);
|
||||
if (snapshotLimit.longValue() <= 0) {
|
||||
s_logger.debug("Global config parameter " + Config.ConcurrentSnapshotsThresholdPerHost.toString() + " is less or equal 0; defaulting to unlimited");
|
||||
} else {
|
||||
@ -219,20 +230,20 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
|
||||
}
|
||||
}
|
||||
|
||||
Set<Class<?>> cmdClasses = new HashSet<Class<?>>();
|
||||
for (PluggableService pluggableService : _pluggableServices) {
|
||||
final Set<Class<?>> cmdClasses = new HashSet<Class<?>>();
|
||||
for (final PluggableService pluggableService : _pluggableServices) {
|
||||
cmdClasses.addAll(pluggableService.getCommands());
|
||||
if (s_logger.isDebugEnabled()) {
|
||||
s_logger.debug("Discovered plugin " + pluggableService.getClass().getSimpleName());
|
||||
}
|
||||
}
|
||||
|
||||
for (Class<?> cmdClass : cmdClasses) {
|
||||
APICommand at = cmdClass.getAnnotation(APICommand.class);
|
||||
for (final Class<?> cmdClass : cmdClasses) {
|
||||
final APICommand at = cmdClass.getAnnotation(APICommand.class);
|
||||
if (at == null) {
|
||||
throw new CloudRuntimeException(String.format("%s is claimed as a API command, but it doesn't have @APICommand annotation", cmdClass.getName()));
|
||||
}
|
||||
String apiName = at.name();
|
||||
final String apiName = at.name();
|
||||
if (s_apiNameCmdClassMap.containsKey(apiName)) {
|
||||
s_logger.error("API Cmd class " + cmdClass.getName() + " has non-unique apiname" + apiName);
|
||||
continue;
|
||||
@ -241,13 +252,13 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
|
||||
}
|
||||
|
||||
setEncodeApiResponse(Boolean.valueOf(_configDao.getValue(Config.EncodeApiResponse.key())));
|
||||
String jsonType = _configDao.getValue(Config.JavaScriptDefaultContentType.key());
|
||||
final String jsonType = _configDao.getValue(Config.JavaScriptDefaultContentType.key());
|
||||
if (jsonType != null) {
|
||||
jsonContentType = jsonType;
|
||||
}
|
||||
|
||||
if (apiPort != null) {
|
||||
ListenerThread listenerThread = new ListenerThread(this, apiPort);
|
||||
final ListenerThread listenerThread = new ListenerThread(this, apiPort);
|
||||
listenerThread.start();
|
||||
}
|
||||
|
||||
@ -258,13 +269,13 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
|
||||
// If integration api port is not configured, actual OTW requests will be received by ApiServlet
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
@Override
|
||||
public void handle(HttpRequest request, HttpResponse response, HttpContext context) throws HttpException, IOException {
|
||||
public void handle(final HttpRequest request, final HttpResponse response, final HttpContext context) throws HttpException, IOException {
|
||||
|
||||
// Create StringBuffer to log information in access log
|
||||
StringBuffer sb = new StringBuffer();
|
||||
HttpServerConnection connObj = (HttpServerConnection)context.getAttribute("http.connection");
|
||||
final StringBuffer sb = new StringBuffer();
|
||||
final HttpServerConnection connObj = (HttpServerConnection)context.getAttribute("http.connection");
|
||||
if (connObj instanceof SocketHttpServerConnection) {
|
||||
InetAddress remoteAddr = ((SocketHttpServerConnection)connObj).getRemoteAddress();
|
||||
final InetAddress remoteAddr = ((SocketHttpServerConnection)connObj).getRemoteAddress();
|
||||
sb.append(remoteAddr.toString() + " -- ");
|
||||
}
|
||||
sb.append(StringUtils.cleanString(request.getRequestLine().toString()));
|
||||
@ -272,8 +283,8 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
|
||||
try {
|
||||
List<NameValuePair> paramList = null;
|
||||
try {
|
||||
paramList = URLEncodedUtils.parse(new URI(request.getRequestLine().getUri()), "UTF-8");
|
||||
} catch (URISyntaxException e) {
|
||||
paramList = URLEncodedUtils.parse(new URI(request.getRequestLine().getUri()), UTF_8);
|
||||
} catch (final URISyntaxException e) {
|
||||
s_logger.error("Error parsing url request", e);
|
||||
}
|
||||
|
||||
@ -282,9 +293,9 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
|
||||
// APITODO: Use Guava's (import com.google.common.collect.Multimap;)
|
||||
// (Immutable)Multimap<String, String> paramMultiMap = HashMultimap.create();
|
||||
// Map<String, Collection<String>> parameterMap = paramMultiMap.asMap();
|
||||
Map parameterMap = new HashMap<String, String[]>();
|
||||
final Map parameterMap = new HashMap<String, String[]>();
|
||||
String responseType = BaseCmd.RESPONSE_TYPE_XML;
|
||||
for (NameValuePair param : paramList) {
|
||||
for (final NameValuePair param : paramList) {
|
||||
if (param.getName().equalsIgnoreCase("response")) {
|
||||
responseType = param.getValue();
|
||||
continue;
|
||||
@ -304,15 +315,15 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
|
||||
// always trust commands from API port, user context will always be UID_SYSTEM/ACCOUNT_ID_SYSTEM
|
||||
CallContext.register(_accountMgr.getSystemUser(), _accountMgr.getSystemAccount());
|
||||
sb.insert(0, "(userId=" + User.UID_SYSTEM + " accountId=" + Account.ACCOUNT_ID_SYSTEM + " sessionId=" + null + ") ");
|
||||
String responseText = handleRequest(parameterMap, responseType, sb);
|
||||
final String responseText = handleRequest(parameterMap, responseType, sb);
|
||||
sb.append(" 200 " + ((responseText == null) ? 0 : responseText.length()));
|
||||
|
||||
writeResponse(response, responseText, HttpStatus.SC_OK, responseType, null);
|
||||
} catch (ServerApiException se) {
|
||||
String responseText = getSerializedApiError(se, parameterMap, responseType);
|
||||
} catch (final ServerApiException se) {
|
||||
final String responseText = getSerializedApiError(se, parameterMap, responseType);
|
||||
writeResponse(response, responseText, se.getErrorCode().getHttpCode(), responseType, se.getDescription());
|
||||
sb.append(" " + se.getErrorCode() + " " + se.getDescription());
|
||||
} catch (RuntimeException e) {
|
||||
} catch (final RuntimeException e) {
|
||||
// log runtime exception like NullPointerException to help identify the source easier
|
||||
s_logger.error("Unhandled exception, ", e);
|
||||
throw e;
|
||||
@ -323,9 +334,32 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
public void checkCharacterInkParams(final Map params) {
|
||||
final Map<String, String> stringMap = new HashMap<String, String>();
|
||||
final Set keys = params.keySet();
|
||||
final Iterator keysIter = keys.iterator();
|
||||
while (keysIter.hasNext()) {
|
||||
final String key = (String)keysIter.next();
|
||||
final String[] value = (String[])params.get(key);
|
||||
// fail if parameter value contains ASCII control (non-printable) characters
|
||||
if (value[0] != null) {
|
||||
final Pattern pattern = Pattern.compile(CONTROL_CHARACTERS);
|
||||
final Matcher matcher = pattern.matcher(value[0]);
|
||||
if (matcher.find()) {
|
||||
throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Received value " + value[0] + " for parameter " + key +
|
||||
" is invalid, contains illegal ASCII non-printable characters");
|
||||
}
|
||||
}
|
||||
stringMap.put(key, value[0]);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("rawtypes")
|
||||
public String handleRequest(Map params, String responseType, StringBuffer auditTrailSb) throws ServerApiException {
|
||||
public String handleRequest(final Map params, final String responseType, final StringBuffer auditTrailSb) throws ServerApiException {
|
||||
checkCharacterInkParams(params);
|
||||
|
||||
String response = null;
|
||||
String[] command = null;
|
||||
|
||||
@ -335,37 +369,27 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
|
||||
s_logger.error("invalid request, no command sent");
|
||||
if (s_logger.isTraceEnabled()) {
|
||||
s_logger.trace("dumping request parameters");
|
||||
for (Object key : params.keySet()) {
|
||||
String keyStr = (String)key;
|
||||
String[] value = (String[])params.get(key);
|
||||
for (final Object key : params.keySet()) {
|
||||
final String keyStr = (String)key;
|
||||
final String[] value = (String[])params.get(key);
|
||||
s_logger.trace(" key: " + keyStr + ", value: " + ((value == null) ? "'null'" : value[0]));
|
||||
}
|
||||
}
|
||||
throw new ServerApiException(ApiErrorCode.UNSUPPORTED_ACTION_ERROR, "Invalid request, no command sent");
|
||||
} else {
|
||||
Map<String, String> paramMap = new HashMap<String, String>();
|
||||
Set keys = params.keySet();
|
||||
Iterator keysIter = keys.iterator();
|
||||
final Map<String, String> paramMap = new HashMap<String, String>();
|
||||
final Set keys = params.keySet();
|
||||
final Iterator keysIter = keys.iterator();
|
||||
while (keysIter.hasNext()) {
|
||||
String key = (String)keysIter.next();
|
||||
final String key = (String)keysIter.next();
|
||||
if ("command".equalsIgnoreCase(key)) {
|
||||
continue;
|
||||
}
|
||||
String[] value = (String[])params.get(key);
|
||||
// fail if parameter value contains ASCII control (non-printable) characters
|
||||
if (value[0] != null) {
|
||||
Pattern pattern = Pattern.compile(controlCharacters);
|
||||
Matcher matcher = pattern.matcher(value[0]);
|
||||
if (matcher.find()) {
|
||||
throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Received value " + value[0] + " for parameter " + key +
|
||||
" is invalid, contains illegal ASCII non-printable characters");
|
||||
}
|
||||
}
|
||||
final String[] value = (String[])params.get(key);
|
||||
paramMap.put(key, value[0]);
|
||||
}
|
||||
|
||||
Class<?> cmdClass = getCmdClass(command[0]);
|
||||
|
||||
if (cmdClass != null) {
|
||||
APICommand annotation = cmdClass.getAnnotation(APICommand.class);
|
||||
if (annotation == null) {
|
||||
@ -378,7 +402,7 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
|
||||
cmdObj.configure();
|
||||
cmdObj.setFullUrlParams(paramMap);
|
||||
cmdObj.setResponseType(responseType);
|
||||
cmdObj.setHttpMethod(paramMap.get("httpmethod").toString());
|
||||
cmdObj.setHttpMethod(paramMap.get(ApiConstants.HTTPMETHOD).toString());
|
||||
|
||||
// This is where the command is either serialized, or directly dispatched
|
||||
response = queueCommand(cmdObj, paramMap);
|
||||
@ -391,24 +415,24 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
|
||||
buildAuditTrail(auditTrailSb, command[0], response);
|
||||
} else {
|
||||
if (!command[0].equalsIgnoreCase("login") && !command[0].equalsIgnoreCase("logout")) {
|
||||
String errorString = "Unknown API command: " + command[0];
|
||||
final String errorString = "Unknown API command: " + command[0];
|
||||
s_logger.warn(errorString);
|
||||
auditTrailSb.append(" " + errorString);
|
||||
throw new ServerApiException(ApiErrorCode.UNSUPPORTED_ACTION_ERROR, errorString);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (InvalidParameterValueException ex) {
|
||||
} catch (final InvalidParameterValueException ex) {
|
||||
s_logger.info(ex.getMessage());
|
||||
throw new ServerApiException(ApiErrorCode.PARAM_ERROR, ex.getMessage(), ex);
|
||||
} catch (IllegalArgumentException ex) {
|
||||
} catch (final IllegalArgumentException ex) {
|
||||
s_logger.info(ex.getMessage());
|
||||
throw new ServerApiException(ApiErrorCode.PARAM_ERROR, ex.getMessage(), ex);
|
||||
} catch (PermissionDeniedException ex) {
|
||||
ArrayList<ExceptionProxyObject> idList = ex.getIdProxyList();
|
||||
} catch (final PermissionDeniedException ex) {
|
||||
final ArrayList<ExceptionProxyObject> idList = ex.getIdProxyList();
|
||||
if (idList != null) {
|
||||
StringBuffer buf = new StringBuffer();
|
||||
for (ExceptionProxyObject obj : idList) {
|
||||
final StringBuffer buf = new StringBuffer();
|
||||
for (final ExceptionProxyObject obj : idList) {
|
||||
buf.append(obj.getDescription());
|
||||
buf.append(":");
|
||||
buf.append(obj.getUuid());
|
||||
@ -419,10 +443,10 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
|
||||
s_logger.info("PermissionDenied: " + ex.getMessage());
|
||||
}
|
||||
throw new ServerApiException(ApiErrorCode.ACCOUNT_ERROR, ex.getMessage(), ex);
|
||||
} catch (AccountLimitException ex) {
|
||||
} catch (final AccountLimitException ex) {
|
||||
s_logger.info(ex.getMessage());
|
||||
throw new ServerApiException(ApiErrorCode.ACCOUNT_RESOURCE_LIMIT_ERROR, ex.getMessage(), ex);
|
||||
} catch (InsufficientCapacityException ex) {
|
||||
} catch (final InsufficientCapacityException ex) {
|
||||
s_logger.info(ex.getMessage());
|
||||
String errorMsg = ex.getMessage();
|
||||
if (CallContext.current().getCallingAccount().getType() != Account.ACCOUNT_TYPE_ADMIN) {
|
||||
@ -430,10 +454,10 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
|
||||
errorMsg = BaseCmd.USER_ERROR_MESSAGE;
|
||||
}
|
||||
throw new ServerApiException(ApiErrorCode.INSUFFICIENT_CAPACITY_ERROR, errorMsg, ex);
|
||||
} catch (ResourceAllocationException ex) {
|
||||
} catch (final ResourceAllocationException ex) {
|
||||
s_logger.info(ex.getMessage());
|
||||
throw new ServerApiException(ApiErrorCode.RESOURCE_ALLOCATION_ERROR, ex.getMessage(), ex);
|
||||
} catch (ResourceUnavailableException ex) {
|
||||
} catch (final ResourceUnavailableException ex) {
|
||||
s_logger.info(ex.getMessage());
|
||||
String errorMsg = ex.getMessage();
|
||||
if (CallContext.current().getCallingAccount().getType() != Account.ACCOUNT_TYPE_ADMIN) {
|
||||
@ -441,11 +465,11 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
|
||||
errorMsg = BaseCmd.USER_ERROR_MESSAGE;
|
||||
}
|
||||
throw new ServerApiException(ApiErrorCode.RESOURCE_UNAVAILABLE_ERROR, errorMsg, ex);
|
||||
} catch (ServerApiException ex) {
|
||||
} catch (final ServerApiException ex) {
|
||||
s_logger.info(ex.getDescription());
|
||||
throw ex;
|
||||
} catch (Exception ex) {
|
||||
s_logger.error("unhandled exception executing api command: " + ((command == null) ? "null" : command[0]), ex);
|
||||
} catch (final Exception ex) {
|
||||
s_logger.error("unhandled exception executing api command: " + ((command == null) ? "null" : command), ex);
|
||||
String errorMsg = ex.getMessage();
|
||||
if (CallContext.current().getCallingAccount().getType() != Account.ACCOUNT_TYPE_ADMIN) {
|
||||
// hide internal details to non-admin user for security reason
|
||||
@ -457,28 +481,28 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
|
||||
return response;
|
||||
}
|
||||
|
||||
private String getBaseAsyncResponse(long jobId, BaseAsyncCmd cmd) {
|
||||
AsyncJobResponse response = new AsyncJobResponse();
|
||||
private String getBaseAsyncResponse(final long jobId, final BaseAsyncCmd cmd) {
|
||||
final AsyncJobResponse response = new AsyncJobResponse();
|
||||
|
||||
AsyncJob job = _entityMgr.findById(AsyncJob.class, jobId);
|
||||
final AsyncJob job = _entityMgr.findById(AsyncJob.class, jobId);
|
||||
response.setJobId(job.getUuid());
|
||||
response.setResponseName(cmd.getCommandName());
|
||||
return ApiResponseSerializer.toSerializedString(response, cmd.getResponseType());
|
||||
}
|
||||
|
||||
private String getBaseAsyncCreateResponse(long jobId, BaseAsyncCreateCmd cmd, String objectUuid) {
|
||||
CreateCmdResponse response = new CreateCmdResponse();
|
||||
AsyncJob job = _entityMgr.findById(AsyncJob.class, jobId);
|
||||
private String getBaseAsyncCreateResponse(final long jobId, final BaseAsyncCreateCmd cmd, final String objectUuid) {
|
||||
final CreateCmdResponse response = new CreateCmdResponse();
|
||||
final AsyncJob job = _entityMgr.findById(AsyncJob.class, jobId);
|
||||
response.setJobId(job.getUuid());
|
||||
response.setId(objectUuid);
|
||||
response.setResponseName(cmd.getCommandName());
|
||||
return ApiResponseSerializer.toSerializedString(response, cmd.getResponseType());
|
||||
}
|
||||
|
||||
private String queueCommand(BaseCmd cmdObj, Map<String, String> params) throws Exception {
|
||||
CallContext ctx = CallContext.current();
|
||||
Long callerUserId = ctx.getCallingUserId();
|
||||
Account caller = ctx.getCallingAccount();
|
||||
private String queueCommand(final BaseCmd cmdObj, final Map<String, String> params) throws Exception {
|
||||
final CallContext ctx = CallContext.current();
|
||||
final Long callerUserId = ctx.getCallingUserId();
|
||||
final Account caller = ctx.getCallingAccount();
|
||||
|
||||
// Queue command based on Cmd super class:
|
||||
// BaseCmd: cmd is dispatched to ApiDispatcher, executed, serialized and returned.
|
||||
@ -488,16 +512,16 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
|
||||
Long objectId = null;
|
||||
String objectUuid = null;
|
||||
if (cmdObj instanceof BaseAsyncCreateCmd) {
|
||||
BaseAsyncCreateCmd createCmd = (BaseAsyncCreateCmd)cmdObj;
|
||||
final BaseAsyncCreateCmd createCmd = (BaseAsyncCreateCmd)cmdObj;
|
||||
_dispatcher.dispatchCreateCmd(createCmd, params);
|
||||
objectId = createCmd.getEntityId();
|
||||
objectUuid = createCmd.getEntityUuid();
|
||||
params.put("id", objectId.toString());
|
||||
} else {
|
||||
ApiDispatcher.processParameters(cmdObj, params);
|
||||
dispatchChainFactory.getStandardDispatchChain().dispatch(new DispatchTask(cmdObj, params));
|
||||
}
|
||||
|
||||
BaseAsyncCmd asyncCmd = (BaseAsyncCmd)cmdObj;
|
||||
final BaseAsyncCmd asyncCmd = (BaseAsyncCmd)cmdObj;
|
||||
|
||||
if (callerUserId != null) {
|
||||
params.put("ctxUserId", callerUserId.toString());
|
||||
@ -510,7 +534,7 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
|
||||
asyncCmd.setStartEventId(startEventId);
|
||||
|
||||
// save the scheduled event
|
||||
Long eventId =
|
||||
final Long eventId =
|
||||
ActionEventUtils.onScheduledActionEvent((callerUserId == null) ? User.UID_SYSTEM : callerUserId, asyncCmd.getEntityOwnerId(), asyncCmd.getEventType(),
|
||||
asyncCmd.getEventDescription(), asyncCmd.isDisplayResourceEnabled(), startEventId);
|
||||
if (startEventId == 0) {
|
||||
@ -527,16 +551,16 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
|
||||
asyncCmd.getInstanceType() != null ? asyncCmd.getInstanceType().toString() : null);
|
||||
job.setDispatcher(_asyncDispatcher.getName());
|
||||
|
||||
long jobId = _asyncMgr.submitAsyncJob(job);
|
||||
final long jobId = _asyncMgr.submitAsyncJob(job);
|
||||
|
||||
if (jobId == 0L) {
|
||||
String errorMsg = "Unable to schedule async job for command " + job.getCmd();
|
||||
final String errorMsg = "Unable to schedule async job for command " + job.getCmd();
|
||||
s_logger.warn(errorMsg);
|
||||
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, errorMsg);
|
||||
}
|
||||
|
||||
if (objectId != null) {
|
||||
String objUuid = (objectUuid == null) ? objectId.toString() : objectUuid;
|
||||
final String objUuid = (objectUuid == null) ? objectId.toString() : objectUuid;
|
||||
return getBaseAsyncCreateResponse(jobId, (BaseAsyncCreateCmd)asyncCmd, objUuid);
|
||||
} else {
|
||||
SerializationContext.current().setUuidTranslation(true);
|
||||
@ -563,8 +587,8 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void buildAsyncListResponse(BaseListCmd command, Account account) {
|
||||
List<ResponseObject> responses = ((ListResponse)command.getResponseObject()).getResponses();
|
||||
private void buildAsyncListResponse(final BaseListCmd command, final Account account) {
|
||||
final List<ResponseObject> responses = ((ListResponse)command.getResponseObject()).getResponses();
|
||||
if (responses != null && responses.size() > 0) {
|
||||
List<? extends AsyncJob> jobs = null;
|
||||
|
||||
@ -579,18 +603,18 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
|
||||
return;
|
||||
}
|
||||
|
||||
Map<String, AsyncJob> objectJobMap = new HashMap<String, AsyncJob>();
|
||||
for (AsyncJob job : jobs) {
|
||||
final Map<String, AsyncJob> objectJobMap = new HashMap<String, AsyncJob>();
|
||||
for (final AsyncJob job : jobs) {
|
||||
if (job.getInstanceId() == null) {
|
||||
continue;
|
||||
}
|
||||
String instanceUuid = ApiDBUtils.findJobInstanceUuid(job);
|
||||
final String instanceUuid = ApiDBUtils.findJobInstanceUuid(job);
|
||||
objectJobMap.put(instanceUuid, job);
|
||||
}
|
||||
|
||||
for (ResponseObject response : responses) {
|
||||
for (final ResponseObject response : responses) {
|
||||
if (response.getObjectId() != null && objectJobMap.containsKey(response.getObjectId())) {
|
||||
AsyncJob job = objectJobMap.get(response.getObjectId());
|
||||
final AsyncJob job = objectJobMap.get(response.getObjectId());
|
||||
response.setJobId(job.getUuid());
|
||||
response.setJobStatus(job.getStatus().ordinal());
|
||||
}
|
||||
@ -598,7 +622,7 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
|
||||
}
|
||||
}
|
||||
|
||||
private void buildAuditTrail(StringBuffer auditTrailSb, String command, String result) {
|
||||
private void buildAuditTrail(final StringBuffer auditTrailSb, final String command, final String result) {
|
||||
if (result == null) {
|
||||
return;
|
||||
}
|
||||
@ -611,31 +635,31 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean verifyRequest(Map<String, Object[]> requestParameters, Long userId) throws ServerApiException {
|
||||
public boolean verifyRequest(final Map<String, Object[]> requestParameters, final Long userId) throws ServerApiException {
|
||||
try {
|
||||
String apiKey = null;
|
||||
String secretKey = null;
|
||||
String signature = null;
|
||||
String unsignedRequest = null;
|
||||
|
||||
String[] command = (String[])requestParameters.get("command");
|
||||
final String[] command = (String[])requestParameters.get(ApiConstants.COMMAND);
|
||||
if (command == null) {
|
||||
s_logger.info("missing command, ignoring request...");
|
||||
return false;
|
||||
}
|
||||
|
||||
String commandName = command[0];
|
||||
final String commandName = command[0];
|
||||
|
||||
// if userId not null, that mean that user is logged in
|
||||
if (userId != null) {
|
||||
User user = ApiDBUtils.findUserById(userId);
|
||||
final User user = ApiDBUtils.findUserById(userId);
|
||||
|
||||
try {
|
||||
checkCommandAvailable(user, commandName);
|
||||
} catch (RequestLimitException ex) {
|
||||
} catch (final RequestLimitException ex) {
|
||||
s_logger.debug(ex.getMessage());
|
||||
throw new ServerApiException(ApiErrorCode.API_LIMIT_EXCEED, ex.getMessage());
|
||||
} catch (PermissionDeniedException ex) {
|
||||
} catch (final PermissionDeniedException ex) {
|
||||
s_logger.debug("The given command:" + commandName + " does not exist or it is not available for user with id:" + userId);
|
||||
throw new ServerApiException(ApiErrorCode.UNSUPPORTED_ACTION_ERROR, "The given command does not exist or it is not available for user");
|
||||
}
|
||||
@ -650,9 +674,9 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
|
||||
|
||||
// - build a request string with sorted params, make sure it's all lowercase
|
||||
// - sign the request, verify the signature is the same
|
||||
List<String> parameterNames = new ArrayList<String>();
|
||||
final List<String> parameterNames = new ArrayList<String>();
|
||||
|
||||
for (Object paramNameObj : requestParameters.keySet()) {
|
||||
for (final Object paramNameObj : requestParameters.keySet()) {
|
||||
parameterNames.add((String)paramNameObj); // put the name in a list that we'll sort later
|
||||
}
|
||||
|
||||
@ -661,25 +685,25 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
|
||||
String signatureVersion = null;
|
||||
String expires = null;
|
||||
|
||||
for (String paramName : parameterNames) {
|
||||
for (final String paramName : parameterNames) {
|
||||
// parameters come as name/value pairs in the form String/String[]
|
||||
String paramValue = ((String[])requestParameters.get(paramName))[0];
|
||||
final String paramValue = ((String[])requestParameters.get(paramName))[0];
|
||||
|
||||
if ("signature".equalsIgnoreCase(paramName)) {
|
||||
if (ApiConstants.SIGNATURE.equalsIgnoreCase(paramName)) {
|
||||
signature = paramValue;
|
||||
} else {
|
||||
if ("apikey".equalsIgnoreCase(paramName)) {
|
||||
if (ApiConstants.API_KEY.equalsIgnoreCase(paramName)) {
|
||||
apiKey = paramValue;
|
||||
} else if ("signatureversion".equalsIgnoreCase(paramName)) {
|
||||
} else if (ApiConstants.SIGNATURE_VERSION.equalsIgnoreCase(paramName)) {
|
||||
signatureVersion = paramValue;
|
||||
} else if ("expires".equalsIgnoreCase(paramName)) {
|
||||
} else if (ApiConstants.EXPIRES.equalsIgnoreCase(paramName)) {
|
||||
expires = paramValue;
|
||||
}
|
||||
|
||||
if (unsignedRequest == null) {
|
||||
unsignedRequest = paramName + "=" + URLEncoder.encode(paramValue, "UTF-8").replaceAll("\\+", "%20");
|
||||
unsignedRequest = paramName + "=" + URLEncoder.encode(paramValue, UTF_8).replaceAll("\\+", "%20");
|
||||
} else {
|
||||
unsignedRequest = unsignedRequest + "&" + paramName + "=" + URLEncoder.encode(paramValue, "UTF-8").replaceAll("\\+", "%20");
|
||||
unsignedRequest = unsignedRequest + "&" + paramName + "=" + URLEncoder.encode(paramValue, UTF_8).replaceAll("\\+", "%20");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -701,30 +725,30 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
|
||||
synchronized (DateFormatToUse) {
|
||||
try {
|
||||
expiresTS = DateFormatToUse.parse(expires);
|
||||
} catch (ParseException pe) {
|
||||
} catch (final ParseException pe) {
|
||||
s_logger.debug("Incorrect date format for Expires parameter", pe);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Date now = new Date(System.currentTimeMillis());
|
||||
final Date now = new Date(System.currentTimeMillis());
|
||||
if (expiresTS.before(now)) {
|
||||
s_logger.debug("Request expired -- ignoring ...sig: " + signature + ", apiKey: " + apiKey);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
TransactionLegacy txn = TransactionLegacy.open(TransactionLegacy.CLOUD_DB);
|
||||
final TransactionLegacy txn = TransactionLegacy.open(TransactionLegacy.CLOUD_DB);
|
||||
txn.close();
|
||||
User user = null;
|
||||
// verify there is a user with this api key
|
||||
Pair<User, Account> userAcctPair = _accountMgr.findUserByApiKey(apiKey);
|
||||
final Pair<User, Account> userAcctPair = _accountMgr.findUserByApiKey(apiKey);
|
||||
if (userAcctPair == null) {
|
||||
s_logger.debug("apiKey does not map to a valid user -- ignoring request, apiKey: " + apiKey);
|
||||
return false;
|
||||
}
|
||||
|
||||
user = userAcctPair.first();
|
||||
Account account = userAcctPair.second();
|
||||
final Account account = userAcctPair.second();
|
||||
|
||||
if (user.getState() != Account.State.enabled || !account.getState().equals(Account.State.enabled)) {
|
||||
s_logger.info("disabled or locked user accessing the api, userid = " + user.getId() + "; name = " + user.getUsername() + "; state: " + user.getState() +
|
||||
@ -734,10 +758,10 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
|
||||
|
||||
try {
|
||||
checkCommandAvailable(user, commandName);
|
||||
} catch (RequestLimitException ex) {
|
||||
} catch (final RequestLimitException ex) {
|
||||
s_logger.debug(ex.getMessage());
|
||||
throw new ServerApiException(ApiErrorCode.API_LIMIT_EXCEED, ex.getMessage());
|
||||
} catch (PermissionDeniedException ex) {
|
||||
} catch (final PermissionDeniedException ex) {
|
||||
s_logger.debug("The given command:" + commandName + " does not exist or it is not available for user");
|
||||
throw new ServerApiException(ApiErrorCode.UNSUPPORTED_ACTION_ERROR, "The given command:" + commandName + " does not exist or it is not available for user with id:"
|
||||
+ userId);
|
||||
@ -752,30 +776,30 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
|
||||
|
||||
unsignedRequest = unsignedRequest.toLowerCase();
|
||||
|
||||
Mac mac = Mac.getInstance("HmacSHA1");
|
||||
SecretKeySpec keySpec = new SecretKeySpec(secretKey.getBytes(), "HmacSHA1");
|
||||
final Mac mac = Mac.getInstance("HmacSHA1");
|
||||
final SecretKeySpec keySpec = new SecretKeySpec(secretKey.getBytes(), "HmacSHA1");
|
||||
mac.init(keySpec);
|
||||
mac.update(unsignedRequest.getBytes());
|
||||
byte[] encryptedBytes = mac.doFinal();
|
||||
String computedSignature = Base64.encodeBase64String(encryptedBytes);
|
||||
boolean equalSig = signature.equals(computedSignature);
|
||||
final byte[] encryptedBytes = mac.doFinal();
|
||||
final String computedSignature = Base64.encodeBase64String(encryptedBytes);
|
||||
final boolean equalSig = signature.equals(computedSignature);
|
||||
if (!equalSig) {
|
||||
s_logger.info("User signature: " + signature + " is not equaled to computed signature: " + computedSignature);
|
||||
} else {
|
||||
CallContext.register(user, account);
|
||||
}
|
||||
return equalSig;
|
||||
} catch (ServerApiException ex) {
|
||||
} catch (final ServerApiException ex) {
|
||||
throw ex;
|
||||
} catch (Exception ex) {
|
||||
} catch (final Exception ex) {
|
||||
s_logger.error("unable to verify request signature");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long fetchDomainId(String domainUUID) {
|
||||
Domain domain = _domainMgr.getDomain(domainUUID);
|
||||
public Long fetchDomainId(final String domainUUID) {
|
||||
final Domain domain = _domainMgr.getDomain(domainUUID);
|
||||
if (domain != null)
|
||||
return domain.getId();
|
||||
else
|
||||
@ -783,15 +807,15 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loginUser(HttpSession session, String username, String password, Long domainId, String domainPath, String loginIpAddress,
|
||||
Map<String, Object[]> requestParameters) throws CloudAuthenticationException {
|
||||
public void loginUser(final HttpSession session, final String username, final String password, Long domainId, final String domainPath, final String loginIpAddress,
|
||||
final Map<String, Object[]> requestParameters) throws CloudAuthenticationException {
|
||||
// We will always use domainId first. If that does not exist, we will use domain name. If THAT doesn't exist
|
||||
// we will default to ROOT
|
||||
if (domainId == null) {
|
||||
if (domainPath == null || domainPath.trim().length() == 0) {
|
||||
domainId = Domain.ROOT_DOMAIN;
|
||||
} else {
|
||||
Domain domainObj = _domainMgr.findDomainByPath(domainPath);
|
||||
final Domain domainObj = _domainMgr.findDomainByPath(domainPath);
|
||||
if (domainObj != null) {
|
||||
domainId = domainObj.getId();
|
||||
} else { // if an unknown path is passed in, fail the login call
|
||||
@ -800,26 +824,26 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
|
||||
}
|
||||
}
|
||||
|
||||
UserAccount userAcct = _accountMgr.authenticateUser(username, password, domainId, loginIpAddress, requestParameters);
|
||||
final UserAccount userAcct = _accountMgr.authenticateUser(username, password, domainId, loginIpAddress, requestParameters);
|
||||
if (userAcct != null) {
|
||||
String timezone = userAcct.getTimezone();
|
||||
final String timezone = userAcct.getTimezone();
|
||||
float offsetInHrs = 0f;
|
||||
if (timezone != null) {
|
||||
TimeZone t = TimeZone.getTimeZone(timezone);
|
||||
final TimeZone t = TimeZone.getTimeZone(timezone);
|
||||
s_logger.info("Current user logged in under " + timezone + " timezone");
|
||||
|
||||
java.util.Date date = new java.util.Date();
|
||||
long longDate = date.getTime();
|
||||
float offsetInMs = (t.getOffset(longDate));
|
||||
final java.util.Date date = new java.util.Date();
|
||||
final long longDate = date.getTime();
|
||||
final float offsetInMs = (t.getOffset(longDate));
|
||||
offsetInHrs = offsetInMs / (1000 * 60 * 60);
|
||||
s_logger.info("Timezone offset from UTC is: " + offsetInHrs);
|
||||
}
|
||||
|
||||
Account account = _accountMgr.getAccount(userAcct.getAccountId());
|
||||
final Account account = _accountMgr.getAccount(userAcct.getAccountId());
|
||||
|
||||
// set the userId and account object for everyone
|
||||
session.setAttribute("userid", userAcct.getId());
|
||||
UserVO user = (UserVO)_accountMgr.getActiveUser(userAcct.getId());
|
||||
final UserVO user = (UserVO)_accountMgr.getActiveUser(userAcct.getId());
|
||||
if (user.getUuid() != null) {
|
||||
session.setAttribute("user_UUID", user.getUuid());
|
||||
}
|
||||
@ -831,7 +855,7 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
|
||||
session.setAttribute("account", account.getAccountName());
|
||||
|
||||
session.setAttribute("domainid", account.getDomainId());
|
||||
DomainVO domain = (DomainVO)_domainMgr.getDomain(account.getDomainId());
|
||||
final DomainVO domain = (DomainVO)_domainMgr.getDomain(account.getDomainId());
|
||||
if (domain.getUuid() != null) {
|
||||
session.setAttribute("domain_UUID", domain.getUuid());
|
||||
}
|
||||
@ -847,10 +871,10 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
|
||||
|
||||
// (bug 5483) generate a session key that the user must submit on every request to prevent CSRF, add that
|
||||
// to the login response so that session-based authenticators know to send the key back
|
||||
SecureRandom sesssionKeyRandom = new SecureRandom();
|
||||
byte sessionKeyBytes[] = new byte[20];
|
||||
final SecureRandom sesssionKeyRandom = new SecureRandom();
|
||||
final byte sessionKeyBytes[] = new byte[20];
|
||||
sesssionKeyRandom.nextBytes(sessionKeyBytes);
|
||||
String sessionKey = Base64.encodeBase64String(sessionKeyBytes);
|
||||
final String sessionKey = Base64.encodeBase64String(sessionKeyBytes);
|
||||
session.setAttribute("sessionkey", sessionKey);
|
||||
|
||||
return;
|
||||
@ -859,14 +883,14 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logoutUser(long userId) {
|
||||
public void logoutUser(final long userId) {
|
||||
_accountMgr.logoutUser(userId);
|
||||
return;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean verifyUser(Long userId) {
|
||||
User user = _accountMgr.getUserIncludingRemoved(userId);
|
||||
public boolean verifyUser(final Long userId) {
|
||||
final User user = _accountMgr.getUserIncludingRemoved(userId);
|
||||
Account account = null;
|
||||
if (user != null) {
|
||||
account = _accountMgr.getAccount(user.getAccountId());
|
||||
@ -880,46 +904,46 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
|
||||
return true;
|
||||
}
|
||||
|
||||
private void checkCommandAvailable(User user, String commandName) throws PermissionDeniedException {
|
||||
private void checkCommandAvailable(final User user, final String commandName) throws PermissionDeniedException {
|
||||
if (user == null) {
|
||||
throw new PermissionDeniedException("User is null for role based API access check for command" + commandName);
|
||||
}
|
||||
|
||||
for (APIChecker apiChecker : _apiAccessCheckers) {
|
||||
for (final APIChecker apiChecker : _apiAccessCheckers) {
|
||||
apiChecker.checkAccess(user, commandName);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getCmdClass(String cmdName) {
|
||||
public Class<?> getCmdClass(final String cmdName) {
|
||||
return s_apiNameCmdClassMap.get(cmdName);
|
||||
}
|
||||
|
||||
// FIXME: rather than isError, we might was to pass in the status code to give more flexibility
|
||||
private void writeResponse(HttpResponse resp, final String responseText, final int statusCode, String responseType, String reasonPhrase) {
|
||||
private void writeResponse(final HttpResponse resp, final String responseText, final int statusCode, final String responseType, final String reasonPhrase) {
|
||||
try {
|
||||
resp.setStatusCode(statusCode);
|
||||
resp.setReasonPhrase(reasonPhrase);
|
||||
|
||||
BasicHttpEntity body = new BasicHttpEntity();
|
||||
final BasicHttpEntity body = new BasicHttpEntity();
|
||||
if (BaseCmd.RESPONSE_TYPE_JSON.equalsIgnoreCase(responseType)) {
|
||||
// JSON response
|
||||
body.setContentType(jsonContentType);
|
||||
if (responseText == null) {
|
||||
body.setContent(new ByteArrayInputStream("{ \"error\" : { \"description\" : \"Internal Server Error\" } }".getBytes("UTF-8")));
|
||||
body.setContent(new ByteArrayInputStream("{ \"error\" : { \"description\" : \"Internal Server Error\" } }".getBytes(UTF_8)));
|
||||
}
|
||||
} else {
|
||||
body.setContentType("text/xml");
|
||||
if (responseText == null) {
|
||||
body.setContent(new ByteArrayInputStream("<error>Internal Server Error</error>".getBytes("UTF-8")));
|
||||
body.setContent(new ByteArrayInputStream("<error>Internal Server Error</error>".getBytes(UTF_8)));
|
||||
}
|
||||
}
|
||||
|
||||
if (responseText != null) {
|
||||
body.setContent(new ByteArrayInputStream(responseText.getBytes("UTF-8")));
|
||||
body.setContent(new ByteArrayInputStream(responseText.getBytes(UTF_8)));
|
||||
}
|
||||
resp.setEntity(body);
|
||||
} catch (Exception ex) {
|
||||
} catch (final Exception ex) {
|
||||
s_logger.error("error!", ex);
|
||||
}
|
||||
}
|
||||
@ -934,10 +958,10 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
|
||||
private ServerSocket _serverSocket = null;
|
||||
private HttpParams _params = null;
|
||||
|
||||
public ListenerThread(ApiServer requestHandler, int port) {
|
||||
public ListenerThread(final ApiServer requestHandler, final int port) {
|
||||
try {
|
||||
_serverSocket = new ServerSocket(port);
|
||||
} catch (IOException ioex) {
|
||||
} catch (final IOException ioex) {
|
||||
s_logger.error("error initializing api server", ioex);
|
||||
return;
|
||||
}
|
||||
@ -950,14 +974,14 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
|
||||
.setParameter(CoreProtocolPNames.ORIGIN_SERVER, "HttpComponents/1.1");
|
||||
|
||||
// Set up the HTTP protocol processor
|
||||
BasicHttpProcessor httpproc = new BasicHttpProcessor();
|
||||
final BasicHttpProcessor httpproc = new BasicHttpProcessor();
|
||||
httpproc.addInterceptor(new ResponseDate());
|
||||
httpproc.addInterceptor(new ResponseServer());
|
||||
httpproc.addInterceptor(new ResponseContent());
|
||||
httpproc.addInterceptor(new ResponseConnControl());
|
||||
|
||||
// Set up request handlers
|
||||
HttpRequestHandlerRegistry reqistry = new HttpRequestHandlerRegistry();
|
||||
final HttpRequestHandlerRegistry reqistry = new HttpRequestHandlerRegistry();
|
||||
reqistry.register("*", requestHandler);
|
||||
|
||||
// Set up the HTTP service
|
||||
@ -972,15 +996,15 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
|
||||
while (!Thread.interrupted()) {
|
||||
try {
|
||||
// Set up HTTP connection
|
||||
Socket socket = _serverSocket.accept();
|
||||
DefaultHttpServerConnection conn = new DefaultHttpServerConnection();
|
||||
final Socket socket = _serverSocket.accept();
|
||||
final DefaultHttpServerConnection conn = new DefaultHttpServerConnection();
|
||||
conn.bind(socket, _params);
|
||||
|
||||
// Execute a new worker task to handle the request
|
||||
s_executor.execute(new WorkerTask(_httpService, conn, s_workerCount++));
|
||||
} catch (InterruptedIOException ex) {
|
||||
} catch (final InterruptedIOException ex) {
|
||||
break;
|
||||
} catch (IOException e) {
|
||||
} catch (final IOException e) {
|
||||
s_logger.error("I/O error initializing connection thread", e);
|
||||
break;
|
||||
}
|
||||
@ -999,33 +1023,33 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
|
||||
|
||||
@Override
|
||||
protected void runInContext() {
|
||||
HttpContext context = new BasicHttpContext(null);
|
||||
final HttpContext context = new BasicHttpContext(null);
|
||||
try {
|
||||
while (!Thread.interrupted() && _conn.isOpen()) {
|
||||
_httpService.handleRequest(_conn, context);
|
||||
_conn.close();
|
||||
}
|
||||
} catch (ConnectionClosedException ex) {
|
||||
} catch (final ConnectionClosedException ex) {
|
||||
if (s_logger.isTraceEnabled()) {
|
||||
s_logger.trace("ApiServer: Client closed connection");
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
} catch (final IOException ex) {
|
||||
if (s_logger.isTraceEnabled()) {
|
||||
s_logger.trace("ApiServer: IOException - " + ex);
|
||||
}
|
||||
} catch (HttpException ex) {
|
||||
} catch (final HttpException ex) {
|
||||
s_logger.warn("ApiServer: Unrecoverable HTTP protocol violation" + ex);
|
||||
} finally {
|
||||
try {
|
||||
_conn.shutdown();
|
||||
} catch (IOException ignore) {
|
||||
} catch (final IOException ignore) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSerializedApiError(int errorCode, String errorText, Map<String, Object[]> apiCommandParams, String responseType) {
|
||||
public String getSerializedApiError(final int errorCode, final String errorText, final Map<String, Object[]> apiCommandParams, final String responseType) {
|
||||
String responseName = null;
|
||||
Class<?> cmdClass = null;
|
||||
String responseText = null;
|
||||
@ -1034,10 +1058,10 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
|
||||
if (apiCommandParams == null || apiCommandParams.isEmpty()) {
|
||||
responseName = "errorresponse";
|
||||
} else {
|
||||
Object cmdObj = apiCommandParams.get("command");
|
||||
final Object cmdObj = apiCommandParams.get(ApiConstants.COMMAND);
|
||||
// cmd name can be null when "command" parameter is missing in the request
|
||||
if (cmdObj != null) {
|
||||
String cmdName = ((String[])cmdObj)[0];
|
||||
final String cmdName = ((String[])cmdObj)[0];
|
||||
cmdClass = getCmdClass(cmdName);
|
||||
if (cmdClass != null) {
|
||||
responseName = ((BaseCmd)cmdClass.newInstance()).getCommandName();
|
||||
@ -1046,21 +1070,21 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
|
||||
}
|
||||
}
|
||||
}
|
||||
ExceptionResponse apiResponse = new ExceptionResponse();
|
||||
final ExceptionResponse apiResponse = new ExceptionResponse();
|
||||
apiResponse.setErrorCode(errorCode);
|
||||
apiResponse.setErrorText(errorText);
|
||||
apiResponse.setResponseName(responseName);
|
||||
SerializationContext.current().setUuidTranslation(true);
|
||||
responseText = ApiResponseSerializer.toSerializedString(apiResponse, responseType);
|
||||
|
||||
} catch (Exception e) {
|
||||
} catch (final Exception e) {
|
||||
s_logger.error("Exception responding to http request", e);
|
||||
}
|
||||
return responseText;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSerializedApiError(ServerApiException ex, Map<String, Object[]> apiCommandParams, String responseType) {
|
||||
public String getSerializedApiError(final ServerApiException ex, final Map<String, Object[]> apiCommandParams, final String responseType) {
|
||||
String responseName = null;
|
||||
Class<?> cmdClass = null;
|
||||
String responseText = null;
|
||||
@ -1073,11 +1097,11 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
|
||||
if (ex.getErrorCode() == ApiErrorCode.UNSUPPORTED_ACTION_ERROR || apiCommandParams == null || apiCommandParams.isEmpty()) {
|
||||
responseName = "errorresponse";
|
||||
} else {
|
||||
Object cmdObj = apiCommandParams.get("command");
|
||||
final Object cmdObj = apiCommandParams.get(ApiConstants.COMMAND);
|
||||
// cmd name can be null when "command" parameter is missing in
|
||||
// the request
|
||||
if (cmdObj != null) {
|
||||
String cmdName = ((String[])cmdObj)[0];
|
||||
final String cmdName = ((String[])cmdObj)[0];
|
||||
cmdClass = getCmdClass(cmdName);
|
||||
if (cmdClass != null) {
|
||||
responseName = ((BaseCmd)cmdClass.newInstance()).getCommandName();
|
||||
@ -1086,11 +1110,11 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
|
||||
}
|
||||
}
|
||||
}
|
||||
ExceptionResponse apiResponse = new ExceptionResponse();
|
||||
final ExceptionResponse apiResponse = new ExceptionResponse();
|
||||
apiResponse.setErrorCode(ex.getErrorCode().getHttpCode());
|
||||
apiResponse.setErrorText(ex.getDescription());
|
||||
apiResponse.setResponseName(responseName);
|
||||
ArrayList<ExceptionProxyObject> idList = ex.getIdProxyList();
|
||||
final ArrayList<ExceptionProxyObject> idList = ex.getIdProxyList();
|
||||
if (idList != null) {
|
||||
for (int i = 0; i < idList.size(); i++) {
|
||||
apiResponse.addProxyObject(idList.get(i));
|
||||
@ -1103,7 +1127,7 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
|
||||
SerializationContext.current().setUuidTranslation(true);
|
||||
responseText = ApiResponseSerializer.toSerializedString(apiResponse, responseType);
|
||||
|
||||
} catch (Exception e) {
|
||||
} catch (final Exception e) {
|
||||
s_logger.error("Exception responding to http request", e);
|
||||
}
|
||||
return responseText;
|
||||
@ -1114,7 +1138,7 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
|
||||
}
|
||||
|
||||
@Inject
|
||||
public void setPluggableServices(List<PluggableService> pluggableServices) {
|
||||
public void setPluggableServices(final List<PluggableService> pluggableServices) {
|
||||
_pluggableServices = pluggableServices;
|
||||
}
|
||||
|
||||
@ -1123,7 +1147,7 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
|
||||
}
|
||||
|
||||
@Inject
|
||||
public void setApiAccessCheckers(List<APIChecker> apiAccessCheckers) {
|
||||
public void setApiAccessCheckers(final List<APIChecker> apiAccessCheckers) {
|
||||
_apiAccessCheckers = apiAccessCheckers;
|
||||
}
|
||||
|
||||
@ -1131,7 +1155,7 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
|
||||
return encodeApiResponse;
|
||||
}
|
||||
|
||||
private static void setEncodeApiResponse(boolean encodeApiResponse) {
|
||||
private static void setEncodeApiResponse(final boolean encodeApiResponse) {
|
||||
ApiServer.encodeApiResponse = encodeApiResponse;
|
||||
}
|
||||
|
||||
|
||||
@ -35,6 +35,7 @@ import org.apache.log4j.Logger;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.context.support.SpringBeanAutowiringSupport;
|
||||
|
||||
import org.apache.cloudstack.api.ApiConstants;
|
||||
import org.apache.cloudstack.api.ApiErrorCode;
|
||||
import org.apache.cloudstack.api.BaseCmd;
|
||||
import org.apache.cloudstack.api.ServerApiException;
|
||||
@ -67,40 +68,40 @@ public class ApiServlet extends HttpServlet {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(ServletConfig config) throws ServletException {
|
||||
public void init(final ServletConfig config) throws ServletException {
|
||||
SpringBeanAutowiringSupport.processInjectionBasedOnServletContext(this, config.getServletContext());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
|
||||
protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) {
|
||||
processRequest(req, resp);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doPost(HttpServletRequest req, HttpServletResponse resp) {
|
||||
protected void doPost(final HttpServletRequest req, final HttpServletResponse resp) {
|
||||
processRequest(req, resp);
|
||||
}
|
||||
|
||||
private void utf8Fixup(HttpServletRequest req, Map<String, Object[]> params) {
|
||||
private void utf8Fixup(final HttpServletRequest req, final Map<String, Object[]> params) {
|
||||
if (req.getQueryString() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
String[] paramsInQueryString = req.getQueryString().split("&");
|
||||
final String[] paramsInQueryString = req.getQueryString().split("&");
|
||||
if (paramsInQueryString != null) {
|
||||
for (String param : paramsInQueryString) {
|
||||
String[] paramTokens = param.split("=", 2);
|
||||
for (final String param : paramsInQueryString) {
|
||||
final String[] paramTokens = param.split("=", 2);
|
||||
if (paramTokens != null && paramTokens.length == 2) {
|
||||
String name = paramTokens[0];
|
||||
String value = paramTokens[1];
|
||||
|
||||
try {
|
||||
name = URLDecoder.decode(name, "UTF-8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
} catch (final UnsupportedEncodingException e) {
|
||||
}
|
||||
try {
|
||||
value = URLDecoder.decode(value, "UTF-8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
} catch (final UnsupportedEncodingException e) {
|
||||
}
|
||||
params.put(name, new String[] {value});
|
||||
} else {
|
||||
@ -119,13 +120,13 @@ public class ApiServlet extends HttpServlet {
|
||||
});
|
||||
}
|
||||
|
||||
private void processRequestInContext(HttpServletRequest req, HttpServletResponse resp) {
|
||||
StringBuffer auditTrailSb = new StringBuffer();
|
||||
private void processRequestInContext(final HttpServletRequest req, final HttpServletResponse resp) {
|
||||
final StringBuffer auditTrailSb = new StringBuffer();
|
||||
auditTrailSb.append(" " + req.getRemoteAddr());
|
||||
auditTrailSb.append(" -- " + req.getMethod() + " ");
|
||||
// get the response format since we'll need it in a couple of places
|
||||
String responseType = BaseCmd.RESPONSE_TYPE_XML;
|
||||
Map<String, Object[]> params = new HashMap<String, Object[]>();
|
||||
final Map<String, Object[]> params = new HashMap<String, Object[]>();
|
||||
params.putAll(req.getParameterMap());
|
||||
|
||||
// For HTTP GET requests, it seems that HttpServletRequest.getParameterMap() actually tries
|
||||
@ -143,19 +144,19 @@ public class ApiServlet extends HttpServlet {
|
||||
|
||||
try {
|
||||
HttpSession session = req.getSession(false);
|
||||
Object[] responseTypeParam = params.get("response");
|
||||
final Object[] responseTypeParam = params.get(ApiConstants.RESPONSE);
|
||||
if (responseTypeParam != null) {
|
||||
responseType = (String)responseTypeParam[0];
|
||||
}
|
||||
|
||||
Object[] commandObj = params.get("command");
|
||||
final Object[] commandObj = params.get(ApiConstants.COMMAND);
|
||||
if (commandObj != null) {
|
||||
String command = (String)commandObj[0];
|
||||
final String command = (String)commandObj[0];
|
||||
if ("logout".equalsIgnoreCase(command)) {
|
||||
// if this is just a logout, invalidate the session and return
|
||||
if (session != null) {
|
||||
Long userId = (Long)session.getAttribute("userid");
|
||||
Account account = (Account)session.getAttribute("accountobj");
|
||||
final Long userId = (Long)session.getAttribute("userid");
|
||||
final Account account = (Account)session.getAttribute("accountobj");
|
||||
Long accountId = null;
|
||||
if (account != null) {
|
||||
accountId = account.getId();
|
||||
@ -166,7 +167,7 @@ public class ApiServlet extends HttpServlet {
|
||||
}
|
||||
try {
|
||||
session.invalidate();
|
||||
} catch (IllegalStateException ise) {
|
||||
} catch (final IllegalStateException ise) {
|
||||
}
|
||||
}
|
||||
auditTrailSb.append("command=logout");
|
||||
@ -179,18 +180,18 @@ public class ApiServlet extends HttpServlet {
|
||||
if (session != null) {
|
||||
try {
|
||||
session.invalidate();
|
||||
} catch (IllegalStateException ise) {
|
||||
} catch (final IllegalStateException ise) {
|
||||
}
|
||||
}
|
||||
session = req.getSession(true);
|
||||
String[] username = (String[])params.get("username");
|
||||
String[] password = (String[])params.get("password");
|
||||
String[] domainIdArr = (String[])params.get("domainid");
|
||||
final String[] username = (String[])params.get(ApiConstants.USERNAME);
|
||||
final String[] password = (String[])params.get(ApiConstants.PASSWORD);
|
||||
String[] domainIdArr = (String[])params.get(ApiConstants.DOMAIN_ID);
|
||||
|
||||
if (domainIdArr == null) {
|
||||
domainIdArr = (String[])params.get("domainId");
|
||||
domainIdArr = (String[])params.get(ApiConstants.DOMAIN__ID);
|
||||
}
|
||||
String[] domainName = (String[])params.get("domain");
|
||||
final String[] domainName = (String[])params.get(ApiConstants.DOMAIN);
|
||||
Long domainId = null;
|
||||
if ((domainIdArr != null) && (domainIdArr.length > 0)) {
|
||||
try {
|
||||
@ -200,10 +201,10 @@ public class ApiServlet extends HttpServlet {
|
||||
domainId = new Long(Long.parseLong(domainIdArr[0]));
|
||||
}
|
||||
auditTrailSb.append(" domainid=" + domainId);// building the params for POST call
|
||||
} catch (NumberFormatException e) {
|
||||
} catch (final NumberFormatException e) {
|
||||
s_logger.warn("Invalid domain id entered by user");
|
||||
auditTrailSb.append(" " + HttpServletResponse.SC_UNAUTHORIZED + " " + "Invalid domain id entered, please enter a valid one");
|
||||
String serializedResponse =
|
||||
final String serializedResponse =
|
||||
_apiServer.getSerializedApiError(HttpServletResponse.SC_UNAUTHORIZED, "Invalid domain id entered, please enter a valid one", params,
|
||||
responseType);
|
||||
writeResponse(resp, serializedResponse, HttpServletResponse.SC_UNAUTHORIZED, responseType);
|
||||
@ -225,24 +226,24 @@ public class ApiServlet extends HttpServlet {
|
||||
}
|
||||
|
||||
if (username != null) {
|
||||
String pwd = ((password == null) ? null : password[0]);
|
||||
final String pwd = ((password == null) ? null : password[0]);
|
||||
try {
|
||||
_apiServer.loginUser(session, username[0], pwd, domainId, domain, req.getRemoteAddr(), params);
|
||||
auditTrailSb.insert(0, "(userId=" + session.getAttribute("userid") + " accountId=" + ((Account)session.getAttribute("accountobj")).getId() +
|
||||
" sessionId=" + session.getId() + ")");
|
||||
String loginResponse = getLoginSuccessResponse(session, responseType);
|
||||
final String loginResponse = getLoginSuccessResponse(session, responseType);
|
||||
writeResponse(resp, loginResponse, HttpServletResponse.SC_OK, responseType);
|
||||
return;
|
||||
} catch (CloudAuthenticationException ex) {
|
||||
} catch (final CloudAuthenticationException ex) {
|
||||
// TODO: fall through to API key, or just fail here w/ auth error? (HTTP 401)
|
||||
try {
|
||||
session.invalidate();
|
||||
} catch (IllegalStateException ise) {
|
||||
} catch (final IllegalStateException ise) {
|
||||
}
|
||||
|
||||
auditTrailSb.append(" " + ApiErrorCode.ACCOUNT_ERROR + " " + ex.getMessage() != null ? ex.getMessage()
|
||||
: "failed to authenticate user, check if username/password are correct");
|
||||
String serializedResponse =
|
||||
final String serializedResponse =
|
||||
_apiServer.getSerializedApiError(ApiErrorCode.ACCOUNT_ERROR.getHttpCode(), ex.getMessage() != null ? ex.getMessage()
|
||||
: "failed to authenticate user, check if username/password are correct", params, responseType);
|
||||
writeResponse(resp, serializedResponse, ApiErrorCode.ACCOUNT_ERROR.getHttpCode(), responseType);
|
||||
@ -252,7 +253,7 @@ public class ApiServlet extends HttpServlet {
|
||||
}
|
||||
}
|
||||
auditTrailSb.append(req.getQueryString());
|
||||
boolean isNew = ((session == null) ? true : session.isNew());
|
||||
final boolean isNew = ((session == null) ? true : session.isNew());
|
||||
|
||||
// Initialize an empty context and we will update it after we have verified the request below,
|
||||
// we no longer rely on web-session here, verifyRequest will populate user/account information
|
||||
@ -261,17 +262,17 @@ public class ApiServlet extends HttpServlet {
|
||||
|
||||
if (!isNew) {
|
||||
userId = (Long)session.getAttribute("userid");
|
||||
String account = (String)session.getAttribute("account");
|
||||
Object accountObj = session.getAttribute("accountobj");
|
||||
String sessionKey = (String)session.getAttribute("sessionkey");
|
||||
String[] sessionKeyParam = (String[])params.get("sessionkey");
|
||||
final String account = (String)session.getAttribute("account");
|
||||
final Object accountObj = session.getAttribute("accountobj");
|
||||
final String sessionKey = (String)session.getAttribute("sessionkey");
|
||||
final String[] sessionKeyParam = (String[])params.get(ApiConstants.SESSIONKEY);
|
||||
if ((sessionKeyParam == null) || (sessionKey == null) || !sessionKey.equals(sessionKeyParam[0])) {
|
||||
try {
|
||||
session.invalidate();
|
||||
} catch (IllegalStateException ise) {
|
||||
} catch (final IllegalStateException ise) {
|
||||
}
|
||||
auditTrailSb.append(" " + HttpServletResponse.SC_UNAUTHORIZED + " " + "unable to verify user credentials");
|
||||
String serializedResponse =
|
||||
final String serializedResponse =
|
||||
_apiServer.getSerializedApiError(HttpServletResponse.SC_UNAUTHORIZED, "unable to verify user credentials", params, responseType);
|
||||
writeResponse(resp, serializedResponse, HttpServletResponse.SC_UNAUTHORIZED, responseType);
|
||||
return;
|
||||
@ -279,26 +280,26 @@ public class ApiServlet extends HttpServlet {
|
||||
|
||||
// Do a sanity check here to make sure the user hasn't already been deleted
|
||||
if ((userId != null) && (account != null) && (accountObj != null) && _apiServer.verifyUser(userId)) {
|
||||
String[] command = (String[])params.get("command");
|
||||
final String[] command = (String[])params.get(ApiConstants.COMMAND);
|
||||
if (command == null) {
|
||||
s_logger.info("missing command, ignoring request...");
|
||||
auditTrailSb.append(" " + HttpServletResponse.SC_BAD_REQUEST + " " + "no command specified");
|
||||
String serializedResponse = _apiServer.getSerializedApiError(HttpServletResponse.SC_BAD_REQUEST, "no command specified", params, responseType);
|
||||
final String serializedResponse = _apiServer.getSerializedApiError(HttpServletResponse.SC_BAD_REQUEST, "no command specified", params, responseType);
|
||||
writeResponse(resp, serializedResponse, HttpServletResponse.SC_BAD_REQUEST, responseType);
|
||||
return;
|
||||
}
|
||||
User user = _entityMgr.findById(User.class, userId);
|
||||
final User user = _entityMgr.findById(User.class, userId);
|
||||
CallContext.register(user, (Account)accountObj);
|
||||
} else {
|
||||
// Invalidate the session to ensure we won't allow a request across management server
|
||||
// restarts if the userId was serialized to the stored session
|
||||
try {
|
||||
session.invalidate();
|
||||
} catch (IllegalStateException ise) {
|
||||
} catch (final IllegalStateException ise) {
|
||||
}
|
||||
|
||||
auditTrailSb.append(" " + HttpServletResponse.SC_UNAUTHORIZED + " " + "unable to verify user credentials");
|
||||
String serializedResponse =
|
||||
final String serializedResponse =
|
||||
_apiServer.getSerializedApiError(HttpServletResponse.SC_UNAUTHORIZED, "unable to verify user credentials", params, responseType);
|
||||
writeResponse(resp, serializedResponse, HttpServletResponse.SC_UNAUTHORIZED, responseType);
|
||||
return;
|
||||
@ -326,29 +327,29 @@ public class ApiServlet extends HttpServlet {
|
||||
|
||||
// Add the HTTP method (GET/POST/PUT/DELETE) as well into the params map.
|
||||
params.put("httpmethod", new String[] {req.getMethod()});
|
||||
String response = _apiServer.handleRequest(params, responseType, auditTrailSb);
|
||||
final String response = _apiServer.handleRequest(params, responseType, auditTrailSb);
|
||||
writeResponse(resp, response != null ? response : "", HttpServletResponse.SC_OK, responseType);
|
||||
} else {
|
||||
if (session != null) {
|
||||
try {
|
||||
session.invalidate();
|
||||
} catch (IllegalStateException ise) {
|
||||
} catch (final IllegalStateException ise) {
|
||||
}
|
||||
}
|
||||
|
||||
auditTrailSb.append(" " + HttpServletResponse.SC_UNAUTHORIZED + " " + "unable to verify user credentials and/or request signature");
|
||||
String serializedResponse =
|
||||
final String serializedResponse =
|
||||
_apiServer.getSerializedApiError(HttpServletResponse.SC_UNAUTHORIZED, "unable to verify user credentials and/or request signature", params,
|
||||
responseType);
|
||||
writeResponse(resp, serializedResponse, HttpServletResponse.SC_UNAUTHORIZED, responseType);
|
||||
|
||||
}
|
||||
} catch (ServerApiException se) {
|
||||
String serializedResponseText = _apiServer.getSerializedApiError(se, params, responseType);
|
||||
} catch (final ServerApiException se) {
|
||||
final String serializedResponseText = _apiServer.getSerializedApiError(se, params, responseType);
|
||||
resp.setHeader("X-Description", se.getDescription());
|
||||
writeResponse(resp, serializedResponseText, se.getErrorCode().getHttpCode(), responseType);
|
||||
auditTrailSb.append(" " + se.getErrorCode() + " " + se.getDescription());
|
||||
} catch (Exception ex) {
|
||||
} catch (final Exception ex) {
|
||||
s_logger.error("unknown exception writing api response", ex);
|
||||
auditTrailSb.append(" unknown exception writing api response");
|
||||
} finally {
|
||||
@ -372,7 +373,7 @@ public class ApiServlet extends HttpServlet {
|
||||
*/
|
||||
|
||||
// FIXME: rather than isError, we might was to pass in the status code to give more flexibility
|
||||
private void writeResponse(HttpServletResponse resp, String response, int responseCode, String responseType) {
|
||||
private void writeResponse(final HttpServletResponse resp, final String response, final int responseCode, final String responseType) {
|
||||
try {
|
||||
if (BaseCmd.RESPONSE_TYPE_JSON.equalsIgnoreCase(responseType)) {
|
||||
resp.setContentType(ApiServer.getJsonContentType() + "; charset=UTF-8");
|
||||
@ -382,11 +383,11 @@ public class ApiServlet extends HttpServlet {
|
||||
|
||||
resp.setStatus(responseCode);
|
||||
resp.getWriter().print(response);
|
||||
} catch (IOException ioex) {
|
||||
} catch (final IOException ioex) {
|
||||
if (s_logger.isTraceEnabled()) {
|
||||
s_logger.trace("exception writing response: " + ioex);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
} catch (final Exception ex) {
|
||||
if (!(ex instanceof IllegalStateException)) {
|
||||
s_logger.error("unknown exception writing api response", ex);
|
||||
}
|
||||
@ -394,29 +395,29 @@ public class ApiServlet extends HttpServlet {
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
private String getLoginSuccessResponse(HttpSession session, String responseType) {
|
||||
StringBuffer sb = new StringBuffer();
|
||||
int inactiveInterval = session.getMaxInactiveInterval();
|
||||
private String getLoginSuccessResponse(final HttpSession session, final String responseType) {
|
||||
final StringBuffer sb = new StringBuffer();
|
||||
final int inactiveInterval = session.getMaxInactiveInterval();
|
||||
|
||||
String user_UUID = (String)session.getAttribute("user_UUID");
|
||||
final String user_UUID = (String)session.getAttribute("user_UUID");
|
||||
session.removeAttribute("user_UUID");
|
||||
|
||||
String domain_UUID = (String)session.getAttribute("domain_UUID");
|
||||
final String domain_UUID = (String)session.getAttribute("domain_UUID");
|
||||
session.removeAttribute("domain_UUID");
|
||||
|
||||
if (BaseCmd.RESPONSE_TYPE_JSON.equalsIgnoreCase(responseType)) {
|
||||
sb.append("{ \"loginresponse\" : { ");
|
||||
Enumeration attrNames = session.getAttributeNames();
|
||||
final Enumeration attrNames = session.getAttributeNames();
|
||||
if (attrNames != null) {
|
||||
sb.append("\"timeout\" : \"" + inactiveInterval + "\"");
|
||||
while (attrNames.hasMoreElements()) {
|
||||
String attrName = (String)attrNames.nextElement();
|
||||
final String attrName = (String)attrNames.nextElement();
|
||||
if ("userid".equalsIgnoreCase(attrName)) {
|
||||
sb.append(", \"" + attrName + "\" : \"" + user_UUID + "\"");
|
||||
} else if ("domainid".equalsIgnoreCase(attrName)) {
|
||||
sb.append(", \"" + attrName + "\" : \"" + domain_UUID + "\"");
|
||||
} else {
|
||||
Object attrObj = session.getAttribute(attrName);
|
||||
final Object attrObj = session.getAttribute(attrName);
|
||||
if ((attrObj instanceof String) || (attrObj instanceof Long)) {
|
||||
sb.append(", \"" + attrName + "\" : \"" + attrObj.toString() + "\"");
|
||||
}
|
||||
@ -428,16 +429,16 @@ public class ApiServlet extends HttpServlet {
|
||||
sb.append("<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>");
|
||||
sb.append("<loginresponse cloud-stack-version=\"" + ApiDBUtils.getVersion() + "\">");
|
||||
sb.append("<timeout>" + inactiveInterval + "</timeout>");
|
||||
Enumeration attrNames = session.getAttributeNames();
|
||||
final Enumeration attrNames = session.getAttributeNames();
|
||||
if (attrNames != null) {
|
||||
while (attrNames.hasMoreElements()) {
|
||||
String attrName = (String)attrNames.nextElement();
|
||||
if ("userid".equalsIgnoreCase(attrName)) {
|
||||
final String attrName = (String)attrNames.nextElement();
|
||||
if (ApiConstants.USER_ID.equalsIgnoreCase(attrName)) {
|
||||
sb.append("<" + attrName + ">" + user_UUID + "</" + attrName + ">");
|
||||
} else if ("domainid".equalsIgnoreCase(attrName)) {
|
||||
sb.append("<" + attrName + ">" + domain_UUID + "</" + attrName + ">");
|
||||
} else {
|
||||
Object attrObj = session.getAttribute(attrName);
|
||||
final Object attrObj = session.getAttribute(attrName);
|
||||
if (attrObj instanceof String || attrObj instanceof Long || attrObj instanceof Short) {
|
||||
sb.append("<" + attrName + ">" + attrObj.toString() + "</" + attrName + ">");
|
||||
}
|
||||
@ -450,8 +451,8 @@ public class ApiServlet extends HttpServlet {
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private String getLogoutSuccessResponse(String responseType) {
|
||||
StringBuffer sb = new StringBuffer();
|
||||
private String getLogoutSuccessResponse(final String responseType) {
|
||||
final StringBuffer sb = new StringBuffer();
|
||||
if (BaseCmd.RESPONSE_TYPE_JSON.equalsIgnoreCase(responseType)) {
|
||||
sb.append("{ \"logoutresponse\" : { \"description\" : \"success\" } }");
|
||||
} else {
|
||||
|
||||
56
server/src/com/cloud/api/dispatch/CommandCreationWorker.java
Normal file
56
server/src/com/cloud/api/dispatch/CommandCreationWorker.java
Normal file
@ -0,0 +1,56 @@
|
||||
// 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.dispatch;
|
||||
|
||||
import org.apache.cloudstack.api.ApiErrorCode;
|
||||
import org.apache.cloudstack.api.BaseAsyncCreateCmd;
|
||||
import org.apache.cloudstack.api.BaseCmd;
|
||||
import org.apache.cloudstack.api.ServerApiException;
|
||||
|
||||
import com.cloud.exception.ResourceAllocationException;
|
||||
|
||||
|
||||
/**
|
||||
* This worker invokes create on the {@link BaseCmd} itself
|
||||
*
|
||||
* @author afornie
|
||||
*/
|
||||
public class CommandCreationWorker implements DispatchWorker {
|
||||
|
||||
private static final String ATTEMP_TO_CREATE_NON_CREATION_CMD =
|
||||
"Trying to invoke creation on a Command that is not " +
|
||||
BaseAsyncCreateCmd.class.getName();
|
||||
|
||||
@Override
|
||||
public void handle(final DispatchTask task) {
|
||||
final BaseCmd cmd = task.getCmd();
|
||||
|
||||
if (cmd instanceof BaseAsyncCreateCmd) {
|
||||
try {
|
||||
((BaseAsyncCreateCmd)cmd).create();
|
||||
} catch (final ResourceAllocationException e) {
|
||||
throw new ServerApiException(ApiErrorCode.RESOURCE_ALLOCATION_ERROR,
|
||||
e.getMessage(), e);
|
||||
}
|
||||
} else {
|
||||
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR,
|
||||
ATTEMP_TO_CREATE_NON_CREATION_CMD);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
40
server/src/com/cloud/api/dispatch/DispatchChain.java
Normal file
40
server/src/com/cloud/api/dispatch/DispatchChain.java
Normal file
@ -0,0 +1,40 @@
|
||||
// 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.dispatch;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.apache.cloudstack.api.ServerApiException;
|
||||
|
||||
public class DispatchChain {
|
||||
|
||||
protected List<DispatchWorker> workers = new ArrayList<DispatchWorker>();
|
||||
|
||||
public DispatchChain add(final DispatchWorker worker) {
|
||||
workers.add(worker);
|
||||
return this;
|
||||
}
|
||||
|
||||
public void dispatch(final DispatchTask task)
|
||||
throws ServerApiException {
|
||||
|
||||
for (final DispatchWorker worker : workers) {
|
||||
worker.handle(task);
|
||||
}
|
||||
}
|
||||
}
|
||||
72
server/src/com/cloud/api/dispatch/DispatchChainFactory.java
Normal file
72
server/src/com/cloud/api/dispatch/DispatchChainFactory.java
Normal file
@ -0,0 +1,72 @@
|
||||
// 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.dispatch;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import com.cloud.user.AccountManager;
|
||||
|
||||
public class DispatchChainFactory {
|
||||
|
||||
@Inject
|
||||
protected AccountManager _accountMgr;
|
||||
|
||||
@Inject
|
||||
protected ParamGenericValidationWorker paramGenericValidationWorker;
|
||||
|
||||
@Inject
|
||||
protected ParamUnpackWorker paramUnpackWorker;
|
||||
|
||||
@Inject
|
||||
protected ParamProcessWorker paramProcessWorker;
|
||||
|
||||
@Inject
|
||||
protected SpecificCmdValidationWorker specificCmdValidationWorker;
|
||||
|
||||
@Inject
|
||||
protected CommandCreationWorker commandCreationWorker;
|
||||
|
||||
protected DispatchChain standardDispatchChain;
|
||||
|
||||
protected DispatchChain asyncCreationDispatchChain;
|
||||
|
||||
@PostConstruct
|
||||
public void setup() {
|
||||
standardDispatchChain = new DispatchChain().
|
||||
add(paramUnpackWorker).
|
||||
add(paramProcessWorker).
|
||||
add(paramGenericValidationWorker).
|
||||
add(specificCmdValidationWorker);
|
||||
|
||||
asyncCreationDispatchChain = new DispatchChain().
|
||||
add(paramUnpackWorker).
|
||||
add(paramProcessWorker).
|
||||
add(paramGenericValidationWorker).
|
||||
add(specificCmdValidationWorker).
|
||||
add(commandCreationWorker);
|
||||
}
|
||||
|
||||
public DispatchChain getStandardDispatchChain() {
|
||||
return standardDispatchChain;
|
||||
}
|
||||
|
||||
public DispatchChain getAsyncCreationDispatchChain() {
|
||||
return asyncCreationDispatchChain;
|
||||
}
|
||||
}
|
||||
41
server/src/com/cloud/api/dispatch/DispatchTask.java
Normal file
41
server/src/com/cloud/api/dispatch/DispatchTask.java
Normal file
@ -0,0 +1,41 @@
|
||||
package com.cloud.api.dispatch;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.cloudstack.api.BaseCmd;
|
||||
|
||||
/**
|
||||
* This class wraps all the data that any worker could need. If we don't wrap it this
|
||||
* way and we pass the parameters one by one, in the end we could end up having all the
|
||||
* N workers receiving plenty of parameters and changing the signature, each time one
|
||||
* of them changes. This way, if a certain worker needs something else, you just need
|
||||
* to change it in this wrapper class and the worker itself.
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
public class DispatchTask {
|
||||
|
||||
protected BaseCmd cmd;
|
||||
|
||||
protected Map params;
|
||||
|
||||
public DispatchTask(final BaseCmd cmd, final Map params) {
|
||||
this.cmd = cmd;
|
||||
this.params = params;
|
||||
}
|
||||
|
||||
public BaseCmd getCmd() {
|
||||
return cmd;
|
||||
}
|
||||
|
||||
public void setCmd(final BaseCmd cmd) {
|
||||
this.cmd = cmd;
|
||||
}
|
||||
|
||||
public Map getParams() {
|
||||
return params;
|
||||
}
|
||||
|
||||
public void setParams(final Map params) {
|
||||
this.params = params;
|
||||
}
|
||||
}
|
||||
30
server/src/com/cloud/api/dispatch/DispatchWorker.java
Normal file
30
server/src/com/cloud/api/dispatch/DispatchWorker.java
Normal file
@ -0,0 +1,30 @@
|
||||
// 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.dispatch;
|
||||
|
||||
import org.apache.cloudstack.api.ServerApiException;
|
||||
|
||||
/**
|
||||
* Describes the behavior of the workers in the Chain of Responsibility, that receive and
|
||||
* work on a {@link DispatchTask} which will then be passed to next workers.
|
||||
*/
|
||||
public interface DispatchWorker {
|
||||
|
||||
public void handle(DispatchTask task)
|
||||
throws ServerApiException;
|
||||
}
|
||||
@ -0,0 +1,103 @@
|
||||
// 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.dispatch;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.cloudstack.api.ApiConstants;
|
||||
import org.apache.cloudstack.api.BaseCmd;
|
||||
import org.apache.cloudstack.api.Parameter;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
/**
|
||||
* This worker validates parameters in a generic way, by using annotated
|
||||
* restrictions without involving the {@Link BaseCmd}. This worker doesn't
|
||||
* know or care about the meaning of the parameters and that's why we can
|
||||
* have it out of the {@Link BaseCmd}
|
||||
*
|
||||
* @author afornie
|
||||
*/
|
||||
public class ParamGenericValidationWorker implements DispatchWorker {
|
||||
|
||||
protected static Logger s_logger = Logger.getLogger(ParamGenericValidationWorker.class.getName());
|
||||
|
||||
protected static List<String> defaultParamNames = new ArrayList<String>();
|
||||
|
||||
static {
|
||||
defaultParamNames.add(ApiConstants.CTX_START_EVENT_ID);
|
||||
defaultParamNames.add(ApiConstants.COMMAND);
|
||||
defaultParamNames.add(ApiConstants.CMD_EVENT_TYPE);
|
||||
defaultParamNames.add(ApiConstants.USERNAME);
|
||||
defaultParamNames.add(ApiConstants.USER_ID);
|
||||
defaultParamNames.add(ApiConstants.PASSWORD);
|
||||
defaultParamNames.add(ApiConstants.DOMAIN);
|
||||
defaultParamNames.add(ApiConstants.DOMAIN_ID);
|
||||
defaultParamNames.add(ApiConstants.DOMAIN__ID);
|
||||
defaultParamNames.add(ApiConstants.SESSIONKEY);
|
||||
defaultParamNames.add(ApiConstants.RESPONSE);
|
||||
defaultParamNames.add(ApiConstants.PAGE);
|
||||
defaultParamNames.add(ApiConstants.USER_API_KEY);
|
||||
defaultParamNames.add(ApiConstants.API_KEY);
|
||||
defaultParamNames.add(ApiConstants.PAGE_SIZE);
|
||||
defaultParamNames.add(ApiConstants.HTTPMETHOD);
|
||||
defaultParamNames.add(ApiConstants.SIGNATURE);
|
||||
defaultParamNames.add(ApiConstants.CTX_ACCOUNT_ID);
|
||||
defaultParamNames.add(ApiConstants.CTX_START_EVENT_ID);
|
||||
defaultParamNames.add(ApiConstants.CTX_USER_ID);
|
||||
defaultParamNames.add("_");
|
||||
}
|
||||
|
||||
protected static final String ERROR_MSG_PREFIX = "Unknown parameters :";
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Override
|
||||
public void handle(final DispatchTask task) {
|
||||
final BaseCmd cmd = task.getCmd();
|
||||
final Map params = task.getParams();
|
||||
|
||||
final List<String> expectedParamNames = getParamNamesForCommand(cmd);
|
||||
|
||||
final StringBuilder errorMsg = new StringBuilder(ERROR_MSG_PREFIX);
|
||||
boolean foundUnknownParam = false;
|
||||
for (final Object paramName : params.keySet()) {
|
||||
if (!expectedParamNames.contains(paramName)) {
|
||||
errorMsg.append(" ").append(paramName);
|
||||
foundUnknownParam= true;
|
||||
}
|
||||
}
|
||||
|
||||
if (foundUnknownParam) {
|
||||
s_logger.warn(String.format("Received unknown parameters for command %s. %s", cmd.getActualCommandName(), errorMsg));
|
||||
}
|
||||
}
|
||||
|
||||
protected List<String> getParamNamesForCommand(final BaseCmd cmd) {
|
||||
final List<String> paramNames = new ArrayList<String>();
|
||||
// The expected param names are all the specific for the current command class ...
|
||||
for (final Field field : cmd.getParamFields()) {
|
||||
final Parameter parameterAnnotation = field.getAnnotation(Parameter.class);
|
||||
paramNames.add(parameterAnnotation.name());
|
||||
}
|
||||
// ... plus the default ones
|
||||
paramNames.addAll(defaultParamNames);
|
||||
return paramNames;
|
||||
}
|
||||
}
|
||||
428
server/src/com/cloud/api/dispatch/ParamProcessWorker.java
Normal file
428
server/src/com/cloud/api/dispatch/ParamProcessWorker.java
Normal file
@ -0,0 +1,428 @@
|
||||
// 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.dispatch;
|
||||
|
||||
import static org.apache.commons.lang.StringUtils.isNotBlank;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.text.DateFormat;
|
||||
import java.text.ParseException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.StringTokenizer;
|
||||
import java.util.regex.Matcher;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import org.apache.cloudstack.acl.ControlledEntity;
|
||||
import org.apache.cloudstack.acl.InfrastructureEntity;
|
||||
import org.apache.cloudstack.acl.SecurityChecker.AccessType;
|
||||
import org.apache.cloudstack.api.ACL;
|
||||
import org.apache.cloudstack.api.ApiErrorCode;
|
||||
import org.apache.cloudstack.api.BaseAsyncCreateCmd;
|
||||
import org.apache.cloudstack.api.BaseCmd;
|
||||
import org.apache.cloudstack.api.EntityReference;
|
||||
import org.apache.cloudstack.api.InternalIdentity;
|
||||
import org.apache.cloudstack.api.Parameter;
|
||||
import org.apache.cloudstack.api.ServerApiException;
|
||||
import org.apache.cloudstack.api.BaseCmd.CommandType;
|
||||
import org.apache.cloudstack.api.command.admin.resource.ArchiveAlertsCmd;
|
||||
import org.apache.cloudstack.api.command.admin.resource.DeleteAlertsCmd;
|
||||
import org.apache.cloudstack.api.command.user.event.ArchiveEventsCmd;
|
||||
import org.apache.cloudstack.api.command.user.event.DeleteEventsCmd;
|
||||
import org.apache.cloudstack.api.command.user.event.ListEventsCmd;
|
||||
import org.apache.cloudstack.context.CallContext;
|
||||
|
||||
import com.cloud.exception.InvalidParameterValueException;
|
||||
import com.cloud.user.Account;
|
||||
import com.cloud.user.AccountManager;
|
||||
import com.cloud.utils.DateUtil;
|
||||
import com.cloud.utils.db.EntityManager;
|
||||
import com.cloud.utils.exception.CloudRuntimeException;
|
||||
|
||||
public class ParamProcessWorker implements DispatchWorker {
|
||||
|
||||
private static final Logger s_logger = Logger.getLogger(ParamProcessWorker.class.getName());
|
||||
|
||||
@Inject
|
||||
protected AccountManager _accountMgr;
|
||||
|
||||
@Inject
|
||||
protected EntityManager _entityMgr;
|
||||
|
||||
@Override
|
||||
public void handle(final DispatchTask task) {
|
||||
processParameters(task.getCmd(), task.getParams());
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
public void processParameters(final BaseCmd cmd, final Map params) {
|
||||
final Map<Object, AccessType> entitiesToAccess = new HashMap<Object, AccessType>();
|
||||
|
||||
final List<Field> cmdFields = cmd.getParamFields();
|
||||
|
||||
for (final Field field : cmdFields) {
|
||||
final Parameter parameterAnnotation = field.getAnnotation(Parameter.class);
|
||||
final Object paramObj = params.get(parameterAnnotation.name());
|
||||
if (paramObj == null) {
|
||||
if (parameterAnnotation.required()) {
|
||||
throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Unable to execute API command " +
|
||||
cmd.getCommandName().substring(0, cmd.getCommandName().length() - 8) +
|
||||
" due to missing parameter " + parameterAnnotation.name());
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// marshall the parameter into the correct type and set the field value
|
||||
try {
|
||||
setFieldValue(field, cmd, paramObj, parameterAnnotation);
|
||||
} catch (final IllegalArgumentException argEx) {
|
||||
if (s_logger.isDebugEnabled()) {
|
||||
s_logger.debug("Unable to execute API command " + cmd.getCommandName() + " due to invalid value " + paramObj + " for parameter " +
|
||||
parameterAnnotation.name());
|
||||
}
|
||||
throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Unable to execute API command " +
|
||||
cmd.getCommandName().substring(0, cmd.getCommandName().length() - 8) + " due to invalid value " + paramObj + " for parameter " +
|
||||
parameterAnnotation.name());
|
||||
} catch (final ParseException parseEx) {
|
||||
if (s_logger.isDebugEnabled()) {
|
||||
s_logger.debug("Invalid date parameter " + paramObj + " passed to command " + cmd.getCommandName().substring(0, cmd.getCommandName().length() - 8));
|
||||
}
|
||||
throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Unable to parse date " + paramObj + " for command " +
|
||||
cmd.getCommandName().substring(0, cmd.getCommandName().length() - 8) + ", please pass dates in the format mentioned in the api documentation");
|
||||
} catch (final InvalidParameterValueException invEx) {
|
||||
throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Unable to execute API command " +
|
||||
cmd.getCommandName().substring(0, cmd.getCommandName().length() - 8) + " due to invalid value. " + invEx.getMessage());
|
||||
} catch (final CloudRuntimeException cloudEx) {
|
||||
s_logger.error("CloudRuntimeException", cloudEx);
|
||||
// FIXME: Better error message? This only happens if the API command is not executable, which typically
|
||||
//means
|
||||
// there was
|
||||
// and IllegalAccessException setting one of the parameters.
|
||||
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Internal error executing API command " +
|
||||
cmd.getCommandName().substring(0, cmd.getCommandName().length() - 8));
|
||||
}
|
||||
|
||||
//check access on the resource this field points to
|
||||
try {
|
||||
final ACL checkAccess = field.getAnnotation(ACL.class);
|
||||
final CommandType fieldType = parameterAnnotation.type();
|
||||
|
||||
if (checkAccess != null) {
|
||||
// Verify that caller can perform actions in behalf of vm owner
|
||||
//acumulate all Controlled Entities together.
|
||||
|
||||
//parse the array of resource types and in case of map check access on key or value or both as specified in @acl
|
||||
//implement external dao for classes that need findByName
|
||||
//for maps, specify access to be checkd on key or value.
|
||||
|
||||
// find the controlled entity DBid by uuid
|
||||
if (parameterAnnotation.entityType() != null) {
|
||||
final Class<?>[] entityList = parameterAnnotation.entityType()[0].getAnnotation(EntityReference.class).value();
|
||||
|
||||
for (final Class entity : entityList) {
|
||||
// Check if the parameter type is a single
|
||||
// Id or list of id's/name's
|
||||
switch (fieldType) {
|
||||
case LIST:
|
||||
final CommandType listType = parameterAnnotation.collectionType();
|
||||
switch (listType) {
|
||||
case LONG:
|
||||
case UUID:
|
||||
final List<Long> listParam = (List<Long>)field.get(cmd);
|
||||
for (final Long entityId : listParam) {
|
||||
final Object entityObj = _entityMgr.findById(entity, entityId);
|
||||
entitiesToAccess.put(entityObj, checkAccess.accessType());
|
||||
}
|
||||
break;
|
||||
/*
|
||||
* case STRING: List<String> listParam =
|
||||
* new ArrayList<String>(); listParam =
|
||||
* (List)field.get(cmd); for(String
|
||||
* entityName: listParam){
|
||||
* ControlledEntity entityObj =
|
||||
* (ControlledEntity
|
||||
* )daoClassInstance(entityId);
|
||||
* entitiesToAccess.add(entityObj); }
|
||||
* break;
|
||||
*/
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case LONG:
|
||||
case UUID:
|
||||
final Object entityObj = _entityMgr.findById(entity, (Long)field.get(cmd));
|
||||
entitiesToAccess.put(entityObj, checkAccess.accessType());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (ControlledEntity.class.isAssignableFrom(entity)) {
|
||||
if (s_logger.isDebugEnabled()) {
|
||||
s_logger.debug("ControlledEntity name is:" + entity.getName());
|
||||
}
|
||||
}
|
||||
|
||||
if (InfrastructureEntity.class.isAssignableFrom(entity)) {
|
||||
if (s_logger.isDebugEnabled()) {
|
||||
s_logger.debug("InfrastructureEntity name is:" + entity.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} catch (final IllegalArgumentException e) {
|
||||
s_logger.error("Error initializing command " + cmd.getCommandName() + ", field " + field.getName() + " is not accessible.");
|
||||
throw new CloudRuntimeException("Internal error initializing parameters for command " + cmd.getCommandName() + " [field " + field.getName() +
|
||||
" is not accessible]");
|
||||
} catch (final IllegalAccessException e) {
|
||||
s_logger.error("Error initializing command " + cmd.getCommandName() + ", field " + field.getName() + " is not accessible.");
|
||||
throw new CloudRuntimeException("Internal error initializing parameters for command " + cmd.getCommandName() + " [field " + field.getName() +
|
||||
" is not accessible]");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
doAccessChecks(cmd, entitiesToAccess);
|
||||
}
|
||||
|
||||
|
||||
private void doAccessChecks(final BaseCmd cmd, final Map<Object, AccessType> entitiesToAccess) {
|
||||
final Account caller = CallContext.current().getCallingAccount();
|
||||
final Account owner = _accountMgr.getActiveAccountById(cmd.getEntityOwnerId());
|
||||
|
||||
if (cmd instanceof BaseAsyncCreateCmd) {
|
||||
//check that caller can access the owner account.
|
||||
_accountMgr.checkAccess(caller, null, true, owner);
|
||||
}
|
||||
|
||||
if (!entitiesToAccess.isEmpty()) {
|
||||
//check that caller can access the owner account.
|
||||
_accountMgr.checkAccess(caller, null, true, owner);
|
||||
for (final Object entity : entitiesToAccess.keySet()) {
|
||||
if (entity instanceof ControlledEntity) {
|
||||
_accountMgr.checkAccess(caller, entitiesToAccess.get(entity), true, (ControlledEntity)entity);
|
||||
} else if (entity instanceof InfrastructureEntity) {
|
||||
//FIXME: Move this code in adapter, remove code from Account manager
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
private void setFieldValue(final Field field, final BaseCmd cmdObj, final Object paramObj, final Parameter annotation) throws IllegalArgumentException, ParseException {
|
||||
try {
|
||||
field.setAccessible(true);
|
||||
final CommandType fieldType = annotation.type();
|
||||
switch (fieldType) {
|
||||
case BOOLEAN:
|
||||
field.set(cmdObj, Boolean.valueOf(paramObj.toString()));
|
||||
break;
|
||||
case DATE:
|
||||
// This piece of code is for maintaining backward compatibility
|
||||
// and support both the date formats(Bug 9724)
|
||||
// Do the date messaging for ListEventsCmd only
|
||||
if (cmdObj instanceof ListEventsCmd || cmdObj instanceof DeleteEventsCmd || cmdObj instanceof ArchiveEventsCmd ||
|
||||
cmdObj instanceof ArchiveAlertsCmd || cmdObj instanceof DeleteAlertsCmd) {
|
||||
final boolean isObjInNewDateFormat = isObjInNewDateFormat(paramObj.toString());
|
||||
if (isObjInNewDateFormat) {
|
||||
final DateFormat newFormat = BaseCmd.NEW_INPUT_FORMAT;
|
||||
synchronized (newFormat) {
|
||||
field.set(cmdObj, newFormat.parse(paramObj.toString()));
|
||||
}
|
||||
} else {
|
||||
final DateFormat format = BaseCmd.INPUT_FORMAT;
|
||||
synchronized (format) {
|
||||
Date date = format.parse(paramObj.toString());
|
||||
if (field.getName().equals("startDate")) {
|
||||
date = messageDate(date, 0, 0, 0);
|
||||
} else if (field.getName().equals("endDate")) {
|
||||
date = messageDate(date, 23, 59, 59);
|
||||
}
|
||||
field.set(cmdObj, date);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
final DateFormat format = BaseCmd.INPUT_FORMAT;
|
||||
synchronized (format) {
|
||||
format.setLenient(false);
|
||||
field.set(cmdObj, format.parse(paramObj.toString()));
|
||||
}
|
||||
}
|
||||
break;
|
||||
case FLOAT:
|
||||
// Assuming that the parameters have been checked for required before now,
|
||||
// we ignore blank or null values and defer to the command to set a default
|
||||
// value for optional parameters ...
|
||||
if (paramObj != null && isNotBlank(paramObj.toString())) {
|
||||
field.set(cmdObj, Float.valueOf(paramObj.toString()));
|
||||
}
|
||||
break;
|
||||
case INTEGER:
|
||||
// Assuming that the parameters have been checked for required before now,
|
||||
// we ignore blank or null values and defer to the command to set a default
|
||||
// value for optional parameters ...
|
||||
if (paramObj != null && isNotBlank(paramObj.toString())) {
|
||||
field.set(cmdObj, Integer.valueOf(paramObj.toString()));
|
||||
}
|
||||
break;
|
||||
case LIST:
|
||||
final List listParam = new ArrayList();
|
||||
final StringTokenizer st = new StringTokenizer(paramObj.toString(), ",");
|
||||
while (st.hasMoreTokens()) {
|
||||
final String token = st.nextToken();
|
||||
final CommandType listType = annotation.collectionType();
|
||||
switch (listType) {
|
||||
case INTEGER:
|
||||
listParam.add(Integer.valueOf(token));
|
||||
break;
|
||||
case UUID:
|
||||
if (token.isEmpty())
|
||||
break;
|
||||
final Long internalId = translateUuidToInternalId(token, annotation);
|
||||
listParam.add(internalId);
|
||||
break;
|
||||
case LONG: {
|
||||
listParam.add(Long.valueOf(token));
|
||||
}
|
||||
break;
|
||||
case SHORT:
|
||||
listParam.add(Short.valueOf(token));
|
||||
case STRING:
|
||||
listParam.add(token);
|
||||
break;
|
||||
}
|
||||
}
|
||||
field.set(cmdObj, listParam);
|
||||
break;
|
||||
case UUID:
|
||||
if (paramObj.toString().isEmpty())
|
||||
break;
|
||||
final Long internalId = translateUuidToInternalId(paramObj.toString(), annotation);
|
||||
field.set(cmdObj, internalId);
|
||||
break;
|
||||
case LONG:
|
||||
field.set(cmdObj, Long.valueOf(paramObj.toString()));
|
||||
break;
|
||||
case SHORT:
|
||||
field.set(cmdObj, Short.valueOf(paramObj.toString()));
|
||||
break;
|
||||
case STRING:
|
||||
if ((paramObj != null) && paramObj.toString().length() > annotation.length()) {
|
||||
s_logger.error("Value greater than max allowed length " + annotation.length() + " for param: " + field.getName());
|
||||
throw new InvalidParameterValueException("Value greater than max allowed length " + annotation.length() + " for param: " + field.getName());
|
||||
}
|
||||
field.set(cmdObj, paramObj.toString());
|
||||
break;
|
||||
case TZDATE:
|
||||
field.set(cmdObj, DateUtil.parseTZDateString(paramObj.toString()));
|
||||
break;
|
||||
case MAP:
|
||||
default:
|
||||
field.set(cmdObj, paramObj);
|
||||
break;
|
||||
}
|
||||
} catch (final IllegalAccessException ex) {
|
||||
s_logger.error("Error initializing command " + cmdObj.getCommandName() + ", field " + field.getName() + " is not accessible.");
|
||||
throw new CloudRuntimeException("Internal error initializing parameters for command " + cmdObj.getCommandName() + " [field " + field.getName() +
|
||||
" is not accessible]");
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isObjInNewDateFormat(final String string) {
|
||||
final Matcher matcher = BaseCmd.newInputDateFormat.matcher(string);
|
||||
return matcher.matches();
|
||||
}
|
||||
|
||||
private Date messageDate(final Date date, final int hourOfDay, final int minute, final int second) {
|
||||
final Calendar cal = Calendar.getInstance();
|
||||
cal.setTime(date);
|
||||
cal.set(Calendar.HOUR_OF_DAY, hourOfDay);
|
||||
cal.set(Calendar.MINUTE, minute);
|
||||
cal.set(Calendar.SECOND, second);
|
||||
return cal.getTime();
|
||||
}
|
||||
|
||||
private Long translateUuidToInternalId(final String uuid, final Parameter annotation) {
|
||||
if (uuid.equals("-1")) {
|
||||
// FIXME: This is to handle a lot of hardcoded special cases where -1 is sent
|
||||
// APITODO: Find and get rid of all hardcoded params in API Cmds and service layer
|
||||
return -1L;
|
||||
}
|
||||
Long internalId = null;
|
||||
// If annotation's empty, the cmd existed before 3.x try conversion to long
|
||||
final boolean isPre3x = annotation.since().isEmpty();
|
||||
// Match against Java's UUID regex to check if input is uuid string
|
||||
final boolean isUuid = uuid.matches("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$");
|
||||
// Enforce that it's uuid for newly added apis from version 3.x
|
||||
if (!isPre3x && !isUuid)
|
||||
return null;
|
||||
// Allow both uuid and internal id for pre3x apis
|
||||
if (isPre3x && !isUuid) {
|
||||
try {
|
||||
internalId = Long.parseLong(uuid);
|
||||
} catch (final NumberFormatException e) {
|
||||
internalId = null;
|
||||
}
|
||||
if (internalId != null)
|
||||
return internalId;
|
||||
}
|
||||
// There may be multiple entities defined on the @EntityReference of a Response.class
|
||||
// UUID CommandType would expect only one entityType, so use the first entityType
|
||||
final Class<?>[] entities = annotation.entityType()[0].getAnnotation(EntityReference.class).value();
|
||||
// Go through each entity which is an interface to a VO class and get a VO object
|
||||
// Try to getId() for the object using reflection, break on first non-null value
|
||||
for (final Class<?> entity : entities) {
|
||||
// For backward compatibility, we search within removed entities and let service layer deal
|
||||
// with removed ones, return empty response or error
|
||||
final Object objVO = _entityMgr.findByUuidIncludingRemoved(entity, uuid);
|
||||
if (objVO == null) {
|
||||
continue;
|
||||
}
|
||||
// Invoke the getId method, get the internal long ID
|
||||
// If that fails hide exceptions as the uuid may not exist
|
||||
try {
|
||||
internalId = ((InternalIdentity)objVO).getId();
|
||||
} catch (final IllegalArgumentException e) {
|
||||
} catch (final NullPointerException e) {
|
||||
}
|
||||
// Return on first non-null Id for the uuid entity
|
||||
if (internalId != null)
|
||||
break;
|
||||
}
|
||||
if (internalId == null) {
|
||||
if (s_logger.isDebugEnabled())
|
||||
s_logger.debug("Object entity uuid = " + uuid + " does not exist in the database.");
|
||||
throw new InvalidParameterValueException("Invalid parameter " + annotation.name() + " value=" + uuid +
|
||||
" due to incorrect long value format, or entity does not exist or due to incorrect parameter annotation for the field in api cmd class.");
|
||||
}
|
||||
return internalId;
|
||||
}
|
||||
}
|
||||
114
server/src/com/cloud/api/dispatch/ParamUnpackWorker.java
Normal file
114
server/src/com/cloud/api/dispatch/ParamUnpackWorker.java
Normal file
@ -0,0 +1,114 @@
|
||||
// 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.dispatch;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import org.apache.cloudstack.api.ApiErrorCode;
|
||||
import org.apache.cloudstack.api.ServerApiException;
|
||||
|
||||
public class ParamUnpackWorker implements DispatchWorker {
|
||||
|
||||
private static final Logger s_logger = Logger.getLogger(ParamUnpackWorker.class);
|
||||
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
@Override
|
||||
public void handle(final DispatchTask task) throws ServerApiException {
|
||||
final Map<String, Object> lowercaseParams = new HashMap<String, Object>();
|
||||
final Map<String, String> params = task.getParams();
|
||||
for (final String key : params.keySet()) {
|
||||
final int arrayStartIndex = key.indexOf('[');
|
||||
final int arrayStartLastIndex = key.lastIndexOf('[');
|
||||
if (arrayStartIndex != arrayStartLastIndex) {
|
||||
throw new ServerApiException(ApiErrorCode.MALFORMED_PARAMETER_ERROR, "Unable to decode parameter " + key +
|
||||
"; if specifying an object array, please use parameter[index].field=XXX, e.g. userGroupList[0].group=httpGroup");
|
||||
}
|
||||
|
||||
if (arrayStartIndex > 0) {
|
||||
final int arrayEndIndex = key.indexOf(']');
|
||||
final int arrayEndLastIndex = key.lastIndexOf(']');
|
||||
if ((arrayEndIndex < arrayStartIndex) || (arrayEndIndex != arrayEndLastIndex)) {
|
||||
// malformed parameter
|
||||
throw new ServerApiException(ApiErrorCode.MALFORMED_PARAMETER_ERROR, "Unable to decode parameter " + key +
|
||||
"; if specifying an object array, please use parameter[index].field=XXX, e.g. userGroupList[0].group=httpGroup");
|
||||
}
|
||||
|
||||
// Now that we have an array object, check for a field name in the case of a complex object
|
||||
final int fieldIndex = key.indexOf('.');
|
||||
String fieldName = null;
|
||||
if (fieldIndex < arrayEndIndex) {
|
||||
throw new ServerApiException(ApiErrorCode.MALFORMED_PARAMETER_ERROR, "Unable to decode parameter " + key +
|
||||
"; if specifying an object array, please use parameter[index].field=XXX, e.g. userGroupList[0].group=httpGroup");
|
||||
} else {
|
||||
fieldName = key.substring(fieldIndex + 1);
|
||||
}
|
||||
|
||||
// parse the parameter name as the text before the first '[' character
|
||||
String paramName = key.substring(0, arrayStartIndex);
|
||||
paramName = paramName.toLowerCase();
|
||||
|
||||
Map<Integer, Map> mapArray = null;
|
||||
Map<String, Object> mapValue = null;
|
||||
final String indexStr = key.substring(arrayStartIndex + 1, arrayEndIndex);
|
||||
int index = 0;
|
||||
boolean parsedIndex = false;
|
||||
try {
|
||||
if (indexStr != null) {
|
||||
index = Integer.parseInt(indexStr);
|
||||
parsedIndex = true;
|
||||
}
|
||||
} catch (final NumberFormatException nfe) {
|
||||
s_logger.warn("Invalid parameter " + key + " received, unable to parse object array, returning an error.");
|
||||
}
|
||||
|
||||
if (!parsedIndex) {
|
||||
throw new ServerApiException(ApiErrorCode.MALFORMED_PARAMETER_ERROR, "Unable to decode parameter " + key +
|
||||
"; if specifying an object array, please use parameter[index].field=XXX, e.g. userGroupList[0].group=httpGroup");
|
||||
}
|
||||
|
||||
final Object value = lowercaseParams.get(paramName);
|
||||
if (value == null) {
|
||||
// for now, assume object array with sub fields
|
||||
mapArray = new HashMap<Integer, Map>();
|
||||
mapValue = new HashMap<String, Object>();
|
||||
mapArray.put(Integer.valueOf(index), mapValue);
|
||||
} else if (value instanceof Map) {
|
||||
mapArray = (HashMap)value;
|
||||
mapValue = mapArray.get(Integer.valueOf(index));
|
||||
if (mapValue == null) {
|
||||
mapValue = new HashMap<String, Object>();
|
||||
mapArray.put(Integer.valueOf(index), mapValue);
|
||||
}
|
||||
}
|
||||
|
||||
// we are ready to store the value for a particular field into the map for this object
|
||||
mapValue.put(fieldName, params.get(key));
|
||||
|
||||
lowercaseParams.put(paramName, mapArray);
|
||||
} else {
|
||||
lowercaseParams.put(key.toLowerCase(), params.get(key));
|
||||
}
|
||||
}
|
||||
|
||||
// The chain continues processing the unpacked parameters
|
||||
task.setParams(lowercaseParams);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,34 @@
|
||||
// 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.dispatch;
|
||||
|
||||
/**
|
||||
* This worker validates parameters in a semantic way, that is of
|
||||
* course specific for each {@link BaseCmd}, so actually it delegates
|
||||
* the validation on the {@link BaseCmd} itself
|
||||
*
|
||||
*/
|
||||
public class SpecificCmdValidationWorker implements DispatchWorker {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public void handle(final DispatchTask task) {
|
||||
task.getCmd().validateSpecificParameters(task.getParams());
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -105,7 +105,6 @@ import com.cloud.agent.api.to.StaticNatRuleTO;
|
||||
import com.cloud.agent.manager.Commands;
|
||||
import com.cloud.alert.AlertManager;
|
||||
import com.cloud.api.ApiAsyncJobDispatcher;
|
||||
import com.cloud.api.ApiDispatcher;
|
||||
import com.cloud.api.ApiGsonHelper;
|
||||
import com.cloud.cluster.ManagementServerHostVO;
|
||||
import com.cloud.cluster.dao.ManagementServerHostDao;
|
||||
@ -4195,7 +4194,6 @@ public class VirtualNetworkApplianceManagerImpl extends ManagerBase implements V
|
||||
for(DomainRouterVO router: routers){
|
||||
if(!checkRouterVersion(router)){
|
||||
s_logger.debug("Upgrading template for router: "+router.getId());
|
||||
ApiDispatcher.getInstance();
|
||||
Map<String, String> params = new HashMap<String, String>();
|
||||
params.put("ctxUserId", "1");
|
||||
params.put("ctxAccountId", "" + router.getAccountId());
|
||||
|
||||
@ -82,6 +82,8 @@ public class SnapshotSchedulerImpl extends ManagerBase implements SnapshotSchedu
|
||||
protected VolumeDao _volsDao;
|
||||
@Inject
|
||||
protected ConfigurationDao _configDao;
|
||||
@Inject
|
||||
protected ApiDispatcher _dispatcher;
|
||||
|
||||
protected AsyncJobDispatcher _asyncDispatcher;
|
||||
|
||||
@ -95,21 +97,21 @@ public class SnapshotSchedulerImpl extends ManagerBase implements SnapshotSchedu
|
||||
return _asyncDispatcher;
|
||||
}
|
||||
|
||||
public void setAsyncJobDispatcher(AsyncJobDispatcher dispatcher) {
|
||||
public void setAsyncJobDispatcher(final AsyncJobDispatcher dispatcher) {
|
||||
_asyncDispatcher = dispatcher;
|
||||
}
|
||||
|
||||
private Date getNextScheduledTime(long policyId, Date currentTimestamp) {
|
||||
SnapshotPolicyVO policy = _snapshotPolicyDao.findById(policyId);
|
||||
private Date getNextScheduledTime(final long policyId, final Date currentTimestamp) {
|
||||
final SnapshotPolicyVO policy = _snapshotPolicyDao.findById(policyId);
|
||||
Date nextTimestamp = null;
|
||||
if (policy != null) {
|
||||
short intervalType = policy.getInterval();
|
||||
IntervalType type = DateUtil.getIntervalType(intervalType);
|
||||
String schedule = policy.getSchedule();
|
||||
String timezone = policy.getTimezone();
|
||||
final short intervalType = policy.getInterval();
|
||||
final IntervalType type = DateUtil.getIntervalType(intervalType);
|
||||
final String schedule = policy.getSchedule();
|
||||
final String timezone = policy.getTimezone();
|
||||
nextTimestamp = DateUtil.getNextRunTime(type, schedule, timezone, currentTimestamp);
|
||||
String currentTime = DateUtil.displayDateInTimezone(DateUtil.GMT_TIMEZONE, currentTimestamp);
|
||||
String nextScheduledTime = DateUtil.displayDateInTimezone(DateUtil.GMT_TIMEZONE, nextTimestamp);
|
||||
final String currentTime = DateUtil.displayDateInTimezone(DateUtil.GMT_TIMEZONE, currentTimestamp);
|
||||
final String nextScheduledTime = DateUtil.displayDateInTimezone(DateUtil.GMT_TIMEZONE, nextTimestamp);
|
||||
s_logger.debug("Current time is " + currentTime + ". NextScheduledTime of policyId " + policyId + " is " + nextScheduledTime);
|
||||
}
|
||||
return nextTimestamp;
|
||||
@ -119,7 +121,7 @@ public class SnapshotSchedulerImpl extends ManagerBase implements SnapshotSchedu
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void poll(Date currentTimestamp) {
|
||||
public void poll(final Date currentTimestamp) {
|
||||
// We don't maintain the time. The timer task does.
|
||||
_currentTimestamp = currentTimestamp;
|
||||
|
||||
@ -151,12 +153,12 @@ public class SnapshotSchedulerImpl extends ManagerBase implements SnapshotSchedu
|
||||
}
|
||||
|
||||
private void checkStatusOfCurrentlyExecutingSnapshots() {
|
||||
SearchCriteria<SnapshotScheduleVO> sc = _snapshotScheduleDao.createSearchCriteria();
|
||||
final SearchCriteria<SnapshotScheduleVO> sc = _snapshotScheduleDao.createSearchCriteria();
|
||||
sc.addAnd("asyncJobId", SearchCriteria.Op.NNULL);
|
||||
List<SnapshotScheduleVO> snapshotSchedules = _snapshotScheduleDao.search(sc, null);
|
||||
for (SnapshotScheduleVO snapshotSchedule : snapshotSchedules) {
|
||||
Long asyncJobId = snapshotSchedule.getAsyncJobId();
|
||||
AsyncJobVO asyncJob = _asyncJobDao.findById(asyncJobId);
|
||||
final List<SnapshotScheduleVO> snapshotSchedules = _snapshotScheduleDao.search(sc, null);
|
||||
for (final SnapshotScheduleVO snapshotSchedule : snapshotSchedules) {
|
||||
final Long asyncJobId = snapshotSchedule.getAsyncJobId();
|
||||
final AsyncJobVO asyncJob = _asyncJobDao.findById(asyncJobId);
|
||||
switch (asyncJob.getStatus()) {
|
||||
case SUCCEEDED:
|
||||
// The snapshot has been successfully backed up.
|
||||
@ -167,7 +169,7 @@ public class SnapshotSchedulerImpl extends ManagerBase implements SnapshotSchedu
|
||||
break;
|
||||
case FAILED:
|
||||
// Check the snapshot status.
|
||||
Long snapshotId = snapshotSchedule.getSnapshotId();
|
||||
final Long snapshotId = snapshotSchedule.getSnapshotId();
|
||||
if (snapshotId == null) {
|
||||
// createSnapshotAsync exited, successfully or unsuccessfully,
|
||||
// even before creating a snapshot record
|
||||
@ -175,7 +177,7 @@ public class SnapshotSchedulerImpl extends ManagerBase implements SnapshotSchedu
|
||||
// Schedule the next snapshot.
|
||||
scheduleNextSnapshotJob(snapshotSchedule);
|
||||
} else {
|
||||
SnapshotVO snapshot = _snapshotDao.findById(snapshotId);
|
||||
final SnapshotVO snapshot = _snapshotDao.findById(snapshotId);
|
||||
if (snapshot == null || snapshot.getRemoved() != null) {
|
||||
// This snapshot has been deleted successfully from the primary storage
|
||||
// Again no cleanup needs to be done.
|
||||
@ -219,16 +221,16 @@ public class SnapshotSchedulerImpl extends ManagerBase implements SnapshotSchedu
|
||||
String displayTime = DateUtil.displayDateInTimezone(DateUtil.GMT_TIMEZONE, _currentTimestamp);
|
||||
s_logger.debug("Snapshot scheduler.poll is being called at " + displayTime);
|
||||
|
||||
List<SnapshotScheduleVO> snapshotsToBeExecuted = _snapshotScheduleDao.getSchedulesToExecute(_currentTimestamp);
|
||||
final List<SnapshotScheduleVO> snapshotsToBeExecuted = _snapshotScheduleDao.getSchedulesToExecute(_currentTimestamp);
|
||||
s_logger.debug("Got " + snapshotsToBeExecuted.size() + " snapshots to be executed at " + displayTime);
|
||||
|
||||
for (SnapshotScheduleVO snapshotToBeExecuted : snapshotsToBeExecuted) {
|
||||
for (final SnapshotScheduleVO snapshotToBeExecuted : snapshotsToBeExecuted) {
|
||||
SnapshotScheduleVO tmpSnapshotScheduleVO = null;
|
||||
long snapshotScheId = snapshotToBeExecuted.getId();
|
||||
long policyId = snapshotToBeExecuted.getPolicyId();
|
||||
long volumeId = snapshotToBeExecuted.getVolumeId();
|
||||
final long snapshotScheId = snapshotToBeExecuted.getId();
|
||||
final long policyId = snapshotToBeExecuted.getPolicyId();
|
||||
final long volumeId = snapshotToBeExecuted.getVolumeId();
|
||||
try {
|
||||
VolumeVO volume = _volsDao.findById(volumeId);
|
||||
final VolumeVO volume = _volsDao.findById(volumeId);
|
||||
if (volume.getPoolId() == null) {
|
||||
// this volume is not attached
|
||||
continue;
|
||||
@ -237,26 +239,26 @@ public class SnapshotSchedulerImpl extends ManagerBase implements SnapshotSchedu
|
||||
_snapshotScheduleDao.remove(snapshotToBeExecuted.getId());
|
||||
}
|
||||
if (s_logger.isDebugEnabled()) {
|
||||
Date scheduledTimestamp = snapshotToBeExecuted.getScheduledTimestamp();
|
||||
final Date scheduledTimestamp = snapshotToBeExecuted.getScheduledTimestamp();
|
||||
displayTime = DateUtil.displayDateInTimezone(DateUtil.GMT_TIMEZONE, scheduledTimestamp);
|
||||
s_logger.debug("Scheduling 1 snapshot for volume " + volumeId + " for schedule id: " + snapshotToBeExecuted.getId() + " at " + displayTime);
|
||||
}
|
||||
|
||||
tmpSnapshotScheduleVO = _snapshotScheduleDao.acquireInLockTable(snapshotScheId);
|
||||
Long eventId =
|
||||
final Long eventId =
|
||||
ActionEventUtils.onScheduledActionEvent(User.UID_SYSTEM, volume.getAccountId(), EventTypes.EVENT_SNAPSHOT_CREATE, "creating snapshot for volume Id:" +
|
||||
volumeId, true, 0);
|
||||
|
||||
Map<String, String> params = new HashMap<String, String>();
|
||||
final Map<String, String> params = new HashMap<String, String>();
|
||||
params.put(ApiConstants.VOLUME_ID, "" + volumeId);
|
||||
params.put(ApiConstants.POLICY_ID, "" + policyId);
|
||||
params.put("ctxUserId", "1");
|
||||
params.put("ctxAccountId", "" + volume.getAccountId());
|
||||
params.put("ctxStartEventId", String.valueOf(eventId));
|
||||
|
||||
CreateSnapshotCmd cmd = new CreateSnapshotCmd();
|
||||
final CreateSnapshotCmd cmd = new CreateSnapshotCmd();
|
||||
ComponentContext.inject(cmd);
|
||||
ApiDispatcher.getInstance().dispatchCreateCmd(cmd, params);
|
||||
_dispatcher.dispatchCreateCmd(cmd, params);
|
||||
params.put("id", "" + cmd.getEntityId());
|
||||
params.put("ctxStartEventId", "1");
|
||||
|
||||
@ -265,11 +267,12 @@ public class SnapshotSchedulerImpl extends ManagerBase implements SnapshotSchedu
|
||||
cmd.getInstanceType() != null ? cmd.getInstanceType().toString() : null);
|
||||
job.setDispatcher(_asyncDispatcher.getName());
|
||||
|
||||
long jobId = _asyncMgr.submitAsyncJob(job);
|
||||
final long jobId = _asyncMgr.submitAsyncJob(job);
|
||||
|
||||
tmpSnapshotScheduleVO.setAsyncJobId(jobId);
|
||||
_snapshotScheduleDao.update(snapshotScheId, tmpSnapshotScheduleVO);
|
||||
} catch (Exception e) {
|
||||
} catch (final Exception e) {
|
||||
// TODO Logging this exception is enough?
|
||||
s_logger.warn("Scheduling snapshot failed due to " + e.toString());
|
||||
} finally {
|
||||
if (tmpSnapshotScheduleVO != null) {
|
||||
@ -279,16 +282,16 @@ public class SnapshotSchedulerImpl extends ManagerBase implements SnapshotSchedu
|
||||
}
|
||||
}
|
||||
|
||||
private Date scheduleNextSnapshotJob(SnapshotScheduleVO snapshotSchedule) {
|
||||
private Date scheduleNextSnapshotJob(final SnapshotScheduleVO snapshotSchedule) {
|
||||
if (snapshotSchedule == null) {
|
||||
return null;
|
||||
}
|
||||
Long policyId = snapshotSchedule.getPolicyId();
|
||||
final Long policyId = snapshotSchedule.getPolicyId();
|
||||
if (policyId.longValue() == Snapshot.MANUAL_POLICY_ID) {
|
||||
// Don't need to schedule the next job for this.
|
||||
return null;
|
||||
}
|
||||
SnapshotPolicyVO snapshotPolicy = _snapshotPolicyDao.findById(policyId);
|
||||
final SnapshotPolicyVO snapshotPolicy = _snapshotPolicyDao.findById(policyId);
|
||||
if (snapshotPolicy == null) {
|
||||
_snapshotScheduleDao.expunge(snapshotSchedule.getId());
|
||||
}
|
||||
@ -297,15 +300,15 @@ public class SnapshotSchedulerImpl extends ManagerBase implements SnapshotSchedu
|
||||
|
||||
@Override
|
||||
@DB
|
||||
public Date scheduleNextSnapshotJob(SnapshotPolicyVO policy) {
|
||||
public Date scheduleNextSnapshotJob(final SnapshotPolicyVO policy) {
|
||||
if (policy == null) {
|
||||
return null;
|
||||
}
|
||||
long policyId = policy.getId();
|
||||
final long policyId = policy.getId();
|
||||
if (policyId == Snapshot.MANUAL_POLICY_ID) {
|
||||
return null;
|
||||
}
|
||||
Date nextSnapshotTimestamp = getNextScheduledTime(policyId, _currentTimestamp);
|
||||
final Date nextSnapshotTimestamp = getNextScheduledTime(policyId, _currentTimestamp);
|
||||
SnapshotScheduleVO spstSchedVO = _snapshotScheduleDao.findOneByVolumePolicy(policy.getVolumeId(), policy.getId());
|
||||
if (spstSchedVO == null) {
|
||||
spstSchedVO = new SnapshotScheduleVO(policy.getVolumeId(), policyId, nextSnapshotTimestamp);
|
||||
@ -329,9 +332,9 @@ public class SnapshotSchedulerImpl extends ManagerBase implements SnapshotSchedu
|
||||
|
||||
@Override
|
||||
@DB
|
||||
public boolean removeSchedule(Long volumeId, Long policyId) {
|
||||
public boolean removeSchedule(final Long volumeId, final Long policyId) {
|
||||
// We can only remove schedules which are in the future. Not which are already executed in the past.
|
||||
SnapshotScheduleVO schedule = _snapshotScheduleDao.getCurrentSchedule(volumeId, policyId, false);
|
||||
final SnapshotScheduleVO schedule = _snapshotScheduleDao.getCurrentSchedule(volumeId, policyId, false);
|
||||
boolean success = true;
|
||||
if (schedule != null) {
|
||||
success = _snapshotScheduleDao.remove(schedule.getId());
|
||||
@ -343,18 +346,18 @@ public class SnapshotSchedulerImpl extends ManagerBase implements SnapshotSchedu
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean configure(String name, Map<String, Object> params) throws ConfigurationException {
|
||||
public boolean configure(final String name, final Map<String, Object> params) throws ConfigurationException {
|
||||
|
||||
_snapshotPollInterval = NumbersUtil.parseInt(_configDao.getValue("snapshot.poll.interval"), 300);
|
||||
boolean snapshotsRecurringTest = Boolean.parseBoolean(_configDao.getValue("snapshot.recurring.test"));
|
||||
final boolean snapshotsRecurringTest = Boolean.parseBoolean(_configDao.getValue("snapshot.recurring.test"));
|
||||
if (snapshotsRecurringTest) {
|
||||
// look for some test values in the configuration table so that snapshots can be taken more frequently (QA test code)
|
||||
int minutesPerHour = NumbersUtil.parseInt(_configDao.getValue("snapshot.test.minutes.per.hour"), 60);
|
||||
int hoursPerDay = NumbersUtil.parseInt(_configDao.getValue("snapshot.test.hours.per.day"), 24);
|
||||
int daysPerWeek = NumbersUtil.parseInt(_configDao.getValue("snapshot.test.days.per.week"), 7);
|
||||
int daysPerMonth = NumbersUtil.parseInt(_configDao.getValue("snapshot.test.days.per.month"), 30);
|
||||
int weeksPerMonth = NumbersUtil.parseInt(_configDao.getValue("snapshot.test.weeks.per.month"), 4);
|
||||
int monthsPerYear = NumbersUtil.parseInt(_configDao.getValue("snapshot.test.months.per.year"), 12);
|
||||
final int minutesPerHour = NumbersUtil.parseInt(_configDao.getValue("snapshot.test.minutes.per.hour"), 60);
|
||||
final int hoursPerDay = NumbersUtil.parseInt(_configDao.getValue("snapshot.test.hours.per.day"), 24);
|
||||
final int daysPerWeek = NumbersUtil.parseInt(_configDao.getValue("snapshot.test.days.per.week"), 7);
|
||||
final int daysPerMonth = NumbersUtil.parseInt(_configDao.getValue("snapshot.test.days.per.month"), 30);
|
||||
final int weeksPerMonth = NumbersUtil.parseInt(_configDao.getValue("snapshot.test.weeks.per.month"), 4);
|
||||
final int monthsPerYear = NumbersUtil.parseInt(_configDao.getValue("snapshot.test.months.per.year"), 12);
|
||||
|
||||
_testTimerTask = new TestClock(this, minutesPerHour, hoursPerDay, daysPerWeek, daysPerMonth, weeksPerMonth, monthsPerYear);
|
||||
}
|
||||
@ -369,8 +372,8 @@ public class SnapshotSchedulerImpl extends ManagerBase implements SnapshotSchedu
|
||||
@DB
|
||||
public boolean start() {
|
||||
// reschedule all policies after management restart
|
||||
List<SnapshotPolicyVO> policyInstances = _snapshotPolicyDao.listAll();
|
||||
for (SnapshotPolicyVO policyInstance : policyInstances) {
|
||||
final List<SnapshotPolicyVO> policyInstances = _snapshotPolicyDao.listAll();
|
||||
for (final SnapshotPolicyVO policyInstance : policyInstances) {
|
||||
if (policyInstance.getId() != Snapshot.MANUAL_POLICY_ID) {
|
||||
scheduleNextSnapshotJob(policyInstance);
|
||||
}
|
||||
@ -381,13 +384,13 @@ public class SnapshotSchedulerImpl extends ManagerBase implements SnapshotSchedu
|
||||
// Else it becomes too confusing.
|
||||
_testClockTimer.schedule(_testTimerTask, 100 * 1000L, 60 * 1000L);
|
||||
} else {
|
||||
TimerTask timerTask = new ManagedContextTimerTask() {
|
||||
final TimerTask timerTask = new ManagedContextTimerTask() {
|
||||
@Override
|
||||
protected void runInContext() {
|
||||
try {
|
||||
Date currentTimestamp = new Date();
|
||||
final Date currentTimestamp = new Date();
|
||||
poll(currentTimestamp);
|
||||
} catch (Throwable t) {
|
||||
} catch (final Throwable t) {
|
||||
s_logger.warn("Catch throwable in snapshot scheduler ", t);
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,48 @@
|
||||
// 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.dispatch;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
import org.apache.cloudstack.api.BaseAsyncCreateCmd;
|
||||
|
||||
import com.cloud.exception.ResourceAllocationException;
|
||||
|
||||
public class CommandCreationWorkerTest {
|
||||
|
||||
@Test
|
||||
public void testHandle() throws ResourceAllocationException {
|
||||
// Prepare
|
||||
final BaseAsyncCreateCmd asyncCreateCmd = mock(BaseAsyncCreateCmd.class);
|
||||
final Map<String, String> params = new HashMap<String, String>();
|
||||
|
||||
// Execute
|
||||
final CommandCreationWorker creationWorker = new CommandCreationWorker();
|
||||
|
||||
creationWorker.handle(new DispatchTask(asyncCreateCmd, params));
|
||||
|
||||
// Assert
|
||||
verify(asyncCreateCmd, times(1)).create();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,55 @@
|
||||
// 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.dispatch;
|
||||
|
||||
import org.junit.Test;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class DispatchChainFactoryTest {
|
||||
|
||||
protected static final String STANDARD_CHAIN_ERROR = "Expecting worker of class %s at index %s of StandardChain";
|
||||
protected static final String ASYNC_CHAIN_ERROR = "Expecting worker of class %s at index %s of StandardChain";
|
||||
|
||||
@Test
|
||||
public void testAllChainCreation() {
|
||||
// Prepare
|
||||
final DispatchChainFactory dispatchChainFactory = new DispatchChainFactory();
|
||||
dispatchChainFactory.paramGenericValidationWorker = new ParamGenericValidationWorker();
|
||||
dispatchChainFactory.specificCmdValidationWorker = new SpecificCmdValidationWorker();
|
||||
dispatchChainFactory.paramProcessWorker = new ParamProcessWorker();
|
||||
dispatchChainFactory.commandCreationWorker = new CommandCreationWorker();
|
||||
dispatchChainFactory.paramUnpackWorker = new ParamUnpackWorker();
|
||||
|
||||
final Class<?>[] standardClasses = {ParamUnpackWorker.class, ParamProcessWorker.class,
|
||||
ParamGenericValidationWorker.class, SpecificCmdValidationWorker.class};
|
||||
final Class<?>[] asyncClasses = {ParamUnpackWorker.class, ParamProcessWorker.class,
|
||||
ParamGenericValidationWorker.class, SpecificCmdValidationWorker.class, CommandCreationWorker.class};
|
||||
|
||||
// Execute
|
||||
dispatchChainFactory.setup();
|
||||
final DispatchChain standardChain = dispatchChainFactory.getStandardDispatchChain();
|
||||
final DispatchChain asyncChain = dispatchChainFactory.getAsyncCreationDispatchChain();
|
||||
for (int i = 0; i < standardClasses.length; i++) {
|
||||
assertEquals(String.format(STANDARD_CHAIN_ERROR, standardClasses[i], i),
|
||||
standardClasses[i], standardChain.workers.get(i).getClass());
|
||||
}
|
||||
for (int i = 0; i < asyncClasses.length; i++) {
|
||||
assertEquals(String.format(ASYNC_CHAIN_ERROR, asyncClasses[i], i),
|
||||
asyncClasses[i], asyncChain.workers.get(i).getClass());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,173 @@
|
||||
// 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.dispatch;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import org.apache.cloudstack.acl.RoleType;
|
||||
import org.apache.cloudstack.api.APICommand;
|
||||
import org.apache.cloudstack.api.ApiConstants;
|
||||
import org.apache.cloudstack.api.BaseCmd;
|
||||
import org.apache.cloudstack.api.BaseResponse;
|
||||
import org.apache.cloudstack.api.Parameter;
|
||||
import org.apache.cloudstack.api.ServerApiException;
|
||||
|
||||
import com.cloud.exception.ConcurrentOperationException;
|
||||
import com.cloud.exception.InsufficientCapacityException;
|
||||
import com.cloud.exception.NetworkRuleConflictException;
|
||||
import com.cloud.exception.ResourceAllocationException;
|
||||
import com.cloud.exception.ResourceUnavailableException;
|
||||
import com.cloud.user.Account;
|
||||
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
public class ParamGenericValidationWorkerTest {
|
||||
|
||||
protected static final String FAKE_CMD_NAME = "fakecmdname";
|
||||
|
||||
protected static final String FAKE_CMD_ROLE_NAME = "fakecmdrolename";
|
||||
|
||||
protected String loggerOutput;
|
||||
|
||||
protected void driveTest(final BaseCmd cmd, final Map<String, String> params) {
|
||||
final ParamGenericValidationWorker genValidationWorker = new ParamGenericValidationWorker();
|
||||
|
||||
// We create a mock logger to verify the result
|
||||
ParamGenericValidationWorker.s_logger = new Logger(""){
|
||||
@Override
|
||||
public void warn(final Object msg){
|
||||
loggerOutput = msg.toString();
|
||||
}
|
||||
};
|
||||
|
||||
// Execute
|
||||
genValidationWorker.handle(new DispatchTask(cmd, params));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandle() throws ResourceAllocationException {
|
||||
// Prepare
|
||||
final BaseCmd cmd = new FakeCmd();
|
||||
final Map<String, String> params = new HashMap<String, String>();
|
||||
params.put(ApiConstants.COMMAND, "");
|
||||
params.put("addedParam", "");
|
||||
|
||||
// Execute
|
||||
driveTest(cmd, params);
|
||||
|
||||
// Assert
|
||||
assertEquals("There should be no errors since there are no unknown parameters for this command class", null, loggerOutput);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandleWithUnknownParams() throws ResourceAllocationException {
|
||||
// Prepare
|
||||
final String unknownParamKey = "unknownParam";
|
||||
final BaseCmd cmd = new FakeCmd();
|
||||
final Map<String, String> params = new HashMap<String, String>();
|
||||
params.put(ApiConstants.COMMAND, "");
|
||||
params.put("addedParam", "");
|
||||
params.put(unknownParamKey, "");
|
||||
|
||||
// Execute
|
||||
driveTest(cmd, params);
|
||||
|
||||
// Assert
|
||||
assertTrue("There should be error msg, since there is one unknown parameter", loggerOutput.contains(unknownParamKey));
|
||||
assertTrue("There should be error msg containing the correct command name", loggerOutput.contains(FAKE_CMD_NAME));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandleWithoutAuthorization() throws ResourceAllocationException {
|
||||
final short type = Account.ACCOUNT_TYPE_NORMAL;
|
||||
driveAuthTest(type);
|
||||
|
||||
// Assert
|
||||
assertTrue("There should be error msg, since there is one unauthorized parameter", loggerOutput.contains("paramWithRole"));
|
||||
assertTrue("There should be error msg containing the correct command name", loggerOutput.contains(FAKE_CMD_ROLE_NAME));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandleWithAuthorization() throws ResourceAllocationException {
|
||||
final short type = Account.ACCOUNT_TYPE_ADMIN;
|
||||
driveAuthTest(type);
|
||||
|
||||
// Assert
|
||||
assertEquals("There should be no errors since parameters have authorization", null, loggerOutput);
|
||||
}
|
||||
|
||||
protected void driveAuthTest(final short type) {
|
||||
// Prepare
|
||||
final BaseCmd cmd = new FakeCmdWithRoleAdmin();
|
||||
final Account account = mock(Account.class);
|
||||
((FakeCmdWithRoleAdmin)cmd).account = account;
|
||||
when(account.getType()).thenReturn(type);
|
||||
final Map<String, String> params = new HashMap<String, String>();
|
||||
params.put(ApiConstants.COMMAND, "");
|
||||
params.put("addedParam", "");
|
||||
params.put("paramWithRole", "");
|
||||
|
||||
// Execute
|
||||
driveTest(cmd, params);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@APICommand(name=ParamGenericValidationWorkerTest.FAKE_CMD_NAME, responseObject=BaseResponse.class)
|
||||
class FakeCmd extends BaseCmd {
|
||||
|
||||
@Parameter(name = "addedParam")
|
||||
private String addedParam;
|
||||
|
||||
public Account account;
|
||||
|
||||
@Override
|
||||
protected Account getCurrentContextAccount() {
|
||||
return account;
|
||||
}
|
||||
|
||||
//
|
||||
// Dummy methods for mere correct compilation
|
||||
//
|
||||
@Override
|
||||
public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException,
|
||||
NetworkRuleConflictException {
|
||||
}
|
||||
@Override
|
||||
public String getCommandName() {
|
||||
return null;
|
||||
}
|
||||
@Override
|
||||
public long getEntityOwnerId() {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@APICommand(name=ParamGenericValidationWorkerTest.FAKE_CMD_ROLE_NAME, responseObject=BaseResponse.class)
|
||||
class FakeCmdWithRoleAdmin extends FakeCmd {
|
||||
|
||||
@Parameter(name = "paramWithRole", authorized = {RoleType.Admin})
|
||||
private String paramWithRole;
|
||||
}
|
||||
@ -16,7 +16,7 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package com.cloud.api;
|
||||
package com.cloud.api.dispatch;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
@ -44,10 +44,12 @@ import com.cloud.user.AccountManager;
|
||||
import com.cloud.user.User;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class ApiDispatcherTest {
|
||||
public class ParamProcessWorkerTest {
|
||||
|
||||
@Mock
|
||||
AccountManager accountManager;
|
||||
protected AccountManager accountManager;
|
||||
|
||||
protected ParamProcessWorker paramProcessWorker;
|
||||
|
||||
public static class TestCmd extends BaseCmd {
|
||||
|
||||
@ -81,8 +83,8 @@ public class ApiDispatcherTest {
|
||||
@Before
|
||||
public void setup() {
|
||||
CallContext.register(Mockito.mock(User.class), Mockito.mock(Account.class));
|
||||
new ApiDispatcher().init();
|
||||
ApiDispatcher.getInstance()._accountMgr = accountManager;
|
||||
paramProcessWorker = new ParamProcessWorker();
|
||||
paramProcessWorker._accountMgr = accountManager;
|
||||
}
|
||||
|
||||
@After
|
||||
@ -92,13 +94,12 @@ public class ApiDispatcherTest {
|
||||
|
||||
@Test
|
||||
public void processParameters() {
|
||||
HashMap<String, String> params = new HashMap<String, String>();
|
||||
final HashMap<String, String> params = new HashMap<String, String>();
|
||||
params.put("strparam1", "foo");
|
||||
params.put("intparam1", "100");
|
||||
params.put("boolparam1", "true");
|
||||
TestCmd cmd = new TestCmd();
|
||||
//how lucky that field is not protected, this test would be impossible
|
||||
ApiDispatcher.processParameters(cmd, params);
|
||||
final TestCmd cmd = new TestCmd();
|
||||
paramProcessWorker.processParameters(cmd, params);
|
||||
Assert.assertEquals("foo", cmd.strparam1);
|
||||
Assert.assertEquals(100, cmd.intparam1);
|
||||
}
|
||||
@ -0,0 +1,48 @@
|
||||
// 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.dispatch;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
import org.apache.cloudstack.api.BaseCmd;
|
||||
|
||||
import com.cloud.exception.ResourceAllocationException;
|
||||
|
||||
public class SpecificCmdValidationWorkerTest {
|
||||
|
||||
@Test
|
||||
public void testHandle() throws ResourceAllocationException {
|
||||
// Prepare
|
||||
final BaseCmd cmd = mock(BaseCmd.class);
|
||||
final Map<String, String> params = new HashMap<String, String>();
|
||||
|
||||
// Execute
|
||||
final SpecificCmdValidationWorker worker = new SpecificCmdValidationWorker();
|
||||
|
||||
worker.handle(new DispatchTask(cmd, params));
|
||||
|
||||
// Assert
|
||||
verify(cmd, times(1)).validateSpecificParameters(params);
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user