From 24b06ef273e193819624fd2e021282d2735977b0 Mon Sep 17 00:00:00 2001 From: Jude Cross Date: Mon, 2 Apr 2018 15:07:25 -0700 Subject: [PATCH] Fix limits show command without Nova and Cinder This patch implements an endpoint lookup when showing limits. This addresses the issue when showing limits without both Nova and Cinder and will display limits if one is missing. Change-Id: I2214b281e0206f8fe117aae52de2bf4c4e2c6525 Closes-bug: #1707960 --- openstackclient/common/clientmanager.py | 19 +++ openstackclient/common/limits.py | 32 +++-- .../tests/unit/common/test_limits.py | 125 ++++++++++++++++++ .../tests/unit/compute/v2/fakes.py | 110 +++++++++++++++ openstackclient/tests/unit/fakes.py | 8 ++ openstackclient/tests/unit/volume/v2/fakes.py | 100 ++++++++++++++ 6 files changed, 383 insertions(+), 11 deletions(-) create mode 100644 openstackclient/tests/unit/common/test_limits.py diff --git a/openstackclient/common/clientmanager.py b/openstackclient/common/clientmanager.py index 897810521d..aa1045e469 100644 --- a/openstackclient/common/clientmanager.py +++ b/openstackclient/common/clientmanager.py @@ -125,6 +125,25 @@ class ClientManager(clientmanager.ClientManager): # use Network API by default return self.is_service_available('network') is not False + def is_compute_endpoint_enabled(self): + """Check if Compute endpoint is enabled""" + + return self.is_service_available('compute') is not False + + def is_volume_endpoint_enabled(self, volume_client): + """Check if volume endpoint is enabled""" + # NOTE(jcross): Cinder did some interesting things with their service + # name so we need to figure out which version to look + # for when calling is_service_available() + volume_version = volume_client.api_version.ver_major + if self.is_service_available( + "volumev%s" % volume_version) is not False: + return True + elif self.is_service_available('volume') is not False: + return True + else: + return False + # Plugin Support diff --git a/openstackclient/common/limits.py b/openstackclient/common/limits.py index 957f1d0212..19db35d7df 100644 --- a/openstackclient/common/limits.py +++ b/openstackclient/common/limits.py @@ -83,24 +83,34 @@ class ShowLimits(command.Lister): project_id = utils.find_resource(identity_client.projects, parsed_args.project).id - compute_limits = compute_client.limits.get(parsed_args.is_reserved, - tenant_id=project_id) - volume_limits = volume_client.limits.get() + compute_limits = None + volume_limits = None + if self.app.client_manager.is_compute_endpoint_enabled(): + compute_limits = compute_client.limits.get(parsed_args.is_reserved, + tenant_id=project_id) + + if self.app.client_manager.is_volume_endpoint_enabled(volume_client): + volume_limits = volume_client.limits.get() + + data = [] if parsed_args.is_absolute: - compute_limits = compute_limits.absolute - volume_limits = volume_limits.absolute + if compute_limits: + data.append(compute_limits.absolute) + if volume_limits: + data.append(volume_limits.absolute) columns = ["Name", "Value"] return (columns, (utils.get_item_properties(s, columns) - for s in itertools.chain(compute_limits, volume_limits))) + for s in itertools.chain(*data))) elif parsed_args.is_rate: - compute_limits = compute_limits.rate - volume_limits = volume_limits.rate + if compute_limits: + data.append(compute_limits.rate) + if volume_limits: + data.append(volume_limits.rate) columns = ["Verb", "URI", "Value", "Remain", "Unit", "Next Available"] return (columns, (utils.get_item_properties(s, columns) - for s in itertools.chain(compute_limits, volume_limits))) - + for s in itertools.chain(*data))) else: - return ({}, {}) + return {}, {} diff --git a/openstackclient/tests/unit/common/test_limits.py b/openstackclient/tests/unit/common/test_limits.py new file mode 100644 index 0000000000..d73db2cb17 --- /dev/null +++ b/openstackclient/tests/unit/common/test_limits.py @@ -0,0 +1,125 @@ +# 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 openstackclient.common import limits +from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes +from openstackclient.tests.unit.volume.v2 import fakes as volume_fakes + + +class TestComputeLimits(compute_fakes.TestComputev2): + + absolute_columns = [ + 'Name', + 'Value', + ] + + rate_columns = [ + "Verb", + "URI", + "Value", + "Remain", + "Unit", + "Next Available" + ] + + def setUp(self): + super(TestComputeLimits, self).setUp() + self.app.client_manager.volume_endpoint_enabled = False + self.compute = self.app.client_manager.compute + + self.fake_limits = compute_fakes.FakeLimits() + self.compute.limits.get.return_value = self.fake_limits + + def test_compute_show_absolute(self): + arglist = ['--absolute'] + verifylist = [('is_absolute', True)] + cmd = limits.ShowLimits(self.app, None) + parsed_args = self.check_parser(cmd, arglist, verifylist) + + columns, data = cmd.take_action(parsed_args) + + ret_limits = list(data) + compute_reference_limits = self.fake_limits.absolute_limits() + + self.assertEqual(self.absolute_columns, columns) + self.assertEqual(compute_reference_limits, ret_limits) + self.assertEqual(19, len(ret_limits)) + + def test_compute_show_rate(self): + arglist = ['--rate'] + verifylist = [('is_rate', True)] + cmd = limits.ShowLimits(self.app, None) + parsed_args = self.check_parser(cmd, arglist, verifylist) + + columns, data = cmd.take_action(parsed_args) + + ret_limits = list(data) + compute_reference_limits = self.fake_limits.rate_limits() + + self.assertEqual(self.rate_columns, columns) + self.assertEqual(compute_reference_limits, ret_limits) + self.assertEqual(3, len(ret_limits)) + + +class TestVolumeLimits(volume_fakes.TestVolume): + absolute_columns = [ + 'Name', + 'Value', + ] + + rate_columns = [ + "Verb", + "URI", + "Value", + "Remain", + "Unit", + "Next Available" + ] + + def setUp(self): + super(TestVolumeLimits, self).setUp() + self.app.client_manager.compute_endpoint_enabled = False + self.volume = self.app.client_manager.volume + + self.fake_limits = volume_fakes.FakeLimits() + self.volume.limits.get.return_value = self.fake_limits + + def test_volume_show_absolute(self): + arglist = ['--absolute'] + verifylist = [('is_absolute', True)] + cmd = limits.ShowLimits(self.app, None) + parsed_args = self.check_parser(cmd, arglist, verifylist) + + columns, data = cmd.take_action(parsed_args) + + ret_limits = list(data) + compute_reference_limits = self.fake_limits.absolute_limits() + + self.assertEqual(self.absolute_columns, columns) + self.assertEqual(compute_reference_limits, ret_limits) + self.assertEqual(10, len(ret_limits)) + + def test_volume_show_rate(self): + arglist = ['--rate'] + verifylist = [('is_rate', True)] + cmd = limits.ShowLimits(self.app, None) + parsed_args = self.check_parser(cmd, arglist, verifylist) + + columns, data = cmd.take_action(parsed_args) + + ret_limits = list(data) + compute_reference_limits = self.fake_limits.rate_limits() + + self.assertEqual(self.rate_columns, columns) + self.assertEqual(compute_reference_limits, ret_limits) + self.assertEqual(3, len(ret_limits)) diff --git a/openstackclient/tests/unit/compute/v2/fakes.py b/openstackclient/tests/unit/compute/v2/fakes.py index 1ec717853d..46fa599284 100644 --- a/openstackclient/tests/unit/compute/v2/fakes.py +++ b/openstackclient/tests/unit/compute/v2/fakes.py @@ -149,6 +149,9 @@ class FakeComputev2Client(object): self.images = mock.Mock() self.images.resource_class = fakes.FakeResource(None, {}) + self.limits = mock.Mock() + self.limits.resource_class = fakes.FakeResource(None, {}) + self.servers = mock.Mock() self.servers.resource_class = fakes.FakeResource(None, {}) @@ -1392,3 +1395,110 @@ class FakeQuota(object): quota.project_id = quota_attrs['id'] return quota + + +class FakeLimits(object): + """Fake limits""" + + def __init__(self, absolute_attrs=None, rate_attrs=None): + self.absolute_limits_attrs = { + 'maxServerMeta': 128, + 'maxTotalInstances': 10, + 'maxPersonality': 5, + 'totalServerGroupsUsed': 0, + 'maxImageMeta': 128, + 'maxPersonalitySize': 10240, + 'maxTotalRAMSize': 51200, + 'maxServerGroups': 10, + 'maxSecurityGroupRules': 20, + 'maxTotalKeypairs': 100, + 'totalCoresUsed': 0, + 'totalRAMUsed': 0, + 'maxSecurityGroups': 10, + 'totalFloatingIpsUsed': 0, + 'totalInstancesUsed': 0, + 'maxServerGroupMembers': 10, + 'maxTotalFloatingIps': 10, + 'totalSecurityGroupsUsed': 0, + 'maxTotalCores': 20, + } + absolute_attrs = absolute_attrs or {} + self.absolute_limits_attrs.update(absolute_attrs) + + self.rate_limits_attrs = [{ + "uri": "*", + "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" + } + ] + }] + + @property + def absolute(self): + for (name, value) in self.absolute_limits_attrs.items(): + yield FakeAbsoluteLimit(name, value) + + def absolute_limits(self): + reference_data = [] + for (name, value) in self.absolute_limits_attrs.items(): + reference_data.append((name, value)) + return reference_data + + @property + def rate(self): + for group in self.rate_limits_attrs: + uri = group['uri'] + for rate in group['limit']: + yield FakeRateLimit(rate['verb'], uri, rate['value'], + rate['remaining'], rate['unit'], + rate['next-available']) + + def rate_limits(self): + reference_data = [] + for group in self.rate_limits_attrs: + uri = group['uri'] + for rate in group['limit']: + reference_data.append((rate['verb'], uri, rate['value'], + rate['remaining'], rate['unit'], + rate['next-available'])) + return reference_data + + +class FakeAbsoluteLimit(object): + """Data model that represents an absolute limit""" + + def __init__(self, name, value): + self.name = name + self.value = value + + +class FakeRateLimit(object): + """Data model that represents a flattened view of a single rate limit""" + + def __init__(self, verb, uri, value, remain, + unit, next_available): + self.verb = verb + self.uri = uri + self.value = value + self.remain = remain + self.unit = unit + self.next_available = next_available diff --git a/openstackclient/tests/unit/fakes.py b/openstackclient/tests/unit/fakes.py index 65c76b3e33..954973ef98 100644 --- a/openstackclient/tests/unit/fakes.py +++ b/openstackclient/tests/unit/fakes.py @@ -140,6 +140,8 @@ class FakeClientManager(object): self.auth_ref = None self.auth_plugin_name = None self.network_endpoint_enabled = True + self.compute_endpoint_enabled = True + self.volume_endpoint_enabled = True def get_configuration(self): return { @@ -155,6 +157,12 @@ class FakeClientManager(object): def is_network_endpoint_enabled(self): return self.network_endpoint_enabled + def is_compute_endpoint_enabled(self): + return self.compute_endpoint_enabled + + def is_volume_endpoint_enabled(self, client): + return self.volume_endpoint_enabled + class FakeModule(object): diff --git a/openstackclient/tests/unit/volume/v2/fakes.py b/openstackclient/tests/unit/volume/v2/fakes.py index 27f37bd8c9..481509f3b8 100644 --- a/openstackclient/tests/unit/volume/v2/fakes.py +++ b/openstackclient/tests/unit/volume/v2/fakes.py @@ -200,6 +200,8 @@ class FakeVolumeClient(object): self.volumes.resource_class = fakes.FakeResource(None, {}) self.extensions = mock.Mock() self.extensions.resource_class = fakes.FakeResource(None, {}) + self.limits = mock.Mock() + self.limits.resource_class = fakes.FakeResource(None, {}) self.volume_snapshots = mock.Mock() self.volume_snapshots.resource_class = fakes.FakeResource(None, {}) self.backups = mock.Mock() @@ -1004,3 +1006,101 @@ class FakeQuota(object): quota.project_id = quota_attrs['id'] return quota + + +class FakeLimits(object): + """Fake limits""" + + def __init__(self, absolute_attrs=None): + self.absolute_limits_attrs = { + 'totalSnapshotsUsed': 1, + 'maxTotalBackups': 10, + 'maxTotalVolumeGigabytes': 1000, + 'maxTotalSnapshots': 10, + 'maxTotalBackupGigabytes': 1000, + 'totalBackupGigabytesUsed': 0, + 'maxTotalVolumes': 10, + 'totalVolumesUsed': 4, + 'totalBackupsUsed': 0, + 'totalGigabytesUsed': 35 + } + absolute_attrs = absolute_attrs or {} + self.absolute_limits_attrs.update(absolute_attrs) + + self.rate_limits_attrs = [{ + "uri": "*", + "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" + } + ] + }] + + @property + def absolute(self): + for (name, value) in self.absolute_limits_attrs.items(): + yield FakeAbsoluteLimit(name, value) + + def absolute_limits(self): + reference_data = [] + for (name, value) in self.absolute_limits_attrs.items(): + reference_data.append((name, value)) + return reference_data + + @property + def rate(self): + for group in self.rate_limits_attrs: + uri = group['uri'] + for rate in group['limit']: + yield FakeRateLimit(rate['verb'], uri, rate['value'], + rate['remaining'], rate['unit'], + rate['next-available']) + + def rate_limits(self): + reference_data = [] + for group in self.rate_limits_attrs: + uri = group['uri'] + for rate in group['limit']: + reference_data.append((rate['verb'], uri, rate['value'], + rate['remaining'], rate['unit'], + rate['next-available'])) + return reference_data + + +class FakeAbsoluteLimit(object): + """Data model that represents an absolute limit.""" + + def __init__(self, name, value): + self.name = name + self.value = value + + +class FakeRateLimit(object): + """Data model that represents a flattened view of a single rate limit.""" + + def __init__(self, verb, uri, value, remain, + unit, next_available): + self.verb = verb + self.uri = uri + self.value = value + self.remain = remain + self.unit = unit + self.next_available = next_available