diff options
| author | Cerberus <matt.dietz@rackspace.com> | 2011-04-22 10:39:35 -0500 |
|---|---|---|
| committer | Cerberus <matt.dietz@rackspace.com> | 2011-04-22 10:39:35 -0500 |
| commit | c03e9805328afe1d03fa65ac93d2b91ba04c229e (patch) | |
| tree | 7234fe551b9f6e32b80e91753dbf91a2181d4b9a /nova/tests | |
| parent | 14718afef1cc79b4d41f490be677caf3e4191e2b (diff) | |
| parent | 8af2a2d720b97ef17565d57a9b8b028d449a9c84 (diff) | |
Merge from trunk
Diffstat (limited to 'nova/tests')
30 files changed, 2235 insertions, 286 deletions
diff --git a/nova/tests/api/openstack/extensions/__init__.py b/nova/tests/api/openstack/extensions/__init__.py new file mode 100644 index 000000000..848908a95 --- /dev/null +++ b/nova/tests/api/openstack/extensions/__init__.py @@ -0,0 +1,15 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 OpenStack LLC +# +# 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. diff --git a/nova/tests/api/openstack/test_api.py b/nova/tests/api/openstack/test_api.py index 5112c486f..c63431a45 100644 --- a/nova/tests/api/openstack/test_api.py +++ b/nova/tests/api/openstack/test_api.py @@ -53,13 +53,13 @@ class APITest(test.TestCase): #api.application = succeed api = self._wsgi_app(succeed) resp = Request.blank('/').get_response(api) - self.assertFalse('computeFault' in resp.body, resp.body) + self.assertFalse('cloudServersFault' in resp.body, resp.body) self.assertEqual(resp.status_int, 200, resp.body) #api.application = raise_webob_exc api = self._wsgi_app(raise_webob_exc) resp = Request.blank('/').get_response(api) - self.assertFalse('computeFault' in resp.body, resp.body) + self.assertFalse('cloudServersFault' in resp.body, resp.body) self.assertEqual(resp.status_int, 404, resp.body) #api.application = raise_api_fault @@ -71,11 +71,11 @@ class APITest(test.TestCase): #api.application = fail api = self._wsgi_app(fail) resp = Request.blank('/').get_response(api) - self.assertTrue('{"computeFault' in resp.body, resp.body) + self.assertTrue('{"cloudServersFault' in resp.body, resp.body) self.assertEqual(resp.status_int, 500, resp.body) #api.application = fail api = self._wsgi_app(fail) resp = Request.blank('/.xml').get_response(api) - self.assertTrue('<computeFault' in resp.body, resp.body) + self.assertTrue('<cloudServersFault' in resp.body, resp.body) self.assertEqual(resp.status_int, 500, resp.body) diff --git a/nova/tests/api/openstack/test_faults.py b/nova/tests/api/openstack/test_faults.py index 7667753f4..4d86ffb26 100644 --- a/nova/tests/api/openstack/test_faults.py +++ b/nova/tests/api/openstack/test_faults.py @@ -15,44 +15,127 @@ # License for the specific language governing permissions and limitations # under the License. +import json + import webob import webob.dec import webob.exc from nova import test +from nova.api.openstack import common from nova.api.openstack import faults class TestFaults(test.TestCase): + """Tests covering `nova.api.openstack.faults:Fault` class.""" - def test_fault_parts(self): - req = webob.Request.blank('/.xml') - f = faults.Fault(webob.exc.HTTPBadRequest(explanation='scram')) - resp = req.get_response(f) + def _prepare_xml(self, xml_string): + """Remove characters from string which hinder XML equality testing.""" + xml_string = xml_string.replace(" ", "") + xml_string = xml_string.replace("\n", "") + xml_string = xml_string.replace("\t", "") + return xml_string - first_two_words = resp.body.strip().split()[:2] - self.assertEqual(first_two_words, ['<badRequest', 'code="400">']) - body_without_spaces = ''.join(resp.body.split()) - self.assertTrue('<message>scram</message>' in body_without_spaces) + def test_400_fault_xml(self): + """Test fault serialized to XML via file-extension and/or header.""" + requests = [ + webob.Request.blank('/.xml'), + webob.Request.blank('/', headers={"Accept": "application/xml"}), + ] - def test_retry_header(self): - req = webob.Request.blank('/.xml') - exc = webob.exc.HTTPRequestEntityTooLarge(explanation='sorry', - headers={'Retry-After': 4}) - f = faults.Fault(exc) - resp = req.get_response(f) - first_two_words = resp.body.strip().split()[:2] - self.assertEqual(first_two_words, ['<overLimit', 'code="413">']) - body_sans_spaces = ''.join(resp.body.split()) - self.assertTrue('<message>sorry</message>' in body_sans_spaces) - self.assertTrue('<retryAfter>4</retryAfter>' in body_sans_spaces) - self.assertEqual(resp.headers['Retry-After'], 4) + for request in requests: + fault = faults.Fault(webob.exc.HTTPBadRequest(explanation='scram')) + response = request.get_response(fault) + + expected = self._prepare_xml(""" + <badRequest code="400" xmlns="%s"> + <message>scram</message> + </badRequest> + """ % common.XML_NS_V10) + actual = self._prepare_xml(response.body) + + self.assertEqual(response.content_type, "application/xml") + self.assertEqual(expected, actual) + + def test_400_fault_json(self): + """Test fault serialized to JSON via file-extension and/or header.""" + requests = [ + webob.Request.blank('/.json'), + webob.Request.blank('/', headers={"Accept": "application/json"}), + ] + + for request in requests: + fault = faults.Fault(webob.exc.HTTPBadRequest(explanation='scram')) + response = request.get_response(fault) + + expected = { + "badRequest": { + "message": "scram", + "code": 400, + }, + } + actual = json.loads(response.body) + + self.assertEqual(response.content_type, "application/json") + self.assertEqual(expected, actual) + + def test_413_fault_xml(self): + requests = [ + webob.Request.blank('/.xml'), + webob.Request.blank('/', headers={"Accept": "application/xml"}), + ] + + for request in requests: + exc = webob.exc.HTTPRequestEntityTooLarge + fault = faults.Fault(exc(explanation='sorry', + headers={'Retry-After': 4})) + response = request.get_response(fault) + + expected = self._prepare_xml(""" + <overLimit code="413" xmlns="%s"> + <message>sorry</message> + <retryAfter>4</retryAfter> + </overLimit> + """ % common.XML_NS_V10) + actual = self._prepare_xml(response.body) + + self.assertEqual(expected, actual) + self.assertEqual(response.content_type, "application/xml") + self.assertEqual(response.headers['Retry-After'], 4) + + def test_413_fault_json(self): + """Test fault serialized to JSON via file-extension and/or header.""" + requests = [ + webob.Request.blank('/.json'), + webob.Request.blank('/', headers={"Accept": "application/json"}), + ] + + for request in requests: + exc = webob.exc.HTTPRequestEntityTooLarge + fault = faults.Fault(exc(explanation='sorry', + headers={'Retry-After': 4})) + response = request.get_response(fault) + + expected = { + "overLimit": { + "message": "sorry", + "code": 413, + "retryAfter": 4, + }, + } + actual = json.loads(response.body) + + self.assertEqual(response.content_type, "application/json") + self.assertEqual(expected, actual) def test_raise(self): + """Ensure the ability to raise `Fault`s in WSGI-ified methods.""" @webob.dec.wsgify def raiser(req): raise faults.Fault(webob.exc.HTTPNotFound(explanation='whut?')) + req = webob.Request.blank('/.xml') resp = req.get_response(raiser) + self.assertEqual(resp.content_type, "application/xml") self.assertEqual(resp.status_int, 404) self.assertTrue('whut?' in resp.body) diff --git a/nova/tests/api/openstack/test_image_metadata.py b/nova/tests/api/openstack/test_image_metadata.py index 9be753f84..56be0f1cc 100644 --- a/nova/tests/api/openstack/test_image_metadata.py +++ b/nova/tests/api/openstack/test_image_metadata.py @@ -45,10 +45,8 @@ class ImageMetaDataTest(unittest.TestCase): 'is_public': True, 'deleted_at': None, 'properties': { - 'type': 'ramdisk', 'key1': 'value1', - 'key2': 'value2' - }, + 'key2': 'value2'}, 'size': 5882349}, {'status': 'active', 'name': 'image2', @@ -62,10 +60,21 @@ class ImageMetaDataTest(unittest.TestCase): 'is_public': True, 'deleted_at': None, 'properties': { - 'type': 'ramdisk', 'key1': 'value1', - 'key2': 'value2' - }, + 'key2': 'value2'}, + 'size': 5882349}, + {'status': 'active', + 'name': 'image3', + 'deleted': False, + 'container_format': None, + 'created_at': '2011-03-22T17:40:15', + 'disk_format': None, + 'updated_at': '2011-03-22T17:40:15', + 'id': '3', + 'location': 'file:///var/lib/glance/images/2', + 'is_public': True, + 'deleted_at': None, + 'properties': {}, 'size': 5882349}, ] @@ -77,6 +86,10 @@ class ImageMetaDataTest(unittest.TestCase): fakes.FakeAuthManager.auth_data = {} fakes.FakeAuthDatabase.data = {} fakes.stub_out_auth(self.stubs) + # NOTE(dprince) max out properties/metadata in image 3 for testing + img3 = self.IMAGE_FIXTURES[2] + for num in range(FLAGS.quota_metadata_items): + img3['properties']['key%i' % num] = "blah" fakes.stub_out_glance(self.stubs, self.IMAGE_FIXTURES) def tearDown(self): @@ -164,3 +177,25 @@ class ImageMetaDataTest(unittest.TestCase): req.method = 'DELETE' res = req.get_response(fakes.wsgi_app()) self.assertEqual(404, res.status_int) + + def test_too_many_metadata_items_on_create(self): + data = {"metadata": {}} + for num in range(FLAGS.quota_metadata_items + 1): + data['metadata']['key%i' % num] = "blah" + json_string = str(data).replace("\'", "\"") + req = webob.Request.blank('/v1.1/images/2/meta') + req.environ['api.version'] = '1.1' + req.method = 'POST' + req.body = json_string + req.headers["content-type"] = "application/json" + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(400, res.status_int) + + def test_too_many_metadata_items_on_put(self): + req = webob.Request.blank('/v1.1/images/3/meta/blah') + req.environ['api.version'] = '1.1' + req.method = 'PUT' + req.body = '{"blah": "blah"}' + req.headers["content-type"] = "application/json" + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(400, res.status_int) diff --git a/nova/tests/api/openstack/test_images.py b/nova/tests/api/openstack/test_images.py index 738bdda19..ae86d0686 100644 --- a/nova/tests/api/openstack/test_images.py +++ b/nova/tests/api/openstack/test_images.py @@ -20,11 +20,13 @@ Tests of the new image services, both as a service layer, and as a WSGI layer """ +import copy import json import datetime import os import shutil import tempfile +import xml.dom.minidom as minidom import stubout import webob @@ -144,7 +146,7 @@ class LocalImageServiceTest(_BaseImageServiceTests): for x in [1, 2, 3]: tempfile.mkstemp(prefix='ami-', dir=self.tempdir) # create some valid image directories names - for x in ["1485baed", "1a60f0ee", "3123a73d"]: + for x in ["1485baed", "1a60f0ee", "3123a73d"]: os.makedirs(os.path.join(self.tempdir, x)) found_image_ids = self.service._ids() self.assertEqual(True, isinstance(found_image_ids, list)) @@ -214,12 +216,14 @@ class GlanceImageServiceTest(_BaseImageServiceTests): class ImageControllerWithGlanceServiceTest(test.TestCase): - """Test of the OpenStack API /images application controller""" - + """ + Test of the OpenStack API /images application controller w/Glance. + """ NOW_GLANCE_FORMAT = "2010-10-11T10:30:22" NOW_API_FORMAT = "2010-10-11T10:30:22Z" def setUp(self): + """Run before each test.""" super(ImageControllerWithGlanceServiceTest, self).setUp() self.orig_image_service = FLAGS.image_service FLAGS.image_service = 'nova.image.glance.GlanceImageService' @@ -230,51 +234,465 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): fakes.stub_out_rate_limiting(self.stubs) fakes.stub_out_auth(self.stubs) fakes.stub_out_key_pair_funcs(self.stubs) - fixtures = self._make_image_fixtures() - fakes.stub_out_glance(self.stubs, initial_fixtures=fixtures) + self.fixtures = self._make_image_fixtures() + fakes.stub_out_glance(self.stubs, initial_fixtures=self.fixtures) def tearDown(self): + """Run after each test.""" self.stubs.UnsetAll() FLAGS.image_service = self.orig_image_service super(ImageControllerWithGlanceServiceTest, self).tearDown() + def _applicable_fixture(self, fixture, user_id): + """Determine if this fixture is applicable for given user id.""" + is_public = fixture["is_public"] + try: + uid = int(fixture["properties"]["user_id"]) + except KeyError: + uid = None + return uid == user_id or is_public + def test_get_image_index(self): - req = webob.Request.blank('/v1.0/images') - res = req.get_response(fakes.wsgi_app()) - image_metas = json.loads(res.body)['images'] + request = webob.Request.blank('/v1.0/images') + response = request.get_response(fakes.wsgi_app()) + + response_dict = json.loads(response.body) + response_list = response_dict["images"] expected = [{'id': 123, 'name': 'public image'}, {'id': 124, 'name': 'queued backup'}, {'id': 125, 'name': 'saving backup'}, {'id': 126, 'name': 'active backup'}, - {'id': 127, 'name': 'killed backup'}] - - self.assertDictListMatch(image_metas, expected) + {'id': 127, 'name': 'killed backup'}, + {'id': 129, 'name': None}] + + self.assertDictListMatch(response_list, expected) + + def test_get_image(self): + request = webob.Request.blank('/v1.0/images/123') + response = request.get_response(fakes.wsgi_app()) + + self.assertEqual(200, response.status_int) + + actual_image = json.loads(response.body) + + expected_image = { + "image": { + "id": 123, + "name": "public image", + "updated": self.NOW_API_FORMAT, + "created": self.NOW_API_FORMAT, + "status": "ACTIVE", + }, + } + + self.assertEqual(expected_image, actual_image) + + def test_get_image_v1_1(self): + request = webob.Request.blank('/v1.1/images/123') + response = request.get_response(fakes.wsgi_app()) + + actual_image = json.loads(response.body) + + href = "http://localhost/v1.1/images/123" + + expected_image = { + "image": { + "id": 123, + "name": "public image", + "updated": self.NOW_API_FORMAT, + "created": self.NOW_API_FORMAT, + "status": "ACTIVE", + "links": [{ + "rel": "self", + "href": href, + }, + { + "rel": "bookmark", + "type": "application/json", + "href": href, + }, + { + "rel": "bookmark", + "type": "application/xml", + "href": href, + }], + }, + } + + self.assertEqual(expected_image, actual_image) + + def test_get_image_xml(self): + request = webob.Request.blank('/v1.0/images/123') + request.accept = "application/xml" + response = request.get_response(fakes.wsgi_app()) + + actual_image = minidom.parseString(response.body.replace(" ", "")) + + expected_now = self.NOW_API_FORMAT + expected_image = minidom.parseString(""" + <image id="123" + name="public image" + updated="%(expected_now)s" + created="%(expected_now)s" + status="ACTIVE" + xmlns="http://docs.rackspacecloud.com/servers/api/v1.0" /> + """ % (locals())) + + self.assertEqual(expected_image.toxml(), actual_image.toxml()) + + def test_get_image_xml_no_name(self): + request = webob.Request.blank('/v1.0/images/129') + request.accept = "application/xml" + response = request.get_response(fakes.wsgi_app()) + + actual_image = minidom.parseString(response.body.replace(" ", "")) + + expected_now = self.NOW_API_FORMAT + expected_image = minidom.parseString(""" + <image id="129" + name="None" + updated="%(expected_now)s" + created="%(expected_now)s" + status="ACTIVE" + xmlns="http://docs.rackspacecloud.com/servers/api/v1.0" /> + """ % (locals())) + + self.assertEqual(expected_image.toxml(), actual_image.toxml()) + + def test_get_image_v1_1_xml(self): + request = webob.Request.blank('/v1.1/images/123') + request.accept = "application/xml" + response = request.get_response(fakes.wsgi_app()) + + actual_image = minidom.parseString(response.body.replace(" ", "")) + + expected_href = "http://localhost/v1.1/images/123" + expected_now = self.NOW_API_FORMAT + expected_image = minidom.parseString(""" + <image id="123" + name="public image" + 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="self"/> + <link href="%(expected_href)s" rel="bookmark" + type="application/json" /> + <link href="%(expected_href)s" rel="bookmark" + type="application/xml" /> + </links> + </image> + """.replace(" ", "") % (locals())) + + self.assertEqual(expected_image.toxml(), actual_image.toxml()) + + def test_get_image_404_json(self): + request = webob.Request.blank('/v1.0/images/NonExistantImage') + response = request.get_response(fakes.wsgi_app()) + self.assertEqual(404, response.status_int) + + expected = { + "itemNotFound": { + "message": "Image not found.", + "code": 404, + }, + } + + actual = json.loads(response.body) + + self.assertEqual(expected, actual) + + def test_get_image_404_xml(self): + request = webob.Request.blank('/v1.0/images/NonExistantImage') + request.accept = "application/xml" + response = request.get_response(fakes.wsgi_app()) + self.assertEqual(404, response.status_int) + + expected = minidom.parseString(""" + <itemNotFound code="404" + xmlns="http://docs.rackspacecloud.com/servers/api/v1.0"> + <message> + Image not found. + </message> + </itemNotFound> + """.replace(" ", "")) + + actual = minidom.parseString(response.body.replace(" ", "")) + + self.assertEqual(expected.toxml(), actual.toxml()) + + def test_get_image_404_v1_1_json(self): + request = webob.Request.blank('/v1.1/images/NonExistantImage') + response = request.get_response(fakes.wsgi_app()) + self.assertEqual(404, response.status_int) + + expected = { + "itemNotFound": { + "message": "Image not found.", + "code": 404, + }, + } + + actual = json.loads(response.body) + + self.assertEqual(expected, actual) + + def test_get_image_404_v1_1_xml(self): + request = webob.Request.blank('/v1.1/images/NonExistantImage') + request.accept = "application/xml" + response = request.get_response(fakes.wsgi_app()) + self.assertEqual(404, response.status_int) + + # NOTE(justinsb): I believe this should still use the v1.0 XSD, + # because the element hasn't changed definition + expected = minidom.parseString(""" + <itemNotFound code="404" + xmlns="http://docs.rackspacecloud.com/servers/api/v1.0"> + <message> + Image not found. + </message> + </itemNotFound> + """.replace(" ", "")) + + actual = minidom.parseString(response.body.replace(" ", "")) + + self.assertEqual(expected.toxml(), actual.toxml()) + + def test_get_image_index_v1_1(self): + request = webob.Request.blank('/v1.1/images') + response = request.get_response(fakes.wsgi_app()) + + response_dict = json.loads(response.body) + response_list = response_dict["images"] + + fixtures = copy.copy(self.fixtures) + + for image in fixtures: + if not self._applicable_fixture(image, 1): + fixtures.remove(image) + continue + + href = "http://localhost/v1.1/images/%s" % image["id"] + test_image = { + "id": image["id"], + "name": image["name"], + "links": [{ + "rel": "self", + "href": "http://localhost/v1.1/images/%s" % image["id"], + }, + { + "rel": "bookmark", + "type": "application/json", + "href": href, + }, + { + "rel": "bookmark", + "type": "application/xml", + "href": href, + }], + } + self.assertTrue(test_image in response_list) + + self.assertEqual(len(response_list), len(fixtures)) def test_get_image_details(self): - req = webob.Request.blank('/v1.0/images/detail') - res = req.get_response(fakes.wsgi_app()) - image_metas = json.loads(res.body)['images'] - - now = self.NOW_API_FORMAT - expected = [ - {'id': 123, 'name': 'public image', 'updated': now, - 'created': now, 'status': 'ACTIVE'}, - {'id': 124, 'name': 'queued backup', 'serverId': 42, - 'updated': now, 'created': now, - 'status': 'QUEUED'}, - {'id': 125, 'name': 'saving backup', 'serverId': 42, - 'updated': now, 'created': now, - 'status': 'SAVING', 'progress': 0}, - {'id': 126, 'name': 'active backup', 'serverId': 42, - 'updated': now, 'created': now, - 'status': 'ACTIVE'}, - {'id': 127, 'name': 'killed backup', 'serverId': 42, - 'updated': now, 'created': now, - 'status': 'FAILED'} + request = webob.Request.blank('/v1.0/images/detail') + response = request.get_response(fakes.wsgi_app()) + + response_dict = json.loads(response.body) + response_list = response_dict["images"] + + expected = [{ + 'id': 123, + 'name': 'public image', + 'updated': self.NOW_API_FORMAT, + 'created': self.NOW_API_FORMAT, + 'status': 'ACTIVE', + }, + { + 'id': 124, + 'name': 'queued backup', + 'serverId': 42, + 'updated': self.NOW_API_FORMAT, + 'created': self.NOW_API_FORMAT, + 'status': 'QUEUED', + }, + { + 'id': 125, + 'name': 'saving backup', + 'serverId': 42, + 'updated': self.NOW_API_FORMAT, + 'created': self.NOW_API_FORMAT, + 'status': 'SAVING', + 'progress': 0, + }, + { + 'id': 126, + 'name': 'active backup', + 'serverId': 42, + 'updated': self.NOW_API_FORMAT, + 'created': self.NOW_API_FORMAT, + 'status': 'ACTIVE' + }, + { + 'id': 127, + 'name': 'killed backup', 'serverId': 42, + 'updated': self.NOW_API_FORMAT, + 'created': self.NOW_API_FORMAT, + 'status': 'FAILED', + }, + { + 'id': 129, + 'name': None, + 'updated': self.NOW_API_FORMAT, + 'created': self.NOW_API_FORMAT, + 'status': 'ACTIVE', + }] + + self.assertDictListMatch(expected, response_list) + + def test_get_image_details_v1_1(self): + request = webob.Request.blank('/v1.1/images/detail') + response = request.get_response(fakes.wsgi_app()) + + response_dict = json.loads(response.body) + response_list = response_dict["images"] + + expected = [{ + 'id': 123, + 'name': 'public image', + 'updated': self.NOW_API_FORMAT, + 'created': self.NOW_API_FORMAT, + 'status': 'ACTIVE', + "links": [{ + "rel": "self", + "href": "http://localhost/v1.1/images/123", + }, + { + "rel": "bookmark", + "type": "application/json", + "href": "http://localhost/v1.1/images/123", + }, + { + "rel": "bookmark", + "type": "application/xml", + "href": "http://localhost/v1.1/images/123", + }], + }, + { + 'id': 124, + 'name': 'queued backup', + 'serverId': 42, + 'updated': self.NOW_API_FORMAT, + 'created': self.NOW_API_FORMAT, + 'status': 'QUEUED', + "links": [{ + "rel": "self", + "href": "http://localhost/v1.1/images/124", + }, + { + "rel": "bookmark", + "type": "application/json", + "href": "http://localhost/v1.1/images/124", + }, + { + "rel": "bookmark", + "type": "application/xml", + "href": "http://localhost/v1.1/images/124", + }], + }, + { + 'id': 125, + 'name': 'saving backup', + 'serverId': 42, + 'updated': self.NOW_API_FORMAT, + 'created': self.NOW_API_FORMAT, + 'status': 'SAVING', + 'progress': 0, + "links": [{ + "rel": "self", + "href": "http://localhost/v1.1/images/125", + }, + { + "rel": "bookmark", + "type": "application/json", + "href": "http://localhost/v1.1/images/125", + }, + { + "rel": "bookmark", + "type": "application/xml", + "href": "http://localhost/v1.1/images/125", + }], + }, + { + 'id': 126, + 'name': 'active backup', + 'serverId': 42, + 'updated': self.NOW_API_FORMAT, + 'created': self.NOW_API_FORMAT, + 'status': 'ACTIVE', + "links": [{ + "rel": "self", + "href": "http://localhost/v1.1/images/126", + }, + { + "rel": "bookmark", + "type": "application/json", + "href": "http://localhost/v1.1/images/126", + }, + { + "rel": "bookmark", + "type": "application/xml", + "href": "http://localhost/v1.1/images/126", + }], + }, + { + 'id': 127, + 'name': 'killed backup', 'serverId': 42, + 'updated': self.NOW_API_FORMAT, + 'created': self.NOW_API_FORMAT, + 'status': 'FAILED', + "links": [{ + "rel": "self", + "href": "http://localhost/v1.1/images/127", + }, + { + "rel": "bookmark", + "type": "application/json", + "href": "http://localhost/v1.1/images/127", + }, + { + "rel": "bookmark", + "type": "application/xml", + "href": "http://localhost/v1.1/images/127", + }], + }, + { + 'id': 129, + 'name': None, + 'updated': self.NOW_API_FORMAT, + 'created': self.NOW_API_FORMAT, + 'status': 'ACTIVE', + "links": [{ + "rel": "self", + "href": "http://localhost/v1.1/images/129", + }, + { + "rel": "bookmark", + "type": "application/json", + "href": "http://localhost/v1.1/images/129", + }, + { + "rel": "bookmark", + "type": "application/xml", + "href": "http://localhost/v1.1/images/129", + }], + }, ] - self.assertDictListMatch(image_metas, expected) + self.assertDictListMatch(expected, response_list) def test_get_image_found(self): req = webob.Request.blank('/v1.0/images/123') @@ -331,4 +749,9 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): status='active', properties=other_backup_properties) image_id += 1 + # Image without a name + add_fixture(id=image_id, is_public=True, status='active', + properties={}) + image_id += 1 + return fixtures diff --git a/nova/tests/api/openstack/test_limits.py b/nova/tests/api/openstack/test_limits.py index 05cfacc60..df367005d 100644 --- a/nova/tests/api/openstack/test_limits.py +++ b/nova/tests/api/openstack/test_limits.py @@ -136,10 +136,17 @@ class LimitsControllerTest(BaseLimitTestSuite): request = self._get_index_request("application/xml") response = request.get_response(self.controller) - expected = "<limits><rate/><absolute/></limits>" - body = response.body.replace("\n", "").replace(" ", "") + expected = parseString(""" + <limits + xmlns="http://docs.rackspacecloud.com/servers/api/v1.0"> + <rate/> + <absolute/> + </limits> + """.replace(" ", "")) - self.assertEqual(expected, body) + body = parseString(response.body.replace(" ", "")) + + self.assertEqual(expected.toxml(), body.toxml()) def test_index_xml(self): """Test getting limit details in XML.""" @@ -148,7 +155,8 @@ class LimitsControllerTest(BaseLimitTestSuite): response = request.get_response(self.controller) expected = parseString(""" - <limits> + <limits + xmlns="http://docs.rackspacecloud.com/servers/api/v1.0"> <rate> <limit URI="*" regex=".*" remaining="10" resetTime="0" unit="MINUTE" value="10" verb="GET"/> diff --git a/nova/tests/api/openstack/test_server_metadata.py b/nova/tests/api/openstack/test_server_metadata.py index c8d456472..c4d1d4fd8 100644 --- a/nova/tests/api/openstack/test_server_metadata.py +++ b/nova/tests/api/openstack/test_server_metadata.py @@ -21,11 +21,19 @@ import unittest import webob +from nova import flags from nova.api import openstack from nova.tests.api.openstack import fakes import nova.wsgi +FLAGS = flags.FLAGS + + +def return_create_instance_metadata_max(context, server_id, metadata): + return stub_max_server_metadata() + + def return_create_instance_metadata(context, server_id, metadata): return stub_server_metadata() @@ -48,8 +56,14 @@ def stub_server_metadata(): "key2": "value2", "key3": "value3", "key4": "value4", - "key5": "value5" - } + "key5": "value5"} + return metadata + + +def stub_max_server_metadata(): + metadata = {"metadata": {}} + for num in range(FLAGS.quota_metadata_items): + metadata['metadata']['key%i' % num] = "blah" return metadata @@ -69,7 +83,7 @@ class ServerMetaDataTest(unittest.TestCase): def test_index(self): self.stubs.Set(nova.db.api, 'instance_metadata_get', - return_server_metadata) + return_server_metadata) req = webob.Request.blank('/v1.1/servers/1/meta') req.environ['api.version'] = '1.1' res = req.get_response(fakes.wsgi_app()) @@ -79,7 +93,7 @@ class ServerMetaDataTest(unittest.TestCase): def test_index_no_data(self): self.stubs.Set(nova.db.api, 'instance_metadata_get', - return_empty_server_metadata) + return_empty_server_metadata) req = webob.Request.blank('/v1.1/servers/1/meta') req.environ['api.version'] = '1.1' res = req.get_response(fakes.wsgi_app()) @@ -89,7 +103,7 @@ class ServerMetaDataTest(unittest.TestCase): def test_show(self): self.stubs.Set(nova.db.api, 'instance_metadata_get', - return_server_metadata) + return_server_metadata) req = webob.Request.blank('/v1.1/servers/1/meta/key5') req.environ['api.version'] = '1.1' res = req.get_response(fakes.wsgi_app()) @@ -99,7 +113,7 @@ class ServerMetaDataTest(unittest.TestCase): def test_show_meta_not_found(self): self.stubs.Set(nova.db.api, 'instance_metadata_get', - return_empty_server_metadata) + return_empty_server_metadata) req = webob.Request.blank('/v1.1/servers/1/meta/key6') req.environ['api.version'] = '1.1' res = req.get_response(fakes.wsgi_app()) @@ -108,7 +122,7 @@ class ServerMetaDataTest(unittest.TestCase): def test_delete(self): self.stubs.Set(nova.db.api, 'instance_metadata_delete', - delete_server_metadata) + delete_server_metadata) req = webob.Request.blank('/v1.1/servers/1/meta/key5') req.environ['api.version'] = '1.1' req.method = 'DELETE' @@ -117,7 +131,7 @@ class ServerMetaDataTest(unittest.TestCase): def test_create(self): self.stubs.Set(nova.db.api, 'instance_metadata_update_or_create', - return_create_instance_metadata) + return_create_instance_metadata) req = webob.Request.blank('/v1.1/servers/1/meta') req.environ['api.version'] = '1.1' req.method = 'POST' @@ -130,7 +144,7 @@ class ServerMetaDataTest(unittest.TestCase): def test_update_item(self): self.stubs.Set(nova.db.api, 'instance_metadata_update_or_create', - return_create_instance_metadata) + return_create_instance_metadata) req = webob.Request.blank('/v1.1/servers/1/meta/key1') req.environ['api.version'] = '1.1' req.method = 'PUT' @@ -143,7 +157,7 @@ class ServerMetaDataTest(unittest.TestCase): def test_update_item_too_many_keys(self): self.stubs.Set(nova.db.api, 'instance_metadata_update_or_create', - return_create_instance_metadata) + return_create_instance_metadata) req = webob.Request.blank('/v1.1/servers/1/meta/key1') req.environ['api.version'] = '1.1' req.method = 'PUT' @@ -154,7 +168,7 @@ class ServerMetaDataTest(unittest.TestCase): def test_update_item_body_uri_mismatch(self): self.stubs.Set(nova.db.api, 'instance_metadata_update_or_create', - return_create_instance_metadata) + return_create_instance_metadata) req = webob.Request.blank('/v1.1/servers/1/meta/bad') req.environ['api.version'] = '1.1' req.method = 'PUT' @@ -162,3 +176,29 @@ class ServerMetaDataTest(unittest.TestCase): req.headers["content-type"] = "application/json" res = req.get_response(fakes.wsgi_app()) self.assertEqual(400, res.status_int) + + def test_too_many_metadata_items_on_create(self): + self.stubs.Set(nova.db.api, 'instance_metadata_update_or_create', + return_create_instance_metadata) + data = {"metadata": {}} + for num in range(FLAGS.quota_metadata_items + 1): + data['metadata']['key%i' % num] = "blah" + json_string = str(data).replace("\'", "\"") + req = webob.Request.blank('/v1.1/servers/1/meta') + req.environ['api.version'] = '1.1' + req.method = 'POST' + req.body = json_string + req.headers["content-type"] = "application/json" + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(400, res.status_int) + + def test_to_many_metadata_items_on_update_item(self): + self.stubs.Set(nova.db.api, 'instance_metadata_update_or_create', + return_create_instance_metadata_max) + req = webob.Request.blank('/v1.1/servers/1/meta/key1') + req.environ['api.version'] = '1.1' + req.method = 'PUT' + req.body = '{"a new key": "a new value"}' + req.headers["content-type"] = "application/json" + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(400, res.status_int) diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 737b43c7b..556046e9d 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -32,6 +32,7 @@ from nova import test import nova.api.openstack from nova.api.openstack import servers import nova.compute.api +from nova.compute import instance_types import nova.db.api from nova.db.sqlalchemy.models import Instance from nova.db.sqlalchemy.models import InstanceMetadata @@ -71,13 +72,19 @@ def instance_address(context, instance_id): return None -def stub_instance(id, user_id=1, private_address=None, public_addresses=None): +def stub_instance(id, user_id=1, private_address=None, public_addresses=None, + host=None): metadata = [] metadata.append(InstanceMetadata(key='seq', value=id)) - if public_addresses == None: + inst_type = instance_types.get_instance_type_by_flavor_id(1) + + if public_addresses is None: public_addresses = list() + if host is not None: + host = str(host) + instance = { "id": id, "admin_pass": "", @@ -95,8 +102,8 @@ def stub_instance(id, user_id=1, private_address=None, public_addresses=None): "vcpus": 0, "local_gb": 0, "hostname": "", - "host": None, - "instance_type": "1", + "host": host, + "instance_type": dict(inst_type), "user_data": "", "reservation_id": "", "mac_address": "", @@ -192,6 +199,26 @@ class ServersTest(test.TestCase): print res_dict['server'] self.assertEqual(res_dict['server']['links'], expected_links) + def test_get_server_by_id_with_addresses_xml(self): + private = "192.168.0.3" + public = ["1.2.3.4"] + new_return_server = return_server_with_addresses(private, public) + self.stubs.Set(nova.db.api, 'instance_get', new_return_server) + req = webob.Request.blank('/v1.0/servers/1') + req.headers['Accept'] = 'application/xml' + res = req.get_response(fakes.wsgi_app()) + dom = minidom.parseString(res.body) + server = dom.childNodes[0] + self.assertEquals(server.nodeName, 'server') + self.assertEquals(server.getAttribute('id'), '1') + self.assertEquals(server.getAttribute('name'), 'server1') + (public,) = server.getElementsByTagName('public') + (ip,) = public.getElementsByTagName('ip') + self.assertEquals(ip.getAttribute('addr'), '1.2.3.4') + (private,) = server.getElementsByTagName('private') + (ip,) = private.getElementsByTagName('ip') + self.assertEquals(ip.getAttribute('addr'), '192.168.0.3') + def test_get_server_by_id_with_addresses(self): private = "192.168.0.3" public = ["1.2.3.4"] @@ -208,6 +235,84 @@ class ServersTest(test.TestCase): self.assertEqual(len(addresses["private"]), 1) self.assertEqual(addresses["private"][0], private) + def test_get_server_addresses_V10(self): + private = '192.168.0.3' + public = ['1.2.3.4'] + new_return_server = return_server_with_addresses(private, public) + self.stubs.Set(nova.db.api, 'instance_get', new_return_server) + req = webob.Request.blank('/v1.0/servers/1/ips') + res = req.get_response(fakes.wsgi_app()) + res_dict = json.loads(res.body) + self.assertEqual(res_dict, { + 'addresses': {'public': public, 'private': [private]}}) + + def test_get_server_addresses_xml_V10(self): + private_expected = "192.168.0.3" + public_expected = ["1.2.3.4"] + new_return_server = return_server_with_addresses(private_expected, + public_expected) + self.stubs.Set(nova.db.api, 'instance_get', new_return_server) + req = webob.Request.blank('/v1.0/servers/1/ips') + req.headers['Accept'] = 'application/xml' + res = req.get_response(fakes.wsgi_app()) + dom = minidom.parseString(res.body) + (addresses,) = dom.childNodes + self.assertEquals(addresses.nodeName, 'addresses') + (public,) = addresses.getElementsByTagName('public') + (ip,) = public.getElementsByTagName('ip') + self.assertEquals(ip.getAttribute('addr'), public_expected[0]) + (private,) = addresses.getElementsByTagName('private') + (ip,) = private.getElementsByTagName('ip') + self.assertEquals(ip.getAttribute('addr'), private_expected) + + def test_get_server_addresses_public_V10(self): + private = "192.168.0.3" + public = ["1.2.3.4"] + new_return_server = return_server_with_addresses(private, public) + self.stubs.Set(nova.db.api, 'instance_get', new_return_server) + req = webob.Request.blank('/v1.0/servers/1/ips/public') + res = req.get_response(fakes.wsgi_app()) + res_dict = json.loads(res.body) + self.assertEqual(res_dict, {'public': public}) + + def test_get_server_addresses_private_V10(self): + private = "192.168.0.3" + public = ["1.2.3.4"] + new_return_server = return_server_with_addresses(private, public) + self.stubs.Set(nova.db.api, 'instance_get', new_return_server) + req = webob.Request.blank('/v1.0/servers/1/ips/private') + res = req.get_response(fakes.wsgi_app()) + res_dict = json.loads(res.body) + self.assertEqual(res_dict, {'private': [private]}) + + def test_get_server_addresses_public_xml_V10(self): + private = "192.168.0.3" + public = ["1.2.3.4"] + new_return_server = return_server_with_addresses(private, public) + self.stubs.Set(nova.db.api, 'instance_get', new_return_server) + req = webob.Request.blank('/v1.0/servers/1/ips/public') + req.headers['Accept'] = 'application/xml' + res = req.get_response(fakes.wsgi_app()) + dom = minidom.parseString(res.body) + (public_node,) = dom.childNodes + self.assertEquals(public_node.nodeName, 'public') + (ip,) = public_node.getElementsByTagName('ip') + self.assertEquals(ip.getAttribute('addr'), public[0]) + + def test_get_server_addresses_private_xml_V10(self): + private = "192.168.0.3" + public = ["1.2.3.4"] + new_return_server = return_server_with_addresses(private, public) + self.stubs.Set(nova.db.api, 'instance_get', new_return_server) + req = webob.Request.blank('/v1.0/servers/1/ips/private') + req.headers['Accept'] = 'application/xml' + res = req.get_response(fakes.wsgi_app()) + dom = minidom.parseString(res.body) + (private_node,) = dom.childNodes + self.assertEquals(private_node.nodeName, 'private') + (ip,) = private_node.getElementsByTagName('ip') + self.assertEquals(ip.getAttribute('addr'), private) + def test_get_server_by_id_with_addresses_v11(self): private = "192.168.0.3" public = ["1.2.3.4"] @@ -377,7 +482,6 @@ class ServersTest(test.TestCase): res = req.get_response(fakes.wsgi_app()) server = json.loads(res.body)['server'] - self.assertEqual('serv', server['adminPass'][:4]) self.assertEqual(16, len(server['adminPass'])) self.assertEqual('server_test', server['name']) self.assertEqual(1, server['id']) @@ -392,6 +496,74 @@ class ServersTest(test.TestCase): fakes.stub_out_key_pair_funcs(self.stubs, have_key_pair=False) self._test_create_instance_helper() + def test_create_instance_no_name(self): + self._setup_for_create_instance() + + body = { + 'server': { + 'imageId': 3, + 'flavorId': 1, + 'metadata': { + 'hello': 'world', + 'open': 'stack', + }, + 'personality': {}, + }, + } + + req = webob.Request.blank('/v1.0/servers') + req.method = 'POST' + req.body = json.dumps(body) + req.headers["content-type"] = "application/json" + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 400) + + def test_create_instance_nonstring_name(self): + self._setup_for_create_instance() + + body = { + 'server': { + 'name': 12, + 'imageId': 3, + 'flavorId': 1, + 'metadata': { + 'hello': 'world', + 'open': 'stack', + }, + 'personality': {}, + }, + } + + req = webob.Request.blank('/v1.0/servers') + req.method = 'POST' + req.body = json.dumps(body) + req.headers["content-type"] = "application/json" + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 400) + + def test_create_instance_whitespace_name(self): + self._setup_for_create_instance() + + body = { + 'server': { + 'name': ' ', + 'imageId': 3, + 'flavorId': 1, + 'metadata': { + 'hello': 'world', + 'open': 'stack', + }, + 'personality': {}, + }, + } + + req = webob.Request.blank('/v1.0/servers') + req.method = 'POST' + req.body = json.dumps(body) + req.headers["content-type"] = "application/json" + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 400) + def test_create_instance_v11(self): self._setup_for_create_instance() @@ -418,7 +590,6 @@ class ServersTest(test.TestCase): res = req.get_response(fakes.wsgi_app()) server = json.loads(res.body)['server'] - self.assertEqual('serv', server['adminPass'][:4]) self.assertEqual(16, len(server['adminPass'])) self.assertEqual('server_test', server['name']) self.assertEqual(1, server['id']) @@ -442,62 +613,195 @@ class ServersTest(test.TestCase): res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 400) + def test_create_instance_with_admin_pass_v10(self): + self._setup_for_create_instance() + + body = { + 'server': { + 'name': 'test-server-create', + 'imageId': 3, + 'flavorId': 1, + 'adminPass': 'testpass', + }, + } + + req = webob.Request.blank('/v1.0/servers') + req.method = 'POST' + req.body = json.dumps(body) + req.headers['content-type'] = "application/json" + res = req.get_response(fakes.wsgi_app()) + res = json.loads(res.body) + self.assertNotEqual(res['server']['adminPass'], + body['server']['adminPass']) + + def test_create_instance_with_admin_pass_v11(self): + self._setup_for_create_instance() + + imageRef = 'http://localhost/v1.1/images/2' + flavorRef = 'http://localhost/v1.1/flavors/3' + body = { + 'server': { + 'name': 'server_test', + 'imageRef': imageRef, + 'flavorRef': flavorRef, + 'adminPass': 'testpass', + }, + } + + req = webob.Request.blank('/v1.1/servers') + req.method = 'POST' + req.body = json.dumps(body) + req.headers['content-type'] = "application/json" + res = req.get_response(fakes.wsgi_app()) + server = json.loads(res.body)['server'] + self.assertEqual(server['adminPass'], body['server']['adminPass']) + + def test_create_instance_with_empty_admin_pass_v11(self): + self._setup_for_create_instance() + + imageRef = 'http://localhost/v1.1/images/2' + flavorRef = 'http://localhost/v1.1/flavors/3' + body = { + 'server': { + 'name': 'server_test', + 'imageRef': imageRef, + 'flavorRef': flavorRef, + 'adminPass': '', + }, + } + + req = webob.Request.blank('/v1.1/servers') + req.method = 'POST' + req.body = json.dumps(body) + req.headers['content-type'] = "application/json" + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 400) + def test_update_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) - def test_update_bad_params(self): + def test_update_nonstring_name(self): """ Confirm that update is filtering params """ - inst_dict = dict(cat='leopard', name='server_test', adminPass='bacon') + inst_dict = dict(name=12, adminPass='bacon') + self.body = json.dumps(dict(server=inst_dict)) + + req = webob.Request.blank('/v1.0/servers/1') + req.method = 'PUT' + req.content_type = "application/json" + req.body = self.body + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 400) + + def test_update_whitespace_name(self): + """ Confirm that update is filtering params """ + inst_dict = dict(name=' ', adminPass='bacon') + self.body = json.dumps(dict(server=inst_dict)) + + req = webob.Request.blank('/v1.0/servers/1') + req.method = 'PUT' + req.content_type = "application/json" + req.body = self.body + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 400) + + def test_update_null_name(self): + """ Confirm that update is filtering params """ + inst_dict = dict(name='', adminPass='bacon') + self.body = json.dumps(dict(server=inst_dict)) + + req = webob.Request.blank('/v1.0/servers/1') + req.method = 'PUT' + req.content_type = "application/json" + req.body = self.body + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 400) + + def test_update_server_v10(self): + inst_dict = dict(name='server_test', adminPass='bacon') self.body = json.dumps(dict(server=inst_dict)) def server_update(context, id, params): - self.update_called = True - filtered_dict = dict(name='server_test', admin_pass='bacon') + filtered_dict = dict( + display_name='server_test', + admin_pass='bacon', + ) self.assertEqual(params, filtered_dict) + return filtered_dict self.stubs.Set(nova.db.api, 'instance_update', server_update) req = webob.Request.blank('/v1.0/servers/1') req.method = 'PUT' + req.content_type = "application/json" req.body = self.body - req.get_response(fakes.wsgi_app()) + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 204) - def test_update_server(self): + def test_update_server_adminPass_ignored_v11(self): inst_dict = dict(name='server_test', adminPass='bacon') self.body = json.dumps(dict(server=inst_dict)) def server_update(context, id, params): - filtered_dict = dict(name='server_test', admin_pass='bacon') + filtered_dict = dict(display_name='server_test') self.assertEqual(params, filtered_dict) + return filtered_dict self.stubs.Set(nova.db.api, 'instance_update', server_update) - req = webob.Request.blank('/v1.0/servers/1') + req = webob.Request.blank('/v1.1/servers/1') req.method = 'PUT' + req.content_type = "application/json" req.body = self.body - req.get_response(fakes.wsgi_app()) + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 204) def test_create_backup_schedules(self): - req = webob.Request.blank('/v1.0/servers/1/backup_schedules') + req = webob.Request.blank('/v1.0/servers/1/backup_schedule') req.method = 'POST' res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status, '404 Not Found') + self.assertEqual(res.status_int, 501) def test_delete_backup_schedules(self): - req = webob.Request.blank('/v1.0/servers/1/backup_schedules') + req = webob.Request.blank('/v1.0/servers/1/backup_schedule/1') req.method = 'DELETE' res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status, '404 Not Found') + self.assertEqual(res.status_int, 501) def test_get_server_backup_schedules(self): - req = webob.Request.blank('/v1.0/servers/1/backup_schedules') + req = webob.Request.blank('/v1.0/servers/1/backup_schedule') + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 501) + + def test_get_server_backup_schedule(self): + req = webob.Request.blank('/v1.0/servers/1/backup_schedule/1') + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 501) + + def test_server_backup_schedule_deprecated_v11(self): + req = webob.Request.blank('/v1.1/servers/1/backup_schedule') + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 404) + + def test_get_all_server_details_xml_v1_0(self): + req = webob.Request.blank('/v1.0/servers/detail') + req.headers['Accept'] = 'application/xml' res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status, '404 Not Found') + print res.body + dom = minidom.parseString(res.body) + for i, server in enumerate(dom.getElementsByTagName('server')): + self.assertEqual(server.getAttribute('id'), str(i)) + self.assertEqual(server.getAttribute('hostId'), '') + self.assertEqual(server.getAttribute('name'), 'server%d' % i) + self.assertEqual(server.getAttribute('imageId'), '10') + self.assertEqual(server.getAttribute('status'), 'BUILD') + (meta,) = server.getElementsByTagName('meta') + self.assertEqual(meta.getAttribute('key'), 'seq') + self.assertEqual(meta.firstChild.data.strip(), str(i)) def test_get_all_server_details_v1_0(self): req = webob.Request.blank('/v1.0/servers/detail') @@ -509,8 +813,9 @@ class ServersTest(test.TestCase): self.assertEqual(s['hostId'], '') self.assertEqual(s['name'], 'server%d' % i) self.assertEqual(s['imageId'], '10') - self.assertEqual(s['flavorId'], '1') - self.assertEqual(s['metadata']['seq'], i) + self.assertEqual(s['flavorId'], 1) + self.assertEqual(s['status'], 'BUILD') + self.assertEqual(s['metadata']['seq'], str(i)) def test_get_all_server_details_v1_1(self): req = webob.Request.blank('/v1.1/servers/detail') @@ -523,7 +828,8 @@ class ServersTest(test.TestCase): self.assertEqual(s['name'], 'server%d' % i) self.assertEqual(s['imageRef'], 'http://localhost/v1.1/images/10') self.assertEqual(s['flavorRef'], 'http://localhost/v1.1/flavors/1') - self.assertEqual(s['metadata']['seq'], i) + self.assertEqual(s['status'], 'BUILD') + self.assertEqual(s['metadata']['seq'], str(i)) def test_get_all_server_details_with_host(self): ''' @@ -533,12 +839,8 @@ class ServersTest(test.TestCase): instances - 2 on one host and 3 on another. ''' - def stub_instance(id, user_id=1): - return Instance(id=id, state=0, image_id=10, user_id=user_id, - display_name='server%s' % id, host='host%s' % (id % 2)) - def return_servers_with_host(context, user_id=1): - return [stub_instance(i) for i in xrange(5)] + return [stub_instance(i, 1, None, None, i % 2) for i in xrange(5)] self.stubs.Set(nova.db.api, 'instance_get_all_by_user', return_servers_with_host) @@ -556,7 +858,8 @@ class ServersTest(test.TestCase): self.assertEqual(s['id'], i) self.assertEqual(s['hostId'], host_ids[i % 2]) self.assertEqual(s['name'], 'server%d' % i) - self.assertEqual(s['imageId'], 10) + self.assertEqual(s['imageId'], '10') + self.assertEqual(s['flavorId'], 1) def test_server_pause(self): FLAGS.allow_admin_api = True @@ -643,6 +946,74 @@ class ServersTest(test.TestCase): res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 404) + def test_server_change_password(self): + body = {'changePassword': {'adminPass': '1234pass'}} + req = webob.Request.blank('/v1.0/servers/1/action') + req.method = 'POST' + req.content_type = 'application/json' + req.body = json.dumps(body) + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 501) + + def test_server_change_password_v1_1(self): + + class MockSetAdminPassword(object): + def __init__(self): + self.instance_id = None + self.password = None + + def __call__(self, context, instance_id, password): + self.instance_id = instance_id + self.password = password + + mock_method = MockSetAdminPassword() + self.stubs.Set(nova.compute.api.API, 'set_admin_password', mock_method) + body = {'changePassword': {'adminPass': '1234pass'}} + req = webob.Request.blank('/v1.1/servers/1/action') + req.method = 'POST' + req.content_type = 'application/json' + req.body = json.dumps(body) + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 202) + self.assertEqual(mock_method.instance_id, '1') + self.assertEqual(mock_method.password, '1234pass') + + def test_server_change_password_bad_request_v1_1(self): + body = {'changePassword': {'pass': '12345'}} + req = webob.Request.blank('/v1.1/servers/1/action') + req.method = 'POST' + req.content_type = 'application/json' + req.body = json.dumps(body) + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 400) + + def test_server_change_password_empty_string_v1_1(self): + body = {'changePassword': {'adminPass': ''}} + req = webob.Request.blank('/v1.1/servers/1/action') + req.method = 'POST' + req.content_type = 'application/json' + req.body = json.dumps(body) + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 400) + + def test_server_change_password_none_v1_1(self): + body = {'changePassword': {'adminPass': None}} + req = webob.Request.blank('/v1.1/servers/1/action') + req.method = 'POST' + req.content_type = 'application/json' + req.body = json.dumps(body) + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 400) + + def test_server_change_password_not_a_string_v1_1(self): + body = {'changePassword': {'adminPass': 1234}} + req = webob.Request.blank('/v1.1/servers/1/action') + req.method = 'POST' + req.content_type = 'application/json' + req.body = json.dumps(body) + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 400) + def test_server_reboot(self): body = dict(server=dict( name='server_test', imageId=2, flavorId=2, metadata={}, @@ -728,7 +1099,7 @@ class ServersTest(test.TestCase): fake_migration_get) res = req.get_response(fakes.wsgi_app()) body = json.loads(res.body) - self.assertEqual(body['server']['status'], 'resize-confirm') + self.assertEqual(body['server']['status'], 'RESIZE-CONFIRM') def test_confirm_resize_server(self): req = self.webreq('/1/action', 'POST', dict(confirmResize=None)) @@ -1305,7 +1676,7 @@ class TestServerInstanceCreation(test.TestCase): self.assertEquals(response.status_int, 200) response = json.loads(response.body) self.assertTrue('adminPass' in response['server']) - self.assertTrue(response['server']['adminPass'].startswith('fake')) + self.assertEqual(16, len(response['server']['adminPass'])) def test_create_instance_admin_pass_xml(self): request, response, dummy = \ @@ -1314,7 +1685,7 @@ class TestServerInstanceCreation(test.TestCase): dom = minidom.parseString(response.body) server = dom.childNodes[0] self.assertEquals(server.nodeName, 'server') - self.assertTrue(server.getAttribute('adminPass').startswith('fake')) + self.assertEqual(16, len(server.getAttribute('adminPass'))) class TestGetKernelRamdiskFromImage(test.TestCase): @@ -1336,29 +1707,27 @@ class TestGetKernelRamdiskFromImage(test.TestCase): def test_not_ami(self): """Anything other than ami should return no kernel and no ramdisk""" - image_meta = {'id': 1, 'status': 'active', - 'properties': {'disk_format': 'vhd'}} + image_meta = {'id': 1, 'status': 'active', 'container_format': 'vhd'} kernel_id, ramdisk_id = self._get_k_r(image_meta) self.assertEqual(kernel_id, None) self.assertEqual(ramdisk_id, None) def test_ami_no_kernel(self): """If an ami is missing a kernel it should raise NotFound""" - image_meta = {'id': 1, 'status': 'active', - 'properties': {'disk_format': 'ami', 'ramdisk_id': 1}} + image_meta = {'id': 1, 'status': 'active', 'container_format': 'ami', + 'properties': {'ramdisk_id': 1}} self.assertRaises(exception.NotFound, self._get_k_r, image_meta) def test_ami_no_ramdisk(self): """If an ami is missing a ramdisk it should raise NotFound""" - image_meta = {'id': 1, 'status': 'active', - 'properties': {'disk_format': 'ami', 'kernel_id': 1}} + image_meta = {'id': 1, 'status': 'active', 'container_format': 'ami', + 'properties': {'kernel_id': 1}} self.assertRaises(exception.NotFound, self._get_k_r, image_meta) def test_ami_kernel_ramdisk_present(self): """Return IDs if both kernel and ramdisk are present""" - image_meta = {'id': 1, 'status': 'active', - 'properties': {'disk_format': 'ami', 'kernel_id': 1, - 'ramdisk_id': 2}} + image_meta = {'id': 1, 'status': 'active', 'container_format': 'ami', + 'properties': {'kernel_id': 1, 'ramdisk_id': 2}} kernel_id, ramdisk_id = self._get_k_r(image_meta) self.assertEqual(kernel_id, 1) self.assertEqual(ramdisk_id, 2) diff --git a/nova/tests/api/openstack/test_shared_ip_groups.py b/nova/tests/api/openstack/test_shared_ip_groups.py index b4de2ef41..c2bd7e45a 100644 --- a/nova/tests/api/openstack/test_shared_ip_groups.py +++ b/nova/tests/api/openstack/test_shared_ip_groups.py @@ -16,25 +16,49 @@ # under the License. import stubout +import webob from nova import test from nova.api.openstack import shared_ip_groups +from nova.tests.api.openstack import fakes class SharedIpGroupsTest(test.TestCase): def setUp(self): super(SharedIpGroupsTest, self).setUp() self.stubs = stubout.StubOutForTesting() + fakes.FakeAuthManager.reset_fake_data() + fakes.FakeAuthDatabase.data = {} + fakes.stub_out_auth(self.stubs) def tearDown(self): self.stubs.UnsetAll() super(SharedIpGroupsTest, self).tearDown() def test_get_shared_ip_groups(self): - pass + req = webob.Request.blank('/v1.0/shared_ip_groups') + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 501) def test_create_shared_ip_group(self): - pass + req = webob.Request.blank('/v1.0/shared_ip_groups') + req.method = 'POST' + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 501) + + def test_update_shared_ip_group(self): + req = webob.Request.blank('/v1.0/shared_ip_groups/12') + req.method = 'PUT' + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 501) def test_delete_shared_ip_group(self): - pass + req = webob.Request.blank('/v1.0/shared_ip_groups/12') + req.method = 'DELETE' + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 501) + + def test_deprecated_v11(self): + req = webob.Request.blank('/v1.1/shared_ip_groups') + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 404) diff --git a/nova/tests/api/openstack/test_versions.py b/nova/tests/api/openstack/test_versions.py index ebb59a9a6..fd8d50904 100644 --- a/nova/tests/api/openstack/test_versions.py +++ b/nova/tests/api/openstack/test_versions.py @@ -34,8 +34,10 @@ class VersionsTest(test.TestCase): def test_get_version_list(self): req = webob.Request.blank('/') + req.accept = "application/json" res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 200) + self.assertEqual(res.content_type, "application/json") versions = json.loads(res.body)["versions"] expected = [ { @@ -45,8 +47,7 @@ class VersionsTest(test.TestCase): { "rel": "self", "href": "http://localhost/v1.1", - } - ], + }], }, { "id": "v1.0", @@ -55,12 +56,35 @@ class VersionsTest(test.TestCase): { "rel": "self", "href": "http://localhost/v1.0", - } - ], + }], }, ] self.assertEqual(versions, expected) + def test_get_version_list_xml(self): + req = webob.Request.blank('/') + req.accept = "application/xml" + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 200) + self.assertEqual(res.content_type, "application/xml") + + expected = """<versions> + <version id="v1.1" status="CURRENT"> + <links> + <link href="http://localhost/v1.1" rel="self"/> + </links> + </version> + <version id="v1.0" status="DEPRECATED"> + <links> + <link href="http://localhost/v1.0" rel="self"/> + </links> + </version> + </versions>""".replace(" ", "").replace("\n", "") + + actual = res.body.replace(" ", "").replace("\n", "") + + self.assertEqual(expected, actual) + def test_view_builder(self): base_url = "http://example.org/" diff --git a/nova/tests/api/test_wsgi.py b/nova/tests/api/test_wsgi.py index 1ecdd1cfb..5820ecdc2 100644 --- a/nova/tests/api/test_wsgi.py +++ b/nova/tests/api/test_wsgi.py @@ -136,6 +136,12 @@ class RequestTest(test.TestCase): request.body = "asdf<br />" self.assertRaises(webob.exc.HTTPBadRequest, request.get_content_type) + def test_request_content_type_with_charset(self): + request = wsgi.Request.blank('/tests/123') + request.headers["Content-Type"] = "application/json; charset=UTF-8" + result = request.get_content_type() + self.assertEqual(result, "application/json") + def test_content_type_from_accept_xml(self): request = wsgi.Request.blank('/tests/123') request.headers["Accept"] = "application/xml" diff --git a/nova/tests/db/fakes.py b/nova/tests/db/fakes.py index 21a5481bd..58d251b1e 100644 --- a/nova/tests/db/fakes.py +++ b/nova/tests/db/fakes.py @@ -25,58 +25,75 @@ from nova import utils def stub_out_db_instance_api(stubs, injected=True): - """ Stubs out the db API for creating Instances """ + """Stubs out the db API for creating Instances.""" INSTANCE_TYPES = { - 'm1.tiny': dict(memory_mb=512, + 'm1.tiny': dict(id=2, + memory_mb=512, vcpus=1, local_gb=0, flavorid=1, rxtx_cap=1), - 'm1.small': dict(memory_mb=2048, + 'm1.small': dict(id=5, + memory_mb=2048, vcpus=1, local_gb=20, flavorid=2, rxtx_cap=2), 'm1.medium': - dict(memory_mb=4096, + dict(id=1, + memory_mb=4096, vcpus=2, local_gb=40, flavorid=3, rxtx_cap=3), - 'm1.large': dict(memory_mb=8192, + 'm1.large': dict(id=3, + memory_mb=8192, vcpus=4, local_gb=80, flavorid=4, rxtx_cap=4), 'm1.xlarge': - dict(memory_mb=16384, + dict(id=4, + memory_mb=16384, vcpus=8, local_gb=160, flavorid=5, rxtx_cap=5)} - network_fields = { - 'id': 'test', - 'bridge': 'xenbr0', - 'label': 'test_network', - 'netmask': '255.255.255.0', - 'cidr_v6': 'fe80::a00:0/120', - 'netmask_v6': '120', - 'gateway': '10.0.0.1', - 'gateway_v6': 'fe80::a00:1', - 'broadcast': '10.0.0.255', - 'dns': '10.0.0.2', - 'ra_server': None, - 'injected': injected} - - fixed_ip_fields = { - 'address': '10.0.0.3', - 'address_v6': 'fe80::a00:3', - 'network_id': 'test'} + flat_network_fields = {'id': 'fake_flat', + 'bridge': 'xenbr0', + 'label': 'fake_flat_network', + 'netmask': '255.255.255.0', + 'cidr_v6': 'fe80::a00:0/120', + 'netmask_v6': '120', + 'gateway': '10.0.0.1', + 'gateway_v6': 'fe80::a00:1', + 'broadcast': '10.0.0.255', + 'dns': '10.0.0.2', + 'ra_server': None, + 'injected': injected} + + vlan_network_fields = {'id': 'fake_vlan', + 'bridge': 'br111', + 'label': 'fake_vlan_network', + 'netmask': '255.255.255.0', + 'cidr_v6': 'fe80::a00:0/120', + 'netmask_v6': '120', + 'gateway': '10.0.0.1', + 'gateway_v6': 'fe80::a00:1', + 'broadcast': '10.0.0.255', + 'dns': '10.0.0.2', + 'ra_server': None, + 'vlan': 111, + 'injected': False} + + fixed_ip_fields = {'address': '10.0.0.3', + 'address_v6': 'fe80::a00:3', + 'network_id': 'fake_flat'} class FakeModel(object): - """ Stubs out for model """ + """Stubs out for model.""" def __init__(self, values): self.values = values @@ -95,11 +112,26 @@ def stub_out_db_instance_api(stubs, injected=True): def fake_instance_type_get_by_name(context, name): return INSTANCE_TYPES[name] + def fake_instance_type_get_by_id(context, id): + for name, inst_type in INSTANCE_TYPES.iteritems(): + if str(inst_type['id']) == str(id): + return inst_type + return None + def fake_network_get_by_instance(context, instance_id): + # Even instance numbers are on vlan networks + if instance_id % 2 == 0: + return FakeModel(vlan_network_fields) + else: + return FakeModel(flat_network_fields) return FakeModel(network_fields) def fake_network_get_all_by_instance(context, instance_id): - return [FakeModel(network_fields)] + # Even instance numbers are on vlan networks + if instance_id % 2 == 0: + return [FakeModel(vlan_network_fields)] + else: + return [FakeModel(flat_network_fields)] def fake_instance_get_fixed_address(context, instance_id): return FakeModel(fixed_ip_fields).address @@ -111,8 +143,11 @@ def stub_out_db_instance_api(stubs, injected=True): return [FakeModel(fixed_ip_fields)] stubs.Set(db, 'network_get_by_instance', fake_network_get_by_instance) + stubs.Set(db, 'network_get_all_by_instance', + fake_network_get_all_by_instance) stubs.Set(db, 'instance_type_get_all', fake_instance_type_get_all) stubs.Set(db, 'instance_type_get_by_name', fake_instance_type_get_by_name) + stubs.Set(db, 'instance_type_get_by_id', fake_instance_type_get_by_id) stubs.Set(db, 'instance_get_fixed_address', fake_instance_get_fixed_address) stubs.Set(db, 'instance_get_fixed_address_v6', diff --git a/nova/tests/fake_utils.py b/nova/tests/fake_utils.py index 823c775cb..be59970c9 100644 --- a/nova/tests/fake_utils.py +++ b/nova/tests/fake_utils.py @@ -14,8 +14,7 @@ # License for the specific language governing permissions and limitations # under the License. -"""This modules stubs out functions in nova.utils -""" +"""This modules stubs out functions in nova.utils.""" import re import types @@ -42,21 +41,25 @@ def fake_execute_clear_log(): def fake_execute_set_repliers(repliers): - """Allows the client to configure replies to commands""" + """Allows the client to configure replies to commands.""" global _fake_execute_repliers _fake_execute_repliers = repliers def fake_execute_default_reply_handler(*ignore_args, **ignore_kwargs): - """A reply handler for commands that haven't been added to the reply - list. Returns empty strings for stdout and stderr + """A reply handler for commands that haven't been added to the reply list. + + Returns empty strings for stdout and stderr. + """ return '', '' def fake_execute(*cmd_parts, **kwargs): - """This function stubs out execute, optionally executing - a preconfigued function to return expected data + """This function stubs out execute. + + It optionally executes a preconfigued function to return expected data. + """ global _fake_execute_repliers diff --git a/nova/tests/image/test_glance.py b/nova/tests/image/test_glance.py index d03aa9cc8..109905ded 100644 --- a/nova/tests/image/test_glance.py +++ b/nova/tests/image/test_glance.py @@ -55,7 +55,8 @@ class NullWriter(object): class BaseGlanceTest(unittest.TestCase): - NOW_GLANCE_FORMAT = "2010-10-11T10:30:22" + NOW_GLANCE_OLD_FORMAT = "2010-10-11T10:30:22" + NOW_GLANCE_FORMAT = "2010-10-11T10:30:22.000000" NOW_DATETIME = datetime.datetime(2010, 10, 11, 10, 30, 22) def setUp(self): @@ -74,6 +75,10 @@ class BaseGlanceTest(unittest.TestCase): self.assertEqual(image_meta['updated_at'], None) self.assertEqual(image_meta['deleted_at'], None) + def assertDateTimesBlank(self, image_meta): + self.assertEqual(image_meta['updated_at'], '') + self.assertEqual(image_meta['deleted_at'], '') + class TestGlanceImageServiceProperties(BaseGlanceTest): def test_show_passes_through_to_client(self): @@ -108,38 +113,72 @@ class TestGetterDateTimeNoneTests(BaseGlanceTest): image_meta = self.service.show(self.context, 'image1') self.assertDateTimesEmpty(image_meta) + def test_show_handles_blank_datetimes(self): + self.client.images = self._make_blank_datetime_fixtures() + image_meta = self.service.show(self.context, 'image1') + self.assertDateTimesBlank(image_meta) + def test_detail_handles_none_datetimes(self): self.client.images = self._make_none_datetime_fixtures() image_meta = self.service.detail(self.context)[0] self.assertDateTimesEmpty(image_meta) + def test_detail_handles_blank_datetimes(self): + self.client.images = self._make_blank_datetime_fixtures() + image_meta = self.service.detail(self.context)[0] + self.assertDateTimesBlank(image_meta) + def test_get_handles_none_datetimes(self): self.client.images = self._make_none_datetime_fixtures() writer = NullWriter() image_meta = self.service.get(self.context, 'image1', writer) self.assertDateTimesEmpty(image_meta) + def test_get_handles_blank_datetimes(self): + self.client.images = self._make_blank_datetime_fixtures() + writer = NullWriter() + image_meta = self.service.get(self.context, 'image1', writer) + self.assertDateTimesBlank(image_meta) + def test_show_makes_datetimes(self): self.client.images = self._make_datetime_fixtures() image_meta = self.service.show(self.context, 'image1') self.assertDateTimesFilled(image_meta) + image_meta = self.service.show(self.context, 'image2') + self.assertDateTimesFilled(image_meta) def test_detail_makes_datetimes(self): self.client.images = self._make_datetime_fixtures() image_meta = self.service.detail(self.context)[0] self.assertDateTimesFilled(image_meta) + image_meta = self.service.detail(self.context)[1] + self.assertDateTimesFilled(image_meta) def test_get_makes_datetimes(self): self.client.images = self._make_datetime_fixtures() writer = NullWriter() image_meta = self.service.get(self.context, 'image1', writer) self.assertDateTimesFilled(image_meta) + image_meta = self.service.get(self.context, 'image2', writer) + self.assertDateTimesFilled(image_meta) def _make_datetime_fixtures(self): - fixtures = {'image1': {'name': 'image1', 'is_public': True, - 'created_at': self.NOW_GLANCE_FORMAT, - 'updated_at': self.NOW_GLANCE_FORMAT, - 'deleted_at': self.NOW_GLANCE_FORMAT}} + fixtures = { + 'image1': { + 'name': 'image1', + 'is_public': True, + 'created_at': self.NOW_GLANCE_FORMAT, + 'updated_at': self.NOW_GLANCE_FORMAT, + 'deleted_at': self.NOW_GLANCE_FORMAT, + }, + 'image2': { + 'name': 'image2', + 'is_public': True, + 'created_at': self.NOW_GLANCE_OLD_FORMAT, + 'updated_at': self.NOW_GLANCE_OLD_FORMAT, + 'deleted_at': self.NOW_GLANCE_OLD_FORMAT, + }, + } return fixtures def _make_none_datetime_fixtures(self): @@ -148,6 +187,12 @@ class TestGetterDateTimeNoneTests(BaseGlanceTest): 'deleted_at': None}} return fixtures + def _make_blank_datetime_fixtures(self): + fixtures = {'image1': {'name': 'image1', 'is_public': True, + 'updated_at': '', + 'deleted_at': ''}} + return fixtures + class TestMutatorDateTimeTests(BaseGlanceTest): """Tests create(), update()""" @@ -164,17 +209,17 @@ class TestMutatorDateTimeTests(BaseGlanceTest): self.assertDateTimesEmpty(image_meta) def test_update_handles_datetimes(self): + self.client.images = {'image1': self._make_datetime_fixture()} self.client.update_response = self._make_datetime_fixture() - dummy_id = 'dummy_id' dummy_meta = {} - image_meta = self.service.update(self.context, 'dummy_id', dummy_meta) + image_meta = self.service.update(self.context, 'image1', dummy_meta) self.assertDateTimesFilled(image_meta) def test_update_handles_none_datetimes(self): + self.client.images = {'image1': self._make_datetime_fixture()} self.client.update_response = self._make_none_datetime_fixture() - dummy_id = 'dummy_id' dummy_meta = {} - image_meta = self.service.update(self.context, 'dummy_id', dummy_meta) + image_meta = self.service.update(self.context, 'image1', dummy_meta) self.assertDateTimesEmpty(image_meta) def _make_datetime_fixture(self): diff --git a/nova/tests/integrated/api/client.py b/nova/tests/integrated/api/client.py index fc7c344e7..7e20c9b00 100644 --- a/nova/tests/integrated/api/client.py +++ b/nova/tests/integrated/api/client.py @@ -56,8 +56,12 @@ class OpenStackApiNotFoundException(OpenStackApiException): class TestOpenStackClient(object): - """ A really basic OpenStack API client that is under our control, - so we can make changes / insert hooks for testing""" + """Simple OpenStack API Client. + + This is a really basic OpenStack API client that is under our control, + so we can make changes / insert hooks for testing + + """ def __init__(self, auth_user, auth_key, auth_uri): super(TestOpenStackClient, self).__init__() @@ -90,6 +94,7 @@ 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) response = conn.getresponse() @@ -121,7 +126,7 @@ class TestOpenStackClient(object): def api_request(self, relative_uri, check_response_status=None, **kwargs): auth_result = self._authenticate() - #NOTE(justinsb): httplib 'helpfully' converts headers to lower case + # NOTE(justinsb): httplib 'helpfully' converts headers to lower case base_uri = auth_result['x-server-management-url'] full_uri = base_uri + relative_uri @@ -208,3 +213,32 @@ class TestOpenStackClient(object): def delete_flavor(self, flavor_id): return self.api_delete('/flavors/%s' % flavor_id) + + def get_volume(self, volume_id): + return self.api_get('/volumes/%s' % volume_id)['volume'] + + def get_volumes(self, detail=True): + rel_url = '/volumes/detail' if detail else '/volumes' + return self.api_get(rel_url)['volumes'] + + def post_volume(self, volume): + return self.api_post('/volumes', volume)['volume'] + + def delete_volume(self, volume_id): + return self.api_delete('/volumes/%s' % volume_id) + + def get_server_volume(self, server_id, attachment_id): + return self.api_get('/servers/%s/volume_attachments/%s' % + (server_id, attachment_id))['volumeAttachment'] + + def get_server_volumes(self, server_id): + return self.api_get('/servers/%s/volume_attachments' % + (server_id))['volumeAttachments'] + + def post_server_volume(self, server_id, volume_attachment): + return self.api_post('/servers/%s/volume_attachments' % + (server_id), volume_attachment)['volumeAttachment'] + + def delete_server_volume(self, server_id, attachment_id): + return self.api_delete('/servers/%s/volume_attachments/%s' % + (server_id, attachment_id)) diff --git a/nova/tests/integrated/integrated_helpers.py b/nova/tests/integrated/integrated_helpers.py index cc7326e73..2e5d67017 100644 --- a/nova/tests/integrated/integrated_helpers.py +++ b/nova/tests/integrated/integrated_helpers.py @@ -27,7 +27,6 @@ from nova import flags from nova import service from nova import test # For the flags from nova.auth import manager -from nova.exception import Error from nova.log import logging from nova.tests.integrated.api import client @@ -38,19 +37,19 @@ LOG = logging.getLogger('nova.tests.integrated') def generate_random_alphanumeric(length): - """Creates a random alphanumeric string of specified length""" + """Creates a random alphanumeric string of specified length.""" return ''.join(random.choice(string.ascii_uppercase + string.digits) for _x in range(length)) def generate_random_numeric(length): - """Creates a random numeric string of specified length""" + """Creates a random numeric string of specified length.""" return ''.join(random.choice(string.digits) for _x in range(length)) def generate_new_element(items, prefix, numeric=False): - """Creates a random string with prefix, that is not in 'items' list""" + """Creates a random string with prefix, that is not in 'items' list.""" while True: if numeric: candidate = prefix + generate_random_numeric(8) @@ -58,7 +57,7 @@ def generate_new_element(items, prefix, numeric=False): candidate = prefix + generate_random_alphanumeric(8) if not candidate in items: return candidate - print "Random collision on %s" % candidate + LOG.debug("Random collision on %s" % candidate) class TestUser(object): @@ -73,23 +72,41 @@ class TestUser(object): self.secret, self.auth_url) + def get_unused_server_name(self): + servers = self.openstack_api.get_servers() + server_names = [server['name'] for server in servers] + return generate_new_element(server_names, 'server') + + def get_invalid_image(self): + images = self.openstack_api.get_images() + image_ids = [image['id'] for image in images] + return generate_new_element(image_ids, '', numeric=True) + + def get_valid_image(self, create=False): + images = self.openstack_api.get_images() + if create and not images: + # TODO(justinsb): No way currently to create an image through API + #created_image = self.openstack_api.post_image(image) + #images.append(created_image) + raise exception.Error("No way to create an image through API") + + if images: + return images[0] + return None + class IntegratedUnitTestContext(object): - def __init__(self): + def __init__(self, auth_url): self.auth_manager = manager.AuthManager() - self.wsgi_server = None - self.wsgi_apps = [] - self.api_service = None - - self.services = [] - self.auth_url = None + self.auth_url = auth_url self.project_name = None + self.test_user = None + self.setup() def setup(self): - self._start_services() self._create_test_user() def _create_test_user(self): @@ -99,12 +116,6 @@ class IntegratedUnitTestContext(object): self.project_name = 'openstack' self._configure_project(self.project_name, self.test_user) - def _start_services(self): - # WSGI shutdown broken :-( - # bug731668 - if not self.api_service: - self._start_api_service() - def cleanup(self): self.test_user = None @@ -132,6 +143,30 @@ class IntegratedUnitTestContext(object): else: self.auth_manager.add_to_project(user.name, project_name) + +class _IntegratedTestBase(test.TestCase): + def setUp(self): + super(_IntegratedTestBase, self).setUp() + + f = self._get_flags() + self.flags(**f) + + # set up services + self.start_service('compute') + self.start_service('volume') + # NOTE(justinsb): There's a bug here which is eluding me... + # If we start the network_service, all is good, but then subsequent + # tests fail: CloudTestCase.test_ajax_console in particular. + #self.start_service('network') + self.start_service('scheduler') + + self.auth_url = self._start_api_service() + + self.context = IntegratedUnitTestContext(self.auth_url) + + self.user = self.context.test_user + self.api = self.user.openstack_api + def _start_api_service(self): api_service = service.ApiService.create() api_service.start() @@ -139,8 +174,48 @@ class IntegratedUnitTestContext(object): if not api_service: raise Exception("API Service was None") - self.api_service = api_service + auth_url = 'http://localhost:8774/v1.1' + return auth_url + + def tearDown(self): + self.context.cleanup() + super(_IntegratedTestBase, self).tearDown() + + def _get_flags(self): + """An opportunity to setup flags, before the services are started.""" + f = {} + f['image_service'] = 'nova.image.fake.FakeImageService' + f['fake_network'] = True + return f + + def _build_minimal_create_server_request(self): + server = {} + + image = self.user.get_valid_image(create=True) + LOG.debug("Image: %s" % image) + + if 'imageRef' in image: + image_ref = image['imageRef'] + else: + # NOTE(justinsb): The imageRef code hasn't yet landed + LOG.warning("imageRef not yet in images output") + image_ref = image['id'] + + # TODO(justinsb): This is FUBAR + image_ref = abs(hash(image_ref)) + + image_ref = 'http://fake.server/%s' % image_ref + + # We now have a valid imageId + server['imageRef'] = image_ref + + # Set a valid flavorId + flavor = self.api.get_flavors()[0] + LOG.debug("Using flavor: %s" % flavor) + server['flavorRef'] = 'http://fake.server/%s' % flavor['id'] - self.auth_url = 'http://localhost:8774/v1.0' + # Set a valid server name + server_name = self.user.get_unused_server_name() + server['name'] = server_name - return api_service + return server diff --git a/nova/tests/integrated/test_extensions.py b/nova/tests/integrated/test_extensions.py new file mode 100644 index 000000000..0d4ee8cab --- /dev/null +++ b/nova/tests/integrated/test_extensions.py @@ -0,0 +1,44 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 Justin Santa Barbara +# 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 os + +from nova import flags +from nova.log import logging +from nova.tests.integrated import integrated_helpers + + +LOG = logging.getLogger('nova.tests.integrated') + + +FLAGS = flags.FLAGS +FLAGS.verbose = True + + +class ExtensionsTest(integrated_helpers._IntegratedTestBase): + def _get_flags(self): + f = super(ExtensionsTest, self)._get_flags() + f['osapi_extensions_path'] = os.path.join(os.path.dirname(__file__), + "../api/openstack/extensions") + return f + + def test_get_foxnsocks(self): + """Simple check that fox-n-socks works.""" + response = self.api.api_request('/foxnsocks') + foxnsocks = response.read() + LOG.debug("foxnsocks: %s" % foxnsocks) + self.assertEqual('Try to say this Mr. Knox, sir...', foxnsocks) diff --git a/nova/tests/integrated/test_login.py b/nova/tests/integrated/test_login.py index 6b241f240..a5180b6bc 100644 --- a/nova/tests/integrated/test_login.py +++ b/nova/tests/integrated/test_login.py @@ -18,7 +18,6 @@ import unittest from nova import flags -from nova import test from nova.log import logging from nova.tests.integrated import integrated_helpers from nova.tests.integrated.api import client @@ -30,25 +29,15 @@ FLAGS = flags.FLAGS FLAGS.verbose = True -class LoginTest(test.TestCase): - def setUp(self): - super(LoginTest, self).setUp() - self.context = integrated_helpers.IntegratedUnitTestContext() - self.user = self.context.test_user - self.api = self.user.openstack_api - - def tearDown(self): - self.context.cleanup() - super(LoginTest, self).tearDown() - +class LoginTest(integrated_helpers._IntegratedTestBase): def test_login(self): - """Simple check - we list flavors - so we know we're logged in""" + """Simple check - we list flavors - so we know we're logged in.""" flavors = self.api.get_flavors() for flavor in flavors: LOG.debug(_("flavor: %s") % flavor) def test_bad_login_password(self): - """Test that I get a 401 with a bad username""" + """Test that I get a 401 with a bad username.""" bad_credentials_api = client.TestOpenStackClient(self.user.name, "notso_password", self.user.auth_url) @@ -57,7 +46,7 @@ class LoginTest(test.TestCase): bad_credentials_api.get_flavors) def test_bad_login_username(self): - """Test that I get a 401 with a bad password""" + """Test that I get a 401 with a bad password.""" bad_credentials_api = client.TestOpenStackClient("notso_username", self.user.secret, self.user.auth_url) @@ -66,7 +55,7 @@ class LoginTest(test.TestCase): bad_credentials_api.get_flavors) def test_bad_login_both_bad(self): - """Test that I get a 401 with both bad username and bad password""" + """Test that I get a 401 with both bad username and bad password.""" bad_credentials_api = client.TestOpenStackClient("notso_username", "notso_password", self.user.auth_url) diff --git a/nova/tests/integrated/test_servers.py b/nova/tests/integrated/test_servers.py new file mode 100644 index 000000000..e89d0100a --- /dev/null +++ b/nova/tests/integrated/test_servers.py @@ -0,0 +1,184 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 Justin Santa Barbara +# 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 time +import unittest + +from nova import flags +from nova.log import logging +from nova.tests.integrated import integrated_helpers +from nova.tests.integrated.api import client + + +LOG = logging.getLogger('nova.tests.integrated') + + +FLAGS = flags.FLAGS +FLAGS.verbose = True + + +class ServersTest(integrated_helpers._IntegratedTestBase): + def test_get_servers(self): + """Simple check that listing servers works.""" + servers = self.api.get_servers() + for server in servers: + LOG.debug("server: %s" % server) + + def test_create_and_delete_server(self): + """Creates and deletes a server.""" + + # Create server + + # Build the server data gradually, checking errors along the way + server = {} + good_server = self._build_minimal_create_server_request() + + post = {'server': server} + + # Without an imageRef, this throws 500. + # TODO(justinsb): Check whatever the spec says should be thrown here + self.assertRaises(client.OpenStackApiException, + self.api.post_server, post) + + # With an invalid imageRef, this throws 500. + server['imageRef'] = self.user.get_invalid_image() + # TODO(justinsb): Check whatever the spec says should be thrown here + self.assertRaises(client.OpenStackApiException, + self.api.post_server, post) + + # Add a valid imageId/imageRef + server['imageId'] = good_server.get('imageId') + server['imageRef'] = good_server.get('imageRef') + + # Without flavorId, this throws 500 + # TODO(justinsb): Check whatever the spec says should be thrown here + self.assertRaises(client.OpenStackApiException, + self.api.post_server, post) + + # Set a valid flavorId/flavorRef + server['flavorRef'] = good_server.get('flavorRef') + server['flavorId'] = good_server.get('flavorId') + + # Without a name, this throws 500 + # TODO(justinsb): Check whatever the spec says should be thrown here + self.assertRaises(client.OpenStackApiException, + self.api.post_server, post) + + # Set a valid server name + server['name'] = good_server['name'] + + created_server = self.api.post_server(post) + LOG.debug("created_server: %s" % created_server) + self.assertTrue(created_server['id']) + created_server_id = created_server['id'] + + # Check it's there + found_server = self.api.get_server(created_server_id) + self.assertEqual(created_server_id, found_server['id']) + + # It should also be in the all-servers list + servers = self.api.get_servers() + server_ids = [server['id'] for server in servers] + self.assertTrue(created_server_id in server_ids) + + # Wait (briefly) for creation + retries = 0 + while found_server['status'] == 'build': + LOG.debug("found server: %s" % found_server) + time.sleep(1) + found_server = self.api.get_server(created_server_id) + retries = retries + 1 + if retries > 5: + break + + # It should be available... + # TODO(justinsb): Mock doesn't yet do this... + #self.assertEqual('available', found_server['status']) + + self._delete_server(created_server_id) + + def _delete_server(self, server_id): + # Delete the server + self.api.delete_server(server_id) + + # Wait (briefly) for deletion + for _retries in range(5): + try: + found_server = self.api.get_server(server_id) + except client.OpenStackApiNotFoundException: + found_server = None + LOG.debug("Got 404, proceeding") + break + + LOG.debug("Found_server=%s" % found_server) + + # TODO(justinsb): Mock doesn't yet do accurate state changes + #if found_server['status'] != 'deleting': + # break + time.sleep(1) + + # Should be gone + self.assertFalse(found_server) + + def test_create_server_with_metadata(self): + """Creates a server with metadata.""" + + # Build the server data gradually, checking errors along the way + server = self._build_minimal_create_server_request() + + metadata = {} + for i in range(30): + metadata['key_%s' % i] = 'value_%s' % i + + server['metadata'] = metadata + + post = {'server': server} + created_server = self.api.post_server(post) + LOG.debug("created_server: %s" % created_server) + self.assertTrue(created_server['id']) + created_server_id = created_server['id'] + + # Reenable when bug fixed + self.assertEqual(metadata, created_server.get('metadata')) + # Check it's there + + found_server = self.api.get_server(created_server_id) + self.assertEqual(created_server_id, found_server['id']) + self.assertEqual(metadata, found_server.get('metadata')) + + # The server should also be in the all-servers details list + servers = self.api.get_servers(detail=True) + server_map = dict((server['id'], server) for server in servers) + found_server = server_map.get(created_server_id) + self.assertTrue(found_server) + # Details do include metadata + self.assertEqual(metadata, found_server.get('metadata')) + + # The server should also be in the all-servers summary list + servers = self.api.get_servers(detail=False) + server_map = dict((server['id'], server) for server in servers) + found_server = server_map.get(created_server_id) + self.assertTrue(found_server) + # Summary should not include metadata + self.assertFalse(found_server.get('metadata')) + + # Cleanup + self._delete_server(created_server_id) + + +if __name__ == "__main__": + unittest.main() diff --git a/nova/tests/integrated/test_volumes.py b/nova/tests/integrated/test_volumes.py new file mode 100644 index 000000000..e9fb3c4d1 --- /dev/null +++ b/nova/tests/integrated/test_volumes.py @@ -0,0 +1,295 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 Justin Santa Barbara +# 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 unittest +import time + +from nova import flags +from nova.log import logging +from nova.tests.integrated import integrated_helpers +from nova.tests.integrated.api import client +from nova.volume import driver + + +LOG = logging.getLogger('nova.tests.integrated') + + +FLAGS = flags.FLAGS +FLAGS.verbose = True + + +class VolumesTest(integrated_helpers._IntegratedTestBase): + def setUp(self): + super(VolumesTest, self).setUp() + driver.LoggingVolumeDriver.clear_logs() + + def _get_flags(self): + f = super(VolumesTest, self)._get_flags() + f['use_local_volumes'] = False # Avoids calling local_path + f['volume_driver'] = 'nova.volume.driver.LoggingVolumeDriver' + return f + + def test_get_volumes_summary(self): + """Simple check that listing volumes works.""" + volumes = self.api.get_volumes(False) + for volume in volumes: + LOG.debug("volume: %s" % volume) + + def test_get_volumes(self): + """Simple check that listing volumes works.""" + volumes = self.api.get_volumes() + for volume in volumes: + LOG.debug("volume: %s" % volume) + + def _poll_while(self, volume_id, continue_states, max_retries=5): + """Poll (briefly) while the state is in continue_states.""" + retries = 0 + while True: + try: + found_volume = self.api.get_volume(volume_id) + except client.OpenStackApiNotFoundException: + found_volume = None + LOG.debug("Got 404, proceeding") + break + + LOG.debug("Found %s" % found_volume) + + self.assertEqual(volume_id, found_volume['id']) + + if not found_volume['status'] in continue_states: + break + + time.sleep(1) + retries = retries + 1 + if retries > max_retries: + break + return found_volume + + def test_create_and_delete_volume(self): + """Creates and deletes a volume.""" + + # Create volume + created_volume = self.api.post_volume({'volume': {'size': 1}}) + LOG.debug("created_volume: %s" % created_volume) + self.assertTrue(created_volume['id']) + created_volume_id = created_volume['id'] + + # Check it's there + found_volume = self.api.get_volume(created_volume_id) + self.assertEqual(created_volume_id, found_volume['id']) + + # It should also be in the all-volume list + volumes = self.api.get_volumes() + volume_names = [volume['id'] for volume in volumes] + self.assertTrue(created_volume_id in volume_names) + + # Wait (briefly) for creation. Delay is due to the 'message queue' + found_volume = self._poll_while(created_volume_id, ['creating']) + + # It should be available... + self.assertEqual('available', found_volume['status']) + + # Delete the volume + self.api.delete_volume(created_volume_id) + + # Wait (briefly) for deletion. Delay is due to the 'message queue' + found_volume = self._poll_while(created_volume_id, ['deleting']) + + # Should be gone + self.assertFalse(found_volume) + + LOG.debug("Logs: %s" % driver.LoggingVolumeDriver.all_logs()) + + create_actions = driver.LoggingVolumeDriver.logs_like( + 'create_volume', + id=created_volume_id) + LOG.debug("Create_Actions: %s" % create_actions) + + self.assertEquals(1, len(create_actions)) + create_action = create_actions[0] + self.assertEquals(create_action['id'], created_volume_id) + self.assertEquals(create_action['availability_zone'], 'nova') + self.assertEquals(create_action['size'], 1) + + export_actions = driver.LoggingVolumeDriver.logs_like( + 'create_export', + id=created_volume_id) + self.assertEquals(1, len(export_actions)) + export_action = export_actions[0] + self.assertEquals(export_action['id'], created_volume_id) + self.assertEquals(export_action['availability_zone'], 'nova') + + delete_actions = driver.LoggingVolumeDriver.logs_like( + 'delete_volume', + id=created_volume_id) + self.assertEquals(1, len(delete_actions)) + delete_action = export_actions[0] + self.assertEquals(delete_action['id'], created_volume_id) + + def test_attach_and_detach_volume(self): + """Creates, attaches, detaches and deletes a volume.""" + + # Create server + server_req = {'server': self._build_minimal_create_server_request()} + # NOTE(justinsb): Create an extra server so that server_id != volume_id + self.api.post_server(server_req) + created_server = self.api.post_server(server_req) + LOG.debug("created_server: %s" % created_server) + server_id = created_server['id'] + + # Create volume + created_volume = self.api.post_volume({'volume': {'size': 1}}) + LOG.debug("created_volume: %s" % created_volume) + volume_id = created_volume['id'] + self._poll_while(volume_id, ['creating']) + + # Check we've got different IDs + self.assertNotEqual(server_id, volume_id) + + # List current server attachments - should be none + attachments = self.api.get_server_volumes(server_id) + self.assertEquals([], attachments) + + # Template attach request + device = '/dev/sdc' + attach_req = {'device': device} + post_req = {'volumeAttachment': attach_req} + + # Try to attach to a non-existent volume; should fail + attach_req['volumeId'] = 3405691582 + self.assertRaises(client.OpenStackApiNotFoundException, + self.api.post_server_volume, server_id, post_req) + + # Try to attach to a non-existent server; should fail + attach_req['volumeId'] = volume_id + self.assertRaises(client.OpenStackApiNotFoundException, + self.api.post_server_volume, 3405691582, post_req) + + # Should still be no attachments... + attachments = self.api.get_server_volumes(server_id) + self.assertEquals([], attachments) + + # Do a real attach + attach_req['volumeId'] = volume_id + attach_result = self.api.post_server_volume(server_id, post_req) + LOG.debug(_("Attachment = %s") % attach_result) + + attachment_id = attach_result['id'] + self.assertEquals(volume_id, attach_result['volumeId']) + + # These fields aren't set because it's async + #self.assertEquals(server_id, attach_result['serverId']) + #self.assertEquals(device, attach_result['device']) + + # This is just an implementation detail, but let's check it... + self.assertEquals(volume_id, attachment_id) + + # NOTE(justinsb): There's an issue with the attach code, in that + # it's currently asynchronous and not recorded until the attach + # completes. So the caller must be 'smart', like this... + attach_done = None + retries = 0 + while True: + try: + attach_done = self.api.get_server_volume(server_id, + attachment_id) + break + except client.OpenStackApiNotFoundException: + LOG.debug("Got 404, waiting") + + time.sleep(1) + retries = retries + 1 + if retries > 10: + break + + expect_attach = {} + expect_attach['id'] = volume_id + expect_attach['volumeId'] = volume_id + expect_attach['serverId'] = server_id + expect_attach['device'] = device + + self.assertEqual(expect_attach, attach_done) + + # Should be one attachemnt + attachments = self.api.get_server_volumes(server_id) + self.assertEquals([expect_attach], attachments) + + # Should be able to get details + attachment_info = self.api.get_server_volume(server_id, attachment_id) + self.assertEquals(expect_attach, attachment_info) + + # Getting details on a different id should fail + self.assertRaises(client.OpenStackApiNotFoundException, + self.api.get_server_volume, server_id, 3405691582) + self.assertRaises(client.OpenStackApiNotFoundException, + self.api.get_server_volume, + 3405691582, attachment_id) + + # Trying to detach a different id should fail + self.assertRaises(client.OpenStackApiNotFoundException, + self.api.delete_server_volume, server_id, 3405691582) + + # Detach should work + self.api.delete_server_volume(server_id, attachment_id) + + # Again, it's async, so wait... + retries = 0 + while True: + try: + attachment = self.api.get_server_volume(server_id, + attachment_id) + LOG.debug("Attachment still there: %s" % attachment) + except client.OpenStackApiNotFoundException: + LOG.debug("Got 404, delete done") + break + + time.sleep(1) + retries = retries + 1 + self.assertTrue(retries < 10) + + # Should be no attachments again + attachments = self.api.get_server_volumes(server_id) + self.assertEquals([], attachments) + + LOG.debug("Logs: %s" % driver.LoggingVolumeDriver.all_logs()) + + # Discover_volume and undiscover_volume are called from compute + # on attach/detach + + disco_moves = driver.LoggingVolumeDriver.logs_like( + 'discover_volume', + id=volume_id) + LOG.debug("discover_volume actions: %s" % disco_moves) + + self.assertEquals(1, len(disco_moves)) + disco_move = disco_moves[0] + self.assertEquals(disco_move['id'], volume_id) + + last_days_of_disco_moves = driver.LoggingVolumeDriver.logs_like( + 'undiscover_volume', + id=volume_id) + LOG.debug("undiscover_volume actions: %s" % last_days_of_disco_moves) + + self.assertEquals(1, len(last_days_of_disco_moves)) + undisco_move = last_days_of_disco_moves[0] + self.assertEquals(undisco_move['id'], volume_id) + self.assertEquals(undisco_move['mountpoint'], device) + self.assertEquals(undisco_move['instance_id'], server_id) + + +if __name__ == "__main__": + unittest.main() diff --git a/nova/tests/integrated/test_xml.py b/nova/tests/integrated/test_xml.py new file mode 100644 index 000000000..8a9754777 --- /dev/null +++ b/nova/tests/integrated/test_xml.py @@ -0,0 +1,56 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 Justin Santa Barbara +# 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 flags +from nova.log import logging +from nova.tests.integrated import integrated_helpers +from nova.api.openstack import common + + +LOG = logging.getLogger('nova.tests.integrated') + + +FLAGS = flags.FLAGS +FLAGS.verbose = True + + +class XmlTests(integrated_helpers._IntegratedTestBase): + """"Some basic XML sanity checks.""" + + def test_namespace_limits(self): + """/limits should have v1.0 namespace (hasn't changed in 1.1).""" + headers = {} + headers['Accept'] = 'application/xml' + + response = self.api.api_request('/limits', headers=headers) + data = response.read() + LOG.debug("data: %s" % data) + + prefix = '<limits xmlns="%s"' % common.XML_NS_V10 + self.assertTrue(data.startswith(prefix)) + + def test_namespace_servers(self): + """/servers should have v1.1 namespace (has changed in 1.1).""" + headers = {} + headers['Accept'] = 'application/xml' + + response = self.api.api_request('/servers', headers=headers) + data = response.read() + LOG.debug("data: %s" % data) + + prefix = '<servers xmlns="%s"' % common.XML_NS_V11 + self.assertTrue(data.startswith(prefix)) diff --git a/nova/tests/test_cloud.py b/nova/tests/test_cloud.py index 00803d0ad..c45bdd12c 100644 --- a/nova/tests/test_cloud.py +++ b/nova/tests/test_cloud.py @@ -36,11 +36,13 @@ from nova import rpc from nova import service from nova import test from nova import utils +from nova import exception from nova.auth import manager from nova.compute import power_state from nova.api.ec2 import cloud from nova.api.ec2 import ec2utils from nova.image import local +from nova.exception import NotFound FLAGS = flags.FLAGS @@ -71,7 +73,8 @@ class CloudTestCase(test.TestCase): host = self.network.get_network_host(self.context.elevated()) def fake_show(meh, context, id): - return {'id': 1, 'properties': {'kernel_id': 1, 'ramdisk_id': 1}} + return {'id': 1, 'properties': {'kernel_id': 1, 'ramdisk_id': 1, + 'type': 'machine'}} self.stubs.Set(local.LocalImageService, 'show', fake_show) self.stubs.Set(local.LocalImageService, 'show_by_name', fake_show) @@ -216,6 +219,66 @@ class CloudTestCase(test.TestCase): db.service_destroy(self.context, comp1['id']) db.service_destroy(self.context, comp2['id']) + 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, + 'type': 'machine'}}] + + def fake_show_none(meh, context, id): + raise NotFound + + self.stubs.Set(local.LocalImageService, 'detail', fake_detail) + # list all + result1 = describe_images(self.context) + result1 = result1['imagesSet'][0] + self.assertEqual(result1['imageId'], 'ami-00000001') + # provided a valid image_id + result2 = describe_images(self.context, ['ami-00000001']) + self.assertEqual(1, len(result2['imagesSet'])) + # provide more than 1 valid image_id + result3 = describe_images(self.context, ['ami-00000001', + 'ami-00000002']) + self.assertEqual(2, len(result3['imagesSet'])) + # provide an non-existing image_id + self.stubs.UnsetAll() + self.stubs.Set(local.LocalImageService, 'show', fake_show_none) + self.stubs.Set(local.LocalImageService, 'show_by_name', fake_show_none) + self.assertRaises(NotFound, describe_images, + self.context, ['ami-fake']) + + 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} + + self.stubs.Set(local.LocalImageService, 'show', fake_show) + self.stubs.Set(local.LocalImageService, 'show_by_name', fake_show) + result = describe_image_attribute(self.context, 'ami-00000001', + 'launchPermission') + self.assertEqual([{'group': 'all'}], result['launchPermission']) + + 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, + 'type': 'machine'}, 'is_public': False} + + def fake_update(meh, context, image_id, metadata, data=None): + return metadata + + self.stubs.Set(local.LocalImageService, 'show', fake_show) + self.stubs.Set(local.LocalImageService, 'show_by_name', fake_show) + self.stubs.Set(local.LocalImageService, 'update', fake_update) + result = modify_image_attribute(self.context, 'ami-00000001', + 'launchPermission', 'add', + user_group=['all']) + self.assertEqual(True, result['is_public']) + def test_console_output(self): instance_type = FLAGS.default_instance_type max_count = 1 @@ -310,6 +373,19 @@ class CloudTestCase(test.TestCase): LOG.debug(_("Terminating instance %s"), instance_id) rv = self.compute.terminate_instance(instance_id) + def test_terminate_instances(self): + inst1 = db.instance_create(self.context, {'reservation_id': 'a', + 'image_id': 1, + 'host': 'host1'}) + terminate_instances = self.cloud.terminate_instances + # valid instance_id + result = terminate_instances(self.context, ['i-00000001']) + self.assertTrue(result) + # non-existing instance_id + self.assertRaises(exception.InstanceNotFound, terminate_instances, + self.context, ['i-2']) + db.instance_destroy(self.context, inst1['id']) + def test_update_of_instance_display_fields(self): inst = db.instance_create(self.context, {}) ec2_id = ec2utils.id_to_ec2_id(inst['id']) diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py index d1ef68de4..393110791 100644 --- a/nova/tests/test_compute.py +++ b/nova/tests/test_compute.py @@ -84,7 +84,8 @@ class ComputeTestCase(test.TestCase): inst['launch_time'] = '10' inst['user_id'] = self.user.id inst['project_id'] = self.project.id - inst['instance_type'] = 'm1.tiny' + type_id = instance_types.get_instance_type_by_name('m1.tiny')['id'] + inst['instance_type_id'] = type_id inst['mac_address'] = utils.generate_mac() inst['ami_launch_index'] = 0 inst.update(params) @@ -132,7 +133,7 @@ class ComputeTestCase(test.TestCase): cases = [dict(), dict(display_name=None)] for instance in cases: ref = self.compute_api.create(self.context, - FLAGS.default_instance_type, None, **instance) + instance_types.get_default_instance_type(), None, **instance) try: self.assertNotEqual(ref[0]['display_name'], None) finally: @@ -143,7 +144,7 @@ class ComputeTestCase(test.TestCase): group = self._create_group() ref = self.compute_api.create( self.context, - instance_type=FLAGS.default_instance_type, + instance_type=instance_types.get_default_instance_type(), image_id=None, security_group=['testgroup']) try: @@ -161,7 +162,7 @@ class ComputeTestCase(test.TestCase): ref = self.compute_api.create( self.context, - instance_type=FLAGS.default_instance_type, + instance_type=instance_types.get_default_instance_type(), image_id=None, security_group=['testgroup']) try: @@ -177,7 +178,7 @@ class ComputeTestCase(test.TestCase): ref = self.compute_api.create( self.context, - instance_type=FLAGS.default_instance_type, + instance_type=instance_types.get_default_instance_type(), image_id=None, security_group=['testgroup']) @@ -286,6 +287,16 @@ class ComputeTestCase(test.TestCase): console = self.compute.get_ajax_console(self.context, instance_id) + self.assert_(set(['token', 'host', 'port']).issubset(console.keys())) + self.compute.terminate_instance(self.context, instance_id) + + def test_vnc_console(self): + """Make sure we can a vnc console for an instance.""" + instance_id = self._create_instance() + self.compute.run_instance(self.context, instance_id) + + console = self.compute.get_vnc_console(self.context, + instance_id) self.assert_(console) self.compute.terminate_instance(self.context, instance_id) @@ -349,8 +360,9 @@ class ComputeTestCase(test.TestCase): instance_id = self._create_instance() self.compute.run_instance(self.context, instance_id) + inst_type = instance_types.get_instance_type_by_name('m1.xlarge') db.instance_update(self.context, instance_id, - {'instance_type': 'm1.xlarge'}) + {'instance_type_id': inst_type['id']}) self.assertRaises(exception.ApiError, self.compute_api.resize, context, instance_id, 1) @@ -370,8 +382,8 @@ class ComputeTestCase(test.TestCase): self.compute.terminate_instance(context, instance_id) def test_get_by_flavor_id(self): - type = instance_types.get_by_flavor_id(1) - self.assertEqual(type, 'm1.tiny') + type = instance_types.get_instance_type_by_flavor_id(1) + self.assertEqual(type['name'], 'm1.tiny') def test_resize_same_source_fails(self): """Ensure instance fails to migrate when source and destination are @@ -654,4 +666,5 @@ class ComputeTestCase(test.TestCase): instances = db.instance_get_all(context.get_admin_context()) LOG.info(_("After force-killing instances: %s"), instances) - self.assertEqual(len(instances), 0) + self.assertEqual(len(instances), 1) + self.assertEqual(power_state.SHUTOFF, instances[0]['state']) diff --git a/nova/tests/test_console.py b/nova/tests/test_console.py index d47c70d88..1a9a867ee 100644 --- a/nova/tests/test_console.py +++ b/nova/tests/test_console.py @@ -62,7 +62,7 @@ class ConsoleTestCase(test.TestCase): inst['launch_time'] = '10' inst['user_id'] = self.user.id inst['project_id'] = self.project.id - inst['instance_type'] = 'm1.tiny' + inst['instance_type_id'] = 1 inst['mac_address'] = utils.generate_mac() inst['ami_launch_index'] = 0 return db.instance_create(self.context, inst)['id'] diff --git a/nova/tests/test_instance_types.py b/nova/tests/test_instance_types.py index edc538879..dd7d0737e 100644 --- a/nova/tests/test_instance_types.py +++ b/nova/tests/test_instance_types.py @@ -40,7 +40,11 @@ class InstanceTypeTestCase(test.TestCase): max_flavorid = session.query(models.InstanceTypes).\ order_by("flavorid desc").\ first() + max_id = session.query(models.InstanceTypes).\ + order_by("id desc").\ + first() self.flavorid = max_flavorid["flavorid"] + 1 + self.id = max_id["id"] + 1 self.name = str(int(time.time())) def test_instance_type_create_then_delete(self): @@ -53,7 +57,7 @@ class InstanceTypeTestCase(test.TestCase): 'instance type was not created') instance_types.destroy(self.name) self.assertEqual(1, - instance_types.get_instance_type(self.name)["deleted"]) + instance_types.get_instance_type(self.id)["deleted"]) self.assertEqual(starting_inst_list, instance_types.get_all_types()) instance_types.purge(self.name) self.assertEqual(len(starting_inst_list), @@ -84,3 +88,12 @@ class InstanceTypeTestCase(test.TestCase): """Ensures that instance type creation fails with invalid args""" self.assertRaises(exception.ApiError, instance_types.destroy, "sfsfsdfdfs") + + def test_repeated_inst_types_should_raise_api_error(self): + """Ensures that instance duplicates raises ApiError""" + new_name = self.name + "dup" + instance_types.create(new_name, 256, 1, 120, self.flavorid + 1) + instance_types.destroy(new_name) + self.assertRaises( + exception.ApiError, + instance_types.create, new_name, 256, 1, 120, self.flavorid) diff --git a/nova/tests/test_quota.py b/nova/tests/test_quota.py index c65bc459d..39a123158 100644 --- a/nova/tests/test_quota.py +++ b/nova/tests/test_quota.py @@ -67,7 +67,7 @@ class QuotaTestCase(test.TestCase): inst['reservation_id'] = 'r-fakeres' inst['user_id'] = self.user.id inst['project_id'] = self.project.id - inst['instance_type'] = 'm1.large' + inst['instance_type_id'] = '3' # m1.large inst['vcpus'] = cores inst['mac_address'] = utils.generate_mac() return db.instance_create(self.context, inst)['id'] @@ -124,11 +124,12 @@ class QuotaTestCase(test.TestCase): for i in range(FLAGS.quota_instances): instance_id = self._create_instance() instance_ids.append(instance_id) + inst_type = instance_types.get_instance_type_by_name('m1.small') self.assertRaises(quota.QuotaError, compute.API().create, self.context, min_count=1, max_count=1, - instance_type='m1.small', + instance_type=inst_type, image_id=1) for instance_id in instance_ids: db.instance_destroy(self.context, instance_id) @@ -137,11 +138,12 @@ class QuotaTestCase(test.TestCase): instance_ids = [] instance_id = self._create_instance(cores=4) instance_ids.append(instance_id) + inst_type = instance_types.get_instance_type_by_name('m1.small') self.assertRaises(quota.QuotaError, compute.API().create, self.context, min_count=1, max_count=1, - instance_type='m1.small', + instance_type=inst_type, image_id=1) for instance_id in instance_ids: db.instance_destroy(self.context, instance_id) @@ -192,11 +194,12 @@ class QuotaTestCase(test.TestCase): metadata = {} for i in range(FLAGS.quota_metadata_items + 1): metadata['key%s' % i] = 'value%s' % i + inst_type = instance_types.get_instance_type_by_name('m1.small') self.assertRaises(quota.QuotaError, compute.API().create, self.context, min_count=1, max_count=1, - instance_type='m1.small', + instance_type=inst_type, image_id='fake', metadata=metadata) @@ -207,13 +210,15 @@ class QuotaTestCase(test.TestCase): def _create_with_injected_files(self, files): api = compute.API(image_service=self.StubImageService()) + inst_type = instance_types.get_instance_type_by_name('m1.small') api.create(self.context, min_count=1, max_count=1, - instance_type='m1.small', image_id='fake', + instance_type=inst_type, image_id='fake', injected_files=files) def test_no_injected_files(self): api = compute.API(image_service=self.StubImageService()) - api.create(self.context, instance_type='m1.small', image_id='fake') + inst_type = instance_types.get_instance_type_by_name('m1.small') + api.create(self.context, instance_type=inst_type, image_id='fake') def test_max_injected_files(self): files = [] diff --git a/nova/tests/test_scheduler.py b/nova/tests/test_scheduler.py index 6df74dd61..51d987288 100644 --- a/nova/tests/test_scheduler.py +++ b/nova/tests/test_scheduler.py @@ -263,7 +263,7 @@ class SimpleDriverTestCase(test.TestCase): inst['reservation_id'] = 'r-fakeres' inst['user_id'] = self.user.id inst['project_id'] = self.project.id - inst['instance_type'] = 'm1.tiny' + inst['instance_type_id'] = '1' inst['mac_address'] = utils.generate_mac() inst['vcpus'] = kwargs.get('vcpus', 1) inst['ami_launch_index'] = 0 @@ -737,7 +737,7 @@ class SimpleDriverTestCase(test.TestCase): ret = self.scheduler.driver._live_migration_src_check(self.context, i_ref) - self.assertTrue(ret == None) + self.assertTrue(ret is None) db.instance_destroy(self.context, instance_id) db.service_destroy(self.context, s_ref['id']) @@ -805,7 +805,7 @@ class SimpleDriverTestCase(test.TestCase): ret = self.scheduler.driver._live_migration_dest_check(self.context, i_ref, 'somewhere') - self.assertTrue(ret == None) + self.assertTrue(ret is None) db.instance_destroy(self.context, instance_id) db.service_destroy(self.context, s_ref['id']) diff --git a/nova/tests/test_virt.py b/nova/tests/test_virt.py index 3a03159ff..0a0c7a958 100644 --- a/nova/tests/test_virt.py +++ b/nova/tests/test_virt.py @@ -140,7 +140,7 @@ class LibvirtConnTestCase(test.TestCase): 'vcpus': 2, 'project_id': 'fake', 'bridge': 'br101', - 'instance_type': 'm1.small'} + 'instance_type_id': '5'} # m1.small def lazy_load_library_exists(self): """check if libvirt is available.""" @@ -225,6 +225,49 @@ class LibvirtConnTestCase(test.TestCase): self._check_xml_and_uri(instance_data, expect_kernel=True, expect_ramdisk=True, rescue=True) + def test_lxc_container_and_uri(self): + instance_data = dict(self.test_instance) + self._check_xml_and_container(instance_data) + + def _check_xml_and_container(self, instance): + user_context = context.RequestContext(project=self.project, + user=self.user) + instance_ref = db.instance_create(user_context, instance) + host = self.network.get_network_host(user_context.elevated()) + network_ref = db.project_get_network(context.get_admin_context(), + self.project.id) + + fixed_ip = {'address': self.test_ip, + 'network_id': network_ref['id']} + + ctxt = context.get_admin_context() + fixed_ip_ref = db.fixed_ip_create(ctxt, fixed_ip) + db.fixed_ip_update(ctxt, self.test_ip, + {'allocated': True, + 'instance_id': instance_ref['id']}) + + self.flags(libvirt_type='lxc') + conn = libvirt_conn.LibvirtConnection(True) + + uri = conn.get_uri() + self.assertEquals(uri, 'lxc:///') + + xml = conn.to_xml(instance_ref) + tree = xml_to_tree(xml) + + check = [ + (lambda t: t.find('.').get('type'), 'lxc'), + (lambda t: t.find('./os/type').text, 'exe'), + (lambda t: t.find('./devices/filesystem/target').get('dir'), '/')] + + for i, (check, expected_result) in enumerate(check): + self.assertEqual(check(tree), + expected_result, + '%s failed common check %d' % (xml, i)) + + target = tree.find('./devices/filesystem/source').get('dir') + self.assertTrue(len(target) > 0) + def _check_xml_and_uri(self, instance, expect_ramdisk, expect_kernel, rescue=False): user_context = context.RequestContext(project=self.project, @@ -436,7 +479,7 @@ class LibvirtConnTestCase(test.TestCase): fake_timer = FakeTime() - self.create_fake_libvirt_mock(nwfilterLookupByName=fake_raise) + self.create_fake_libvirt_mock() instance_ref = db.instance_create(self.context, self.test_instance) # Start test @@ -445,6 +488,7 @@ class LibvirtConnTestCase(test.TestCase): conn = libvirt_conn.LibvirtConnection(False) conn.firewall_driver.setattr('setup_basic_filtering', fake_none) conn.firewall_driver.setattr('prepare_instance_filter', fake_none) + conn.firewall_driver.setattr('instance_filter_exists', fake_none) conn.ensure_filtering_rules_for_instance(instance_ref, time=fake_timer) except exception.Error, e: @@ -574,7 +618,8 @@ class IptablesFirewallTestCase(test.TestCase): instance_ref = db.instance_create(self.context, {'user_id': 'fake', 'project_id': 'fake', - 'mac_address': '56:12:12:12:12:12'}) + 'mac_address': '56:12:12:12:12:12', + 'instance_type_id': 1}) ip = '10.11.12.13' network_ref = db.project_get_network(self.context, @@ -797,7 +842,8 @@ class NWFilterTestCase(test.TestCase): instance_ref = db.instance_create(self.context, {'user_id': 'fake', 'project_id': 'fake', - 'mac_address': '00:A0:C9:14:C8:29'}) + 'mac_address': '00:A0:C9:14:C8:29', + 'instance_type_id': 1}) inst_id = instance_ref['id'] ip = '10.11.12.13' diff --git a/nova/tests/test_volume.py b/nova/tests/test_volume.py index d71b75f3f..e9d8289aa 100644 --- a/nova/tests/test_volume.py +++ b/nova/tests/test_volume.py @@ -106,7 +106,7 @@ class VolumeTestCase(test.TestCase): inst['launch_time'] = '10' inst['user_id'] = 'fake' inst['project_id'] = 'fake' - inst['instance_type'] = 'm1.tiny' + inst['instance_type_id'] = '2' # m1.tiny inst['mac_address'] = utils.generate_mac() inst['ami_launch_index'] = 0 instance_id = db.instance_create(self.context, inst)['id'] diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index 36c88b020..375480a2e 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -14,9 +14,7 @@ # License for the specific language governing permissions and limitations # under the License. -""" -Test suite for XenAPI -""" +"""Test suite for XenAPI.""" import functools import os @@ -65,9 +63,7 @@ def stub_vm_utils_with_vdi_attached_here(function, should_return=True): class XenAPIVolumeTestCase(test.TestCase): - """ - Unit tests for Volume operations - """ + """Unit tests for Volume operations.""" def setUp(self): super(XenAPIVolumeTestCase, self).setUp() self.stubs = stubout.StubOutForTesting() @@ -84,7 +80,7 @@ class XenAPIVolumeTestCase(test.TestCase): 'image_id': 1, 'kernel_id': 2, 'ramdisk_id': 3, - 'instance_type': 'm1.large', + 'instance_type_id': '3', # m1.large 'mac_address': 'aa:bb:cc:dd:ee:ff', 'os_type': 'linux'} @@ -101,7 +97,7 @@ class XenAPIVolumeTestCase(test.TestCase): return db.volume_create(self.context, vol) def test_create_iscsi_storage(self): - """ This shows how to test helper classes' methods """ + """This shows how to test helper classes' methods.""" stubs.stubout_session(self.stubs, stubs.FakeSessionForVolumeTests) session = xenapi_conn.XenAPISession('test_url', 'root', 'test_pass') helper = volume_utils.VolumeHelper @@ -116,7 +112,7 @@ class XenAPIVolumeTestCase(test.TestCase): db.volume_destroy(context.get_admin_context(), vol['id']) def test_parse_volume_info_raise_exception(self): - """ This shows how to test helper classes' methods """ + """This shows how to test helper classes' methods.""" stubs.stubout_session(self.stubs, stubs.FakeSessionForVolumeTests) session = xenapi_conn.XenAPISession('test_url', 'root', 'test_pass') helper = volume_utils.VolumeHelper @@ -130,7 +126,7 @@ class XenAPIVolumeTestCase(test.TestCase): db.volume_destroy(context.get_admin_context(), vol['id']) def test_attach_volume(self): - """ This shows how to test Ops classes' methods """ + """This shows how to test Ops classes' methods.""" stubs.stubout_session(self.stubs, stubs.FakeSessionForVolumeTests) conn = xenapi_conn.get_connection(False) volume = self._create_volume() @@ -149,7 +145,7 @@ class XenAPIVolumeTestCase(test.TestCase): check() def test_attach_volume_raise_exception(self): - """ This shows how to test when exceptions are raised """ + """This shows how to test when exceptions are raised.""" stubs.stubout_session(self.stubs, stubs.FakeSessionForVolumeFailedTests) conn = xenapi_conn.get_connection(False) @@ -172,9 +168,7 @@ def reset_network(*args): class XenAPIVMTestCase(test.TestCase): - """ - Unit tests for VM operations - """ + """Unit tests for VM operations.""" def setUp(self): super(XenAPIVMTestCase, self).setUp() self.manager = manager.AuthManager() @@ -188,6 +182,7 @@ class XenAPIVMTestCase(test.TestCase): instance_name_template='%d') xenapi_fake.reset() xenapi_fake.create_local_srs() + xenapi_fake.create_local_pifs() db_fakes.stub_out_db_instance_api(self.stubs) xenapi_fake.create_network('fake', FLAGS.flat_network_bridge) stubs.stubout_session(self.stubs, stubs.FakeSessionForVMTests) @@ -247,12 +242,12 @@ class XenAPIVMTestCase(test.TestCase): check() - def create_vm_record(self, conn, os_type): + def create_vm_record(self, conn, os_type, instance_id=1): instances = conn.list_instances() - self.assertEquals(instances, ['1']) + self.assertEquals(instances, [str(instance_id)]) # Get Nova record for VM - vm_info = conn.get_info(1) + vm_info = conn.get_info(instance_id) # Get XenAPI record for VM vms = [rec for ref, rec in xenapi_fake.get_all_records('VM').iteritems() @@ -286,19 +281,19 @@ class XenAPIVMTestCase(test.TestCase): key = 'vm-data/networking/aabbccddeeff' xenstore_value = xenstore_data[key] tcpip_data = ast.literal_eval(xenstore_value) - self.assertEquals(tcpip_data, { - 'label': 'test_network', - 'broadcast': '10.0.0.255', - 'ips': [{'ip': '10.0.0.3', - 'netmask':'255.255.255.0', - 'enabled':'1'}], - 'ip6s': [{'ip': 'fe80::a8bb:ccff:fedd:eeff', - 'netmask': '120', - 'enabled': '1', - 'gateway': 'fe80::a00:1'}], - 'mac': 'aa:bb:cc:dd:ee:ff', - 'dns': ['10.0.0.2'], - 'gateway': '10.0.0.1'}) + self.assertEquals(tcpip_data, + {'label': 'fake_flat_network', + 'broadcast': '10.0.0.255', + 'ips': [{'ip': '10.0.0.3', + 'netmask':'255.255.255.0', + 'enabled':'1'}], + 'ip6s': [{'ip': 'fe80::a8bb:ccff:fedd:eeff', + 'netmask': '120', + 'enabled': '1'}], + 'mac': 'aa:bb:cc:dd:ee:ff', + 'dns': ['10.0.0.2'], + 'gateway': '10.0.0.1', + 'gateway6': 'fe80::a00:1'}) def check_vm_params_for_windows(self): self.assertEquals(self.vm['platform']['nx'], 'true') @@ -333,28 +328,28 @@ class XenAPIVMTestCase(test.TestCase): self.assertEquals(self.vm['HVM_boot_policy'], '') def _test_spawn(self, image_id, kernel_id, ramdisk_id, - instance_type="m1.large", os_type="linux", - check_injection=False): + instance_type_id="3", os_type="linux", + instance_id=1, check_injection=False): stubs.stubout_loopingcall_start(self.stubs) - values = {'id': 1, + values = {'id': instance_id, 'project_id': self.project.id, 'user_id': self.user.id, 'image_id': image_id, 'kernel_id': kernel_id, 'ramdisk_id': ramdisk_id, - 'instance_type': instance_type, + 'instance_type_id': instance_type_id, 'mac_address': 'aa:bb:cc:dd:ee:ff', 'os_type': os_type} instance = db.instance_create(self.context, values) self.conn.spawn(instance) - self.create_vm_record(self.conn, os_type) + self.create_vm_record(self.conn, os_type, instance_id) self.check_vm_record(self.conn, check_injection) def test_spawn_not_enough_memory(self): FLAGS.xenapi_image_service = 'glance' self.assertRaises(Exception, self._test_spawn, - 1, 2, 3, "m1.xlarge") + 1, 2, 3, "4") # m1.xlarge def test_spawn_raw_objectstore(self): FLAGS.xenapi_image_service = 'objectstore' @@ -468,6 +463,28 @@ class XenAPIVMTestCase(test.TestCase): # guest agent is detected self.assertFalse(self._tee_executed) + def test_spawn_vlanmanager(self): + self.flags(xenapi_image_service='glance', + network_manager='nova.network.manager.VlanManager', + network_driver='nova.network.xenapi_net', + vlan_interface='fake0') + # Reset network table + xenapi_fake.reset_table('network') + # Instance id = 2 will use vlan network (see db/fakes.py) + fake_instance_id = 2 + network_bk = self.network + # Ensure we use xenapi_net driver + self.network = utils.import_object(FLAGS.network_manager) + self.network.setup_compute_network(None, fake_instance_id) + self._test_spawn(glance_stubs.FakeGlance.IMAGE_MACHINE, + glance_stubs.FakeGlance.IMAGE_KERNEL, + glance_stubs.FakeGlance.IMAGE_RAMDISK, + instance_id=fake_instance_id) + # TODO(salvatore-orlando): a complete test here would require + # a check for making sure the bridge for the VM's VIF is + # consistent with bridge specified in nova db + self.network = network_bk + def test_spawn_with_network_qos(self): self._create_instance() for vif_ref in xenapi_fake.get_all('VIF'): @@ -497,7 +514,7 @@ class XenAPIVMTestCase(test.TestCase): self.stubs.UnsetAll() def _create_instance(self): - """Creates and spawns a test instance""" + """Creates and spawns a test instance.""" stubs.stubout_loopingcall_start(self.stubs) values = { 'id': 1, @@ -506,7 +523,7 @@ class XenAPIVMTestCase(test.TestCase): 'image_id': 1, 'kernel_id': 2, 'ramdisk_id': 3, - 'instance_type': 'm1.large', + 'instance_type_id': '3', # m1.large 'mac_address': 'aa:bb:cc:dd:ee:ff', 'os_type': 'linux'} instance = db.instance_create(self.context, values) @@ -515,9 +532,7 @@ class XenAPIVMTestCase(test.TestCase): class XenAPIDiffieHellmanTestCase(test.TestCase): - """ - Unit tests for Diffie-Hellman code - """ + """Unit tests for Diffie-Hellman code.""" def setUp(self): super(XenAPIDiffieHellmanTestCase, self).setUp() self.alice = SimpleDH() @@ -541,9 +556,7 @@ class XenAPIDiffieHellmanTestCase(test.TestCase): class XenAPIMigrateInstance(test.TestCase): - """ - Unit test for verifying migration-related actions - """ + """Unit test for verifying migration-related actions.""" def setUp(self): super(XenAPIMigrateInstance, self).setUp() @@ -567,7 +580,7 @@ class XenAPIMigrateInstance(test.TestCase): 'kernel_id': None, 'ramdisk_id': None, 'local_gb': 5, - 'instance_type': 'm1.large', + 'instance_type_id': '3', # m1.large 'mac_address': 'aa:bb:cc:dd:ee:ff', 'os_type': 'linux'} @@ -598,9 +611,7 @@ class XenAPIMigrateInstance(test.TestCase): class XenAPIDetermineDiskImageTestCase(test.TestCase): - """ - Unit tests for code that detects the ImageType - """ + """Unit tests for code that detects the ImageType.""" def setUp(self): super(XenAPIDetermineDiskImageTestCase, self).setUp() glance_stubs.stubout_glance_client(self.stubs, @@ -619,9 +630,7 @@ class XenAPIDetermineDiskImageTestCase(test.TestCase): self.assertEqual(disk_type, dt) def test_instance_disk(self): - """ - If a kernel is specified then the image type is DISK (aka machine) - """ + """If a kernel is specified, the image type is DISK (aka machine).""" FLAGS.xenapi_image_service = 'objectstore' self.fake_instance.image_id = glance_stubs.FakeGlance.IMAGE_MACHINE self.fake_instance.kernel_id = glance_stubs.FakeGlance.IMAGE_KERNEL |
