Support listing network availability zones

Update the "os availability zone list" command to support listing
network availability zones along with the currently listed compute
and volume availability zones. This adds the --network option to
the command in order to only list network availability zones. By
default, all availability zones are listed. The --long option
was also updated to include a "Zone Resource" column which is
applicable to network availability zones. Example zone resources
include "network" and "router".

If the Network API does not support listing availability zones
then a warning message will be issued when the --network option
is specified.

This support requires an updated release of the SDK in order
to pull in [1].

[1] https://bugs.launchpad.net/python-openstacksdk/+bug/1532274

Change-Id: I78811d659b793d9d2111ea54665d5fe7e4887264
Closes-Bug: #1534202
This commit is contained in:
Richard Theis 2016-01-15 15:23:08 -06:00
parent f36177ebdd
commit 4d332defbc
6 changed files with 188 additions and 12 deletions

View File

@ -2,7 +2,7 @@
availability zone availability zone
================= =================
Compute v2, Block Storage v2 Block Storage v2, Compute v2, Network v2
availability zone list availability zone list
---------------------- ----------------------
@ -14,6 +14,7 @@ List availability zones and their status
os availability zone list os availability zone list
[--compute] [--compute]
[--network]
[--volume] [--volume]
[--long] [--long]
@ -21,6 +22,10 @@ List availability zones and their status
List compute availability zones List compute availability zones
.. option:: --network
List network availability zones
.. option:: --volume .. option:: --volume
List volume availability zones List volume availability zones

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. referring to both Compute and Volume quotas.
* ``access token``: (**Identity**) long-lived OAuth-based token * ``access token``: (**Identity**) long-lived OAuth-based token
* ``availability zone``: (**Compute**, **Volume**) a logical partition of hosts or block storage services * ``availability zone``: (**Compute**, **Network**, **Volume**) a logical partition of hosts or block storage or network services
* ``aggregate``: (**Compute**) a grouping of servers * ``aggregate``: (**Compute**) a grouping of servers
* ``backup``: (**Volume**) a volume copy * ``backup``: (**Volume**) a volume copy
* ``catalog``: (**Identity**) service catalog * ``catalog``: (**Identity**) service catalog

View File

@ -30,6 +30,8 @@ def _xform_common_availability_zone(az, zone_info):
if hasattr(az, 'zoneName'): if hasattr(az, 'zoneName'):
zone_info['zone_name'] = az.zoneName zone_info['zone_name'] = az.zoneName
zone_info['zone_resource'] = ''
def _xform_compute_availability_zone(az, include_extra): def _xform_compute_availability_zone(az, include_extra):
result = [] result = []
@ -69,6 +71,18 @@ def _xform_volume_availability_zone(az):
return result return result
def _xform_network_availability_zone(az):
result = []
zone_info = {}
zone_info['zone_name'] = getattr(az, 'name', '')
zone_info['zone_status'] = getattr(az, 'state', '')
if 'unavailable' == zone_info['zone_status']:
zone_info['zone_status'] = 'not available'
zone_info['zone_resource'] = getattr(az, 'resource', '')
result.append(zone_info)
return result
class ListAvailabilityZone(command.Lister): class ListAvailabilityZone(command.Lister):
"""List availability zones and their status""" """List availability zones and their status"""
@ -79,6 +93,11 @@ class ListAvailabilityZone(command.Lister):
action='store_true', action='store_true',
default=False, default=False,
help='List compute availability zones') help='List compute availability zones')
parser.add_argument(
'--network',
action='store_true',
default=False,
help='List network availability zones')
parser.add_argument( parser.add_argument(
'--volume', '--volume',
action='store_true', action='store_true',
@ -92,7 +111,7 @@ class ListAvailabilityZone(command.Lister):
) )
return parser return parser
def get_compute_availability_zones(self, parsed_args): def _get_compute_availability_zones(self, parsed_args):
compute_client = self.app.client_manager.compute compute_client = self.app.client_manager.compute
try: try:
data = compute_client.availability_zones.list() data = compute_client.availability_zones.list()
@ -108,36 +127,63 @@ class ListAvailabilityZone(command.Lister):
result += _xform_compute_availability_zone(zone, parsed_args.long) result += _xform_compute_availability_zone(zone, parsed_args.long)
return result return result
def get_volume_availability_zones(self, parsed_args): def _get_volume_availability_zones(self, parsed_args):
volume_client = self.app.client_manager.volume volume_client = self.app.client_manager.volume
data = []
try: try:
data = volume_client.availability_zones.list() data = volume_client.availability_zones.list()
except Exception: except Exception as e:
message = "Availability zones list not supported by " \ self.log.debug('Volume availability zone exception: ' + str(e))
"Block Storage API" if parsed_args.volume:
self.log.warning(message) message = "Availability zones list not supported by " \
"Block Storage API"
self.log.warning(message)
result = [] result = []
for zone in data: for zone in data:
result += _xform_volume_availability_zone(zone) result += _xform_volume_availability_zone(zone)
return result return result
def _get_network_availability_zones(self, parsed_args):
network_client = self.app.client_manager.network
data = []
try:
# Verify that the extension exists.
network_client.find_extension('Availability Zone',
ignore_missing=False)
data = network_client.availability_zones()
except Exception as e:
self.log.debug('Network availability zone exception: ' + str(e))
if parsed_args.network:
message = "Availability zones list not supported by " \
"Network API"
self.log.warning(message)
result = []
for zone in data:
result += _xform_network_availability_zone(zone)
return result
def take_action(self, parsed_args): def take_action(self, parsed_args):
if parsed_args.long: if parsed_args.long:
columns = ('Zone Name', 'Zone Status', columns = ('Zone Name', 'Zone Status', 'Zone Resource',
'Host Name', 'Service Name', 'Service Status') 'Host Name', 'Service Name', 'Service Status')
else: else:
columns = ('Zone Name', 'Zone Status') columns = ('Zone Name', 'Zone Status')
# Show everything by default. # Show everything by default.
show_all = (not parsed_args.compute and not parsed_args.volume) show_all = (not parsed_args.compute and
not parsed_args.volume and
not parsed_args.network)
result = [] result = []
if parsed_args.compute or show_all: if parsed_args.compute or show_all:
result += self.get_compute_availability_zones(parsed_args) result += self._get_compute_availability_zones(parsed_args)
if parsed_args.volume or show_all: if parsed_args.volume or show_all:
result += self.get_volume_availability_zones(parsed_args) result += self._get_volume_availability_zones(parsed_args)
if parsed_args.network or show_all:
result += self._get_network_availability_zones(parsed_args)
return (columns, return (columns,
(utils.get_dict_properties( (utils.get_dict_properties(

View File

@ -11,11 +11,13 @@
# under the License. # under the License.
# #
import mock
import six import six
from openstackclient.common import availability_zone from openstackclient.common import availability_zone
from openstackclient.tests.compute.v2 import fakes as compute_fakes from openstackclient.tests.compute.v2 import fakes as compute_fakes
from openstackclient.tests import fakes from openstackclient.tests import fakes
from openstackclient.tests.network.v2 import fakes as network_fakes
from openstackclient.tests import utils from openstackclient.tests import utils
from openstackclient.tests.volume.v2 import fakes as volume_fakes from openstackclient.tests.volume.v2 import fakes as volume_fakes
@ -33,6 +35,7 @@ def _build_compute_az_datalist(compute_az, long_datalist=False):
datalist += ( datalist += (
compute_az.zoneName, compute_az.zoneName,
'available', 'available',
'',
host, host,
service, service,
'enabled :-) ' + state['updated_at'], 'enabled :-) ' + state['updated_at'],
@ -51,6 +54,23 @@ def _build_volume_az_datalist(volume_az, long_datalist=False):
datalist = ( datalist = (
volume_az.zoneName, volume_az.zoneName,
'available', 'available',
'', '', '', '',
)
return (datalist,)
def _build_network_az_datalist(network_az, long_datalist=False):
datalist = ()
if not long_datalist:
datalist = (
network_az.name,
network_az.state,
)
else:
datalist = (
network_az.name,
network_az.state,
network_az.resource,
'', '', '', '', '', '',
) )
return (datalist,) return (datalist,)
@ -79,6 +99,16 @@ class TestAvailabilityZone(utils.TestCommand):
self.volume_azs_mock = volume_client.availability_zones self.volume_azs_mock = volume_client.availability_zones
self.volume_azs_mock.reset_mock() self.volume_azs_mock.reset_mock()
network_client = network_fakes.FakeNetworkV2Client(
endpoint=fakes.AUTH_URL,
token=fakes.AUTH_TOKEN,
)
self.app.client_manager.network = network_client
network_client.availability_zones = mock.Mock()
network_client.find_extension = mock.Mock()
self.network_azs_mock = network_client.availability_zones
class TestAvailabilityZoneList(TestAvailabilityZone): class TestAvailabilityZoneList(TestAvailabilityZone):
@ -86,11 +116,14 @@ class TestAvailabilityZoneList(TestAvailabilityZone):
compute_fakes.FakeAvailabilityZone.create_availability_zones() compute_fakes.FakeAvailabilityZone.create_availability_zones()
volume_azs = \ volume_azs = \
volume_fakes.FakeAvailabilityZone.create_availability_zones(count=1) volume_fakes.FakeAvailabilityZone.create_availability_zones(count=1)
network_azs = \
network_fakes.FakeAvailabilityZone.create_availability_zones()
short_columnslist = ('Zone Name', 'Zone Status') short_columnslist = ('Zone Name', 'Zone Status')
long_columnslist = ( long_columnslist = (
'Zone Name', 'Zone Name',
'Zone Status', 'Zone Status',
'Zone Resource',
'Host Name', 'Host Name',
'Service Name', 'Service Name',
'Service Status', 'Service Status',
@ -101,6 +134,7 @@ class TestAvailabilityZoneList(TestAvailabilityZone):
self.compute_azs_mock.list.return_value = self.compute_azs self.compute_azs_mock.list.return_value = self.compute_azs
self.volume_azs_mock.list.return_value = self.volume_azs self.volume_azs_mock.list.return_value = self.volume_azs
self.network_azs_mock.return_value = self.network_azs
# Get the command object to test # Get the command object to test
self.cmd = availability_zone.ListAvailabilityZone(self.app, None) self.cmd = availability_zone.ListAvailabilityZone(self.app, None)
@ -115,6 +149,7 @@ class TestAvailabilityZoneList(TestAvailabilityZone):
self.compute_azs_mock.list.assert_called_with() self.compute_azs_mock.list.assert_called_with()
self.volume_azs_mock.list.assert_called_with() self.volume_azs_mock.list.assert_called_with()
self.network_azs_mock.assert_called_with()
self.assertEqual(self.short_columnslist, columns) self.assertEqual(self.short_columnslist, columns)
datalist = () datalist = ()
@ -122,6 +157,8 @@ class TestAvailabilityZoneList(TestAvailabilityZone):
datalist += _build_compute_az_datalist(compute_az) datalist += _build_compute_az_datalist(compute_az)
for volume_az in self.volume_azs: for volume_az in self.volume_azs:
datalist += _build_volume_az_datalist(volume_az) datalist += _build_volume_az_datalist(volume_az)
for network_az in self.network_azs:
datalist += _build_network_az_datalist(network_az)
self.assertEqual(datalist, tuple(data)) self.assertEqual(datalist, tuple(data))
def test_availability_zone_list_long(self): def test_availability_zone_list_long(self):
@ -138,6 +175,7 @@ class TestAvailabilityZoneList(TestAvailabilityZone):
self.compute_azs_mock.list.assert_called_with() self.compute_azs_mock.list.assert_called_with()
self.volume_azs_mock.list.assert_called_with() self.volume_azs_mock.list.assert_called_with()
self.network_azs_mock.assert_called_with()
self.assertEqual(self.long_columnslist, columns) self.assertEqual(self.long_columnslist, columns)
datalist = () datalist = ()
@ -147,6 +185,9 @@ class TestAvailabilityZoneList(TestAvailabilityZone):
for volume_az in self.volume_azs: for volume_az in self.volume_azs:
datalist += _build_volume_az_datalist(volume_az, datalist += _build_volume_az_datalist(volume_az,
long_datalist=True) long_datalist=True)
for network_az in self.network_azs:
datalist += _build_network_az_datalist(network_az,
long_datalist=True)
self.assertEqual(datalist, tuple(data)) self.assertEqual(datalist, tuple(data))
def test_availability_zone_list_compute(self): def test_availability_zone_list_compute(self):
@ -163,6 +204,7 @@ class TestAvailabilityZoneList(TestAvailabilityZone):
self.compute_azs_mock.list.assert_called_with() self.compute_azs_mock.list.assert_called_with()
self.volume_azs_mock.list.assert_not_called() self.volume_azs_mock.list.assert_not_called()
self.network_azs_mock.assert_not_called()
self.assertEqual(self.short_columnslist, columns) self.assertEqual(self.short_columnslist, columns)
datalist = () datalist = ()
@ -184,9 +226,32 @@ class TestAvailabilityZoneList(TestAvailabilityZone):
self.compute_azs_mock.list.assert_not_called() self.compute_azs_mock.list.assert_not_called()
self.volume_azs_mock.list.assert_called_with() self.volume_azs_mock.list.assert_called_with()
self.network_azs_mock.assert_not_called()
self.assertEqual(self.short_columnslist, columns) self.assertEqual(self.short_columnslist, columns)
datalist = () datalist = ()
for volume_az in self.volume_azs: for volume_az in self.volume_azs:
datalist += _build_volume_az_datalist(volume_az) datalist += _build_volume_az_datalist(volume_az)
self.assertEqual(datalist, tuple(data)) self.assertEqual(datalist, tuple(data))
def test_availability_zone_list_network(self):
arglist = [
'--network',
]
verifylist = [
('network', 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_not_called()
self.network_azs_mock.assert_called_with()
self.assertEqual(self.short_columnslist, columns)
datalist = ()
for network_az in self.network_azs:
datalist += _build_network_az_datalist(network_az)
self.assertEqual(datalist, tuple(data))

View File

@ -69,6 +69,59 @@ class TestNetworkV2(utils.TestCommand):
) )
class FakeAvailabilityZone(object):
"""Fake one or more network 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 name, state, etc.
"""
# Set default attributes.
availability_zone = {
'name': uuid.uuid4().hex,
'state': 'available',
'resource': 'network',
}
# 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
class FakeNetwork(object): class FakeNetwork(object):
"""Fake one or more networks.""" """Fake one or more networks."""

View File

@ -0,0 +1,7 @@
---
features:
- |
Add network support to `os availability zone list`
[Bug `1534202 <https://bugs.launchpad.net/bugs/1534202>`_]
* New `--network` option to only list network availability zones.