Skip to content

Commit ee78fb2

Browse files
Jenkinsopenstack-gerrit
authored andcommitted
Merge "Support listing volume availability zones"
2 parents 09be6a4 + a8ec2ac commit ee78fb2

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)