From 9c5c4753e9210d863c0aec0da0e1771d5ca991ba Mon Sep 17 00:00:00 2001 From: Min Chen Date: Mon, 4 Mar 2013 16:31:58 -0800 Subject: [PATCH] CLOUDSTACK-1484: provide api.throttling.enabled gloabl configuration settings to enable/disable api throttling feature. --- .../user/config/ListCapabilitiesCmd.java | 8 ++++-- .../admin/ratelimit/ResetApiLimitCmd.java | 9 +++++++ .../user/ratelimit/GetApiLimitCmd.java | 11 ++++++++ .../ratelimit/ApiRateLimitService.java | 2 ++ .../ratelimit/ApiRateLimitServiceImpl.java | 19 ++++++++++++++ .../ratelimit/ApiRateLimitTest.java | 25 +++++++++++++++++++ .../src/com/cloud/configuration/Config.java | 3 ++- .../cloud/server/ManagementServerImpl.java | 7 ++++-- setup/db/db/schema-40to410.sql | 1 + 9 files changed, 80 insertions(+), 5 deletions(-) diff --git a/api/src/org/apache/cloudstack/api/command/user/config/ListCapabilitiesCmd.java b/api/src/org/apache/cloudstack/api/command/user/config/ListCapabilitiesCmd.java index eb862e62f47..a30e26cfd8b 100644 --- a/api/src/org/apache/cloudstack/api/command/user/config/ListCapabilitiesCmd.java +++ b/api/src/org/apache/cloudstack/api/command/user/config/ListCapabilitiesCmd.java @@ -52,8 +52,12 @@ public class ListCapabilitiesCmd extends BaseCmd { response.setProjectInviteRequired((Boolean)capabilities.get("projectInviteRequired")); response.setAllowUsersCreateProjects((Boolean)capabilities.get("allowusercreateprojects")); response.setDiskOffMaxSize((Long)capabilities.get("customDiskOffMaxSize")); - response.setApiLimitInterval((Integer)capabilities.get("apiLimitInterval")); - response.setApiLimitMax((Integer)capabilities.get("apiLimitMax")); + if (capabilities.containsKey("apiLimitInterval")) { + response.setApiLimitInterval((Integer) capabilities.get("apiLimitInterval")); + } + if (capabilities.containsKey("apiLimitMax")) { + response.setApiLimitMax((Integer) capabilities.get("apiLimitMax")); + } response.setObjectName("capability"); response.setResponseName(getCommandName()); this.setResponseObject(response); diff --git a/plugins/api/rate-limit/src/org/apache/cloudstack/api/command/admin/ratelimit/ResetApiLimitCmd.java b/plugins/api/rate-limit/src/org/apache/cloudstack/api/command/admin/ratelimit/ResetApiLimitCmd.java index 5a7ac863abc..7ec53163c91 100644 --- a/plugins/api/rate-limit/src/org/apache/cloudstack/api/command/admin/ratelimit/ResetApiLimitCmd.java +++ b/plugins/api/rate-limit/src/org/apache/cloudstack/api/command/admin/ratelimit/ResetApiLimitCmd.java @@ -29,6 +29,8 @@ import org.apache.cloudstack.api.response.SuccessResponse; import org.apache.cloudstack.ratelimit.ApiRateLimitService; import org.apache.log4j.Logger; +import com.cloud.configuration.Config; +import com.cloud.configuration.dao.ConfigurationDao; import com.cloud.user.Account; import com.cloud.user.UserContext; @@ -43,6 +45,9 @@ public class ResetApiLimitCmd extends BaseCmd { @Inject ApiRateLimitService _apiLimitService; + @Inject + ConfigurationDao _configDao; + ///////////////////////////////////////////////////// //////////////// API parameters ///////////////////// ///////////////////////////////////////////////////// @@ -89,6 +94,10 @@ public class ResetApiLimitCmd extends BaseCmd { @Override public void execute(){ + boolean apiLimitEnabled = Boolean.parseBoolean(_configDao.getValue(Config.ApiLimitEnabled.key())); + if ( !apiLimitEnabled ){ + throw new ServerApiException(ApiErrorCode.UNSUPPORTED_ACTION_ERROR, "This api is only available when api.throttling.enabled = true."); + } boolean result = _apiLimitService.resetApiLimit(this.accountId); if (result) { SuccessResponse response = new SuccessResponse(getCommandName()); diff --git a/plugins/api/rate-limit/src/org/apache/cloudstack/api/command/user/ratelimit/GetApiLimitCmd.java b/plugins/api/rate-limit/src/org/apache/cloudstack/api/command/user/ratelimit/GetApiLimitCmd.java index 1afa9322d75..ba92e8b60c8 100644 --- a/plugins/api/rate-limit/src/org/apache/cloudstack/api/command/user/ratelimit/GetApiLimitCmd.java +++ b/plugins/api/rate-limit/src/org/apache/cloudstack/api/command/user/ratelimit/GetApiLimitCmd.java @@ -21,6 +21,7 @@ import java.util.List; import org.apache.cloudstack.api.ACL; import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; import org.apache.cloudstack.api.BaseCmd; import org.apache.cloudstack.api.BaseListCmd; import org.apache.cloudstack.api.Parameter; @@ -35,6 +36,9 @@ import org.apache.log4j.Logger; import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.response.ListResponse; import org.apache.cloudstack.ratelimit.ApiRateLimitService; + +import com.cloud.configuration.Config; +import com.cloud.configuration.dao.ConfigurationDao; import com.cloud.exception.ConcurrentOperationException; import com.cloud.exception.InsufficientCapacityException; import com.cloud.exception.InvalidParameterValueException; @@ -55,6 +59,9 @@ public class GetApiLimitCmd extends BaseCmd { @Inject ApiRateLimitService _apiLimitService; + @Inject + ConfigurationDao _configDao; + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// @@ -76,6 +83,10 @@ public class GetApiLimitCmd extends BaseCmd { @Override public void execute(){ + boolean apiLimitEnabled = Boolean.parseBoolean(_configDao.getValue(Config.ApiLimitEnabled.key())); + if ( !apiLimitEnabled ){ + throw new ServerApiException(ApiErrorCode.UNSUPPORTED_ACTION_ERROR, "This api is only available when api.throttling.enabled = true."); + } Account caller = UserContext.current().getCaller(); ApiLimitResponse response = _apiLimitService.searchApiLimit(caller); response.setResponseName(getCommandName()); diff --git a/plugins/api/rate-limit/src/org/apache/cloudstack/ratelimit/ApiRateLimitService.java b/plugins/api/rate-limit/src/org/apache/cloudstack/ratelimit/ApiRateLimitService.java index a135556a502..ad421b673cb 100644 --- a/plugins/api/rate-limit/src/org/apache/cloudstack/ratelimit/ApiRateLimitService.java +++ b/plugins/api/rate-limit/src/org/apache/cloudstack/ratelimit/ApiRateLimitService.java @@ -33,4 +33,6 @@ public interface ApiRateLimitService extends PluggableService{ public void setTimeToLive(int timeToLive); public void setMaxAllowed(int max); + + public void setEnabled(boolean enabled); } diff --git a/plugins/api/rate-limit/src/org/apache/cloudstack/ratelimit/ApiRateLimitServiceImpl.java b/plugins/api/rate-limit/src/org/apache/cloudstack/ratelimit/ApiRateLimitServiceImpl.java index d23a11d3fe0..7d1b43ae6d5 100644 --- a/plugins/api/rate-limit/src/org/apache/cloudstack/ratelimit/ApiRateLimitServiceImpl.java +++ b/plugins/api/rate-limit/src/org/apache/cloudstack/ratelimit/ApiRateLimitServiceImpl.java @@ -49,6 +49,11 @@ import org.springframework.stereotype.Component; public class ApiRateLimitServiceImpl extends AdapterBase implements APIChecker, ApiRateLimitService { private static final Logger s_logger = Logger.getLogger(ApiRateLimitServiceImpl.class); + /** + * True if api rate limiting is enabled + */ + private boolean enabled = false; + /** * Fixed time duration where api rate limit is set, in seconds */ @@ -73,6 +78,10 @@ public class ApiRateLimitServiceImpl extends AdapterBase implements APIChecker, if (_store == null) { // get global configured duration and max values + String isEnabled = _configDao.getValue(Config.ApiLimitEnabled.key()); + if ( isEnabled != null ){ + enabled = Boolean.parseBoolean(isEnabled); + } String duration = _configDao.getValue(Config.ApiLimitInterval.key()); if (duration != null) { timeToLive = Integer.parseInt(duration); @@ -140,6 +149,10 @@ public class ApiRateLimitServiceImpl extends AdapterBase implements APIChecker, @Override public boolean checkAccess(User user, String apiCommandName) throws PermissionDeniedException { + // check if api rate limiting is enabled or not + if (!enabled){ + return true; + } Long accountId = user.getAccountId(); Account account = _accountService.getAccount(accountId); if ( _accountService.isRootAdmin(account.getType())){ @@ -192,5 +205,11 @@ public class ApiRateLimitServiceImpl extends AdapterBase implements APIChecker, } + @Override + public void setEnabled(boolean enabled) { + this.enabled = enabled; + + } + } diff --git a/plugins/api/rate-limit/test/org/apache/cloudstack/ratelimit/ApiRateLimitTest.java b/plugins/api/rate-limit/test/org/apache/cloudstack/ratelimit/ApiRateLimitTest.java index 1a77a4ef3a6..3c6cadfc33c 100644 --- a/plugins/api/rate-limit/test/org/apache/cloudstack/ratelimit/ApiRateLimitTest.java +++ b/plugins/api/rate-limit/test/org/apache/cloudstack/ratelimit/ApiRateLimitTest.java @@ -55,6 +55,7 @@ public class ApiRateLimitTest { when(_configDao.getValue(Config.ApiLimitInterval.key())).thenReturn(null); when(_configDao.getValue(Config.ApiLimitMax.key())).thenReturn(null); when(_configDao.getValue(Config.ApiLimitCacheSize.key())).thenReturn(null); + when(_configDao.getValue(Config.ApiLimitEnabled.key())).thenReturn("true"); // enable api rate limiting _limitService._configDao = _configDao; _limitService.configure("ApiRateLimitTest", Collections. emptyMap()); @@ -106,6 +107,8 @@ public class ApiRateLimitTest { + " accesses take less than a second to perform", isUnderLimit(key)); } + + @Test public void canDoReasonableNumberOfApiAccessPerSecond() throws Exception { int allowedRequests = 200; @@ -232,4 +235,26 @@ public class ApiRateLimitTest { } + @Test + public void disableApiLimit() throws Exception { + try { + int allowedRequests = 200; + _limitService.setMaxAllowed(allowedRequests); + _limitService.setTimeToLive(1); + _limitService.setEnabled(false); + + User key = createFakeUser(); + + for (int i = 0; i < allowedRequests + 1; i++) { + assertTrue("We should allow more than " + allowedRequests + " requests per second when api throttling is disabled.", + isUnderLimit(key)); + } + } finally { + _limitService.setEnabled(true); // enable api throttling to avoid + // impacting other testcases + } + + } + + } diff --git a/server/src/com/cloud/configuration/Config.java b/server/src/com/cloud/configuration/Config.java index 8a75a96845a..418f97d8c71 100755 --- a/server/src/com/cloud/configuration/Config.java +++ b/server/src/com/cloud/configuration/Config.java @@ -372,7 +372,8 @@ public enum Config { IntervalToEchoBaremetalSecurityGroupAgent("Advanced", ManagementServer.class, Integer.class, "interval.baremetal.securitygroup.agent.echo", "10", "Interval to echo baremetal security group agent, in seconds", null), TimeoutToEchoBaremetalSecurityGroupAgent("Advanced", ManagementServer.class, Integer.class, "timeout.baremetal.securitygroup.agent.echo", "3600", "Timeout to echo baremetal security group agent, in seconds, the provisioning process will be treated as a failure", null), - ApiLimitInterval("Advanced", ManagementServer.class, Integer.class, "api.throttling.interval", "1", "Time interval (in seconds) to reset API count", null), + ApiLimitEnabled("Advanced", ManagementServer.class, Boolean.class, "api.throttling.enabled", "true", "Enable/disable Api rate limit", null), + ApiLimitInterval("Advanced", ManagementServer.class, Integer.class, "api.throttling.interval", "1", "Time interval (in seconds) to reset API count", null), ApiLimitMax("Advanced", ManagementServer.class, Integer.class, "api.throttling.max", "25", "Max allowed number of APIs within fixed interval", null), ApiLimitCacheSize("Advanced", ManagementServer.class, Integer.class, "api.throttling.cachesize", "50000", "Account based API count cache size", null), diff --git a/server/src/com/cloud/server/ManagementServerImpl.java b/server/src/com/cloud/server/ManagementServerImpl.java index 95b2973a384..3c615e1957c 100755 --- a/server/src/com/cloud/server/ManagementServerImpl.java +++ b/server/src/com/cloud/server/ManagementServerImpl.java @@ -2535,6 +2535,7 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe String userPublicTemplateEnabled = _configs.get(Config.AllowPublicUserTemplates.key()); // add some parameters UI needs to handle API throttling + boolean apiLimitEnabled = Boolean.parseBoolean(_configDao.getValue(Config.ApiLimitEnabled.key())); Integer apiLimitInterval = Integer.valueOf(_configDao.getValue(Config.ApiLimitInterval.key())); Integer apiLimitMax = Integer.valueOf(_configDao.getValue(Config.ApiLimitMax.key())); @@ -2546,8 +2547,10 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe capabilities.put("projectInviteRequired", _projectMgr.projectInviteRequired()); capabilities.put("allowusercreateprojects", _projectMgr.allowUserToCreateProject()); capabilities.put("customDiskOffMaxSize", diskOffMaxSize); - capabilities.put("apiLimitInterval", apiLimitInterval); - capabilities.put("apiLimitMax", apiLimitMax); + if (apiLimitEnabled) { + capabilities.put("apiLimitInterval", apiLimitInterval); + capabilities.put("apiLimitMax", apiLimitMax); + } return capabilities; } diff --git a/setup/db/db/schema-40to410.sql b/setup/db/db/schema-40to410.sql index 4fa83a0a40e..706a1973bee 100644 --- a/setup/db/db/schema-40to410.sql +++ b/setup/db/db/schema-40to410.sql @@ -241,6 +241,7 @@ UPDATE `cloud`.`volumes` set uuid=id WHERE uuid is NULL; -- UPDATE `cloud`.`conditions` set uuid=id WHERE uuid is NULL; INSERT IGNORE INTO `cloud`.`configuration` VALUES ('Advanced', 'DEFAULT', 'management-server', 'detail.batch.query.size', '2000', 'Default entity detail batch query size for listing'); +INSERT IGNORE INTO `cloud`.`configuration` VALUES ('Advanced', 'DEFAULT', 'management-server', 'api.throttling.enabled', 'false', 'Enable/Disable Api rate limit'); INSERT IGNORE INTO `cloud`.`configuration` VALUES ('Advanced', 'DEFAULT', 'management-server', 'api.throttling.interval', '1', 'Time interval (in seconds) to reset API count'); INSERT IGNORE INTO `cloud`.`configuration` VALUES ('Advanced', 'DEFAULT', 'management-server', 'api.throttling.max', '25', 'Max allowed number of APIs within fixed interval'); INSERT IGNORE INTO `cloud`.`configuration` VALUES ('Advanced', 'DEFAULT', 'management-server', 'api.throttling.cachesize', '50000', 'Account based API count cache size');