From 4d332defbc4231f77b7459d4abda88a36a65d37d Mon Sep 17 00:00:00 2001 From: Richard Theis Date: Fri, 15 Jan 2016 15:23:08 -0600 Subject: [PATCH] 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 --- .../command-objects/availability-zone.rst | 7 +- doc/source/commands.rst | 2 +- openstackclient/common/availability_zone.py | 66 ++++++++++++++++--- .../tests/common/test_availability_zone.py | 65 ++++++++++++++++++ openstackclient/tests/network/v2/fakes.py | 53 +++++++++++++++ .../notes/bug-1534202-1ba78f0bb744961f.yaml | 7 ++ 6 files changed, 188 insertions(+), 12 deletions(-) create mode 100644 releasenotes/notes/bug-1534202-1ba78f0bb744961f.yaml diff --git a/doc/source/command-objects/availability-zone.rst b/doc/source/command-objects/availability-zone.rst index 8c021529c8..1f5684383d 100644 --- a/doc/source/command-objects/availability-zone.rst +++ b/doc/source/command-objects/availability-zone.rst @@ -2,7 +2,7 @@ availability zone ================= -Compute v2, Block Storage v2 +Block Storage v2, Compute v2, Network v2 availability zone list ---------------------- @@ -14,6 +14,7 @@ List availability zones and their status os availability zone list [--compute] + [--network] [--volume] [--long] @@ -21,6 +22,10 @@ List availability zones and their status List compute availability zones +.. option:: --network + + List network availability zones + .. option:: --volume List volume availability zones diff --git a/doc/source/commands.rst b/doc/source/commands.rst index f5484b2118..1c4f84b28b 100644 --- a/doc/source/commands.rst +++ b/doc/source/commands.rst @@ -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**, **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 * ``backup``: (**Volume**) a volume copy * ``catalog``: (**Identity**) service catalog diff --git a/openstackclient/common/availability_zone.py b/openstackclient/common/availability_zone.py index fa5aee4726..a941418be2 100644 --- a/openstackclient/common/availability_zone.py +++ b/openstackclient/common/availability_zone.py @@ -30,6 +30,8 @@ def _xform_common_availability_zone(az, zone_info): if hasattr(az, 'zoneName'): zone_info['zone_name'] = az.zoneName + zone_info['zone_resource'] = '' + def _xform_compute_availability_zone(az, include_extra): result = [] @@ -69,6 +71,18 @@ def _xform_volume_availability_zone(az): 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): """List availability zones and their status""" @@ -79,6 +93,11 @@ class ListAvailabilityZone(command.Lister): action='store_true', default=False, help='List compute availability zones') + parser.add_argument( + '--network', + action='store_true', + default=False, + help='List network availability zones') parser.add_argument( '--volume', action='store_true', @@ -92,7 +111,7 @@ class ListAvailabilityZone(command.Lister): ) 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 try: data = compute_client.availability_zones.list() @@ -108,36 +127,63 @@ class ListAvailabilityZone(command.Lister): result += _xform_compute_availability_zone(zone, parsed_args.long) 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 + data = [] try: data = volume_client.availability_zones.list() - except Exception: - message = "Availability zones list not supported by " \ - "Block Storage API" - self.log.warning(message) + except Exception as e: + self.log.debug('Volume availability zone exception: ' + str(e)) + if parsed_args.volume: + 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 + 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): if parsed_args.long: - columns = ('Zone Name', 'Zone Status', + columns = ('Zone Name', 'Zone Status', 'Zone Resource', '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) + show_all = (not parsed_args.compute and + not parsed_args.volume and + not parsed_args.network) result = [] 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: - 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, (utils.get_dict_properties( diff --git a/openstackclient/tests/common/test_availability_zone.py b/openstackclient/tests/common/test_availability_zone.py index 232b56c99c..e44455c7b4 100644 --- a/openstackclient/tests/common/test_availability_zone.py +++ b/openstackclient/tests/common/test_availability_zone.py @@ -11,11 +11,13 @@ # under the License. # +import mock import six from openstackclient.common import availability_zone from openstackclient.tests.compute.v2 import fakes as compute_fakes from openstackclient.tests import fakes +from openstackclient.tests.network.v2 import fakes as network_fakes from openstackclient.tests import utils 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 += ( compute_az.zoneName, 'available', + '', host, service, 'enabled :-) ' + state['updated_at'], @@ -51,6 +54,23 @@ def _build_volume_az_datalist(volume_az, long_datalist=False): datalist = ( volume_az.zoneName, '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,) @@ -79,6 +99,16 @@ class TestAvailabilityZone(utils.TestCommand): self.volume_azs_mock = volume_client.availability_zones 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): @@ -86,11 +116,14 @@ class TestAvailabilityZoneList(TestAvailabilityZone): compute_fakes.FakeAvailabilityZone.create_availability_zones() volume_azs = \ volume_fakes.FakeAvailabilityZone.create_availability_zones(count=1) + network_azs = \ + network_fakes.FakeAvailabilityZone.create_availability_zones() short_columnslist = ('Zone Name', 'Zone Status') long_columnslist = ( 'Zone Name', 'Zone Status', + 'Zone Resource', 'Host Name', 'Service Name', 'Service Status', @@ -101,6 +134,7 @@ class TestAvailabilityZoneList(TestAvailabilityZone): self.compute_azs_mock.list.return_value = self.compute_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 self.cmd = availability_zone.ListAvailabilityZone(self.app, None) @@ -115,6 +149,7 @@ class TestAvailabilityZoneList(TestAvailabilityZone): self.compute_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) datalist = () @@ -122,6 +157,8 @@ class TestAvailabilityZoneList(TestAvailabilityZone): datalist += _build_compute_az_datalist(compute_az) for volume_az in self.volume_azs: 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)) def test_availability_zone_list_long(self): @@ -138,6 +175,7 @@ class TestAvailabilityZoneList(TestAvailabilityZone): self.compute_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) datalist = () @@ -147,6 +185,9 @@ class TestAvailabilityZoneList(TestAvailabilityZone): for volume_az in self.volume_azs: datalist += _build_volume_az_datalist(volume_az, 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)) def test_availability_zone_list_compute(self): @@ -163,6 +204,7 @@ class TestAvailabilityZoneList(TestAvailabilityZone): self.compute_azs_mock.list.assert_called_with() self.volume_azs_mock.list.assert_not_called() + self.network_azs_mock.assert_not_called() self.assertEqual(self.short_columnslist, columns) datalist = () @@ -184,9 +226,32 @@ class TestAvailabilityZoneList(TestAvailabilityZone): self.compute_azs_mock.list.assert_not_called() self.volume_azs_mock.list.assert_called_with() + self.network_azs_mock.assert_not_called() 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)) + + 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)) diff --git a/openstackclient/tests/network/v2/fakes.py b/openstackclient/tests/network/v2/fakes.py index 4c862bd332..6f03d79718 100644 --- a/openstackclient/tests/network/v2/fakes.py +++ b/openstackclient/tests/network/v2/fakes.py @@ -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): """Fake one or more networks.""" diff --git a/releasenotes/notes/bug-1534202-1ba78f0bb744961f.yaml b/releasenotes/notes/bug-1534202-1ba78f0bb744961f.yaml new file mode 100644 index 0000000000..36c0a3dbf7 --- /dev/null +++ b/releasenotes/notes/bug-1534202-1ba78f0bb744961f.yaml @@ -0,0 +1,7 @@ +--- +features: + - | + Add network support to `os availability zone list` + [Bug `1534202 `_] + + * New `--network` option to only list network availability zones.