Merge "Adding REST API to show all availability zones of an region"

This commit is contained in:
Jenkins 2013-01-23 16:53:27 +00:00 committed by Gerrit Code Review
commit 4220e0110e
10 changed files with 549 additions and 27 deletions

View File

@ -83,6 +83,8 @@
"compute_extension:virtual_storage_arrays": "",
"compute_extension:volumes": "",
"compute_extension:volumetypes": "",
"compute_extension:availability_zone:list": "",
"compute_extension:availability_zone:detail": "rule:admin_api",
"volume:create": "",

View File

@ -250,32 +250,10 @@ class CloudController(object):
else:
return self._describe_availability_zones(context, **kwargs)
def _get_zones(self, context):
"""Return available and unavailable zones."""
enabled_services = db.service_get_all(context, False)
disabled_services = db.service_get_all(context, True)
enabled_services = availability_zones.set_availability_zones(context,
enabled_services)
disabled_services = availability_zones.set_availability_zones(context,
disabled_services)
available_zones = []
for zone in [service['availability_zone'] for service
in enabled_services]:
if not zone in available_zones:
available_zones.append(zone)
not_available_zones = []
zones = [service['available_zones'] for service in disabled_services
if service['available_zones'] not in available_zones]
for zone in zones:
if zone not in not_available_zones:
not_available_zones.append(zone)
return (available_zones, not_available_zones)
def _describe_availability_zones(self, context, **kwargs):
ctxt = context.elevated()
available_zones, not_available_zones = self._get_zones(ctxt)
available_zones, not_available_zones = \
availability_zones.get_availability_zones(ctxt)
result = []
for zone in available_zones:
@ -291,7 +269,8 @@ class CloudController(object):
def _describe_availability_zones_verbose(self, context, **kwargs):
ctxt = context.elevated()
available_zones, not_available_zones = self._get_zones(ctxt)
available_zones, not_available_zones = \
availability_zones.get_availability_zones(ctxt)
# Available services
enabled_services = db.service_get_all(context, False)

View File

@ -14,14 +14,165 @@
# License for the specific language governing permissions and limitations
# under the License
from nova.api.openstack import common
from nova.api.openstack import extensions
from nova.api.openstack import wsgi
from nova.api.openstack import xmlutil
from nova import availability_zones
from nova import db
from nova.openstack.common import cfg
from nova.openstack.common import log as logging
from nova import servicegroup
LOG = logging.getLogger(__name__)
CONF = cfg.CONF
authorize_list = extensions.extension_authorizer('compute',
'availability_zone:list')
authorize_detail = extensions.extension_authorizer('compute',
'availability_zone:detail')
def make_availability_zone(elem):
elem.set('name', 'zoneName')
zoneStateElem = xmlutil.SubTemplateElement(elem, 'zoneState',
selector='zoneState')
zoneStateElem.set('available')
hostsElem = xmlutil.SubTemplateElement(elem, 'hosts', selector='hosts')
hostElem = xmlutil.SubTemplateElement(hostsElem, 'host',
selector=xmlutil.get_items)
hostElem.set('name', 0)
svcsElem = xmlutil.SubTemplateElement(hostElem, 'services', selector=1)
svcElem = xmlutil.SubTemplateElement(svcsElem, 'service',
selector=xmlutil.get_items)
svcElem.set('name', 0)
svcStateElem = xmlutil.SubTemplateElement(svcElem, 'serviceState',
selector=1)
svcStateElem.set('available')
svcStateElem.set('active')
svcStateElem.set('updated_at')
# Attach metadata node
elem.append(common.MetadataTemplate())
class AvailabilityZonesTemplate(xmlutil.TemplateBuilder):
def construct(self):
root = xmlutil.TemplateElement('availabilityZones')
zoneElem = xmlutil.SubTemplateElement(root, 'availabilityZone',
selector='availabilityZoneInfo')
make_availability_zone(zoneElem)
return xmlutil.MasterTemplate(root, 1, nsmap={
Availability_zone.alias: Availability_zone.namespace})
class AvailabilityZoneController(wsgi.Controller):
"""The Availability Zone API controller for the OpenStack API."""
def __init__(self):
super(AvailabilityZoneController, self).__init__()
self.servicegroup_api = servicegroup.API()
def _describe_availability_zones(self, context, **kwargs):
ctxt = context.elevated()
available_zones, not_available_zones = \
availability_zones.get_availability_zones(ctxt)
result = []
for zone in available_zones:
# Hide internal_service_availability_zone
if zone == CONF.internal_service_availability_zone:
continue
result.append({'zoneName': zone,
'zoneState': {'available': True},
"hosts": None})
for zone in not_available_zones:
result.append({'zoneName': zone,
'zoneState': {'available': False},
"hosts": None})
return {'availabilityZoneInfo': result}
def _describe_availability_zones_verbose(self, context, **kwargs):
ctxt = context.elevated()
available_zones, not_available_zones = \
availability_zones.get_availability_zones(ctxt)
# Available services
enabled_services = db.service_get_all(context, False)
enabled_services = availability_zones.set_availability_zones(context,
enabled_services)
zone_hosts = {}
host_services = {}
for service in enabled_services:
zone_hosts.setdefault(service['availability_zone'], [])
if not service['host'] in zone_hosts[service['availability_zone']]:
zone_hosts[service['availability_zone']].append(
service['host'])
host_services.setdefault(service['availability_zone'] +
service['host'], [])
host_services[service['availability_zone'] + service['host']].\
append(service)
result = []
for zone in available_zones:
hosts = {}
for host in zone_hosts[zone]:
hosts[host] = {}
for service in host_services[zone + host]:
alive = self.servicegroup_api.service_is_up(service)
hosts[host][service['binary']] = {'available': alive,
'active': True != service['disabled'],
'updated_at': service['updated_at']}
result.append({'zoneName': zone,
'zoneState': {'available': True},
"hosts": hosts})
for zone in not_available_zones:
result.append({'zoneName': zone,
'zoneState': {'available': False},
"hosts": None})
return {'availabilityZoneInfo': result}
@wsgi.serializers(xml=AvailabilityZonesTemplate)
def index(self, req):
"""Returns a summary list of availability zone."""
context = req.environ['nova.context']
authorize_list(context)
return self._describe_availability_zones(context)
@wsgi.serializers(xml=AvailabilityZonesTemplate)
def detail(self, req):
"""Returns a detailed list of availability zone."""
context = req.environ['nova.context']
authorize_detail(context)
return self._describe_availability_zones_verbose(context)
class Availability_zone(extensions.ExtensionDescriptor):
"""Add availability_zone to the Create Server v1.1 API."""
"""1. Add availability_zone to the Create Server v1.1 API.
2. Add availability zones describing.
"""
name = "AvailabilityZone"
alias = "os-availability-zone"
namespace = ("http://docs.openstack.org/compute/ext/"
"availabilityzone/api/v1.1")
updated = "2012-08-09T00:00:00+00:00"
updated = "2012-12-21T00:00:00+00:00"
def get_resources(self):
resources = []
res = extensions.ResourceExtension('os-availability-zone',
AvailabilityZoneController(),
collection_actions={'detail': 'GET'})
resources.append(res)
return resources

View File

@ -60,3 +60,25 @@ def get_host_availability_zone(context, host):
return list(metadata['availability_zone'])[0]
else:
return CONF.default_availability_zone
def get_availability_zones(context):
"""Return available and unavailable zones."""
enabled_services = db.service_get_all(context, False)
disabled_services = db.service_get_all(context, True)
enabled_services = set_availability_zones(context, enabled_services)
disabled_services = set_availability_zones(context, disabled_services)
available_zones = []
for zone in [service['availability_zone'] for service
in enabled_services]:
if not zone in available_zones:
available_zones.append(zone)
not_available_zones = []
zones = [service['available_zones'] for service in disabled_services
if service['available_zones'] not in available_zones]
for zone in zones:
if zone not in not_available_zones:
not_available_zones.append(zone)
return (available_zones, not_available_zones)

View File

@ -0,0 +1,244 @@
# Copyright 2012 IBM
# 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 datetime import datetime
from lxml import etree
import webob
from nova.api.openstack.compute.contrib import availability_zone
from nova import availability_zones
from nova import context
from nova import db
from nova.openstack.common import jsonutils
from nova import servicegroup
from nova import test
from nova.tests.api.openstack import fakes
def fake_service_get_all(context, disabled=None):
def __fake_service(binary, availability_zone,
created_at, updated_at, host, disabled):
return {'binary': binary,
'availability_zone': availability_zone,
'available_zones': availability_zone,
'created_at': created_at,
'updated_at': updated_at,
'host': host,
'disabled': disabled}
if disabled:
return [__fake_service("nova-compute", "zone-2",
datetime(2012, 11, 14, 9, 53, 25, 0),
datetime(2012, 12, 26, 14, 45, 25, 0),
"fake_host-1", True),
__fake_service("nova-scheduler", "internal",
datetime(2012, 11, 14, 9, 57, 3, 0),
datetime(2012, 12, 26, 14, 45, 25, 0),
"fake_host-1", True),
__fake_service("nova-network", "internal",
datetime(2012, 11, 16, 7, 25, 46, 0),
datetime(2012, 12, 26, 14, 45, 24, 0),
"fake_host-2", True)]
else:
return [__fake_service("nova-compute", "zone-1",
datetime(2012, 11, 14, 9, 53, 25, 0),
datetime(2012, 12, 26, 14, 45, 25, 0),
"fake_host-1", False),
__fake_service("nova-sched", "internal",
datetime(2012, 11, 14, 9, 57, 03, 0),
datetime(2012, 12, 26, 14, 45, 25, 0),
"fake_host-1", False),
__fake_service("nova-network", "internal",
datetime(2012, 11, 16, 7, 25, 46, 0),
datetime(2012, 12, 26, 14, 45, 24, 0),
"fake_host-2", False)]
def fake_service_is_up(self, service):
return service['binary'] != u"nova-network"
def fake_set_availability_zones(context, services):
return services
class AvailabilityZoneApiTest(test.TestCase):
def setUp(self):
super(AvailabilityZoneApiTest, self).setUp()
self.stubs.Set(db, 'service_get_all', fake_service_get_all)
self.stubs.Set(availability_zones, 'set_availability_zones',
fake_set_availability_zones)
self.stubs.Set(servicegroup.API, 'service_is_up', fake_service_is_up)
def test_availability_zone_index(self):
req = webob.Request.blank('/v2/fake/os-availability-zone')
resp = req.get_response(fakes.wsgi_app())
self.assertEqual(resp.status_int, 200)
resp_dict = jsonutils.loads(resp.body)
self.assertTrue('availabilityZoneInfo' in resp_dict)
zones = resp_dict['availabilityZoneInfo']
self.assertEqual(len(zones), 2)
self.assertEqual(zones[0]['zoneName'], u'zone-1')
self.assertTrue(zones[0]['zoneState']['available'])
self.assertIsNone(zones[0]['hosts'])
self.assertEqual(zones[1]['zoneName'], u'zone-2')
self.assertFalse(zones[1]['zoneState']['available'])
self.assertIsNone(zones[1]['hosts'])
def test_availability_zone_detail(self):
def _formatZone(zone_dict):
result = []
# Zone tree view item
result.append({'zoneName': zone_dict['zoneName'],
'zoneState': u'available'
if zone_dict['zoneState']['available'] else
u'not available'})
if zone_dict['hosts'] is not None:
for (host, services) in zone_dict['hosts'].items():
# Host tree view item
result.append({'zoneName': u'|- %s' % host,
'zoneState': u''})
for (svc, state) in services.items():
# Service tree view item
result.append({'zoneName': u'| |- %s' % svc,
'zoneState': u'%s %s %s' % (
'enabled' if state['active'] else
'disabled',
':-)' if state['available'] else
'XXX',
jsonutils.to_primitive(
state['updated_at']))})
return result
def _assertZone(zone, name, status):
self.assertEqual(zone['zoneName'], name)
self.assertEqual(zone['zoneState'], status)
availabilityZone = availability_zone.AvailabilityZoneController()
req = webob.Request.blank('/v2/fake/os-availability-zone/detail')
req.method = 'GET'
req.environ['nova.context'] = context.get_admin_context()
resp_dict = availabilityZone.detail(req)
self.assertTrue('availabilityZoneInfo' in resp_dict)
zones = resp_dict['availabilityZoneInfo']
self.assertEqual(len(zones), 3)
''' availabilityZoneInfo field content in response body:
[{'zoneName': 'zone-1',
'zoneState': {'available': True},
'hosts': {'fake_host-1': {
'nova-compute': {'active': True, 'available': True,
'updated_at': datetime(2012, 12, 26, 14, 45, 25)}}}},
{'zoneName': 'internal',
'zoneState': {'available': True},
'hosts': {'fake_host-1': {
'nova-sched': {'active': True, 'available': True,
'updated_at': datetime(2012, 12, 26, 14, 45, 25)}},
'fake_host-2': {
'nova-network': {'active': True, 'available': False,
'updated_at': datetime(2012, 12, 26, 14, 45, 24)}}}},
{'zoneName': 'zone-2',
'zoneState': {'available': False},
'hosts': None}]
'''
l0 = [u'zone-1', u'available']
l1 = [u'|- fake_host-1', u'']
l2 = [u'| |- nova-compute', u'enabled :-) 2012-12-26T14:45:25.000000']
l3 = [u'internal', u'available']
l4 = [u'|- fake_host-1', u'']
l5 = [u'| |- nova-sched', u'enabled :-) 2012-12-26T14:45:25.000000']
l6 = [u'|- fake_host-2', u'']
l7 = [u'| |- nova-network', u'enabled XXX 2012-12-26T14:45:24.000000']
l8 = [u'zone-2', u'not available']
z0 = _formatZone(zones[0])
z1 = _formatZone(zones[1])
z2 = _formatZone(zones[2])
self.assertEqual(len(z0), 3)
self.assertEqual(len(z1), 5)
self.assertEqual(len(z2), 1)
_assertZone(z0[0], l0[0], l0[1])
_assertZone(z0[1], l1[0], l1[1])
_assertZone(z0[2], l2[0], l2[1])
_assertZone(z1[0], l3[0], l3[1])
_assertZone(z1[1], l4[0], l4[1])
_assertZone(z1[2], l5[0], l5[1])
_assertZone(z1[3], l6[0], l6[1])
_assertZone(z1[4], l7[0], l7[1])
_assertZone(z2[0], l8[0], l8[1])
class AvailabilityZoneSerializerTest(test.TestCase):
def test_availability_zone_index_detail_serializer(self):
def _verify_zone(zone_dict, tree):
self.assertEqual(tree.tag, 'availabilityZone')
self.assertEqual(zone_dict['zoneName'], tree.get('name'))
self.assertEqual(str(zone_dict['zoneState']['available']),
tree[0].get('available'))
for _idx, host_child in enumerate(tree[1]):
self.assertTrue(host_child.get('name') in zone_dict['hosts'])
svcs = zone_dict['hosts'][host_child.get('name')]
for _idx, svc_child in enumerate(host_child[0]):
self.assertTrue(svc_child.get('name') in svcs)
svc = svcs[svc_child.get('name')]
self.assertEqual(len(svc_child), 1)
self.assertEqual(str(svc['available']),
svc_child[0].get('available'))
self.assertEqual(str(svc['active']),
svc_child[0].get('active'))
self.assertEqual(str(svc['updated_at']),
svc_child[0].get('updated_at'))
serializer = availability_zone.AvailabilityZonesTemplate()
raw_availability_zones = \
[{'zoneName': 'zone-1',
'zoneState': {'available': True},
'hosts': {'fake_host-1': {
'nova-compute': {'active': True, 'available': True,
'updated_at':
datetime(2012, 12, 26, 14, 45, 25)}}}},
{'zoneName': 'internal',
'zoneState': {'available': True},
'hosts': {'fake_host-1': {
'nova-sched': {'active': True, 'available': True,
'updated_at':
datetime(2012, 12, 26, 14, 45, 25)}},
'fake_host-2': {
'nova-network': {'active': True,
'available': False,
'updated_at':
datetime(2012, 12, 26, 14, 45, 24)}}}},
{'zoneName': 'zone-2',
'zoneState': {'available': False},
'hosts': None}]
text = serializer.serialize(
dict(availabilityZoneInfo=raw_availability_zones))
tree = etree.fromstring(text)
self.assertEqual('availabilityZones', tree.tag)
self.assertEqual(len(raw_availability_zones), len(tree))
for idx, child in enumerate(tree):
_verify_zone(raw_availability_zones[idx], child)

View File

@ -158,6 +158,8 @@ policy_data = """
"compute_extension:volumes": "",
"compute_extension:volumetypes": "",
"compute_extension:zones": "",
"compute_extension:availability_zone:list": "",
"compute_extension:availability_zone:detail": "is_admin:True",
"volume:create": "",

View File

@ -0,0 +1,48 @@
{
"availabilityZoneInfo": [
{
"zoneName": "zone-1",
"zoneState": {
"available": true
},
"hosts": {
"fake_host-1": {
"nova-compute": {
"active": true,
"available": true,
"updated_at": "2012-12-26T14:45:25.000000"
}
}
}
},
{
"zoneName": "internal",
"zoneState": {
"available": true
},
"hosts": {
"fake_host-1": {
"nova-sched": {
"active": true,
"available": true,
"updated_at": "2012-12-26T14:45:25.000000"
}
},
"fake_host-2": {
"nova-network": {
"active": true,
"available": false,
"updated_at": "2012-12-26T14:45:24.000000"
}
}
}
},
{
"zoneName": "zone-2",
"zoneState": {
"available": false
},
"hosts": null
}
]
}

View File

@ -0,0 +1,44 @@
<?xml version='1.0' encoding='UTF-8'?>
<availabilityZones
xmlns:os-availability-zone="http://docs.openstack.org/compute/ext/availabilityzone/api/v1.1">
<availabilityZone name="zone-1">
<zoneState available="True" />
<hosts>
<host name="fake_host-1">
<services>
<service name="nova-compute">
<serviceState available="True" active="True"
updated_at="2012-12-26 14:45:25" />
</service>
</services>
</host>
</hosts>
<metadata />
</availabilityZone>
<availabilityZone name="internal">
<zoneState available="True" />
<hosts>
<host name="fake_host-1">
<services>
<service name="nova-sched">
<serviceState available="True" active="True"
updated_at="2012-12-26 14:45:25" />
</service>
</services>
</host>
<host name="fake_host-2">
<services>
<service name="nova-network">
<serviceState available="False" active="True"
updated_at="2012-12-26 14:45:24" />
</service>
</services>
</host>
</hosts>
<metadata />
</availabilityZone>
<availabilityZone name="zone-2">
<zoneState available="False" />
<metadata />
</availabilityZone>
</availabilityZones>

View File

@ -0,0 +1,18 @@
{
"availabilityZoneInfo": [
{
"zoneName": "zone-1",
"zoneState": {
"available": true
},
"hosts": null
},
{
"zoneName": "zone-2",
"zoneState": {
"available": false
},
"hosts": null
}
]
}

View File

@ -0,0 +1,12 @@
<?xml version='1.0' encoding='UTF-8'?>
<availabilityZones
xmlns:os-availability-zone="http://docs.openstack.org/compute/ext/availabilityzone/api/v1.1">
<availabilityZone name="zone-1">
<zoneState available="True" />
<metadata />
</availabilityZone>
<availabilityZone name="zone-2">
<zoneState available="False" />
<metadata />
</availabilityZone>
</availabilityZones>