From 0e2726f452fe6991797728bca1e514943725e7a2 Mon Sep 17 00:00:00 2001 From: William Wolf Date: Fri, 22 Jul 2011 10:34:01 -0400 Subject: initial test for v1.1 detail request --- nova/tests/api/openstack/test_versions.py | 53 +++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) (limited to 'nova') diff --git a/nova/tests/api/openstack/test_versions.py b/nova/tests/api/openstack/test_versions.py index 0eae18a62..aaa1f4976 100644 --- a/nova/tests/api/openstack/test_versions.py +++ b/nova/tests/api/openstack/test_versions.py @@ -16,6 +16,7 @@ # under the License. import json +import stubout import webob from nova import context @@ -29,6 +30,9 @@ class VersionsTest(test.TestCase): def setUp(self): super(VersionsTest, self).setUp() self.context = context.get_admin_context() + self.stubs = stubout.StubOutForTesting() + fakes.stub_out_auth(self.stubs) + def tearDown(self): super(VersionsTest, self).tearDown() @@ -64,6 +68,55 @@ class VersionsTest(test.TestCase): ] self.assertEqual(versions, expected) + def test_get_version_1_1_detail(self): + req = webob.Request.blank('/v1.1/') + req.accept = "application/json" + res = req.get_response(fakes.wsgi_app()) + print res.body + self.assertEqual(res.status_int, 200) + self.assertEqual(res.content_type, "application/json") + versions = json.loads(res.body)["versions"] + expected = [ + { + "version" : { + "id" : "v1.1", + "status" : "CURRENT", + "updated" : "2011-01-21T11:33:21-06:00", + "links": [ + { + "rel" : "self", + "href" : "http://servers.api.openstack.org/v1.0/" + }, + { + "rel" : "describedby", + "type" : "application/pdf", + "href" : "http://docs.rackspacecloud.com/" + "servers/api/v1.1/cs-devguide-20110125.pdf" + }, + { + "rel" : "describedby", + "type" : "application/vnd.sun.wadl+xml", + "href" : "http://docs.rackspacecloud.com/" + "servers/api/v1.1/application.wadl" + } + ], + "media-types": [ + { + "base" : "application/xml", + "type" : "application/" + "vnd.openstack.compute-v1.1+xml" + }, + { + "base" : "application/json", + "type" : "application/" + "vnd.openstack.compute-v1.1+json" + } + ] + } + } + ] + self.assertEqual(versions, expected) + def test_get_version_list_xml(self): req = webob.Request.blank('/') req.accept = "application/xml" -- cgit From 99bc14f16bce9f125715fbe436b7fc0969b62420 Mon Sep 17 00:00:00 2001 From: William Wolf Date: Mon, 25 Jul 2011 14:10:08 -0400 Subject: added 1.0 detail test, added VersionRequestDeserializer to support Versions actions properly, started 300/multiple choice work --- nova/api/openstack/__init__.py | 6 ++ nova/api/openstack/versions.py | 92 ++++++++++++++++++++++++++++++- nova/tests/api/openstack/test_versions.py | 51 ++++++++++++++++- 3 files changed, 145 insertions(+), 4 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index e87d7c754..fb6f5515e 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -40,6 +40,7 @@ from nova.api.openstack import servers from nova.api.openstack import server_metadata from nova.api.openstack import shared_ip_groups from nova.api.openstack import users +from nova.api.openstack import versions from nova.api.openstack import wsgi from nova.api.openstack import zones @@ -115,6 +116,10 @@ class APIRouter(base_wsgi.Router): 'select': 'POST', 'boot': 'POST'}) + mapper.connect("versions", "/", + controller=versions.create_resource(version), + action="index") + mapper.resource("console", "consoles", controller=consoles.create_resource(), parent_resource=dict(member_name='server', @@ -164,6 +169,7 @@ class APIRouterV11(APIRouter): def _setup_routes(self, mapper): super(APIRouterV11, self)._setup_routes(mapper, '1.1') + mapper.resource("image_meta", "meta", controller=image_metadata.create_resource(), parent_resource=dict(member_name='image', diff --git a/nova/api/openstack/versions.py b/nova/api/openstack/versions.py index df7a94b7e..445a14372 100644 --- a/nova/api/openstack/versions.py +++ b/nova/api/openstack/versions.py @@ -28,6 +28,13 @@ ATOM_XMLNS = "http://www.w3.org/2005/Atom" class Versions(wsgi.Resource): + @classmethod + def factory(cls, global_config, **local_config): + """Paste factory.""" + def _factory(app): + return cls(app, **local_config) + return _factory + def __init__(self): metadata = { "attributes": { @@ -45,7 +52,7 @@ class Versions(wsgi.Resource): supported_content_types = ('application/json', 'application/xml', 'application/atom+xml') - deserializer = wsgi.RequestDeserializer( + deserializer = VersionsRequestDeserializer( supported_content_types=supported_content_types) wsgi.Resource.__init__(self, None, serializer=serializer, @@ -53,6 +60,14 @@ class Versions(wsgi.Resource): def dispatch(self, request, *args): """Respond to a request for all OpenStack API versions.""" + if request.path == '/': + # List Versions + return self._versions_list(request) + else: + # Versions Multiple Choice + return self._versions_multi_choice(request) + + def _versions_list(self, request): version_objs = [ { "id": "v1.1", @@ -72,6 +87,47 @@ class Versions(wsgi.Resource): versions = [builder.build(version) for version in version_objs] return dict(versions=versions) + def _versions_multi_choice(self, request): + version_objs = [ + { + "id": "v1.1", + "status": "CURRENT", + #TODO(wwolf) get correct value for these + "updated": "2011-07-18T11:30:00Z", + }, + { + "id": "v1.0", + "status": "DEPRECATED", + #TODO(wwolf) get correct value for these + "updated": "2010-10-09T11:30:00Z", + }, + ] + + builder = nova.api.openstack.views.versions.get_view_builder(request) + versions = [builder.build(version) for version in version_objs] + return dict(versions=versions) + + +class VersionV10(object): + def index(self, req): + return "test index 1.0" + + +class VersionV11(object): + def index(self, req): + return "test index 1.1" + +class VersionsRequestDeserializer(wsgi.RequestDeserializer): + def get_action_args(self, request_environment): + """Parse dictionary created by routes library.""" + + args = {} + if request_environment['PATH_INFO'] == '/': + args['action'] = 'index' + else: + args['action'] = 'multi' + + return args class VersionsXMLSerializer(wsgi.XMLDictSerializer): def _versions_to_xml(self, versions): @@ -96,7 +152,13 @@ class VersionsXMLSerializer(wsgi.XMLDictSerializer): return version_node - def default(self, data): + def index(self, data): + self._xml_doc = minidom.Document() + node = self._versions_to_xml(data['versions']) + + return self.to_xml_string(node) + + def multi(self, data): self._xml_doc = minidom.Document() node = self._versions_to_xml(data['versions']) @@ -190,10 +252,34 @@ class VersionsAtomSerializer(wsgi.XMLDictSerializer): entry.appendChild(content) root.appendChild(entry) - def default(self, data): + def index(self, data): self._xml_doc = minidom.Document() node = self._xml_doc.createElementNS(self.xmlns, 'feed') self._create_meta(node, data['versions']) self._create_version_entries(node, data['versions']) return self.to_xml_string(node) + + def multi(self, data): + self._xml_doc = minidom.Document() + node = self._xml_doc.createElementNS(self.xmlns, 'feed') + self._create_meta(node, data['versions']) + self._create_version_entries(node, data['versions']) + + return self.to_xml_string(node) + + +def create_resource(version='1.0'): + controller = { + '1.0': VersionV10, + '1.1': VersionV11, + }[version]() + + body_serializers = { + 'application/xml': VersionsXMLSerializer(), + 'application/atom+xml': VersionsAtomSerializer(), + } + + serializer = wsgi.ResponseSerializer(body_serializers) + + return wsgi.Resource(controller, serializer=serializer) diff --git a/nova/tests/api/openstack/test_versions.py b/nova/tests/api/openstack/test_versions.py index aaa1f4976..b2896a780 100644 --- a/nova/tests/api/openstack/test_versions.py +++ b/nova/tests/api/openstack/test_versions.py @@ -68,6 +68,55 @@ class VersionsTest(test.TestCase): ] self.assertEqual(versions, expected) + def test_get_version_1_0_detail(self): + req = webob.Request.blank('/v1.0/') + req.accept = "application/json" + res = req.get_response(fakes.wsgi_app()) + print res.body + self.assertEqual(res.status_int, 200) + self.assertEqual(res.content_type, "application/json") + versions = json.loads(res.body)["versions"] + expected = [ + { + "version" : { + "id" : "v1.0", + "status" : "CURRENT", + "updated" : "2011-01-21T11:33:21-06:00", + "links": [ + { + "rel" : "self", + "href" : "http://servers.api.openstack.org/v1.0/" + }, + { + "rel" : "describedby", + "type" : "application/pdf", + "href" : "http://docs.rackspacecloud.com/" + "servers/api/v1.0/cs-devguide-20110125.pdf" + }, + { + "rel" : "describedby", + "type" : "application/vnd.sun.wadl+xml", + "href" : "http://docs.rackspacecloud.com/" + "servers/api/v1.0/application.wadl" + } + ], + "media-types": [ + { + "base" : "application/xml", + "type" : "application/" + "vnd.openstack.compute-v1.0+xml" + }, + { + "base" : "application/json", + "type" : "application/" + "vnd.openstack.compute-v1.0+json" + } + ] + } + } + ] + self.assertEqual(versions, expected) + def test_get_version_1_1_detail(self): req = webob.Request.blank('/v1.1/') req.accept = "application/json" @@ -85,7 +134,7 @@ class VersionsTest(test.TestCase): "links": [ { "rel" : "self", - "href" : "http://servers.api.openstack.org/v1.0/" + "href" : "http://servers.api.openstack.org/v1.1/" }, { "rel" : "describedby", -- cgit From 810d4b89cbbfa9388fb61f9069ea0104a7d77752 Mon Sep 17 00:00:00 2001 From: William Wolf Date: Mon, 25 Jul 2011 15:28:06 -0400 Subject: removed prints, got versions detail tests passing, still need to do xml/atom --- nova/api/openstack/__init__.py | 2 +- nova/api/openstack/versions.py | 96 ++++++++-- nova/tests/api/openstack/test_versions.py | 295 +++++++++++++++++++++--------- 3 files changed, 292 insertions(+), 101 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index fb6f5515e..a6a8b4595 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -118,7 +118,7 @@ class APIRouter(base_wsgi.Router): mapper.connect("versions", "/", controller=versions.create_resource(version), - action="index") + action="detail") mapper.resource("console", "consoles", controller=consoles.create_resource(), diff --git a/nova/api/openstack/versions.py b/nova/api/openstack/versions.py index 445a14372..5655edcfc 100644 --- a/nova/api/openstack/versions.py +++ b/nova/api/openstack/versions.py @@ -88,6 +88,7 @@ class Versions(wsgi.Resource): return dict(versions=versions) def _versions_multi_choice(self, request): + #TODO version_objs = [ { "id": "v1.1", @@ -109,18 +110,87 @@ class Versions(wsgi.Resource): class VersionV10(object): - def index(self, req): - return "test index 1.0" + def detail(self, req): + #TODO + return { + "version" : { + "id": "v1.0", + "status": "CURRENT", + "updated": "2011-01-21T11:33:21-06:00", + "links": [ + { + "rel": "self", + "href": "http://servers.api.openstack.org/v1.0/" + }, + { + "rel": "describedby", + "type": "application/pdf", + "href": "http://docs.rackspacecloud.com/" + "servers/api/v1.0/cs-devguide-20110125.pdf" + }, + { + "rel": "describedby", + "type": "application/vnd.sun.wadl+xml", + "href": "http://docs.rackspacecloud.com/" + "servers/api/v1.0/application.wadl" + }, + ], + "media-types": [ + { + "base" : "application/xml", + "type" : "application/vnd.openstack.compute-v1.0+xml" + }, + { + "base" : "application/json", + "type" : "application/vnd.openstack.compute-v1.0+json" + } + ], + }, + } class VersionV11(object): - def index(self, req): - return "test index 1.1" + def detail(self, req): + return { + "version" : { + "id": "v1.1", + "status": "CURRENT", + "updated": "2011-01-21T11:33:21-06:00", + "links": [ + { + "rel": "self", + "href": "http://servers.api.openstack.org/v1.1/" + }, + { + "rel": "describedby", + "type": "application/pdf", + "href": "http://docs.rackspacecloud.com/" + "servers/api/v1.1/cs-devguide-20110125.pdf" + }, + { + "rel": "describedby", + "type": "application/vnd.sun.wadl+xml", + "href": "http://docs.rackspacecloud.com/" + "servers/api/v1.1/application.wadl" + }, + ], + "media-types": [ + { + "base" : "application/xml", + "type" : "application/vnd.openstack.compute-v1.1+xml" + }, + { + "base" : "application/json", + "type" : "application/vnd.openstack.compute-v1.1+json" + } + ], + }, + } + class VersionsRequestDeserializer(wsgi.RequestDeserializer): def get_action_args(self, request_environment): """Parse dictionary created by routes library.""" - args = {} if request_environment['PATH_INFO'] == '/': args['action'] = 'index' @@ -129,6 +199,7 @@ class VersionsRequestDeserializer(wsgi.RequestDeserializer): return args + class VersionsXMLSerializer(wsgi.XMLDictSerializer): def _versions_to_xml(self, versions): root = self._xml_doc.createElement('versions') @@ -158,6 +229,9 @@ class VersionsXMLSerializer(wsgi.XMLDictSerializer): return self.to_xml_string(node) + def detail(self,data): + return "" + def multi(self, data): self._xml_doc = minidom.Document() node = self._versions_to_xml(data['versions']) @@ -167,6 +241,7 @@ class VersionsXMLSerializer(wsgi.XMLDictSerializer): class VersionsAtomSerializer(wsgi.XMLDictSerializer): def __init__(self, metadata=None, xmlns=None): + self.metadata = metadata or {} if not xmlns: self.xmlns = ATOM_XMLNS else: @@ -260,14 +335,9 @@ class VersionsAtomSerializer(wsgi.XMLDictSerializer): return self.to_xml_string(node) - def multi(self, data): - self._xml_doc = minidom.Document() - node = self._xml_doc.createElementNS(self.xmlns, 'feed') - self._create_meta(node, data['versions']) - self._create_version_entries(node, data['versions']) - - return self.to_xml_string(node) - + def detail(self, data): + #TODO + pass def create_resource(version='1.0'): controller = { diff --git a/nova/tests/api/openstack/test_versions.py b/nova/tests/api/openstack/test_versions.py index b2896a780..9c4af0787 100644 --- a/nova/tests/api/openstack/test_versions.py +++ b/nova/tests/api/openstack/test_versions.py @@ -72,99 +72,132 @@ class VersionsTest(test.TestCase): req = webob.Request.blank('/v1.0/') req.accept = "application/json" res = req.get_response(fakes.wsgi_app()) - print res.body self.assertEqual(res.status_int, 200) self.assertEqual(res.content_type, "application/json") - versions = json.loads(res.body)["versions"] - expected = [ - { - "version" : { - "id" : "v1.0", - "status" : "CURRENT", - "updated" : "2011-01-21T11:33:21-06:00", - "links": [ - { - "rel" : "self", - "href" : "http://servers.api.openstack.org/v1.0/" - }, - { - "rel" : "describedby", - "type" : "application/pdf", - "href" : "http://docs.rackspacecloud.com/" - "servers/api/v1.0/cs-devguide-20110125.pdf" - }, - { - "rel" : "describedby", - "type" : "application/vnd.sun.wadl+xml", - "href" : "http://docs.rackspacecloud.com/" - "servers/api/v1.0/application.wadl" - } - ], - "media-types": [ - { - "base" : "application/xml", - "type" : "application/" - "vnd.openstack.compute-v1.0+xml" - }, - { - "base" : "application/json", - "type" : "application/" - "vnd.openstack.compute-v1.0+json" - } - ] - } + version = json.loads(res.body) + expected = { + "version" : { + "id" : "v1.0", + "status" : "CURRENT", + "updated" : "2011-01-21T11:33:21-06:00", + "links": [ + { + "rel" : "self", + "href" : "http://servers.api.openstack.org/v1.0/" + }, + { + "rel" : "describedby", + "type" : "application/pdf", + "href" : "http://docs.rackspacecloud.com/" + "servers/api/v1.0/cs-devguide-20110125.pdf" + }, + { + "rel" : "describedby", + "type" : "application/vnd.sun.wadl+xml", + "href" : "http://docs.rackspacecloud.com/" + "servers/api/v1.0/application.wadl" + } + ], + "media-types": [ + { + "base" : "application/xml", + "type" : "application/" + "vnd.openstack.compute-v1.0+xml" + }, + { + "base" : "application/json", + "type" : "application/" + "vnd.openstack.compute-v1.0+json" + } + ] } - ] - self.assertEqual(versions, expected) + } + self.assertEqual(expected, version) def test_get_version_1_1_detail(self): req = webob.Request.blank('/v1.1/') req.accept = "application/json" res = req.get_response(fakes.wsgi_app()) - print res.body self.assertEqual(res.status_int, 200) self.assertEqual(res.content_type, "application/json") - versions = json.loads(res.body)["versions"] - expected = [ - { - "version" : { - "id" : "v1.1", - "status" : "CURRENT", - "updated" : "2011-01-21T11:33:21-06:00", - "links": [ - { - "rel" : "self", - "href" : "http://servers.api.openstack.org/v1.1/" - }, - { - "rel" : "describedby", - "type" : "application/pdf", - "href" : "http://docs.rackspacecloud.com/" - "servers/api/v1.1/cs-devguide-20110125.pdf" - }, - { - "rel" : "describedby", - "type" : "application/vnd.sun.wadl+xml", - "href" : "http://docs.rackspacecloud.com/" - "servers/api/v1.1/application.wadl" - } - ], - "media-types": [ - { - "base" : "application/xml", - "type" : "application/" - "vnd.openstack.compute-v1.1+xml" - }, - { - "base" : "application/json", - "type" : "application/" - "vnd.openstack.compute-v1.1+json" - } - ] - } + version = json.loads(res.body) + expected = { + "version" : { + "id" : "v1.1", + "status" : "CURRENT", + "updated" : "2011-01-21T11:33:21-06:00", + "links": [ + { + "rel" : "self", + "href" : "http://servers.api.openstack.org/v1.1/" + }, + { + "rel" : "describedby", + "type" : "application/pdf", + "href" : "http://docs.rackspacecloud.com/" + "servers/api/v1.1/cs-devguide-20110125.pdf" + }, + { + "rel" : "describedby", + "type" : "application/vnd.sun.wadl+xml", + "href" : "http://docs.rackspacecloud.com/" + "servers/api/v1.1/application.wadl" + } + ], + "media-types": [ + { + "base" : "application/xml", + "type" : "application/" + "vnd.openstack.compute-v1.1+xml" + }, + { + "base" : "application/json", + "type" : "application/" + "vnd.openstack.compute-v1.1+json" + } + ] } - ] - self.assertEqual(versions, expected) + } + self.assertEqual(expected, version) + + def test_get_version_1_0_detail_xml(self): + pass + + def test_get_version_1_1_detail_xml(self): + req = webob.Request.blank('/v1.1/') + 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 = """ + + + + + + + + + + + + + + """.replace(" ", "").replace("\n", "") + + actual = res.body.replace(" ", "").replace("\n", "") + self.assertEqual(expected, actual) def test_get_version_list_xml(self): req = webob.Request.blank('/') @@ -265,7 +298,37 @@ class VersionsTest(test.TestCase): self.assertEqual(actual, expected) - def test_xml_serializer(self): + def test_versions_list_xml_serializer(self): + versions_data = { + 'versions': [ + { + "id": "2.7.1", + "updated": "2011-07-18T11:30:00Z", + "status": "DEPRECATED", + "links": [ + { + "rel": "self", + "href": "http://test/2.7.1", + } + ], + }, + ] + } + + expected = """ + + + + + """.replace(" ", "").replace("\n", "") + + serializer = versions.VersionsXMLSerializer() + response = serializer.index(versions_data) + response = response.replace(" ", "").replace("\n", "") + self.assertEqual(expected, response) + + def test_versions_detail_xml_serializer(self): versions_data = { 'versions': [ { @@ -291,11 +354,70 @@ class VersionsTest(test.TestCase): """.replace(" ", "").replace("\n", "") serializer = versions.VersionsXMLSerializer() - response = serializer.default(versions_data) + response = serializer.index(versions_data) + response = response.replace(" ", "").replace("\n", "") + self.assertEqual(expected, response) + + def test_versions_list_atom_serializer(self): + versions_data = { + 'versions': [ + { + "id": "2.9.8", + "updated": "2011-07-20T11:40:00Z", + "status": "CURRENT", + "links": [ + { + "rel": "self", + "href": "http://test/2.9.8", + } + ], + }, + ] + } + + expected = """ + + + Available API Versions + + + 2011-07-20T11:40:00Z + + + http://test/ + + + + Rackspace + + + http://www.rackspace.com/ + + + + + + http://test/2.9.8 + + + Version 2.9.8 + + + 2011-07-20T11:40:00Z + + + + Version 2.9.8 CURRENT (2011-07-20T11:40:00Z) + + + """.replace(" ", "").replace("\n", "") + + serializer = versions.VersionsAtomSerializer() + response = serializer.index(versions_data) response = response.replace(" ", "").replace("\n", "") self.assertEqual(expected, response) - def test_atom_serializer(self): + def test_version_detail_atom_serializer(self): versions_data = { 'versions': [ { @@ -350,7 +472,6 @@ class VersionsTest(test.TestCase): """.replace(" ", "").replace("\n", "") serializer = versions.VersionsAtomSerializer() - response = serializer.default(versions_data) - print response + response = serializer.index(versions_data) response = response.replace(" ", "").replace("\n", "") self.assertEqual(expected, response) -- cgit From 71a103822b41df3d90a1e958baffda55a9cb8730 Mon Sep 17 00:00:00 2001 From: William Wolf Date: Mon, 25 Jul 2011 16:25:19 -0400 Subject: xml version detail working with tests --- nova/api/openstack/versions.py | 30 +++++- nova/tests/api/openstack/test_versions.py | 165 +++++++++++++++++++++--------- 2 files changed, 147 insertions(+), 48 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/versions.py b/nova/api/openstack/versions.py index 5655edcfc..00fc8d98f 100644 --- a/nova/api/openstack/versions.py +++ b/nova/api/openstack/versions.py @@ -209,16 +209,39 @@ class VersionsXMLSerializer(wsgi.XMLDictSerializer): return root - def _create_version_node(self, version): + def _create_media_types(self, media_types): + base = self._xml_doc.createElement('media-types') + for type in media_types: + node = self._xml_doc.createElement('media-type') + node.setAttribute('base', type['base']) + node.setAttribute('type', type['type']) + base.appendChild(node) + + return base + + def _create_version_node(self, version, create_ns=False): version_node = self._xml_doc.createElement('version') + if create_ns: + xmlns = "http://docs.openstack.org/common/api/%s" % version['id'] + xmlns_atom = "http://www.w3.org/2005/Atom" + version_node.setAttribute('xmlns', xmlns) + version_node.setAttribute('xmlns:atom', xmlns_atom) + version_node.setAttribute('id', version['id']) version_node.setAttribute('status', version['status']) version_node.setAttribute('updated', version['updated']) + if 'media-types' in version: + media_types = self._create_media_types(version['media-types']) + version_node.appendChild(media_types) + for link in version['links']: link_node = self._xml_doc.createElement('atom:link') link_node.setAttribute('rel', link['rel']) link_node.setAttribute('href', link['href']) + if 'type' in link: + link_node.setAttribute('type', link['type']) + version_node.appendChild(link_node) return version_node @@ -230,7 +253,10 @@ class VersionsXMLSerializer(wsgi.XMLDictSerializer): return self.to_xml_string(node) def detail(self,data): - return "" + self._xml_doc = minidom.Document() + node = self._create_version_node(data['version'], True) + + return self.to_xml_string(node) def multi(self, data): self._xml_doc = minidom.Document() diff --git a/nova/tests/api/openstack/test_versions.py b/nova/tests/api/openstack/test_versions.py index 9c4af0787..632d388fa 100644 --- a/nova/tests/api/openstack/test_versions.py +++ b/nova/tests/api/openstack/test_versions.py @@ -161,7 +161,40 @@ class VersionsTest(test.TestCase): self.assertEqual(expected, version) def test_get_version_1_0_detail_xml(self): - pass + req = webob.Request.blank('/v1.0/') + 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 = """ + + + + + + + + + + + + + """.replace(" ", "").replace("\n", "") + + actual = res.body.replace(" ", "").replace("\n", "") + self.assertEqual(expected, actual) def test_get_version_1_1_detail_xml(self): req = webob.Request.blank('/v1.1/') @@ -170,31 +203,31 @@ class VersionsTest(test.TestCase): self.assertEqual(res.status_int, 200) self.assertEqual(res.content_type, "application/xml") expected = """ - - - - - - - - - - - - - - """.replace(" ", "").replace("\n", "") + + + + + + + + + + + + + """.replace(" ", "").replace("\n", "") actual = res.body.replace(" ", "").replace("\n", "") self.assertEqual(expected, actual) @@ -330,31 +363,70 @@ class VersionsTest(test.TestCase): def test_versions_detail_xml_serializer(self): versions_data = { - 'versions': [ - { - "id": "2.7.1", - "updated": "2011-07-18T11:30:00Z", - "status": "DEPRECATED", - "links": [ - { - "rel": "self", - "href": "http://test/2.7.1", - } - ], - }, - ] + "version" : { + "id": "v1.0", + "status": "CURRENT", + "updated": "2011-01-21T11:33:21-06:00", + "links": [ + { + "rel": "self", + "href": "http://servers.api.openstack.org/v1.0/" + }, + { + "rel": "describedby", + "type": "application/pdf", + "href": "http://docs.rackspacecloud.com/" + "servers/api/v1.0/cs-devguide-20110125.pdf" + }, + { + "rel": "describedby", + "type": "application/vnd.sun.wadl+xml", + "href": "http://docs.rackspacecloud.com/" + "servers/api/v1.0/application.wadl" + }, + ], + "media-types": [ + { + "base" : "application/xml", + "type" : "application/vnd.openstack.compute-v1.0+xml" + }, + { + "base" : "application/json", + "type" : "application/vnd.openstack.compute-v1.0+json" + } + ], + }, } expected = """ - - - - - """.replace(" ", "").replace("\n", "") + + + + + + + + + + + + + """.replace(" ", "").replace("\n", "") serializer = versions.VersionsXMLSerializer() - response = serializer.index(versions_data) + response = serializer.detail(versions_data) response = response.replace(" ", "").replace("\n", "") self.assertEqual(expected, response) @@ -418,6 +490,7 @@ class VersionsTest(test.TestCase): self.assertEqual(expected, response) def test_version_detail_atom_serializer(self): + #TODO versions_data = { 'versions': [ { -- cgit From 7be2b2482fde20be8802cfe6a200590933a73d7e Mon Sep 17 00:00:00 2001 From: William Wolf Date: Mon, 25 Jul 2011 18:28:43 -0400 Subject: atom and xml_detail working, with tests --- nova/api/openstack/versions.py | 49 ++++++-- nova/tests/api/openstack/test_versions.py | 200 +++++++++++++++++++++--------- 2 files changed, 183 insertions(+), 66 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/versions.py b/nova/api/openstack/versions.py index 00fc8d98f..6fba4bbe0 100644 --- a/nova/api/openstack/versions.py +++ b/nova/api/openstack/versions.py @@ -116,7 +116,7 @@ class VersionV10(object): "version" : { "id": "v1.0", "status": "CURRENT", - "updated": "2011-01-21T11:33:21-06:00", + "updated": "2011-01-21T11:33:21Z", "links": [ { "rel": "self", @@ -155,7 +155,7 @@ class VersionV11(object): "version" : { "id": "v1.1", "status": "CURRENT", - "updated": "2011-01-21T11:33:21-06:00", + "updated": "2011-01-21T11:33:21Z", "links": [ { "rel": "self", @@ -298,8 +298,33 @@ class VersionsAtomSerializer(wsgi.XMLDictSerializer): link_href = link_href.rstrip('/') return link_href.rsplit('/', 1)[0] + '/' - def _create_meta(self, root, versions): - title = self._create_text_elem('title', 'Available API Versions', + def _create_detail_meta(self, root, version): + title = self._create_text_elem('title', "About This Version", + type='text') + + updated = self._create_text_elem('updated', version['updated']) + + uri = version['links'][0]['href'] + id = self._create_text_elem('id', uri) + + link = self._xml_doc.createElement('link') + link.setAttribute('rel', 'self') + link.setAttribute('href', uri) + + author = self._xml_doc.createElement('author') + author_name = self._create_text_elem('name', 'Rackspace') + author_uri = self._create_text_elem('uri', 'http://www.rackspace.com/') + author.appendChild(author_name) + author.appendChild(author_uri) + + root.appendChild(title) + root.appendChild(updated) + root.appendChild(id) + root.appendChild(author) + root.appendChild(link) + + def _create_list_meta(self, root, versions): + title = self._create_text_elem('title', "Available API Versions", type='text') # Set this updated to the most recently updated version recent = self._get_most_recent_update(versions) @@ -307,6 +332,7 @@ class VersionsAtomSerializer(wsgi.XMLDictSerializer): base_url = self._get_base_url(versions[0]['links'][0]['href']) id = self._create_text_elem('id', base_url) + link = self._xml_doc.createElement('link') link.setAttribute('rel', 'self') link.setAttribute('href', base_url) @@ -341,7 +367,10 @@ class VersionsAtomSerializer(wsgi.XMLDictSerializer): link_node = self._xml_doc.createElement('link') link_node.setAttribute('rel', link['rel']) link_node.setAttribute('href', link['href']) - entry.appendChild(link_node) + if 'type' in link: + link_node.setAttribute('type', link['type']) + + entry.appendChild(link_node) content = self._create_text_elem('content', 'Version %s %s (%s)' % @@ -356,14 +385,18 @@ class VersionsAtomSerializer(wsgi.XMLDictSerializer): def index(self, data): self._xml_doc = minidom.Document() node = self._xml_doc.createElementNS(self.xmlns, 'feed') - self._create_meta(node, data['versions']) + self._create_list_meta(node, data['versions']) self._create_version_entries(node, data['versions']) return self.to_xml_string(node) def detail(self, data): - #TODO - pass + self._xml_doc = minidom.Document() + node = self._xml_doc.createElementNS(self.xmlns, 'feed') + self._create_detail_meta(node, data['version']) + self._create_version_entries(node, [data['version']]) + + return self.to_xml_string(node) def create_resource(version='1.0'): controller = { diff --git a/nova/tests/api/openstack/test_versions.py b/nova/tests/api/openstack/test_versions.py index 632d388fa..11b69ec83 100644 --- a/nova/tests/api/openstack/test_versions.py +++ b/nova/tests/api/openstack/test_versions.py @@ -79,7 +79,7 @@ class VersionsTest(test.TestCase): "version" : { "id" : "v1.0", "status" : "CURRENT", - "updated" : "2011-01-21T11:33:21-06:00", + "updated" : "2011-01-21T11:33:21Z", "links": [ { "rel" : "self", @@ -125,7 +125,7 @@ class VersionsTest(test.TestCase): "version" : { "id" : "v1.1", "status" : "CURRENT", - "updated" : "2011-01-21T11:33:21-06:00", + "updated" : "2011-01-21T11:33:21Z", "links": [ { "rel" : "self", @@ -168,7 +168,7 @@ class VersionsTest(test.TestCase): self.assertEqual(res.content_type, "application/xml") expected = """ @@ -204,7 +204,7 @@ class VersionsTest(test.TestCase): self.assertEqual(res.content_type, "application/xml") expected = """ @@ -253,6 +253,80 @@ class VersionsTest(test.TestCase): self.assertEqual(expected, actual) + def test_get_version_1_0_detail_atom(self): + #TODO + req = webob.Request.blank('/v1.0/') + 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 = """ + + + + + + + + + + + + + """.replace(" ", "").replace("\n", "") + + actual = res.body.replace(" ", "").replace("\n", "") + self.assertEqual(expected, actual) + + def test_get_version_1_1_detail_atom(self): + #TODO + req = webob.Request.blank('/v1.1/') + 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 = """ + + + + + + + + + + + + + """.replace(" ", "").replace("\n", "") + + actual = res.body.replace(" ", "").replace("\n", "") + self.assertEqual(expected, actual) + def test_get_version_list_atom(self): req = webob.Request.blank('/') req.accept = "application/atom+xml" @@ -361,12 +435,12 @@ class VersionsTest(test.TestCase): response = response.replace(" ", "").replace("\n", "") self.assertEqual(expected, response) - def test_versions_detail_xml_serializer(self): - versions_data = { + def test_version_detail_xml_serializer(self): + version_data = { "version" : { "id": "v1.0", "status": "CURRENT", - "updated": "2011-01-21T11:33:21-06:00", + "updated": "2011-01-21T11:33:21Z", "links": [ { "rel": "self", @@ -400,7 +474,7 @@ class VersionsTest(test.TestCase): expected = """ @@ -426,7 +500,7 @@ class VersionsTest(test.TestCase): """.replace(" ", "").replace("\n", "") serializer = versions.VersionsXMLSerializer() - response = serializer.detail(versions_data) + response = serializer.detail(version_data) response = response.replace(" ", "").replace("\n", "") self.assertEqual(expected, response) @@ -492,59 +566,69 @@ class VersionsTest(test.TestCase): def test_version_detail_atom_serializer(self): #TODO versions_data = { - 'versions': [ - { - "id": "2.9.8", - "updated": "2011-07-20T11:40:00Z", - "status": "CURRENT", - "links": [ - { - "rel": "self", - "href": "http://test/2.9.8", - } - ], - }, - ] + "version" : { + "id": "v1.1", + "status": "CURRENT", + "updated": "2011-01-21T11:33:21Z", + "links": [ + { + "rel": "self", + "href": "http://servers.api.openstack.org/v1.1/" + }, + { + "rel": "describedby", + "type": "application/pdf", + "href": "http://docs.rackspacecloud.com/" + "servers/api/v1.1/cs-devguide-20110125.pdf" + }, + { + "rel": "describedby", + "type": "application/vnd.sun.wadl+xml", + "href": "http://docs.rackspacecloud.com/" + "servers/api/v1.1/application.wadl" + }, + ], + "media-types": [ + { + "base" : "application/xml", + "type" : "application/vnd.openstack.compute-v1.1+xml" + }, + { + "base" : "application/json", + "type" : "application/vnd.openstack.compute-v1.1+json" + } + ], + }, } expected = """ - - - Available API Versions - - - 2011-07-20T11:40:00Z - - - http://test/ - - - - Rackspace - - - http://www.rackspace.com/ - - - - - - http://test/2.9.8 - - - Version 2.9.8 - - - 2011-07-20T11:40:00Z - - - - Version 2.9.8 CURRENT (2011-07-20T11:40:00Z) - - - """.replace(" ", "").replace("\n", "") + + About This Version + 2011-01-21T11:33:21Z + http://servers.api.openstack.org/v1.1/ + + Rackspace + http://www.rackspace.com/ + + + + http://servers.api.openstack.org/v1.1/ + Version v1.1 + 2011-01-21T11:33:21Z + + + + + Version v1.1 CURRENT (2011-01-21T11:33:21Z) + + + """.replace(" ", "").replace("\n", "") serializer = versions.VersionsAtomSerializer() - response = serializer.index(versions_data) + response = serializer.detail(versions_data) response = response.replace(" ", "").replace("\n", "") self.assertEqual(expected, response) -- cgit From 4236f438a81e361beb1b05edd87154b4d5e1ce85 Mon Sep 17 00:00:00 2001 From: "Kevin L. Mitchell" Date: Mon, 25 Jul 2011 22:38:59 +0000 Subject: Pass on auth_token --- nova/context.py | 4 +++- nova/image/glance.py | 11 +++++++++++ nova/tests/glance/stubs.py | 5 ++++- nova/tests/image/test_glance.py | 3 +++ 4 files changed, 21 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/context.py b/nova/context.py index 99085ed75..a765d1695 100644 --- a/nova/context.py +++ b/nova/context.py @@ -32,7 +32,8 @@ class RequestContext(object): """ def __init__(self, user, project, is_admin=None, read_deleted=False, - remote_address=None, timestamp=None, request_id=None): + remote_address=None, timestamp=None, request_id=None, + auth_token=None): if hasattr(user, 'id'): self._user = user self.user_id = user.id @@ -63,6 +64,7 @@ class RequestContext(object): chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-' request_id = ''.join([random.choice(chars) for x in xrange(20)]) self.request_id = request_id + self.auth_token = auth_token @property def user(self): diff --git a/nova/image/glance.py b/nova/image/glance.py index 5c2dc957b..44a3c6f83 100644 --- a/nova/image/glance.py +++ b/nova/image/glance.py @@ -83,11 +83,16 @@ class GlanceImageService(service.BaseImageService): client = property(_get_client, _set_client) + def _set_client_context(self, context): + """Sets the client's auth token.""" + self.client.set_auth_token(context.auth_token) + def index(self, context, filters=None, marker=None, limit=None): """Calls out to Glance for a list of images available.""" # NOTE(sirp): We need to use `get_images_detailed` and not # `get_images` here because we need `is_public` and `properties` # included so we can filter by user + self._set_client_context(context) filtered = [] filters = filters or {} if 'is_public' not in filters: @@ -104,6 +109,7 @@ class GlanceImageService(service.BaseImageService): def detail(self, context, filters=None, marker=None, limit=None): """Calls out to Glance for a list of detailed image information.""" + self._set_client_context(context) filtered = [] filters = filters or {} if 'is_public' not in filters: @@ -120,6 +126,7 @@ class GlanceImageService(service.BaseImageService): def show(self, context, image_id): """Returns a dict with image data for the given opaque image id.""" + self._set_client_context(context) try: image_meta = self.client.get_image_meta(image_id) except glance_exception.NotFound: @@ -143,6 +150,7 @@ class GlanceImageService(service.BaseImageService): def get(self, context, image_id, data): """Calls out to Glance for metadata and data and writes data.""" + self._set_client_context(context) try: image_meta, image_chunks = self.client.get_image(image_id) except glance_exception.NotFound: @@ -160,6 +168,7 @@ class GlanceImageService(service.BaseImageService): :raises: AlreadyExists if the image already exist. """ + self._set_client_context(context) # Translate Base -> Service LOG.debug(_('Creating image in Glance. Metadata passed in %s'), image_meta) @@ -182,6 +191,7 @@ class GlanceImageService(service.BaseImageService): :raises: ImageNotFound if the image does not exist. """ + self._set_client_context(context) # NOTE(vish): show is to check if image is available self.show(context, image_id) try: @@ -198,6 +208,7 @@ class GlanceImageService(service.BaseImageService): :raises: ImageNotFound if the image does not exist. """ + self._set_client_context(context) # NOTE(vish): show is to check if image is available self.show(context, image_id) try: diff --git a/nova/tests/glance/stubs.py b/nova/tests/glance/stubs.py index aac3ff330..d51b19ccd 100644 --- a/nova/tests/glance/stubs.py +++ b/nova/tests/glance/stubs.py @@ -60,7 +60,10 @@ class FakeGlance(object): 'container_format': 'ovf'}, 'image_data': StringIO.StringIO('')}} - def __init__(self, host, port=None, use_ssl=False): + def __init__(self, host, port=None, use_ssl=False, auth_tok=None): + pass + + def set_auth_token(self, auth_tok): pass def get_image_meta(self, image_id): diff --git a/nova/tests/image/test_glance.py b/nova/tests/image/test_glance.py index 223e7ae57..5a40f578f 100644 --- a/nova/tests/image/test_glance.py +++ b/nova/tests/image/test_glance.py @@ -31,6 +31,9 @@ class StubGlanceClient(object): self.add_response = add_response self.update_response = update_response + def set_auth_token(self, auth_tok): + pass + def get_image_meta(self, image_id): return self.images[image_id] -- cgit From eba09454a21ce49afa821ec63ed801883354ff7e Mon Sep 17 00:00:00 2001 From: William Wolf Date: Mon, 25 Jul 2011 20:34:41 -0400 Subject: initial stuff to get away from string comparisons for XML, and use ElementTree --- nova/api/openstack/versions.py | 7 ++ nova/tests/api/openstack/test_versions.py | 109 +++++++++++++++++++++++------- 2 files changed, 90 insertions(+), 26 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/versions.py b/nova/api/openstack/versions.py index 6fba4bbe0..c250dac8c 100644 --- a/nova/api/openstack/versions.py +++ b/nova/api/openstack/versions.py @@ -201,6 +201,13 @@ class VersionsRequestDeserializer(wsgi.RequestDeserializer): class VersionsXMLSerializer(wsgi.XMLDictSerializer): + #TODO(wwolf): this is temporary until we get rid of toprettyxml + # in the base class (XMLDictSerializer), which I plan to do in + # another branch + def to_xml_string(self, node, has_atom=False): + self._add_xmlns(node, has_atom) + return node.toxml(encoding='UTF-8') + def _versions_to_xml(self, versions): root = self._xml_doc.createElement('versions') diff --git a/nova/tests/api/openstack/test_versions.py b/nova/tests/api/openstack/test_versions.py index 11b69ec83..31b201214 100644 --- a/nova/tests/api/openstack/test_versions.py +++ b/nova/tests/api/openstack/test_versions.py @@ -18,6 +18,8 @@ import json import stubout import webob +import xml.etree.ElementTree + from nova import context from nova import test @@ -166,6 +168,21 @@ class VersionsTest(test.TestCase): res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 200) self.assertEqual(res.content_type, "application/xml") + root = xml.etree.ElementTree.XML(res.body) + self.assertEqual(root.tag.split('}')[1], "version") + self.assertEqual(root.tag.split('}')[0].strip('{'), + "http://docs.openstack.org/common/api/v1.0") + + children = list(root) + media_types = children[0] + media_type_nodes = list(media_types) + links = (children[1], children[2], children[3]) + + self.assertEqual(media_types.tag.split('}')[1], 'media-types') + for media_node in media_type_nodes: + self.assertEqual(media_node.tag.split('}')[1], 'media-type') + + expected = """ + serializer = versions.VersionsXMLSerializer() + response = serializer.detail(version_data) - - - - + root = xml.etree.ElementTree.XML(response) + self.assertEqual(root.tag.split('}')[1], "version") + self.assertEqual(root.tag.split('}')[0].strip('{'), + "http://docs.openstack.org/common/api/v1.0") - + children = list(root) + media_types = children[0] + media_type_nodes = list(media_types) + links = (children[1], children[2], children[3]) - + self.assertEqual(media_types.tag.split('}')[1], 'media-types') + for i, media_node in enumerate(media_type_nodes): + self.assertEqual(media_node.tag.split('}')[1], 'media-type') + for key, val in version_data['version']['media-types'][i].items(): + self.assertEqual(val, media_node.get(key)) - - """.replace(" ", "").replace("\n", "") + for i, link in enumerate(links): + self.assertEqual(link.tag.split('}')[0].strip('{'), + 'http://www.w3.org/2005/Atom') + self.assertEqual(link.tag.split('}')[1], 'link') + for key, val in version_data['version']['links'][i].items(): + self.assertEqual(val, link.get(key)) - serializer = versions.VersionsXMLSerializer() - response = serializer.detail(version_data) - response = response.replace(" ", "").replace("\n", "") - self.assertEqual(expected, response) def test_versions_list_atom_serializer(self): versions_data = { @@ -563,6 +575,51 @@ class VersionsTest(test.TestCase): response = response.replace(" ", "").replace("\n", "") self.assertEqual(expected, response) + root = xml.etree.ElementTree.XML(response) + self.assertEqual(root.tag.split('}')[1], "feed") + self.assertEqual(root.tag.split('}')[0].strip('{'), + "http://www.w3.org/2005/Atom") + + children = list(root) + title = children[0] + updated = children[1] + id = children[2] + author = children[3] + link = children[4] + entry = children[5] + + self.assertEqual(title.tag.split('}')[1], 'title') + self.assertEqual(title.text, 'Available API Versions') + self.assertEqual(updated.tag.split('}')[1], 'updated') + self.assertEqual(updated.text, '2011-07-20T11:40:00Z') + self.assertEqual(id.tag.split('}')[1], 'id') + self.assertEqual(id.text, 'http://test/') + + self.assertEqual(author.tag.split('}')[1], 'author') + author_name = list(author)[0] + author_uri = list(author)[1] + self.assertEqual(author_name.tag.split('}')[1], 'name') + self.assertEqual(author_name.text, 'Rackspace') + self.assertEqual(author_uri.tag.split('}')[1], 'uri') + self.assertEqual(author_uri.text, 'http://www.rackspace.com/') + + self.assertEqual(link.get('href'), 'http://test/') + self.assertEqual(link.get('rel'), 'self') + + self.assertEqual(entry.tag.split('}')[1], 'entry') + entry_children = list(entry) + entry_id = entry_children[0] + entry_title = entry_children[1] + entry_updated = entry_children[1] + + + + #self.assertEqual(media_types.tag.split('}')[1], 'media-types') + #for i, media_node in enumerate(media_type_nodes): + #self.assertEqual(media_node.tag.split('}')[1], 'media-type') + #for key, val in version_data['version']['media-types'][i].items(): + #self.assertEqual(val, media_node.get(key)) + def test_version_detail_atom_serializer(self): #TODO versions_data = { -- cgit From 91eff4fd95ce28650800839aa50368a3c9280e72 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Mon, 25 Jul 2011 22:55:37 -0400 Subject: removing xenapi_image_service flag --- nova/tests/test_xenapi.py | 35 ++++++++++++++++++----------------- nova/virt/xenapi/vm_utils.py | 14 ++++++++------ nova/virt/xenapi_conn.py | 3 --- 3 files changed, 26 insertions(+), 26 deletions(-) (limited to 'nova') diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index 4cb7447d3..87e2e93b3 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -429,7 +429,7 @@ class XenAPIVMTestCase(test.TestCase): self.assertTrue(instance.architecture) def test_spawn_not_enough_memory(self): - FLAGS.xenapi_image_service = 'glance' + FLAGS.image_service = 'nova.image.glance.GlanceImageService' self.assertRaises(Exception, self._test_spawn, 1, 2, 3, "4") # m1.xlarge @@ -441,7 +441,7 @@ class XenAPIVMTestCase(test.TestCase): """ vdi_recs_start = self._list_vdis() - FLAGS.xenapi_image_service = 'glance' + FLAGS.image_service = 'nova.image.glance.GlanceImageService' stubs.stubout_fetch_image_glance_disk(self.stubs) self.assertRaises(xenapi_fake.Failure, self._test_spawn, 1, 2, 3) @@ -456,7 +456,7 @@ class XenAPIVMTestCase(test.TestCase): """ vdi_recs_start = self._list_vdis() - FLAGS.xenapi_image_service = 'glance' + FLAGS.image_service = 'nova.image.glance.GlanceImageService' stubs.stubout_create_vm(self.stubs) self.assertRaises(xenapi_fake.Failure, self._test_spawn, 1, 2, 3) @@ -465,21 +465,21 @@ class XenAPIVMTestCase(test.TestCase): self._check_vdis(vdi_recs_start, vdi_recs_end) def test_spawn_raw_objectstore(self): - FLAGS.xenapi_image_service = 'objectstore' + FLAGS.image_service = 'nova.image.s3.S3ImageService' self._test_spawn(1, None, None) def test_spawn_objectstore(self): - FLAGS.xenapi_image_service = 'objectstore' + FLAGS.image_service = 'nova.image.s3.S3ImageService' self._test_spawn(1, 2, 3) @stub_vm_utils_with_vdi_attached_here def test_spawn_raw_glance(self): - FLAGS.xenapi_image_service = 'glance' + FLAGS.image_service = 'nova.image.glance.GlanceImageService' self._test_spawn(glance_stubs.FakeGlance.IMAGE_RAW, None, None) self.check_vm_params_for_linux() def test_spawn_vhd_glance_linux(self): - FLAGS.xenapi_image_service = 'glance' + FLAGS.image_service = 'nova.image.glance.GlanceImageService' self._test_spawn(glance_stubs.FakeGlance.IMAGE_VHD, None, None, os_type="linux", architecture="x86-64") self.check_vm_params_for_linux() @@ -508,20 +508,20 @@ class XenAPIVMTestCase(test.TestCase): self.assertEqual(len(self.vm['VBDs']), 1) def test_spawn_vhd_glance_windows(self): - FLAGS.xenapi_image_service = 'glance' + FLAGS.image_service = 'nova.image.glance.GlanceImageService' self._test_spawn(glance_stubs.FakeGlance.IMAGE_VHD, None, None, os_type="windows", architecture="i386") self.check_vm_params_for_windows() def test_spawn_glance(self): - FLAGS.xenapi_image_service = 'glance' + FLAGS.image_service = 'nova.image.glance.GlanceImageService' self._test_spawn(glance_stubs.FakeGlance.IMAGE_MACHINE, glance_stubs.FakeGlance.IMAGE_KERNEL, glance_stubs.FakeGlance.IMAGE_RAMDISK) self.check_vm_params_for_linux_with_external_kernel() def test_spawn_netinject_file(self): - FLAGS.xenapi_image_service = 'glance' + FLAGS.image_service = 'nova.image.glance.GlanceImageService' db_fakes.stub_out_db_instance_api(self.stubs, injected=True) self._tee_executed = False @@ -547,7 +547,7 @@ class XenAPIVMTestCase(test.TestCase): # Capture the sudo tee .../etc/network/interfaces command (r'(sudo\s+)?tee.*interfaces', _tee_handler), ]) - FLAGS.xenapi_image_service = 'glance' + FLAGS.image_service = 'nova.image.glance.GlanceImageService' self._test_spawn(glance_stubs.FakeGlance.IMAGE_MACHINE, glance_stubs.FakeGlance.IMAGE_KERNEL, glance_stubs.FakeGlance.IMAGE_RAMDISK, @@ -555,7 +555,7 @@ class XenAPIVMTestCase(test.TestCase): self.assertTrue(self._tee_executed) def test_spawn_netinject_xenstore(self): - FLAGS.xenapi_image_service = 'glance' + FLAGS.image_service = 'nova.image.glance.GlanceImageService' db_fakes.stub_out_db_instance_api(self.stubs, injected=True) self._tee_executed = False @@ -601,7 +601,7 @@ class XenAPIVMTestCase(test.TestCase): @test.skip_test("Never gets an address, not sure why") def test_spawn_vlanmanager(self): - self.flags(xenapi_image_service='glance', + self.flags(image_service='nova.image.glance.GlanceImageService', network_manager='nova.network.manager.VlanManager', network_driver='nova.network.xenapi_net', vlan_interface='fake0') @@ -784,6 +784,7 @@ class XenAPIMigrateInstance(test.TestCase): conn.migrate_disk_and_power_off(instance, '127.0.0.1') def test_finish_resize(self): + FLAGS.image_service = 'nova.image.glance.GlanceImageService' instance = db.instance_create(self.context, self.values) stubs.stubout_session(self.stubs, stubs.FakeSessionForMigrationTests) stubs.stubout_loopingcall_start(self.stubs) @@ -827,7 +828,7 @@ class XenAPIDetermineDiskImageTestCase(test.TestCase): def test_instance_disk(self): """If a kernel is specified, the image type is DISK (aka machine).""" - FLAGS.xenapi_image_service = 'objectstore' + FLAGS.image_service = 'nova.image.s3.S3ImageService' self.fake_instance.image_ref = glance_stubs.FakeGlance.IMAGE_MACHINE self.fake_instance.kernel_id = glance_stubs.FakeGlance.IMAGE_KERNEL self.assert_disk_type(vm_utils.ImageType.DISK) @@ -837,7 +838,7 @@ class XenAPIDetermineDiskImageTestCase(test.TestCase): If the kernel isn't specified, and we're not using Glance, then DISK_RAW is assumed. """ - FLAGS.xenapi_image_service = 'objectstore' + FLAGS.image_service = 'nova.image.s3.S3ImageService' self.fake_instance.image_ref = glance_stubs.FakeGlance.IMAGE_RAW self.fake_instance.kernel_id = None self.assert_disk_type(vm_utils.ImageType.DISK_RAW) @@ -847,7 +848,7 @@ class XenAPIDetermineDiskImageTestCase(test.TestCase): If we're using Glance, then defer to the image_type field, which in this case will be 'raw'. """ - FLAGS.xenapi_image_service = 'glance' + FLAGS.image_service = 'nova.image.glance.GlanceImageService' self.fake_instance.image_ref = glance_stubs.FakeGlance.IMAGE_RAW self.fake_instance.kernel_id = None self.assert_disk_type(vm_utils.ImageType.DISK_RAW) @@ -857,7 +858,7 @@ class XenAPIDetermineDiskImageTestCase(test.TestCase): If we're using Glance, then defer to the image_type field, which in this case will be 'vhd'. """ - FLAGS.xenapi_image_service = 'glance' + FLAGS.image_service = 'nova.image.glance.GlanceImageService' self.fake_instance.image_ref = glance_stubs.FakeGlance.IMAGE_VHD self.fake_instance.kernel_id = None self.assert_disk_type(vm_utils.ImageType.DISK_VHD) diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index 71107aff4..76a228c39 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -411,7 +411,10 @@ class VMHelper(HelperBase): """ image_type is interpreted as an ImageType instance Related flags: - xenapi_image_service = ['glance', 'objectstore'] + image_service = [ + 'nova.image.glance.GlanceImageService', + 'nova.image.s3.S3ImageService', + ] glance_address = 'address for glance services' glance_port = 'port for glance services' @@ -420,7 +423,7 @@ class VMHelper(HelperBase): """ access = AuthManager().get_access_key(user, project) - if FLAGS.xenapi_image_service == 'glance': + if FLAGS.image_service == 'nova.image.glance.GlanceImageService': return cls._fetch_image_glance(session, instance_id, image, access, image_type) else: @@ -600,9 +603,7 @@ class VMHelper(HelperBase): else: return ImageType.DISK_RAW - # FIXME(sirp): can we unify the ImageService and xenapi_image_service - # abstractions? - if FLAGS.xenapi_image_service == 'glance': + if FLAGS.image_service == 'nova.image.glance.GlanceImageService': image_type = determine_from_glance() else: image_type = determine_from_instance() @@ -678,11 +679,12 @@ class VMHelper(HelperBase): 4. Glance (DISK): pv is assumed """ - if FLAGS.xenapi_image_service == 'glance': + if FLAGS.image_service == 'nova.image.glance.GlanceImageService': # 2, 3, 4: Glance return cls._determine_is_pv_glance( session, vdi_ref, disk_image_type, os_type) else: + print FLAGS.image_service # 1. Objecstore return cls._determine_is_pv_objectstore(session, instance_id, vdi_ref) diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index ec8c44c1c..bcdb0ab84 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -101,9 +101,6 @@ flags.DEFINE_float('xenapi_task_poll_interval', 'The interval used for polling of remote tasks ' '(Async.VM.start, etc). Used only if ' 'connection_type=xenapi.') -flags.DEFINE_string('xenapi_image_service', - 'glance', - 'Where to get VM images: glance or objectstore.') flags.DEFINE_float('xenapi_vhd_coalesce_poll_interval', 5.0, 'The interval used for polling of coalescing vhds.' -- cgit From ac1e571c6e767d5f6f1dc01b3d0b38333b3c3eb2 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Mon, 25 Jul 2011 23:03:01 -0400 Subject: removing rogue print --- nova/virt/xenapi/vm_utils.py | 1 - 1 file changed, 1 deletion(-) (limited to 'nova') diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index 76a228c39..9e5527916 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -684,7 +684,6 @@ class VMHelper(HelperBase): return cls._determine_is_pv_glance( session, vdi_ref, disk_image_type, os_type) else: - print FLAGS.image_service # 1. Objecstore return cls._determine_is_pv_objectstore(session, instance_id, vdi_ref) -- cgit From 696dc56b74a08d224beccdfd644536ec4217321d Mon Sep 17 00:00:00 2001 From: William Wolf Date: Tue, 26 Jul 2011 11:15:44 -0400 Subject: updated atom tests --- nova/api/openstack/versions.py | 14 +++- nova/tests/api/openstack/test_versions.py | 111 +++++++++++++++--------------- 2 files changed, 65 insertions(+), 60 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/versions.py b/nova/api/openstack/versions.py index c250dac8c..03b99f342 100644 --- a/nova/api/openstack/versions.py +++ b/nova/api/openstack/versions.py @@ -115,7 +115,7 @@ class VersionV10(object): return { "version" : { "id": "v1.0", - "status": "CURRENT", + "status": "DEPRECATED", "updated": "2011-01-21T11:33:21Z", "links": [ { @@ -415,7 +415,15 @@ def create_resource(version='1.0'): 'application/xml': VersionsXMLSerializer(), 'application/atom+xml': VersionsAtomSerializer(), } - serializer = wsgi.ResponseSerializer(body_serializers) - return wsgi.Resource(controller, serializer=serializer) + supported_content_types = ('application/json', + 'application/xml', + 'application/atom+xml') + deserializer = wsgi.RequestDeserializer( + supported_content_types=supported_content_types) + + + + return wsgi.Resource(controller, serializer=serializer, + deserializer=deserializer) diff --git a/nova/tests/api/openstack/test_versions.py b/nova/tests/api/openstack/test_versions.py index c48f397a1..88fe71041 100644 --- a/nova/tests/api/openstack/test_versions.py +++ b/nova/tests/api/openstack/test_versions.py @@ -271,75 +271,73 @@ class VersionsTest(test.TestCase): self.assertEqual(expected, actual) def test_get_version_1_0_detail_atom(self): - #TODO req = webob.Request.blank('/v1.0/') - req.accept = "application/xml" + req.accept = "application/atom+xml" res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 200) - self.assertEqual(res.content_type, "application/xml") + self.assertEqual("application/atom+xml", res.content_type) expected = """ - - - - - - - - - - - - - """.replace(" ", "").replace("\n", "") + + About This Version + 2011-01-21T11:33:21Z + http://servers.api.openstack.org/v1.0/ + + Rackspace + http://www.rackspace.com/ + + + + http://servers.api.openstack.org/v1.0/ + Version v1.0 + 2011-01-21T11:33:21Z + + + + + Version v1.0 DEPRECATED (2011-01-21T11:33:21Z) + + + """.replace(" ", "").replace("\n", "") actual = res.body.replace(" ", "").replace("\n", "") self.assertEqual(expected, actual) def test_get_version_1_1_detail_atom(self): - #TODO req = webob.Request.blank('/v1.1/') - req.accept = "application/xml" + req.accept = "application/atom+xml" res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 200) - self.assertEqual(res.content_type, "application/xml") + self.assertEqual("application/atom+xml", res.content_type) expected = """ - - - - - - - - - - - - - """.replace(" ", "").replace("\n", "") + + About This Version + 2011-01-21T11:33:21Z + http://servers.api.openstack.org/v1.1/ + + Rackspace + http://www.rackspace.com/ + + + + http://servers.api.openstack.org/v1.1/ + Version v1.1 + 2011-01-21T11:33:21Z + + + + + Version v1.1 CURRENT (2011-01-21T11:33:21Z) + + + """.replace(" ", "").replace("\n", "") actual = res.body.replace(" ", "").replace("\n", "") self.assertEqual(expected, actual) @@ -621,7 +619,6 @@ class VersionsTest(test.TestCase): #self.assertEqual(val, media_node.get(key)) def test_version_detail_atom_serializer(self): - #TODO versions_data = { "version" : { "id": "v1.1", -- cgit From cdcc860cd5d513638c9d85b692f4b46b5e2832ef Mon Sep 17 00:00:00 2001 From: William Wolf Date: Tue, 26 Jul 2011 11:18:08 -0400 Subject: fixed detail xml and json tests that got broken --- nova/tests/api/openstack/test_versions.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/tests/api/openstack/test_versions.py b/nova/tests/api/openstack/test_versions.py index 88fe71041..448a69c07 100644 --- a/nova/tests/api/openstack/test_versions.py +++ b/nova/tests/api/openstack/test_versions.py @@ -80,7 +80,7 @@ class VersionsTest(test.TestCase): expected = { "version" : { "id" : "v1.0", - "status" : "CURRENT", + "status" : "DEPRECATED", "updated" : "2011-01-21T11:33:21Z", "links": [ { @@ -184,7 +184,7 @@ class VersionsTest(test.TestCase): expected = """ - @@ -609,6 +609,9 @@ class VersionsTest(test.TestCase): entry_id = entry_children[0] entry_title = entry_children[1] entry_updated = entry_children[1] + entry_link = entry_children[1] + entry_content = entry_children[1] + self.assertEqual(entry_id.text, "http://test/2.9.8") -- cgit From d2ec9df5027befcfe1ffed19ed983906c2bc77a7 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Tue, 26 Jul 2011 11:26:33 -0400 Subject: removing objectstore and image_service flag checking --- nova/tests/test_xenapi.py | 23 ---------- nova/virt/xenapi/vm_utils.py | 104 ++++--------------------------------------- 2 files changed, 9 insertions(+), 118 deletions(-) (limited to 'nova') diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index 87e2e93b3..77d3062be 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -429,7 +429,6 @@ class XenAPIVMTestCase(test.TestCase): self.assertTrue(instance.architecture) def test_spawn_not_enough_memory(self): - FLAGS.image_service = 'nova.image.glance.GlanceImageService' self.assertRaises(Exception, self._test_spawn, 1, 2, 3, "4") # m1.xlarge @@ -441,7 +440,6 @@ class XenAPIVMTestCase(test.TestCase): """ vdi_recs_start = self._list_vdis() - FLAGS.image_service = 'nova.image.glance.GlanceImageService' stubs.stubout_fetch_image_glance_disk(self.stubs) self.assertRaises(xenapi_fake.Failure, self._test_spawn, 1, 2, 3) @@ -456,7 +454,6 @@ class XenAPIVMTestCase(test.TestCase): """ vdi_recs_start = self._list_vdis() - FLAGS.image_service = 'nova.image.glance.GlanceImageService' stubs.stubout_create_vm(self.stubs) self.assertRaises(xenapi_fake.Failure, self._test_spawn, 1, 2, 3) @@ -464,22 +461,12 @@ class XenAPIVMTestCase(test.TestCase): vdi_recs_end = self._list_vdis() self._check_vdis(vdi_recs_start, vdi_recs_end) - def test_spawn_raw_objectstore(self): - FLAGS.image_service = 'nova.image.s3.S3ImageService' - self._test_spawn(1, None, None) - - def test_spawn_objectstore(self): - FLAGS.image_service = 'nova.image.s3.S3ImageService' - self._test_spawn(1, 2, 3) - @stub_vm_utils_with_vdi_attached_here def test_spawn_raw_glance(self): - FLAGS.image_service = 'nova.image.glance.GlanceImageService' self._test_spawn(glance_stubs.FakeGlance.IMAGE_RAW, None, None) self.check_vm_params_for_linux() def test_spawn_vhd_glance_linux(self): - FLAGS.image_service = 'nova.image.glance.GlanceImageService' self._test_spawn(glance_stubs.FakeGlance.IMAGE_VHD, None, None, os_type="linux", architecture="x86-64") self.check_vm_params_for_linux() @@ -508,20 +495,17 @@ class XenAPIVMTestCase(test.TestCase): self.assertEqual(len(self.vm['VBDs']), 1) def test_spawn_vhd_glance_windows(self): - FLAGS.image_service = 'nova.image.glance.GlanceImageService' self._test_spawn(glance_stubs.FakeGlance.IMAGE_VHD, None, None, os_type="windows", architecture="i386") self.check_vm_params_for_windows() def test_spawn_glance(self): - FLAGS.image_service = 'nova.image.glance.GlanceImageService' self._test_spawn(glance_stubs.FakeGlance.IMAGE_MACHINE, glance_stubs.FakeGlance.IMAGE_KERNEL, glance_stubs.FakeGlance.IMAGE_RAMDISK) self.check_vm_params_for_linux_with_external_kernel() def test_spawn_netinject_file(self): - FLAGS.image_service = 'nova.image.glance.GlanceImageService' db_fakes.stub_out_db_instance_api(self.stubs, injected=True) self._tee_executed = False @@ -547,7 +531,6 @@ class XenAPIVMTestCase(test.TestCase): # Capture the sudo tee .../etc/network/interfaces command (r'(sudo\s+)?tee.*interfaces', _tee_handler), ]) - FLAGS.image_service = 'nova.image.glance.GlanceImageService' self._test_spawn(glance_stubs.FakeGlance.IMAGE_MACHINE, glance_stubs.FakeGlance.IMAGE_KERNEL, glance_stubs.FakeGlance.IMAGE_RAMDISK, @@ -555,7 +538,6 @@ class XenAPIVMTestCase(test.TestCase): self.assertTrue(self._tee_executed) def test_spawn_netinject_xenstore(self): - FLAGS.image_service = 'nova.image.glance.GlanceImageService' db_fakes.stub_out_db_instance_api(self.stubs, injected=True) self._tee_executed = False @@ -784,7 +766,6 @@ class XenAPIMigrateInstance(test.TestCase): conn.migrate_disk_and_power_off(instance, '127.0.0.1') def test_finish_resize(self): - FLAGS.image_service = 'nova.image.glance.GlanceImageService' instance = db.instance_create(self.context, self.values) stubs.stubout_session(self.stubs, stubs.FakeSessionForMigrationTests) stubs.stubout_loopingcall_start(self.stubs) @@ -828,7 +809,6 @@ class XenAPIDetermineDiskImageTestCase(test.TestCase): def test_instance_disk(self): """If a kernel is specified, the image type is DISK (aka machine).""" - FLAGS.image_service = 'nova.image.s3.S3ImageService' self.fake_instance.image_ref = glance_stubs.FakeGlance.IMAGE_MACHINE self.fake_instance.kernel_id = glance_stubs.FakeGlance.IMAGE_KERNEL self.assert_disk_type(vm_utils.ImageType.DISK) @@ -838,7 +818,6 @@ class XenAPIDetermineDiskImageTestCase(test.TestCase): If the kernel isn't specified, and we're not using Glance, then DISK_RAW is assumed. """ - FLAGS.image_service = 'nova.image.s3.S3ImageService' self.fake_instance.image_ref = glance_stubs.FakeGlance.IMAGE_RAW self.fake_instance.kernel_id = None self.assert_disk_type(vm_utils.ImageType.DISK_RAW) @@ -848,7 +827,6 @@ class XenAPIDetermineDiskImageTestCase(test.TestCase): If we're using Glance, then defer to the image_type field, which in this case will be 'raw'. """ - FLAGS.image_service = 'nova.image.glance.GlanceImageService' self.fake_instance.image_ref = glance_stubs.FakeGlance.IMAGE_RAW self.fake_instance.kernel_id = None self.assert_disk_type(vm_utils.ImageType.DISK_RAW) @@ -858,7 +836,6 @@ class XenAPIDetermineDiskImageTestCase(test.TestCase): If we're using Glance, then defer to the image_type field, which in this case will be 'vhd'. """ - FLAGS.image_service = 'nova.image.glance.GlanceImageService' self.fake_instance.image_ref = glance_stubs.FakeGlance.IMAGE_VHD self.fake_instance.kernel_id = None self.assert_disk_type(vm_utils.ImageType.DISK_VHD) diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index 9e5527916..8b6868c31 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -423,13 +423,8 @@ class VMHelper(HelperBase): """ access = AuthManager().get_access_key(user, project) - if FLAGS.image_service == 'nova.image.glance.GlanceImageService': - return cls._fetch_image_glance(session, instance_id, image, - access, image_type) - else: - return cls._fetch_image_objectstore(session, instance_id, image, - access, user.secret, - image_type) + return cls._fetch_image_glance(session, instance_id, image, + access, image_type) @classmethod def _fetch_image_glance_vhd(cls, session, instance_id, image, access, @@ -603,10 +598,7 @@ class VMHelper(HelperBase): else: return ImageType.DISK_RAW - if FLAGS.image_service == 'nova.image.glance.GlanceImageService': - image_type = determine_from_glance() - else: - image_type = determine_from_instance() + image_type = determine_from_glance() log_disk_format(image_type) return image_type @@ -626,42 +618,6 @@ class VMHelper(HelperBase): return cls._fetch_image_glance_disk( session, instance_id, image, access, image_type) - @classmethod - def _fetch_image_objectstore(cls, session, instance_id, image, access, - secret, image_type): - """Fetch an image from objectstore. - - Returns: A single filename if image_type is KERNEL or RAMDISK - A list of dictionaries that describe VDIs, otherwise - """ - url = "http://%s:%s/_images/%s/image" % (FLAGS.s3_host, FLAGS.s3_port, - image) - LOG.debug(_("Asking xapi to fetch %(url)s as %(access)s") % locals()) - if image_type in (ImageType.KERNEL, ImageType.RAMDISK): - fn = 'get_kernel' - else: - fn = 'get_vdi' - args = {} - args['src_url'] = url - args['username'] = access - args['password'] = secret - args['add_partition'] = 'false' - args['raw'] = 'false' - if not image_type in (ImageType.KERNEL, ImageType.RAMDISK): - args['add_partition'] = 'true' - if image_type == ImageType.DISK_RAW: - args['raw'] = 'true' - task = session.async_call_plugin('objectstore', fn, args) - vdi_uuid = None - filename = None - if image_type in (ImageType.KERNEL, ImageType.RAMDISK): - filename = session.wait_for_task(task, instance_id) - else: - vdi_uuid = session.wait_for_task(task, instance_id) - return [dict(vdi_type=ImageType.to_string(image_type), - vdi_uuid=vdi_uuid, - file=filename)] - @classmethod def determine_is_pv(cls, session, instance_id, vdi_ref, disk_image_type, os_type): @@ -669,68 +625,26 @@ class VMHelper(HelperBase): Determine whether the VM will use a paravirtualized kernel or if it will use hardware virtualization. - 1. Objectstore (any image type): - We use plugin to figure out whether the VDI uses PV - - 2. Glance (VHD): then we use `os_type`, raise if not set - - 3. Glance (DISK_RAW): use Pygrub to figure out if pv kernel is - available - - 4. Glance (DISK): pv is assumed - """ - if FLAGS.image_service == 'nova.image.glance.GlanceImageService': - # 2, 3, 4: Glance - return cls._determine_is_pv_glance( - session, vdi_ref, disk_image_type, os_type) - else: - # 1. Objecstore - return cls._determine_is_pv_objectstore(session, instance_id, - vdi_ref) - - @classmethod - def _determine_is_pv_objectstore(cls, session, instance_id, vdi_ref): - LOG.debug(_("Looking up vdi %s for PV kernel"), vdi_ref) - fn = "is_vdi_pv" - args = {} - args['vdi-ref'] = vdi_ref - task = session.async_call_plugin('objectstore', fn, args) - pv_str = session.wait_for_task(task, instance_id) - pv = None - if pv_str.lower() == 'true': - pv = True - elif pv_str.lower() == 'false': - pv = False - LOG.debug(_("PV Kernel in VDI:%s"), pv) - return pv - - @classmethod - def _determine_is_pv_glance(cls, session, vdi_ref, disk_image_type, - os_type): - """ - For a Glance image, determine if we need paravirtualization. - - The relevant scenarios are: - 2. Glance (VHD): then we use `os_type`, raise if not set + 1. Glance (VHD): then we use `os_type`, raise if not set - 3. Glance (DISK_RAW): use Pygrub to figure out if pv kernel is + 2. Glance (DISK_RAW): use Pygrub to figure out if pv kernel is available - 4. Glance (DISK): pv is assumed + 3. Glance (DISK): pv is assumed """ LOG.debug(_("Looking up vdi %s for PV kernel"), vdi_ref) if disk_image_type == ImageType.DISK_VHD: - # 2. VHD + # 1. VHD if os_type == 'windows': is_pv = False else: is_pv = True elif disk_image_type == ImageType.DISK_RAW: - # 3. RAW + # 2. RAW is_pv = with_vdi_attached_here(session, vdi_ref, True, _is_vdi_pv) elif disk_image_type == ImageType.DISK: - # 4. Disk + # 3. Disk is_pv = True else: raise exception.Error(_("Unknown image format %(disk_image_type)s") -- cgit From bde063a98dad2ce75be1016b39a2c3f08759d4f6 Mon Sep 17 00:00:00 2001 From: William Wolf Date: Tue, 26 Jul 2011 11:30:58 -0400 Subject: got rid of string comparisons in serializer tests --- nova/api/openstack/versions.py | 9 +++++ nova/tests/api/openstack/test_versions.py | 67 ++++++++----------------------- 2 files changed, 26 insertions(+), 50 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/versions.py b/nova/api/openstack/versions.py index 03b99f342..40c187607 100644 --- a/nova/api/openstack/versions.py +++ b/nova/api/openstack/versions.py @@ -205,6 +205,7 @@ class VersionsXMLSerializer(wsgi.XMLDictSerializer): # in the base class (XMLDictSerializer), which I plan to do in # another branch def to_xml_string(self, node, has_atom=False): + print "TOXML" self._add_xmlns(node, has_atom) return node.toxml(encoding='UTF-8') @@ -273,6 +274,14 @@ class VersionsXMLSerializer(wsgi.XMLDictSerializer): class VersionsAtomSerializer(wsgi.XMLDictSerializer): + #TODO(wwolf): this is temporary until we get rid of toprettyxml + # in the base class (XMLDictSerializer), which I plan to do in + # another branch + def to_xml_string(self, node, has_atom=False): + print "TOXML" + self._add_xmlns(node, has_atom) + return node.toxml(encoding='UTF-8') + def __init__(self, metadata=None, xmlns=None): self.metadata = metadata or {} if not xmlns: diff --git a/nova/tests/api/openstack/test_versions.py b/nova/tests/api/openstack/test_versions.py index 448a69c07..b8c985e4d 100644 --- a/nova/tests/api/openstack/test_versions.py +++ b/nova/tests/api/openstack/test_versions.py @@ -531,47 +531,9 @@ class VersionsTest(test.TestCase): ] } - expected = """ - - - Available API Versions - - - 2011-07-20T11:40:00Z - - - http://test/ - - - - Rackspace - - - http://www.rackspace.com/ - - - - - - http://test/2.9.8 - - - Version 2.9.8 - - - 2011-07-20T11:40:00Z - - - - Version 2.9.8 CURRENT (2011-07-20T11:40:00Z) - - - """.replace(" ", "").replace("\n", "") - serializer = versions.VersionsAtomSerializer() response = serializer.index(versions_data) - response = response.replace(" ", "").replace("\n", "") - self.assertEqual(expected, response) + print response root = xml.etree.ElementTree.XML(response) self.assertEqual(root.tag.split('}')[1], "feed") @@ -608,18 +570,23 @@ class VersionsTest(test.TestCase): entry_children = list(entry) entry_id = entry_children[0] entry_title = entry_children[1] - entry_updated = entry_children[1] - entry_link = entry_children[1] - entry_content = entry_children[1] + entry_updated = entry_children[2] + entry_link = entry_children[3] + entry_content = entry_children[4] + self.assertEqual(entry_id.tag.split('}')[1], "id") self.assertEqual(entry_id.text, "http://test/2.9.8") - - - - #self.assertEqual(media_types.tag.split('}')[1], 'media-types') - #for i, media_node in enumerate(media_type_nodes): - #self.assertEqual(media_node.tag.split('}')[1], 'media-type') - #for key, val in version_data['version']['media-types'][i].items(): - #self.assertEqual(val, media_node.get(key)) + self.assertEqual(entry_title.tag.split('}')[1], "title") + self.assertEqual(entry_title.get('type'), "text") + self.assertEqual(entry_title.text, "Version 2.9.8") + self.assertEqual(entry_updated.tag.split('}')[1], "updated") + self.assertEqual(entry_updated.text, "2011-07-20T11:40:00Z") + self.assertEqual(entry_link.tag.split('}')[1], "link") + self.assertEqual(entry_link.get('href'), "http://test/2.9.8") + self.assertEqual(entry_link.get('rel'), "self") + self.assertEqual(entry_content.tag.split('}')[1], "content") + self.assertEqual(entry_content.get('type'), "text") + self.assertEqual(entry_content.text, + "Version 2.9.8 CURRENT (2011-07-20T11:40:00Z)") def test_version_detail_atom_serializer(self): versions_data = { -- cgit From 26f980c955e357df3685bcccda005a3008f86afb Mon Sep 17 00:00:00 2001 From: William Wolf Date: Tue, 26 Jul 2011 11:31:16 -0400 Subject: got rid of some prints --- nova/api/openstack/versions.py | 2 -- 1 file changed, 2 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/versions.py b/nova/api/openstack/versions.py index 40c187607..f389933b9 100644 --- a/nova/api/openstack/versions.py +++ b/nova/api/openstack/versions.py @@ -205,7 +205,6 @@ class VersionsXMLSerializer(wsgi.XMLDictSerializer): # in the base class (XMLDictSerializer), which I plan to do in # another branch def to_xml_string(self, node, has_atom=False): - print "TOXML" self._add_xmlns(node, has_atom) return node.toxml(encoding='UTF-8') @@ -278,7 +277,6 @@ class VersionsAtomSerializer(wsgi.XMLDictSerializer): # in the base class (XMLDictSerializer), which I plan to do in # another branch def to_xml_string(self, node, has_atom=False): - print "TOXML" self._add_xmlns(node, has_atom) return node.toxml(encoding='UTF-8') -- cgit From d3e557ae0d49ea8d4a1cd50abbada6e8c1c4a7fe Mon Sep 17 00:00:00 2001 From: William Wolf Date: Tue, 26 Jul 2011 11:34:19 -0400 Subject: atom test updates --- nova/tests/api/openstack/test_versions.py | 1 + 1 file changed, 1 insertion(+) (limited to 'nova') diff --git a/nova/tests/api/openstack/test_versions.py b/nova/tests/api/openstack/test_versions.py index b8c985e4d..59dc9fb92 100644 --- a/nova/tests/api/openstack/test_versions.py +++ b/nova/tests/api/openstack/test_versions.py @@ -548,6 +548,7 @@ class VersionsTest(test.TestCase): link = children[4] entry = children[5] + self.assertEqual(root.tag.split('}')[1], 'feed') self.assertEqual(title.tag.split('}')[1], 'title') self.assertEqual(title.text, 'Available API Versions') self.assertEqual(updated.tag.split('}')[1], 'updated') -- cgit From 3b6208f44c079323efa290dfeb68a4afbdfb3349 Mon Sep 17 00:00:00 2001 From: William Wolf Date: Tue, 26 Jul 2011 11:43:57 -0400 Subject: got rid of more xml string comparisons --- nova/tests/api/openstack/test_versions.py | 90 ++++++++++++++++++++----------- 1 file changed, 60 insertions(+), 30 deletions(-) (limited to 'nova') diff --git a/nova/tests/api/openstack/test_versions.py b/nova/tests/api/openstack/test_versions.py index 59dc9fb92..992efbf1c 100644 --- a/nova/tests/api/openstack/test_versions.py +++ b/nova/tests/api/openstack/test_versions.py @@ -548,7 +548,6 @@ class VersionsTest(test.TestCase): link = children[4] entry = children[5] - self.assertEqual(root.tag.split('}')[1], 'feed') self.assertEqual(title.tag.split('}')[1], 'title') self.assertEqual(title.text, 'Available API Versions') self.assertEqual(updated.tag.split('}')[1], 'updated') @@ -626,34 +625,65 @@ class VersionsTest(test.TestCase): }, } - expected = """ - - About This Version - 2011-01-21T11:33:21Z - http://servers.api.openstack.org/v1.1/ - - Rackspace - http://www.rackspace.com/ - - - - http://servers.api.openstack.org/v1.1/ - Version v1.1 - 2011-01-21T11:33:21Z - - - - - Version v1.1 CURRENT (2011-01-21T11:33:21Z) - - - """.replace(" ", "").replace("\n", "") - serializer = versions.VersionsAtomSerializer() response = serializer.detail(versions_data) - response = response.replace(" ", "").replace("\n", "") - self.assertEqual(expected, response) + + root = xml.etree.ElementTree.XML(response) + self.assertEqual(root.tag.split('}')[1], "feed") + self.assertEqual(root.tag.split('}')[0].strip('{'), + "http://www.w3.org/2005/Atom") + + children = list(root) + title = children[0] + updated = children[1] + id = children[2] + author = children[3] + link = children[4] + entry = children[5] + + self.assertEqual(root.tag.split('}')[1], 'feed') + self.assertEqual(title.tag.split('}')[1], 'title') + self.assertEqual(title.text, 'About This Version') + self.assertEqual(updated.tag.split('}')[1], 'updated') + self.assertEqual(updated.text, '2011-01-21T11:33:21Z') + self.assertEqual(id.tag.split('}')[1], 'id') + self.assertEqual(id.text, 'http://servers.api.openstack.org/v1.1/') + + self.assertEqual(author.tag.split('}')[1], 'author') + author_name = list(author)[0] + author_uri = list(author)[1] + self.assertEqual(author_name.tag.split('}')[1], 'name') + self.assertEqual(author_name.text, 'Rackspace') + self.assertEqual(author_uri.tag.split('}')[1], 'uri') + self.assertEqual(author_uri.text, 'http://www.rackspace.com/') + + self.assertEqual(link.get('href'), + 'http://servers.api.openstack.org/v1.1/') + self.assertEqual(link.get('rel'), 'self') + + self.assertEqual(entry.tag.split('}')[1], 'entry') + entry_children = list(entry) + entry_id = entry_children[0] + entry_title = entry_children[1] + entry_updated = entry_children[2] + entry_links = (entry_children[3], entry_children[4], entry_children[5]) + entry_content = entry_children[6] + + self.assertEqual(entry_id.tag.split('}')[1], "id") + self.assertEqual(entry_id.text, + "http://servers.api.openstack.org/v1.1/") + self.assertEqual(entry_title.tag.split('}')[1], "title") + self.assertEqual(entry_title.get('type'), "text") + self.assertEqual(entry_title.text, "Version v1.1") + self.assertEqual(entry_updated.tag.split('}')[1], "updated") + self.assertEqual(entry_updated.text, "2011-01-21T11:33:21Z") + + for i, link in enumerate(versions_data["version"]["links"]): + self.assertEqual(entry_links[i].tag.split('}')[1], "link") + for key, val in versions_data["version"]["links"][i].items(): + self.assertEqual(entry_links[i].get(key), val) + + self.assertEqual(entry_content.tag.split('}')[1], "content") + self.assertEqual(entry_content.get('type'), "text") + self.assertEqual(entry_content.text, + "Version v1.1 CURRENT (2011-01-21T11:33:21Z)") -- cgit From 58eef7695eb5539f75e358b2f55b50063551a44d Mon Sep 17 00:00:00 2001 From: William Wolf Date: Tue, 26 Jul 2011 11:44:19 -0400 Subject: got rid of print --- nova/tests/api/openstack/test_versions.py | 1 - 1 file changed, 1 deletion(-) (limited to 'nova') diff --git a/nova/tests/api/openstack/test_versions.py b/nova/tests/api/openstack/test_versions.py index 992efbf1c..d1ec0b84a 100644 --- a/nova/tests/api/openstack/test_versions.py +++ b/nova/tests/api/openstack/test_versions.py @@ -533,7 +533,6 @@ class VersionsTest(test.TestCase): serializer = versions.VersionsAtomSerializer() response = serializer.index(versions_data) - print response root = xml.etree.ElementTree.XML(response) self.assertEqual(root.tag.split('}')[1], "feed") -- cgit From 241a926ed682cb6154ff8f37c4940e7b5885b6fe Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Tue, 26 Jul 2011 16:13:09 -0400 Subject: moved v1.1 image creation from /images to /servers//action --- nova/api/openstack/images.py | 37 +++++------ nova/api/openstack/servers.py | 93 +++++++++++++++++++++++++-- nova/tests/api/openstack/test_images.py | 107 ------------------------------- nova/tests/api/openstack/test_servers.py | 66 +++++++++++++++++++ 4 files changed, 172 insertions(+), 131 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index 30e4fd389..517a51662 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -98,6 +98,20 @@ class Controller(object): self._image_service.delete(context, id) return webob.exc.HTTPNoContent() + def get_builder(self, request): + """Indicates that you must use a Controller subclass.""" + raise NotImplementedError() + + def _server_id_from_req(self, req, data): + raise NotImplementedError() + + def _get_extra_properties(self, req, data): + return {} + + +class ControllerV10(Controller): + """Version 1.0 specific controller logic.""" + def create(self, req, body): """Snapshot or backup a server instance and save the image. @@ -157,20 +171,6 @@ class Controller(object): return dict(image=self.get_builder(req).build(image, detail=True)) - def get_builder(self, request): - """Indicates that you must use a Controller subclass.""" - raise NotImplementedError() - - def _server_id_from_req(self, req, data): - raise NotImplementedError() - - def _get_extra_properties(self, req, data): - return {} - - -class ControllerV10(Controller): - """Version 1.0 specific controller logic.""" - def get_builder(self, request): """Property to get the ViewBuilder class we need to use.""" base_url = request.application_url @@ -278,6 +278,9 @@ class ControllerV11(Controller): server_ref) return {'instance_ref': server_ref} + def create(self, *args, **kwargs): + raise webob.exc.HTTPMethodNotAllowed() + class ImageXMLSerializer(wsgi.XMLDictSerializer): @@ -369,12 +372,6 @@ class ImageXMLSerializer(wsgi.XMLDictSerializer): image_dict['image']) return self.to_xml_string(node, True) - def create(self, image_dict): - xml_doc = minidom.Document() - node = self._image_to_xml_detailed(xml_doc, - image_dict['image']) - return self.to_xml_string(node, True) - def create_resource(version='1.0'): controller = { diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index d7cabb067..5371e62c9 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -14,6 +14,7 @@ # under the License. import base64 +import os import traceback from webob import exc @@ -155,20 +156,26 @@ class Controller(object): """Multi-purpose method used to reboot, rebuild, or resize a server""" - actions = { + self.actions = { 'changePassword': self._action_change_password, 'reboot': self._action_reboot, 'resize': self._action_resize, 'confirmResize': self._action_confirm_resize, 'revertResize': self._action_revert_resize, 'rebuild': self._action_rebuild, - 'migrate': self._action_migrate} + 'migrate': self._action_migrate, + 'createImage': self._action_create_image, + } - for key in actions.keys(): + + for key in self.actions.keys(): if key in body: - return actions[key](body, req, id) + return self.actions[key](body, req, id) raise exc.HTTPNotImplemented() + def _action_create_image(self, input_dict, req, id): + return exc.HTTPNotImplemented() + def _action_change_password(self, input_dict, req, id): return exc.HTTPNotImplemented() @@ -585,6 +592,80 @@ class ControllerV11(Controller): return webob.Response(status_int=202) + def _action_create_image(self, input_dict, req, instance_id): + """Snapshot or backup a server instance and save the image. + + Images now have an `image_type` associated with them, which can be + 'snapshot' or the backup type, like 'daily' or 'weekly'. + + If the image_type is backup-like, then the rotation factor can be + included and that will cause the oldest backups that exceed the + rotation factor to be deleted. + + """ + entity = input_dict.get('createImage', {}) + + def get_param(param): + try: + return entity[param] + except KeyError: + msg = _("Missing required param: %s") % param + raise webob.exc.HTTPBadRequest(explanation=msg) + + context = req.environ['nova.context'] + + image_name = get_param("name") + image_type = entity.get("image_type", "snapshot") + + # preserve link to server in image properties + server_ref = os.path.join(req.application_url, + 'servers', + str(instance_id)) + props = {'instance_ref': server_ref} + + metadata = entity.get('metadata', {}) + try: + props.update(metadata) + except ValueError: + msg = _("Invalid metadata") + raise webob.exc.HTTPBadRequest(explanation=msg) + + if image_type == "snapshot": + image = self.compute_api.snapshot(context, + instance_id, + image_name, + extra_properties=props) + + elif image_type == "backup": + # NOTE(sirp): Unlike snapshot, backup is not a customer facing + # API call; rather, it's used by the internal backup scheduler + if not FLAGS.allow_admin_api: + msg = _("Admin API Required") + raise webob.exc.HTTPBadRequest(explanation=msg) + + backup_type = get_param("backup_type") + rotation = int(get_param("rotation")) + + image = self.compute_api.backup(context, + instance_id, + image_name, + backup_type, + rotation, + extra_properties=props) + else: + msg = _("Invalid image_type '%s'") % image_type + raise webob.exc.HTTPBadRequest(explanation=msg) + + + # build location of newly-created image entity + image_ref = os.path.join(req.application_url, + 'images', + str(image['id'])) + + resp = webob.Response(status_int=202) + resp.headers['Location'] = image_ref + return resp + def get_default_xmlns(self, req): return common.XML_NS_V11 @@ -593,6 +674,10 @@ class ControllerV11(Controller): return self.helper._get_server_admin_password_new_style(server) + + + + class HeadersSerializer(wsgi.ResponseHeadersSerializer): def delete(self, response, data): diff --git a/nova/tests/api/openstack/test_images.py b/nova/tests/api/openstack/test_images.py index 87a695dde..f4e63b48b 100644 --- a/nova/tests/api/openstack/test_images.py +++ b/nova/tests/api/openstack/test_images.py @@ -1131,113 +1131,6 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): response = req.get_response(fakes.wsgi_app()) self.assertEqual(400, response.status_int) - def test_create_image_v1_1(self): - - body = dict(image=dict(serverRef='123', name='Snapshot 1')) - req = webob.Request.blank('/v1.1/images') - req.method = 'POST' - req.body = json.dumps(body) - req.headers["content-type"] = "application/json" - response = req.get_response(fakes.wsgi_app()) - self.assertEqual(200, response.status_int) - - def test_create_image_v1_1_actual_server_ref(self): - - serverRef = 'http://localhost/v1.1/servers/1' - serverBookmark = 'http://localhost/servers/1' - body = dict(image=dict(serverRef=serverRef, name='Backup 1')) - req = webob.Request.blank('/v1.1/images') - req.method = 'POST' - req.body = json.dumps(body) - req.headers["content-type"] = "application/json" - response = req.get_response(fakes.wsgi_app()) - self.assertEqual(200, response.status_int) - result = json.loads(response.body) - expected = { - 'id': 1, - 'links': [ - { - 'rel': 'self', - 'href': serverRef, - }, - { - 'rel': 'bookmark', - 'href': serverBookmark, - }, - ] - } - self.assertEqual(result['image']['server'], expected) - - def test_create_image_v1_1_actual_server_ref_port(self): - - serverRef = 'http://localhost:8774/v1.1/servers/1' - serverBookmark = 'http://localhost:8774/servers/1' - body = dict(image=dict(serverRef=serverRef, name='Backup 1')) - req = webob.Request.blank('/v1.1/images') - req.method = 'POST' - req.body = json.dumps(body) - req.headers["content-type"] = "application/json" - response = req.get_response(fakes.wsgi_app()) - self.assertEqual(200, response.status_int) - result = json.loads(response.body) - expected = { - 'id': 1, - 'links': [ - { - 'rel': 'self', - 'href': serverRef, - }, - { - 'rel': 'bookmark', - 'href': serverBookmark, - }, - ] - } - self.assertEqual(result['image']['server'], expected) - - def test_create_image_v1_1_server_ref_bad_hostname(self): - - serverRef = 'http://asdf/v1.1/servers/1' - body = dict(image=dict(serverRef=serverRef, name='Backup 1')) - req = webob.Request.blank('/v1.1/images') - req.method = 'POST' - req.body = json.dumps(body) - req.headers["content-type"] = "application/json" - response = req.get_response(fakes.wsgi_app()) - self.assertEqual(400, response.status_int) - - def test_create_image_v1_1_no_server_ref(self): - - body = dict(image=dict(name='Snapshot 1')) - req = webob.Request.blank('/v1.1/images') - req.method = 'POST' - req.body = json.dumps(body) - req.headers["content-type"] = "application/json" - response = req.get_response(fakes.wsgi_app()) - self.assertEqual(400, response.status_int) - - def test_create_image_v1_1_server_ref_missing_version(self): - - serverRef = 'http://localhost/servers/1' - body = dict(image=dict(serverRef=serverRef, name='Backup 1')) - req = webob.Request.blank('/v1.1/images') - req.method = 'POST' - req.body = json.dumps(body) - req.headers["content-type"] = "application/json" - response = req.get_response(fakes.wsgi_app()) - self.assertEqual(400, response.status_int) - - def test_create_image_v1_1_server_ref_missing_id(self): - - serverRef = 'http://localhost/v1.1/servers' - body = dict(image=dict(serverRef=serverRef, name='Backup 1')) - req = webob.Request.blank('/v1.1/images') - req.method = 'POST' - req.body = json.dumps(body) - req.headers["content-type"] = "application/json" - response = req.get_response(fakes.wsgi_app()) - self.assertEqual(400, response.status_int) - @classmethod def _make_image_fixtures(cls): image_id = 123 diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 4ca79434f..6e17bb191 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -263,6 +263,16 @@ class ServersTest(test.TestCase): self.stubs.Set(nova.compute.API, 'resume', fake_compute_api) self.stubs.Set(nova.compute.API, "get_diagnostics", fake_compute_api) self.stubs.Set(nova.compute.API, "get_actions", fake_compute_api) + + fakes.stub_out_glance(self.stubs) + fakes.stub_out_compute_api_snapshot(self.stubs) + service_class = 'nova.image.glance.GlanceImageService' + self.service = utils.import_object(service_class) + self.context = context.RequestContext(1, None) + self.service.delete_all() + self.sent_to_glance = {} + fakes.stub_out_glance_add_image(self.stubs, self.sent_to_glance) + self.allow_admin = FLAGS.allow_admin_api self.webreq = common.webob_factory('/v1.0/servers') @@ -2234,6 +2244,62 @@ class ServersTest(test.TestCase): res_dict = json.loads(res.body) self.assertEqual(res_dict['server']['status'], 'SHUTOFF') + def test_create_image_v1_1(self): + body = { + 'createImage': { + 'name': 'Snapshot 1', + }, + } + req = webob.Request.blank('/v1.1/servers/1/action') + req.method = 'POST' + req.body = json.dumps(body) + req.headers["content-type"] = "application/json" + response = req.get_response(fakes.wsgi_app()) + self.assertEqual(202, response.status_int) + location = response.headers['Location'] + self.assertEqual('http://localhost/v1.1/images/123', location) + + def test_create_image_v1_1_with_metadata(self): + body = { + 'createImage': { + 'name': 'Snapshot 1', + 'metadata': {'key': 'asdf'}, + }, + } + req = webob.Request.blank('/v1.1/servers/1/action') + req.method = 'POST' + req.body = json.dumps(body) + req.headers["content-type"] = "application/json" + response = req.get_response(fakes.wsgi_app()) + self.assertEqual(202, response.status_int) + location = response.headers['Location'] + self.assertEqual('http://localhost/v1.1/images/123', location) + + def test_create_image_v1_1_no_name(self): + body = { + 'createImage': {}, + } + req = webob.Request.blank('/v1.1/servers/1/action') + req.method = 'POST' + req.body = json.dumps(body) + req.headers["content-type"] = "application/json" + response = req.get_response(fakes.wsgi_app()) + self.assertEqual(400, response.status_int) + + def test_create_image_v1_1_bad_metadata(self): + body = { + 'createImage': { + 'name': 'geoff', + 'metadata': 'henry', + }, + } + req = webob.Request.blank('/v1.1/servers/1/action') + req.method = 'POST' + req.body = json.dumps(body) + req.headers["content-type"] = "application/json" + response = req.get_response(fakes.wsgi_app()) + self.assertEqual(400, response.status_int) + class TestServerCreateRequestXMLDeserializer(unittest.TestCase): -- cgit From c8b1a357c9bd5fe4bc54e6472f9667123d91c02a Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Tue, 26 Jul 2011 16:53:00 -0400 Subject: adding xml deserialization for createImage action --- nova/api/openstack/create_instance_helper.py | 24 +++++++++ nova/tests/api/openstack/test_images.py | 73 ---------------------------- nova/tests/api/openstack/test_servers.py | 43 +++++++++++++++- 3 files changed, 66 insertions(+), 74 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index f8317565e..eb556ae8b 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -293,6 +293,30 @@ class ServerXMLDeserializer(wsgi.MetadataXMLDeserializer): and personality attributes """ + def action(self, string): + dom = minidom.parseString(string) + action_node = dom.childNodes[0] + action_name = action_node.tagName + + action_deserializer = { + 'createImage': self._action_create_image, + }.get(action_name, self.default) + + action_data = action_deserializer(action_node) + + return {'body': {action_name: action_data}} + + def _action_create_image(self, node): + data = {} + attributes = ['name', 'image_type', 'backup_type', 'rotation'] + for attribute in attributes: + value = node.getAttribute(attribute) + if value: + data[attribute] = value + metadata_node = self.find_first_child_named(node, 'metadata') + data['metadata'] = self.extract_metadata(metadata_node) + return data + def create(self, string): """Deserialize an xml-formatted server create request""" dom = minidom.parseString(string) diff --git a/nova/tests/api/openstack/test_images.py b/nova/tests/api/openstack/test_images.py index f4e63b48b..50a6be66c 100644 --- a/nova/tests/api/openstack/test_images.py +++ b/nova/tests/api/openstack/test_images.py @@ -1609,76 +1609,3 @@ class ImageXMLSerializationTest(test.TestCase): """.replace(" ", "") % (locals())) self.assertEqual(expected.toxml(), actual.toxml()) - - def test_create(self): - serializer = images.ImageXMLSerializer() - - fixture = { - 'image': { - 'id': 1, - 'name': 'Image1', - 'created': self.TIMESTAMP, - 'updated': self.TIMESTAMP, - 'status': 'SAVING', - 'progress': 80, - 'server': { - 'id': 1, - 'links': [ - { - 'href': self.SERVER_HREF, - 'rel': 'self', - }, - { - 'href': self.SERVER_BOOKMARK, - 'rel': 'bookmark', - }, - ], - }, - 'metadata': { - 'key1': 'value1', - }, - 'links': [ - { - 'href': self.IMAGE_HREF % 1, - 'rel': 'self', - }, - { - 'href': self.IMAGE_BOOKMARK % 1, - 'rel': 'bookmark', - }, - ], - }, - } - - output = serializer.serialize(fixture, 'create') - actual = minidom.parseString(output.replace(" ", "")) - - expected_server_href = self.SERVER_HREF - expected_server_bookmark = self.SERVER_BOOKMARK - expected_href = self.IMAGE_HREF % 1 - expected_bookmark = self.IMAGE_BOOKMARK % 1 - expected_now = self.TIMESTAMP - expected = minidom.parseString(""" - - - - - - - - value1 - - - - - - """.replace(" ", "") % (locals())) - - self.assertEqual(expected.toxml(), actual.toxml()) diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 6e17bb191..0ade0b19e 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -2301,6 +2301,47 @@ class ServersTest(test.TestCase): self.assertEqual(400, response.status_int) +class TestServerActionXMLDeserializer(test.TestCase): + + def setUp(self): + self.deserializer = create_instance_helper.ServerXMLDeserializer() + + def tearDown(self): + pass + + def test_create_image(self): + serial_request = """ +""" + request = self.deserializer.deserialize(serial_request, 'action') + expected = { + "createImage": { + "name": "new-server-test", + "metadata": {}, + }, + } + self.assertEquals(request['body'], expected) + + def test_create_image_with_metadata(self): + serial_request = """ + + + value1 + +""" + request = self.deserializer.deserialize(serial_request, 'action') + expected = { + "createImage": { + "name": "new-server-test", + "metadata": {"key1": "value1"}, + }, + } + self.assertEquals(request['body'], expected) + + + + class TestServerCreateRequestXMLDeserializer(unittest.TestCase): def setUp(self): @@ -2595,7 +2636,7 @@ b25zLiINCg0KLVJpY2hhcmQgQmFjaA==""", "http://localhost:8774/v1.1/images/1") -class TextAddressesXMLSerialization(test.TestCase): +class TestAddressesXMLSerialization(test.TestCase): serializer = nova.api.openstack.ips.IPXMLSerializer() -- cgit From b18754473785611112ae54523677da83dff24075 Mon Sep 17 00:00:00 2001 From: "Kevin L. Mitchell" Date: Tue, 26 Jul 2011 20:58:33 +0000 Subject: First pass at converting this stuff--pass context down into vmops. Still need to fix unit tests and actually use auth_token from the context... --- nova/compute/manager.py | 9 +++++---- nova/virt/xenapi/vm_utils.py | 22 +++++++++++----------- nova/virt/xenapi/vmops.py | 29 +++++++++++++++-------------- 3 files changed, 31 insertions(+), 29 deletions(-) (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 31627fe3b..667d231ae 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -326,7 +326,7 @@ class ComputeManager(manager.SchedulerDependentManager): self._update_state(context, instance_id, power_state.BUILDING) try: - self.driver.spawn(instance, network_info, bd_mapping) + self.driver.spawn(context, instance, network_info, bd_mapping) except Exception as ex: # pylint: disable=W0702 msg = _("Instance '%(instance_id)s' failed to spawn. Is " "virtualization enabled in the BIOS? Details: " @@ -430,7 +430,7 @@ class ComputeManager(manager.SchedulerDependentManager): image_ref = kwargs.get('image_ref') instance_ref.image_ref = image_ref instance_ref.injected_files = kwargs.get('injected_files', []) - self.driver.spawn(instance_ref, network_info) + self.driver.spawn(context, instance_ref, network_info) self._update_image_ref(context, instance_id, image_ref) self._update_launched_at(context, instance_id) @@ -498,7 +498,7 @@ class ComputeManager(manager.SchedulerDependentManager): 'instance: %(instance_id)s (state: %(state)s ' 'expected: %(running)s)') % locals()) - self.driver.snapshot(instance_ref, image_id) + self.driver.snapshot(context, instance_ref, image_id) if image_type == 'snapshot': if rotation: @@ -855,7 +855,8 @@ class ComputeManager(manager.SchedulerDependentManager): instance_ref = self.db.instance_get_by_uuid(context, instance_ref.uuid) network_info = self._get_instance_nw_info(context, instance_ref) - self.driver.finish_resize(instance_ref, disk_info, network_info) + self.driver.finish_resize(context, instance_ref, disk_info, + network_info) self.db.migration_update(context, migration_id, {'status': 'finished', }) diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index 62863c6d8..aa0e4c2df 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -359,7 +359,7 @@ class VMHelper(HelperBase): return os.path.join(FLAGS.xenapi_sr_base_path, sr_uuid) @classmethod - def upload_image(cls, session, instance, vdi_uuids, image_id): + def upload_image(cls, context, session, instance, vdi_uuids, image_id): """ Requests that the Glance plugin bundle the specified VDIs and push them into Glance using the specified human-friendly name. """ @@ -384,7 +384,7 @@ class VMHelper(HelperBase): session.wait_for_task(task, instance.id) @classmethod - def fetch_image(cls, session, instance_id, image, user, project, + def fetch_image(cls, context, session, instance_id, image, user, project, image_type): """ image_type is interpreted as an ImageType instance @@ -399,16 +399,16 @@ class VMHelper(HelperBase): access = AuthManager().get_access_key(user, project) if FLAGS.xenapi_image_service == 'glance': - return cls._fetch_image_glance(session, instance_id, image, - access, image_type) + return cls._fetch_image_glance(context, session, instance_id, + image, access, image_type) else: return cls._fetch_image_objectstore(session, instance_id, image, access, user.secret, image_type) @classmethod - def _fetch_image_glance_vhd(cls, session, instance_id, image, access, - image_type): + def _fetch_image_glance_vhd(cls, context, session, instance_id, image, + access, image_type): """Tell glance to download an image and put the VHDs into the SR Returns: A list of dictionaries that describe VDIs @@ -455,8 +455,8 @@ class VMHelper(HelperBase): return vdis @classmethod - def _fetch_image_glance_disk(cls, session, instance_id, image, access, - image_type): + def _fetch_image_glance_disk(cls, context, session, instance_id, image, + access, image_type): """Fetch the image from Glance NOTE: @@ -589,7 +589,7 @@ class VMHelper(HelperBase): return image_type @classmethod - def _fetch_image_glance(cls, session, instance_id, image, access, + def _fetch_image_glance(cls, context, session, instance_id, image, access, image_type): """Fetch image from glance based on image type. @@ -597,10 +597,10 @@ class VMHelper(HelperBase): A list of dictionaries that describe VDIs, otherwise """ if image_type == ImageType.DISK_VHD: - return cls._fetch_image_glance_vhd( + return cls._fetch_image_glance_vhd(context, session, instance_id, image, access, image_type) else: - return cls._fetch_image_glance_disk( + return cls._fetch_image_glance_disk(context, session, instance_id, image, access, image_type) @classmethod diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 0473abb97..1c6604836 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -114,10 +114,10 @@ class VMOps(object): vm_ref = VMHelper.lookup(self._session, instance.name) self._start(instance, vm_ref) - def finish_resize(self, instance, disk_info, network_info): + def finish_resize(self, context, instance, disk_info, network_info): vdi_uuid = self.link_disks(instance, disk_info['base_copy'], disk_info['cow']) - vm_ref = self._create_vm(instance, + vm_ref = self._create_vm(context, instance, [dict(vdi_type='os', vdi_uuid=vdi_uuid)], network_info) self.resize_instance(instance, vdi_uuid) @@ -133,20 +133,20 @@ class VMOps(object): LOG.debug(_("Starting instance %s"), instance.name) self._session.call_xenapi('VM.start', vm_ref, False, False) - def _create_disks(self, instance): + def _create_disks(self, context, instance): user = AuthManager().get_user(instance.user_id) project = AuthManager().get_project(instance.project_id) disk_image_type = VMHelper.determine_disk_image_type(instance) - vdis = VMHelper.fetch_image(self._session, + vdis = VMHelper.fetch_image(context, self._session, instance.id, instance.image_ref, user, project, disk_image_type) return vdis - def spawn(self, instance, network_info): + def spawn(self, context, instance, network_info): vdis = None try: - vdis = self._create_disks(instance) - vm_ref = self._create_vm(instance, vdis, network_info) + vdis = self._create_disks(context, instance) + vm_ref = self._create_vm(context, instance, vdis, network_info) self._spawn(instance, vm_ref) except (self.XenAPI.Failure, OSError, IOError) as spawn_error: LOG.exception(_("instance %s: Failed to spawn"), @@ -160,7 +160,7 @@ class VMOps(object): """Spawn a rescue instance.""" self.spawn(instance) - def _create_vm(self, instance, vdis, network_info): + def _create_vm(self, context, instance, vdis, network_info): """Create VM instance.""" instance_name = instance.name vm_ref = VMHelper.lookup(self._session, instance_name) @@ -184,12 +184,12 @@ class VMOps(object): ramdisk = None try: if instance.kernel_id: - kernel = VMHelper.fetch_image(self._session, instance.id, - instance.kernel_id, user, project, + kernel = VMHelper.fetch_image(context, self._session, + instance.id, instance.kernel_id, user, project, ImageType.KERNEL)[0] if instance.ramdisk_id: - ramdisk = VMHelper.fetch_image(self._session, instance.id, - instance.ramdisk_id, user, project, + ramdisk = VMHelper.fetch_image(context, self._session, + instance.id, instance.ramdisk_id, user, project, ImageType.RAMDISK)[0] # Create the VM ref and attach the first disk first_vdi_ref = self._session.call_xenapi('VDI.get_by_uuid', @@ -440,9 +440,10 @@ class VMOps(object): vm, "start") - def snapshot(self, instance, image_id): + def snapshot(self, context, instance, image_id): """Create snapshot from a running VM instance. + :param context: request context :param instance: instance to be snapshotted :param image_id: id of image to upload to @@ -467,7 +468,7 @@ class VMOps(object): try: template_vm_ref, template_vdi_uuids = self._get_snapshot(instance) # call plugin to ship snapshot off to glance - VMHelper.upload_image( + VMHelper.upload_image(context, self._session, instance, template_vdi_uuids, image_id) finally: if template_vm_ref: -- cgit From 84909f4a7733dde453afcc5cc540854ac1bc458c Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Tue, 26 Jul 2011 17:00:10 -0400 Subject: pep8 --- nova/api/openstack/servers.py | 9 +-------- nova/tests/api/openstack/test_servers.py | 2 -- 2 files changed, 1 insertion(+), 10 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 7a900cb54..0cc81009b 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -153,8 +153,7 @@ class Controller(object): @scheduler_api.redirect_handler def action(self, req, id, body): - """Multi-purpose method used to reboot, rebuild, or - resize a server""" + """Multi-purpose method used to take actions on a server""" self.actions = { 'changePassword': self._action_change_password, @@ -167,7 +166,6 @@ class Controller(object): 'createImage': self._action_create_image, } - for key in self.actions.keys(): if key in body: return self.actions[key](body, req, id) @@ -665,7 +663,6 @@ class ControllerV11(Controller): msg = _("Invalid image_type '%s'") % image_type raise webob.exc.HTTPBadRequest(explanation=msg) - # build location of newly-created image entity image_ref = os.path.join(req.application_url, 'images', @@ -683,10 +680,6 @@ class ControllerV11(Controller): return self.helper._get_server_admin_password_new_style(server) - - - - class HeadersSerializer(wsgi.ResponseHeadersSerializer): def delete(self, response, data): diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index d87edfedf..9d2c7b73f 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -2344,8 +2344,6 @@ class TestServerActionXMLDeserializer(test.TestCase): self.assertEquals(request['body'], expected) - - class TestServerCreateRequestXMLDeserializerV10(unittest.TestCase): def setUp(self): -- cgit From e14754bbdbacaf6943c4061e3488f2580acd26ad Mon Sep 17 00:00:00 2001 From: William Wolf Date: Tue, 26 Jul 2011 17:51:46 -0400 Subject: initial working 300 multiple choice stuff --- nova/api/openstack/__init__.py | 2 +- nova/api/openstack/versions.py | 191 +++++++++++++++++------------- nova/api/openstack/views/versions.py | 8 ++ nova/tests/api/openstack/test_versions.py | 59 ++++++++- 4 files changed, 174 insertions(+), 86 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index c10937bd6..0dfad0ef6 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -118,7 +118,7 @@ class APIRouter(base_wsgi.Router): mapper.connect("versions", "/", controller=versions.create_resource(version), - action="detail") + action='show') mapper.resource("console", "consoles", controller=consoles.create_resource(), diff --git a/nova/api/openstack/versions.py b/nova/api/openstack/versions.py index f389933b9..4b567957d 100644 --- a/nova/api/openstack/versions.py +++ b/nova/api/openstack/versions.py @@ -25,6 +25,80 @@ from nova.api.openstack import wsgi ATOM_XMLNS = "http://www.w3.org/2005/Atom" +VERSIONS = { + "v1.0": { + "version" : { + "id": "v1.0", + "status": "DEPRECATED", + "updated": "2011-01-21T11:33:21Z", + "links": [ + { + "rel": "self", + "href": "http://servers.api.openstack.org/v1.0/" + }, + { + "rel": "describedby", + "type": "application/pdf", + "href": "http://docs.rackspacecloud.com/" + "servers/api/v1.0/cs-devguide-20110125.pdf" + }, + { + "rel": "describedby", + "type": "application/vnd.sun.wadl+xml", + "href": "http://docs.rackspacecloud.com/" + "servers/api/v1.0/application.wadl" + }, + ], + "media-types": [ + { + "base" : "application/xml", + "type" : "application/vnd.openstack.compute-v1.0+xml" + }, + { + "base" : "application/json", + "type" : "application/vnd.openstack.compute-v1.0+json" + } + ], + }, + }, + "v1.1": { + "version" : { + "id": "v1.1", + "status": "CURRENT", + "updated": "2011-01-21T11:33:21Z", + "links": [ + { + "rel": "self", + "href": "http://servers.api.openstack.org/v1.1/" + }, + { + "rel": "describedby", + "type": "application/pdf", + "href": "http://docs.rackspacecloud.com/" + "servers/api/v1.1/cs-devguide-20110125.pdf" + }, + { + "rel": "describedby", + "type": "application/vnd.sun.wadl+xml", + "href": "http://docs.rackspacecloud.com/" + "servers/api/v1.1/application.wadl" + }, + ], + "media-types": [ + { + "base" : "application/xml", + "type" : "application/vnd.openstack.compute-v1.1+xml" + }, + { + "base" : "application/json", + "type" : "application/vnd.openstack.compute-v1.1+json" + } + ], + }, + }, +} + + class Versions(wsgi.Resource): @@ -43,11 +117,15 @@ class Versions(wsgi.Resource): } } + headers_serializer = VersionsHeadersSerializer() + body_serializers = { 'application/atom+xml': VersionsAtomSerializer(metadata=metadata), 'application/xml': VersionsXMLSerializer(metadata=metadata), } - serializer = wsgi.ResponseSerializer(body_serializers) + serializer = wsgi.ResponseSerializer( + body_serializers=body_serializers, + headers_serializer=headers_serializer) supported_content_types = ('application/json', 'application/xml', @@ -93,100 +171,41 @@ class Versions(wsgi.Resource): { "id": "v1.1", "status": "CURRENT", - #TODO(wwolf) get correct value for these - "updated": "2011-07-18T11:30:00Z", + "links": [ + { + "rel": "self", + } + ], + "media-types": VERSIONS['v1.1']['version']['media-types'], }, { "id": "v1.0", "status": "DEPRECATED", - #TODO(wwolf) get correct value for these - "updated": "2010-10-09T11:30:00Z", + "links": [ + { + "rel": "self", + } + ], + "media-types": VERSIONS['v1.0']['version']['media-types'], }, ] builder = nova.api.openstack.views.versions.get_view_builder(request) - versions = [builder.build(version) for version in version_objs] - return dict(versions=versions) + choices = [ + builder.build_choices(version, request) + for version in version_objs] + + return dict(choices=choices) class VersionV10(object): - def detail(self, req): - #TODO - return { - "version" : { - "id": "v1.0", - "status": "DEPRECATED", - "updated": "2011-01-21T11:33:21Z", - "links": [ - { - "rel": "self", - "href": "http://servers.api.openstack.org/v1.0/" - }, - { - "rel": "describedby", - "type": "application/pdf", - "href": "http://docs.rackspacecloud.com/" - "servers/api/v1.0/cs-devguide-20110125.pdf" - }, - { - "rel": "describedby", - "type": "application/vnd.sun.wadl+xml", - "href": "http://docs.rackspacecloud.com/" - "servers/api/v1.0/application.wadl" - }, - ], - "media-types": [ - { - "base" : "application/xml", - "type" : "application/vnd.openstack.compute-v1.0+xml" - }, - { - "base" : "application/json", - "type" : "application/vnd.openstack.compute-v1.0+json" - } - ], - }, - } + def show(self, req): + return VERSIONS['v1.0'] class VersionV11(object): - def detail(self, req): - return { - "version" : { - "id": "v1.1", - "status": "CURRENT", - "updated": "2011-01-21T11:33:21Z", - "links": [ - { - "rel": "self", - "href": "http://servers.api.openstack.org/v1.1/" - }, - { - "rel": "describedby", - "type": "application/pdf", - "href": "http://docs.rackspacecloud.com/" - "servers/api/v1.1/cs-devguide-20110125.pdf" - }, - { - "rel": "describedby", - "type": "application/vnd.sun.wadl+xml", - "href": "http://docs.rackspacecloud.com/" - "servers/api/v1.1/application.wadl" - }, - ], - "media-types": [ - { - "base" : "application/xml", - "type" : "application/vnd.openstack.compute-v1.1+xml" - }, - { - "base" : "application/json", - "type" : "application/vnd.openstack.compute-v1.1+json" - } - ], - }, - } - + def show(self, req): + return VERSIONS['v1.1'] class VersionsRequestDeserializer(wsgi.RequestDeserializer): def get_action_args(self, request_environment): @@ -259,7 +278,7 @@ class VersionsXMLSerializer(wsgi.XMLDictSerializer): return self.to_xml_string(node) - def detail(self,data): + def show(self,data): self._xml_doc = minidom.Document() node = self._create_version_node(data['version'], True) @@ -404,7 +423,7 @@ class VersionsAtomSerializer(wsgi.XMLDictSerializer): return self.to_xml_string(node) - def detail(self, data): + def show(self, data): self._xml_doc = minidom.Document() node = self._xml_doc.createElementNS(self.xmlns, 'feed') self._create_detail_meta(node, data['version']) @@ -412,6 +431,12 @@ class VersionsAtomSerializer(wsgi.XMLDictSerializer): return self.to_xml_string(node) + +class VersionsHeadersSerializer(wsgi.ResponseHeadersSerializer): + def multi(self, response, data): + response.status_int = 300 + + def create_resource(version='1.0'): controller = { '1.0': VersionV10, diff --git a/nova/api/openstack/views/versions.py b/nova/api/openstack/views/versions.py index 9fa8f49dc..97e35c983 100644 --- a/nova/api/openstack/views/versions.py +++ b/nova/api/openstack/views/versions.py @@ -31,6 +31,11 @@ class ViewBuilder(object): """ self.base_url = base_url + def build_choices(self, version_data, request): + version_data['links'][0]['href'] = self._build_versioned_link(request, + version_data['id']) + return version_data + def build(self, version_data): """Generic method used to generate a version entity.""" version = { @@ -42,6 +47,9 @@ class ViewBuilder(object): return version + def _build_versioned_link(self, req, version): + return '%s://%s/%s%s' % (req.scheme, req.host, version, req.path) + def _build_links(self, version_data): """Generate a container of links that refer to the provided version.""" href = self.generate_href(version_data["id"]) diff --git a/nova/tests/api/openstack/test_versions.py b/nova/tests/api/openstack/test_versions.py index d1ec0b84a..f33ef5e6a 100644 --- a/nova/tests/api/openstack/test_versions.py +++ b/nova/tests/api/openstack/test_versions.py @@ -384,6 +384,61 @@ class VersionsTest(test.TestCase): self.assertEqual(expected, actual) + def test_multi_choice_servers_list(self): + req = webob.Request.blank('/servers/2') + req.accept = "application/json" + res = req.get_response(fakes.wsgi_app()) + print res.body + self.assertEqual(res.status_int, 300) + self.assertEqual(res.content_type, "application/json") + + expected = { + "choices": [ + { + "id": "v1.1", + "status": "CURRENT", + "links": [ + { + "href": "http://localhost:80/v1.1/servers/2", + "rel": "self", + }, + ], + "media-types": [ + { + "base": "application/xml", + "type": "application/vnd.openstack.compute-v1.1+xml" + }, + { + "base": "application/json", + "type": "application/vnd.openstack.compute-v1.1+json" + }, + ], + }, + { + "id": "v1.0", + "status": "DEPRECATED", + "links": [ + { + "href": "http://localhost:80/v1.0/servers/2", + "rel": "self", + }, + ], + "media-types": [ + { + "base": "application/xml", + "type": "application/vnd.openstack.compute-v1.0+xml" + }, + { + "base": "application/json", + "type": "application/vnd.openstack.compute-v1.0+json" + }, + ], + }, + ],} + + self.assertDictMatch(expected, json.loads(res.body)) + + def test_view_builder(self): base_url = "http://example.org/" @@ -488,7 +543,7 @@ class VersionsTest(test.TestCase): } serializer = versions.VersionsXMLSerializer() - response = serializer.detail(version_data) + response = serializer.show(version_data) root = xml.etree.ElementTree.XML(response) self.assertEqual(root.tag.split('}')[1], "version") @@ -625,7 +680,7 @@ class VersionsTest(test.TestCase): } serializer = versions.VersionsAtomSerializer() - response = serializer.detail(versions_data) + response = serializer.show(versions_data) root = xml.etree.ElementTree.XML(response) self.assertEqual(root.tag.split('}')[1], "feed") -- cgit From 6dbd7583f4f1ca4be59e163c4c568423a91cd29e Mon Sep 17 00:00:00 2001 From: William Wolf Date: Tue, 26 Jul 2011 18:10:36 -0400 Subject: pep8 fixes --- nova/api/openstack/__init__.py | 4 +-- nova/api/openstack/versions.py | 31 +++++++++--------- nova/tests/api/openstack/test_versions.py | 52 +++++++++++++++---------------- 3 files changed, 41 insertions(+), 46 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index 0dfad0ef6..96a2f20e0 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -116,8 +116,8 @@ class APIRouter(base_wsgi.Router): 'select': 'POST', 'boot': 'POST'}) - mapper.connect("versions", "/", - controller=versions.create_resource(version), + mapper.connect("versions", "/", + controller=versions.create_resource(version), action='show') mapper.resource("console", "consoles", diff --git a/nova/api/openstack/versions.py b/nova/api/openstack/versions.py index 4b567957d..58c767d5c 100644 --- a/nova/api/openstack/versions.py +++ b/nova/api/openstack/versions.py @@ -25,12 +25,12 @@ from nova.api.openstack import wsgi ATOM_XMLNS = "http://www.w3.org/2005/Atom" -VERSIONS = { +VERSIONS = { "v1.0": { - "version" : { + "version" : { "id": "v1.0", "status": "DEPRECATED", - "updated": "2011-01-21T11:33:21Z", + "updated": "2011-01-21T11:33:21Z", "links": [ { "rel": "self", @@ -41,7 +41,7 @@ VERSIONS = { "type": "application/pdf", "href": "http://docs.rackspacecloud.com/" "servers/api/v1.0/cs-devguide-20110125.pdf" - }, + }, { "rel": "describedby", "type": "application/vnd.sun.wadl+xml", @@ -62,7 +62,7 @@ VERSIONS = { }, }, "v1.1": { - "version" : { + "version" : { "id": "v1.1", "status": "CURRENT", "updated": "2011-01-21T11:33:21Z", @@ -76,7 +76,7 @@ VERSIONS = { "type": "application/pdf", "href": "http://docs.rackspacecloud.com/" "servers/api/v1.1/cs-devguide-20110125.pdf" - }, + }, { "rel": "describedby", "type": "application/vnd.sun.wadl+xml", @@ -99,8 +99,6 @@ VERSIONS = { } - - class Versions(wsgi.Resource): @classmethod def factory(cls, global_config, **local_config): @@ -192,7 +190,7 @@ class Versions(wsgi.Resource): builder = nova.api.openstack.views.versions.get_view_builder(request) choices = [ - builder.build_choices(version, request) + builder.build_choices(version, request) for version in version_objs] return dict(choices=choices) @@ -207,6 +205,7 @@ class VersionV11(object): def show(self, req): return VERSIONS['v1.1'] + class VersionsRequestDeserializer(wsgi.RequestDeserializer): def get_action_args(self, request_environment): """Parse dictionary created by routes library.""" @@ -220,8 +219,8 @@ class VersionsRequestDeserializer(wsgi.RequestDeserializer): class VersionsXMLSerializer(wsgi.XMLDictSerializer): - #TODO(wwolf): this is temporary until we get rid of toprettyxml - # in the base class (XMLDictSerializer), which I plan to do in + #TODO(wwolf): this is temporary until we get rid of toprettyxml + # in the base class (XMLDictSerializer), which I plan to do in # another branch def to_xml_string(self, node, has_atom=False): self._add_xmlns(node, has_atom) @@ -244,7 +243,7 @@ class VersionsXMLSerializer(wsgi.XMLDictSerializer): base.appendChild(node) return base - + def _create_version_node(self, version, create_ns=False): version_node = self._xml_doc.createElement('version') if create_ns: @@ -278,7 +277,7 @@ class VersionsXMLSerializer(wsgi.XMLDictSerializer): return self.to_xml_string(node) - def show(self,data): + def show(self, data): self._xml_doc = minidom.Document() node = self._create_version_node(data['version'], True) @@ -292,8 +291,8 @@ class VersionsXMLSerializer(wsgi.XMLDictSerializer): class VersionsAtomSerializer(wsgi.XMLDictSerializer): - #TODO(wwolf): this is temporary until we get rid of toprettyxml - # in the base class (XMLDictSerializer), which I plan to do in + #TODO(wwolf): this is temporary until we get rid of toprettyxml + # in the base class (XMLDictSerializer), which I plan to do in # another branch def to_xml_string(self, node, has_atom=False): self._add_xmlns(node, has_atom) @@ -455,7 +454,5 @@ def create_resource(version='1.0'): deserializer = wsgi.RequestDeserializer( supported_content_types=supported_content_types) - - return wsgi.Resource(controller, serializer=serializer, deserializer=deserializer) diff --git a/nova/tests/api/openstack/test_versions.py b/nova/tests/api/openstack/test_versions.py index f33ef5e6a..587b085c3 100644 --- a/nova/tests/api/openstack/test_versions.py +++ b/nova/tests/api/openstack/test_versions.py @@ -35,7 +35,6 @@ class VersionsTest(test.TestCase): self.stubs = stubout.StubOutForTesting() fakes.stub_out_auth(self.stubs) - def tearDown(self): super(VersionsTest, self).tearDown() @@ -182,12 +181,11 @@ class VersionsTest(test.TestCase): for media_node in media_type_nodes: self.assertEqual(media_node.tag.split('}')[1], 'media-type') - expected = """ - + + rel="describedby" + type="application/vnd.sun.wadl+xml"/> """.replace(" ", "").replace("\n", "") actual = res.body.replace(" ", "").replace("\n", "") @@ -220,10 +218,10 @@ class VersionsTest(test.TestCase): self.assertEqual(res.status_int, 200) self.assertEqual(res.content_type, "application/xml") expected = """ - + + rel="describedby" + type="application/vnd.sun.wadl+xml"/> """.replace(" ", "").replace("\n", "") actual = res.body.replace(" ", "").replace("\n", "") @@ -290,7 +288,8 @@ class VersionsTest(test.TestCase): http://servers.api.openstack.org/v1.0/ Version v1.0 2011-01-21T11:33:21Z - + @@ -326,7 +325,8 @@ class VersionsTest(test.TestCase): http://servers.api.openstack.org/v1.1/ Version v1.1 2011-01-21T11:33:21Z - + @@ -407,13 +407,13 @@ class VersionsTest(test.TestCase): { "base": "application/xml", "type": "application/vnd.openstack.compute-v1.1+xml" - }, + }, { "base": "application/json", "type": "application/vnd.openstack.compute-v1.1+json" }, ], - }, + }, { "id": "v1.0", "status": "DEPRECATED", @@ -432,13 +432,12 @@ class VersionsTest(test.TestCase): "base": "application/json", "type": "application/vnd.openstack.compute-v1.0+json" }, - ], + ], }, - ],} + ], } self.assertDictMatch(expected, json.loads(res.body)) - def test_view_builder(self): base_url = "http://example.org/" @@ -507,10 +506,10 @@ class VersionsTest(test.TestCase): def test_version_detail_xml_serializer(self): version_data = { - "version" : { + "version" : { "id": "v1.0", "status": "CURRENT", - "updated": "2011-01-21T11:33:21Z", + "updated": "2011-01-21T11:33:21Z", "links": [ { "rel": "self", @@ -521,7 +520,7 @@ class VersionsTest(test.TestCase): "type": "application/pdf", "href": "http://docs.rackspacecloud.com/" "servers/api/v1.0/cs-devguide-20110125.pdf" - }, + }, { "rel": "describedby", "type": "application/vnd.sun.wadl+xml", @@ -568,7 +567,6 @@ class VersionsTest(test.TestCase): for key, val in version_data['version']['links'][i].items(): self.assertEqual(val, link.get(key)) - def test_versions_list_atom_serializer(self): versions_data = { 'versions': [ @@ -644,7 +642,7 @@ class VersionsTest(test.TestCase): def test_version_detail_atom_serializer(self): versions_data = { - "version" : { + "version" : { "id": "v1.1", "status": "CURRENT", "updated": "2011-01-21T11:33:21Z", @@ -658,7 +656,7 @@ class VersionsTest(test.TestCase): "type": "application/pdf", "href": "http://docs.rackspacecloud.com/" "servers/api/v1.1/cs-devguide-20110125.pdf" - }, + }, { "rel": "describedby", "type": "application/vnd.sun.wadl+xml", -- cgit From 15068c4038d93db77278ea3306d992b424168c24 Mon Sep 17 00:00:00 2001 From: William Wolf Date: Tue, 26 Jul 2011 18:16:28 -0400 Subject: added multi_choice test just to hit another resource --- nova/tests/api/openstack/test_versions.py | 56 +++++++++++++++++++++++++++++-- 1 file changed, 54 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/tests/api/openstack/test_versions.py b/nova/tests/api/openstack/test_versions.py index 587b085c3..d8d6cebe2 100644 --- a/nova/tests/api/openstack/test_versions.py +++ b/nova/tests/api/openstack/test_versions.py @@ -384,11 +384,63 @@ class VersionsTest(test.TestCase): self.assertEqual(expected, actual) - def test_multi_choice_servers_list(self): + def test_multi_choice_image(self): + req = webob.Request.blank('/images/1') + req.accept = "application/json" + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 300) + self.assertEqual(res.content_type, "application/json") + + expected = { + "choices": [ + { + "id": "v1.1", + "status": "CURRENT", + "links": [ + { + "href": "http://localhost:80/v1.1/images/1", + "rel": "self", + }, + ], + "media-types": [ + { + "base": "application/xml", + "type": "application/vnd.openstack.compute-v1.1+xml" + }, + { + "base": "application/json", + "type": "application/vnd.openstack.compute-v1.1+json" + }, + ], + }, + { + "id": "v1.0", + "status": "DEPRECATED", + "links": [ + { + "href": "http://localhost:80/v1.0/images/1", + "rel": "self", + }, + ], + "media-types": [ + { + "base": "application/xml", + "type": "application/vnd.openstack.compute-v1.0+xml" + }, + { + "base": "application/json", + "type": "application/vnd.openstack.compute-v1.0+json" + }, + ], + }, + ], } + + self.assertDictMatch(expected, json.loads(res.body)) + + def test_multi_choice_server(self): req = webob.Request.blank('/servers/2') req.accept = "application/json" res = req.get_response(fakes.wsgi_app()) - print res.body self.assertEqual(res.status_int, 300) self.assertEqual(res.content_type, "application/json") -- cgit From c95ee1625a6a88afdb77d305077d1ee7eeaae854 Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Tue, 26 Jul 2011 16:20:31 -0700 Subject: fix for lp816713: In instance creation, when nova-api is passed imageRefs generated by itself, strip the url down to an id so that default glance connection params are used --- nova/api/openstack/create_instance_helper.py | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'nova') diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index f8317565e..0e6c0a87c 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -87,6 +87,10 @@ class CreateInstanceHelper(object): key_data = key_pair['public_key'] image_href = self.controller._image_ref_from_req_data(body) + # If the image href was generated by nova api, strip image_href + # down to an id and use the default glance connection params + if image_href.startswith(req.application_url): + image_href = image_href.split('/').pop() try: image_service, image_id = nova.image.get_image_service(image_href) kernel_id, ramdisk_id = self._get_kernel_ramdisk_from_image( -- cgit From 6b33d0dfbfea7ee66a47947973133573070303cd Mon Sep 17 00:00:00 2001 From: John Tran Date: Tue, 26 Jul 2011 23:03:16 -0700 Subject: code was checking for key in sqlalchemy instance but if floating_ip is a non-sqlalchemy dict instance instead, value=None will cause NoneType exception. --- nova/api/openstack/contrib/floating_ips.py | 2 +- nova/tests/api/openstack/contrib/test_floating_ips.py | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/api/openstack/contrib/floating_ips.py b/nova/api/openstack/contrib/floating_ips.py index b4a211857..a02f34b8c 100644 --- a/nova/api/openstack/contrib/floating_ips.py +++ b/nova/api/openstack/contrib/floating_ips.py @@ -27,7 +27,7 @@ from nova.api.openstack import extensions def _translate_floating_ip_view(floating_ip): result = {'id': floating_ip['id'], 'ip': floating_ip['address']} - if 'fixed_ip' in floating_ip: + if 'fixed_ip' in floating_ip and floating_ip['fixed_ip']: result['fixed_ip'] = floating_ip['fixed_ip']['address'] else: result['fixed_ip'] = None diff --git a/nova/tests/api/openstack/contrib/test_floating_ips.py b/nova/tests/api/openstack/contrib/test_floating_ips.py index de006d088..f0e7c1f6c 100644 --- a/nova/tests/api/openstack/contrib/test_floating_ips.py +++ b/nova/tests/api/openstack/contrib/test_floating_ips.py @@ -111,6 +111,11 @@ class FloatingIpTest(test.TestCase): self.assertEqual(view['floating_ip']['fixed_ip'], None) self.assertEqual(view['floating_ip']['instance_id'], None) + def test_translate_floating_ip_view_dict(self): + floating_ip = {'id': 0, 'address': '10.0.0.10', 'fixed_ip': None} + view = _translate_floating_ip_view(floating_ip) + self.assertTrue('floating_ip' in view) + def test_floating_ips_list(self): req = webob.Request.blank('/v1.1/os-floating-ips') res = req.get_response(fakes.wsgi_app()) -- cgit From 74e3218d2b6045457019c4de518ca4a869e37807 Mon Sep 17 00:00:00 2001 From: William Wolf Date: Wed, 27 Jul 2011 09:11:41 -0400 Subject: multi choice XML responses with tests --- nova/api/openstack/versions.py | 15 ++++-- nova/tests/api/openstack/test_versions.py | 87 +++++++++++++++++++++++++++---- 2 files changed, 88 insertions(+), 14 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/versions.py b/nova/api/openstack/versions.py index 58c767d5c..9909d8d0d 100644 --- a/nova/api/openstack/versions.py +++ b/nova/api/openstack/versions.py @@ -25,6 +25,7 @@ from nova.api.openstack import wsgi ATOM_XMLNS = "http://www.w3.org/2005/Atom" +OS_XMLNS_BASE = "http://docs.openstack.org/common/api/" VERSIONS = { "v1.0": { "version" : { @@ -226,8 +227,10 @@ class VersionsXMLSerializer(wsgi.XMLDictSerializer): self._add_xmlns(node, has_atom) return node.toxml(encoding='UTF-8') - def _versions_to_xml(self, versions): - root = self._xml_doc.createElement('versions') + def _versions_to_xml(self, versions, name="versions", xmlns=None): + root = self._xml_doc.createElement(name) + root.setAttribute("xmlns", "%sv1.0" % OS_XMLNS_BASE) + root.setAttribute("xmlns:atom", ATOM_XMLNS) for version in versions: root.appendChild(self._create_version_node(version)) @@ -247,14 +250,15 @@ class VersionsXMLSerializer(wsgi.XMLDictSerializer): def _create_version_node(self, version, create_ns=False): version_node = self._xml_doc.createElement('version') if create_ns: - xmlns = "http://docs.openstack.org/common/api/%s" % version['id'] + xmlns = "%s%s" % (OS_XMLNS_BASE, version['id']) xmlns_atom = "http://www.w3.org/2005/Atom" version_node.setAttribute('xmlns', xmlns) version_node.setAttribute('xmlns:atom', xmlns_atom) version_node.setAttribute('id', version['id']) version_node.setAttribute('status', version['status']) - version_node.setAttribute('updated', version['updated']) + if 'updated' in version: + version_node.setAttribute('updated', version['updated']) if 'media-types' in version: media_types = self._create_media_types(version['media-types']) @@ -285,7 +289,8 @@ class VersionsXMLSerializer(wsgi.XMLDictSerializer): def multi(self, data): self._xml_doc = minidom.Document() - node = self._versions_to_xml(data['versions']) + node = self._versions_to_xml(data['choices'], 'choices', + xmlns="%sv1.0" % OS_XMLNS_BASE) return self.to_xml_string(node) diff --git a/nova/tests/api/openstack/test_versions.py b/nova/tests/api/openstack/test_versions.py index d8d6cebe2..dce8e38f1 100644 --- a/nova/tests/api/openstack/test_versions.py +++ b/nova/tests/api/openstack/test_versions.py @@ -27,6 +27,9 @@ from nova.tests.api.openstack import fakes from nova.api.openstack import versions from nova.api.openstack import views +ATOM_XMLNS = versions.ATOM_XMLNS +OS_XMLNS_BASE = versions.OS_XMLNS_BASE + class VersionsTest(test.TestCase): def setUp(self): @@ -170,7 +173,7 @@ class VersionsTest(test.TestCase): root = xml.etree.ElementTree.XML(res.body) self.assertEqual(root.tag.split('}')[1], "version") self.assertEqual(root.tag.split('}')[0].strip('{'), - "http://docs.openstack.org/common/api/v1.0") + "%sv1.0" % OS_XMLNS_BASE) children = list(root) media_types = children[0] @@ -184,7 +187,7 @@ class VersionsTest(test.TestCase): expected = """ @@ -206,7 +209,7 @@ class VersionsTest(test.TestCase): api/v1.0/application.wadl" rel="describedby" type="application/vnd.sun.wadl+xml"/> - """.replace(" ", "").replace("\n", "") + """.replace(" ", "").replace("\n", "") % OS_XMLNS_BASE actual = res.body.replace(" ", "").replace("\n", "") self.assertEqual(expected, actual) @@ -220,7 +223,7 @@ class VersionsTest(test.TestCase): expected = """ @@ -242,7 +245,8 @@ class VersionsTest(test.TestCase): api/v1.1/application.wadl" rel="describedby" type="application/vnd.sun.wadl+xml"/> - """.replace(" ", "").replace("\n", "") + """.replace(" ", "").replace("\n", "") % OS_XMLNS_BASE + actual = res.body.replace(" ", "").replace("\n", "") self.assertEqual(expected, actual) @@ -254,7 +258,8 @@ class VersionsTest(test.TestCase): self.assertEqual(res.status_int, 200) self.assertEqual(res.content_type, "application/xml") - expected = """ + expected = """ + @@ -262,7 +267,8 @@ class VersionsTest(test.TestCase): updated="2010-10-09T11:30:00Z"> - """.replace(" ", "").replace("\n", "") + """.replace(" ", "").replace("\n", "") % (OS_XMLNS_BASE, + ATOM_XMLNS) actual = res.body.replace(" ", "").replace("\n", "") @@ -437,6 +443,36 @@ class VersionsTest(test.TestCase): self.assertDictMatch(expected, json.loads(res.body)) + def test_multi_choice_image_xml(self): + req = webob.Request.blank('/images/1') + req.accept = "application/xml" + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 300) + self.assertEqual(res.content_type, "application/xml") + + expected = """ + + + + + + + + + + + + + + + + """.replace(" ", "").replace("\n","") % (OS_XMLNS_BASE, + ATOM_XMLNS) + def test_multi_choice_server(self): req = webob.Request.blank('/servers/2') req.accept = "application/json" @@ -544,18 +580,51 @@ class VersionsTest(test.TestCase): } expected = """ - + - """.replace(" ", "").replace("\n", "") + """.replace(" ", "").replace("\n", "") % ( + OS_XMLNS_BASE,ATOM_XMLNS) serializer = versions.VersionsXMLSerializer() response = serializer.index(versions_data) response = response.replace(" ", "").replace("\n", "") self.assertEqual(expected, response) + def test_versions_multi_xml_serializer(self): + versions_data = { + 'choices': [ + { + "id": "2.7.1", + "updated": "2011-07-18T11:30:00Z", + "status": "DEPRECATED", + "links": [ + { + "rel": "self", + "href": "http://test/2.7.1/images", + }, + ], + }, + ] + } + + expected = """ + + + + + """.replace(" ", "").replace("\n", "") % (OS_XMLNS_BASE, + ATOM_XMLNS) + + serializer = versions.VersionsXMLSerializer() + response = serializer.multi(versions_data) + response = response.replace(" ", "").replace("\n", "") + self.assertEqual(expected, response) + + def test_version_detail_xml_serializer(self): version_data = { "version" : { -- cgit From 8389d214a917f1c4f2d8ddb471f94c6087ec9ea9 Mon Sep 17 00:00:00 2001 From: William Wolf Date: Wed, 27 Jul 2011 09:13:48 -0400 Subject: pep8 cleanup --- nova/tests/api/openstack/test_versions.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'nova') diff --git a/nova/tests/api/openstack/test_versions.py b/nova/tests/api/openstack/test_versions.py index dce8e38f1..b2fb0efb6 100644 --- a/nova/tests/api/openstack/test_versions.py +++ b/nova/tests/api/openstack/test_versions.py @@ -247,7 +247,6 @@ class VersionsTest(test.TestCase): type="application/vnd.sun.wadl+xml"/> """.replace(" ", "").replace("\n", "") % OS_XMLNS_BASE - actual = res.body.replace(" ", "").replace("\n", "") self.assertEqual(expected, actual) @@ -470,7 +469,7 @@ class VersionsTest(test.TestCase): - """.replace(" ", "").replace("\n","") % (OS_XMLNS_BASE, + """.replace(" ", "").replace("\n", "") % (OS_XMLNS_BASE, ATOM_XMLNS) def test_multi_choice_server(self): @@ -586,7 +585,7 @@ class VersionsTest(test.TestCase): """.replace(" ", "").replace("\n", "") % ( - OS_XMLNS_BASE,ATOM_XMLNS) + OS_XMLNS_BASE, ATOM_XMLNS) serializer = versions.VersionsXMLSerializer() response = serializer.index(versions_data) @@ -624,7 +623,6 @@ class VersionsTest(test.TestCase): response = response.replace(" ", "").replace("\n", "") self.assertEqual(expected, response) - def test_version_detail_xml_serializer(self): version_data = { "version" : { -- cgit From c5c3a5696d11320e7fe0bfbe942610e93fbd1ab4 Mon Sep 17 00:00:00 2001 From: William Wolf Date: Wed, 27 Jul 2011 09:30:15 -0400 Subject: updated serializer tests for multi choice --- nova/tests/api/openstack/test_versions.py | 71 +++++++++++++++++++++---------- 1 file changed, 49 insertions(+), 22 deletions(-) (limited to 'nova') diff --git a/nova/tests/api/openstack/test_versions.py b/nova/tests/api/openstack/test_versions.py index b2fb0efb6..9460e1004 100644 --- a/nova/tests/api/openstack/test_versions.py +++ b/nova/tests/api/openstack/test_versions.py @@ -29,6 +29,7 @@ from nova.api.openstack import views ATOM_XMLNS = versions.ATOM_XMLNS OS_XMLNS_BASE = versions.OS_XMLNS_BASE +VERSIONS = versions.VERSIONS class VersionsTest(test.TestCase): @@ -578,19 +579,26 @@ class VersionsTest(test.TestCase): ] } - expected = """ - - - - - """.replace(" ", "").replace("\n", "") % ( - OS_XMLNS_BASE, ATOM_XMLNS) - serializer = versions.VersionsXMLSerializer() response = serializer.index(versions_data) - response = response.replace(" ", "").replace("\n", "") - self.assertEqual(expected, response) + + root = xml.etree.ElementTree.XML(response) + self.assertEqual(root.tag.split('}')[1], "versions") + self.assertEqual(root.tag.split('}')[0].strip('{'), + "%sv1.0" % OS_XMLNS_BASE) + version = list(root)[0] + self.assertEqual(version.tag.split('}')[1], "version") + self.assertEqual(version.get('id'), + versions_data['versions'][0]['id']) + self.assertEqual(version.get('status'), + versions_data['versions'][0]['status']) + + link = list(version)[0] + + self.assertEqual(link.tag.split('}')[1], "link") + self.assertEqual(link.tag.split('}')[0].strip('{'), ATOM_XMLNS) + for key, val in versions_data['versions'][0]['links'][0].items(): + self.assertEqual(link.get(key), val) def test_versions_multi_xml_serializer(self): versions_data = { @@ -599,6 +607,7 @@ class VersionsTest(test.TestCase): "id": "2.7.1", "updated": "2011-07-18T11:30:00Z", "status": "DEPRECATED", + "media-types": VERSIONS['v1.1']['version']['media-types'], "links": [ { "rel": "self", @@ -609,19 +618,37 @@ class VersionsTest(test.TestCase): ] } - expected = """ - - - - - """.replace(" ", "").replace("\n", "") % (OS_XMLNS_BASE, - ATOM_XMLNS) - serializer = versions.VersionsXMLSerializer() response = serializer.multi(versions_data) - response = response.replace(" ", "").replace("\n", "") - self.assertEqual(expected, response) + + root = xml.etree.ElementTree.XML(response) + self.assertEqual(root.tag.split('}')[1], "choices") + self.assertEqual(root.tag.split('}')[0].strip('{'), + "%sv1.0" % OS_XMLNS_BASE) + version = list(root)[0] + self.assertEqual(version.tag.split('}')[1], "version") + self.assertEqual(version.get('id'), versions_data['choices'][0]['id']) + self.assertEqual(version.get('status'), + versions_data['choices'][0]['status']) + + media_types = list(version)[0] + media_type_nodes = list(media_types) + self.assertEqual(media_types.tag.split('}')[1], "media-types") + + set_types = versions_data['choices'][0]['media-types'] + for i, type in enumerate(set_types): + node = media_type_nodes[i] + self.assertEqual(node.tag.split('}')[1], "media-type") + for key, val in set_types[i].items(): + self.assertEqual(node.get(key), val) + + link = list(version)[1] + + self.assertEqual(link.tag.split('}')[1], "link") + self.assertEqual(link.tag.split('}')[0].strip('{'), ATOM_XMLNS) + for key, val in versions_data['choices'][0]['links'][0].items(): + self.assertEqual(link.get(key), val) + def test_version_detail_xml_serializer(self): version_data = { -- cgit From 6dead0b1706f3b2279504437aca63a3291dc2347 Mon Sep 17 00:00:00 2001 From: William Wolf Date: Wed, 27 Jul 2011 12:06:12 -0400 Subject: pep8 --- nova/tests/api/openstack/test_versions.py | 1 - 1 file changed, 1 deletion(-) (limited to 'nova') diff --git a/nova/tests/api/openstack/test_versions.py b/nova/tests/api/openstack/test_versions.py index 9460e1004..cbc98a4b4 100644 --- a/nova/tests/api/openstack/test_versions.py +++ b/nova/tests/api/openstack/test_versions.py @@ -649,7 +649,6 @@ class VersionsTest(test.TestCase): for key, val in versions_data['choices'][0]['links'][0].items(): self.assertEqual(link.get(key), val) - def test_version_detail_xml_serializer(self): version_data = { "version" : { -- cgit From 5ca4d3a88f1dd758c4ab6133e26cf2f8b05a8339 Mon Sep 17 00:00:00 2001 From: William Wolf Date: Wed, 27 Jul 2011 12:50:52 -0400 Subject: pep8 --- nova/api/openstack/versions.py | 20 ++++---- nova/tests/api/openstack/test_versions.py | 84 +++++++++++++++---------------- 2 files changed, 52 insertions(+), 52 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/versions.py b/nova/api/openstack/versions.py index 9909d8d0d..4d40274b3 100644 --- a/nova/api/openstack/versions.py +++ b/nova/api/openstack/versions.py @@ -28,7 +28,7 @@ ATOM_XMLNS = "http://www.w3.org/2005/Atom" OS_XMLNS_BASE = "http://docs.openstack.org/common/api/" VERSIONS = { "v1.0": { - "version" : { + "version": { "id": "v1.0", "status": "DEPRECATED", "updated": "2011-01-21T11:33:21Z", @@ -52,18 +52,18 @@ VERSIONS = { ], "media-types": [ { - "base" : "application/xml", - "type" : "application/vnd.openstack.compute-v1.0+xml" + "base": "application/xml", + "type": "application/vnd.openstack.compute-v1.0+xml" }, { - "base" : "application/json", - "type" : "application/vnd.openstack.compute-v1.0+json" + "base": "application/json", + "type": "application/vnd.openstack.compute-v1.0+json" } ], }, }, "v1.1": { - "version" : { + "version": { "id": "v1.1", "status": "CURRENT", "updated": "2011-01-21T11:33:21Z", @@ -87,12 +87,12 @@ VERSIONS = { ], "media-types": [ { - "base" : "application/xml", - "type" : "application/vnd.openstack.compute-v1.1+xml" + "base": "application/xml", + "type": "application/vnd.openstack.compute-v1.1+xml" }, { - "base" : "application/json", - "type" : "application/vnd.openstack.compute-v1.1+json" + "base": "application/json", + "type": "application/vnd.openstack.compute-v1.1+json" } ], }, diff --git a/nova/tests/api/openstack/test_versions.py b/nova/tests/api/openstack/test_versions.py index cbc98a4b4..3f7867ff4 100644 --- a/nova/tests/api/openstack/test_versions.py +++ b/nova/tests/api/openstack/test_versions.py @@ -81,37 +81,37 @@ class VersionsTest(test.TestCase): self.assertEqual(res.content_type, "application/json") version = json.loads(res.body) expected = { - "version" : { - "id" : "v1.0", - "status" : "DEPRECATED", - "updated" : "2011-01-21T11:33:21Z", + "version": { + "id": "v1.0", + "status": "DEPRECATED", + "updated": "2011-01-21T11:33:21Z", "links": [ { - "rel" : "self", - "href" : "http://servers.api.openstack.org/v1.0/" + "rel": "self", + "href": "http://servers.api.openstack.org/v1.0/" }, { - "rel" : "describedby", - "type" : "application/pdf", - "href" : "http://docs.rackspacecloud.com/" + "rel": "describedby", + "type": "application/pdf", + "href": "http://docs.rackspacecloud.com/" "servers/api/v1.0/cs-devguide-20110125.pdf" }, { - "rel" : "describedby", - "type" : "application/vnd.sun.wadl+xml", - "href" : "http://docs.rackspacecloud.com/" + "rel": "describedby", + "type": "application/vnd.sun.wadl+xml", + "href": "http://docs.rackspacecloud.com/" "servers/api/v1.0/application.wadl" } ], "media-types": [ { - "base" : "application/xml", - "type" : "application/" + "base": "application/xml", + "type": "application/" "vnd.openstack.compute-v1.0+xml" }, { - "base" : "application/json", - "type" : "application/" + "base": "application/json", + "type": "application/" "vnd.openstack.compute-v1.0+json" } ] @@ -127,37 +127,37 @@ class VersionsTest(test.TestCase): self.assertEqual(res.content_type, "application/json") version = json.loads(res.body) expected = { - "version" : { - "id" : "v1.1", - "status" : "CURRENT", - "updated" : "2011-01-21T11:33:21Z", + "version": { + "id": "v1.1", + "status": "CURRENT", + "updated": "2011-01-21T11:33:21Z", "links": [ { - "rel" : "self", - "href" : "http://servers.api.openstack.org/v1.1/" + "rel": "self", + "href": "http://servers.api.openstack.org/v1.1/" }, { - "rel" : "describedby", - "type" : "application/pdf", - "href" : "http://docs.rackspacecloud.com/" + "rel": "describedby", + "type": "application/pdf", + "href": "http://docs.rackspacecloud.com/" "servers/api/v1.1/cs-devguide-20110125.pdf" }, { - "rel" : "describedby", - "type" : "application/vnd.sun.wadl+xml", - "href" : "http://docs.rackspacecloud.com/" + "rel": "describedby", + "type": "application/vnd.sun.wadl+xml", + "href": "http://docs.rackspacecloud.com/" "servers/api/v1.1/application.wadl" } ], "media-types": [ { - "base" : "application/xml", - "type" : "application/" + "base": "application/xml", + "type": "application/" "vnd.openstack.compute-v1.1+xml" }, { - "base" : "application/json", - "type" : "application/" + "base": "application/json", + "type": "application/" "vnd.openstack.compute-v1.1+json" } ] @@ -651,7 +651,7 @@ class VersionsTest(test.TestCase): def test_version_detail_xml_serializer(self): version_data = { - "version" : { + "version": { "id": "v1.0", "status": "CURRENT", "updated": "2011-01-21T11:33:21Z", @@ -675,12 +675,12 @@ class VersionsTest(test.TestCase): ], "media-types": [ { - "base" : "application/xml", - "type" : "application/vnd.openstack.compute-v1.0+xml" + "base": "application/xml", + "type": "application/vnd.openstack.compute-v1.0+xml" }, { - "base" : "application/json", - "type" : "application/vnd.openstack.compute-v1.0+json" + "base": "application/json", + "type": "application/vnd.openstack.compute-v1.0+json" } ], }, @@ -787,7 +787,7 @@ class VersionsTest(test.TestCase): def test_version_detail_atom_serializer(self): versions_data = { - "version" : { + "version": { "id": "v1.1", "status": "CURRENT", "updated": "2011-01-21T11:33:21Z", @@ -811,12 +811,12 @@ class VersionsTest(test.TestCase): ], "media-types": [ { - "base" : "application/xml", - "type" : "application/vnd.openstack.compute-v1.1+xml" + "base": "application/xml", + "type": "application/vnd.openstack.compute-v1.1+xml" }, { - "base" : "application/json", - "type" : "application/vnd.openstack.compute-v1.1+json" + "base": "application/json", + "type": "application/vnd.openstack.compute-v1.1+json" } ], }, -- cgit From b847ed1cbac345bd2d7a8c252080656c8109c052 Mon Sep 17 00:00:00 2001 From: "Kevin L. Mitchell" Date: Wed, 27 Jul 2011 17:16:46 +0000 Subject: Add context argument a lot more places and make unit tests work --- nova/tests/test_libvirt.py | 6 +++--- nova/tests/test_xenapi.py | 16 +++++++++------- nova/tests/xenapi/stubs.py | 4 ++-- nova/virt/driver.py | 7 ++++--- nova/virt/fake.py | 5 +++-- nova/virt/hyperv.py | 3 ++- nova/virt/libvirt/connection.py | 5 +++-- nova/virt/vmwareapi/vmops.py | 4 ++-- nova/virt/vmwareapi_conn.py | 9 +++++---- nova/virt/xenapi/vmops.py | 4 ++-- nova/virt/xenapi_conn.py | 13 +++++++------ 11 files changed, 42 insertions(+), 34 deletions(-) (limited to 'nova') diff --git a/nova/tests/test_libvirt.py b/nova/tests/test_libvirt.py index ad0931a89..c4af38426 100644 --- a/nova/tests/test_libvirt.py +++ b/nova/tests/test_libvirt.py @@ -365,7 +365,7 @@ class LibvirtConnTestCase(test.TestCase): self.mox.ReplayAll() conn = connection.LibvirtConnection(False) - conn.snapshot(instance_ref, recv_meta['id']) + conn.snapshot(self.context, instance_ref, recv_meta['id']) snapshot = image_service.show(context, recv_meta['id']) self.assertEquals(snapshot['properties']['image_state'], 'available') @@ -405,7 +405,7 @@ class LibvirtConnTestCase(test.TestCase): self.mox.ReplayAll() conn = connection.LibvirtConnection(False) - conn.snapshot(instance_ref, recv_meta['id']) + conn.snapshot(self.context, instance_ref, recv_meta['id']) snapshot = image_service.show(context, recv_meta['id']) self.assertEquals(snapshot['properties']['image_state'], 'available') @@ -775,7 +775,7 @@ class LibvirtConnTestCase(test.TestCase): network_info = [(network, mapping)] try: - conn.spawn(instance, network_info) + conn.spawn(context.get_admin_context(), instance, network_info) except Exception, e: count = (0 <= str(e.message).find('Unexpected method call')) diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index 199a8bc52..fd8416424 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -227,7 +227,7 @@ class XenAPIVMTestCase(test.TestCase): 'mac': 'DE:AD:BE:EF:00:00', 'rxtx_cap': 3})] instance = db.instance_create(self.context, values) - self.conn.spawn(instance, network_info) + self.conn.spawn(self.context, instance, network_info) gt1 = eventlet.spawn(_do_build, 1, self.project.id, self.user.id) gt2 = eventlet.spawn(_do_build, 2, self.project.id, self.user.id) @@ -257,14 +257,15 @@ class XenAPIVMTestCase(test.TestCase): instance = self._create_instance() name = "MySnapshot" - self.assertRaises(exception.Error, self.conn.snapshot, instance, name) + self.assertRaises(exception.Error, self.conn.snapshot, + self.context, instance, name) def test_instance_snapshot(self): stubs.stubout_instance_snapshot(self.stubs) instance = self._create_instance() name = "MySnapshot" - template_vm_ref = self.conn.snapshot(instance, name) + template_vm_ref = self.conn.snapshot(self.context, instance, name) def ensure_vm_was_torn_down(): vm_labels = [] @@ -422,7 +423,7 @@ class XenAPIVMTestCase(test.TestCase): 'label': 'fake', 'mac': 'DE:AD:BE:EF:00:00', 'rxtx_cap': 3})] - self.conn.spawn(instance, network_info) + self.conn.spawn(self.context, instance, network_info) self.create_vm_record(self.conn, os_type, instance_id) self.check_vm_record(self.conn, check_injection) self.assertTrue(instance.os_type) @@ -691,7 +692,7 @@ class XenAPIVMTestCase(test.TestCase): 'label': 'fake', 'mac': 'DE:AD:BE:EF:00:00', 'rxtx_cap': 3})] - self.conn.spawn(instance, network_info) + self.conn.spawn(self.context, instance, network_info) return instance @@ -802,8 +803,9 @@ class XenAPIMigrateInstance(test.TestCase): 'label': 'fake', 'mac': 'DE:AD:BE:EF:00:00', 'rxtx_cap': 3})] - conn.finish_resize(instance, dict(base_copy='hurr', cow='durr'), - network_info) + conn.finish_resize(self.context, instance, + dict(base_copy='hurr', cow='durr'), + network_info) class XenAPIDetermineDiskImageTestCase(test.TestCase): diff --git a/nova/tests/xenapi/stubs.py b/nova/tests/xenapi/stubs.py index 66c79d465..3a142081c 100644 --- a/nova/tests/xenapi/stubs.py +++ b/nova/tests/xenapi/stubs.py @@ -28,8 +28,8 @@ from nova import utils def stubout_instance_snapshot(stubs): @classmethod - def fake_fetch_image(cls, session, instance_id, image, user, project, - type): + def fake_fetch_image(cls, context, session, instance_id, image, user, + project, type): from nova.virt.xenapi.fake import create_vdi name_label = "instance-%s" % instance_id #TODO: create fake SR record diff --git a/nova/virt/driver.py b/nova/virt/driver.py index 34dc5f544..40cb877ce 100644 --- a/nova/virt/driver.py +++ b/nova/virt/driver.py @@ -61,7 +61,8 @@ class ComputeDriver(object): """Return a list of InstanceInfo for all registered VMs""" raise NotImplementedError() - def spawn(self, instance, network_info, block_device_mapping=None): + def spawn(self, context, instance, network_info, + block_device_mapping=None): """Launch a VM for the specified instance""" raise NotImplementedError() @@ -118,11 +119,11 @@ class ComputeDriver(object): off the instance copies over the COW disk""" raise NotImplementedError() - def snapshot(self, instance, image_id): + def snapshot(self, context, instance, image_id): """Create snapshot from a running VM instance.""" raise NotImplementedError() - def finish_resize(self, instance, disk_info): + def finish_resize(self, context, instance, disk_info): """Completes a resize, turning on the migrated instance""" raise NotImplementedError() diff --git a/nova/virt/fake.py b/nova/virt/fake.py index 26bc421c0..f67c2309e 100644 --- a/nova/virt/fake.py +++ b/nova/virt/fake.py @@ -129,7 +129,8 @@ class FakeConnection(driver.ComputeDriver): info_list.append(self._map_to_instance_info(instance)) return info_list - def spawn(self, instance, network_info, block_device_mapping=None): + def spawn(self, context, instance, network_info, + block_device_mapping=None): """ Create a new instance/VM/domain on the virtualization platform. @@ -153,7 +154,7 @@ class FakeConnection(driver.ComputeDriver): fake_instance = FakeInstance(name, state) self.instances[name] = fake_instance - def snapshot(self, instance, name): + def snapshot(self, context, instance, name): """ Snapshots the specified instance. diff --git a/nova/virt/hyperv.py b/nova/virt/hyperv.py index 81c7dea58..8236b5a45 100644 --- a/nova/virt/hyperv.py +++ b/nova/virt/hyperv.py @@ -139,7 +139,8 @@ class HyperVConnection(driver.ComputeDriver): return instance_infos - def spawn(self, instance, network_info, block_device_mapping=None): + def spawn(self, context, instance, network_info, + block_device_mapping=None): """ Create a new VM and start it.""" vm = self._lookup(instance.name) if vm is not None: diff --git a/nova/virt/libvirt/connection.py b/nova/virt/libvirt/connection.py index 96f9c41f9..9a964dfd2 100644 --- a/nova/virt/libvirt/connection.py +++ b/nova/virt/libvirt/connection.py @@ -399,7 +399,7 @@ class LibvirtConnection(driver.ComputeDriver): virt_dom.detachDevice(xml) @exception.wrap_exception() - def snapshot(self, instance, image_href): + def snapshot(self, context, instance, image_href): """Create snapshot from a running VM instance. This command only works with qemu 0.14+, the qemu_img flag is @@ -595,7 +595,8 @@ class LibvirtConnection(driver.ComputeDriver): # NOTE(ilyaalekseyev): Implementation like in multinics # for xenapi(tr3buchet) @exception.wrap_exception() - def spawn(self, instance, network_info, block_device_mapping=None): + def spawn(self, context, instance, network_info, + block_device_mapping=None): xml = self.to_xml(instance, False, network_info=network_info, block_device_mapping=block_device_mapping) block_device_mapping = block_device_mapping or [] diff --git a/nova/virt/vmwareapi/vmops.py b/nova/virt/vmwareapi/vmops.py index 7e7d2dac3..5fa92230d 100644 --- a/nova/virt/vmwareapi/vmops.py +++ b/nova/virt/vmwareapi/vmops.py @@ -89,7 +89,7 @@ class VMWareVMOps(object): LOG.debug(_("Got total of %s instances") % str(len(lst_vm_names))) return lst_vm_names - def spawn(self, instance, network_info): + def spawn(self, context, instance, network_info): """ Creates a VM instance. @@ -329,7 +329,7 @@ class VMWareVMOps(object): LOG.debug(_("Powered on the VM instance %s") % instance.name) _power_on_vm() - def snapshot(self, instance, snapshot_name): + def snapshot(self, context, instance, snapshot_name): """ Create snapshot from a running VM instance. Steps followed are: diff --git a/nova/virt/vmwareapi_conn.py b/nova/virt/vmwareapi_conn.py index ce57847b2..3d209fa99 100644 --- a/nova/virt/vmwareapi_conn.py +++ b/nova/virt/vmwareapi_conn.py @@ -124,13 +124,14 @@ class VMWareESXConnection(driver.ComputeDriver): """List VM instances.""" return self._vmops.list_instances() - def spawn(self, instance, network_info, block_device_mapping=None): + def spawn(self, context, instance, network_info, + block_device_mapping=None): """Create VM instance.""" - self._vmops.spawn(instance, network_info) + self._vmops.spawn(context, instance, network_info) - def snapshot(self, instance, name): + def snapshot(self, context, instance, name): """Create snapshot from a running VM instance.""" - self._vmops.snapshot(instance, name) + self._vmops.snapshot(context, instance, name) def reboot(self, instance, network_info): """Reboot VM instance.""" diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 1c6604836..9d73cfb05 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -209,8 +209,8 @@ class VMOps(object): if instance.vm_mode != vm_mode: # Update database with normalized (or determined) value - db.instance_update(context.get_admin_context(), - instance['id'], {'vm_mode': vm_mode}) + db.instance_update(context, instance['id'], + {'vm_mode': vm_mode}) vm_ref = VMHelper.create_vm(self._session, instance, kernel and kernel.get('file', None) or None, ramdisk and ramdisk.get('file', None) or None, diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index 7c355a55b..a1928dfa8 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -194,21 +194,22 @@ class XenAPIConnection(driver.ComputeDriver): def list_instances_detail(self): return self._vmops.list_instances_detail() - def spawn(self, instance, network_info, block_device_mapping=None): + def spawn(self, context, instance, network_info, + block_device_mapping=None): """Create VM instance""" - self._vmops.spawn(instance, network_info) + self._vmops.spawn(context, instance, network_info) def revert_resize(self, instance): """Reverts a resize, powering back on the instance""" self._vmops.revert_resize(instance) - def finish_resize(self, instance, disk_info, network_info): + def finish_resize(self, context, instance, disk_info, network_info): """Completes a resize, turning on the migrated instance""" - self._vmops.finish_resize(instance, disk_info, network_info) + self._vmops.finish_resize(context, instance, disk_info, network_info) - def snapshot(self, instance, image_id): + def snapshot(self, context, instance, image_id): """ Create snapshot from a running VM instance """ - self._vmops.snapshot(instance, image_id) + self._vmops.snapshot(context, instance, image_id) def reboot(self, instance, network_info): """Reboot VM instance""" -- cgit From 094b9845500e28d315f70aa1fbc37b75c143d0c0 Mon Sep 17 00:00:00 2001 From: John Tran Date: Wed, 27 Jul 2011 10:34:58 -0700 Subject: improved the code per peer review --- nova/api/openstack/contrib/floating_ips.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/contrib/floating_ips.py b/nova/api/openstack/contrib/floating_ips.py index a02f34b8c..3d8049324 100644 --- a/nova/api/openstack/contrib/floating_ips.py +++ b/nova/api/openstack/contrib/floating_ips.py @@ -27,9 +27,9 @@ from nova.api.openstack import extensions def _translate_floating_ip_view(floating_ip): result = {'id': floating_ip['id'], 'ip': floating_ip['address']} - if 'fixed_ip' in floating_ip and floating_ip['fixed_ip']: + try: result['fixed_ip'] = floating_ip['fixed_ip']['address'] - else: + except (TypeError, KeyError): result['fixed_ip'] = None if 'instance' in floating_ip: result['instance_id'] = floating_ip['instance']['id'] -- cgit From 50eb566ba5ce50127ad3df8984dd6895c31361a3 Mon Sep 17 00:00:00 2001 From: "Kevin L. Mitchell" Date: Wed, 27 Jul 2011 17:56:12 +0000 Subject: Fix context argument in a test; add TODOs --- nova/tests/test_libvirt.py | 2 +- nova/virt/driver.py | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/tests/test_libvirt.py b/nova/tests/test_libvirt.py index c4af38426..d4f8f00d8 100644 --- a/nova/tests/test_libvirt.py +++ b/nova/tests/test_libvirt.py @@ -775,7 +775,7 @@ class LibvirtConnTestCase(test.TestCase): network_info = [(network, mapping)] try: - conn.spawn(context.get_admin_context(), instance, network_info) + conn.spawn(self.context, instance, network_info) except Exception, e: count = (0 <= str(e.message).find('Unexpected method call')) diff --git a/nova/virt/driver.py b/nova/virt/driver.py index 40cb877ce..8e17d7540 100644 --- a/nova/virt/driver.py +++ b/nova/virt/driver.py @@ -40,6 +40,7 @@ class ComputeDriver(object): def init_host(self, host): """Adopt existing VM's running here""" + # TODO(Vek): Need to pass context in for access to auth_token raise NotImplementedError() def get_info(self, instance_name): @@ -52,13 +53,16 @@ class ComputeDriver(object): :num_cpu: (int) the number of virtual CPUs for the domain :cpu_time: (int) the CPU time used in nanoseconds """ + # TODO(Vek): Need to pass context in for access to auth_token raise NotImplementedError() def list_instances(self): + # TODO(Vek): Need to pass context in for access to auth_token raise NotImplementedError() def list_instances_detail(self): """Return a list of InstanceInfo for all registered VMs""" + # TODO(Vek): Need to pass context in for access to auth_token raise NotImplementedError() def spawn(self, context, instance, network_info, @@ -80,29 +84,36 @@ class ComputeDriver(object): warning in that case. """ + # TODO(Vek): Need to pass context in for access to auth_token raise NotImplementedError() def reboot(self, instance, network_info): """Reboot specified VM""" + # TODO(Vek): Need to pass context in for access to auth_token raise NotImplementedError() def snapshot_instance(self, context, instance_id, image_id): raise NotImplementedError() def get_console_pool_info(self, console_type): + # TODO(Vek): Need to pass context in for access to auth_token raise NotImplementedError() def get_console_output(self, instance): + # TODO(Vek): Need to pass context in for access to auth_token raise NotImplementedError() def get_ajax_console(self, instance): + # TODO(Vek): Need to pass context in for access to auth_token raise NotImplementedError() def get_diagnostics(self, instance): """Return data about VM diagnostics""" + # TODO(Vek): Need to pass context in for access to auth_token raise NotImplementedError() def get_host_ip_addr(self): + # TODO(Vek): Need to pass context in for access to auth_token raise NotImplementedError() def attach_volume(self, context, instance_id, volume_id, mountpoint): @@ -117,6 +128,7 @@ class ComputeDriver(object): def migrate_disk_and_power_off(self, instance, dest): """Transfers the VHD of a running instance to another host, then shuts off the instance copies over the COW disk""" + # TODO(Vek): Need to pass context in for access to auth_token raise NotImplementedError() def snapshot(self, context, instance, image_id): @@ -129,30 +141,37 @@ class ComputeDriver(object): def revert_resize(self, instance): """Reverts a resize, powering back on the instance""" + # TODO(Vek): Need to pass context in for access to auth_token raise NotImplementedError() def pause(self, instance, callback): """Pause VM instance""" + # TODO(Vek): Need to pass context in for access to auth_token raise NotImplementedError() def unpause(self, instance, callback): """Unpause paused VM instance""" + # TODO(Vek): Need to pass context in for access to auth_token raise NotImplementedError() def suspend(self, instance, callback): """suspend the specified instance""" + # TODO(Vek): Need to pass context in for access to auth_token raise NotImplementedError() def resume(self, instance, callback): """resume the specified instance""" + # TODO(Vek): Need to pass context in for access to auth_token raise NotImplementedError() def rescue(self, instance, callback, network_info): """Rescue the specified instance""" + # TODO(Vek): Need to pass context in for access to auth_token raise NotImplementedError() def unrescue(self, instance, callback, network_info): """Unrescue the specified instance""" + # TODO(Vek): Need to pass context in for access to auth_token raise NotImplementedError() def update_available_resource(self, ctxt, host): @@ -165,6 +184,7 @@ class ComputeDriver(object): :param host: hostname that compute manager is currently running """ + # TODO(Vek): Need to pass context in for access to auth_token raise NotImplementedError() def live_migration(self, ctxt, instance_ref, dest, @@ -184,20 +204,25 @@ class ComputeDriver(object): expected nova.compute.manager.recover_live_migration. """ + # TODO(Vek): Need to pass context in for access to auth_token raise NotImplementedError() def refresh_security_group_rules(self, security_group_id): + # TODO(Vek): Need to pass context in for access to auth_token raise NotImplementedError() def refresh_security_group_members(self, security_group_id): + # TODO(Vek): Need to pass context in for access to auth_token raise NotImplementedError() def refresh_provider_fw_rules(self, security_group_id): """See: nova/virt/fake.py for docs.""" + # TODO(Vek): Need to pass context in for access to auth_token raise NotImplementedError() def reset_network(self, instance): """reset networking for specified instance""" + # TODO(Vek): Need to pass context in for access to auth_token pass def ensure_filtering_rules_for_instance(self, instance_ref): @@ -223,10 +248,12 @@ class ComputeDriver(object): :params instance_ref: nova.db.sqlalchemy.models.Instance object """ + # TODO(Vek): Need to pass context in for access to auth_token raise NotImplementedError() def unfilter_instance(self, instance, network_info): """Stop filtering instance""" + # TODO(Vek): Need to pass context in for access to auth_token raise NotImplementedError() def set_admin_password(self, context, instance_id, new_pass=None): @@ -237,24 +264,30 @@ class ComputeDriver(object): """Create a file on the VM instance. The file path and contents should be base64-encoded. """ + # TODO(Vek): Need to pass context in for access to auth_token raise NotImplementedError() def agent_update(self, instance, url, md5hash): """Update agent on the VM instance.""" + # TODO(Vek): Need to pass context in for access to auth_token raise NotImplementedError() def inject_network_info(self, instance, nw_info): """inject network info for specified instance""" + # TODO(Vek): Need to pass context in for access to auth_token pass def poll_rescued_instances(self, timeout): """Poll for rescued instances""" + # TODO(Vek): Need to pass context in for access to auth_token raise NotImplementedError() def set_host_enabled(self, host, enabled): """Sets the specified host's ability to accept new instances.""" + # TODO(Vek): Need to pass context in for access to auth_token raise NotImplementedError() def plug_vifs(self, instance, network_info): """Plugs in VIFs to networks.""" + # TODO(Vek): Need to pass context in for access to auth_token raise NotImplementedError() -- cgit From 9a84b87ae04dc5220f95992d9a6c4e210fbc374f Mon Sep 17 00:00:00 2001 From: William Wolf Date: Wed, 27 Jul 2011 13:59:53 -0400 Subject: fixed issue with factory for Versions Resource --- nova/api/openstack/versions.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/versions.py b/nova/api/openstack/versions.py index 4d40274b3..c39e9dae7 100644 --- a/nova/api/openstack/versions.py +++ b/nova/api/openstack/versions.py @@ -104,9 +104,7 @@ class Versions(wsgi.Resource): @classmethod def factory(cls, global_config, **local_config): """Paste factory.""" - def _factory(app): - return cls(app, **local_config) - return _factory + return cls() def __init__(self): metadata = { -- cgit From f1830708f823a9de9c2f1cd24af5bed80302788f Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Wed, 27 Jul 2011 11:08:44 -0700 Subject: Some work on testing. Two cases related to lp816713 have some coverage already: using an id as an imageRef (test_create_instance_v1_1_local_href), and using a nova href as a url (test_create_instance_v1_1) --- nova/api/openstack/create_instance_helper.py | 4 +++- nova/tests/api/openstack/test_servers.py | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index 0e6c0a87c..234c16bcc 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -89,7 +89,9 @@ class CreateInstanceHelper(object): image_href = self.controller._image_ref_from_req_data(body) # If the image href was generated by nova api, strip image_href # down to an id and use the default glance connection params - if image_href.startswith(req.application_url): + + if isinstance(image_href, basestring) and\ + image_href.startswith(req.application_url): image_href = image_href.split('/').pop() try: image_service, image_id = nova.image.get_image_service(image_href) diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 4ca79434f..edc11b761 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -1250,7 +1250,8 @@ class ServersTest(test.TestCase): def test_create_instance_v1_1(self): self._setup_for_create_instance() - image_href = 'http://localhost/images/2' + # proper local hrefs must start with 'http://localhost/v1.1/' + image_href = 'http://localhost/v1.1/images/2' flavor_ref = 'http://localhost/flavors/3' expected_flavor = { "id": "3", -- cgit From 4def65d37886fff0dc9f238bca5454abaacb6f76 Mon Sep 17 00:00:00 2001 From: "Kevin L. Mitchell" Date: Wed, 27 Jul 2011 18:13:04 +0000 Subject: Use auth_token to set x-auth-token header in glance requests --- nova/virt/xenapi/vm_utils.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index aa0e4c2df..b1b0ddd0c 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -377,7 +377,8 @@ class VMHelper(HelperBase): 'glance_host': glance_host, 'glance_port': glance_port, 'sr_path': cls.get_sr_path(session), - 'os_type': os_type} + 'os_type': os_type, + 'auth_token': getattr(context, 'auth_token', None)} kwargs = {'params': pickle.dumps(params)} task = session.async_call_plugin('glance', 'upload_vhd', kwargs) @@ -429,7 +430,8 @@ class VMHelper(HelperBase): 'glance_host': glance_host, 'glance_port': glance_port, 'uuid_stack': uuid_stack, - 'sr_path': cls.get_sr_path(session)} + 'sr_path': cls.get_sr_path(session), + 'auth_token': getattr(context, 'auth_token', None)} kwargs = {'params': pickle.dumps(params)} task = session.async_call_plugin('glance', 'download_vhd', kwargs) @@ -475,6 +477,7 @@ class VMHelper(HelperBase): sr_ref = safe_find_sr(session) glance_client, image_id = nova.image.get_glance_client(image) + glance_client.set_auth_token(getattr(context, 'auth_token', None)) meta, image_file = glance_client.get_image(image_id) virtual_size = int(meta['size']) vdi_size = virtual_size -- cgit From 22beaf8802fdc44242f4a96e291c4fbb60af0e3a Mon Sep 17 00:00:00 2001 From: William Wolf Date: Wed, 27 Jul 2011 14:24:35 -0400 Subject: make atom+xml accept header be ignored on 300 responses in the VersionsRequestDeserializer --- nova/api/openstack/versions.py | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'nova') diff --git a/nova/api/openstack/versions.py b/nova/api/openstack/versions.py index c39e9dae7..e063ff272 100644 --- a/nova/api/openstack/versions.py +++ b/nova/api/openstack/versions.py @@ -206,6 +206,14 @@ class VersionV11(object): class VersionsRequestDeserializer(wsgi.RequestDeserializer): + def get_expected_content_type(self, request): + supported_content_types = list(self.supported_content_types) + if request.path != '/': + # Remove atom+xml accept type for 300 responses + del supported_content_types[2] + + return request.best_match_content_type(supported_content_types) + def get_action_args(self, request_environment): """Parse dictionary created by routes library.""" args = {} -- cgit From e2ce48eb3ffde56fa8d74b397682814ad278ae63 Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Wed, 27 Jul 2011 11:33:56 -0700 Subject: this change will require that local urls be input with a properly constructed local url: http://localhost/v1.1/images/[id]. Such urls are translated to ids at the api layer. Previously, any url ending with and int was ok. --- nova/tests/api/openstack/fakes.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/tests/api/openstack/fakes.py b/nova/tests/api/openstack/fakes.py index 26b1de818..a1d94876f 100644 --- a/nova/tests/api/openstack/fakes.py +++ b/nova/tests/api/openstack/fakes.py @@ -104,8 +104,7 @@ def stub_out_key_pair_funcs(stubs, have_key_pair=True): def stub_out_image_service(stubs): def fake_get_image_service(image_href): - image_id = int(str(image_href).split('/')[-1]) - return (nova.image.fake.FakeImageService(), image_id) + return (nova.image.fake.FakeImageService(), image_href) stubs.Set(nova.image, 'get_image_service', fake_get_image_service) stubs.Set(nova.image, 'get_default_image_service', lambda: nova.image.fake.FakeImageService()) -- cgit From c20a4845afc47d124017de698657c1713dc11e7f Mon Sep 17 00:00:00 2001 From: William Wolf Date: Wed, 27 Jul 2011 14:35:47 -0400 Subject: fixed xmlns issue --- nova/api/openstack/versions.py | 8 ++++---- nova/tests/api/openstack/test_versions.py | 17 +++++++---------- 2 files changed, 11 insertions(+), 14 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/versions.py b/nova/api/openstack/versions.py index e063ff272..b38c48939 100644 --- a/nova/api/openstack/versions.py +++ b/nova/api/openstack/versions.py @@ -25,7 +25,7 @@ from nova.api.openstack import wsgi ATOM_XMLNS = "http://www.w3.org/2005/Atom" -OS_XMLNS_BASE = "http://docs.openstack.org/common/api/" +OS_XMLNS_BASE = "http://docs.openstack.org/common/api/v1.0" VERSIONS = { "v1.0": { "version": { @@ -235,7 +235,7 @@ class VersionsXMLSerializer(wsgi.XMLDictSerializer): def _versions_to_xml(self, versions, name="versions", xmlns=None): root = self._xml_doc.createElement(name) - root.setAttribute("xmlns", "%sv1.0" % OS_XMLNS_BASE) + root.setAttribute("xmlns", OS_XMLNS_BASE) root.setAttribute("xmlns:atom", ATOM_XMLNS) for version in versions: @@ -256,7 +256,7 @@ class VersionsXMLSerializer(wsgi.XMLDictSerializer): def _create_version_node(self, version, create_ns=False): version_node = self._xml_doc.createElement('version') if create_ns: - xmlns = "%s%s" % (OS_XMLNS_BASE, version['id']) + xmlns = OS_XMLNS_BASE xmlns_atom = "http://www.w3.org/2005/Atom" version_node.setAttribute('xmlns', xmlns) version_node.setAttribute('xmlns:atom', xmlns_atom) @@ -296,7 +296,7 @@ class VersionsXMLSerializer(wsgi.XMLDictSerializer): def multi(self, data): self._xml_doc = minidom.Document() node = self._versions_to_xml(data['choices'], 'choices', - xmlns="%sv1.0" % OS_XMLNS_BASE) + xmlns=OS_XMLNS_BASE) return self.to_xml_string(node) diff --git a/nova/tests/api/openstack/test_versions.py b/nova/tests/api/openstack/test_versions.py index 3f7867ff4..bc9000900 100644 --- a/nova/tests/api/openstack/test_versions.py +++ b/nova/tests/api/openstack/test_versions.py @@ -173,8 +173,7 @@ class VersionsTest(test.TestCase): self.assertEqual(res.content_type, "application/xml") root = xml.etree.ElementTree.XML(res.body) self.assertEqual(root.tag.split('}')[1], "version") - self.assertEqual(root.tag.split('}')[0].strip('{'), - "%sv1.0" % OS_XMLNS_BASE) + self.assertEqual(root.tag.split('}')[0].strip('{'), OS_XMLNS_BASE) children = list(root) media_types = children[0] @@ -188,7 +187,7 @@ class VersionsTest(test.TestCase): expected = """ @@ -224,7 +223,7 @@ class VersionsTest(test.TestCase): expected = """ @@ -259,7 +258,7 @@ class VersionsTest(test.TestCase): self.assertEqual(res.content_type, "application/xml") expected = """ - + @@ -451,7 +450,7 @@ class VersionsTest(test.TestCase): self.assertEqual(res.content_type, "application/xml") expected = """ - + Date: Wed, 27 Jul 2011 15:29:15 -0400 Subject: update everything to use global VERSIONS --- nova/api/openstack/versions.py | 49 +++++++++++-------------------- nova/tests/api/openstack/test_versions.py | 18 ++++++------ 2 files changed, 26 insertions(+), 41 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/versions.py b/nova/api/openstack/versions.py index b38c48939..32ee64339 100644 --- a/nova/api/openstack/versions.py +++ b/nova/api/openstack/versions.py @@ -143,20 +143,14 @@ class Versions(wsgi.Resource): return self._versions_multi_choice(request) def _versions_list(self, request): - version_objs = [ - { - "id": "v1.1", - "status": "CURRENT", - #TODO(wwolf) get correct value for these - "updated": "2011-07-18T11:30:00Z", - }, - { - "id": "v1.0", - "status": "DEPRECATED", - #TODO(wwolf) get correct value for these - "updated": "2010-10-09T11:30:00Z", - }, - ] + version_objs = [] + for version in VERSIONS: + version = VERSIONS[version]['version'] + version_objs.append({ + "id": version['id'], + "status": version['status'], + "updated": version['updated'], + }) builder = nova.api.openstack.views.versions.get_view_builder(request) versions = [builder.build(version) for version in version_objs] @@ -164,28 +158,19 @@ class Versions(wsgi.Resource): def _versions_multi_choice(self, request): #TODO - version_objs = [ - { - "id": "v1.1", - "status": "CURRENT", + version_objs = [] + for version in VERSIONS: + version = VERSIONS[version]['version'] + version_objs.append({ + "id": version['id'], + "status": version['status'], "links": [ { - "rel": "self", + "rel": "self" } ], - "media-types": VERSIONS['v1.1']['version']['media-types'], - }, - { - "id": "v1.0", - "status": "DEPRECATED", - "links": [ - { - "rel": "self", - } - ], - "media-types": VERSIONS['v1.0']['version']['media-types'], - }, - ] + "media-types": version['media-types'] + }) builder = nova.api.openstack.views.versions.get_view_builder(request) choices = [ diff --git a/nova/tests/api/openstack/test_versions.py b/nova/tests/api/openstack/test_versions.py index bc9000900..f059b140d 100644 --- a/nova/tests/api/openstack/test_versions.py +++ b/nova/tests/api/openstack/test_versions.py @@ -53,7 +53,7 @@ class VersionsTest(test.TestCase): { "id": "v1.1", "status": "CURRENT", - "updated": "2011-07-18T11:30:00Z", + "updated": "2011-01-21T11:33:21Z", "links": [ { "rel": "self", @@ -63,7 +63,7 @@ class VersionsTest(test.TestCase): { "id": "v1.0", "status": "DEPRECATED", - "updated": "2010-10-09T11:30:00Z", + "updated": "2011-01-21T11:33:21Z", "links": [ { "rel": "self", @@ -259,11 +259,11 @@ class VersionsTest(test.TestCase): expected = """ - + + updated="2011-01-21T11:33:21Z"> """.replace(" ", "").replace("\n", "") % (OS_XMLNS_BASE, @@ -357,7 +357,7 @@ class VersionsTest(test.TestCase): expected = """ Available API Versions - 2011-07-18T11:30:00Z + 2011-01-21T11:33:21Z http://localhost/ Rackspace @@ -367,19 +367,19 @@ class VersionsTest(test.TestCase): http://localhost/v1.1/ Version v1.1 - 2011-07-18T11:30:00Z + 2011-01-21T11:33:21Z - Version v1.1 CURRENT (2011-07-18T11:30:00Z) + Version v1.1 CURRENT (2011-01-21T11:33:21Z) http://localhost/v1.0/ Version v1.0 - 2010-10-09T11:30:00Z + 2011-01-21T11:33:21Z - Version v1.0 DEPRECATED (2010-10-09T11:30:00Z) + Version v1.0 DEPRECATED (2011-01-21T11:33:21Z) -- cgit From f2d8e91b83ff3a3bd1e2f3c53c25a418a578cd27 Mon Sep 17 00:00:00 2001 From: William Wolf Date: Wed, 27 Jul 2011 16:34:02 -0400 Subject: moved rest of build logic into builder --- nova/api/openstack/versions.py | 42 ++-------------------------- nova/api/openstack/views/versions.py | 46 +++++++++++++++++++++---------- nova/tests/api/openstack/test_versions.py | 31 +++++++++++++-------- 3 files changed, 54 insertions(+), 65 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/versions.py b/nova/api/openstack/versions.py index 32ee64339..fd0ee46b7 100644 --- a/nova/api/openstack/versions.py +++ b/nova/api/openstack/versions.py @@ -135,49 +135,13 @@ class Versions(wsgi.Resource): def dispatch(self, request, *args): """Respond to a request for all OpenStack API versions.""" + builder = nova.api.openstack.views.versions.get_view_builder(request) if request.path == '/': # List Versions - return self._versions_list(request) + return builder.build(VERSIONS) else: # Versions Multiple Choice - return self._versions_multi_choice(request) - - def _versions_list(self, request): - version_objs = [] - for version in VERSIONS: - version = VERSIONS[version]['version'] - version_objs.append({ - "id": version['id'], - "status": version['status'], - "updated": version['updated'], - }) - - builder = nova.api.openstack.views.versions.get_view_builder(request) - versions = [builder.build(version) for version in version_objs] - return dict(versions=versions) - - def _versions_multi_choice(self, request): - #TODO - version_objs = [] - for version in VERSIONS: - version = VERSIONS[version]['version'] - version_objs.append({ - "id": version['id'], - "status": version['status'], - "links": [ - { - "rel": "self" - } - ], - "media-types": version['media-types'] - }) - - builder = nova.api.openstack.views.versions.get_view_builder(request) - choices = [ - builder.build_choices(version, request) - for version in version_objs] - - return dict(choices=choices) + return builder.build_choices(VERSIONS, request) class VersionV10(object): diff --git a/nova/api/openstack/views/versions.py b/nova/api/openstack/views/versions.py index 97e35c983..87ec251e6 100644 --- a/nova/api/openstack/views/versions.py +++ b/nova/api/openstack/views/versions.py @@ -31,21 +31,37 @@ class ViewBuilder(object): """ self.base_url = base_url - def build_choices(self, version_data, request): - version_data['links'][0]['href'] = self._build_versioned_link(request, - version_data['id']) - return version_data - - def build(self, version_data): - """Generic method used to generate a version entity.""" - version = { - "id": version_data["id"], - "status": version_data["status"], - "updated": version_data["updated"], - "links": self._build_links(version_data), - } - - return version + def build_choices(self, VERSIONS, request): + version_objs = [] + for version in VERSIONS: + version = VERSIONS[version]['version'] + version_objs.append({ + "id": version['id'], + "status": version['status'], + "links": [ + { + "rel": "self", + "href": self._build_versioned_link(request, + version['id']) + } + ], + "media-types": version['media-types'] + }) + + return dict(choices=version_objs) + + def build(self, VERSIONS): + version_objs = [] + for version in VERSIONS: + version = VERSIONS[version]['version'] + version_objs.append({ + "id": version['id'], + "status": version['status'], + "updated": version['updated'], + "links": self._build_links(version), + }) + + return dict(versions=version_objs) def _build_versioned_link(self, req, version): return '%s://%s/%s%s' % (req.scheme, req.host, version, req.path) diff --git a/nova/tests/api/openstack/test_versions.py b/nova/tests/api/openstack/test_versions.py index f059b140d..6e4042b3f 100644 --- a/nova/tests/api/openstack/test_versions.py +++ b/nova/tests/api/openstack/test_versions.py @@ -529,20 +529,29 @@ class VersionsTest(test.TestCase): base_url = "http://example.org/" version_data = { - "id": "3.2.1", - "status": "CURRENT", - "updated": "2011-07-18T11:30:00Z"} + "v3.2.1": { + "version": { + "id": "3.2.1", + "status": "CURRENT", + "updated": "2011-07-18T11:30:00Z", + } + } + } expected = { - "id": "3.2.1", - "status": "CURRENT", - "updated": "2011-07-18T11:30:00Z", - "links": [ + "versions": [ { - "rel": "self", - "href": "http://example.org/3.2.1/", - }, - ], + "id": "3.2.1", + "status": "CURRENT", + "updated": "2011-07-18T11:30:00Z", + "links": [ + { + "rel": "self", + "href": "http://example.org/3.2.1/", + }, + ], + } + ] } builder = views.versions.ViewBuilder(base_url) -- cgit From 9c220b1c4547ad2cdd6110fa029b6f9478bae99f Mon Sep 17 00:00:00 2001 From: William Wolf Date: Wed, 27 Jul 2011 16:54:09 -0400 Subject: added test for accept header of atom+xml on 300 responses to make sure it defaults back to json, and reworked some of the logic to make how this happens clearer --- nova/api/openstack/versions.py | 3 ++- nova/tests/api/openstack/test_versions.py | 10 ++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/api/openstack/versions.py b/nova/api/openstack/versions.py index fd0ee46b7..02b0e1df8 100644 --- a/nova/api/openstack/versions.py +++ b/nova/api/openstack/versions.py @@ -159,7 +159,8 @@ class VersionsRequestDeserializer(wsgi.RequestDeserializer): supported_content_types = list(self.supported_content_types) if request.path != '/': # Remove atom+xml accept type for 300 responses - del supported_content_types[2] + if 'application/atom+xml' in supported_content_types: + supported_content_types.remove('application/atom+xml') return request.best_match_content_type(supported_content_types) diff --git a/nova/tests/api/openstack/test_versions.py b/nova/tests/api/openstack/test_versions.py index 6e4042b3f..63bca7066 100644 --- a/nova/tests/api/openstack/test_versions.py +++ b/nova/tests/api/openstack/test_versions.py @@ -471,6 +471,16 @@ class VersionsTest(test.TestCase): """.replace(" ", "").replace("\n", "") % (OS_XMLNS_BASE, ATOM_XMLNS) + def test_multi_choice_server_atom(self): + """ + Make sure multi choice responses do not have content-type + application/atom+xml (should use default of json) + """ + req = webob.Request.blank('/servers/2') + req.accept = "application/atom+xml" + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 300) + self.assertEqual(res.content_type, "application/json") def test_multi_choice_server(self): req = webob.Request.blank('/servers/2') -- cgit From 77d06c7c82bfafd956f1108b2adbcb378628511f Mon Sep 17 00:00:00 2001 From: William Wolf Date: Wed, 27 Jul 2011 17:09:17 -0400 Subject: utilize _create_link_nodes base class function --- nova/api/openstack/versions.py | 11 +++-------- nova/api/openstack/wsgi.py | 2 ++ 2 files changed, 5 insertions(+), 8 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/versions.py b/nova/api/openstack/versions.py index 02b0e1df8..928cf467a 100644 --- a/nova/api/openstack/versions.py +++ b/nova/api/openstack/versions.py @@ -220,14 +220,9 @@ class VersionsXMLSerializer(wsgi.XMLDictSerializer): media_types = self._create_media_types(version['media-types']) version_node.appendChild(media_types) - for link in version['links']: - link_node = self._xml_doc.createElement('atom:link') - link_node.setAttribute('rel', link['rel']) - link_node.setAttribute('href', link['href']) - if 'type' in link: - link_node.setAttribute('type', link['type']) - - version_node.appendChild(link_node) + link_nodes = self._create_link_nodes(self._xml_doc, version['links']) + for link in link_nodes: + version_node.appendChild(link) return version_node diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py index a28443d12..ca502021d 100644 --- a/nova/api/openstack/wsgi.py +++ b/nova/api/openstack/wsgi.py @@ -387,6 +387,8 @@ class XMLDictSerializer(DictSerializer): link_node = xml_doc.createElement('atom:link') link_node.setAttribute('rel', link['rel']) link_node.setAttribute('href', link['href']) + if 'type' in link: + link_node.setAttribute('type', link['type']) link_nodes.append(link_node) return link_nodes -- cgit From 22f502cdca94a20ebb061f434a9a78789a3b165d Mon Sep 17 00:00:00 2001 From: William Wolf Date: Wed, 27 Jul 2011 17:19:59 -0400 Subject: move viewbuilder and serializer tests into their own test cases --- nova/tests/api/openstack/test_versions.py | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'nova') diff --git a/nova/tests/api/openstack/test_versions.py b/nova/tests/api/openstack/test_versions.py index 63bca7066..7bb762c71 100644 --- a/nova/tests/api/openstack/test_versions.py +++ b/nova/tests/api/openstack/test_versions.py @@ -535,6 +535,8 @@ class VersionsTest(test.TestCase): self.assertDictMatch(expected, json.loads(res.body)) + +class VersionsViewBuilderTests(VersionsTest): def test_view_builder(self): base_url = "http://example.org/" @@ -580,6 +582,8 @@ class VersionsTest(test.TestCase): self.assertEqual(actual, expected) + +class VersionsSerializerTests(VersionsTest): def test_versions_list_xml_serializer(self): versions_data = { 'versions': [ -- cgit From 9cc71286d6e5339e42d6957570bfc02ea71353fe Mon Sep 17 00:00:00 2001 From: William Wolf Date: Wed, 27 Jul 2011 17:21:14 -0400 Subject: pep8 issue --- nova/tests/api/openstack/test_versions.py | 1 + 1 file changed, 1 insertion(+) (limited to 'nova') diff --git a/nova/tests/api/openstack/test_versions.py b/nova/tests/api/openstack/test_versions.py index 7bb762c71..1f2c1deef 100644 --- a/nova/tests/api/openstack/test_versions.py +++ b/nova/tests/api/openstack/test_versions.py @@ -471,6 +471,7 @@ class VersionsTest(test.TestCase): """.replace(" ", "").replace("\n", "") % (OS_XMLNS_BASE, ATOM_XMLNS) + def test_multi_choice_server_atom(self): """ Make sure multi choice responses do not have content-type -- cgit From 634702e9a6813b8793a82ddd87d24690b05ffc1e Mon Sep 17 00:00:00 2001 From: William Wolf Date: Wed, 27 Jul 2011 17:59:05 -0400 Subject: stub out VERSIONS for the tests --- nova/tests/api/openstack/test_versions.py | 75 ++++++++++++++++++++++++++++++- 1 file changed, 74 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/tests/api/openstack/test_versions.py b/nova/tests/api/openstack/test_versions.py index 1f2c1deef..dfbc67f15 100644 --- a/nova/tests/api/openstack/test_versions.py +++ b/nova/tests/api/openstack/test_versions.py @@ -29,7 +29,78 @@ from nova.api.openstack import views ATOM_XMLNS = versions.ATOM_XMLNS OS_XMLNS_BASE = versions.OS_XMLNS_BASE -VERSIONS = versions.VERSIONS +VERSIONS = { + "v1.0": { + "version": { + "id": "v1.0", + "status": "DEPRECATED", + "updated": "2011-01-21T11:33:21Z", + "links": [ + { + "rel": "self", + "href": "http://servers.api.openstack.org/v1.0/" + }, + { + "rel": "describedby", + "type": "application/pdf", + "href": "http://docs.rackspacecloud.com/" + "servers/api/v1.0/cs-devguide-20110125.pdf" + }, + { + "rel": "describedby", + "type": "application/vnd.sun.wadl+xml", + "href": "http://docs.rackspacecloud.com/" + "servers/api/v1.0/application.wadl" + }, + ], + "media-types": [ + { + "base": "application/xml", + "type": "application/vnd.openstack.compute-v1.0+xml" + }, + { + "base": "application/json", + "type": "application/vnd.openstack.compute-v1.0+json" + } + ], + }, + }, + "v1.1": { + "version": { + "id": "v1.1", + "status": "CURRENT", + "updated": "2011-01-21T11:33:21Z", + "links": [ + { + "rel": "self", + "href": "http://servers.api.openstack.org/v1.1/" + }, + { + "rel": "describedby", + "type": "application/pdf", + "href": "http://docs.rackspacecloud.com/" + "servers/api/v1.1/cs-devguide-20110125.pdf" + }, + { + "rel": "describedby", + "type": "application/vnd.sun.wadl+xml", + "href": "http://docs.rackspacecloud.com/" + "servers/api/v1.1/application.wadl" + }, + ], + "media-types": [ + { + "base": "application/xml", + "type": "application/vnd.openstack.compute-v1.1+xml" + }, + { + "base": "application/json", + "type": "application/vnd.openstack.compute-v1.1+json" + } + ], + }, + }, +} class VersionsTest(test.TestCase): @@ -38,6 +109,8 @@ class VersionsTest(test.TestCase): self.context = context.get_admin_context() self.stubs = stubout.StubOutForTesting() fakes.stub_out_auth(self.stubs) + #Stub out VERSIONS + versions.VERSIONS = VERSIONS def tearDown(self): super(VersionsTest, self).tearDown() -- cgit From 03aac3ffd546ab1528b73ee36c8632f30ed8af2f Mon Sep 17 00:00:00 2001 From: William Wolf Date: Wed, 27 Jul 2011 18:07:01 -0400 Subject: use ATOM_XMLNS everywhere --- nova/api/openstack/versions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/api/openstack/versions.py b/nova/api/openstack/versions.py index 928cf467a..95681b788 100644 --- a/nova/api/openstack/versions.py +++ b/nova/api/openstack/versions.py @@ -207,7 +207,7 @@ class VersionsXMLSerializer(wsgi.XMLDictSerializer): version_node = self._xml_doc.createElement('version') if create_ns: xmlns = OS_XMLNS_BASE - xmlns_atom = "http://www.w3.org/2005/Atom" + xmlns_atom = ATOM_XMLNS version_node.setAttribute('xmlns', xmlns) version_node.setAttribute('xmlns:atom', xmlns_atom) -- cgit From 40683658929e38905c87e72988c797180797501e Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Thu, 28 Jul 2011 08:34:27 -0700 Subject: make payload json serializable --- nova/notifier/api.py | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'nova') diff --git a/nova/notifier/api.py b/nova/notifier/api.py index 98969fd3e..45105d00f 100644 --- a/nova/notifier/api.py +++ b/nova/notifier/api.py @@ -80,6 +80,12 @@ def notify(publisher_id, event_type, priority, payload): if priority not in log_levels: raise BadPriorityException( _('%s not in valid priorities' % priority)) + + # Ensure everything is JSON serializable. + for k, v in payload.iteritems(): + if not isinstance(v, (basestring, int, long, float)): + payload[k] = str(v) + driver = utils.import_object(FLAGS.notification_driver) msg = dict(message_id=str(uuid.uuid4()), publisher_id=publisher_id, -- cgit From 1a97658a4bf7d0dab562f8b8c22a430f2da27f10 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Thu, 28 Jul 2011 08:44:01 -0700 Subject: unicode instead of str() --- nova/notifier/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/notifier/api.py b/nova/notifier/api.py index 45105d00f..8eea2a032 100644 --- a/nova/notifier/api.py +++ b/nova/notifier/api.py @@ -84,7 +84,7 @@ def notify(publisher_id, event_type, priority, payload): # Ensure everything is JSON serializable. for k, v in payload.iteritems(): if not isinstance(v, (basestring, int, long, float)): - payload[k] = str(v) + payload[k] = unicode(v) driver = utils.import_object(FLAGS.notification_driver) msg = dict(message_id=str(uuid.uuid4()), -- cgit From 71414e65333692956023647b55be06de6a73f11f Mon Sep 17 00:00:00 2001 From: William Wolf Date: Thu, 28 Jul 2011 14:59:28 -0400 Subject: use wsgi XMLNS/ATOM vars --- nova/api/openstack/versions.py | 14 ++++++-------- nova/api/openstack/wsgi.py | 1 + nova/tests/api/openstack/test_versions.py | 32 +++++++++++++++---------------- 3 files changed, 22 insertions(+), 25 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/versions.py b/nova/api/openstack/versions.py index 95681b788..292043b94 100644 --- a/nova/api/openstack/versions.py +++ b/nova/api/openstack/versions.py @@ -24,8 +24,6 @@ import nova.api.openstack.views.versions from nova.api.openstack import wsgi -ATOM_XMLNS = "http://www.w3.org/2005/Atom" -OS_XMLNS_BASE = "http://docs.openstack.org/common/api/v1.0" VERSIONS = { "v1.0": { "version": { @@ -185,8 +183,8 @@ class VersionsXMLSerializer(wsgi.XMLDictSerializer): def _versions_to_xml(self, versions, name="versions", xmlns=None): root = self._xml_doc.createElement(name) - root.setAttribute("xmlns", OS_XMLNS_BASE) - root.setAttribute("xmlns:atom", ATOM_XMLNS) + root.setAttribute("xmlns", wsgi.XMLNS_V11) + root.setAttribute("xmlns:atom", wsgi.XMLNS_ATOM) for version in versions: root.appendChild(self._create_version_node(version)) @@ -206,8 +204,8 @@ class VersionsXMLSerializer(wsgi.XMLDictSerializer): def _create_version_node(self, version, create_ns=False): version_node = self._xml_doc.createElement('version') if create_ns: - xmlns = OS_XMLNS_BASE - xmlns_atom = ATOM_XMLNS + xmlns = wsgi.XMLNS_V11 + xmlns_atom = wsgi.XMLNS_ATOM version_node.setAttribute('xmlns', xmlns) version_node.setAttribute('xmlns:atom', xmlns_atom) @@ -241,7 +239,7 @@ class VersionsXMLSerializer(wsgi.XMLDictSerializer): def multi(self, data): self._xml_doc = minidom.Document() node = self._versions_to_xml(data['choices'], 'choices', - xmlns=OS_XMLNS_BASE) + xmlns=wsgi.XMLNS_V11) return self.to_xml_string(node) @@ -257,7 +255,7 @@ class VersionsAtomSerializer(wsgi.XMLDictSerializer): def __init__(self, metadata=None, xmlns=None): self.metadata = metadata or {} if not xmlns: - self.xmlns = ATOM_XMLNS + self.xmlns = wsgi.XMLNS_ATOM else: self.xmlns = xmlns diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py index ca502021d..c6ece7d45 100644 --- a/nova/api/openstack/wsgi.py +++ b/nova/api/openstack/wsgi.py @@ -13,6 +13,7 @@ from nova import wsgi XMLNS_V10 = 'http://docs.rackspacecloud.com/servers/api/v1.0' XMLNS_V11 = 'http://docs.openstack.org/compute/api/v1.1' + XMLNS_ATOM = 'http://www.w3.org/2005/Atom' LOG = logging.getLogger('nova.api.openstack.wsgi') diff --git a/nova/tests/api/openstack/test_versions.py b/nova/tests/api/openstack/test_versions.py index dfbc67f15..5d3b3d743 100644 --- a/nova/tests/api/openstack/test_versions.py +++ b/nova/tests/api/openstack/test_versions.py @@ -26,9 +26,8 @@ from nova import test from nova.tests.api.openstack import fakes from nova.api.openstack import versions from nova.api.openstack import views +from nova.api.openstack import wsgi -ATOM_XMLNS = versions.ATOM_XMLNS -OS_XMLNS_BASE = versions.OS_XMLNS_BASE VERSIONS = { "v1.0": { "version": { @@ -246,7 +245,7 @@ class VersionsTest(test.TestCase): self.assertEqual(res.content_type, "application/xml") root = xml.etree.ElementTree.XML(res.body) self.assertEqual(root.tag.split('}')[1], "version") - self.assertEqual(root.tag.split('}')[0].strip('{'), OS_XMLNS_BASE) + self.assertEqual(root.tag.split('}')[0].strip('{'), wsgi.XMLNS_V11) children = list(root) media_types = children[0] @@ -282,7 +281,7 @@ class VersionsTest(test.TestCase): api/v1.0/application.wadl" rel="describedby" type="application/vnd.sun.wadl+xml"/> - """.replace(" ", "").replace("\n", "") % OS_XMLNS_BASE + """.replace(" ", "").replace("\n", "") % wsgi.XMLNS_V11 actual = res.body.replace(" ", "").replace("\n", "") self.assertEqual(expected, actual) @@ -318,7 +317,7 @@ class VersionsTest(test.TestCase): api/v1.1/application.wadl" rel="describedby" type="application/vnd.sun.wadl+xml"/> - """.replace(" ", "").replace("\n", "") % OS_XMLNS_BASE + """.replace(" ", "").replace("\n", "") % wsgi.XMLNS_V11 actual = res.body.replace(" ", "").replace("\n", "") self.assertEqual(expected, actual) @@ -339,8 +338,8 @@ class VersionsTest(test.TestCase): updated="2011-01-21T11:33:21Z"> - """.replace(" ", "").replace("\n", "") % (OS_XMLNS_BASE, - ATOM_XMLNS) + """.replace(" ", "").replace("\n", "") % (wsgi.XMLNS_V11, + wsgi.XMLNS_ATOM) actual = res.body.replace(" ", "").replace("\n", "") @@ -542,8 +541,8 @@ class VersionsTest(test.TestCase): - """.replace(" ", "").replace("\n", "") % (OS_XMLNS_BASE, - ATOM_XMLNS) + """.replace(" ", "").replace("\n", "") % (wsgi.XMLNS_V11, + wsgi.XMLNS_ATOM) def test_multi_choice_server_atom(self): """ @@ -610,7 +609,7 @@ class VersionsTest(test.TestCase): self.assertDictMatch(expected, json.loads(res.body)) -class VersionsViewBuilderTests(VersionsTest): +class VersionsViewBuilderTests(test.TestCase): def test_view_builder(self): base_url = "http://example.org/" @@ -657,7 +656,7 @@ class VersionsViewBuilderTests(VersionsTest): self.assertEqual(actual, expected) -class VersionsSerializerTests(VersionsTest): +class VersionsSerializerTests(test.TestCase): def test_versions_list_xml_serializer(self): versions_data = { 'versions': [ @@ -680,7 +679,7 @@ class VersionsSerializerTests(VersionsTest): root = xml.etree.ElementTree.XML(response) self.assertEqual(root.tag.split('}')[1], "versions") - self.assertEqual(root.tag.split('}')[0].strip('{'), OS_XMLNS_BASE) + self.assertEqual(root.tag.split('}')[0].strip('{'), wsgi.XMLNS_V11) version = list(root)[0] self.assertEqual(version.tag.split('}')[1], "version") self.assertEqual(version.get('id'), @@ -691,7 +690,7 @@ class VersionsSerializerTests(VersionsTest): link = list(version)[0] self.assertEqual(link.tag.split('}')[1], "link") - self.assertEqual(link.tag.split('}')[0].strip('{'), ATOM_XMLNS) + self.assertEqual(link.tag.split('}')[0].strip('{'), wsgi.XMLNS_ATOM) for key, val in versions_data['versions'][0]['links'][0].items(): self.assertEqual(link.get(key), val) @@ -718,7 +717,7 @@ class VersionsSerializerTests(VersionsTest): root = xml.etree.ElementTree.XML(response) self.assertEqual(root.tag.split('}')[1], "choices") - self.assertEqual(root.tag.split('}')[0].strip('{'), OS_XMLNS_BASE) + self.assertEqual(root.tag.split('}')[0].strip('{'), wsgi.XMLNS_V11) version = list(root)[0] self.assertEqual(version.tag.split('}')[1], "version") self.assertEqual(version.get('id'), versions_data['choices'][0]['id']) @@ -739,7 +738,7 @@ class VersionsSerializerTests(VersionsTest): link = list(version)[1] self.assertEqual(link.tag.split('}')[1], "link") - self.assertEqual(link.tag.split('}')[0].strip('{'), ATOM_XMLNS) + self.assertEqual(link.tag.split('}')[0].strip('{'), wsgi.XMLNS_ATOM) for key, val in versions_data['choices'][0]['links'][0].items(): self.assertEqual(link.get(key), val) @@ -785,8 +784,7 @@ class VersionsSerializerTests(VersionsTest): root = xml.etree.ElementTree.XML(response) self.assertEqual(root.tag.split('}')[1], "version") - self.assertEqual(root.tag.split('}')[0].strip('{'), - "http://docs.openstack.org/common/api/v1.0") + self.assertEqual(root.tag.split('}')[0].strip('{'), wsgi.XMLNS_V11) children = list(root) media_types = children[0] -- cgit From 1c2ac1e7646d1432f57104c6ee3d1fa434387741 Mon Sep 17 00:00:00 2001 From: William Wolf Date: Thu, 28 Jul 2011 15:48:30 -0400 Subject: refactoring and make self links correct (not hard coded) --- nova/api/openstack/versions.py | 136 +++++++++++----------- nova/api/openstack/views/versions.py | 33 ++++-- nova/tests/api/openstack/test_versions.py | 186 +++++++++++++++--------------- 3 files changed, 178 insertions(+), 177 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/versions.py b/nova/api/openstack/versions.py index 292043b94..b546462d4 100644 --- a/nova/api/openstack/versions.py +++ b/nova/api/openstack/versions.py @@ -26,74 +26,70 @@ from nova.api.openstack import wsgi VERSIONS = { "v1.0": { - "version": { - "id": "v1.0", - "status": "DEPRECATED", - "updated": "2011-01-21T11:33:21Z", - "links": [ - { - "rel": "self", - "href": "http://servers.api.openstack.org/v1.0/" - }, - { - "rel": "describedby", - "type": "application/pdf", - "href": "http://docs.rackspacecloud.com/" - "servers/api/v1.0/cs-devguide-20110125.pdf" - }, - { - "rel": "describedby", - "type": "application/vnd.sun.wadl+xml", - "href": "http://docs.rackspacecloud.com/" - "servers/api/v1.0/application.wadl" - }, - ], - "media-types": [ - { - "base": "application/xml", - "type": "application/vnd.openstack.compute-v1.0+xml" - }, - { - "base": "application/json", - "type": "application/vnd.openstack.compute-v1.0+json" - } - ], - }, + "id": "v1.0", + "status": "DEPRECATED", + "updated": "2011-01-21T11:33:21Z", + "links": [ + { + "rel": "self", + "href": "" + }, + { + "rel": "describedby", + "type": "application/pdf", + "href": "http://docs.rackspacecloud.com/" + "servers/api/v1.0/cs-devguide-20110125.pdf" + }, + { + "rel": "describedby", + "type": "application/vnd.sun.wadl+xml", + "href": "http://docs.rackspacecloud.com/" + "servers/api/v1.0/application.wadl" + }, + ], + "media-types": [ + { + "base": "application/xml", + "type": "application/vnd.openstack.compute-v1.0+xml" + }, + { + "base": "application/json", + "type": "application/vnd.openstack.compute-v1.0+json" + } + ], }, "v1.1": { - "version": { - "id": "v1.1", - "status": "CURRENT", - "updated": "2011-01-21T11:33:21Z", - "links": [ - { - "rel": "self", - "href": "http://servers.api.openstack.org/v1.1/" - }, - { - "rel": "describedby", - "type": "application/pdf", - "href": "http://docs.rackspacecloud.com/" - "servers/api/v1.1/cs-devguide-20110125.pdf" - }, - { - "rel": "describedby", - "type": "application/vnd.sun.wadl+xml", - "href": "http://docs.rackspacecloud.com/" - "servers/api/v1.1/application.wadl" - }, - ], - "media-types": [ - { - "base": "application/xml", - "type": "application/vnd.openstack.compute-v1.1+xml" - }, - { - "base": "application/json", - "type": "application/vnd.openstack.compute-v1.1+json" - } - ], - }, + "id": "v1.1", + "status": "CURRENT", + "updated": "2011-01-21T11:33:21Z", + "links": [ + { + "rel": "self", + "href": "" + }, + { + "rel": "describedby", + "type": "application/pdf", + "href": "http://docs.rackspacecloud.com/" + "servers/api/v1.1/cs-devguide-20110125.pdf" + }, + { + "rel": "describedby", + "type": "application/vnd.sun.wadl+xml", + "href": "http://docs.rackspacecloud.com/" + "servers/api/v1.1/application.wadl" + }, + ], + "media-types": [ + { + "base": "application/xml", + "type": "application/vnd.openstack.compute-v1.1+xml" + }, + { + "base": "application/json", + "type": "application/vnd.openstack.compute-v1.1+json" + } + ], }, } @@ -136,7 +132,7 @@ class Versions(wsgi.Resource): builder = nova.api.openstack.views.versions.get_view_builder(request) if request.path == '/': # List Versions - return builder.build(VERSIONS) + return builder.build_versions(VERSIONS) else: # Versions Multiple Choice return builder.build_choices(VERSIONS, request) @@ -144,12 +140,14 @@ class Versions(wsgi.Resource): class VersionV10(object): def show(self, req): - return VERSIONS['v1.0'] + builder = nova.api.openstack.views.versions.get_view_builder(req) + return builder.build_version(VERSIONS['v1.0']) class VersionV11(object): def show(self, req): - return VERSIONS['v1.1'] + builder = nova.api.openstack.views.versions.get_view_builder(req) + return builder.build_version(VERSIONS['v1.1']) class VersionsRequestDeserializer(wsgi.RequestDeserializer): diff --git a/nova/api/openstack/views/versions.py b/nova/api/openstack/views/versions.py index 87ec251e6..7673ffe9e 100644 --- a/nova/api/openstack/views/versions.py +++ b/nova/api/openstack/views/versions.py @@ -31,18 +31,17 @@ class ViewBuilder(object): """ self.base_url = base_url - def build_choices(self, VERSIONS, request): + def build_choices(self, VERSIONS, req): version_objs = [] for version in VERSIONS: - version = VERSIONS[version]['version'] + version = VERSIONS[version] version_objs.append({ "id": version['id'], "status": version['status'], "links": [ { "rel": "self", - "href": self._build_versioned_link(request, - version['id']) + "href": self.generate_href(version['id'], req.path) } ], "media-types": version['media-types'] @@ -50,10 +49,10 @@ class ViewBuilder(object): return dict(choices=version_objs) - def build(self, VERSIONS): + def build_versions(self, versions): version_objs = [] - for version in VERSIONS: - version = VERSIONS[version]['version'] + for version in versions: + version = versions[version] version_objs.append({ "id": version['id'], "status": version['status'], @@ -63,8 +62,13 @@ class ViewBuilder(object): return dict(versions=version_objs) - def _build_versioned_link(self, req, version): - return '%s://%s/%s%s' % (req.scheme, req.host, version, req.path) + def build_version(self, version): + + for link in version['links']: + if link['rel'] == 'self': + link['href'] = self.base_url.rstrip('/') + '/' + + return dict(version=version) def _build_links(self, version_data): """Generate a container of links that refer to the provided version.""" @@ -73,12 +77,17 @@ class ViewBuilder(object): links = [ { "rel": "self", - "href": href, + "href": href }, ] return links - def generate_href(self, version_number): + def generate_href(self, version_number, path=None): """Create an url that refers to a specific version_number.""" - return os.path.join(self.base_url, version_number) + '/' + version_number = version_number.strip('/') + if path: + path = path.strip('/') + return os.path.join(self.base_url, version_number, path) + else: + return os.path.join(self.base_url, version_number) + '/' diff --git a/nova/tests/api/openstack/test_versions.py b/nova/tests/api/openstack/test_versions.py index 5d3b3d743..1373f2e39 100644 --- a/nova/tests/api/openstack/test_versions.py +++ b/nova/tests/api/openstack/test_versions.py @@ -30,74 +30,70 @@ from nova.api.openstack import wsgi VERSIONS = { "v1.0": { - "version": { - "id": "v1.0", - "status": "DEPRECATED", - "updated": "2011-01-21T11:33:21Z", - "links": [ - { - "rel": "self", - "href": "http://servers.api.openstack.org/v1.0/" - }, - { - "rel": "describedby", - "type": "application/pdf", - "href": "http://docs.rackspacecloud.com/" - "servers/api/v1.0/cs-devguide-20110125.pdf" - }, - { - "rel": "describedby", - "type": "application/vnd.sun.wadl+xml", - "href": "http://docs.rackspacecloud.com/" - "servers/api/v1.0/application.wadl" - }, - ], - "media-types": [ - { - "base": "application/xml", - "type": "application/vnd.openstack.compute-v1.0+xml" - }, - { - "base": "application/json", - "type": "application/vnd.openstack.compute-v1.0+json" - } - ], - }, + "id": "v1.0", + "status": "DEPRECATED", + "updated": "2011-01-21T11:33:21Z", + "links": [ + { + "rel": "self", + "href": "" + }, + { + "rel": "describedby", + "type": "application/pdf", + "href": "http://docs.rackspacecloud.com/" + "servers/api/v1.0/cs-devguide-20110125.pdf" + }, + { + "rel": "describedby", + "type": "application/vnd.sun.wadl+xml", + "href": "http://docs.rackspacecloud.com/" + "servers/api/v1.0/application.wadl" + }, + ], + "media-types": [ + { + "base": "application/xml", + "type": "application/vnd.openstack.compute-v1.0+xml" + }, + { + "base": "application/json", + "type": "application/vnd.openstack.compute-v1.0+json" + } + ], }, "v1.1": { - "version": { - "id": "v1.1", - "status": "CURRENT", - "updated": "2011-01-21T11:33:21Z", - "links": [ - { - "rel": "self", - "href": "http://servers.api.openstack.org/v1.1/" - }, - { - "rel": "describedby", - "type": "application/pdf", - "href": "http://docs.rackspacecloud.com/" - "servers/api/v1.1/cs-devguide-20110125.pdf" - }, - { - "rel": "describedby", - "type": "application/vnd.sun.wadl+xml", - "href": "http://docs.rackspacecloud.com/" - "servers/api/v1.1/application.wadl" - }, - ], - "media-types": [ - { - "base": "application/xml", - "type": "application/vnd.openstack.compute-v1.1+xml" - }, - { - "base": "application/json", - "type": "application/vnd.openstack.compute-v1.1+json" - } - ], - }, + "id": "v1.1", + "status": "CURRENT", + "updated": "2011-01-21T11:33:21Z", + "links": [ + { + "rel": "self", + "href": "" + }, + { + "rel": "describedby", + "type": "application/pdf", + "href": "http://docs.rackspacecloud.com/" + "servers/api/v1.1/cs-devguide-20110125.pdf" + }, + { + "rel": "describedby", + "type": "application/vnd.sun.wadl+xml", + "href": "http://docs.rackspacecloud.com/" + "servers/api/v1.1/application.wadl" + }, + ], + "media-types": [ + { + "base": "application/xml", + "type": "application/vnd.openstack.compute-v1.1+xml" + }, + { + "base": "application/json", + "type": "application/vnd.openstack.compute-v1.1+json" + } + ], }, } @@ -160,7 +156,7 @@ class VersionsTest(test.TestCase): "links": [ { "rel": "self", - "href": "http://servers.api.openstack.org/v1.0/" + "href": "http://localhost/v1.0/" }, { "rel": "describedby", @@ -206,7 +202,7 @@ class VersionsTest(test.TestCase): "links": [ { "rel": "self", - "href": "http://servers.api.openstack.org/v1.1/" + "href": "http://localhost/v1.1/" }, { "rel": "describedby", @@ -269,7 +265,7 @@ class VersionsTest(test.TestCase): type="application/vnd.openstack.compute-v1.0+json"/> - - About This Version 2011-01-21T11:33:21Z - http://servers.api.openstack.org/v1.0/ + http://localhost/v1.0/ Rackspace http://www.rackspace.com/ - + - http://servers.api.openstack.org/v1.0/ + http://localhost/v1.0/ Version v1.0 2011-01-21T11:33:21Z - About This Version 2011-01-21T11:33:21Z - http://servers.api.openstack.org/v1.1/ + http://localhost/v1.1/ Rackspace http://www.rackspace.com/ - + - http://servers.api.openstack.org/v1.1/ + http://localhost/v1.1/ Version v1.1 2011-01-21T11:33:21Z - - + @@ -539,7 +535,7 @@ class VersionsTest(test.TestCase): - + """.replace(" ", "").replace("\n", "") % (wsgi.XMLNS_V11, wsgi.XMLNS_ATOM) @@ -569,7 +565,7 @@ class VersionsTest(test.TestCase): "status": "CURRENT", "links": [ { - "href": "http://localhost:80/v1.1/servers/2", + "href": "http://localhost/v1.1/servers/2", "rel": "self", }, ], @@ -589,7 +585,7 @@ class VersionsTest(test.TestCase): "status": "DEPRECATED", "links": [ { - "href": "http://localhost:80/v1.0/servers/2", + "href": "http://localhost/v1.0/servers/2", "rel": "self", }, ], @@ -615,11 +611,9 @@ class VersionsViewBuilderTests(test.TestCase): version_data = { "v3.2.1": { - "version": { - "id": "3.2.1", - "status": "CURRENT", - "updated": "2011-07-18T11:30:00Z", - } + "id": "3.2.1", + "status": "CURRENT", + "updated": "2011-07-18T11:30:00Z", } } @@ -640,7 +634,7 @@ class VersionsViewBuilderTests(test.TestCase): } builder = views.versions.ViewBuilder(base_url) - output = builder.build(version_data) + output = builder.build_versions(version_data) self.assertEqual(output, expected) @@ -701,7 +695,7 @@ class VersionsSerializerTests(test.TestCase): "id": "2.7.1", "updated": "2011-07-18T11:30:00Z", "status": "DEPRECATED", - "media-types": VERSIONS['v1.1']['version']['media-types'], + "media-types": VERSIONS['v1.1']['media-types'], "links": [ { "rel": "self", @@ -751,7 +745,7 @@ class VersionsSerializerTests(test.TestCase): "links": [ { "rel": "self", - "href": "http://servers.api.openstack.org/v1.0/" + "href": "http://localhost/v1.0/" }, { "rel": "describedby", @@ -886,7 +880,7 @@ class VersionsSerializerTests(test.TestCase): "links": [ { "rel": "self", - "href": "http://servers.api.openstack.org/v1.1/" + "href": "http://localhost/v1.1/" }, { "rel": "describedby", @@ -936,7 +930,7 @@ class VersionsSerializerTests(test.TestCase): self.assertEqual(updated.tag.split('}')[1], 'updated') self.assertEqual(updated.text, '2011-01-21T11:33:21Z') self.assertEqual(id.tag.split('}')[1], 'id') - self.assertEqual(id.text, 'http://servers.api.openstack.org/v1.1/') + self.assertEqual(id.text, 'http://localhost/v1.1/') self.assertEqual(author.tag.split('}')[1], 'author') author_name = list(author)[0] @@ -947,7 +941,7 @@ class VersionsSerializerTests(test.TestCase): self.assertEqual(author_uri.text, 'http://www.rackspace.com/') self.assertEqual(link.get('href'), - 'http://servers.api.openstack.org/v1.1/') + 'http://localhost/v1.1/') self.assertEqual(link.get('rel'), 'self') self.assertEqual(entry.tag.split('}')[1], 'entry') @@ -960,7 +954,7 @@ class VersionsSerializerTests(test.TestCase): self.assertEqual(entry_id.tag.split('}')[1], "id") self.assertEqual(entry_id.text, - "http://servers.api.openstack.org/v1.1/") + "http://localhost/v1.1/") self.assertEqual(entry_title.tag.split('}')[1], "title") self.assertEqual(entry_title.get('type'), "text") self.assertEqual(entry_title.text, "Version v1.1") -- cgit From 8141ef4139fbf8512150ce970cea4dc4bee22e1a Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Thu, 28 Jul 2011 16:21:12 -0400 Subject: moving server backup to /servers//action instead of POST /images --- nova/api/openstack/create_instance_helper.py | 12 +- nova/api/openstack/images.py | 112 +++--------------- nova/api/openstack/servers.py | 133 +++++++++++++-------- nova/tests/api/openstack/test_images.py | 76 ------------ nova/tests/api/openstack/test_servers.py | 169 +++++++++++++++++++++++++++ 5 files changed, 280 insertions(+), 222 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index 73ff191e8..e165899e7 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -300,6 +300,7 @@ class ServerXMLDeserializer(wsgi.MetadataXMLDeserializer): action_deserializer = { 'createImage': self._action_create_image, + 'createBackup': self._action_create_image, }.get(action_name, self.default) action_data = action_deserializer(action_node) @@ -308,7 +309,16 @@ class ServerXMLDeserializer(wsgi.MetadataXMLDeserializer): def _action_create_image(self, node): data = {} - attributes = ['name', 'image_type', 'backup_type', 'rotation'] + value = node.getAttribute('name') + if value: + data['name'] = value + metadata_node = self.find_first_child_named(node, 'metadata') + data['metadata'] = self.extract_metadata(metadata_node) + return data + + def _action_create_image(self, node): + data = {} + attributes = ['name', 'backup_type', 'rotation'] for attribute in attributes: value = node.getAttribute(attribute) if value: diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index 517a51662..b3e16c997 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -102,72 +102,27 @@ class Controller(object): """Indicates that you must use a Controller subclass.""" raise NotImplementedError() - def _server_id_from_req(self, req, data): - raise NotImplementedError() - - def _get_extra_properties(self, req, data): - return {} - class ControllerV10(Controller): """Version 1.0 specific controller logic.""" def create(self, req, body): - """Snapshot or backup a server instance and save the image. - - Images now have an `image_type` associated with them, which can be - 'snapshot' or the backup type, like 'daily' or 'weekly'. - - If the image_type is backup-like, then the rotation factor can be - included and that will cause the oldest backups that exceed the - rotation factor to be deleted. - - :param req: `wsgi.Request` object - """ - def get_param(param): - try: - return body["image"][param] - except KeyError: - raise webob.exc.HTTPBadRequest(explanation="Missing required " - "param: %s" % param) - - context = req.environ['nova.context'] - content_type = req.get_content_type() - - if not body: - raise webob.exc.HTTPBadRequest() - - image_type = body["image"].get("image_type", "snapshot") + """Snapshot a server instance and save the image.""" + try: + image = body["image"] + except (KeyError, TypeError): + msg = _("Invalid image entity") + raise webob.exc.HTTPBadRequest(explanation=msg) try: - server_id = self._server_id_from_req(req, body) - except KeyError: - raise webob.exc.HTTPBadRequest() - - image_name = get_param("name") - props = self._get_extra_properties(req, body) - - if image_type == "snapshot": - image = self._compute_service.snapshot( - context, server_id, image_name, - extra_properties=props) - elif image_type == "backup": - # NOTE(sirp): Unlike snapshot, backup is not a customer facing - # API call; rather, it's used by the internal backup scheduler - if not FLAGS.allow_admin_api: - raise webob.exc.HTTPBadRequest( - explanation="Admin API Required") - - backup_type = get_param("backup_type") - rotation = int(get_param("rotation")) - - image = self._compute_service.backup( - context, server_id, image_name, - backup_type, rotation, extra_properties=props) - else: - LOG.error(_("Invalid image_type '%s' passed") % image_type) - raise webob.exc.HTTPBadRequest(explanation="Invalue image_type: " - "%s" % image_type) + image_name = image["name"] + server_id = image["serverId"] + except KeyError as missing_key: + msg = _("Image entity requires %s") % missing_key + raise webob.exc.HTTPBadRequest(explanation=msg) + + context = req.environ["nova.context"] + image = self._compute_service.snapshot(context, server_id, image_name) return dict(image=self.get_builder(req).build(image, detail=True)) @@ -202,13 +157,6 @@ class ControllerV10(Controller): builder = self.get_builder(req).build return dict(images=[builder(image, detail=True) for image in images]) - def _server_id_from_req(self, req, data): - try: - return data['image']['serverId'] - except KeyError: - msg = _("Expected serverId attribute on server entity.") - raise webob.exc.HTTPBadRequest(explanation=msg) - class ControllerV11(Controller): """Version 1.1 specific controller logic.""" @@ -246,38 +194,6 @@ class ControllerV11(Controller): builder = self.get_builder(req).build return dict(images=[builder(image, detail=True) for image in images]) - def _server_id_from_req(self, req, data): - try: - server_ref = data['image']['serverRef'] - except KeyError: - msg = _("Expected serverRef attribute on server entity.") - raise webob.exc.HTTPBadRequest(explanation=msg) - - if not server_ref.startswith('http'): - return server_ref - - passed = urlparse.urlparse(server_ref) - expected = urlparse.urlparse(req.application_url) - version = expected.path.split('/')[1] - expected_prefix = "/%s/servers/" % version - _empty, _sep, server_id = passed.path.partition(expected_prefix) - scheme_ok = passed.scheme == expected.scheme - host_ok = passed.hostname == expected.hostname - port_ok = (passed.port == expected.port or - passed.port == FLAGS.osapi_port) - if not (scheme_ok and port_ok and host_ok and server_id): - msg = _("serverRef must match request url") - raise webob.exc.HTTPBadRequest(explanation=msg) - - return server_id - - def _get_extra_properties(self, req, data): - server_ref = data['image']['serverRef'] - if not server_ref.startswith('http'): - server_ref = os.path.join(req.application_url, 'servers', - server_ref) - return {'instance_ref': server_ref} - def create(self, *args, **kwargs): raise webob.exc.HTTPMethodNotAllowed() diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 0cc81009b..fafde1c15 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -166,11 +166,79 @@ class Controller(object): 'createImage': self._action_create_image, } + if FLAGS.allow_admin_api: + admin_actions = { + 'createBackup': self._action_create_backup, + } + self.actions.update(admin_actions) + for key in self.actions.keys(): if key in body: return self.actions[key](body, req, id) + raise exc.HTTPNotImplemented() + def _action_create_backup(self, input_dict, req, instance_id): + """Backup a server instance. + + Images now have an `image_type` associated with them, which can be + 'snapshot' or the backup type, like 'daily' or 'weekly'. + + If the image_type is backup-like, then the rotation factor can be + included and that will cause the oldest backups that exceed the + rotation factor to be deleted. + + """ + entity = input_dict["createBackup"] + + try: + image_name = entity["name"] + backup_type = entity["backup_type"] + rotation = entity["rotation"] + + except KeyError as missing_key: + msg = _("createBackup entity requires %s attribute") % missing_key + raise webob.exc.HTTPBadRequest(explanation=msg) + + except TypeError: + msg = _("Malformed createBackup entity") + raise webob.exc.HTTPBadRequest(explanation=msg) + + try: + rotation = int(rotation) + except ValueError: + msg = _("createBackup attribute 'rotation' must be an integer") + raise webob.exc.HTTPBadRequest(explanation=msg) + + # preserve link to server in image properties + server_ref = os.path.join(req.application_url, + 'servers', + str(instance_id)) + props = {'instance_ref': server_ref} + + metadata = entity.get('metadata', {}) + try: + props.update(metadata) + except ValueError: + msg = _("Invalid metadata") + raise webob.exc.HTTPBadRequest(explanation=msg) + + context = req.environ["nova.context"] + image = self.compute_api.backup(context, + instance_id, + image_name, + backup_type, + rotation, + extra_properties=props) + + # build location of newly-created image entity + image_id = str(image['id']) + image_ref = os.path.join(req.application_url, 'images', image_id) + + resp = webob.Response(status_int=202) + resp.headers['Location'] = image_ref + return resp + def _action_create_image(self, input_dict, req, id): return exc.HTTPNotImplemented() @@ -599,30 +667,22 @@ class ControllerV11(Controller): return webob.Response(status_int=202) - def _action_create_image(self, input_dict, req, instance_id): - """Snapshot or backup a server instance and save the image. - - Images now have an `image_type` associated with them, which can be - 'snapshot' or the backup type, like 'daily' or 'weekly'. - If the image_type is backup-like, then the rotation factor can be - included and that will cause the oldest backups that exceed the - rotation factor to be deleted. - """ - entity = input_dict.get('createImage', {}) + def _action_create_image(self, input_dict, req, instance_id): + """Snapshot a server instance.""" + entity = input_dict.get("createImage", {}) - def get_param(param): - try: - return entity[param] - except KeyError: - msg = _("Missing required param: %s") % param - raise webob.exc.HTTPBadRequest(explanation=msg) + try: + image_name = entity["name"] - context = req.environ['nova.context'] + except KeyError: + msg = _("createImage entity requires name attribute") + raise webob.exc.HTTPBadRequest(explanation=msg) - image_name = get_param("name") - image_type = entity.get("image_type", "snapshot") + except TypeError: + msg = _("Malformed createImage entity") + raise webob.exc.HTTPBadRequest(explanation=msg) # preserve link to server in image properties server_ref = os.path.join(req.application_url, @@ -637,36 +697,15 @@ class ControllerV11(Controller): msg = _("Invalid metadata") raise webob.exc.HTTPBadRequest(explanation=msg) - if image_type == "snapshot": - image = self.compute_api.snapshot(context, - instance_id, - image_name, - extra_properties=props) - - elif image_type == "backup": - # NOTE(sirp): Unlike snapshot, backup is not a customer facing - # API call; rather, it's used by the internal backup scheduler - if not FLAGS.allow_admin_api: - msg = _("Admin API Required") - raise webob.exc.HTTPBadRequest(explanation=msg) - - backup_type = get_param("backup_type") - rotation = int(get_param("rotation")) - - image = self.compute_api.backup(context, - instance_id, - image_name, - backup_type, - rotation, - extra_properties=props) - else: - msg = _("Invalid image_type '%s'") % image_type - raise webob.exc.HTTPBadRequest(explanation=msg) + context = req.environ['nova.context'] + image = self.compute_api.snapshot(context, + instance_id, + image_name, + extra_properties=props) # build location of newly-created image entity - image_ref = os.path.join(req.application_url, - 'images', - str(image['id'])) + image_id = str(image['id']) + image_ref = os.path.join(req.application_url, 'images', image_id) resp = webob.Response(status_int=202) resp.headers['Location'] = image_ref diff --git a/nova/tests/api/openstack/test_images.py b/nova/tests/api/openstack/test_images.py index 50a6be66c..fa5422955 100644 --- a/nova/tests/api/openstack/test_images.py +++ b/nova/tests/api/openstack/test_images.py @@ -1045,82 +1045,6 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): response = req.get_response(fakes.wsgi_app()) self.assertEqual(400, response.status_int) - def test_create_backup_no_name(self): - """Name is also required for backups""" - body = dict(image=dict(serverId='123', image_type='backup', - backup_type='daily', rotation=1)) - req = webob.Request.blank('/v1.0/images') - req.method = 'POST' - req.body = json.dumps(body) - req.headers["content-type"] = "application/json" - response = req.get_response(fakes.wsgi_app()) - self.assertEqual(400, response.status_int) - - def test_create_backup_with_rotation_and_backup_type(self): - """The happy path for creating backups - - Creating a backup is an admin-only operation, as opposed to snapshots - which are available to anybody. - """ - # FIXME(sirp): teardown needed? - FLAGS.allow_admin_api = True - - # FIXME(sirp): should the fact that backups are admin_only be a FLAG - body = dict(image=dict(serverId='123', image_type='backup', - name='Backup 1', - backup_type='daily', rotation=1)) - req = webob.Request.blank('/v1.0/images') - req.method = 'POST' - req.body = json.dumps(body) - req.headers["content-type"] = "application/json" - response = req.get_response(fakes.wsgi_app()) - self.assertEqual(200, response.status_int) - - def test_create_backup_no_rotation(self): - """Rotation is required for backup requests""" - # FIXME(sirp): teardown needed? - FLAGS.allow_admin_api = True - - # FIXME(sirp): should the fact that backups are admin_only be a FLAG - body = dict(image=dict(serverId='123', name='daily', - image_type='backup', backup_type='daily')) - req = webob.Request.blank('/v1.0/images') - req.method = 'POST' - req.body = json.dumps(body) - req.headers["content-type"] = "application/json" - response = req.get_response(fakes.wsgi_app()) - self.assertEqual(400, response.status_int) - - def test_create_backup_no_backup_type(self): - """Backup Type (daily or weekly) is required for backup requests""" - # FIXME(sirp): teardown needed? - FLAGS.allow_admin_api = True - - # FIXME(sirp): should the fact that backups are admin_only be a FLAG - body = dict(image=dict(serverId='123', name='daily', - image_type='backup', rotation=1)) - req = webob.Request.blank('/v1.0/images') - req.method = 'POST' - req.body = json.dumps(body) - req.headers["content-type"] = "application/json" - response = req.get_response(fakes.wsgi_app()) - self.assertEqual(400, response.status_int) - - def test_create_image_with_invalid_image_type(self): - """Valid image_types are snapshot | daily | weekly""" - # FIXME(sirp): teardown needed? - FLAGS.allow_admin_api = True - - # FIXME(sirp): should the fact that backups are admin_only be a FLAG - body = dict(image=dict(serverId='123', image_type='monthly', - rotation=1)) - req = webob.Request.blank('/v1.0/images') - req.method = 'POST' - req.body = json.dumps(body) - req.headers["content-type"] = "application/json" - response = req.get_response(fakes.wsgi_app()) - self.assertEqual(400, response.status_int) - def test_create_image_no_server_id(self): body = dict(image=dict(name='Snapshot 1')) diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 9d2c7b73f..62501ed9d 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -2304,6 +2304,152 @@ class ServersTest(test.TestCase): response = req.get_response(fakes.wsgi_app()) self.assertEqual(400, response.status_int) + def test_create_backup(self): + """The happy path for creating backups""" + FLAGS.allow_admin_api = True + + body = { + 'createBackup': { + 'name': 'Backup 1', + 'backup_type': 'daily', + 'rotation': 1, + }, + } + + req = webob.Request.blank('/v1.0/servers/1/action') + req.method = 'POST' + req.body = json.dumps(body) + req.headers["content-type"] = "application/json" + response = req.get_response(fakes.wsgi_app()) + self.assertEqual(202, response.status_int) + self.assertTrue(response.headers['Location']) + + def test_create_backup_v1_1(self): + """The happy path for creating backups through v1.1 api""" + FLAGS.allow_admin_api = True + + body = { + 'createBackup': { + 'name': 'Backup 1', + 'backup_type': 'daily', + 'rotation': 1, + }, + } + + req = webob.Request.blank('/v1.1/servers/1/action') + req.method = 'POST' + req.body = json.dumps(body) + req.headers["content-type"] = "application/json" + response = req.get_response(fakes.wsgi_app()) + self.assertEqual(202, response.status_int) + self.assertTrue(response.headers['Location']) + + def test_create_backup_admin_api_off(self): + """The happy path for creating backups""" + FLAGS.allow_admin_api = False + + body = { + 'createBackup': { + 'name': 'Backup 1', + 'backup_type': 'daily', + 'rotation': 1, + }, + } + + req = webob.Request.blank('/v1.0/servers/1/action') + req.method = 'POST' + req.body = json.dumps(body) + req.headers["content-type"] = "application/json" + response = req.get_response(fakes.wsgi_app()) + self.assertEqual(501, response.status_int) + + def test_create_backup_with_metadata(self): + FLAGS.allow_admin_api = True + + body = { + 'createBackup': { + 'name': 'Backup 1', + 'backup_type': 'daily', + 'rotation': 1, + 'metadata': {'123': 'asdf'}, + }, + } + + req = webob.Request.blank('/v1.0/servers/1/action') + req.method = 'POST' + req.body = json.dumps(body) + req.headers["content-type"] = "application/json" + response = req.get_response(fakes.wsgi_app()) + self.assertEqual(202, response.status_int) + self.assertTrue(response.headers['Location']) + + def test_create_backup_no_name(self): + """Name is required for backups""" + FLAGS.allow_admin_api = True + + body = { + 'createBackup': { + 'backup_type': 'daily', + 'rotation': 1, + }, + } + + req = webob.Request.blank('/v1.0/images') + req.method = 'POST' + req.body = json.dumps(body) + req.headers["content-type"] = "application/json" + response = req.get_response(fakes.wsgi_app()) + self.assertEqual(400, response.status_int) + + def test_create_backup_no_rotation(self): + """Rotation is required for backup requests""" + FLAGS.allow_admin_api = True + + body = { + 'createBackup': { + 'name': 'Backup 1', + 'backup_type': 'daily', + }, + } + + req = webob.Request.blank('/v1.0/images') + req.method = 'POST' + req.body = json.dumps(body) + req.headers["content-type"] = "application/json" + + response = req.get_response(fakes.wsgi_app()) + self.assertEqual(400, response.status_int) + + def test_create_backup_no_backup_type(self): + """Backup Type (daily or weekly) is required for backup requests""" + FLAGS.allow_admin_api = True + + body = { + 'createBackup': { + 'name': 'Backup 1', + 'rotation': 1, + }, + } + req = webob.Request.blank('/v1.0/images') + req.method = 'POST' + req.body = json.dumps(body) + req.headers["content-type"] = "application/json" + + response = req.get_response(fakes.wsgi_app()) + self.assertEqual(400, response.status_int) + + def test_create_backup_bad_entity(self): + FLAGS.allow_admin_api = True + + body = {'createBackup': 'go'} + req = webob.Request.blank('/v1.0/images') + req.method = 'POST' + req.body = json.dumps(body) + req.headers["content-type"] = "application/json" + + response = req.get_response(fakes.wsgi_app()) + self.assertEqual(400, response.status_int) + class TestServerActionXMLDeserializer(test.TestCase): @@ -2343,6 +2489,29 @@ class TestServerActionXMLDeserializer(test.TestCase): } self.assertEquals(request['body'], expected) + def test_create_backup_with_metadata(self): + serial_request = """ + + + value1 + +""" + request = self.deserializer.deserialize(serial_request, 'action') + expected = { + "createBackup": { + "name": "new-server-test", + "rotation": "12", + "backup_type": "daily", + "metadata": {"key1": "value1"}, + }, + } + self.assertEquals(request['body'], expected) + + + class TestServerCreateRequestXMLDeserializerV10(unittest.TestCase): -- cgit From 0a7e19481849f451f04063d3d2fc45b8f3328119 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Thu, 28 Jul 2011 16:33:14 -0400 Subject: pep8 --- nova/api/openstack/servers.py | 2 -- nova/tests/api/openstack/test_servers.py | 2 -- 2 files changed, 4 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index fafde1c15..636d79b66 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -667,8 +667,6 @@ class ControllerV11(Controller): return webob.Response(status_int=202) - - def _action_create_image(self, input_dict, req, instance_id): """Snapshot a server instance.""" entity = input_dict.get("createImage", {}) diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 62501ed9d..a1cca3cef 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -2511,8 +2511,6 @@ class TestServerActionXMLDeserializer(test.TestCase): self.assertEquals(request['body'], expected) - - class TestServerCreateRequestXMLDeserializerV10(unittest.TestCase): def setUp(self): -- cgit From c0355038b462cfd75b423a535601c4463c68f80f Mon Sep 17 00:00:00 2001 From: "Kevin L. Mitchell" Date: Thu, 28 Jul 2011 21:00:38 +0000 Subject: Fix to_dict() and elevated() to preserve auth_token; revert an accidental change from context.get_admin_context() to simply context --- nova/context.py | 6 ++++-- nova/virt/xenapi/vmops.py | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) (limited to 'nova') diff --git a/nova/context.py b/nova/context.py index a765d1695..a23a02a87 100644 --- a/nova/context.py +++ b/nova/context.py @@ -98,7 +98,8 @@ class RequestContext(object): 'read_deleted': self.read_deleted, 'remote_address': self.remote_address, 'timestamp': utils.isotime(self.timestamp), - 'request_id': self.request_id} + 'request_id': self.request_id, + 'auth_token': self.auth_token} @classmethod def from_dict(cls, values): @@ -112,7 +113,8 @@ class RequestContext(object): read_deleted, self.remote_address, self.timestamp, - self.request_id) + self.request_id, + self.auth_token) def get_admin_context(read_deleted=False): diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 57c035ffd..f7a800d58 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -209,8 +209,8 @@ class VMOps(object): if instance.vm_mode != vm_mode: # Update database with normalized (or determined) value - db.instance_update(context, instance['id'], - {'vm_mode': vm_mode}) + db.instance_update(context.get_admin_context(), + instance['id'], {'vm_mode': vm_mode}) vm_ref = VMHelper.create_vm(self._session, instance, kernel and kernel.get('file', None) or None, ramdisk and ramdisk.get('file', None) or None, -- cgit From 54f652bbffaf8edf9ccfe35e1e1b15c20327340a Mon Sep 17 00:00:00 2001 From: William Wolf Date: Thu, 28 Jul 2011 20:59:07 -0400 Subject: fixed pep8 issues and removed unnecessary factory function --- nova/api/openstack/versions.py | 15 +++++---------- nova/api/openstack/views/versions.py | 3 +-- 2 files changed, 6 insertions(+), 12 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/versions.py b/nova/api/openstack/versions.py index b546462d4..40e966c5f 100644 --- a/nova/api/openstack/versions.py +++ b/nova/api/openstack/versions.py @@ -38,13 +38,13 @@ VERSIONS = { "rel": "describedby", "type": "application/pdf", "href": "http://docs.rackspacecloud.com/" - "servers/api/v1.0/cs-devguide-20110125.pdf" + "servers/api/v1.0/cs-devguide-20110125.pdf" }, { "rel": "describedby", "type": "application/vnd.sun.wadl+xml", "href": "http://docs.rackspacecloud.com/" - "servers/api/v1.0/application.wadl" + "servers/api/v1.0/application.wadl" }, ], "media-types": [ @@ -71,13 +71,13 @@ VERSIONS = { "rel": "describedby", "type": "application/pdf", "href": "http://docs.rackspacecloud.com/" - "servers/api/v1.1/cs-devguide-20110125.pdf" + "servers/api/v1.1/cs-devguide-20110125.pdf" }, { "rel": "describedby", "type": "application/vnd.sun.wadl+xml", "href": "http://docs.rackspacecloud.com/" - "servers/api/v1.1/application.wadl" + "servers/api/v1.1/application.wadl" }, ], "media-types": [ @@ -95,12 +95,7 @@ VERSIONS = { class Versions(wsgi.Resource): - @classmethod - def factory(cls, global_config, **local_config): - """Paste factory.""" - return cls() - - def __init__(self): + eef __init__(self): metadata = { "attributes": { "version": ["status", "id"], diff --git a/nova/api/openstack/views/versions.py b/nova/api/openstack/views/versions.py index 7673ffe9e..cdf758b77 100644 --- a/nova/api/openstack/views/versions.py +++ b/nova/api/openstack/views/versions.py @@ -63,7 +63,6 @@ class ViewBuilder(object): return dict(versions=version_objs) def build_version(self, version): - for link in version['links']: if link['rel'] == 'self': link['href'] = self.base_url.rstrip('/') + '/' @@ -77,7 +76,7 @@ class ViewBuilder(object): links = [ { "rel": "self", - "href": href + "href": href, }, ] -- cgit From ad8d33165f52ddf14dc9bd745db00eb039d74af7 Mon Sep 17 00:00:00 2001 From: William Wolf Date: Thu, 28 Jul 2011 21:00:55 -0400 Subject: fixed typo --- nova/api/openstack/versions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/api/openstack/versions.py b/nova/api/openstack/versions.py index 40e966c5f..607cf6a81 100644 --- a/nova/api/openstack/versions.py +++ b/nova/api/openstack/versions.py @@ -95,7 +95,7 @@ VERSIONS = { class Versions(wsgi.Resource): - eef __init__(self): + def __init__(self): metadata = { "attributes": { "version": ["status", "id"], -- cgit From 7250fe0521ccb77e73563a0a36d23cac81956457 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Fri, 29 Jul 2011 07:05:27 -0700 Subject: added instance support to to_primitive and tests --- nova/notifier/api.py | 4 +-- nova/tests/test_utils.py | 71 ++++++++++++++++++++++++++++++++++++++++++++++++ nova/utils.py | 3 ++ 3 files changed, 75 insertions(+), 3 deletions(-) (limited to 'nova') diff --git a/nova/notifier/api.py b/nova/notifier/api.py index 8eea2a032..70264efa8 100644 --- a/nova/notifier/api.py +++ b/nova/notifier/api.py @@ -82,9 +82,7 @@ def notify(publisher_id, event_type, priority, payload): _('%s not in valid priorities' % priority)) # Ensure everything is JSON serializable. - for k, v in payload.iteritems(): - if not isinstance(v, (basestring, int, long, float)): - payload[k] = unicode(v) + payload = utils.to_primitive(payload) driver = utils.import_object(FLAGS.notification_driver) msg = dict(message_id=str(uuid.uuid4()), diff --git a/nova/tests/test_utils.py b/nova/tests/test_utils.py index 0c359e981..3797478f3 100644 --- a/nova/tests/test_utils.py +++ b/nova/tests/test_utils.py @@ -14,6 +14,7 @@ # License for the specific language governing permissions and limitations # under the License. +import datetime import os import tempfile @@ -306,3 +307,73 @@ class IsUUIDLikeTestCase(test.TestCase): def test_non_uuid_string_passed(self): val = 'foo-fooo' self.assertUUIDLike(val, False) + + +class ToPrimitiveTestCase(test.TestCase): + def test_list(self): + self.assertEquals(utils.to_primitive([1, 2, 3]), [1, 2, 3]) + + def test_empty_list(self): + self.assertEquals(utils.to_primitive([]), []) + + def test_tuple(self): + self.assertEquals(utils.to_primitive((1, 2, 3)), [1, 2, 3]) + + def test_dict(self): + self.assertEquals(utils.to_primitive(dict(a=1, b=2, c=3)), + dict(a=1, b=2, c=3)) + + def test_empty_dict(self): + self.assertEquals(utils.to_primitive({}), {}) + + def test_datetime(self): + x = datetime.datetime(1,2,3,4,5,6,7) + self.assertEquals(utils.to_primitive(x), "0001-02-03 04:05:06.000007") + + def test_iter(self): + class IterClass(object): + def __init__(self): + self.data = [1, 2, 3, 4, 5] + self.index = 0 + + def __iter__(self): + return self + + def next(self): + if self.index == len(self.data): + raise StopIteration + self.index = self.index + 1 + return self.data[self.index - 1] + + x = IterClass() + self.assertEquals(utils.to_primitive(x), [1, 2, 3, 4, 5]) + + def test_iteritems(self): + class IterItemsClass(object): + def __init__(self): + self.data = dict(a=1, b=2, c=3).items() + self.index = 0 + + def __iter__(self): + return self + + def next(self): + if self.index == len(self.data): + raise StopIteration + self.index = self.index + 1 + return self.data[self.index - 1] + + x = IterItemsClass() + ordered = utils.to_primitive(x) + ordered.sort() + self.assertEquals(ordered, [['a', 1], ['b', 2], ['c', 3]]) + + def test_instance(self): + class MysteryClass(object): + a = 10 + + def __init__(self): + self.x = 1 + + x = MysteryClass() + self.assertEquals(utils.to_primitive(x), dict(x=1)) diff --git a/nova/utils.py b/nova/utils.py index 8784a227d..f54bf72ed 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -521,6 +521,9 @@ def to_primitive(value): return to_primitive(dict(value.iteritems())) elif hasattr(value, '__iter__'): return to_primitive(list(value)) + elif hasattr(value, '__dict__'): + # Class member variables not supported. + return to_primitive(value.__dict__) else: return value -- cgit From eca19199bdfcc64948f41d7e6b1728cb17b3baa2 Mon Sep 17 00:00:00 2001 From: William Wolf Date: Fri, 29 Jul 2011 10:08:20 -0400 Subject: fix more spacing issues, and removed self link from versions template data --- nova/api/openstack/versions.py | 8 ------ nova/api/openstack/views/versions.py | 12 +++++---- nova/tests/api/openstack/test_versions.py | 42 +++++++++++++------------------ 3 files changed, 25 insertions(+), 37 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/versions.py b/nova/api/openstack/versions.py index 607cf6a81..3ef72b7f6 100644 --- a/nova/api/openstack/versions.py +++ b/nova/api/openstack/versions.py @@ -30,10 +30,6 @@ VERSIONS = { "status": "DEPRECATED", "updated": "2011-01-21T11:33:21Z", "links": [ - { - "rel": "self", - "href": "" - }, { "rel": "describedby", "type": "application/pdf", @@ -63,10 +59,6 @@ VERSIONS = { "status": "CURRENT", "updated": "2011-01-21T11:33:21Z", "links": [ - { - "rel": "self", - "href": "" - }, { "rel": "describedby", "type": "application/pdf", diff --git a/nova/api/openstack/views/versions.py b/nova/api/openstack/views/versions.py index cdf758b77..547289034 100644 --- a/nova/api/openstack/views/versions.py +++ b/nova/api/openstack/views/versions.py @@ -15,6 +15,7 @@ # License for the specific language governing permissions and limitations # under the License. +import copy import os @@ -63,11 +64,12 @@ class ViewBuilder(object): return dict(versions=version_objs) def build_version(self, version): - for link in version['links']: - if link['rel'] == 'self': - link['href'] = self.base_url.rstrip('/') + '/' - - return dict(version=version) + reval = copy.deepcopy(version) + reval['links'].insert(0, { + "rel": "self", + "href": self.base_url.rstrip('/') + '/', + }) + return dict(version=reval) def _build_links(self, version_data): """Generate a container of links that refer to the provided version.""" diff --git a/nova/tests/api/openstack/test_versions.py b/nova/tests/api/openstack/test_versions.py index 1373f2e39..e68455778 100644 --- a/nova/tests/api/openstack/test_versions.py +++ b/nova/tests/api/openstack/test_versions.py @@ -34,21 +34,17 @@ VERSIONS = { "status": "DEPRECATED", "updated": "2011-01-21T11:33:21Z", "links": [ - { - "rel": "self", - "href": "" - }, { "rel": "describedby", "type": "application/pdf", "href": "http://docs.rackspacecloud.com/" - "servers/api/v1.0/cs-devguide-20110125.pdf" + "servers/api/v1.0/cs-devguide-20110125.pdf" }, { "rel": "describedby", "type": "application/vnd.sun.wadl+xml", "href": "http://docs.rackspacecloud.com/" - "servers/api/v1.0/application.wadl" + "servers/api/v1.0/application.wadl" }, ], "media-types": [ @@ -67,21 +63,17 @@ VERSIONS = { "status": "CURRENT", "updated": "2011-01-21T11:33:21Z", "links": [ - { - "rel": "self", - "href": "" - }, { "rel": "describedby", "type": "application/pdf", "href": "http://docs.rackspacecloud.com/" - "servers/api/v1.1/cs-devguide-20110125.pdf" + "servers/api/v1.1/cs-devguide-20110125.pdf" }, { "rel": "describedby", "type": "application/vnd.sun.wadl+xml", "href": "http://docs.rackspacecloud.com/" - "servers/api/v1.1/application.wadl" + "servers/api/v1.1/application.wadl" }, ], "media-types": [ @@ -105,9 +97,11 @@ class VersionsTest(test.TestCase): self.stubs = stubout.StubOutForTesting() fakes.stub_out_auth(self.stubs) #Stub out VERSIONS + self.old_versions = versions.VERSIONS versions.VERSIONS = VERSIONS def tearDown(self): + versions.VERSIONS = self.old_versions super(VersionsTest, self).tearDown() def test_get_version_list(self): @@ -162,25 +156,25 @@ class VersionsTest(test.TestCase): "rel": "describedby", "type": "application/pdf", "href": "http://docs.rackspacecloud.com/" - "servers/api/v1.0/cs-devguide-20110125.pdf" + "servers/api/v1.0/cs-devguide-20110125.pdf" }, { "rel": "describedby", "type": "application/vnd.sun.wadl+xml", "href": "http://docs.rackspacecloud.com/" - "servers/api/v1.0/application.wadl" + "servers/api/v1.0/application.wadl" } ], "media-types": [ { "base": "application/xml", "type": "application/" - "vnd.openstack.compute-v1.0+xml" + "vnd.openstack.compute-v1.0+xml" }, { "base": "application/json", "type": "application/" - "vnd.openstack.compute-v1.0+json" + "vnd.openstack.compute-v1.0+json" } ] } @@ -208,25 +202,25 @@ class VersionsTest(test.TestCase): "rel": "describedby", "type": "application/pdf", "href": "http://docs.rackspacecloud.com/" - "servers/api/v1.1/cs-devguide-20110125.pdf" + "servers/api/v1.1/cs-devguide-20110125.pdf" }, { "rel": "describedby", "type": "application/vnd.sun.wadl+xml", "href": "http://docs.rackspacecloud.com/" - "servers/api/v1.1/application.wadl" + "servers/api/v1.1/application.wadl" } ], "media-types": [ { "base": "application/xml", "type": "application/" - "vnd.openstack.compute-v1.1+xml" + "vnd.openstack.compute-v1.1+xml" }, { "base": "application/json", "type": "application/" - "vnd.openstack.compute-v1.1+json" + "vnd.openstack.compute-v1.1+json" } ] } @@ -751,13 +745,13 @@ class VersionsSerializerTests(test.TestCase): "rel": "describedby", "type": "application/pdf", "href": "http://docs.rackspacecloud.com/" - "servers/api/v1.0/cs-devguide-20110125.pdf" + "servers/api/v1.0/cs-devguide-20110125.pdf" }, { "rel": "describedby", "type": "application/vnd.sun.wadl+xml", "href": "http://docs.rackspacecloud.com/" - "servers/api/v1.0/application.wadl" + "servers/api/v1.0/application.wadl" }, ], "media-types": [ @@ -886,13 +880,13 @@ class VersionsSerializerTests(test.TestCase): "rel": "describedby", "type": "application/pdf", "href": "http://docs.rackspacecloud.com/" - "servers/api/v1.1/cs-devguide-20110125.pdf" + "servers/api/v1.1/cs-devguide-20110125.pdf" }, { "rel": "describedby", "type": "application/vnd.sun.wadl+xml", "href": "http://docs.rackspacecloud.com/" - "servers/api/v1.1/application.wadl" + "servers/api/v1.1/application.wadl" }, ], "media-types": [ -- cgit From 51f4d4c2e0c7d9f066b328014aa955b150b62c3a Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Fri, 29 Jul 2011 12:09:17 -0700 Subject: made the whole instance handling thing optional --- nova/notifier/api.py | 2 +- nova/tests/test_utils.py | 23 ++++++++++----- nova/utils.py | 77 ++++++++++++++++++++++++++++++++++-------------- 3 files changed, 71 insertions(+), 31 deletions(-) (limited to 'nova') diff --git a/nova/notifier/api.py b/nova/notifier/api.py index 70264efa8..e18f3e280 100644 --- a/nova/notifier/api.py +++ b/nova/notifier/api.py @@ -82,7 +82,7 @@ def notify(publisher_id, event_type, priority, payload): _('%s not in valid priorities' % priority)) # Ensure everything is JSON serializable. - payload = utils.to_primitive(payload) + payload = utils.to_primitive(payload, convert_instances=True) driver = utils.import_object(FLAGS.notification_driver) msg = dict(message_id=str(uuid.uuid4()), diff --git a/nova/tests/test_utils.py b/nova/tests/test_utils.py index 3797478f3..ec5098a37 100644 --- a/nova/tests/test_utils.py +++ b/nova/tests/test_utils.py @@ -318,16 +318,16 @@ class ToPrimitiveTestCase(test.TestCase): def test_tuple(self): self.assertEquals(utils.to_primitive((1, 2, 3)), [1, 2, 3]) - + def test_dict(self): - self.assertEquals(utils.to_primitive(dict(a=1, b=2, c=3)), + self.assertEquals(utils.to_primitive(dict(a=1, b=2, c=3)), dict(a=1, b=2, c=3)) def test_empty_dict(self): self.assertEquals(utils.to_primitive({}), {}) def test_datetime(self): - x = datetime.datetime(1,2,3,4,5,6,7) + x = datetime.datetime(1, 2, 3, 4, 5, 6, 7) self.assertEquals(utils.to_primitive(x), "0001-02-03 04:05:06.000007") def test_iter(self): @@ -335,7 +335,7 @@ class ToPrimitiveTestCase(test.TestCase): def __init__(self): self.data = [1, 2, 3, 4, 5] self.index = 0 - + def __iter__(self): return self @@ -347,13 +347,13 @@ class ToPrimitiveTestCase(test.TestCase): x = IterClass() self.assertEquals(utils.to_primitive(x), [1, 2, 3, 4, 5]) - + def test_iteritems(self): class IterItemsClass(object): def __init__(self): self.data = dict(a=1, b=2, c=3).items() self.index = 0 - + def __iter__(self): return self @@ -373,7 +373,14 @@ class ToPrimitiveTestCase(test.TestCase): a = 10 def __init__(self): - self.x = 1 + self.b = 1 x = MysteryClass() - self.assertEquals(utils.to_primitive(x), dict(x=1)) + self.assertEquals(utils.to_primitive(x, convert_instances=True), + dict(b=1)) + + self.assertEquals(utils.to_primitive(x), x) + + def test_typeerror(self): + x = bytearray # Class, not instance + self.assertEquals(utils.to_primitive(x), u"") diff --git a/nova/utils.py b/nova/utils.py index f54bf72ed..1a04c471c 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -504,28 +504,61 @@ def utf8(value): return value -def to_primitive(value): - if type(value) is type([]) or type(value) is type((None,)): - o = [] - for v in value: - o.append(to_primitive(v)) - return o - elif type(value) is type({}): - o = {} - for k, v in value.iteritems(): - o[k] = to_primitive(v) - return o - elif isinstance(value, datetime.datetime): - return str(value) - elif hasattr(value, 'iteritems'): - return to_primitive(dict(value.iteritems())) - elif hasattr(value, '__iter__'): - return to_primitive(list(value)) - elif hasattr(value, '__dict__'): - # Class member variables not supported. - return to_primitive(value.__dict__) - else: - return value +def to_primitive(value, convert_instances=False, level=0): + """Convert a complex object into primitives. + + Handy for JSON serialization. We can optionally handle instances, + but since this is a recursive function, we could have cyclical + data structures. + + To handle cyclical data structures we could track the actual objects + visited in a set, but not all objects are hashable. Instead we just + track the depth of the object inspections and don't go too deep. + + Therefore, convert_instances=True is lossy ... be aware. + + """ + if inspect.isclass(value): + return unicode(value) + + if level > 3: + return [] + + # The try block may not be necessary after the class check above, + # but just in case ... + try: + if type(value) is type([]) or type(value) is type((None,)): + o = [] + for v in value: + o.append(to_primitive(v, convert_instances=convert_instances, + level=level)) + return o + elif type(value) is type({}): + o = {} + for k, v in value.iteritems(): + o[k] = to_primitive(v, convert_instances=convert_instances, + level=level) + return o + elif isinstance(value, datetime.datetime): + return str(value) + elif hasattr(value, 'iteritems'): + return to_primitive(dict(value.iteritems()), + convert_instances=convert_instances, + level=level) + elif hasattr(value, '__iter__'): + return to_primitive(list(value), level) + elif convert_instances and hasattr(value, '__dict__'): + # Likely an instance of something. Watch for cycles. + # Ignore class member vars. + return to_primitive(value.__dict__, + convert_instances=convert_instances, + level=level + 1) + else: + return value + except TypeError, e: + # Class objects are tricky since they may define something like + # __iter__ defined but it isn't callable as list(). + return unicode(value) def dumps(value): -- cgit From a14a18157bb53934b3a86fc4a7b9c58d2aae8d56 Mon Sep 17 00:00:00 2001 From: Jason Koelker Date: Fri, 29 Jul 2011 16:16:05 -0500 Subject: allow getting by the cidr_v6 --- nova/db/sqlalchemy/api.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index a13d60ec4..066fbd98a 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -1680,7 +1680,8 @@ def network_get_by_bridge(context, bridge): def network_get_by_cidr(context, cidr): session = get_session() result = session.query(models.Network).\ - filter_by(cidr=cidr).first() + filter(or_(models.Network.cidr == cidr, + models.Network.cidr_v6 == cidr).first() if not result: raise exception.NetworkNotFoundForCidr(cidr=cidr) -- cgit From 99db7ffaeaba067f2914c59423bb1a953f860e57 Mon Sep 17 00:00:00 2001 From: Jason Koelker Date: Fri, 29 Jul 2011 16:25:46 -0500 Subject: allow the manager to try to do the right thing --- nova/network/manager.py | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) (limited to 'nova') diff --git a/nova/network/manager.py b/nova/network/manager.py index 4a3791d8a..bdb57d634 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -613,34 +613,39 @@ class NetworkManager(manager.SchedulerDependentManager): network_size, cidr_v6, gateway_v6, bridge, bridge_interface, dns1=None, dns2=None, **kwargs): """Create networks based on parameters.""" - fixed_net = netaddr.IPNetwork(cidr) - if FLAGS.use_ipv6: + if cidr_v6: fixed_net_v6 = netaddr.IPNetwork(cidr_v6) significant_bits_v6 = 64 network_size_v6 = 1 << 64 - for index in range(num_networks): - start = index * network_size + if cidr: + fixed_net = netaddr.IPNetwork(cidr) significant_bits = 32 - int(math.log(network_size, 2)) - cidr = '%s/%s' % (fixed_net[start], significant_bits) - project_net = netaddr.IPNetwork(cidr) + + for index in range(num_networks): net = {} net['bridge'] = bridge net['bridge_interface'] = bridge_interface net['dns1'] = dns1 net['dns2'] = dns2 - net['cidr'] = cidr - net['multi_host'] = multi_host - net['netmask'] = str(project_net.netmask) - net['gateway'] = str(project_net[1]) - net['broadcast'] = str(project_net.broadcast) - net['dhcp_start'] = str(project_net[2]) + + if cidr: + start = index * network_size + project_net = netaddr.IPNetwork('%s/%s' % (fixed_net[start], + significant_bits)) + net['cidr'] = str(project_net) + net['multi_host'] = multi_host + net['netmask'] = str(project_net.netmask) + net['gateway'] = str(project_net[1]) + net['broadcast'] = str(project_net.broadcast) + net['dhcp_start'] = str(project_net[2]) + if num_networks > 1: net['label'] = '%s_%d' % (label, index) else: net['label'] = label - if FLAGS.use_ipv6: + if cidr_v6: start_v6 = index * network_size_v6 cidr_v6 = '%s/%s' % (fixed_net_v6[start_v6], significant_bits_v6) -- cgit From 36846b40016a27b5f9e0762273de532f4e0e10cd Mon Sep 17 00:00:00 2001 From: Jason Koelker Date: Fri, 29 Jul 2011 16:40:42 -0500 Subject: You see what happens Danny when you forget to close the parenthesis --- nova/db/sqlalchemy/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 066fbd98a..ae8b9685a 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -1681,7 +1681,7 @@ def network_get_by_cidr(context, cidr): session = get_session() result = session.query(models.Network).\ filter(or_(models.Network.cidr == cidr, - models.Network.cidr_v6 == cidr).first() + models.Network.cidr_v6 == cidr)).first() if not result: raise exception.NetworkNotFoundForCidr(cidr=cidr) -- cgit From fc2682bc9c01c445b9ee7003c5cb561c452757e5 Mon Sep 17 00:00:00 2001 From: "Kevin L. Mitchell" Date: Fri, 29 Jul 2011 21:58:27 +0000 Subject: Revert to using context; to avoid conflict, we import context module as nova_context; add context to rescue --- nova/compute/manager.py | 2 +- nova/virt/driver.py | 9 ++++---- nova/virt/fake.py | 6 ++--- nova/virt/hyperv.py | 2 +- nova/virt/images.py | 6 ++--- nova/virt/libvirt/connection.py | 36 ++++++++++++++++------------- nova/virt/vmwareapi/vmops.py | 12 +++++----- nova/virt/vmwareapi_conn.py | 8 +++---- nova/virt/xenapi/vm_utils.py | 24 ++++++++++---------- nova/virt/xenapi/vmops.py | 50 ++++++++++++++++++++--------------------- nova/virt/xenapi_conn.py | 16 ++++++------- 11 files changed, 86 insertions(+), 85 deletions(-) (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 56331f86b..cf4ee229d 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -660,7 +660,7 @@ class ComputeManager(manager.SchedulerDependentManager): _update_state = lambda result: self._update_state_callback( self, context, instance_id, result) network_info = self._get_instance_nw_info(context, instance_ref) - self.driver.rescue(instance_ref, _update_state, network_info) + self.driver.rescue(context, instance_ref, _update_state, network_info) self._update_state(context, instance_id) @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id()) diff --git a/nova/virt/driver.py b/nova/virt/driver.py index 4be847dc6..4f3cfefad 100644 --- a/nova/virt/driver.py +++ b/nova/virt/driver.py @@ -65,7 +65,7 @@ class ComputeDriver(object): # TODO(Vek): Need to pass context in for access to auth_token raise NotImplementedError() - def spawn(self, cxt, instance, network_info, + def spawn(self, context, instance, network_info, block_device_mapping=None): """Launch a VM for the specified instance""" raise NotImplementedError() @@ -131,11 +131,11 @@ class ComputeDriver(object): # TODO(Vek): Need to pass context in for access to auth_token raise NotImplementedError() - def snapshot(self, cxt, instance, image_id): + def snapshot(self, context, instance, image_id): """Create snapshot from a running VM instance.""" raise NotImplementedError() - def finish_migration(self, cxt, instance, disk_info, network_info, + def finish_migration(self, context, instance, disk_info, network_info, resize_instance): """Completes a resize, turning on the migrated instance""" raise NotImplementedError() @@ -165,9 +165,8 @@ class ComputeDriver(object): # TODO(Vek): Need to pass context in for access to auth_token raise NotImplementedError() - def rescue(self, instance, callback, network_info): + def rescue(self, context, instance, callback, network_info): """Rescue the specified instance""" - # TODO(Vek): Need to pass context in for access to auth_token raise NotImplementedError() def unrescue(self, instance, callback, network_info): diff --git a/nova/virt/fake.py b/nova/virt/fake.py index 23aa603ef..c08f8069d 100644 --- a/nova/virt/fake.py +++ b/nova/virt/fake.py @@ -129,7 +129,7 @@ class FakeConnection(driver.ComputeDriver): info_list.append(self._map_to_instance_info(instance)) return info_list - def spawn(self, cxt, instance, network_info, + def spawn(self, context, instance, network_info, block_device_mapping=None): """ Create a new instance/VM/domain on the virtualization platform. @@ -154,7 +154,7 @@ class FakeConnection(driver.ComputeDriver): fake_instance = FakeInstance(name, state) self.instances[name] = fake_instance - def snapshot(self, cxt, instance, name): + def snapshot(self, context, instance, name): """ Snapshots the specified instance. @@ -241,7 +241,7 @@ class FakeConnection(driver.ComputeDriver): """ pass - def rescue(self, instance, callback, network_info): + def rescue(self, context, instance, callback, network_info): """ Rescue the specified instance. """ diff --git a/nova/virt/hyperv.py b/nova/virt/hyperv.py index cbfe5fad1..3428a7fc1 100644 --- a/nova/virt/hyperv.py +++ b/nova/virt/hyperv.py @@ -138,7 +138,7 @@ class HyperVConnection(driver.ComputeDriver): return instance_infos - def spawn(self, cxt, instance, network_info, + def spawn(self, context, instance, network_info, block_device_mapping=None): """ Create a new VM and start it.""" vm = self._lookup(instance.name) diff --git a/nova/virt/images.py b/nova/virt/images.py index 2e9fca3d6..54c691a40 100644 --- a/nova/virt/images.py +++ b/nova/virt/images.py @@ -21,7 +21,6 @@ Handling of VM disk images. """ -from nova import context from nova import flags from nova.image import glance as glance_image_service import nova.image @@ -33,13 +32,12 @@ FLAGS = flags.FLAGS LOG = logging.getLogger('nova.virt.images') -def fetch(image_href, path, _user_id, _project_id): +def fetch(context, image_href, path, _user_id, _project_id): # TODO(vish): Improve context handling and add owner and auth data # when it is added to glance. Right now there is no # auth checking in glance, so we assume that access was # checked before we got here. (image_service, image_id) = nova.image.get_image_service(image_href) with open(path, "wb") as image_file: - elevated = context.get_admin_context() - metadata = image_service.get(elevated, image_id, image_file) + metadata = image_service.get(context, image_id, image_file) return metadata diff --git a/nova/virt/libvirt/connection.py b/nova/virt/libvirt/connection.py index 81d302ea6..71ccdf57d 100644 --- a/nova/virt/libvirt/connection.py +++ b/nova/virt/libvirt/connection.py @@ -54,7 +54,7 @@ from xml.etree import ElementTree from eventlet import greenthread from eventlet import tpool -from nova import context +from nova import context as nova_context from nova import db from nova import exception from nova import flags @@ -174,7 +174,7 @@ class LibvirtConnection(driver.ComputeDriver): def init_host(self, host): # Adopt existing VM's running here - ctxt = context.get_admin_context() + ctxt = nova_context.get_admin_context() for instance in db.instance_get_all_by_host(ctxt, host): try: LOG.debug(_('Checking state of %s'), instance['name']) @@ -396,7 +396,7 @@ class LibvirtConnection(driver.ComputeDriver): virt_dom.detachDevice(xml) @exception.wrap_exception() - def snapshot(self, cxt, instance, image_href): + def snapshot(self, context, instance, image_href): """Create snapshot from a running VM instance. This command only works with qemu 0.14+, the qemu_img flag is @@ -405,14 +405,13 @@ class LibvirtConnection(driver.ComputeDriver): """ virt_dom = self._lookup_by_name(instance['name']) - elevated = context.get_admin_context() (image_service, image_id) = nova.image.get_image_service( instance['image_ref']) - base = image_service.show(elevated, image_id) + base = image_service.show(context, image_id) (snapshot_image_service, snapshot_image_id) = \ nova.image.get_image_service(image_href) - snapshot = snapshot_image_service.show(elevated, snapshot_image_id) + snapshot = snapshot_image_service.show(context, snapshot_image_id) metadata = {'disk_format': base['disk_format'], 'container_format': base['container_format'], @@ -463,7 +462,7 @@ class LibvirtConnection(driver.ComputeDriver): # Upload that image to the image service with open(out_path) as image_file: - image_service.update(elevated, + image_service.update(context, image_href, metadata, image_file) @@ -538,7 +537,7 @@ class LibvirtConnection(driver.ComputeDriver): dom.create() @exception.wrap_exception() - def rescue(self, instance, callback, network_info): + def rescue(self, context, instance, callback, network_info): """Loads a VM using rescue images. A rescue is normally performed when something goes wrong with the @@ -553,7 +552,7 @@ class LibvirtConnection(driver.ComputeDriver): rescue_images = {'image_id': FLAGS.rescue_image_id, 'kernel_id': FLAGS.rescue_kernel_id, 'ramdisk_id': FLAGS.rescue_ramdisk_id} - self._create_image(instance, xml, '.rescue', rescue_images) + self._create_image(context, instance, xml, '.rescue', rescue_images) self._create_new_domain(xml) def _wait_for_rescue(): @@ -592,14 +591,14 @@ class LibvirtConnection(driver.ComputeDriver): # NOTE(ilyaalekseyev): Implementation like in multinics # for xenapi(tr3buchet) @exception.wrap_exception() - def spawn(self, cxt, instance, network_info, + def spawn(self, context, instance, network_info, block_device_mapping=None): xml = self.to_xml(instance, False, network_info=network_info, block_device_mapping=block_device_mapping) block_device_mapping = block_device_mapping or [] self.firewall_driver.setup_basic_filtering(instance, network_info) self.firewall_driver.prepare_instance_filter(instance, network_info) - self._create_image(instance, xml, network_info=network_info, + self._create_image(context, instance, xml, network_info=network_info, block_device_mapping=block_device_mapping) domain = self._create_new_domain(xml) LOG.debug(_("instance %s: is running"), instance['name']) @@ -770,9 +769,10 @@ class LibvirtConnection(driver.ComputeDriver): else: utils.execute('cp', base, target) - def _fetch_image(self, target, image_id, user_id, project_id, size=None): + def _fetch_image(self, context, target, image_id, user_id, project_id, + size=None): """Grab image and optionally attempt to resize it""" - images.fetch(image_id, target, user_id, project_id) + images.fetch(context, image_id, target, user_id, project_id) if size: disk.extend(target, size) @@ -781,8 +781,9 @@ class LibvirtConnection(driver.ComputeDriver): utils.execute('truncate', target, '-s', "%dG" % local_gb) # TODO(vish): should we format disk by default? - def _create_image(self, inst, libvirt_xml, suffix='', disk_images=None, - network_info=None, block_device_mapping=None): + def _create_image(self, context, inst, libvirt_xml, suffix='', + disk_images=None, network_info=None, + block_device_mapping=None): block_device_mapping = block_device_mapping or [] if not suffix: @@ -818,6 +819,7 @@ class LibvirtConnection(driver.ComputeDriver): if disk_images['kernel_id']: fname = '%08x' % int(disk_images['kernel_id']) self._cache_image(fn=self._fetch_image, + context=context, target=basepath('kernel'), fname=fname, image_id=disk_images['kernel_id'], @@ -826,6 +828,7 @@ class LibvirtConnection(driver.ComputeDriver): if disk_images['ramdisk_id']: fname = '%08x' % int(disk_images['ramdisk_id']) self._cache_image(fn=self._fetch_image, + context=context, target=basepath('ramdisk'), fname=fname, image_id=disk_images['ramdisk_id'], @@ -844,6 +847,7 @@ class LibvirtConnection(driver.ComputeDriver): if not self._volume_in_mapping(self.root_mount_device, block_device_mapping): self._cache_image(fn=self._fetch_image, + context=context, target=basepath('disk'), fname=root_fname, cow=FLAGS.use_cow_images, @@ -880,7 +884,7 @@ class LibvirtConnection(driver.ComputeDriver): ifc_template = open(FLAGS.injected_network_template).read() ifc_num = -1 have_injected_networks = False - admin_context = context.get_admin_context() + admin_context = nova_context.get_admin_context() for (network_ref, mapping) in network_info: ifc_num += 1 diff --git a/nova/virt/vmwareapi/vmops.py b/nova/virt/vmwareapi/vmops.py index ad354bc3a..07a6ba6ab 100644 --- a/nova/virt/vmwareapi/vmops.py +++ b/nova/virt/vmwareapi/vmops.py @@ -26,7 +26,7 @@ import urllib import urllib2 import uuid -from nova import context +from nova import context as nova_context from nova import db from nova import exception from nova import flags @@ -89,7 +89,7 @@ class VMWareVMOps(object): LOG.debug(_("Got total of %s instances") % str(len(lst_vm_names))) return lst_vm_names - def spawn(self, cxt, instance, network_info): + def spawn(self, context, instance, network_info): """ Creates a VM instance. @@ -111,7 +111,7 @@ class VMWareVMOps(object): client_factory = self._session._get_vim().client.factory service_content = self._session._get_vim().get_service_content() - network = db.network_get_by_instance(context.get_admin_context(), + network = db.network_get_by_instance(nova_context.get_admin_context(), instance['id']) net_name = network['bridge'] @@ -329,7 +329,7 @@ class VMWareVMOps(object): LOG.debug(_("Powered on the VM instance %s") % instance.name) _power_on_vm() - def snapshot(self, cxt, instance, snapshot_name): + def snapshot(self, context, instance, snapshot_name): """ Create snapshot from a running VM instance. Steps followed are: @@ -721,11 +721,11 @@ class VMWareVMOps(object): Set the machine id of the VM for guest tools to pick up and change the IP. """ - admin_context = context.get_admin_context() + admin_context = nova_context.get_admin_context() vm_ref = self._get_vm_ref_from_the_name(instance.name) if vm_ref is None: raise exception.InstanceNotFound(instance_id=instance.id) - network = db.network_get_by_instance(context.get_admin_context(), + network = db.network_get_by_instance(nova_context.get_admin_context(), instance['id']) mac_address = None if instance['mac_addresses']: diff --git a/nova/virt/vmwareapi_conn.py b/nova/virt/vmwareapi_conn.py index a26a02290..3d209fa99 100644 --- a/nova/virt/vmwareapi_conn.py +++ b/nova/virt/vmwareapi_conn.py @@ -124,14 +124,14 @@ class VMWareESXConnection(driver.ComputeDriver): """List VM instances.""" return self._vmops.list_instances() - def spawn(self, cxt, instance, network_info, + def spawn(self, context, instance, network_info, block_device_mapping=None): """Create VM instance.""" - self._vmops.spawn(cxt, instance, network_info) + self._vmops.spawn(context, instance, network_info) - def snapshot(self, cxt, instance, name): + def snapshot(self, context, instance, name): """Create snapshot from a running VM instance.""" - self._vmops.snapshot(cxt, instance, name) + self._vmops.snapshot(context, instance, name) def reboot(self, instance, network_info): """Reboot VM instance.""" diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index b696bdb02..6bbe6072c 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -342,7 +342,7 @@ class VMHelper(HelperBase): return os.path.join(FLAGS.xenapi_sr_base_path, sr_uuid) @classmethod - def upload_image(cls, ctx, session, instance, vdi_uuids, image_id): + def upload_image(cls, context, session, instance, vdi_uuids, image_id): """ Requests that the Glance plugin bundle the specified VDIs and push them into Glance using the specified human-friendly name. """ @@ -361,15 +361,15 @@ class VMHelper(HelperBase): 'glance_port': glance_port, 'sr_path': cls.get_sr_path(session), 'os_type': os_type, - 'auth_token': getattr(ctx, 'auth_token', None)} + 'auth_token': getattr(context, 'auth_token', None)} kwargs = {'params': pickle.dumps(params)} task = session.async_call_plugin('glance', 'upload_vhd', kwargs) session.wait_for_task(task, instance.id) @classmethod - def fetch_image(cls, ctx, session, instance_id, image, user_id, project_id, - image_type): + def fetch_image(cls, context, session, instance_id, image, user_id, + project_id, image_type): """ image_type is interpreted as an ImageType instance Related flags: @@ -382,7 +382,7 @@ class VMHelper(HelperBase): """ if FLAGS.xenapi_image_service == 'glance': - return cls._fetch_image_glance(ctx, session, instance_id, + return cls._fetch_image_glance(context, session, instance_id, image, image_type) else: # TODO(vish): this shouldn't be used anywhere anymore and @@ -396,7 +396,7 @@ class VMHelper(HelperBase): image_type) @classmethod - def _fetch_image_glance_vhd(cls, ctx, session, instance_id, image, + def _fetch_image_glance_vhd(cls, context, session, instance_id, image, image_type): """Tell glance to download an image and put the VHDs into the SR @@ -419,7 +419,7 @@ class VMHelper(HelperBase): 'glance_port': glance_port, 'uuid_stack': uuid_stack, 'sr_path': cls.get_sr_path(session), - 'auth_token': getattr(ctx, 'auth_token', None)} + 'auth_token': getattr(context, 'auth_token', None)} kwargs = {'params': pickle.dumps(params)} task = session.async_call_plugin('glance', 'download_vhd', kwargs) @@ -445,7 +445,7 @@ class VMHelper(HelperBase): return vdis @classmethod - def _fetch_image_glance_disk(cls, ctx, session, instance_id, image, + def _fetch_image_glance_disk(cls, context, session, instance_id, image, image_type): """Fetch the image from Glance @@ -465,7 +465,7 @@ class VMHelper(HelperBase): sr_ref = safe_find_sr(session) glance_client, image_id = nova.image.get_glance_client(image) - glance_client.set_auth_token(getattr(ctx, 'auth_token', None)) + glance_client.set_auth_token(getattr(context, 'auth_token', None)) meta, image_file = glance_client.get_image(image_id) virtual_size = int(meta['size']) vdi_size = virtual_size @@ -580,7 +580,7 @@ class VMHelper(HelperBase): return image_type @classmethod - def _fetch_image_glance(cls, ctx, session, instance_id, image, + def _fetch_image_glance(cls, context, session, instance_id, image, image_type): """Fetch image from glance based on image type. @@ -588,10 +588,10 @@ class VMHelper(HelperBase): A list of dictionaries that describe VDIs, otherwise """ if image_type == ImageType.DISK_VHD: - return cls._fetch_image_glance_vhd(ctx, + return cls._fetch_image_glance_vhd(context, session, instance_id, image, image_type) else: - return cls._fetch_image_glance_disk(ctx, + return cls._fetch_image_glance_disk(context, session, instance_id, image, image_type) @classmethod diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index f6c86566b..b3b812a48 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -30,7 +30,7 @@ import sys import time import uuid -from nova import context +from nova import context as nova_context from nova import db from nova import exception from nova import flags @@ -113,11 +113,11 @@ class VMOps(object): vm_ref = VMHelper.lookup(self._session, instance.name) self._start(instance, vm_ref) - def finish_migration(self, ctx, instance, disk_info, network_info, + def finish_migration(self, context, instance, disk_info, network_info, resize_instance): vdi_uuid = self.link_disks(instance, disk_info['base_copy'], disk_info['cow']) - vm_ref = self._create_vm(ctx, instance, + vm_ref = self._create_vm(context, instance, [dict(vdi_type='os', vdi_uuid=vdi_uuid)], network_info) if resize_instance: @@ -134,19 +134,19 @@ class VMOps(object): LOG.debug(_("Starting instance %s"), instance.name) self._session.call_xenapi('VM.start', vm_ref, False, False) - def _create_disks(self, ctx, instance): + def _create_disks(self, context, instance): disk_image_type = VMHelper.determine_disk_image_type(instance) - vdis = VMHelper.fetch_image(ctx, self._session, + vdis = VMHelper.fetch_image(context, self._session, instance.id, instance.image_ref, instance.user_id, instance.project_id, disk_image_type) return vdis - def spawn(self, ctx, instance, network_info): + def spawn(self, context, instance, network_info): vdis = None try: - vdis = self._create_disks(ctx, instance) - vm_ref = self._create_vm(ctx, instance, vdis, network_info) + vdis = self._create_disks(context, instance) + vm_ref = self._create_vm(context, instance, vdis, network_info) self._spawn(instance, vm_ref) except (self.XenAPI.Failure, OSError, IOError) as spawn_error: LOG.exception(_("instance %s: Failed to spawn"), @@ -156,11 +156,11 @@ class VMOps(object): self._handle_spawn_error(vdis, spawn_error) raise spawn_error - def spawn_rescue(self, instance): + def spawn_rescue(self, context, instance, network_info): """Spawn a rescue instance.""" - self.spawn(instance) + self.spawn(context, instance, network_info) - def _create_vm(self, ctx, instance, vdis, network_info): + def _create_vm(self, context, instance, vdis, network_info): """Create VM instance.""" instance_name = instance.name vm_ref = VMHelper.lookup(self._session, instance_name) @@ -171,7 +171,7 @@ class VMOps(object): if not VMHelper.ensure_free_mem(self._session, instance): LOG.exception(_('instance %(instance_name)s: not enough free ' 'memory') % locals()) - db.instance_set_state(context.get_admin_context(), + db.instance_set_state(nova_context.get_admin_context(), instance['id'], power_state.SHUTDOWN) return @@ -181,12 +181,12 @@ class VMOps(object): ramdisk = None try: if instance.kernel_id: - kernel = VMHelper.fetch_image(ctx, self._session, instance.id, - instance.kernel_id, instance.user_id, + kernel = VMHelper.fetch_image(context, self._session, + instance.id, instance.kernel_id, instance.user_id, instance.project_id, ImageType.KERNEL)[0] if instance.ramdisk_id: - ramdisk = VMHelper.fetch_image(ctx, self._session, instance.id, - instance.kernel_id, instance.user_id, + ramdisk = VMHelper.fetch_image(context, self._session, + instance.id, instance.kernel_id, instance.user_id, instance.project_id, ImageType.RAMDISK)[0] # Create the VM ref and attach the first disk first_vdi_ref = self._session.call_xenapi('VDI.get_by_uuid', @@ -206,7 +206,7 @@ class VMOps(object): if instance.vm_mode != vm_mode: # Update database with normalized (or determined) value - db.instance_update(context.get_admin_context(), + db.instance_update(nova_context.get_admin_context(), instance['id'], {'vm_mode': vm_mode}) vm_ref = VMHelper.create_vm(self._session, instance, kernel and kernel.get('file', None) or None, @@ -268,7 +268,7 @@ class VMOps(object): LOG.info(_('Spawning VM %(instance_name)s created %(vm_ref)s.') % locals()) - ctx = context.get_admin_context() + ctx = nova_context.get_admin_context() agent_build = db.agent_build_get_by_triple(ctx, 'xen', instance.os_type, instance.architecture) if agent_build: @@ -412,7 +412,7 @@ class VMOps(object): # if instance_or_vm is an int/long it must be instance id elif isinstance(instance_or_vm, (int, long)): - ctx = context.get_admin_context() + ctx = nova_context.get_admin_context() instance_obj = db.instance_get(ctx, instance_or_vm) instance_name = instance_obj.name else: @@ -437,10 +437,10 @@ class VMOps(object): vm, "start") - def snapshot(self, ctx, instance, image_id): + def snapshot(self, context, instance, image_id): """Create snapshot from a running VM instance. - :param ctx: request context + :param context: request context :param instance: instance to be snapshotted :param image_id: id of image to upload to @@ -465,7 +465,7 @@ class VMOps(object): try: template_vm_ref, template_vdi_uuids = self._get_snapshot(instance) # call plugin to ship snapshot off to glance - VMHelper.upload_image(ctx, + VMHelper.upload_image(context, self._session, instance, template_vdi_uuids, image_id) finally: if template_vm_ref: @@ -686,7 +686,7 @@ class VMOps(object): # Successful return code from password is '0' if resp_dict['returncode'] != '0': raise RuntimeError(resp_dict['message']) - db.instance_update(context.get_admin_context(), + db.instance_update(nova_context.get_admin_context(), instance['id'], dict(admin_pass=new_pass)) return resp_dict['message'] @@ -914,7 +914,7 @@ class VMOps(object): True) self._wait_with_callback(instance.id, task, callback) - def rescue(self, instance, callback): + def rescue(self, context, instance, callback, network_info): """Rescue the specified instance. - shutdown the instance VM. @@ -932,7 +932,7 @@ class VMOps(object): self._shutdown(instance, vm_ref) self._acquire_bootlock(vm_ref) instance._rescue = True - self.spawn_rescue(instance) + self.spawn_rescue(context, instance, network_info) rescue_vm_ref = VMHelper.lookup(self._session, instance.name) vbd_ref = self._session.get_xenapi().VM.get_VBDs(vm_ref)[0] diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index 9315c2f4a..665b7eed6 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -187,24 +187,24 @@ class XenAPIConnection(driver.ComputeDriver): def list_instances_detail(self): return self._vmops.list_instances_detail() - def spawn(self, cxt, instance, network_info, + def spawn(self, context, instance, network_info, block_device_mapping=None): """Create VM instance""" - self._vmops.spawn(cxt, instance, network_info) + self._vmops.spawn(context, instance, network_info) def revert_migration(self, instance): """Reverts a resize, powering back on the instance""" self._vmops.revert_resize(instance) - def finish_migration(self, cxt, instance, disk_info, network_info, + def finish_migration(self, context, instance, disk_info, network_info, resize_instance=False): """Completes a resize, turning on the migrated instance""" - self._vmops.finish_migration(cxt, instance, disk_info, + self._vmops.finish_migration(context, instance, disk_info, network_info, resize_instance) - def snapshot(self, cxt, instance, image_id): + def snapshot(self, context, instance, image_id): """ Create snapshot from a running VM instance """ - self._vmops.snapshot(cxt, instance, image_id) + self._vmops.snapshot(context, instance, image_id) def reboot(self, instance, network_info): """Reboot VM instance""" @@ -245,9 +245,9 @@ class XenAPIConnection(driver.ComputeDriver): """resume the specified instance""" self._vmops.resume(instance, callback) - def rescue(self, instance, callback, network_info): + def rescue(self, context, instance, callback, network_info): """Rescue the specified instance""" - self._vmops.rescue(instance, callback) + self._vmops.rescue(instance, callback, network_info) def unrescue(self, instance, callback, network_info): """Unrescue the specified instance""" -- cgit From c1f530c606a84de05becd412fe396acca385e342 Mon Sep 17 00:00:00 2001 From: Jason Koelker Date: Fri, 29 Jul 2011 16:58:42 -0500 Subject: only create fixed_ips if we have an ipv4 range --- nova/network/manager.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'nova') diff --git a/nova/network/manager.py b/nova/network/manager.py index bdb57d634..4db2213b8 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -678,11 +678,11 @@ class NetworkManager(manager.SchedulerDependentManager): # None if network with cidr or cidr_v6 already exists network = self.db.network_create_safe(context, net) - if network: + if not network: + raise ValueError(_('Network already exists!')) + + if network and cidr: self._create_fixed_ips(context, network['id']) - else: - raise ValueError(_('Network with cidr %s already exists') % - cidr) @property def _bottom_reserved_ips(self): # pylint: disable=R0201 -- cgit From 055a422643fc229ec0e7db3f6dcba9904c5a4f5d Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Fri, 29 Jul 2011 18:05:31 -0400 Subject: Created exceptions for accepting in OSAPI, and handled them appropriately. --- nova/api/openstack/servers.py | 40 ++++++++++++++++++++-------------------- nova/compute/api.py | 9 +++------ nova/exception.py | 12 ++++++++++++ 3 files changed, 35 insertions(+), 26 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 30169d450..e62e4e4f8 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -405,6 +405,24 @@ class Controller(object): error=item.error)) return dict(actions=actions) + def resize(self, req, instance_id, flavor_id): + """Begin the resize process with given instance/flavor.""" + context = req.environ["nova.context"] + + try: + self.compute_api.resize(context, instance_id, flavor_id) + except exception.FlavorDoesNotExist: + msg = _("Unable to locate requested flavor.") + raise exc.HTTPBadRequest(explanation=msg) + except exception.CannotResizeToSameSize: + msg = _("Resize requires a change in size.") + raise exc.HTTPBadRequest(explanation=msg) + except exception.CannotResizeToSmallerSize: + msg = _("Resizing to a smaller size is not supported.") + raise exc.HTTPBadRequest(explanation=msg) + + return webob.Response(status_int=202) + class ControllerV10(Controller): @@ -444,16 +462,7 @@ class ControllerV10(Controller): msg = _("Resize requests require 'flavorId' attribute.") raise exc.HTTPBadRequest(explanation=msg) - try: - i_type = instance_types.get_instance_type_by_flavor_id(flavor_id) - except exception.FlavorNotFound: - msg = _("Unable to locate requested flavor.") - raise exc.HTTPBadRequest(explanation=msg) - - context = req.environ["nova.context"] - self.compute_api.resize(context, id, i_type["id"]) - - return webob.Response(status_int=202) + return Controller.resize(self, req, id, flavor_id) def _action_rebuild(self, info, request, instance_id): context = request.environ['nova.context'] @@ -568,16 +577,7 @@ class ControllerV11(Controller): msg = _("Resize requests require 'flavorRef' attribute.") raise exc.HTTPBadRequest(explanation=msg) - try: - i_type = instance_types.get_instance_type_by_flavor_id(flavor_ref) - except exception.FlavorNotFound: - msg = _("Unable to locate requested flavor.") - raise exc.HTTPBadRequest(explanation=msg) - - context = req.environ["nova.context"] - self.compute_api.resize(context, id, i_type["id"]) - - return webob.Response(status_int=202) + return Controller.resize(self, req, id, flavor_ref) def _action_rebuild(self, info, request, instance_id): context = request.environ['nova.context'] diff --git a/nova/compute/api.py b/nova/compute/api.py index 8f7b3c3ef..9e4841e64 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -940,18 +940,15 @@ class API(base.Base): LOG.debug(_("Old instance type %(current_instance_type_name)s, " " new instance type %(new_instance_type_name)s") % locals()) if not new_instance_type: - raise exception.ApiError(_("Requested flavor %(flavor_id)d " - "does not exist") % locals()) + raise exception.FlavorDoesNotExist(flavor_id=flavor_id) current_memory_mb = current_instance_type['memory_mb'] new_memory_mb = new_instance_type['memory_mb'] if current_memory_mb > new_memory_mb: - raise exception.ApiError(_("Invalid flavor: cannot downsize" - "instances")) + raise exception.CannotResizeToSmallerSize() if (current_memory_mb == new_memory_mb) and flavor_id: - raise exception.ApiError(_("Invalid flavor: cannot use" - "the same flavor. ")) + raise exception.CannotResizeToSameSize() instance_ref = self._get_instance(context, instance_id, 'resize') self._cast_scheduler_message(context, diff --git a/nova/exception.py b/nova/exception.py index 8c9b45a80..b6455db9f 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -692,3 +692,15 @@ class PasteConfigNotFound(NotFound): class PasteAppNotFound(NotFound): message = _("Could not load paste app '%(name)s' from %(path)s") + + +class CannotResizeToSameSize(NovaException): + message = _("When resizing, instances must change size!") + + +class CannotResizeToSmallerSize(NovaException): + message = _("Resizing to a smaller size is not supported.") + + +class FlavorDoesNotExist(NovaException): + message = _("Requested flavor '%(flavor_id)s' does not exist.") -- cgit From a7f0eb04236b15a026654346b47bc434886b9d97 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Fri, 29 Jul 2011 18:13:28 -0400 Subject: FlavorNotFound already existed, no need to create another exception. --- nova/api/openstack/servers.py | 2 +- nova/compute/api.py | 2 +- nova/db/sqlalchemy/api.py | 9 +++++++-- nova/exception.py | 4 ---- 4 files changed, 9 insertions(+), 8 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index e62e4e4f8..f35eec884 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -411,7 +411,7 @@ class Controller(object): try: self.compute_api.resize(context, instance_id, flavor_id) - except exception.FlavorDoesNotExist: + except exception.FlavorNotFound: msg = _("Unable to locate requested flavor.") raise exc.HTTPBadRequest(explanation=msg) except exception.CannotResizeToSameSize: diff --git a/nova/compute/api.py b/nova/compute/api.py index 9e4841e64..aae16d1da 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -940,7 +940,7 @@ class API(base.Base): LOG.debug(_("Old instance type %(current_instance_type_name)s, " " new instance type %(new_instance_type_name)s") % locals()) if not new_instance_type: - raise exception.FlavorDoesNotExist(flavor_id=flavor_id) + raise exception.FlavorNotFound(flavor_id=flavor_id) current_memory_mb = current_instance_type['memory_mb'] new_memory_mb = new_instance_type['memory_mb'] diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index a13d60ec4..36614df1f 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -3041,13 +3041,18 @@ def instance_type_get_by_name(context, name): @require_context def instance_type_get_by_flavor_id(context, id): """Returns a dict describing specific flavor_id""" + try: + flavor_id = int(id) + except ValueError: + raise exception.FlavorNotFound(flavor_id=id) + session = get_session() inst_type = session.query(models.InstanceTypes).\ options(joinedload('extra_specs')).\ - filter_by(flavorid=int(id)).\ + filter_by(flavorid=flavor_id).\ first() if not inst_type: - raise exception.FlavorNotFound(flavor_id=id) + raise exception.FlavorNotFound(flavor_id=flavor_id) else: return _dict_with_extra_specs(inst_type) diff --git a/nova/exception.py b/nova/exception.py index b6455db9f..68e6ac937 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -700,7 +700,3 @@ class CannotResizeToSameSize(NovaException): class CannotResizeToSmallerSize(NovaException): message = _("Resizing to a smaller size is not supported.") - - -class FlavorDoesNotExist(NovaException): - message = _("Requested flavor '%(flavor_id)s' does not exist.") -- cgit From cbb09485442d472f883829ba96f4c2d8b6d830fd Mon Sep 17 00:00:00 2001 From: Jason Koelker Date: Fri, 29 Jul 2011 17:15:50 -0500 Subject: only attempt to get a fixed_up from a v4 subnet if there is a v4 subnet --- nova/network/manager.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) (limited to 'nova') diff --git a/nova/network/manager.py b/nova/network/manager.py index 4db2213b8..0e802f545 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -551,15 +551,17 @@ class NetworkManager(manager.SchedulerDependentManager): # with a network, or a cluster of computes with a network # and use that network here with a method like # network_get_by_compute_host - address = self.db.fixed_ip_associate_pool(context.elevated(), - network['id'], - instance_id) - vif = self.db.virtual_interface_get_by_instance_and_network(context, - instance_id, - network['id']) - values = {'allocated': True, - 'virtual_interface_id': vif['id']} - self.db.fixed_ip_update(context, address, values) + address = None + if network['cidr']: + address = self.db.fixed_ip_associate_pool(context.elevated(), + network['id'], + instance_id) + get_vif = self.db.virtual_interface_get_by_instance_and_network + vif = get_vif(context, instance_id, network['id']) + values = {'allocated': True, + 'virtual_interface_id': vif['id']} + self.db.fixed_ip_update(context, address, values) + self._setup_network(context, network) return address -- cgit From 3ab21a13aeca78cc310a93199b10891d2a6deaec Mon Sep 17 00:00:00 2001 From: "Kevin L. Mitchell" Date: Fri, 29 Jul 2011 22:19:16 +0000 Subject: Make unit tests pass --- nova/tests/test_xenapi.py | 2 +- nova/tests/xenapi/stubs.py | 2 +- nova/virt/xenapi_conn.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) (limited to 'nova') diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index 2b96249de..1072e20b5 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -666,7 +666,7 @@ class XenAPIVMTestCase(test.TestCase): self.flags(flat_injected=False) instance = self._create_instance() conn = xenapi_conn.get_connection(False) - conn.rescue(instance, None, []) + conn.rescue(self.context, instance, None, []) def test_unrescue(self): instance = self._create_instance() diff --git a/nova/tests/xenapi/stubs.py b/nova/tests/xenapi/stubs.py index 3a142081c..0d0f84e32 100644 --- a/nova/tests/xenapi/stubs.py +++ b/nova/tests/xenapi/stubs.py @@ -227,7 +227,7 @@ def stub_out_vm_methods(stubs): def fake_release_bootlock(self, vm): pass - def fake_spawn_rescue(self, inst): + def fake_spawn_rescue(self, context, inst, network_info): inst._rescue = False stubs.Set(vmops.VMOps, "_shutdown", fake_shutdown) diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index 665b7eed6..972f4f8bb 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -247,7 +247,7 @@ class XenAPIConnection(driver.ComputeDriver): def rescue(self, context, instance, callback, network_info): """Rescue the specified instance""" - self._vmops.rescue(instance, callback, network_info) + self._vmops.rescue(context, instance, callback, network_info) def unrescue(self, instance, callback, network_info): """Unrescue the specified instance""" -- cgit From 1db6b5768a1160ee5e45ddadae7ba5c56b0abb4b Mon Sep 17 00:00:00 2001 From: "Kevin L. Mitchell" Date: Fri, 29 Jul 2011 22:24:02 +0000 Subject: Only call set_auth_token() on the glance client if there's one available --- nova/image/glance.py | 3 ++- nova/virt/xenapi/vm_utils.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/image/glance.py b/nova/image/glance.py index 44a3c6f83..588d86b6e 100644 --- a/nova/image/glance.py +++ b/nova/image/glance.py @@ -85,7 +85,8 @@ class GlanceImageService(service.BaseImageService): def _set_client_context(self, context): """Sets the client's auth token.""" - self.client.set_auth_token(context.auth_token) + if hasattr(self.client, 'set_auth_token'): + self.client.set_auth_token(context.auth_token) def index(self, context, filters=None, marker=None, limit=None): """Calls out to Glance for a list of images available.""" diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index 6bbe6072c..c6320f917 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -465,7 +465,8 @@ class VMHelper(HelperBase): sr_ref = safe_find_sr(session) glance_client, image_id = nova.image.get_glance_client(image) - glance_client.set_auth_token(getattr(context, 'auth_token', None)) + if hasattr(glance_client, 'set_auth_token'): + glance_client.set_auth_token(getattr(context, 'auth_token', None)) meta, image_file = glance_client.get_image(image_id) virtual_size = int(meta['size']) vdi_size = virtual_size -- cgit From 1b3a1d6a9abf1c248f4f379ba98f17d3947af5ea Mon Sep 17 00:00:00 2001 From: Jason Koelker Date: Fri, 29 Jul 2011 17:26:44 -0500 Subject: wow, someone whent all crazy with exceptions, why not just return an empty list? --- nova/network/manager.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/network/manager.py b/nova/network/manager.py index 0e802f545..8fc6a295f 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -410,8 +410,11 @@ class NetworkManager(manager.SchedulerDependentManager): kwargs can contain fixed_ips to circumvent another db lookup """ instance_id = kwargs.pop('instance_id') - fixed_ips = kwargs.get('fixed_ips') or \ + try: + fixed_ips = kwargs.get('fixed_ips') or \ self.db.fixed_ip_get_by_instance(context, instance_id) + except exceptions.FixedIpNotFoundForInstance: + fixed_ips = [] LOG.debug(_("network deallocation for instance |%s|"), instance_id, context=context) # deallocate fixed ips -- cgit From e34a4dfd067ddf29651bf006869c012e48813a24 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Fri, 29 Jul 2011 18:29:23 -0400 Subject: pep8 --- nova/virt/xenapi/vm_utils.py | 1 - 1 file changed, 1 deletion(-) (limited to 'nova') diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index 542257747..60ef0df43 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -558,7 +558,6 @@ class VMHelper(HelperBase): log_disk_format(image_type) return image_type - @classmethod def determine_is_pv(cls, session, instance_id, vdi_ref, disk_image_type, os_type): -- cgit From ede656a3e4615d2cfaf45b5b2354ace9283e2c4e Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Mon, 1 Aug 2011 13:38:14 -0400 Subject: Update compute tests to use new exceptions. --- nova/tests/test_compute.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'nova') diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py index 860cdedd3..879e4b9cb 100644 --- a/nova/tests/test_compute.py +++ b/nova/tests/test_compute.py @@ -496,8 +496,8 @@ class ComputeTestCase(test.TestCase): db.instance_update(self.context, instance_id, {'instance_type_id': inst_type['id']}) - self.assertRaises(exception.ApiError, self.compute_api.resize, - context, instance_id, 1) + self.assertRaises(exception.CannotResizeToSmallerSize, + self.compute_api.resize, context, instance_id, 1) self.compute.terminate_instance(context, instance_id) @@ -508,8 +508,8 @@ class ComputeTestCase(test.TestCase): self.compute.run_instance(self.context, instance_id) - self.assertRaises(exception.ApiError, self.compute_api.resize, - context, instance_id, 1) + self.assertRaises(exception.CannotResizeToSameSize, + self.compute_api.resize, context, instance_id, 1) self.compute.terminate_instance(context, instance_id) -- cgit From 4c5f84fa890da6dfe11aefd5b3c27478a2aad5eb Mon Sep 17 00:00:00 2001 From: Jason Koelker Date: Mon, 1 Aug 2011 13:37:22 -0500 Subject: it makes the pep8, or else it gets the vim again --- nova/tests/test_db_api.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/tests/test_db_api.py b/nova/tests/test_db_api.py index 54448f9d6..0c07cbb7c 100644 --- a/nova/tests/test_db_api.py +++ b/nova/tests/test_db_api.py @@ -57,7 +57,7 @@ class DbApiTestCase(test.TestCase): def test_instance_get_project_vpn(self): values = {'instance_type_id': FLAGS.default_instance_type, 'image_ref': FLAGS.vpn_image_id, - 'project_id': self.project_id + 'project_id': self.project_id, } instance = db.instance_create(self.context, values) result = db.instance_get_project_vpn(self.context.elevated(), @@ -67,7 +67,7 @@ class DbApiTestCase(test.TestCase): def test_instance_get_project_vpn_joins(self): values = {'instance_type_id': FLAGS.default_instance_type, 'image_ref': FLAGS.vpn_image_id, - 'project_id': self.project_id + 'project_id': self.project_id, } instance = db.instance_create(self.context, values) _setup_networking(instance['id']) -- cgit From 8e7a4e6147cb78b28fc2a2131591dbd37de2fa30 Mon Sep 17 00:00:00 2001 From: "Kevin L. Mitchell" Date: Mon, 1 Aug 2011 18:59:29 +0000 Subject: Revert hasattr() check on 'set_auth_token' for clients --- nova/image/glance.py | 3 +-- nova/virt/xenapi/vm_utils.py | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) (limited to 'nova') diff --git a/nova/image/glance.py b/nova/image/glance.py index 588d86b6e..44a3c6f83 100644 --- a/nova/image/glance.py +++ b/nova/image/glance.py @@ -85,8 +85,7 @@ class GlanceImageService(service.BaseImageService): def _set_client_context(self, context): """Sets the client's auth token.""" - if hasattr(self.client, 'set_auth_token'): - self.client.set_auth_token(context.auth_token) + self.client.set_auth_token(context.auth_token) def index(self, context, filters=None, marker=None, limit=None): """Calls out to Glance for a list of images available.""" diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index c6320f917..6bbe6072c 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -465,8 +465,7 @@ class VMHelper(HelperBase): sr_ref = safe_find_sr(session) glance_client, image_id = nova.image.get_glance_client(image) - if hasattr(glance_client, 'set_auth_token'): - glance_client.set_auth_token(getattr(context, 'auth_token', None)) + glance_client.set_auth_token(getattr(context, 'auth_token', None)) meta, image_file = glance_client.get_image(image_id) virtual_size = int(meta['size']) vdi_size = virtual_size -- cgit From 78379de0561073facd6f23239be5081428349ce6 Mon Sep 17 00:00:00 2001 From: "Kevin L. Mitchell" Date: Mon, 1 Aug 2011 19:05:32 +0000 Subject: Carry auth_token in nova's RequestContext --- nova/context.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'nova') diff --git a/nova/context.py b/nova/context.py index 5b2776d4e..b917a1d81 100644 --- a/nova/context.py +++ b/nova/context.py @@ -32,7 +32,7 @@ class RequestContext(object): def __init__(self, user_id, project_id, is_admin=None, read_deleted=False, roles=None, remote_address=None, timestamp=None, - request_id=None): + request_id=None, auth_token=None): self.user_id = user_id self.project_id = project_id self.roles = roles or [] @@ -49,6 +49,7 @@ class RequestContext(object): if not request_id: request_id = unicode(uuid.uuid4()) self.request_id = request_id + self.auth_token = auth_token def to_dict(self): return {'user_id': self.user_id, @@ -58,7 +59,8 @@ class RequestContext(object): 'roles': self.roles, 'remote_address': self.remote_address, 'timestamp': utils.strtime(self.timestamp), - 'request_id': self.request_id} + 'request_id': self.request_id, + 'auth_token': self.auth_token} @classmethod def from_dict(cls, values): @@ -74,7 +76,8 @@ class RequestContext(object): roles=self.roles, remote_address=self.remote_address, timestamp=self.timestamp, - request_id=self.request_id) + request_id=self.request_id, + auth_token=self.auth_token) def get_admin_context(read_deleted=False): -- cgit From 25a831fd449dbbb7f0c2cdac404d7600a6da9f27 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Mon, 1 Aug 2011 18:01:04 -0400 Subject: Controller -> self --- nova/api/openstack/servers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index f35eec884..8d2ccc2ad 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -462,7 +462,7 @@ class ControllerV10(Controller): msg = _("Resize requests require 'flavorId' attribute.") raise exc.HTTPBadRequest(explanation=msg) - return Controller.resize(self, req, id, flavor_id) + return self.resize(req, id, flavor_id) def _action_rebuild(self, info, request, instance_id): context = request.environ['nova.context'] @@ -577,7 +577,7 @@ class ControllerV11(Controller): msg = _("Resize requests require 'flavorRef' attribute.") raise exc.HTTPBadRequest(explanation=msg) - return Controller.resize(self, req, id, flavor_ref) + return self.resize(req, id, flavor_ref) def _action_rebuild(self, info, request, instance_id): context = request.environ['nova.context'] -- cgit From 0e5b2b0bfb7f6b0af7f2c962a963830a8691410e Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Mon, 1 Aug 2011 18:56:16 -0700 Subject: avoid explicit type checking, per brian waldon's comment --- nova/api/openstack/create_instance_helper.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index 234c16bcc..b414b2173 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -90,8 +90,7 @@ class CreateInstanceHelper(object): # If the image href was generated by nova api, strip image_href # down to an id and use the default glance connection params - if isinstance(image_href, basestring) and\ - image_href.startswith(req.application_url): + if str(image_href).startswith(req.application_url): image_href = image_href.split('/').pop() try: image_service, image_id = nova.image.get_image_service(image_href) -- cgit From 07646e85841a4f7c81e80254ac63715bece2aadd Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Tue, 2 Aug 2011 10:09:58 -0400 Subject: removing compute monitor --- nova/compute/monitor.py | 435 ---------------------------------------- nova/tests/test_twistd.py | 53 ----- nova/twistd.py | 267 ------------------------ nova/virt/fake.py | 12 +- nova/virt/libvirt/connection.py | 12 +- 5 files changed, 8 insertions(+), 771 deletions(-) delete mode 100644 nova/compute/monitor.py delete mode 100644 nova/tests/test_twistd.py delete mode 100644 nova/twistd.py (limited to 'nova') diff --git a/nova/compute/monitor.py b/nova/compute/monitor.py deleted file mode 100644 index 9d8e2a25d..000000000 --- a/nova/compute/monitor.py +++ /dev/null @@ -1,435 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2010 United States Government as represented by the -# Administrator of the National Aeronautics and Space Administration. -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -""" -Instance Monitoring: - - Optionally may be run on each compute node. Provides RRD - based statistics and graphs and makes them internally available - in the object store. -""" - -import datetime -import os -import time - -import boto -import boto.s3 -import rrdtool -from twisted.internet import task -from twisted.application import service - -from nova import flags -from nova import log as logging -from nova import utils -from nova.virt import connection as virt_connection - - -FLAGS = flags.FLAGS -flags.DEFINE_integer('monitoring_instances_delay', 5, - 'Sleep time between updates') -flags.DEFINE_integer('monitoring_instances_step', 300, - 'Interval of RRD updates') -flags.DEFINE_string('monitoring_rrd_path', '$state_path/monitor/instances', - 'Location of RRD files') - - -RRD_VALUES = { - 'cpu': [ - 'DS:cpu:GAUGE:600:0:100', - 'RRA:AVERAGE:0.5:1:800', - 'RRA:AVERAGE:0.5:6:800', - 'RRA:AVERAGE:0.5:24:800', - 'RRA:AVERAGE:0.5:288:800', - 'RRA:MAX:0.5:1:800', - 'RRA:MAX:0.5:6:800', - 'RRA:MAX:0.5:24:800', - 'RRA:MAX:0.5:288:800', - ], - 'net': [ - 'DS:rx:COUNTER:600:0:1250000', - 'DS:tx:COUNTER:600:0:1250000', - 'RRA:AVERAGE:0.5:1:800', - 'RRA:AVERAGE:0.5:6:800', - 'RRA:AVERAGE:0.5:24:800', - 'RRA:AVERAGE:0.5:288:800', - 'RRA:MAX:0.5:1:800', - 'RRA:MAX:0.5:6:800', - 'RRA:MAX:0.5:24:800', - 'RRA:MAX:0.5:288:800', - ], - 'disk': [ - 'DS:rd:COUNTER:600:U:U', - 'DS:wr:COUNTER:600:U:U', - 'RRA:AVERAGE:0.5:1:800', - 'RRA:AVERAGE:0.5:6:800', - 'RRA:AVERAGE:0.5:24:800', - 'RRA:AVERAGE:0.5:288:800', - 'RRA:MAX:0.5:1:800', - 'RRA:MAX:0.5:6:800', - 'RRA:MAX:0.5:24:800', - 'RRA:MAX:0.5:444:800', - ]} - - -utcnow = utils.utcnow - - -LOG = logging.getLogger('nova.compute.monitor') - - -def update_rrd(instance, name, data): - """ - Updates the specified RRD file. - """ - filename = os.path.join(instance.get_rrd_path(), '%s.rrd' % name) - - if not os.path.exists(filename): - init_rrd(instance, name) - - timestamp = int(time.mktime(utcnow().timetuple())) - rrdtool.update(filename, '%d:%s' % (timestamp, data)) - - -def init_rrd(instance, name): - """ - Initializes the specified RRD file. - """ - path = os.path.join(FLAGS.monitoring_rrd_path, instance.instance_id) - - if not os.path.exists(path): - os.makedirs(path) - - filename = os.path.join(path, '%s.rrd' % name) - - if not os.path.exists(filename): - rrdtool.create( - filename, - '--step', '%d' % FLAGS.monitoring_instances_step, - '--start', '0', - *RRD_VALUES[name]) - - -def graph_cpu(instance, duration): - """ - Creates a graph of cpu usage for the specified instance and duration. - """ - path = instance.get_rrd_path() - filename = os.path.join(path, 'cpu-%s.png' % duration) - - rrdtool.graph( - filename, - '--disable-rrdtool-tag', - '--imgformat', 'PNG', - '--width', '400', - '--height', '120', - '--start', 'now-%s' % duration, - '--vertical-label', '% cpu used', - '-l', '0', - '-u', '100', - 'DEF:cpu=%s:cpu:AVERAGE' % os.path.join(path, 'cpu.rrd'), - 'AREA:cpu#eacc00:% CPU',) - - store_graph(instance.instance_id, filename) - - -def graph_net(instance, duration): - """ - Creates a graph of network usage for the specified instance and duration. - """ - path = instance.get_rrd_path() - filename = os.path.join(path, 'net-%s.png' % duration) - - rrdtool.graph( - filename, - '--disable-rrdtool-tag', - '--imgformat', 'PNG', - '--width', '400', - '--height', '120', - '--start', 'now-%s' % duration, - '--vertical-label', 'bytes/s', - '--logarithmic', - '--units', 'si', - '--lower-limit', '1000', - '--rigid', - 'DEF:rx=%s:rx:AVERAGE' % os.path.join(path, 'net.rrd'), - 'DEF:tx=%s:tx:AVERAGE' % os.path.join(path, 'net.rrd'), - 'AREA:rx#00FF00:In traffic', - 'LINE1:tx#0000FF:Out traffic',) - - store_graph(instance.instance_id, filename) - - -def graph_disk(instance, duration): - """ - Creates a graph of disk usage for the specified duration. - """ - path = instance.get_rrd_path() - filename = os.path.join(path, 'disk-%s.png' % duration) - - rrdtool.graph( - filename, - '--disable-rrdtool-tag', - '--imgformat', 'PNG', - '--width', '400', - '--height', '120', - '--start', 'now-%s' % duration, - '--vertical-label', 'bytes/s', - '--logarithmic', - '--units', 'si', - '--lower-limit', '1000', - '--rigid', - 'DEF:rd=%s:rd:AVERAGE' % os.path.join(path, 'disk.rrd'), - 'DEF:wr=%s:wr:AVERAGE' % os.path.join(path, 'disk.rrd'), - 'AREA:rd#00FF00:Read', - 'LINE1:wr#0000FF:Write',) - - store_graph(instance.instance_id, filename) - - -def store_graph(instance_id, filename): - """ - Transmits the specified graph file to internal object store on cloud - controller. - """ - # TODO(devcamcar): Need to use an asynchronous method to make this - # connection. If boto has some separate method that generates - # the request it would like to make and another method to parse - # the response we can make our own client that does the actual - # request and hands it off to the response parser. - s3 = boto.s3.connection.S3Connection( - aws_access_key_id=FLAGS.aws_access_key_id, - aws_secret_access_key=FLAGS.aws_secret_access_key, - is_secure=False, - calling_format=boto.s3.connection.OrdinaryCallingFormat(), - port=FLAGS.s3_port, - host=FLAGS.s3_host) - bucket_name = '_%s.monitor' % instance_id - - # Object store isn't creating the bucket like it should currently - # when it is first requested, so have to catch and create manually. - try: - bucket = s3.get_bucket(bucket_name) - except Exception: - bucket = s3.create_bucket(bucket_name) - - key = boto.s3.Key(bucket) - key.key = os.path.basename(filename) - key.set_contents_from_filename(filename) - - -class Instance(object): - def __init__(self, conn, instance_id): - self.conn = conn - self.instance_id = instance_id - self.last_updated = datetime.datetime.min - self.cputime = 0 - self.cputime_last_updated = None - - init_rrd(self, 'cpu') - init_rrd(self, 'net') - init_rrd(self, 'disk') - - def needs_update(self): - """ - Indicates whether this instance is due to have its statistics updated. - """ - delta = utcnow() - self.last_updated - return delta.seconds >= FLAGS.monitoring_instances_step - - def update(self): - """ - Updates the instances statistics and stores the resulting graphs - in the internal object store on the cloud controller. - """ - LOG.debug(_('updating %s...'), self.instance_id) - - try: - data = self.fetch_cpu_stats() - if data is not None: - LOG.debug('CPU: %s', data) - update_rrd(self, 'cpu', data) - - data = self.fetch_net_stats() - LOG.debug('NET: %s', data) - update_rrd(self, 'net', data) - - data = self.fetch_disk_stats() - LOG.debug('DISK: %s', data) - update_rrd(self, 'disk', data) - - # TODO(devcamcar): Turn these into pool.ProcessPool.execute() calls - # and make the methods @defer.inlineCallbacks. - graph_cpu(self, '1d') - graph_cpu(self, '1w') - graph_cpu(self, '1m') - - graph_net(self, '1d') - graph_net(self, '1w') - graph_net(self, '1m') - - graph_disk(self, '1d') - graph_disk(self, '1w') - graph_disk(self, '1m') - except Exception: - LOG.exception(_('unexpected error during update')) - - self.last_updated = utcnow() - - def get_rrd_path(self): - """ - Returns the path to where RRD files are stored. - """ - return os.path.join(FLAGS.monitoring_rrd_path, self.instance_id) - - def fetch_cpu_stats(self): - """ - Returns cpu usage statistics for this instance. - """ - info = self.conn.get_info(self.instance_id) - - # Get the previous values. - cputime_last = self.cputime - cputime_last_updated = self.cputime_last_updated - - # Get the raw CPU time used in nanoseconds. - self.cputime = float(info['cpu_time']) - self.cputime_last_updated = utcnow() - - LOG.debug('CPU: %d', self.cputime) - - # Skip calculation on first pass. Need delta to get a meaningful value. - if cputime_last_updated is None: - return None - - # Calculate the number of seconds between samples. - d = self.cputime_last_updated - cputime_last_updated - t = d.days * 86400 + d.seconds - - LOG.debug('t = %d', t) - - # Calculate change over time in number of nanoseconds of CPU time used. - cputime_delta = self.cputime - cputime_last - - LOG.debug('cputime_delta = %s', cputime_delta) - - # Get the number of virtual cpus in this domain. - vcpus = int(info['num_cpu']) - - LOG.debug('vcpus = %d', vcpus) - - # Calculate CPU % used and cap at 100. - return min(cputime_delta / (t * vcpus * 1.0e9) * 100, 100) - - def fetch_disk_stats(self): - """ - Returns disk usage statistics for this instance. - """ - rd = 0 - wr = 0 - - disks = self.conn.get_disks(self.instance_id) - - # Aggregate the read and write totals. - for disk in disks: - try: - rd_req, rd_bytes, wr_req, wr_bytes, errs = \ - self.conn.block_stats(self.instance_id, disk) - rd += rd_bytes - wr += wr_bytes - except TypeError: - iid = self.instance_id - LOG.error(_('Cannot get blockstats for "%(disk)s"' - ' on "%(iid)s"') % locals()) - raise - - return '%d:%d' % (rd, wr) - - def fetch_net_stats(self): - """ - Returns network usage statistics for this instance. - """ - rx = 0 - tx = 0 - - interfaces = self.conn.get_interfaces(self.instance_id) - - # Aggregate the in and out totals. - for interface in interfaces: - try: - stats = self.conn.interface_stats(self.instance_id, interface) - rx += stats[0] - tx += stats[4] - except TypeError: - iid = self.instance_id - LOG.error(_('Cannot get ifstats for "%(interface)s"' - ' on "%(iid)s"') % locals()) - raise - - return '%d:%d' % (rx, tx) - - -class InstanceMonitor(object, service.Service): - """ - Monitors the running instances of the current machine. - """ - - def __init__(self): - """ - Initialize the monitoring loop. - """ - self._instances = {} - self._loop = task.LoopingCall(self.updateInstances) - - def startService(self): - self._instances = {} - self._loop.start(interval=FLAGS.monitoring_instances_delay) - service.Service.startService(self) - - def stopService(self): - self._loop.stop() - service.Service.stopService(self) - - def updateInstances(self): - """ - Update resource usage for all running instances. - """ - try: - conn = virt_connection.get_connection(read_only=True) - except Exception, exn: - LOG.exception(_('unexpected exception getting connection')) - time.sleep(FLAGS.monitoring_instances_delay) - return - - domain_ids = conn.list_instances() - try: - self.updateInstances_(conn, domain_ids) - except Exception, exn: - LOG.exception('updateInstances_') - - def updateInstances_(self, conn, domain_ids): - for domain_id in domain_ids: - if not domain_id in self._instances: - instance = Instance(conn, domain_id) - self._instances[domain_id] = instance - LOG.debug(_('Found instance: %s'), domain_id) - - for key in self._instances.keys(): - instance = self._instances[key] - if instance.needs_update(): - instance.update() diff --git a/nova/tests/test_twistd.py b/nova/tests/test_twistd.py deleted file mode 100644 index ff8627c3b..000000000 --- a/nova/tests/test_twistd.py +++ /dev/null @@ -1,53 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2010 United States Government as represented by the -# Administrator of the National Aeronautics and Space Administration. -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -import StringIO -import sys - -from nova import twistd -from nova import exception -from nova import flags -from nova import test - - -FLAGS = flags.FLAGS - - -class TwistdTestCase(test.TestCase): - def setUp(self): - super(TwistdTestCase, self).setUp() - self.Options = twistd.WrapTwistedOptions(twistd.TwistdServerOptions) - sys.stdout = StringIO.StringIO() - - def tearDown(self): - super(TwistdTestCase, self).tearDown() - sys.stdout = sys.__stdout__ - - def test_basic(self): - options = self.Options() - argv = options.parseOptions() - - def test_logfile(self): - options = self.Options() - argv = options.parseOptions(['--logfile=foo']) - self.assertEqual(FLAGS.logfile, 'foo') - - def test_help(self): - options = self.Options() - self.assertRaises(SystemExit, options.parseOptions, ['--help']) - self.assert_('pidfile' in sys.stdout.getvalue()) diff --git a/nova/twistd.py b/nova/twistd.py deleted file mode 100644 index 15cf67825..000000000 --- a/nova/twistd.py +++ /dev/null @@ -1,267 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2010 United States Government as represented by the -# Administrator of the National Aeronautics and Space Administration. -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -""" -Twisted daemon helpers, specifically to parse out gFlags from twisted flags, -manage pid files and support syslogging. -""" - -import gflags -import os -import signal -import sys -import time -from twisted.scripts import twistd -from twisted.python import log -from twisted.python import reflect -from twisted.python import runtime -from twisted.python import usage - -from nova import flags -from nova import log as logging - - -if runtime.platformType == "win32": - from twisted.scripts._twistw import ServerOptions -else: - from twisted.scripts._twistd_unix import ServerOptions - - -FLAGS = flags.FLAGS - - -class TwistdServerOptions(ServerOptions): - def parseArgs(self, *args): - return - - -class FlagParser(object): - # this is a required attribute for gflags - syntactic_help = '' - - def __init__(self, parser): - self.parser = parser - - def Parse(self, s): - return self.parser(s) - - -def WrapTwistedOptions(wrapped): - class TwistedOptionsToFlags(wrapped): - subCommands = None - - def __init__(self): - # NOTE(termie): _data exists because Twisted stuff expects - # to be able to set arbitrary things that are - # not actual flags - self._data = {} - self._flagHandlers = {} - self._paramHandlers = {} - - # Absorb the twistd flags into our FLAGS - self._absorbFlags() - self._absorbParameters() - self._absorbHandlers() - - wrapped.__init__(self) - - def _absorbFlags(self): - twistd_flags = [] - reflect.accumulateClassList(self.__class__, 'optFlags', - twistd_flags) - for flag in twistd_flags: - key = flag[0].replace('-', '_') - if hasattr(FLAGS, key): - continue - flags.DEFINE_boolean(key, None, str(flag[-1])) - - def _absorbParameters(self): - twistd_params = [] - reflect.accumulateClassList(self.__class__, 'optParameters', - twistd_params) - for param in twistd_params: - key = param[0].replace('-', '_') - if hasattr(FLAGS, key): - continue - if len(param) > 4: - flags.DEFINE(FlagParser(param[4]), - key, param[2], str(param[3]), - serializer=gflags.ArgumentSerializer()) - else: - flags.DEFINE_string(key, param[2], str(param[3])) - - def _absorbHandlers(self): - twistd_handlers = {} - reflect.addMethodNamesToDict(self.__class__, twistd_handlers, - "opt_") - - # NOTE(termie): Much of the following is derived/copied from - # twisted.python.usage with the express purpose of - # providing compatibility - for name in twistd_handlers.keys(): - method = getattr(self, 'opt_' + name) - - takesArg = not usage.flagFunction(method, name) - doc = getattr(method, '__doc__', None) - if not doc: - doc = 'undocumented' - - if not takesArg: - if name not in FLAGS: - flags.DEFINE_boolean(name, None, doc) - self._flagHandlers[name] = method - else: - if name not in FLAGS: - flags.DEFINE_string(name, None, doc) - self._paramHandlers[name] = method - - def _doHandlers(self): - for flag, handler in self._flagHandlers.iteritems(): - if self[flag]: - handler() - for param, handler in self._paramHandlers.iteritems(): - if self[param] is not None: - handler(self[param]) - - def __str__(self): - return str(FLAGS) - - def parseOptions(self, options=None): - if options is None: - options = sys.argv - else: - options.insert(0, '') - - args = FLAGS(options) - logging.setup() - argv = args[1:] - # ignore subcommands - - try: - self.parseArgs(*argv) - except TypeError: - raise usage.UsageError(_("Wrong number of arguments.")) - - self.postOptions() - return args - - def parseArgs(self, *args): - # TODO(termie): figure out a decent way of dealing with args - #return - wrapped.parseArgs(self, *args) - - def postOptions(self): - self._doHandlers() - - wrapped.postOptions(self) - - def __getitem__(self, key): - key = key.replace('-', '_') - try: - return getattr(FLAGS, key) - except (AttributeError, KeyError): - return self._data[key] - - def __setitem__(self, key, value): - key = key.replace('-', '_') - try: - return setattr(FLAGS, key, value) - except (AttributeError, KeyError): - self._data[key] = value - - def get(self, key, default): - key = key.replace('-', '_') - try: - return getattr(FLAGS, key) - except (AttributeError, KeyError): - self._data.get(key, default) - - return TwistedOptionsToFlags - - -def stop(pidfile): - """ - Stop the daemon - """ - # Get the pid from the pidfile - try: - pf = file(pidfile, 'r') - pid = int(pf.read().strip()) - pf.close() - except IOError: - pid = None - - if not pid: - message = _("pidfile %s does not exist. Daemon not running?\n") - sys.stderr.write(message % pidfile) - # Not an error in a restart - return - - # Try killing the daemon process - try: - while 1: - os.kill(pid, signal.SIGKILL) - time.sleep(0.1) - except OSError, err: - err = str(err) - if err.find(_("No such process")) > 0: - if os.path.exists(pidfile): - os.remove(pidfile) - else: - print str(err) - sys.exit(1) - - -def serve(filename): - logging.debug(_("Serving %s") % filename) - name = os.path.basename(filename) - OptionsClass = WrapTwistedOptions(TwistdServerOptions) - options = OptionsClass() - argv = options.parseOptions() - FLAGS.python = filename - FLAGS.no_save = True - if not FLAGS.pidfile: - FLAGS.pidfile = '%s.pid' % name - elif FLAGS.pidfile.endswith('twistd.pid'): - FLAGS.pidfile = FLAGS.pidfile.replace('twistd.pid', '%s.pid' % name) - if not FLAGS.prefix: - FLAGS.prefix = name - elif FLAGS.prefix.endswith('twisted'): - FLAGS.prefix = FLAGS.prefix.replace('twisted', name) - - action = 'start' - if len(argv) > 1: - action = argv.pop() - - if action == 'stop': - stop(FLAGS.pidfile) - sys.exit() - elif action == 'restart': - stop(FLAGS.pidfile) - elif action == 'start': - pass - else: - print 'usage: %s [options] [start|stop|restart]' % argv[0] - sys.exit(1) - - logging.debug(_("Full set of FLAGS:")) - for flag in FLAGS: - logging.debug("%s : %s" % (flag, FLAGS.get(flag, None))) - - logging.audit(_("Starting %s"), name) - twistd.runApp(options) diff --git a/nova/virt/fake.py b/nova/virt/fake.py index 26bc421c0..2898f23a4 100644 --- a/nova/virt/fake.py +++ b/nova/virt/fake.py @@ -340,8 +340,7 @@ class FakeConnection(driver.ComputeDriver): only useful for giving back to this layer as a parameter to disk_stats). These IDs only need to be unique for a given instance. - Note that this function takes an instance ID, not a - compute.service.Instance, so that it can be called by compute.monitor. + Note that this function takes an instance ID. """ return ['A_DISK'] @@ -353,8 +352,7 @@ class FakeConnection(driver.ComputeDriver): interface_stats). These IDs only need to be unique for a given instance. - Note that this function takes an instance ID, not a - compute.service.Instance, so that it can be called by compute.monitor. + Note that this function takes an instance ID. """ return ['A_VIF'] @@ -374,8 +372,7 @@ class FakeConnection(driver.ComputeDriver): having to do the aggregation. On those platforms, this method is unused. - Note that this function takes an instance ID, not a - compute.service.Instance, so that it can be called by compute.monitor. + Note that this function takes an instance ID. """ return [0L, 0L, 0L, 0L, None] @@ -395,8 +392,7 @@ class FakeConnection(driver.ComputeDriver): having to do the aggregation. On those platforms, this method is unused. - Note that this function takes an instance ID, not a - compute.service.Instance, so that it can be called by compute.monitor. + Note that this function takes an instance ID. """ return [0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L] diff --git a/nova/virt/libvirt/connection.py b/nova/virt/libvirt/connection.py index 17c328a83..b9b247aab 100644 --- a/nova/virt/libvirt/connection.py +++ b/nova/virt/libvirt/connection.py @@ -1087,8 +1087,7 @@ class LibvirtConnection(driver.ComputeDriver): def get_disks(self, instance_name): """ - Note that this function takes an instance name, not an Instance, so - that it can be called by monitor. + Note that this function takes an instance name. Returns a list of all block devices for this domain. """ @@ -1129,8 +1128,7 @@ class LibvirtConnection(driver.ComputeDriver): def get_interfaces(self, instance_name): """ - Note that this function takes an instance name, not an Instance, so - that it can be called by monitor. + Note that this function takes an instance name. Returns a list of all network interfaces for this instance. """ @@ -1345,16 +1343,14 @@ class LibvirtConnection(driver.ComputeDriver): def block_stats(self, instance_name, disk): """ - Note that this function takes an instance name, not an Instance, so - that it can be called by monitor. + Note that this function takes an instance name. """ domain = self._lookup_by_name(instance_name) return domain.blockStats(disk) def interface_stats(self, instance_name, interface): """ - Note that this function takes an instance name, not an Instance, so - that it can be called by monitor. + Note that this function takes an instance name. """ domain = self._lookup_by_name(instance_name) return domain.interfaceStats(interface) -- cgit From 804bbc7656080597880e9705532ac161d3124aa4 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Tue, 2 Aug 2011 10:21:36 -0400 Subject: fixing method naming problem --- nova/api/openstack/create_instance_helper.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index 5a745ded3..ea5d4af73 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -303,7 +303,7 @@ class ServerXMLDeserializer(wsgi.MetadataXMLDeserializer): action_deserializer = { 'createImage': self._action_create_image, - 'createBackup': self._action_create_image, + 'createBackup': self._action_create_backup, }.get(action_name, self.default) action_data = action_deserializer(action_node) @@ -319,7 +319,7 @@ class ServerXMLDeserializer(wsgi.MetadataXMLDeserializer): data['metadata'] = self.extract_metadata(metadata_node) return data - def _action_create_image(self, node): + def _action_create_backup(self, node): data = {} attributes = ['name', 'backup_type', 'rotation'] for attribute in attributes: -- cgit From 1fc4c4cb9bdfca2cf6a931cec44fa25ee76c502d Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Tue, 2 Aug 2011 10:24:23 -0400 Subject: abstraction of xml deserialization --- nova/api/openstack/create_instance_helper.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index ea5d4af73..b0f422655 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -311,18 +311,15 @@ class ServerXMLDeserializer(wsgi.MetadataXMLDeserializer): return {'body': {action_name: action_data}} def _action_create_image(self, node): - data = {} - value = node.getAttribute('name') - if value: - data['name'] = value - metadata_node = self.find_first_child_named(node, 'metadata') - data['metadata'] = self.extract_metadata(metadata_node) - return data + return self._deserialize_image_action(node, ('name',)) def _action_create_backup(self, node): + attributes = ('name', 'backup_type', 'rotation') + return self._deserialize_image_action(node, attributes) + + def _deserialize_image_action(self, node, allowed_attribtues): data = {} - attributes = ['name', 'backup_type', 'rotation'] - for attribute in attributes: + for attribute in allowed_attributes: value = node.getAttribute(attribute) if value: data[attribute] = value -- cgit From b65c7e2378d8344d1948fe4cf0dde66ef34b7204 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Tue, 2 Aug 2011 13:32:14 -0400 Subject: fixing typo --- nova/api/openstack/create_instance_helper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index b0f422655..53e814cd5 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -317,7 +317,7 @@ class ServerXMLDeserializer(wsgi.MetadataXMLDeserializer): attributes = ('name', 'backup_type', 'rotation') return self._deserialize_image_action(node, attributes) - def _deserialize_image_action(self, node, allowed_attribtues): + def _deserialize_image_action(self, node, allowed_attributes): data = {} for attribute in allowed_attributes: value = node.getAttribute(attribute) -- cgit From fa6cc945111dfa10fc98500765c71ced0c59015c Mon Sep 17 00:00:00 2001 From: "Kevin L. Mitchell" Date: Tue, 2 Aug 2011 18:36:12 +0000 Subject: Fix an error in fetch_image() --- nova/virt/xenapi/vm_utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index 2be5d0f4a..63bc191cf 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -376,10 +376,10 @@ class VMHelper(HelperBase): A list of dictionaries that describe VDIs, otherwise """ if image_type == ImageType.DISK_VHD: - return cls._fetch_image_glance_vhd( + return cls._fetch_image_glance_vhd(context, session, instance_id, image, image_type) else: - return cls._fetch_image_glance_disk( + return cls._fetch_image_glance_disk(context, session, instance_id, image, image_type) @classmethod -- cgit From 1c2bb5bf81e384a2e833dfb172cdaf72b6ecdbae Mon Sep 17 00:00:00 2001 From: "matt.dietz@rackspace.com" <> Date: Tue, 2 Aug 2011 13:56:58 -0500 Subject: Changed migration to be an admin only method and updated the tests --- nova/api/openstack/__init__.py | 1 + nova/api/openstack/servers.py | 20 ++++++++++---------- nova/tests/api/openstack/test_servers.py | 2 +- 3 files changed, 12 insertions(+), 11 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index 868b98a31..05baa0721 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -96,6 +96,7 @@ class APIRouter(base_wsgi.Router): server_members['suspend'] = 'POST' server_members['resume'] = 'POST' server_members['rescue'] = 'POST' + server_members['migrate'] = 'POST' server_members['unrescue'] = 'POST' server_members['reset_network'] = 'POST' server_members['inject_network_info'] = 'POST' diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 8d2ccc2ad..78e650c88 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -163,8 +163,7 @@ class Controller(object): 'resize': self._action_resize, 'confirmResize': self._action_confirm_resize, 'revertResize': self._action_revert_resize, - 'rebuild': self._action_rebuild, - 'migrate': self._action_migrate} + 'rebuild': self._action_rebuild,} for key in actions.keys(): if key in body: @@ -208,14 +207,6 @@ class Controller(object): raise exc.HTTPUnprocessableEntity() return webob.Response(status_int=202) - def _action_migrate(self, input_dict, req, id): - try: - self.compute_api.resize(req.environ['nova.context'], id) - except Exception, e: - LOG.exception(_("Error in migrate %s"), e) - raise exc.HTTPBadRequest() - return webob.Response(status_int=202) - @scheduler_api.redirect_handler def lock(self, req, id): """ @@ -341,6 +332,15 @@ class Controller(object): raise exc.HTTPUnprocessableEntity() return webob.Response(status_int=202) + @scheduler_api.redirect_handler + def migrate(self, req, id): + try: + self.compute_api.resize(req.environ['nova.context'], id) + except Exception, e: + LOG.exception(_("Error in migrate %s"), e) + raise exc.HTTPBadRequest() + return webob.Response(status_int=202) + @scheduler_api.redirect_handler def rescue(self, req, id): """Permit users to rescue the server.""" diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 14ce42837..c41dbc046 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -2315,7 +2315,7 @@ class ServersTest(test.TestCase): """This is basically the same as resize, only we provide the `migrate` attribute in the body's dict. """ - req = self.webreq('/1/action', 'POST', dict(migrate=None)) + req = self.webreq('/1/migrate', 'POST') self.resize_called = False -- cgit From 0d8942fc5f47a5f434115ac0c1444b6485c6ba1f Mon Sep 17 00:00:00 2001 From: Matt Dietz Date: Tue, 2 Aug 2011 20:23:12 +0000 Subject: Bad merge res --- nova/api/openstack/servers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 070b89a60..002b47edb 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -163,7 +163,7 @@ class Controller(object): 'resize': self._action_resize, 'confirmResize': self._action_confirm_resize, 'revertResize': self._action_revert_resize, - 'rebuild': self._action_rebuild,} + 'rebuild': self._action_rebuild, 'createImage': self._action_create_image, } -- cgit