You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cloudstack.apache.org by mc...@apache.org on 2013/01/16 00:55:10 UTC
[43/43] git commit: Reuse APIChecker adapter interface for APi Rate
limit checking and optimize ApiRateLimitService interface.
Updated Branches:
refs/heads/api_limit 57e67c57d -> 4355d06a8
Reuse APIChecker adapter interface for APi Rate limit checking and optimize ApiRateLimitService interface.
Signed-off-by: Min Chen <mi...@citrix.com>
Project: http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/commit/4355d06a
Tree: http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/tree/4355d06a
Diff: http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/diff/4355d06a
Branch: refs/heads/api_limit
Commit: 4355d06a869ce1a745bfc483f7bd0fc246cdf2c5
Parents: ec3dd71 7f1486e
Author: Min Chen <mi...@citrix.com>
Authored: Tue Jan 15 15:53:19 2013 -0800
Committer: Min Chen <mi...@citrix.com>
Committed: Tue Jan 15 15:53:19 2013 -0800
----------------------------------------------------------------------
.../com/cloud/exception/RequestLimitException.java | 43 ++++
api/src/com/cloud/network/NetworkService.java | 2 +
api/src/com/cloud/user/DomainService.java | 2 +
api/src/org/apache/cloudstack/acl/APIChecker.java | 11 +-
.../apache/cloudstack/api/ResponseGenerator.java | 7 -
.../api/command/user/vm/DeployVMCmd.java | 17 ++-
.../api/command/user/volume/ExtractVolumeCmd.java | 2 +-
client/bindir/cloud-setup-management.in | 10 +-
client/tomcatconf/api-limit_commands.properties.in | 24 ---
.../cisconexusvsm_commands.properties.in | 25 ---
client/tomcatconf/commands-ext.properties.in | 28 ---
client/tomcatconf/commands.properties.in | 89 ++++++++
client/tomcatconf/components.xml.in | 8 +-
client/tomcatconf/f5bigip_commands.properties.in | 32 ---
.../tomcatconf/junipersrx_commands.properties.in | 30 ---
client/tomcatconf/netapp_commands.properties.in | 33 ---
.../netscalerloadbalancer_commands.properties.in | 26 ---
.../tomcatconf/nicira-nvp_commands.properties.in | 29 ---
client/tomcatconf/simulator_commands.properties.in | 19 --
.../virtualrouter_commands.properties.in | 24 ---
.../acl/StaticRoleBasedAPIAccessChecker.java | 52 +++---
.../api/command/user/discovery/ListApisCmd.java | 10 +-
.../cloudstack/discovery/ApiDiscoveryService.java | 4 +-
.../discovery/ApiDiscoveryServiceImpl.java | 116 +++++------
.../command/admin/ratelimit/ResetApiLimitCmd.java | 2 +-
.../api/command/user/ratelimit/GetApiLimitCmd.java | 3 +-
.../cloudstack/ratelimit/ApiRateLimitService.java | 9 +-
.../ratelimit/ApiRateLimitServiceImpl.java | 65 ++++---
.../cloudstack/ratelimit/ApiRateLimitTest.java | 72 ++++---
.../hypervisor/kvm/resource/KVMGuestOsMapper.java | 2 +
.../kvm/resource/LibvirtComputingResource.java | 5 +-
.../server/ManagementServerSimulatorImpl.java | 11 +-
.../network/element/CiscoNexusVSMElement.java | 11 +-
.../xen/resource/CitrixResourceBase.java | 6 +-
.../element/F5ExternalLoadBalancerElement.java | 15 +-
.../element/JuniperSRXExternalFirewallElement.java | 14 +-
.../network/element/MidokuraMidonetElement.java | 9 +-
.../cloud/network/element/NetscalerElement.java | 12 +-
.../cloud/network/element/NiciraNvpElement.java | 10 +-
pom.xml | 2 +
python/lib/cloudutils/utilities.py | 6 +-
server/src/com/cloud/api/ApiDispatcher.java | 6 -
server/src/com/cloud/api/ApiResponseHelper.java | 5 -
server/src/com/cloud/api/ApiServer.java | 90 +++------
server/src/com/cloud/api/ApiServlet.java | 45 ++---
server/src/com/cloud/configuration/Config.java | 5 +-
.../configuration/ConfigurationManagerImpl.java | 54 +++---
.../consoleproxy/ConsoleProxyManagerImpl.java | 35 ++--
.../com/cloud/hypervisor/HypervisorGuruBase.java | 1 +
.../src/com/cloud/network/NetworkManagerImpl.java | 6 +
.../network/element/VirtualRouterElement.java | 10 +-
.../router/VirtualNetworkApplianceManagerImpl.java | 4 +-
.../com/cloud/server/ManagementServerExtImpl.java | 6 +-
.../src/com/cloud/server/ManagementServerImpl.java | 7 +-
server/src/com/cloud/user/DomainManagerImpl.java | 5 +
server/test/com/cloud/api/ListPerfTest.java | 22 ++-
.../com/cloud/network/MockNetworkManagerImpl.java | 6 +
.../test/com/cloud/user/MockDomainManagerImpl.java | 6 +
.../test/com/cloud/vpc/MockNetworkManagerImpl.java | 6 +
setup/db/db/schema-40to410.sql | 2 -
setup/db/templates.kvm.sql | 11 +-
setup/db/templates.sql | 7 +
tools/apidoc/gen_toc.py | 1 +
tools/apidoc/pom.xml | 2 +-
tools/devcloud-kvm/devcloud-kvm-advanced.cfg | 115 +++++++++++
tools/devcloud-kvm/devcloud-kvm.py | 157 +++++++++++++++
tools/devcloud-kvm/kvm.properties | 62 ++++++
tools/devcloud/devcloud-advanced.cfg | 118 +++++++++++
tools/devcloud/devcloud-advanced_internal-mgt.cfg | 124 ++++++++++++
tools/marvin/marvin/configGenerator.py | 3 +-
tools/marvin/marvin/deployDataCenter.py | 10 +-
tools/marvin/marvin/integration/lib/base.py | 3 +
.../marvin/marvin/sandbox/advanced/advanced_env.py | 18 ++-
.../marvin/sandbox/advanced/setup.properties | 16 +-
ui/scripts/network.js | 10 +-
ui/scripts/sharedFunctions.js | 3 +-
.../cloud/utils/component/PluggableService.java | 3 +-
utils/src/com/cloud/utils/db/GenericDao.java | 2 +-
utils/src/com/cloud/utils/db/GenericDaoBase.java | 2 +-
.../utils/exception/CSExceptionErrorCode.java | 1 +
80 files changed, 1209 insertions(+), 677 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/4355d06a/api/src/com/cloud/exception/RequestLimitException.java
----------------------------------------------------------------------
diff --cc api/src/com/cloud/exception/RequestLimitException.java
index 0000000,0000000..0142f8e
new file mode 100644
--- /dev/null
+++ b/api/src/com/cloud/exception/RequestLimitException.java
@@@ -1,0 -1,0 +1,43 @@@
++// 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.exception;
++
++import com.cloud.utils.SerialVersionUID;
++import com.cloud.utils.exception.CloudRuntimeException;
++
++/**
++ * Exception thrown if number of requests is over api rate limit set.
++ * @author minc
++ *
++ */
++public class RequestLimitException extends CloudRuntimeException {
++
++ private static final long serialVersionUID = SerialVersionUID.AccountLimitException;
++
++ protected RequestLimitException() {
++ super();
++ }
++
++ public RequestLimitException(String msg) {
++ super(msg);
++ }
++
++ public RequestLimitException(String msg, Throwable cause) {
++ super(msg, cause);
++ }
++
++}
http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/4355d06a/api/src/org/apache/cloudstack/acl/APIChecker.java
----------------------------------------------------------------------
diff --cc api/src/org/apache/cloudstack/acl/APIChecker.java
index 61dd7de,0d0dfd1..2e2b73b
--- a/api/src/org/apache/cloudstack/acl/APIChecker.java
+++ b/api/src/org/apache/cloudstack/acl/APIChecker.java
@@@ -16,13 -16,15 +16,16 @@@
// under the License.
package org.apache.cloudstack.acl;
- import org.apache.cloudstack.acl.RoleType;
+ import com.cloud.exception.PermissionDeniedException;
++import com.cloud.exception.RequestLimitException;
+ import com.cloud.user.User;
import com.cloud.utils.component.Adapter;
// APIChecker checks the ownership and access control to API requests
public interface APIChecker extends Adapter {
// Interface for checking access for a role using apiname
- boolean checkAccess(RoleType roleType, String apiCommandName);
- // Interface for checking existence of an api by name
- boolean checkExistence(String apiCommandName);
+ // If true, apiChecker has checked the operation
+ // If false, apiChecker is unable to handle the operation or not implemented
+ // On exception, checkAccess failed don't allow
- boolean checkAccess(User user, String apiCommandName) throws PermissionDeniedException;
++ boolean checkAccess(User user, String apiCommandName) throws PermissionDeniedException, RequestLimitException;
}
http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/4355d06a/client/tomcatconf/commands.properties.in
----------------------------------------------------------------------
diff --cc client/tomcatconf/commands.properties.in
index 3872f2a,99cb874..db722ad
--- a/client/tomcatconf/commands.properties.in
+++ b/client/tomcatconf/commands.properties.in
@@@ -424,3 -424,87 +424,92 @@@ resetVpnConnection=1
listVpnCustomerGateways=15
listVpnGateways=15
listVpnConnections=15
+
+ #### router commands
+ createVirtualRouterElement=7
+ configureVirtualRouterElement=7
+ listVirtualRouterElements=7
+
+ #### usage commands
+ generateUsageRecords=1
+ listUsageRecords=1
+ listUsageTypes=1
+
+ #### traffic monitor commands
+ addTrafficMonitor=1
+ deleteTrafficMonitor=1
+ listTrafficMonitors=1
+
+ #### Cisco Nexus 1000v Virtual Supervisor Module (VSM) commands
+ deleteCiscoNexusVSM=1
+ enableCiscoNexusVSM=1
+ disableCiscoNexusVSM=1
+ listCiscoNexusVSMs=1
+
+ #### f5 big ip load balancer commands
+
+ #Deprecated commands
+ addExternalLoadBalancer=1
+ deleteExternalLoadBalancer=1
+ listExternalLoadBalancers=1
+
+ addF5LoadBalancer=1
+ configureF5LoadBalancer=1
+ deleteF5LoadBalancer=1
+ listF5LoadBalancers=1
+ listF5LoadBalancerNetworks=1
+
+ #### juniper srx firewall commands
+ addExternalFirewall=1
+ deleteExternalFirewall=1
+ listExternalFirewalls=1
+
+ addSrxFirewall=1
+ deleteSrxFirewall=1
+ configureSrxFirewall=1
+ listSrxFirewalls=1
+ listSrxFirewallNetworks=1
+
+ ####Netapp integration commands
+ createVolumeOnFiler=15
+ destroyVolumeOnFiler=15
+ listVolumesOnFiler=15
+ createLunOnFiler=15
+ destroyLunOnFiler=15
+ listLunsOnFiler=15
+ associateLun=15
+ dissociateLun=15
+ createPool=15
+ deletePool=15
+ modifyPool=15
+ listPools=15
+
+ #### netscaler load balancer commands
+ addNetscalerLoadBalancer=1
+ deleteNetscalerLoadBalancer=1
+ configureNetscalerLoadBalancer=1
+ listNetscalerLoadBalancers=1
+ listNetscalerLoadBalancerNetworks=1
+
+ #### nicira nvp commands
+
+ addNiciraNvpDevice=1
+ deleteNiciraNvpDevice=1
+ listNiciraNvpDevices=1
+ listNiciraNvpDeviceNetworks=1
+
+ # Not implemented (yet)
+ #configureNiciraNvpDevice=1
+
+ #### host simulator commands
+
+ configureSimulator=1
+
+ #### api discovery commands
+
+ listApis=15
++
++#### API Rate Limit service command
++
++getApiLimit=15
++resetApiLimit=1
http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/4355d06a/client/tomcatconf/components.xml.in
----------------------------------------------------------------------
diff --cc client/tomcatconf/components.xml.in
index 630bd97,bb39839..e19b418
--- a/client/tomcatconf/components.xml.in
+++ b/client/tomcatconf/components.xml.in
@@@ -54,15 -54,8 +54,13 @@@ under the License
<param name="premium">true</param>
</dao>
<adapters key="org.apache.cloudstack.acl.APIChecker">
- <adapter name="StaticRoleBasedAPIAccessChecker" class="org.apache.cloudstack.acl.StaticRoleBasedAPIAccessChecker"/>
- </adapters>
- <adapters key="org.apache.cloudstack.acl.APILimitChecker">
+ <adapter name="AccountBasedAPIRateLimit" class="org.apache.cloudstack.ratelimit.ApiRateLimitServiceImpl" singleton="true">
+ <param name="api.throttling.interval">1</param>
+ <param name="api.throttling.max">25</param>
+ <param name="api.throttling.cachesize">50000</param>
- </adapter>
- </adapters>
++ </adapter>
+ <adapter name="StaticRoleBasedAPIAccessChecker" class="org.apache.cloudstack.acl.StaticRoleBasedAPIAccessChecker"/>
+ </adapters>
<adapters key="com.cloud.agent.manager.allocator.HostAllocator">
<adapter name="FirstFitRouting" class="com.cloud.agent.manager.allocator.impl.FirstFitRoutingAllocator"/>
<!--adapter name="FirstFitRouting" class="com.cloud.agent.manager.allocator.impl.RecreateHostAllocator"/-->
http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/4355d06a/plugins/api/rate-limit/src/org/apache/cloudstack/api/command/admin/ratelimit/ResetApiLimitCmd.java
----------------------------------------------------------------------
diff --cc plugins/api/rate-limit/src/org/apache/cloudstack/api/command/admin/ratelimit/ResetApiLimitCmd.java
index 3c612fa,0000000..771b63a
mode 100644,000000..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
@@@ -1,94 -1,0 +1,94 @@@
+// 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.command.admin.ratelimit;
+
+import org.apache.cloudstack.api.*;
+import org.apache.cloudstack.api.response.AccountResponse;
+import org.apache.cloudstack.api.response.ApiLimitResponse;
+import org.apache.cloudstack.api.response.SuccessResponse;
+import org.apache.log4j.Logger;
+
+import org.apache.cloudstack.api.APICommand;
+import org.apache.cloudstack.ratelimit.ApiRateLimitService;
+
+import com.cloud.user.Account;
+import com.cloud.user.UserContext;
+
+@APICommand(name = "resetApiLimit", responseObject=ApiLimitResponse.class, description="Reset api count")
+public class ResetApiLimitCmd extends BaseCmd {
+ private static final Logger s_logger = Logger.getLogger(ResetApiLimitCmd.class.getName());
+
+ private static final String s_name = "resetapilimitresponse";
+
+ @PlugService
+ ApiRateLimitService _apiLimitService;
+
+ /////////////////////////////////////////////////////
+ //////////////// API parameters /////////////////////
+ /////////////////////////////////////////////////////
+
+ @ACL
+ @Parameter(name=ApiConstants.ACCOUNT, type=CommandType.UUID, entityType=AccountResponse.class,
+ description="the ID of the acount whose limit to be reset")
+ private Long accountId;
+
+ /////////////////////////////////////////////////////
+ /////////////////// Accessors ///////////////////////
+ /////////////////////////////////////////////////////
+
+
+ public Long getAccountId() {
+ return accountId;
+ }
+
+
+ public void setAccountId(Long accountId) {
+ this.accountId = accountId;
+ }
+
+
+ /////////////////////////////////////////////////////
+ /////////////// API Implementation///////////////////
+ /////////////////////////////////////////////////////
+
+
+ @Override
+ public String getCommandName() {
+ return s_name;
+ }
+
+ @Override
+ public long getEntityOwnerId() {
+ Account account = UserContext.current().getCaller();
+ if (account != null) {
+ return account.getId();
+ }
+
+ return Account.ACCOUNT_ID_SYSTEM; // no account info given, parent this command to SYSTEM so ERROR events are tracked
+ }
+
+ @Override
+ public void execute(){
- boolean result = _apiLimitService.resetApiLimit(this);
++ boolean result = _apiLimitService.resetApiLimit(this.accountId);
+ if (result) {
+ SuccessResponse response = new SuccessResponse(getCommandName());
+ this.setResponseObject(response);
+ } else {
+ throw new ServerApiException(BaseCmd.INTERNAL_ERROR, "Failed to reset api limit counter");
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/4355d06a/plugins/api/rate-limit/src/org/apache/cloudstack/api/command/user/ratelimit/GetApiLimitCmd.java
----------------------------------------------------------------------
diff --cc plugins/api/rate-limit/src/org/apache/cloudstack/api/command/user/ratelimit/GetApiLimitCmd.java
index 0397fa8,0000000..ad1fb28
mode 100644,000000..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
@@@ -1,87 -1,0 +1,88 @@@
+// 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.command.user.ratelimit;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.cloudstack.api.ACL;
+import org.apache.cloudstack.api.ApiConstants;
+import org.apache.cloudstack.api.BaseCmd;
+import org.apache.cloudstack.api.BaseListCmd;
+import org.apache.cloudstack.api.Parameter;
+import org.apache.cloudstack.api.PlugService;
+import org.apache.cloudstack.api.ServerApiException;
+import org.apache.cloudstack.api.BaseCmd.CommandType;
+import org.apache.cloudstack.api.command.admin.ratelimit.ResetApiLimitCmd;
+import org.apache.cloudstack.api.response.AccountResponse;
+import org.apache.cloudstack.api.response.ApiLimitResponse;
+import org.apache.cloudstack.api.response.PhysicalNetworkResponse;
+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.exception.ConcurrentOperationException;
+import com.cloud.exception.InsufficientCapacityException;
+import com.cloud.exception.InvalidParameterValueException;
+import com.cloud.exception.ResourceAllocationException;
+import com.cloud.exception.ResourceUnavailableException;
+import com.cloud.user.Account;
+import com.cloud.user.UserContext;
+import com.cloud.utils.exception.CloudRuntimeException;
+
+@APICommand(name = "getApiLimit", responseObject=ApiLimitResponse.class, description="Get API limit count for the caller")
+public class GetApiLimitCmd extends BaseListCmd {
+ private static final Logger s_logger = Logger.getLogger(GetApiLimitCmd.class.getName());
+
+ private static final String s_name = "getapilimitresponse";
+
+ @PlugService
+ ApiRateLimitService _apiLimitService;
+
+
+
+
+ /////////////////////////////////////////////////////
+ /////////////// API Implementation///////////////////
+ /////////////////////////////////////////////////////
+
+ @Override
+ public String getCommandName() {
+ return s_name;
+ }
+
+ @Override
+ public long getEntityOwnerId() {
+ Account account = UserContext.current().getCaller();
+ if (account != null) {
+ return account.getId();
+ }
+
+ return Account.ACCOUNT_ID_SYSTEM; // no account info given, parent this command to SYSTEM so ERROR events are tracked
+ }
+
+ @Override
+ public void execute(){
- ApiLimitResponse response = _apiLimitService.searchApiLimit(this);
++ Account caller = UserContext.current().getCaller();
++ ApiLimitResponse response = _apiLimitService.searchApiLimit(caller);
+ response.setResponseName(getCommandName());
+ this.setResponseObject(response);
+ }
+}
+
+
http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/4355d06a/plugins/api/rate-limit/src/org/apache/cloudstack/ratelimit/ApiRateLimitService.java
----------------------------------------------------------------------
diff --cc plugins/api/rate-limit/src/org/apache/cloudstack/ratelimit/ApiRateLimitService.java
index 8c9d49b,0000000..c5b7150
mode 100644,000000..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
@@@ -1,40 -1,0 +1,37 @@@
+// 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.ratelimit;
+
- import org.apache.cloudstack.api.command.admin.ratelimit.ResetApiLimitCmd;
- import org.apache.cloudstack.api.command.user.ratelimit.GetApiLimitCmd;
+import org.apache.cloudstack.api.response.ApiLimitResponse;
- import org.apache.cloudstack.api.response.ListResponse;
-
++import com.cloud.user.Account;
+import com.cloud.utils.component.PluggableService;
+
+/**
+ * Provide API rate limit service
+ * @author minc
+ *
+ */
+public interface ApiRateLimitService extends PluggableService{
+
- public ApiLimitResponse searchApiLimit(GetApiLimitCmd cmd);
++ public ApiLimitResponse searchApiLimit(Account caller);
+
- public boolean resetApiLimit(ResetApiLimitCmd cmd);
++ public boolean resetApiLimit(Long accountId);
+
+ public void setTimeToLive(int timeToLive);
+
+ public void setMaxAllowed(int max);
+}
http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/4355d06a/plugins/api/rate-limit/src/org/apache/cloudstack/ratelimit/ApiRateLimitServiceImpl.java
----------------------------------------------------------------------
diff --cc plugins/api/rate-limit/src/org/apache/cloudstack/ratelimit/ApiRateLimitServiceImpl.java
index 00f39af,0000000..1e9b9ad
mode 100644,000000..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
@@@ -1,181 -1,0 +1,196 @@@
+// 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.ratelimit;
+
++import java.util.ArrayList;
++import java.util.List;
+import java.util.Map;
+import javax.ejb.Local;
+import javax.naming.ConfigurationException;
+
+import net.sf.ehcache.Cache;
+import net.sf.ehcache.CacheManager;
+
+import org.apache.log4j.Logger;
+
- import org.apache.cloudstack.acl.APILimitChecker;
- import org.apache.cloudstack.api.BaseCmd;
- import org.apache.cloudstack.api.ServerApiException;
++import org.apache.cloudstack.acl.APIChecker;
+import org.apache.cloudstack.api.command.admin.ratelimit.ResetApiLimitCmd;
+import org.apache.cloudstack.api.command.user.ratelimit.GetApiLimitCmd;
+import org.apache.cloudstack.api.response.ApiLimitResponse;
++
++import com.cloud.exception.PermissionDeniedException;
++import com.cloud.exception.RequestLimitException;
+import com.cloud.user.Account;
- import com.cloud.user.UserContext;
- import com.cloud.utils.PropertiesUtil;
++import com.cloud.user.AccountService;
++import com.cloud.user.User;
+import com.cloud.utils.component.AdapterBase;
++import com.cloud.utils.component.Inject;
+
- @Local(value = APILimitChecker.class)
- public class ApiRateLimitServiceImpl extends AdapterBase implements APILimitChecker, ApiRateLimitService {
++@Local(value = APIChecker.class)
++public class ApiRateLimitServiceImpl extends AdapterBase implements APIChecker, ApiRateLimitService {
+ private static final Logger s_logger = Logger.getLogger(ApiRateLimitServiceImpl.class);
+
+ /**
+ * Fixed time duration where api rate limit is set, in seconds
+ */
+ private int timeToLive = 1;
+
+ /**
+ * Max number of api requests during timeToLive duration.
+ */
+ private int maxAllowed = 30;
+
+ private LimitStore _store = null;
+
++ @Inject
++ AccountService _accountService;
++
++
+
+ @Override
+ public boolean configure(String name, Map<String, Object> params) throws ConfigurationException {
+ super.configure(name, params);
+
+ if (_store == null) {
+ // not configured yet, note that since this class is both adapter
+ // and pluggableService, so this method
+ // may be invoked twice in ComponentLocator.
+ // get global configured duration and max values
+ Object duration = params.get("api.throttling.interval");
+ if (duration != null) {
+ timeToLive = Integer.parseInt((String) duration);
+ }
+ Object maxReqs = params.get("api.throttling.max");
+ if (maxReqs != null) {
+ maxAllowed = Integer.parseInt((String) maxReqs);
+ }
+ // create limit store
+ EhcacheLimitStore cacheStore = new EhcacheLimitStore();
+ int maxElements = 10000;
+ Object cachesize = params.get("api.throttling.cachesize");
+ if ( cachesize != null ){
+ maxElements = Integer.parseInt((String)cachesize);
+ }
+ CacheManager cm = CacheManager.create();
+ Cache cache = new Cache("api-limit-cache", maxElements, false, false, timeToLive, timeToLive);
+ cm.addCache(cache);
- s_logger.info("Limit Cache created: " + cache.toString());
++ s_logger.info("Limit Cache created with timeToLive=" + timeToLive + ", maxAllowed=" + maxAllowed + ", maxElements=" + maxElements );
+ cacheStore.setCache(cache);
+ _store = cacheStore;
++
+ }
+
+ return true;
+
+ }
+
+
+
+ @Override
- public ApiLimitResponse searchApiLimit(GetApiLimitCmd cmd) {
- Account caller = UserContext.current().getCaller();
++ public ApiLimitResponse searchApiLimit(Account caller) {
+ ApiLimitResponse response = new ApiLimitResponse();
+ response.setAccountId(caller.getUuid());
+ response.setAccountName(caller.getAccountName());
+ StoreEntry entry = _store.get(caller.getId());
+ if (entry == null) {
+
+ /* Populate the entry, thus unlocking any underlying mutex */
+ entry = _store.create(caller.getId(), timeToLive);
+ response.setApiIssued(0);
+ response.setApiAllowed(maxAllowed);
+ response.setExpireAfter(timeToLive);
+ }
+ else{
+ response.setApiIssued(entry.getCounter());
+ response.setApiAllowed(maxAllowed - entry.getCounter());
+ response.setExpireAfter(entry.getExpireDuration());
+ }
+
+ return response;
+ }
+
+
+
+ @Override
- public boolean resetApiLimit(ResetApiLimitCmd cmd) {
- if ( cmd.getAccountId() != null ){
- _store.create(cmd.getAccountId(), timeToLive);
++ public boolean resetApiLimit(Long accountId) {
++ if ( accountId != null ){
++ _store.create(accountId, timeToLive);
+ }
+ else{
+ _store.resetCounters();
+ }
+ return true;
+ }
+
+
- @Override
- public void checkLimit(Account account) throws ServerApiException {
+
- Long accountId = account.getId();
++ @Override
++ public boolean checkAccess(User user, String apiCommandName) throws PermissionDeniedException, RequestLimitException {
++ Long accountId = user.getAccountId();
++ Account account = _accountService.getAccount(accountId);
++ if ( _accountService.isRootAdmin(account.getType())){
++ // no API throttling on root admin
++ return true;
++ }
+ StoreEntry entry = _store.get(accountId);
+
+ if (entry == null) {
+
+ /* Populate the entry, thus unlocking any underlying mutex */
+ entry = _store.create(accountId, timeToLive);
+ }
+
+ /* Increment the client count and see whether we have hit the maximum allowed clients yet. */
+ int current = entry.incrementAndGet();
+
+ if (current <= maxAllowed) {
- return;
++ s_logger.info("current count = " + current);
++ return true;
+ } else {
+ long expireAfter = entry.getExpireDuration();
- s_logger.warn("The given user has reached his/her account api limit, please retry after " + expireAfter + " ms.");
- throw new ServerApiException(BaseCmd.API_LIMIT_EXCEED, "The given user has reached his/her account api limit, please retry after " +
- expireAfter + " ms.");
++ // for this exception, we can just show the same message to user and admin users.
++ String msg = "The given user has reached his/her account api limit, please retry after " + expireAfter + " ms.";
++ s_logger.warn(msg);
++ throw new RequestLimitException(msg);
+ }
+ }
+
+
-
+ @Override
- public Map<String, String> getProperties() {
- return PropertiesUtil.processConfigFile(new String[]
- { "api-limit_commands.properties" });
++ public List<Class<?>> getCommands() {
++ List<Class<?>> cmdList = new ArrayList<Class<?>>();
++ cmdList.add(ResetApiLimitCmd.class);
++ cmdList.add(GetApiLimitCmd.class);
++ return cmdList;
+ }
+
+
-
+ @Override
+ public void setTimeToLive(int timeToLive) {
+ this.timeToLive = timeToLive;
+ }
+
+
+
+ @Override
+ public void setMaxAllowed(int max) {
+ this.maxAllowed = max;
+
+ }
+
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/4355d06a/plugins/api/rate-limit/test/org/apache/cloudstack/ratelimit/ApiRateLimitTest.java
----------------------------------------------------------------------
diff --cc plugins/api/rate-limit/test/org/apache/cloudstack/ratelimit/ApiRateLimitTest.java
index ef3cf6d,0000000..850182d
mode 100644,000000..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
@@@ -1,214 -1,0 +1,226 @@@
+// 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.ratelimit;
+
+import java.util.Collections;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import javax.naming.ConfigurationException;
+
- import org.apache.cloudstack.api.ServerApiException;
- import org.apache.cloudstack.api.command.admin.ratelimit.ResetApiLimitCmd;
- import org.apache.cloudstack.api.command.user.ratelimit.GetApiLimitCmd;
+import org.apache.cloudstack.api.response.ApiLimitResponse;
+import org.apache.cloudstack.ratelimit.ApiRateLimitServiceImpl;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
- import com.cloud.configuration.Config;
- import com.cloud.configuration.dao.ConfigurationDao;
++import com.cloud.exception.RequestLimitException;
+import com.cloud.user.Account;
++import com.cloud.user.AccountService;
+import com.cloud.user.AccountVO;
- import com.cloud.user.UserContext;
++import com.cloud.user.User;
++import com.cloud.user.UserVO;
+
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.*;
+
+public class ApiRateLimitTest {
+
+ static ApiRateLimitServiceImpl _limitService = new ApiRateLimitServiceImpl();
- private static long acctIdSeq = 0L;
++ static AccountService _accountService = mock(AccountService.class);
++ private static long acctIdSeq = 5L;
++ private static Account testAccount;
+
+ @BeforeClass
+ public static void setUp() throws ConfigurationException {
+
+ _limitService.configure("ApiRateLimitTest", Collections.<String, Object> emptyMap());
++
++ _limitService._accountService = _accountService;
++
++ // Standard responses
++ AccountVO acct = new AccountVO(acctIdSeq);
++ acct.setType(Account.ACCOUNT_TYPE_NORMAL);
++ acct.setAccountName("demo");
++ testAccount = acct;
++
++ when(_accountService.getAccount(5L)).thenReturn(testAccount);
++ when(_accountService.isRootAdmin(Account.ACCOUNT_TYPE_NORMAL)).thenReturn(false);
+ }
+
++ @Before
++ public void testSetUp() {
++ // reset counter for each test
++ _limitService.resetApiLimit(null);
++ }
+
- private Account createFakeAccount(){
- return new AccountVO(acctIdSeq++);
++ private User createFakeUser(){
++ UserVO user = new UserVO();
++ user.setAccountId(acctIdSeq);
++ return user;
+ }
+
- private boolean isUnderLimit(Account key){
++ private boolean isUnderLimit(User key){
+ try{
- _limitService.checkLimit(key);
++ _limitService.checkAccess(key, null);
+ return true;
+ }
- catch (ServerApiException ex){
++ catch (RequestLimitException ex){
+ return false;
+ }
+ }
+
+ @Test
+ public void sequentialApiAccess() {
+ int allowedRequests = 1;
+ _limitService.setMaxAllowed(allowedRequests);
+ _limitService.setTimeToLive(1);
+
- Account key = createFakeAccount();
++ User key = createFakeUser();
+ assertTrue("Allow for the first request", isUnderLimit(key));
+
+ assertFalse("Second request should be blocked, since we assume that the two api "
+ + " accesses take less than a second to perform", isUnderLimit(key));
+ }
+
+ @Test
+ public void canDoReasonableNumberOfApiAccessPerSecond() throws Exception {
+ int allowedRequests = 50000;
+ _limitService.setMaxAllowed(allowedRequests);
+ _limitService.setTimeToLive(1);
+
- Account key = createFakeAccount();
++ User key = createFakeUser();
+
+ for (int i = 0; i < allowedRequests; i++) {
- assertTrue("We should allow " + allowedRequests + " requests per second", isUnderLimit(key));
++ assertTrue("We should allow " + allowedRequests + " requests per second, but failed at request " + i, isUnderLimit(key));
+ }
+
+
+ assertFalse("We should block >" + allowedRequests + " requests per second", isUnderLimit(key));
+ }
+
+ @Test
+ public void multipleClientsCanAccessWithoutBlocking() throws Exception {
+ int allowedRequests = 200;
+ _limitService.setMaxAllowed(allowedRequests);
+ _limitService.setTimeToLive(1);
+
+
- final Account key = createFakeAccount();
++ final User key = createFakeUser();
+
+ int clientCount = allowedRequests;
+ Runnable[] clients = new Runnable[clientCount];
+ final boolean[] isUsable = new boolean[clientCount];
+
+ final CountDownLatch startGate = new CountDownLatch(1);
+
+ final CountDownLatch endGate = new CountDownLatch(clientCount);
+
+
+ for (int i = 0; i < isUsable.length; ++i) {
+ final int j = i;
+ clients[j] = new Runnable() {
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void run() {
+ try {
+ startGate.await();
+
+ isUsable[j] = isUnderLimit(key);
+
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ } finally {
+ endGate.countDown();
+ }
+ }
+ };
+ }
+
+ ExecutorService executor = Executors.newFixedThreadPool(clientCount);
+
+ for (Runnable runnable : clients) {
+ executor.execute(runnable);
+ }
+
+ startGate.countDown();
+
+ endGate.await();
+
+ for (boolean b : isUsable) {
+ assertTrue("Concurrent client request should be allowed within limit", b);
+ }
+ }
+
+ @Test
+ public void expiryOfCounterIsSupported() throws Exception {
+ int allowedRequests = 1;
+ _limitService.setMaxAllowed(allowedRequests);
+ _limitService.setTimeToLive(1);
+
- Account key = this.createFakeAccount();
++ User key = this.createFakeUser();
+
+ assertTrue("The first request should be allowed", isUnderLimit(key));
+
+ // Allow the token to expire
+ Thread.sleep(1001);
+
+ assertTrue("Another request after interval should be allowed as well", isUnderLimit(key));
+ }
+
+ @Test
+ public void verifyResetCounters() throws Exception {
+ int allowedRequests = 1;
+ _limitService.setMaxAllowed(allowedRequests);
+ _limitService.setTimeToLive(1);
+
- Account key = this.createFakeAccount();
++ User key = this.createFakeUser();
+
+ assertTrue("The first request should be allowed", isUnderLimit(key));
+
+ assertFalse("Another request should be blocked", isUnderLimit(key));
+
- ResetApiLimitCmd cmd = new ResetApiLimitCmd();
- cmd.setAccountId(key.getId());
-
- _limitService.resetApiLimit(cmd);
++ _limitService.resetApiLimit(key.getAccountId());
+
+ assertTrue("Another request should be allowed after reset counter", isUnderLimit(key));
+ }
+
- /* Disable this since I cannot mock Static method UserContext.current()
++
+ @Test
+ public void verifySearchCounter() throws Exception {
+ int allowedRequests = 10;
+ _limitService.setMaxAllowed(allowedRequests);
+ _limitService.setTimeToLive(1);
+
- Account key = this.createFakeAccount();
++ User key = this.createFakeUser();
+
+ for ( int i = 0; i < 5; i++ ){
+ assertTrue("Issued 5 requests", isUnderLimit(key));
+ }
+
- GetApiLimitCmd cmd = new GetApiLimitCmd();
- UserContext ctx = mock(UserContext.class);
- when(UserContext.current().getCaller()).thenReturn(key);
- ApiLimitResponse response = _limitService.searchApiLimit(cmd);
++ ApiLimitResponse response = _limitService.searchApiLimit(testAccount);
+ assertEquals("apiIssued is incorrect", 5, response.getApiIssued());
+ assertEquals("apiAllowed is incorrect", 5, response.getApiAllowed());
- assertTrue("expiredAfter is incorrect", response.getExpireAfter() < 1);
++ assertTrue("expiredAfter is incorrect", response.getExpireAfter() < 1000);
+
+ }
- */
++
+}
http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/4355d06a/server/src/com/cloud/api/ApiServer.java
----------------------------------------------------------------------
diff --cc server/src/com/cloud/api/ApiServer.java
index 1d15acf,e106f03..72bed44
--- a/server/src/com/cloud/api/ApiServer.java
+++ b/server/src/com/cloud/api/ApiServer.java
@@@ -115,6 -114,6 +115,7 @@@ import com.cloud.event.EventUtils
import com.cloud.exception.CloudAuthenticationException;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.exception.PermissionDeniedException;
++import com.cloud.exception.RequestLimitException;
import com.cloud.server.ManagementServer;
import com.cloud.user.Account;
import com.cloud.user.AccountManager;
@@@ -555,15 -549,13 +553,18 @@@ public class ApiServer implements HttpR
// 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, throw exception inside adapter implementation so that message
- // can contain some detailed information only known for each adapter implementation.
- checkRequestLimit(user);
++
+ try{
+ checkCommandAvailable(user, commandName);
}
- if (!isCommandAvailable(user, commandName)) {
+ catch (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(BaseCmd.UNSUPPORTED_ACTION_ERROR, "The given command does not exist or it is not available for user");
}
++ catch (RequestLimitException ex){
++ s_logger.debug(ex.getMessage());
++ throw new ServerApiException(BaseCmd.API_LIMIT_EXCEED, ex.getMessage());
++ }
return true;
} else {
// check against every available command to see if the command exists or not
@@@ -686,11 -680,11 +689,10 @@@
s_logger.info("User signature: " + signature + " is not equaled to computed signature: " + computedSignature);
}
return equalSig;
-- } catch (Exception ex) {
-- if (ex instanceof ServerApiException && ((ServerApiException) ex).getErrorCode() == BaseCmd.UNSUPPORTED_ACTION_ERROR) {
-- throw (ServerApiException) ex;
-- }
- s_logger.error("unable to verifty request signature", ex);
- s_logger.error("unable to verify request signature", ex);
++ } catch (ServerApiException ex){
++ throw ex;
++ } catch (Exception ex){
++ s_logger.error("unable to verify request signature");
}
return false;
}
@@@ -799,42 -786,14 +794,15 @@@
return true;
}
+
- private void checkRequestLimit(User user) throws ServerApiException {
- Account account = ApiDBUtils.findAccountById(user.getAccountId());
- if ( _accountMgr.isRootAdmin(account.getType()) ){
- // no api throttling for root admin
- return;
- }
- for (APILimitChecker apiChecker : _apiLimitCheckers) {
- // Fail the checking if any checker fails to verify
- apiChecker.checkLimit(account);
- }
- }
-
-
- private boolean doesCommandExist(String apiName) {
- for (APIChecker apiChecker : _apiAccessCheckers) {
- // If any checker has api info on the command, return true
- if (apiChecker.checkExistence(apiName))
- return true;
- }
- return false;
- }
-
- private boolean isCommandAvailable(User user, String commandName) {
+ private void checkCommandAvailable(User user, String commandName) throws PermissionDeniedException {
if (user == null) {
- return false;
+ throw new PermissionDeniedException("User is null for role based API access check for command" + commandName);
}
- Account account = _accountMgr.getAccount(user.getAccountId());
- RoleType roleType = _accountMgr.getRoleType(account);
for (APIChecker apiChecker : _apiAccessCheckers) {
- // Fail the checking if any checker fails to verify
- if (!apiChecker.checkAccess(roleType, commandName))
- return false;
+ apiChecker.checkAccess(user, commandName);
}
- return true;
}
private Class<?> getCmdClass(String cmdName) {
http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/4355d06a/server/src/com/cloud/api/ApiServlet.java
----------------------------------------------------------------------
diff --cc server/src/com/cloud/api/ApiServlet.java
index 19091f2,19091f2..21373cd
--- a/server/src/com/cloud/api/ApiServlet.java
+++ b/server/src/com/cloud/api/ApiServlet.java
@@@ -128,7 -128,7 +128,7 @@@ public class ApiServlet extends HttpSer
reqStr = auditTrailSb.toString() + " " + req.getQueryString();
s_logger.debug("===START=== " + StringUtils.cleanString(reqStr));
}
--
++
try {
HttpSession session = req.getSession(false);
Object[] responseTypeParam = params.get("response");
@@@ -298,24 -298,24 +298,16 @@@
* params.put(BaseCmd.Properties.ACCOUNT_OBJ.getName(), new Object[] { accountObj }); } else {
* params.put(BaseCmd.Properties.USER_ID.getName(), new String[] { userId });
* params.put(BaseCmd.Properties.ACCOUNT_OBJ.getName(), new Object[] { accountObj }); } }
-- *
++ *
* // update user context info here so that we can take information if the request is authenticated // via api
* key mechanism updateUserContext(params, session != null ? session.getId() : null);
*/
-- auditTrailSb.insert(0,
-- "(userId=" + UserContext.current().getCallerUserId() + " accountId=" + UserContext.current().getCaller().getId() + " sessionId=" + (session != null ? session.getId() : null)
-- + ")");
--
-- try {
-- String response = _apiServer.handleRequest(params, false, responseType, auditTrailSb);
-- writeResponse(resp, response != null ? response : "", HttpServletResponse.SC_OK, responseType);
-- } catch (ServerApiException se) {
-- String serializedResponseText = _apiServer.getSerializedApiError(se.getErrorCode(), se.getDescription(), params, responseType, null);
-- resp.setHeader("X-Description", se.getDescription());
-- writeResponse(resp, serializedResponseText, se.getErrorCode(), responseType);
-- auditTrailSb.append(" " + se.getErrorCode() + " " + se.getDescription());
-- }
++ auditTrailSb.insert(0, "(userId=" + UserContext.current().getCallerUserId() + " accountId="
++ + UserContext.current().getCaller().getId() + " sessionId=" + (session != null ? session.getId() : null) + ")");
++
++ String response = _apiServer.handleRequest(params, false, responseType, auditTrailSb);
++ writeResponse(resp, response != null ? response : "", HttpServletResponse.SC_OK, responseType);
} else {
if (session != null) {
try {
@@@ -329,17 -329,17 +321,14 @@@
writeResponse(resp, serializedResponse, HttpServletResponse.SC_UNAUTHORIZED, responseType);
}
++ } catch (ServerApiException se) {
++ String serializedResponseText = _apiServer.getSerializedApiError(se.getErrorCode(), se.getDescription(), params, responseType, null);
++ resp.setHeader("X-Description", se.getDescription());
++ writeResponse(resp, serializedResponseText, se.getErrorCode(), responseType);
++ auditTrailSb.append(" " + se.getErrorCode() + " " + se.getDescription());
} catch (Exception ex) {
-- if (ex instanceof ServerApiException && ((ServerApiException) ex).getErrorCode() == BaseCmd.UNSUPPORTED_ACTION_ERROR) {
-- ServerApiException se = (ServerApiException) ex;
-- String serializedResponseText = _apiServer.getSerializedApiError(se.getErrorCode(), se.getDescription(), params, responseType, null);
-- resp.setHeader("X-Description", se.getDescription());
-- writeResponse(resp, serializedResponseText, se.getErrorCode(), responseType);
-- auditTrailSb.append(" " + se.getErrorCode() + " " + se.getDescription());
-- } else {
-- s_logger.error("unknown exception writing api response", ex);
-- auditTrailSb.append(" unknown exception writing api response");
-- }
++ s_logger.error("unknown exception writing api response", ex);
++ auditTrailSb.append(" unknown exception writing api response");
} finally {
s_accessLogger.info(auditTrailSb.toString());
if (s_logger.isDebugEnabled()) {
@@@ -354,9 -354,9 +343,9 @@@
* private void updateUserContext(Map<String, Object[]> requestParameters, String sessionId) { String userIdStr =
* (String)(requestParameters.get(BaseCmd.Properties.USER_ID.getName())[0]); Account accountObj =
* (Account)(requestParameters.get(BaseCmd.Properties.ACCOUNT_OBJ.getName())[0]);
-- *
++ *
* Long userId = null; Long accountId = null; if(userIdStr != null) userId = Long.parseLong(userIdStr);
-- *
++ *
* if(accountObj != null) accountId = accountObj.getId(); UserContext.updateContext(userId, accountId, sessionId); }
*/
@@@ -386,7 -386,7 +375,7 @@@
private String getLoginSuccessResponse(HttpSession session, String responseType) {
StringBuffer sb = new StringBuffer();
int inactiveInterval = session.getMaxInactiveInterval();
--
++
String user_UUID = (String)session.getAttribute("user_UUID");
session.removeAttribute("user_UUID");
http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/4355d06a/server/src/com/cloud/configuration/Config.java
----------------------------------------------------------------------
diff --cc server/src/com/cloud/configuration/Config.java
index e6bf3d5,b91fbdd..59abd99
--- a/server/src/com/cloud/configuration/Config.java
+++ b/server/src/com/cloud/configuration/Config.java
@@@ -358,11 -358,9 +358,8 @@@ 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),
-
- // API throttling
- ApiLimitEnabled("Advanced", ManagementServer.class, Boolean.class, "api.throttling.enable", "true", "If true, api throttline feature is enabled", "true,false");
+ null, "Limits number of snapshots that can be handled by the host concurrently; default is NULL - unlimited", null);
-
private final String _category;
private final Class<?> _componentClass;
private final Class<?> _type;
http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/4355d06a/server/test/com/cloud/api/ListPerfTest.java
----------------------------------------------------------------------
diff --cc server/test/com/cloud/api/ListPerfTest.java
index e5d277a,eb98d91..350dde8
--- a/server/test/com/cloud/api/ListPerfTest.java
+++ b/server/test/com/cloud/api/ListPerfTest.java
@@@ -16,14 -16,11 +16,18 @@@
// under the License.
package com.cloud.api;
++import static org.junit.Assert.*;
++
import java.util.HashMap;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
import org.junit.Before;
import org.junit.Test;
++import com.cloud.utils.exception.CloudRuntimeException;
++
/**
* Test fixture to do performance test for list command
@@@ -166,57 -163,6 +170,71 @@@ public class ListPerfTest extends APITe
}
+ @Test
+ public void testMultiListAccounts() throws Exception {
+ // log in using normal user
+ login("demo", "password");
+ // issue list Accounts calls
+ final HashMap<String, String> params = new HashMap<String, String>();
+ params.put("response", "json");
+ params.put("listAll", "true");
+ params.put("sessionkey", sessionKey);
- int clientCount = 6;
++ // assuming ApiRateLimitService set api.throttling.max = 25
++ int clientCount = 26;
+ Runnable[] clients = new Runnable[clientCount];
+ final boolean[] isUsable = new boolean[clientCount];
+
+ final CountDownLatch startGate = new CountDownLatch(1);
+
+ final CountDownLatch endGate = new CountDownLatch(clientCount);
+
+
+ for (int i = 0; i < isUsable.length; ++i) {
+ final int j = i;
+ clients[j] = new Runnable() {
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void run() {
+ try {
+ startGate.await();
+
- System.out.println(sendRequest("listAccounts", params));
++ sendRequest("listAccounts", params);
+
++ isUsable[j] = true;
++
++ } catch (CloudRuntimeException e){
++ isUsable[j] = false;
++ e.printStackTrace();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ } finally {
+ endGate.countDown();
+ }
+ }
+ };
+ }
+
+ ExecutorService executor = Executors.newFixedThreadPool(clientCount);
+
+ for (Runnable runnable : clients) {
+ executor.execute(runnable);
+ }
+
+ startGate.countDown();
+
+ endGate.await();
+
++ int rejectCount = 0;
++ for ( int i = 0; i < isUsable.length; ++i){
++ if ( !isUsable[i])
++ rejectCount++;
++ }
++
++ assertEquals("Only one request should be rejected!", 1, rejectCount);
++
+ }
}
http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/4355d06a/utils/src/com/cloud/utils/exception/CSExceptionErrorCode.java
----------------------------------------------------------------------
diff --cc utils/src/com/cloud/utils/exception/CSExceptionErrorCode.java
index 303e0d6,303e0d6..8f40233
--- a/utils/src/com/cloud/utils/exception/CSExceptionErrorCode.java
+++ b/utils/src/com/cloud/utils/exception/CSExceptionErrorCode.java
@@@ -96,6 -96,6 +96,7 @@@ public class CSExceptionErrorCode
ExceptionErrorCodeMap.put("com.cloud.exception.UnsupportedServiceException", 4530);
ExceptionErrorCodeMap.put("com.cloud.exception.VirtualMachineMigrationException", 4535);
ExceptionErrorCodeMap.put("com.cloud.async.AsyncCommandQueued", 4540);
++ ExceptionErrorCodeMap.put("com.cloud.exception.RequestLimitException", 4545);
// Have a special error code for ServerApiException when it is
// thrown in a standalone manner when failing to detect any of the above