diff --git a/novaclient/base.py b/novaclient/base.py index 088aa5ed3..9b35ffed5 100644 --- a/novaclient/base.py +++ b/novaclient/base.py @@ -268,7 +268,11 @@ class Resource(object): def _add_details(self, info): for (k, v) in info.iteritems(): - setattr(self, k, v) + try: + setattr(self, k, v) + except AttributeError: + # In this case we already defined the attribute on the class + pass def __getattr__(self, k): if k not in self.__dict__: diff --git a/novaclient/v1_1/client.py b/novaclient/v1_1/client.py index 2e00345c5..cf82b1cfc 100644 --- a/novaclient/v1_1/client.py +++ b/novaclient/v1_1/client.py @@ -3,10 +3,11 @@ from novaclient.v1_1 import flavors from novaclient.v1_1 import floating_ips from novaclient.v1_1 import images from novaclient.v1_1 import keypairs +from novaclient.v1_1 import limits +from novaclient.v1_1 import quotas from novaclient.v1_1 import security_group_rules from novaclient.v1_1 import security_groups from novaclient.v1_1 import servers -from novaclient.v1_1 import quotas from novaclient.v1_1 import volumes from novaclient.v1_1 import volume_snapshots from novaclient.v1_1 import zones @@ -37,11 +38,12 @@ class Client(object): # know it's not being used as keyword argument password = api_key self.flavors = flavors.FlavorManager(self) - self.floating_ips = floating_ips.FloatingIPManager(self) self.images = images.ImageManager(self) + self.limits = limits.LimitsManager(self) self.servers = servers.ServerManager(self) # extensions + self.floating_ips = floating_ips.FloatingIPManager(self) self.volumes = volumes.VolumeManager(self) self.volume_snapshots = volume_snapshots.SnapshotManager(self) self.keypairs = keypairs.KeypairManager(self) diff --git a/novaclient/v1_1/limits.py b/novaclient/v1_1/limits.py new file mode 100644 index 000000000..75bf54161 --- /dev/null +++ b/novaclient/v1_1/limits.py @@ -0,0 +1,79 @@ +# Copyright 2011 OpenStack LLC. + +from novaclient import base + + +class Limits(base.Resource): + """A collection of RateLimit and AbsoluteLimit objects""" + + def __repr__(self): + return "" + + @property + def absolute(self): + for (name, value) in self._info['absolute'].items(): + yield AbsoluteLimit(name, value) + + @property + def rate(self): + for group in self._info['rate']: + uri = group['uri'] + regex = group['regex'] + for rate in group['limit']: + yield RateLimit(rate['verb'], uri, regex, rate['value'], + rate['remaining'], rate['unit'], + rate['next-available']) + + +class RateLimit(object): + """Data model that represents a flattened view of a single rate limit""" + + def __init__(self, verb, uri, regex, value, remain, + unit, next_available): + self.verb = verb + self.uri = uri + self.regex = regex + self.value = value + self.remain = remain + self.unit = unit + self.next_available = next_available + + def __eq__(self, other): + return self.uri == other.uri \ + and self.regex == other.regex \ + and self.value == other.value \ + and self.verb == other.verb \ + and self.remain == other.remain \ + and self.unit == other.unit \ + and self.next_available == other.next_available + + def __repr__(self): + return "" % (self.method, self.uri) + + +class AbsoluteLimit(object): + """Data model that represents a single absolute limit""" + + def __init__(self, name, value): + self.name = name + self.value = value + + def __eq__(self, other): + return self.value == other.value and self.name == other.name + + def __repr__(self): + return "" % (self.name) + + +class LimitsManager(base.Manager): + """Manager object used to interact with limits resource""" + + resource_class = Limits + + def get(self): + """ + Get a specific extension. + + :rtype: :class:`Limits` + """ + return self._get("/limits", "limits") diff --git a/novaclient/v1_1/shell.py b/novaclient/v1_1/shell.py index 97e576c67..45398b3c0 100644 --- a/novaclient/v1_1/shell.py +++ b/novaclient/v1_1/shell.py @@ -1207,3 +1207,17 @@ def do_keypair_list(cs, args): keypairs = cs.keypairs.list() columns = ['Name', 'Fingerprint'] utils.print_list(keypairs, columns) + + +def do_absolute_limits(cs, args): + """Print a list of absolute limits for a user""" + limits = cs.limits.get().absolute + columns = ['Name', 'Value'] + utils.print_list(limits, columns) + + +def do_rate_limits(cs, args): + """Print a list of rate limits for a user""" + limits = cs.limits.get().rate + columns = ['Verb', 'URI', 'Value', 'Remain', 'Unit', 'Next_Available'] + utils.print_list(limits, columns) diff --git a/setup.py b/setup.py index d69ad981d..4741af5fb 100644 --- a/setup.py +++ b/setup.py @@ -28,7 +28,7 @@ def read_file(file_name): setuptools.setup( name="python-novaclient", - version="2.6.8", + version="2.6.9", author="Rackspace, based on work by Jacob Kaplan-Moss", author_email="github@racklabs.com", description="Client library for OpenStack Nova API.", diff --git a/tests/v1_1/fakes.py b/tests/v1_1/fakes.py index ce6379089..ccfb64358 100644 --- a/tests/v1_1/fakes.py +++ b/tests/v1_1/fakes.py @@ -55,56 +55,55 @@ class FakeHTTPClient(base_client.HTTPClient): return (200, {"limits": { "rate": [ { - "verb": "POST", - "URI": "*", + "uri": "*", "regex": ".*", - "value": 10, - "remaining": 2, - "unit": "MINUTE", - "resetTime": 1244425439 + "limit": [ + { + "value": 10, + "verb": "POST", + "remaining": 2, + "unit": "MINUTE", + "next-available": "2011-12-15T22:42:45Z" + }, + { + "value": 10, + "verb": "PUT", + "remaining": 2, + "unit": "MINUTE", + "next-available": "2011-12-15T22:42:45Z" + }, + { + "value": 100, + "verb": "DELETE", + "remaining": 100, + "unit": "MINUTE", + "next-available": "2011-12-15T22:42:45Z" + } + ] }, { - "verb": "POST", - "URI": "*/servers", + "uri": "*/servers", "regex": "^/servers", - "value": 50, - "remaining": 49, - "unit": "DAY", "resetTime": 1244511839 - }, - { - "verb": "PUT", - "URI": "*", - "regex": ".*", - "value": 10, - "remaining": 2, - "unit": "MINUTE", - "resetTime": 1244425439 - }, - { - "verb": "GET", - "URI": "*changes-since*", - "regex": "changes-since", - "value": 3, - "remaining": 3, - "unit": "MINUTE", - "resetTime": 1244425439 - }, - { - "verb": "DELETE", - "URI": "*", - "regex": ".*", - "value": 100, - "remaining": 100, - "unit": "MINUTE", - "resetTime": 1244425439 + "limit": [ + { + "verb": "POST", + "value": 25, + "remaining": 24, + "unit": "DAY", + "next-available": "2011-12-15T22:42:45Z" + } + ] } ], "absolute": { "maxTotalRAMSize": 51200, - "maxIPGroups": 50, - "maxIPGroupMembers": 25 - } - }}) + "maxServerMeta": 5, + "maxImageMeta": 5, + "maxPersonality": 5, + "maxPersonalitySize": 10240 + }, + }, + }) # # Servers diff --git a/tests/v1_1/test_limits.py b/tests/v1_1/test_limits.py new file mode 100644 index 000000000..91e6f23e2 --- /dev/null +++ b/tests/v1_1/test_limits.py @@ -0,0 +1,52 @@ + +from novaclient.v1_1 import limits +from tests.v1_1 import fakes +from tests import utils + + +cs = fakes.FakeClient() + + +class LimitsTest(utils.TestCase): + + def test_get_limits(self): + obj = cs.limits.get() + cs.assert_called('GET', '/limits') + self.assertTrue(isinstance(obj, limits.Limits)) + + def test_absolute_limits(self): + obj = cs.limits.get() + + expected = ( + limits.AbsoluteLimit("maxTotalRAMSize", 51200), + limits.AbsoluteLimit("maxServerMeta", 5), + limits.AbsoluteLimit("maxImageMeta", 5), + limits.AbsoluteLimit("maxPersonality", 5), + limits.AbsoluteLimit("maxPersonalitySize", 10240), + ) + + abs_limits = list(obj.absolute) + self.assertEqual(len(abs_limits), len(expected)) + + for limit in abs_limits: + self.assertTrue(limit in expected) + + def test_rate_limits(self): + obj = cs.limits.get() + + expected = ( + limits.RateLimit('POST', '*', '.*', 10, 2, 'MINUTE', + '2011-12-15T22:42:45Z' ), + limits.RateLimit('PUT', '*', '.*', 10, 2, 'MINUTE', + '2011-12-15T22:42:45Z' ), + limits.RateLimit('DELETE', '*', '.*', 100, 100, 'MINUTE', + '2011-12-15T22:42:45Z' ), + limits.RateLimit('POST', '*/servers', '^/servers', 25, 24, 'DAY', + '2011-12-15T22:42:45Z' ), + ) + + rate_limits = list(obj.rate) + self.assertEqual(len(rate_limits), len(expected)) + + for limit in rate_limits: + self.assertTrue(limit in expected)