From 40a1c128283f64ed7d30f37004112429bc1f0628 Mon Sep 17 00:00:00 2001 From: Chris Yeoh Date: Wed, 11 Dec 2013 13:24:56 +1030 Subject: [PATCH] Adds availability zone support for Nova V3 API Adds support and tests for the os-availability-zones extension for the Nova V3 API. Also implements sorting for zone host display which was applied to the v1_1 version, but not the v3 version in I9ab25ef52d6d19b45a39f04cbcde864ee225b4cc Partially implements blueprint v3-api Change-Id: I8daa2503a2dc8767e9157bdfa6c9adaedfc8f3c0 --- .../tests/v1_1/test_availability_zone.py | 40 ++++++++++------- novaclient/tests/v3/fakes.py | 43 +++++++++++++++++++ novaclient/tests/v3/test_availability_zone.py | 38 ++++++++++++++++ novaclient/v1_1/availability_zones.py | 6 ++- novaclient/v3/availability_zones.py | 33 ++++++++++++++ novaclient/v3/client.py | 3 ++ novaclient/v3/shell.py | 34 +++++++-------- 7 files changed, 163 insertions(+), 34 deletions(-) create mode 100644 novaclient/tests/v3/test_availability_zone.py create mode 100644 novaclient/v3/availability_zones.py diff --git a/novaclient/tests/v1_1/test_availability_zone.py b/novaclient/tests/v1_1/test_availability_zone.py index fe9e885f4..b248b6f0e 100644 --- a/novaclient/tests/v1_1/test_availability_zone.py +++ b/novaclient/tests/v1_1/test_availability_zone.py @@ -19,33 +19,43 @@ import six from novaclient.tests import utils from novaclient.tests.v1_1 import fakes from novaclient.v1_1 import availability_zones -from novaclient.v1_1 import shell - - -cs = fakes.FakeClient() class AvailabilityZoneTest(utils.TestCase): + # NOTE(cyeoh): import shell here so the V3 version of + # this class can inherit off the v3 version of shell + from novaclient.v1_1 import shell # noqa + + def setUp(self): + super(AvailabilityZoneTest, self).setUp() + self.cs = self._get_fake_client() + self.availability_zone_type = self._get_availability_zone_type() + + def _get_fake_client(self): + return fakes.FakeClient() + + def _get_availability_zone_type(self): + return availability_zones.AvailabilityZone def _assertZone(self, zone, name, status): self.assertEqual(zone.zoneName, name) self.assertEqual(zone.zoneState, status) def test_list_availability_zone(self): - zones = cs.availability_zones.list(detailed=False) - cs.assert_called('GET', '/os-availability-zone') + zones = self.cs.availability_zones.list(detailed=False) + self.cs.assert_called('GET', '/os-availability-zone') for zone in zones: self.assertTrue(isinstance(zone, - availability_zones.AvailabilityZone)) + self.availability_zone_type)) self.assertEqual(2, len(zones)) l0 = [six.u('zone-1'), six.u('available')] l1 = [six.u('zone-2'), six.u('not available')] - z0 = shell._treeizeAvailabilityZone(zones[0]) - z1 = shell._treeizeAvailabilityZone(zones[1]) + z0 = self.shell._treeizeAvailabilityZone(zones[0]) + z1 = self.shell._treeizeAvailabilityZone(zones[1]) self.assertEqual((len(z0), len(z1)), (1, 1)) @@ -53,12 +63,12 @@ class AvailabilityZoneTest(utils.TestCase): self._assertZone(z1[0], l1[0], l1[1]) def test_detail_availability_zone(self): - zones = cs.availability_zones.list(detailed=True) - cs.assert_called('GET', '/os-availability-zone/detail') + zones = self.cs.availability_zones.list(detailed=True) + self.cs.assert_called('GET', '/os-availability-zone/detail') for zone in zones: self.assertTrue(isinstance(zone, - availability_zones.AvailabilityZone)) + self.availability_zone_type)) self.assertEqual(3, len(zones)) @@ -75,9 +85,9 @@ class AvailabilityZoneTest(utils.TestCase): six.u('enabled XXX 2012-12-26 14:45:24')] l8 = [six.u('zone-2'), six.u('not available')] - z0 = shell._treeizeAvailabilityZone(zones[0]) - z1 = shell._treeizeAvailabilityZone(zones[1]) - z2 = shell._treeizeAvailabilityZone(zones[2]) + z0 = self.shell._treeizeAvailabilityZone(zones[0]) + z1 = self.shell._treeizeAvailabilityZone(zones[1]) + z2 = self.shell._treeizeAvailabilityZone(zones[2]) self.assertEqual((len(z0), len(z1), len(z2)), (3, 5, 1)) diff --git a/novaclient/tests/v3/fakes.py b/novaclient/tests/v3/fakes.py index c2fd0c8cf..e1f5dbb2c 100644 --- a/novaclient/tests/v3/fakes.py +++ b/novaclient/tests/v3/fakes.py @@ -14,6 +14,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from datetime import datetime + from novaclient.openstack.common import strutils from novaclient.tests import fakes from novaclient.tests.v1_1 import fakes as fakes_v1_1 @@ -228,3 +230,44 @@ class FakeHTTPClient(fakes_v1_1.FakeHTTPClient): def delete_servers_1234_os_server_password(self, **kw): return (202, {}, None) + + # + # Availability Zones + # + def get_os_availability_zone(self, **kw): + return (200, {}, {"availability_zone_info": [ + {"zone_name": "zone-1", + "zone_state": {"available": True}, + "hosts": None}, + {"zone_name": "zone-2", + "zone_state": {"available": False}, + "hosts": None}]}) + + def get_os_availability_zone_detail(self, **kw): + return (200, {}, {"availability_zone_info": [ + {"zone_name": "zone-1", + "zone_state": {"available": True}, + "hosts": { + "fake_host-1": { + "nova-compute": {"active": True, + "available": True, + "updated_at": + datetime(2012, 12, 26, 14, 45, 25, 0)}}}}, + {"zone_name": "internal", + "zone_state": {"available": True}, + "hosts": { + "fake_host-1": { + "nova-sched": { + "active": True, + "available": True, + "updated_at": + datetime(2012, 12, 26, 14, 45, 25, 0)}}, + "fake_host-2": { + "nova-network": { + "active": True, + "available": False, + "updated_at": + datetime(2012, 12, 26, 14, 45, 24, 0)}}}}, + {"zone_name": "zone-2", + "zone_state": {"available": False}, + "hosts": None}]}) diff --git a/novaclient/tests/v3/test_availability_zone.py b/novaclient/tests/v3/test_availability_zone.py new file mode 100644 index 000000000..004ba95fa --- /dev/null +++ b/novaclient/tests/v3/test_availability_zone.py @@ -0,0 +1,38 @@ +# Copyright 2011 OpenStack Foundation +# Copyright 2013 IBM Corp. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from novaclient.tests.v1_1 import test_availability_zone +from novaclient.tests.v3 import fakes +from novaclient.v3 import availability_zones + + +class AvailabilityZoneTest(test_availability_zone.AvailabilityZoneTest): + from novaclient.v3 import shell # noqa + + def setUp(self): + super(AvailabilityZoneTest, self).setUp() + self.cs = self._get_fake_client() + self.availability_zone_type = self._get_availability_zone_type() + + def _assertZone(self, zone, name, status): + self.assertEqual(zone.zone_name, name) + self.assertEqual(zone.zone_state, status) + + def _get_fake_client(self): + return fakes.FakeClient() + + def _get_availability_zone_type(self): + return availability_zones.AvailabilityZone diff --git a/novaclient/v1_1/availability_zones.py b/novaclient/v1_1/availability_zones.py index 41362aa25..bf5903785 100644 --- a/novaclient/v1_1/availability_zones.py +++ b/novaclient/v1_1/availability_zones.py @@ -36,6 +36,7 @@ class AvailabilityZoneManager(base.ManagerWithFind): Manage :class:`AvailabilityZone` resources. """ resource_class = AvailabilityZone + return_parameter_name = "availabilityZoneInfo" def list(self, detailed=True): """ @@ -45,6 +46,7 @@ class AvailabilityZoneManager(base.ManagerWithFind): """ if detailed is True: return self._list("/os-availability-zone/detail", - "availabilityZoneInfo") + self.return_parameter_name) else: - return self._list("/os-availability-zone", "availabilityZoneInfo") + return self._list("/os-availability-zone", + self.return_parameter_name) diff --git a/novaclient/v3/availability_zones.py b/novaclient/v3/availability_zones.py new file mode 100644 index 000000000..bd2a9d239 --- /dev/null +++ b/novaclient/v3/availability_zones.py @@ -0,0 +1,33 @@ +# Copyright 2011 OpenStack Foundation +# Copyright 2013 IBM Corp. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +""" +Availability Zone interface. +""" + +from novaclient.v1_1 import availability_zones + + +class AvailabilityZone(availability_zones.AvailabilityZone): + pass + + +class AvailabilityZoneManager(availability_zones.AvailabilityZoneManager): + """ + Manage :class:`AvailabilityZone` resources. + """ + resource_class = AvailabilityZone + return_parameter_name = 'availability_zone_info' diff --git a/novaclient/v3/client.py b/novaclient/v3/client.py index 4cbf8d3e7..8bfb2bb09 100644 --- a/novaclient/v3/client.py +++ b/novaclient/v3/client.py @@ -16,6 +16,7 @@ from novaclient import client from novaclient.v3 import agents +from novaclient.v3 import availability_zones from novaclient.v3 import flavor_access from novaclient.v3 import flavors from novaclient.v3 import hosts @@ -56,6 +57,8 @@ class Client(object): self.os_cache = os_cache or not no_cache #TODO(bnemec): Add back in v3 extensions self.agents = agents.AgentsManager(self) + self.availability_zones = \ + availability_zones.AvailabilityZoneManager(self) self.hosts = hosts.HostManager(self) self.flavors = flavors.FlavorManager(self) self.flavor_access = flavor_access.FlavorAccessManager(self) diff --git a/novaclient/v3/shell.py b/novaclient/v3/shell.py index 52aa45936..8638224dc 100644 --- a/novaclient/v3/shell.py +++ b/novaclient/v3/shell.py @@ -32,8 +32,8 @@ from novaclient.openstack.common import strutils from novaclient.openstack.common import timeutils from novaclient.openstack.common import uuidutils from novaclient import utils -from novaclient.v1_1 import availability_zones from novaclient.v1_1 import quotas +from novaclient.v3 import availability_zones from novaclient.v3 import servers @@ -1433,7 +1433,7 @@ def _translate_volume_snapshot_keys(collection): def _translate_availability_zone_keys(collection): _translate_keys(collection, - [('zoneName', 'name'), ('zoneState', 'status')]) + [('zone_name', 'name'), ('zone_state', 'status')]) @utils.arg('--all-tenants', @@ -3161,40 +3161,40 @@ def _treeizeAvailabilityZone(zone): result = [] # Zone tree view item - az.zoneName = zone.zoneName - az.zoneState = ('available' - if zone.zoneState['available'] else 'not available') - az._info['zoneName'] = az.zoneName - az._info['zoneState'] = az.zoneState + az.zone_name = zone.zone_name + az.zone_state = ('available' + if zone.zone_state['available'] else 'not available') + az._info['zone_name'] = az.zone_name + az._info['zone_state'] = az.zone_state result.append(az) if zone.hosts is not None: - for (host, services) in zone.hosts.items(): + zone_hosts = sorted(zone.hosts.items(), key=lambda x: x[0]) + for (host, services) in zone_hosts: # Host tree view item az = AvailabilityZone(zone.manager, copy.deepcopy(zone._info), zone._loaded) - az.zoneName = '|- %s' % host - az.zoneState = '' - az._info['zoneName'] = az.zoneName - az._info['zoneState'] = az.zoneState + az.zone_name = '|- %s' % host + az.zone_state = '' + az._info['zone_name'] = az.zone_name + az._info['zone_state'] = az.zone_state result.append(az) for (svc, state) in services.items(): # Service tree view item az = AvailabilityZone(zone.manager, copy.deepcopy(zone._info), zone._loaded) - az.zoneName = '| |- %s' % svc - az.zoneState = '%s %s %s' % ( + az.zone_name = '| |- %s' % svc + az.zone_state = '%s %s %s' % ( 'enabled' if state['active'] else 'disabled', ':-)' if state['available'] else 'XXX', state['updated_at']) - az._info['zoneName'] = az.zoneName - az._info['zoneState'] = az.zoneState + az._info['zone_name'] = az.zone_name + az._info['zone_state'] = az.zone_state result.append(az) return result -@utils.service_type('compute') def do_availability_zone_list(cs, _args): """List all the availability zones.""" try: