Support listing volume availability zones
Update the "os availability zone list" command to support listing volume availability zones along with the currently listed compute availability zones. This adds a --compute and --volume option to the command in order to select the availability zones to list. By default, all availability zones are listed. If the Block Storage API does not support listing availability zones then an warning message will be issued. Change-Id: I8159509a41bd1fb1b4e77fdbb512cf64a5ac11a9 Closes-Bug: #1532945
This commit is contained in:
parent
84174440fc
commit
a8ec2ac494
@ -2,7 +2,7 @@
|
||||
availability zone
|
||||
=================
|
||||
|
||||
Compute v2
|
||||
Compute v2, Block Storage v2
|
||||
|
||||
availability zone list
|
||||
----------------------
|
||||
@ -13,8 +13,18 @@ List availability zones and their status
|
||||
.. code:: bash
|
||||
|
||||
os availability zone list
|
||||
[--compute]
|
||||
[--volume]
|
||||
[--long]
|
||||
|
||||
.. option:: --compute
|
||||
|
||||
List compute availability zones
|
||||
|
||||
.. option:: --volume
|
||||
|
||||
List volume availability zones
|
||||
|
||||
.. option:: --long
|
||||
|
||||
List additional fields in output
|
||||
|
@ -70,7 +70,7 @@ the API resources will be merged, as in the ``quota`` object that has options
|
||||
referring to both Compute and Volume quotas.
|
||||
|
||||
* ``access token``: (**Identity**) long-lived OAuth-based token
|
||||
* ``availability zone``: (**Compute**) a logical partition of hosts or block storage services
|
||||
* ``availability zone``: (**Compute**, **Volume**) a logical partition of hosts or block storage services
|
||||
* ``aggregate``: (**Compute**) a grouping of servers
|
||||
* ``backup``: (**Volume**) a volume copy
|
||||
* ``catalog``: (**Identity**) service catalog
|
||||
|
@ -11,7 +11,7 @@
|
||||
# under the License.
|
||||
#
|
||||
|
||||
"""Compute v2 Availability Zone action implementations"""
|
||||
"""Availability Zone action implementations"""
|
||||
|
||||
import copy
|
||||
import logging
|
||||
@ -24,15 +24,19 @@ from openstackclient.common import utils
|
||||
from openstackclient.i18n import _ # noqa
|
||||
|
||||
|
||||
def _xform_availability_zone(az, include_extra):
|
||||
result = []
|
||||
zone_info = {}
|
||||
def _xform_common_availability_zone(az, zone_info):
|
||||
if hasattr(az, 'zoneState'):
|
||||
zone_info['zone_status'] = ('available' if az.zoneState['available']
|
||||
else 'not available')
|
||||
if hasattr(az, 'zoneName'):
|
||||
zone_info['zone_name'] = az.zoneName
|
||||
|
||||
|
||||
def _xform_compute_availability_zone(az, include_extra):
|
||||
result = []
|
||||
zone_info = {}
|
||||
_xform_common_availability_zone(az, zone_info)
|
||||
|
||||
if not include_extra:
|
||||
result.append(zone_info)
|
||||
return result
|
||||
@ -58,6 +62,14 @@ def _xform_availability_zone(az, include_extra):
|
||||
return result
|
||||
|
||||
|
||||
def _xform_volume_availability_zone(az):
|
||||
result = []
|
||||
zone_info = {}
|
||||
_xform_common_availability_zone(az, zone_info)
|
||||
result.append(zone_info)
|
||||
return result
|
||||
|
||||
|
||||
class ListAvailabilityZone(lister.Lister):
|
||||
"""List availability zones and their status"""
|
||||
|
||||
@ -65,6 +77,16 @@ class ListAvailabilityZone(lister.Lister):
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(ListAvailabilityZone, self).get_parser(prog_name)
|
||||
parser.add_argument(
|
||||
'--compute',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help='List compute availability zones')
|
||||
parser.add_argument(
|
||||
'--volume',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help='List volume availability zones')
|
||||
parser.add_argument(
|
||||
'--long',
|
||||
action='store_true',
|
||||
@ -73,15 +95,7 @@ class ListAvailabilityZone(lister.Lister):
|
||||
)
|
||||
return parser
|
||||
|
||||
@utils.log_method(log)
|
||||
def take_action(self, parsed_args):
|
||||
|
||||
if parsed_args.long:
|
||||
columns = ('Zone Name', 'Zone Status',
|
||||
'Host Name', 'Service Name', 'Service Status')
|
||||
else:
|
||||
columns = ('Zone Name', 'Zone Status')
|
||||
|
||||
def get_compute_availability_zones(self, parsed_args):
|
||||
compute_client = self.app.client_manager.compute
|
||||
try:
|
||||
data = compute_client.availability_zones.list()
|
||||
@ -94,7 +108,40 @@ class ListAvailabilityZone(lister.Lister):
|
||||
# Argh, the availability zones are not iterable...
|
||||
result = []
|
||||
for zone in data:
|
||||
result += _xform_availability_zone(zone, parsed_args.long)
|
||||
result += _xform_compute_availability_zone(zone, parsed_args.long)
|
||||
return result
|
||||
|
||||
def get_volume_availability_zones(self, parsed_args):
|
||||
volume_client = self.app.client_manager.volume
|
||||
try:
|
||||
data = volume_client.availability_zones.list()
|
||||
except Exception:
|
||||
message = "Availability zones list not supported by " \
|
||||
"Block Storage API"
|
||||
self.log.warning(message)
|
||||
|
||||
result = []
|
||||
for zone in data:
|
||||
result += _xform_volume_availability_zone(zone)
|
||||
return result
|
||||
|
||||
@utils.log_method(log)
|
||||
def take_action(self, parsed_args):
|
||||
|
||||
if parsed_args.long:
|
||||
columns = ('Zone Name', 'Zone Status',
|
||||
'Host Name', 'Service Name', 'Service Status')
|
||||
else:
|
||||
columns = ('Zone Name', 'Zone Status')
|
||||
|
||||
# Show everything by default.
|
||||
show_all = (not parsed_args.compute and not parsed_args.volume)
|
||||
|
||||
result = []
|
||||
if parsed_args.compute or show_all:
|
||||
result += self.get_compute_availability_zones(parsed_args)
|
||||
if parsed_args.volume or show_all:
|
||||
result += self.get_volume_availability_zones(parsed_args)
|
||||
|
||||
return (columns,
|
||||
(utils.get_dict_properties(
|
||||
|
@ -17,6 +17,7 @@ from openstackclient.common import availability_zone
|
||||
from openstackclient.tests.compute.v2 import fakes as compute_fakes
|
||||
from openstackclient.tests import fakes
|
||||
from openstackclient.tests import utils
|
||||
from openstackclient.tests.volume.v2 import fakes as volume_fakes
|
||||
|
||||
|
||||
def _build_compute_az_datalist(compute_az, long_datalist=False):
|
||||
@ -39,6 +40,22 @@ def _build_compute_az_datalist(compute_az, long_datalist=False):
|
||||
return (datalist,)
|
||||
|
||||
|
||||
def _build_volume_az_datalist(volume_az, long_datalist=False):
|
||||
datalist = ()
|
||||
if not long_datalist:
|
||||
datalist = (
|
||||
volume_az.zoneName,
|
||||
'available',
|
||||
)
|
||||
else:
|
||||
datalist = (
|
||||
volume_az.zoneName,
|
||||
'available',
|
||||
'', '', '',
|
||||
)
|
||||
return (datalist,)
|
||||
|
||||
|
||||
class TestAvailabilityZone(utils.TestCommand):
|
||||
|
||||
def setUp(self):
|
||||
@ -53,16 +70,37 @@ class TestAvailabilityZone(utils.TestCommand):
|
||||
self.compute_azs_mock = compute_client.availability_zones
|
||||
self.compute_azs_mock.reset_mock()
|
||||
|
||||
volume_client = volume_fakes.FakeVolumeClient(
|
||||
endpoint=fakes.AUTH_URL,
|
||||
token=fakes.AUTH_TOKEN,
|
||||
)
|
||||
self.app.client_manager.volume = volume_client
|
||||
|
||||
self.volume_azs_mock = volume_client.availability_zones
|
||||
self.volume_azs_mock.reset_mock()
|
||||
|
||||
|
||||
class TestAvailabilityZoneList(TestAvailabilityZone):
|
||||
|
||||
compute_azs = \
|
||||
compute_fakes.FakeAvailabilityZone.create_availability_zones()
|
||||
volume_azs = \
|
||||
volume_fakes.FakeAvailabilityZone.create_availability_zones(count=1)
|
||||
|
||||
short_columnslist = ('Zone Name', 'Zone Status')
|
||||
long_columnslist = (
|
||||
'Zone Name',
|
||||
'Zone Status',
|
||||
'Host Name',
|
||||
'Service Name',
|
||||
'Service Status',
|
||||
)
|
||||
|
||||
def setUp(self):
|
||||
super(TestAvailabilityZoneList, self).setUp()
|
||||
|
||||
self.compute_azs_mock.list.return_value = self.compute_azs
|
||||
self.volume_azs_mock.list.return_value = self.volume_azs
|
||||
|
||||
# Get the command object to test
|
||||
self.cmd = availability_zone.ListAvailabilityZone(self.app, None)
|
||||
@ -76,12 +114,14 @@ class TestAvailabilityZoneList(TestAvailabilityZone):
|
||||
columns, data = self.cmd.take_action(parsed_args)
|
||||
|
||||
self.compute_azs_mock.list.assert_called_with()
|
||||
self.volume_azs_mock.list.assert_called_with()
|
||||
|
||||
columnslist = ('Zone Name', 'Zone Status')
|
||||
self.assertEqual(columnslist, columns)
|
||||
self.assertEqual(self.short_columnslist, columns)
|
||||
datalist = ()
|
||||
for compute_az in self.compute_azs:
|
||||
datalist += _build_compute_az_datalist(compute_az)
|
||||
for volume_az in self.volume_azs:
|
||||
datalist += _build_volume_az_datalist(volume_az)
|
||||
self.assertEqual(datalist, tuple(data))
|
||||
|
||||
def test_availability_zone_list_long(self):
|
||||
@ -97,17 +137,56 @@ class TestAvailabilityZoneList(TestAvailabilityZone):
|
||||
columns, data = self.cmd.take_action(parsed_args)
|
||||
|
||||
self.compute_azs_mock.list.assert_called_with()
|
||||
self.volume_azs_mock.list.assert_called_with()
|
||||
|
||||
columnslist = (
|
||||
'Zone Name',
|
||||
'Zone Status',
|
||||
'Host Name',
|
||||
'Service Name',
|
||||
'Service Status',
|
||||
)
|
||||
self.assertEqual(columnslist, columns)
|
||||
self.assertEqual(self.long_columnslist, columns)
|
||||
datalist = ()
|
||||
for compute_az in self.compute_azs:
|
||||
datalist += _build_compute_az_datalist(compute_az,
|
||||
long_datalist=True)
|
||||
for volume_az in self.volume_azs:
|
||||
datalist += _build_volume_az_datalist(volume_az,
|
||||
long_datalist=True)
|
||||
self.assertEqual(datalist, tuple(data))
|
||||
|
||||
def test_availability_zone_list_compute(self):
|
||||
arglist = [
|
||||
'--compute',
|
||||
]
|
||||
verifylist = [
|
||||
('compute', True),
|
||||
]
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
# DisplayCommandBase.take_action() returns two tuples
|
||||
columns, data = self.cmd.take_action(parsed_args)
|
||||
|
||||
self.compute_azs_mock.list.assert_called_with()
|
||||
self.volume_azs_mock.list.assert_not_called()
|
||||
|
||||
self.assertEqual(self.short_columnslist, columns)
|
||||
datalist = ()
|
||||
for compute_az in self.compute_azs:
|
||||
datalist += _build_compute_az_datalist(compute_az)
|
||||
self.assertEqual(datalist, tuple(data))
|
||||
|
||||
def test_availability_zone_list_volume(self):
|
||||
arglist = [
|
||||
'--volume',
|
||||
]
|
||||
verifylist = [
|
||||
('volume', True),
|
||||
]
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
# DisplayCommandBase.take_action() returns two tuples
|
||||
columns, data = self.cmd.take_action(parsed_args)
|
||||
|
||||
self.compute_azs_mock.list.assert_not_called()
|
||||
self.volume_azs_mock.list.assert_called_with()
|
||||
|
||||
self.assertEqual(self.short_columnslist, columns)
|
||||
datalist = ()
|
||||
for volume_az in self.volume_azs:
|
||||
datalist += _build_volume_az_datalist(volume_az)
|
||||
self.assertEqual(datalist, tuple(data))
|
||||
|
@ -202,6 +202,8 @@ class FakeVolumeClient(object):
|
||||
self.restores.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.auth_token = kwargs['token']
|
||||
self.management_url = kwargs['endpoint']
|
||||
|
||||
@ -304,3 +306,55 @@ class FakeVolume(object):
|
||||
volumes = FakeVolume.create_volumes(count)
|
||||
|
||||
return mock.MagicMock(side_effect=volumes)
|
||||
|
||||
|
||||
class FakeAvailabilityZone(object):
|
||||
"""Fake one or more volume availability zones (AZs)."""
|
||||
|
||||
@staticmethod
|
||||
def create_one_availability_zone(attrs={}, methods={}):
|
||||
"""Create a fake AZ.
|
||||
|
||||
:param Dictionary attrs:
|
||||
A dictionary with all attributes
|
||||
:param Dictionary methods:
|
||||
A dictionary with all methods
|
||||
:return:
|
||||
A FakeResource object with zoneName, zoneState, etc.
|
||||
"""
|
||||
# Set default attributes.
|
||||
availability_zone = {
|
||||
'zoneName': uuid.uuid4().hex,
|
||||
'zoneState': {'available': True},
|
||||
}
|
||||
|
||||
# Overwrite default attributes.
|
||||
availability_zone.update(attrs)
|
||||
|
||||
availability_zone = fakes.FakeResource(
|
||||
info=copy.deepcopy(availability_zone),
|
||||
methods=methods,
|
||||
loaded=True)
|
||||
return availability_zone
|
||||
|
||||
@staticmethod
|
||||
def create_availability_zones(attrs={}, methods={}, count=2):
|
||||
"""Create multiple fake AZs.
|
||||
|
||||
:param Dictionary attrs:
|
||||
A dictionary with all attributes
|
||||
:param Dictionary methods:
|
||||
A dictionary with all methods
|
||||
:param int count:
|
||||
The number of AZs to fake
|
||||
:return:
|
||||
A list of FakeResource objects faking the AZs
|
||||
"""
|
||||
availability_zones = []
|
||||
for i in range(0, count):
|
||||
availability_zone = \
|
||||
FakeAvailabilityZone.create_one_availability_zone(
|
||||
attrs, methods)
|
||||
availability_zones.append(availability_zone)
|
||||
|
||||
return availability_zones
|
||||
|
8
releasenotes/notes/bug-1532945-1a5485b8d0ebddb8.yaml
Normal file
8
releasenotes/notes/bug-1532945-1a5485b8d0ebddb8.yaml
Normal file
@ -0,0 +1,8 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
Add volume support to `os availability zone list`
|
||||
[Bug `1532945 <https://bugs.launchpad.net/bugs/1532945>`_]
|
||||
|
||||
* New `--compute` option to only list compute availability zones.
|
||||
* New `--volume` option to only list volume availability zones.
|
Loading…
Reference in New Issue
Block a user