summaryrefslogtreecommitdiff
path: root/openstackclient
diff options
context:
space:
mode:
Diffstat (limited to 'openstackclient')
-rw-r--r--openstackclient/common/versions.py16
-rw-r--r--openstackclient/compute/v2/server.py11
-rw-r--r--openstackclient/identity/v3/endpoint.py2
-rw-r--r--openstackclient/tests/functional/compute/v2/test_server.py10
-rw-r--r--openstackclient/tests/unit/compute/v2/fakes.py3
-rw-r--r--openstackclient/tests/unit/compute/v2/test_server.py49
-rw-r--r--openstackclient/tests/unit/identity/v3/test_endpoint.py41
-rw-r--r--openstackclient/tests/unit/volume/test_find_resource.py4
-rw-r--r--openstackclient/tests/unit/volume/v2/fakes.py156
-rw-r--r--openstackclient/tests/unit/volume/v2/test_volume.py11
-rw-r--r--openstackclient/tests/unit/volume/v2/test_volume_backend.py168
-rw-r--r--openstackclient/volume/client.py21
-rw-r--r--openstackclient/volume/v2/volume.py4
-rw-r--r--openstackclient/volume/v2/volume_backend.py113
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))