Add limits to V3 and improve limits formatting in shell

Adds support for the limits API to the v3 client.

Also improve the formatting of absolute-limits from

+-------------------------+-------+
| Name                    | Value |
+-------------------------+-------+
| maxServerMeta           | 128   |
| maxPersonality          | 5     |
| maxImageMeta            | 128   |
| maxPersonalitySize      | 10240 |
| maxTotalRAMSize         | 51200 |
| maxSecurityGroupRules   | 20    |
| maxTotalKeypairs        | 100   |
| totalRAMUsed            | 512   |
| maxSecurityGroups       | 10    |
| totalFloatingIpsUsed    | 0     |
| totalInstancesUsed      | 1     |
| totalSecurityGroupsUsed | 1     |
| maxTotalFloatingIps     | 10    |
| maxTotalInstances       | 2     |
| totalCoresUsed          | 1     |
| maxTotalCores           | 20    |
+-------------------------+-------+

to

+--------------------+------+-------+
| Name               | Used | Max   |
+--------------------+------+-------+
| Cores              | 1    | 20    |
| FloatingIps        | 0    | 10    |
| ImageMeta          | -    | 128   |
| Instances          | 1    | 2     |
| Keypairs           | -    | 100   |
| Personality        | -    | 5     |
| PersonalitySize    | -    | 10240 |
| RAM                | 512  | 51200 |
| SecurityGroupRules | -    | 20    |
| SecurityGroups     | 1    | 10    |
| ServerMeta         | -    | 128   |
+--------------------+------+-------+

Change-Id: I93a456b402aeba8e39480567edb090cbb1898d16
This commit is contained in:
Phil Day 2014-09-10 17:45:47 +00:00
parent 4dbf1323cc
commit ae8eadc451
5 changed files with 281 additions and 2 deletions

View File

@ -0,0 +1,88 @@
#
# 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 novaclient.tests.fixture_data import client
from novaclient.tests.fixture_data import limits as data
from novaclient.tests import utils
from novaclient.v3 import limits
class LimitsTest(utils.FixturedTestCase):
client_fixture_class = client.V3
data_fixture_class = data.Fixture
def test_get_limits(self):
obj = self.cs.limits.get()
self.assert_called('GET', '/limits')
self.assertIsInstance(obj, limits.Limits)
def test_get_limits_for_a_tenant(self):
obj = self.cs.limits.get(tenant_id=1234)
self.assert_called('GET', '/limits?tenant_id=1234')
self.assertIsInstance(obj, limits.Limits)
def test_absolute_limits(self):
obj = self.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_absolute_limits_reserved(self):
obj = self.cs.limits.get(reserved=True)
expected = (
limits.AbsoluteLimit("maxTotalRAMSize", 51200),
limits.AbsoluteLimit("maxServerMeta", 5),
limits.AbsoluteLimit("maxImageMeta", 5),
limits.AbsoluteLimit("maxPersonality", 5),
limits.AbsoluteLimit("maxPersonalitySize", 10240),
)
self.assert_called('GET', '/limits?reserved=1')
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 = self.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)

View File

@ -2616,8 +2616,65 @@ def _find_keypair(cs, keypair):
def do_absolute_limits(cs, args):
"""Print a list of absolute limits for a user"""
limits = cs.limits.get(args.reserved, args.tenant).absolute
columns = ['Name', 'Value']
utils.print_list(limits, columns)
class Limit(object):
def __init__(self, name, used, max, other):
self.name = name
self.used = used
self.max = max
self.other = other
limit_map = {
'maxServerMeta': {'name': 'Server Meta', 'type': 'max'},
'maxPersonality': {'name': 'Personality', 'type': 'max'},
'maxPersonalitySize': {'name': 'Personality Size', 'type': 'max'},
'maxImageMeta': {'name': 'ImageMeta', 'type': 'max'},
'maxTotalKeypairs': {'name': 'Keypairs', 'type': 'max'},
'totalCoresUsed': {'name': 'Cores', 'type': 'used'},
'maxTotalCores': {'name': 'Cores', 'type': 'max'},
'totalRAMUsed': {'name': 'RAM', 'type': 'used'},
'maxTotalRAMSize': {'name': 'RAM', 'type': 'max'},
'totalInstancesUsed': {'name': 'Instances', 'type': 'used'},
'maxTotalInstances': {'name': 'Instances', 'type': 'max'},
'totalFloatingIpsUsed': {'name': 'FloatingIps', 'type': 'used'},
'maxTotalFloatingIps': {'name': 'FloatingIps', 'type': 'max'},
'totalSecurityGroupsUsed': {'name': 'SecurityGroups', 'type': 'used'},
'maxSecurityGroups': {'name': 'SecurityGroups', 'type': 'max'},
'maxSecurityGroupRules': {'name': 'SecurityGroupRules', 'type': 'max'},
'maxServerGroups': {'name': 'ServerGroups', 'type': 'max'},
'totalServerGroupsUsed': {'name': 'ServerGroups', 'type': 'used'},
'maxServerGroupMembers': {'name': 'ServerGroupMembers', 'type': 'max'},
}
max = {}
used = {}
other = {}
limit_names = []
columns = ['Name', 'Used', 'Max']
for l in limits:
map = limit_map.get(l.name, {'name': l.name, 'type': 'other'})
name = map['name']
if map['type'] == 'max':
max[name] = l.value
elif map['type'] == 'used':
used[name] = l.value
else:
other[name] = l.value
columns.append('Other')
if name not in limit_names:
limit_names.append(name)
limit_names.sort()
limit_list = []
for name in limit_names:
l = Limit(name,
used.get(name, "-"),
max.get(name, "-"),
other.get(name, "-"))
limit_list.append(l)
utils.print_list(limit_list, columns)
def do_rate_limits(cs, args):

View File

@ -24,6 +24,7 @@ from novaclient.v3 import hosts
from novaclient.v3 import hypervisors
from novaclient.v3 import images
from novaclient.v3 import keypairs
from novaclient.v3 import limits
from novaclient.v3 import list_extensions
from novaclient.v3 import quotas
from novaclient.v3 import servers
@ -110,6 +111,7 @@ class Client(object):
self.hypervisors = hypervisors.HypervisorManager(self)
self.images = images.ImageManager(self)
self.keypairs = keypairs.KeypairManager(self)
self.limits = limits.LimitsManager(self)
self.quotas = quotas.QuotaSetManager(self)
self.servers = servers.ServerManager(self)
self.services = services.ServiceManager(self)

50
novaclient/v3/limits.py Normal file
View File

@ -0,0 +1,50 @@
# Copyright 2011 OpenStack Foundation
#
# 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 six.moves.urllib import parse
from novaclient.v1_1 import limits
class Limits(limits.Limits):
pass
class RateLimit(limits.RateLimit):
pass
class AbsoluteLimit(limits.AbsoluteLimit):
pass
class LimitsManager(limits.LimitsManager):
"""Manager object used to interact with limits resource."""
resource_class = Limits
def get(self, reserved=False, tenant_id=None):
"""
Get a specific extension.
:rtype: :class:`Limits`
"""
opts = {}
if reserved:
opts['reserved'] = 1
if tenant_id:
opts['tenant_id'] = tenant_id
query_string = "?%s" % parse.urlencode(opts) if opts else ""
return self._get("/limits%s" % query_string, "limits")

View File

@ -3066,3 +3066,85 @@ def do_availability_zone_list(cs, _args):
_translate_availability_zone_keys(result)
utils.print_list(result, ['Name', 'Status'],
sortby_index=None)
@utils.arg('--tenant',
# nova db searches by project_id
dest='tenant',
metavar='<tenant>',
nargs='?',
help=_('Display information from single tenant (Admin only).'))
@utils.arg('--reserved',
dest='reserved',
action='store_true',
default=False,
help=_('Include reservations count.'))
def do_absolute_limits(cs, args):
"""Print a list of absolute limits for a user"""
limits = cs.limits.get(args.reserved, args.tenant).absolute
class Limit(object):
def __init__(self, name, used, max, other):
self.name = name
self.used = used
self.max = max
self.other = other
limit_map = {
'maxServerMeta': {'name': 'Server Meta', 'type': 'max'},
'maxPersonality': {'name': 'Personality', 'type': 'max'},
'maxPersonalitySize': {'name': 'Personality Size', 'type': 'max'},
'maxImageMeta': {'name': 'ImageMeta', 'type': 'max'},
'maxTotalKeypairs': {'name': 'Keypairs', 'type': 'max'},
'totalCoresUsed': {'name': 'Cores', 'type': 'used'},
'maxTotalCores': {'name': 'Cores', 'type': 'max'},
'totalRAMUsed': {'name': 'RAM', 'type': 'used'},
'maxTotalRAMSize': {'name': 'RAM', 'type': 'max'},
'totalInstancesUsed': {'name': 'Instances', 'type': 'used'},
'maxTotalInstances': {'name': 'Instances', 'type': 'max'},
'totalFloatingIpsUsed': {'name': 'FloatingIps', 'type': 'used'},
'maxTotalFloatingIps': {'name': 'FloatingIps', 'type': 'max'},
'totalSecurityGroupsUsed': {'name': 'SecurityGroups', 'type': 'used'},
'maxSecurityGroups': {'name': 'SecurityGroups', 'type': 'max'},
'maxSecurityGroupRules': {'name': 'SecurityGroupRules', 'type': 'max'},
'maxServerGroups': {'name': 'ServerGroups', 'type': 'max'},
'totalServerGroupsUsed': {'name': 'ServerGroups', 'type': 'used'},
'maxServerGroupMembers': {'name': 'ServerGroupMembers', 'type': 'max'},
}
max = {}
used = {}
other = {}
limit_names = []
columns = ['Name', 'Used', 'Max']
for l in limits:
map = limit_map.get(l.name, {'name': l.name, 'type': 'other'})
name = map['name']
if map['type'] == 'max':
max[name] = l.value
elif map['type'] == 'used':
used[name] = l.value
else:
other[name] = l.value
columns.append('Other')
if name not in limit_names:
limit_names.append(name)
limit_names.sort()
limit_list = []
for name in limit_names:
l = Limit(name,
used.get(name, "-"),
max.get(name, "-"),
other.get(name, "-"))
limit_list.append(l)
utils.print_list(limit_list, 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)