diff --git a/doc/api_samples/OS-EXT-AZ/server-get-resp.json b/doc/api_samples/OS-EXT-AZ/server-get-resp.json
new file mode 100644
index 000000000000..a7cf031efd2a
--- /dev/null
+++ b/doc/api_samples/OS-EXT-AZ/server-get-resp.json
@@ -0,0 +1,56 @@
+{
+ "server": {
+ "OS-EXT-AZ:availability_zone": null,
+ "OS-EXT-AZ:host_availability_zone": "nova",
+ "accessIPv4": "",
+ "accessIPv6": "",
+ "addresses": {
+ "private": [
+ {
+ "addr": "192.168.0.3",
+ "version": 4
+ }
+ ]
+ },
+ "created": "2013-01-30T13:38:47Z",
+ "flavor": {
+ "id": "1",
+ "links": [
+ {
+ "href": "http://openstack.example.com/openstack/flavors/1",
+ "rel": "bookmark"
+ }
+ ]
+ },
+ "hostId": "d38ea49a033b0efaf80c165de63f4805c886dfb94dc0fe731227eccb",
+ "id": "fb7babfd-e1a1-4add-90e6-3558180983c7",
+ "image": {
+ "id": "70a599e0-31e7-49b7-b260-868f441e862b",
+ "links": [
+ {
+ "href": "http://openstack.example.com/openstack/images/70a599e0-31e7-49b7-b260-868f441e862b",
+ "rel": "bookmark"
+ }
+ ]
+ },
+ "links": [
+ {
+ "href": "http://openstack.example.com/v2/openstack/servers/fb7babfd-e1a1-4add-90e6-3558180983c7",
+ "rel": "self"
+ },
+ {
+ "href": "http://openstack.example.com/openstack/servers/fb7babfd-e1a1-4add-90e6-3558180983c7",
+ "rel": "bookmark"
+ }
+ ],
+ "metadata": {
+ "My Server Name": "Apache1"
+ },
+ "name": "new-server-test",
+ "progress": 0,
+ "status": "ACTIVE",
+ "tenant_id": "openstack",
+ "updated": "2013-01-30T13:38:49Z",
+ "user_id": "fake"
+ }
+}
\ No newline at end of file
diff --git a/doc/api_samples/OS-EXT-AZ/server-get-resp.xml b/doc/api_samples/OS-EXT-AZ/server-get-resp.xml
new file mode 100644
index 000000000000..7a6edf0571f6
--- /dev/null
+++ b/doc/api_samples/OS-EXT-AZ/server-get-resp.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+ Apache1
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/doc/api_samples/OS-EXT-AZ/server-post-req.json b/doc/api_samples/OS-EXT-AZ/server-post-req.json
new file mode 100644
index 000000000000..d88eb4122223
--- /dev/null
+++ b/doc/api_samples/OS-EXT-AZ/server-post-req.json
@@ -0,0 +1,16 @@
+{
+ "server" : {
+ "name" : "new-server-test",
+ "imageRef" : "http://openstack.example.com/openstack/images/70a599e0-31e7-49b7-b260-868f441e862b",
+ "flavorRef" : "http://openstack.example.com/openstack/flavors/1",
+ "metadata" : {
+ "My Server Name" : "Apache1"
+ },
+ "personality" : [
+ {
+ "path" : "/etc/banner.txt",
+ "contents" : "ICAgICAgDQoiQSBjbG91ZCBkb2VzIG5vdCBrbm93IHdoeSBpdCBtb3ZlcyBpbiBqdXN0IHN1Y2ggYSBkaXJlY3Rpb24gYW5kIGF0IHN1Y2ggYSBzcGVlZC4uLkl0IGZlZWxzIGFuIGltcHVsc2lvbi4uLnRoaXMgaXMgdGhlIHBsYWNlIHRvIGdvIG5vdy4gQnV0IHRoZSBza3kga25vd3MgdGhlIHJlYXNvbnMgYW5kIHRoZSBwYXR0ZXJucyBiZWhpbmQgYWxsIGNsb3VkcywgYW5kIHlvdSB3aWxsIGtub3csIHRvbywgd2hlbiB5b3UgbGlmdCB5b3Vyc2VsZiBoaWdoIGVub3VnaCB0byBzZWUgYmV5b25kIGhvcml6b25zLiINCg0KLVJpY2hhcmQgQmFjaA=="
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/doc/api_samples/OS-EXT-AZ/server-post-req.xml b/doc/api_samples/OS-EXT-AZ/server-post-req.xml
new file mode 100644
index 000000000000..68f0933c7710
--- /dev/null
+++ b/doc/api_samples/OS-EXT-AZ/server-post-req.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/doc/api_samples/OS-EXT-AZ/server-post-resp.json b/doc/api_samples/OS-EXT-AZ/server-post-resp.json
new file mode 100644
index 000000000000..76a723f87d6e
--- /dev/null
+++ b/doc/api_samples/OS-EXT-AZ/server-post-resp.json
@@ -0,0 +1,16 @@
+{
+ "server": {
+ "adminPass": "MgXXGVx9ctdF",
+ "id": "75a88708-cecb-459c-8c3d-b7c93e67edbe",
+ "links": [
+ {
+ "href": "http://openstack.example.com/v2/openstack/servers/75a88708-cecb-459c-8c3d-b7c93e67edbe",
+ "rel": "self"
+ },
+ {
+ "href": "http://openstack.example.com/openstack/servers/75a88708-cecb-459c-8c3d-b7c93e67edbe",
+ "rel": "bookmark"
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/doc/api_samples/OS-EXT-AZ/server-post-resp.xml b/doc/api_samples/OS-EXT-AZ/server-post-resp.xml
new file mode 100644
index 000000000000..d8b1f0837acf
--- /dev/null
+++ b/doc/api_samples/OS-EXT-AZ/server-post-resp.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/doc/api_samples/OS-EXT-AZ/servers-detail-resp.json b/doc/api_samples/OS-EXT-AZ/servers-detail-resp.json
new file mode 100644
index 000000000000..5fab02ac0cce
--- /dev/null
+++ b/doc/api_samples/OS-EXT-AZ/servers-detail-resp.json
@@ -0,0 +1,58 @@
+{
+ "servers": [
+ {
+ "OS-EXT-AZ:availability_zone": null,
+ "OS-EXT-AZ:host_availability_zone": "nova",
+ "accessIPv4": "",
+ "accessIPv6": "",
+ "addresses": {
+ "private": [
+ {
+ "addr": "192.168.0.3",
+ "version": 4
+ }
+ ]
+ },
+ "created": "2013-01-30T13:26:51Z",
+ "flavor": {
+ "id": "1",
+ "links": [
+ {
+ "href": "http://openstack.example.com/openstack/flavors/1",
+ "rel": "bookmark"
+ }
+ ]
+ },
+ "hostId": "60c988a84401fa15888a32833e5848e9caa99a45778310ba7b363165",
+ "id": "3dbf5b00-dabc-41ff-b6ab-4409568fae9d",
+ "image": {
+ "id": "70a599e0-31e7-49b7-b260-868f441e862b",
+ "links": [
+ {
+ "href": "http://openstack.example.com/openstack/images/70a599e0-31e7-49b7-b260-868f441e862b",
+ "rel": "bookmark"
+ }
+ ]
+ },
+ "links": [
+ {
+ "href": "http://openstack.example.com/v2/openstack/servers/3dbf5b00-dabc-41ff-b6ab-4409568fae9d",
+ "rel": "self"
+ },
+ {
+ "href": "http://openstack.example.com/openstack/servers/3dbf5b00-dabc-41ff-b6ab-4409568fae9d",
+ "rel": "bookmark"
+ }
+ ],
+ "metadata": {
+ "My Server Name": "Apache1"
+ },
+ "name": "new-server-test",
+ "progress": 0,
+ "status": "ACTIVE",
+ "tenant_id": "openstack",
+ "updated": "2013-01-30T13:26:52Z",
+ "user_id": "fake"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/doc/api_samples/OS-EXT-AZ/servers-detail-resp.xml b/doc/api_samples/OS-EXT-AZ/servers-detail-resp.xml
new file mode 100644
index 000000000000..4f1f311a81f0
--- /dev/null
+++ b/doc/api_samples/OS-EXT-AZ/servers-detail-resp.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+ Apache1
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/doc/api_samples/all_extensions/extensions-get-resp.json b/doc/api_samples/all_extensions/extensions-get-resp.json
index 604ad676311f..3cb31fc2d7a9 100644
--- a/doc/api_samples/all_extensions/extensions-get-resp.json
+++ b/doc/api_samples/all_extensions/extensions-get-resp.json
@@ -16,6 +16,14 @@
"namespace": "http://docs.openstack.org/compute/ext/disk_config/api/v1.1",
"updated": "2011-09-27T00:00:00+00:00"
},
+ {
+ "alias": "OS-EXT-AZ",
+ "description": "Extended Server Attributes support.",
+ "links": [],
+ "name": "ExtendedAvailabilityZone",
+ "namespace": "http://docs.openstack.org/compute/ext/extended_availability_zone/api/v2",
+ "updated": "2013-01-30T00:00:00+00:00"
+ },
{
"alias": "OS-EXT-SRV-ATTR",
"description": "Extended Server Attributes support.",
diff --git a/doc/api_samples/all_extensions/extensions-get-resp.xml b/doc/api_samples/all_extensions/extensions-get-resp.xml
index d7f4837459fc..8b0b218aa527 100644
--- a/doc/api_samples/all_extensions/extensions-get-resp.xml
+++ b/doc/api_samples/all_extensions/extensions-get-resp.xml
@@ -6,6 +6,9 @@
Disk Management Extension.
+
+ Extended Server Attributes support.
+
Extended Server Attributes support.
diff --git a/doc/api_samples/all_extensions/server-get-resp.json b/doc/api_samples/all_extensions/server-get-resp.json
index a174bd7794a9..56feee20915e 100644
--- a/doc/api_samples/all_extensions/server-get-resp.json
+++ b/doc/api_samples/all_extensions/server-get-resp.json
@@ -1,7 +1,9 @@
{
"server": {
"OS-DCF:diskConfig": "AUTO",
- "OS-EXT-SRV-ATTR:host": "543330fc7412414094e79c867798cefd",
+ "OS-EXT-AZ:availability_zone": null,
+ "OS-EXT-AZ:host_availability_zone": "nova",
+ "OS-EXT-SRV-ATTR:host": "9373c31dbfe6422d9a9997c5f42a8789",
"OS-EXT-SRV-ATTR:hypervisor_hostname": "fake-mini",
"OS-EXT-SRV-ATTR:instance_name": "instance-00000001",
"OS-EXT-STS:power_state": 1,
@@ -18,7 +20,7 @@
]
},
"config_drive": "",
- "created": "2012-11-15T19:28:30Z",
+ "created": "2013-01-30T14:03:57Z",
"flavor": {
"id": "1",
"links": [
@@ -28,8 +30,8 @@
}
]
},
- "hostId": "edc4f072b6ca46a2d95c717401aa9835a204d3e4e6b148a7faba9ab0",
- "id": "05c070bf-1c34-4d99-901c-0f97a7239b86",
+ "hostId": "fc3a98f0b240ff341eb60ce8c0ae7412970d218381c5a827cfd398ee",
+ "id": "61608b15-33ea-412f-b9eb-78c6a347ec7b",
"image": {
"id": "70a599e0-31e7-49b7-b260-868f441e862b",
"links": [
@@ -42,11 +44,11 @@
"key_name": null,
"links": [
{
- "href": "http://openstack.example.com/v2/openstack/servers/05c070bf-1c34-4d99-901c-0f97a7239b86",
+ "href": "http://openstack.example.com/v2/openstack/servers/61608b15-33ea-412f-b9eb-78c6a347ec7b",
"rel": "self"
},
{
- "href": "http://openstack.example.com/openstack/servers/05c070bf-1c34-4d99-901c-0f97a7239b86",
+ "href": "http://openstack.example.com/openstack/servers/61608b15-33ea-412f-b9eb-78c6a347ec7b",
"rel": "bookmark"
}
],
@@ -62,7 +64,7 @@
],
"status": "ACTIVE",
"tenant_id": "openstack",
- "updated": "2012-11-15T19:28:31Z",
+ "updated": "2013-01-30T14:04:01Z",
"user_id": "fake"
}
}
\ No newline at end of file
diff --git a/doc/api_samples/all_extensions/server-get-resp.xml b/doc/api_samples/all_extensions/server-get-resp.xml
index 8751a79ca940..45e873147f4f 100644
--- a/doc/api_samples/all_extensions/server-get-resp.xml
+++ b/doc/api_samples/all_extensions/server-get-resp.xml
@@ -1,5 +1,5 @@
-
+
@@ -14,8 +14,8 @@
-
-
+
+
diff --git a/doc/api_samples/all_extensions/servers-details-resp.json b/doc/api_samples/all_extensions/servers-details-resp.json
index dd0b8ab05183..ed5f4d204aca 100644
--- a/doc/api_samples/all_extensions/servers-details-resp.json
+++ b/doc/api_samples/all_extensions/servers-details-resp.json
@@ -2,7 +2,9 @@
"servers": [
{
"OS-DCF:diskConfig": "AUTO",
- "OS-EXT-SRV-ATTR:host": "f2df66e47d1f427cbd106cf9058360cc",
+ "OS-EXT-AZ:availability_zone": null,
+ "OS-EXT-AZ:host_availability_zone": "nova",
+ "OS-EXT-SRV-ATTR:host": "94d2ccc30d73475ab987661158405463",
"OS-EXT-SRV-ATTR:hypervisor_hostname": "fake-mini",
"OS-EXT-SRV-ATTR:instance_name": "instance-00000001",
"OS-EXT-STS:power_state": 1,
@@ -19,7 +21,7 @@
]
},
"config_drive": "",
- "created": "2012-11-15T19:28:29Z",
+ "created": "2013-01-30T14:03:44Z",
"flavor": {
"id": "1",
"links": [
@@ -29,8 +31,8 @@
}
]
},
- "hostId": "7eedbc35a14388e24ec12917b1eb0bd5dc969619a0e367591d55d9ef",
- "id": "7d7b3dfc-3423-446f-b4b0-7fba038bf8b2",
+ "hostId": "f74e355ecde489405dfc0a1a48f2a85a5e2564e2ac6633d2b3e1b525",
+ "id": "033cc72d-708b-473b-ae8e-41064ea3fa21",
"image": {
"id": "70a599e0-31e7-49b7-b260-868f441e862b",
"links": [
@@ -43,11 +45,11 @@
"key_name": null,
"links": [
{
- "href": "http://openstack.example.com/v2/openstack/servers/7d7b3dfc-3423-446f-b4b0-7fba038bf8b2",
+ "href": "http://openstack.example.com/v2/openstack/servers/033cc72d-708b-473b-ae8e-41064ea3fa21",
"rel": "self"
},
{
- "href": "http://openstack.example.com/openstack/servers/7d7b3dfc-3423-446f-b4b0-7fba038bf8b2",
+ "href": "http://openstack.example.com/openstack/servers/033cc72d-708b-473b-ae8e-41064ea3fa21",
"rel": "bookmark"
}
],
@@ -63,7 +65,7 @@
],
"status": "ACTIVE",
"tenant_id": "openstack",
- "updated": "2012-11-15T19:28:30Z",
+ "updated": "2013-01-30T14:03:50Z",
"user_id": "fake"
}
]
diff --git a/doc/api_samples/all_extensions/servers-details-resp.xml b/doc/api_samples/all_extensions/servers-details-resp.xml
index bbef848d9089..47e452ba12f7 100644
--- a/doc/api_samples/all_extensions/servers-details-resp.xml
+++ b/doc/api_samples/all_extensions/servers-details-resp.xml
@@ -1,6 +1,6 @@
-
-
+
+
@@ -15,8 +15,8 @@
-
-
+
+
diff --git a/etc/nova/policy.json b/etc/nova/policy.json
index 1a446263fce9..7dc7d20f0750 100644
--- a/etc/nova/policy.json
+++ b/etc/nova/policy.json
@@ -42,6 +42,7 @@
"compute_extension:disk_config": "",
"compute_extension:extended_server_attributes": "rule:admin_api",
"compute_extension:extended_status": "",
+ "compute_extension:extended_availability_zone": "",
"compute_extension:fixed_ips": "rule:admin_api",
"compute_extension:flavor_access": "",
"compute_extension:flavor_disabled": "",
diff --git a/nova/api/openstack/compute/contrib/extended_availability_zone.py b/nova/api/openstack/compute/contrib/extended_availability_zone.py
new file mode 100644
index 000000000000..b7451cb6a5f5
--- /dev/null
+++ b/nova/api/openstack/compute/contrib/extended_availability_zone.py
@@ -0,0 +1,106 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 Netease, LLC.
+# 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.
+
+"""The Extended Availability Zone Status API extension."""
+
+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.openstack.common import log as logging
+
+LOG = logging.getLogger(__name__)
+authorize = extensions.soft_extension_authorizer('compute',
+ 'extended_availability_zone')
+
+
+class ExtendedAZController(wsgi.Controller):
+
+ def _get_host_az(self, context, instance):
+ admin_context = context.elevated()
+ if instance['host']:
+ return availability_zones.get_host_availability_zone(
+ admin_context, instance['host'])
+
+ def _extend_server(self, context, server, instance):
+ key = "%s:availability_zone" % Extended_availability_zone.alias
+ server[key] = instance.get('availability_zone', None)
+
+ key = "%s:host_availability_zone" % Extended_availability_zone.alias
+ server[key] = self._get_host_az(context, instance)
+
+ @wsgi.extends
+ def show(self, req, resp_obj, id):
+ context = req.environ['nova.context']
+ if authorize(context):
+ resp_obj.attach(xml=ExtendedAZTemplate())
+ server = resp_obj.obj['server']
+ db_instance = req.get_db_instance(server['id'])
+ self._extend_server(context, server, db_instance)
+
+ @wsgi.extends
+ def detail(self, req, resp_obj):
+ context = req.environ['nova.context']
+ if authorize(context):
+ resp_obj.attach(xml=ExtendedAZsTemplate())
+ servers = list(resp_obj.obj['servers'])
+ for server in servers:
+ db_instance = req.get_db_instance(server['id'])
+ self._extend_server(context, server, db_instance)
+
+
+class Extended_availability_zone(extensions.ExtensionDescriptor):
+ """Extended Server Attributes support."""
+
+ name = "ExtendedAvailabilityZone"
+ alias = "OS-EXT-AZ"
+ namespace = ("http://docs.openstack.org/compute/ext/"
+ "extended_availability_zone/api/v2")
+ updated = "2013-01-30T00:00:00+00:00"
+
+ def get_controller_extensions(self):
+ controller = ExtendedAZController()
+ extension = extensions.ControllerExtension(self, 'servers', controller)
+ return [extension]
+
+
+def make_server(elem):
+ elem.set('{%s}availability_zone' % Extended_availability_zone.namespace,
+ '%s:availability_zone' % Extended_availability_zone.alias)
+ elem.set('{%s}host_availability_zone' %
+ Extended_availability_zone.namespace,
+ '%s:host_availability_zone' %
+ Extended_availability_zone.alias)
+
+
+class ExtendedAZTemplate(xmlutil.TemplateBuilder):
+ def construct(self):
+ root = xmlutil.TemplateElement('server', selector='server')
+ make_server(root)
+ alias = Extended_availability_zone.alias
+ namespace = Extended_availability_zone.namespace
+ return xmlutil.SlaveTemplate(root, 1, nsmap={alias: namespace})
+
+
+class ExtendedAZsTemplate(xmlutil.TemplateBuilder):
+ def construct(self):
+ root = xmlutil.TemplateElement('servers')
+ elem = xmlutil.SubTemplateElement(root, 'server', selector='servers')
+ make_server(elem)
+ alias = Extended_availability_zone.alias
+ namespace = Extended_availability_zone.namespace
+ return xmlutil.SlaveTemplate(root, 1, nsmap={alias: namespace})
diff --git a/nova/tests/api/openstack/compute/contrib/test_extended_availability_zone.py b/nova/tests/api/openstack/compute/contrib/test_extended_availability_zone.py
new file mode 100644
index 000000000000..8ebd810acab4
--- /dev/null
+++ b/nova/tests/api/openstack/compute/contrib/test_extended_availability_zone.py
@@ -0,0 +1,118 @@
+# Copyright 2011 OpenStack LLC.
+# 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 lxml import etree
+import webob
+
+from nova.api.openstack.compute.contrib import extended_availability_zone
+from nova import compute
+from nova import exception
+from nova.openstack.common import jsonutils
+from nova import test
+from nova.tests.api.openstack import fakes
+
+UUID1 = '00000000-0000-0000-0000-000000000001'
+UUID2 = '00000000-0000-0000-0000-000000000002'
+UUID3 = '00000000-0000-0000-0000-000000000003'
+
+
+def fake_compute_get(*args, **kwargs):
+ inst = fakes.stub_instance(1, uuid=UUID3, host="host-fake")
+ inst['availability_zone'] = 'az-i'
+ return inst
+
+
+def fake_compute_get_all(*args, **kwargs):
+ inst1 = fakes.stub_instance(1, uuid=UUID1, host="host-1")
+ inst2 = fakes.stub_instance(2, uuid=UUID2, host="host-2")
+ inst1['availability_zone'] = 'az-i'
+ inst2['availability_zone'] = 'az-i'
+ return [inst1, inst2]
+
+
+class ExtendedServerAttributesTest(test.TestCase):
+ content_type = 'application/json'
+ prefix = 'OS-EXT-AZ:'
+
+ def setUp(self):
+ super(ExtendedServerAttributesTest, self).setUp()
+ fakes.stub_out_nw_api(self.stubs)
+ self.stubs.Set(compute.api.API, 'get', fake_compute_get)
+ self.stubs.Set(compute.api.API, 'get_all', fake_compute_get_all)
+
+ self.flags(
+ osapi_compute_extension=[
+ 'nova.api.openstack.compute.contrib.select_extensions'],
+ osapi_compute_ext_list=['Extended_availability_zone'])
+
+ def _make_request(self, url):
+ req = webob.Request.blank(url)
+ req.headers['Accept'] = self.content_type
+ res = req.get_response(fakes.wsgi_app(init_only=('servers',)))
+ return res
+
+ def _get_server(self, body):
+ return jsonutils.loads(body).get('server')
+
+ def _get_servers(self, body):
+ return jsonutils.loads(body).get('servers')
+
+ def assertServerAttributes(self, server, az_instance, az_host):
+ self.assertEqual(server.get('%savailability_zone' % self.prefix),
+ az_instance)
+ self.assertEqual(server.get('%shost_availability_zone' % self.prefix),
+ az_host)
+
+ def test_show(self):
+ url = '/v2/fake/servers/%s' % UUID3
+ res = self._make_request(url)
+
+ self.assertEqual(res.status_int, 200)
+ self.assertServerAttributes(self._get_server(res.body),
+ az_instance='az-i',
+ az_host='nova')
+
+ def test_detail(self):
+ url = '/v2/fake/servers/detail'
+ res = self._make_request(url)
+
+ self.assertEqual(res.status_int, 200)
+ for i, server in enumerate(self._get_servers(res.body)):
+ self.assertServerAttributes(server,
+ az_instance='az-i',
+ az_host='nova')
+
+ def test_no_instance_passthrough_404(self):
+
+ def fake_compute_get(*args, **kwargs):
+ raise exception.InstanceNotFound(instance_id='fake')
+
+ self.stubs.Set(compute.api.API, 'get', fake_compute_get)
+ url = '/v2/fake/servers/70f6db34-de8d-4fbd-aafb-4065bdfa6115'
+ res = self._make_request(url)
+
+ self.assertEqual(res.status_int, 404)
+
+
+class ExtendedServerAttributesXmlTest(ExtendedServerAttributesTest):
+ content_type = 'application/xml'
+ prefix = '{%s}' % extended_availability_zone.\
+ Extended_availability_zone.namespace
+
+ def _get_server(self, body):
+ return etree.XML(body)
+
+ def _get_servers(self, body):
+ return etree.XML(body).getchildren()
diff --git a/nova/tests/api/openstack/compute/test_extensions.py b/nova/tests/api/openstack/compute/test_extensions.py
index 485968209289..52798b9aade7 100644
--- a/nova/tests/api/openstack/compute/test_extensions.py
+++ b/nova/tests/api/openstack/compute/test_extensions.py
@@ -166,6 +166,7 @@ class ExtensionControllerTest(ExtensionTestCase):
"Createserverext",
"DeferredDelete",
"DiskConfig",
+ "ExtendedAvailabilityZone",
"ExtendedStatus",
"ExtendedServerAttributes",
"FixedIPs",
diff --git a/nova/tests/fake_policy.py b/nova/tests/fake_policy.py
index ead43adeae66..43d9e72b360d 100644
--- a/nova/tests/fake_policy.py
+++ b/nova/tests/fake_policy.py
@@ -119,6 +119,7 @@ policy_data = """
"compute_extension:disk_config": "",
"compute_extension:extended_server_attributes": "",
"compute_extension:extended_status": "",
+ "compute_extension:extended_availability_zone": "",
"compute_extension:fixed_ips": "",
"compute_extension:flavor_access": "",
"compute_extension:flavor_disabled": "",
diff --git a/nova/tests/integrated/api_samples/OS-EXT-AZ/server-get-resp.json.tpl b/nova/tests/integrated/api_samples/OS-EXT-AZ/server-get-resp.json.tpl
new file mode 100644
index 000000000000..25915610dc67
--- /dev/null
+++ b/nova/tests/integrated/api_samples/OS-EXT-AZ/server-get-resp.json.tpl
@@ -0,0 +1,56 @@
+{
+ "server": {
+ "updated": "%(timestamp)s",
+ "created": "%(timestamp)s",
+ "OS-EXT-AZ:availability_zone": null,
+ "OS-EXT-AZ:host_availability_zone": "nova",
+ "accessIPv4": "",
+ "accessIPv6": "",
+ "addresses": {
+ "private": [
+ {
+ "addr": "%(ip)s",
+ "version": 4
+ }
+ ]
+ },
+ "flavor": {
+ "id": "1",
+ "links": [
+ {
+ "href": "%(host)s/openstack/flavors/1",
+ "rel": "bookmark"
+ }
+ ]
+ },
+ "hostId": "%(hostid)s",
+ "id": "%(uuid)s",
+ "image": {
+ "id": "%(uuid)s",
+ "links": [
+ {
+ "href": "%(host)s/openstack/images/%(uuid)s",
+ "rel": "bookmark"
+ }
+ ]
+ },
+ "links": [
+ {
+ "href": "%(host)s/v2/openstack/servers/%(uuid)s",
+ "rel": "self"
+ },
+ {
+ "href": "%(host)s/openstack/servers/%(uuid)s",
+ "rel": "bookmark"
+ }
+ ],
+ "metadata": {
+ "My Server Name": "Apache1"
+ },
+ "name": "new-server-test",
+ "progress": 0,
+ "status": "ACTIVE",
+ "tenant_id": "openstack",
+ "user_id": "fake"
+ }
+}
diff --git a/nova/tests/integrated/api_samples/OS-EXT-AZ/server-get-resp.xml.tpl b/nova/tests/integrated/api_samples/OS-EXT-AZ/server-get-resp.xml.tpl
new file mode 100644
index 000000000000..1cdbd2012eab
--- /dev/null
+++ b/nova/tests/integrated/api_samples/OS-EXT-AZ/server-get-resp.xml.tpl
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+ Apache1
+
+
+
+
+
+
+
+
+
diff --git a/nova/tests/integrated/api_samples/OS-EXT-AZ/server-post-req.json.tpl b/nova/tests/integrated/api_samples/OS-EXT-AZ/server-post-req.json.tpl
new file mode 100644
index 000000000000..d3916d1aa68a
--- /dev/null
+++ b/nova/tests/integrated/api_samples/OS-EXT-AZ/server-post-req.json.tpl
@@ -0,0 +1,16 @@
+{
+ "server" : {
+ "name" : "new-server-test",
+ "imageRef" : "%(host)s/openstack/images/%(image_id)s",
+ "flavorRef" : "%(host)s/openstack/flavors/1",
+ "metadata" : {
+ "My Server Name" : "Apache1"
+ },
+ "personality" : [
+ {
+ "path" : "/etc/banner.txt",
+ "contents" : "ICAgICAgDQoiQSBjbG91ZCBkb2VzIG5vdCBrbm93IHdoeSBpdCBtb3ZlcyBpbiBqdXN0IHN1Y2ggYSBkaXJlY3Rpb24gYW5kIGF0IHN1Y2ggYSBzcGVlZC4uLkl0IGZlZWxzIGFuIGltcHVsc2lvbi4uLnRoaXMgaXMgdGhlIHBsYWNlIHRvIGdvIG5vdy4gQnV0IHRoZSBza3kga25vd3MgdGhlIHJlYXNvbnMgYW5kIHRoZSBwYXR0ZXJucyBiZWhpbmQgYWxsIGNsb3VkcywgYW5kIHlvdSB3aWxsIGtub3csIHRvbywgd2hlbiB5b3UgbGlmdCB5b3Vyc2VsZiBoaWdoIGVub3VnaCB0byBzZWUgYmV5b25kIGhvcml6b25zLiINCg0KLVJpY2hhcmQgQmFjaA=="
+ }
+ ]
+ }
+}
diff --git a/nova/tests/integrated/api_samples/OS-EXT-AZ/server-post-req.xml.tpl b/nova/tests/integrated/api_samples/OS-EXT-AZ/server-post-req.xml.tpl
new file mode 100644
index 000000000000..f92614984242
--- /dev/null
+++ b/nova/tests/integrated/api_samples/OS-EXT-AZ/server-post-req.xml.tpl
@@ -0,0 +1,19 @@
+
+
+
+ Apache1
+
+
+
+ ICAgICAgDQoiQSBjbG91ZCBkb2VzIG5vdCBrbm93IHdoeSBp
+ dCBtb3ZlcyBpbiBqdXN0IHN1Y2ggYSBkaXJlY3Rpb24gYW5k
+ IGF0IHN1Y2ggYSBzcGVlZC4uLkl0IGZlZWxzIGFuIGltcHVs
+ c2lvbi4uLnRoaXMgaXMgdGhlIHBsYWNlIHRvIGdvIG5vdy4g
+ QnV0IHRoZSBza3kga25vd3MgdGhlIHJlYXNvbnMgYW5kIHRo
+ ZSBwYXR0ZXJucyBiZWhpbmQgYWxsIGNsb3VkcywgYW5kIHlv
+ dSB3aWxsIGtub3csIHRvbywgd2hlbiB5b3UgbGlmdCB5b3Vy
+ c2VsZiBoaWdoIGVub3VnaCB0byBzZWUgYmV5b25kIGhvcml6
+ b25zLiINCg0KLVJpY2hhcmQgQmFjaA==
+
+
+
diff --git a/nova/tests/integrated/api_samples/OS-EXT-AZ/server-post-resp.json.tpl b/nova/tests/integrated/api_samples/OS-EXT-AZ/server-post-resp.json.tpl
new file mode 100644
index 000000000000..d5f030c8730b
--- /dev/null
+++ b/nova/tests/integrated/api_samples/OS-EXT-AZ/server-post-resp.json.tpl
@@ -0,0 +1,16 @@
+{
+ "server": {
+ "adminPass": "%(password)s",
+ "id": "%(id)s",
+ "links": [
+ {
+ "href": "%(host)s/v2/openstack/servers/%(uuid)s",
+ "rel": "self"
+ },
+ {
+ "href": "%(host)s/openstack/servers/%(uuid)s",
+ "rel": "bookmark"
+ }
+ ]
+ }
+}
diff --git a/nova/tests/integrated/api_samples/OS-EXT-AZ/server-post-resp.xml.tpl b/nova/tests/integrated/api_samples/OS-EXT-AZ/server-post-resp.xml.tpl
new file mode 100644
index 000000000000..3bb13e69bd6d
--- /dev/null
+++ b/nova/tests/integrated/api_samples/OS-EXT-AZ/server-post-resp.xml.tpl
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/nova/tests/integrated/api_samples/OS-EXT-AZ/servers-detail-resp.json.tpl b/nova/tests/integrated/api_samples/OS-EXT-AZ/servers-detail-resp.json.tpl
new file mode 100644
index 000000000000..895f0a514f55
--- /dev/null
+++ b/nova/tests/integrated/api_samples/OS-EXT-AZ/servers-detail-resp.json.tpl
@@ -0,0 +1,58 @@
+{
+ "servers": [
+ {
+ "updated": "%(timestamp)s",
+ "created": "%(timestamp)s",
+ "OS-EXT-AZ:availability_zone": null,
+ "OS-EXT-AZ:host_availability_zone": "nova",
+ "accessIPv4": "",
+ "accessIPv6": "",
+ "addresses": {
+ "private": [
+ {
+ "addr": "%(ip)s",
+ "version": 4
+ }
+ ]
+ },
+ "flavor": {
+ "id": "1",
+ "links": [
+ {
+ "href": "%(host)s/openstack/flavors/1",
+ "rel": "bookmark"
+ }
+ ]
+ },
+ "hostId": "%(hostid)s",
+ "id": "%(uuid)s",
+ "image": {
+ "id": "%(uuid)s",
+ "links": [
+ {
+ "href": "%(host)s/openstack/images/%(uuid)s",
+ "rel": "bookmark"
+ }
+ ]
+ },
+ "links": [
+ {
+ "href": "%(host)s/v2/openstack/servers/%(id)s",
+ "rel": "self"
+ },
+ {
+ "href": "%(host)s/openstack/servers/%(id)s",
+ "rel": "bookmark"
+ }
+ ],
+ "metadata": {
+ "My Server Name": "Apache1"
+ },
+ "name": "new-server-test",
+ "progress": 0,
+ "status": "ACTIVE",
+ "tenant_id": "openstack",
+ "user_id": "fake"
+ }
+ ]
+}
diff --git a/nova/tests/integrated/api_samples/OS-EXT-AZ/servers-detail-resp.xml.tpl b/nova/tests/integrated/api_samples/OS-EXT-AZ/servers-detail-resp.xml.tpl
new file mode 100644
index 000000000000..15cd9b1e17b1
--- /dev/null
+++ b/nova/tests/integrated/api_samples/OS-EXT-AZ/servers-detail-resp.xml.tpl
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+ Apache1
+
+
+
+
+
+
+
+
+
+
diff --git a/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.json.tpl b/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.json.tpl
index be2fabec4469..50415fef3838 100644
--- a/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.json.tpl
+++ b/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.json.tpl
@@ -16,6 +16,14 @@
"namespace": "http://docs.openstack.org/compute/ext/disk_config/api/v1.1",
"updated": "%(timestamp)s"
},
+ {
+ "alias": "OS-EXT-AZ",
+ "description": "%(text)s",
+ "links": [],
+ "name": "ExtendedAvailabilityZone",
+ "namespace": "http://docs.openstack.org/compute/ext/extended_availability_zone/api/v2",
+ "updated": "%(timestamp)s"
+ },
{
"alias": "OS-EXT-SRV-ATTR",
"description": "%(text)s",
diff --git a/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.xml.tpl b/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.xml.tpl
index ae2e9ff9e937..f7251d13be01 100644
--- a/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.xml.tpl
+++ b/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.xml.tpl
@@ -6,6 +6,9 @@
%(text)s
+
+ %(text)s
+
%(text)s
diff --git a/nova/tests/integrated/api_samples/all_extensions/server-get-resp.json.tpl b/nova/tests/integrated/api_samples/all_extensions/server-get-resp.json.tpl
index 92e62629356b..85fc6f605375 100644
--- a/nova/tests/integrated/api_samples/all_extensions/server-get-resp.json.tpl
+++ b/nova/tests/integrated/api_samples/all_extensions/server-get-resp.json.tpl
@@ -1,6 +1,8 @@
{
"server": {
"OS-DCF:diskConfig": "AUTO",
+ "OS-EXT-AZ:availability_zone": null,
+ "OS-EXT-AZ:host_availability_zone": "nova",
"OS-EXT-SRV-ATTR:host": "%(compute_host)s",
"OS-EXT-SRV-ATTR:hypervisor_hostname": "%(hypervisor_hostname)s",
"OS-EXT-SRV-ATTR:instance_name": "instance-00000001",
diff --git a/nova/tests/integrated/api_samples/all_extensions/server-get-resp.xml.tpl b/nova/tests/integrated/api_samples/all_extensions/server-get-resp.xml.tpl
index 137e59686647..bd73accda505 100644
--- a/nova/tests/integrated/api_samples/all_extensions/server-get-resp.xml.tpl
+++ b/nova/tests/integrated/api_samples/all_extensions/server-get-resp.xml.tpl
@@ -1,5 +1,5 @@
-
+
diff --git a/nova/tests/integrated/api_samples/all_extensions/servers-details-resp.json.tpl b/nova/tests/integrated/api_samples/all_extensions/servers-details-resp.json.tpl
index 8f1583bafd10..a4918203b6e5 100644
--- a/nova/tests/integrated/api_samples/all_extensions/servers-details-resp.json.tpl
+++ b/nova/tests/integrated/api_samples/all_extensions/servers-details-resp.json.tpl
@@ -2,6 +2,8 @@
"servers": [
{
"OS-DCF:diskConfig": "AUTO",
+ "OS-EXT-AZ:availability_zone": null,
+ "OS-EXT-AZ:host_availability_zone": "nova",
"OS-EXT-SRV-ATTR:host": "%(compute_host)s",
"OS-EXT-SRV-ATTR:hypervisor_hostname": "%(hypervisor_hostname)s",
"OS-EXT-SRV-ATTR:instance_name": "instance-00000001",
diff --git a/nova/tests/integrated/api_samples/all_extensions/servers-details-resp.xml.tpl b/nova/tests/integrated/api_samples/all_extensions/servers-details-resp.xml.tpl
index 344686e6687d..d26eb38ef6d0 100644
--- a/nova/tests/integrated/api_samples/all_extensions/servers-details-resp.xml.tpl
+++ b/nova/tests/integrated/api_samples/all_extensions/servers-details-resp.xml.tpl
@@ -1,6 +1,6 @@
-
-
+
+
diff --git a/nova/tests/integrated/test_api_samples.py b/nova/tests/integrated/test_api_samples.py
index a53279e47ea6..f1f86d21b94c 100644
--- a/nova/tests/integrated/test_api_samples.py
+++ b/nova/tests/integrated/test_api_samples.py
@@ -2799,3 +2799,29 @@ class FpingSampleJsonTests(ServersSampleBase):
class FpingSampleXmlTests(FpingSampleJsonTests):
ctype = 'xml'
+
+
+class ExtendedAvailabilityZoneJsonTests(ServersSampleBase):
+ extension_name = ("nova.api.openstack.compute.contrib"
+ ".extended_availability_zone"
+ ".Extended_availability_zone")
+
+ def test_get(self):
+ uuid = self._post_server()
+ response = self._do_get('servers/%s' % uuid)
+ self.assertEqual(response.status, 200)
+ subs = self._get_regexes()
+ subs['hostid'] = '[a-f0-9]+'
+ return self._verify_response('server-get-resp', subs, response)
+
+ def test_detail(self):
+ uuid = self._post_server()
+ response = self._do_get('servers/detail')
+ self.assertEqual(response.status, 200)
+ subs = self._get_regexes()
+ subs['hostid'] = '[a-f0-9]+'
+ return self._verify_response('servers-detail-resp', subs, response)
+
+
+class ExtendedAvailabilityZoneXmlTests(ExtendedAvailabilityZoneJsonTests):
+ ctype = 'xml'