Add API throttling config items and APILimitChecker Adapter interface, add api limit checking in APIServer flow.
Project: http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/commit/8d98daa1 Tree: http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/tree/8d98daa1 Diff: http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/diff/8d98daa1 Branch: refs/heads/add_remove_nics Commit: 8d98daa1beb46fe61022727b25c9e5b75db5e630 Parents: dcbb0ec Author: Min Chen <min.c...@citrix.com> Authored: Wed Jan 9 16:11:23 2013 -0800 Committer: Min Chen <min.c...@citrix.com> Committed: Wed Jan 9 16:11:23 2013 -0800 ---------------------------------------------------------------------- .../org/apache/cloudstack/acl/APILimitChecker.java | 28 +++++++++++++++ api/src/org/apache/cloudstack/api/BaseCmd.java | 1 + server/src/com/cloud/api/ApiServer.java | 27 ++++++++++++++ server/src/com/cloud/configuration/Config.java | 6 +++- 4 files changed, 61 insertions(+), 1 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/8d98daa1/api/src/org/apache/cloudstack/acl/APILimitChecker.java ---------------------------------------------------------------------- diff --git a/api/src/org/apache/cloudstack/acl/APILimitChecker.java b/api/src/org/apache/cloudstack/acl/APILimitChecker.java new file mode 100644 index 0000000..3a1db70 --- /dev/null +++ b/api/src/org/apache/cloudstack/acl/APILimitChecker.java @@ -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); +} http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/8d98daa1/api/src/org/apache/cloudstack/api/BaseCmd.java ---------------------------------------------------------------------- diff --git a/api/src/org/apache/cloudstack/api/BaseCmd.java b/api/src/org/apache/cloudstack/api/BaseCmd.java index d964e70..ae13012 100644 --- a/api/src/org/apache/cloudstack/api/BaseCmd.java +++ b/api/src/org/apache/cloudstack/api/BaseCmd.java @@ -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; http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/8d98daa1/server/src/com/cloud/api/ApiServer.java ---------------------------------------------------------------------- diff --git a/server/src/com/cloud/api/ApiServer.java b/server/src/com/cloud/api/ApiServer.java index 56cef12..2bb7fdd 100755 --- a/server/src/com/cloud/api/ApiServer.java +++ b/server/src/com/cloud/api/ApiServer.java @@ -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 http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/8d98daa1/server/src/com/cloud/configuration/Config.java ---------------------------------------------------------------------- diff --git a/server/src/com/cloud/configuration/Config.java b/server/src/com/cloud/configuration/Config.java index b91fbdd..ae7651c 100755 --- a/server/src/com/cloud/configuration/Config.java +++ b/server/src/com/cloud/configuration/Config.java @@ -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;