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:
Leonardo Maycotte
2015-05-13 16:23:02 -05:00
parent 74a338ce01
commit e50c55a10a
11 changed files with 506 additions and 0 deletions

View File

@@ -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.
"""

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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'

View File

@@ -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

View File

@@ -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.
"""

View File

@@ -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

View File

@@ -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.
"""

View File

@@ -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.
"""

View File

@@ -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()