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:
Richard Theis 2016-01-14 08:22:09 -06:00
parent 84174440fc
commit a8ec2ac494
6 changed files with 224 additions and 26 deletions

View File

@ -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

View File

@ -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

View File

@ -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(

View File

@ -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))

View File

@ -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

View 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.