Skip to content

Commit a8ec2ac

Browse files
committed
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
1 parent 8417444 commit a8ec2ac

6 files changed

Lines changed: 224 additions & 26 deletions

File tree

doc/source/command-objects/availability-zone.rst

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
availability zone
33
=================
44

5-
Compute v2
5+
Compute v2, Block Storage v2
66

77
availability zone list
88
----------------------
@@ -13,8 +13,18 @@ List availability zones and their status
1313
.. code:: bash
1414
1515
os availability zone list
16+
[--compute]
17+
[--volume]
1618
[--long]
1719
20+
.. option:: --compute
21+
22+
List compute availability zones
23+
24+
.. option:: --volume
25+
26+
List volume availability zones
27+
1828
.. option:: --long
1929

2030
List additional fields in output

doc/source/commands.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ the API resources will be merged, as in the ``quota`` object that has options
7070
referring to both Compute and Volume quotas.
7171

7272
* ``access token``: (**Identity**) long-lived OAuth-based token
73-
* ``availability zone``: (**Compute**) a logical partition of hosts or block storage services
73+
* ``availability zone``: (**Compute**, **Volume**) a logical partition of hosts or block storage services
7474
* ``aggregate``: (**Compute**) a grouping of servers
7575
* ``backup``: (**Volume**) a volume copy
7676
* ``catalog``: (**Identity**) service catalog

openstackclient/common/availability_zone.py

Lines changed: 61 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
# under the License.
1212
#
1313

14-
"""Compute v2 Availability Zone action implementations"""
14+
"""Availability Zone action implementations"""
1515

1616
import copy
1717
import logging
@@ -24,15 +24,19 @@
2424
from openstackclient.i18n import _ # noqa
2525

2626

27-
def _xform_availability_zone(az, include_extra):
28-
result = []
29-
zone_info = {}
27+
def _xform_common_availability_zone(az, zone_info):
3028
if hasattr(az, 'zoneState'):
3129
zone_info['zone_status'] = ('available' if az.zoneState['available']
3230
else 'not available')
3331
if hasattr(az, 'zoneName'):
3432
zone_info['zone_name'] = az.zoneName
3533

34+
35+
def _xform_compute_availability_zone(az, include_extra):
36+
result = []
37+
zone_info = {}
38+
_xform_common_availability_zone(az, zone_info)
39+
3640
if not include_extra:
3741
result.append(zone_info)
3842
return result
@@ -58,13 +62,31 @@ def _xform_availability_zone(az, include_extra):
5862
return result
5963

6064

65+
def _xform_volume_availability_zone(az):
66+
result = []
67+
zone_info = {}
68+
_xform_common_availability_zone(az, zone_info)
69+
result.append(zone_info)
70+
return result
71+
72+
6173
class ListAvailabilityZone(lister.Lister):
6274
"""List availability zones and their status"""
6375

6476
log = logging.getLogger(__name__ + '.ListAvailabilityZone')
6577

6678
def get_parser(self, prog_name):
6779
parser = super(ListAvailabilityZone, self).get_parser(prog_name)
80+
parser.add_argument(
81+
'--compute',
82+
action='store_true',
83+
default=False,
84+
help='List compute availability zones')
85+
parser.add_argument(
86+
'--volume',
87+
action='store_true',
88+
default=False,
89+
help='List volume availability zones')
6890
parser.add_argument(
6991
'--long',
7092
action='store_true',
@@ -73,15 +95,7 @@ def get_parser(self, prog_name):
7395
)
7496
return parser
7597

76-
@utils.log_method(log)
77-
def take_action(self, parsed_args):
78-
79-
if parsed_args.long:
80-
columns = ('Zone Name', 'Zone Status',
81-
'Host Name', 'Service Name', 'Service Status')
82-
else:
83-
columns = ('Zone Name', 'Zone Status')
84-
98+
def get_compute_availability_zones(self, parsed_args):
8599
compute_client = self.app.client_manager.compute
86100
try:
87101
data = compute_client.availability_zones.list()
@@ -94,7 +108,40 @@ def take_action(self, parsed_args):
94108
# Argh, the availability zones are not iterable...
95109
result = []
96110
for zone in data:
97-
result += _xform_availability_zone(zone, parsed_args.long)
111+
result += _xform_compute_availability_zone(zone, parsed_args.long)
112+
return result
113+
114+
def get_volume_availability_zones(self, parsed_args):
115+
volume_client = self.app.client_manager.volume
116+
try:
117+
data = volume_client.availability_zones.list()
118+
except Exception:
119+
message = "Availability zones list not supported by " \
120+
"Block Storage API"
121+
self.log.warning(message)
122+
123+
result = []
124+
for zone in data:
125+
result += _xform_volume_availability_zone(zone)
126+
return result
127+
128+
@utils.log_method(log)
129+
def take_action(self, parsed_args):
130+
131+
if parsed_args.long:
132+
columns = ('Zone Name', 'Zone Status',
133+
'Host Name', 'Service Name', 'Service Status')
134+
else:
135+
columns = ('Zone Name', 'Zone Status')
136+
137+
# Show everything by default.
138+
show_all = (not parsed_args.compute and not parsed_args.volume)
139+
140+
result = []
141+
if parsed_args.compute or show_all:
142+
result += self.get_compute_availability_zones(parsed_args)
143+
if parsed_args.volume or show_all:
144+
result += self.get_volume_availability_zones(parsed_args)
98145

99146
return (columns,
100147
(utils.get_dict_properties(

openstackclient/tests/common/test_availability_zone.py

Lines changed: 89 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from openstackclient.tests.compute.v2 import fakes as compute_fakes
1818
from openstackclient.tests import fakes
1919
from openstackclient.tests import utils
20+
from openstackclient.tests.volume.v2 import fakes as volume_fakes
2021

2122

2223
def _build_compute_az_datalist(compute_az, long_datalist=False):
@@ -39,6 +40,22 @@ def _build_compute_az_datalist(compute_az, long_datalist=False):
3940
return (datalist,)
4041

4142

43+
def _build_volume_az_datalist(volume_az, long_datalist=False):
44+
datalist = ()
45+
if not long_datalist:
46+
datalist = (
47+
volume_az.zoneName,
48+
'available',
49+
)
50+
else:
51+
datalist = (
52+
volume_az.zoneName,
53+
'available',
54+
'', '', '',
55+
)
56+
return (datalist,)
57+
58+
4259
class TestAvailabilityZone(utils.TestCommand):
4360

4461
def setUp(self):
@@ -53,16 +70,37 @@ def setUp(self):
5370
self.compute_azs_mock = compute_client.availability_zones
5471
self.compute_azs_mock.reset_mock()
5572

73+
volume_client = volume_fakes.FakeVolumeClient(
74+
endpoint=fakes.AUTH_URL,
75+
token=fakes.AUTH_TOKEN,
76+
)
77+
self.app.client_manager.volume = volume_client
78+
79+
self.volume_azs_mock = volume_client.availability_zones
80+
self.volume_azs_mock.reset_mock()
81+
5682

5783
class TestAvailabilityZoneList(TestAvailabilityZone):
5884

5985
compute_azs = \
6086
compute_fakes.FakeAvailabilityZone.create_availability_zones()
87+
volume_azs = \
88+
volume_fakes.FakeAvailabilityZone.create_availability_zones(count=1)
89+
90+
short_columnslist = ('Zone Name', 'Zone Status')
91+
long_columnslist = (
92+
'Zone Name',
93+
'Zone Status',
94+
'Host Name',
95+
'Service Name',
96+
'Service Status',
97+
)
6198

6299
def setUp(self):
63100
super(TestAvailabilityZoneList, self).setUp()
64101

65102
self.compute_azs_mock.list.return_value = self.compute_azs
103+
self.volume_azs_mock.list.return_value = self.volume_azs
66104

67105
# Get the command object to test
68106
self.cmd = availability_zone.ListAvailabilityZone(self.app, None)
@@ -76,12 +114,14 @@ def test_availability_zone_list_no_options(self):
76114
columns, data = self.cmd.take_action(parsed_args)
77115

78116
self.compute_azs_mock.list.assert_called_with()
117+
self.volume_azs_mock.list.assert_called_with()
79118

80-
columnslist = ('Zone Name', 'Zone Status')
81-
self.assertEqual(columnslist, columns)
119+
self.assertEqual(self.short_columnslist, columns)
82120
datalist = ()
83121
for compute_az in self.compute_azs:
84122
datalist += _build_compute_az_datalist(compute_az)
123+
for volume_az in self.volume_azs:
124+
datalist += _build_volume_az_datalist(volume_az)
85125
self.assertEqual(datalist, tuple(data))
86126

87127
def test_availability_zone_list_long(self):
@@ -97,17 +137,56 @@ def test_availability_zone_list_long(self):
97137
columns, data = self.cmd.take_action(parsed_args)
98138

99139
self.compute_azs_mock.list.assert_called_with()
140+
self.volume_azs_mock.list.assert_called_with()
100141

101-
columnslist = (
102-
'Zone Name',
103-
'Zone Status',
104-
'Host Name',
105-
'Service Name',
106-
'Service Status',
107-
)
108-
self.assertEqual(columnslist, columns)
142+
self.assertEqual(self.long_columnslist, columns)
109143
datalist = ()
110144
for compute_az in self.compute_azs:
111145
datalist += _build_compute_az_datalist(compute_az,
112146
long_datalist=True)
147+
for volume_az in self.volume_azs:
148+
datalist += _build_volume_az_datalist(volume_az,
149+
long_datalist=True)
150+
self.assertEqual(datalist, tuple(data))
151+
152+
def test_availability_zone_list_compute(self):
153+
arglist = [
154+
'--compute',
155+
]
156+
verifylist = [
157+
('compute', True),
158+
]
159+
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
160+
161+
# DisplayCommandBase.take_action() returns two tuples
162+
columns, data = self.cmd.take_action(parsed_args)
163+
164+
self.compute_azs_mock.list.assert_called_with()
165+
self.volume_azs_mock.list.assert_not_called()
166+
167+
self.assertEqual(self.short_columnslist, columns)
168+
datalist = ()
169+
for compute_az in self.compute_azs:
170+
datalist += _build_compute_az_datalist(compute_az)
171+
self.assertEqual(datalist, tuple(data))
172+
173+
def test_availability_zone_list_volume(self):
174+
arglist = [
175+
'--volume',
176+
]
177+
verifylist = [
178+
('volume', True),
179+
]
180+
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
181+
182+
# DisplayCommandBase.take_action() returns two tuples
183+
columns, data = self.cmd.take_action(parsed_args)
184+
185+
self.compute_azs_mock.list.assert_not_called()
186+
self.volume_azs_mock.list.assert_called_with()
187+
188+
self.assertEqual(self.short_columnslist, columns)
189+
datalist = ()
190+
for volume_az in self.volume_azs:
191+
datalist += _build_volume_az_datalist(volume_az)
113192
self.assertEqual(datalist, tuple(data))

openstackclient/tests/volume/v2/fakes.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,8 @@ def __init__(self, **kwargs):
202202
self.restores.resource_class = fakes.FakeResource(None, {})
203203
self.qos_specs = mock.Mock()
204204
self.qos_specs.resource_class = fakes.FakeResource(None, {})
205+
self.availability_zones = mock.Mock()
206+
self.availability_zones.resource_class = fakes.FakeResource(None, {})
205207
self.auth_token = kwargs['token']
206208
self.management_url = kwargs['endpoint']
207209

@@ -304,3 +306,55 @@ def get_volumes(volumes=None, count=2):
304306
volumes = FakeVolume.create_volumes(count)
305307

306308
return mock.MagicMock(side_effect=volumes)
309+
310+
311+
class FakeAvailabilityZone(object):
312+
"""Fake one or more volume availability zones (AZs)."""
313+
314+
@staticmethod
315+
def create_one_availability_zone(attrs={}, methods={}):
316+
"""Create a fake AZ.
317+
318+
:param Dictionary attrs:
319+
A dictionary with all attributes
320+
:param Dictionary methods:
321+
A dictionary with all methods
322+
:return:
323+
A FakeResource object with zoneName, zoneState, etc.
324+
"""
325+
# Set default attributes.
326+
availability_zone = {
327+
'zoneName': uuid.uuid4().hex,
328+
'zoneState': {'available': True},
329+
}
330+
331+
# Overwrite default attributes.
332+
availability_zone.update(attrs)
333+
334+
availability_zone = fakes.FakeResource(
335+
info=copy.deepcopy(availability_zone),
336+
methods=methods,
337+
loaded=True)
338+
return availability_zone
339+
340+
@staticmethod
341+
def create_availability_zones(attrs={}, methods={}, count=2):
342+
"""Create multiple fake AZs.
343+
344+
:param Dictionary attrs:
345+
A dictionary with all attributes
346+
:param Dictionary methods:
347+
A dictionary with all methods
348+
:param int count:
349+
The number of AZs to fake
350+
:return:
351+
A list of FakeResource objects faking the AZs
352+
"""
353+
availability_zones = []
354+
for i in range(0, count):
355+
availability_zone = \
356+
FakeAvailabilityZone.create_one_availability_zone(
357+
attrs, methods)
358+
availability_zones.append(availability_zone)
359+
360+
return availability_zones
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
features:
3+
- |
4+
Add volume support to `os availability zone list`
5+
[Bug `1532945 <https://bugs.launchpad.net/bugs/1532945>`_]
6+
7+
* New `--compute` option to only list compute availability zones.
8+
* New `--volume` option to only list volume availability zones.

0 commit comments

Comments
 (0)