diff options
Diffstat (limited to 'openstackclient')
| -rw-r--r-- | openstackclient/common/versions.py | 16 | ||||
| -rw-r--r-- | openstackclient/compute/v2/server.py | 11 | ||||
| -rw-r--r-- | openstackclient/identity/v3/endpoint.py | 2 | ||||
| -rw-r--r-- | openstackclient/tests/functional/compute/v2/test_server.py | 10 | ||||
| -rw-r--r-- | openstackclient/tests/unit/compute/v2/fakes.py | 3 | ||||
| -rw-r--r-- | openstackclient/tests/unit/compute/v2/test_server.py | 49 | ||||
| -rw-r--r-- | openstackclient/tests/unit/identity/v3/test_endpoint.py | 41 | ||||
| -rw-r--r-- | openstackclient/tests/unit/volume/test_find_resource.py | 4 | ||||
| -rw-r--r-- | openstackclient/tests/unit/volume/v2/fakes.py | 156 | ||||
| -rw-r--r-- | openstackclient/tests/unit/volume/v2/test_volume.py | 11 | ||||
| -rw-r--r-- | openstackclient/tests/unit/volume/v2/test_volume_backend.py | 168 | ||||
| -rw-r--r-- | openstackclient/volume/client.py | 21 | ||||
| -rw-r--r-- | openstackclient/volume/v2/volume.py | 4 | ||||
| -rw-r--r-- | openstackclient/volume/v2/volume_backend.py | 113 |
14 files changed, 537 insertions, 72 deletions
diff --git a/openstackclient/common/versions.py b/openstackclient/common/versions.py index 6a93d300..3c267bfe 100644 --- a/openstackclient/common/versions.py +++ b/openstackclient/common/versions.py @@ -14,7 +14,6 @@ """Versions Action Implementation""" -import os_service_types from osc_lib.command import command from openstackclient.i18n import _ @@ -67,7 +66,8 @@ class ShowVersions(command.Lister): session = self.app.client_manager.session version_data = session.get_all_version_data( interface=interface, - region_name=parsed_args.region_name) + region_name=parsed_args.region_name, + service_type=parsed_args.service) columns = [ "Region Name", @@ -83,22 +83,10 @@ class ShowVersions(command.Lister): if status: status = status.upper() - service = parsed_args.service - if service: - # Normalize service type argument to official type - service_type_manager = os_service_types.ServiceTypes() - service = service_type_manager.get_service_type(service) - versions = [] for region_name, interfaces in version_data.items(): for interface, services in interfaces.items(): for service_type, service_versions in services.items(): - if service and service != service_type: - # TODO(mordred) Once there is a version of - # keystoneauth that can do this filtering - # before making all the discovery calls, switch - # to that. - continue for data in service_versions: if status and status != data['status']: continue diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 5723dae3..4b0aedd7 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -809,9 +809,14 @@ class CreateServer(command.ShowOne): raise exceptions.CommandError(msg) nics = nics[0] else: - # Default to empty list if nothing was specified, let nova side to - # decide the default behavior. - nics = [] + # Compute API version >= 2.37 requires a value, so default to + # 'auto' to maintain legacy behavior if a nic wasn't specified. + if compute_client.api_version >= api_versions.APIVersion('2.37'): + nics = 'auto' + else: + # Default to empty list if nothing was specified, let nova + # side to decide the default behavior. + nics = [] # Check security group exist and convert ID to name security_group_names = [] diff --git a/openstackclient/identity/v3/endpoint.py b/openstackclient/identity/v3/endpoint.py index 3229240e..858b5036 100644 --- a/openstackclient/identity/v3/endpoint.py +++ b/openstackclient/identity/v3/endpoint.py @@ -199,7 +199,7 @@ class ListEndpoint(command.Lister): metavar='<project>', help=_('Project to list filters (name or ID)'), ) - common.add_project_domain_option_to_parser(list_group) + common.add_project_domain_option_to_parser(parser) return parser def take_action(self, parsed_args): diff --git a/openstackclient/tests/functional/compute/v2/test_server.py b/openstackclient/tests/functional/compute/v2/test_server.py index bba16f62..3cb72d9f 100644 --- a/openstackclient/tests/functional/compute/v2/test_server.py +++ b/openstackclient/tests/functional/compute/v2/test_server.py @@ -618,7 +618,9 @@ class ServerTests(common.ComputeTestCase): server_name ) except exceptions.CommandFailed as e: - self.assertIn('nics are required after microversion 2.36', - e.stderr) - else: - self.fail('CommandFailed should be raised.') + # If we got here, it shouldn't be because a nics value wasn't + # provided to the server; it is likely due to something else in + # the functional tests like there being multiple available + # networks and the test didn't specify a specific network. + self.assertNotIn('nics are required after microversion 2.36', + e.stderr) diff --git a/openstackclient/tests/unit/compute/v2/fakes.py b/openstackclient/tests/unit/compute/v2/fakes.py index 9a065be1..234bbd9b 100644 --- a/openstackclient/tests/unit/compute/v2/fakes.py +++ b/openstackclient/tests/unit/compute/v2/fakes.py @@ -17,6 +17,7 @@ import copy import uuid import mock +from novaclient import api_versions from openstackclient.api import compute_v2 from openstackclient.tests.unit import fakes @@ -201,6 +202,8 @@ class FakeComputev2Client(object): self.management_url = kwargs['endpoint'] + self.api_version = api_versions.APIVersion('2.1') + class TestComputev2(utils.TestCommand): diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 9adc3cc6..928794fb 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -845,6 +845,55 @@ class TestServerCreate(TestServer): self.assertEqual(self.columns, columns) self.assertEqual(self.datalist(), data) + def test_server_create_with_auto_network_default_v2_37(self): + """Tests creating a server without specifying --nic using 2.37.""" + arglist = [ + '--image', 'image1', + '--flavor', 'flavor1', + self.new_server.name, + ] + verifylist = [ + ('image', 'image1'), + ('flavor', 'flavor1'), + ('config_drive', False), + ('server_name', self.new_server.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # Since check_parser doesn't handle compute global options like + # --os-compute-api-version, we have to mock the construction of + # the novaclient client object with our own APIVersion. + with mock.patch.object(self.app.client_manager.compute, 'api_version', + api_versions.APIVersion('2.37')): + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = dict( + meta=None, + files={}, + reservation_id=None, + min_count=1, + max_count=1, + security_groups=[], + userdata=None, + key_name=None, + availability_zone=None, + block_device_mapping_v2=[], + nics='auto', + scheduler_hints={}, + config_drive=None, + ) + # ServerManager.create(name, image, flavor, **kwargs) + self.servers_mock.create.assert_called_with( + self.new_server.name, + self.image, + self.flavor, + **kwargs + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist(), data) + def test_server_create_with_none_network(self): arglist = [ '--image', 'image1', diff --git a/openstackclient/tests/unit/identity/v3/test_endpoint.py b/openstackclient/tests/unit/identity/v3/test_endpoint.py index bfe930d6..62dcf58d 100644 --- a/openstackclient/tests/unit/identity/v3/test_endpoint.py +++ b/openstackclient/tests/unit/identity/v3/test_endpoint.py @@ -439,6 +439,47 @@ class TestEndpointList(TestEndpoint): ) self.assertEqual(datalist, tuple(data)) + def test_endpoint_list_project_with_project_domain(self): + project = identity_fakes.FakeProject.create_one_project() + domain = identity_fakes.FakeDomain.create_one_domain() + + self.ep_filter_mock.list_endpoints_for_project.return_value = [ + self.endpoint + ] + self.projects_mock.get.return_value = project + + arglist = [ + '--project', project.name, + '--project-domain', domain.name + ] + verifylist = [ + ('project', project.name), + ('project_domain', domain.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. + columns, data = self.cmd.take_action(parsed_args) + self.ep_filter_mock.list_endpoints_for_project.assert_called_with( + project=project.id + ) + + self.assertEqual(self.columns, columns) + datalist = ( + ( + self.endpoint.id, + self.endpoint.region, + self.service.name, + self.service.type, + True, + self.endpoint.interface, + self.endpoint.url, + ), + ) + self.assertEqual(datalist, tuple(data)) + class TestEndpointSet(TestEndpoint): diff --git a/openstackclient/tests/unit/volume/test_find_resource.py b/openstackclient/tests/unit/volume/test_find_resource.py index dbf9592f..60591eff 100644 --- a/openstackclient/tests/unit/volume/test_find_resource.py +++ b/openstackclient/tests/unit/volume/test_find_resource.py @@ -15,8 +15,8 @@ import mock -from cinderclient.v1 import volume_snapshots -from cinderclient.v1 import volumes +from cinderclient.v3 import volume_snapshots +from cinderclient.v3 import volumes from osc_lib import exceptions from osc_lib import utils diff --git a/openstackclient/tests/unit/volume/v2/fakes.py b/openstackclient/tests/unit/volume/v2/fakes.py index 481509f3..59b08d0b 100644 --- a/openstackclient/tests/unit/volume/v2/fakes.py +++ b/openstackclient/tests/unit/volume/v2/fakes.py @@ -193,46 +193,144 @@ class FakeService(object): return services +class FakeCapability(object): + """Fake capability.""" + + @staticmethod + def create_one_capability(attrs=None): + """Create a fake volume backend capability. + + :param Dictionary attrs: + A dictionary with all attributes of the Capabilities. + :return: + A FakeResource object with capability name and attrs. + """ + # Set default attribute + capability_info = { + "namespace": "OS::Storage::Capabilities::fake", + "vendor_name": "OpenStack", + "volume_backend_name": "lvmdriver-1", + "pool_name": "pool", + "driver_version": "2.0.0", + "storage_protocol": "iSCSI", + "display_name": "Capabilities of Cinder LVM driver", + "description": "Blah, blah.", + "visibility": "public", + "replication_targets": [], + "properties": { + "compression": { + "title": "Compression", + "description": "Enables compression.", + "type": "boolean" + }, + "qos": { + "title": "QoS", + "description": "Enables QoS.", + "type": "boolean" + }, + "replication": { + "title": "Replication", + "description": "Enables replication.", + "type": "boolean" + }, + "thin_provisioning": { + "title": "Thin Provisioning", + "description": "Sets thin provisioning.", + "type": "boolean" + } + } + } + + # Overwrite default attributes if there are some attributes set + capability_info.update(attrs or {}) + + capability = fakes.FakeResource( + None, + capability_info, + loaded=True) + + return capability + + +class FakePool(object): + """Fake Pools.""" + + @staticmethod + def create_one_pool(attrs=None): + """Create a fake pool. + + :param Dictionary attrs: + A dictionary with all attributes of the pool + :return: + A FakeResource object with pool name and attrs. + """ + # Set default attribute + pool_info = { + 'name': 'host@lvmdriver-1#lvmdriver-1', + 'storage_protocol': 'iSCSI', + 'thick_provisioning_support': False, + 'thin_provisioning_support': True, + 'total_volumes': 99, + 'total_capacity_gb': 1000.00, + 'allocated_capacity_gb': 100, + 'max_over_subscription_ratio': 200.0, + } + + # Overwrite default attributes if there are some attributes set + pool_info.update(attrs or {}) + + pool = fakes.FakeResource( + None, + pool_info, + loaded=True) + + return pool + + class FakeVolumeClient(object): def __init__(self, **kwargs): - self.volumes = mock.Mock() - self.volumes.resource_class = fakes.FakeResource(None, {}) + self.auth_token = kwargs['token'] + self.management_url = kwargs['endpoint'] + self.availability_zones = mock.Mock() + self.availability_zones.resource_class = fakes.FakeResource(None, {}) + self.backups = mock.Mock() + self.backups.resource_class = fakes.FakeResource(None, {}) + self.capabilities = mock.Mock() + self.capabilities.resource_class = fakes.FakeResource(None, {}) + self.cgsnapshots = mock.Mock() + self.cgsnapshots.resource_class = fakes.FakeResource(None, {}) + self.consistencygroups = mock.Mock() + self.consistencygroups.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() - self.backups.resource_class = fakes.FakeResource(None, {}) - self.volume_types = mock.Mock() - self.volume_types.resource_class = fakes.FakeResource(None, {}) - self.volume_type_access = mock.Mock() - self.volume_type_access.resource_class = fakes.FakeResource(None, {}) - self.volume_encryption_types = mock.Mock() - self.volume_encryption_types.resource_class = ( - fakes.FakeResource(None, {})) - self.restores = mock.Mock() - self.restores.resource_class = fakes.FakeResource(None, {}) + self.pools = mock.Mock() + self.pools.resource_class = fakes.FakeResource(None, {}) self.qos_specs = mock.Mock() self.qos_specs.resource_class = fakes.FakeResource(None, {}) - self.availability_zones = mock.Mock() - self.availability_zones.resource_class = fakes.FakeResource(None, {}) - self.transfers = mock.Mock() - self.transfers.resource_class = fakes.FakeResource(None, {}) - self.services = mock.Mock() - self.services.resource_class = fakes.FakeResource(None, {}) - self.quotas = mock.Mock() - self.quotas.resource_class = fakes.FakeResource(None, {}) self.quota_classes = mock.Mock() self.quota_classes.resource_class = fakes.FakeResource(None, {}) - self.consistencygroups = mock.Mock() - self.consistencygroups.resource_class = fakes.FakeResource(None, {}) - self.cgsnapshots = mock.Mock() - self.cgsnapshots.resource_class = fakes.FakeResource(None, {}) - self.auth_token = kwargs['token'] - self.management_url = kwargs['endpoint'] + self.quotas = mock.Mock() + self.quotas.resource_class = fakes.FakeResource(None, {}) + self.restores = mock.Mock() + self.restores.resource_class = fakes.FakeResource(None, {}) + self.services = mock.Mock() + self.services.resource_class = fakes.FakeResource(None, {}) + self.transfers = mock.Mock() + self.transfers.resource_class = fakes.FakeResource(None, {}) + self.volume_encryption_types = mock.Mock() + self.volume_encryption_types.resource_class = ( + fakes.FakeResource(None, {})) + self.volume_snapshots = mock.Mock() + self.volume_snapshots.resource_class = fakes.FakeResource(None, {}) + self.volume_type_access = mock.Mock() + self.volume_type_access.resource_class = fakes.FakeResource(None, {}) + self.volume_types = mock.Mock() + self.volume_types.resource_class = fakes.FakeResource(None, {}) + self.volumes = mock.Mock() + self.volumes.resource_class = fakes.FakeResource(None, {}) class TestVolume(utils.TestCommand): diff --git a/openstackclient/tests/unit/volume/v2/test_volume.py b/openstackclient/tests/unit/volume/v2/test_volume.py index bb6263bb..183fb228 100644 --- a/openstackclient/tests/unit/volume/v2/test_volume.py +++ b/openstackclient/tests/unit/volume/v2/test_volume.py @@ -131,7 +131,6 @@ class TestVolumeCreate(TestVolume): imageRef=None, source_volid=None, consistencygroup_id=None, - multiattach=False, scheduler_hints=None, ) @@ -149,7 +148,6 @@ class TestVolumeCreate(TestVolume): '--availability-zone', self.new_volume.availability_zone, '--consistency-group', consistency_group.id, '--hint', 'k=v', - '--multi-attach', self.new_volume.name, ] verifylist = [ @@ -159,7 +157,6 @@ class TestVolumeCreate(TestVolume): ('availability_zone', self.new_volume.availability_zone), ('consistency_group', consistency_group.id), ('hint', {'k': 'v'}), - ('multi_attach', True), ('name', self.new_volume.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -180,7 +177,6 @@ class TestVolumeCreate(TestVolume): imageRef=None, source_volid=None, consistencygroup_id=consistency_group.id, - multiattach=True, scheduler_hints={'k': 'v'}, ) @@ -251,7 +247,6 @@ class TestVolumeCreate(TestVolume): imageRef=None, source_volid=None, consistencygroup_id=None, - multiattach=False, scheduler_hints=None, ) @@ -290,7 +285,6 @@ class TestVolumeCreate(TestVolume): imageRef=image.id, source_volid=None, consistencygroup_id=None, - multiattach=False, scheduler_hints=None, ) @@ -329,7 +323,6 @@ class TestVolumeCreate(TestVolume): imageRef=image.id, source_volid=None, consistencygroup_id=None, - multiattach=False, scheduler_hints=None, ) @@ -367,7 +360,6 @@ class TestVolumeCreate(TestVolume): imageRef=None, source_volid=None, consistencygroup_id=None, - multiattach=False, scheduler_hints=None, ) @@ -406,7 +398,6 @@ class TestVolumeCreate(TestVolume): imageRef=None, source_volid=None, consistencygroup_id=None, - multiattach=False, scheduler_hints=None, ) @@ -449,7 +440,6 @@ class TestVolumeCreate(TestVolume): imageRef=None, source_volid=None, consistencygroup_id=None, - multiattach=False, scheduler_hints=None, ) @@ -501,7 +491,6 @@ class TestVolumeCreate(TestVolume): imageRef=None, source_volid=None, consistencygroup_id=None, - multiattach=False, scheduler_hints=None, ) diff --git a/openstackclient/tests/unit/volume/v2/test_volume_backend.py b/openstackclient/tests/unit/volume/v2/test_volume_backend.py new file mode 100644 index 00000000..db188660 --- /dev/null +++ b/openstackclient/tests/unit/volume/v2/test_volume_backend.py @@ -0,0 +1,168 @@ +# +# 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.tests.unit.volume.v2 import fakes as volume_fakes +from openstackclient.volume.v2 import volume_backend + + +class TestShowVolumeCapability(volume_fakes.TestVolume): + """Test backend capability functionality.""" + + # The capability to be listed + capability = volume_fakes.FakeCapability.create_one_capability() + + def setUp(self): + super(TestShowVolumeCapability, self).setUp() + + # Get a shortcut to the capability Mock + self.capability_mock = self.app.client_manager.volume.capabilities + self.capability_mock.get.return_value = self.capability + + # Get the command object to test + self.cmd = volume_backend.ShowCapability(self.app, None) + + def test_capability_show(self): + arglist = [ + 'fake', + ] + verifylist = [ + ('host', 'fake'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. + columns, data = self.cmd.take_action(parsed_args) + + expected_columns = [ + 'Title', + 'Key', + 'Type', + 'Description', + ] + + # confirming if all expected columns are present in the result. + self.assertEqual(expected_columns, columns) + + capabilities = [ + 'Compression', + 'Replication', + 'QoS', + 'Thin Provisioning', + ] + + # confirming if all expected values are present in the result. + for cap in data: + self.assertTrue(cap[0] in capabilities) + + # checking if proper call was made to get capabilities + self.capability_mock.get.assert_called_with( + 'fake', + ) + + +class TestListVolumePool(volume_fakes.TestVolume): + """Tests for volume backend pool listing.""" + + # The pool to be listed + pools = volume_fakes.FakePool.create_one_pool() + + def setUp(self): + super(TestListVolumePool, self).setUp() + + self.pool_mock = self.app.client_manager.volume.pools + self.pool_mock.list.return_value = [self.pools] + + # Get the command object to test + self.cmd = volume_backend.ListPool(self.app, None) + + def test_pool_list(self): + arglist = [] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. + columns, data = self.cmd.take_action(parsed_args) + + expected_columns = [ + 'Name', + ] + + # confirming if all expected columns are present in the result. + self.assertEqual(expected_columns, columns) + + datalist = (( + self.pools.name, + ), ) + + # confirming if all expected values are present in the result. + self.assertEqual(datalist, tuple(data)) + + # checking if proper call was made to list pools + self.pool_mock.list.assert_called_with( + detailed=False, + ) + + # checking if long columns are present in output + self.assertNotIn("total_volumes", columns) + self.assertNotIn("storage_protocol", columns) + + def test_service_list_with_long_option(self): + arglist = [ + '--long' + ] + verifylist = [ + ('long', True) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. + columns, data = self.cmd.take_action(parsed_args) + + expected_columns = [ + 'Name', + 'Protocol', + 'Thick', + 'Thin', + 'Volumes', + 'Capacity', + 'Allocated', + 'Max Over Ratio', + ] + + # confirming if all expected columns are present in the result. + self.assertEqual(expected_columns, columns) + + datalist = (( + self.pools.name, + self.pools.storage_protocol, + self.pools.thick_provisioning_support, + self.pools.thin_provisioning_support, + self.pools.total_volumes, + self.pools.total_capacity_gb, + self.pools.allocated_capacity_gb, + self.pools.max_over_subscription_ratio, + ), ) + + # confirming if all expected values are present in the result. + self.assertEqual(datalist, tuple(data)) + + self.pool_mock.list.assert_called_with( + detailed=True, + ) diff --git a/openstackclient/volume/client.py b/openstackclient/volume/client.py index c4b0dfca..e0e670a9 100644 --- a/openstackclient/volume/client.py +++ b/openstackclient/volume/client.py @@ -37,13 +37,20 @@ def make_client(instance): # Defer client imports until we actually need them from cinderclient import extension - from cinderclient.v1.contrib import list_extensions - from cinderclient.v1 import volume_snapshots - from cinderclient.v1 import volumes - - # Monkey patch for v1 cinderclient - volumes.Volume.NAME_ATTR = 'display_name' - volume_snapshots.Snapshot.NAME_ATTR = 'display_name' + from cinderclient.v3.contrib import list_extensions + from cinderclient.v3 import volume_snapshots + from cinderclient.v3 import volumes + + # Try a small import to check if cinderclient v1 is supported + try: + from cinderclient.v1 import services # noqa + except Exception: + del API_VERSIONS['1'] + + if instance._api_version[API_NAME] == '1': + # Monkey patch for v1 cinderclient + volumes.Volume.NAME_ATTR = 'display_name' + volume_snapshots.Snapshot.NAME_ATTR = 'display_name' volume_client = utils.get_client_class( API_NAME, diff --git a/openstackclient/volume/v2/volume.py b/openstackclient/volume/v2/volume.py index 8ab61d2a..7a5c207a 100644 --- a/openstackclient/volume/v2/volume.py +++ b/openstackclient/volume/v2/volume.py @@ -212,6 +212,9 @@ class CreateVolume(command.ShowOne): raise exceptions.CommandError( _("ERROR: --user is deprecated, please use" " --os-username instead.")) + if parsed_args.multi_attach: + LOG.warning(_("'--multi-attach' option is no longer supported by " + "the block storage service.")) volume = volume_client.volumes.create( size=size, @@ -224,7 +227,6 @@ class CreateVolume(command.ShowOne): imageRef=image, source_volid=source_volume, consistencygroup_id=consistency_group, - multiattach=parsed_args.multi_attach, scheduler_hints=parsed_args.hint, ) diff --git a/openstackclient/volume/v2/volume_backend.py b/openstackclient/volume/v2/volume_backend.py new file mode 100644 index 00000000..c5194d35 --- /dev/null +++ b/openstackclient/volume/v2/volume_backend.py @@ -0,0 +1,113 @@ +# +# 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. +# + +"""Storage backend action implementations""" + +from osc_lib.command import command +from osc_lib import utils + +from openstackclient.i18n import _ + + +class ShowCapability(command.Lister): + _description = _("Show capability command") + + def get_parser(self, prog_name): + parser = super(ShowCapability, self).get_parser(prog_name) + parser.add_argument( + "host", + metavar="<host>", + help=_("List capabilities of specified host (host@backend-name)") + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + + columns = [ + 'Title', + 'Key', + 'Type', + 'Description', + ] + + data = volume_client.capabilities.get(parsed_args.host) + + # The get capabilities API is... interesting. We only want the names of + # the capabilities that can set for a backend through extra specs, so + # we need to extract out that part of the mess that is returned. + print_data = [] + keys = data.properties + for key in keys: + # Stuff the key into the details to make it easier to output + capability_data = data.properties[key] + capability_data['key'] = key + print_data.append(capability_data) + + return (columns, + (utils.get_dict_properties( + s, columns, + ) for s in print_data)) + + +class ListPool(command.Lister): + _description = _("List pool command") + + def get_parser(self, prog_name): + parser = super(ListPool, self).get_parser(prog_name) + parser.add_argument( + "--long", + action="store_true", + default=False, + help=_("Show detailed information about pools.") + ) + # TODO(smcginnis): Starting with Cinder microversion 3.33, user is also + # able to pass in --filters with a <key>=<value> pair to filter on. + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + + if parsed_args.long: + columns = [ + 'name', + 'storage_protocol', + 'thick_provisioning_support', + 'thin_provisioning_support', + 'total_volumes', + 'total_capacity_gb', + 'allocated_capacity_gb', + 'max_over_subscription_ratio', + ] + headers = [ + 'Name', + 'Protocol', + 'Thick', + 'Thin', + 'Volumes', + 'Capacity', + 'Allocated', + 'Max Over Ratio' + ] + else: + columns = [ + 'Name', + ] + headers = columns + + data = volume_client.pools.list(detailed=parsed_args.long) + return (headers, + (utils.get_item_properties( + s, columns, + ) for s in data)) |
