summaryrefslogtreecommitdiffstats
path: root/nova/tests
diff options
context:
space:
mode:
authorVishvananda Ishaya <vishvananda@gmail.com>2011-07-18 16:58:23 -0700
committerVishvananda Ishaya <vishvananda@gmail.com>2011-07-18 16:58:23 -0700
commitb5ceab5a46ffac11cb229de86c49802bba3fa383 (patch)
tree62459fbead53019efe4efc3e762fe1e07e84a450 /nova/tests
parent67e5492d6723a00b0ad5d7e8c44f5762a9b0a206 (diff)
parent77db06c908f9c08c80beb11241c0e23247129ad6 (diff)
merged trunk
Diffstat (limited to 'nova/tests')
-rw-r--r--nova/tests/api/openstack/contrib/test_floating_ips.py3
-rw-r--r--nova/tests/api/openstack/contrib/test_multinic_xs.py115
-rw-r--r--nova/tests/api/openstack/test_common.py57
-rw-r--r--nova/tests/api/openstack/test_images.py557
-rw-r--r--nova/tests/api/openstack/test_limits.py96
-rw-r--r--nova/tests/api/openstack/test_servers.py292
-rw-r--r--nova/tests/api/openstack/test_wsgi.py141
-rw-r--r--nova/tests/image/test_s3.py122
-rw-r--r--nova/tests/integrated/api/client.py23
-rw-r--r--nova/tests/integrated/test_servers.py19
-rw-r--r--nova/tests/scheduler/test_zone_aware_scheduler.py6
-rw-r--r--nova/tests/test_api.py70
-rw-r--r--nova/tests/test_bdm.py233
-rw-r--r--nova/tests/test_cloud.py447
-rw-r--r--nova/tests/test_compute.py111
-rw-r--r--nova/tests/test_exception.py63
-rw-r--r--nova/tests/test_metadata.py76
-rw-r--r--nova/tests/test_network.py33
-rw-r--r--nova/tests/test_volume.py31
-rw-r--r--nova/tests/test_zones.py175
20 files changed, 2397 insertions, 273 deletions
diff --git a/nova/tests/api/openstack/contrib/test_floating_ips.py b/nova/tests/api/openstack/contrib/test_floating_ips.py
index de1eb2f53..de006d088 100644
--- a/nova/tests/api/openstack/contrib/test_floating_ips.py
+++ b/nova/tests/api/openstack/contrib/test_floating_ips.py
@@ -139,7 +139,9 @@ class FloatingIpTest(test.TestCase):
def test_floating_ip_allocate(self):
req = webob.Request.blank('/v1.1/os-floating-ips')
req.method = 'POST'
+ req.headers['Content-Type'] = 'application/json'
res = req.get_response(fakes.wsgi_app())
+ print res
self.assertEqual(res.status_int, 200)
ip = json.loads(res.body)['allocated']
expected = {
@@ -177,6 +179,7 @@ class FloatingIpTest(test.TestCase):
def test_floating_ip_disassociate(self):
req = webob.Request.blank('/v1.1/os-floating-ips/1/disassociate')
req.method = 'POST'
+ req.headers['Content-Type'] = 'application/json'
res = req.get_response(fakes.wsgi_app())
self.assertEqual(res.status_int, 200)
ip = json.loads(res.body)['disassociated']
diff --git a/nova/tests/api/openstack/contrib/test_multinic_xs.py b/nova/tests/api/openstack/contrib/test_multinic_xs.py
new file mode 100644
index 000000000..b0a9f7676
--- /dev/null
+++ b/nova/tests/api/openstack/contrib/test_multinic_xs.py
@@ -0,0 +1,115 @@
+# 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.
+
+import json
+import stubout
+import webob
+
+from nova import compute
+from nova import context
+from nova import test
+from nova.tests.api.openstack import fakes
+
+
+last_add_fixed_ip = (None, None)
+last_remove_fixed_ip = (None, None)
+
+
+def compute_api_add_fixed_ip(self, context, instance_id, network_id):
+ global last_add_fixed_ip
+
+ last_add_fixed_ip = (instance_id, network_id)
+
+
+def compute_api_remove_fixed_ip(self, context, instance_id, address):
+ global last_remove_fixed_ip
+
+ last_remove_fixed_ip = (instance_id, address)
+
+
+class FixedIpTest(test.TestCase):
+ def setUp(self):
+ super(FixedIpTest, self).setUp()
+ self.stubs = stubout.StubOutForTesting()
+ fakes.FakeAuthManager.reset_fake_data()
+ fakes.FakeAuthDatabase.data = {}
+ fakes.stub_out_networking(self.stubs)
+ fakes.stub_out_rate_limiting(self.stubs)
+ fakes.stub_out_auth(self.stubs)
+ self.stubs.Set(compute.api.API, "add_fixed_ip",
+ compute_api_add_fixed_ip)
+ self.stubs.Set(compute.api.API, "remove_fixed_ip",
+ compute_api_remove_fixed_ip)
+ self.context = context.get_admin_context()
+
+ def tearDown(self):
+ self.stubs.UnsetAll()
+ super(FixedIpTest, self).tearDown()
+
+ def test_add_fixed_ip(self):
+ global last_add_fixed_ip
+ last_add_fixed_ip = (None, None)
+
+ body = dict(addFixedIp=dict(networkId='test_net'))
+ req = webob.Request.blank('/v1.1/servers/test_inst/action')
+ req.method = 'POST'
+ req.body = json.dumps(body)
+ req.headers['content-type'] = 'application/json'
+
+ resp = req.get_response(fakes.wsgi_app())
+ self.assertEqual(resp.status_int, 202)
+ self.assertEqual(last_add_fixed_ip, ('test_inst', 'test_net'))
+
+ def test_add_fixed_ip_no_network(self):
+ global last_add_fixed_ip
+ last_add_fixed_ip = (None, None)
+
+ body = dict(addFixedIp=dict())
+ req = webob.Request.blank('/v1.1/servers/test_inst/action')
+ req.method = 'POST'
+ req.body = json.dumps(body)
+ req.headers['content-type'] = 'application/json'
+
+ resp = req.get_response(fakes.wsgi_app())
+ self.assertEqual(resp.status_int, 422)
+ self.assertEqual(last_add_fixed_ip, (None, None))
+
+ def test_remove_fixed_ip(self):
+ global last_remove_fixed_ip
+ last_remove_fixed_ip = (None, None)
+
+ body = dict(removeFixedIp=dict(address='10.10.10.1'))
+ req = webob.Request.blank('/v1.1/servers/test_inst/action')
+ req.method = 'POST'
+ req.body = json.dumps(body)
+ req.headers['content-type'] = 'application/json'
+
+ resp = req.get_response(fakes.wsgi_app())
+ self.assertEqual(resp.status_int, 202)
+ self.assertEqual(last_remove_fixed_ip, ('test_inst', '10.10.10.1'))
+
+ def test_remove_fixed_ip_no_address(self):
+ global last_remove_fixed_ip
+ last_remove_fixed_ip = (None, None)
+
+ body = dict(removeFixedIp=dict())
+ req = webob.Request.blank('/v1.1/servers/test_inst/action')
+ req.method = 'POST'
+ req.body = json.dumps(body)
+ req.headers['content-type'] = 'application/json'
+
+ resp = req.get_response(fakes.wsgi_app())
+ self.assertEqual(resp.status_int, 422)
+ self.assertEqual(last_remove_fixed_ip, (None, None))
diff --git a/nova/tests/api/openstack/test_common.py b/nova/tests/api/openstack/test_common.py
index 29cb8b944..4c4d03995 100644
--- a/nova/tests/api/openstack/test_common.py
+++ b/nova/tests/api/openstack/test_common.py
@@ -190,3 +190,60 @@ class PaginationParamsTest(test.TestCase):
req = Request.blank('/?limit=20&marker=40')
self.assertEqual(common.get_pagination_params(req),
{'marker': 40, 'limit': 20})
+
+
+class MiscFunctionsTest(test.TestCase):
+
+ def test_remove_version_from_href(self):
+ fixture = 'http://www.testsite.com/v1.1/images'
+ expected = 'http://www.testsite.com/images'
+ actual = common.remove_version_from_href(fixture)
+ self.assertEqual(actual, expected)
+
+ def test_remove_version_from_href_2(self):
+ fixture = 'http://www.testsite.com/v1.1/'
+ expected = 'http://www.testsite.com/'
+ actual = common.remove_version_from_href(fixture)
+ self.assertEqual(actual, expected)
+
+ def test_remove_version_from_href_3(self):
+ fixture = 'http://www.testsite.com/v10.10'
+ expected = 'http://www.testsite.com'
+ actual = common.remove_version_from_href(fixture)
+ self.assertEqual(actual, expected)
+
+ def test_remove_version_from_href_4(self):
+ fixture = 'http://www.testsite.com/v1.1/images/v10.5'
+ expected = 'http://www.testsite.com/images/v10.5'
+ actual = common.remove_version_from_href(fixture)
+ self.assertEqual(actual, expected)
+
+ def test_remove_version_from_href_bad_request(self):
+ fixture = 'http://www.testsite.com/1.1/images'
+ self.assertRaises(ValueError,
+ common.remove_version_from_href,
+ fixture)
+
+ def test_remove_version_from_href_bad_request_2(self):
+ fixture = 'http://www.testsite.com/v/images'
+ self.assertRaises(ValueError,
+ common.remove_version_from_href,
+ fixture)
+
+ def test_remove_version_from_href_bad_request_3(self):
+ fixture = 'http://www.testsite.com/v1.1images'
+ self.assertRaises(ValueError,
+ common.remove_version_from_href,
+ fixture)
+
+ def test_get_id_from_href(self):
+ fixture = 'http://www.testsite.com/dir/45'
+ actual = common.get_id_from_href(fixture)
+ expected = 45
+ self.assertEqual(actual, expected)
+
+ def test_get_id_from_href_bad_request(self):
+ fixture = 'http://45'
+ self.assertRaises(ValueError,
+ common.get_id_from_href,
+ fixture)
diff --git a/nova/tests/api/openstack/test_images.py b/nova/tests/api/openstack/test_images.py
index 54601f35a..534460d46 100644
--- a/nova/tests/api/openstack/test_images.py
+++ b/nova/tests/api/openstack/test_images.py
@@ -401,15 +401,27 @@ class ImageControllerWithGlanceServiceTest(test.TestCase):
href = "http://localhost/v1.1/images/124"
bookmark = "http://localhost/images/124"
+ server_href = "http://localhost/v1.1/servers/42"
+ server_bookmark = "http://localhost/servers/42"
expected_image = {
"image": {
"id": 124,
"name": "queued snapshot",
- "serverRef": "http://localhost/v1.1/servers/42",
"updated": self.NOW_API_FORMAT,
"created": self.NOW_API_FORMAT,
"status": "QUEUED",
+ 'server': {
+ 'id': 42,
+ "links": [{
+ "rel": "self",
+ "href": server_href,
+ },
+ {
+ "rel": "bookmark",
+ "href": server_bookmark,
+ }],
+ },
"metadata": {
"instance_ref": "http://localhost/v1.1/servers/42",
"user_id": "1",
@@ -556,14 +568,16 @@ class ImageControllerWithGlanceServiceTest(test.TestCase):
test_image = {
"id": image["id"],
"name": image["name"],
- "links": [{
- "rel": "self",
- "href": href,
- },
- {
- "rel": "bookmark",
- "href": bookmark,
- }],
+ "links": [
+ {
+ "rel": "self",
+ "href": href,
+ },
+ {
+ "rel": "bookmark",
+ "href": bookmark,
+ },
+ ],
}
self.assertTrue(test_image in response_list)
@@ -628,6 +642,8 @@ class ImageControllerWithGlanceServiceTest(test.TestCase):
response_dict = json.loads(response.body)
response_list = response_dict["images"]
+ server_href = "http://localhost/v1.1/servers/42"
+ server_bookmark = "http://localhost/servers/42"
expected = [{
'id': 123,
@@ -652,10 +668,20 @@ class ImageControllerWithGlanceServiceTest(test.TestCase):
u'instance_ref': u'http://localhost/v1.1/servers/42',
u'user_id': u'1',
},
- 'serverRef': "http://localhost/v1.1/servers/42",
'updated': self.NOW_API_FORMAT,
'created': self.NOW_API_FORMAT,
'status': 'QUEUED',
+ 'server': {
+ 'id': 42,
+ "links": [{
+ "rel": "self",
+ "href": server_href,
+ },
+ {
+ "rel": "bookmark",
+ "href": server_bookmark,
+ }],
+ },
"links": [{
"rel": "self",
"href": "http://localhost/v1.1/images/124",
@@ -672,11 +698,21 @@ class ImageControllerWithGlanceServiceTest(test.TestCase):
u'instance_ref': u'http://localhost/v1.1/servers/42',
u'user_id': u'1',
},
- 'serverRef': "http://localhost/v1.1/servers/42",
'updated': self.NOW_API_FORMAT,
'created': self.NOW_API_FORMAT,
'status': 'SAVING',
'progress': 0,
+ 'server': {
+ 'id': 42,
+ "links": [{
+ "rel": "self",
+ "href": server_href,
+ },
+ {
+ "rel": "bookmark",
+ "href": server_bookmark,
+ }],
+ },
"links": [{
"rel": "self",
"href": "http://localhost/v1.1/images/125",
@@ -693,10 +729,20 @@ class ImageControllerWithGlanceServiceTest(test.TestCase):
u'instance_ref': u'http://localhost/v1.1/servers/42',
u'user_id': u'1',
},
- 'serverRef': "http://localhost/v1.1/servers/42",
'updated': self.NOW_API_FORMAT,
'created': self.NOW_API_FORMAT,
'status': 'ACTIVE',
+ 'server': {
+ 'id': 42,
+ "links": [{
+ "rel": "self",
+ "href": server_href,
+ },
+ {
+ "rel": "bookmark",
+ "href": server_bookmark,
+ }],
+ },
"links": [{
"rel": "self",
"href": "http://localhost/v1.1/images/126",
@@ -713,10 +759,20 @@ class ImageControllerWithGlanceServiceTest(test.TestCase):
u'instance_ref': u'http://localhost/v1.1/servers/42',
u'user_id': u'1',
},
- 'serverRef': "http://localhost/v1.1/servers/42",
'updated': self.NOW_API_FORMAT,
'created': self.NOW_API_FORMAT,
'status': 'FAILED',
+ 'server': {
+ 'id': 42,
+ "links": [{
+ "rel": "self",
+ "href": server_href,
+ },
+ {
+ "rel": "bookmark",
+ "href": server_bookmark,
+ }],
+ },
"links": [{
"rel": "self",
"href": "http://localhost/v1.1/images/127",
@@ -1036,6 +1092,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase):
def test_create_image_v1_1_actual_server_ref(self):
serverRef = 'http://localhost/v1.1/servers/1'
+ serverBookmark = 'http://localhost/servers/1'
body = dict(image=dict(serverRef=serverRef, name='Backup 1'))
req = webob.Request.blank('/v1.1/images')
req.method = 'POST'
@@ -1044,7 +1101,47 @@ class ImageControllerWithGlanceServiceTest(test.TestCase):
response = req.get_response(fakes.wsgi_app())
self.assertEqual(200, response.status_int)
result = json.loads(response.body)
- self.assertEqual(result['image']['serverRef'], serverRef)
+ expected = {
+ 'id': 1,
+ 'links': [
+ {
+ 'rel': 'self',
+ 'href': serverRef,
+ },
+ {
+ 'rel': 'bookmark',
+ 'href': serverBookmark,
+ },
+ ]
+ }
+ self.assertEqual(result['image']['server'], expected)
+
+ def test_create_image_v1_1_actual_server_ref_port(self):
+
+ serverRef = 'http://localhost:8774/v1.1/servers/1'
+ serverBookmark = 'http://localhost:8774/servers/1'
+ body = dict(image=dict(serverRef=serverRef, name='Backup 1'))
+ req = webob.Request.blank('/v1.1/images')
+ req.method = 'POST'
+ req.body = json.dumps(body)
+ req.headers["content-type"] = "application/json"
+ response = req.get_response(fakes.wsgi_app())
+ self.assertEqual(200, response.status_int)
+ result = json.loads(response.body)
+ expected = {
+ 'id': 1,
+ 'links': [
+ {
+ 'rel': 'self',
+ 'href': serverRef,
+ },
+ {
+ 'rel': 'bookmark',
+ 'href': serverBookmark,
+ },
+ ]
+ }
+ self.assertEqual(result['image']['server'], expected)
def test_create_image_v1_1_server_ref_bad_hostname(self):
@@ -1067,6 +1164,28 @@ class ImageControllerWithGlanceServiceTest(test.TestCase):
response = req.get_response(fakes.wsgi_app())
self.assertEqual(400, response.status_int)
+ def test_create_image_v1_1_server_ref_missing_version(self):
+
+ serverRef = 'http://localhost/servers/1'
+ body = dict(image=dict(serverRef=serverRef, name='Backup 1'))
+ req = webob.Request.blank('/v1.1/images')
+ req.method = 'POST'
+ req.body = json.dumps(body)
+ req.headers["content-type"] = "application/json"
+ response = req.get_response(fakes.wsgi_app())
+ self.assertEqual(400, response.status_int)
+
+ def test_create_image_v1_1_server_ref_missing_id(self):
+
+ serverRef = 'http://localhost/v1.1/servers'
+ body = dict(image=dict(serverRef=serverRef, name='Backup 1'))
+ req = webob.Request.blank('/v1.1/images')
+ req.method = 'POST'
+ req.body = json.dumps(body)
+ req.headers["content-type"] = "application/json"
+ response = req.get_response(fakes.wsgi_app())
+ self.assertEqual(400, response.status_int)
+
@classmethod
def _make_image_fixtures(cls):
image_id = 123
@@ -1115,7 +1234,9 @@ class ImageXMLSerializationTest(test.TestCase):
TIMESTAMP = "2010-10-11T10:30:22Z"
SERVER_HREF = 'http://localhost/v1.1/servers/123'
+ SERVER_BOOKMARK = 'http://localhost/servers/123'
IMAGE_HREF = 'http://localhost/v1.1/images/%s'
+ IMAGE_BOOKMARK = 'http://localhost/images/%s'
def test_show(self):
serializer = images.ImageXMLSerializer()
@@ -1126,16 +1247,32 @@ class ImageXMLSerializationTest(test.TestCase):
'name': 'Image1',
'created': self.TIMESTAMP,
'updated': self.TIMESTAMP,
- 'serverRef': self.SERVER_HREF,
'status': 'ACTIVE',
+ 'progress': 80,
+ 'server': {
+ 'id': 1,
+ 'links': [
+ {
+ 'href': self.SERVER_HREF,
+ 'rel': 'self',
+ },
+ {
+ 'href': self.SERVER_BOOKMARK,
+ 'rel': 'bookmark',
+ },
+ ],
+ },
'metadata': {
'key1': 'value1',
},
'links': [
{
- 'href': self.IMAGE_HREF % (1,),
+ 'href': self.IMAGE_HREF % 1,
+ 'rel': 'self',
+ },
+ {
+ 'href': self.IMAGE_BOOKMARK % 1,
'rel': 'bookmark',
- 'type': 'application/json',
},
],
},
@@ -1145,25 +1282,30 @@ class ImageXMLSerializationTest(test.TestCase):
actual = minidom.parseString(output.replace(" ", ""))
expected_server_href = self.SERVER_HREF
- expected_href = self.IMAGE_HREF % (1, )
+ expected_server_bookmark = self.SERVER_BOOKMARK
+ expected_href = self.IMAGE_HREF % 1
+ expected_bookmark = self.IMAGE_BOOKMARK % 1
expected_now = self.TIMESTAMP
expected = minidom.parseString("""
<image id="1"
+ xmlns="http://docs.openstack.org/compute/api/v1.1"
+ xmlns:atom="http://www.w3.org/2005/Atom"
name="Image1"
- serverRef="%(expected_server_href)s"
updated="%(expected_now)s"
created="%(expected_now)s"
status="ACTIVE"
- xmlns="http://docs.openstack.org/compute/api/v1.1">
- <links>
- <link href="%(expected_href)s" rel="bookmark"
- type="application/json" />
- </links>
+ progress="80">
+ <server id="1">
+ <atom:link rel="self" href="%(expected_server_href)s"/>
+ <atom:link rel="bookmark" href="%(expected_server_bookmark)s"/>
+ </server>
<metadata>
<meta key="key1">
value1
</meta>
</metadata>
+ <atom:link href="%(expected_href)s" rel="self"/>
+ <atom:link href="%(expected_bookmark)s" rel="bookmark"/>
</image>
""".replace(" ", "") % (locals()))
@@ -1178,14 +1320,29 @@ class ImageXMLSerializationTest(test.TestCase):
'name': 'Image1',
'created': self.TIMESTAMP,
'updated': self.TIMESTAMP,
- 'serverRef': self.SERVER_HREF,
'status': 'ACTIVE',
+ 'server': {
+ 'id': 1,
+ 'links': [
+ {
+ 'href': self.SERVER_HREF,
+ 'rel': 'self',
+ },
+ {
+ 'href': self.SERVER_BOOKMARK,
+ 'rel': 'bookmark',
+ },
+ ],
+ },
'metadata': {},
'links': [
{
- 'href': self.IMAGE_HREF % (1,),
+ 'href': self.IMAGE_HREF % 1,
+ 'rel': 'self',
+ },
+ {
+ 'href': self.IMAGE_BOOKMARK % 1,
'rel': 'bookmark',
- 'type': 'application/json',
},
],
},
@@ -1195,21 +1352,24 @@ class ImageXMLSerializationTest(test.TestCase):
actual = minidom.parseString(output.replace(" ", ""))
expected_server_href = self.SERVER_HREF
- expected_href = self.IMAGE_HREF % (1, )
+ expected_server_bookmark = self.SERVER_BOOKMARK
+ expected_href = self.IMAGE_HREF % 1
+ expected_bookmark = self.IMAGE_BOOKMARK % 1
expected_now = self.TIMESTAMP
expected = minidom.parseString("""
<image id="1"
+ xmlns="http://docs.openstack.org/compute/api/v1.1"
+ xmlns:atom="http://www.w3.org/2005/Atom"
name="Image1"
- serverRef="%(expected_server_href)s"
updated="%(expected_now)s"
created="%(expected_now)s"
- status="ACTIVE"
- xmlns="http://docs.openstack.org/compute/api/v1.1">
- <links>
- <link href="%(expected_href)s" rel="bookmark"
- type="application/json" />
- </links>
- <metadata />
+ status="ACTIVE">
+ <server id="1">
+ <atom:link rel="self" href="%(expected_server_href)s"/>
+ <atom:link rel="bookmark" href="%(expected_server_bookmark)s"/>
+ </server>
+ <atom:link href="%(expected_href)s" rel="self"/>
+ <atom:link href="%(expected_bookmark)s" rel="bookmark"/>
</image>
""".replace(" ", "") % (locals()))
@@ -1224,16 +1384,30 @@ class ImageXMLSerializationTest(test.TestCase):
'name': 'Image1',
'created': self.TIMESTAMP,
'updated': self.TIMESTAMP,
- 'serverRef': self.SERVER_HREF,
'status': 'ACTIVE',
+ 'server': {
+ 'id': 1,
+ 'links': [
+ {
+ 'href': self.SERVER_HREF,
+ 'rel': 'self',
+ },
+ {
+ 'href': self.SERVER_BOOKMARK,
+ 'rel': 'bookmark',
+ },
+ ],
+ },
'links': [
{
- 'href': self.IMAGE_HREF % (1,),
+ 'href': self.IMAGE_HREF % 1,
+ 'rel': 'self',
+ },
+ {
+ 'href': self.IMAGE_BOOKMARK % 1,
'rel': 'bookmark',
- 'type': 'application/json',
},
],
-
},
}
@@ -1241,21 +1415,76 @@ class ImageXMLSerializationTest(test.TestCase):
actual = minidom.parseString(output.replace(" ", ""))
expected_server_href = self.SERVER_HREF
- expected_href = self.IMAGE_HREF % (1, )
+ expected_server_bookmark = self.SERVER_BOOKMARK
+ expected_href = self.IMAGE_HREF % 1
+ expected_bookmark = self.IMAGE_BOOKMARK % 1
expected_now = self.TIMESTAMP
expected = minidom.parseString("""
<image id="1"
+ xmlns="http://docs.openstack.org/compute/api/v1.1"
+ xmlns:atom="http://www.w3.org/2005/Atom"
name="Image1"
- serverRef="%(expected_server_href)s"
updated="%(expected_now)s"
created="%(expected_now)s"
- status="ACTIVE"
- xmlns="http://docs.openstack.org/compute/api/v1.1">
- <links>
- <link href="%(expected_href)s" rel="bookmark"
- type="application/json" />
- </links>
- <metadata />
+ status="ACTIVE">
+ <server id="1">
+ <atom:link rel="self" href="%(expected_server_href)s"/>
+ <atom:link rel="bookmark" href="%(expected_server_bookmark)s"/>
+ </server>
+ <atom:link href="%(expected_href)s" rel="self"/>
+ <atom:link href="%(expected_bookmark)s" rel="bookmark"/>
+ </image>
+ """.replace(" ", "") % (locals()))
+
+ self.assertEqual(expected.toxml(), actual.toxml())
+
+ def test_show_no_server(self):
+ serializer = images.ImageXMLSerializer()
+
+ fixture = {
+ 'image': {
+ 'id': 1,
+ 'name': 'Image1',
+ 'created': self.TIMESTAMP,
+ 'updated': self.TIMESTAMP,
+ 'status': 'ACTIVE',
+ 'metadata': {
+ 'key1': 'value1',
+ },
+ 'links': [
+ {
+ 'href': self.IMAGE_HREF % 1,
+ 'rel': 'self',
+ },
+ {
+ 'href': self.IMAGE_BOOKMARK % 1,
+ 'rel': 'bookmark',
+ },
+ ],
+ },
+ }
+
+ output = serializer.serialize(fixture, 'show')
+ actual = minidom.parseString(output.replace(" ", ""))
+
+ expected_href = self.IMAGE_HREF % 1
+ expected_bookmark = self.IMAGE_BOOKMARK % 1
+ expected_now = self.TIMESTAMP
+ expected = minidom.parseString("""
+ <image id="1"
+ xmlns="http://docs.openstack.org/compute/api/v1.1"
+ xmlns:atom="http://www.w3.org/2005/Atom"
+ name="Image1"
+ updated="%(expected_now)s"
+ created="%(expected_now)s"
+ status="ACTIVE">
+ <metadata>
+ <meta key="key1">
+ value1
+ </meta>
+ </metadata>
+ <atom:link href="%(expected_href)s" rel="self"/>
+ <atom:link href="%(expected_bookmark)s" rel="bookmark"/>
</image>
""".replace(" ", "") % (locals()))
@@ -1264,70 +1493,51 @@ class ImageXMLSerializationTest(test.TestCase):
def test_index(self):
serializer = images.ImageXMLSerializer()
- fixtures = {
+ fixture = {
'images': [
{
'id': 1,
'name': 'Image1',
- 'created': self.TIMESTAMP,
- 'updated': self.TIMESTAMP,
- 'serverRef': self.SERVER_HREF,
- 'status': 'ACTIVE',
'links': [
{
- 'href': 'http://localhost/v1.1/images/1',
- 'rel': 'bookmark',
- 'type': 'application/json',
+ 'href': self.IMAGE_HREF % 1,
+ 'rel': 'self',
},
],
},
{
'id': 2,
- 'name': 'queued image',
- 'created': self.TIMESTAMP,
- 'updated': self.TIMESTAMP,
- 'serverRef': self.SERVER_HREF,
- 'status': 'QUEUED',
+ 'name': 'Image2',
'links': [
{
- 'href': 'http://localhost/v1.1/images/2',
- 'rel': 'bookmark',
- 'type': 'application/json',
+ 'href': self.IMAGE_HREF % 2,
+ 'rel': 'self',
},
],
},
- ],
+ ]
}
- output = serializer.serialize(fixtures, 'index')
+ output = serializer.serialize(fixture, 'index')
actual = minidom.parseString(output.replace(" ", ""))
- expected_serverRef = self.SERVER_HREF
+ expected_server_href = self.SERVER_HREF
+ expected_server_bookmark = self.SERVER_BOOKMARK
+ expected_href = self.IMAGE_HREF % 1
+ expected_bookmark = self.IMAGE_BOOKMARK % 1
+ expected_href_two = self.IMAGE_HREF % 2
+ expected_bookmark_two = self.IMAGE_BOOKMARK % 2
expected_now = self.TIMESTAMP
expected = minidom.parseString("""
- <images xmlns="http://docs.openstack.org/compute/api/v1.1">
- <image id="1"
- name="Image1"
- serverRef="%(expected_serverRef)s"
- updated="%(expected_now)s"
- created="%(expected_now)s"
- status="ACTIVE">
- <links>
- <link href="http://localhost/v1.1/images/1" rel="bookmark"
- type="application/json" />
- </links>
- </image>
- <image id="2"
- name="queued image"
- serverRef="%(expected_serverRef)s"
- updated="%(expected_now)s"
- created="%(expected_now)s"
- status="QUEUED">
- <links>
- <link href="http://localhost/v1.1/images/2" rel="bookmark"
- type="application/json" />
- </links>
- </image>
+ <images
+ xmlns="http://docs.openstack.org/compute/api/v1.1"
+ xmlns:atom="http://www.w3.org/2005/Atom">
+ <image id="1" name="Image1">
+ <atom:link href="%(expected_href)s" rel="self"/>
+ </image>
+ <image id="2" name="Image2">
+ <atom:link href="%(expected_href_two)s" rel="self"/>
+ </image>
</images>
""".replace(" ", "") % (locals()))
@@ -1343,10 +1553,10 @@ class ImageXMLSerializationTest(test.TestCase):
output = serializer.serialize(fixtures, 'index')
actual = minidom.parseString(output.replace(" ", ""))
- expected_serverRef = self.SERVER_HREF
- expected_now = self.TIMESTAMP
expected = minidom.parseString("""
- <images xmlns="http://docs.openstack.org/compute/api/v1.1" />
+ <images
+ xmlns="http://docs.openstack.org/compute/api/v1.1"
+ xmlns:atom="http://www.w3.org/2005/Atom" />
""".replace(" ", "") % (locals()))
self.assertEqual(expected.toxml(), actual.toxml())
@@ -1354,84 +1564,102 @@ class ImageXMLSerializationTest(test.TestCase):
def test_detail(self):
serializer = images.ImageXMLSerializer()
- fixtures = {
+ fixture = {
'images': [
{
'id': 1,
'name': 'Image1',
'created': self.TIMESTAMP,
'updated': self.TIMESTAMP,
- 'serverRef': self.SERVER_HREF,
'status': 'ACTIVE',
- 'metadata': {
- 'key1': 'value1',
- 'key2': 'value2',
+ 'server': {
+ 'id': 1,
+ 'links': [
+ {
+ 'href': self.SERVER_HREF,
+ 'rel': 'self',
+ },
+ {
+ 'href': self.SERVER_BOOKMARK,
+ 'rel': 'bookmark',
+ },
+ ],
},
'links': [
{
- 'href': 'http://localhost/v1.1/images/1',
+ 'href': self.IMAGE_HREF % 1,
+ 'rel': 'self',
+ },
+ {
+ 'href': self.IMAGE_BOOKMARK % 1,
'rel': 'bookmark',
- 'type': 'application/json',
},
],
},
{
'id': 2,
- 'name': 'queued image',
+ 'name': 'Image2',
'created': self.TIMESTAMP,
'updated': self.TIMESTAMP,
- 'serverRef': self.SERVER_HREF,
- 'metadata': {},
- 'status': 'QUEUED',
+ 'status': 'SAVING',
+ 'progress': 80,
+ 'metadata': {
+ 'key1': 'value1',
+ },
'links': [
{
- 'href': 'http://localhost/v1.1/images/2',
+ 'href': self.IMAGE_HREF % 2,
+ 'rel': 'self',
+ },
+ {
+ 'href': self.IMAGE_BOOKMARK % 2,
'rel': 'bookmark',
- 'type': 'application/json',
},
],
},
- ],
+ ]
}
- output = serializer.serialize(fixtures, 'detail')
+ output = serializer.serialize(fixture, 'detail')
actual = minidom.parseString(output.replace(" ", ""))
- expected_serverRef = self.SERVER_HREF
+ expected_server_href = self.SERVER_HREF
+ expected_server_bookmark = self.SERVER_BOOKMARK
+ expected_href = self.IMAGE_HREF % 1
+ expected_bookmark = self.IMAGE_BOOKMARK % 1
+ expected_href_two = self.IMAGE_HREF % 2
+ expected_bookmark_two = self.IMAGE_BOOKMARK % 2
expected_now = self.TIMESTAMP
expected = minidom.parseString("""
- <images xmlns="http://docs.openstack.org/compute/api/v1.1">
- <image id="1"
- name="Image1"
- serverRef="%(expected_serverRef)s"
- updated="%(expected_now)s"
- created="%(expected_now)s"
- status="ACTIVE">
- <links>
- <link href="http://localhost/v1.1/images/1" rel="bookmark"
- type="application/json" />
- </links>
- <metadata>
- <meta key="key2">
- value2
- </meta>
- <meta key="key1">
- value1
- </meta>
- </metadata>
- </image>
- <image id="2"
- name="queued image"
- serverRef="%(expected_serverRef)s"
- updated="%(expected_now)s"
- created="%(expected_now)s"
- status="QUEUED">
- <links>
- <link href="http://localhost/v1.1/images/2" rel="bookmark"
- type="application/json" />
- </links>
- <metadata />
- </image>
+ <images
+ xmlns="http://docs.openstack.org/compute/api/v1.1"
+ xmlns:atom="http://www.w3.org/2005/Atom">
+ <image id="1"
+ name="Image1"
+ updated="%(expected_now)s"
+ created="%(expected_now)s"
+ status="ACTIVE">
+ <server id="1">
+ <atom:link rel="self" href="%(expected_server_href)s"/>
+ <atom:link rel="bookmark" href="%(expected_server_bookmark)s"/>
+ </server>
+ <atom:link href="%(expected_href)s" rel="self"/>
+ <atom:link href="%(expected_bookmark)s" rel="bookmark"/>
+ </image>
+ <image id="2"
+ name="Image2"
+ updated="%(expected_now)s"
+ created="%(expected_now)s"
+ status="SAVING"
+ progress="80">
+ <metadata>
+ <meta key="key1">
+ value1
+ </meta>
+ </metadata>
+ <atom:link href="%(expected_href_two)s" rel="self"/>
+ <atom:link href="%(expected_bookmark_two)s" rel="bookmark"/>
+ </image>
</images>
""".replace(" ", "") % (locals()))
@@ -1446,16 +1674,32 @@ class ImageXMLSerializationTest(test.TestCase):
'name': 'Image1',
'created': self.TIMESTAMP,
'updated': self.TIMESTAMP,
- 'serverRef': self.SERVER_HREF,
- 'status': 'ACTIVE',
+ 'status': 'SAVING',
+ 'progress': 80,
+ 'server': {
+ 'id': 1,
+ 'links': [
+ {
+ 'href': self.SERVER_HREF,
+ 'rel': 'self',
+ },
+ {
+ 'href': self.SERVER_BOOKMARK,
+ 'rel': 'bookmark',
+ },
+ ],
+ },
'metadata': {
'key1': 'value1',
},
'links': [
{
- 'href': self.IMAGE_HREF % (1,),
+ 'href': self.IMAGE_HREF % 1,
+ 'rel': 'self',
+ },
+ {
+ 'href': self.IMAGE_BOOKMARK % 1,
'rel': 'bookmark',
- 'type': 'application/json',
},
],
},
@@ -1465,25 +1709,30 @@ class ImageXMLSerializationTest(test.TestCase):
actual = minidom.parseString(output.replace(" ", ""))
expected_server_href = self.SERVER_HREF
- expected_href = self.IMAGE_HREF % (1, )
+ expected_server_bookmark = self.SERVER_BOOKMARK
+ expected_href = self.IMAGE_HREF % 1
+ expected_bookmark = self.IMAGE_BOOKMARK % 1
expected_now = self.TIMESTAMP
expected = minidom.parseString("""
<image id="1"
+ xmlns="http://docs.openstack.org/compute/api/v1.1"
+ xmlns:atom="http://www.w3.org/2005/Atom"
name="Image1"
- serverRef="%(expected_server_href)s"
updated="%(expected_now)s"
created="%(expected_now)s"
- status="ACTIVE"
- xmlns="http://docs.openstack.org/compute/api/v1.1">
- <links>
- <link href="%(expected_href)s" rel="bookmark"
- type="application/json" />
- </links>
+ status="SAVING"
+ progress="80">
+ <server id="1">
+ <atom:link rel="self" href="%(expected_server_href)s"/>
+ <atom:link rel="bookmark" href="%(expected_server_bookmark)s"/>
+ </server>
<metadata>
<meta key="key1">
value1
</meta>
</metadata>
+ <atom:link href="%(expected_href)s" rel="self"/>
+ <atom:link href="%(expected_bookmark)s" rel="bookmark"/>
</image>
""".replace(" ", "") % (locals()))
diff --git a/nova/tests/api/openstack/test_limits.py b/nova/tests/api/openstack/test_limits.py
index 38c959fae..76363450d 100644
--- a/nova/tests/api/openstack/test_limits.py
+++ b/nova/tests/api/openstack/test_limits.py
@@ -400,6 +400,10 @@ class LimitsControllerV11Test(BaseLimitTestSuite):
self._test_index_absolute_limits_json(expected)
+class TestLimiter(limits.Limiter):
+ pass
+
+
class LimitMiddlewareTest(BaseLimitTestSuite):
"""
Tests for the `limits.RateLimitingMiddleware` class.
@@ -413,10 +417,14 @@ class LimitMiddlewareTest(BaseLimitTestSuite):
def setUp(self):
"""Prepare middleware for use through fake WSGI app."""
BaseLimitTestSuite.setUp(self)
- _limits = [
- limits.Limit("GET", "*", ".*", 1, 60),
- ]
- self.app = limits.RateLimitingMiddleware(self._empty_app, _limits)
+ _limits = '(GET, *, .*, 1, MINUTE)'
+ self.app = limits.RateLimitingMiddleware(self._empty_app, _limits,
+ "%s.TestLimiter" %
+ self.__class__.__module__)
+
+ def test_limit_class(self):
+ """Test that middleware selected correct limiter class."""
+ assert isinstance(self.app._limiter, TestLimiter)
def test_good_request(self):
"""Test successful GET request through middleware."""
@@ -492,6 +500,72 @@ class LimitTest(BaseLimitTestSuite):
self.assertEqual(4, limit.last_request)
+class ParseLimitsTest(BaseLimitTestSuite):
+ """
+ Tests for the default limits parser in the in-memory
+ `limits.Limiter` class.
+ """
+
+ def test_invalid(self):
+ """Test that parse_limits() handles invalid input correctly."""
+ self.assertRaises(ValueError, limits.Limiter.parse_limits,
+ ';;;;;')
+
+ def test_bad_rule(self):
+ """Test that parse_limits() handles bad rules correctly."""
+ self.assertRaises(ValueError, limits.Limiter.parse_limits,
+ 'GET, *, .*, 20, minute')
+
+ def test_missing_arg(self):
+ """Test that parse_limits() handles missing args correctly."""
+ self.assertRaises(ValueError, limits.Limiter.parse_limits,
+ '(GET, *, .*, 20)')
+
+ def test_bad_value(self):
+ """Test that parse_limits() handles bad values correctly."""
+ self.assertRaises(ValueError, limits.Limiter.parse_limits,
+ '(GET, *, .*, foo, minute)')
+
+ def test_bad_unit(self):
+ """Test that parse_limits() handles bad units correctly."""
+ self.assertRaises(ValueError, limits.Limiter.parse_limits,
+ '(GET, *, .*, 20, lightyears)')
+
+ def test_multiple_rules(self):
+ """Test that parse_limits() handles multiple rules correctly."""
+ try:
+ l = limits.Limiter.parse_limits('(get, *, .*, 20, minute);'
+ '(PUT, /foo*, /foo.*, 10, hour);'
+ '(POST, /bar*, /bar.*, 5, second);'
+ '(Say, /derp*, /derp.*, 1, day)')
+ except ValueError, e:
+ assert False, str(e)
+
+ # Make sure the number of returned limits are correct
+ self.assertEqual(len(l), 4)
+
+ # Check all the verbs...
+ expected = ['GET', 'PUT', 'POST', 'SAY']
+ self.assertEqual([t.verb for t in l], expected)
+
+ # ...the URIs...
+ expected = ['*', '/foo*', '/bar*', '/derp*']
+ self.assertEqual([t.uri for t in l], expected)
+
+ # ...the regexes...
+ expected = ['.*', '/foo.*', '/bar.*', '/derp.*']
+ self.assertEqual([t.regex for t in l], expected)
+
+ # ...the values...
+ expected = [20, 10, 5, 1]
+ self.assertEqual([t.value for t in l], expected)
+
+ # ...and the units...
+ expected = [limits.PER_MINUTE, limits.PER_HOUR,
+ limits.PER_SECOND, limits.PER_DAY]
+ self.assertEqual([t.unit for t in l], expected)
+
+
class LimiterTest(BaseLimitTestSuite):
"""
Tests for the in-memory `limits.Limiter` class.
@@ -500,7 +574,8 @@ class LimiterTest(BaseLimitTestSuite):
def setUp(self):
"""Run before each test."""
BaseLimitTestSuite.setUp(self)
- self.limiter = limits.Limiter(TEST_LIMITS)
+ userlimits = {'user:user3': ''}
+ self.limiter = limits.Limiter(TEST_LIMITS, **userlimits)
def _check(self, num, verb, url, username=None):
"""Check and yield results from checks."""
@@ -605,6 +680,12 @@ class LimiterTest(BaseLimitTestSuite):
results = list(self._check(10, "PUT", "/anything"))
self.assertEqual(expected, results)
+ def test_user_limit(self):
+ """
+ Test user-specific limits.
+ """
+ self.assertEqual(self.limiter.levels['user3'], [])
+
def test_multiple_users(self):
"""
Tests involving multiple users.
@@ -619,6 +700,11 @@ class LimiterTest(BaseLimitTestSuite):
results = list(self._check(15, "PUT", "/anything", "user2"))
self.assertEqual(expected, results)
+ # User3
+ expected = [None] * 20
+ results = list(self._check(20, "PUT", "/anything", "user3"))
+ self.assertEqual(expected, results)
+
self.time += 1.0
# User1 again
diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py
index 1f369c4c8..1577c922b 100644
--- a/nova/tests/api/openstack/test_servers.py
+++ b/nova/tests/api/openstack/test_servers.py
@@ -65,6 +65,18 @@ def return_server_by_uuid(context, uuid):
return stub_instance(id, uuid=uuid)
+def return_virtual_interface_by_instance(interfaces):
+ def _return_virtual_interface_by_instance(context, instance_id):
+ return interfaces
+ return _return_virtual_interface_by_instance
+
+
+def return_virtual_interface_instance_nonexistant(interfaces):
+ def _return_virtual_interface_by_instance(context, instance_id):
+ raise exception.InstanceNotFound(instance_id=instance_id)
+ return _return_virtual_interface_by_instance
+
+
def return_server_with_addresses(private, public):
def _return_server(context, id):
return stub_instance(id, private_address=private,
@@ -72,6 +84,12 @@ def return_server_with_addresses(private, public):
return _return_server
+def return_server_with_interfaces(interfaces):
+ def _return_server(context, id):
+ return stub_instance(id, interfaces=interfaces)
+ return _return_server
+
+
def return_server_with_power_state(power_state):
def _return_server(context, id):
return stub_instance(id, power_state=power_state)
@@ -124,10 +142,13 @@ def instance_addresses(context, instance_id):
def stub_instance(id, user_id=1, private_address=None, public_addresses=None,
host=None, power_state=0, reservation_id="",
- uuid=FAKE_UUID):
+ uuid=FAKE_UUID, interfaces=None):
metadata = []
metadata.append(InstanceMetadata(key='seq', value=id))
+ if interfaces is None:
+ interfaces = []
+
inst_type = instance_types.get_instance_type_by_flavor_id(1)
if public_addresses is None:
@@ -171,7 +192,8 @@ def stub_instance(id, user_id=1, private_address=None, public_addresses=None,
"display_description": "",
"locked": False,
"metadata": metadata,
- "uuid": uuid}
+ "uuid": uuid,
+ "virtual_interfaces": interfaces}
instance["fixed_ips"] = {
"address": private_address,
@@ -411,23 +433,152 @@ class ServersTest(test.TestCase):
self.assertEquals(ip.getAttribute('addr'), private)
def test_get_server_by_id_with_addresses_v1_1(self):
- private = "192.168.0.3"
- public = ["1.2.3.4"]
- new_return_server = return_server_with_addresses(private, public)
+ interfaces = [
+ {
+ 'network': {'label': 'network_1'},
+ 'fixed_ips': [
+ {'address': '192.168.0.3'},
+ {'address': '192.168.0.4'},
+ ],
+ },
+ {
+ 'network': {'label': 'network_2'},
+ 'fixed_ips': [
+ {'address': '172.19.0.1'},
+ {'address': '172.19.0.2'},
+ ],
+ },
+ ]
+ new_return_server = return_server_with_interfaces(interfaces)
self.stubs.Set(nova.db.api, 'instance_get', new_return_server)
+
req = webob.Request.blank('/v1.1/servers/1')
res = req.get_response(fakes.wsgi_app())
+
res_dict = json.loads(res.body)
self.assertEqual(res_dict['server']['id'], 1)
self.assertEqual(res_dict['server']['name'], 'server1')
addresses = res_dict['server']['addresses']
- # RM(4047): Figure otu what is up with the 1.1 api and multi-nic
- #self.assertEqual(len(addresses["public"]), len(public))
- #self.assertEqual(addresses["public"][0],
- # {"version": 4, "addr": public[0]})
- #self.assertEqual(len(addresses["private"]), 1)
- #self.assertEqual(addresses["private"][0],
- # {"version": 4, "addr": private})
+ expected = {
+ 'network_1': [
+ {'addr': '192.168.0.3', 'version': 4},
+ {'addr': '192.168.0.4', 'version': 4},
+ ],
+ 'network_2': [
+ {'addr': '172.19.0.1', 'version': 4},
+ {'addr': '172.19.0.2', 'version': 4},
+ ],
+ }
+
+ self.assertEqual(addresses, expected)
+
+ def test_get_server_addresses_v1_1(self):
+ interfaces = [
+ {
+ 'network': {'label': 'network_1'},
+ 'fixed_ips': [
+ {'address': '192.168.0.3'},
+ {'address': '192.168.0.4'},
+ ],
+ },
+ {
+ 'network': {'label': 'network_2'},
+ 'fixed_ips': [
+ {
+ 'address': '172.19.0.1',
+ 'floating_ips': [
+ {'address': '1.2.3.4'},
+ ],
+ },
+ {'address': '172.19.0.2'},
+ ],
+ },
+ ]
+
+ _return_vifs = return_virtual_interface_by_instance(interfaces)
+ self.stubs.Set(nova.db.api,
+ 'virtual_interface_get_by_instance',
+ _return_vifs)
+
+ req = webob.Request.blank('/v1.1/servers/1/ips')
+ res = req.get_response(fakes.wsgi_app())
+ res_dict = json.loads(res.body)
+
+ expected = {
+ 'addresses': {
+ 'network_1': [
+ {'version': 4, 'addr': '192.168.0.3'},
+ {'version': 4, 'addr': '192.168.0.4'},
+ ],
+ 'network_2': [
+ {'version': 4, 'addr': '172.19.0.1'},
+ {'version': 4, 'addr': '1.2.3.4'},
+ {'version': 4, 'addr': '172.19.0.2'},
+ ],
+ },
+ }
+
+ self.assertEqual(res_dict, expected)
+
+ def test_get_server_addresses_single_network_v1_1(self):
+ interfaces = [
+ {
+ 'network': {'label': 'network_1'},
+ 'fixed_ips': [
+ {'address': '192.168.0.3'},
+ {'address': '192.168.0.4'},
+ ],
+ },
+ {
+ 'network': {'label': 'network_2'},
+ 'fixed_ips': [
+ {
+ 'address': '172.19.0.1',
+ 'floating_ips': [
+ {'address': '1.2.3.4'},
+ ],
+ },
+ {'address': '172.19.0.2'},
+ ],
+ },
+ ]
+ _return_vifs = return_virtual_interface_by_instance(interfaces)
+ self.stubs.Set(nova.db.api,
+ 'virtual_interface_get_by_instance',
+ _return_vifs)
+
+ req = webob.Request.blank('/v1.1/servers/1/ips/network_2')
+ res = req.get_response(fakes.wsgi_app())
+ self.assertEqual(res.status_int, 200)
+ res_dict = json.loads(res.body)
+ expected = {
+ 'network_2': [
+ {'version': 4, 'addr': '172.19.0.1'},
+ {'version': 4, 'addr': '1.2.3.4'},
+ {'version': 4, 'addr': '172.19.0.2'},
+ ],
+ }
+ self.assertEqual(res_dict, expected)
+
+ def test_get_server_addresses_nonexistant_network_v1_1(self):
+ _return_vifs = return_virtual_interface_by_instance([])
+ self.stubs.Set(nova.db.api,
+ 'virtual_interface_get_by_instance',
+ _return_vifs)
+
+ req = webob.Request.blank('/v1.1/servers/1/ips/network_0')
+ res = req.get_response(fakes.wsgi_app())
+ self.assertEqual(res.status_int, 404)
+
+ def test_get_server_addresses_nonexistant_server_v1_1(self):
+ _return_vifs = return_virtual_interface_instance_nonexistant([])
+ self.stubs.Set(nova.db.api,
+ 'virtual_interface_get_by_instance',
+ _return_vifs)
+
+ req = webob.Request.blank('/v1.1/servers/600/ips')
+ res = req.get_response(fakes.wsgi_app())
+ self.assertEqual(res.status_int, 404)
def test_get_server_list(self):
req = webob.Request.blank('/v1.0/servers')
@@ -787,13 +938,13 @@ class ServersTest(test.TestCase):
res = req.get_response(fakes.wsgi_app())
+ self.assertEqual(res.status_int, 200)
server = json.loads(res.body)['server']
self.assertEqual(16, len(server['adminPass']))
self.assertEqual('server_test', server['name'])
self.assertEqual(1, server['id'])
self.assertEqual(flavor_ref, server['flavorRef'])
self.assertEqual(image_href, server['imageRef'])
- self.assertEqual(res.status_int, 200)
def test_create_instance_v1_1_bad_href(self):
self._setup_for_create_instance()
@@ -901,11 +1052,11 @@ class ServersTest(test.TestCase):
res = req.get_response(fakes.wsgi_app())
self.assertEqual(res.status_int, 400)
- def test_update_no_body(self):
+ def test_update_server_no_body(self):
req = webob.Request.blank('/v1.0/servers/1')
req.method = 'PUT'
res = req.get_response(fakes.wsgi_app())
- self.assertEqual(res.status_int, 422)
+ self.assertEqual(res.status_int, 400)
def test_update_nonstring_name(self):
""" Confirm that update is filtering params """
@@ -967,6 +1118,21 @@ class ServersTest(test.TestCase):
self.assertEqual(mock_method.instance_id, '1')
self.assertEqual(mock_method.password, 'bacon')
+ def test_update_server_no_body_v1_1(self):
+ req = webob.Request.blank('/v1.0/servers/1')
+ req.method = 'PUT'
+ res = req.get_response(fakes.wsgi_app())
+ self.assertEqual(res.status_int, 400)
+
+ def test_update_server_name_v1_1(self):
+ req = webob.Request.blank('/v1.1/servers/1')
+ req.method = 'PUT'
+ req.content_type = 'application/json'
+ req.body = json.dumps({'server': {'name': 'new-name'}})
+ res = req.get_response(fakes.wsgi_app())
+ self.assertEqual(res.status_int, 204)
+ self.assertEqual(res.body, '')
+
def test_update_server_adminPass_ignored_v1_1(self):
inst_dict = dict(name='server_test', adminPass='bacon')
self.body = json.dumps(dict(server=inst_dict))
@@ -985,6 +1151,7 @@ class ServersTest(test.TestCase):
req.body = self.body
res = req.get_response(fakes.wsgi_app())
self.assertEqual(res.status_int, 204)
+ self.assertEqual(res.body, '')
def test_create_backup_schedules(self):
req = webob.Request.blank('/v1.0/servers/1/backup_schedule')
@@ -1433,6 +1600,57 @@ class ServersTest(test.TestCase):
self.assertEqual(res.status, '202 Accepted')
self.assertEqual(self.server_delete_called, True)
+ def test_rescue_accepted(self):
+ FLAGS.allow_admin_api = True
+ body = {}
+
+ self.called = False
+
+ def rescue_mock(*args, **kwargs):
+ self.called = True
+
+ self.stubs.Set(nova.compute.api.API, 'rescue', rescue_mock)
+ req = webob.Request.blank('/v1.0/servers/1/rescue')
+ req.method = 'POST'
+ req.content_type = 'application/json'
+
+ res = req.get_response(fakes.wsgi_app())
+
+ self.assertEqual(self.called, True)
+ self.assertEqual(res.status_int, 202)
+
+ def test_rescue_raises_handled(self):
+ FLAGS.allow_admin_api = True
+ body = {}
+
+ def rescue_mock(*args, **kwargs):
+ raise Exception('Who cares?')
+
+ self.stubs.Set(nova.compute.api.API, 'rescue', rescue_mock)
+ req = webob.Request.blank('/v1.0/servers/1/rescue')
+ req.method = 'POST'
+ req.content_type = 'application/json'
+
+ res = req.get_response(fakes.wsgi_app())
+
+ self.assertEqual(res.status_int, 422)
+
+ def test_delete_server_instance_v1_1(self):
+ req = webob.Request.blank('/v1.1/servers/1')
+ req.method = 'DELETE'
+
+ self.server_delete_called = False
+
+ def instance_destroy_mock(context, id):
+ self.server_delete_called = True
+
+ self.stubs.Set(nova.db.api, 'instance_destroy',
+ instance_destroy_mock)
+
+ res = req.get_response(fakes.wsgi_app())
+ self.assertEqual(res.status_int, 204)
+ self.assertEqual(self.server_delete_called, True)
+
def test_resize_server(self):
req = self.webreq('/1/action', 'POST', dict(resize=dict(flavorId=3)))
@@ -1608,7 +1826,7 @@ class TestServerCreateRequestXMLDeserializer(unittest.TestCase):
"imageId": "1",
"flavorId": "1",
}}
- self.assertEquals(request, expected)
+ self.assertEquals(request['body'], expected)
def test_request_with_empty_metadata(self):
serial_request = """
@@ -1623,7 +1841,7 @@ class TestServerCreateRequestXMLDeserializer(unittest.TestCase):
"flavorId": "1",
"metadata": {},
}}
- self.assertEquals(request, expected)
+ self.assertEquals(request['body'], expected)
def test_request_with_empty_personality(self):
serial_request = """
@@ -1638,7 +1856,7 @@ class TestServerCreateRequestXMLDeserializer(unittest.TestCase):
"flavorId": "1",
"personality": [],
}}
- self.assertEquals(request, expected)
+ self.assertEquals(request['body'], expected)
def test_request_with_empty_metadata_and_personality(self):
serial_request = """
@@ -1655,7 +1873,7 @@ class TestServerCreateRequestXMLDeserializer(unittest.TestCase):
"metadata": {},
"personality": [],
}}
- self.assertEquals(request, expected)
+ self.assertEquals(request['body'], expected)
def test_request_with_empty_metadata_and_personality_reversed(self):
serial_request = """
@@ -1672,7 +1890,7 @@ class TestServerCreateRequestXMLDeserializer(unittest.TestCase):
"metadata": {},
"personality": [],
}}
- self.assertEquals(request, expected)
+ self.assertEquals(request['body'], expected)
def test_request_with_one_personality(self):
serial_request = """
@@ -1684,7 +1902,7 @@ class TestServerCreateRequestXMLDeserializer(unittest.TestCase):
</server>"""
request = self.deserializer.deserialize(serial_request, 'create')
expected = [{"path": "/etc/conf", "contents": "aabbccdd"}]
- self.assertEquals(request["server"]["personality"], expected)
+ self.assertEquals(request['body']["server"]["personality"], expected)
def test_request_with_two_personalities(self):
serial_request = """
@@ -1695,7 +1913,7 @@ class TestServerCreateRequestXMLDeserializer(unittest.TestCase):
request = self.deserializer.deserialize(serial_request, 'create')
expected = [{"path": "/etc/conf", "contents": "aabbccdd"},
{"path": "/etc/sudoers", "contents": "abcd"}]
- self.assertEquals(request["server"]["personality"], expected)
+ self.assertEquals(request['body']["server"]["personality"], expected)
def test_request_second_personality_node_ignored(self):
serial_request = """
@@ -1710,7 +1928,7 @@ class TestServerCreateRequestXMLDeserializer(unittest.TestCase):
</server>"""
request = self.deserializer.deserialize(serial_request, 'create')
expected = [{"path": "/etc/conf", "contents": "aabbccdd"}]
- self.assertEquals(request["server"]["personality"], expected)
+ self.assertEquals(request['body']["server"]["personality"], expected)
def test_request_with_one_personality_missing_path(self):
serial_request = """
@@ -1719,7 +1937,7 @@ class TestServerCreateRequestXMLDeserializer(unittest.TestCase):
<personality><file>aabbccdd</file></personality></server>"""
request = self.deserializer.deserialize(serial_request, 'create')
expected = [{"contents": "aabbccdd"}]
- self.assertEquals(request["server"]["personality"], expected)
+ self.assertEquals(request['body']["server"]["personality"], expected)
def test_request_with_one_personality_empty_contents(self):
serial_request = """
@@ -1728,7 +1946,7 @@ class TestServerCreateRequestXMLDeserializer(unittest.TestCase):
<personality><file path="/etc/conf"></file></personality></server>"""
request = self.deserializer.deserialize(serial_request, 'create')
expected = [{"path": "/etc/conf", "contents": ""}]
- self.assertEquals(request["server"]["personality"], expected)
+ self.assertEquals(request['body']["server"]["personality"], expected)
def test_request_with_one_personality_empty_contents_variation(self):
serial_request = """
@@ -1737,7 +1955,7 @@ class TestServerCreateRequestXMLDeserializer(unittest.TestCase):
<personality><file path="/etc/conf"/></personality></server>"""
request = self.deserializer.deserialize(serial_request, 'create')
expected = [{"path": "/etc/conf", "contents": ""}]
- self.assertEquals(request["server"]["personality"], expected)
+ self.assertEquals(request['body']["server"]["personality"], expected)
def test_request_with_one_metadata(self):
serial_request = """
@@ -1749,7 +1967,7 @@ class TestServerCreateRequestXMLDeserializer(unittest.TestCase):
</server>"""
request = self.deserializer.deserialize(serial_request, 'create')
expected = {"alpha": "beta"}
- self.assertEquals(request["server"]["metadata"], expected)
+ self.assertEquals(request['body']["server"]["metadata"], expected)
def test_request_with_two_metadata(self):
serial_request = """
@@ -1762,7 +1980,7 @@ class TestServerCreateRequestXMLDeserializer(unittest.TestCase):
</server>"""
request = self.deserializer.deserialize(serial_request, 'create')
expected = {"alpha": "beta", "foo": "bar"}
- self.assertEquals(request["server"]["metadata"], expected)
+ self.assertEquals(request['body']["server"]["metadata"], expected)
def test_request_with_metadata_missing_value(self):
serial_request = """
@@ -1774,7 +1992,7 @@ class TestServerCreateRequestXMLDeserializer(unittest.TestCase):
</server>"""
request = self.deserializer.deserialize(serial_request, 'create')
expected = {"alpha": ""}
- self.assertEquals(request["server"]["metadata"], expected)
+ self.assertEquals(request['body']["server"]["metadata"], expected)
def test_request_with_two_metadata_missing_value(self):
serial_request = """
@@ -1787,7 +2005,7 @@ class TestServerCreateRequestXMLDeserializer(unittest.TestCase):
</server>"""
request = self.deserializer.deserialize(serial_request, 'create')
expected = {"alpha": "", "delta": ""}
- self.assertEquals(request["server"]["metadata"], expected)
+ self.assertEquals(request['body']["server"]["metadata"], expected)
def test_request_with_metadata_missing_key(self):
serial_request = """
@@ -1799,7 +2017,7 @@ class TestServerCreateRequestXMLDeserializer(unittest.TestCase):
</server>"""
request = self.deserializer.deserialize(serial_request, 'create')
expected = {"": "beta"}
- self.assertEquals(request["server"]["metadata"], expected)
+ self.assertEquals(request['body']["server"]["metadata"], expected)
def test_request_with_two_metadata_missing_key(self):
serial_request = """
@@ -1812,7 +2030,7 @@ class TestServerCreateRequestXMLDeserializer(unittest.TestCase):
</server>"""
request = self.deserializer.deserialize(serial_request, 'create')
expected = {"": "gamma"}
- self.assertEquals(request["server"]["metadata"], expected)
+ self.assertEquals(request['body']["server"]["metadata"], expected)
def test_request_with_metadata_duplicate_key(self):
serial_request = """
@@ -1825,7 +2043,7 @@ class TestServerCreateRequestXMLDeserializer(unittest.TestCase):
</server>"""
request = self.deserializer.deserialize(serial_request, 'create')
expected = {"foo": "baz"}
- self.assertEquals(request["server"]["metadata"], expected)
+ self.assertEquals(request['body']["server"]["metadata"], expected)
def test_canonical_request_from_docs(self):
serial_request = """
@@ -1871,7 +2089,7 @@ b25zLiINCg0KLVJpY2hhcmQgQmFjaA==""",
],
}}
request = self.deserializer.deserialize(serial_request, 'create')
- self.assertEqual(request, expected)
+ self.assertEqual(request['body'], expected)
def test_request_xmlser_with_flavor_image_href(self):
serial_request = """
@@ -1881,9 +2099,9 @@ b25zLiINCg0KLVJpY2hhcmQgQmFjaA==""",
flavorRef="http://localhost:8774/v1.1/flavors/1">
</server>"""
request = self.deserializer.deserialize(serial_request, 'create')
- self.assertEquals(request["server"]["flavorRef"],
+ self.assertEquals(request['body']["server"]["flavorRef"],
"http://localhost:8774/v1.1/flavors/1")
- self.assertEquals(request["server"]["imageRef"],
+ self.assertEquals(request['body']["server"]["imageRef"],
"http://localhost:8774/v1.1/images/1")
@@ -1948,7 +2166,7 @@ class TestServerInstanceCreation(test.TestCase):
def _get_create_request_json(self, body_dict):
req = webob.Request.blank('/v1.0/servers')
- req.content_type = 'application/json'
+ req.headers['Content-Type'] = 'application/json'
req.method = 'POST'
req.body = json.dumps(body_dict)
return req
diff --git a/nova/tests/api/openstack/test_wsgi.py b/nova/tests/api/openstack/test_wsgi.py
index 73a26a087..5bdda7c7e 100644
--- a/nova/tests/api/openstack/test_wsgi.py
+++ b/nova/tests/api/openstack/test_wsgi.py
@@ -12,8 +12,7 @@ class RequestTest(test.TestCase):
def test_content_type_missing(self):
request = wsgi.Request.blank('/tests/123', method='POST')
request.body = "<body />"
- self.assertRaises(exception.InvalidContentType,
- request.get_content_type)
+ self.assertEqual(None, request.get_content_type())
def test_content_type_unsupported(self):
request = wsgi.Request.blank('/tests/123', method='POST')
@@ -76,24 +75,48 @@ class RequestTest(test.TestCase):
self.assertEqual(result, "application/json")
-class DictSerializerTest(test.TestCase):
+class ActionDispatcherTest(test.TestCase):
def test_dispatch(self):
- serializer = wsgi.DictSerializer()
+ serializer = wsgi.ActionDispatcher()
+ serializer.create = lambda x: 'pants'
+ self.assertEqual(serializer.dispatch({}, action='create'), 'pants')
+
+ def test_dispatch_action_None(self):
+ serializer = wsgi.ActionDispatcher()
serializer.create = lambda x: 'pants'
serializer.default = lambda x: 'trousers'
- self.assertEqual(serializer.serialize({}, 'create'), 'pants')
+ self.assertEqual(serializer.dispatch({}, action=None), 'trousers')
def test_dispatch_default(self):
- serializer = wsgi.DictSerializer()
+ serializer = wsgi.ActionDispatcher()
serializer.create = lambda x: 'pants'
serializer.default = lambda x: 'trousers'
- self.assertEqual(serializer.serialize({}, 'update'), 'trousers')
+ self.assertEqual(serializer.dispatch({}, action='update'), 'trousers')
- def test_dispatch_action_None(self):
+
+class ResponseHeadersSerializerTest(test.TestCase):
+ def test_default(self):
+ serializer = wsgi.ResponseHeadersSerializer()
+ response = webob.Response()
+ serializer.serialize(response, {'v': '123'}, 'asdf')
+ self.assertEqual(response.status_int, 200)
+
+ def test_custom(self):
+ class Serializer(wsgi.ResponseHeadersSerializer):
+ def update(self, response, data):
+ response.status_int = 404
+ response.headers['X-Custom-Header'] = data['v']
+ serializer = Serializer()
+ response = webob.Response()
+ serializer.serialize(response, {'v': '123'}, 'update')
+ self.assertEqual(response.status_int, 404)
+ self.assertEqual(response.headers['X-Custom-Header'], '123')
+
+
+class DictSerializerTest(test.TestCase):
+ def test_dispatch_default(self):
serializer = wsgi.DictSerializer()
- serializer.create = lambda x: 'pants'
- serializer.default = lambda x: 'trousers'
- self.assertEqual(serializer.serialize({}, None), 'trousers')
+ self.assertEqual(serializer.serialize({}, 'update'), '')
class XMLDictSerializerTest(test.TestCase):
@@ -117,23 +140,9 @@ class JSONDictSerializerTest(test.TestCase):
class TextDeserializerTest(test.TestCase):
- def test_dispatch(self):
- deserializer = wsgi.TextDeserializer()
- deserializer.create = lambda x: 'pants'
- deserializer.default = lambda x: 'trousers'
- self.assertEqual(deserializer.deserialize({}, 'create'), 'pants')
-
def test_dispatch_default(self):
deserializer = wsgi.TextDeserializer()
- deserializer.create = lambda x: 'pants'
- deserializer.default = lambda x: 'trousers'
- self.assertEqual(deserializer.deserialize({}, 'update'), 'trousers')
-
- def test_dispatch_action_None(self):
- deserializer = wsgi.TextDeserializer()
- deserializer.create = lambda x: 'pants'
- deserializer.default = lambda x: 'trousers'
- self.assertEqual(deserializer.deserialize({}, None), 'trousers')
+ self.assertEqual(deserializer.deserialize({}, 'update'), {})
class JSONDeserializerTest(test.TestCase):
@@ -144,12 +153,17 @@ class JSONDeserializerTest(test.TestCase):
"bs": ["1", "2", "3", {"c": {"c1": "1"}}],
"d": {"e": "1"},
"f": "1"}}"""
- as_dict = dict(a={
- 'a1': '1',
- 'a2': '2',
- 'bs': ['1', '2', '3', {'c': dict(c1='1')}],
- 'd': {'e': '1'},
- 'f': '1'})
+ as_dict = {
+ 'body': {
+ 'a': {
+ 'a1': '1',
+ 'a2': '2',
+ 'bs': ['1', '2', '3', {'c': {'c1': '1'}}],
+ 'd': {'e': '1'},
+ 'f': '1',
+ },
+ },
+ }
deserializer = wsgi.JSONDeserializer()
self.assertEqual(deserializer.deserialize(data), as_dict)
@@ -163,23 +177,44 @@ class XMLDeserializerTest(test.TestCase):
<f>1</f>
</a>
""".strip()
- as_dict = dict(a={
- 'a1': '1',
- 'a2': '2',
- 'bs': ['1', '2', '3', {'c': dict(c1='1')}],
- 'd': {'e': '1'},
- 'f': '1'})
+ as_dict = {
+ 'body': {
+ 'a': {
+ 'a1': '1',
+ 'a2': '2',
+ 'bs': ['1', '2', '3', {'c': {'c1': '1'}}],
+ 'd': {'e': '1'},
+ 'f': '1',
+ },
+ },
+ }
metadata = {'plurals': {'bs': 'b', 'ts': 't'}}
deserializer = wsgi.XMLDeserializer(metadata=metadata)
self.assertEqual(deserializer.deserialize(xml), as_dict)
def test_xml_empty(self):
xml = """<a></a>"""
- as_dict = {"a": {}}
+ as_dict = {"body": {"a": {}}}
deserializer = wsgi.XMLDeserializer()
self.assertEqual(deserializer.deserialize(xml), as_dict)
+class RequestHeadersDeserializerTest(test.TestCase):
+ def test_default(self):
+ deserializer = wsgi.RequestHeadersDeserializer()
+ req = wsgi.Request.blank('/')
+ self.assertEqual(deserializer.deserialize(req, 'asdf'), {})
+
+ def test_custom(self):
+ class Deserializer(wsgi.RequestHeadersDeserializer):
+ def update(self, request):
+ return {'a': request.headers['X-Custom-Header']}
+ deserializer = Deserializer()
+ req = wsgi.Request.blank('/')
+ req.headers['X-Custom-Header'] = 'b'
+ self.assertEqual(deserializer.deserialize(req, 'update'), {'a': 'b'})
+
+
class ResponseSerializerTest(test.TestCase):
def setUp(self):
class JSONSerializer(object):
@@ -190,29 +225,36 @@ class ResponseSerializerTest(test.TestCase):
def serialize(self, data, action='default'):
return 'pew_xml'
- self.serializers = {
+ class HeadersSerializer(object):
+ def serialize(self, response, data, action):
+ response.status_int = 404
+
+ self.body_serializers = {
'application/json': JSONSerializer(),
'application/XML': XMLSerializer(),
}
- self.serializer = wsgi.ResponseSerializer(serializers=self.serializers)
+ self.serializer = wsgi.ResponseSerializer(self.body_serializers,
+ HeadersSerializer())
def tearDown(self):
pass
def test_get_serializer(self):
- self.assertEqual(self.serializer.get_serializer('application/json'),
- self.serializers['application/json'])
+ ctype = 'application/json'
+ self.assertEqual(self.serializer.get_body_serializer(ctype),
+ self.body_serializers[ctype])
def test_get_serializer_unknown_content_type(self):
self.assertRaises(exception.InvalidContentType,
- self.serializer.get_serializer,
+ self.serializer.get_body_serializer,
'application/unknown')
def test_serialize_response(self):
response = self.serializer.serialize({}, 'application/json')
self.assertEqual(response.headers['Content-Type'], 'application/json')
self.assertEqual(response.body, 'pew_json')
+ self.assertEqual(response.status_int, 404)
def test_serialize_response_dict_to_unknown_content_type(self):
self.assertRaises(exception.InvalidContentType,
@@ -230,24 +272,23 @@ class RequestDeserializerTest(test.TestCase):
def deserialize(self, data, action='default'):
return 'pew_xml'
- self.deserializers = {
+ self.body_deserializers = {
'application/json': JSONDeserializer(),
'application/XML': XMLDeserializer(),
}
- self.deserializer = wsgi.RequestDeserializer(
- deserializers=self.deserializers)
+ self.deserializer = wsgi.RequestDeserializer(self.body_deserializers)
def tearDown(self):
pass
def test_get_deserializer(self):
- expected = self.deserializer.get_deserializer('application/json')
- self.assertEqual(expected, self.deserializers['application/json'])
+ expected = self.deserializer.get_body_deserializer('application/json')
+ self.assertEqual(expected, self.body_deserializers['application/json'])
def test_get_deserializer_unknown_content_type(self):
self.assertRaises(exception.InvalidContentType,
- self.deserializer.get_deserializer,
+ self.deserializer.get_body_deserializer,
'application/unknown')
def test_get_expected_content_type(self):
diff --git a/nova/tests/image/test_s3.py b/nova/tests/image/test_s3.py
new file mode 100644
index 000000000..231e109f8
--- /dev/null
+++ b/nova/tests/image/test_s3.py
@@ -0,0 +1,122 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2011 Isaku Yamahata
+# 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 nova import context
+from nova import flags
+from nova import test
+from nova.image import s3
+
+FLAGS = flags.FLAGS
+
+
+ami_manifest_xml = """<?xml version="1.0" ?>
+<manifest>
+ <version>2011-06-17</version>
+ <bundler>
+ <name>test-s3</name>
+ <version>0</version>
+ <release>0</release>
+ </bundler>
+ <machine_configuration>
+ <architecture>x86_64</architecture>
+ <block_device_mapping>
+ <mapping>
+ <virtual>ami</virtual>
+ <device>sda1</device>
+ </mapping>
+ <mapping>
+ <virtual>root</virtual>
+ <device>/dev/sda1</device>
+ </mapping>
+ <mapping>
+ <virtual>ephemeral0</virtual>
+ <device>sda2</device>
+ </mapping>
+ <mapping>
+ <virtual>swap</virtual>
+ <device>sda3</device>
+ </mapping>
+ </block_device_mapping>
+ </machine_configuration>
+</manifest>
+"""
+
+
+class TestS3ImageService(test.TestCase):
+ def setUp(self):
+ super(TestS3ImageService, self).setUp()
+ self.orig_image_service = FLAGS.image_service
+ FLAGS.image_service = 'nova.image.fake.FakeImageService'
+ self.image_service = s3.S3ImageService()
+ self.context = context.RequestContext(None, None)
+
+ def tearDown(self):
+ super(TestS3ImageService, self).tearDown()
+ FLAGS.image_service = self.orig_image_service
+
+ def _assertEqualList(self, list0, list1, keys):
+ self.assertEqual(len(list0), len(list1))
+ key = keys[0]
+ for x in list0:
+ self.assertEqual(len(x), len(keys))
+ self.assertTrue(key in x)
+ for y in list1:
+ self.assertTrue(key in y)
+ if x[key] == y[key]:
+ for k in keys:
+ self.assertEqual(x[k], y[k])
+
+ def test_s3_create(self):
+ metadata = {'properties': {
+ 'root_device_name': '/dev/sda1',
+ 'block_device_mapping': [
+ {'device_name': '/dev/sda1',
+ 'snapshot_id': 'snap-12345678',
+ 'delete_on_termination': True},
+ {'device_name': '/dev/sda2',
+ 'virutal_name': 'ephemeral0'},
+ {'device_name': '/dev/sdb0',
+ 'no_device': True}]}}
+ _manifest, image = self.image_service._s3_parse_manifest(
+ self.context, metadata, ami_manifest_xml)
+ image_id = image['id']
+
+ ret_image = self.image_service.show(self.context, image_id)
+ self.assertTrue('properties' in ret_image)
+ properties = ret_image['properties']
+
+ self.assertTrue('mappings' in properties)
+ mappings = properties['mappings']
+ expected_mappings = [
+ {"device": "sda1", "virtual": "ami"},
+ {"device": "/dev/sda1", "virtual": "root"},
+ {"device": "sda2", "virtual": "ephemeral0"},
+ {"device": "sda3", "virtual": "swap"}]
+ self._assertEqualList(mappings, expected_mappings,
+ ['device', 'virtual'])
+
+ self.assertTrue('block_device_mapping', properties)
+ block_device_mapping = properties['block_device_mapping']
+ expected_bdm = [
+ {'device_name': '/dev/sda1',
+ 'snapshot_id': 'snap-12345678',
+ 'delete_on_termination': True},
+ {'device_name': '/dev/sda2',
+ 'virutal_name': 'ephemeral0'},
+ {'device_name': '/dev/sdb0',
+ 'no_device': True}]
+ self.assertEqual(block_device_mapping, expected_bdm)
diff --git a/nova/tests/integrated/api/client.py b/nova/tests/integrated/api/client.py
index 76c03c5fa..035a35aab 100644
--- a/nova/tests/integrated/api/client.py
+++ b/nova/tests/integrated/api/client.py
@@ -71,8 +71,8 @@ class TestOpenStackClient(object):
self.auth_uri = auth_uri
def request(self, url, method='GET', body=None, headers=None):
- if headers is None:
- headers = {}
+ _headers = {'Content-Type': 'application/json'}
+ _headers.update(headers or {})
parsed_url = urlparse.urlparse(url)
port = parsed_url.port
@@ -94,9 +94,8 @@ class TestOpenStackClient(object):
LOG.info(_("Doing %(method)s on %(relative_url)s") % locals())
if body:
LOG.info(_("Body: %s") % body)
- headers.setdefault('Content-Type', 'application/json')
- conn.request(method, relative_url, body, headers)
+ conn.request(method, relative_url, body, _headers)
response = conn.getresponse()
return response
@@ -173,9 +172,20 @@ class TestOpenStackClient(object):
response = self.api_request(relative_uri, **kwargs)
return self._decode_json(response)
+ def api_put(self, relative_uri, body, **kwargs):
+ kwargs['method'] = 'PUT'
+ if body:
+ headers = kwargs.setdefault('headers', {})
+ headers['Content-Type'] = 'application/json'
+ kwargs['body'] = json.dumps(body)
+
+ kwargs.setdefault('check_response_status', [200, 202, 204])
+ response = self.api_request(relative_uri, **kwargs)
+ return self._decode_json(response)
+
def api_delete(self, relative_uri, **kwargs):
kwargs['method'] = 'DELETE'
- kwargs.setdefault('check_response_status', [200, 202])
+ kwargs.setdefault('check_response_status', [200, 202, 204])
return self.api_request(relative_uri, **kwargs)
def get_server(self, server_id):
@@ -188,6 +198,9 @@ class TestOpenStackClient(object):
def post_server(self, server):
return self.api_post('/servers', server)['server']
+ def put_server(self, server_id, server):
+ return self.api_put('/servers/%s' % server_id, server)
+
def post_server_action(self, server_id, data):
return self.api_post('/servers/%s/action' % server_id, data)
diff --git a/nova/tests/integrated/test_servers.py b/nova/tests/integrated/test_servers.py
index fcb517cf5..4e8e85c7b 100644
--- a/nova/tests/integrated/test_servers.py
+++ b/nova/tests/integrated/test_servers.py
@@ -285,6 +285,25 @@ class ServersTest(integrated_helpers._IntegratedTestBase):
# Cleanup
self._delete_server(created_server_id)
+ def test_rename_server(self):
+ """Test building and renaming a server."""
+
+ # Create a server
+ server = self._build_minimal_create_server_request()
+ created_server = self.api.post_server({'server': server})
+ LOG.debug("created_server: %s" % created_server)
+ server_id = created_server['id']
+ self.assertTrue(server_id)
+
+ # Rename the server to 'new-name'
+ self.api.put_server(server_id, {'server': {'name': 'new-name'}})
+
+ # Check the name of the server
+ created_server = self.api.get_server(server_id)
+ self.assertEqual(created_server['name'], 'new-name')
+
+ # Cleanup
+ self._delete_server(server_id)
if __name__ == "__main__":
unittest.main()
diff --git a/nova/tests/scheduler/test_zone_aware_scheduler.py b/nova/tests/scheduler/test_zone_aware_scheduler.py
index 5950f4551..d74b71fb6 100644
--- a/nova/tests/scheduler/test_zone_aware_scheduler.py
+++ b/nova/tests/scheduler/test_zone_aware_scheduler.py
@@ -122,19 +122,19 @@ def fake_decrypt_blob_returns_child_info(blob):
def fake_call_zone_method(context, method, specs, zones):
return [
- ('zone1', [
+ (1, [
dict(weight=1, blob='AAAAAAA'),
dict(weight=111, blob='BBBBBBB'),
dict(weight=112, blob='CCCCCCC'),
dict(weight=113, blob='DDDDDDD'),
]),
- ('zone2', [
+ (2, [
dict(weight=120, blob='EEEEEEE'),
dict(weight=2, blob='FFFFFFF'),
dict(weight=122, blob='GGGGGGG'),
dict(weight=123, blob='HHHHHHH'),
]),
- ('zone3', [
+ (3, [
dict(weight=130, blob='IIIIIII'),
dict(weight=131, blob='JJJJJJJ'),
dict(weight=132, blob='KKKKKKK'),
diff --git a/nova/tests/test_api.py b/nova/tests/test_api.py
index 20b20fcbf..26ac5ff24 100644
--- a/nova/tests/test_api.py
+++ b/nova/tests/test_api.py
@@ -92,7 +92,9 @@ class XmlConversionTestCase(test.TestCase):
conv = ec2utils._try_convert
self.assertEqual(conv('None'), None)
self.assertEqual(conv('True'), True)
+ self.assertEqual(conv('true'), True)
self.assertEqual(conv('False'), False)
+ self.assertEqual(conv('false'), False)
self.assertEqual(conv('0'), 0)
self.assertEqual(conv('42'), 42)
self.assertEqual(conv('3.14'), 3.14)
@@ -107,6 +109,8 @@ class Ec2utilsTestCase(test.TestCase):
def test_ec2_id_to_id(self):
self.assertEqual(ec2utils.ec2_id_to_id('i-0000001e'), 30)
self.assertEqual(ec2utils.ec2_id_to_id('ami-1d'), 29)
+ self.assertEqual(ec2utils.ec2_id_to_id('snap-0000001c'), 28)
+ self.assertEqual(ec2utils.ec2_id_to_id('vol-0000001b'), 27)
def test_bad_ec2_id(self):
self.assertRaises(exception.InvalidEc2Id,
@@ -116,6 +120,72 @@ class Ec2utilsTestCase(test.TestCase):
def test_id_to_ec2_id(self):
self.assertEqual(ec2utils.id_to_ec2_id(30), 'i-0000001e')
self.assertEqual(ec2utils.id_to_ec2_id(29, 'ami-%08x'), 'ami-0000001d')
+ self.assertEqual(ec2utils.id_to_ec2_snap_id(28), 'snap-0000001c')
+ self.assertEqual(ec2utils.id_to_ec2_vol_id(27), 'vol-0000001b')
+
+ def test_dict_from_dotted_str(self):
+ in_str = [('BlockDeviceMapping.1.DeviceName', '/dev/sda1'),
+ ('BlockDeviceMapping.1.Ebs.SnapshotId', 'snap-0000001c'),
+ ('BlockDeviceMapping.1.Ebs.VolumeSize', '80'),
+ ('BlockDeviceMapping.1.Ebs.DeleteOnTermination', 'false'),
+ ('BlockDeviceMapping.2.DeviceName', '/dev/sdc'),
+ ('BlockDeviceMapping.2.VirtualName', 'ephemeral0')]
+ expected_dict = {
+ 'block_device_mapping': {
+ '1': {'device_name': '/dev/sda1',
+ 'ebs': {'snapshot_id': 'snap-0000001c',
+ 'volume_size': 80,
+ 'delete_on_termination': False}},
+ '2': {'device_name': '/dev/sdc',
+ 'virtual_name': 'ephemeral0'}}}
+ out_dict = ec2utils.dict_from_dotted_str(in_str)
+
+ self.assertDictMatch(out_dict, expected_dict)
+
+ def test_properties_root_defice_name(self):
+ mappings = [{"device": "/dev/sda1", "virtual": "root"}]
+ properties0 = {'mappings': mappings}
+ properties1 = {'root_device_name': '/dev/sdb', 'mappings': mappings}
+
+ root_device_name = ec2utils.properties_root_device_name(properties0)
+ self.assertEqual(root_device_name, '/dev/sda1')
+
+ root_device_name = ec2utils.properties_root_device_name(properties1)
+ self.assertEqual(root_device_name, '/dev/sdb')
+
+ def test_mapping_prepend_dev(self):
+ mappings = [
+ {'virtual': 'ami',
+ 'device': 'sda1'},
+ {'virtual': 'root',
+ 'device': '/dev/sda1'},
+
+ {'virtual': 'swap',
+ 'device': 'sdb1'},
+ {'virtual': 'swap',
+ 'device': '/dev/sdb2'},
+
+ {'virtual': 'ephemeral0',
+ 'device': 'sdc1'},
+ {'virtual': 'ephemeral1',
+ 'device': '/dev/sdc1'}]
+ expected_result = [
+ {'virtual': 'ami',
+ 'device': 'sda1'},
+ {'virtual': 'root',
+ 'device': '/dev/sda1'},
+
+ {'virtual': 'swap',
+ 'device': '/dev/sdb1'},
+ {'virtual': 'swap',
+ 'device': '/dev/sdb2'},
+
+ {'virtual': 'ephemeral0',
+ 'device': '/dev/sdc1'},
+ {'virtual': 'ephemeral1',
+ 'device': '/dev/sdc1'}]
+ self.assertDictListMatch(ec2utils.mappings_prepend_dev(mappings),
+ expected_result)
class ApiEc2TestCase(test.TestCase):
diff --git a/nova/tests/test_bdm.py b/nova/tests/test_bdm.py
new file mode 100644
index 000000000..b258f6a75
--- /dev/null
+++ b/nova/tests/test_bdm.py
@@ -0,0 +1,233 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2011 Isaku Yamahata
+# 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.
+
+"""
+Tests for Block Device Mapping Code.
+"""
+
+from nova.api.ec2 import cloud
+from nova import test
+
+
+class BlockDeviceMappingEc2CloudTestCase(test.TestCase):
+ """Test Case for Block Device Mapping"""
+
+ def setUp(self):
+ super(BlockDeviceMappingEc2CloudTestCase, self).setUp()
+
+ def tearDown(self):
+ super(BlockDeviceMappingEc2CloudTestCase, self).tearDown()
+
+ def _assertApply(self, action, bdm_list):
+ for bdm, expected_result in bdm_list:
+ self.assertDictMatch(action(bdm), expected_result)
+
+ def test_parse_block_device_mapping(self):
+ bdm_list = [
+ ({'device_name': '/dev/fake0',
+ 'ebs': {'snapshot_id': 'snap-12345678',
+ 'volume_size': 1}},
+ {'device_name': '/dev/fake0',
+ 'snapshot_id': 0x12345678,
+ 'volume_size': 1,
+ 'delete_on_termination': True}),
+
+ ({'device_name': '/dev/fake1',
+ 'ebs': {'snapshot_id': 'snap-23456789',
+ 'delete_on_termination': False}},
+ {'device_name': '/dev/fake1',
+ 'snapshot_id': 0x23456789,
+ 'delete_on_termination': False}),
+
+ ({'device_name': '/dev/fake2',
+ 'ebs': {'snapshot_id': 'vol-87654321',
+ 'volume_size': 2}},
+ {'device_name': '/dev/fake2',
+ 'volume_id': 0x87654321,
+ 'volume_size': 2,
+ 'delete_on_termination': True}),
+
+ ({'device_name': '/dev/fake3',
+ 'ebs': {'snapshot_id': 'vol-98765432',
+ 'delete_on_termination': False}},
+ {'device_name': '/dev/fake3',
+ 'volume_id': 0x98765432,
+ 'delete_on_termination': False}),
+
+ ({'device_name': '/dev/fake4',
+ 'ebs': {'no_device': True}},
+ {'device_name': '/dev/fake4',
+ 'no_device': True}),
+
+ ({'device_name': '/dev/fake5',
+ 'virtual_name': 'ephemeral0'},
+ {'device_name': '/dev/fake5',
+ 'virtual_name': 'ephemeral0'}),
+
+ ({'device_name': '/dev/fake6',
+ 'virtual_name': 'swap'},
+ {'device_name': '/dev/fake6',
+ 'virtual_name': 'swap'}),
+ ]
+ self._assertApply(cloud._parse_block_device_mapping, bdm_list)
+
+ def test_format_block_device_mapping(self):
+ bdm_list = [
+ ({'device_name': '/dev/fake0',
+ 'snapshot_id': 0x12345678,
+ 'volume_size': 1,
+ 'delete_on_termination': True},
+ {'deviceName': '/dev/fake0',
+ 'ebs': {'snapshotId': 'snap-12345678',
+ 'volumeSize': 1,
+ 'deleteOnTermination': True}}),
+
+ ({'device_name': '/dev/fake1',
+ 'snapshot_id': 0x23456789},
+ {'deviceName': '/dev/fake1',
+ 'ebs': {'snapshotId': 'snap-23456789'}}),
+
+ ({'device_name': '/dev/fake2',
+ 'snapshot_id': 0x23456789,
+ 'delete_on_termination': False},
+ {'deviceName': '/dev/fake2',
+ 'ebs': {'snapshotId': 'snap-23456789',
+ 'deleteOnTermination': False}}),
+
+ ({'device_name': '/dev/fake3',
+ 'volume_id': 0x12345678,
+ 'volume_size': 1,
+ 'delete_on_termination': True},
+ {'deviceName': '/dev/fake3',
+ 'ebs': {'snapshotId': 'vol-12345678',
+ 'volumeSize': 1,
+ 'deleteOnTermination': True}}),
+
+ ({'device_name': '/dev/fake4',
+ 'volume_id': 0x23456789},
+ {'deviceName': '/dev/fake4',
+ 'ebs': {'snapshotId': 'vol-23456789'}}),
+
+ ({'device_name': '/dev/fake5',
+ 'volume_id': 0x23456789,
+ 'delete_on_termination': False},
+ {'deviceName': '/dev/fake5',
+ 'ebs': {'snapshotId': 'vol-23456789',
+ 'deleteOnTermination': False}}),
+ ]
+ self._assertApply(cloud._format_block_device_mapping, bdm_list)
+
+ def test_format_mapping(self):
+ properties = {
+ 'mappings': [
+ {'virtual': 'ami',
+ 'device': 'sda1'},
+ {'virtual': 'root',
+ 'device': '/dev/sda1'},
+
+ {'virtual': 'swap',
+ 'device': 'sdb1'},
+ {'virtual': 'swap',
+ 'device': 'sdb2'},
+ {'virtual': 'swap',
+ 'device': 'sdb3'},
+ {'virtual': 'swap',
+ 'device': 'sdb4'},
+
+ {'virtual': 'ephemeral0',
+ 'device': 'sdc1'},
+ {'virtual': 'ephemeral1',
+ 'device': 'sdc2'},
+ {'virtual': 'ephemeral2',
+ 'device': 'sdc3'},
+ ],
+
+ 'block_device_mapping': [
+ # root
+ {'device_name': '/dev/sda1',
+ 'snapshot_id': 0x12345678,
+ 'delete_on_termination': False},
+
+
+ # overwrite swap
+ {'device_name': '/dev/sdb2',
+ 'snapshot_id': 0x23456789,
+ 'delete_on_termination': False},
+ {'device_name': '/dev/sdb3',
+ 'snapshot_id': 0x3456789A},
+ {'device_name': '/dev/sdb4',
+ 'no_device': True},
+
+ # overwrite ephemeral
+ {'device_name': '/dev/sdc2',
+ 'snapshot_id': 0x3456789A,
+ 'delete_on_termination': False},
+ {'device_name': '/dev/sdc3',
+ 'snapshot_id': 0x456789AB},
+ {'device_name': '/dev/sdc4',
+ 'no_device': True},
+
+ # volume
+ {'device_name': '/dev/sdd1',
+ 'snapshot_id': 0x87654321,
+ 'delete_on_termination': False},
+ {'device_name': '/dev/sdd2',
+ 'snapshot_id': 0x98765432},
+ {'device_name': '/dev/sdd3',
+ 'snapshot_id': 0xA9875463},
+ {'device_name': '/dev/sdd4',
+ 'no_device': True}]}
+
+ expected_result = {
+ 'blockDeviceMapping': [
+ # root
+ {'deviceName': '/dev/sda1',
+ 'ebs': {'snapshotId': 'snap-12345678',
+ 'deleteOnTermination': False}},
+
+ # swap
+ {'deviceName': '/dev/sdb1',
+ 'virtualName': 'swap'},
+ {'deviceName': '/dev/sdb2',
+ 'ebs': {'snapshotId': 'snap-23456789',
+ 'deleteOnTermination': False}},
+ {'deviceName': '/dev/sdb3',
+ 'ebs': {'snapshotId': 'snap-3456789a'}},
+
+ # ephemeral
+ {'deviceName': '/dev/sdc1',
+ 'virtualName': 'ephemeral0'},
+ {'deviceName': '/dev/sdc2',
+ 'ebs': {'snapshotId': 'snap-3456789a',
+ 'deleteOnTermination': False}},
+ {'deviceName': '/dev/sdc3',
+ 'ebs': {'snapshotId': 'snap-456789ab'}},
+
+ # volume
+ {'deviceName': '/dev/sdd1',
+ 'ebs': {'snapshotId': 'snap-87654321',
+ 'deleteOnTermination': False}},
+ {'deviceName': '/dev/sdd2',
+ 'ebs': {'snapshotId': 'snap-98765432'}},
+ {'deviceName': '/dev/sdd3',
+ 'ebs': {'snapshotId': 'snap-a9875463'}}]}
+
+ result = {}
+ cloud._format_mappings(properties, result)
+ print result
+ self.assertEqual(result['blockDeviceMapping'].sort(),
+ expected_result['blockDeviceMapping'].sort())
diff --git a/nova/tests/test_cloud.py b/nova/tests/test_cloud.py
index bf7a2b7ca..a0d50b287 100644
--- a/nova/tests/test_cloud.py
+++ b/nova/tests/test_cloud.py
@@ -45,7 +45,8 @@ LOG = logging.getLogger('nova.tests.cloud')
class CloudTestCase(test.TestCase):
def setUp(self):
super(CloudTestCase, self).setUp()
- self.flags(connection_type='fake')
+ self.flags(connection_type='fake',
+ stub_network=True)
self.conn = rpc.Connection.instance()
@@ -67,7 +68,8 @@ class CloudTestCase(test.TestCase):
host = self.network.host
def fake_show(meh, context, id):
- return {'id': 1, 'properties': {'kernel_id': 1, 'ramdisk_id': 1,
+ return {'id': 1, 'container_format': 'ami',
+ 'properties': {'kernel_id': 1, 'ramdisk_id': 1,
'type': 'machine', 'image_state': 'available'}}
self.stubs.Set(fake._FakeImageService, 'show', fake_show)
@@ -289,7 +291,7 @@ class CloudTestCase(test.TestCase):
vol2 = db.volume_create(self.context, {})
result = self.cloud.describe_volumes(self.context)
self.assertEqual(len(result['volumeSet']), 2)
- volume_id = ec2utils.id_to_ec2_id(vol2['id'], 'vol-%08x')
+ volume_id = ec2utils.id_to_ec2_vol_id(vol2['id'])
result = self.cloud.describe_volumes(self.context,
volume_id=[volume_id])
self.assertEqual(len(result['volumeSet']), 1)
@@ -305,7 +307,7 @@ class CloudTestCase(test.TestCase):
snap = db.snapshot_create(self.context, {'volume_id': vol['id'],
'volume_size': vol['size'],
'status': "available"})
- snapshot_id = ec2utils.id_to_ec2_id(snap['id'], 'snap-%08x')
+ snapshot_id = ec2utils.id_to_ec2_snap_id(snap['id'])
result = self.cloud.create_volume(self.context,
snapshot_id=snapshot_id)
@@ -344,7 +346,7 @@ class CloudTestCase(test.TestCase):
snap2 = db.snapshot_create(self.context, {'volume_id': vol['id']})
result = self.cloud.describe_snapshots(self.context)
self.assertEqual(len(result['snapshotSet']), 2)
- snapshot_id = ec2utils.id_to_ec2_id(snap2['id'], 'snap-%08x')
+ snapshot_id = ec2utils.id_to_ec2_snap_id(snap2['id'])
result = self.cloud.describe_snapshots(self.context,
snapshot_id=[snapshot_id])
self.assertEqual(len(result['snapshotSet']), 1)
@@ -358,7 +360,7 @@ class CloudTestCase(test.TestCase):
def test_create_snapshot(self):
"""Makes sure create_snapshot works."""
vol = db.volume_create(self.context, {'status': "available"})
- volume_id = ec2utils.id_to_ec2_id(vol['id'], 'vol-%08x')
+ volume_id = ec2utils.id_to_ec2_vol_id(vol['id'])
result = self.cloud.create_snapshot(self.context,
volume_id=volume_id)
@@ -375,7 +377,7 @@ class CloudTestCase(test.TestCase):
vol = db.volume_create(self.context, {'status': "available"})
snap = db.snapshot_create(self.context, {'volume_id': vol['id'],
'status': "available"})
- snapshot_id = ec2utils.id_to_ec2_id(snap['id'], 'snap-%08x')
+ snapshot_id = ec2utils.id_to_ec2_snap_id(snap['id'])
result = self.cloud.delete_snapshot(self.context,
snapshot_id=snapshot_id)
@@ -414,11 +416,191 @@ class CloudTestCase(test.TestCase):
db.service_destroy(self.context, comp1['id'])
db.service_destroy(self.context, comp2['id'])
+ def _block_device_mapping_create(self, instance_id, mappings):
+ volumes = []
+ for bdm in mappings:
+ db.block_device_mapping_create(self.context, bdm)
+ if 'volume_id' in bdm:
+ values = {'id': bdm['volume_id']}
+ for bdm_key, vol_key in [('snapshot_id', 'snapshot_id'),
+ ('snapshot_size', 'volume_size'),
+ ('delete_on_termination',
+ 'delete_on_termination')]:
+ if bdm_key in bdm:
+ values[vol_key] = bdm[bdm_key]
+ vol = db.volume_create(self.context, values)
+ db.volume_attached(self.context, vol['id'],
+ instance_id, bdm['device_name'])
+ volumes.append(vol)
+ return volumes
+
+ def _setUpBlockDeviceMapping(self):
+ inst1 = db.instance_create(self.context,
+ {'image_ref': 1,
+ 'root_device_name': '/dev/sdb1'})
+ inst2 = db.instance_create(self.context,
+ {'image_ref': 2,
+ 'root_device_name': '/dev/sdc1'})
+
+ instance_id = inst1['id']
+ mappings0 = [
+ {'instance_id': instance_id,
+ 'device_name': '/dev/sdb1',
+ 'snapshot_id': '1',
+ 'volume_id': '2'},
+ {'instance_id': instance_id,
+ 'device_name': '/dev/sdb2',
+ 'volume_id': '3',
+ 'volume_size': 1},
+ {'instance_id': instance_id,
+ 'device_name': '/dev/sdb3',
+ 'delete_on_termination': True,
+ 'snapshot_id': '4',
+ 'volume_id': '5'},
+ {'instance_id': instance_id,
+ 'device_name': '/dev/sdb4',
+ 'delete_on_termination': False,
+ 'snapshot_id': '6',
+ 'volume_id': '7'},
+ {'instance_id': instance_id,
+ 'device_name': '/dev/sdb5',
+ 'snapshot_id': '8',
+ 'volume_id': '9',
+ 'volume_size': 0},
+ {'instance_id': instance_id,
+ 'device_name': '/dev/sdb6',
+ 'snapshot_id': '10',
+ 'volume_id': '11',
+ 'volume_size': 1},
+ {'instance_id': instance_id,
+ 'device_name': '/dev/sdb7',
+ 'no_device': True},
+ {'instance_id': instance_id,
+ 'device_name': '/dev/sdb8',
+ 'virtual_name': 'swap'},
+ {'instance_id': instance_id,
+ 'device_name': '/dev/sdb9',
+ 'virtual_name': 'ephemeral3'}]
+
+ volumes = self._block_device_mapping_create(instance_id, mappings0)
+ return (inst1, inst2, volumes)
+
+ def _tearDownBlockDeviceMapping(self, inst1, inst2, volumes):
+ for vol in volumes:
+ db.volume_destroy(self.context, vol['id'])
+ for id in (inst1['id'], inst2['id']):
+ for bdm in db.block_device_mapping_get_all_by_instance(
+ self.context, id):
+ db.block_device_mapping_destroy(self.context, bdm['id'])
+ db.instance_destroy(self.context, inst2['id'])
+ db.instance_destroy(self.context, inst1['id'])
+
+ _expected_instance_bdm1 = {
+ 'instanceId': 'i-00000001',
+ 'rootDeviceName': '/dev/sdb1',
+ 'rootDeviceType': 'ebs'}
+
+ _expected_block_device_mapping0 = [
+ {'deviceName': '/dev/sdb1',
+ 'ebs': {'status': 'in-use',
+ 'deleteOnTermination': False,
+ 'volumeId': 2,
+ }},
+ {'deviceName': '/dev/sdb2',
+ 'ebs': {'status': 'in-use',
+ 'deleteOnTermination': False,
+ 'volumeId': 3,
+ }},
+ {'deviceName': '/dev/sdb3',
+ 'ebs': {'status': 'in-use',
+ 'deleteOnTermination': True,
+ 'volumeId': 5,
+ }},
+ {'deviceName': '/dev/sdb4',
+ 'ebs': {'status': 'in-use',
+ 'deleteOnTermination': False,
+ 'volumeId': 7,
+ }},
+ {'deviceName': '/dev/sdb5',
+ 'ebs': {'status': 'in-use',
+ 'deleteOnTermination': False,
+ 'volumeId': 9,
+ }},
+ {'deviceName': '/dev/sdb6',
+ 'ebs': {'status': 'in-use',
+ 'deleteOnTermination': False,
+ 'volumeId': 11, }}]
+ # NOTE(yamahata): swap/ephemeral device case isn't supported yet.
+
+ _expected_instance_bdm2 = {
+ 'instanceId': 'i-00000002',
+ 'rootDeviceName': '/dev/sdc1',
+ 'rootDeviceType': 'instance-store'}
+
+ def test_format_instance_bdm(self):
+ (inst1, inst2, volumes) = self._setUpBlockDeviceMapping()
+
+ result = {}
+ self.cloud._format_instance_bdm(self.context, inst1['id'], '/dev/sdb1',
+ result)
+ self.assertSubDictMatch(
+ {'rootDeviceType': self._expected_instance_bdm1['rootDeviceType']},
+ result)
+ self._assertEqualBlockDeviceMapping(
+ self._expected_block_device_mapping0, result['blockDeviceMapping'])
+
+ result = {}
+ self.cloud._format_instance_bdm(self.context, inst2['id'], '/dev/sdc1',
+ result)
+ self.assertSubDictMatch(
+ {'rootDeviceType': self._expected_instance_bdm2['rootDeviceType']},
+ result)
+
+ self._tearDownBlockDeviceMapping(inst1, inst2, volumes)
+
+ def _assertInstance(self, instance_id):
+ ec2_instance_id = ec2utils.id_to_ec2_id(instance_id)
+ result = self.cloud.describe_instances(self.context,
+ instance_id=[ec2_instance_id])
+ result = result['reservationSet'][0]
+ self.assertEqual(len(result['instancesSet']), 1)
+ result = result['instancesSet'][0]
+ self.assertEqual(result['instanceId'], ec2_instance_id)
+ return result
+
+ def _assertEqualBlockDeviceMapping(self, expected, result):
+ self.assertEqual(len(expected), len(result))
+ for x in expected:
+ found = False
+ for y in result:
+ if x['deviceName'] == y['deviceName']:
+ self.assertSubDictMatch(x, y)
+ found = True
+ break
+ self.assertTrue(found)
+
+ def test_describe_instances_bdm(self):
+ """Make sure describe_instances works with root_device_name and
+ block device mappings
+ """
+ (inst1, inst2, volumes) = self._setUpBlockDeviceMapping()
+
+ result = self._assertInstance(inst1['id'])
+ self.assertSubDictMatch(self._expected_instance_bdm1, result)
+ self._assertEqualBlockDeviceMapping(
+ self._expected_block_device_mapping0, result['blockDeviceMapping'])
+
+ result = self._assertInstance(inst2['id'])
+ self.assertSubDictMatch(self._expected_instance_bdm2, result)
+
+ self._tearDownBlockDeviceMapping(inst1, inst2, volumes)
+
def test_describe_images(self):
describe_images = self.cloud.describe_images
def fake_detail(meh, context):
- return [{'id': 1, 'properties': {'kernel_id': 1, 'ramdisk_id': 1,
+ return [{'id': 1, 'container_format': 'ami',
+ 'properties': {'kernel_id': 1, 'ramdisk_id': 1,
'type': 'machine'}}]
def fake_show_none(meh, context, id):
@@ -443,12 +625,168 @@ class CloudTestCase(test.TestCase):
self.assertRaises(exception.ImageNotFound, describe_images,
self.context, ['ami-fake'])
+ def assertDictListUnorderedMatch(self, L1, L2, key):
+ self.assertEqual(len(L1), len(L2))
+ for d1 in L1:
+ self.assertTrue(key in d1)
+ for d2 in L2:
+ self.assertTrue(key in d2)
+ if d1[key] == d2[key]:
+ self.assertDictMatch(d1, d2)
+
+ def _setUpImageSet(self, create_volumes_and_snapshots=False):
+ mappings1 = [
+ {'device': '/dev/sda1', 'virtual': 'root'},
+
+ {'device': 'sdb0', 'virtual': 'ephemeral0'},
+ {'device': 'sdb1', 'virtual': 'ephemeral1'},
+ {'device': 'sdb2', 'virtual': 'ephemeral2'},
+ {'device': 'sdb3', 'virtual': 'ephemeral3'},
+ {'device': 'sdb4', 'virtual': 'ephemeral4'},
+
+ {'device': 'sdc0', 'virtual': 'swap'},
+ {'device': 'sdc1', 'virtual': 'swap'},
+ {'device': 'sdc2', 'virtual': 'swap'},
+ {'device': 'sdc3', 'virtual': 'swap'},
+ {'device': 'sdc4', 'virtual': 'swap'}]
+ block_device_mapping1 = [
+ {'device_name': '/dev/sdb1', 'snapshot_id': 01234567},
+ {'device_name': '/dev/sdb2', 'volume_id': 01234567},
+ {'device_name': '/dev/sdb3', 'virtual_name': 'ephemeral5'},
+ {'device_name': '/dev/sdb4', 'no_device': True},
+
+ {'device_name': '/dev/sdc1', 'snapshot_id': 12345678},
+ {'device_name': '/dev/sdc2', 'volume_id': 12345678},
+ {'device_name': '/dev/sdc3', 'virtual_name': 'ephemeral6'},
+ {'device_name': '/dev/sdc4', 'no_device': True}]
+ image1 = {
+ 'id': 1,
+ 'properties': {
+ 'kernel_id': 1,
+ 'type': 'machine',
+ 'image_state': 'available',
+ 'mappings': mappings1,
+ 'block_device_mapping': block_device_mapping1,
+ }
+ }
+
+ mappings2 = [{'device': '/dev/sda1', 'virtual': 'root'}]
+ block_device_mapping2 = [{'device_name': '/dev/sdb1',
+ 'snapshot_id': 01234567}]
+ image2 = {
+ 'id': 2,
+ 'properties': {
+ 'kernel_id': 2,
+ 'type': 'machine',
+ 'root_device_name': '/dev/sdb1',
+ 'mappings': mappings2,
+ 'block_device_mapping': block_device_mapping2}}
+
+ def fake_show(meh, context, image_id):
+ for i in [image1, image2]:
+ if i['id'] == image_id:
+ return i
+ raise exception.ImageNotFound(image_id=image_id)
+
+ def fake_detail(meh, context):
+ return [image1, image2]
+
+ self.stubs.Set(fake._FakeImageService, 'show', fake_show)
+ self.stubs.Set(fake._FakeImageService, 'detail', fake_detail)
+
+ volumes = []
+ snapshots = []
+ if create_volumes_and_snapshots:
+ for bdm in block_device_mapping1:
+ if 'volume_id' in bdm:
+ vol = self._volume_create(bdm['volume_id'])
+ volumes.append(vol['id'])
+ if 'snapshot_id' in bdm:
+ snap = db.snapshot_create(self.context,
+ {'id': bdm['snapshot_id'],
+ 'volume_id': 76543210,
+ 'status': "available",
+ 'volume_size': 1})
+ snapshots.append(snap['id'])
+ return (volumes, snapshots)
+
+ def _assertImageSet(self, result, root_device_type, root_device_name):
+ self.assertEqual(1, len(result['imagesSet']))
+ result = result['imagesSet'][0]
+ self.assertTrue('rootDeviceType' in result)
+ self.assertEqual(result['rootDeviceType'], root_device_type)
+ self.assertTrue('rootDeviceName' in result)
+ self.assertEqual(result['rootDeviceName'], root_device_name)
+ self.assertTrue('blockDeviceMapping' in result)
+
+ return result
+
+ _expected_root_device_name1 = '/dev/sda1'
+ # NOTE(yamahata): noDevice doesn't make sense when returning mapping
+ # It makes sense only when user overriding existing
+ # mapping.
+ _expected_bdms1 = [
+ {'deviceName': '/dev/sdb0', 'virtualName': 'ephemeral0'},
+ {'deviceName': '/dev/sdb1', 'ebs': {'snapshotId':
+ 'snap-00053977'}},
+ {'deviceName': '/dev/sdb2', 'ebs': {'snapshotId':
+ 'vol-00053977'}},
+ {'deviceName': '/dev/sdb3', 'virtualName': 'ephemeral5'},
+ # {'deviceName': '/dev/sdb4', 'noDevice': True},
+
+ {'deviceName': '/dev/sdc0', 'virtualName': 'swap'},
+ {'deviceName': '/dev/sdc1', 'ebs': {'snapshotId':
+ 'snap-00bc614e'}},
+ {'deviceName': '/dev/sdc2', 'ebs': {'snapshotId':
+ 'vol-00bc614e'}},
+ {'deviceName': '/dev/sdc3', 'virtualName': 'ephemeral6'},
+ # {'deviceName': '/dev/sdc4', 'noDevice': True}
+ ]
+
+ _expected_root_device_name2 = '/dev/sdb1'
+ _expected_bdms2 = [{'deviceName': '/dev/sdb1',
+ 'ebs': {'snapshotId': 'snap-00053977'}}]
+
+ # NOTE(yamahata):
+ # InstanceBlockDeviceMappingItemType
+ # rootDeviceType
+ # rootDeviceName
+ # blockDeviceMapping
+ # deviceName
+ # virtualName
+ # ebs
+ # snapshotId
+ # volumeSize
+ # deleteOnTermination
+ # noDevice
+ def test_describe_image_mapping(self):
+ """test for rootDeviceName and blockDeiceMapping"""
+ describe_images = self.cloud.describe_images
+ self._setUpImageSet()
+
+ result = describe_images(self.context, ['ami-00000001'])
+ result = self._assertImageSet(result, 'instance-store',
+ self._expected_root_device_name1)
+
+ self.assertDictListUnorderedMatch(result['blockDeviceMapping'],
+ self._expected_bdms1, 'deviceName')
+
+ result = describe_images(self.context, ['ami-00000002'])
+ result = self._assertImageSet(result, 'ebs',
+ self._expected_root_device_name2)
+
+ self.assertDictListUnorderedMatch(result['blockDeviceMapping'],
+ self._expected_bdms2, 'deviceName')
+
+ self.stubs.UnsetAll()
+
def test_describe_image_attribute(self):
describe_image_attribute = self.cloud.describe_image_attribute
def fake_show(meh, context, id):
return {'id': 1, 'properties': {'kernel_id': 1, 'ramdisk_id': 1,
- 'type': 'machine'}, 'is_public': True}
+ 'type': 'machine'}, 'container_format': 'ami',
+ 'is_public': True}
self.stubs.Set(fake._FakeImageService, 'show', fake_show)
self.stubs.Set(fake._FakeImageService, 'show_by_name', fake_show)
@@ -456,11 +794,38 @@ class CloudTestCase(test.TestCase):
'launchPermission')
self.assertEqual([{'group': 'all'}], result['launchPermission'])
+ def test_describe_image_attribute_root_device_name(self):
+ describe_image_attribute = self.cloud.describe_image_attribute
+ self._setUpImageSet()
+
+ result = describe_image_attribute(self.context, 'ami-00000001',
+ 'rootDeviceName')
+ self.assertEqual(result['rootDeviceName'],
+ self._expected_root_device_name1)
+ result = describe_image_attribute(self.context, 'ami-00000002',
+ 'rootDeviceName')
+ self.assertEqual(result['rootDeviceName'],
+ self._expected_root_device_name2)
+
+ def test_describe_image_attribute_block_device_mapping(self):
+ describe_image_attribute = self.cloud.describe_image_attribute
+ self._setUpImageSet()
+
+ result = describe_image_attribute(self.context, 'ami-00000001',
+ 'blockDeviceMapping')
+ self.assertDictListUnorderedMatch(result['blockDeviceMapping'],
+ self._expected_bdms1, 'deviceName')
+ result = describe_image_attribute(self.context, 'ami-00000002',
+ 'blockDeviceMapping')
+ self.assertDictListUnorderedMatch(result['blockDeviceMapping'],
+ self._expected_bdms2, 'deviceName')
+
def test_modify_image_attribute(self):
modify_image_attribute = self.cloud.modify_image_attribute
def fake_show(meh, context, id):
- return {'id': 1, 'properties': {'kernel_id': 1, 'ramdisk_id': 1,
+ return {'id': 1, 'container_format': 'ami',
+ 'properties': {'kernel_id': 1, 'ramdisk_id': 1,
'type': 'machine'}, 'is_public': False}
def fake_update(meh, context, image_id, metadata, data=None):
@@ -494,6 +859,16 @@ class CloudTestCase(test.TestCase):
self.assertRaises(exception.ImageNotFound, deregister_image,
self.context, 'ami-bad001')
+ def test_deregister_image_wrong_container_type(self):
+ deregister_image = self.cloud.deregister_image
+
+ def fake_delete(self, context, id):
+ return None
+
+ self.stubs.Set(fake._FakeImageService, 'delete', fake_delete)
+ self.assertRaises(exception.NotFound, deregister_image, self.context,
+ 'aki-00000001')
+
def _run_instance(self, **kwargs):
rv = self.cloud.run_instances(self.context, **kwargs)
instance_id = rv['instancesSet'][0]['instanceId']
@@ -609,7 +984,7 @@ class CloudTestCase(test.TestCase):
def fake_show_no_state(self, context, id):
return {'id': 1, 'properties': {'kernel_id': 1, 'ramdisk_id': 1,
- 'type': 'machine'}}
+ 'type': 'machine'}, 'container_format': 'ami'}
self.stubs.UnsetAll()
self.stubs.Set(fake._FakeImageService, 'show', fake_show_no_state)
@@ -623,7 +998,8 @@ class CloudTestCase(test.TestCase):
run_instances = self.cloud.run_instances
def fake_show_decrypt(self, context, id):
- return {'id': 1, 'properties': {'kernel_id': 1, 'ramdisk_id': 1,
+ return {'id': 1, 'container_format': 'ami',
+ 'properties': {'kernel_id': 1, 'ramdisk_id': 1,
'type': 'machine', 'image_state': 'decrypting'}}
self.stubs.UnsetAll()
@@ -638,7 +1014,8 @@ class CloudTestCase(test.TestCase):
run_instances = self.cloud.run_instances
def fake_show_stat_active(self, context, id):
- return {'id': 1, 'properties': {'kernel_id': 1, 'ramdisk_id': 1,
+ return {'id': 1, 'container_format': 'ami',
+ 'properties': {'kernel_id': 1, 'ramdisk_id': 1,
'type': 'machine'}, 'status': 'active'}
self.stubs.Set(fake._FakeImageService, 'show', fake_show_stat_active)
@@ -683,7 +1060,7 @@ class CloudTestCase(test.TestCase):
def test_update_of_volume_display_fields(self):
vol = db.volume_create(self.context, {})
self.cloud.update_volume(self.context,
- ec2utils.id_to_ec2_id(vol['id'], 'vol-%08x'),
+ ec2utils.id_to_ec2_vol_id(vol['id']),
display_name='c00l v0lum3')
vol = db.volume_get(self.context, vol['id'])
self.assertEqual('c00l v0lum3', vol['display_name'])
@@ -692,7 +1069,7 @@ class CloudTestCase(test.TestCase):
def test_update_of_volume_wont_update_private_fields(self):
vol = db.volume_create(self.context, {})
self.cloud.update_volume(self.context,
- ec2utils.id_to_ec2_id(vol['id'], 'vol-%08x'),
+ ec2utils.id_to_ec2_vol_id(vol['id']),
mountpoint='/not/here')
vol = db.volume_get(self.context, vol['id'])
self.assertEqual(None, vol['mountpoint'])
@@ -770,11 +1147,13 @@ class CloudTestCase(test.TestCase):
self._restart_compute_service()
- def _volume_create(self):
+ def _volume_create(self, volume_id=None):
kwargs = {'status': 'available',
'host': self.volume.host,
'size': 1,
'attach_status': 'detached', }
+ if volume_id:
+ kwargs['id'] = volume_id
return db.volume_create(self.context, kwargs)
def _assert_volume_attached(self, vol, instance_id, mountpoint):
@@ -803,10 +1182,10 @@ class CloudTestCase(test.TestCase):
'max_count': 1,
'block_device_mapping': [{'device_name': '/dev/vdb',
'volume_id': vol1['id'],
- 'delete_on_termination': False, },
+ 'delete_on_termination': False},
{'device_name': '/dev/vdc',
'volume_id': vol2['id'],
- 'delete_on_termination': True, },
+ 'delete_on_termination': True},
]}
ec2_instance_id = self._run_instance_wait(**kwargs)
instance_id = ec2utils.ec2_id_to_id(ec2_instance_id)
@@ -938,7 +1317,7 @@ class CloudTestCase(test.TestCase):
def test_run_with_snapshot(self):
"""Makes sure run/stop/start instance with snapshot works."""
vol = self._volume_create()
- ec2_volume_id = ec2utils.id_to_ec2_id(vol['id'], 'vol-%08x')
+ ec2_volume_id = ec2utils.id_to_ec2_vol_id(vol['id'])
ec2_snapshot1_id = self._create_snapshot(ec2_volume_id)
snapshot1_id = ec2utils.ec2_id_to_id(ec2_snapshot1_id)
@@ -997,3 +1376,33 @@ class CloudTestCase(test.TestCase):
self.cloud.delete_snapshot(self.context, snapshot_id)
greenthread.sleep(0.3)
db.volume_destroy(self.context, vol['id'])
+
+ def test_create_image(self):
+ """Make sure that CreateImage works"""
+ # enforce periodic tasks run in short time to avoid wait for 60s.
+ self._restart_compute_service(periodic_interval=0.3)
+
+ (volumes, snapshots) = self._setUpImageSet(
+ create_volumes_and_snapshots=True)
+
+ kwargs = {'image_id': 'ami-1',
+ 'instance_type': FLAGS.default_instance_type,
+ 'max_count': 1}
+ ec2_instance_id = self._run_instance_wait(**kwargs)
+
+ # TODO(yamahata): s3._s3_create() can't be tested easily by unit test
+ # as there is no unit test for s3.create()
+ ## result = self.cloud.create_image(self.context, ec2_instance_id,
+ ## no_reboot=True)
+ ## ec2_image_id = result['imageId']
+ ## created_image = self.cloud.describe_images(self.context,
+ ## [ec2_image_id])
+
+ self.cloud.terminate_instances(self.context, [ec2_instance_id])
+ for vol in volumes:
+ db.volume_destroy(self.context, vol)
+ for snap in snapshots:
+ db.snapshot_destroy(self.context, snap)
+ # TODO(yamahata): clean up snapshot created by CreateImage.
+
+ self._restart_compute_service()
diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py
index 04bb194d5..2900c594e 100644
--- a/nova/tests/test_compute.py
+++ b/nova/tests/test_compute.py
@@ -818,3 +818,114 @@ class ComputeTestCase(test.TestCase):
LOG.info(_("After force-killing instances: %s"), instances)
self.assertEqual(len(instances), 1)
self.assertEqual(power_state.SHUTOFF, instances[0]['state'])
+
+ @staticmethod
+ def _parse_db_block_device_mapping(bdm_ref):
+ attr_list = ('delete_on_termination', 'device_name', 'no_device',
+ 'virtual_name', 'volume_id', 'volume_size', 'snapshot_id')
+ bdm = {}
+ for attr in attr_list:
+ val = bdm_ref.get(attr, None)
+ if val:
+ bdm[attr] = val
+
+ return bdm
+
+ def test_update_block_device_mapping(self):
+ instance_id = self._create_instance()
+ mappings = [
+ {'virtual': 'ami', 'device': 'sda1'},
+ {'virtual': 'root', 'device': '/dev/sda1'},
+
+ {'virtual': 'swap', 'device': 'sdb1'},
+ {'virtual': 'swap', 'device': 'sdb2'},
+ {'virtual': 'swap', 'device': 'sdb3'},
+ {'virtual': 'swap', 'device': 'sdb4'},
+
+ {'virtual': 'ephemeral0', 'device': 'sdc1'},
+ {'virtual': 'ephemeral1', 'device': 'sdc2'},
+ {'virtual': 'ephemeral2', 'device': 'sdc3'}]
+ block_device_mapping = [
+ # root
+ {'device_name': '/dev/sda1',
+ 'snapshot_id': 0x12345678,
+ 'delete_on_termination': False},
+
+
+ # overwrite swap
+ {'device_name': '/dev/sdb2',
+ 'snapshot_id': 0x23456789,
+ 'delete_on_termination': False},
+ {'device_name': '/dev/sdb3',
+ 'snapshot_id': 0x3456789A},
+ {'device_name': '/dev/sdb4',
+ 'no_device': True},
+
+ # overwrite ephemeral
+ {'device_name': '/dev/sdc2',
+ 'snapshot_id': 0x456789AB,
+ 'delete_on_termination': False},
+ {'device_name': '/dev/sdc3',
+ 'snapshot_id': 0x56789ABC},
+ {'device_name': '/dev/sdc4',
+ 'no_device': True},
+
+ # volume
+ {'device_name': '/dev/sdd1',
+ 'snapshot_id': 0x87654321,
+ 'delete_on_termination': False},
+ {'device_name': '/dev/sdd2',
+ 'snapshot_id': 0x98765432},
+ {'device_name': '/dev/sdd3',
+ 'snapshot_id': 0xA9875463},
+ {'device_name': '/dev/sdd4',
+ 'no_device': True}]
+
+ self.compute_api._update_image_block_device_mapping(
+ self.context, instance_id, mappings)
+
+ bdms = [self._parse_db_block_device_mapping(bdm_ref)
+ for bdm_ref in db.block_device_mapping_get_all_by_instance(
+ self.context, instance_id)]
+ expected_result = [
+ {'virtual_name': 'swap', 'device_name': '/dev/sdb1'},
+ {'virtual_name': 'swap', 'device_name': '/dev/sdb2'},
+ {'virtual_name': 'swap', 'device_name': '/dev/sdb3'},
+ {'virtual_name': 'swap', 'device_name': '/dev/sdb4'},
+ {'virtual_name': 'ephemeral0', 'device_name': '/dev/sdc1'},
+ {'virtual_name': 'ephemeral1', 'device_name': '/dev/sdc2'},
+ {'virtual_name': 'ephemeral2', 'device_name': '/dev/sdc3'}]
+ bdms.sort()
+ expected_result.sort()
+ self.assertDictListMatch(bdms, expected_result)
+
+ self.compute_api._update_block_device_mapping(
+ self.context, instance_id, block_device_mapping)
+ bdms = [self._parse_db_block_device_mapping(bdm_ref)
+ for bdm_ref in db.block_device_mapping_get_all_by_instance(
+ self.context, instance_id)]
+ expected_result = [
+ {'snapshot_id': 0x12345678, 'device_name': '/dev/sda1'},
+
+ {'virtual_name': 'swap', 'device_name': '/dev/sdb1'},
+ {'snapshot_id': 0x23456789, 'device_name': '/dev/sdb2'},
+ {'snapshot_id': 0x3456789A, 'device_name': '/dev/sdb3'},
+ {'no_device': True, 'device_name': '/dev/sdb4'},
+
+ {'virtual_name': 'ephemeral0', 'device_name': '/dev/sdc1'},
+ {'snapshot_id': 0x456789AB, 'device_name': '/dev/sdc2'},
+ {'snapshot_id': 0x56789ABC, 'device_name': '/dev/sdc3'},
+ {'no_device': True, 'device_name': '/dev/sdc4'},
+
+ {'snapshot_id': 0x87654321, 'device_name': '/dev/sdd1'},
+ {'snapshot_id': 0x98765432, 'device_name': '/dev/sdd2'},
+ {'snapshot_id': 0xA9875463, 'device_name': '/dev/sdd3'},
+ {'no_device': True, 'device_name': '/dev/sdd4'}]
+ bdms.sort()
+ expected_result.sort()
+ self.assertDictListMatch(bdms, expected_result)
+
+ for bdm in db.block_device_mapping_get_all_by_instance(
+ self.context, instance_id):
+ db.block_device_mapping_destroy(self.context, bdm['id'])
+ self.compute.terminate_instance(self.context, instance_id)
diff --git a/nova/tests/test_exception.py b/nova/tests/test_exception.py
index 4d3b9cc73..cd74f8871 100644
--- a/nova/tests/test_exception.py
+++ b/nova/tests/test_exception.py
@@ -32,3 +32,66 @@ class ApiErrorTestCase(test.TestCase):
self.assertEqual(err.__str__(), 'blah code: fake error')
self.assertEqual(err.code, 'blah code')
self.assertEqual(err.msg, 'fake error')
+
+
+class FakeNotifier(object):
+ """Acts like the nova.notifier.api module."""
+ ERROR = 88
+
+ def __init__(self):
+ self.provided_publisher = None
+ self.provided_event = None
+ self.provided_priority = None
+ self.provided_payload = None
+
+ def notify(self, publisher, event, priority, payload):
+ self.provided_publisher = publisher
+ self.provided_event = event
+ self.provided_priority = priority
+ self.provided_payload = payload
+
+
+def good_function():
+ return 99
+
+
+def bad_function_error():
+ raise exception.Error()
+
+
+def bad_function_exception():
+ raise Exception()
+
+
+class WrapExceptionTestCase(test.TestCase):
+ def test_wrap_exception_good_return(self):
+ wrapped = exception.wrap_exception()
+ self.assertEquals(99, wrapped(good_function)())
+
+ def test_wrap_exception_throws_error(self):
+ wrapped = exception.wrap_exception()
+ self.assertRaises(exception.Error, wrapped(bad_function_error))
+
+ def test_wrap_exception_throws_exception(self):
+ wrapped = exception.wrap_exception()
+ # Note that Exception is converted to Error ...
+ self.assertRaises(exception.Error, wrapped(bad_function_exception))
+
+ def test_wrap_exception_with_notifier(self):
+ notifier = FakeNotifier()
+ wrapped = exception.wrap_exception(notifier, "publisher", "event",
+ "level")
+ self.assertRaises(exception.Error, wrapped(bad_function_exception))
+ self.assertEquals(notifier.provided_publisher, "publisher")
+ self.assertEquals(notifier.provided_event, "event")
+ self.assertEquals(notifier.provided_priority, "level")
+ for key in ['exception', 'args']:
+ self.assertTrue(key in notifier.provided_payload.keys())
+
+ def test_wrap_exception_with_notifier_defaults(self):
+ notifier = FakeNotifier()
+ wrapped = exception.wrap_exception(notifier)
+ self.assertRaises(exception.Error, wrapped(bad_function_exception))
+ self.assertEquals(notifier.provided_publisher, None)
+ self.assertEquals(notifier.provided_event, "bad_function_exception")
+ self.assertEquals(notifier.provided_priority, notifier.ERROR)
diff --git a/nova/tests/test_metadata.py b/nova/tests/test_metadata.py
new file mode 100644
index 000000000..c862726ab
--- /dev/null
+++ b/nova/tests/test_metadata.py
@@ -0,0 +1,76 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2010 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# 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.
+
+"""Tests for the testing the metadata code."""
+
+import base64
+import httplib
+
+import webob
+
+from nova import test
+from nova import wsgi
+from nova.api.ec2 import metadatarequesthandler
+from nova.db.sqlalchemy import api
+
+
+class MetadataTestCase(test.TestCase):
+ """Test that metadata is returning proper values."""
+
+ def setUp(self):
+ super(MetadataTestCase, self).setUp()
+ self.instance = ({'id': 1,
+ 'project_id': 'test',
+ 'key_name': None,
+ 'host': 'test',
+ 'launch_index': 1,
+ 'instance_type': 'm1.tiny',
+ 'reservation_id': 'r-xxxxxxxx',
+ 'user_data': '',
+ 'image_ref': 7,
+ 'hostname': 'test'})
+
+ def instance_get(*args, **kwargs):
+ return self.instance
+
+ def floating_get(*args, **kwargs):
+ return '99.99.99.99'
+
+ self.stubs.Set(api, 'instance_get', instance_get)
+ self.stubs.Set(api, 'fixed_ip_get_instance', instance_get)
+ self.stubs.Set(api, 'instance_get_floating_address', floating_get)
+ self.app = metadatarequesthandler.MetadataRequestHandler()
+
+ def request(self, relative_url):
+ request = webob.Request.blank(relative_url)
+ request.remote_addr = "127.0.0.1"
+ return request.get_response(self.app).body
+
+ def test_base(self):
+ self.assertEqual(self.request('/'), 'meta-data/\nuser-data')
+
+ def test_user_data(self):
+ self.instance['user_data'] = base64.b64encode('happy')
+ self.assertEqual(self.request('/user-data'), 'happy')
+
+ def test_security_groups(self):
+ def sg_get(*args, **kwargs):
+ return [{'name': 'default'}, {'name': 'other'}]
+ self.stubs.Set(api, 'security_group_get_by_instance', sg_get)
+ self.assertEqual(self.request('/meta-data/security-groups'),
+ 'default\nother')
diff --git a/nova/tests/test_network.py b/nova/tests/test_network.py
index 1740d4f54..7cc174842 100644
--- a/nova/tests/test_network.py
+++ b/nova/tests/test_network.py
@@ -16,6 +16,7 @@
# under the License.
from nova import db
+from nova import exception
from nova import flags
from nova import log as logging
from nova import test
@@ -227,3 +228,35 @@ class VlanNetworkTestCase(test.TestCase):
self.assertRaises(ValueError, self.network.create_networks, None,
num_networks=100, vlan_start=1,
cidr='192.168.0.1/24', network_size=100)
+
+
+class CommonNetworkTestCase(test.TestCase):
+
+ class FakeNetworkManager(network_manager.NetworkManager):
+ """This NetworkManager doesn't call the base class so we can bypass all
+ inherited service cruft and just perform unit tests.
+ """
+
+ class FakeDB:
+ def fixed_ip_get_by_instance(self, context, instance_id):
+ return [dict(address='10.0.0.0'), dict(address='10.0.0.1'),
+ dict(address='10.0.0.2')]
+
+ def __init__(self):
+ self.db = self.FakeDB()
+ self.deallocate_called = None
+
+ def deallocate_fixed_ip(self, context, address):
+ self.deallocate_called = address
+
+ def test_remove_fixed_ip_from_instance(self):
+ manager = self.FakeNetworkManager()
+ manager.remove_fixed_ip_from_instance(None, 99, '10.0.0.1')
+
+ self.assertEquals(manager.deallocate_called, '10.0.0.1')
+
+ def test_remove_fixed_ip_from_instance_bad_input(self):
+ manager = self.FakeNetworkManager()
+ self.assertRaises(exception.FixedIpNotFoundForSpecificInstance,
+ manager.remove_fixed_ip_from_instance,
+ None, 99, 'bad input')
diff --git a/nova/tests/test_volume.py b/nova/tests/test_volume.py
index 62cc4b325..c0f89601f 100644
--- a/nova/tests/test_volume.py
+++ b/nova/tests/test_volume.py
@@ -27,8 +27,10 @@ from nova import exception
from nova import db
from nova import flags
from nova import log as logging
+from nova import rpc
from nova import test
from nova import utils
+from nova import volume
FLAGS = flags.FLAGS
LOG = logging.getLogger('nova.tests.volume')
@@ -43,6 +45,11 @@ class VolumeTestCase(test.TestCase):
self.flags(connection_type='fake')
self.volume = utils.import_object(FLAGS.volume_manager)
self.context = context.get_admin_context()
+ self.instance_id = db.instance_create(self.context, {})['id']
+
+ def tearDown(self):
+ db.instance_destroy(self.context, self.instance_id)
+ super(VolumeTestCase, self).tearDown()
@staticmethod
def _create_volume(size='0', snapshot_id=None):
@@ -223,6 +230,30 @@ class VolumeTestCase(test.TestCase):
snapshot_id)
self.volume.delete_volume(self.context, volume_id)
+ def test_create_snapshot_force(self):
+ """Test snapshot in use can be created forcibly."""
+
+ def fake_cast(ctxt, topic, msg):
+ pass
+ self.stubs.Set(rpc, 'cast', fake_cast)
+
+ volume_id = self._create_volume()
+ self.volume.create_volume(self.context, volume_id)
+ db.volume_attached(self.context, volume_id, self.instance_id,
+ '/dev/sda1')
+
+ volume_api = volume.api.API()
+ self.assertRaises(exception.ApiError,
+ volume_api.create_snapshot,
+ self.context, volume_id,
+ 'fake_name', 'fake_description')
+ snapshot_ref = volume_api.create_snapshot_force(self.context,
+ volume_id,
+ 'fake_name',
+ 'fake_description')
+ db.snapshot_destroy(self.context, snapshot_ref['id'])
+ db.volume_destroy(self.context, volume_id)
+
class DriverTestCase(test.TestCase):
"""Base Test class for Drivers."""
diff --git a/nova/tests/test_zones.py b/nova/tests/test_zones.py
index e132809dc..a943fee27 100644
--- a/nova/tests/test_zones.py
+++ b/nova/tests/test_zones.py
@@ -198,3 +198,178 @@ class ZoneManagerTestCase(test.TestCase):
self.assertEquals(zone_state.attempt, 3)
self.assertFalse(zone_state.is_active)
self.assertEquals(zone_state.name, None)
+
+ def test_host_service_caps_stale_no_stale_service(self):
+ zm = zone_manager.ZoneManager()
+
+ # services just updated capabilities
+ zm.update_service_capabilities("svc1", "host1", dict(a=1, b=2))
+ zm.update_service_capabilities("svc2", "host1", dict(a=3, b=4))
+ self.assertFalse(zm.host_service_caps_stale("host1", "svc1"))
+ self.assertFalse(zm.host_service_caps_stale("host1", "svc2"))
+
+ def test_host_service_caps_stale_all_stale_services(self):
+ zm = zone_manager.ZoneManager()
+ expiry_time = (FLAGS.periodic_interval * 3) + 1
+
+ # Both services became stale
+ zm.update_service_capabilities("svc1", "host1", dict(a=1, b=2))
+ zm.update_service_capabilities("svc2", "host1", dict(a=3, b=4))
+ time_future = utils.utcnow() + datetime.timedelta(seconds=expiry_time)
+ utils.set_time_override(time_future)
+ self.assertTrue(zm.host_service_caps_stale("host1", "svc1"))
+ self.assertTrue(zm.host_service_caps_stale("host1", "svc2"))
+ utils.clear_time_override()
+
+ def test_host_service_caps_stale_one_stale_service(self):
+ zm = zone_manager.ZoneManager()
+ expiry_time = (FLAGS.periodic_interval * 3) + 1
+
+ # One service became stale
+ zm.update_service_capabilities("svc1", "host1", dict(a=1, b=2))
+ zm.update_service_capabilities("svc2", "host1", dict(a=3, b=4))
+ caps = zm.service_states["host1"]["svc1"]
+ caps["timestamp"] = utils.utcnow() - \
+ datetime.timedelta(seconds=expiry_time)
+ self.assertTrue(zm.host_service_caps_stale("host1", "svc1"))
+ self.assertFalse(zm.host_service_caps_stale("host1", "svc2"))
+
+ def test_delete_expired_host_services_del_one_service(self):
+ zm = zone_manager.ZoneManager()
+
+ # Delete one service in a host
+ zm.update_service_capabilities("svc1", "host1", dict(a=1, b=2))
+ zm.update_service_capabilities("svc2", "host1", dict(a=3, b=4))
+ stale_host_services = {"host1": ["svc1"]}
+ zm.delete_expired_host_services(stale_host_services)
+ self.assertFalse("svc1" in zm.service_states["host1"])
+ self.assertTrue("svc2" in zm.service_states["host1"])
+
+ def test_delete_expired_host_services_del_all_hosts(self):
+ zm = zone_manager.ZoneManager()
+
+ # Delete all services in a host
+ zm.update_service_capabilities("svc2", "host1", dict(a=3, b=4))
+ zm.update_service_capabilities("svc1", "host1", dict(a=1, b=2))
+ stale_host_services = {"host1": ["svc1", "svc2"]}
+ zm.delete_expired_host_services(stale_host_services)
+ self.assertFalse("host1" in zm.service_states)
+
+ def test_delete_expired_host_services_del_one_service_per_host(self):
+ zm = zone_manager.ZoneManager()
+
+ # Delete one service per host
+ zm.update_service_capabilities("svc1", "host1", dict(a=1, b=2))
+ zm.update_service_capabilities("svc1", "host2", dict(a=3, b=4))
+ stale_host_services = {"host1": ["svc1"], "host2": ["svc1"]}
+ zm.delete_expired_host_services(stale_host_services)
+ self.assertFalse("host1" in zm.service_states)
+ self.assertFalse("host2" in zm.service_states)
+
+ def test_get_zone_capabilities_one_host(self):
+ zm = zone_manager.ZoneManager()
+
+ # Service capabilities recent
+ zm.update_service_capabilities("svc1", "host1", dict(a=1, b=2))
+ caps = zm.get_zone_capabilities(None)
+ self.assertEquals(caps, dict(svc1_a=(1, 1), svc1_b=(2, 2)))
+
+ def test_get_zone_capabilities_expired_host(self):
+ zm = zone_manager.ZoneManager()
+ expiry_time = (FLAGS.periodic_interval * 3) + 1
+
+ # Service capabilities stale
+ zm.update_service_capabilities("svc1", "host1", dict(a=1, b=2))
+ time_future = utils.utcnow() + datetime.timedelta(seconds=expiry_time)
+ utils.set_time_override(time_future)
+ caps = zm.get_zone_capabilities(None)
+ self.assertEquals(caps, {})
+ utils.clear_time_override()
+
+ def test_get_zone_capabilities_multiple_hosts(self):
+ zm = zone_manager.ZoneManager()
+
+ # Both host service capabilities recent
+ zm.update_service_capabilities("svc1", "host1", dict(a=1, b=2))
+ zm.update_service_capabilities("svc1", "host2", dict(a=3, b=4))
+ caps = zm.get_zone_capabilities(None)
+ self.assertEquals(caps, dict(svc1_a=(1, 3), svc1_b=(2, 4)))
+
+ def test_get_zone_capabilities_one_stale_host(self):
+ zm = zone_manager.ZoneManager()
+ expiry_time = (FLAGS.periodic_interval * 3) + 1
+
+ # One host service capabilities become stale
+ zm.update_service_capabilities("svc1", "host1", dict(a=1, b=2))
+ zm.update_service_capabilities("svc1", "host2", dict(a=3, b=4))
+ serv_caps = zm.service_states["host1"]["svc1"]
+ serv_caps["timestamp"] = utils.utcnow() - \
+ datetime.timedelta(seconds=expiry_time)
+ caps = zm.get_zone_capabilities(None)
+ self.assertEquals(caps, dict(svc1_a=(3, 3), svc1_b=(4, 4)))
+
+ def test_get_zone_capabilities_multiple_service_per_host(self):
+ zm = zone_manager.ZoneManager()
+
+ # Multiple services per host
+ zm.update_service_capabilities("svc1", "host1", dict(a=1, b=2))
+ zm.update_service_capabilities("svc1", "host2", dict(a=3, b=4))
+ zm.update_service_capabilities("svc2", "host1", dict(a=5, b=6))
+ zm.update_service_capabilities("svc2", "host2", dict(a=7, b=8))
+ caps = zm.get_zone_capabilities(None)
+ self.assertEquals(caps, dict(svc1_a=(1, 3), svc1_b=(2, 4),
+ svc2_a=(5, 7), svc2_b=(6, 8)))
+
+ def test_get_zone_capabilities_one_stale_service_per_host(self):
+ zm = zone_manager.ZoneManager()
+ expiry_time = (FLAGS.periodic_interval * 3) + 1
+
+ # Two host services among four become stale
+ zm.update_service_capabilities("svc1", "host1", dict(a=1, b=2))
+ zm.update_service_capabilities("svc1", "host2", dict(a=3, b=4))
+ zm.update_service_capabilities("svc2", "host1", dict(a=5, b=6))
+ zm.update_service_capabilities("svc2", "host2", dict(a=7, b=8))
+ serv_caps_1 = zm.service_states["host1"]["svc2"]
+ serv_caps_1["timestamp"] = utils.utcnow() - \
+ datetime.timedelta(seconds=expiry_time)
+ serv_caps_2 = zm.service_states["host2"]["svc1"]
+ serv_caps_2["timestamp"] = utils.utcnow() - \
+ datetime.timedelta(seconds=expiry_time)
+ caps = zm.get_zone_capabilities(None)
+ self.assertEquals(caps, dict(svc1_a=(1, 1), svc1_b=(2, 2),
+ svc2_a=(7, 7), svc2_b=(8, 8)))
+
+ def test_get_zone_capabilities_three_stale_host_services(self):
+ zm = zone_manager.ZoneManager()
+ expiry_time = (FLAGS.periodic_interval * 3) + 1
+
+ # Three host services among four become stale
+ zm.update_service_capabilities("svc1", "host1", dict(a=1, b=2))
+ zm.update_service_capabilities("svc1", "host2", dict(a=3, b=4))
+ zm.update_service_capabilities("svc2", "host1", dict(a=5, b=6))
+ zm.update_service_capabilities("svc2", "host2", dict(a=7, b=8))
+ serv_caps_1 = zm.service_states["host1"]["svc2"]
+ serv_caps_1["timestamp"] = utils.utcnow() - \
+ datetime.timedelta(seconds=expiry_time)
+ serv_caps_2 = zm.service_states["host2"]["svc1"]
+ serv_caps_2["timestamp"] = utils.utcnow() - \
+ datetime.timedelta(seconds=expiry_time)
+ serv_caps_3 = zm.service_states["host2"]["svc2"]
+ serv_caps_3["timestamp"] = utils.utcnow() - \
+ datetime.timedelta(seconds=expiry_time)
+ caps = zm.get_zone_capabilities(None)
+ self.assertEquals(caps, dict(svc1_a=(1, 1), svc1_b=(2, 2)))
+
+ def test_get_zone_capabilities_all_stale_host_services(self):
+ zm = zone_manager.ZoneManager()
+ expiry_time = (FLAGS.periodic_interval * 3) + 1
+
+ # All the host services become stale
+ zm.update_service_capabilities("svc1", "host1", dict(a=1, b=2))
+ zm.update_service_capabilities("svc1", "host2", dict(a=3, b=4))
+ zm.update_service_capabilities("svc2", "host1", dict(a=5, b=6))
+ zm.update_service_capabilities("svc2", "host2", dict(a=7, b=8))
+ time_future = utils.utcnow() + datetime.timedelta(seconds=expiry_time)
+ utils.set_time_override(time_future)
+ caps = zm.get_zone_capabilities(None)
+ self.assertEquals(caps, {})