Neutron Limits API Infrastructure
- Adding JSON response model - Adding response model meta test - Adding API client and behavior GET method - Adding config and constants file - Adding composite Change-Id: Ic0adefc67c1ceac1bb03bc4ec784e111ea4cdfc2
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
"""
|
||||
Copyright 2015 Rackspace
|
||||
|
||||
Licensed 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.
|
||||
"""
|
||||
@@ -0,0 +1,83 @@
|
||||
"""
|
||||
Copyright 2015 Rackspace
|
||||
|
||||
Licensed 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.
|
||||
"""
|
||||
|
||||
import time
|
||||
|
||||
from cloudcafe.networking.networks.common.behaviors \
|
||||
import NetworkingBaseBehaviors, NetworkingResponse
|
||||
from cloudcafe.networking.networks.common.exceptions \
|
||||
import ResourceGetException
|
||||
from cloudcafe.networking.networks.extensions.limits_api.constants \
|
||||
import LimitsResponseCodes
|
||||
|
||||
|
||||
class LimitsBehaviors(NetworkingBaseBehaviors):
|
||||
|
||||
def __init__(self, limits_client, limits_config):
|
||||
super(LimitsBehaviors, self).__init__()
|
||||
self.config = limits_config
|
||||
self.client = limits_client
|
||||
|
||||
def get_limits(self, page_reverse=None,
|
||||
resource_get_attempts=None,
|
||||
raise_exception=False, poll_interval=None):
|
||||
"""
|
||||
@summary: get rate limits
|
||||
@param page_reverse: direction of the page
|
||||
@type page_reverse: bool
|
||||
@param resource_get_attempts: number of API retries
|
||||
@type resource_get_attempts: int
|
||||
@param raise_exception: flag to raise an exception if the get
|
||||
limits call was not as expected or to return None
|
||||
@type raise_exception: bool
|
||||
@param poll_interval: sleep time interval between API retries
|
||||
@type poll_interval: int
|
||||
@return: NetworkingResponse object with api response and failure list
|
||||
@rtype: common.behaviors.NetworkingResponse
|
||||
"""
|
||||
poll_interval = poll_interval or self.config.api_poll_interval
|
||||
resource_get_attempts = (resource_get_attempts or
|
||||
self.config.api_retries)
|
||||
|
||||
result = NetworkingResponse()
|
||||
err_msg = 'Limits GET failure'
|
||||
for attempt in range(resource_get_attempts):
|
||||
self._log.debug('Attempt {0} of {1} with limits GET'.
|
||||
format(attempt + 1, resource_get_attempts))
|
||||
|
||||
resp = self.client.get_limits(page_reverse=page_reverse)
|
||||
resp_check = self.check_response(resp=resp,
|
||||
status_code=LimitsResponseCodes.GET_LIMITS,
|
||||
label='', message=err_msg)
|
||||
result.response = resp
|
||||
|
||||
# resp_check will have the response failure or None if no failure
|
||||
if resp_check is None:
|
||||
return result
|
||||
|
||||
# Failures will be an empty list if the list was successful the
|
||||
# first time
|
||||
result.failures.append(resp_check)
|
||||
time.sleep(poll_interval)
|
||||
|
||||
else:
|
||||
err_msg = (
|
||||
'Unable to GET limits after {0} attempts: '
|
||||
'{1}').format(resource_get_attempts, result.failures)
|
||||
self._log.error(err_msg)
|
||||
if raise_exception:
|
||||
raise ResourceGetException(err_msg)
|
||||
return result
|
||||
@@ -0,0 +1,67 @@
|
||||
"""
|
||||
Copyright 2015 Rackspace
|
||||
|
||||
Licensed 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.
|
||||
"""
|
||||
|
||||
from cafe.engine.http.client import AutoMarshallingHTTPClient
|
||||
from cloudcafe.networking.networks.extensions.limits_api.models.response \
|
||||
import Limits
|
||||
|
||||
|
||||
class LimitsClient(AutoMarshallingHTTPClient):
|
||||
|
||||
def __init__(self, url, auth_token, serialize_format=None,
|
||||
deserialize_format=None, tenant_id=None):
|
||||
"""
|
||||
@param url: Base URL for the networks service
|
||||
@type url: string
|
||||
@param auth_token: Auth token to be used for all requests
|
||||
@type auth_token: string
|
||||
@param serialize_format: Format for serializing requests
|
||||
@type serialize_format: string
|
||||
@param deserialize_format: Format for de-serializing responses
|
||||
@type deserialize_format: string
|
||||
@param tenant_id: optional tenant id to be included in the
|
||||
header if given
|
||||
@type tenant_id: string
|
||||
"""
|
||||
super(LimitsClient, self).__init__(serialize_format,
|
||||
deserialize_format)
|
||||
self.auth_token = auth_token
|
||||
self.default_headers['X-Auth-Token'] = auth_token
|
||||
ct = '{content_type}/{content_subtype}'.format(
|
||||
content_type='application',
|
||||
content_subtype=self.serialize_format)
|
||||
accept = '{content_type}/{content_subtype}'.format(
|
||||
content_type='application',
|
||||
content_subtype=self.deserialize_format)
|
||||
self.default_headers['Content-Type'] = ct
|
||||
self.default_headers['Accept'] = accept
|
||||
if tenant_id:
|
||||
self.default_headers['X-Auth-Project-Id'] = tenant_id
|
||||
self.url = url
|
||||
self.limits_url = '{url}/limits'.format(url=self.url)
|
||||
|
||||
def get_limits(self, page_reverse=None, requestslib_kwargs=None):
|
||||
"""
|
||||
@summary: Shows rate limit user information
|
||||
@return: get limits response
|
||||
@rtype: Requests.response
|
||||
"""
|
||||
params = {'page_reverse': page_reverse}
|
||||
url = self.limits_url
|
||||
resp = self.request('GET', url, params=params,
|
||||
response_entity_type=Limits,
|
||||
requestslib_kwargs=requestslib_kwargs)
|
||||
return resp
|
||||
@@ -0,0 +1,37 @@
|
||||
"""
|
||||
Copyright 2015 Rackspace
|
||||
|
||||
Licensed 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.
|
||||
"""
|
||||
|
||||
from cloudcafe.networking.networks.composites import _NetworkingAuthComposite
|
||||
from cloudcafe.networking.networks.extensions.limits_api.behaviors \
|
||||
import LimitsBehaviors
|
||||
from cloudcafe.networking.networks.extensions.limits_api.client \
|
||||
import LimitsClient
|
||||
from cloudcafe.networking.networks.extensions.limits_api.config \
|
||||
import LimitsConfig
|
||||
|
||||
|
||||
class LimitsComposite(object):
|
||||
networking_auth_composite = _NetworkingAuthComposite
|
||||
|
||||
def __init__(self):
|
||||
auth_composite = self.networking_auth_composite()
|
||||
self.url = auth_composite.networking_url
|
||||
self.user = auth_composite._auth_user_config
|
||||
self.config = LimitsConfig()
|
||||
self.client = LimitsClient(**auth_composite.client_args)
|
||||
|
||||
self.behaviors = LimitsBehaviors(
|
||||
limits_client=self.client, limits_config=self.config)
|
||||
@@ -0,0 +1,25 @@
|
||||
"""
|
||||
Copyright 2015 Rackspace
|
||||
|
||||
Licensed 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.
|
||||
"""
|
||||
|
||||
from cloudcafe.networking.networks.common.config import NetworkingBaseConfig
|
||||
|
||||
|
||||
class LimitsConfig(NetworkingBaseConfig):
|
||||
"""Network limits config params"""
|
||||
|
||||
# Adding networks_ in case the limits section for the config files is
|
||||
# already in use by another product
|
||||
SECTION_NAME = 'networks_limits'
|
||||
@@ -0,0 +1,23 @@
|
||||
"""
|
||||
Copyright 2015 Rackspace
|
||||
|
||||
Licensed 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.
|
||||
"""
|
||||
from cloudcafe.networking.networks.common.constants \
|
||||
import NeutronResponseCodes
|
||||
|
||||
|
||||
class LimitsResponseCodes(NeutronResponseCodes):
|
||||
"""HTTP Limits API Response codes"""
|
||||
|
||||
GET_LIMITS = 200
|
||||
@@ -0,0 +1,15 @@
|
||||
"""
|
||||
Copyright 2015 Rackspace
|
||||
|
||||
Licensed 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.
|
||||
"""
|
||||
@@ -0,0 +1,103 @@
|
||||
"""
|
||||
Copyright 2015 Rackspace
|
||||
|
||||
Licensed 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.
|
||||
"""
|
||||
|
||||
import json
|
||||
|
||||
from cafe.engine.models.base import AutoMarshallingListModel, \
|
||||
AutoMarshallingModel
|
||||
|
||||
|
||||
class Limits(AutoMarshallingModel):
|
||||
"""
|
||||
@summary: Limits model response object
|
||||
@param rate: User HTTP rate limits for Neutron API calls
|
||||
For ex. Neutron API PUT calls per minute
|
||||
@type rate: list
|
||||
"""
|
||||
|
||||
LIMITS = 'limits'
|
||||
|
||||
def __init__(self, rate=None, **kwargs):
|
||||
super(Limits, self).__init__()
|
||||
self.rate = rate
|
||||
self.kwargs = kwargs
|
||||
|
||||
@classmethod
|
||||
def _json_to_obj(cls, serialized_str):
|
||||
"""Return limits object from a JSON serialized string"""
|
||||
|
||||
limits = None
|
||||
json_dict = json.loads(serialized_str)
|
||||
|
||||
json_dict = cls._replace_dict_key(
|
||||
json_dict, 'next-available', 'next_available', recursion=True)
|
||||
|
||||
if cls.LIMITS in json_dict:
|
||||
limits_dict = json_dict.get(cls.LIMITS)
|
||||
limits = Limits(**limits_dict)
|
||||
rate = []
|
||||
for rate_limit in limits.rate:
|
||||
rate_obj = Rate(**rate_limit)
|
||||
limit = [Limit(**limit_type) for limit_type in rate_obj.limit]
|
||||
rate_obj.limit = limit
|
||||
rate.append(rate_obj)
|
||||
limits.rate = rate
|
||||
|
||||
return limits
|
||||
|
||||
|
||||
class Rate(AutoMarshallingModel):
|
||||
"""
|
||||
@summary: Rate model response object
|
||||
@param limit: rate limit
|
||||
@type limit: list
|
||||
@param uri: rate limit uniform resource identifier
|
||||
@type uri: str
|
||||
@param regex: regular expression for the API URL the rate limit applies
|
||||
@type regex: str
|
||||
"""
|
||||
|
||||
def __init__(self, limit=None, uri=None, regex=None, **kwargs):
|
||||
super(Rate, self).__init__()
|
||||
self.limit = limit
|
||||
self.uri = uri
|
||||
self.regex = regex
|
||||
self.kwargs = kwargs
|
||||
|
||||
|
||||
class Limit(AutoMarshallingModel):
|
||||
"""
|
||||
@summary: Limit model response object
|
||||
@param unit: rate limit time units
|
||||
@type unit: str
|
||||
@param next_available: datetime when the available rate limits will reset
|
||||
@type next_available: str
|
||||
@param value: rate limit value
|
||||
@type value: int
|
||||
@param remaining: remaining rate limits
|
||||
@type remaining: int
|
||||
@param verb: HTTP request method
|
||||
@type verb: str
|
||||
"""
|
||||
def __init__(self, unit=None, next_available=None, value=None,
|
||||
remaining=None, verb=None, **kwargs):
|
||||
super(Limit, self).__init__()
|
||||
self.unit = unit
|
||||
self.next_available = next_available
|
||||
self.value = value
|
||||
self.remaining = remaining
|
||||
self.verb = verb
|
||||
self.kwargs = kwargs
|
||||
@@ -0,0 +1,15 @@
|
||||
"""
|
||||
Copyright 2015 Rackspace
|
||||
|
||||
Licensed 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.
|
||||
"""
|
||||
@@ -0,0 +1,15 @@
|
||||
"""
|
||||
Copyright 2015 Rackspace
|
||||
|
||||
Licensed 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.
|
||||
"""
|
||||
@@ -0,0 +1,108 @@
|
||||
"""
|
||||
Copyright 2015 Rackspace
|
||||
|
||||
Licensed 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.
|
||||
"""
|
||||
|
||||
|
||||
import unittest
|
||||
|
||||
from cloudcafe.networking.networks.extensions.limits_api.models.response \
|
||||
import Limits, Rate, Limit
|
||||
|
||||
|
||||
LIMITS_DATA = (
|
||||
"""{"limits" : {
|
||||
"rate" : [
|
||||
{
|
||||
"limit" : [
|
||||
{
|
||||
"unit" : "MINUTE",
|
||||
"next-available" : "2015-05-12T21:43:00.182Z",
|
||||
"value" : 1000,
|
||||
"remaining" : 1000,
|
||||
"verb" : "PUT"
|
||||
}
|
||||
],
|
||||
"uri" : "LoadTestingPUTs",
|
||||
"regex" : ".*"
|
||||
},
|
||||
{
|
||||
"limit" : [
|
||||
{
|
||||
"unit" : "HOUR",
|
||||
"next-available" : "2015-05-12T21:43:00.181Z",
|
||||
"value" : 1000,
|
||||
"remaining" : 1000,
|
||||
"verb" : "DELETE"
|
||||
},
|
||||
{
|
||||
"unit" : "HOUR",
|
||||
"next-available" : "2015-05-12T21:43:00.181Z",
|
||||
"value" : 1000,
|
||||
"remaining" : 1000,
|
||||
"verb" : "POST"
|
||||
},
|
||||
{
|
||||
"unit" : "HOUR",
|
||||
"next-available" : "2015-05-12T21:43:00.181Z",
|
||||
"value" : 1000,
|
||||
"remaining" : 1000,
|
||||
"verb" : "PUT"
|
||||
}
|
||||
],
|
||||
"uri" : "LoadTestingPorts",
|
||||
"regex" : "regex_data"
|
||||
}
|
||||
]}}
|
||||
""")
|
||||
|
||||
|
||||
class GetLimitsTest(unittest.TestCase):
|
||||
"""Test for the limits (GET) model object response"""
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
"""Creating the expected models"""
|
||||
|
||||
limit_obj_a1 = Limit(unit='MINUTE',
|
||||
next_available='2015-05-12T21:43:00.182Z',
|
||||
value=1000, remaining=1000, verb='PUT')
|
||||
rate_limits_a = [limit_obj_a1]
|
||||
rate_obj_a = Rate(limit=rate_limits_a,
|
||||
uri='LoadTestingPUTs', regex='.*')
|
||||
|
||||
limit_obj_b1 = Limit(unit='HOUR',
|
||||
next_available='2015-05-12T21:43:00.181Z',
|
||||
value=1000, remaining=1000, verb='DELETE')
|
||||
limit_obj_b2 = Limit(unit='HOUR',
|
||||
next_available='2015-05-12T21:43:00.181Z', value=1000,
|
||||
remaining=1000, verb='POST')
|
||||
limit_obj_b3 = Limit(unit='HOUR',
|
||||
next_available='2015-05-12T21:43:00.181Z',
|
||||
value=1000, remaining=1000, verb='PUT')
|
||||
rate_limits_b = [limit_obj_b1, limit_obj_b2, limit_obj_b3]
|
||||
rate_obj_b = Rate(limit=rate_limits_b,
|
||||
uri='LoadTestingPorts', regex='regex_data')
|
||||
rate = [rate_obj_a, rate_obj_b]
|
||||
cls.expected_response = Limits(rate=rate)
|
||||
|
||||
def test_json_response(self):
|
||||
api_json_resp = LIMITS_DATA
|
||||
response = Limits()._json_to_obj(api_json_resp)
|
||||
msg = ('Unexpected JSON response, expected:\n{0}\n\n'
|
||||
'instead of:\n{1}\n').format(self.expected_response,
|
||||
response)
|
||||
self.assertEqual(response, self.expected_response, msg)
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user