Add API throttling config items and APILimitChecker Adapter interface,

add api limit checking in APIServer flow.
This commit is contained in:
Min Chen 2013-01-09 16:11:23 -08:00
parent dcbb0ecef5
commit 8d98daa1be
4 changed files with 61 additions and 1 deletions

View File

@ -0,0 +1,28 @@
// 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.acl;
import com.cloud.user.Account;
import com.cloud.utils.component.Adapter;
/**
* APILimitChecker checks if we should block an API request based on pre-set account based api limit.
*/
public interface APILimitChecker extends Adapter {
// Interface for checking if the account is over its api limit
boolean isUnderLimit(Account account);
}

View File

@ -89,6 +89,7 @@ public abstract class BaseCmd {
public static final int PARAM_ERROR = 431;
public static final int UNSUPPORTED_ACTION_ERROR = 432;
public static final int PAGE_LIMIT_EXCEED = 433;
public static final int API_LIMIT_EXCEED = 434;
// Server error codes
public static final int INTERNAL_ERROR = 530;

View File

@ -52,6 +52,7 @@ import javax.servlet.http.HttpSession;
import com.cloud.utils.ReflectUtil;
import org.apache.cloudstack.acl.APIAccessChecker;
import org.apache.cloudstack.acl.APILimitChecker;
import org.apache.cloudstack.acl.ControlledEntity;
import org.apache.cloudstack.api.*;
import org.apache.cloudstack.api.command.user.account.ListAccountsCmd;
@ -141,6 +142,7 @@ public class ApiServer implements HttpRequestHandler {
private static final Logger s_accessLogger = Logger.getLogger("apiserver." + ApiServer.class.getName());
public static boolean encodeApiResponse = false;
public static boolean apiThrottlingEnabled = true;
public static String jsonContentType = "text/javascript";
private ApiDispatcher _dispatcher;
@ -148,6 +150,8 @@ public class ApiServer implements HttpRequestHandler {
@Inject private DomainManager _domainMgr = null;
@Inject private AsyncJobManager _asyncMgr = null;
@Inject(adapter = APILimitChecker.class)
protected Adapters<APILimitChecker> _apiLimitCheckers;
@Inject(adapter = APIAccessChecker.class)
protected Adapters<APIAccessChecker> _apiAccessCheckers;
@Inject(adapter = ApiDiscoveryService.class)
@ -217,6 +221,7 @@ public class ApiServer implements HttpRequestHandler {
if (jsonType != null) {
jsonContentType = jsonType;
}
apiThrottlingEnabled = Boolean.valueOf(configDao.getValue(Config.ApiLimitEnabled.key()));
if (apiPort != null) {
ListenerThread listenerThread = new ListenerThread(this, apiPort);
@ -552,6 +557,14 @@ public class ApiServer implements HttpRequestHandler {
// if userId not null, that mean that user is logged in
if (userId != null) {
User user = ApiDBUtils.findUserById(userId);
if (apiThrottlingEnabled){
// go through each API limit checker
if (!isRequestAllowed(user)) {
//FIXME: more detailed message regarding when he/she can retry
s_logger.warn("The given user has reached his/her account api limit, please retry later");
throw new ServerApiException(BaseCmd.API_LIMIT_EXCEED, "The given user has reached his/her account api limit");
}
}
if (!isCommandAvailable(user, commandName)) {
s_logger.warn("The given command:" + commandName + " does not exist or it is not available for user");
throw new ServerApiException(BaseCmd.UNSUPPORTED_ACTION_ERROR, "The given command does not exist or it is not available for user");
@ -791,6 +804,20 @@ public class ApiServer implements HttpRequestHandler {
return true;
}
private boolean isRequestAllowed(User user) {
Account account = ApiDBUtils.findAccountById(user.getAccountId());
if ( _accountMgr.isRootAdmin(account.getType()) ){
// no api throttling for root admin
return true;
}
for (APILimitChecker apiChecker : _apiLimitCheckers) {
// Fail the checking if any checker fails to verify
if (!apiChecker.isUnderLimit(account))
return false;
}
return true;
}
private boolean isCommandAvailable(User user, String commandName) {
for (APIAccessChecker apiChecker : _apiAccessCheckers) {
// Fail the checking if any checker fails to verify

View File

@ -358,8 +358,12 @@ public enum Config {
DetailBatchQuerySize("Advanced", ManagementServer.class, Integer.class, "detail.batch.query.size", "2000", "Default entity detail batch query size for listing", null),
ConcurrentSnapshotsThresholdPerHost("Advanced", ManagementServer.class, Long.class, "concurrent.snapshots.threshold.perhost",
null, "Limits number of snapshots that can be handled by the host concurrently; default is NULL - unlimited", null);
null, "Limits number of snapshots that can be handled by the host concurrently; default is NULL - unlimited", null),
// API throttling
ApiLimitInterval("Advanced", ManagementServer.class, Long.class, "api.throttling.interval", "1", "The default time interval in seconds used to set account based api limit", null),
ApiLimitMax("Advanced", ManagementServer.class, Long.class, "api.throttling.max", "25", "The max number of API requests within api.throttling.interval duration", null),
ApiLimitEnabled("Advanced", ManagementServer.class, Boolean.class, "api.throttling.enabled", "true", "If true, api throttline feature is enabled", "true,false");
private final String _category;
private final Class<?> _componentClass;