Merge "Support listing network availability zones"

This commit is contained in:
Jenkins 2016-02-04 00:40:09 +00:00 committed by Gerrit Code Review
commit 48697de28f
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,11 +127,14 @@ 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:
self.log.debug('Volume availability zone exception: ' + str(e))
if parsed_args.volume:
message = "Availability zones list not supported by " \ message = "Availability zones list not supported by " \
"Block Storage API" "Block Storage API"
self.log.warning(message) self.log.warning(message)
@ -122,22 +144,46 @@ class ListAvailabilityZone(command.Lister):
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.