From 6b83e1cd31f5e138af20fbd5c118d55da092eb35 Mon Sep 17 00:00:00 2001 From: Ed Leafe Date: Thu, 7 Jul 2011 15:24:12 +0000 Subject: Added API and supporting code for rebooting or shutting down XenServer hosts. --- nova/api/openstack/contrib/hosts.py | 26 ++++++++++++++++++++++++++ nova/compute/api.py | 6 ++++++ nova/compute/manager.py | 6 ++++++ nova/tests/test_hosts.py | 32 ++++++++++++++++++++++++++++---- nova/virt/driver.py | 4 ++++ nova/virt/fake.py | 4 ++++ nova/virt/hyperv.py | 4 ++++ nova/virt/libvirt/connection.py | 4 ++++ nova/virt/vmwareapi_conn.py | 4 ++++ nova/virt/xenapi/vmops.py | 21 ++++++++++++++++++--- nova/virt/xenapi_conn.py | 4 ++++ 11 files changed, 108 insertions(+), 7 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/contrib/hosts.py b/nova/api/openstack/contrib/hosts.py index 55e57e1a4..cc71cadbd 100644 --- a/nova/api/openstack/contrib/hosts.py +++ b/nova/api/openstack/contrib/hosts.py @@ -78,6 +78,12 @@ class HostController(object): else: explanation = _("Invalid status: '%s'") % raw_val raise webob.exc.HTTPBadRequest(explanation=explanation) + elif key == "power_state": + if val in ("reboot", "off", "on"): + return self._set_power_state(req, id, val) + else: + explanation = _("Invalid status: '%s'") % raw_val + raise webob.exc.HTTPBadRequest(explanation=explanation) else: explanation = _("Invalid update setting: '%s'") % raw_key raise webob.exc.HTTPBadRequest(explanation=explanation) @@ -89,8 +95,28 @@ class HostController(object): LOG.audit(_("Setting host %(host)s to %(state)s.") % locals()) result = self.compute_api.set_host_enabled(context, host=host, enabled=enabled) + if result not in ("enabled", "disabled"): + # An error message was returned + raise webob.exc.HTTPBadRequest(explanation=result) return {"host": host, "status": result} + def _set_power_state(self, req, host, power_state): + """Turns the specified host on/off, or reboots the host.""" + context = req.environ['nova.context'] + if power_state == "on": + raise webob.exc.HTTPNotImplemented() + if power_state == "reboot": + msg = _("Rebooting host %(host)s") + else: + msg = _("Powering off host %(host)s.") + LOG.audit(msg % locals()) + result = self.compute_api.set_power_state(context, host=host, + power_state=power_state) + if result != power_state: + # An error message was returned + raise webob.exc.HTTPBadRequest(explanation=result) + return {"host": host, "power_state": result} + class Hosts(extensions.ExtensionDescriptor): def get_name(self): diff --git a/nova/compute/api.py b/nova/compute/api.py index b0eedcd64..71e11c5ea 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -917,6 +917,12 @@ class API(base.Base): return self._call_compute_message("set_host_enabled", context, instance_id=None, host=host, params={"enabled": enabled}) + def set_power_state(self, context, host, power_state): + """Turns the specified host on/off, or reboots the host.""" + return self._call_compute_message("set_power_state", context, + instance_id=None, host=host, + params={"power_state": power_state}) + @scheduler_api.reroute_compute("diagnostics") def get_diagnostics(self, context, instance_id): """Retrieve diagnostics for the given instance.""" diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 91a604934..eb8e4df3c 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -880,6 +880,12 @@ class ComputeManager(manager.SchedulerDependentManager): """Sets the specified host's ability to accept new instances.""" return self.driver.set_host_enabled(host, enabled) + @exception.wrap_exception + def set_power_state(self, context, instance_id=None, host=None, + power_state=None): + """Turns the specified host on/off, or reboots the host.""" + return self.driver.set_power_state(host, power_state) + @exception.wrap_exception def get_diagnostics(self, context, instance_id): """Retrieve diagnostics for an instance on this host.""" diff --git a/nova/tests/test_hosts.py b/nova/tests/test_hosts.py index 548f81f8b..5a52e36e2 100644 --- a/nova/tests/test_hosts.py +++ b/nova/tests/test_hosts.py @@ -48,6 +48,16 @@ def stub_set_host_enabled(context, host, enabled): return status +def stub_set_power_state(context, host, power_state): + # We'll simulate success and failure by assuming + # that 'host_c1' always succeeds, and 'host_c2' + # always fails + if host == "host_c1": + return power_state + else: + return "fail" + + class FakeRequest(object): environ = {"nova.context": context.get_admin_context()} @@ -62,6 +72,8 @@ class HostTestCase(test.TestCase): self.stubs.Set(scheduler_api, 'get_host_list', stub_get_host_list) self.stubs.Set(self.controller.compute_api, 'set_host_enabled', stub_set_host_enabled) + self.stubs.Set(self.controller.compute_api, 'set_power_state', + stub_set_power_state) def test_list_hosts(self): """Verify that the compute hosts are returned.""" @@ -87,15 +99,27 @@ class HostTestCase(test.TestCase): result_c2 = self.controller.update(self.req, "host_c2", body=en_body) self.assertEqual(result_c2["status"], "disabled") + def test_power_state(self): + en_body = {"power_state": "reboot"} + result_c1 = self.controller.update(self.req, "host_c1", body=en_body) + self.assertEqual(result_c1["power_state"], "reboot") + result_c2 = self.controller.update(self.req, "host_c2", body=en_body) + self.assertEqual(result_c2["power_state"], "fail") + + def test_bad_power_state_value(self): + bad_body = {"power_state": "bad"} + result = self.controller.update(self.req, "host_c1", body=bad_body) + self.assertEqual(str(result.wrapped_exc)[:15], "400 Bad Request") + def test_bad_status_value(self): bad_body = {"status": "bad"} - self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update, - self.req, "host_c1", body=bad_body) + result = self.controller.update(self.req, "host_c1", body=bad_body) + self.assertEqual(str(result.wrapped_exc)[:15], "400 Bad Request") def test_bad_update_key(self): bad_body = {"crazy": "bad"} - self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update, - self.req, "host_c1", body=bad_body) + result = self.controller.update(self.req, "host_c1", body=bad_body) + self.assertEqual(str(result.wrapped_exc)[:15], "400 Bad Request") def test_bad_host(self): self.assertRaises(exception.HostNotFound, self.controller.update, diff --git a/nova/virt/driver.py b/nova/virt/driver.py index 3c4a073bf..eed32d8d6 100644 --- a/nova/virt/driver.py +++ b/nova/virt/driver.py @@ -253,3 +253,7 @@ class ComputeDriver(object): def set_host_enabled(self, host, enabled): """Sets the specified host's ability to accept new instances.""" raise NotImplementedError() + + def set_power_state(self, host, power_state): + """Reboots, shuts down or starts up the host.""" + raise NotImplementedError() diff --git a/nova/virt/fake.py b/nova/virt/fake.py index ea0a59f21..0596079e8 100644 --- a/nova/virt/fake.py +++ b/nova/virt/fake.py @@ -518,3 +518,7 @@ class FakeConnection(driver.ComputeDriver): def set_host_enabled(self, host, enabled): """Sets the specified host's ability to accept new instances.""" pass + + def set_power_state(self, host, power_state): + """Reboots, shuts down or starts up the host.""" + pass diff --git a/nova/virt/hyperv.py b/nova/virt/hyperv.py index 5c1dc772d..a438ff2e8 100644 --- a/nova/virt/hyperv.py +++ b/nova/virt/hyperv.py @@ -503,3 +503,7 @@ class HyperVConnection(driver.ComputeDriver): def set_host_enabled(self, host, enabled): """Sets the specified host's ability to accept new instances.""" pass + + def set_power_state(self, host, power_state): + """Reboots, shuts down or starts up the host.""" + pass diff --git a/nova/virt/libvirt/connection.py b/nova/virt/libvirt/connection.py index b80a3daee..2a02e5a2d 100644 --- a/nova/virt/libvirt/connection.py +++ b/nova/virt/libvirt/connection.py @@ -1595,3 +1595,7 @@ class LibvirtConnection(driver.ComputeDriver): def set_host_enabled(self, host, enabled): """Sets the specified host's ability to accept new instances.""" pass + + def set_power_state(self, host, power_state): + """Reboots, shuts down or starts up the host.""" + pass diff --git a/nova/virt/vmwareapi_conn.py b/nova/virt/vmwareapi_conn.py index d80e14931..0136225dd 100644 --- a/nova/virt/vmwareapi_conn.py +++ b/nova/virt/vmwareapi_conn.py @@ -194,6 +194,10 @@ class VMWareESXConnection(driver.ComputeDriver): """Sets the specified host's ability to accept new instances.""" pass + def set_power_state(self, host, power_state): + """Reboots, shuts down or starts up the host.""" + pass + class VMWareAPISession(object): """ diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index cb96930c1..ec90ba9fe 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -936,10 +936,25 @@ class VMOps(object): def set_host_enabled(self, host, enabled): """Sets the specified host's ability to accept new instances.""" args = {"enabled": json.dumps(enabled)} - json_resp = self._call_xenhost("set_host_enabled", args) - resp = json.loads(json_resp) + xenapi_resp = self._call_xenhost("set_host_enabled", args) + try: + resp = json.loads(xenapi_resp) + except TypeError as e: + # Already logged; return the message + return xenapi_resp.details[-1] return resp["status"] + def set_power_state(self, host, power_state): + """Reboots, shuts down or starts up the host.""" + args = {"power_state": power_state} + xenapi_resp = self._call_xenhost("set_power_state", args) + try: + resp = json.loads(xenapi_resp) + except TypeError as e: + # Already logged; return the message + return xenapi_resp.details[-1] + return resp["power_state"] + def _call_xenhost(self, method, arg_dict): """There will be several methods that will need this general handling for interacting with the xenhost plugin, so this abstracts @@ -953,7 +968,7 @@ class VMOps(object): #args={"params": arg_dict}) ret = self._session.wait_for_task(task, task_id) except self.XenAPI.Failure as e: - ret = None + ret = e LOG.error(_("The call to %(method)s returned an error: %(e)s.") % locals()) return ret diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index ec8c44c1c..0b88e0999 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -340,6 +340,10 @@ class XenAPIConnection(driver.ComputeDriver): """Sets the specified host's ability to accept new instances.""" return self._vmops.set_host_enabled(host, enabled) + def set_power_state(self, host, power_state): + """Reboots, shuts down or starts up the host.""" + return self._vmops.set_power_state(host, power_state) + class XenAPISession(object): """The session to invoke XenAPI SDK calls""" -- cgit From 60a9763382ccd77735a75b6047c821477eab684e Mon Sep 17 00:00:00 2001 From: Ed Leafe Date: Thu, 7 Jul 2011 15:36:39 +0000 Subject: pep8 fixes --- nova/tests/test_hosts.py | 16 ++++++++-------- nova/virt/xenapi/vmops.py | 4 ++-- 2 files changed, 10 insertions(+), 10 deletions(-) (limited to 'nova') diff --git a/nova/tests/test_hosts.py b/nova/tests/test_hosts.py index 5a52e36e2..417737638 100644 --- a/nova/tests/test_hosts.py +++ b/nova/tests/test_hosts.py @@ -103,23 +103,23 @@ class HostTestCase(test.TestCase): en_body = {"power_state": "reboot"} result_c1 = self.controller.update(self.req, "host_c1", body=en_body) self.assertEqual(result_c1["power_state"], "reboot") - result_c2 = self.controller.update(self.req, "host_c2", body=en_body) - self.assertEqual(result_c2["power_state"], "fail") + self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update, + self.req, "host_c2", body=en_body) def test_bad_power_state_value(self): bad_body = {"power_state": "bad"} - result = self.controller.update(self.req, "host_c1", body=bad_body) - self.assertEqual(str(result.wrapped_exc)[:15], "400 Bad Request") + self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update, + self.req, "host_c1", body=bad_body) def test_bad_status_value(self): bad_body = {"status": "bad"} - result = self.controller.update(self.req, "host_c1", body=bad_body) - self.assertEqual(str(result.wrapped_exc)[:15], "400 Bad Request") + self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update, + self.req, "host_c1", body=bad_body) def test_bad_update_key(self): bad_body = {"crazy": "bad"} - result = self.controller.update(self.req, "host_c1", body=bad_body) - self.assertEqual(str(result.wrapped_exc)[:15], "400 Bad Request") + self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update, + self.req, "host_c1", body=bad_body) def test_bad_host(self): self.assertRaises(exception.HostNotFound, self.controller.update, diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index ec90ba9fe..aec802eff 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -941,7 +941,7 @@ class VMOps(object): resp = json.loads(xenapi_resp) except TypeError as e: # Already logged; return the message - return xenapi_resp.details[-1] + return xenapi_resp.details[-1] return resp["status"] def set_power_state(self, host, power_state): @@ -952,7 +952,7 @@ class VMOps(object): resp = json.loads(xenapi_resp) except TypeError as e: # Already logged; return the message - return xenapi_resp.details[-1] + return xenapi_resp.details[-1] return resp["power_state"] def _call_xenhost(self, method, arg_dict): -- cgit From 718d4cf5cd4122bcecf0974c441d098f57a124b0 Mon Sep 17 00:00:00 2001 From: "Dave Walker (Daviey)" Date: Sun, 17 Jul 2011 22:49:22 +0100 Subject: Initial test case proving we have a bug of, ec2 security group name can exceed 255 chars. --- nova/tests/test_api.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) (limited to 'nova') diff --git a/nova/tests/test_api.py b/nova/tests/test_api.py index 20b20fcbf..63f040ffd 100644 --- a/nova/tests/test_api.py +++ b/nova/tests/test_api.py @@ -293,6 +293,26 @@ class ApiEc2TestCase(test.TestCase): self.manager.delete_project(project) self.manager.delete_user(user) + def test_group_name_valid_security_group(self): + """Test that we sanely handle invalid security group names. """ + self.expect_http() + self.mox.ReplayAll() + user = self.manager.create_user('fake', 'fake', 'fake', admin=True) + project = self.manager.create_project('fake', 'fake', 'fake') + + # At the moment, you need both of these to actually be netadmin + self.manager.add_role('fake', 'netadmin') + project.add_role('fake', 'netadmin') + + security_group_name = "".join(random.choice("poiuytrewqasdfghjklmnbvc") + for x in range(random.randint(256, 266))) + try: + self.ec2.create_security_group(security_group_name, 'test group') + except: + pass + else: + self.fail('Exception not raised.') + def test_authorize_revoke_security_group_cidr(self): """ Test that we can add and remove CIDR based rules -- cgit From 5c6e4aa80672966ad4449007feea970cd62dee10 Mon Sep 17 00:00:00 2001 From: "Dave Walker (Daviey)" Date: Sun, 17 Jul 2011 23:52:50 +0100 Subject: Some basic validation for creating ec2 security groups. (LP: #715443) --- nova/api/ec2/__init__.py | 4 ++++ nova/api/ec2/cloud.py | 17 +++++++++++++++++ nova/exception.py | 4 ++++ 3 files changed, 25 insertions(+) (limited to 'nova') diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py index 890d57fe7..027e35933 100644 --- a/nova/api/ec2/__init__.py +++ b/nova/api/ec2/__init__.py @@ -349,6 +349,10 @@ class Executor(wsgi.Application): LOG.debug(_('KeyPairExists raised: %s'), unicode(ex), context=context) return self._error(req, context, type(ex).__name__, unicode(ex)) + except exception.InvalidParameterValue as ex: + LOG.debug(_('InvalidParameterValue raised: %s'), unicode(ex), + context=context) + return self._error(req, context, type(ex).__name__, unicode(ex)) except Exception as ex: extra = {'environment': req.environ} LOG.exception(_('Unexpected error raised: %s'), unicode(ex), diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index acfd1361c..3ef64afa7 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -28,6 +28,7 @@ import os import urllib import tempfile import shutil +import re from nova import compute from nova import context @@ -602,6 +603,22 @@ class CloudController(object): return source_project_id def create_security_group(self, context, group_name, group_description): + if not re.match('^[a-zA-Z0-9_\- ]+$',group_name): + # Some validation to ensure that values match API spec. + # - Alphanumeric characters, spaces, dashes, and underscores. + # TODO(Daviey): extend beyond group_name checking, and probably + # create a param validator function that can be used elsewhere. + err = _("Value (%s) for parameter GroupName is invalid." + " Content limited to Alphanumeric characters, " + "spaces, dashes, and underscores.") % group_name + # err not that of master ec2 implementation, as they fail to raise. + raise exception.InvalidParameterValue(err=err) + + if len(str(group_name)) > 255: + err = _("Value (%s) for parameter GroupName is invalid." + " Length exceeds maximum of 255.") % group_name + raise exception.InvalidParameterValue(err=err) + LOG.audit(_("Create Security Group %s"), group_name, context=context) self.compute_api.ensure_default_security_group(context) if db.security_group_exists(context, context.project_id, group_name): diff --git a/nova/exception.py b/nova/exception.py index ad6c005f8..8771328d8 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -196,6 +196,10 @@ class InvalidIpProtocol(Invalid): class InvalidContentType(Invalid): message = _("Invalid content type %(content_type)s.") +class InvalidParameterValue(Invalid): + # Cannot be templated as the error syntax varies. + # msg needs to be constructed when raised. + message = _("%(err)s") class InstanceNotRunning(Invalid): message = _("Instance %(instance_id)s is not running.") -- cgit From 64a03d48bd714672a3d68136d365bf941201affa Mon Sep 17 00:00:00 2001 From: "Dave Walker (Daviey)" Date: Mon, 18 Jul 2011 00:06:48 +0100 Subject: Extended test to check for error specific error code and test cover for bad chars. --- nova/tests/test_api.py | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/tests/test_api.py b/nova/tests/test_api.py index 63f040ffd..de399d76e 100644 --- a/nova/tests/test_api.py +++ b/nova/tests/test_api.py @@ -304,12 +304,32 @@ class ApiEc2TestCase(test.TestCase): self.manager.add_role('fake', 'netadmin') project.add_role('fake', 'netadmin') + # Test block group_name of non alphanumeric characters, spaces, + # dashes, and underscores. + security_group_name = "aa #$% -=99" + + try: + self.ec2.create_security_group(security_group_name, 'test group') + except EC2ResponseError, e: + if e.code == 'InvalidParameterValue': + pass + else: + self.fail("Unexpected EC2ResponseError: %s " + "(expected InvalidParameterValue)" % e.code) + else: + self.fail('Exception not raised.') + + # Test block group_name > 255 chars security_group_name = "".join(random.choice("poiuytrewqasdfghjklmnbvc") for x in range(random.randint(256, 266))) try: self.ec2.create_security_group(security_group_name, 'test group') - except: - pass + except EC2ResponseError, e: + if e.code == 'InvalidParameterValue': + pass + else: + self.fail("Unexpected EC2ResponseError: %s " + "(expected InvalidParameterValue)" % e.code) else: self.fail('Exception not raised.') -- cgit From 9d0b441939ab5a9227e91bb868f499d700c7c907 Mon Sep 17 00:00:00 2001 From: "Dave Walker (Daviey)" Date: Mon, 18 Jul 2011 00:16:53 +0100 Subject: pep8'd --- nova/api/ec2/cloud.py | 6 +++--- nova/exception.py | 6 ++++-- nova/tests/test_api.py | 4 ++-- 3 files changed, 9 insertions(+), 7 deletions(-) (limited to 'nova') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 3ef64afa7..8d7aa9953 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -603,11 +603,11 @@ class CloudController(object): return source_project_id def create_security_group(self, context, group_name, group_description): - if not re.match('^[a-zA-Z0-9_\- ]+$',group_name): + if not re.match('^[a-zA-Z0-9_\- ]+$', group_name): # Some validation to ensure that values match API spec. # - Alphanumeric characters, spaces, dashes, and underscores. - # TODO(Daviey): extend beyond group_name checking, and probably - # create a param validator function that can be used elsewhere. + # TODO(Daviey): extend beyond group_name checking, and probably + # create a param validator function that can be used elsewhere. err = _("Value (%s) for parameter GroupName is invalid." " Content limited to Alphanumeric characters, " "spaces, dashes, and underscores.") % group_name diff --git a/nova/exception.py b/nova/exception.py index 8771328d8..8f3cf0af6 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -196,11 +196,13 @@ class InvalidIpProtocol(Invalid): class InvalidContentType(Invalid): message = _("Invalid content type %(content_type)s.") + +# Cannot be templated as the error syntax varies. +# msg needs to be constructed when raised. class InvalidParameterValue(Invalid): - # Cannot be templated as the error syntax varies. - # msg needs to be constructed when raised. message = _("%(err)s") + class InstanceNotRunning(Invalid): message = _("Instance %(instance_id)s is not running.") diff --git a/nova/tests/test_api.py b/nova/tests/test_api.py index de399d76e..6e4f2c95e 100644 --- a/nova/tests/test_api.py +++ b/nova/tests/test_api.py @@ -304,8 +304,8 @@ class ApiEc2TestCase(test.TestCase): self.manager.add_role('fake', 'netadmin') project.add_role('fake', 'netadmin') - # Test block group_name of non alphanumeric characters, spaces, - # dashes, and underscores. + # Test block group_name of non alphanumeric characters, spaces, + # dashes, and underscores. security_group_name = "aa #$% -=99" try: -- cgit From beb2337f002178b7e764f3a6dcbab4637321aa34 Mon Sep 17 00:00:00 2001 From: "Dave Walker (Daviey)" Date: Mon, 18 Jul 2011 00:22:01 +0100 Subject: nova/api/ec2/cloud.py: Rearranged imports to be alphabetical as per HACKING. --- nova/api/ec2/cloud.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'nova') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 8d7aa9953..9de5b58f5 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -25,10 +25,10 @@ datastore. import base64 import netaddr import os -import urllib -import tempfile -import shutil import re +import shutil +import tempfile +import urllib from nova import compute from nova import context -- cgit From e68d53df98890f424e361c7c79a5b2cd62723963 Mon Sep 17 00:00:00 2001 From: "Dave Walker (Daviey)" Date: Mon, 18 Jul 2011 00:41:51 +0100 Subject: convert group_name to string, incase it's a long --- nova/api/ec2/cloud.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 9de5b58f5..e4b008b85 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -603,7 +603,7 @@ class CloudController(object): return source_project_id def create_security_group(self, context, group_name, group_description): - if not re.match('^[a-zA-Z0-9_\- ]+$', group_name): + if not re.match('^[a-zA-Z0-9_\- ]+$', str(group_name)): # Some validation to ensure that values match API spec. # - Alphanumeric characters, spaces, dashes, and underscores. # TODO(Daviey): extend beyond group_name checking, and probably -- cgit From 5e9e62c2382f29a55b9b0c7a2b4aefc16b9d623d Mon Sep 17 00:00:00 2001 From: "Dave Walker (Daviey)" Date: Wed, 20 Jul 2011 20:11:47 +0100 Subject: Split tests into 2 --- nova/tests/test_api.py | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) (limited to 'nova') diff --git a/nova/tests/test_api.py b/nova/tests/test_api.py index 48a43a46b..5759e7726 100644 --- a/nova/tests/test_api.py +++ b/nova/tests/test_api.py @@ -363,8 +363,10 @@ class ApiEc2TestCase(test.TestCase): self.manager.delete_project(project) self.manager.delete_user(user) - def test_group_name_valid_security_group(self): - """Test that we sanely handle invalid security group names. """ + def test_group_name_valid_chars_security_group(self): + """ Test that we sanely handle invalid security group names. + API Spec states we should only accept alphanumeric characters, + spaces, dashes, and underscores. """ self.expect_http() self.mox.ReplayAll() user = self.manager.create_user('fake', 'fake', 'fake', admin=True) @@ -376,7 +378,7 @@ class ApiEc2TestCase(test.TestCase): # Test block group_name of non alphanumeric characters, spaces, # dashes, and underscores. - security_group_name = "aa #$% -=99" + security_group_name = "aa #^% -=99" try: self.ec2.create_security_group(security_group_name, 'test group') @@ -389,6 +391,18 @@ class ApiEc2TestCase(test.TestCase): else: self.fail('Exception not raised.') + def test_group_name_valid_length_security_group(self): + """Test that we sanely handle invalid security group names. + API Spec states that the length should not exceed 255 chars """ + self.expect_http() + self.mox.ReplayAll() + user = self.manager.create_user('fake', 'fake', 'fake', admin=True) + project = self.manager.create_project('fake', 'fake', 'fake') + + # At the moment, you need both of these to actually be netadmin + self.manager.add_role('fake', 'netadmin') + project.add_role('fake', 'netadmin') + # Test block group_name > 255 chars security_group_name = "".join(random.choice("poiuytrewqasdfghjklmnbvc") for x in range(random.randint(256, 266))) -- cgit From 25bd75bfd2c72899bf139e671fd42fd2dc1dc0e1 Mon Sep 17 00:00:00 2001 From: "Dave Walker (Daviey)" Date: Wed, 20 Jul 2011 20:12:19 +0100 Subject: Added LP bug num to TODO --- nova/api/ec2/cloud.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index cd493e3e7..ecdbad895 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -695,8 +695,8 @@ class CloudController(object): if not re.match('^[a-zA-Z0-9_\- ]+$', str(group_name)): # Some validation to ensure that values match API spec. # - Alphanumeric characters, spaces, dashes, and underscores. - # TODO(Daviey): extend beyond group_name checking, and probably - # create a param validator function that can be used elsewhere. + # TODO(Daviey): LP: #813685 extend beyond group_name checking, and + # probably create a param validator that can be used elsewhere. err = _("Value (%s) for parameter GroupName is invalid." " Content limited to Alphanumeric characters, " "spaces, dashes, and underscores.") % group_name -- cgit From 77347efae5171e5a6ffa5af885c0ffd7220688cf Mon Sep 17 00:00:00 2001 From: "matt.dietz@rackspace.com" <> Date: Wed, 20 Jul 2011 15:38:29 -0500 Subject: Fix plus passing tests --- nova/tests/test_xenapi.py | 39 +++++++++++++++++++++++++++++++++++++++ nova/virt/xenapi/vmops.py | 9 ++++++--- 2 files changed, 45 insertions(+), 3 deletions(-) (limited to 'nova') diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index 4cb7447d3..9b512b73b 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -785,6 +785,45 @@ class XenAPIMigrateInstance(test.TestCase): def test_finish_resize(self): instance = db.instance_create(self.context, self.values) + self.called = False + + def fake_vdi_resize(*args, **kwargs): + self.called = True + + self.stubs.Set(stubs.FakeSessionForMigrationTests, + "VDI_resize_online", fake_vdi_resize) + stubs.stubout_session(self.stubs, stubs.FakeSessionForMigrationTests) + stubs.stubout_loopingcall_start(self.stubs) + conn = xenapi_conn.get_connection(False) + network_info = [({'bridge': 'fa0', 'id': 0, 'injected': False}, + {'broadcast': '192.168.0.255', + 'dns': ['192.168.0.1'], + 'gateway': '192.168.0.1', + 'gateway6': 'dead:beef::1', + 'ip6s': [{'enabled': '1', + 'ip': 'dead:beef::dcad:beff:feef:0', + 'netmask': '64'}], + 'ips': [{'enabled': '1', + 'ip': '192.168.0.100', + 'netmask': '255.255.255.0'}], + 'label': 'fake', + 'mac': 'DE:AD:BE:EF:00:00', + 'rxtx_cap': 3})] + conn.finish_resize(instance, dict(base_copy='hurr', cow='durr'), + network_info) + self.assertEqual(self.called, True) + + def test_finish_migrate_no_resize_vdi(self): + tiny_type_id = \ + instance_types.get_instance_type_by_name('m1.tiny')['id'] + self.values.update({'instance_type_id': tiny_type_id, 'local_gb': 0}) + instance = db.instance_create(self.context, self.values) + + def fake_vdi_resize(*args, **kwargs): + raise Exception("This shouldn't be called") + + self.stubs.Set(stubs.FakeSessionForMigrationTests, + "VDI_resize_online", fake_vdi_resize) stubs.stubout_session(self.stubs, stubs.FakeSessionForMigrationTests) stubs.stubout_loopingcall_start(self.stubs) conn = xenapi_conn.get_connection(False) diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index c332c27b0..8bce6bb89 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -566,17 +566,20 @@ class VMOps(object): return new_cow_uuid def resize_instance(self, instance, vdi_uuid): - """Resize a running instance by changing it's RAM and disk size.""" + """Resize a running instance by changing its RAM and disk size.""" #TODO(mdietz): this will need to be adjusted for swap later #The new disk size must be in bytes - new_disk_size = str(instance.local_gb * 1024 * 1024 * 1024) + new_disk_size = instance.local_gb * 1024 * 1024 * 1024 instance_name = instance.name instance_local_gb = instance.local_gb LOG.debug(_("Resizing VDI %(vdi_uuid)s for instance %(instance_name)s." " Expanding to %(instance_local_gb)d GB") % locals()) vdi_ref = self._session.call_xenapi('VDI.get_by_uuid', vdi_uuid) - self._session.call_xenapi('VDI.resize_online', vdi_ref, new_disk_size) + # for an instance with no local storage + if new_disk_size > 0: + self._session.call_xenapi('VDI.resize_online', vdi_ref, + str(new_disk_size)) LOG.debug(_("Resize instance %s complete") % (instance.name)) def reboot(self, instance): -- cgit From 76aab6d65fa35ae88f9b16acd4ee2968dfe049ce Mon Sep 17 00:00:00 2001 From: "matt.dietz@rackspace.com" <> Date: Wed, 20 Jul 2011 16:56:45 -0500 Subject: CHanges based on feedback --- nova/compute/manager.py | 20 ++++++++++++-------- nova/tests/test_xenapi.py | 36 +++++++++++++++++++++++++++++++++--- nova/virt/xenapi/vmops.py | 9 +++++---- nova/virt/xenapi_conn.py | 6 ++++-- 4 files changed, 54 insertions(+), 17 deletions(-) (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index eb3996d29..01a7d195f 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -820,20 +820,24 @@ class ComputeManager(manager.SchedulerDependentManager): migration_ref['instance_id']) # TODO(mdietz): apply the rest of the instance_type attributes going # after they're supported - instance_type = self.db.instance_type_get_by_flavor_id(context, - migration_ref['new_flavor_id']) - self.db.instance_update(context, instance_id, - dict(instance_type_id=instance_type['id'], - memory_mb=instance_type['memory_mb'], - vcpus=instance_type['vcpus'], - local_gb=instance_type['local_gb'])) + resize_instance = False + if migration_ref['old_flavor_id'] != migration_ref['new_flavor_id']: + instance_type = self.db.instance_type_get_by_flavor_id(context, + migration_ref['new_flavor_id']) + self.db.instance_update(context, instance_id, + dict(instance_type_id=instance_type['id'], + memory_mb=instance_type['memory_mb'], + vcpus=instance_type['vcpus'], + local_gb=instance_type['local_gb'])) + resize_instance = True # reload the updated instance ref # FIXME(mdietz): is there reload functionality? instance = self.db.instance_get(context, instance_id) network_info = self.network_api.get_instance_nw_info(context, instance) - self.driver.finish_resize(instance, disk_info, network_info) + self.driver.finish_resize(instance, disk_info, network_info, + resize_instance) self.db.migration_update(context, migration_id, {'status': 'finished', }) diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index 9b512b73b..be263d17c 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -810,10 +810,10 @@ class XenAPIMigrateInstance(test.TestCase): 'mac': 'DE:AD:BE:EF:00:00', 'rxtx_cap': 3})] conn.finish_resize(instance, dict(base_copy='hurr', cow='durr'), - network_info) + network_info, resize_instance=True) self.assertEqual(self.called, True) - def test_finish_migrate_no_resize_vdi(self): + def test_finish_migrate_no_local_storage(self): tiny_type_id = \ instance_types.get_instance_type_by_name('m1.tiny')['id'] self.values.update({'instance_type_id': tiny_type_id, 'local_gb': 0}) @@ -842,7 +842,37 @@ class XenAPIMigrateInstance(test.TestCase): 'mac': 'DE:AD:BE:EF:00:00', 'rxtx_cap': 3})] conn.finish_resize(instance, dict(base_copy='hurr', cow='durr'), - network_info) + network_info, resize_instance=True) + + def test_finish_migrate_no_resize_vdi(self): + instance = db.instance_create(self.context, self.values) + + def fake_vdi_resize(*args, **kwargs): + raise Exception("This shouldn't be called") + + self.stubs.Set(stubs.FakeSessionForMigrationTests, + "VDI_resize_online", fake_vdi_resize) + stubs.stubout_session(self.stubs, stubs.FakeSessionForMigrationTests) + stubs.stubout_loopingcall_start(self.stubs) + conn = xenapi_conn.get_connection(False) + network_info = [({'bridge': 'fa0', 'id': 0, 'injected': False}, + {'broadcast': '192.168.0.255', + 'dns': ['192.168.0.1'], + 'gateway': '192.168.0.1', + 'gateway6': 'dead:beef::1', + 'ip6s': [{'enabled': '1', + 'ip': 'dead:beef::dcad:beff:feef:0', + 'netmask': '64'}], + 'ips': [{'enabled': '1', + 'ip': '192.168.0.100', + 'netmask': '255.255.255.0'}], + 'label': 'fake', + 'mac': 'DE:AD:BE:EF:00:00', + 'rxtx_cap': 3})] + + # Resize instance would be determined by the compute call + conn.finish_resize(instance, dict(base_copy='hurr', cow='durr'), + network_info, resize_instance=False) class XenAPIDetermineDiskImageTestCase(test.TestCase): diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 8bce6bb89..aec14f880 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -110,13 +110,14 @@ 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, 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(instance, [dict(vdi_type='os', vdi_uuid=vdi_uuid)], network_info) - self.resize_instance(instance, vdi_uuid) + self.resize_instance(instance, vdi_uuid, resize_instance) self._spawn(instance, vm_ref) def _start(self, instance, vm_ref=None): @@ -565,7 +566,7 @@ class VMOps(object): return new_cow_uuid - def resize_instance(self, instance, vdi_uuid): + def resize_instance(self, instance, vdi_uuid, resize_instance): """Resize a running instance by changing its RAM and disk size.""" #TODO(mdietz): this will need to be adjusted for swap later #The new disk size must be in bytes @@ -577,7 +578,7 @@ class VMOps(object): " Expanding to %(instance_local_gb)d GB") % locals()) vdi_ref = self._session.call_xenapi('VDI.get_by_uuid', vdi_uuid) # for an instance with no local storage - if new_disk_size > 0: + if resize_instance and new_disk_size > 0: self._session.call_xenapi('VDI.resize_online', vdi_ref, str(new_disk_size)) LOG.debug(_("Resize instance %s complete") % (instance.name)) diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index ec8c44c1c..18654d7e5 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -202,9 +202,11 @@ class XenAPIConnection(driver.ComputeDriver): """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, instance, disk_info, network_info, + resize_instance=False): """Completes a resize, turning on the migrated instance""" - self._vmops.finish_resize(instance, disk_info, network_info) + self._vmops.finish_resize(instance, disk_info, network_info, + resize_instance) def snapshot(self, instance, image_id): """ Create snapshot from a running VM instance """ -- cgit From 806be42000cf54e5b2ff9fb03446e8e6924bd38b Mon Sep 17 00:00:00 2001 From: "matt.dietz@rackspace.com" <> Date: Thu, 21 Jul 2011 12:46:58 -0500 Subject: Renamed the virt driver resize methods to migration for marginally more understandable code --- nova/compute/manager.py | 4 ++-- nova/tests/test_compute.py | 6 +++--- nova/tests/test_xenapi.py | 8 ++++---- nova/virt/driver.py | 4 ++-- nova/virt/xenapi/vmops.py | 24 +++++++++++++----------- nova/virt/xenapi_conn.py | 6 +++--- 6 files changed, 27 insertions(+), 25 deletions(-) (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 01a7d195f..78d7f6479 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -723,7 +723,7 @@ class ComputeManager(manager.SchedulerDependentManager): local_gb=instance_type['local_gb'], instance_type_id=instance_type['id'])) - self.driver.revert_resize(instance_ref) + self.driver.revert_migration(instance_ref) self.db.migration_update(context, migration_id, {'status': 'reverted'}) usage_info = utils.usage_from_instance(instance_ref) @@ -836,7 +836,7 @@ class ComputeManager(manager.SchedulerDependentManager): instance = self.db.instance_get(context, instance_id) network_info = self.network_api.get_instance_nw_info(context, instance) - self.driver.finish_resize(instance, disk_info, network_info, + self.driver.finish_migration(instance, disk_info, network_info, resize_instance) self.db.migration_update(context, migration_id, diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py index dc3f0596d..352011e52 100644 --- a/nova/tests/test_compute.py +++ b/nova/tests/test_compute.py @@ -420,7 +420,7 @@ class ComputeTestCase(test.TestCase): def fake(*args, **kwargs): pass - self.stubs.Set(self.compute.driver, 'finish_resize', fake) + self.stubs.Set(self.compute.driver, 'finish_migration', fake) self.stubs.Set(self.compute.network_api, 'get_instance_nw_info', fake) context = self.context.elevated() instance_id = self._create_instance() @@ -527,8 +527,8 @@ class ComputeTestCase(test.TestCase): def fake(*args, **kwargs): pass - self.stubs.Set(self.compute.driver, 'finish_resize', fake) - self.stubs.Set(self.compute.driver, 'revert_resize', fake) + self.stubs.Set(self.compute.driver, 'finish_migration', fake) + self.stubs.Set(self.compute.driver, 'revert_migration', fake) self.stubs.Set(self.compute.network_api, 'get_instance_nw_info', fake) self.compute.run_instance(self.context, instance_id) diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index be263d17c..9f203c477 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -783,7 +783,7 @@ class XenAPIMigrateInstance(test.TestCase): conn = xenapi_conn.get_connection(False) conn.migrate_disk_and_power_off(instance, '127.0.0.1') - def test_finish_resize(self): + def test_finish_migrate(self): instance = db.instance_create(self.context, self.values) self.called = False @@ -809,7 +809,7 @@ 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'), + conn.finish_migration(instance, dict(base_copy='hurr', cow='durr'), network_info, resize_instance=True) self.assertEqual(self.called, True) @@ -841,7 +841,7 @@ 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'), + conn.finish_migration(instance, dict(base_copy='hurr', cow='durr'), network_info, resize_instance=True) def test_finish_migrate_no_resize_vdi(self): @@ -871,7 +871,7 @@ class XenAPIMigrateInstance(test.TestCase): 'rxtx_cap': 3})] # Resize instance would be determined by the compute call - conn.finish_resize(instance, dict(base_copy='hurr', cow='durr'), + conn.finish_migration(instance, dict(base_copy='hurr', cow='durr'), network_info, resize_instance=False) diff --git a/nova/virt/driver.py b/nova/virt/driver.py index 178279d31..59582d253 100644 --- a/nova/virt/driver.py +++ b/nova/virt/driver.py @@ -122,11 +122,11 @@ class ComputeDriver(object): """Create snapshot from a running VM instance.""" raise NotImplementedError() - def finish_resize(self, instance, disk_info): + def finish_migration(self, instance, disk_info): """Completes a resize, turning on the migrated instance""" raise NotImplementedError() - def revert_resize(self, instance): + def revert_migration(self, instance): """Reverts a resize, powering back on the instance""" raise NotImplementedError() diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index aec14f880..008feea0a 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -106,18 +106,19 @@ class VMOps(object): instance_infos.append(instance_info) return instance_infos - def revert_resize(self, instance): + def revert_migration(self, instance): vm_ref = VMHelper.lookup(self._session, instance.name) self._start(instance, vm_ref) - def finish_resize(self, instance, disk_info, network_info, + def finish_migration(self, 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(instance, [dict(vdi_type='os', vdi_uuid=vdi_uuid)], network_info) - self.resize_instance(instance, vdi_uuid, resize_instance) + if resize_instance: + self.resize_instance(instance, vdi_uuid, resize_instance) self._spawn(instance, vm_ref) def _start(self, instance, vm_ref=None): @@ -572,16 +573,17 @@ class VMOps(object): #The new disk size must be in bytes new_disk_size = instance.local_gb * 1024 * 1024 * 1024 - instance_name = instance.name - instance_local_gb = instance.local_gb - LOG.debug(_("Resizing VDI %(vdi_uuid)s for instance %(instance_name)s." - " Expanding to %(instance_local_gb)d GB") % locals()) - vdi_ref = self._session.call_xenapi('VDI.get_by_uuid', vdi_uuid) - # for an instance with no local storage - if resize_instance and new_disk_size > 0: + if new_disk_size > 0: + instance_name = instance.name + instance_local_gb = instance.local_gb + LOG.debug(_("Resizing VDI %(vdi_uuid)s for instance" + "%(instance_name)s. Expanding to %(instance_local_gb)d" + " GB") % locals()) + vdi_ref = self._session.call_xenapi('VDI.get_by_uuid', vdi_uuid) + # for an instance with no local storage self._session.call_xenapi('VDI.resize_online', vdi_ref, str(new_disk_size)) - LOG.debug(_("Resize instance %s complete") % (instance.name)) + LOG.debug(_("Resize instance %s complete") % (instance.name)) def reboot(self, instance): """Reboot VM instance.""" diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index 18654d7e5..0e86d9e76 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -198,14 +198,14 @@ class XenAPIConnection(driver.ComputeDriver): """Create VM instance""" self._vmops.spawn(instance, network_info) - def revert_resize(self, instance): + def revert_migration(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_migration(self, instance, disk_info, network_info, resize_instance=False): """Completes a resize, turning on the migrated instance""" - self._vmops.finish_resize(instance, disk_info, network_info, + self._vmops.finish_migration(instance, disk_info, network_info, resize_instance) def snapshot(self, instance, image_id): -- cgit From 8383838afffeedcde8cd0dc486e32d2f5bb26f8e Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Thu, 21 Jul 2011 22:46:36 +0000 Subject: change context to maintain exact time, store roles, use ids instead of objects and use a uuid for request_id --- nova/context.py | 88 ++++++++++++++++++--------------------------------------- nova/utils.py | 23 ++++++++++----- 2 files changed, 43 insertions(+), 68 deletions(-) (limited to 'nova') diff --git a/nova/context.py b/nova/context.py index 99085ed75..e7c60142c 100644 --- a/nova/context.py +++ b/nova/context.py @@ -18,9 +18,8 @@ """RequestContext: context for requests that persist through all of nova.""" -import random +import uuid -from nova import exception from nova import utils @@ -31,86 +30,53 @@ class RequestContext(object): """ - def __init__(self, user, project, is_admin=None, read_deleted=False, - remote_address=None, timestamp=None, request_id=None): - if hasattr(user, 'id'): - self._user = user - self.user_id = user.id - else: - self._user = None - self.user_id = user - if hasattr(project, 'id'): - self._project = project - self.project_id = project.id - else: - self._project = None - self.project_id = project - if is_admin is None: - if self.user_id and self.user: - self.is_admin = self.user.is_admin() + def __init__(self, user_id, project_id, is_admin=None, read_deleted=False, + roles=None, remote_address=None, timestamp=None, request_id=None): + self.user_id = user_id + self.project_id = project_id + self.roles = roles or [] + self.is_admin = is_admin + if self.is_admin is None: + if 'admin' in self.roles: + self.is_admin = True else: self.is_admin = False - else: - self.is_admin = is_admin self.read_deleted = read_deleted self.remote_address = remote_address if not timestamp: timestamp = utils.utcnow() - if isinstance(timestamp, str) or isinstance(timestamp, unicode): - timestamp = utils.parse_isotime(timestamp) + if isinstance(timestamp, basestring): + timestamp = utils.parse_strtime(timestamp) self.timestamp = timestamp if not request_id: - chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-' - request_id = ''.join([random.choice(chars) for x in xrange(20)]) + request_id = unicode(uuid.uuid4()) self.request_id = request_id - @property - def user(self): - # NOTE(vish): Delay import of manager, so that we can import this - # file from manager. - from nova.auth import manager - if not self._user: - try: - self._user = manager.AuthManager().get_user(self.user_id) - except exception.NotFound: - pass - return self._user - - @property - def project(self): - # NOTE(vish): Delay import of manager, so that we can import this - # file from manager. - from nova.auth import manager - if not self._project: - try: - auth_manager = manager.AuthManager() - self._project = auth_manager.get_project(self.project_id) - except exception.NotFound: - pass - return self._project - def to_dict(self): - return {'user': self.user_id, - 'project': self.project_id, + return {'user_id': self.user_id, + 'project_id': self.project_id, 'is_admin': self.is_admin, 'read_deleted': self.read_deleted, + 'roles': self.roles, 'remote_address': self.remote_address, - 'timestamp': utils.isotime(self.timestamp), + 'timestamp': utils.strtime(self.timestamp), 'request_id': self.request_id} @classmethod def from_dict(cls, values): return cls(**values) - def elevated(self, read_deleted=False): + def elevated(self, read_deleted=None): """Return a version of this context with admin flag set.""" - return RequestContext(self.user_id, - self.project_id, - True, - read_deleted, - self.remote_address, - self.timestamp, - self.request_id) + rd = self.read_deleted if read_deleted is None else read_deleted + return RequestContext(user_id=self.user_id, + project_id=self.project_id, + is_admin=True, + read_deleted=rd, + roles=self.roles, + remote_address=self.remote_address, + timestamp=self.timestamp, + request_id=self.request_id) def get_admin_context(read_deleted=False): diff --git a/nova/utils.py b/nova/utils.py index 8784a227d..737903f81 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -19,7 +19,6 @@ """Utilities and helper functions.""" -import base64 import datetime import functools import inspect @@ -30,7 +29,6 @@ import os import random import re import socket -import string import struct import sys import time @@ -50,7 +48,8 @@ from nova import version LOG = logging.getLogger("nova.utils") -TIME_FORMAT = "%Y-%m-%dT%H:%M:%SZ" +ISO_TIME_FORMAT = "%Y-%m-%dT%H:%M:%SZ" +PERFECT_TIME_FORMAT = "%Y-%m-%dT%H:%M:%S.%f" FLAGS = flags.FLAGS @@ -361,16 +360,26 @@ def clear_time_override(): utcnow.override_time = None -def isotime(at=None): - """Returns iso formatted utcnow.""" +def strtime(at=None, fmt=PERFECT_TIME_FORMAT): + """Returns formatted utcnow.""" if not at: at = utcnow() - return at.strftime(TIME_FORMAT) + return at.strftime(fmt) + + +def parse_strtime(timestr, fmt=PERFECT_TIME_FORMAT): + """Turn a formatted time back into a datetime.""" + return datetime.datetime.strptime(timestr, fmt) + + +def isotime(at=None): + """Returns iso formatted utcnow.""" + return strtime(at, ISO_TIME_FORMAT) def parse_isotime(timestr): """Turn an iso formatted time back into a datetime.""" - return datetime.datetime.strptime(timestr, TIME_FORMAT) + return parse_strtime(timestr, ISO_TIME_FORMAT) def parse_mailmap(mailmap='.mailmap'): -- cgit From 5f75097eb46fa03814fe53c5d9fda84f0000fdd4 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Thu, 21 Jul 2011 22:46:57 +0000 Subject: start removing references to AuthManager --- nova/api/direct.py | 3 +- nova/api/ec2/__init__.py | 10 +++--- nova/api/openstack/auth.py | 26 ++++----------- nova/cloudpipe/pipelib.py | 18 +++++----- nova/compute/api.py | 2 +- nova/db/sqlalchemy/api.py | 4 +-- nova/image/s3.py | 6 ++-- nova/log.py | 4 +-- nova/tests/api/openstack/test_auth.py | 1 - nova/tests/hyperv_unittest.py | 2 +- nova/tests/scheduler/test_scheduler.py | 17 +++------- nova/tests/test_access.py | 2 +- nova/tests/test_adminapi.py | 4 +-- nova/tests/test_cloud.py | 12 +++---- nova/tests/test_libvirt.py | 9 +++-- nova/tests/test_quota.py | 60 +++++++++++++++------------------- 16 files changed, 77 insertions(+), 103 deletions(-) (limited to 'nova') diff --git a/nova/api/direct.py b/nova/api/direct.py index ec79151b1..993815fc7 100644 --- a/nova/api/direct.py +++ b/nova/api/direct.py @@ -107,7 +107,8 @@ class DelegatedAuthMiddleware(wsgi.Middleware): def process_request(self, request): os_user = request.headers['X-OpenStack-User'] os_project = request.headers['X-OpenStack-Project'] - context_ref = context.RequestContext(user=os_user, project=os_project) + context_ref = context.RequestContext(user_id=os_user, + project_id=os_project) request.environ['openstack.context'] = context_ref diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py index cf1734281..8bb2ea944 100644 --- a/nova/api/ec2/__init__.py +++ b/nova/api/ec2/__init__.py @@ -174,8 +174,8 @@ class Authenticate(wsgi.Middleware): remote_address = req.remote_addr if FLAGS.use_forwarded_for: remote_address = req.headers.get('X-Forwarded-For', remote_address) - ctxt = context.RequestContext(user=user, - project=project, + ctxt = context.RequestContext(user_id=user.id, + project_id=project.id, remote_address=remote_address) req.environ['ec2.context'] = ctxt uname = user.name @@ -295,13 +295,15 @@ class Authorizer(wsgi.Middleware): def _matches_any_role(self, context, roles): """Return True if any role in roles is allowed in context.""" - if context.user.is_superuser(): + authman = manager.AuthManager() + user = authman.get_user(context.user_id) + if user.is_superuser(): return True if 'all' in roles: return True if 'none' in roles: return False - return any(context.project.has_role(context.user_id, role) + return any(authman.has_role(context.user_id, role, context.project_id) for role in roles) diff --git a/nova/api/openstack/auth.py b/nova/api/openstack/auth.py index 7c3e683d6..5b387c081 100644 --- a/nova/api/openstack/auth.py +++ b/nova/api/openstack/auth.py @@ -48,31 +48,19 @@ class AuthMiddleware(wsgi.Middleware): def __call__(self, req): if not self.has_authentication(req): return self.authenticate(req) - user = self.get_user_by_authentication(req) - if not user: + user_id = self.get_user_by_authentication(req) + if not user_id: token = req.headers["X-Auth-Token"] - msg = _("%(user)s could not be found with token '%(token)s'") + msg = _("%(user_id)s could not be found with token '%(token)s'") LOG.warn(msg % locals()) return faults.Fault(webob.exc.HTTPUnauthorized()) try: - account = req.headers["X-Auth-Project-Id"] + project_id = req.headers["X-Auth-Project-Id"] except KeyError: - # FIXME(usrleon): It needed only for compatibility - # while osapi clients don't use this header - accounts = self.auth.get_projects(user=user) - if accounts: - account = accounts[0] - else: - return faults.Fault(webob.exc.HTTPUnauthorized()) - - if not self.auth.is_admin(user) and \ - not self.auth.is_project_member(user, account): - msg = _("%(user)s must be an admin or a member of %(account)s") - LOG.warn(msg % locals()) - return faults.Fault(webob.exc.HTTPUnauthorized()) + project_id = user_id - req.environ['nova.context'] = context.RequestContext(user, account) + req.environ['nova.context'] = context.RequestContext(user_id, project_id) return self.application def has_authentication(self, req): @@ -133,7 +121,7 @@ class AuthMiddleware(wsgi.Middleware): if delta.days >= 2: self.db.auth_token_destroy(ctxt, token['token_hash']) else: - return self.auth.get_user(token['user_id']) + return token['user_id'] return None def _authorize_user(self, username, key, req): diff --git a/nova/cloudpipe/pipelib.py b/nova/cloudpipe/pipelib.py index 7844d31e1..521525205 100644 --- a/nova/cloudpipe/pipelib.py +++ b/nova/cloudpipe/pipelib.py @@ -96,8 +96,8 @@ class CloudPipe(object): def launch_vpn_instance(self, project_id): LOG.debug(_("Launching VPN for %s") % (project_id)) project = self.manager.get_project(project_id) - ctxt = context.RequestContext(user=project.project_manager, - project=project) + ctxt = context.RequestContext(user=project.project_manager_id, + project=project.id) key_name = self.setup_key_pair(ctxt) group_name = self.setup_security_group(ctxt) @@ -112,11 +112,11 @@ class CloudPipe(object): security_group=[group_name]) def setup_security_group(self, context): - group_name = '%s%s' % (context.project.id, FLAGS.vpn_key_suffix) - if db.security_group_exists(context, context.project.id, group_name): + group_name = '%s%s' % (context.project_id, FLAGS.vpn_key_suffix) + if db.security_group_exists(context, context.project_id, group_name): return group_name - group = {'user_id': context.user.id, - 'project_id': context.project.id, + group = {'user_id': context.user_id, + 'project_id': context.project_id, 'name': group_name, 'description': 'Group for vpn'} group_ref = db.security_group_create(context, group) @@ -137,12 +137,12 @@ class CloudPipe(object): return group_name def setup_key_pair(self, context): - key_name = '%s%s' % (context.project.id, FLAGS.vpn_key_suffix) + key_name = '%s%s' % (context.project_id, FLAGS.vpn_key_suffix) try: - result = cloud._gen_key(context, context.user.id, key_name) + result = cloud._gen_key(context, context.user_id, key_name) private_key = result['private_key'] try: - key_dir = os.path.join(FLAGS.keys_path, context.user.id) + key_dir = os.path.join(FLAGS.keys_path, context.user_id) if not os.path.exists(key_dir): os.makedirs(key_dir) key_path = os.path.join(key_dir, '%s.pem' % key_name) diff --git a/nova/compute/api.py b/nova/compute/api.py index 67aa3c20f..51a903d40 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -688,7 +688,7 @@ class API(base.Base): raise instances = None elif project_id or not context.is_admin: - if not context.project: + if not context.project_id: instances = self.db.instance_get_all_by_user( context, context.user_id) else: diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index ba03cabbc..6be3f483e 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -62,7 +62,7 @@ def is_user_context(context): def authorize_project_context(context, project_id): """Ensures a request has permission to access the given project.""" if is_user_context(context): - if not context.project: + if not context.project_id: raise exception.NotAuthorized() elif context.project_id != project_id: raise exception.NotAuthorized() @@ -71,7 +71,7 @@ def authorize_project_context(context, project_id): def authorize_user_context(context, user_id): """Ensures a request has permission to access the given user.""" if is_user_context(context): - if not context.user: + if not context.user_id: raise exception.NotAuthorized() elif context.user_id != user_id: raise exception.NotAuthorized() diff --git a/nova/image/s3.py b/nova/image/s3.py index 4a3df98ba..dd5c957a5 100644 --- a/nova/image/s3.py +++ b/nova/image/s3.py @@ -84,9 +84,9 @@ class S3ImageService(service.BaseImageService): def _conn(context): # TODO(vish): is there a better way to get creds to sign # for the user? - access = manager.AuthManager().get_access_key(context.user, - context.project) - secret = str(context.user.secret) + authman = manager.AuthManager() + access = authman.get_access_key(context.user_id, context.project_id) + secret = str(authman.get_user(context.user_id).secret) calling = boto.s3.connection.OrdinaryCallingFormat() return boto.s3.connection.S3Connection(aws_access_key_id=access, aws_secret_access_key=secret, diff --git a/nova/log.py b/nova/log.py index f8c0ba68d..b4f6c1d2e 100644 --- a/nova/log.py +++ b/nova/log.py @@ -43,8 +43,8 @@ from nova import version FLAGS = flags.FLAGS flags.DEFINE_string('logging_context_format_string', '%(asctime)s %(levelname)s %(name)s ' - '[%(request_id)s %(user)s ' - '%(project)s] %(message)s', + '[%(request_id)s %(user_id)s ' + '%(project_id)s] %(message)s', 'format string to use for log messages with context') flags.DEFINE_string('logging_default_format_string', '%(asctime)s %(levelname)s %(name)s [-] ' diff --git a/nova/tests/api/openstack/test_auth.py b/nova/tests/api/openstack/test_auth.py index af3478c7d..25fd2e8c5 100644 --- a/nova/tests/api/openstack/test_auth.py +++ b/nova/tests/api/openstack/test_auth.py @@ -24,7 +24,6 @@ import webob.dec import nova.api import nova.api.openstack.auth import nova.auth.manager -from nova import auth from nova import context from nova import db from nova import test diff --git a/nova/tests/hyperv_unittest.py b/nova/tests/hyperv_unittest.py index 042819b9c..ab2995923 100644 --- a/nova/tests/hyperv_unittest.py +++ b/nova/tests/hyperv_unittest.py @@ -38,7 +38,7 @@ class HyperVTestCase(test.TestCase): self.user = self.manager.create_user('fake', 'fake', 'fake', admin=True) self.project = self.manager.create_project('fake', 'fake', 'fake') - self.context = context.RequestContext(self.user, self.project) + self.context = context.RequestContext(self.user.id, self.project.id) def test_create_destroy(self): """Create a VM and destroy it""" diff --git a/nova/tests/scheduler/test_scheduler.py b/nova/tests/scheduler/test_scheduler.py index daea826fd..ef4ef156c 100644 --- a/nova/tests/scheduler/test_scheduler.py +++ b/nova/tests/scheduler/test_scheduler.py @@ -23,7 +23,6 @@ import datetime import mox import novaclient.exceptions import stubout -import webob from mox import IgnoreArg from nova import context @@ -34,12 +33,10 @@ from nova import service from nova import test from nova import rpc from nova import utils -from nova.auth import manager as auth_manager from nova.scheduler import api from nova.scheduler import manager from nova.scheduler import driver from nova.compute import power_state -from nova.db.sqlalchemy import models FLAGS = flags.FLAGS @@ -250,23 +247,17 @@ class SimpleDriverTestCase(test.TestCase): volume_driver='nova.volume.driver.FakeISCSIDriver', scheduler_driver='nova.scheduler.simple.SimpleScheduler') self.scheduler = manager.SchedulerManager() - self.manager = auth_manager.AuthManager() - self.user = self.manager.create_user('fake', 'fake', 'fake') - self.project = self.manager.create_project('fake', 'fake', 'fake') self.context = context.get_admin_context() - - def tearDown(self): - self.manager.delete_user(self.user) - self.manager.delete_project(self.project) - super(SimpleDriverTestCase, self).tearDown() + self.user_id = 'fake' + self.project_id = 'fake' def _create_instance(self, **kwargs): """Create a test instance""" inst = {} inst['image_id'] = 1 inst['reservation_id'] = 'r-fakeres' - inst['user_id'] = self.user.id - inst['project_id'] = self.project.id + inst['user_id'] = self.user_id + inst['project_id'] = self.project_id inst['instance_type_id'] = '1' inst['vcpus'] = kwargs.get('vcpus', 1) inst['ami_launch_index'] = 0 diff --git a/nova/tests/test_access.py b/nova/tests/test_access.py index e170ccee6..6069c5d71 100644 --- a/nova/tests/test_access.py +++ b/nova/tests/test_access.py @@ -93,7 +93,7 @@ class AccessTestCase(test.TestCase): super(AccessTestCase, self).tearDown() def response_status(self, user, methodName): - ctxt = context.RequestContext(user, self.project) + ctxt = context.RequestContext(user.id, self.project.id) environ = self._env_for(ctxt, methodName) req = webob.Request.blank('/', environ) resp = req.get_response(self.mw) diff --git a/nova/tests/test_adminapi.py b/nova/tests/test_adminapi.py index 877cf4ea1..f8abe609d 100644 --- a/nova/tests/test_adminapi.py +++ b/nova/tests/test_adminapi.py @@ -54,8 +54,8 @@ class AdminApiTestCase(test.TestCase): self.manager = manager.AuthManager() self.user = self.manager.create_user('admin', 'admin', 'admin', True) self.project = self.manager.create_project('proj', 'admin', 'proj') - self.context = context.RequestContext(user=self.user, - project=self.project) + self.context = context.RequestContext(user_id=self.user.id, + project_id=self.project.id) def fake_show(meh, context, id): return {'id': 1, 'properties': {'kernel_id': 1, 'ramdisk_id': 1, diff --git a/nova/tests/test_cloud.py b/nova/tests/test_cloud.py index 8cdc73a66..71ac7f473 100644 --- a/nova/tests/test_cloud.py +++ b/nova/tests/test_cloud.py @@ -65,8 +65,8 @@ class CloudTestCase(test.TestCase): self.manager = manager.AuthManager() self.user = self.manager.create_user('admin', 'admin', 'admin', True) self.project = self.manager.create_project('proj', 'admin', 'proj') - self.context = context.RequestContext(user=self.user, - project=self.project) + self.context = context.RequestContext(user_id=self.user.id, + project_id=self.project.id) host = self.network.host def fake_show(meh, context, id): @@ -97,7 +97,7 @@ class CloudTestCase(test.TestCase): def _create_key(self, name): # NOTE(vish): create depends on pool, so just call helper directly - return cloud._gen_key(self.context, self.context.user.id, name) + return cloud._gen_key(self.context, self.context.user_id, name) def test_describe_regions(self): """Makes sure describe regions runs without raising an exception""" @@ -936,7 +936,7 @@ class CloudTestCase(test.TestCase): key = RSA.load_key_string(private_key, callback=lambda: None) bio = BIO.MemoryBuffer() public_key = db.key_pair_get(self.context, - self.context.user.id, + self.context.user_id, 'test')['public_key'] key.save_pub_key_bio(bio) converted = crypto.ssl_pub_to_ssh_pub(bio.read()) @@ -960,7 +960,7 @@ class CloudTestCase(test.TestCase): 'mytestfprint') self.assertTrue(result1) keydata = db.key_pair_get(self.context, - self.context.user.id, + self.context.user_id, 'testimportkey1') self.assertEqual('mytestpubkey', keydata['public_key']) self.assertEqual('mytestfprint', keydata['fingerprint']) @@ -977,7 +977,7 @@ class CloudTestCase(test.TestCase): dummypub) self.assertTrue(result2) keydata = db.key_pair_get(self.context, - self.context.user.id, + self.context.user_id, 'testimportkey2') self.assertEqual(dummypub, keydata['public_key']) self.assertEqual(dummyfprint, keydata['fingerprint']) diff --git a/nova/tests/test_libvirt.py b/nova/tests/test_libvirt.py index 6e2ec7ed6..948ca215f 100644 --- a/nova/tests/test_libvirt.py +++ b/nova/tests/test_libvirt.py @@ -429,8 +429,8 @@ class LibvirtConnTestCase(test.TestCase): self.assertEquals(parameters[1].get('value'), 'fake') def _check_xml_and_container(self, instance): - user_context = context.RequestContext(project=self.project, - user=self.user) + user_context = context.RequestContext(self.user.id, + self.project.id) instance_ref = db.instance_create(user_context, instance) # Re-get the instance so it's bound to an actual session instance_ref = db.instance_get(user_context, instance_ref['id']) @@ -475,8 +475,7 @@ class LibvirtConnTestCase(test.TestCase): def _check_xml_and_uri(self, instance, expect_ramdisk, expect_kernel, rescue=False): - user_context = context.RequestContext(project=self.project, - user=self.user) + user_context = context.RequestContext(self.user.id, self.project.id) instance_ref = db.instance_create(user_context, instance) network_ref = db.project_get_networks(context.get_admin_context(), self.project.id)[0] @@ -1166,7 +1165,7 @@ class NWFilterTestCase(test.TestCase): self.user = self.manager.create_user('fake', 'fake', 'fake', admin=True) self.project = self.manager.create_project('fake', 'fake', 'fake') - self.context = context.RequestContext(self.user, self.project) + self.context = context.RequestContext(self.user.id, self.project.id) self.fake_libvirt_connection = Mock() diff --git a/nova/tests/test_quota.py b/nova/tests/test_quota.py index 69d2deafe..fcb99b7c9 100644 --- a/nova/tests/test_quota.py +++ b/nova/tests/test_quota.py @@ -25,7 +25,6 @@ from nova import quota from nova import test from nova import utils from nova import volume -from nova.auth import manager from nova.compute import instance_types @@ -48,25 +47,20 @@ class QuotaTestCase(test.TestCase): quota_gigabytes=20, quota_floating_ips=1) - self.manager = manager.AuthManager() - self.user = self.manager.create_user('admin', 'admin', 'admin', True) - self.project = self.manager.create_project('admin', 'admin', 'admin') self.network = self.network = self.start_service('network') - self.context = context.RequestContext(project=self.project, - user=self.user) - - def tearDown(self): - manager.AuthManager().delete_project(self.project) - manager.AuthManager().delete_user(self.user) - super(QuotaTestCase, self).tearDown() + self.user_id = 'admin' + self.project_id = 'admin' + self.context = context.RequestContext(self.user_id, + self.project_id, + True) def _create_instance(self, cores=2): """Create a test instance""" inst = {} inst['image_id'] = 1 inst['reservation_id'] = 'r-fakeres' - inst['user_id'] = self.user.id - inst['project_id'] = self.project.id + inst['user_id'] = self.user_id + inst['project_id'] = self.project_id inst['instance_type_id'] = '3' # m1.large inst['vcpus'] = cores return db.instance_create(self.context, inst)['id'] @@ -74,8 +68,8 @@ class QuotaTestCase(test.TestCase): def _create_volume(self, size=10): """Create a test volume""" vol = {} - vol['user_id'] = self.user.id - vol['project_id'] = self.project.id + vol['user_id'] = self.user_id + vol['project_id'] = self.project_id vol['size'] = size return db.volume_create(self.context, vol)['id'] @@ -95,15 +89,15 @@ class QuotaTestCase(test.TestCase): num_instances = quota.allowed_instances(self.context, 100, self._get_instance_type('m1.small')) self.assertEqual(num_instances, 2) - db.quota_create(self.context, self.project.id, 'instances', 10) + db.quota_create(self.context, self.project_id, 'instances', 10) num_instances = quota.allowed_instances(self.context, 100, self._get_instance_type('m1.small')) self.assertEqual(num_instances, 4) - db.quota_create(self.context, self.project.id, 'cores', 100) + db.quota_create(self.context, self.project_id, 'cores', 100) num_instances = quota.allowed_instances(self.context, 100, self._get_instance_type('m1.small')) self.assertEqual(num_instances, 10) - db.quota_create(self.context, self.project.id, 'ram', 3 * 2048) + db.quota_create(self.context, self.project_id, 'ram', 3 * 2048) num_instances = quota.allowed_instances(self.context, 100, self._get_instance_type('m1.small')) self.assertEqual(num_instances, 3) @@ -113,13 +107,13 @@ class QuotaTestCase(test.TestCase): num_metadata_items = quota.allowed_metadata_items(self.context, too_many_items) self.assertEqual(num_metadata_items, FLAGS.quota_metadata_items) - db.quota_create(self.context, self.project.id, 'metadata_items', 5) + db.quota_create(self.context, self.project_id, 'metadata_items', 5) num_metadata_items = quota.allowed_metadata_items(self.context, too_many_items) self.assertEqual(num_metadata_items, 5) # Cleanup - db.quota_destroy_all_by_project(self.context, self.project.id) + db.quota_destroy_all_by_project(self.context, self.project_id) def test_unlimited_instances(self): FLAGS.quota_instances = 2 @@ -129,7 +123,7 @@ class QuotaTestCase(test.TestCase): num_instances = quota.allowed_instances(self.context, 100, instance_type) self.assertEqual(num_instances, 2) - db.quota_create(self.context, self.project.id, 'instances', None) + db.quota_create(self.context, self.project_id, 'instances', None) num_instances = quota.allowed_instances(self.context, 100, instance_type) self.assertEqual(num_instances, 100) @@ -145,7 +139,7 @@ class QuotaTestCase(test.TestCase): num_instances = quota.allowed_instances(self.context, 100, instance_type) self.assertEqual(num_instances, 2) - db.quota_create(self.context, self.project.id, 'ram', None) + db.quota_create(self.context, self.project_id, 'ram', None) num_instances = quota.allowed_instances(self.context, 100, instance_type) self.assertEqual(num_instances, 100) @@ -161,7 +155,7 @@ class QuotaTestCase(test.TestCase): num_instances = quota.allowed_instances(self.context, 100, instance_type) self.assertEqual(num_instances, 2) - db.quota_create(self.context, self.project.id, 'cores', None) + db.quota_create(self.context, self.project_id, 'cores', None) num_instances = quota.allowed_instances(self.context, 100, instance_type) self.assertEqual(num_instances, 100) @@ -174,7 +168,7 @@ class QuotaTestCase(test.TestCase): FLAGS.quota_gigabytes = -1 volumes = quota.allowed_volumes(self.context, 100, 1) self.assertEqual(volumes, 10) - db.quota_create(self.context, self.project.id, 'volumes', None) + db.quota_create(self.context, self.project_id, 'volumes', None) volumes = quota.allowed_volumes(self.context, 100, 1) self.assertEqual(volumes, 100) volumes = quota.allowed_volumes(self.context, 101, 1) @@ -185,7 +179,7 @@ class QuotaTestCase(test.TestCase): FLAGS.quota_gigabytes = 10 volumes = quota.allowed_volumes(self.context, 100, 1) self.assertEqual(volumes, 10) - db.quota_create(self.context, self.project.id, 'gigabytes', None) + db.quota_create(self.context, self.project_id, 'gigabytes', None) volumes = quota.allowed_volumes(self.context, 100, 1) self.assertEqual(volumes, 100) volumes = quota.allowed_volumes(self.context, 101, 1) @@ -195,7 +189,7 @@ class QuotaTestCase(test.TestCase): FLAGS.quota_floating_ips = 10 floating_ips = quota.allowed_floating_ips(self.context, 100) self.assertEqual(floating_ips, 10) - db.quota_create(self.context, self.project.id, 'floating_ips', None) + db.quota_create(self.context, self.project_id, 'floating_ips', None) floating_ips = quota.allowed_floating_ips(self.context, 100) self.assertEqual(floating_ips, 100) floating_ips = quota.allowed_floating_ips(self.context, 101) @@ -205,7 +199,7 @@ class QuotaTestCase(test.TestCase): FLAGS.quota_metadata_items = 10 items = quota.allowed_metadata_items(self.context, 100) self.assertEqual(items, 10) - db.quota_create(self.context, self.project.id, 'metadata_items', None) + db.quota_create(self.context, self.project_id, 'metadata_items', None) items = quota.allowed_metadata_items(self.context, 100) self.assertEqual(items, 100) items = quota.allowed_metadata_items(self.context, 101) @@ -274,11 +268,11 @@ class QuotaTestCase(test.TestCase): address = '192.168.0.100' db.floating_ip_create(context.get_admin_context(), {'address': address, 'host': FLAGS.host, - 'project_id': self.project.id}) + 'project_id': self.project_id}) self.assertRaises(quota.QuotaError, self.network.allocate_floating_ip, self.context, - self.project.id) + self.project_id) db.floating_ip_destroy(context.get_admin_context(), address) def test_too_many_metadata_items(self): @@ -300,7 +294,7 @@ class QuotaTestCase(test.TestCase): def test_overridden_allowed_injected_files(self): FLAGS.quota_max_injected_files = 5 - db.quota_create(self.context, self.project.id, 'injected_files', 77) + db.quota_create(self.context, self.project_id, 'injected_files', 77) self.assertEqual(quota.allowed_injected_files(self.context, 100), 77) def test_unlimited_default_allowed_injected_files(self): @@ -309,7 +303,7 @@ class QuotaTestCase(test.TestCase): def test_unlimited_db_allowed_injected_files(self): FLAGS.quota_max_injected_files = 5 - db.quota_create(self.context, self.project.id, 'injected_files', None) + db.quota_create(self.context, self.project_id, 'injected_files', None) self.assertEqual(quota.allowed_injected_files(self.context, 100), 100) def test_default_allowed_injected_file_content_bytes(self): @@ -319,7 +313,7 @@ class QuotaTestCase(test.TestCase): def test_overridden_allowed_injected_file_content_bytes(self): FLAGS.quota_max_injected_file_content_bytes = 12345 - db.quota_create(self.context, self.project.id, + db.quota_create(self.context, self.project_id, 'injected_file_content_bytes', 5678) limit = quota.allowed_injected_file_content_bytes(self.context, 23456) self.assertEqual(limit, 5678) @@ -331,7 +325,7 @@ class QuotaTestCase(test.TestCase): def test_unlimited_db_allowed_injected_file_content_bytes(self): FLAGS.quota_max_injected_file_content_bytes = 12345 - db.quota_create(self.context, self.project.id, + db.quota_create(self.context, self.project_id, 'injected_file_content_bytes', None) limit = quota.allowed_injected_file_content_bytes(self.context, 23456) self.assertEqual(limit, 23456) -- cgit From e1cf345fa82c3a9b8088237f1025c41db0f4e829 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Fri, 22 Jul 2011 00:39:53 +0000 Subject: fix a whole bunch of tests --- nova/api/ec2/__init__.py | 20 +++++++--- nova/tests/hyperv_unittest.py | 9 ++--- nova/tests/test_adminapi.py | 16 +++----- nova/tests/test_api.py | 74 ++++------------------------------- nova/tests/test_cloud.py | 16 +++----- nova/tests/test_compute.py | 37 +++++++----------- nova/tests/test_console.py | 19 +++------ nova/tests/test_libvirt.py | 66 ++++++++------------------------ nova/tests/test_objectstore.py | 24 +++--------- nova/tests/test_vmwareapi.py | 15 +++----- nova/tests/test_xenapi.py | 85 +++++++++++++++++++++-------------------- nova/virt/hyperv.py | 6 +-- nova/virt/images.py | 2 +- nova/virt/libvirt/connection.py | 19 ++++----- nova/virt/xenapi/vm_utils.py | 27 +++++++------ nova/virt/xenapi/vmops.py | 17 +++------ 16 files changed, 155 insertions(+), 297 deletions(-) (limited to 'nova') diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py index 8bb2ea944..edae94331 100644 --- a/nova/api/ec2/__init__.py +++ b/nova/api/ec2/__init__.py @@ -138,8 +138,19 @@ class Lockout(wsgi.Middleware): return res -class Authenticate(wsgi.Middleware): +class InjectContext(wsgi.Middleware): + """Always add a fake 'ec2.context' to WSGI environ.""" + def __init__(self, context, *args, **kwargs): + self.context = context + super(InjectContext, self).__init__(*args, **kwargs) + @webob.dec.wsgify(RequestClass=wsgi.Request) + def __call__(self, req): + req.environ['ec2.context'] = self.context + return self.application + + +class Authenticate(wsgi.Middleware): """Authenticate an EC2 request and add 'ec2.context' to WSGI environ.""" @webob.dec.wsgify(RequestClass=wsgi.Request) @@ -295,16 +306,13 @@ class Authorizer(wsgi.Middleware): def _matches_any_role(self, context, roles): """Return True if any role in roles is allowed in context.""" - authman = manager.AuthManager() - user = authman.get_user(context.user_id) - if user.is_superuser(): + if context.is_admin: return True if 'all' in roles: return True if 'none' in roles: return False - return any(authman.has_role(context.user_id, role, context.project_id) - for role in roles) + return any(role in context.roles for role in roles) class Executor(wsgi.Application): diff --git a/nova/tests/hyperv_unittest.py b/nova/tests/hyperv_unittest.py index ab2995923..0ea196950 100644 --- a/nova/tests/hyperv_unittest.py +++ b/nova/tests/hyperv_unittest.py @@ -23,7 +23,6 @@ from nova import context from nova import db from nova import flags from nova import test -from nova.auth import manager from nova.virt import hyperv FLAGS = flags.FLAGS @@ -34,11 +33,9 @@ class HyperVTestCase(test.TestCase): """Test cases for the Hyper-V driver""" def setUp(self): super(HyperVTestCase, self).setUp() - self.manager = manager.AuthManager() - self.user = self.manager.create_user('fake', 'fake', 'fake', - admin=True) - self.project = self.manager.create_project('fake', 'fake', 'fake') - self.context = context.RequestContext(self.user.id, self.project.id) + self.user_id = 'fake' + self.project_id = 'fake' + self.context = context.RequestContext(self.user_id, self.project_id) def test_create_destroy(self): """Create a VM and destroy it""" diff --git a/nova/tests/test_adminapi.py b/nova/tests/test_adminapi.py index f8abe609d..fde26e31a 100644 --- a/nova/tests/test_adminapi.py +++ b/nova/tests/test_adminapi.py @@ -25,7 +25,6 @@ from nova import log as logging from nova import rpc from nova import test from nova import utils -from nova.auth import manager from nova.api.ec2 import admin from nova.image import fake @@ -51,11 +50,11 @@ class AdminApiTestCase(test.TestCase): self.volume = self.start_service('volume') self.image_service = utils.import_object(FLAGS.image_service) - self.manager = manager.AuthManager() - self.user = self.manager.create_user('admin', 'admin', 'admin', True) - self.project = self.manager.create_project('proj', 'admin', 'proj') - self.context = context.RequestContext(user_id=self.user.id, - project_id=self.project.id) + self.user_id = 'admin' + self.project_id = 'admin' + self.context = context.RequestContext(self.user_id, + self.project_id, + True) def fake_show(meh, context, id): return {'id': 1, 'properties': {'kernel_id': 1, 'ramdisk_id': 1, @@ -73,11 +72,6 @@ class AdminApiTestCase(test.TestCase): self.stubs.Set(rpc, 'cast', finish_cast) - def tearDown(self): - self.manager.delete_project(self.project) - self.manager.delete_user(self.user) - super(AdminApiTestCase, self).tearDown() - def test_block_external_ips(self): """Make sure provider firewall rules are created.""" result = self.api.block_external_addresses(self.context, '1.1.1.1/32') diff --git a/nova/tests/test_api.py b/nova/tests/test_api.py index 26ac5ff24..978e43abd 100644 --- a/nova/tests/test_api.py +++ b/nova/tests/test_api.py @@ -34,7 +34,6 @@ from nova.api import ec2 from nova.api.ec2 import apirequest from nova.api.ec2 import cloud from nova.api.ec2 import ec2utils -from nova.auth import manager class FakeHttplibSocket(object): @@ -192,10 +191,13 @@ class ApiEc2TestCase(test.TestCase): """Unit test for the cloud controller on an EC2 API""" def setUp(self): super(ApiEc2TestCase, self).setUp() - self.manager = manager.AuthManager() self.host = '127.0.0.1' - self.app = ec2.Authenticate(ec2.Requestify(ec2.Executor(), - 'nova.api.ec2.cloud.CloudController')) + # NOTE(vish): skipping the Authorizer + roles = ['sysadmin', 'netadmin'] + ctxt = context.RequestContext('fake', 'fake', roles=roles) + self.app = ec2.InjectContext(ctxt, + ec2.Requestify(ec2.Authorizer(ec2.Executor()), + 'nova.api.ec2.cloud.CloudController')) def expect_http(self, host=None, is_secure=False, api_version=None): """Returns a new EC2 connection""" @@ -242,39 +244,25 @@ class ApiEc2TestCase(test.TestCase): self.expect_http(api_version='2010-10-30') self.mox.ReplayAll() - user = self.manager.create_user('fake', 'fake', 'fake') - project = self.manager.create_project('fake', 'fake', 'fake') - # Any request should be fine self.ec2.get_all_instances() self.assertTrue(self.ec2.APIVersion in self.http.getresponsebody(), 'The version in the xmlns of the response does ' 'not match the API version given in the request.') - self.manager.delete_project(project) - self.manager.delete_user(user) - def test_describe_instances(self): """Test that, after creating a user and a project, the describe instances call to the API works properly""" self.expect_http() self.mox.ReplayAll() - user = self.manager.create_user('fake', 'fake', 'fake') - project = self.manager.create_project('fake', 'fake', 'fake') self.assertEqual(self.ec2.get_all_instances(), []) - self.manager.delete_project(project) - self.manager.delete_user(user) def test_terminate_invalid_instance(self): """Attempt to terminate an invalid instance""" self.expect_http() self.mox.ReplayAll() - user = self.manager.create_user('fake', 'fake', 'fake') - project = self.manager.create_project('fake', 'fake', 'fake') self.assertRaises(EC2ResponseError, self.ec2.terminate_instances, "i-00000005") - self.manager.delete_project(project) - self.manager.delete_user(user) def test_get_all_key_pairs(self): """Test that, after creating a user and project and generating @@ -283,16 +271,12 @@ class ApiEc2TestCase(test.TestCase): self.mox.ReplayAll() keyname = "".join(random.choice("sdiuisudfsdcnpaqwertasd") \ for x in range(random.randint(4, 8))) - user = self.manager.create_user('fake', 'fake', 'fake') - project = self.manager.create_project('fake', 'fake', 'fake') # NOTE(vish): create depends on pool, so call helper directly - cloud._gen_key(context.get_admin_context(), user.id, keyname) + cloud._gen_key(context.get_admin_context(), 'fake', keyname) rv = self.ec2.get_all_key_pairs() results = [k for k in rv if k.name == keyname] self.assertEquals(len(results), 1) - self.manager.delete_project(project) - self.manager.delete_user(user) def test_create_duplicate_key_pair(self): """Test that, after successfully generating a keypair, @@ -301,8 +285,6 @@ class ApiEc2TestCase(test.TestCase): self.mox.ReplayAll() keyname = "".join(random.choice("sdiuisudfsdcnpaqwertasd") \ for x in range(random.randint(4, 8))) - user = self.manager.create_user('fake', 'fake', 'fake') - project = self.manager.create_project('fake', 'fake', 'fake') # NOTE(vish): create depends on pool, so call helper directly self.ec2.create_key_pair('test') @@ -321,27 +303,16 @@ class ApiEc2TestCase(test.TestCase): """Test that we can retrieve security groups""" self.expect_http() self.mox.ReplayAll() - user = self.manager.create_user('fake', 'fake', 'fake', admin=True) - project = self.manager.create_project('fake', 'fake', 'fake') rv = self.ec2.get_all_security_groups() self.assertEquals(len(rv), 1) self.assertEquals(rv[0].name, 'default') - self.manager.delete_project(project) - self.manager.delete_user(user) - def test_create_delete_security_group(self): """Test that we can create a security group""" self.expect_http() self.mox.ReplayAll() - user = self.manager.create_user('fake', 'fake', 'fake', admin=True) - project = self.manager.create_project('fake', 'fake', 'fake') - - # At the moment, you need both of these to actually be netadmin - self.manager.add_role('fake', 'netadmin') - project.add_role('fake', 'netadmin') security_group_name = "".join(random.choice("sdiuisudfsdcnpaqwertasd") for x in range(random.randint(4, 8))) @@ -360,9 +331,6 @@ class ApiEc2TestCase(test.TestCase): self.ec2.delete_security_group(security_group_name) - self.manager.delete_project(project) - self.manager.delete_user(user) - def test_authorize_revoke_security_group_cidr(self): """ Test that we can add and remove CIDR based rules @@ -370,12 +338,6 @@ class ApiEc2TestCase(test.TestCase): """ self.expect_http() self.mox.ReplayAll() - user = self.manager.create_user('fake', 'fake', 'fake') - project = self.manager.create_project('fake', 'fake', 'fake') - - # At the moment, you need both of these to actually be netadmin - self.manager.add_role('fake', 'netadmin') - project.add_role('fake', 'netadmin') security_group_name = "".join(random.choice("sdiuisudfsdcnpaqwertasd") for x in range(random.randint(4, 8))) @@ -422,9 +384,6 @@ class ApiEc2TestCase(test.TestCase): self.assertEqual(len(rv), 1) self.assertEqual(rv[0].name, 'default') - self.manager.delete_project(project) - self.manager.delete_user(user) - return def test_authorize_revoke_security_group_cidr_v6(self): @@ -434,12 +393,7 @@ class ApiEc2TestCase(test.TestCase): """ self.expect_http() self.mox.ReplayAll() - user = self.manager.create_user('fake', 'fake', 'fake') - project = self.manager.create_project('fake', 'fake', 'fake') - # At the moment, you need both of these to actually be netadmin - self.manager.add_role('fake', 'netadmin') - project.add_role('fake', 'netadmin') security_group_name = "".join(random.choice("sdiuisudfsdcnpaqwertasd") for x in range(random.randint(4, 8))) @@ -485,9 +439,6 @@ class ApiEc2TestCase(test.TestCase): self.assertEqual(len(rv), 1) self.assertEqual(rv[0].name, 'default') - self.manager.delete_project(project) - self.manager.delete_user(user) - return def test_authorize_revoke_security_group_foreign_group(self): @@ -497,12 +448,6 @@ class ApiEc2TestCase(test.TestCase): """ self.expect_http() self.mox.ReplayAll() - user = self.manager.create_user('fake', 'fake', 'fake', admin=True) - project = self.manager.create_project('fake', 'fake', 'fake') - - # At the moment, you need both of these to actually be netadmin - self.manager.add_role('fake', 'netadmin') - project.add_role('fake', 'netadmin') rand_string = 'sdiuisudfsdcnpaqwertasd' security_group_name = "".join(random.choice(rand_string) @@ -556,8 +501,3 @@ class ApiEc2TestCase(test.TestCase): self.mox.ReplayAll() self.ec2.delete_security_group(security_group_name) - - self.manager.delete_project(project) - self.manager.delete_user(user) - - return diff --git a/nova/tests/test_cloud.py b/nova/tests/test_cloud.py index 71ac7f473..c414e0ddc 100644 --- a/nova/tests/test_cloud.py +++ b/nova/tests/test_cloud.py @@ -34,7 +34,6 @@ from nova import network from nova import rpc from nova import test from nova import utils -from nova.auth import manager from nova.api.ec2 import cloud from nova.api.ec2 import ec2utils from nova.image import fake @@ -62,12 +61,11 @@ class CloudTestCase(test.TestCase): self.volume = self.start_service('volume') self.image_service = utils.import_object(FLAGS.image_service) - self.manager = manager.AuthManager() - self.user = self.manager.create_user('admin', 'admin', 'admin', True) - self.project = self.manager.create_project('proj', 'admin', 'proj') - self.context = context.RequestContext(user_id=self.user.id, - project_id=self.project.id) - host = self.network.host + self.user_id = 'fake' + self.project_id = 'fake' + self.context = context.RequestContext(self.user_id, + self.project_id, + True) def fake_show(meh, context, id): return {'id': 1, 'container_format': 'ami', @@ -87,12 +85,10 @@ class CloudTestCase(test.TestCase): self.stubs.Set(rpc, 'cast', finish_cast) def tearDown(self): - networks = db.project_get_networks(self.context, self.project.id, + networks = db.project_get_networks(self.context, self.project_id, associate=False) for network in networks: db.network_disassociate(self.context, network['id']) - self.manager.delete_project(self.project) - self.manager.delete_user(self.user) super(CloudTestCase, self).tearDown() def _create_key(self, name): diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py index 5d59b628a..a1b86276f 100644 --- a/nova/tests/test_compute.py +++ b/nova/tests/test_compute.py @@ -19,10 +19,6 @@ Tests For Compute """ -import mox -import stubout - -from nova.auth import manager from nova import compute from nova.compute import instance_types from nova.compute import manager as compute_manager @@ -67,10 +63,9 @@ class ComputeTestCase(test.TestCase): network_manager='nova.network.manager.FlatManager') self.compute = utils.import_object(FLAGS.compute_manager) self.compute_api = compute.API() - self.manager = manager.AuthManager() - self.user = self.manager.create_user('fake', 'fake', 'fake') - self.project = self.manager.create_project('fake', 'fake', 'fake') - self.context = context.RequestContext('fake', 'fake', False) + self.user_id = 'fake' + self.project_id = 'fake' + self.context = context.RequestContext(self.user_id, self.project_id) test_notifier.NOTIFICATIONS = [] def fake_show(meh, context, id): @@ -78,19 +73,14 @@ class ComputeTestCase(test.TestCase): self.stubs.Set(nova.image.fake._FakeImageService, 'show', fake_show) - def tearDown(self): - self.manager.delete_user(self.user) - self.manager.delete_project(self.project) - super(ComputeTestCase, self).tearDown() - def _create_instance(self, params={}): """Create a test instance""" inst = {} inst['image_ref'] = 1 inst['reservation_id'] = 'r-fakeres' inst['launch_time'] = '10' - inst['user_id'] = self.user.id - inst['project_id'] = self.project.id + inst['user_id'] = self.user_id + inst['project_id'] = self.project_id type_id = instance_types.get_instance_type_by_name('m1.tiny')['id'] inst['instance_type_id'] = type_id inst['ami_launch_index'] = 0 @@ -115,8 +105,8 @@ class ComputeTestCase(test.TestCase): def _create_group(self): values = {'name': 'testgroup', 'description': 'testgroup', - 'user_id': self.user.id, - 'project_id': self.project.id} + 'user_id': self.user_id, + 'project_id': self.project_id} return db.security_group_create(self.context, values) def _get_dummy_instance(self): @@ -350,8 +340,8 @@ class ComputeTestCase(test.TestCase): self.assertEquals(msg['priority'], 'INFO') self.assertEquals(msg['event_type'], 'compute.instance.create') payload = msg['payload'] - self.assertEquals(payload['tenant_id'], self.project.id) - self.assertEquals(payload['user_id'], self.user.id) + self.assertEquals(payload['tenant_id'], self.project_id) + self.assertEquals(payload['user_id'], self.user_id) self.assertEquals(payload['instance_id'], instance_id) self.assertEquals(payload['instance_type'], 'm1.tiny') type_id = instance_types.get_instance_type_by_name('m1.tiny')['id'] @@ -374,8 +364,8 @@ class ComputeTestCase(test.TestCase): self.assertEquals(msg['priority'], 'INFO') self.assertEquals(msg['event_type'], 'compute.instance.delete') payload = msg['payload'] - self.assertEquals(payload['tenant_id'], self.project.id) - self.assertEquals(payload['user_id'], self.user.id) + self.assertEquals(payload['tenant_id'], self.project_id) + self.assertEquals(payload['user_id'], self.user_id) self.assertEquals(payload['instance_id'], instance_id) self.assertEquals(payload['instance_type'], 'm1.tiny') type_id = instance_types.get_instance_type_by_name('m1.tiny')['id'] @@ -457,8 +447,8 @@ class ComputeTestCase(test.TestCase): self.assertEquals(msg['priority'], 'INFO') self.assertEquals(msg['event_type'], 'compute.instance.resize.prep') payload = msg['payload'] - self.assertEquals(payload['tenant_id'], self.project.id) - self.assertEquals(payload['user_id'], self.user.id) + self.assertEquals(payload['tenant_id'], self.project_id) + self.assertEquals(payload['user_id'], self.user_id) self.assertEquals(payload['instance_id'], instance_id) self.assertEquals(payload['instance_type'], 'm1.tiny') type_id = instance_types.get_instance_type_by_name('m1.tiny')['id'] @@ -850,7 +840,6 @@ class ComputeTestCase(test.TestCase): def test_run_kill_vm(self): """Detect when a vm is terminated behind the scenes""" - self.stubs = stubout.StubOutForTesting() self.stubs.Set(compute_manager.ComputeManager, '_report_driver_status', nop_report_driver_status) diff --git a/nova/tests/test_console.py b/nova/tests/test_console.py index 1806cc1ea..cf7f592cf 100644 --- a/nova/tests/test_console.py +++ b/nova/tests/test_console.py @@ -26,10 +26,9 @@ from nova import exception from nova import flags from nova import test from nova import utils -from nova.auth import manager -from nova.console import manager as console_manager FLAGS = flags.FLAGS +flags.DECLARE('console_driver', 'nova.console.manager') class ConsoleTestCase(test.TestCase): @@ -39,17 +38,11 @@ class ConsoleTestCase(test.TestCase): self.flags(console_driver='nova.console.fake.FakeConsoleProxy', stub_compute=True) self.console = utils.import_object(FLAGS.console_manager) - self.manager = manager.AuthManager() - self.user = self.manager.create_user('fake', 'fake', 'fake') - self.project = self.manager.create_project('fake', 'fake', 'fake') - self.context = context.get_admin_context() + self.user_id = 'fake' + self.project_id = 'fake' + self.context = context.RequestContext(self.user_id, self.project_id) self.host = 'test_compute_host' - def tearDown(self): - self.manager.delete_user(self.user) - self.manager.delete_project(self.project) - super(ConsoleTestCase, self).tearDown() - def _create_instance(self): """Create a test instance""" inst = {} @@ -58,8 +51,8 @@ class ConsoleTestCase(test.TestCase): inst['image_id'] = 1 inst['reservation_id'] = 'r-fakeres' inst['launch_time'] = '10' - inst['user_id'] = self.user.id - inst['project_id'] = self.project.id + inst['user_id'] = self.user_id + inst['project_id'] = self.project_id inst['instance_type_id'] = 1 inst['ami_launch_index'] = 0 return db.instance_create(self.context, inst)['id'] diff --git a/nova/tests/test_libvirt.py b/nova/tests/test_libvirt.py index 948ca215f..61e95c05e 100644 --- a/nova/tests/test_libvirt.py +++ b/nova/tests/test_libvirt.py @@ -32,7 +32,6 @@ from nova import flags from nova import test from nova import utils from nova.api.ec2 import cloud -from nova.auth import manager from nova.compute import power_state from nova.virt.libvirt import connection from nova.virt.libvirt import firewall @@ -150,35 +149,14 @@ class LibvirtConnTestCase(test.TestCase): super(LibvirtConnTestCase, self).setUp() connection._late_load_cheetah() self.flags(fake_call=True) - self.manager = manager.AuthManager() - - try: - pjs = self.manager.get_projects() - pjs = [p for p in pjs if p.name == 'fake'] - if 0 != len(pjs): - self.manager.delete_project(pjs[0]) - - users = self.manager.get_users() - users = [u for u in users if u.name == 'fake'] - if 0 != len(users): - self.manager.delete_user(users[0]) - except Exception, e: - pass - - users = self.manager.get_users() - self.user = self.manager.create_user('fake', 'fake', 'fake', - admin=True) - self.project = self.manager.create_project('fake', 'fake', 'fake') + self.user_id = 'fake' + self.project_id = 'fake' + self.context = context.RequestContext(self.user_id, self.project_id) self.network = utils.import_object(FLAGS.network_manager) self.context = context.get_admin_context() FLAGS.instances_path = '' self.call_libvirt_dependant_setup = False - def tearDown(self): - self.manager.delete_project(self.project) - self.manager.delete_user(self.user) - super(LibvirtConnTestCase, self).tearDown() - test_ip = '10.11.12.13' test_instance = {'memory_kb': '1024000', 'basepath': '/some/path', @@ -429,13 +407,13 @@ class LibvirtConnTestCase(test.TestCase): self.assertEquals(parameters[1].get('value'), 'fake') def _check_xml_and_container(self, instance): - user_context = context.RequestContext(self.user.id, - self.project.id) + user_context = context.RequestContext(self.user_id, + self.project_id) instance_ref = db.instance_create(user_context, instance) # Re-get the instance so it's bound to an actual session instance_ref = db.instance_get(user_context, instance_ref['id']) network_ref = db.project_get_networks(context.get_admin_context(), - self.project.id)[0] + self.project_id)[0] vif = {'address': '56:12:12:12:12:12', 'network_id': network_ref['id'], @@ -475,10 +453,10 @@ class LibvirtConnTestCase(test.TestCase): def _check_xml_and_uri(self, instance, expect_ramdisk, expect_kernel, rescue=False): - user_context = context.RequestContext(self.user.id, self.project.id) + user_context = context.RequestContext(self.user_id, self.project_id) instance_ref = db.instance_create(user_context, instance) network_ref = db.project_get_networks(context.get_admin_context(), - self.project.id)[0] + self.project_id)[0] _setup_networking(instance_ref['id'], ip=self.test_ip) @@ -759,7 +737,7 @@ class LibvirtConnTestCase(test.TestCase): conn.firewall_driver.setattr('prepare_instance_filter', fake_none) network = db.project_get_networks(context.get_admin_context(), - self.project.id)[0] + self.project_id)[0] ip_dict = {'ip': self.test_ip, 'netmask': network['netmask'], 'enabled': '1'} @@ -814,11 +792,9 @@ class IptablesFirewallTestCase(test.TestCase): def setUp(self): super(IptablesFirewallTestCase, self).setUp() - self.manager = manager.AuthManager() - self.user = self.manager.create_user('fake', 'fake', 'fake', - admin=True) - self.project = self.manager.create_project('fake', 'fake', 'fake') - self.context = context.RequestContext('fake', 'fake') + self.user_id = 'fake' + self.project_id = 'fake' + self.context = context.RequestContext(self.user_id, self.project_id) self.network = utils.import_object(FLAGS.network_manager) class FakeLibvirtConnection(object): @@ -843,11 +819,6 @@ class IptablesFirewallTestCase(test.TestCase): connection.libxml2 = __import__('libxml2') return True - def tearDown(self): - self.manager.delete_project(self.project) - self.manager.delete_user(self.user) - super(IptablesFirewallTestCase, self).tearDown() - in_nat_rules = [ '# Generated by iptables-save v1.4.10 on Sat Feb 19 00:03:19 2011', '*nat', @@ -1161,22 +1132,15 @@ class NWFilterTestCase(test.TestCase): class Mock(object): pass - self.manager = manager.AuthManager() - self.user = self.manager.create_user('fake', 'fake', 'fake', - admin=True) - self.project = self.manager.create_project('fake', 'fake', 'fake') - self.context = context.RequestContext(self.user.id, self.project.id) + self.user_id = 'fake' + self.project_id = 'fake' + self.context = context.RequestContext(self.user_id, self.project_id) self.fake_libvirt_connection = Mock() self.fw = firewall.NWFilterFirewall( lambda: self.fake_libvirt_connection) - def tearDown(self): - self.manager.delete_project(self.project) - self.manager.delete_user(self.user) - super(NWFilterTestCase, self).tearDown() - def test_cidr_rule_nwfilter_xml(self): cloud_controller = cloud.CloudController() cloud_controller.create_security_group(self.context, diff --git a/nova/tests/test_objectstore.py b/nova/tests/test_objectstore.py index 39b4e18d7..af4ee27cd 100644 --- a/nova/tests/test_objectstore.py +++ b/nova/tests/test_objectstore.py @@ -21,8 +21,6 @@ Unittets for S3 objectstore clone. """ import boto -import glob -import hashlib import os import shutil import tempfile @@ -30,12 +28,9 @@ import tempfile from boto import exception as boto_exception from boto.s3 import connection as s3 -from nova import context -from nova import exception from nova import flags from nova import wsgi from nova import test -from nova.auth import manager from nova.objectstore import s3server @@ -61,11 +56,6 @@ class S3APITestCase(test.TestCase): buckets_path=os.path.join(OSS_TEMPDIR, 'buckets'), s3_host='127.0.0.1') - self.auth_manager = manager.AuthManager() - self.admin_user = self.auth_manager.create_user('admin', admin=True) - self.admin_project = self.auth_manager.create_project('admin', - self.admin_user) - shutil.rmtree(FLAGS.buckets_path) os.mkdir(FLAGS.buckets_path) @@ -80,8 +70,8 @@ class S3APITestCase(test.TestCase): boto.config.add_section('Boto') boto.config.set('Boto', 'num_retries', '0') - conn = s3.S3Connection(aws_access_key_id=self.admin_user.access, - aws_secret_access_key=self.admin_user.secret, + conn = s3.S3Connection(aws_access_key_id='fake', + aws_secret_access_key='fake', host=FLAGS.s3_host, port=FLAGS.s3_port, is_secure=False, @@ -104,11 +94,11 @@ class S3APITestCase(test.TestCase): self.assertEquals(buckets[0].name, name, "Wrong name") return True - def test_000_list_buckets(self): + def test_list_buckets(self): """Make sure we are starting with no buckets.""" self._ensure_no_buckets(self.conn.get_all_buckets()) - def test_001_create_and_delete_bucket(self): + def test_create_and_delete_bucket(self): """Test bucket creation and deletion.""" bucket_name = 'testbucket' @@ -117,7 +107,7 @@ class S3APITestCase(test.TestCase): self.conn.delete_bucket(bucket_name) self._ensure_no_buckets(self.conn.get_all_buckets()) - def test_002_create_bucket_and_key_and_delete_key_again(self): + def test_create_bucket_and_key_and_delete_key_again(self): """Test key operations on buckets.""" bucket_name = 'testbucket' key_name = 'somekey' @@ -146,8 +136,6 @@ class S3APITestCase(test.TestCase): bucket_name) def tearDown(self): - """Tear down auth and test server.""" - self.auth_manager.delete_user('admin') - self.auth_manager.delete_project('admin') + """Tear down test server.""" self.server.stop() super(S3APITestCase, self).tearDown() diff --git a/nova/tests/test_vmwareapi.py b/nova/tests/test_vmwareapi.py index cbf7801cf..52b5debf5 100644 --- a/nova/tests/test_vmwareapi.py +++ b/nova/tests/test_vmwareapi.py @@ -26,7 +26,6 @@ from nova import db from nova import flags from nova import test from nova import utils -from nova.auth import manager from nova.compute import power_state from nova.tests.glance import stubs as glance_stubs from nova.tests.vmwareapi import db_fakes @@ -48,12 +47,10 @@ class VMWareAPIVMTestCase(test.TestCase): # self.flags(vmwareapi_host_ip='test_url', # vmwareapi_host_username='test_username', # vmwareapi_host_password='test_pass') - # self.manager = manager.AuthManager() - # self.user = self.manager.create_user('fake', 'fake', 'fake', - # admin=True) - # self.project = self.manager.create_project('fake', 'fake', 'fake') # self.network = utils.import_object(FLAGS.network_manager) - # self.stubs = stubout.StubOutForTesting() + # self.user_id = 'fake' + # self.project_id = 'fake' + # self.context = context.RequestContext(self.user_id, self.project_id) # vmwareapi_fake.reset() # db_fakes.stub_out_db_instance_api(self.stubs) # stubs.set_stubs(self.stubs) @@ -64,15 +61,13 @@ class VMWareAPIVMTestCase(test.TestCase): #def tearDown(self): # super(VMWareAPIVMTestCase, self).tearDown() # vmwareapi_fake.cleanup() - # self.manager.delete_project(self.project) - # self.manager.delete_user(self.user) # self.stubs.UnsetAll() def _create_instance_in_the_db(self): values = {'name': 1, 'id': 1, - 'project_id': self.project.id, - 'user_id': self.user.id, + 'project_id': self.project_id, + 'user_id': self.user_id, 'image_id': "1", 'kernel_id': "1", 'ramdisk_id': "1", diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index 4cb7447d3..651c7f9e7 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -30,7 +30,6 @@ from nova import flags from nova import log as logging from nova import test from nova import utils -from nova.auth import manager from nova.compute import instance_types from nova.compute import power_state from nova import exception @@ -69,7 +68,9 @@ class XenAPIVolumeTestCase(test.TestCase): def setUp(self): super(XenAPIVolumeTestCase, self).setUp() self.stubs = stubout.StubOutForTesting() - self.context = context.RequestContext('fake', 'fake', False) + self.user_id = 'fake' + self.project_id = 'fake' + self.context = context.RequestContext(self.user_id, self.project_id) FLAGS.target_host = '127.0.0.1' FLAGS.xenapi_connection_url = 'test_url' FLAGS.xenapi_connection_password = 'test_pass' @@ -77,7 +78,7 @@ class XenAPIVolumeTestCase(test.TestCase): stubs.stub_out_get_target(self.stubs) xenapi_fake.reset() self.values = {'id': 1, - 'project_id': 'fake', + 'project_id': self.user_id, 'user_id': 'fake', 'image_ref': 1, 'kernel_id': 2, @@ -173,10 +174,6 @@ class XenAPIVMTestCase(test.TestCase): """Unit tests for VM operations.""" def setUp(self): super(XenAPIVMTestCase, self).setUp() - self.manager = manager.AuthManager() - self.user = self.manager.create_user('fake', 'fake', 'fake', - admin=True) - self.project = self.manager.create_project('fake', 'fake', 'fake') self.network = utils.import_object(FLAGS.network_manager) self.stubs = stubout.StubOutForTesting() self.flags(xenapi_connection_url='test_url', @@ -195,7 +192,9 @@ class XenAPIVMTestCase(test.TestCase): stubs.stub_out_vm_methods(self.stubs) glance_stubs.stubout_glance_client(self.stubs) fake_utils.stub_out_utils_execute(self.stubs) - self.context = context.RequestContext('fake', 'fake', False) + self.user_id = 'fake' + self.project_id = 'fake' + self.context = context.RequestContext(self.user_id, self.project_id) self.conn = xenapi_conn.get_connection(False) def test_parallel_builds(self): @@ -229,8 +228,8 @@ class XenAPIVMTestCase(test.TestCase): instance = db.instance_create(self.context, values) self.conn.spawn(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) + gt1 = eventlet.spawn(_do_build, 1, self.project_id, self.user_id) + gt2 = eventlet.spawn(_do_build, 2, self.project_id, self.user_id) gt1.wait() gt2.wait() @@ -399,8 +398,8 @@ class XenAPIVMTestCase(test.TestCase): check_injection=False): stubs.stubout_loopingcall_start(self.stubs) values = {'id': instance_id, - 'project_id': self.project.id, - 'user_id': self.user.id, + 'project_id': self.project_id, + 'user_id': self.user_id, 'image_ref': image_ref, 'kernel_id': kernel_id, 'ramdisk_id': ramdisk_id, @@ -465,12 +464,30 @@ class XenAPIVMTestCase(test.TestCase): self._check_vdis(vdi_recs_start, vdi_recs_end) def test_spawn_raw_objectstore(self): - FLAGS.xenapi_image_service = 'objectstore' - self._test_spawn(1, None, None) + # TODO(vish): deprecated + from nova.auth import manager + authman = manager.AuthManager() + authman.create_user('fake', 'fake') + authman.create_project('fake', 'fake') + try: + FLAGS.xenapi_image_service = 'objectstore' + self._test_spawn(1, None, None) + finally: + authman.delete_project('fake') + authman.delete_user('fake') def test_spawn_objectstore(self): - FLAGS.xenapi_image_service = 'objectstore' - self._test_spawn(1, 2, 3) + # TODO(vish): deprecated + from nova.auth import manager + authman = manager.AuthManager() + authman.create_user('fake', 'fake') + authman.create_project('fake', 'fake') + try: + FLAGS.xenapi_image_service = 'objectstore' + self._test_spawn(1, 2, 3) + finally: + authman.delete_project('fake') + authman.delete_user('fake') @stub_vm_utils_with_vdi_attached_here def test_spawn_raw_glance(self): @@ -599,7 +616,7 @@ class XenAPIVMTestCase(test.TestCase): # guest agent is detected self.assertFalse(self._tee_executed) - @test.skip_test("Never gets an address, not sure why") + @test.skip_test("Key Error on domid") def test_spawn_vlanmanager(self): self.flags(xenapi_image_service='glance', network_manager='nova.network.manager.VlanManager', @@ -609,7 +626,7 @@ class XenAPIVMTestCase(test.TestCase): def dummy(*args, **kwargs): pass - self.stubs.Set(VMOps, 'create_vifs', dummy) + self.stubs.Set(vmops.VMOps, 'create_vifs', dummy) # Reset network table xenapi_fake.reset_table('network') # Instance id = 2 will use vlan network (see db/fakes.py) @@ -623,7 +640,7 @@ class XenAPIVMTestCase(test.TestCase): self.network.set_network_host(ctxt, network['id']) self.network.allocate_for_instance(ctxt, instance_id=instance_ref.id, - instance_type_id=1, project_id=self.project.id) + instance_type_id=1, project_id=self.project_id) self.network.setup_compute_network(ctxt, instance_ref.id) self._test_spawn(glance_stubs.FakeGlance.IMAGE_MACHINE, glance_stubs.FakeGlance.IMAGE_KERNEL, @@ -655,21 +672,13 @@ class XenAPIVMTestCase(test.TestCase): # Ensure that it will not unrescue a non-rescued instance. self.assertRaises(Exception, conn.unrescue, instance, None) - def tearDown(self): - super(XenAPIVMTestCase, self).tearDown() - self.manager.delete_project(self.project) - self.manager.delete_user(self.user) - self.vm_info = None - self.vm = None - self.stubs.UnsetAll() - def _create_instance(self, instance_id=1): """Creates and spawns a test instance.""" stubs.stubout_loopingcall_start(self.stubs) values = { 'id': instance_id, - 'project_id': self.project.id, - 'user_id': self.user.id, + 'project_id': self.project_id, + 'user_id': self.user_id, 'image_ref': 1, 'kernel_id': 2, 'ramdisk_id': 3, @@ -750,14 +759,12 @@ class XenAPIMigrateInstance(test.TestCase): stubs.stub_out_get_target(self.stubs) xenapi_fake.reset() xenapi_fake.create_network('fake', FLAGS.flat_network_bridge) - self.manager = manager.AuthManager() - self.user = self.manager.create_user('fake', 'fake', 'fake', - admin=True) - self.project = self.manager.create_project('fake', 'fake', 'fake') - self.context = context.RequestContext('fake', 'fake', False) + self.user_id = 'fake' + self.project_id = 'fake' + self.context = context.RequestContext(self.user_id, self.project_id) self.values = {'id': 1, - 'project_id': self.project.id, - 'user_id': self.user.id, + 'project_id': self.project_id, + 'user_id': self.user_id, 'image_ref': 1, 'kernel_id': None, 'ramdisk_id': None, @@ -771,12 +778,6 @@ class XenAPIMigrateInstance(test.TestCase): stubs.stubout_get_this_vm_uuid(self.stubs) glance_stubs.stubout_glance_client(self.stubs) - def tearDown(self): - super(XenAPIMigrateInstance, self).tearDown() - self.manager.delete_project(self.project) - self.manager.delete_user(self.user) - self.stubs.UnsetAll() - def test_migrate_disk_and_power_off(self): instance = db.instance_create(self.context, self.values) stubs.stubout_session(self.stubs, stubs.FakeSessionForMigrationTests) diff --git a/nova/virt/hyperv.py b/nova/virt/hyperv.py index 5c1dc772d..490c9174b 100644 --- a/nova/virt/hyperv.py +++ b/nova/virt/hyperv.py @@ -66,7 +66,6 @@ import time from nova import exception from nova import flags from nova import log as logging -from nova.auth import manager from nova.compute import power_state from nova.virt import driver from nova.virt import images @@ -145,13 +144,12 @@ class HyperVConnection(driver.ComputeDriver): if vm is not None: raise exception.InstanceExists(name=instance.name) - user = manager.AuthManager().get_user(instance['user_id']) - project = manager.AuthManager().get_project(instance['project_id']) #Fetch the file, assume it is a VHD file. base_vhd_filename = os.path.join(FLAGS.instances_path, instance.name) vhdfile = "%s.vhd" % (base_vhd_filename) - images.fetch(instance['image_ref'], vhdfile, user, project) + images.fetch(instance['image_ref'], vhdfile, + instance['user_id'], instance['project_id']) try: self._create_vm(instance) diff --git a/nova/virt/images.py b/nova/virt/images.py index 40bf6107c..2e9fca3d6 100644 --- a/nova/virt/images.py +++ b/nova/virt/images.py @@ -33,7 +33,7 @@ FLAGS = flags.FLAGS LOG = logging.getLogger('nova.virt.images') -def fetch(image_href, path, _user, _project): +def fetch(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 diff --git a/nova/virt/libvirt/connection.py b/nova/virt/libvirt/connection.py index 342dea98f..9c57d43b5 100644 --- a/nova/virt/libvirt/connection.py +++ b/nova/virt/libvirt/connection.py @@ -757,9 +757,9 @@ class LibvirtConnection(driver.ComputeDriver): else: utils.execute('cp', base, target) - def _fetch_image(self, target, image_id, user, project, size=None): + def _fetch_image(self, target, image_id, user_id, project_id, size=None): """Grab image and optionally attempt to resize it""" - images.fetch(image_id, target, user, project) + images.fetch(image_id, target, user_id, project_id) if size: disk.extend(target, size) @@ -797,9 +797,6 @@ class LibvirtConnection(driver.ComputeDriver): os.close(os.open(basepath('console.log', ''), os.O_CREAT | os.O_WRONLY, 0660)) - user = manager.AuthManager().get_user(inst['user_id']) - project = manager.AuthManager().get_project(inst['project_id']) - if not disk_images: disk_images = {'image_id': inst['image_ref'], 'kernel_id': inst['kernel_id'], @@ -811,16 +808,16 @@ class LibvirtConnection(driver.ComputeDriver): target=basepath('kernel'), fname=fname, image_id=disk_images['kernel_id'], - user=user, - project=project) + user_id=inst['user_id'], + project_id=inst['project_id']) if disk_images['ramdisk_id']: fname = '%08x' % int(disk_images['ramdisk_id']) self._cache_image(fn=self._fetch_image, target=basepath('ramdisk'), fname=fname, image_id=disk_images['ramdisk_id'], - user=user, - project=project) + user_id=inst['user_id'], + project_id=inst['project_id']) root_fname = hashlib.sha1(disk_images['image_id']).hexdigest() size = FLAGS.minimum_root_size @@ -838,8 +835,8 @@ class LibvirtConnection(driver.ComputeDriver): fname=root_fname, cow=FLAGS.use_cow_images, image_id=disk_images['image_id'], - user=user, - project=project, + user_id=inst['user_id'], + project_id=inst['project_id'], size=size) if inst_type['local_gb'] and not self._volume_in_mapping( diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index 71107aff4..d146ee2c7 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -37,7 +37,6 @@ import nova.image from nova.image import glance as glance_image_service from nova import log as logging from nova import utils -from nova.auth.manager import AuthManager from nova.compute import instance_types from nova.compute import power_state from nova.virt import disk @@ -406,7 +405,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, session, instance_id, image, user_id, project_id, image_type): """ image_type is interpreted as an ImageType instance @@ -418,18 +417,23 @@ class VMHelper(HelperBase): Returns: A single filename if image_type is KERNEL_RAMDISK A list of dictionaries that describe VDIs, otherwise """ - 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(session, instance_id, + image, image_type) else: + # TODO(vish): this shouldn't be used anywhere anymore and + # can probably be removed + from nova.auth.manager import AuthManager + manager = AuthManager() + access = manager.get_access_key(user_id, project_id) + secret = manager.get_user(user_id).secret return cls._fetch_image_objectstore(session, instance_id, image, - access, user.secret, + access, secret, image_type) @classmethod - def _fetch_image_glance_vhd(cls, session, instance_id, image, access, + def _fetch_image_glance_vhd(cls, session, instance_id, image, image_type): """Tell glance to download an image and put the VHDs into the SR @@ -477,7 +481,7 @@ class VMHelper(HelperBase): return vdis @classmethod - def _fetch_image_glance_disk(cls, session, instance_id, image, access, + def _fetch_image_glance_disk(cls, session, instance_id, image, image_type): """Fetch the image from Glance @@ -611,8 +615,7 @@ class VMHelper(HelperBase): return image_type @classmethod - def _fetch_image_glance(cls, session, instance_id, image, access, - image_type): + def _fetch_image_glance(cls, session, instance_id, image, image_type): """Fetch image from glance based on image type. Returns: A single filename if image_type is KERNEL or RAMDISK @@ -620,10 +623,10 @@ class VMHelper(HelperBase): """ if image_type == ImageType.DISK_VHD: return cls._fetch_image_glance_vhd( - session, instance_id, image, access, image_type) + session, instance_id, image, image_type) else: return cls._fetch_image_glance_disk( - session, instance_id, image, access, image_type) + session, instance_id, image, image_type) @classmethod def _fetch_image_objectstore(cls, session, instance_id, image, access, diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 7995576a6..7c6f12ce2 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -38,7 +38,6 @@ from nova import ipv6 from nova import log as logging from nova import utils -from nova.auth.manager import AuthManager from nova.compute import power_state from nova.virt import driver from nova.virt.xenapi.network_utils import NetworkHelper @@ -130,11 +129,10 @@ class VMOps(object): self._session.call_xenapi('VM.start', vm_ref, False, False) def _create_disks(self, 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, - instance.id, instance.image_ref, user, project, + instance.id, instance.image_ref, + instance.user_id, instance.project_id, disk_image_type) return vdis @@ -172,21 +170,18 @@ class VMOps(object): power_state.SHUTDOWN) return - user = AuthManager().get_user(instance.user_id) - project = AuthManager().get_project(instance.project_id) - disk_image_type = VMHelper.determine_disk_image_type(instance) kernel = None ramdisk = None try: if instance.kernel_id: kernel = VMHelper.fetch_image(self._session, instance.id, - instance.kernel_id, user, project, - ImageType.KERNEL)[0] + instance.kernel_id, instance.user_id, + instance.project_id, ImageType.KERNEL)[0] if instance.ramdisk_id: ramdisk = VMHelper.fetch_image(self._session, instance.id, - instance.ramdisk_id, user, project, - ImageType.RAMDISK)[0] + 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', vdis[0]['vdi_uuid']) -- cgit From 8d97118be776fcaad3053d1f93f61d339685a4ae Mon Sep 17 00:00:00 2001 From: Nikolay Sokolov Date: Fri, 22 Jul 2011 17:26:11 +0400 Subject: Moved restaring instances from livbirt driver to ComputeManager. --- nova/compute/manager.py | 19 +++++++++++++++++++ nova/flags.py | 3 +++ nova/virt/libvirt/connection.py | 25 ++----------------------- 3 files changed, 24 insertions(+), 23 deletions(-) (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 5819a520a..c7d3004a5 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -146,7 +146,26 @@ class ComputeManager(manager.SchedulerDependentManager): def init_host(self): """Initialization for a standalone compute service.""" + # NOTE(nsokolov): based on itoumsn's implementation from libvirt driver + from nova import context self.driver.init_host(host=self.host) + admin_context = context.get_admin_context() + for instance in self.db.instance_get_all_by_host(admin_context, self.host): + try: + LOG.debug(_('Checking state of %s'), instance['name']) + state = self.driver.get_info(instance['name'])['state'] + except exception.NotFound: + state = power_state.SHUTOFF + + LOG.debug(_('Current state of %(name)s is %(state)s, state in DB is %(db_state)s.'), + {'name': instance['name'], 'state': state, 'db_state': instance['state']}) + + if instance['state'] == power_state.RUNNING and state != power_state.RUNNING \ + and FLAGS.start_guests_on_host_boot: + LOG.debug(_('Rebooting instance %(name)s after nova-compute restart.')) + self.reboot_instance(admin_context, instance[id]) + else: + self.db.instance_set_state(ctxt, instance['id'], state) def _update_state(self, context, instance_id, state=None): """Update the state of an instance from the driver info.""" diff --git a/nova/flags.py b/nova/flags.py index 49355b436..23ca38b17 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -387,3 +387,6 @@ DEFINE_list('zone_capabilities', 'Key/Multi-value list representng capabilities of this zone') DEFINE_string('build_plan_encryption_key', None, '128bit (hex) encryption key for scheduler build plans.') + +DEFINE_bool('start_guests_on_host_boot', False, + 'Whether to restart guests when the host reboots') diff --git a/nova/virt/libvirt/connection.py b/nova/virt/libvirt/connection.py index 342dea98f..d85b91ee2 100644 --- a/nova/virt/libvirt/connection.py +++ b/nova/virt/libvirt/connection.py @@ -121,8 +121,6 @@ flags.DEFINE_integer('live_migration_bandwidth', 0, 'Define live migration behavior') flags.DEFINE_string('qemu_img', 'qemu-img', 'binary to use for qemu-img commands') -flags.DEFINE_bool('start_guests_on_host_boot', False, - 'Whether to restart guests when the host reboots') def get_connection(read_only): @@ -167,27 +165,8 @@ class LibvirtConnection(driver.ComputeDriver): self.firewall_driver = fw_class(get_connection=self._get_connection) def init_host(self, host): - # Adopt existing VM's running here - ctxt = context.get_admin_context() - for instance in db.instance_get_all_by_host(ctxt, host): - try: - LOG.debug(_('Checking state of %s'), instance['name']) - state = self.get_info(instance['name'])['state'] - except exception.NotFound: - state = power_state.SHUTOFF - - LOG.debug(_('Current state of %(name)s was %(state)s.'), - {'name': instance['name'], 'state': state}) - db.instance_set_state(ctxt, instance['id'], state) - - # NOTE(justinsb): We no longer delete SHUTOFF instances, - # the user may want to power them back on - - if state != power_state.RUNNING: - continue - self.firewall_driver.setup_basic_filtering(instance) - self.firewall_driver.prepare_instance_filter(instance) - self.firewall_driver.apply_instance_filter(instance) + # NOTE(nsokolov): moved instance restarting to ComputeManager + pass def _get_connection(self): if not self._wrapped_conn or not self._test_connection(): -- cgit 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 44d1024a53b8150cf9542d08d5886f430365f161 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Fri, 22 Jul 2011 19:47:41 +0000 Subject: fix all tests --- nova/api/ec2/__init__.py | 29 +-- nova/api/openstack/auth.py | 20 ++- nova/compute/api.py | 2 + .../extensions/test_flavors_extra_specs.py | 198 --------------------- nova/tests/api/openstack/fakes.py | 40 +++-- nova/tests/api/openstack/test_auth.py | 37 ++-- nova/tests/api/openstack/test_extensions.py | 29 ++- .../api/openstack/test_flavors_extra_specs.py | 180 +++++++++++++++++++ nova/tests/api/openstack/test_images.py | 27 ++- nova/tests/api/openstack/test_servers.py | 45 +++-- nova/tests/test_access.py | 2 +- nova/tests/test_api.py | 3 +- nova/wsgi.py | 12 ++ 13 files changed, 313 insertions(+), 311 deletions(-) delete mode 100644 nova/tests/api/openstack/extensions/test_flavors_extra_specs.py create mode 100644 nova/tests/api/openstack/test_flavors_extra_specs.py (limited to 'nova') diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py index edae94331..0a743075c 100644 --- a/nova/api/ec2/__init__.py +++ b/nova/api/ec2/__init__.py @@ -66,7 +66,7 @@ class RequestLogging(wsgi.Middleware): else: controller = None action = None - ctxt = request.environ.get('ec2.context', None) + ctxt = request.environ.get('nova.context', None) delta = utils.utcnow() - start seconds = delta.seconds microseconds = delta.microseconds @@ -138,20 +138,8 @@ class Lockout(wsgi.Middleware): return res -class InjectContext(wsgi.Middleware): - """Always add a fake 'ec2.context' to WSGI environ.""" - def __init__(self, context, *args, **kwargs): - self.context = context - super(InjectContext, self).__init__(*args, **kwargs) - - @webob.dec.wsgify(RequestClass=wsgi.Request) - def __call__(self, req): - req.environ['ec2.context'] = self.context - return self.application - - class Authenticate(wsgi.Middleware): - """Authenticate an EC2 request and add 'ec2.context' to WSGI environ.""" + """Authenticate an EC2 request and add 'nova.context' to WSGI environ.""" @webob.dec.wsgify(RequestClass=wsgi.Request) def __call__(self, req): @@ -187,12 +175,13 @@ class Authenticate(wsgi.Middleware): remote_address = req.headers.get('X-Forwarded-For', remote_address) ctxt = context.RequestContext(user_id=user.id, project_id=project.id, + is_admin=user.is_admin(), remote_address=remote_address) - req.environ['ec2.context'] = ctxt + req.environ['nova.context'] = ctxt uname = user.name pname = project.name msg = _('Authenticated Request For %(uname)s:%(pname)s)') % locals() - LOG.audit(msg, context=req.environ['ec2.context']) + LOG.audit(msg, context=req.environ['nova.context']) return self.application @@ -239,7 +228,7 @@ class Authorizer(wsgi.Middleware): """Authorize an EC2 API request. Return a 401 if ec2.controller and ec2.action in WSGI environ may not be - executed in ec2.context. + executed in nova.context. """ def __init__(self, application): @@ -293,7 +282,7 @@ class Authorizer(wsgi.Middleware): @webob.dec.wsgify(RequestClass=wsgi.Request) def __call__(self, req): - context = req.environ['ec2.context'] + context = req.environ['nova.context'] controller = req.environ['ec2.request'].controller.__class__.__name__ action = req.environ['ec2.request'].action allowed_roles = self.action_roles[controller].get(action, ['none']) @@ -319,14 +308,14 @@ class Executor(wsgi.Application): """Execute an EC2 API request. - Executes 'ec2.action' upon 'ec2.controller', passing 'ec2.context' and + Executes 'ec2.action' upon 'ec2.controller', passing 'nova.context' and 'ec2.action_args' (all variables in WSGI environ.) Returns an XML response, or a 400 upon failure. """ @webob.dec.wsgify(RequestClass=wsgi.Request) def __call__(self, req): - context = req.environ['ec2.context'] + context = req.environ['nova.context'] api_request = req.environ['ec2.request'] result = None try: diff --git a/nova/api/openstack/auth.py b/nova/api/openstack/auth.py index 5b387c081..9caa14a4e 100644 --- a/nova/api/openstack/auth.py +++ b/nova/api/openstack/auth.py @@ -58,9 +58,25 @@ class AuthMiddleware(wsgi.Middleware): try: project_id = req.headers["X-Auth-Project-Id"] except KeyError: - project_id = user_id + # FIXME(usrleon): It needed only for compatibility + # while osapi clients don't use this header + projects = self.auth.get_projects(user_id) + if projects: + project_id = projects[0] + else: + return faults.Fault(webob.exc.HTTPUnauthorized()) + + is_admin = self.auth.is_admin(user_id) + req.environ['nova.context'] = context.RequestContext(user_id, + project_id, + is_admin) + if not is_admin and not self.auth.is_project_member(user_id, + project_id): + msg = _("%(user_id)s must be an admin or a " + "member of %(project_id)s") + LOG.warn(msg % locals()) + return faults.Fault(webob.exc.HTTPUnauthorized()) - req.environ['nova.context'] = context.RequestContext(user_id, project_id) return self.application def has_authentication(self, req): diff --git a/nova/compute/api.py b/nova/compute/api.py index 51a903d40..9a1ce7452 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -676,6 +676,7 @@ class API(base.Base): all instances in the system. """ + LOG.info(locals()) if reservation_id is not None: recurse_zones = True instances = self.db.instance_get_all_by_reservation( @@ -688,6 +689,7 @@ class API(base.Base): raise instances = None elif project_id or not context.is_admin: + LOG.info(context.project_id) if not context.project_id: instances = self.db.instance_get_all_by_user( context, context.user_id) diff --git a/nova/tests/api/openstack/extensions/test_flavors_extra_specs.py b/nova/tests/api/openstack/extensions/test_flavors_extra_specs.py deleted file mode 100644 index 2c1c335b0..000000000 --- a/nova/tests/api/openstack/extensions/test_flavors_extra_specs.py +++ /dev/null @@ -1,198 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2011 University of Southern California -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -import json -import stubout -import unittest -import webob -import os.path - - -from nova import flags -from nova.api import openstack -from nova.api.openstack import auth -from nova.api.openstack import extensions -from nova.tests.api.openstack import fakes -import nova.wsgi - -FLAGS = flags.FLAGS - - -def return_create_flavor_extra_specs(context, flavor_id, extra_specs): - return stub_flavor_extra_specs() - - -def return_flavor_extra_specs(context, flavor_id): - return stub_flavor_extra_specs() - - -def return_flavor_extra_specs(context, flavor_id): - return stub_flavor_extra_specs() - - -def return_empty_flavor_extra_specs(context, flavor_id): - return {} - - -def delete_flavor_extra_specs(context, flavor_id, key): - pass - - -def stub_flavor_extra_specs(): - specs = { - "key1": "value1", - "key2": "value2", - "key3": "value3", - "key4": "value4", - "key5": "value5"} - return specs - - -class FlavorsExtraSpecsTest(unittest.TestCase): - - def setUp(self): - super(FlavorsExtraSpecsTest, self).setUp() - FLAGS.osapi_extensions_path = os.path.join(os.path.dirname(__file__), - "extensions") - self.stubs = stubout.StubOutForTesting() - fakes.FakeAuthManager.auth_data = {} - fakes.FakeAuthDatabase.data = {} - fakes.stub_out_auth(self.stubs) - fakes.stub_out_key_pair_funcs(self.stubs) - self.mware = auth.AuthMiddleware( - extensions.ExtensionMiddleware( - openstack.APIRouterV11())) - - def tearDown(self): - self.stubs.UnsetAll() - super(FlavorsExtraSpecsTest, self).tearDown() - - def test_index(self): - self.stubs.Set(nova.db.api, 'instance_type_extra_specs_get', - return_flavor_extra_specs) - request = webob.Request.blank('/flavors/1/os-extra_specs') - res = request.get_response(self.mware) - self.assertEqual(200, res.status_int) - res_dict = json.loads(res.body) - self.assertEqual('application/json', res.headers['Content-Type']) - self.assertEqual('value1', res_dict['extra_specs']['key1']) - - def test_index_no_data(self): - self.stubs.Set(nova.db.api, 'instance_type_extra_specs_get', - return_empty_flavor_extra_specs) - req = webob.Request.blank('/flavors/1/os-extra_specs') - res = req.get_response(self.mware) - res_dict = json.loads(res.body) - self.assertEqual(200, res.status_int) - self.assertEqual('application/json', res.headers['Content-Type']) - self.assertEqual(0, len(res_dict['extra_specs'])) - - def test_show(self): - self.stubs.Set(nova.db.api, 'instance_type_extra_specs_get', - return_flavor_extra_specs) - req = webob.Request.blank('/flavors/1/os-extra_specs/key5') - res = req.get_response(self.mware) - self.assertEqual(200, res.status_int) - res_dict = json.loads(res.body) - self.assertEqual('application/json', res.headers['Content-Type']) - self.assertEqual('value5', res_dict['key5']) - - def test_show_spec_not_found(self): - self.stubs.Set(nova.db.api, 'instance_type_extra_specs_get', - return_empty_flavor_extra_specs) - req = webob.Request.blank('/flavors/1/os-extra_specs/key6') - res = req.get_response(self.mware) - res_dict = json.loads(res.body) - self.assertEqual(404, res.status_int) - - def test_delete(self): - self.stubs.Set(nova.db.api, 'instance_type_extra_specs_delete', - delete_flavor_extra_specs) - req = webob.Request.blank('/flavors/1/os-extra_specs/key5') - req.method = 'DELETE' - res = req.get_response(self.mware) - self.assertEqual(200, res.status_int) - - def test_create(self): - self.stubs.Set(nova.db.api, - 'instance_type_extra_specs_update_or_create', - return_create_flavor_extra_specs) - req = webob.Request.blank('/flavors/1/os-extra_specs') - req.method = 'POST' - req.body = '{"extra_specs": {"key1": "value1"}}' - req.headers["content-type"] = "application/json" - res = req.get_response(self.mware) - res_dict = json.loads(res.body) - self.assertEqual(200, res.status_int) - self.assertEqual('application/json', res.headers['Content-Type']) - self.assertEqual('value1', res_dict['extra_specs']['key1']) - - def test_create_empty_body(self): - self.stubs.Set(nova.db.api, - 'instance_type_extra_specs_update_or_create', - return_create_flavor_extra_specs) - req = webob.Request.blank('/flavors/1/os-extra_specs') - req.method = 'POST' - req.headers["content-type"] = "application/json" - res = req.get_response(self.mware) - self.assertEqual(400, res.status_int) - - def test_update_item(self): - self.stubs.Set(nova.db.api, - 'instance_type_extra_specs_update_or_create', - return_create_flavor_extra_specs) - req = webob.Request.blank('/flavors/1/os-extra_specs/key1') - req.method = 'PUT' - req.body = '{"key1": "value1"}' - req.headers["content-type"] = "application/json" - res = req.get_response(self.mware) - self.assertEqual(200, res.status_int) - self.assertEqual('application/json', res.headers['Content-Type']) - res_dict = json.loads(res.body) - self.assertEqual('value1', res_dict['key1']) - - def test_update_item_empty_body(self): - self.stubs.Set(nova.db.api, - 'instance_type_extra_specs_update_or_create', - return_create_flavor_extra_specs) - req = webob.Request.blank('/flavors/1/os-extra_specs/key1') - req.method = 'PUT' - req.headers["content-type"] = "application/json" - res = req.get_response(self.mware) - self.assertEqual(400, res.status_int) - - def test_update_item_too_many_keys(self): - self.stubs.Set(nova.db.api, - 'instance_type_extra_specs_update_or_create', - return_create_flavor_extra_specs) - req = webob.Request.blank('/flavors/1/os-extra_specs/key1') - req.method = 'PUT' - req.body = '{"key1": "value1", "key2": "value2"}' - req.headers["content-type"] = "application/json" - res = req.get_response(self.mware) - self.assertEqual(400, res.status_int) - - def test_update_item_body_uri_mismatch(self): - self.stubs.Set(nova.db.api, - 'instance_type_extra_specs_update_or_create', - return_create_flavor_extra_specs) - req = webob.Request.blank('/flavors/1/os-extra_specs/bad') - req.method = 'PUT' - req.body = '{"key1": "value1"}' - req.headers["content-type"] = "application/json" - res = req.get_response(self.mware) - self.assertEqual(400, res.status_int) diff --git a/nova/tests/api/openstack/fakes.py b/nova/tests/api/openstack/fakes.py index 26b1de818..28969d5f8 100644 --- a/nova/tests/api/openstack/fakes.py +++ b/nova/tests/api/openstack/fakes.py @@ -29,6 +29,7 @@ from glance.common import exception as glance_exc from nova import context from nova import exception as exc from nova import utils +from nova import wsgi import nova.api.openstack.auth from nova.api import openstack from nova.api.openstack import auth @@ -40,14 +41,13 @@ import nova.image.fake from nova.image import glance from nova.image import service from nova.tests import fake_flags -from nova.wsgi import Router class Context(object): pass -class FakeRouter(Router): +class FakeRouter(wsgi.Router): def __init__(self): pass @@ -68,21 +68,30 @@ def fake_auth_init(self, application): @webob.dec.wsgify def fake_wsgi(self, req): - req.environ['nova.context'] = context.RequestContext(1, 1) return self.application -def wsgi_app(inner_app10=None, inner_app11=None): +def wsgi_app(inner_app10=None, inner_app11=None, fake_auth=True): if not inner_app10: inner_app10 = openstack.APIRouterV10() if not inner_app11: inner_app11 = openstack.APIRouterV11() - mapper = urlmap.URLMap() - api10 = openstack.FaultWrapper(auth.AuthMiddleware( + + if fake_auth: + ctxt = context.RequestContext('fake', 'fake') + api10 = openstack.FaultWrapper(wsgi.InjectContext(ctxt, + limits.RateLimitingMiddleware(inner_app10))) + api11 = openstack.FaultWrapper(wsgi.InjectContext(ctxt, + limits.RateLimitingMiddleware( + extensions.ExtensionMiddleware(inner_app11)))) + else: + api10 = openstack.FaultWrapper(auth.AuthMiddleware( limits.RateLimitingMiddleware(inner_app10))) - api11 = openstack.FaultWrapper(auth.AuthMiddleware( + api11 = openstack.FaultWrapper(auth.AuthMiddleware( limits.RateLimitingMiddleware( extensions.ExtensionMiddleware(inner_app11)))) + Auth = auth + mapper = urlmap.URLMap() mapper['/v1.0'] = api10 mapper['/v1.1'] = api11 mapper['/'] = openstack.FaultWrapper(versions.Versions()) @@ -359,17 +368,18 @@ class FakeAuthManager(object): if admin is not None: user.admin = admin - def is_admin(self, user): + def is_admin(self, user_id): + user = self.get_user(user_id) return user.admin - def is_project_member(self, user, project): + def is_project_member(self, user_id, project): if not isinstance(project, Project): try: project = self.get_project(project) except exc.NotFound: raise webob.exc.HTTPUnauthorized() - return ((user.id in project.member_ids) or - (user.id == project.project_manager_id)) + return ((user_id in project.member_ids) or + (user_id == project.project_manager_id)) def create_project(self, name, manager_user, description=None, member_users=None): @@ -396,13 +406,13 @@ class FakeAuthManager(object): else: raise exc.NotFound - def get_projects(self, user=None): - if not user: + def get_projects(self, user_id=None): + if not user_id: return FakeAuthManager.projects.values() else: return [p for p in FakeAuthManager.projects.values() - if (user.id in p.member_ids) or - (user.id == p.project_manager_id)] + if (user_id in p.member_ids) or + (user_id == p.project_manager_id)] class FakeRateLimiter(object): diff --git a/nova/tests/api/openstack/test_auth.py b/nova/tests/api/openstack/test_auth.py index 25fd2e8c5..1b1d44368 100644 --- a/nova/tests/api/openstack/test_auth.py +++ b/nova/tests/api/openstack/test_auth.py @@ -34,7 +34,6 @@ class Test(test.TestCase): def setUp(self): super(Test, self).setUp() - self.stubs = stubout.StubOutForTesting() self.stubs.Set(nova.api.openstack.auth.AuthMiddleware, '__init__', fakes.fake_auth_init) self.stubs.Set(context, 'RequestContext', fakes.FakeRequestContext) @@ -56,7 +55,7 @@ class Test(test.TestCase): req = webob.Request.blank('/v1.0/') req.headers['X-Auth-User'] = 'user1' req.headers['X-Auth-Key'] = 'user1_key' - result = req.get_response(fakes.wsgi_app()) + result = req.get_response(fakes.wsgi_app(fake_auth=False)) self.assertEqual(result.status, '204 No Content') self.assertEqual(len(result.headers['X-Auth-Token']), 40) self.assertEqual(result.headers['X-CDN-Management-Url'], @@ -72,7 +71,7 @@ class Test(test.TestCase): req = webob.Request.blank('/v1.0/', {'HTTP_HOST': 'foo'}) req.headers['X-Auth-User'] = 'user1' req.headers['X-Auth-Key'] = 'user1_key' - result = req.get_response(fakes.wsgi_app()) + result = req.get_response(fakes.wsgi_app(fake_auth=False)) self.assertEqual(result.status, '204 No Content') self.assertEqual(len(result.headers['X-Auth-Token']), 40) self.assertEqual(result.headers['X-Server-Management-Url'], @@ -85,7 +84,7 @@ class Test(test.TestCase): self.stubs.Set(nova.api.openstack, 'APIRouterV10', fakes.FakeRouter) req = webob.Request.blank('/v1.0/fake') req.headers['X-Auth-Token'] = token - result = req.get_response(fakes.wsgi_app()) + result = req.get_response(fakes.wsgi_app(fake_auth=False)) self.assertEqual(result.status, '200 OK') self.assertEqual(result.headers['X-Test-Success'], 'True') @@ -109,7 +108,7 @@ class Test(test.TestCase): req = webob.Request.blank('/v1.0/') req.headers['X-Auth-Token'] = 'token_hash' - result = req.get_response(fakes.wsgi_app()) + result = req.get_response(fakes.wsgi_app(fake_auth=False)) self.assertEqual(result.status, '401 Unauthorized') self.assertEqual(self.destroy_called, True) @@ -123,7 +122,7 @@ class Test(test.TestCase): req = webob.Request.blank('/v1.0/', {'HTTP_HOST': 'foo'}) req.headers['X-Auth-User'] = 'user1' req.headers['X-Auth-Key'] = 'user1_key' - result = req.get_response(fakes.wsgi_app()) + result = req.get_response(fakes.wsgi_app(fake_auth=False)) self.assertEqual(result.status, '204 No Content') token = result.headers['X-Auth-Token'] @@ -131,7 +130,7 @@ class Test(test.TestCase): req = webob.Request.blank('/v1.0/fake') req.headers['X-Auth-Token'] = token req.headers['X-Auth-Project-Id'] = 'user2_project' - result = req.get_response(fakes.wsgi_app()) + result = req.get_response(fakes.wsgi_app(fake_auth=False)) self.assertEqual(result.status, '200 OK') self.assertEqual(result.headers['X-Test-Success'], 'True') @@ -139,7 +138,7 @@ class Test(test.TestCase): req = webob.Request.blank('/v1.0/') req.headers['X-Auth-User'] = 'unknown_user' req.headers['X-Auth-Key'] = 'unknown_user_key' - result = req.get_response(fakes.wsgi_app()) + result = req.get_response(fakes.wsgi_app(fake_auth=False)) self.assertEqual(result.status, '401 Unauthorized') def test_bad_user_good_key(self): @@ -150,18 +149,18 @@ class Test(test.TestCase): req = webob.Request.blank('/v1.0/') req.headers['X-Auth-User'] = 'unknown_user' req.headers['X-Auth-Key'] = 'user1_key' - result = req.get_response(fakes.wsgi_app()) + result = req.get_response(fakes.wsgi_app(fake_auth=False)) self.assertEqual(result.status, '401 Unauthorized') def test_no_user(self): req = webob.Request.blank('/v1.0/') - result = req.get_response(fakes.wsgi_app()) + result = req.get_response(fakes.wsgi_app(fake_auth=False)) self.assertEqual(result.status, '401 Unauthorized') def test_bad_token(self): req = webob.Request.blank('/v1.0/') req.headers['X-Auth-Token'] = 'unknown_token' - result = req.get_response(fakes.wsgi_app()) + result = req.get_response(fakes.wsgi_app(fake_auth=False)) self.assertEqual(result.status, '401 Unauthorized') def test_bad_project(self): @@ -176,7 +175,7 @@ class Test(test.TestCase): req = webob.Request.blank('/v1.0/', {'HTTP_HOST': 'foo'}) req.headers['X-Auth-User'] = 'user1' req.headers['X-Auth-Key'] = 'user1_key' - result = req.get_response(fakes.wsgi_app()) + result = req.get_response(fakes.wsgi_app(fake_auth=False)) self.assertEqual(result.status, '204 No Content') token = result.headers['X-Auth-Token'] @@ -184,7 +183,7 @@ class Test(test.TestCase): req = webob.Request.blank('/v1.0/fake') req.headers['X-Auth-Token'] = token req.headers['X-Auth-Project-Id'] = 'user2_project' - result = req.get_response(fakes.wsgi_app()) + result = req.get_response(fakes.wsgi_app(fake_auth=False)) self.assertEqual(result.status, '401 Unauthorized') def test_not_existing_project(self): @@ -196,7 +195,7 @@ class Test(test.TestCase): req = webob.Request.blank('/v1.0/', {'HTTP_HOST': 'foo'}) req.headers['X-Auth-User'] = 'user1' req.headers['X-Auth-Key'] = 'user1_key' - result = req.get_response(fakes.wsgi_app()) + result = req.get_response(fakes.wsgi_app(fake_auth=False)) self.assertEqual(result.status, '204 No Content') token = result.headers['X-Auth-Token'] @@ -204,7 +203,7 @@ class Test(test.TestCase): req = webob.Request.blank('/v1.0/fake') req.headers['X-Auth-Token'] = token req.headers['X-Auth-Project-Id'] = 'unknown_project' - result = req.get_response(fakes.wsgi_app()) + result = req.get_response(fakes.wsgi_app(fake_auth=False)) self.assertEqual(result.status, '401 Unauthorized') @@ -225,13 +224,13 @@ class TestFunctional(test.TestCase): req = webob.Request.blank('/v1.0/') req.headers['X-Auth-Token'] = 'test_token_hash' - result = req.get_response(fakes.wsgi_app()) + result = req.get_response(fakes.wsgi_app(fake_auth=False)) self.assertEqual(result.status, '401 Unauthorized') def test_token_doesnotexist(self): req = webob.Request.blank('/v1.0/') req.headers['X-Auth-Token'] = 'nonexistant_token_hash' - result = req.get_response(fakes.wsgi_app()) + result = req.get_response(fakes.wsgi_app(fake_auth=False)) self.assertEqual(result.status, '401 Unauthorized') @@ -260,7 +259,7 @@ class TestLimiter(test.TestCase): req = webob.Request.blank('/v1.0/') req.headers['X-Auth-User'] = 'user1' req.headers['X-Auth-Key'] = 'user1_key' - result = req.get_response(fakes.wsgi_app()) + result = req.get_response(fakes.wsgi_app(fake_auth=False)) self.assertEqual(len(result.headers['X-Auth-Token']), 40) token = result.headers['X-Auth-Token'] @@ -268,6 +267,6 @@ class TestLimiter(test.TestCase): req = webob.Request.blank('/v1.0/fake') req.method = 'POST' req.headers['X-Auth-Token'] = token - result = req.get_response(fakes.wsgi_app()) + result = req.get_response(fakes.wsgi_app(fake_auth=False)) self.assertEqual(result.status, '200 OK') self.assertEqual(result.headers['X-Test-Success'], 'True') diff --git a/nova/tests/api/openstack/test_extensions.py b/nova/tests/api/openstack/test_extensions.py index 697c62e5c..0a5603fc3 100644 --- a/nova/tests/api/openstack/test_extensions.py +++ b/nova/tests/api/openstack/test_extensions.py @@ -16,13 +16,12 @@ # under the License. import json -import stubout -import unittest import webob import os.path from nova import context from nova import flags +from nova import test from nova.api import openstack from nova.api.openstack import extensions from nova.api.openstack import flavors @@ -78,7 +77,7 @@ class StubExtensionManager(object): return request_extensions -class ExtensionControllerTest(unittest.TestCase): +class ExtensionControllerTest(test.TestCase): def test_index(self): app = openstack.APIRouterV11() @@ -95,7 +94,7 @@ class ExtensionControllerTest(unittest.TestCase): self.assertEqual(200, response.status_int) -class ResourceExtensionTest(unittest.TestCase): +class ResourceExtensionTest(test.TestCase): def test_no_extension_present(self): manager = StubExtensionManager(None) @@ -133,13 +132,14 @@ class InvalidExtension(object): return "THIRD" -class ExtensionManagerTest(unittest.TestCase): +class ExtensionManagerTest(test.TestCase): response_body = "Try to say this Mr. Knox, sir..." def setUp(self): - FLAGS.osapi_extensions_path = os.path.join(os.path.dirname(__file__), - "extensions") + super(ExtensionManagerTest, self).setUp() + ext_path = os.path.join(os.path.dirname(__file__), "extensions") + self.flags(osapi_extensions_path=ext_path) def test_get_resources(self): app = openstack.APIRouterV11() @@ -158,11 +158,12 @@ class ExtensionManagerTest(unittest.TestCase): self.assertTrue('THIRD' not in ext_mgr.extensions) -class ActionExtensionTest(unittest.TestCase): +class ActionExtensionTest(test.TestCase): def setUp(self): - FLAGS.osapi_extensions_path = os.path.join(os.path.dirname(__file__), - "extensions") + super(ActionExtensionTest, self).setUp() + ext_path = os.path.join(os.path.dirname(__file__), "extensions") + self.flags(osapi_extensions_path=ext_path) def _send_server_action_request(self, url, body): app = openstack.APIRouterV11() @@ -196,20 +197,14 @@ class ActionExtensionTest(unittest.TestCase): self.assertEqual(404, response.status_int) -class RequestExtensionTest(unittest.TestCase): +class RequestExtensionTest(test.TestCase): def setUp(self): super(RequestExtensionTest, self).setUp() - self.stubs = stubout.StubOutForTesting() fakes.FakeAuthManager.reset_fake_data() fakes.FakeAuthDatabase.data = {} - fakes.stub_out_auth(self.stubs) self.context = context.get_admin_context() - def tearDown(self): - self.stubs.UnsetAll() - super(RequestExtensionTest, self).tearDown() - def test_get_resources_with_stub_mgr(self): def _req_handler(req, res): diff --git a/nova/tests/api/openstack/test_flavors_extra_specs.py b/nova/tests/api/openstack/test_flavors_extra_specs.py new file mode 100644 index 000000000..d386958db --- /dev/null +++ b/nova/tests/api/openstack/test_flavors_extra_specs.py @@ -0,0 +1,180 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 University of Southern California +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import json +import stubout +import webob +import os.path + + +from nova import flags +from nova import test +from nova.api import openstack +from nova.api.openstack import extensions +from nova.tests.api.openstack import fakes +import nova.wsgi + +FLAGS = flags.FLAGS + + +def return_create_flavor_extra_specs(context, flavor_id, extra_specs): + return stub_flavor_extra_specs() + + +def return_flavor_extra_specs(context, flavor_id): + return stub_flavor_extra_specs() + + +def return_empty_flavor_extra_specs(context, flavor_id): + return {} + + +def delete_flavor_extra_specs(context, flavor_id, key): + pass + + +def stub_flavor_extra_specs(): + specs = { + "key1": "value1", + "key2": "value2", + "key3": "value3", + "key4": "value4", + "key5": "value5"} + return specs + + +class FlavorsExtraSpecsTest(test.TestCase): + + def setUp(self): + super(FlavorsExtraSpecsTest, self).setUp() + fakes.stub_out_key_pair_funcs(self.stubs) + + def test_index(self): + self.stubs.Set(nova.db.api, 'instance_type_extra_specs_get', + return_flavor_extra_specs) + request = webob.Request.blank('/v1.1/flavors/1/os-extra_specs') + res = request.get_response(fakes.wsgi_app()) + self.assertEqual(200, res.status_int) + res_dict = json.loads(res.body) + self.assertEqual('application/json', res.headers['Content-Type']) + self.assertEqual('value1', res_dict['extra_specs']['key1']) + + def test_index_no_data(self): + self.stubs.Set(nova.db.api, 'instance_type_extra_specs_get', + return_empty_flavor_extra_specs) + req = webob.Request.blank('/v1.1/flavors/1/os-extra_specs') + res = req.get_response(fakes.wsgi_app()) + res_dict = json.loads(res.body) + self.assertEqual(200, res.status_int) + self.assertEqual('application/json', res.headers['Content-Type']) + self.assertEqual(0, len(res_dict['extra_specs'])) + + def test_show(self): + self.stubs.Set(nova.db.api, 'instance_type_extra_specs_get', + return_flavor_extra_specs) + req = webob.Request.blank('/v1.1/flavors/1/os-extra_specs/key5') + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(200, res.status_int) + res_dict = json.loads(res.body) + self.assertEqual('application/json', res.headers['Content-Type']) + self.assertEqual('value5', res_dict['key5']) + + def test_show_spec_not_found(self): + self.stubs.Set(nova.db.api, 'instance_type_extra_specs_get', + return_empty_flavor_extra_specs) + req = webob.Request.blank('/v1.1/flavors/1/os-extra_specs/key6') + res = req.get_response(fakes.wsgi_app()) + res_dict = json.loads(res.body) + self.assertEqual(404, res.status_int) + + def test_delete(self): + self.stubs.Set(nova.db.api, 'instance_type_extra_specs_delete', + delete_flavor_extra_specs) + req = webob.Request.blank('/v1.1/flavors/1/os-extra_specs/key5') + req.method = 'DELETE' + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(200, res.status_int) + + def test_create(self): + self.stubs.Set(nova.db.api, + 'instance_type_extra_specs_update_or_create', + return_create_flavor_extra_specs) + req = webob.Request.blank('/v1.1/flavors/1/os-extra_specs') + req.method = 'POST' + req.body = '{"extra_specs": {"key1": "value1"}}' + req.headers["content-type"] = "application/json" + res = req.get_response(fakes.wsgi_app()) + res_dict = json.loads(res.body) + self.assertEqual(200, res.status_int) + self.assertEqual('application/json', res.headers['Content-Type']) + self.assertEqual('value1', res_dict['extra_specs']['key1']) + + def test_create_empty_body(self): + self.stubs.Set(nova.db.api, + 'instance_type_extra_specs_update_or_create', + return_create_flavor_extra_specs) + req = webob.Request.blank('/v1.1/flavors/1/os-extra_specs') + req.method = 'POST' + req.headers["content-type"] = "application/json" + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(400, res.status_int) + + def test_update_item(self): + self.stubs.Set(nova.db.api, + 'instance_type_extra_specs_update_or_create', + return_create_flavor_extra_specs) + req = webob.Request.blank('/v1.1/flavors/1/os-extra_specs/key1') + req.method = 'PUT' + req.body = '{"key1": "value1"}' + req.headers["content-type"] = "application/json" + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(200, res.status_int) + self.assertEqual('application/json', res.headers['Content-Type']) + res_dict = json.loads(res.body) + self.assertEqual('value1', res_dict['key1']) + + def test_update_item_empty_body(self): + self.stubs.Set(nova.db.api, + 'instance_type_extra_specs_update_or_create', + return_create_flavor_extra_specs) + req = webob.Request.blank('/v1.1/flavors/1/os-extra_specs/key1') + req.method = 'PUT' + req.headers["content-type"] = "application/json" + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(400, res.status_int) + + def test_update_item_too_many_keys(self): + self.stubs.Set(nova.db.api, + 'instance_type_extra_specs_update_or_create', + return_create_flavor_extra_specs) + req = webob.Request.blank('/v1.1/flavors/1/os-extra_specs/key1') + req.method = 'PUT' + req.body = '{"key1": "value1", "key2": "value2"}' + req.headers["content-type"] = "application/json" + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(400, res.status_int) + + def test_update_item_body_uri_mismatch(self): + self.stubs.Set(nova.db.api, + 'instance_type_extra_specs_update_or_create', + return_create_flavor_extra_specs) + req = webob.Request.blank('/v1.1/flavors/1/os-extra_specs/bad') + req.method = 'PUT' + req.body = '{"key1": "value1"}' + req.headers["content-type"] = "application/json" + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(400, res.status_int) diff --git a/nova/tests/api/openstack/test_images.py b/nova/tests/api/openstack/test_images.py index 17f2fb755..3f42bc1db 100644 --- a/nova/tests/api/openstack/test_images.py +++ b/nova/tests/api/openstack/test_images.py @@ -155,7 +155,7 @@ class GlanceImageServiceTest(_BaseImageServiceTests): 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.context = context.RequestContext('fake', 'fake') self.service.delete_all() self.sent_to_glance = {} fakes.stub_out_glance_add_image(self.stubs, self.sent_to_glance) @@ -168,7 +168,7 @@ class GlanceImageServiceTest(_BaseImageServiceTests): """Ensure instance_id is persisted as an image-property""" fixture = {'name': 'test image', 'is_public': False, - 'properties': {'instance_id': '42', 'user_id': '1'}} + 'properties': {'instance_id': '42', 'user_id': 'fake'}} image_id = self.service.create(self.context, fixture)['id'] expected = fixture @@ -178,7 +178,7 @@ class GlanceImageServiceTest(_BaseImageServiceTests): expected = {'id': image_id, 'name': 'test image', 'is_public': False, - 'properties': {'instance_id': '42', 'user_id': '1'}} + 'properties': {'instance_id': '42', 'user_id': 'fake'}} self.assertDictMatch(image_meta, expected) image_metas = self.service.detail(self.context) @@ -331,11 +331,8 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): self.orig_image_service = FLAGS.image_service FLAGS.image_service = 'nova.image.glance.GlanceImageService' self.stubs = stubout.StubOutForTesting() - fakes.FakeAuthManager.reset_fake_data() - fakes.FakeAuthDatabase.data = {} fakes.stub_out_networking(self.stubs) fakes.stub_out_rate_limiting(self.stubs) - fakes.stub_out_auth(self.stubs) fakes.stub_out_key_pair_funcs(self.stubs) self.fixtures = self._make_image_fixtures() fakes.stub_out_glance(self.stubs, initial_fixtures=self.fixtures) @@ -352,7 +349,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): """Determine if this fixture is applicable for given user id.""" is_public = fixture["is_public"] try: - uid = int(fixture["properties"]["user_id"]) + uid = fixture["properties"]["user_id"] except KeyError: uid = None return uid == user_id or is_public @@ -424,7 +421,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): }, "metadata": { "instance_ref": "http://localhost/v1.1/servers/42", - "user_id": "1", + "user_id": "fake", }, "links": [{ "rel": "self", @@ -559,7 +556,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): fixtures = copy.copy(self.fixtures) for image in fixtures: - if not self._applicable_fixture(image, 1): + if not self._applicable_fixture(image, "fake"): fixtures.remove(image) continue @@ -666,7 +663,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): 'name': 'queued snapshot', 'metadata': { u'instance_ref': u'http://localhost/v1.1/servers/42', - u'user_id': u'1', + u'user_id': u'fake', }, 'updated': self.NOW_API_FORMAT, 'created': self.NOW_API_FORMAT, @@ -696,7 +693,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): 'name': 'saving snapshot', 'metadata': { u'instance_ref': u'http://localhost/v1.1/servers/42', - u'user_id': u'1', + u'user_id': u'fake', }, 'updated': self.NOW_API_FORMAT, 'created': self.NOW_API_FORMAT, @@ -727,7 +724,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): 'name': 'active snapshot', 'metadata': { u'instance_ref': u'http://localhost/v1.1/servers/42', - u'user_id': u'1', + u'user_id': u'fake', }, 'updated': self.NOW_API_FORMAT, 'created': self.NOW_API_FORMAT, @@ -757,7 +754,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): 'name': 'killed snapshot', 'metadata': { u'instance_ref': u'http://localhost/v1.1/servers/42', - u'user_id': u'1', + u'user_id': u'fake', }, 'updated': self.NOW_API_FORMAT, 'created': self.NOW_API_FORMAT, @@ -1259,7 +1256,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): # Snapshot for User 1 server_ref = 'http://localhost/v1.1/servers/42' - snapshot_properties = {'instance_ref': server_ref, 'user_id': '1'} + snapshot_properties = {'instance_ref': server_ref, 'user_id': 'fake'} for status in ('queued', 'saving', 'active', 'killed'): add_fixture(id=image_id, name='%s snapshot' % status, is_public=False, status=status, @@ -1267,7 +1264,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): image_id += 1 # Snapshot for User 2 - other_snapshot_properties = {'instance_id': '43', 'user_id': '2'} + other_snapshot_properties = {'instance_id': '43', 'user_id': 'other'} add_fixture(id=image_id, name='someone elses snapshot', is_public=False, status='active', properties=other_snapshot_properties) diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 3fc38b73c..e0456781a 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -96,8 +96,8 @@ def return_server_with_power_state(power_state): return _return_server -def return_servers(context, user_id=1): - return [stub_instance(i, user_id) for i in xrange(5)] +def return_servers(context, *args, **kwargs): + return [stub_instance(i, 'fake', 'fake') for i in xrange(5)] def return_servers_by_reservation(context, reservation_id=""): @@ -140,9 +140,9 @@ def instance_addresses(context, instance_id): return None -def stub_instance(id, user_id=1, private_address=None, public_addresses=None, - host=None, power_state=0, reservation_id="", - uuid=FAKE_UUID, interfaces=None): +def stub_instance(id, user_id='fake', project_id='fake', private_address=None, + public_addresses=None, host=None, power_state=0, + reservation_id="", uuid=FAKE_UUID, interfaces=None): metadata = [] metadata.append(InstanceMetadata(key='seq', value=id)) @@ -166,7 +166,7 @@ def stub_instance(id, user_id=1, private_address=None, public_addresses=None, "id": int(id), "admin_pass": "", "user_id": user_id, - "project_id": "", + "project_id": project_id, "image_ref": "10", "kernel_id": "", "ramdisk_id": "", @@ -225,11 +225,9 @@ class ServersTest(test.TestCase): def setUp(self): super(ServersTest, self).setUp() self.stubs = stubout.StubOutForTesting() - fakes.FakeAuthManager.reset_fake_data() fakes.FakeAuthDatabase.data = {} fakes.stub_out_networking(self.stubs) fakes.stub_out_rate_limiting(self.stubs) - fakes.stub_out_auth(self.stubs) fakes.stub_out_key_pair_funcs(self.stubs) fakes.stub_out_image_service(self.stubs) self.stubs.Set(utils, 'gen_uuid', fake_gen_uuid) @@ -237,7 +235,7 @@ class ServersTest(test.TestCase): self.stubs.Set(nova.db.api, 'instance_get', return_server_by_id) self.stubs.Set(nova.db, 'instance_get_by_uuid', return_server_by_uuid) - self.stubs.Set(nova.db.api, 'instance_get_all_by_user', + self.stubs.Set(nova.db.api, 'instance_get_all_by_project', return_servers) self.stubs.Set(nova.db.api, 'instance_add_security_group', return_security_group) @@ -636,6 +634,7 @@ class ServersTest(test.TestCase): res = req.get_response(fakes.wsgi_app()) res_dict = json.loads(res.body) + self.assertEqual(len(res_dict['servers']), 5) i = 0 for s in res_dict['servers']: self.assertEqual(s['id'], i) @@ -699,23 +698,24 @@ class ServersTest(test.TestCase): res = req.get_response(fakes.wsgi_app()) res_dict = json.loads(res.body) + self.assertEqual(len(res_dict['servers']), 5) for i, s in enumerate(res_dict['servers']): self.assertEqual(s['id'], i) self.assertEqual(s['name'], 'server%d' % i) self.assertEqual(s.get('imageId', None), None) expected_links = [ - { - "rel": "self", - "href": "http://localhost/v1.1/servers/%d" % (i,), - }, - { - "rel": "bookmark", - "href": "http://localhost/servers/%d" % (i,), - }, - ] + { + "rel": "self", + "href": "http://localhost/v1.1/servers/%d" % (i,), + }, + { + "rel": "bookmark", + "href": "http://localhost/servers/%d" % (i,), + }, + ] - self.assertEqual(s['links'], expected_links) + self.assertEqual(s['links'], expected_links) def test_get_servers_with_limit(self): req = webob.Request.blank('/v1.0/servers?limit=3') @@ -1282,10 +1282,10 @@ class ServersTest(test.TestCase): instances - 2 on one host and 3 on another. ''' - def return_servers_with_host(context, user_id=1): - return [stub_instance(i, 1, None, None, i % 2) for i in xrange(5)] + def return_servers_with_host(context, *args, **kwargs): + return [stub_instance(i, 'fake', 'fake', None, None, i % 2) for i in xrange(5)] - self.stubs.Set(nova.db.api, 'instance_get_all_by_user', + self.stubs.Set(nova.db.api, 'instance_get_all_by_project', return_servers_with_host) req = webob.Request.blank('/v1.0/servers/detail') @@ -2161,7 +2161,6 @@ class TestServerInstanceCreation(test.TestCase): def setUp(self): super(TestServerInstanceCreation, self).setUp() self.stubs = stubout.StubOutForTesting() - fakes.FakeAuthManager.auth_data = {} fakes.FakeAuthDatabase.data = {} fakes.stub_out_auth(self.stubs) fakes.stub_out_image_service(self.stubs) diff --git a/nova/tests/test_access.py b/nova/tests/test_access.py index 6069c5d71..39558b1cf 100644 --- a/nova/tests/test_access.py +++ b/nova/tests/test_access.py @@ -41,7 +41,7 @@ class FakeApiRequest(object): class AccessTestCase(test.TestCase): def _env_for(self, ctxt, action): env = {} - env['ec2.context'] = ctxt + env['nova.context'] = ctxt env['ec2.request'] = FakeApiRequest(action) return env diff --git a/nova/tests/test_api.py b/nova/tests/test_api.py index 978e43abd..292f9d668 100644 --- a/nova/tests/test_api.py +++ b/nova/tests/test_api.py @@ -30,6 +30,7 @@ import webob from nova import context from nova import exception from nova import test +from nova import wsgi from nova.api import ec2 from nova.api.ec2 import apirequest from nova.api.ec2 import cloud @@ -195,7 +196,7 @@ class ApiEc2TestCase(test.TestCase): # NOTE(vish): skipping the Authorizer roles = ['sysadmin', 'netadmin'] ctxt = context.RequestContext('fake', 'fake', roles=roles) - self.app = ec2.InjectContext(ctxt, + self.app = wsgi.InjectContext(ctxt, ec2.Requestify(ec2.Authorizer(ec2.Executor()), 'nova.api.ec2.cloud.CloudController')) diff --git a/nova/wsgi.py b/nova/wsgi.py index eae3afcb4..c8ddb97d7 100644 --- a/nova/wsgi.py +++ b/nova/wsgi.py @@ -274,6 +274,18 @@ class Middleware(Application): return self.process_response(response) +class InjectContext(Middleware): + """Add a 'nova.context' to WSGI environ.""" + def __init__(self, context, *args, **kwargs): + self.context = context + super(InjectContext, self).__init__(*args, **kwargs) + + @webob.dec.wsgify(RequestClass=Request) + def __call__(self, req): + req.environ['nova.context'] = self.context + return self.application + + class Debug(Middleware): """Helper class for debugging a WSGI application. -- cgit From f9fb313ca2acaf2fd491d5b695381846969b132c Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Fri, 22 Jul 2011 19:54:11 +0000 Subject: fix extensions tests --- nova/tests/api/openstack/test_extensions.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) (limited to 'nova') diff --git a/nova/tests/api/openstack/test_extensions.py b/nova/tests/api/openstack/test_extensions.py index 0a5603fc3..0e709d3af 100644 --- a/nova/tests/api/openstack/test_extensions.py +++ b/nova/tests/api/openstack/test_extensions.py @@ -79,6 +79,11 @@ class StubExtensionManager(object): class ExtensionControllerTest(test.TestCase): + def setUp(self): + super(ExtensionControllerTest, self).setUp() + ext_path = os.path.join(os.path.dirname(__file__), "extensions") + self.flags(osapi_extensions_path=ext_path) + def test_index(self): app = openstack.APIRouterV11() ext_midware = extensions.ExtensionMiddleware(app) @@ -96,6 +101,11 @@ class ExtensionControllerTest(test.TestCase): class ResourceExtensionTest(test.TestCase): + def setUp(self): + super(ResourceExtensionTest, self).setUp() + ext_path = os.path.join(os.path.dirname(__file__), "extensions") + self.flags(osapi_extensions_path=ext_path) + def test_no_extension_present(self): manager = StubExtensionManager(None) app = openstack.APIRouterV11() @@ -201,9 +211,8 @@ class RequestExtensionTest(test.TestCase): def setUp(self): super(RequestExtensionTest, self).setUp() - fakes.FakeAuthManager.reset_fake_data() - fakes.FakeAuthDatabase.data = {} - self.context = context.get_admin_context() + ext_path = os.path.join(os.path.dirname(__file__), "extensions") + self.flags(osapi_extensions_path=ext_path) def test_get_resources_with_stub_mgr(self): -- cgit From 0f8eee7ff32a91c866742939b1f551f3610f1276 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Fri, 22 Jul 2011 20:20:31 +0000 Subject: fix auth tests --- nova/api/openstack/auth.py | 2 +- nova/auth/manager.py | 2 +- nova/tests/test_auth.py | 19 +++++++++---------- 3 files changed, 11 insertions(+), 12 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/auth.py b/nova/api/openstack/auth.py index 9caa14a4e..d42abe1f8 100644 --- a/nova/api/openstack/auth.py +++ b/nova/api/openstack/auth.py @@ -62,7 +62,7 @@ class AuthMiddleware(wsgi.Middleware): # while osapi clients don't use this header projects = self.auth.get_projects(user_id) if projects: - project_id = projects[0] + project_id = projects[0].id else: return faults.Fault(webob.exc.HTTPUnauthorized()) diff --git a/nova/auth/manager.py b/nova/auth/manager.py index b6131fb7f..06af7e781 100644 --- a/nova/auth/manager.py +++ b/nova/auth/manager.py @@ -785,7 +785,7 @@ class AuthManager(object): return read_buffer def get_environment_rc(self, user, project=None, use_dmz=True): - """Get credential zip for user in project""" + """Get environment rc for user in project""" if not isinstance(user, User): user = self.get_user(user) if project is None: diff --git a/nova/tests/test_auth.py b/nova/tests/test_auth.py index 71e0d17c9..7c0f783bb 100644 --- a/nova/tests/test_auth.py +++ b/nova/tests/test_auth.py @@ -102,7 +102,7 @@ class _AuthManagerBaseTestCase(test.TestCase): self.assertEqual('classified', u.secret) self.assertEqual('private-party', u.access) - def test_004_signature_is_valid(self): + def test_signature_is_valid(self): with user_generator(self.manager, name='admin', secret='admin', access='admin'): with project_generator(self.manager, name="admin", @@ -141,15 +141,14 @@ class _AuthManagerBaseTestCase(test.TestCase): '127.0.0.1', '/services/Cloud')) - def test_005_can_get_credentials(self): - return - credentials = self.manager.get_user('test1').get_credentials() - self.assertEqual(credentials, - 'export EC2_ACCESS_KEY="access"\n' + - 'export EC2_SECRET_KEY="secret"\n' + - 'export EC2_URL="http://127.0.0.1:8773/services/Cloud"\n' + - 'export S3_URL="http://127.0.0.1:3333/"\n' + - 'export EC2_USER_ID="test1"\n') + def test_can_get_credentials(self): + st = {'access': 'access', 'secret': 'secret'} + with user_and_project_generator(self.manager, user_state=st) as (u, p): + credentials = self.manager.get_environment_rc(u, p) + LOG.debug(credentials) + self.assertTrue('export EC2_ACCESS_KEY="access:testproj"\n' + in credentials) + self.assertTrue('export EC2_SECRET_KEY="secret"\n' in credentials) def test_can_list_users(self): with user_generator(self.manager): -- cgit From e8defa6bdd5af85486d0d3acce8956670ca16882 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Fri, 22 Jul 2011 20:41:46 +0000 Subject: fix test_access --- nova/api/ec2/__init__.py | 7 ++++++- nova/auth/manager.py | 9 +++++++++ nova/tests/test_access.py | 19 +++++++++---------- 3 files changed, 24 insertions(+), 11 deletions(-) (limited to 'nova') diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py index 0a743075c..1ea26fdeb 100644 --- a/nova/api/ec2/__init__.py +++ b/nova/api/ec2/__init__.py @@ -156,8 +156,9 @@ class Authenticate(wsgi.Middleware): auth_params.pop('Signature') # Authenticate the request. + authman = manager.AuthManager() try: - (user, project) = manager.AuthManager().authenticate( + (user, project) = authman.authenticate( access, signature, auth_params, @@ -173,9 +174,12 @@ class Authenticate(wsgi.Middleware): remote_address = req.remote_addr if FLAGS.use_forwarded_for: remote_address = req.headers.get('X-Forwarded-For', remote_address) + roles = authman.get_active_roles(user, project) + LOG.warn(roles) ctxt = context.RequestContext(user_id=user.id, project_id=project.id, is_admin=user.is_admin(), + roles=roles, remote_address=remote_address) req.environ['nova.context'] = ctxt uname = user.name @@ -295,6 +299,7 @@ class Authorizer(wsgi.Middleware): def _matches_any_role(self, context, roles): """Return True if any role in roles is allowed in context.""" + LOG.info(context.roles) if context.is_admin: return True if 'all' in roles: diff --git a/nova/auth/manager.py b/nova/auth/manager.py index 06af7e781..7f99d9016 100644 --- a/nova/auth/manager.py +++ b/nova/auth/manager.py @@ -518,6 +518,15 @@ class AuthManager(object): return drv.get_user_roles(User.safe_id(user), Project.safe_id(project)) + def get_active_roles(self, user, project=None): + """Get all active roles for context""" + if project: + roles = FLAGS.allowed_roles + roles.append('projectmanager') + else: + roles = FLAGS.global_roles + return [role for role in roles if self.has_role(user, role, project)] + def get_project(self, pid): """Get project object by id""" with self.driver() as drv: diff --git a/nova/tests/test_access.py b/nova/tests/test_access.py index 39558b1cf..3b54fc249 100644 --- a/nova/tests/test_access.py +++ b/nova/tests/test_access.py @@ -16,7 +16,6 @@ # License for the specific language governing permissions and limitations # under the License. -import unittest import webob from nova import context @@ -93,7 +92,11 @@ class AccessTestCase(test.TestCase): super(AccessTestCase, self).tearDown() def response_status(self, user, methodName): - ctxt = context.RequestContext(user.id, self.project.id) + roles = manager.AuthManager().get_active_roles(user, self.project) + ctxt = context.RequestContext(user.id, + self.project.id, + is_admin=user.is_admin(), + roles=roles) environ = self._env_for(ctxt, methodName) req = webob.Request.blank('/', environ) resp = req.get_response(self.mw) @@ -105,30 +108,26 @@ class AccessTestCase(test.TestCase): def shouldDeny(self, user, methodName): self.assertEqual(401, self.response_status(user, methodName)) - def test_001_allow_all(self): + def test_allow_all(self): users = [self.testadmin, self.testpmsys, self.testnet, self.testsys] for user in users: self.shouldAllow(user, '_allow_all') - def test_002_allow_none(self): + def test_allow_none(self): self.shouldAllow(self.testadmin, '_allow_none') users = [self.testpmsys, self.testnet, self.testsys] for user in users: self.shouldDeny(user, '_allow_none') - def test_003_allow_project_manager(self): + def test_allow_project_manager(self): for user in [self.testadmin, self.testpmsys]: self.shouldAllow(user, '_allow_project_manager') for user in [self.testnet, self.testsys]: self.shouldDeny(user, '_allow_project_manager') - def test_004_allow_sys_and_net(self): + def test_allow_sys_and_net(self): for user in [self.testadmin, self.testnet, self.testsys]: self.shouldAllow(user, '_allow_sys_and_net') # denied because it doesn't have the per project sysadmin for user in [self.testpmsys]: self.shouldDeny(user, '_allow_sys_and_net') - -if __name__ == "__main__": - # TODO: Implement use_fake as an option - unittest.main() -- cgit From ccb5119280d341a2ea1b3e8352acbf32b7f243af Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Fri, 22 Jul 2011 21:36:41 +0000 Subject: clean up fake auth manager in other places --- nova/api/ec2/__init__.py | 2 -- nova/auth/manager.py | 3 +- .../api/openstack/contrib/test_floating_ips.py | 5 --- .../api/openstack/contrib/test_multinic_xs.py | 8 ----- nova/tests/api/openstack/test_adminapi.py | 17 ++-------- nova/tests/api/openstack/test_flavors.py | 5 --- nova/tests/api/openstack/test_image_metadata.py | 14 +------- nova/tests/api/openstack/test_server_metadata.py | 14 ++------ nova/tests/api/openstack/test_servers.py | 38 ++++++---------------- nova/tests/api/openstack/test_shared_ip_groups.py | 13 -------- nova/tests/api/openstack/test_zones.py | 24 +++----------- nova/tests/test_objectstore.py | 3 +- 12 files changed, 21 insertions(+), 125 deletions(-) (limited to 'nova') diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py index 1ea26fdeb..af232edda 100644 --- a/nova/api/ec2/__init__.py +++ b/nova/api/ec2/__init__.py @@ -175,7 +175,6 @@ class Authenticate(wsgi.Middleware): if FLAGS.use_forwarded_for: remote_address = req.headers.get('X-Forwarded-For', remote_address) roles = authman.get_active_roles(user, project) - LOG.warn(roles) ctxt = context.RequestContext(user_id=user.id, project_id=project.id, is_admin=user.is_admin(), @@ -299,7 +298,6 @@ class Authorizer(wsgi.Middleware): def _matches_any_role(self, context, roles): """Return True if any role in roles is allowed in context.""" - LOG.info(context.roles) if context.is_admin: return True if 'all' in roles: diff --git a/nova/auth/manager.py b/nova/auth/manager.py index 7f99d9016..5118abba2 100644 --- a/nova/auth/manager.py +++ b/nova/auth/manager.py @@ -521,8 +521,7 @@ class AuthManager(object): def get_active_roles(self, user, project=None): """Get all active roles for context""" if project: - roles = FLAGS.allowed_roles - roles.append('projectmanager') + roles = FLAGS.allowed_roles + ['projectmanager'] else: roles = FLAGS.global_roles return [role for role in roles if self.has_role(user, role, project)] diff --git a/nova/tests/api/openstack/contrib/test_floating_ips.py b/nova/tests/api/openstack/contrib/test_floating_ips.py index de006d088..50ad7de08 100644 --- a/nova/tests/api/openstack/contrib/test_floating_ips.py +++ b/nova/tests/api/openstack/contrib/test_floating_ips.py @@ -74,12 +74,8 @@ class FloatingIpTest(test.TestCase): def setUp(self): super(FloatingIpTest, self).setUp() self.controller = FloatingIPController() - self.stubs = stubout.StubOutForTesting() - fakes.FakeAuthManager.reset_fake_data() - fakes.FakeAuthDatabase.data = {} fakes.stub_out_networking(self.stubs) fakes.stub_out_rate_limiting(self.stubs) - fakes.stub_out_auth(self.stubs) self.stubs.Set(network.api.API, "get_floating_ip", network_api_get_floating_ip) self.stubs.Set(network.api.API, "list_floating_ips", @@ -96,7 +92,6 @@ class FloatingIpTest(test.TestCase): self._create_floating_ip() def tearDown(self): - self.stubs.UnsetAll() self._delete_floating_ip() super(FloatingIpTest, self).tearDown() diff --git a/nova/tests/api/openstack/contrib/test_multinic_xs.py b/nova/tests/api/openstack/contrib/test_multinic_xs.py index b0a9f7676..ac28f6be6 100644 --- a/nova/tests/api/openstack/contrib/test_multinic_xs.py +++ b/nova/tests/api/openstack/contrib/test_multinic_xs.py @@ -42,22 +42,14 @@ def compute_api_remove_fixed_ip(self, context, instance_id, address): class FixedIpTest(test.TestCase): def setUp(self): super(FixedIpTest, self).setUp() - self.stubs = stubout.StubOutForTesting() - fakes.FakeAuthManager.reset_fake_data() - fakes.FakeAuthDatabase.data = {} fakes.stub_out_networking(self.stubs) fakes.stub_out_rate_limiting(self.stubs) - fakes.stub_out_auth(self.stubs) self.stubs.Set(compute.api.API, "add_fixed_ip", compute_api_add_fixed_ip) self.stubs.Set(compute.api.API, "remove_fixed_ip", compute_api_remove_fixed_ip) self.context = context.get_admin_context() - def tearDown(self): - self.stubs.UnsetAll() - super(FixedIpTest, self).tearDown() - def test_add_fixed_ip(self): global last_add_fixed_ip last_add_fixed_ip = (None, None) diff --git a/nova/tests/api/openstack/test_adminapi.py b/nova/tests/api/openstack/test_adminapi.py index e87255b18..b83de40cf 100644 --- a/nova/tests/api/openstack/test_adminapi.py +++ b/nova/tests/api/openstack/test_adminapi.py @@ -16,14 +16,10 @@ # under the License. -import stubout import webob -from paste import urlmap from nova import flags from nova import test -from nova.api import openstack -from nova.api.openstack import auth from nova.tests.api.openstack import fakes FLAGS = flags.FLAGS @@ -33,21 +29,12 @@ class AdminAPITest(test.TestCase): def setUp(self): super(AdminAPITest, self).setUp() - self.stubs = stubout.StubOutForTesting() - fakes.FakeAuthManager.reset_fake_data() - fakes.FakeAuthDatabase.data = {} fakes.stub_out_networking(self.stubs) fakes.stub_out_rate_limiting(self.stubs) - fakes.stub_out_auth(self.stubs) self.allow_admin = FLAGS.allow_admin_api - def tearDown(self): - self.stubs.UnsetAll() - FLAGS.allow_admin_api = self.allow_admin - super(AdminAPITest, self).tearDown() - def test_admin_enabled(self): - FLAGS.allow_admin_api = True + self.flags(allow_admin_api=True) # We should still be able to access public operations. req = webob.Request.blank('/v1.0/flavors') res = req.get_response(fakes.wsgi_app()) @@ -55,7 +42,7 @@ class AdminAPITest(test.TestCase): # TODO: Confirm admin operations are available. def test_admin_disabled(self): - FLAGS.allow_admin_api = False + self.flags(allow_admin_api=False) # We should still be able to access public operations. req = webob.Request.blank('/v1.0/flavors') res = req.get_response(fakes.wsgi_app()) diff --git a/nova/tests/api/openstack/test_flavors.py b/nova/tests/api/openstack/test_flavors.py index 4ac35b26b..d0fe72001 100644 --- a/nova/tests/api/openstack/test_flavors.py +++ b/nova/tests/api/openstack/test_flavors.py @@ -16,7 +16,6 @@ # under the License. import json -import stubout import webob import xml.dom.minidom as minidom @@ -56,12 +55,8 @@ def return_instance_type_not_found(context, flavor_id): class FlavorsTest(test.TestCase): def setUp(self): super(FlavorsTest, self).setUp() - self.stubs = stubout.StubOutForTesting() - fakes.FakeAuthManager.reset_fake_data() - fakes.FakeAuthDatabase.data = {} fakes.stub_out_networking(self.stubs) fakes.stub_out_rate_limiting(self.stubs) - fakes.stub_out_auth(self.stubs) self.stubs.Set(nova.db.api, "instance_type_get_all", return_instance_types) self.stubs.Set(nova.db.api, "instance_type_get_by_flavor_id", diff --git a/nova/tests/api/openstack/test_image_metadata.py b/nova/tests/api/openstack/test_image_metadata.py index d9fb61e2a..0e9d9fe55 100644 --- a/nova/tests/api/openstack/test_image_metadata.py +++ b/nova/tests/api/openstack/test_image_metadata.py @@ -16,8 +16,6 @@ # under the License. import json -import stubout -import unittest import webob import xml.dom.minidom as minidom @@ -85,23 +83,13 @@ class ImageMetaDataTest(test.TestCase): def setUp(self): super(ImageMetaDataTest, self).setUp() - self.stubs = stubout.StubOutForTesting() - self.orig_image_service = FLAGS.image_service - FLAGS.image_service = 'nova.image.glance.GlanceImageService' - fakes.FakeAuthManager.auth_data = {} - fakes.FakeAuthDatabase.data = {} - fakes.stub_out_auth(self.stubs) + self.flags(image_service='nova.image.glance.GlanceImageService') # NOTE(dprince) max out properties/metadata in image 3 for testing img3 = self.IMAGE_FIXTURES[2] for num in range(FLAGS.quota_metadata_items): img3['properties']['key%i' % num] = "blah" fakes.stub_out_glance(self.stubs, self.IMAGE_FIXTURES) - def tearDown(self): - self.stubs.UnsetAll() - FLAGS.image_service = self.orig_image_service - super(ImageMetaDataTest, self).tearDown() - def test_index(self): req = webob.Request.blank('/v1.1/images/1/meta') req.environ['api.version'] = '1.1' diff --git a/nova/tests/api/openstack/test_server_metadata.py b/nova/tests/api/openstack/test_server_metadata.py index 0431e68d2..f90485067 100644 --- a/nova/tests/api/openstack/test_server_metadata.py +++ b/nova/tests/api/openstack/test_server_metadata.py @@ -16,14 +16,12 @@ # under the License. import json -import stubout -import unittest import webob from nova import exception from nova import flags -from nova.api import openstack +from nova import test from nova.tests.api.openstack import fakes import nova.wsgi @@ -76,21 +74,13 @@ def return_server_nonexistant(context, server_id): raise exception.InstanceNotFound() -class ServerMetaDataTest(unittest.TestCase): +class ServerMetaDataTest(test.TestCase): def setUp(self): super(ServerMetaDataTest, self).setUp() - self.stubs = stubout.StubOutForTesting() - fakes.FakeAuthManager.auth_data = {} - fakes.FakeAuthDatabase.data = {} - fakes.stub_out_auth(self.stubs) fakes.stub_out_key_pair_funcs(self.stubs) self.stubs.Set(nova.db.api, 'instance_get', return_server) - def tearDown(self): - self.stubs.UnsetAll() - super(ServerMetaDataTest, self).tearDown() - def test_index(self): self.stubs.Set(nova.db.api, 'instance_metadata_get', return_server_metadata) diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index e0456781a..91025fcb9 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -20,7 +20,6 @@ import json import unittest from xml.dom import minidom -import stubout import webob from nova import context @@ -224,8 +223,6 @@ class ServersTest(test.TestCase): def setUp(self): super(ServersTest, self).setUp() - self.stubs = stubout.StubOutForTesting() - fakes.FakeAuthDatabase.data = {} fakes.stub_out_networking(self.stubs) fakes.stub_out_rate_limiting(self.stubs) fakes.stub_out_key_pair_funcs(self.stubs) @@ -250,15 +247,9 @@ 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) - self.allow_admin = FLAGS.allow_admin_api self.webreq = common.webob_factory('/v1.0/servers') - def tearDown(self): - self.stubs.UnsetAll() - FLAGS.allow_admin_api = self.allow_admin - super(ServersTest, self).tearDown() - def test_get_server_by_id(self): req = webob.Request.blank('/v1.0/servers/1') res = req.get_response(fakes.wsgi_app()) @@ -853,7 +844,7 @@ class ServersTest(test.TestCase): def test_create_instance_via_zones(self): """Server generated ReservationID""" self._setup_for_create_instance() - FLAGS.allow_admin_api = True + self.flags(allow_admin_api=True) body = dict(server=dict( name='server_test', imageId=3, flavorId=2, @@ -875,7 +866,7 @@ class ServersTest(test.TestCase): def test_create_instance_via_zones_with_resid(self): """User supplied ReservationID""" self._setup_for_create_instance() - FLAGS.allow_admin_api = True + self.flags(allow_admin_api=True) body = dict(server=dict( name='server_test', imageId=3, flavorId=2, @@ -1305,7 +1296,7 @@ class ServersTest(test.TestCase): self.assertEqual(s['flavorId'], 1) def test_server_pause(self): - FLAGS.allow_admin_api = True + self.flags(allow_admin_api=True) body = dict(server=dict( name='server_test', imageId=2, flavorId=2, metadata={}, personality={})) @@ -1317,7 +1308,7 @@ class ServersTest(test.TestCase): self.assertEqual(res.status_int, 202) def test_server_unpause(self): - FLAGS.allow_admin_api = True + self.flags(allow_admin_api=True) body = dict(server=dict( name='server_test', imageId=2, flavorId=2, metadata={}, personality={})) @@ -1329,7 +1320,7 @@ class ServersTest(test.TestCase): self.assertEqual(res.status_int, 202) def test_server_suspend(self): - FLAGS.allow_admin_api = True + self.flags(allow_admin_api=True) body = dict(server=dict( name='server_test', imageId=2, flavorId=2, metadata={}, personality={})) @@ -1341,7 +1332,7 @@ class ServersTest(test.TestCase): self.assertEqual(res.status_int, 202) def test_server_resume(self): - FLAGS.allow_admin_api = True + self.flags(allow_admin_api=True) body = dict(server=dict( name='server_test', imageId=2, flavorId=2, metadata={}, personality={})) @@ -1353,7 +1344,7 @@ class ServersTest(test.TestCase): self.assertEqual(res.status_int, 202) def test_server_reset_network(self): - FLAGS.allow_admin_api = True + self.flags(allow_admin_api=True) body = dict(server=dict( name='server_test', imageId=2, flavorId=2, metadata={}, personality={})) @@ -1365,7 +1356,7 @@ class ServersTest(test.TestCase): self.assertEqual(res.status_int, 202) def test_server_inject_network_info(self): - FLAGS.allow_admin_api = True + self.flags(allow_admin_api=True) body = dict(server=dict( name='server_test', imageId=2, flavorId=2, metadata={}, personality={})) @@ -1652,7 +1643,7 @@ class ServersTest(test.TestCase): self.assertEqual(self.server_delete_called, True) def test_rescue_accepted(self): - FLAGS.allow_admin_api = True + self.flags(allow_admin_api=True) body = {} self.called = False @@ -1671,7 +1662,7 @@ class ServersTest(test.TestCase): self.assertEqual(res.status_int, 202) def test_rescue_raises_handled(self): - FLAGS.allow_admin_api = True + self.flags(allow_admin_api=True) body = {} def rescue_mock(*args, **kwargs): @@ -2160,17 +2151,8 @@ class TestServerInstanceCreation(test.TestCase): def setUp(self): super(TestServerInstanceCreation, self).setUp() - self.stubs = stubout.StubOutForTesting() - fakes.FakeAuthDatabase.data = {} - fakes.stub_out_auth(self.stubs) fakes.stub_out_image_service(self.stubs) fakes.stub_out_key_pair_funcs(self.stubs) - self.allow_admin = FLAGS.allow_admin_api - - def tearDown(self): - self.stubs.UnsetAll() - FLAGS.allow_admin_api = self.allow_admin - super(TestServerInstanceCreation, self).tearDown() def _setup_mock_compute_api_for_personality(self): diff --git a/nova/tests/api/openstack/test_shared_ip_groups.py b/nova/tests/api/openstack/test_shared_ip_groups.py index c2bd7e45a..36fa1de0f 100644 --- a/nova/tests/api/openstack/test_shared_ip_groups.py +++ b/nova/tests/api/openstack/test_shared_ip_groups.py @@ -15,26 +15,13 @@ # License for the specific language governing permissions and limitations # under the License. -import stubout import webob from nova import test -from nova.api.openstack import shared_ip_groups from nova.tests.api.openstack import fakes class SharedIpGroupsTest(test.TestCase): - def setUp(self): - super(SharedIpGroupsTest, self).setUp() - self.stubs = stubout.StubOutForTesting() - fakes.FakeAuthManager.reset_fake_data() - fakes.FakeAuthDatabase.data = {} - fakes.stub_out_auth(self.stubs) - - def tearDown(self): - self.stubs.UnsetAll() - super(SharedIpGroupsTest, self).tearDown() - def test_get_shared_ip_groups(self): req = webob.Request.blank('/v1.0/shared_ip_groups') res = req.get_response(fakes.wsgi_app()) diff --git a/nova/tests/api/openstack/test_zones.py b/nova/tests/api/openstack/test_zones.py index 6a6e13d93..4a208ea24 100644 --- a/nova/tests/api/openstack/test_zones.py +++ b/nova/tests/api/openstack/test_zones.py @@ -95,31 +95,15 @@ def zone_select(context, specs): class ZonesTest(test.TestCase): def setUp(self): super(ZonesTest, self).setUp() - self.stubs = stubout.StubOutForTesting() - fakes.FakeAuthManager.reset_fake_data() - fakes.FakeAuthDatabase.data = {} + self.flags(allow_admin_api=True) fakes.stub_out_networking(self.stubs) fakes.stub_out_rate_limiting(self.stubs) - fakes.stub_out_auth(self.stubs) - - self.allow_admin = FLAGS.allow_admin_api - FLAGS.allow_admin_api = True self.stubs.Set(nova.db, 'zone_get', zone_get) self.stubs.Set(nova.db, 'zone_update', zone_update) self.stubs.Set(nova.db, 'zone_create', zone_create) self.stubs.Set(nova.db, 'zone_delete', zone_delete) - self.old_zone_name = FLAGS.zone_name - self.old_zone_capabilities = FLAGS.zone_capabilities - - def tearDown(self): - self.stubs.UnsetAll() - FLAGS.allow_admin_api = self.allow_admin - FLAGS.zone_name = self.old_zone_name - FLAGS.zone_capabilities = self.old_zone_capabilities - super(ZonesTest, self).tearDown() - def test_get_zone_list_scheduler(self): self.stubs.Set(api, '_call_scheduler', zone_get_all_scheduler) req = webob.Request.blank('/v1.0/zones') @@ -190,8 +174,8 @@ class ZonesTest(test.TestCase): self.assertFalse('username' in res_dict['zone']) def test_zone_info(self): - FLAGS.zone_name = 'darksecret' - FLAGS.zone_capabilities = ['cap1=a;b', 'cap2=c;d'] + caps = ['cap1=a;b', 'cap2=c;d'] + self.flags(zone_name='darksecret', zone_capabilities=caps) self.stubs.Set(api, '_call_scheduler', zone_capabilities) body = dict(zone=dict(username='zeb', password='sneaky')) @@ -205,7 +189,7 @@ class ZonesTest(test.TestCase): self.assertEqual(res_dict['zone']['cap2'], 'c;d') def test_zone_select(self): - FLAGS.build_plan_encryption_key = 'c286696d887c9aa0611bbb3e2025a45a' + self.flags(build_plan_encryption_key='c286696d887c9aa0611bbb3e2025a45a') self.stubs.Set(api, 'select', zone_select) req = webob.Request.blank('/v1.0/zones/select') diff --git a/nova/tests/test_objectstore.py b/nova/tests/test_objectstore.py index af4ee27cd..0b2dce20e 100644 --- a/nova/tests/test_objectstore.py +++ b/nova/tests/test_objectstore.py @@ -52,8 +52,7 @@ class S3APITestCase(test.TestCase): def setUp(self): """Setup users, projects, and start a test server.""" super(S3APITestCase, self).setUp() - self.flags(auth_driver='nova.auth.ldapdriver.FakeLdapDriver', - buckets_path=os.path.join(OSS_TEMPDIR, 'buckets'), + self.flags(buckets_path=os.path.join(OSS_TEMPDIR, 'buckets'), s3_host='127.0.0.1') shutil.rmtree(FLAGS.buckets_path) -- cgit From 634a195da129fb043184ac1589efd0bdac5df256 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Fri, 22 Jul 2011 21:40:10 +0000 Subject: remove some more stubouts and fakes --- nova/tests/api/openstack/test_accounts.py | 10 +--------- nova/tests/api/openstack/test_auth.py | 4 ---- nova/tests/api/openstack/test_users.py | 9 +-------- 3 files changed, 2 insertions(+), 21 deletions(-) (limited to 'nova') diff --git a/nova/tests/api/openstack/test_accounts.py b/nova/tests/api/openstack/test_accounts.py index 64abcf48c..89dbf5213 100644 --- a/nova/tests/api/openstack/test_accounts.py +++ b/nova/tests/api/openstack/test_accounts.py @@ -16,7 +16,6 @@ import json -import stubout import webob from nova import flags @@ -41,7 +40,7 @@ def fake_admin_check(self, req): class AccountsTest(test.TestCase): def setUp(self): super(AccountsTest, self).setUp() - self.stubs = stubout.StubOutForTesting() + self.flags(allow_admin_api=True) self.stubs.Set(accounts.Controller, '__init__', fake_init) self.stubs.Set(accounts.Controller, '_check_admin', @@ -52,8 +51,6 @@ class AccountsTest(test.TestCase): fakes.stub_out_rate_limiting(self.stubs) fakes.stub_out_auth(self.stubs) - self.allow_admin = FLAGS.allow_admin_api - FLAGS.allow_admin_api = True fakemgr = fakes.FakeAuthManager() joeuser = User('id1', 'guy1', 'acc1', 'secret1', False) superuser = User('id2', 'guy2', 'acc2', 'secret2', True) @@ -62,11 +59,6 @@ class AccountsTest(test.TestCase): fakemgr.create_project('test1', joeuser) fakemgr.create_project('test2', superuser) - def tearDown(self): - self.stubs.UnsetAll() - FLAGS.allow_admin_api = self.allow_admin - super(AccountsTest, self).tearDown() - def test_get_account(self): req = webob.Request.blank('/v1.0/accounts/test1') res = req.get_response(fakes.wsgi_app()) diff --git a/nova/tests/api/openstack/test_auth.py b/nova/tests/api/openstack/test_auth.py index 1b1d44368..306ae1aa0 100644 --- a/nova/tests/api/openstack/test_auth.py +++ b/nova/tests/api/openstack/test_auth.py @@ -17,7 +17,6 @@ import datetime -import stubout import webob import webob.dec @@ -43,7 +42,6 @@ class Test(test.TestCase): fakes.stub_out_networking(self.stubs) def tearDown(self): - self.stubs.UnsetAll() fakes.fake_data_store = {} super(Test, self).tearDown() @@ -237,7 +235,6 @@ class TestFunctional(test.TestCase): class TestLimiter(test.TestCase): def setUp(self): super(TestLimiter, self).setUp() - self.stubs = stubout.StubOutForTesting() self.stubs.Set(nova.api.openstack.auth.AuthMiddleware, '__init__', fakes.fake_auth_init) self.stubs.Set(context, 'RequestContext', fakes.FakeRequestContext) @@ -246,7 +243,6 @@ class TestLimiter(test.TestCase): fakes.stub_out_networking(self.stubs) def tearDown(self): - self.stubs.UnsetAll() fakes.fake_data_store = {} super(TestLimiter, self).tearDown() diff --git a/nova/tests/api/openstack/test_users.py b/nova/tests/api/openstack/test_users.py index effb2f592..705c02f6b 100644 --- a/nova/tests/api/openstack/test_users.py +++ b/nova/tests/api/openstack/test_users.py @@ -15,7 +15,6 @@ import json -import stubout import webob from nova import flags @@ -41,7 +40,7 @@ def fake_admin_check(self, req): class UsersTest(test.TestCase): def setUp(self): super(UsersTest, self).setUp() - self.stubs = stubout.StubOutForTesting() + self.flags(allow_admin_api=True) self.stubs.Set(users.Controller, '__init__', fake_init) self.stubs.Set(users.Controller, '_check_admin', @@ -58,16 +57,10 @@ class UsersTest(test.TestCase): fakes.stub_out_auth(self.stubs) self.allow_admin = FLAGS.allow_admin_api - FLAGS.allow_admin_api = True fakemgr = fakes.FakeAuthManager() fakemgr.add_user(User('id1', 'guy1', 'acc1', 'secret1', False)) fakemgr.add_user(User('id2', 'guy2', 'acc2', 'secret2', True)) - def tearDown(self): - self.stubs.UnsetAll() - FLAGS.allow_admin_api = self.allow_admin - super(UsersTest, self).tearDown() - def test_get_user_list(self): req = webob.Request.blank('/v1.0/users') res = req.get_response(fakes.wsgi_app()) -- cgit From 3b9af8a63abb21aac1ef9ef8dcb801b7a3686ce8 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Fri, 22 Jul 2011 21:52:11 +0000 Subject: pull out auth manager from db --- nova/db/sqlalchemy/models.py | 16 ---------------- 1 file changed, 16 deletions(-) (limited to 'nova') diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index c1150f7ca..14dc09e9f 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -177,14 +177,6 @@ class Instance(BASE, NovaBase): user_id = Column(String(255)) project_id = Column(String(255)) - @property - def user(self): - return auth.manager.AuthManager().get_user(self.user_id) - - @property - def project(self): - return auth.manager.AuthManager().get_project(self.project_id) - image_ref = Column(String(255)) kernel_id = Column(String(255)) ramdisk_id = Column(String(255)) @@ -465,14 +457,6 @@ class SecurityGroup(BASE, NovaBase): 'Instance.deleted == False)', backref='security_groups') - @property - def user(self): - return auth.manager.AuthManager().get_user(self.user_id) - - @property - def project(self): - return auth.manager.AuthManager().get_project(self.project_id) - class SecurityGroupIngressRule(BASE, NovaBase): """Represents a rule in a security group.""" -- cgit From 164afd51017721b9cbaf2880b9dada3d4cd9b42c Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Fri, 22 Jul 2011 22:04:52 +0000 Subject: remove auth manager from instance helper --- nova/api/openstack/create_instance_helper.py | 5 +++-- nova/auth/manager.py | 4 ---- 2 files changed, 3 insertions(+), 6 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index 7249f1261..2034e8ada 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -20,6 +20,7 @@ import webob from webob import exc from xml.dom import minidom +from nova import db from nova import exception from nova import flags from nova import log as logging @@ -29,7 +30,6 @@ from nova import utils from nova.compute import instance_types from nova.api.openstack import wsgi -from nova.auth import manager as auth_manager LOG = logging.getLogger('nova.api.openstack.create_instance_helper') @@ -77,7 +77,8 @@ class CreateInstanceHelper(object): key_name = None key_data = None - key_pairs = auth_manager.AuthManager.get_key_pairs(context) + key_pairs = db.key_pair_get_all_by_user(context.elevated(), + context.user_id) if key_pairs: key_pair = key_pairs[0] key_name = key_pair['name'] diff --git a/nova/auth/manager.py b/nova/auth/manager.py index 5118abba2..6205cfb56 100644 --- a/nova/auth/manager.py +++ b/nova/auth/manager.py @@ -738,10 +738,6 @@ class AuthManager(object): with self.driver() as drv: drv.modify_user(uid, access_key, secret_key, admin) - @staticmethod - def get_key_pairs(context): - return db.key_pair_get_all_by_user(context.elevated(), context.user_id) - def get_credentials(self, user, project=None, use_dmz=True): """Get credential zip for user in project""" if not isinstance(user, User): -- cgit From 4e4bbda2a15df9f2366488d092bc466655a170b9 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Fri, 22 Jul 2011 22:12:22 +0000 Subject: pep cleanup --- nova/context.py | 3 ++- nova/tests/api/openstack/test_servers.py | 3 ++- nova/tests/api/openstack/test_zones.py | 3 ++- nova/tests/test_api.py | 1 - 4 files changed, 6 insertions(+), 4 deletions(-) (limited to 'nova') diff --git a/nova/context.py b/nova/context.py index e7c60142c..db19f136c 100644 --- a/nova/context.py +++ b/nova/context.py @@ -31,7 +31,8 @@ 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): + roles=None, remote_address=None, timestamp=None, + request_id=None): self.user_id = user_id self.project_id = project_id self.roles = roles or [] diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index f05310325..98d4bed52 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -1306,7 +1306,8 @@ class ServersTest(test.TestCase): ''' def return_servers_with_host(context, *args, **kwargs): - return [stub_instance(i, 'fake', 'fake', None, None, i % 2) for i in xrange(5)] + return [stub_instance(i, 'fake', 'fake', None, None, i % 2) + for i in xrange(5)] self.stubs.Set(nova.db.api, 'instance_get_all_by_project', return_servers_with_host) diff --git a/nova/tests/api/openstack/test_zones.py b/nova/tests/api/openstack/test_zones.py index 4a208ea24..3deb844aa 100644 --- a/nova/tests/api/openstack/test_zones.py +++ b/nova/tests/api/openstack/test_zones.py @@ -189,7 +189,8 @@ class ZonesTest(test.TestCase): self.assertEqual(res_dict['zone']['cap2'], 'c;d') def test_zone_select(self): - self.flags(build_plan_encryption_key='c286696d887c9aa0611bbb3e2025a45a') + key = 'c286696d887c9aa0611bbb3e2025a45a' + self.flags(build_plan_encryption_key=key) self.stubs.Set(api, 'select', zone_select) req = webob.Request.blank('/v1.0/zones/select') diff --git a/nova/tests/test_api.py b/nova/tests/test_api.py index 292f9d668..3ec1c9abf 100644 --- a/nova/tests/test_api.py +++ b/nova/tests/test_api.py @@ -395,7 +395,6 @@ class ApiEc2TestCase(test.TestCase): self.expect_http() self.mox.ReplayAll() - security_group_name = "".join(random.choice("sdiuisudfsdcnpaqwertasd") for x in range(random.randint(4, 8))) -- cgit From 1f55e116adbf00a0a5bd990f99a680e9d6b1618e Mon Sep 17 00:00:00 2001 From: Isaku Yamahata Date: Sat, 23 Jul 2011 16:55:25 +0900 Subject: ec2utils: factor generic helper function into generic place This patch moves out a helper function, properties_root_device_name(), into generic file nova/block_device.py. --- nova/api/ec2/cloud.py | 5 +++-- nova/api/ec2/ec2utils.py | 19 ------------------- nova/block_device.py | 35 +++++++++++++++++++++++++++++++++++ nova/compute/api.py | 3 ++- nova/tests/test_api.py | 7 +++++-- 5 files changed, 45 insertions(+), 24 deletions(-) create mode 100644 nova/block_device.py (limited to 'nova') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 16ca1ed2a..b25f74f61 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -30,6 +30,7 @@ import tempfile import time import shutil +from nova import block_device from nova import compute from nova import context @@ -1240,7 +1241,7 @@ class CloudController(object): i['architecture'] = image['properties'].get('architecture') properties = image['properties'] - root_device_name = ec2utils.properties_root_device_name(properties) + root_device_name = block_device.properties_root_device_name(properties) root_device_type = 'instance-store' for bdm in properties.get('block_device_mapping', []): if (bdm.get('device_name') == root_device_name and @@ -1313,7 +1314,7 @@ class CloudController(object): def _root_device_name_attribute(image, result): result['rootDeviceName'] = \ - ec2utils.properties_root_device_name(image['properties']) + block_device.properties_root_device_name(image['properties']) if result['rootDeviceName'] is None: result['rootDeviceName'] = _DEFAULT_ROOT_DEVICE_NAME diff --git a/nova/api/ec2/ec2utils.py b/nova/api/ec2/ec2utils.py index bae1e0ee5..14891debb 100644 --- a/nova/api/ec2/ec2utils.py +++ b/nova/api/ec2/ec2utils.py @@ -137,25 +137,6 @@ def dict_from_dotted_str(items): return args -def properties_root_device_name(properties): - """get root device name from image meta data. - If it isn't specified, return None. - """ - root_device_name = None - - # NOTE(yamahata): see image_service.s3.s3create() - for bdm in properties.get('mappings', []): - if bdm['virtual'] == 'root': - root_device_name = bdm['device'] - - # NOTE(yamahata): register_image's command line can override - # .manifest.xml - if 'root_device_name' in properties: - root_device_name = properties['root_device_name'] - - return root_device_name - - def mappings_prepend_dev(mappings): """Prepend '/dev/' to 'device' entry of swap/ephemeral virtual type""" for m in mappings: diff --git a/nova/block_device.py b/nova/block_device.py new file mode 100644 index 000000000..963dffa37 --- /dev/null +++ b/nova/block_device.py @@ -0,0 +1,35 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 Isaku Yamahata +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + + +def properties_root_device_name(properties): + """get root device name from image meta data. + If it isn't specified, return None. + """ + root_device_name = None + + # NOTE(yamahata): see image_service.s3.s3create() + for bdm in properties.get('mappings', []): + if bdm['virtual'] == 'root': + root_device_name = bdm['device'] + + # NOTE(yamahata): register_image's command line can override + # .manifest.xml + if 'root_device_name' in properties: + root_device_name = properties['root_device_name'] + + return root_device_name diff --git a/nova/compute/api.py b/nova/compute/api.py index 9994e5724..43a95aa17 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -22,6 +22,7 @@ import eventlet import re import time +from nova import block_device from nova import db from nova import exception from nova import flags @@ -218,7 +219,7 @@ class API(base.Base): if reservation_id is None: reservation_id = utils.generate_uid('r') - root_device_name = ec2utils.properties_root_device_name( + root_device_name = block_device.properties_root_device_name( image['properties']) base_options = { diff --git a/nova/tests/test_api.py b/nova/tests/test_api.py index 26ac5ff24..d5f653bc6 100644 --- a/nova/tests/test_api.py +++ b/nova/tests/test_api.py @@ -27,6 +27,7 @@ import random import StringIO import webob +from nova import block_device from nova import context from nova import exception from nova import test @@ -147,10 +148,12 @@ class Ec2utilsTestCase(test.TestCase): properties0 = {'mappings': mappings} properties1 = {'root_device_name': '/dev/sdb', 'mappings': mappings} - root_device_name = ec2utils.properties_root_device_name(properties0) + root_device_name = block_device.properties_root_device_name( + properties0) self.assertEqual(root_device_name, '/dev/sda1') - root_device_name = ec2utils.properties_root_device_name(properties1) + root_device_name = block_device.properties_root_device_name( + properties1) self.assertEqual(root_device_name, '/dev/sdb') def test_mapping_prepend_dev(self): -- cgit From ba6b6a20eeedb0311e06090d2f60d36964d67cf4 Mon Sep 17 00:00:00 2001 From: Isaku Yamahata Date: Sat, 23 Jul 2011 16:55:25 +0900 Subject: block_device: introduce helper function to check swap or ephemeral device and move generic function, mappings_prepend_dev() from ec2utils to block_device --- nova/api/ec2/cloud.py | 8 +++----- nova/api/ec2/ec2utils.py | 10 ---------- nova/block_device.py | 36 ++++++++++++++++++++++++++++++++++++ nova/tests/test_api.py | 2 +- 4 files changed, 40 insertions(+), 16 deletions(-) (limited to 'nova') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index b25f74f61..c35194f6f 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -106,7 +106,7 @@ def _parse_block_device_mapping(bdm): def _properties_get_mappings(properties): - return ec2utils.mappings_prepend_dev(properties.get('mappings', [])) + return block_device.mappings_prepend_dev(properties.get('mappings', [])) def _format_block_device_mapping(bdm): @@ -145,8 +145,7 @@ def _format_mappings(properties, result): """Format multiple BlockDeviceMappingItemType""" mappings = [{'virtualName': m['virtual'], 'deviceName': m['device']} for m in _properties_get_mappings(properties) - if (m['virtual'] == 'swap' or - m['virtual'].startswith('ephemeral'))] + if block_device.is_swap_or_ephemeral(m['virtual'])] block_device_mapping = [_format_block_device_mapping(bdm) for bdm in properties.get('block_device_mapping', [])] @@ -1447,8 +1446,7 @@ class CloudController(object): if virtual_name in ('ami', 'root'): continue - assert (virtual_name == 'swap' or - virtual_name.startswith('ephemeral')) + assert block_device.is_swap_or_ephemeral(virtual_name) device_name = m['device'] if device_name in [b['device_name'] for b in mapping if not b.get('no_device', False)]: diff --git a/nova/api/ec2/ec2utils.py b/nova/api/ec2/ec2utils.py index 14891debb..bcdf2ba78 100644 --- a/nova/api/ec2/ec2utils.py +++ b/nova/api/ec2/ec2utils.py @@ -135,13 +135,3 @@ def dict_from_dotted_str(items): args[key] = value return args - - -def mappings_prepend_dev(mappings): - """Prepend '/dev/' to 'device' entry of swap/ephemeral virtual type""" - for m in mappings: - virtual = m['virtual'] - if ((virtual == 'swap' or virtual.startswith('ephemeral')) and - (not m['device'].startswith('/'))): - m['device'] = '/dev/' + m['device'] - return mappings diff --git a/nova/block_device.py b/nova/block_device.py index 963dffa37..8d95e0029 100644 --- a/nova/block_device.py +++ b/nova/block_device.py @@ -15,6 +15,8 @@ # License for the specific language governing permissions and limitations # under the License. +import re + def properties_root_device_name(properties): """get root device name from image meta data. @@ -33,3 +35,37 @@ def properties_root_device_name(properties): root_device_name = properties['root_device_name'] return root_device_name + + +_ephemeral = re.compile('^ephemeral(\d|[1-9]\d+)$') + + +def is_ephemeral(device_name): + return _ephemeral.match(device_name) + + +def ephemeral_num(ephemeral_name): + assert is_ephemeral(ephemeral_name) + return int(_ephemeral.sub('\\1', ephemeral_name)) + + +def is_swap_or_ephemeral(device_name): + return device_name == 'swap' or is_ephemeral(device_name) + + +def mappings_prepend_dev(mappings): + """Prepend '/dev/' to 'device' entry of swap/ephemeral virtual type""" + for m in mappings: + virtual = m['virtual'] + if (is_swap_or_ephemeral(virtual) and + (not m['device'].startswith('/'))): + m['device'] = '/dev/' + m['device'] + return mappings + + +_dev = re.compile('^/dev/') + + +def strip_dev(device_name): + """remove leading '/dev/'""" + return _dev.sub('', device_name) diff --git a/nova/tests/test_api.py b/nova/tests/test_api.py index d5f653bc6..e3d2ee2fc 100644 --- a/nova/tests/test_api.py +++ b/nova/tests/test_api.py @@ -187,7 +187,7 @@ class Ec2utilsTestCase(test.TestCase): 'device': '/dev/sdc1'}, {'virtual': 'ephemeral1', 'device': '/dev/sdc1'}] - self.assertDictListMatch(ec2utils.mappings_prepend_dev(mappings), + self.assertDictListMatch(block_device.mappings_prepend_dev(mappings), expected_result) -- cgit From 9be2793c2e057a5e4f8c8c4dd2131ddcc3b11608 Mon Sep 17 00:00:00 2001 From: Isaku Yamahata Date: Sat, 23 Jul 2011 16:55:25 +0900 Subject: db/api: block_device_mapping_update_or_create() It is possible to have same virtual device name. So eliminate old entries whose entry has same virtual device name. --- nova/db/sqlalchemy/api.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) (limited to 'nova') diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index ba03cabbc..ad51f5192 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -20,6 +20,7 @@ Implementation of SQLAlchemy backend. """ import warnings +from nova import block_device from nova import db from nova import exception from nova import flags @@ -2264,6 +2265,20 @@ def block_device_mapping_update_or_create(context, values): else: result.update(values) + # NOTE(yamahata): same virtual device name can be specified multiple + # times. So delete the existing ones. + virtual_name = values['virtual_name'] + if (virtual_name is not None and + block_device.is_swap_or_ephemeral(virtual_name)): + session.query(models.BlockDeviceMapping).\ + filter_by(instance_id=values['instance_id']).\ + filter_by(virtual_name=virtual_name).\ + filter(models.BlockDeviceMapping.device_name != + values['device_name']).\ + update({'deleted': True, + 'deleted_at': utils.utcnow(), + 'updated_at': literal_column('updated_at')}) + @require_context def block_device_mapping_get_all_by_instance(context, instance_id): -- cgit From 92ac32e148d31a957be6e8f3e90724216e10106a Mon Sep 17 00:00:00 2001 From: Isaku Yamahata Date: Sat, 23 Jul 2011 16:55:25 +0900 Subject: api/ec2: implement describe_instance_attribute() This patch implements DescribeInstanceAttribute. --- nova/api/ec2/cloud.py | 131 ++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 117 insertions(+), 14 deletions(-) (limited to 'nova') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index c35194f6f..de75b912b 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -260,7 +260,7 @@ class CloudController(object): instance_ref['id']) security_groups = [x['name'] for x in security_groups] data = { - 'user-data': base64.b64decode(instance_ref['user_data']), + 'user-data': self._format_user_data(instance_ref), 'meta-data': { 'ami-id': image_ec2_id, 'ami-launch-index': instance_ref['launch_index'], @@ -874,13 +874,102 @@ class CloudController(object): 'status': volume['attach_status'], 'volumeId': ec2utils.id_to_ec2_vol_id(volume_id)} - def _convert_to_set(self, lst, label): + @staticmethod + def _convert_to_set(lst, label): if lst is None or lst == []: return None if not isinstance(lst, list): lst = [lst] return [{label: x} for x in lst] + def _format_kernel_id(self, instance_ref, result, key): + kernel_id = instance_ref['kernel_id'] + if kernel_id is None: + return + result[key] = self.image_ec2_id(instance_ref['kernel_id'], 'aki') + + def _format_ramdisk_id(self, instance_ref, result, key): + ramdisk_id = instance_ref['ramdisk_id'] + if ramdisk_id is None: + return + result[key] = self.image_ec2_id(instance_ref['ramdisk_id'], 'ari') + + @staticmethod + def _format_user_data(instance_ref): + return base64.b64decode(instance_ref['user_data']) + + def describe_instance_attribute(self, context, instance_id, attribute, + **kwargs): + def _unsupported_attribute(instance, result): + raise exception.ApiError(_('attribute not supported: %s') % + attribute) + + def _format_attr_block_device_mapping(instance, result): + tmp = {} + self._format_instance_root_device_name(instance, tmp) + self._format_instance_bdm(context, instance_id, + tmp['rootDeviceName'], result) + + def _format_attr_disable_api_termination(instance, result): + _unsupported_attribute(instance, result) + + def _format_attr_group_set(instance, result): + CloudController._format_group_set(instance, result) + + def _format_attr_instance_initiated_shutdown_behavior(instance, + result): + state_description = instance['state_description'] + state_to_value = {'stopping': 'stop', + 'stopped': 'stop', + 'terminating': 'terminate'} + value = state_to_value.get(state_description) + if value: + result['instanceInitiatedShutdownBehavior'] = value + + def _format_attr_instance_type(instance, result): + self._format_instance_type(instance, result) + + def _format_attr_kernel(instance, result): + self._format_kernel_id(instance, result, 'kernel') + + def _format_attr_ramdisk(instance, result): + self._format_ramdisk_id(instance, result, 'ramdisk') + + def _format_attr_root_device_name(instance, result): + self._format_instance_root_device_name(instance, result) + + def _format_attr_source_dest_check(instance, result): + _unsupported_attribute(instance, result) + + def _format_attr_user_data(instance, result): + result['userData'] = self._format_user_data(instance) + + attribute_formatter = { + 'blockDeviceMapping': _format_attr_block_device_mapping, + 'disableApiTermination': _format_attr_disable_api_termination, + 'groupSet': _format_attr_group_set, + 'instanceInitiatedShutdownBehavior': + _format_attr_instance_initiated_shutdown_behavior, + 'instanceType': _format_attr_instance_type, + 'kernel': _format_attr_kernel, + 'ramdisk': _format_attr_ramdisk, + 'rootDeviceName': _format_attr_root_device_name, + 'sourceDestCheck': _format_attr_source_dest_check, + 'userData': _format_attr_user_data, + } + + fn = attribute_formatter.get(attribute) + if fn is None: + raise exception.ApiError( + _('attribute not supported: %s') % attribute) + + ec2_instance_id = instance_id + instance_id = ec2utils.ec2_id_to_id(ec2_instance_id) + instance = self.compute_api.get(context, instance_id) + result = {'instance_id': ec2_instance_id} + fn(instance, result) + return result + def describe_instances(self, context, **kwargs): return self._format_describe_instances(context, **kwargs) @@ -927,6 +1016,27 @@ class CloudController(object): result['blockDeviceMapping'] = mapping result['rootDeviceType'] = root_device_type + @staticmethod + def _format_instance_root_device_name(instance, result): + result['rootDeviceName'] = (instance.get('root_device_name') or + _DEFAULT_ROOT_DEVICE_NAME) + + @staticmethod + def _format_instance_type(instance, result): + if instance['instance_type']: + result['instanceType'] = instance['instance_type'].get('name') + else: + result['instanceType'] = None + + @staticmethod + def _format_group_set(instance, result): + security_group_names = [] + if instance.get('security_groups'): + for security_group in instance['security_groups']: + security_group_names.append(security_group['name']) + result['groupSet'] = CloudController._convert_to_set( + security_group_names, 'groupId') + def _format_instances(self, context, instance_id=None, **kwargs): # TODO(termie): this method is poorly named as its name does not imply # that it will be making a variety of database calls @@ -952,6 +1062,8 @@ class CloudController(object): ec2_id = ec2utils.id_to_ec2_id(instance_id) i['instanceId'] = ec2_id i['imageId'] = self.image_ec2_id(instance['image_ref']) + self._format_kernel_id(instance, i, 'kernelId') + self._format_ramdisk_id(instance, i, 'ramdiskId') i['instanceState'] = { 'code': instance['state'], 'name': instance['state_description']} @@ -980,16 +1092,12 @@ class CloudController(object): instance['project_id'], instance['host']) i['productCodesSet'] = self._convert_to_set([], 'product_codes') - if instance['instance_type']: - i['instanceType'] = instance['instance_type'].get('name') - else: - i['instanceType'] = None + self._format_instance_type(instance, i) i['launchTime'] = instance['created_at'] i['amiLaunchIndex'] = instance['launch_index'] i['displayName'] = instance['display_name'] i['displayDescription'] = instance['display_description'] - i['rootDeviceName'] = (instance.get('root_device_name') or - _DEFAULT_ROOT_DEVICE_NAME) + self._format_instance_root_device_name(instance, i) self._format_instance_bdm(context, instance_id, i['rootDeviceName'], i) host = instance['host'] @@ -999,12 +1107,7 @@ class CloudController(object): r = {} r['reservationId'] = instance['reservation_id'] r['ownerId'] = instance['project_id'] - security_group_names = [] - if instance.get('security_groups'): - for security_group in instance['security_groups']: - security_group_names.append(security_group['name']) - r['groupSet'] = self._convert_to_set(security_group_names, - 'groupId') + self._format_group_set(instance, r) r['instancesSet'] = [] reservations[instance['reservation_id']] = r reservations[instance['reservation_id']]['instancesSet'].append(i) -- cgit From a840e368235938a2fda96ab1694196e551ad22cc Mon Sep 17 00:00:00 2001 From: Isaku Yamahata Date: Sat, 23 Jul 2011 16:55:25 +0900 Subject: ec2/get_metadata: teach block device mapping to get_metadata() This patch teachs bout block device mapping to get_metadata() --- nova/api/ec2/cloud.py | 36 ++++++++++++++++++++++++++++++------ 1 file changed, 30 insertions(+), 6 deletions(-) (limited to 'nova') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index de75b912b..65f18ddbf 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -79,6 +79,10 @@ def _gen_key(context, user_id, key_name): # TODO(yamahata): hypervisor dependent default device name _DEFAULT_ROOT_DEVICE_NAME = '/dev/sda1' +_DEFAULT_MAPPINGS = {'ami': 'sda1', + 'ephemeral0': 'sda2', + 'root': _DEFAULT_ROOT_DEVICE_NAME, + 'swap': 'sda3'} def _parse_block_device_mapping(bdm): @@ -233,6 +237,30 @@ class CloudController(object): state = 'available' return image['properties'].get('image_state', state) + def _get_instance_mapping(self, ctxt, instance_ref): + root_device_name = instance_ref['root_device_name'] + if root_device_name is None: + return _DEFAULT_MAPPINGS + + mappings = {} + mappings['ami'] = block_device.strip_dev(root_device_name) + mappings['root'] = root_device_name + + # 'ephemeralN' and 'swap' + for bdm in db.block_device_mapping_get_all_by_instance( + ctxt, instance_ref['id']): + if (bdm['volume_id'] or bdm['snapshot_id'] or bdm['no_device']): + continue + + virtual_name = bdm['virtual_name'] + if not virtual_name: + continue + + if block_device.is_swap_or_ephemeral(virtual_name): + mappings[virtual_name] = bdm['device_name'] + + return mappings + def get_metadata(self, address): ctxt = context.get_admin_context() instance_ref = self.compute_api.get_all(ctxt, fixed_ip=address) @@ -259,18 +287,14 @@ class CloudController(object): security_groups = db.security_group_get_by_instance(ctxt, instance_ref['id']) security_groups = [x['name'] for x in security_groups] + mappings = self._get_instance_mapping(ctxt, instance_ref) data = { 'user-data': self._format_user_data(instance_ref), 'meta-data': { 'ami-id': image_ec2_id, 'ami-launch-index': instance_ref['launch_index'], 'ami-manifest-path': 'FIXME', - 'block-device-mapping': { - # TODO(vish): replace with real data - 'ami': 'sda1', - 'ephemeral0': 'sda2', - 'root': _DEFAULT_ROOT_DEVICE_NAME, - 'swap': 'sda3'}, + 'block-device-mapping': mappings, 'hostname': hostname, 'instance-action': 'none', 'instance-id': ec2_id, -- cgit From e0517aef19bb00aa88809cb3c7d650ea38a08be2 Mon Sep 17 00:00:00 2001 From: Isaku Yamahata Date: Sat, 23 Jul 2011 16:55:25 +0900 Subject: compute/manager, virt: pass down root device name/swap/ephemeral to virt driver This patch makes compute/manager pass down infos about root device name, swap and ephemerals to virt driver. --- nova/compute/manager.py | 33 +++++++++++++++++++++++++-------- nova/virt/driver.py | 25 ++++++++++++++++++++++++- nova/virt/fake.py | 2 +- nova/virt/hyperv.py | 2 +- nova/virt/libvirt/connection.py | 25 ++++++++++++++----------- nova/virt/xenapi_conn.py | 2 +- 6 files changed, 66 insertions(+), 23 deletions(-) (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 5819a520a..a4d2797d6 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -44,6 +44,7 @@ import functools from eventlet import greenthread +from nova import block_device from nova import exception from nova import flags import nova.image @@ -223,6 +224,8 @@ class ComputeManager(manager.SchedulerDependentManager): volume_api = volume.API() block_device_mapping = [] + swap = None + ephemerals = [] for bdm in self.db.block_device_mapping_get_all_by_instance( context, instance_id): LOG.debug(_("setting up bdm %s"), bdm) @@ -230,11 +233,18 @@ class ComputeManager(manager.SchedulerDependentManager): if bdm['no_device']: continue if bdm['virtual_name']: - # TODO(yamahata): - # block devices for swap and ephemeralN will be - # created by virt driver locally in compute node. - assert (bdm['virtual_name'] == 'swap' or - bdm['virtual_name'].startswith('ephemeral')) + virtual_name = bdm['virtual_name'] + device_name = bdm['device_name'] + assert block_device.is_swap_or_ephemeral(virtual_name) + if virtual_name == 'swap': + swap = {'device_name': device_name, + 'swap_size': bdm['volume_size']} + elif block_device.is_ephemeral(virtual_name): + eph = {'num': block_device.ephemeral_num(virtual_name), + 'virtual_name': virtual_name, + 'device_name': device_name, + 'size': bdm['volume_size']} + ephemerals.append(eph) continue if ((bdm['snapshot_id'] is not None) and @@ -270,7 +280,7 @@ class ComputeManager(manager.SchedulerDependentManager): 'mount_device': bdm['device_name']}) - return block_device_mapping + return (swap, ephemerals, block_device_mapping) def _run_instance(self, context, instance_id, **kwargs): """Launch a new instance with specified options.""" @@ -313,13 +323,20 @@ class ComputeManager(manager.SchedulerDependentManager): # all vif creation and network injection, maybe this is correct network_info = [] - bd_mapping = self._setup_block_device_mapping(context, instance_id) + (swap, ephemerals, + block_device_mapping) = self._setup_block_device_mapping( + context, instance_id) + block_device_info = { + 'root_device_name': instance['root_device_name'], + 'swap': swap, + 'ephemerals': ephemerals, + 'block_device_mapping': block_device_mapping} # TODO(vish) check to make sure the availability zone matches self._update_state(context, instance_id, power_state.BUILDING) try: - self.driver.spawn(instance, network_info, bd_mapping) + self.driver.spawn(instance, network_info, block_device_info) except Exception as ex: # pylint: disable=W0702 msg = _("Instance '%(instance_id)s' failed to spawn. Is " "virtualization enabled in the BIOS? Details: " diff --git a/nova/virt/driver.py b/nova/virt/driver.py index 178279d31..62c4f7ead 100644 --- a/nova/virt/driver.py +++ b/nova/virt/driver.py @@ -32,6 +32,29 @@ class InstanceInfo(object): self.state = state +def block_device_info_get_root(block_device_info): + block_device_info = block_device_info or {} + return block_device_info.get('root_device_name') + + +def block_device_info_get_swap(block_device_info): + block_device_info = block_device_info or {} + return block_device_info.get('swap') or {'device_name': None, + 'swap_size': 0} + + +def block_device_info_get_ephemerals(block_device_info): + block_device_info = block_device_info or {} + ephemerals = block_device_info.get('ephemerals') or [] + return ephemerals + + +def block_device_info_get_mapping(block_device_info): + block_device_info = block_device_info or {} + block_device_mapping = block_device_info.get('block_device_mapping') or [] + return block_device_mapping + + class ComputeDriver(object): """Base class for compute drivers. @@ -61,7 +84,7 @@ class ComputeDriver(object): """Return a list of InstanceInfo for all registered VMs""" raise NotImplementedError() - def spawn(self, instance, network_info=None, block_device_mapping=None): + def spawn(self, instance, network_info=None, block_device_info=None): """Launch a VM for the specified instance""" raise NotImplementedError() diff --git a/nova/virt/fake.py b/nova/virt/fake.py index ea0a59f21..48a03dac8 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, instance, network_info, block_device_mapping=None): + def spawn(self, instance, network_info=None, block_device_info=None): """ Create a new instance/VM/domain on the virtualization platform. diff --git a/nova/virt/hyperv.py b/nova/virt/hyperv.py index 5c1dc772d..f0ce71392 100644 --- a/nova/virt/hyperv.py +++ b/nova/virt/hyperv.py @@ -139,7 +139,7 @@ class HyperVConnection(driver.ComputeDriver): return instance_infos - def spawn(self, instance, network_info=None, block_device_mapping=None): + def spawn(self, instance, network_info=None, block_device_info=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 342dea98f..264c88a9e 100644 --- a/nova/virt/libvirt/connection.py +++ b/nova/virt/libvirt/connection.py @@ -580,14 +580,13 @@ class LibvirtConnection(driver.ComputeDriver): # NOTE(ilyaalekseyev): Implementation like in multinics # for xenapi(tr3buchet) @exception.wrap_exception() - def spawn(self, instance, network_info=None, block_device_mapping=None): + def spawn(self, instance, network_info=None, block_device_info=None): xml = self.to_xml(instance, False, network_info=network_info, - block_device_mapping=block_device_mapping) - block_device_mapping = block_device_mapping or [] + block_device_info=block_device_info) 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, - block_device_mapping=block_device_mapping) + block_device_info=block_device_info) domain = self._create_new_domain(xml) LOG.debug(_("instance %s: is running"), instance['name']) self.firewall_driver.apply_instance_filter(instance) @@ -769,8 +768,12 @@ class LibvirtConnection(driver.ComputeDriver): # 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): - block_device_mapping = block_device_mapping or [] + network_info=None, block_device_info=None): + block_device_mapping = driver.block_device_info_get_mapping( + block_device_info) + + if not network_info: + network_info = netutils.get_network_info(inst) if not suffix: suffix = '' @@ -974,8 +977,9 @@ class LibvirtConnection(driver.ComputeDriver): return False def _prepare_xml_info(self, instance, rescue=False, network_info=None, - block_device_mapping=None): - block_device_mapping = block_device_mapping or [] + block_device_info=None): + block_device_mapping = driver.block_device_info_get_mapping( + block_device_info) # TODO(adiantum) remove network_info creation code # when multinics will be completed if not network_info: @@ -1030,12 +1034,11 @@ class LibvirtConnection(driver.ComputeDriver): return xml_info def to_xml(self, instance, rescue=False, network_info=None, - block_device_mapping=None): - block_device_mapping = block_device_mapping or [] + block_device_info=None): # TODO(termie): cache? LOG.debug(_('instance %s: starting toXML method'), instance['name']) xml_info = self._prepare_xml_info(instance, rescue, network_info, - block_device_mapping) + block_device_info) xml = str(Template(self.libvirt_xml, searchList=[xml_info])) LOG.debug(_('instance %s: finished toXML method'), instance['name']) return xml diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index ec8c44c1c..4c6f9fe46 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -194,7 +194,7 @@ 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, instance, network_info=None, block_device_info=None): """Create VM instance""" self._vmops.spawn(instance, network_info) -- cgit From e05b3b11e67f18a6ff4867dfbc75554fd78cad1b Mon Sep 17 00:00:00 2001 From: Isaku Yamahata Date: Sat, 23 Jul 2011 16:55:25 +0900 Subject: compute/api: pass down ephemeral device info --- nova/compute/api.py | 70 +++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 54 insertions(+), 16 deletions(-) (limited to 'nova') diff --git a/nova/compute/api.py b/nova/compute/api.py index 43a95aa17..942114161 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -33,7 +33,6 @@ from nova import quota from nova import rpc from nova import utils from nova import volume -from nova.api.ec2 import ec2utils from nova.compute import instance_types from nova.compute import power_state from nova.compute.utils import terminate_volumes @@ -251,34 +250,64 @@ class API(base.Base): return (num_instances, base_options, image) - def _update_image_block_device_mapping(self, elevated_context, instance_id, + @staticmethod + def _ephemeral_size(instance_type, ephemeral_name): + num = block_device.ephemeral_num(ephemeral_name) + + # TODO(yamahata): ephemeralN where N > 0 + # Only ephemeral0 is allowed for now because InstanceTypes + # table only allows single local disk, local_gb. + # In order to enhance it, we need to add a new columns to + # instance_types table. + if num > 0: + return 0 + + return instance_type.get('local_gb') + + def _update_image_block_device_mapping(self, elevated_context, + instance_type, instance_id, mappings): """tell vm driver to create ephemeral/swap device at boot time by updating BlockDeviceMapping """ - for bdm in ec2utils.mappings_prepend_dev(mappings): + instance_type = (instance_type or + instance_types.get_default_instance_type()) + + for bdm in block_device.mappings_prepend_dev(mappings): LOG.debug(_("bdm %s"), bdm) virtual_name = bdm['virtual'] if virtual_name == 'ami' or virtual_name == 'root': continue - assert (virtual_name == 'swap' or - virtual_name.startswith('ephemeral')) + if not block_device.is_swap_or_ephemeral(virtual_name): + continue + + size = 0 + if virtual_name == 'swap': + size = instance_type.get('swap', 0) + elif block_device.is_ephemeral(virtual_name): + size = self._ephemeral_size(instance_type, virtual_name) + + if size == 0: + continue + values = { 'instance_id': instance_id, 'device_name': bdm['device'], - 'virtual_name': virtual_name, } + 'virtual_name': virtual_name, + 'volume_size': size} self.db.block_device_mapping_update_or_create(elevated_context, values) - def _update_block_device_mapping(self, elevated_context, instance_id, + def _update_block_device_mapping(self, elevated_context, + instance_type, instance_id, block_device_mapping): """tell vm driver to attach volume at boot time by updating BlockDeviceMapping """ + LOG.debug(_("block_device_mapping %s"), block_device_mapping) for bdm in block_device_mapping: - LOG.debug(_('bdm %s'), bdm) assert 'device_name' in bdm values = {'instance_id': instance_id} @@ -287,10 +316,18 @@ class API(base.Base): 'no_device'): values[key] = bdm.get(key) + virtual_name = bdm.get('virtual_name') + if (virtual_name is not None and + block_device.is_ephemeral(virtual_name)): + size = self._ephemeral_size(instance_type, virtual_name) + if size == 0: + continue + values['volume_size'] = size + # NOTE(yamahata): NoDevice eliminates devices defined in image # files by command line option. # (--block-device-mapping) - if bdm.get('virtual_name') == 'NoDevice': + if virtual_name == 'NoDevice': values['no_device'] = True for k in ('delete_on_termination', 'volume_id', 'snapshot_id', 'volume_id', 'volume_size', @@ -300,8 +337,8 @@ class API(base.Base): self.db.block_device_mapping_update_or_create(elevated_context, values) - def create_db_entry_for_new_instance(self, context, image, base_options, - security_group, block_device_mapping, num=1): + def create_db_entry_for_new_instance(self, context, instance_type, image, + base_options, security_group, block_device_mapping, num=1): """Create an entry in the DB for this new instance, including any related table updates (such as security group, etc). @@ -334,12 +371,12 @@ class API(base.Base): security_group_id) # BlockDeviceMapping table - self._update_image_block_device_mapping(elevated, instance_id, - image['properties'].get('mappings', [])) - self._update_block_device_mapping(elevated, instance_id, + self._update_image_block_device_mapping(elevated, instance_type, + instance_id, image['properties'].get('mappings', [])) + self._update_block_device_mapping(elevated, instance_type, instance_id, image['properties'].get('block_device_mapping', [])) # override via command line option - self._update_block_device_mapping(elevated, instance_id, + self._update_block_device_mapping(elevated, instance_type, instance_id, block_device_mapping) # Set sane defaults if not specified @@ -454,7 +491,8 @@ class API(base.Base): instances = [] LOG.debug(_("Going to run %s instances..."), num_instances) for num in range(num_instances): - instance = self.create_db_entry_for_new_instance(context, image, + instance = self.create_db_entry_for_new_instance(context, + instance_type, image, base_options, security_group, block_device_mapping, num=num) instances.append(instance) -- cgit From 3c8cc5b06f477b88d20a748a924d6afac5c5260f Mon Sep 17 00:00:00 2001 From: Isaku Yamahata Date: Sat, 23 Jul 2011 16:57:01 +0900 Subject: virt/libvirt: teach libvirt driver root device name This patch teaches libvirt driver root device name. --- nova/virt/libvirt.xml.template | 11 +++++++---- nova/virt/libvirt/connection.py | 18 ++++++++++++------ 2 files changed, 19 insertions(+), 10 deletions(-) (limited to 'nova') diff --git a/nova/virt/libvirt.xml.template b/nova/virt/libvirt.xml.template index e1a683da8..e46a75915 100644 --- a/nova/virt/libvirt.xml.template +++ b/nova/virt/libvirt.xml.template @@ -12,13 +12,15 @@ #set $disk_bus = 'uml' uml /usr/bin/linux - /dev/ubda + #set $root_device_name = $getVar('root_device_name', '/dev/ubda') + ${root_device_name} #else #if $type == 'xen' #set $disk_prefix = 'sd' #set $disk_bus = 'scsi' linux - /dev/xvda + #set $root_device_name = $getVar('root_device_name', '/dev/xvda') + ${root_device_name} #else #set $disk_prefix = 'vd' #set $disk_bus = 'virtio' @@ -33,7 +35,8 @@ #if $type == 'xen' ro #else - root=/dev/vda console=ttyS0 + #set $root_device_name = $getVar('root_device_name', '/dev/vda') + root=${root_device_name} console=ttyS0 #end if #if $getVar('ramdisk', None) ${ramdisk} @@ -71,7 +74,7 @@ - + #end if #if $getVar('local', False) diff --git a/nova/virt/libvirt/connection.py b/nova/virt/libvirt/connection.py index 264c88a9e..30ad3c4fb 100644 --- a/nova/virt/libvirt/connection.py +++ b/nova/virt/libvirt/connection.py @@ -54,6 +54,7 @@ from xml.etree import ElementTree from eventlet import greenthread from eventlet import tpool +from nova import block_device from nova import context from nova import db from nova import exception @@ -834,7 +835,7 @@ class LibvirtConnection(driver.ComputeDriver): size = None root_fname += "_sm" - if not self._volume_in_mapping(self.root_mount_device, + if not self._volume_in_mapping(self.default_root_device, block_device_mapping): self._cache_image(fn=self._fetch_image, target=basepath('disk'), @@ -965,13 +966,13 @@ class LibvirtConnection(driver.ComputeDriver): return result - root_mount_device = 'vda' # FIXME for now. it's hard coded. + default_root_device = 'vda' # FIXME for now. it's hard coded. local_mount_device = 'vdb' # FIXME for now. it's hard coded. def _volume_in_mapping(self, mount_device, block_device_mapping): - mount_device_ = _strip_dev(mount_device) + mount_device_ = block_device.strip_dev(mount_device) for vol in block_device_mapping: - vol_mount_device = _strip_dev(vol['mount_device']) + vol_mount_device = block_device.strip_dev(vol['mount_device']) if vol_mount_device == mount_device_: return True return False @@ -998,8 +999,8 @@ class LibvirtConnection(driver.ComputeDriver): driver_type = 'raw' for vol in block_device_mapping: - vol['mount_device'] = _strip_dev(vol['mount_device']) - ebs_root = self._volume_in_mapping(self.root_mount_device, + vol['mount_device'] = block_device.strip_dev(vol['mount_device']) + ebs_root = self._volume_in_mapping(self.default_root_device, block_device_mapping) if self._volume_in_mapping(self.local_mount_device, block_device_mapping): @@ -1020,6 +1021,11 @@ class LibvirtConnection(driver.ComputeDriver): 'ebs_root': ebs_root, 'volumes': block_device_mapping} + root_device_name = driver.block_device_info_get_root(block_device_info) + if root_device_name: + xml_info['root_device'] = block_device.strip_dev(root_device_name) + xml_info['root_device_name'] = root_device_name + if FLAGS.vnc_enabled and FLAGS.libvirt_type not in ('lxc', 'uml'): xml_info['vncserver_host'] = FLAGS.vncserver_host xml_info['vnc_keymap'] = FLAGS.vnc_keymap -- cgit From 2c1b9ac98673c0ef1ae931c6b9d84e4b0741eed9 Mon Sep 17 00:00:00 2001 From: Isaku Yamahata Date: Sat, 23 Jul 2011 16:57:03 +0900 Subject: virt/libvirt: teach libvirt driver swap/ephemeral device This patch teaches libvirt virt driver swap/ephemeral device. --- nova/virt/driver.py | 4 ++ nova/virt/libvirt.xml.template | 22 +++++-- nova/virt/libvirt/connection.py | 127 ++++++++++++++++++++++++++++++++-------- 3 files changed, 122 insertions(+), 31 deletions(-) (limited to 'nova') diff --git a/nova/virt/driver.py b/nova/virt/driver.py index 62c4f7ead..b2406e306 100644 --- a/nova/virt/driver.py +++ b/nova/virt/driver.py @@ -43,6 +43,10 @@ def block_device_info_get_swap(block_device_info): 'swap_size': 0} +def swap_is_usable(swap): + return swap and swap['device_name'] and swap['swap_size'] > 0 + + def block_device_info_get_ephemerals(block_device_info): block_device_info = block_device_info or {} ephemerals = block_device_info.get('ephemerals') or [] diff --git a/nova/virt/libvirt.xml.template b/nova/virt/libvirt.xml.template index e46a75915..f7cf306bc 100644 --- a/nova/virt/libvirt.xml.template +++ b/nova/virt/libvirt.xml.template @@ -3,12 +3,10 @@ ${memory_kb} #if $type == 'lxc' - #set $disk_prefix = '' #set $disk_bus = '' exe /sbin/init #else if $type == 'uml' - #set $disk_prefix = 'ubd' #set $disk_bus = 'uml' uml /usr/bin/linux @@ -16,13 +14,11 @@ ${root_device_name} #else #if $type == 'xen' - #set $disk_prefix = 'sd' #set $disk_bus = 'scsi' linux #set $root_device_name = $getVar('root_device_name', '/dev/xvda') ${root_device_name} #else - #set $disk_prefix = 'vd' #set $disk_bus = 'virtio' hvm #end if @@ -77,13 +73,27 @@ #end if - #if $getVar('local', False) + #if $getVar('local_device', False) - + #end if + #for $eph in $ephemerals + + + + + + #end for + #if $getVar('swap_device', False) + + + + + + #end if #for $vol in $volumes diff --git a/nova/virt/libvirt/connection.py b/nova/virt/libvirt/connection.py index 30ad3c4fb..cf013df30 100644 --- a/nova/virt/libvirt/connection.py +++ b/nova/virt/libvirt/connection.py @@ -149,8 +149,8 @@ def _late_load_cheetah(): Template = t.Template -def _strip_dev(mount_path): - return re.sub(r'^/dev/', '', mount_path) +def _get_eph_disk(ephemeral): + return 'disk.eph' + str(ephemeral['num']) class LibvirtConnection(driver.ComputeDriver): @@ -768,6 +768,11 @@ class LibvirtConnection(driver.ComputeDriver): utils.execute('truncate', target, '-s', "%dG" % local_gb) # TODO(vish): should we format disk by default? + def _create_swap(self, target, swap_gb): + """Create a swap file of specified size""" + self._create_local(target, swap_gb) + utils.execute('mkswap', target) + def _create_image(self, inst, libvirt_xml, suffix='', disk_images=None, network_info=None, block_device_info=None): block_device_mapping = driver.block_device_info_get_mapping( @@ -836,7 +841,7 @@ class LibvirtConnection(driver.ComputeDriver): root_fname += "_sm" if not self._volume_in_mapping(self.default_root_device, - block_device_mapping): + block_device_info): self._cache_image(fn=self._fetch_image, target=basepath('disk'), fname=root_fname, @@ -846,13 +851,38 @@ class LibvirtConnection(driver.ComputeDriver): project=project, size=size) - if inst_type['local_gb'] and not self._volume_in_mapping( - self.local_mount_device, block_device_mapping): + local_gb = inst['local_gb'] + if local_gb and not self._volume_in_mapping( + self.default_local_device, block_device_info): self._cache_image(fn=self._create_local, target=basepath('disk.local'), - fname="local_%s" % inst_type['local_gb'], + fname="local_%s" % local_gb, + cow=FLAGS.use_cow_images, + local_gb=local_gb) + + for eph in driver.block_device_info_get_ephemerals(block_device_info): + self._cache_image(fn=self._create_local, + target=basepath(_get_eph_disk(eph)), + fname="local_%s" % eph['size'], + cow=FLAGS.use_cow_images, + local_gb=eph['size']) + + swap_gb = 0 + + swap = driver.block_device_info_get_swap(block_device_info) + if driver.swap_is_usable(swap): + swap_gb = swap['swap_size'] + elif (inst_type['swap'] > 0 and + not self._volume_in_mapping(self.default_swap_device, + block_device_info)): + swap_gb = inst_type['swap'] + + if swap_gb > 0: + self._cache_image(fn=self._create_swap, + target=basepath('disk.swap'), + fname="swap_%s" % swap_gb, cow=FLAGS.use_cow_images, - local_gb=inst_type['local_gb']) + swap_gb=swap_gb) # For now, we assume that if we're not using a kernel, we're using a # partitioned disk image where the target partition is the first @@ -966,16 +996,35 @@ class LibvirtConnection(driver.ComputeDriver): return result - default_root_device = 'vda' # FIXME for now. it's hard coded. - local_mount_device = 'vdb' # FIXME for now. it's hard coded. - - def _volume_in_mapping(self, mount_device, block_device_mapping): - mount_device_ = block_device.strip_dev(mount_device) - for vol in block_device_mapping: - vol_mount_device = block_device.strip_dev(vol['mount_device']) - if vol_mount_device == mount_device_: - return True - return False + if FLAGS.libvirt_type == 'uml': + _disk_prefix = 'ubd' + elif FLAGS.libvirt_type == 'xen': + _disk_prefix = 'sd' + elif FLAGS.libvirt_type == 'lxc': + _disk_prefix = '' + else: + _disk_prefix = 'vd' + + default_root_device = _disk_prefix + 'a' + default_local_device = _disk_prefix + 'b' + default_swap_device = _disk_prefix + 'c' + + def _volume_in_mapping(self, mount_device, block_device_info): + block_device_list = [block_device.strip_dev(vol['mount_device']) + for vol in + driver.block_device_info_get_mapping( + block_device_info)] + swap = driver.block_device_info_get_swap(block_device_info) + if driver.swap_is_usable(swap): + block_device_list.append( + block_device.strip_dev(swap['device_name'])) + block_device_list += [block_device.strip_dev(ephemeral['device_name']) + for ephemeral in + driver.block_device_info_get_ephemerals( + block_device_info)] + + LOG.debug(_("block_device_list %s"), block_device_list) + return block_device.strip_dev(mount_device) in block_device_list def _prepare_xml_info(self, instance, rescue=False, network_info=None, block_device_info=None): @@ -1000,13 +1049,24 @@ class LibvirtConnection(driver.ComputeDriver): for vol in block_device_mapping: vol['mount_device'] = block_device.strip_dev(vol['mount_device']) + ebs_root = self._volume_in_mapping(self.default_root_device, - block_device_mapping) - if self._volume_in_mapping(self.local_mount_device, - block_device_mapping): - local_gb = False - else: - local_gb = inst_type['local_gb'] + block_device_info) + + local_device = False + if not (self._volume_in_mapping(self.default_local_device, + block_device_info) or + 0 in [eph['num'] for eph in + driver.block_device_info_get_ephemerals( + block_device_info)]): + if instance['local_gb'] > 0: + local_device = self.default_local_device + + ephemerals = [] + for eph in driver.block_device_info_get_ephemerals(block_device_info): + ephemerals.append({'device_path': _get_eph_disk(eph), + 'device': block_device.strip_dev( + eph['device_name'])}) xml_info = {'type': FLAGS.libvirt_type, 'name': instance['name'], @@ -1015,16 +1075,33 @@ class LibvirtConnection(driver.ComputeDriver): 'memory_kb': inst_type['memory_mb'] * 1024, 'vcpus': inst_type['vcpus'], 'rescue': rescue, - 'local': local_gb, + 'disk_prefix': self._disk_prefix, 'driver_type': driver_type, 'nics': nics, 'ebs_root': ebs_root, - 'volumes': block_device_mapping} + 'local_device': local_device, + 'volumes': block_device_mapping, + 'ephemerals': ephemerals} root_device_name = driver.block_device_info_get_root(block_device_info) if root_device_name: xml_info['root_device'] = block_device.strip_dev(root_device_name) xml_info['root_device_name'] = root_device_name + else: + # NOTE(yamahata): + # for nova.api.ec2.cloud.CloudController.get_metadata() + xml_info['root_device'] = self.default_root_device + db.instance_update(context.get_admin_context(), instance['id'], + {'root_device_name': '/dev/' + self.default_root_device}) + + swap = driver.block_device_info_get_swap(block_device_info) + if driver.swap_is_usable(swap): + xml_info['swap_device'] = block_device.strip_dev( + swap['device_name']) + elif (inst_type['swap'] > 0 and + not self._volume_in_mapping(self.default_swap_device, + block_device_info)): + xml_info['swap_device'] = self.default_swap_device if FLAGS.vnc_enabled and FLAGS.libvirt_type not in ('lxc', 'uml'): xml_info['vncserver_host'] = FLAGS.vncserver_host -- cgit From af21767505b668c882734552115decdf8a798581 Mon Sep 17 00:00:00 2001 From: Isaku Yamahata Date: Sat, 23 Jul 2011 16:57:03 +0900 Subject: test_libvirt: fix up for local_gb --- nova/tests/test_libvirt.py | 1 + 1 file changed, 1 insertion(+) (limited to 'nova') diff --git a/nova/tests/test_libvirt.py b/nova/tests/test_libvirt.py index 6e2ec7ed6..2a21d0d32 100644 --- a/nova/tests/test_libvirt.py +++ b/nova/tests/test_libvirt.py @@ -187,6 +187,7 @@ class LibvirtConnTestCase(test.TestCase): 'project_id': 'fake', 'bridge': 'br101', 'image_ref': '123456', + 'local_gb': 20, 'instance_type_id': '5'} # m1.small def lazy_load_library_exists(self): -- cgit From 47e7a21d74ebd06d994ad41088adb92d615aab0c Mon Sep 17 00:00:00 2001 From: Isaku Yamahata Date: Sat, 23 Jul 2011 16:57:04 +0900 Subject: test_compute: make test_compute pass --- nova/tests/test_compute.py | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) (limited to 'nova') diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py index 5d59b628a..c5ce18495 100644 --- a/nova/tests/test_compute.py +++ b/nova/tests/test_compute.py @@ -887,15 +887,17 @@ class ComputeTestCase(test.TestCase): return bdm def test_update_block_device_mapping(self): + swap_size = 1 + instance_type = {'swap': swap_size} instance_id = self._create_instance() mappings = [ {'virtual': 'ami', 'device': 'sda1'}, {'virtual': 'root', 'device': '/dev/sda1'}, - {'virtual': 'swap', 'device': 'sdb1'}, - {'virtual': 'swap', 'device': 'sdb2'}, - {'virtual': 'swap', 'device': 'sdb3'}, {'virtual': 'swap', 'device': 'sdb4'}, + {'virtual': 'swap', 'device': 'sdb3'}, + {'virtual': 'swap', 'device': 'sdb2'}, + {'virtual': 'swap', 'device': 'sdb1'}, {'virtual': 'ephemeral0', 'device': 'sdc1'}, {'virtual': 'ephemeral1', 'device': 'sdc2'}, @@ -937,19 +939,21 @@ class ComputeTestCase(test.TestCase): 'no_device': True}] self.compute_api._update_image_block_device_mapping( - self.context, instance_id, mappings) + self.context, instance_type, instance_id, mappings) bdms = [self._parse_db_block_device_mapping(bdm_ref) for bdm_ref in db.block_device_mapping_get_all_by_instance( self.context, instance_id)] expected_result = [ - {'virtual_name': 'swap', 'device_name': '/dev/sdb1'}, - {'virtual_name': 'swap', 'device_name': '/dev/sdb2'}, - {'virtual_name': 'swap', 'device_name': '/dev/sdb3'}, - {'virtual_name': 'swap', 'device_name': '/dev/sdb4'}, + {'virtual_name': 'swap', 'device_name': '/dev/sdb1', + 'volume_size': swap_size}, {'virtual_name': 'ephemeral0', 'device_name': '/dev/sdc1'}, - {'virtual_name': 'ephemeral1', 'device_name': '/dev/sdc2'}, - {'virtual_name': 'ephemeral2', 'device_name': '/dev/sdc3'}] + + # NOTE(yamahata): ATM only ephemeral0 is supported. + # they're ignored for now + #{'virtual_name': 'ephemeral1', 'device_name': '/dev/sdc2'}, + #{'virtual_name': 'ephemeral2', 'device_name': '/dev/sdc3'} + ] bdms.sort() expected_result.sort() self.assertDictListMatch(bdms, expected_result) @@ -962,7 +966,8 @@ class ComputeTestCase(test.TestCase): expected_result = [ {'snapshot_id': 0x12345678, 'device_name': '/dev/sda1'}, - {'virtual_name': 'swap', 'device_name': '/dev/sdb1'}, + {'virtual_name': 'swap', 'device_name': '/dev/sdb1', + 'volume_size': swap_size}, {'snapshot_id': 0x23456789, 'device_name': '/dev/sdb2'}, {'snapshot_id': 0x3456789A, 'device_name': '/dev/sdb3'}, {'no_device': True, 'device_name': '/dev/sdb4'}, -- cgit From 51c0c36bc5357102d0fa564a73631f1420e253b1 Mon Sep 17 00:00:00 2001 From: Isaku Yamahata Date: Sat, 23 Jul 2011 16:57:04 +0900 Subject: test_metadata: make test_metadata pass --- nova/tests/test_metadata.py | 1 + 1 file changed, 1 insertion(+) (limited to 'nova') diff --git a/nova/tests/test_metadata.py b/nova/tests/test_metadata.py index c862726ab..f81e7a00a 100644 --- a/nova/tests/test_metadata.py +++ b/nova/tests/test_metadata.py @@ -43,6 +43,7 @@ class MetadataTestCase(test.TestCase): 'reservation_id': 'r-xxxxxxxx', 'user_data': '', 'image_ref': 7, + 'root_device_name': '/dev/sda1', 'hostname': 'test'}) def instance_get(*args, **kwargs): -- cgit From 77c34f0223a21d122062b2057e9ed1584dbbf8bf Mon Sep 17 00:00:00 2001 From: Isaku Yamahata Date: Sat, 23 Jul 2011 16:57:04 +0900 Subject: nova/tests/test_compute.py: make test_compute.test_update_block_device_mapping happy --- nova/tests/test_compute.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py index c5ce18495..8f1364532 100644 --- a/nova/tests/test_compute.py +++ b/nova/tests/test_compute.py @@ -959,7 +959,8 @@ class ComputeTestCase(test.TestCase): self.assertDictListMatch(bdms, expected_result) self.compute_api._update_block_device_mapping( - self.context, instance_id, block_device_mapping) + self.context, instance_types.get_default_instance_type(), + instance_id, block_device_mapping) bdms = [self._parse_db_block_device_mapping(bdm_ref) for bdm_ref in db.block_device_mapping_get_all_by_instance( self.context, instance_id)] -- cgit From 4c1fd45270faef4b42504bb5e2b8bd3e49b14d8c Mon Sep 17 00:00:00 2001 From: Isaku Yamahata Date: Sat, 23 Jul 2011 16:57:04 +0900 Subject: tests/test_cloud:test_modify_image: make it pass --- nova/tests/test_cloud.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) (limited to 'nova') diff --git a/nova/tests/test_cloud.py b/nova/tests/test_cloud.py index 8cdc73a66..0f1dfb813 100644 --- a/nova/tests/test_cloud.py +++ b/nova/tests/test_cloud.py @@ -852,13 +852,16 @@ class CloudTestCase(test.TestCase): def test_modify_image_attribute(self): modify_image_attribute = self.cloud.modify_image_attribute + fake_metadata = {'id': 1, 'container_format': 'ami', + 'properties': {'kernel_id': 1, 'ramdisk_id': 1, + 'type': 'machine'}, 'is_public': False} + def fake_show(meh, context, id): - return {'id': 1, 'container_format': 'ami', - 'properties': {'kernel_id': 1, 'ramdisk_id': 1, - 'type': 'machine'}, 'is_public': False} + return fake_metadata def fake_update(meh, context, image_id, metadata, data=None): - return metadata + fake_metadata.update(metadata) + return fake_metadata self.stubs.Set(fake._FakeImageService, 'show', fake_show) self.stubs.Set(fake._FakeImageService, 'show_by_name', fake_show) -- cgit From 405df88f00ce71621d3fda3ec52e5cf1217c8e05 Mon Sep 17 00:00:00 2001 From: Isaku Yamahata Date: Sat, 23 Jul 2011 16:57:04 +0900 Subject: image/glance: teach glance block device mapping --- nova/image/glance.py | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) (limited to 'nova') diff --git a/nova/image/glance.py b/nova/image/glance.py index 5c2dc957b..88d3cf3af 100644 --- a/nova/image/glance.py +++ b/nova/image/glance.py @@ -19,7 +19,9 @@ from __future__ import absolute_import +import copy import datetime +import json import random from glance.common import exception as glance_exception @@ -184,6 +186,7 @@ class GlanceImageService(service.BaseImageService): """ # NOTE(vish): show is to check if image is available self.show(context, image_id) + image_meta = _convert_to_string(image_meta) try: image_meta = self.client.update_image(image_id, image_meta, data) except glance_exception.NotFound: @@ -210,12 +213,20 @@ class GlanceImageService(service.BaseImageService): """Clears out all images.""" pass + @classmethod + def _translate_to_service(cls, image_meta): + image_meta = super(GlanceImageService, + cls)._translate_to_service(image_meta) + image_meta = _convert_to_string(image_meta) + return image_meta + @classmethod def _translate_to_base(cls, image_meta): """Override translation to handle conversion to datetime objects.""" image_meta = service.BaseImageService._propertify_metadata( image_meta, cls.SERVICE_IMAGE_ATTRS) image_meta = _convert_timestamps_to_datetimes(image_meta) + image_meta = _convert_from_string(image_meta) return image_meta @@ -241,3 +252,38 @@ def _parse_glance_iso8601_timestamp(timestamp): raise ValueError(_('%(timestamp)s does not follow any of the ' 'signatures: %(ISO_FORMATS)s') % locals()) + + +# TODO(yamahata): use block-device-mapping extension to glance +def _json_loads(properties, attr): + prop = properties[attr] + if isinstance(prop, basestring): + properties[attr] = json.loads(prop) + + +def _json_dumps(properties, attr): + prop = properties[attr] + if not isinstance(prop, basestring): + properties[attr] = json.dumps(prop) + + +_CONVERT_PROPS = ('block_device_mapping', 'mappings') + + +def _convert(method, metadata): + metadata = copy.deepcopy(metadata) # don't touch original metadata + properties = metadata.get('properties') + if properties: + for attr in _CONVERT_PROPS: + if attr in properties: + method(properties, attr) + + return metadata + + +def _convert_from_string(metadata): + return _convert(_json_loads, metadata) + + +def _convert_to_string(metadata): + return _convert(_json_dumps, metadata) -- cgit From 24b6597035c4393383ed1bdc2a6e52830743a7ea Mon Sep 17 00:00:00 2001 From: Isaku Yamahata Date: Sat, 23 Jul 2011 16:57:04 +0900 Subject: db/api: fix network_get_by_cidr() User of the function is only 'nova-manage network delete'. It doesn't check deleted flag which must be checked. Otherwise some it might pick up deleted column depending on query result, and tries to delete already deleted columns and results in exception. --- nova/db/sqlalchemy/api.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index ad51f5192..abfa6a3b7 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -1681,7 +1681,9 @@ 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_by(cidr=cidr).\ + filter_by(deleted=False).\ + first() if not result: raise exception.NetworkNotFoundForCidr(cidr=cidr) -- cgit From 4960b77202aba106adb8780ea724b26d958d5c81 Mon Sep 17 00:00:00 2001 From: Isaku Yamahata Date: Sat, 23 Jul 2011 16:57:04 +0900 Subject: tests: unit tests for nova.block_device --- nova/tests/test_block_device.py | 87 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 nova/tests/test_block_device.py (limited to 'nova') diff --git a/nova/tests/test_block_device.py b/nova/tests/test_block_device.py new file mode 100644 index 000000000..b8e9b35e2 --- /dev/null +++ b/nova/tests/test_block_device.py @@ -0,0 +1,87 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 Isaku Yamahata +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +""" +Tests for Block Device utility functions. +""" + +from nova import block_device +from nova import test + + +class BlockDeviceTestCase(test.TestCase): + def test_properties(self): + root_device0 = '/dev/sda' + root_device1 = '/dev/sdb' + mappings = [{'virtual': 'root', + 'device': root_device0}] + + properties0 = {'mappings': mappings} + properties1 = {'mappings': mappings, + 'root_device_name': root_device1} + + self.assertEqual(block_device.properties_root_device_name({}), None) + self.assertEqual( + block_device.properties_root_device_name(properties0), + root_device0) + self.assertEqual( + block_device.properties_root_device_name(properties1), + root_device1) + + def test_ephemeral(self): + self.assertFalse(block_device.is_ephemeral('ephemeral')) + self.assertTrue(block_device.is_ephemeral('ephemeral0')) + self.assertTrue(block_device.is_ephemeral('ephemeral1')) + self.assertTrue(block_device.is_ephemeral('ephemeral11')) + self.assertFalse(block_device.is_ephemeral('root')) + self.assertFalse(block_device.is_ephemeral('swap')) + self.assertFalse(block_device.is_ephemeral('/dev/sda1')) + + self.assertEqual(block_device.ephemeral_num('ephemeral0'), 0) + self.assertEqual(block_device.ephemeral_num('ephemeral1'), 1) + self.assertEqual(block_device.ephemeral_num('ephemeral11'), 11) + + self.assertFalse(block_device.is_swap_or_ephemeral('ephemeral')) + self.assertTrue(block_device.is_swap_or_ephemeral('ephemeral0')) + self.assertTrue(block_device.is_swap_or_ephemeral('ephemeral1')) + self.assertTrue(block_device.is_swap_or_ephemeral('swap')) + self.assertFalse(block_device.is_swap_or_ephemeral('root')) + self.assertFalse(block_device.is_swap_or_ephemeral('/dev/sda1')) + + def test_mappings_prepend_dev(self): + mapping = [ + {'virtual': 'ami', 'device': '/dev/sda'}, + {'virtual': 'root', 'device': 'sda'}, + {'virtual': 'ephemeral0', 'device': 'sdb'}, + {'virtual': 'swap', 'device': 'sdc'}, + {'virtual': 'ephemeral1', 'device': 'sdd'}, + {'virtual': 'ephemeral2', 'device': 'sde'}] + + expected = [ + {'virtual': 'ami', 'device': '/dev/sda'}, + {'virtual': 'root', 'device': 'sda'}, + {'virtual': 'ephemeral0', 'device': '/dev/sdb'}, + {'virtual': 'swap', 'device': '/dev/sdc'}, + {'virtual': 'ephemeral1', 'device': '/dev/sdd'}, + {'virtual': 'ephemeral2', 'device': '/dev/sde'}] + + prepended = block_device.mappings_prepend_dev(mapping) + self.assertEqual(prepended.sort(), expected.sort()) + + def test_strip_dev(self): + self.assertEqual(block_device.strip_dev('/dev/sda'), 'sda') + self.assertEqual(block_device.strip_dev('sda'), 'sda') -- cgit From 3af916ba0d87d383a89250b3aac4cf5e5b728f69 Mon Sep 17 00:00:00 2001 From: Isaku Yamahata Date: Sat, 23 Jul 2011 16:57:05 +0900 Subject: tests: unit tests for nova.virt --- nova/tests/test_virt.py | 83 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 nova/tests/test_virt.py (limited to 'nova') diff --git a/nova/tests/test_virt.py b/nova/tests/test_virt.py new file mode 100644 index 000000000..388f075af --- /dev/null +++ b/nova/tests/test_virt.py @@ -0,0 +1,83 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 Isaku Yamahata +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from nova import flags +from nova import test +from nova.virt import driver + +FLAGS = flags.FLAGS + + +class TestVirtDriver(test.TestCase): + def test_block_device(self): + swap = {'device_name': '/dev/sdb', + 'swap_size': 1} + ephemerals = [{'num': 0, + 'virtual_name': 'ephemeral0', + 'device_name': '/dev/sdc1', + 'size': 1}] + block_device_mapping = [{'mount_device': '/dev/sde', + 'device_path': 'fake_device'}] + block_device_info = { + 'root_device_name': '/dev/sda', + 'swap': swap, + 'ephemerals': ephemerals, + 'block_device_mapping': block_device_mapping} + + empty_block_device_info = {} + + self.assertEqual( + driver.block_device_info_get_root(block_device_info), '/dev/sda') + self.assertEqual( + driver.block_device_info_get_root(empty_block_device_info), None) + self.assertEqual( + driver.block_device_info_get_root(None), None) + + self.assertEqual( + driver.block_device_info_get_swap(block_device_info), swap) + self.assertEqual(driver.block_device_info_get_swap( + empty_block_device_info)['device_name'], None) + self.assertEqual(driver.block_device_info_get_swap( + empty_block_device_info)['swap_size'], 0) + self.assertEqual( + driver.block_device_info_get_swap({'swap': None})['device_name'], + None) + self.assertEqual( + driver.block_device_info_get_swap({'swap': None})['swap_size'], + 0) + self.assertEqual( + driver.block_device_info_get_swap(None)['device_name'], None) + self.assertEqual( + driver.block_device_info_get_swap(None)['swap_size'], 0) + + self.assertEqual( + driver.block_device_info_get_ephemerals(block_device_info), + ephemerals) + self.assertEqual( + driver.block_device_info_get_ephemerals(empty_block_device_info), + []) + self.assertEqual( + driver.block_device_info_get_ephemerals(None), + []) + + def test_swap_is_usable(self): + self.assertFalse(driver.swap_is_usable(None)) + self.assertFalse(driver.swap_is_usable({'device_name': None})) + self.assertFalse(driver.swap_is_usable({'device_name': '/dev/sdb', + 'swap_size': 0})) + self.assertTrue(driver.swap_is_usable({'device_name': '/dev/sdb', + 'swap_size': 1})) -- cgit From ba6404f05d9fb34a729d45e1ee055c7a7156c5c4 Mon Sep 17 00:00:00 2001 From: Isaku Yamahata Date: Sat, 23 Jul 2011 16:57:05 +0900 Subject: tests/glance: unit tests for glance serializer --- nova/tests/image/test_glance.py | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) (limited to 'nova') diff --git a/nova/tests/image/test_glance.py b/nova/tests/image/test_glance.py index 223e7ae57..488b03f9c 100644 --- a/nova/tests/image/test_glance.py +++ b/nova/tests/image/test_glance.py @@ -232,3 +232,39 @@ class TestMutatorDateTimeTests(BaseGlanceTest): 'updated_at': None, 'deleted_at': None} return fixture + + +class TestGlanceSerializer(unittest.TestCase): + def test_serialize(self): + metadata = {'name': 'image1', + 'is_public': True, + 'foo': 'bar', + 'properties': { + 'prop1': 'propvalue1', + 'mappings': [ + {'virtual': 'aaa', + 'device': 'bbb'}, + {'virtual': 'xxx', + 'device': 'yyy'}], + 'block_device_mapping': [ + {'virtual_device': 'fake', + 'device_name': '/dev/fake'}, + {'virtual_device': 'ephemeral0', + 'device_name': '/dev/fake0'}]}} + + converted_expected = { + 'name': 'image1', + 'is_public': True, + 'foo': 'bar', + 'properties': { + 'prop1': 'propvalue1', + 'mappings': + '[{"device": "bbb", "virtual": "aaa"}, ' + '{"device": "yyy", "virtual": "xxx"}]', + 'block_device_mapping': + '[{"virtual_device": "fake", "device_name": "/dev/fake"}, ' + '{"virtual_device": "ephemeral0", ' + '"device_name": "/dev/fake0"}]'}} + converted = glance._convert_to_string(metadata) + self.assertEqual(converted, converted_expected) + self.assertEqual(glance._convert_from_string(converted), metadata) -- cgit From 5113f78ddb8d7ccecea4e4ec8cbf35765af46d40 Mon Sep 17 00:00:00 2001 From: Isaku Yamahata Date: Sat, 23 Jul 2011 16:57:05 +0900 Subject: tests: unit tests for nova.virt.libvirt.connection._volume_in_mapping() --- nova/tests/test_libvirt.py | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) (limited to 'nova') diff --git a/nova/tests/test_libvirt.py b/nova/tests/test_libvirt.py index 2a21d0d32..0c198f1b5 100644 --- a/nova/tests/test_libvirt.py +++ b/nova/tests/test_libvirt.py @@ -786,6 +786,42 @@ class LibvirtConnTestCase(test.TestCase): ip = conn.get_host_ip_addr() self.assertEquals(ip, FLAGS.my_ip) + def test_volume_in_mapping(self): + conn = connection.LibvirtConnection(False) + swap = {'device_name': '/dev/sdb', + 'swap_size': 1} + ephemerals = [{'num': 0, + 'virtual_name': 'ephemeral0', + 'device_name': '/dev/sdc1', + 'size': 1}, + {'num': 2, + 'virtual_name': 'ephemeral2', + 'device_name': '/dev/sdd', + 'size': 1}] + block_device_mapping = [{'mount_device': '/dev/sde', + 'device_path': 'fake_device'}, + {'mount_device': '/dev/sdf', + 'device_path': 'fake_device'}] + block_device_info = { + 'root_device_name': '/dev/sda', + 'swap': swap, + 'ephemerals': ephemerals, + 'block_device_mapping': block_device_mapping} + + def _assert_volume_in_mapping(device_name, true_or_false): + self.assertEquals(conn._volume_in_mapping(device_name, + block_device_info), + true_or_false) + + _assert_volume_in_mapping('sda', False) + _assert_volume_in_mapping('sdb', True) + _assert_volume_in_mapping('sdc1', True) + _assert_volume_in_mapping('sdd', True) + _assert_volume_in_mapping('sde', True) + _assert_volume_in_mapping('sdf', True) + _assert_volume_in_mapping('sdg', False) + _assert_volume_in_mapping('sdh1', False) + class NWFilterFakes: def __init__(self): -- cgit From 142a95a223a4259bcb3b35087b6d24f8310e3fa6 Mon Sep 17 00:00:00 2001 From: Isaku Yamahata Date: Sat, 23 Jul 2011 16:57:05 +0900 Subject: tests: an unit test for nova.compute.api.API._ephemeral_size() --- nova/tests/test_compute.py | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'nova') diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py index 8f1364532..32f55c6e2 100644 --- a/nova/tests/test_compute.py +++ b/nova/tests/test_compute.py @@ -990,3 +990,13 @@ class ComputeTestCase(test.TestCase): self.context, instance_id): db.block_device_mapping_destroy(self.context, bdm['id']) self.compute.terminate_instance(self.context, instance_id) + + def test_ephemeral_size(self): + local_size = 2 + inst_type = {'local_gb': local_size} + self.assertEqual(self.compute_api._ephemeral_size(inst_type, + 'ephemeral0'), + local_size) + self.assertEqual(self.compute_api._ephemeral_size(inst_type, + 'ephemeral1'), + 0) -- cgit From 916231fd945c5e726a21decdf1b6370b2fcefe70 Mon Sep 17 00:00:00 2001 From: Isaku Yamahata Date: Sat, 23 Jul 2011 16:57:05 +0900 Subject: tests: unit tests for describe instance attribute --- nova/tests/test_cloud.py | 144 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 144 insertions(+) (limited to 'nova') diff --git a/nova/tests/test_cloud.py b/nova/tests/test_cloud.py index 0f1dfb813..507b35d22 100644 --- a/nova/tests/test_cloud.py +++ b/nova/tests/test_cloud.py @@ -17,6 +17,8 @@ # under the License. import mox +import functools + from base64 import b64decode from M2Crypto import BIO from M2Crypto import RSA @@ -1438,3 +1440,145 @@ class CloudTestCase(test.TestCase): # TODO(yamahata): clean up snapshot created by CreateImage. self._restart_compute_service() + + @staticmethod + def _fake_bdm_get(ctxt, id): + return [{'volume_id': 87654321, + 'snapshot_id': None, + 'no_device': None, + 'virtual_name': None, + 'delete_on_termination': True, + 'device_name': '/dev/sdh'}, + {'volume_id': None, + 'snapshot_id': 98765432, + 'no_device': None, + 'virtual_name': None, + 'delete_on_termination': True, + 'device_name': '/dev/sdi'}, + {'volume_id': None, + 'snapshot_id': None, + 'no_device': True, + 'virtual_name': None, + 'delete_on_termination': None, + 'device_name': None}, + {'volume_id': None, + 'snapshot_id': None, + 'no_device': None, + 'virtual_name': 'ephemeral0', + 'delete_on_termination': None, + 'device_name': '/dev/sdb'}, + {'volume_id': None, + 'snapshot_id': None, + 'no_device': None, + 'virtual_name': 'swap', + 'delete_on_termination': None, + 'device_name': '/dev/sdc'}, + {'volume_id': None, + 'snapshot_id': None, + 'no_device': None, + 'virtual_name': 'ephemeral1', + 'delete_on_termination': None, + 'device_name': '/dev/sdd'}, + {'volume_id': None, + 'snapshot_id': None, + 'no_device': None, + 'virtual_name': 'ephemeral2', + 'delete_on_termination': None, + 'device_name': '/dev/sd3'}, + ] + + def test_get_instance_mapping(self): + """Make sure that _get_instance_mapping works""" + ctxt = None + instance_ref0 = {'id': 0, + 'root_device_name': None} + instance_ref1 = {'id': 0, + 'root_device_name': '/dev/sda1'} + + self.stubs.Set(db, 'block_device_mapping_get_all_by_instance', + self._fake_bdm_get) + + expected = {'ami': 'sda1', + 'root': '/dev/sda1', + 'ephemeral0': '/dev/sdb', + 'swap': '/dev/sdc', + 'ephemeral1': '/dev/sdd', + 'ephemeral2': '/dev/sd3'} + + self.assertEqual(self.cloud._get_instance_mapping(ctxt, instance_ref0), + cloud._DEFAULT_MAPPINGS) + self.assertEqual(self.cloud._get_instance_mapping(ctxt, instance_ref1), + expected) + + def test_describe_instance_attribute(self): + """Make sure that describe_instance_attribute works""" + self.stubs.Set(db, 'block_device_mapping_get_all_by_instance', + self._fake_bdm_get) + + def fake_get(ctxt, instance_id): + return { + 'id': 0, + 'root_device_name': '/dev/sdh', + 'security_groups': [{'name': 'fake0'}, {'name': 'fake1'}], + 'state_description': 'stopping', + 'instance_type': {'name': 'fake_type'}, + 'kernel_id': 1, + 'ramdisk_id': 2, + 'user_data': 'fake-user data', + } + self.stubs.Set(self.cloud.compute_api, 'get', fake_get) + + def fake_volume_get(ctxt, volume_id, session=None): + if volume_id == 87654321: + return {'id': volume_id, + 'attach_time': '13:56:24', + 'status': 'in-use'} + raise exception.VolumeNotFound(volume_id=volume_id) + self.stubs.Set(db.api, 'volume_get', fake_volume_get) + + get_attribute = functools.partial( + self.cloud.describe_instance_attribute, + self.context, 'i-12345678') + + bdm = get_attribute('blockDeviceMapping') + bdm['blockDeviceMapping'].sort() + + expected_bdm = {'instance_id': 'i-12345678', + 'rootDeviceType': 'ebs', + 'blockDeviceMapping': [ + {'deviceName': '/dev/sdh', + 'ebs': {'status': 'in-use', + 'deleteOnTermination': True, + 'volumeId': 87654321, + 'attachTime': '13:56:24'}}]} + expected_bdm['blockDeviceMapping'].sort() + self.assertEqual(bdm, expected_bdm) + # NOTE(yamahata): this isn't supported + # get_attribute('disableApiTermination') + groupSet = get_attribute('groupSet') + groupSet['groupSet'].sort() + expected_groupSet = {'instance_id': 'i-12345678', + 'groupSet': [{'groupId': 'fake0'}, + {'groupId': 'fake1'}]} + expected_groupSet['groupSet'].sort() + self.assertEqual(groupSet, expected_groupSet) + self.assertEqual(get_attribute('instanceInitiatedShutdownBehavior'), + {'instance_id': 'i-12345678', + 'instanceInitiatedShutdownBehavior': 'stop'}) + self.assertEqual(get_attribute('instanceType'), + {'instance_id': 'i-12345678', + 'instanceType': 'fake_type'}) + self.assertEqual(get_attribute('kernel'), + {'instance_id': 'i-12345678', + 'kernel': 'aki-00000001'}) + self.assertEqual(get_attribute('ramdisk'), + {'instance_id': 'i-12345678', + 'ramdisk': 'ari-00000002'}) + self.assertEqual(get_attribute('rootDeviceName'), + {'instance_id': 'i-12345678', + 'rootDeviceName': '/dev/sdh'}) + # NOTE(yamahata): this isn't supported + # get_attribute('sourceDestCheck') + self.assertEqual(get_attribute('userData'), + {'instance_id': 'i-12345678', + 'userData': '}\xa9\x1e\xba\xc7\xabu\xabZ'}) -- cgit From 4937c2f2c757776eacba20a6446c059c4938d6b8 Mon Sep 17 00:00:00 2001 From: Nikolay Sokolov Date: Mon, 25 Jul 2011 11:06:06 +0400 Subject: Removed driver-specific autostart code. --- nova/virt/libvirt/connection.py | 5 ----- 1 file changed, 5 deletions(-) (limited to 'nova') diff --git a/nova/virt/libvirt/connection.py b/nova/virt/libvirt/connection.py index d85b91ee2..65a6b6393 100644 --- a/nova/virt/libvirt/connection.py +++ b/nova/virt/libvirt/connection.py @@ -571,11 +571,6 @@ class LibvirtConnection(driver.ComputeDriver): LOG.debug(_("instance %s: is running"), instance['name']) self.firewall_driver.apply_instance_filter(instance) - if FLAGS.start_guests_on_host_boot: - LOG.debug(_("instance %s: setting autostart ON") % - instance['name']) - domain.setAutostart(1) - def _wait_for_boot(): """Called at an interval until the VM is running.""" instance_name = instance['name'] -- cgit From 708b0cb65a672e9f6b8bab4817061be4fa2a8928 Mon Sep 17 00:00:00 2001 From: Nikolay Sokolov Date: Mon, 25 Jul 2011 14:22:38 +0400 Subject: Fixed init_host context name. --- nova/compute/manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index c7d3004a5..b876e87d0 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -165,7 +165,7 @@ class ComputeManager(manager.SchedulerDependentManager): LOG.debug(_('Rebooting instance %(name)s after nova-compute restart.')) self.reboot_instance(admin_context, instance[id]) else: - self.db.instance_set_state(ctxt, instance['id'], state) + self.db.instance_set_state(admin_context, instance['id'], state) def _update_state(self, context, instance_id, state=None): """Update the state of an instance from the driver info.""" -- cgit From d8f4d773b08a94b171ff2643d48daa5b2709118a Mon Sep 17 00:00:00 2001 From: Nikolay Sokolov Date: Mon, 25 Jul 2011 14:33:45 +0400 Subject: Fixed id. --- nova/compute/manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index b876e87d0..b7e54d903 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -163,7 +163,7 @@ class ComputeManager(manager.SchedulerDependentManager): if instance['state'] == power_state.RUNNING and state != power_state.RUNNING \ and FLAGS.start_guests_on_host_boot: LOG.debug(_('Rebooting instance %(name)s after nova-compute restart.')) - self.reboot_instance(admin_context, instance[id]) + self.reboot_instance(admin_context, instance['id']) else: self.db.instance_set_state(admin_context, instance['id'], state) -- cgit From 19379c78e6efd4637d876c91b022e6e7dbd38836 Mon Sep 17 00:00:00 2001 From: Nikolay Sokolov Date: Mon, 25 Jul 2011 14:37:21 +0400 Subject: Fixed logging. --- nova/compute/manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index b7e54d903..071eabcfe 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -162,7 +162,7 @@ class ComputeManager(manager.SchedulerDependentManager): if instance['state'] == power_state.RUNNING and state != power_state.RUNNING \ and FLAGS.start_guests_on_host_boot: - LOG.debug(_('Rebooting instance %(name)s after nova-compute restart.')) + LOG.debug(_('Rebooting instance %(name)s after nova-compute restart.'), {'name': instance['name']}) self.reboot_instance(admin_context, instance['id']) else: self.db.instance_set_state(admin_context, instance['id'], state) -- cgit From cf4aea379eb337b16a9816d45c50c0553c500d0d Mon Sep 17 00:00:00 2001 From: Nikolay Sokolov Date: Mon, 25 Jul 2011 15:03:02 +0400 Subject: pep8 --- nova/compute/manager.py | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 071eabcfe..ebd91177f 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -150,22 +150,29 @@ class ComputeManager(manager.SchedulerDependentManager): from nova import context self.driver.init_host(host=self.host) admin_context = context.get_admin_context() - for instance in self.db.instance_get_all_by_host(admin_context, self.host): + for instance in self.db.instance_get_all_by_host(admin_context, + self.host): try: LOG.debug(_('Checking state of %s'), instance['name']) state = self.driver.get_info(instance['name'])['state'] except exception.NotFound: state = power_state.SHUTOFF - LOG.debug(_('Current state of %(name)s is %(state)s, state in DB is %(db_state)s.'), - {'name': instance['name'], 'state': state, 'db_state': instance['state']}) - - if instance['state'] == power_state.RUNNING and state != power_state.RUNNING \ - and FLAGS.start_guests_on_host_boot: - LOG.debug(_('Rebooting instance %(name)s after nova-compute restart.'), {'name': instance['name']}) + LOG.debug(_('Current state of %(name)s is %(state)s, state in ' + 'DB is %(db_state)s.'), {'name': instance['name'], + 'state': state, + 'db_state': + instance['state']}) + + if instance['state'] == power_state.RUNNING \ + and state != power_state.RUNNING \ + and FLAGS.start_guests_on_host_boot: + LOG.debug(_('Rebooting instance %(name)s after nova-compute ' + ' restart.'), {'name': instance['name']}) self.reboot_instance(admin_context, instance['id']) else: - self.db.instance_set_state(admin_context, instance['id'], state) + self.db.instance_set_state(admin_context, instance['id'], + state) def _update_state(self, context, instance_id, state=None): """Update the state of an instance from the driver info.""" -- cgit From d85a43c4cdb1bfd28355ded486af2ded8f43d6b0 Mon Sep 17 00:00:00 2001 From: Nikolay Sokolov Date: Mon, 25 Jul 2011 19:54:23 +0400 Subject: Some estetic refactoring. --- nova/compute/manager.py | 48 +++++++++++++++++++++--------------------------- 1 file changed, 21 insertions(+), 27 deletions(-) (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index ebd91177f..950fe0d53 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -44,6 +44,7 @@ import functools from eventlet import greenthread +import nova.context from nova import exception from nova import flags import nova.image @@ -145,34 +146,26 @@ class ComputeManager(manager.SchedulerDependentManager): *args, **kwargs) def init_host(self): - """Initialization for a standalone compute service.""" - # NOTE(nsokolov): based on itoumsn's implementation from libvirt driver - from nova import context + """Initialization for a standalone compute service. + + Reboots instances marked as running in DB if they is not running.""" self.driver.init_host(host=self.host) - admin_context = context.get_admin_context() - for instance in self.db.instance_get_all_by_host(admin_context, - self.host): - try: - LOG.debug(_('Checking state of %s'), instance['name']) - state = self.driver.get_info(instance['name'])['state'] - except exception.NotFound: - state = power_state.SHUTOFF - - LOG.debug(_('Current state of %(name)s is %(state)s, state in ' - 'DB is %(db_state)s.'), {'name': instance['name'], - 'state': state, - 'db_state': - instance['state']}) - - if instance['state'] == power_state.RUNNING \ - and state != power_state.RUNNING \ - and FLAGS.start_guests_on_host_boot: - LOG.debug(_('Rebooting instance %(name)s after nova-compute ' - ' restart.'), {'name': instance['name']}) - self.reboot_instance(admin_context, instance['id']) - else: - self.db.instance_set_state(admin_context, instance['id'], - state) + context = nova.context.get_admin_context() + instances = self.db.instance_get_all_by_host(context, self.host) + for instance in instances: + inst_name = instance['name'] + db_state = instance['state'] + drv_state = self._update_state(context, instance['id']) + + expect_running = db_state == power_state.RUNNING != drv_state + + LOG.debug(_('Current state of %(inst_name)s is %(drv_state)s, ' + 'state in DB is %(db_state)s.'), locals()) + + if expect_running and FLAGS.start_guests_on_host_boot: + LOG.info(_('Rebooting instance %(inst_name)s after ' + 'nova-compute restart.'), locals()) + self.reboot_instance(context, instance['id']) def _update_state(self, context, instance_id, state=None): """Update the state of an instance from the driver info.""" @@ -180,6 +173,7 @@ class ComputeManager(manager.SchedulerDependentManager): if state is None: try: + LOG.debug(_('Checking state of %s'), instance_ref['name']) info = self.driver.get_info(instance_ref['name']) except exception.NotFound: info = None -- cgit From 9c88bbee56dd05703af8f7c0df839a4da73f491a Mon Sep 17 00:00:00 2001 From: Nikolay Sokolov Date: Mon, 25 Jul 2011 19:59:00 +0400 Subject: Hotfix. --- nova/compute/manager.py | 1 + 1 file changed, 1 insertion(+) (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 950fe0d53..79e9b16d3 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -184,6 +184,7 @@ class ComputeManager(manager.SchedulerDependentManager): state = power_state.FAILED self.db.instance_set_state(context, instance_id, state) + return state def _update_launched_at(self, context, instance_id, launched_at=None): """Update the launched_at parameter of the given instance.""" -- cgit From 8de3c0fcaee546fae3d415ef5ddcbb51fb1db6d7 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Mon, 25 Jul 2011 17:49:09 +0000 Subject: fix for reviews --- nova/api/openstack/create_instance_helper.py | 2 ++ nova/compute/api.py | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index 2034e8ada..573153f68 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -77,6 +77,8 @@ class CreateInstanceHelper(object): key_name = None key_data = None + # TODO(vish): Key pair access should move into a common library + # instead of being accessed directly from the db. key_pairs = db.key_pair_get_all_by_user(context.elevated(), context.user_id) if key_pairs: diff --git a/nova/compute/api.py b/nova/compute/api.py index d1c3fd6fd..487d23b0d 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -676,7 +676,6 @@ class API(base.Base): all instances in the system. """ - LOG.info(locals()) if reservation_id is not None: recurse_zones = True instances = self.db.instance_get_all_by_reservation( -- 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 ba4946d0d3c73e5d9f67f42203d103bf98563458 Mon Sep 17 00:00:00 2001 From: Nikolay Sokolov Date: Tue, 26 Jul 2011 00:31:42 +0400 Subject: Fixed old libvirt semantics, added resume_guests_state_on_host_boot flag. --- nova/compute/manager.py | 9 ++++----- nova/flags.py | 2 ++ 2 files changed, 6 insertions(+), 5 deletions(-) (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 79e9b16d3..bfac3df28 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -146,9 +146,7 @@ class ComputeManager(manager.SchedulerDependentManager): *args, **kwargs) def init_host(self): - """Initialization for a standalone compute service. - - Reboots instances marked as running in DB if they is not running.""" + """Initialization for a standalone compute service.""" self.driver.init_host(host=self.host) context = nova.context.get_admin_context() instances = self.db.instance_get_all_by_host(context, self.host) @@ -157,12 +155,13 @@ class ComputeManager(manager.SchedulerDependentManager): db_state = instance['state'] drv_state = self._update_state(context, instance['id']) - expect_running = db_state == power_state.RUNNING != drv_state + expect_running = (db_state == power_state.RUNNING != drv_state) LOG.debug(_('Current state of %(inst_name)s is %(drv_state)s, ' 'state in DB is %(db_state)s.'), locals()) - if expect_running and FLAGS.start_guests_on_host_boot: + if (expect_running and FLAGS.resume_guests_state_on_host_boot)\ + or FLAGS.start_guests_on_host_boot: LOG.info(_('Rebooting instance %(inst_name)s after ' 'nova-compute restart.'), locals()) self.reboot_instance(context, instance['id']) diff --git a/nova/flags.py b/nova/flags.py index 23ca38b17..6c7e448ad 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -390,3 +390,5 @@ DEFINE_string('build_plan_encryption_key', None, DEFINE_bool('start_guests_on_host_boot', False, 'Whether to restart guests when the host reboots') +DEFINE_bool('resume_guests_state_on_host_boot', False, + 'Whether to start guests, that was running before the host reboot') -- cgit From 879e49c67b78d3336a24cf1af12f21258c2225fa Mon Sep 17 00:00:00 2001 From: Nikolay Sokolov Date: Tue, 26 Jul 2011 01:46:40 +0400 Subject: Estetic fix. --- nova/compute/manager.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index bfac3df28..a6eb1efb2 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -155,7 +155,8 @@ class ComputeManager(manager.SchedulerDependentManager): db_state = instance['state'] drv_state = self._update_state(context, instance['id']) - expect_running = (db_state == power_state.RUNNING != drv_state) + expect_running = db_state == power_state.RUNNING \ + and drv_state != db_state LOG.debug(_('Current state of %(inst_name)s is %(drv_state)s, ' 'state in DB is %(db_state)s.'), locals()) -- 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 d4842ac958bda3b446d14c7348692acc231e0041 Mon Sep 17 00:00:00 2001 From: Nikolay Sokolov Date: Tue, 26 Jul 2011 02:30:20 +0400 Subject: Added ensuring filter rules for all VMs. --- nova/compute/manager.py | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index a6eb1efb2..53390098a 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -166,6 +166,11 @@ class ComputeManager(manager.SchedulerDependentManager): LOG.info(_('Rebooting instance %(inst_name)s after ' 'nova-compute restart.'), locals()) self.reboot_instance(context, instance['id']) + elif drv_state == power_state.RUNNING: + try: # Hyper-V and VMWareAPI drivers will raise and exception + self.driver.ensure_filtering_rules_for_instance(instance) + except NotImplementedError: + pass def _update_state(self, context, instance_id, state=None): """Update the state of an instance from the driver info.""" -- cgit From 8f5a6d15e671c95c6e38147ca15fb49fd672e788 Mon Sep 17 00:00:00 2001 From: Nikolay Sokolov Date: Tue, 26 Jul 2011 02:33:39 +0400 Subject: Warn user instead of ignoring --- nova/compute/manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 53390098a..1f207e6f0 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -170,7 +170,7 @@ class ComputeManager(manager.SchedulerDependentManager): try: # Hyper-V and VMWareAPI drivers will raise and exception self.driver.ensure_filtering_rules_for_instance(instance) except NotImplementedError: - pass + LOG.warning(_('Hypervisor driver does not support firewall rules')) def _update_state(self, context, instance_id, state=None): """Update the state of an instance from the driver info.""" -- 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 534b8c3c5b2f6eb3d4c3545c3d5dc2d15061cf6e Mon Sep 17 00:00:00 2001 From: Zed Shaw Date: Tue, 26 Jul 2011 16:29:50 -0700 Subject: Implements a simplified messaging abstraction with the least amount of impact to the code base. --- nova/rpc.py | 598 ++---------------------------------------- nova/rpc_backends/__init__.py | 0 nova/rpc_backends/amqp.py | 591 +++++++++++++++++++++++++++++++++++++++++ nova/rpc_backends/common.py | 23 ++ nova/service.py | 28 +- nova/test.py | 16 -- nova/tests/test_adminapi.py | 2 +- nova/tests/test_cloud.py | 68 +---- nova/tests/test_rpc.py | 61 +---- nova/tests/test_rpc_amqp.py | 68 +++++ nova/tests/test_service.py | 170 ------------ nova/tests/test_test.py | 13 +- nova/utils.py | 11 + 13 files changed, 760 insertions(+), 889 deletions(-) create mode 100644 nova/rpc_backends/__init__.py create mode 100644 nova/rpc_backends/amqp.py create mode 100644 nova/rpc_backends/common.py create mode 100644 nova/tests/test_rpc_amqp.py (limited to 'nova') diff --git a/nova/rpc.py b/nova/rpc.py index e2771ca88..8b0c6df67 100644 --- a/nova/rpc.py +++ b/nova/rpc.py @@ -16,597 +16,51 @@ # License for the specific language governing permissions and limitations # under the License. -"""AMQP-based RPC. -Queues have consumers and publishers. - -No fan-out support yet. - -""" - -import json -import sys -import time -import traceback -import types -import uuid - -from carrot import connection as carrot_connection -from carrot import messaging -from eventlet import greenpool -from eventlet import pools -from eventlet import queue -import greenlet - -from nova import context -from nova import exception -from nova import fakerabbit +from nova.utils import load_module +from nova.rpc_backends.common import RemoteError, LOG from nova import flags -from nova import log as logging -from nova import utils - - -LOG = logging.getLogger('nova.rpc') - FLAGS = flags.FLAGS -flags.DEFINE_integer('rpc_thread_pool_size', 1024, - 'Size of RPC thread pool') -flags.DEFINE_integer('rpc_conn_pool_size', 30, - 'Size of RPC connection pool') - - -class Connection(carrot_connection.BrokerConnection): - """Connection instance object.""" - - @classmethod - def instance(cls, new=True): - """Returns the instance.""" - if new or not hasattr(cls, '_instance'): - params = dict(hostname=FLAGS.rabbit_host, - port=FLAGS.rabbit_port, - ssl=FLAGS.rabbit_use_ssl, - userid=FLAGS.rabbit_userid, - password=FLAGS.rabbit_password, - virtual_host=FLAGS.rabbit_virtual_host) - - if FLAGS.fake_rabbit: - params['backend_cls'] = fakerabbit.Backend - - # NOTE(vish): magic is fun! - # pylint: disable=W0142 - if new: - return cls(**params) - else: - cls._instance = cls(**params) - return cls._instance - - @classmethod - def recreate(cls): - """Recreates the connection instance. - - This is necessary to recover from some network errors/disconnects. - - """ - try: - del cls._instance - except AttributeError, e: - # The _instance stuff is for testing purposes. Usually we don't use - # it. So don't freak out if it doesn't exist. - pass - return cls.instance() - - -class Pool(pools.Pool): - """Class that implements a Pool of Connections.""" - - # TODO(comstud): Timeout connections not used in a while - def create(self): - LOG.debug('Creating new connection') - return Connection.instance(new=True) - -# Create a ConnectionPool to use for RPC calls. We'll order the -# pool as a stack (LIFO), so that we can potentially loop through and -# timeout old unused connections at some point -ConnectionPool = Pool( - max_size=FLAGS.rpc_conn_pool_size, - order_as_stack=True) - - -class Consumer(messaging.Consumer): - """Consumer base class. - - Contains methods for connecting the fetch method to async loops. - - """ - - def __init__(self, *args, **kwargs): - for i in xrange(FLAGS.rabbit_max_retries): - if i > 0: - time.sleep(FLAGS.rabbit_retry_interval) - try: - super(Consumer, self).__init__(*args, **kwargs) - self.failed_connection = False - break - except Exception as e: # Catching all because carrot sucks - fl_host = FLAGS.rabbit_host - fl_port = FLAGS.rabbit_port - fl_intv = FLAGS.rabbit_retry_interval - LOG.error(_('AMQP server on %(fl_host)s:%(fl_port)d is' - ' unreachable: %(e)s. Trying again in %(fl_intv)d' - ' seconds.') % locals()) - self.failed_connection = True - if self.failed_connection: - LOG.error(_('Unable to connect to AMQP server ' - 'after %d tries. Shutting down.'), - FLAGS.rabbit_max_retries) - sys.exit(1) - - def fetch(self, no_ack=None, auto_ack=None, enable_callbacks=False): - """Wraps the parent fetch with some logic for failed connection.""" - # TODO(vish): the logic for failed connections and logging should be - # refactored into some sort of connection manager object - try: - if self.failed_connection: - # NOTE(vish): connection is defined in the parent class, we can - # recreate it as long as we create the backend too - # pylint: disable=W0201 - self.connection = Connection.recreate() - self.backend = self.connection.create_backend() - self.declare() - return super(Consumer, self).fetch(no_ack, - auto_ack, - enable_callbacks) - if self.failed_connection: - LOG.error(_('Reconnected to queue')) - self.failed_connection = False - # NOTE(vish): This is catching all errors because we really don't - # want exceptions to be logged 10 times a second if some - # persistent failure occurs. - except Exception, e: # pylint: disable=W0703 - if not self.failed_connection: - LOG.exception(_('Failed to fetch message from queue: %s' % e)) - self.failed_connection = True - - def attach_to_eventlet(self): - """Only needed for unit tests!""" - timer = utils.LoopingCall(self.fetch, enable_callbacks=True) - timer.start(0.1) - return timer - - -class AdapterConsumer(Consumer): - """Calls methods on a proxy object based on method and args.""" - - def __init__(self, connection=None, topic='broadcast', proxy=None): - LOG.debug(_('Initing the Adapter Consumer for %s') % topic) - self.proxy = proxy - self.pool = greenpool.GreenPool(FLAGS.rpc_thread_pool_size) - super(AdapterConsumer, self).__init__(connection=connection, - topic=topic) - self.register_callback(self.process_data) - - def process_data(self, message_data, message): - """Consumer callback to call a method on a proxy object. - - Parses the message for validity and fires off a thread to call the - proxy object method. - - Message data should be a dictionary with two keys: - method: string representing the method to call - args: dictionary of arg: value - - Example: {'method': 'echo', 'args': {'value': 42}} - - """ - LOG.debug(_('received %s') % message_data) - # This will be popped off in _unpack_context - msg_id = message_data.get('_msg_id', None) - ctxt = _unpack_context(message_data) - - method = message_data.get('method') - args = message_data.get('args', {}) - message.ack() - if not method: - # NOTE(vish): we may not want to ack here, but that means that bad - # messages stay in the queue indefinitely, so for now - # we just log the message and send an error string - # back to the caller - LOG.warn(_('no method for message: %s') % message_data) - if msg_id: - msg_reply(msg_id, - _('No method for message: %s') % message_data) - return - self.pool.spawn_n(self._process_data, msg_id, ctxt, method, args) - - @exception.wrap_exception() - def _process_data(self, msg_id, ctxt, method, args): - """Thread that maigcally looks for a method on the proxy - object and calls it. - """ - - node_func = getattr(self.proxy, str(method)) - node_args = dict((str(k), v) for k, v in args.iteritems()) - # NOTE(vish): magic is fun! - try: - rval = node_func(context=ctxt, **node_args) - if msg_id: - # Check if the result was a generator - if isinstance(rval, types.GeneratorType): - for x in rval: - msg_reply(msg_id, x, None) - else: - msg_reply(msg_id, rval, None) - - # This final None tells multicall that it is done. - msg_reply(msg_id, None, None) - elif isinstance(rval, types.GeneratorType): - # NOTE(vish): this iterates through the generator - list(rval) - except Exception as e: - logging.exception('Exception during message handling') - if msg_id: - msg_reply(msg_id, None, sys.exc_info()) - return - - -class TopicAdapterConsumer(AdapterConsumer): - """Consumes messages on a specific topic.""" - - exchange_type = 'topic' - - def __init__(self, connection=None, topic='broadcast', proxy=None): - self.queue = topic - self.routing_key = topic - self.exchange = FLAGS.control_exchange - self.durable = False - super(TopicAdapterConsumer, self).__init__(connection=connection, - topic=topic, proxy=proxy) - - -class FanoutAdapterConsumer(AdapterConsumer): - """Consumes messages from a fanout exchange.""" - - exchange_type = 'fanout' - - def __init__(self, connection=None, topic='broadcast', proxy=None): - self.exchange = '%s_fanout' % topic - self.routing_key = topic - unique = uuid.uuid4().hex - self.queue = '%s_fanout_%s' % (topic, unique) - self.durable = False - # Fanout creates unique queue names, so we should auto-remove - # them when done, so they're not left around on restart. - # Also, we're the only one that should be consuming. exclusive - # implies auto_delete, so we'll just set that.. - self.exclusive = True - LOG.info(_('Created "%(exchange)s" fanout exchange ' - 'with "%(key)s" routing key'), - dict(exchange=self.exchange, key=self.routing_key)) - super(FanoutAdapterConsumer, self).__init__(connection=connection, - topic=topic, proxy=proxy) - - -class ConsumerSet(object): - """Groups consumers to listen on together on a single connection.""" - - def __init__(self, connection, consumer_list): - self.consumer_list = set(consumer_list) - self.consumer_set = None - self.enabled = True - self.init(connection) - - def init(self, conn): - if not conn: - conn = Connection.instance(new=True) - if self.consumer_set: - self.consumer_set.close() - self.consumer_set = messaging.ConsumerSet(conn) - for consumer in self.consumer_list: - consumer.connection = conn - # consumer.backend is set for us - self.consumer_set.add_consumer(consumer) - - def reconnect(self): - self.init(None) - - def wait(self, limit=None): - running = True - while running: - it = self.consumer_set.iterconsume(limit=limit) - if not it: - break - while True: - try: - it.next() - except StopIteration: - return - except greenlet.GreenletExit: - running = False - break - except Exception as e: - LOG.exception(_("Exception while processing consumer")) - self.reconnect() - # Break to outer loop - break +flags.DEFINE_string('rpc_backend', + 'nova.rpc_backends.amqp', + "The messaging module to use, defaults to AMQP.") - def close(self): - self.consumer_set.close() +RPCIMPL = load_module(FLAGS.rpc_backend) -class Publisher(messaging.Publisher): - """Publisher base class.""" - pass +def create_connection(new=True): + return RPCIMPL.Connection.instance(new=True) -class TopicPublisher(Publisher): - """Publishes messages on a specific topic.""" +def create_consumer(conn, topic, proxy, fanout=False): + if fanout: + return RPCIMPL.FanoutAdapterConsumer( + connection=conn, + topic=topic, + proxy=proxy) + else: + return RPCIMPL.TopicAdapterConsumer( + connection=conn, + topic=topic, + proxy=proxy) - exchange_type = 'topic' - def __init__(self, connection=None, topic='broadcast'): - self.routing_key = topic - self.exchange = FLAGS.control_exchange - self.durable = False - super(TopicPublisher, self).__init__(connection=connection) - - -class FanoutPublisher(Publisher): - """Publishes messages to a fanout exchange.""" - - exchange_type = 'fanout' - - def __init__(self, topic, connection=None): - self.exchange = '%s_fanout' % topic - self.queue = '%s_fanout' % topic - self.durable = False - self.auto_delete = True - LOG.info(_('Creating "%(exchange)s" fanout exchange'), - dict(exchange=self.exchange)) - super(FanoutPublisher, self).__init__(connection=connection) - - -class DirectConsumer(Consumer): - """Consumes messages directly on a channel specified by msg_id.""" - - exchange_type = 'direct' - - def __init__(self, connection=None, msg_id=None): - self.queue = msg_id - self.routing_key = msg_id - self.exchange = msg_id - self.auto_delete = True - self.exclusive = True - super(DirectConsumer, self).__init__(connection=connection) - - -class DirectPublisher(Publisher): - """Publishes messages directly on a channel specified by msg_id.""" - - exchange_type = 'direct' - - def __init__(self, connection=None, msg_id=None): - self.routing_key = msg_id - self.exchange = msg_id - self.auto_delete = True - super(DirectPublisher, self).__init__(connection=connection) - - -def msg_reply(msg_id, reply=None, failure=None): - """Sends a reply or an error on the channel signified by msg_id. - - Failure should be a sys.exc_info() tuple. - - """ - if failure: - message = str(failure[1]) - tb = traceback.format_exception(*failure) - LOG.error(_("Returning exception %s to caller"), message) - LOG.error(tb) - failure = (failure[0].__name__, str(failure[1]), tb) - - with ConnectionPool.item() as conn: - publisher = DirectPublisher(connection=conn, msg_id=msg_id) - try: - publisher.send({'result': reply, 'failure': failure}) - except TypeError: - publisher.send( - {'result': dict((k, repr(v)) - for k, v in reply.__dict__.iteritems()), - 'failure': failure}) - - publisher.close() - - -class RemoteError(exception.Error): - """Signifies that a remote class has raised an exception. - - Containes a string representation of the type of the original exception, - the value of the original exception, and the traceback. These are - sent to the parent as a joined string so printing the exception - contains all of the relevent info. - - """ - - def __init__(self, exc_type, value, traceback): - self.exc_type = exc_type - self.value = value - self.traceback = traceback - super(RemoteError, self).__init__('%s %s\n%s' % (exc_type, - value, - traceback)) - - -def _unpack_context(msg): - """Unpack context from msg.""" - context_dict = {} - for key in list(msg.keys()): - # NOTE(vish): Some versions of python don't like unicode keys - # in kwargs. - key = str(key) - if key.startswith('_context_'): - value = msg.pop(key) - context_dict[key[9:]] = value - context_dict['msg_id'] = msg.pop('_msg_id', None) - LOG.debug(_('unpacked context: %s'), context_dict) - return RpcContext.from_dict(context_dict) - - -def _pack_context(msg, context): - """Pack context into msg. - - Values for message keys need to be less than 255 chars, so we pull - context out into a bunch of separate keys. If we want to support - more arguments in rabbit messages, we may want to do the same - for args at some point. - - """ - context_d = dict([('_context_%s' % key, value) - for (key, value) in context.to_dict().iteritems()]) - msg.update(context_d) - - -class RpcContext(context.RequestContext): - def __init__(self, *args, **kwargs): - msg_id = kwargs.pop('msg_id', None) - self.msg_id = msg_id - super(RpcContext, self).__init__(*args, **kwargs) - - def reply(self, *args, **kwargs): - msg_reply(self.msg_id, *args, **kwargs) - - -def multicall(context, topic, msg): - """Make a call that returns multiple times.""" - LOG.debug(_('Making asynchronous call on %s ...'), topic) - msg_id = uuid.uuid4().hex - msg.update({'_msg_id': msg_id}) - LOG.debug(_('MSG_ID is %s') % (msg_id)) - _pack_context(msg, context) - - con_conn = ConnectionPool.get() - consumer = DirectConsumer(connection=con_conn, msg_id=msg_id) - wait_msg = MulticallWaiter(consumer) - consumer.register_callback(wait_msg) - - publisher = TopicPublisher(connection=con_conn, topic=topic) - publisher.send(msg) - publisher.close() - - return wait_msg - - -class MulticallWaiter(object): - def __init__(self, consumer): - self._consumer = consumer - self._results = queue.Queue() - self._closed = False - - def close(self): - self._closed = True - self._consumer.close() - ConnectionPool.put(self._consumer.connection) - - def __call__(self, data, message): - """Acks message and sets result.""" - message.ack() - if data['failure']: - self._results.put(RemoteError(*data['failure'])) - else: - self._results.put(data['result']) - - def __iter__(self): - return self.wait() - - def wait(self): - while True: - rv = None - while rv is None and not self._closed: - try: - rv = self._consumer.fetch(enable_callbacks=True) - except Exception: - self.close() - raise - time.sleep(0.01) - - result = self._results.get() - if isinstance(result, Exception): - self.close() - raise result - if result == None: - self.close() - raise StopIteration - yield result +def create_consumer_set(conn, consumers): + return RPCIMPL.ConsumerSet(connection=conn, consumer_list=consumers) def call(context, topic, msg): - """Sends a message on a topic and wait for a response.""" - rv = multicall(context, topic, msg) - # NOTE(vish): return the last result from the multicall - rv = list(rv) - if not rv: - return - return rv[-1] + return RPCIMPL.call(context, topic, msg) def cast(context, topic, msg): - """Sends a message on a topic without waiting for a response.""" - LOG.debug(_('Making asynchronous cast on %s...'), topic) - _pack_context(msg, context) - with ConnectionPool.item() as conn: - publisher = TopicPublisher(connection=conn, topic=topic) - publisher.send(msg) - publisher.close() + return RPCIMPL.cast(context, topic, msg) def fanout_cast(context, topic, msg): - """Sends a message on a fanout exchange without waiting for a response.""" - LOG.debug(_('Making asynchronous fanout cast...')) - _pack_context(msg, context) - with ConnectionPool.item() as conn: - publisher = FanoutPublisher(topic, connection=conn) - publisher.send(msg) - publisher.close() - - -def generic_response(message_data, message): - """Logs a result and exits.""" - LOG.debug(_('response %s'), message_data) - message.ack() - sys.exit(0) + return RPCIMPL.fanout_cast(context, topic, msg) -def send_message(topic, message, wait=True): - """Sends a message for testing.""" - msg_id = uuid.uuid4().hex - message.update({'_msg_id': msg_id}) - LOG.debug(_('topic is %s'), topic) - LOG.debug(_('message %s'), message) - - if wait: - consumer = messaging.Consumer(connection=Connection.instance(), - queue=msg_id, - exchange=msg_id, - auto_delete=True, - exchange_type='direct', - routing_key=msg_id) - consumer.register_callback(generic_response) - - publisher = messaging.Publisher(connection=Connection.instance(), - exchange=FLAGS.control_exchange, - durable=False, - exchange_type='topic', - routing_key=topic) - publisher.send(message) - publisher.close() - - if wait: - consumer.wait() - consumer.close() - - -if __name__ == '__main__': - # You can send messages from the command line using - # topic and a json string representing a dictionary - # for the method - send_message(sys.argv[1], json.loads(sys.argv[2])) +def multicall(context, topic, msg): + return RPCIMPL.multicall(context, topic, msg) diff --git a/nova/rpc_backends/__init__.py b/nova/rpc_backends/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/nova/rpc_backends/amqp.py b/nova/rpc_backends/amqp.py new file mode 100644 index 000000000..efa178bd2 --- /dev/null +++ b/nova/rpc_backends/amqp.py @@ -0,0 +1,591 @@ +# 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. + +"""AMQP-based RPC. + +Queues have consumers and publishers. + +No fan-out support yet. + +""" + +import json +import sys +import time +import traceback +import types +import uuid + +from carrot import connection as carrot_connection +from carrot import messaging +from eventlet import greenpool +from eventlet import pools +from eventlet import queue +import greenlet + +from nova import context +from nova import exception +from nova import fakerabbit +from nova import flags +from nova import log as logging +from nova import utils +from nova.rpc_backends.common import RemoteError, LOG + + +FLAGS = flags.FLAGS +flags.DEFINE_integer('rpc_thread_pool_size', 1024, + 'Size of RPC thread pool') +flags.DEFINE_integer('rpc_conn_pool_size', 30, + 'Size of RPC connection pool') + + +class Connection(carrot_connection.BrokerConnection): + """Connection instance object.""" + + @classmethod + def instance(cls, new=True): + """Returns the instance.""" + if new or not hasattr(cls, '_instance'): + params = dict(hostname=FLAGS.rabbit_host, + port=FLAGS.rabbit_port, + ssl=FLAGS.rabbit_use_ssl, + userid=FLAGS.rabbit_userid, + password=FLAGS.rabbit_password, + virtual_host=FLAGS.rabbit_virtual_host) + + if FLAGS.fake_rabbit: + params['backend_cls'] = fakerabbit.Backend + + # NOTE(vish): magic is fun! + # pylint: disable=W0142 + if new: + return cls(**params) + else: + cls._instance = cls(**params) + return cls._instance + + @classmethod + def recreate(cls): + """Recreates the connection instance. + + This is necessary to recover from some network errors/disconnects. + + """ + try: + del cls._instance + except AttributeError, e: + # The _instance stuff is for testing purposes. Usually we don't use + # it. So don't freak out if it doesn't exist. + pass + return cls.instance() + + +class Pool(pools.Pool): + """Class that implements a Pool of Connections.""" + + # TODO(comstud): Timeout connections not used in a while + def create(self): + LOG.debug('Creating new connection') + return Connection.instance(new=True) + +# Create a ConnectionPool to use for RPC calls. We'll order the +# pool as a stack (LIFO), so that we can potentially loop through and +# timeout old unused connections at some point +ConnectionPool = Pool( + max_size=FLAGS.rpc_conn_pool_size, + order_as_stack=True) + + +class Consumer(messaging.Consumer): + """Consumer base class. + + Contains methods for connecting the fetch method to async loops. + + """ + + def __init__(self, *args, **kwargs): + for i in xrange(FLAGS.rabbit_max_retries): + if i > 0: + time.sleep(FLAGS.rabbit_retry_interval) + try: + super(Consumer, self).__init__(*args, **kwargs) + self.failed_connection = False + break + except Exception as e: # Catching all because carrot sucks + fl_host = FLAGS.rabbit_host + fl_port = FLAGS.rabbit_port + fl_intv = FLAGS.rabbit_retry_interval + LOG.error(_('AMQP server on %(fl_host)s:%(fl_port)d is' + ' unreachable: %(e)s. Trying again in %(fl_intv)d' + ' seconds.') % locals()) + self.failed_connection = True + if self.failed_connection: + LOG.error(_('Unable to connect to AMQP server ' + 'after %d tries. Shutting down.'), + FLAGS.rabbit_max_retries) + sys.exit(1) + + def fetch(self, no_ack=None, auto_ack=None, enable_callbacks=False): + """Wraps the parent fetch with some logic for failed connection.""" + # TODO(vish): the logic for failed connections and logging should be + # refactored into some sort of connection manager object + try: + if self.failed_connection: + # NOTE(vish): connection is defined in the parent class, we can + # recreate it as long as we create the backend too + # pylint: disable=W0201 + self.connection = Connection.recreate() + self.backend = self.connection.create_backend() + self.declare() + return super(Consumer, self).fetch(no_ack, + auto_ack, + enable_callbacks) + if self.failed_connection: + LOG.error(_('Reconnected to queue')) + self.failed_connection = False + # NOTE(vish): This is catching all errors because we really don't + # want exceptions to be logged 10 times a second if some + # persistent failure occurs. + except Exception, e: # pylint: disable=W0703 + if not self.failed_connection: + LOG.exception(_('Failed to fetch message from queue: %s' % e)) + self.failed_connection = True + + def attach_to_eventlet(self): + """Only needed for unit tests!""" + timer = utils.LoopingCall(self.fetch, enable_callbacks=True) + timer.start(0.1) + return timer + + +class AdapterConsumer(Consumer): + """Calls methods on a proxy object based on method and args.""" + + def __init__(self, connection=None, topic='broadcast', proxy=None): + LOG.debug(_('Initing the Adapter Consumer for %s') % topic) + self.proxy = proxy + self.pool = greenpool.GreenPool(FLAGS.rpc_thread_pool_size) + super(AdapterConsumer, self).__init__(connection=connection, + topic=topic) + self.register_callback(self.process_data) + + def process_data(self, message_data, message): + """Consumer callback to call a method on a proxy object. + + Parses the message for validity and fires off a thread to call the + proxy object method. + + Message data should be a dictionary with two keys: + method: string representing the method to call + args: dictionary of arg: value + + Example: {'method': 'echo', 'args': {'value': 42}} + + """ + LOG.debug(_('received %s') % message_data) + # This will be popped off in _unpack_context + msg_id = message_data.get('_msg_id', None) + ctxt = _unpack_context(message_data) + + method = message_data.get('method') + args = message_data.get('args', {}) + message.ack() + if not method: + # NOTE(vish): we may not want to ack here, but that means that bad + # messages stay in the queue indefinitely, so for now + # we just log the message and send an error string + # back to the caller + LOG.warn(_('no method for message: %s') % message_data) + if msg_id: + msg_reply(msg_id, + _('No method for message: %s') % message_data) + return + self.pool.spawn_n(self._process_data, msg_id, ctxt, method, args) + + @exception.wrap_exception() + def _process_data(self, msg_id, ctxt, method, args): + """Thread that maigcally looks for a method on the proxy + object and calls it. + """ + + node_func = getattr(self.proxy, str(method)) + node_args = dict((str(k), v) for k, v in args.iteritems()) + # NOTE(vish): magic is fun! + try: + rval = node_func(context=ctxt, **node_args) + if msg_id: + # Check if the result was a generator + if isinstance(rval, types.GeneratorType): + for x in rval: + msg_reply(msg_id, x, None) + else: + msg_reply(msg_id, rval, None) + + # This final None tells multicall that it is done. + msg_reply(msg_id, None, None) + elif isinstance(rval, types.GeneratorType): + # NOTE(vish): this iterates through the generator + list(rval) + except Exception as e: + logging.exception('Exception during message handling') + if msg_id: + msg_reply(msg_id, None, sys.exc_info()) + return + + +class TopicAdapterConsumer(AdapterConsumer): + """Consumes messages on a specific topic.""" + + exchange_type = 'topic' + + def __init__(self, connection=None, topic='broadcast', proxy=None): + self.queue = topic + self.routing_key = topic + self.exchange = FLAGS.control_exchange + self.durable = False + super(TopicAdapterConsumer, self).__init__(connection=connection, + topic=topic, proxy=proxy) + + +class FanoutAdapterConsumer(AdapterConsumer): + """Consumes messages from a fanout exchange.""" + + exchange_type = 'fanout' + + def __init__(self, connection=None, topic='broadcast', proxy=None): + self.exchange = '%s_fanout' % topic + self.routing_key = topic + unique = uuid.uuid4().hex + self.queue = '%s_fanout_%s' % (topic, unique) + self.durable = False + # Fanout creates unique queue names, so we should auto-remove + # them when done, so they're not left around on restart. + # Also, we're the only one that should be consuming. exclusive + # implies auto_delete, so we'll just set that.. + self.exclusive = True + LOG.info(_('Created "%(exchange)s" fanout exchange ' + 'with "%(key)s" routing key'), + dict(exchange=self.exchange, key=self.routing_key)) + super(FanoutAdapterConsumer, self).__init__(connection=connection, + topic=topic, proxy=proxy) + + +class ConsumerSet(object): + """Groups consumers to listen on together on a single connection.""" + + def __init__(self, connection, consumer_list): + self.consumer_list = set(consumer_list) + self.consumer_set = None + self.enabled = True + self.init(connection) + + def init(self, conn): + if not conn: + conn = Connection.instance(new=True) + if self.consumer_set: + self.consumer_set.close() + self.consumer_set = messaging.ConsumerSet(conn) + for consumer in self.consumer_list: + consumer.connection = conn + # consumer.backend is set for us + self.consumer_set.add_consumer(consumer) + + def reconnect(self): + self.init(None) + + def wait(self, limit=None): + running = True + while running: + it = self.consumer_set.iterconsume(limit=limit) + if not it: + break + while True: + try: + it.next() + except StopIteration: + return + except greenlet.GreenletExit: + running = False + break + except Exception as e: + LOG.exception(_("Exception while processing consumer")) + self.reconnect() + # Break to outer loop + break + + def close(self): + self.consumer_set.close() + + +class Publisher(messaging.Publisher): + """Publisher base class.""" + pass + + +class TopicPublisher(Publisher): + """Publishes messages on a specific topic.""" + + exchange_type = 'topic' + + def __init__(self, connection=None, topic='broadcast'): + self.routing_key = topic + self.exchange = FLAGS.control_exchange + self.durable = False + super(TopicPublisher, self).__init__(connection=connection) + + +class FanoutPublisher(Publisher): + """Publishes messages to a fanout exchange.""" + + exchange_type = 'fanout' + + def __init__(self, topic, connection=None): + self.exchange = '%s_fanout' % topic + self.queue = '%s_fanout' % topic + self.durable = False + self.auto_delete = True + LOG.info(_('Creating "%(exchange)s" fanout exchange'), + dict(exchange=self.exchange)) + super(FanoutPublisher, self).__init__(connection=connection) + + +class DirectConsumer(Consumer): + """Consumes messages directly on a channel specified by msg_id.""" + + exchange_type = 'direct' + + def __init__(self, connection=None, msg_id=None): + self.queue = msg_id + self.routing_key = msg_id + self.exchange = msg_id + self.auto_delete = True + self.exclusive = True + super(DirectConsumer, self).__init__(connection=connection) + + +class DirectPublisher(Publisher): + """Publishes messages directly on a channel specified by msg_id.""" + + exchange_type = 'direct' + + def __init__(self, connection=None, msg_id=None): + self.routing_key = msg_id + self.exchange = msg_id + self.auto_delete = True + super(DirectPublisher, self).__init__(connection=connection) + + +def msg_reply(msg_id, reply=None, failure=None): + """Sends a reply or an error on the channel signified by msg_id. + + Failure should be a sys.exc_info() tuple. + + """ + if failure: + message = str(failure[1]) + tb = traceback.format_exception(*failure) + LOG.error(_("Returning exception %s to caller"), message) + LOG.error(tb) + failure = (failure[0].__name__, str(failure[1]), tb) + + with ConnectionPool.item() as conn: + publisher = DirectPublisher(connection=conn, msg_id=msg_id) + try: + publisher.send({'result': reply, 'failure': failure}) + except TypeError: + publisher.send( + {'result': dict((k, repr(v)) + for k, v in reply.__dict__.iteritems()), + 'failure': failure}) + + publisher.close() + + +def _unpack_context(msg): + """Unpack context from msg.""" + context_dict = {} + for key in list(msg.keys()): + # NOTE(vish): Some versions of python don't like unicode keys + # in kwargs. + key = str(key) + if key.startswith('_context_'): + value = msg.pop(key) + context_dict[key[9:]] = value + context_dict['msg_id'] = msg.pop('_msg_id', None) + LOG.debug(_('unpacked context: %s'), context_dict) + return RpcContext.from_dict(context_dict) + + +def _pack_context(msg, context): + """Pack context into msg. + + Values for message keys need to be less than 255 chars, so we pull + context out into a bunch of separate keys. If we want to support + more arguments in rabbit messages, we may want to do the same + for args at some point. + + """ + context_d = dict([('_context_%s' % key, value) + for (key, value) in context.to_dict().iteritems()]) + msg.update(context_d) + + +class RpcContext(context.RequestContext): + def __init__(self, *args, **kwargs): + msg_id = kwargs.pop('msg_id', None) + self.msg_id = msg_id + super(RpcContext, self).__init__(*args, **kwargs) + + def reply(self, *args, **kwargs): + msg_reply(self.msg_id, *args, **kwargs) + + +def multicall(context, topic, msg): + """Make a call that returns multiple times.""" + LOG.debug(_('Making asynchronous call on %s ...'), topic) + msg_id = uuid.uuid4().hex + msg.update({'_msg_id': msg_id}) + LOG.debug(_('MSG_ID is %s') % (msg_id)) + _pack_context(msg, context) + + con_conn = ConnectionPool.get() + consumer = DirectConsumer(connection=con_conn, msg_id=msg_id) + wait_msg = MulticallWaiter(consumer) + consumer.register_callback(wait_msg) + + publisher = TopicPublisher(connection=con_conn, topic=topic) + publisher.send(msg) + publisher.close() + + return wait_msg + + +class MulticallWaiter(object): + def __init__(self, consumer): + self._consumer = consumer + self._results = queue.Queue() + self._closed = False + + def close(self): + self._closed = True + self._consumer.close() + ConnectionPool.put(self._consumer.connection) + + def __call__(self, data, message): + """Acks message and sets result.""" + message.ack() + if data['failure']: + self._results.put(RemoteError(*data['failure'])) + else: + self._results.put(data['result']) + + def __iter__(self): + return self.wait() + + def wait(self): + while True: + rv = None + while rv is None and not self._closed: + try: + rv = self._consumer.fetch(enable_callbacks=True) + except Exception: + self.close() + raise + time.sleep(0.01) + + result = self._results.get() + if isinstance(result, Exception): + self.close() + raise result + if result == None: + self.close() + raise StopIteration + yield result + + +def call(context, topic, msg): + """Sends a message on a topic and wait for a response.""" + rv = multicall(context, topic, msg) + # NOTE(vish): return the last result from the multicall + rv = list(rv) + if not rv: + return + return rv[-1] + + +def cast(context, topic, msg): + """Sends a message on a topic without waiting for a response.""" + LOG.debug(_('Making asynchronous cast on %s...'), topic) + _pack_context(msg, context) + with ConnectionPool.item() as conn: + publisher = TopicPublisher(connection=conn, topic=topic) + publisher.send(msg) + publisher.close() + + +def fanout_cast(context, topic, msg): + """Sends a message on a fanout exchange without waiting for a response.""" + LOG.debug(_('Making asynchronous fanout cast...')) + _pack_context(msg, context) + with ConnectionPool.item() as conn: + publisher = FanoutPublisher(topic, connection=conn) + publisher.send(msg) + publisher.close() + + +def generic_response(message_data, message): + """Logs a result and exits.""" + LOG.debug(_('response %s'), message_data) + message.ack() + sys.exit(0) + + +def send_message(topic, message, wait=True): + """Sends a message for testing.""" + msg_id = uuid.uuid4().hex + message.update({'_msg_id': msg_id}) + LOG.debug(_('topic is %s'), topic) + LOG.debug(_('message %s'), message) + + if wait: + consumer = messaging.Consumer(connection=Connection.instance(), + queue=msg_id, + exchange=msg_id, + auto_delete=True, + exchange_type='direct', + routing_key=msg_id) + consumer.register_callback(generic_response) + + publisher = messaging.Publisher(connection=Connection.instance(), + exchange=FLAGS.control_exchange, + durable=False, + exchange_type='topic', + routing_key=topic) + publisher.send(message) + publisher.close() + + if wait: + consumer.wait() + consumer.close() + + +if __name__ == '__main__': + # You can send messages from the command line using + # topic and a json string representing a dictionary + # for the method + send_message(sys.argv[1], json.loads(sys.argv[2])) diff --git a/nova/rpc_backends/common.py b/nova/rpc_backends/common.py new file mode 100644 index 000000000..1d3065a83 --- /dev/null +++ b/nova/rpc_backends/common.py @@ -0,0 +1,23 @@ +from nova import exception +from nova import log as logging + +LOG = logging.getLogger('nova.rpc') + + +class RemoteError(exception.Error): + """Signifies that a remote class has raised an exception. + + Containes a string representation of the type of the original exception, + the value of the original exception, and the traceback. These are + sent to the parent as a joined string so printing the exception + contains all of the relevent info. + + """ + + def __init__(self, exc_type, value, traceback): + self.exc_type = exc_type + self.value = value + self.traceback = traceback + super(RemoteError, self).__init__('%s %s\n%s' % (exc_type, + value, + traceback)) diff --git a/nova/service.py b/nova/service.py index 00e4f61e5..6e9eddc5a 100644 --- a/nova/service.py +++ b/nova/service.py @@ -149,26 +149,22 @@ class Service(object): if 'nova-compute' == self.binary: self.manager.update_available_resource(ctxt) - self.conn = rpc.Connection.instance(new=True) + self.conn = rpc.create_connection(new=True) logging.debug("Creating Consumer connection for Service %s" % self.topic) # Share this same connection for these Consumers - consumer_all = rpc.TopicAdapterConsumer( - connection=self.conn, - topic=self.topic, - proxy=self) - consumer_node = rpc.TopicAdapterConsumer( - connection=self.conn, - topic='%s.%s' % (self.topic, self.host), - proxy=self) - fanout = rpc.FanoutAdapterConsumer( - connection=self.conn, - topic=self.topic, - proxy=self) - consumer_set = rpc.ConsumerSet( - connection=self.conn, - consumer_list=[consumer_all, consumer_node, fanout]) + consumer_all = rpc.create_consumer(self.conn, self.topic, self, + fanout=False) + + node_topic = '%s.%s' % (self.topic, self.host) + consumer_node = rpc.create_consumer(self.conn, node_topic, self, + fanout=False) + + fanout = rpc.create_consumer(self.conn, self.topic, self, fanout=True) + + consumers = [consumer_all, consumer_node, fanout] + consumer_set = rpc.create_consumer_set(self.conn, consumers) # Wait forever, processing these consumers def _wait(): diff --git a/nova/test.py b/nova/test.py index 9790b0aa1..549aa6fcf 100644 --- a/nova/test.py +++ b/nova/test.py @@ -99,9 +99,7 @@ class TestCase(unittest.TestCase): self.flag_overrides = {} self.injected = [] self._services = [] - self._monkey_patch_attach() self._original_flags = FLAGS.FlagValuesDict() - rpc.ConnectionPool = rpc.Pool(max_size=FLAGS.rpc_conn_pool_size) def tearDown(self): """Runs after each test method to tear down test environment.""" @@ -126,9 +124,6 @@ class TestCase(unittest.TestCase): # Reset any overriden flags self.reset_flags() - # Reset our monkey-patches - rpc.Consumer.attach_to_eventlet = self.original_attach - # Stop any timers for x in self.injected: try: @@ -172,17 +167,6 @@ class TestCase(unittest.TestCase): self._services.append(svc) return svc - def _monkey_patch_attach(self): - self.original_attach = rpc.Consumer.attach_to_eventlet - - def _wrapped(inner_self): - rv = self.original_attach(inner_self) - self.injected.append(rv) - return rv - - _wrapped.func_name = self.original_attach.func_name - rpc.Consumer.attach_to_eventlet = _wrapped - # Useful assertions def assertDictMatch(self, d1, d2, approx_equal=False, tolerance=0.001): """Assert two dicts are equivalent. diff --git a/nova/tests/test_adminapi.py b/nova/tests/test_adminapi.py index 877cf4ea1..6bbe15f53 100644 --- a/nova/tests/test_adminapi.py +++ b/nova/tests/test_adminapi.py @@ -39,7 +39,7 @@ class AdminApiTestCase(test.TestCase): super(AdminApiTestCase, self).setUp() self.flags(connection_type='fake') - self.conn = rpc.Connection.instance() + self.conn = rpc.create_connection() # set up our cloud self.api = admin.AdminController() diff --git a/nova/tests/test_cloud.py b/nova/tests/test_cloud.py index 136082cc1..a1b296a96 100644 --- a/nova/tests/test_cloud.py +++ b/nova/tests/test_cloud.py @@ -50,7 +50,7 @@ class CloudTestCase(test.TestCase): self.flags(connection_type='fake', stub_network=True) - self.conn = rpc.Connection.instance() + self.conn = rpc.create_connection() # set up our cloud self.cloud = cloud.CloudController() @@ -269,63 +269,24 @@ class CloudTestCase(test.TestCase): delete = self.cloud.delete_security_group self.assertRaises(exception.ApiError, delete, self.context) - def test_authorize_security_group_ingress(self): + def test_authorize_revoke_security_group_ingress(self): kwargs = {'project_id': self.context.project_id, 'name': 'test'} sec = db.security_group_create(self.context, kwargs) authz = self.cloud.authorize_security_group_ingress kwargs = {'to_port': '999', 'from_port': '999', 'ip_protocol': 'tcp'} - self.assertTrue(authz(self.context, group_name=sec['name'], **kwargs)) - - def test_authorize_security_group_ingress_ip_permissions_ip_ranges(self): - kwargs = {'project_id': self.context.project_id, 'name': 'test'} - sec = db.security_group_create(self.context, kwargs) - authz = self.cloud.authorize_security_group_ingress - kwargs = {'ip_permissions': [{'to_port': 81, 'from_port': 81, - 'ip_ranges': - {'1': {'cidr_ip': u'0.0.0.0/0'}, - '2': {'cidr_ip': u'10.10.10.10/32'}}, - 'ip_protocol': u'tcp'}]} - self.assertTrue(authz(self.context, group_name=sec['name'], **kwargs)) - - def test_authorize_security_group_ingress_ip_permissions_groups(self): - kwargs = {'project_id': self.context.project_id, 'name': 'test'} - sec = db.security_group_create(self.context, kwargs) - authz = self.cloud.authorize_security_group_ingress - kwargs = {'ip_permissions': [{'to_port': 81, 'from_port': 81, - 'ip_ranges':{'1': {'cidr_ip': u'0.0.0.0/0'}, - '2': {'cidr_ip': u'10.10.10.10/32'}}, - 'groups': {'1': {'user_id': u'someuser', - 'group_name': u'somegroup1'}, - '2': {'user_id': u'someuser', - 'group_name': u'othergroup2'}}, - 'ip_protocol': u'tcp'}]} - self.assertTrue(authz(self.context, group_name=sec['name'], **kwargs)) - - def test_revoke_security_group_ingress(self): - kwargs = {'project_id': self.context.project_id, 'name': 'test'} - sec = db.security_group_create(self.context, kwargs) - authz = self.cloud.authorize_security_group_ingress - kwargs = {'to_port': '999', 'from_port': '999', 'ip_protocol': 'tcp'} - authz(self.context, group_id=sec['id'], **kwargs) + authz(self.context, group_name=sec['name'], **kwargs) revoke = self.cloud.revoke_security_group_ingress self.assertTrue(revoke(self.context, group_name=sec['name'], **kwargs)) - def test_revoke_security_group_ingress_by_id(self): - kwargs = {'project_id': self.context.project_id, 'name': 'test'} - sec = db.security_group_create(self.context, kwargs) - authz = self.cloud.authorize_security_group_ingress - kwargs = {'to_port': '999', 'from_port': '999', 'ip_protocol': 'tcp'} - authz(self.context, group_id=sec['id'], **kwargs) - revoke = self.cloud.revoke_security_group_ingress - self.assertTrue(revoke(self.context, group_id=sec['id'], **kwargs)) - - def test_authorize_security_group_ingress_by_id(self): + def test_authorize_revoke_security_group_ingress_by_id(self): sec = db.security_group_create(self.context, {'project_id': self.context.project_id, 'name': 'test'}) authz = self.cloud.authorize_security_group_ingress kwargs = {'to_port': '999', 'from_port': '999', 'ip_protocol': 'tcp'} - self.assertTrue(authz(self.context, group_id=sec['id'], **kwargs)) + authz(self.context, group_id=sec['id'], **kwargs) + revoke = self.cloud.revoke_security_group_ingress + self.assertTrue(revoke(self.context, group_id=sec['id'], **kwargs)) def test_authorize_security_group_ingress_missing_protocol_params(self): sec = db.security_group_create(self.context, @@ -947,21 +908,6 @@ class CloudTestCase(test.TestCase): self._wait_for_running(ec2_instance_id) return ec2_instance_id - def test_rescue_unrescue_instance(self): - instance_id = self._run_instance( - image_id='ami-1', - instance_type=FLAGS.default_instance_type, - max_count=1) - self.cloud.rescue_instance(context=self.context, - instance_id=instance_id) - # NOTE(vish): This currently does no validation, it simply makes sure - # that the code path doesn't throw an exception. - self.cloud.unrescue_instance(context=self.context, - instance_id=instance_id) - # TODO(soren): We need this until we can stop polling in the rpc code - # for unit tests. - self.cloud.terminate_instances(self.context, [instance_id]) - def test_console_output(self): instance_id = self._run_instance( image_id='ami-1', diff --git a/nova/tests/test_rpc.py b/nova/tests/test_rpc.py index ffd748efe..2d2436175 100644 --- a/nova/tests/test_rpc.py +++ b/nova/tests/test_rpc.py @@ -33,11 +33,12 @@ LOG = logging.getLogger('nova.tests.rpc') class RpcTestCase(test.TestCase): def setUp(self): super(RpcTestCase, self).setUp() - self.conn = rpc.Connection.instance(True) + self.conn = rpc.create_connection(True) self.receiver = TestReceiver() - self.consumer = rpc.TopicAdapterConsumer(connection=self.conn, - topic='test', - proxy=self.receiver) + self.consumer = rpc.create_consumer(self.conn, + 'test', + self.receiver, + False) self.consumer.attach_to_eventlet() self.context = context.get_admin_context() @@ -129,6 +130,8 @@ class RpcTestCase(test.TestCase): """Calls echo in the passed queue""" LOG.debug(_("Nested received %(queue)s, %(value)s") % locals()) + # TODO: so, it will replay the context and use the same REQID? + # that's bizarre. ret = rpc.call(context, queue, {"method": "echo", @@ -137,10 +140,11 @@ class RpcTestCase(test.TestCase): return value nested = Nested() - conn = rpc.Connection.instance(True) - consumer = rpc.TopicAdapterConsumer(connection=conn, - topic='nested', - proxy=nested) + conn = rpc.create_connection(True) + consumer = rpc.create_consumer(conn, + 'nested', + nested, + False) consumer.attach_to_eventlet() value = 42 result = rpc.call(self.context, @@ -149,47 +153,6 @@ class RpcTestCase(test.TestCase): "value": value}}) self.assertEqual(value, result) - def test_connectionpool_single(self): - """Test that ConnectionPool recycles a single connection.""" - conn1 = rpc.ConnectionPool.get() - rpc.ConnectionPool.put(conn1) - conn2 = rpc.ConnectionPool.get() - rpc.ConnectionPool.put(conn2) - self.assertEqual(conn1, conn2) - - def test_connectionpool_double(self): - """Test that ConnectionPool returns and reuses separate connections. - - When called consecutively we should get separate connections and upon - returning them those connections should be reused for future calls - before generating a new connection. - - """ - conn1 = rpc.ConnectionPool.get() - conn2 = rpc.ConnectionPool.get() - - self.assertNotEqual(conn1, conn2) - rpc.ConnectionPool.put(conn1) - rpc.ConnectionPool.put(conn2) - - conn3 = rpc.ConnectionPool.get() - conn4 = rpc.ConnectionPool.get() - self.assertEqual(conn1, conn3) - self.assertEqual(conn2, conn4) - - def test_connectionpool_limit(self): - """Test connection pool limit and connection uniqueness.""" - max_size = FLAGS.rpc_conn_pool_size - conns = [] - - for i in xrange(max_size): - conns.append(rpc.ConnectionPool.get()) - - self.assertFalse(rpc.ConnectionPool.free_items) - self.assertEqual(rpc.ConnectionPool.current_size, - rpc.ConnectionPool.max_size) - self.assertEqual(len(set(conns)), max_size) - class TestReceiver(object): """Simple Proxy class so the consumer has methods to call. diff --git a/nova/tests/test_rpc_amqp.py b/nova/tests/test_rpc_amqp.py new file mode 100644 index 000000000..e3df2393a --- /dev/null +++ b/nova/tests/test_rpc_amqp.py @@ -0,0 +1,68 @@ +from nova import context +from nova import flags +from nova import log as logging +from nova import rpc +from nova.rpc_backends import amqp +from nova import test + + +FLAGS = flags.FLAGS +LOG = logging.getLogger('nova.tests.rpc') + + +class RpcAMQPTestCase(test.TestCase): + def setUp(self): + super(RpcAMQPTestCase, self).setUp() + self.conn = rpc.create_connection(True) + self.receiver = TestReceiver() + self.consumer = rpc.create_consumer(self.conn, + 'test', + self.receiver, + False) + self.consumer.attach_to_eventlet() + self.context = context.get_admin_context() + + def test_connectionpool_single(self): + """Test that ConnectionPool recycles a single connection.""" + conn1 = amqp.ConnectionPool.get() + amqp.ConnectionPool.put(conn1) + conn2 = amqp.ConnectionPool.get() + amqp.ConnectionPool.put(conn2) + self.assertEqual(conn1, conn2) + + +class TestReceiver(object): + """Simple Proxy class so the consumer has methods to call. + + Uses static methods because we aren't actually storing any state. + + """ + + @staticmethod + def echo(context, value): + """Simply returns whatever value is sent in.""" + LOG.debug(_("Received %s"), value) + return value + + @staticmethod + def context(context, value): + """Returns dictionary version of context.""" + LOG.debug(_("Received %s"), context) + return context.to_dict() + + @staticmethod + def echo_three_times(context, value): + context.reply(value) + context.reply(value + 1) + context.reply(value + 2) + + @staticmethod + def echo_three_times_yield(context, value): + yield value + yield value + 1 + yield value + 2 + + @staticmethod + def fail(context, value): + """Raises an exception with the value sent in.""" + raise Exception(value) diff --git a/nova/tests/test_service.py b/nova/tests/test_service.py index f45f76b73..bbf47b50f 100644 --- a/nova/tests/test_service.py +++ b/nova/tests/test_service.py @@ -109,103 +109,8 @@ class ServiceTestCase(test.TestCase): # the looping calls are created in StartService. app = service.Service.create(host=host, binary=binary, topic=topic) - self.mox.StubOutWithMock(service.rpc.Connection, 'instance') - service.rpc.Connection.instance(new=mox.IgnoreArg()) - - self.mox.StubOutWithMock(rpc, - 'TopicAdapterConsumer', - use_mock_anything=True) - self.mox.StubOutWithMock(rpc, - 'FanoutAdapterConsumer', - use_mock_anything=True) - - self.mox.StubOutWithMock(rpc, - 'ConsumerSet', - use_mock_anything=True) - - rpc.TopicAdapterConsumer(connection=mox.IgnoreArg(), - topic=topic, - proxy=mox.IsA(service.Service)).AndReturn( - rpc.TopicAdapterConsumer) - - rpc.TopicAdapterConsumer(connection=mox.IgnoreArg(), - topic='%s.%s' % (topic, host), - proxy=mox.IsA(service.Service)).AndReturn( - rpc.TopicAdapterConsumer) - - rpc.FanoutAdapterConsumer(connection=mox.IgnoreArg(), - topic=topic, - proxy=mox.IsA(service.Service)).AndReturn( - rpc.FanoutAdapterConsumer) - - def wait_func(self, limit=None): - return None - - mock_cset = self.mox.CreateMock(rpc.ConsumerSet, - {'wait': wait_func}) - rpc.ConsumerSet(connection=mox.IgnoreArg(), - consumer_list=mox.IsA(list)).AndReturn(mock_cset) - wait_func(mox.IgnoreArg()) - - service_create = {'host': host, - 'binary': binary, - 'topic': topic, - 'report_count': 0, - 'availability_zone': 'nova'} - service_ref = {'host': host, - 'binary': binary, - 'report_count': 0, - 'id': 1} - - service.db.service_get_by_args(mox.IgnoreArg(), - host, - binary).AndRaise(exception.NotFound()) - service.db.service_create(mox.IgnoreArg(), - service_create).AndReturn(service_ref) - self.mox.ReplayAll() - - app.start() - app.stop() self.assert_(app) - # We're testing sort of weird behavior in how report_state decides - # whether it is disconnected, it looks for a variable on itself called - # 'model_disconnected' and report_state doesn't really do much so this - # these are mostly just for coverage - def test_report_state_no_service(self): - host = 'foo' - binary = 'bar' - topic = 'test' - service_create = {'host': host, - 'binary': binary, - 'topic': topic, - 'report_count': 0, - 'availability_zone': 'nova'} - service_ref = {'host': host, - 'binary': binary, - 'topic': topic, - 'report_count': 0, - 'availability_zone': 'nova', - 'id': 1} - - service.db.service_get_by_args(mox.IgnoreArg(), - host, - binary).AndRaise(exception.NotFound()) - service.db.service_create(mox.IgnoreArg(), - service_create).AndReturn(service_ref) - service.db.service_get(mox.IgnoreArg(), - service_ref['id']).AndReturn(service_ref) - service.db.service_update(mox.IgnoreArg(), service_ref['id'], - mox.ContainsKeyValue('report_count', 1)) - - self.mox.ReplayAll() - serv = service.Service(host, - binary, - topic, - 'nova.tests.test_service.FakeManager') - serv.start() - serv.report_state() - def test_report_state_newly_disconnected(self): host = 'foo' binary = 'bar' @@ -276,81 +181,6 @@ class ServiceTestCase(test.TestCase): self.assert_(not serv.model_disconnected) - def test_compute_can_update_available_resource(self): - """Confirm compute updates their record of compute-service table.""" - host = 'foo' - binary = 'nova-compute' - topic = 'compute' - - # Any mocks are not working without UnsetStubs() here. - self.mox.UnsetStubs() - ctxt = context.get_admin_context() - service_ref = db.service_create(ctxt, {'host': host, - 'binary': binary, - 'topic': topic}) - serv = service.Service(host, - binary, - topic, - 'nova.compute.manager.ComputeManager') - - # This testcase want to test calling update_available_resource. - # No need to call periodic call, then below variable must be set 0. - serv.report_interval = 0 - serv.periodic_interval = 0 - - # Creating mocks - self.mox.StubOutWithMock(service.rpc.Connection, 'instance') - service.rpc.Connection.instance(new=mox.IgnoreArg()) - - self.mox.StubOutWithMock(rpc, - 'TopicAdapterConsumer', - use_mock_anything=True) - self.mox.StubOutWithMock(rpc, - 'FanoutAdapterConsumer', - use_mock_anything=True) - - self.mox.StubOutWithMock(rpc, - 'ConsumerSet', - use_mock_anything=True) - - rpc.TopicAdapterConsumer(connection=mox.IgnoreArg(), - topic=topic, - proxy=mox.IsA(service.Service)).AndReturn( - rpc.TopicAdapterConsumer) - - rpc.TopicAdapterConsumer(connection=mox.IgnoreArg(), - topic='%s.%s' % (topic, host), - proxy=mox.IsA(service.Service)).AndReturn( - rpc.TopicAdapterConsumer) - - rpc.FanoutAdapterConsumer(connection=mox.IgnoreArg(), - topic=topic, - proxy=mox.IsA(service.Service)).AndReturn( - rpc.FanoutAdapterConsumer) - - def wait_func(self, limit=None): - return None - - mock_cset = self.mox.CreateMock(rpc.ConsumerSet, - {'wait': wait_func}) - rpc.ConsumerSet(connection=mox.IgnoreArg(), - consumer_list=mox.IsA(list)).AndReturn(mock_cset) - wait_func(mox.IgnoreArg()) - - self.mox.StubOutWithMock(serv.manager.driver, - 'update_available_resource') - serv.manager.driver.update_available_resource(mox.IgnoreArg(), host) - - # Just doing start()-stop(), not confirm new db record is created, - # because update_available_resource() works only in - # libvirt environment. This testcase confirms - # update_available_resource() is called. Otherwise, mox complains. - self.mox.ReplayAll() - serv.start() - serv.stop() - - db.service_destroy(ctxt, service_ref['id']) - class TestWSGIService(test.TestCase): diff --git a/nova/tests/test_test.py b/nova/tests/test_test.py index 35c838065..64f11fa45 100644 --- a/nova/tests/test_test.py +++ b/nova/tests/test_test.py @@ -33,8 +33,13 @@ class IsolationTestCase(test.TestCase): self.start_service('compute') def test_rpc_consumer_isolation(self): - connection = rpc.Connection.instance(new=True) - consumer = rpc.TopicAdapterConsumer(connection, topic='compute') - consumer.register_callback( - lambda x, y: self.fail('I should never be called')) + class NeverCalled(object): + + def __getattribute__(*args): + assert False, "I should never get called." + + connection = rpc.create_connection(new=True) + proxy = NeverCalled() + consumer = rpc.create_consumer(connection, 'compute', + proxy, fanout=False) consumer.attach_to_eventlet() diff --git a/nova/utils.py b/nova/utils.py index 8784a227d..ad31f88bd 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -809,3 +809,14 @@ class Bootstrapper(object): for key in FLAGS: value = FLAGS.get(key, None) logging.audit(_("%(key)s : %(value)s" % locals())) + + +def load_module(name): + mod = __import__(name) + + components = name.split('.') + + for comp in components[1:]: + mod = getattr(mod, comp) + + return mod -- 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 d5b76d89d7cfc2581e2de618d33331f9267126d4 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Wed, 27 Jul 2011 09:33:20 -0400 Subject: updating tests --- nova/tests/api/openstack/test_server_metadata.py | 55 ++++++++---------------- 1 file changed, 18 insertions(+), 37 deletions(-) (limited to 'nova') diff --git a/nova/tests/api/openstack/test_server_metadata.py b/nova/tests/api/openstack/test_server_metadata.py index 0431e68d2..12fc7665b 100644 --- a/nova/tests/api/openstack/test_server_metadata.py +++ b/nova/tests/api/openstack/test_server_metadata.py @@ -94,8 +94,7 @@ class ServerMetaDataTest(unittest.TestCase): def test_index(self): self.stubs.Set(nova.db.api, 'instance_metadata_get', return_server_metadata) - req = webob.Request.blank('/v1.1/servers/1/meta') - req.environ['api.version'] = '1.1' + req = webob.Request.blank('/v1.1/servers/1/metadata') res = req.get_response(fakes.wsgi_app()) res_dict = json.loads(res.body) self.assertEqual(200, res.status_int) @@ -104,16 +103,14 @@ class ServerMetaDataTest(unittest.TestCase): def test_index_nonexistant_server(self): self.stubs.Set(nova.db.api, 'instance_get', return_server_nonexistant) - req = webob.Request.blank('/v1.1/servers/1/meta') - req.environ['api.version'] = '1.1' + req = webob.Request.blank('/v1.1/servers/1/metadata') res = req.get_response(fakes.wsgi_app()) self.assertEqual(404, res.status_int) def test_index_no_data(self): self.stubs.Set(nova.db.api, 'instance_metadata_get', return_empty_server_metadata) - req = webob.Request.blank('/v1.1/servers/1/meta') - req.environ['api.version'] = '1.1' + req = webob.Request.blank('/v1.1/servers/1/metadata') res = req.get_response(fakes.wsgi_app()) res_dict = json.loads(res.body) self.assertEqual(200, res.status_int) @@ -123,8 +120,7 @@ class ServerMetaDataTest(unittest.TestCase): def test_show(self): self.stubs.Set(nova.db.api, 'instance_metadata_get', return_server_metadata) - req = webob.Request.blank('/v1.1/servers/1/meta/key5') - req.environ['api.version'] = '1.1' + req = webob.Request.blank('/v1.1/servers/1/metadata/key5') res = req.get_response(fakes.wsgi_app()) res_dict = json.loads(res.body) self.assertEqual(200, res.status_int) @@ -133,32 +129,28 @@ class ServerMetaDataTest(unittest.TestCase): def test_show_nonexistant_server(self): self.stubs.Set(nova.db.api, 'instance_get', return_server_nonexistant) - req = webob.Request.blank('/v1.1/servers/1/meta/key5') - req.environ['api.version'] = '1.1' + req = webob.Request.blank('/v1.1/servers/1/metadata/key5') res = req.get_response(fakes.wsgi_app()) self.assertEqual(404, res.status_int) def test_show_meta_not_found(self): self.stubs.Set(nova.db.api, 'instance_metadata_get', return_empty_server_metadata) - req = webob.Request.blank('/v1.1/servers/1/meta/key6') - req.environ['api.version'] = '1.1' + req = webob.Request.blank('/v1.1/servers/1/metadata/key6') res = req.get_response(fakes.wsgi_app()) self.assertEqual(404, res.status_int) def test_delete(self): self.stubs.Set(nova.db.api, 'instance_metadata_delete', delete_server_metadata) - req = webob.Request.blank('/v1.1/servers/1/meta/key5') - req.environ['api.version'] = '1.1' + req = webob.Request.blank('/v1.1/servers/1/metadata/key5') req.method = 'DELETE' res = req.get_response(fakes.wsgi_app()) self.assertEqual(200, res.status_int) def test_delete_nonexistant_server(self): self.stubs.Set(nova.db.api, 'instance_get', return_server_nonexistant) - req = webob.Request.blank('/v1.1/servers/1/meta/key5') - req.environ['api.version'] = '1.1' + req = webob.Request.blank('/v1.1/servers/1/metadata/key5') req.method = 'DELETE' res = req.get_response(fakes.wsgi_app()) self.assertEqual(404, res.status_int) @@ -166,8 +158,7 @@ class ServerMetaDataTest(unittest.TestCase): def test_create(self): self.stubs.Set(nova.db.api, 'instance_metadata_update_or_create', return_create_instance_metadata) - req = webob.Request.blank('/v1.1/servers/1/meta') - req.environ['api.version'] = '1.1' + req = webob.Request.blank('/v1.1/servers/1/metadata') req.method = 'POST' req.body = '{"metadata": {"key1": "value1"}}' req.headers["content-type"] = "application/json" @@ -180,8 +171,7 @@ class ServerMetaDataTest(unittest.TestCase): def test_create_empty_body(self): self.stubs.Set(nova.db.api, 'instance_metadata_update_or_create', return_create_instance_metadata) - req = webob.Request.blank('/v1.1/servers/1/meta') - req.environ['api.version'] = '1.1' + req = webob.Request.blank('/v1.1/servers/1/metadata') req.method = 'POST' req.headers["content-type"] = "application/json" res = req.get_response(fakes.wsgi_app()) @@ -189,8 +179,7 @@ class ServerMetaDataTest(unittest.TestCase): def test_create_nonexistant_server(self): self.stubs.Set(nova.db.api, 'instance_get', return_server_nonexistant) - req = webob.Request.blank('/v1.1/servers/100/meta') - req.environ['api.version'] = '1.1' + req = webob.Request.blank('/v1.1/servers/100/metadata') req.method = 'POST' req.body = '{"metadata": {"key1": "value1"}}' req.headers["content-type"] = "application/json" @@ -200,8 +189,7 @@ class ServerMetaDataTest(unittest.TestCase): def test_update_item(self): self.stubs.Set(nova.db.api, 'instance_metadata_update_or_create', return_create_instance_metadata) - req = webob.Request.blank('/v1.1/servers/1/meta/key1') - req.environ['api.version'] = '1.1' + req = webob.Request.blank('/v1.1/servers/1/metadata/key1') req.method = 'PUT' req.body = '{"key1": "value1"}' req.headers["content-type"] = "application/json" @@ -213,8 +201,7 @@ class ServerMetaDataTest(unittest.TestCase): def test_update_item_nonexistant_server(self): self.stubs.Set(nova.db.api, 'instance_get', return_server_nonexistant) - req = webob.Request.blank('/v1.1/servers/asdf/100/key1') - req.environ['api.version'] = '1.1' + req = webob.Request.blank('/v1.1/servers/asdf/metadata/key1') req.method = 'PUT' req.body = '{"key1": "value1"}' req.headers["content-type"] = "application/json" @@ -224,9 +211,7 @@ class ServerMetaDataTest(unittest.TestCase): def test_update_item_empty_body(self): self.stubs.Set(nova.db.api, 'instance_metadata_update_or_create', return_create_instance_metadata) - req = webob.Request.blank('/v1.1/servers/1/meta/key1') - req.environ['api.version'] = '1.1' - req.method = 'PUT' + req = webob.Request.blank('/v1.1/servers/1/metadata/key1') req.headers["content-type"] = "application/json" res = req.get_response(fakes.wsgi_app()) self.assertEqual(400, res.status_int) @@ -234,8 +219,7 @@ class ServerMetaDataTest(unittest.TestCase): def test_update_item_too_many_keys(self): self.stubs.Set(nova.db.api, 'instance_metadata_update_or_create', return_create_instance_metadata) - req = webob.Request.blank('/v1.1/servers/1/meta/key1') - req.environ['api.version'] = '1.1' + req = webob.Request.blank('/v1.1/servers/1/metadata/key1') req.method = 'PUT' req.body = '{"key1": "value1", "key2": "value2"}' req.headers["content-type"] = "application/json" @@ -245,8 +229,7 @@ class ServerMetaDataTest(unittest.TestCase): def test_update_item_body_uri_mismatch(self): self.stubs.Set(nova.db.api, 'instance_metadata_update_or_create', return_create_instance_metadata) - req = webob.Request.blank('/v1.1/servers/1/meta/bad') - req.environ['api.version'] = '1.1' + req = webob.Request.blank('/v1.1/servers/1/metadata/bad') req.method = 'PUT' req.body = '{"key1": "value1"}' req.headers["content-type"] = "application/json" @@ -260,8 +243,7 @@ class ServerMetaDataTest(unittest.TestCase): for num in range(FLAGS.quota_metadata_items + 1): data['metadata']['key%i' % num] = "blah" json_string = str(data).replace("\'", "\"") - req = webob.Request.blank('/v1.1/servers/1/meta') - req.environ['api.version'] = '1.1' + req = webob.Request.blank('/v1.1/servers/1/metadata') req.method = 'POST' req.body = json_string req.headers["content-type"] = "application/json" @@ -271,8 +253,7 @@ class ServerMetaDataTest(unittest.TestCase): def test_to_many_metadata_items_on_update_item(self): self.stubs.Set(nova.db.api, 'instance_metadata_update_or_create', return_create_instance_metadata_max) - req = webob.Request.blank('/v1.1/servers/1/meta/key1') - req.environ['api.version'] = '1.1' + req = webob.Request.blank('/v1.1/servers/1/metadata/key1') req.method = 'PUT' req.body = '{"a new key": "a new value"}' req.headers["content-type"] = "application/json" -- cgit From 0760948609ac89a43a590b36e79d691a6c79b4c3 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Wed, 27 Jul 2011 11:32:18 -0400 Subject: updating servers metadata resource --- nova/api/openstack/__init__.py | 11 +- nova/api/openstack/common.py | 4 +- nova/api/openstack/server_metadata.py | 113 ++++++++---- nova/tests/api/openstack/test_server_metadata.py | 220 ++++++++++++++++++++--- 4 files changed, 279 insertions(+), 69 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index 1d14474a8..6585f1751 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -164,7 +164,9 @@ class APIRouterV11(APIRouter): def _setup_routes(self, mapper): super(APIRouterV11, self)._setup_routes(mapper, '1.1') + image_metadata_controller = image_metadata.create_resource() + mapper.resource("image_meta", "metadata", controller=image_metadata_controller, parent_resource=dict(member_name='image', @@ -175,7 +177,14 @@ class APIRouterV11(APIRouter): action='update_all', conditions={"method": ['PUT']}) + server_metadata_controller = server_metadata.create_resource() + mapper.resource("server_meta", "metadata", - controller=server_metadata.create_resource(), + controller=server_metadata_controller, parent_resource=dict(member_name='server', collection_name='servers')) + + mapper.connect("metadata", "/servers/{server_id}/metadata", + controller=server_metadata_controller, + action='update_all', + conditions={"method": ['PUT']}) diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py index efa4ab385..24c82035a 100644 --- a/nova/api/openstack/common.py +++ b/nova/api/openstack/common.py @@ -247,7 +247,7 @@ class MetadataXMLSerializer(wsgi.XMLDictSerializer): container_node = self.meta_list_to_xml(xml_doc, items) xml_doc.appendChild(container_node) self._add_xmlns(container_node) - return xml_doc.toprettyxml(indent=' ', encoding='UTF-8') + return xml_doc.toxml('UTF-8') def index(self, metadata_dict): return self._meta_list_to_xml_string(metadata_dict) @@ -264,7 +264,7 @@ class MetadataXMLSerializer(wsgi.XMLDictSerializer): item_node = self._meta_item_to_xml(xml_doc, item_key, item_value) xml_doc.appendChild(item_node) self._add_xmlns(item_node) - return xml_doc.toprettyxml(indent=' ', encoding='UTF-8') + return xml_doc.toxml('UTF-8') def show(self, meta_item_dict): return self._meta_item_to_xml_string(meta_item_dict['meta']) diff --git a/nova/api/openstack/server_metadata.py b/nova/api/openstack/server_metadata.py index d4f42bbf5..d8f9cdbac 100644 --- a/nova/api/openstack/server_metadata.py +++ b/nova/api/openstack/server_metadata.py @@ -18,6 +18,7 @@ from webob import exc from nova import compute +from nova.api.openstack import common from nova.api.openstack import wsgi from nova import exception from nova import quota @@ -31,36 +32,37 @@ class Controller(object): super(Controller, self).__init__() def _get_metadata(self, context, server_id): - metadata = self.compute_api.get_instance_metadata(context, server_id) + try: + meta = self.compute_api.get_instance_metadata(context, server_id) + except exception.InstanceNotFound: + msg = _('Server does not exist') + raise exc.HTTPNotFound(explanation=msg) + meta_dict = {} - for key, value in metadata.iteritems(): + for key, value in meta.iteritems(): meta_dict[key] = value - return dict(metadata=meta_dict) - - def _check_body(self, body): - if body == None or body == "": - expl = _('No Request Body') - raise exc.HTTPBadRequest(explanation=expl) + return meta_dict def index(self, req, server_id): """ Returns the list of metadata for a given instance """ context = req.environ['nova.context'] - try: - return self._get_metadata(context, server_id) - except exception.InstanceNotFound: - msg = _('Server %(server_id)s does not exist') % locals() - raise exc.HTTPNotFound(explanation=msg) + return {'metadata': self._get_metadata(context, server_id)} def create(self, req, server_id, body): - self._check_body(body) + try: + metadata = body['metadata'] + except (KeyError, TypeError): + msg = _("Malformed request body") + raise exc.HTTPBadRequest(esplanation=msg) + context = req.environ['nova.context'] - metadata = body.get('metadata') + try: self.compute_api.update_or_create_instance_metadata(context, server_id, metadata) except exception.InstanceNotFound: - msg = _('Server %(server_id)s does not exist') % locals() + msg = _('Server does not exist') raise exc.HTTPNotFound(explanation=msg) except quota.QuotaError as error: @@ -69,51 +71,80 @@ class Controller(object): return body def update(self, req, server_id, id, body): - self._check_body(body) - context = req.environ['nova.context'] - if not id in body: + try: + meta_item = body['meta'] + except (TypeError, KeyError): + expl = _('Malformed request body') + raise exc.HTTPBadRequest(explanation=expl) + + try: + meta_value = meta_item.pop(id) + except (AttributeError, KeyError): expl = _('Request body and URI mismatch') raise exc.HTTPBadRequest(explanation=expl) - if len(body) > 1: + + if len(meta_item) > 0: expl = _('Request body contains too many items') raise exc.HTTPBadRequest(explanation=expl) + + context = req.environ['nova.context'] + self._set_instance_metadata(context, server_id, meta_item) + + return {'meta': {id: meta_value}} + + def update_all(self, req, server_id, body): + try: + metadata = body['metadata'] + except (TypeError, KeyError): + expl = _('Malformed request body') + raise exc.HTTPBadRequest(explanation=expl) + + context = req.environ['nova.context'] + self._set_instance_metadata(context, server_id, metadata) + + return {'metadata': metadata} + + def _set_instance_metadata(self, context, server_id, metadata): try: self.compute_api.update_or_create_instance_metadata(context, server_id, - body) + metadata) except exception.InstanceNotFound: - msg = _('Server %(server_id)s does not exist') % locals() + msg = _('Server does not exist') raise exc.HTTPNotFound(explanation=msg) + except ValueError: + msg = _("Malformed request body") + raise exc.HTTPBadRequest(explanation=msg) + except quota.QuotaError as error: self._handle_quota_error(error) - return body - def show(self, req, server_id, id): """ Return a single metadata item """ context = req.environ['nova.context'] - try: - data = self._get_metadata(context, server_id) - except exception.InstanceNotFound: - msg = _('Server %(server_id)s does not exist') % locals() - raise exc.HTTPNotFound(explanation=msg) + data = self._get_metadata(context, server_id) try: - return {id: data['metadata'][id]} + return {'meta': {id: data[id]}} except KeyError: - msg = _("metadata item %s was not found" % (id)) + msg = _("Metadata item was not found") raise exc.HTTPNotFound(explanation=msg) def delete(self, req, server_id, id): """ Deletes an existing metadata """ context = req.environ['nova.context'] + + metadata = self._get_metadata(context, server_id) + try: - self.compute_api.delete_instance_metadata(context, server_id, id) - except exception.InstanceNotFound: - msg = _('Server %(server_id)s does not exist') % locals() + meta_key = metadata[id] + except KeyError: + msg = _("Metadata item was not found") raise exc.HTTPNotFound(explanation=msg) + self.compute_api.delete_instance_metadata(context, server_id, meta_key) + def _handle_quota_error(self, error): """Reraise quota errors as api-specific http exceptions.""" if error.code == "MetadataLimitExceeded": @@ -122,10 +153,16 @@ class Controller(object): def create_resource(): - body_serializers = { - 'application/xml': wsgi.XMLDictSerializer(xmlns=wsgi.XMLNS_V11), + headers_serializer = common.MetadataHeadersSerializer() + + body_deserializers = { + 'application/xml': common.MetadataXMLDeserializer(), } - serializer = wsgi.ResponseSerializer(body_serializers) + body_serializers = { + 'application/xml': common.MetadataXMLSerializer(), + } + serializer = wsgi.ResponseSerializer(body_serializers, headers_serializer) + deserializer = wsgi.RequestDeserializer(body_deserializers) - return wsgi.Resource(Controller(), serializer=serializer) + return wsgi.Resource(Controller(), deserializer, serializer) diff --git a/nova/tests/api/openstack/test_server_metadata.py b/nova/tests/api/openstack/test_server_metadata.py index 12fc7665b..ded30a950 100644 --- a/nova/tests/api/openstack/test_server_metadata.py +++ b/nova/tests/api/openstack/test_server_metadata.py @@ -19,7 +19,7 @@ import json import stubout import unittest import webob - +from xml.dom import minidom from nova import exception from nova import flags @@ -53,11 +53,10 @@ def delete_server_metadata(context, server_id, key): def stub_server_metadata(): metadata = { - "key1": "value1", - "key2": "value2", - "key3": "value3", - "key4": "value4", - "key5": "value5"} + "key1": "value1", + "key2": "value2", + "key3": "value3", + } return metadata @@ -96,10 +95,38 @@ class ServerMetaDataTest(unittest.TestCase): return_server_metadata) req = webob.Request.blank('/v1.1/servers/1/metadata') res = req.get_response(fakes.wsgi_app()) - res_dict = json.loads(res.body) self.assertEqual(200, res.status_int) + res_dict = json.loads(res.body) self.assertEqual('application/json', res.headers['Content-Type']) - self.assertEqual('value1', res_dict['metadata']['key1']) + expected = { + 'metadata': { + 'key1':'value1', + 'key2':'value2', + 'key3':'value3', + }, + } + self.assertEqual(expected, res_dict) + + def test_index_xml(self): + self.stubs.Set(nova.db.api, 'instance_metadata_get', + return_server_metadata) + request = webob.Request.blank("/v1.1/servers/1/metadata") + request.accept = "application/xml" + response = request.get_response(fakes.wsgi_app()) + self.assertEqual(200, response.status_int) + self.assertEqual("application/xml", response.content_type) + + actual_metadata = minidom.parseString(response.body.replace(" ", "")) + + expected_metadata = minidom.parseString(""" + + value3 + value2 + value1 + + """.replace(" ", "").replace("\n","")) + + self.assertEqual(expected_metadata.toxml(), actual_metadata.toxml()) def test_index_nonexistant_server(self): self.stubs.Set(nova.db.api, 'instance_get', return_server_nonexistant) @@ -112,24 +139,42 @@ class ServerMetaDataTest(unittest.TestCase): return_empty_server_metadata) req = webob.Request.blank('/v1.1/servers/1/metadata') res = req.get_response(fakes.wsgi_app()) - res_dict = json.loads(res.body) self.assertEqual(200, res.status_int) - self.assertEqual('application/json', res.headers['Content-Type']) - self.assertEqual(0, len(res_dict['metadata'])) + res_dict = json.loads(res.body) + expected = {'metadata': {}} + self.assertEqual(expected, res_dict) def test_show(self): self.stubs.Set(nova.db.api, 'instance_metadata_get', return_server_metadata) - req = webob.Request.blank('/v1.1/servers/1/metadata/key5') + req = webob.Request.blank('/v1.1/servers/1/metadata/key2') res = req.get_response(fakes.wsgi_app()) res_dict = json.loads(res.body) self.assertEqual(200, res.status_int) - self.assertEqual('application/json', res.headers['Content-Type']) - self.assertEqual('value5', res_dict['key5']) + expected = {'meta': {'key2': 'value2'}} + self.assertEqual(expected, res_dict) + + def test_show_xml(self): + self.stubs.Set(nova.db.api, 'instance_metadata_get', + return_server_metadata) + request = webob.Request.blank("/v1.1/servers/1/metadata/key2") + request.accept = "application/xml" + response = request.get_response(fakes.wsgi_app()) + self.assertEqual(200, response.status_int) + self.assertEqual("application/xml", response.content_type) + + actual_metadata = minidom.parseString(response.body.replace(" ", "")) + + expected_metadata = minidom.parseString(""" + value2 + """.replace(" ", "").replace("\n","")) + + self.assertEqual(expected_metadata.toxml(), actual_metadata.toxml()) def test_show_nonexistant_server(self): self.stubs.Set(nova.db.api, 'instance_get', return_server_nonexistant) - req = webob.Request.blank('/v1.1/servers/1/metadata/key5') + req = webob.Request.blank('/v1.1/servers/1/metadata/key2') res = req.get_response(fakes.wsgi_app()) self.assertEqual(404, res.status_int) @@ -141,16 +186,27 @@ class ServerMetaDataTest(unittest.TestCase): self.assertEqual(404, res.status_int) def test_delete(self): + self.stubs.Set(nova.db.api, 'instance_metadata_get', + return_server_metadata) self.stubs.Set(nova.db.api, 'instance_metadata_delete', delete_server_metadata) - req = webob.Request.blank('/v1.1/servers/1/metadata/key5') + req = webob.Request.blank('/v1.1/servers/1/metadata/key2') req.method = 'DELETE' res = req.get_response(fakes.wsgi_app()) - self.assertEqual(200, res.status_int) + self.assertEqual(204, res.status_int) + self.assertEqual('', res.body) def test_delete_nonexistant_server(self): self.stubs.Set(nova.db.api, 'instance_get', return_server_nonexistant) - req = webob.Request.blank('/v1.1/servers/1/metadata/key5') + req = webob.Request.blank('/v1.1/servers/1/metadata/key1') + req.method = 'DELETE' + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(404, res.status_int) + + def test_delete_meta_not_found(self): + self.stubs.Set(nova.db.api, 'instance_metadata_get', + return_empty_server_metadata) + req = webob.Request.blank('/v1.1/servers/1/metadata/key6') req.method = 'DELETE' res = req.get_response(fakes.wsgi_app()) self.assertEqual(404, res.status_int) @@ -160,13 +216,38 @@ class ServerMetaDataTest(unittest.TestCase): return_create_instance_metadata) req = webob.Request.blank('/v1.1/servers/1/metadata') req.method = 'POST' - req.body = '{"metadata": {"key1": "value1"}}' - req.headers["content-type"] = "application/json" + req.content_type = "application/json" + expected = {"metadata": {"key1": "value1"}} + req.body = json.dumps(expected) res = req.get_response(fakes.wsgi_app()) + self.assertEqual(200, res.status_int) res_dict = json.loads(res.body) - self.assertEqual('application/json', res.headers['Content-Type']) - self.assertEqual('value1', res_dict['metadata']['key1']) + self.assertEqual(expected, res_dict) + + def test_create_xml(self): + self.stubs.Set(nova.db.api, "instance_metadata_update_or_create", + return_create_instance_metadata) + req = webob.Request.blank("/v1.1/servers/1/metadata") + req.method = "POST" + req.content_type = "application/xml" + req.accept = "application/xml" + + request_metadata = minidom.parseString(""" + + value3 + value2 + value1 + + """.replace(" ","").replace("\n","")) + + req.body = str(request_metadata.toxml()) + response = req.get_response(fakes.wsgi_app()) + + self.assertEqual(200, response.status_int) + actual_metadata = minidom.parseString(response.body) + + self.assertEqual(request_metadata.toxml(), actual_metadata.toxml()) def test_create_empty_body(self): self.stubs.Set(nova.db.api, 'instance_metadata_update_or_create', @@ -186,24 +267,105 @@ class ServerMetaDataTest(unittest.TestCase): res = req.get_response(fakes.wsgi_app()) self.assertEqual(404, res.status_int) + def test_update_all(self): + self.stubs.Set(nova.db.api, 'instance_metadata_update_or_create', + return_create_instance_metadata) + req = webob.Request.blank('/v1.1/servers/1/metadata') + req.method = 'PUT' + req.content_type = "application/json" + expected = { + 'metadata': { + 'key10': 'value10', + 'key99': 'value99', + }, + } + req.body = json.dumps(expected) + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(200, res.status_int) + res_dict = json.loads(res.body) + self.assertEqual(expected, res_dict) + + def test_update_all_empty_container(self): + self.stubs.Set(nova.db.api, 'instance_metadata_update_or_create', + return_create_instance_metadata) + req = webob.Request.blank('/v1.1/servers/1/metadata') + req.method = 'PUT' + req.content_type = "application/json" + expected = {'metadata': {}} + req.body = json.dumps(expected) + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(200, res.status_int) + res_dict = json.loads(res.body) + self.assertEqual(expected, res_dict) + + def test_update_all_malformed_container(self): + self.stubs.Set(nova.db.api, 'instance_metadata_update_or_create', + return_create_instance_metadata) + req = webob.Request.blank('/v1.1/servers/1/metadata') + req.method = 'PUT' + req.content_type = "application/json" + expected = {'meta': {}} + req.body = json.dumps(expected) + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(400, res.status_int) + + def test_update_all_malformed_data(self): + self.stubs.Set(nova.db.api, 'instance_metadata_update_or_create', + return_create_instance_metadata) + req = webob.Request.blank('/v1.1/servers/1/metadata') + req.method = 'PUT' + req.content_type = "application/json" + expected = {'metadata': ['asdf']} + req.body = json.dumps(expected) + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(400, res.status_int) + + def test_update_all_nonexistant_server(self): + self.stubs.Set(nova.db.api, 'instance_get', return_server_nonexistant) + req = webob.Request.blank('/v1.1/servers/100/metadata') + req.method = 'PUT' + req.content_type = "application/json" + req.body = json.dumps({'metadata': {'key10': 'value10'}}) + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(404, res.status_int) + def test_update_item(self): self.stubs.Set(nova.db.api, 'instance_metadata_update_or_create', return_create_instance_metadata) req = webob.Request.blank('/v1.1/servers/1/metadata/key1') req.method = 'PUT' - req.body = '{"key1": "value1"}' + req.body = '{"meta": {"key1": "value1"}}' req.headers["content-type"] = "application/json" res = req.get_response(fakes.wsgi_app()) self.assertEqual(200, res.status_int) self.assertEqual('application/json', res.headers['Content-Type']) res_dict = json.loads(res.body) - self.assertEqual('value1', res_dict['key1']) + expected = {'meta': {'key1': 'value1'}} + self.assertEqual(expected, res_dict) + + def test_update_item_xml(self): + self.stubs.Set(nova.db.api, 'instance_metadata_update_or_create', + return_create_instance_metadata) + req = webob.Request.blank('/v1.1/servers/1/metadata/key9') + req.method = 'PUT' + req.accept = "application/json" + req.content_type = "application/xml" + req.body = """ + value9 + """ + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(200, res.status_int) + self.assertEqual('application/json', res.headers['Content-Type']) + res_dict = json.loads(res.body) + expected = {'meta': {'key9': 'value9'}} + self.assertEqual(expected, res_dict) def test_update_item_nonexistant_server(self): self.stubs.Set(nova.db.api, 'instance_get', return_server_nonexistant) req = webob.Request.blank('/v1.1/servers/asdf/metadata/key1') req.method = 'PUT' - req.body = '{"key1": "value1"}' + req.body = '{"meta":{"key1": "value1"}}' req.headers["content-type"] = "application/json" res = req.get_response(fakes.wsgi_app()) self.assertEqual(404, res.status_int) @@ -212,6 +374,7 @@ class ServerMetaDataTest(unittest.TestCase): self.stubs.Set(nova.db.api, 'instance_metadata_update_or_create', return_create_instance_metadata) req = webob.Request.blank('/v1.1/servers/1/metadata/key1') + req.method = 'PUT' req.headers["content-type"] = "application/json" res = req.get_response(fakes.wsgi_app()) self.assertEqual(400, res.status_int) @@ -221,7 +384,7 @@ class ServerMetaDataTest(unittest.TestCase): return_create_instance_metadata) req = webob.Request.blank('/v1.1/servers/1/metadata/key1') req.method = 'PUT' - req.body = '{"key1": "value1", "key2": "value2"}' + req.body = '{"meta": {"key1": "value1", "key2": "value2"}}' req.headers["content-type"] = "application/json" res = req.get_response(fakes.wsgi_app()) self.assertEqual(400, res.status_int) @@ -231,7 +394,7 @@ class ServerMetaDataTest(unittest.TestCase): return_create_instance_metadata) req = webob.Request.blank('/v1.1/servers/1/metadata/bad') req.method = 'PUT' - req.body = '{"key1": "value1"}' + req.body = '{"meta": {"key1": "value1"}}' req.headers["content-type"] = "application/json" res = req.get_response(fakes.wsgi_app()) self.assertEqual(400, res.status_int) @@ -255,7 +418,8 @@ class ServerMetaDataTest(unittest.TestCase): return_create_instance_metadata_max) req = webob.Request.blank('/v1.1/servers/1/metadata/key1') req.method = 'PUT' - req.body = '{"a new key": "a new value"}' + req.body = '{"meta": {"a new key": "a new value"}}' req.headers["content-type"] = "application/json" res = req.get_response(fakes.wsgi_app()) self.assertEqual(400, res.status_int) + -- cgit From 4add644dbd1650a3e83e715c6d6a38c4114e4d06 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Wed, 27 Jul 2011 11:39:34 -0400 Subject: Moved server/actions tests to test_server_actions.py --- nova/tests/api/openstack/test_server_actions.py | 628 ++++++++++++++++++++++++ nova/tests/api/openstack/test_servers.py | 492 ------------------- 2 files changed, 628 insertions(+), 492 deletions(-) create mode 100644 nova/tests/api/openstack/test_server_actions.py (limited to 'nova') diff --git a/nova/tests/api/openstack/test_server_actions.py b/nova/tests/api/openstack/test_server_actions.py new file mode 100644 index 000000000..97c108068 --- /dev/null +++ b/nova/tests/api/openstack/test_server_actions.py @@ -0,0 +1,628 @@ +import base64 +import json +import unittest +from xml.dom import minidom + +import stubout +import webob + +from nova import context +from nova import db +from nova import utils +from nova.api.openstack import create_instance_helper +from nova.compute import instance_types +from nova.compute import power_state +import nova.db.api +from nova import test +from nova.tests.api.openstack import common +from nova.tests.api.openstack import fakes + + +def return_server_by_id(context, id): + return _get_instance() + + +def instance_update(context, instance_id, kwargs): + return _get_instance() + + +def return_server_with_power_state(power_state): + def _return_server(context, id): + instance = _get_instance() + instance['state'] = power_state + return instance + return _return_server + + +def return_server_with_uuid_and_power_state(power_state): + def _return_server(context, id): + return return_server_with_power_state(power_state) + return _return_server + + +class MockSetAdminPassword(object): + def __init__(self): + self.instance_id = None + self.password = None + + def __call__(self, context, instance_id, password): + self.instance_id = instance_id + self.password = password + + +def _get_instance(): + instance = { + "id": 1, + "created_at": "2010-10-10T12:00:00Z", + "updated_at": "2010-11-11T11:00:00Z", + "admin_pass": "", + "user_id": "", + "project_id": "", + "image_ref": "5", + "kernel_id": "", + "ramdisk_id": "", + "launch_index": 0, + "key_name": "", + "key_data": "", + "state": 0, + "state_description": "", + "memory_mb": 0, + "vcpus": 0, + "local_gb": 0, + "hostname": "", + "host": "", + "instance_type": { + "flavorid": 1, + }, + "user_data": "", + "reservation_id": "", + "mac_address": "", + "scheduled_at": utils.utcnow(), + "launched_at": utils.utcnow(), + "terminated_at": utils.utcnow(), + "availability_zone": "", + "display_name": "test_server", + "display_description": "", + "locked": False, + "metadata": [], + #"address": , + #"floating_ips": [{"address":ip} for ip in public_addresses]} + "uuid": "deadbeef-feed-edee-beef-d0ea7beefedd"} + + return instance + + +class ServerActionsTest(test.TestCase): + + def setUp(self): + self.maxDiff = None + super(ServerActionsTest, self).setUp() + self.stubs = stubout.StubOutForTesting() + fakes.FakeAuthManager.reset_fake_data() + fakes.FakeAuthDatabase.data = {} + fakes.stub_out_auth(self.stubs) + self.stubs.Set(nova.db.api, 'instance_get', return_server_by_id) + self.stubs.Set(nova.db.api, 'instance_update', instance_update) + + self.webreq = common.webob_factory('/v1.0/servers') + + def tearDown(self): + self.stubs.UnsetAll() + + def test_server_actions(self): + req = webob.Request.blank("/v1.0/servers/1/actions") + req.method = "GET" + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 404) + + def test_server_change_password(self): + body = {'changePassword': {'adminPass': '1234pass'}} + req = webob.Request.blank('/v1.0/servers/1/action') + req.method = 'POST' + req.content_type = 'application/json' + req.body = json.dumps(body) + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 501) + + def test_server_change_password_xml(self): + req = webob.Request.blank('/v1.0/servers/1/action') + req.method = 'POST' + req.content_type = 'application/xml' + req.body = '' +# res = req.get_response(fakes.wsgi_app()) +# self.assertEqual(res.status_int, 501) + + def test_server_change_password_not_a_string_v1_1(self): + body = {'changePassword': {'adminPass': 1234}} + req = webob.Request.blank('/v1.1/servers/1/action') + req.method = 'POST' + req.content_type = 'application/json' + req.body = json.dumps(body) + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 400) + + def test_server_reboot(self): + body = dict(server=dict( + name='server_test', imageId=2, flavorId=2, metadata={}, + personality={})) + req = webob.Request.blank('/v1.0/servers/1/action') + req.method = 'POST' + req.content_type = 'application/json' + req.body = json.dumps(body) + res = req.get_response(fakes.wsgi_app()) + + def test_server_rebuild_accepted(self): + body = { + "rebuild": { + "imageId": 2, + }, + } + + req = webob.Request.blank('/v1.0/servers/1/action') + req.method = 'POST' + req.content_type = 'application/json' + req.body = json.dumps(body) + + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 202) + self.assertEqual(res.body, "") + + def test_server_rebuild_rejected_when_building(self): + body = { + "rebuild": { + "imageId": 2, + }, + } + + state = power_state.BUILDING + new_return_server = return_server_with_power_state(state) + self.stubs.Set(nova.db.api, 'instance_get', new_return_server) + self.stubs.Set(nova.db, 'instance_get_by_uuid', + return_server_with_uuid_and_power_state(state)) + + req = webob.Request.blank('/v1.0/servers/1/action') + req.method = 'POST' + req.content_type = 'application/json' + req.body = json.dumps(body) + + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 409) + + def test_server_rebuild_bad_entity(self): + body = { + "rebuild": { + }, + } + + req = webob.Request.blank('/v1.0/servers/1/action') + req.method = 'POST' + req.content_type = 'application/json' + req.body = json.dumps(body) + + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 400) + + def test_resize_server(self): + req = self.webreq('/1/action', 'POST', dict(resize=dict(flavorId=3))) + + self.resize_called = False + + def resize_mock(*args): + self.resize_called = True + + self.stubs.Set(nova.compute.api.API, 'resize', resize_mock) + + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 202) + self.assertEqual(self.resize_called, True) + + def test_resize_bad_flavor_fails(self): + req = self.webreq('/1/action', 'POST', dict(resize=dict(derp=3))) + + self.resize_called = False + + def resize_mock(*args): + self.resize_called = True + + self.stubs.Set(nova.compute.api.API, 'resize', resize_mock) + + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 422) + self.assertEqual(self.resize_called, False) + + def test_resize_raises_fails(self): + req = self.webreq('/1/action', 'POST', dict(resize=dict(flavorId=3))) + + def resize_mock(*args): + raise Exception('hurr durr') + + self.stubs.Set(nova.compute.api.API, 'resize', resize_mock) + + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 500) + + def test_resized_server_has_correct_status(self): + req = self.webreq('/1', 'GET') + + def fake_migration_get(*args): + return {} + + self.stubs.Set(nova.db, 'migration_get_by_instance_and_status', + fake_migration_get) + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 200) + body = json.loads(res.body) + self.assertEqual(body['server']['status'], 'RESIZE-CONFIRM') + + def test_confirm_resize_server(self): + req = self.webreq('/1/action', 'POST', dict(confirmResize=None)) + + self.resize_called = False + + def confirm_resize_mock(*args): + self.resize_called = True + + self.stubs.Set(nova.compute.api.API, 'confirm_resize', + confirm_resize_mock) + + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 204) + self.assertEqual(self.resize_called, True) + + def test_confirm_resize_server_fails(self): + req = self.webreq('/1/action', 'POST', dict(confirmResize=None)) + + def confirm_resize_mock(*args): + raise Exception('hurr durr') + + self.stubs.Set(nova.compute.api.API, 'confirm_resize', + confirm_resize_mock) + + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 400) + + def test_revert_resize_server(self): + req = self.webreq('/1/action', 'POST', dict(revertResize=None)) + + self.resize_called = False + + def revert_resize_mock(*args): + self.resize_called = True + + self.stubs.Set(nova.compute.api.API, 'revert_resize', + revert_resize_mock) + + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 202) + self.assertEqual(self.resize_called, True) + + def test_revert_resize_server_fails(self): + req = self.webreq('/1/action', 'POST', dict(revertResize=None)) + + def revert_resize_mock(*args): + raise Exception('hurr durr') + + self.stubs.Set(nova.compute.api.API, 'revert_resize', + revert_resize_mock) + + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 400) + + def test_migrate_server(self): + """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)) + + self.resize_called = False + + def resize_mock(*args): + self.resize_called = True + + self.stubs.Set(nova.compute.api.API, 'resize', resize_mock) + + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 202) + self.assertEqual(self.resize_called, True) + + +class ServerActionsTestV11(test.TestCase): + + def setUp(self): + self.maxDiff = None + super(ServerActionsTestV11, self).setUp() + self.stubs = stubout.StubOutForTesting() + fakes.FakeAuthManager.reset_fake_data() + fakes.FakeAuthDatabase.data = {} + fakes.stub_out_auth(self.stubs) + self.stubs.Set(nova.db.api, 'instance_get', return_server_by_id) + self.stubs.Set(nova.db.api, 'instance_update', instance_update) + + 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) + + def tearDown(self): + self.stubs.UnsetAll() + + def test_server_change_password_v1_1(self): + mock_method = MockSetAdminPassword() + self.stubs.Set(nova.compute.api.API, 'set_admin_password', mock_method) + body = {'changePassword': {'adminPass': '1234pass'}} + req = webob.Request.blank('/v1.1/servers/1/action') + req.method = 'POST' + req.content_type = 'application/json' + req.body = json.dumps(body) + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 202) + self.assertEqual(mock_method.instance_id, '1') + self.assertEqual(mock_method.password, '1234pass') + + def test_server_change_password_bad_request_v1_1(self): + body = {'changePassword': {'pass': '12345'}} + req = webob.Request.blank('/v1.1/servers/1/action') + req.method = 'POST' + req.content_type = 'application/json' + req.body = json.dumps(body) + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 400) + + def test_server_change_password_empty_string_v1_1(self): + body = {'changePassword': {'adminPass': ''}} + req = webob.Request.blank('/v1.1/servers/1/action') + req.method = 'POST' + req.content_type = 'application/json' + req.body = json.dumps(body) + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 400) + + def test_server_change_password_none_v1_1(self): + body = {'changePassword': {'adminPass': None}} + req = webob.Request.blank('/v1.1/servers/1/action') + req.method = 'POST' + req.content_type = 'application/json' + req.body = json.dumps(body) + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 400) + + def test_server_rebuild_accepted_minimum_v1_1(self): + body = { + "rebuild": { + "imageRef": "http://localhost/images/2", + }, + } + + req = webob.Request.blank('/v1.1/servers/1/action') + req.method = 'POST' + req.content_type = 'application/json' + req.body = json.dumps(body) + + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 202) + + def test_server_rebuild_rejected_when_building_v1_1(self): + body = { + "rebuild": { + "imageRef": "http://localhost/images/2", + }, + } + + state = power_state.BUILDING + new_return_server = return_server_with_power_state(state) + self.stubs.Set(nova.db.api, 'instance_get', new_return_server) + self.stubs.Set(nova.db, 'instance_get_by_uuid', + return_server_with_uuid_and_power_state(state)) + + req = webob.Request.blank('/v1.1/servers/1/action') + req.method = 'POST' + req.content_type = 'application/json' + req.body = json.dumps(body) + + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 409) + + def test_server_rebuild_accepted_with_metadata_v1_1(self): + body = { + "rebuild": { + "imageRef": "http://localhost/images/2", + "metadata": { + "new": "metadata", + }, + }, + } + + req = webob.Request.blank('/v1.1/servers/1/action') + req.method = 'POST' + req.content_type = 'application/json' + req.body = json.dumps(body) + + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 202) + + def test_server_rebuild_accepted_with_bad_metadata_v1_1(self): + body = { + "rebuild": { + "imageRef": "http://localhost/images/2", + "metadata": "stack", + }, + } + + req = webob.Request.blank('/v1.1/servers/1/action') + req.method = 'POST' + req.content_type = 'application/json' + req.body = json.dumps(body) + + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 400) + + def test_server_rebuild_bad_entity_v1_1(self): + body = { + "rebuild": { + "imageId": 2, + }, + } + + req = webob.Request.blank('/v1.1/servers/1/action') + req.method = 'POST' + req.content_type = 'application/json' + req.body = json.dumps(body) + + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 400) + + def test_server_rebuild_bad_personality_v1_1(self): + body = { + "rebuild": { + "imageRef": "http://localhost/images/2", + "personality": [{ + "path": "/path/to/file", + "contents": "INVALID b64", + }] + }, + } + + req = webob.Request.blank('/v1.1/servers/1/action') + req.method = 'POST' + req.content_type = 'application/json' + req.body = json.dumps(body) + + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 400) + + def test_server_rebuild_personality_v1_1(self): + body = { + "rebuild": { + "imageRef": "http://localhost/images/2", + "personality": [{ + "path": "/path/to/file", + "contents": base64.b64encode("Test String"), + }] + }, + } + + req = webob.Request.blank('/v1.1/servers/1/action') + req.method = 'POST' + req.content_type = 'application/json' + req.body = json.dumps(body) + + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 202) + + def test_resize_server_v11(self): + + req = webob.Request.blank('/v1.1/servers/1/action') + req.content_type = 'application/json' + req.method = 'POST' + body_dict = dict(resize=dict(flavorRef="http://localhost/3")) + req.body = json.dumps(body_dict) + + self.resize_called = False + + def resize_mock(*args): + self.resize_called = True + + self.stubs.Set(nova.compute.api.API, 'resize', resize_mock) + + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 202) + self.assertEqual(self.resize_called, True) + + 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 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) diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 9d2c7b73f..b68183429 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -1766,262 +1766,6 @@ class ServersTest(test.TestCase): res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 404) - def test_server_actions(self): - req = webob.Request.blank("/v1.0/servers/1/actions") - req.method = "GET" - res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status_int, 404) - - def test_server_change_password(self): - body = {'changePassword': {'adminPass': '1234pass'}} - req = webob.Request.blank('/v1.0/servers/1/action') - req.method = 'POST' - req.content_type = 'application/json' - req.body = json.dumps(body) - res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status_int, 501) - - def test_server_change_password_xml(self): - req = webob.Request.blank('/v1.0/servers/1/action') - req.method = 'POST' - req.content_type = 'application/xml' - req.body = '' -# res = req.get_response(fakes.wsgi_app()) -# self.assertEqual(res.status_int, 501) - - def test_server_change_password_v1_1(self): - mock_method = MockSetAdminPassword() - self.stubs.Set(nova.compute.api.API, 'set_admin_password', mock_method) - body = {'changePassword': {'adminPass': '1234pass'}} - req = webob.Request.blank('/v1.1/servers/1/action') - req.method = 'POST' - req.content_type = 'application/json' - req.body = json.dumps(body) - res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status_int, 202) - self.assertEqual(mock_method.instance_id, '1') - self.assertEqual(mock_method.password, '1234pass') - - def test_server_change_password_bad_request_v1_1(self): - body = {'changePassword': {'pass': '12345'}} - req = webob.Request.blank('/v1.1/servers/1/action') - req.method = 'POST' - req.content_type = 'application/json' - req.body = json.dumps(body) - res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status_int, 400) - - def test_server_change_password_empty_string_v1_1(self): - body = {'changePassword': {'adminPass': ''}} - req = webob.Request.blank('/v1.1/servers/1/action') - req.method = 'POST' - req.content_type = 'application/json' - req.body = json.dumps(body) - res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status_int, 400) - - def test_server_change_password_none_v1_1(self): - body = {'changePassword': {'adminPass': None}} - req = webob.Request.blank('/v1.1/servers/1/action') - req.method = 'POST' - req.content_type = 'application/json' - req.body = json.dumps(body) - res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status_int, 400) - - def test_server_change_password_not_a_string_v1_1(self): - body = {'changePassword': {'adminPass': 1234}} - req = webob.Request.blank('/v1.1/servers/1/action') - req.method = 'POST' - req.content_type = 'application/json' - req.body = json.dumps(body) - res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status_int, 400) - - def test_server_reboot(self): - body = dict(server=dict( - name='server_test', imageId=2, flavorId=2, metadata={}, - personality={})) - req = webob.Request.blank('/v1.0/servers/1/action') - req.method = 'POST' - req.content_type = 'application/json' - req.body = json.dumps(body) - res = req.get_response(fakes.wsgi_app()) - - def test_server_rebuild_accepted(self): - body = { - "rebuild": { - "imageId": 2, - }, - } - - req = webob.Request.blank('/v1.0/servers/1/action') - req.method = 'POST' - req.content_type = 'application/json' - req.body = json.dumps(body) - - res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status_int, 202) - self.assertEqual(res.body, "") - - def test_server_rebuild_rejected_when_building(self): - body = { - "rebuild": { - "imageId": 2, - }, - } - - state = power_state.BUILDING - new_return_server = return_server_with_power_state(state) - self.stubs.Set(nova.db.api, 'instance_get', new_return_server) - self.stubs.Set(nova.db, 'instance_get_by_uuid', - return_server_with_uuid_and_power_state(state)) - - req = webob.Request.blank('/v1.0/servers/1/action') - req.method = 'POST' - req.content_type = 'application/json' - req.body = json.dumps(body) - - res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status_int, 409) - - def test_server_rebuild_bad_entity(self): - body = { - "rebuild": { - }, - } - - req = webob.Request.blank('/v1.0/servers/1/action') - req.method = 'POST' - req.content_type = 'application/json' - req.body = json.dumps(body) - - res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status_int, 400) - - def test_server_rebuild_accepted_minimum_v1_1(self): - body = { - "rebuild": { - "imageRef": "http://localhost/images/2", - }, - } - - req = webob.Request.blank('/v1.1/servers/1/action') - req.method = 'POST' - req.content_type = 'application/json' - req.body = json.dumps(body) - - res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status_int, 202) - - def test_server_rebuild_rejected_when_building_v1_1(self): - body = { - "rebuild": { - "imageRef": "http://localhost/images/2", - }, - } - - state = power_state.BUILDING - new_return_server = return_server_with_power_state(state) - self.stubs.Set(nova.db.api, 'instance_get', new_return_server) - self.stubs.Set(nova.db, 'instance_get_by_uuid', - return_server_with_uuid_and_power_state(state)) - - req = webob.Request.blank('/v1.1/servers/1/action') - req.method = 'POST' - req.content_type = 'application/json' - req.body = json.dumps(body) - - res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status_int, 409) - - def test_server_rebuild_accepted_with_metadata_v1_1(self): - body = { - "rebuild": { - "imageRef": "http://localhost/images/2", - "metadata": { - "new": "metadata", - }, - }, - } - - req = webob.Request.blank('/v1.1/servers/1/action') - req.method = 'POST' - req.content_type = 'application/json' - req.body = json.dumps(body) - - res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status_int, 202) - - def test_server_rebuild_accepted_with_bad_metadata_v1_1(self): - body = { - "rebuild": { - "imageRef": "http://localhost/images/2", - "metadata": "stack", - }, - } - - req = webob.Request.blank('/v1.1/servers/1/action') - req.method = 'POST' - req.content_type = 'application/json' - req.body = json.dumps(body) - - res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status_int, 400) - - def test_server_rebuild_bad_entity_v1_1(self): - body = { - "rebuild": { - "imageId": 2, - }, - } - - req = webob.Request.blank('/v1.1/servers/1/action') - req.method = 'POST' - req.content_type = 'application/json' - req.body = json.dumps(body) - - res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status_int, 400) - - def test_server_rebuild_bad_personality_v1_1(self): - body = { - "rebuild": { - "imageRef": "http://localhost/images/2", - "personality": [{ - "path": "/path/to/file", - "contents": "INVALID b64", - }] - }, - } - - req = webob.Request.blank('/v1.1/servers/1/action') - req.method = 'POST' - req.content_type = 'application/json' - req.body = json.dumps(body) - - res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status_int, 400) - - def test_server_rebuild_personality_v1_1(self): - body = { - "rebuild": { - "imageRef": "http://localhost/images/2", - "personality": [{ - "path": "/path/to/file", - "contents": base64.b64encode("Test String"), - }] - }, - } - - req = webob.Request.blank('/v1.1/servers/1/action') - req.method = 'POST' - req.content_type = 'application/json' - req.body = json.dumps(body) - - res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status_int, 202) - def test_delete_server_instance(self): req = webob.Request.blank('/v1.0/servers/1') req.method = 'DELETE' @@ -2089,147 +1833,6 @@ class ServersTest(test.TestCase): self.assertEqual(res.status_int, 204) self.assertEqual(self.server_delete_called, True) - def test_resize_server(self): - req = self.webreq('/1/action', 'POST', dict(resize=dict(flavorId=3))) - - self.resize_called = False - - def resize_mock(*args): - self.resize_called = True - - self.stubs.Set(nova.compute.api.API, 'resize', resize_mock) - - res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status_int, 202) - self.assertEqual(self.resize_called, True) - - def test_resize_server_v11(self): - - req = webob.Request.blank('/v1.1/servers/1/action') - req.content_type = 'application/json' - req.method = 'POST' - body_dict = dict(resize=dict(flavorRef="http://localhost/3")) - req.body = json.dumps(body_dict) - - self.resize_called = False - - def resize_mock(*args): - self.resize_called = True - - self.stubs.Set(nova.compute.api.API, 'resize', resize_mock) - - res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status_int, 202) - self.assertEqual(self.resize_called, True) - - def test_resize_bad_flavor_fails(self): - req = self.webreq('/1/action', 'POST', dict(resize=dict(derp=3))) - - self.resize_called = False - - def resize_mock(*args): - self.resize_called = True - - self.stubs.Set(nova.compute.api.API, 'resize', resize_mock) - - res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status_int, 422) - self.assertEqual(self.resize_called, False) - - def test_resize_raises_fails(self): - req = self.webreq('/1/action', 'POST', dict(resize=dict(flavorId=3))) - - def resize_mock(*args): - raise Exception('hurr durr') - - self.stubs.Set(nova.compute.api.API, 'resize', resize_mock) - - res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status_int, 500) - - def test_resized_server_has_correct_status(self): - req = self.webreq('/1', 'GET') - - def fake_migration_get(*args): - return {} - - self.stubs.Set(nova.db, 'migration_get_by_instance_and_status', - fake_migration_get) - res = req.get_response(fakes.wsgi_app()) - body = json.loads(res.body) - self.assertEqual(body['server']['status'], 'RESIZE-CONFIRM') - - def test_confirm_resize_server(self): - req = self.webreq('/1/action', 'POST', dict(confirmResize=None)) - - self.resize_called = False - - def confirm_resize_mock(*args): - self.resize_called = True - - self.stubs.Set(nova.compute.api.API, 'confirm_resize', - confirm_resize_mock) - - res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status_int, 204) - self.assertEqual(self.resize_called, True) - - def test_confirm_resize_server_fails(self): - req = self.webreq('/1/action', 'POST', dict(confirmResize=None)) - - def confirm_resize_mock(*args): - raise Exception('hurr durr') - - self.stubs.Set(nova.compute.api.API, 'confirm_resize', - confirm_resize_mock) - - res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status_int, 400) - - def test_revert_resize_server(self): - req = self.webreq('/1/action', 'POST', dict(revertResize=None)) - - self.resize_called = False - - def revert_resize_mock(*args): - self.resize_called = True - - self.stubs.Set(nova.compute.api.API, 'revert_resize', - revert_resize_mock) - - res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status_int, 202) - self.assertEqual(self.resize_called, True) - - def test_revert_resize_server_fails(self): - req = self.webreq('/1/action', 'POST', dict(revertResize=None)) - - def revert_resize_mock(*args): - raise Exception('hurr durr') - - self.stubs.Set(nova.compute.api.API, 'revert_resize', - revert_resize_mock) - - res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status_int, 400) - - def test_migrate_server(self): - """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)) - - self.resize_called = False - - def resize_mock(*args): - self.resize_called = True - - self.stubs.Set(nova.compute.api.API, 'resize', resize_mock) - - res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status_int, 202) - self.assertEqual(self.resize_called, True) - def test_shutdown_status(self): new_server = return_server_with_power_state(power_state.SHUTDOWN) self.stubs.Set(nova.db.api, 'instance_get', new_server) @@ -2248,101 +1851,6 @@ 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 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 TestServerCreateRequestXMLDeserializerV10(unittest.TestCase): -- cgit From 154129acf1ecbdd97e5bb8558598a9b24eb8559f Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Wed, 27 Jul 2011 11:43:20 -0400 Subject: Cleaned up test_servers --- nova/tests/api/openstack/test_servers.py | 17 ----------------- 1 file changed, 17 deletions(-) (limited to 'nova') diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index b68183429..c33d6b367 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -97,12 +97,6 @@ def return_server_with_power_state(power_state): return _return_server -def return_server_with_uuid_and_power_state(power_state): - def _return_server(context, id): - return stub_instance(id, uuid=FAKE_UUID, power_state=power_state) - return _return_server - - def return_servers(context, user_id=1): return [stub_instance(i, user_id) for i in xrange(5)] @@ -264,19 +258,8 @@ class ServersTest(test.TestCase): 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') - def tearDown(self): self.stubs.UnsetAll() FLAGS.allow_admin_api = self.allow_admin -- cgit From b6ee05e9575769039aca2c65c2a761c14562e7e0 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Wed, 27 Jul 2011 11:45:22 -0400 Subject: updating common metadata xml serializer tests --- nova/tests/api/openstack/test_common.py | 60 +++++++++++---------------------- 1 file changed, 20 insertions(+), 40 deletions(-) (limited to 'nova') diff --git a/nova/tests/api/openstack/test_common.py b/nova/tests/api/openstack/test_common.py index 0b76841f0..ee96fea7e 100644 --- a/nova/tests/api/openstack/test_common.py +++ b/nova/tests/api/openstack/test_common.py @@ -323,14 +323,10 @@ class MetadataXMLSerializationTest(test.TestCase): expected = minidom.parseString(""" - - four - - - two - + four + two - """.replace(" ", "")) + """.replace(" ", "").replace("\n","")) self.assertEqual(expected.toxml(), actual.toxml()) @@ -346,11 +342,9 @@ class MetadataXMLSerializationTest(test.TestCase): expected = minidom.parseString(""" - - None - + None - """.replace(" ", "")) + """.replace(" ", "").replace("\n","")) self.assertEqual(expected.toxml(), actual.toxml()) @@ -366,11 +360,9 @@ class MetadataXMLSerializationTest(test.TestCase): expected = minidom.parseString(u""" - - Jos\xe9 - + Jos\xe9 - """.encode("UTF-8").replace(" ", "")) + """.encode("UTF-8").replace(" ", "").replace("\n","")) self.assertEqual(expected.toxml(), actual.toxml()) @@ -385,10 +377,9 @@ class MetadataXMLSerializationTest(test.TestCase): actual = minidom.parseString(output.replace(" ", "")) expected = minidom.parseString(""" - - two - - """.replace(" ", "")) + two + """.replace(" ", "").replace("\n","")) self.assertEqual(expected.toxml(), actual.toxml()) @@ -405,14 +396,10 @@ class MetadataXMLSerializationTest(test.TestCase): expected = minidom.parseString(""" - - value6 - - - value4 - + value6 + value4 - """.replace(" ", "")) + """.replace(" ", "").replace("\n","")) self.assertEqual(expected.toxml(), actual.toxml()) @@ -427,10 +414,9 @@ class MetadataXMLSerializationTest(test.TestCase): actual = minidom.parseString(output.replace(" ", "")) expected = minidom.parseString(""" - - two - - """.replace(" ", "")) + two + """.replace(" ", "").replace("\n","")) self.assertEqual(expected.toxml(), actual.toxml()) @@ -448,17 +434,11 @@ class MetadataXMLSerializationTest(test.TestCase): expected = minidom.parseString(""" - - value2 - - - value9 - - - value1 - + value2 + value9 + value1 - """.replace(" ", "")) + """.replace(" ", "").replace("\n","")) self.assertEqual(expected.toxml(), actual.toxml()) -- cgit From 572847f9eb43ce23190566439118547ae6d6a992 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Wed, 27 Jul 2011 11:53:09 -0400 Subject: pep8 --- nova/api/openstack/server_metadata.py | 6 +++--- nova/tests/api/openstack/test_common.py | 14 +++++++------- nova/tests/api/openstack/test_server_metadata.py | 13 ++++++------- 3 files changed, 16 insertions(+), 17 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/server_metadata.py b/nova/api/openstack/server_metadata.py index d8f9cdbac..f25d36535 100644 --- a/nova/api/openstack/server_metadata.py +++ b/nova/api/openstack/server_metadata.py @@ -72,13 +72,13 @@ class Controller(object): def update(self, req, server_id, id, body): try: - meta_item = body['meta'] + meta_item = body['meta'] except (TypeError, KeyError): expl = _('Malformed request body') raise exc.HTTPBadRequest(explanation=expl) try: - meta_value = meta_item.pop(id) + meta_value = meta_item.pop(id) except (AttributeError, KeyError): expl = _('Request body and URI mismatch') raise exc.HTTPBadRequest(explanation=expl) @@ -94,7 +94,7 @@ class Controller(object): def update_all(self, req, server_id, body): try: - metadata = body['metadata'] + metadata = body['metadata'] except (TypeError, KeyError): expl = _('Malformed request body') raise exc.HTTPBadRequest(explanation=expl) diff --git a/nova/tests/api/openstack/test_common.py b/nova/tests/api/openstack/test_common.py index ee96fea7e..5a6e43579 100644 --- a/nova/tests/api/openstack/test_common.py +++ b/nova/tests/api/openstack/test_common.py @@ -326,7 +326,7 @@ class MetadataXMLSerializationTest(test.TestCase): four two - """.replace(" ", "").replace("\n","")) + """.replace(" ", "").replace("\n", "")) self.assertEqual(expected.toxml(), actual.toxml()) @@ -344,7 +344,7 @@ class MetadataXMLSerializationTest(test.TestCase): None - """.replace(" ", "").replace("\n","")) + """.replace(" ", "").replace("\n", "")) self.assertEqual(expected.toxml(), actual.toxml()) @@ -362,7 +362,7 @@ class MetadataXMLSerializationTest(test.TestCase): Jos\xe9 - """.encode("UTF-8").replace(" ", "").replace("\n","")) + """.encode("UTF-8").replace(" ", "").replace("\n", "")) self.assertEqual(expected.toxml(), actual.toxml()) @@ -379,7 +379,7 @@ class MetadataXMLSerializationTest(test.TestCase): expected = minidom.parseString(""" two - """.replace(" ", "").replace("\n","")) + """.replace(" ", "").replace("\n", "")) self.assertEqual(expected.toxml(), actual.toxml()) @@ -399,7 +399,7 @@ class MetadataXMLSerializationTest(test.TestCase): value6 value4 - """.replace(" ", "").replace("\n","")) + """.replace(" ", "").replace("\n", "")) self.assertEqual(expected.toxml(), actual.toxml()) @@ -416,7 +416,7 @@ class MetadataXMLSerializationTest(test.TestCase): expected = minidom.parseString(""" two - """.replace(" ", "").replace("\n","")) + """.replace(" ", "").replace("\n", "")) self.assertEqual(expected.toxml(), actual.toxml()) @@ -438,7 +438,7 @@ class MetadataXMLSerializationTest(test.TestCase): value9 value1 - """.replace(" ", "").replace("\n","")) + """.replace(" ", "").replace("\n", "")) self.assertEqual(expected.toxml(), actual.toxml()) diff --git a/nova/tests/api/openstack/test_server_metadata.py b/nova/tests/api/openstack/test_server_metadata.py index ded30a950..2f432433d 100644 --- a/nova/tests/api/openstack/test_server_metadata.py +++ b/nova/tests/api/openstack/test_server_metadata.py @@ -100,9 +100,9 @@ class ServerMetaDataTest(unittest.TestCase): self.assertEqual('application/json', res.headers['Content-Type']) expected = { 'metadata': { - 'key1':'value1', - 'key2':'value2', - 'key3':'value3', + 'key1': 'value1', + 'key2': 'value2', + 'key3': 'value3', }, } self.assertEqual(expected, res_dict) @@ -124,7 +124,7 @@ class ServerMetaDataTest(unittest.TestCase): value2 value1 - """.replace(" ", "").replace("\n","")) + """.replace(" ", "").replace("\n", "")) self.assertEqual(expected_metadata.toxml(), actual_metadata.toxml()) @@ -168,7 +168,7 @@ class ServerMetaDataTest(unittest.TestCase): expected_metadata = minidom.parseString(""" value2 - """.replace(" ", "").replace("\n","")) + """.replace(" ", "").replace("\n", "")) self.assertEqual(expected_metadata.toxml(), actual_metadata.toxml()) @@ -239,7 +239,7 @@ class ServerMetaDataTest(unittest.TestCase): value2 value1 - """.replace(" ","").replace("\n","")) + """.replace(" ", "").replace("\n", "")) req.body = str(request_metadata.toxml()) response = req.get_response(fakes.wsgi_app()) @@ -422,4 +422,3 @@ class ServerMetaDataTest(unittest.TestCase): req.headers["content-type"] = "application/json" res = req.get_response(fakes.wsgi_app()) self.assertEqual(400, res.status_int) - -- 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 66cf558f1497a1b917fb3db7a61826aefbd6af2e Mon Sep 17 00:00:00 2001 From: "matt.dietz@rackspace.com" <> Date: Wed, 27 Jul 2011 11:10:35 -0700 Subject: Fixed the virt driver base --- nova/virt/driver.py | 3 ++- nova/virt/xenapi_conn.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/virt/driver.py b/nova/virt/driver.py index 59582d253..87d73150f 100644 --- a/nova/virt/driver.py +++ b/nova/virt/driver.py @@ -122,7 +122,8 @@ class ComputeDriver(object): """Create snapshot from a running VM instance.""" raise NotImplementedError() - def finish_migration(self, instance, disk_info): + def finish_migration(self, instance, disk_info, network_info, + resize_instance): """Completes a resize, turning on the migrated instance""" raise NotImplementedError() diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index 0e86d9e76..c6b34864b 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -203,7 +203,7 @@ class XenAPIConnection(driver.ComputeDriver): self._vmops.revert_resize(instance) def finish_migration(self, instance, disk_info, network_info, - resize_instance=False): + resize_instance=False): """Completes a resize, turning on the migrated instance""" self._vmops.finish_migration(instance, disk_info, network_info, resize_instance) -- 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 0c393d704050ab43b1b970428b7740609af86c74 Mon Sep 17 00:00:00 2001 From: "matt.dietz@rackspace.com" <> Date: Wed, 27 Jul 2011 11:20:34 -0700 Subject: Removed superfluous parameter --- nova/virt/xenapi/vmops.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 0fa5fee87..e2cd2b6d5 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -569,7 +569,7 @@ class VMOps(object): return new_cow_uuid - def resize_instance(self, instance, vdi_uuid, resize_instance): + def resize_instance(self, instance, vdi_uuid): """Resize a running instance by changing its RAM and disk size.""" #TODO(mdietz): this will need to be adjusted for swap later #The new disk size must be in bytes -- 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 13:36:16 -0500 Subject: remove unexpected parameter --- nova/virt/xenapi/vmops.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index e2cd2b6d5..326e31704 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -122,7 +122,7 @@ class VMOps(object): [dict(vdi_type='os', vdi_uuid=vdi_uuid)], network_info) if resize_instance: - self.resize_instance(instance, vdi_uuid, resize_instance) + self.resize_instance(instance, vdi_uuid) self._spawn(instance, vm_ref) def _start(self, instance, vm_ref=None): -- cgit From 2ac60cd773bb25e19b50c082e0860b1c495d1527 Mon Sep 17 00:00:00 2001 From: William Wolf 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 9b0979c43bcb9961dfd997a17eed307b1db17acd Mon Sep 17 00:00:00 2001 From: Jason Koelker Date: Wed, 27 Jul 2011 16:02:00 -0500 Subject: pass None in for nw_info --- nova/virt/xenapi/vmops.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 326e31704..4d8747852 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -542,7 +542,7 @@ class VMOps(object): finally: if template_vm_ref: - self._destroy(instance, template_vm_ref, + self._destroy(instance, template_vm_ref, None shutdown=False, destroy_kernel_ramdisk=False) # TODO(mdietz): we could also consider renaming these to something -- cgit From ae6801fa1fe3cd83b3c51d4f3a9a9a265fc49588 Mon Sep 17 00:00:00 2001 From: Jason Koelker Date: Wed, 27 Jul 2011 16:06:08 -0500 Subject: default the paramater to None, not sure why it was required to begin with --- nova/virt/xenapi/vmops.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'nova') diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 4d8747852..1fba5a003 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -473,7 +473,7 @@ class VMOps(object): self._session, instance, template_vdi_uuids, image_id) finally: if template_vm_ref: - self._destroy(instance, template_vm_ref, None, + self._destroy(instance, template_vm_ref, shutdown=False, destroy_kernel_ramdisk=False) logging.debug(_("Finished snapshot and upload for VM %s"), instance) @@ -542,7 +542,7 @@ class VMOps(object): finally: if template_vm_ref: - self._destroy(instance, template_vm_ref, None + self._destroy(instance, template_vm_ref shutdown=False, destroy_kernel_ramdisk=False) # TODO(mdietz): we could also consider renaming these to something @@ -859,7 +859,7 @@ class VMOps(object): vm_ref = VMHelper.lookup(self._session, instance.name) return self._destroy(instance, vm_ref, network_info, shutdown=True) - def _destroy(self, instance, vm_ref, network_info, shutdown=True, + def _destroy(self, instance, vm_ref, network_info=None, shutdown=True, destroy_kernel_ramdisk=True): """Destroys VM instance by performing: -- 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 5ad96e645de174b5d9982a161919293e37aa348d Mon Sep 17 00:00:00 2001 From: Jason Koelker Date: Wed, 27 Jul 2011 16:13:07 -0500 Subject: fix typo --- nova/virt/xenapi/vmops.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 1fba5a003..6ee1a8735 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -542,7 +542,7 @@ class VMOps(object): finally: if template_vm_ref: - self._destroy(instance, template_vm_ref + self._destroy(instance, template_vm_ref, shutdown=False, destroy_kernel_ramdisk=False) # TODO(mdietz): we could also consider renaming these to something -- 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 f9ff78a5ac5f83d789334c36bebfce62af0ea406 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Wed, 27 Jul 2011 17:20:42 -0400 Subject: refactoring MetadataXMLDeserializer in wsgi/common --- nova/api/openstack/common.py | 12 +++++++++++- nova/api/openstack/create_instance_helper.py | 7 +++++-- nova/api/openstack/wsgi.py | 13 ------------- 3 files changed, 16 insertions(+), 16 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py index 24c82035a..a99951764 100644 --- a/nova/api/openstack/common.py +++ b/nova/api/openstack/common.py @@ -196,7 +196,17 @@ def get_version_from_href(href): return version -class MetadataXMLDeserializer(wsgi.MetadataXMLDeserializer): +class MetadataXMLDeserializer(wsgi.XMLDeserializer): + + def extract_metadata(self, metadata_node): + """Marshal the metadata attribute of a parsed request""" + if metadata_node is None: + return None + metadata = {} + for meta_node in self.find_children_named(metadata_node, "meta"): + key = meta_node.getAttribute("key") + metadata[key] = self.extract_text(meta_node) + return metadata def _extract_metadata_container(self, datastring): dom = minidom.parseString(datastring) diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index f8317565e..70532cf79 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -28,6 +28,7 @@ from nova import quota from nova import utils from nova.compute import instance_types +from nova.api.openstack import common from nova.api.openstack import wsgi from nova.auth import manager as auth_manager @@ -285,7 +286,7 @@ class CreateInstanceHelper(object): return password -class ServerXMLDeserializer(wsgi.MetadataXMLDeserializer): +class ServerXMLDeserializer(wsgi.XMLDeserializer): """ Deserializer to handle xml-formatted server create requests. @@ -293,6 +294,8 @@ class ServerXMLDeserializer(wsgi.MetadataXMLDeserializer): and personality attributes """ + metadata_deserializer = common.MetadataXMLDeserializer() + def create(self, string): """Deserialize an xml-formatted server create request""" dom = minidom.parseString(string) @@ -307,7 +310,7 @@ class ServerXMLDeserializer(wsgi.MetadataXMLDeserializer): if server_node.getAttribute(attr): server[attr] = server_node.getAttribute(attr) metadata_node = self.find_first_child_named(server_node, "metadata") - metadata = self.extract_metadata(metadata_node) + metadata = self.metadata_deserializer.extract_metadata(metadata_node) if metadata is not None: server["metadata"] = metadata personality = self._extract_personality(server_node) diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py index a28443d12..d10424d79 100644 --- a/nova/api/openstack/wsgi.py +++ b/nova/api/openstack/wsgi.py @@ -161,19 +161,6 @@ class XMLDeserializer(TextDeserializer): return {'body': self._from_xml(datastring)} -class MetadataXMLDeserializer(XMLDeserializer): - - def extract_metadata(self, metadata_node): - """Marshal the metadata attribute of a parsed request""" - if metadata_node is None: - return None - metadata = {} - for meta_node in self.find_children_named(metadata_node, "meta"): - key = meta_node.getAttribute("key") - metadata[key] = self.extract_text(meta_node) - return metadata - - class RequestHeadersDeserializer(ActionDispatcher): """Default request headers deserializer""" -- 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 7026927fa52e34eebc17b387e0ca6feade99727a Mon Sep 17 00:00:00 2001 From: Matthew Hooker Date: Wed, 27 Jul 2011 20:32:46 -0400 Subject: fix call to nonexistant method to_global_ipv6. Add myself to authors file --- 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 d7810098a..5de25b37a 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -1312,7 +1312,7 @@ def instance_get_fixed_addresses_v6(context, instance_id): # combine prefixes, macs, and project_id into (prefix,mac,p_id) tuples prefix_mac_tuples = zip(prefixes, macs, [project_id for m in macs]) # return list containing ipv6 address for each tuple - return [ipv6.to_global_ipv6(*t) for t in prefix_mac_tuples] + return [ipv6.to_global(*t) for t in prefix_mac_tuples] @require_context -- cgit From bf1cf9bb5089bf81c3fec456db381e9be4c37f81 Mon Sep 17 00:00:00 2001 From: Matthew Hooker Date: Wed, 27 Jul 2011 20:45:04 -0400 Subject: fix undefined variable errors --- 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 5de25b37a..8682fd996 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -34,6 +34,7 @@ from sqlalchemy.orm import joinedload from sqlalchemy.orm import joinedload_all from sqlalchemy.sql import func from sqlalchemy.sql.expression import literal_column +import datetime FLAGS = flags.FLAGS LOG = logging.getLogger("nova.db.sqlalchemy") @@ -3297,7 +3298,7 @@ def instance_type_extra_specs_delete(context, instance_type_id, key): def instance_type_extra_specs_get_item(context, instance_type_id, key): session = get_session() - sppec_result = session.query(models.InstanceTypeExtraSpecs).\ + spec_result = session.query(models.InstanceTypeExtraSpecs).\ filter_by(instance_type_id=instance_type_id).\ filter_by(key=key).\ filter_by(deleted=False).\ -- cgit From 968f0e17cbb9dfe881bcb2fa0c96b6aea4f566fa Mon Sep 17 00:00:00 2001 From: Matthew Hooker Date: Wed, 27 Jul 2011 20:48:26 -0400 Subject: remove unused assignment which causes undeclared name error --- nova/tests/scheduler/test_scheduler.py | 5 ----- 1 file changed, 5 deletions(-) (limited to 'nova') diff --git a/nova/tests/scheduler/test_scheduler.py b/nova/tests/scheduler/test_scheduler.py index daea826fd..18fbef5ca 100644 --- a/nova/tests/scheduler/test_scheduler.py +++ b/nova/tests/scheduler/test_scheduler.py @@ -485,11 +485,6 @@ class SimpleDriverTestCase(test.TestCase): self.assertEqual(host, 'host2') volume1.delete_volume(self.context, volume_id1) db.volume_destroy(self.context, volume_id2) - dic = {'service_id': s_ref['id'], - 'vcpus': 16, 'memory_mb': 32, 'local_gb': 100, - 'vcpus_used': 16, 'memory_mb_used': 12, 'local_gb_used': 10, - 'hypervisor_type': 'qemu', 'hypervisor_version': 12003, - 'cpu_info': ''} def test_doesnt_report_disabled_hosts_as_up(self): """Ensures driver doesn't find hosts before they are enabled""" -- cgit From 50360384800df72fc97a8e9e5e81833e6091c10c Mon Sep 17 00:00:00 2001 From: Matthew Hooker Date: Wed, 27 Jul 2011 20:49:51 -0400 Subject: fix undeclared name errors --- nova/virt/xenapi/vm_utils.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'nova') diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index 62863c6d8..f4bd1ee30 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -109,13 +109,13 @@ class ImageType: def from_string(cls, image_type_str): if image_type_str == ImageType.KERNEL_STR: return ImageType.KERNEL - elif image_type == ImageType.RAMDISK_STR: + elif image_type_str == ImageType.RAMDISK_STR: return ImageType.RAMDISK - elif image_type == ImageType.DISK_STR: + elif image_type_str == ImageType.DISK_STR: return ImageType.DISK - elif image_type == ImageType.DISK_RAW_STR: + elif image_type_str == ImageType.DISK_RAW_STR: return ImageType.DISK_RAW - elif image_type == ImageType.DISK_VHD_STR: + elif image_type_str == ImageType.DISK_VHD_STR: return ImageType.VHD -- cgit From 7b66886c3067015d40b81748d253c3623e328d95 Mon Sep 17 00:00:00 2001 From: Matthew Hooker Date: Wed, 27 Jul 2011 20:55:10 -0400 Subject: fix undeclared name error --- nova/scheduler/zone_aware_scheduler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/scheduler/zone_aware_scheduler.py b/nova/scheduler/zone_aware_scheduler.py index c429fdfcc..d99d7214c 100644 --- a/nova/scheduler/zone_aware_scheduler.py +++ b/nova/scheduler/zone_aware_scheduler.py @@ -81,7 +81,7 @@ class ZoneAwareScheduler(driver.Scheduler): decryptor = crypto.decryptor(FLAGS.build_plan_encryption_key) try: json_entry = decryptor(blob) - return json.dumps(entry) + return json.dumps(json_entry) except M2Crypto.EVP.EVPError: pass return None -- cgit From ca1bd15fdadf95d37669b4977625a364dd171698 Mon Sep 17 00:00:00 2001 From: Matthew Hooker Date: Wed, 27 Jul 2011 20:56:12 -0400 Subject: fix undeclared name error --- nova/scheduler/least_cost.py | 1 + 1 file changed, 1 insertion(+) (limited to 'nova') diff --git a/nova/scheduler/least_cost.py b/nova/scheduler/least_cost.py index 6f5eb66fd..8c400d476 100644 --- a/nova/scheduler/least_cost.py +++ b/nova/scheduler/least_cost.py @@ -28,6 +28,7 @@ from nova import flags from nova import log as logging from nova.scheduler import zone_aware_scheduler from nova import utils +from nova import exception LOG = logging.getLogger('nova.scheduler.least_cost') -- cgit From c789c7e53976f45aa7a891c1399ae399bd39408f Mon Sep 17 00:00:00 2001 From: Matthew Hooker Date: Wed, 27 Jul 2011 21:11:22 -0400 Subject: fix undeclared name error --- 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 f8317565e..768fbccea 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -188,7 +188,7 @@ class CreateInstanceHelper(object): Overrides normal behavior in the case of xml content """ if request.content_type == "application/xml": - deserializer = ServerCreateRequestXMLDeserializer() + deserializer = ServerXMLDeserializer() return deserializer.deserialize(request.body) else: return self._deserialize(request.body, request.get_content_type()) -- cgit From d811f82e524bd7634dd59f0074129fb41fb28c12 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Thu, 28 Jul 2011 01:31:09 +0000 Subject: fix tests broken in the merge --- nova/tests/test_db_api.py | 34 +++++++++++++--------------------- nova/tests/test_quota.py | 4 +--- nova/tests/test_vmwareapi.py | 2 +- 3 files changed, 15 insertions(+), 25 deletions(-) (limited to 'nova') diff --git a/nova/tests/test_db_api.py b/nova/tests/test_db_api.py index 107fd03e3..54448f9d6 100644 --- a/nova/tests/test_db_api.py +++ b/nova/tests/test_db_api.py @@ -22,7 +22,6 @@ from nova import test from nova import context from nova import db from nova import flags -from nova.auth import manager FLAGS = flags.FLAGS @@ -45,42 +44,35 @@ def _setup_networking(instance_id, ip='1.2.3.4', flo_addr='1.2.1.2'): db.fixed_ip_create(ctxt, fixed_ip) fix_ref = db.fixed_ip_get_by_address(ctxt, ip) db.floating_ip_create(ctxt, {'address': flo_addr, - 'fixed_ip_id': fix_ref.id}) + 'fixed_ip_id': fix_ref['id']}) class DbApiTestCase(test.TestCase): def setUp(self): super(DbApiTestCase, self).setUp() - self.manager = manager.AuthManager() - self.user = self.manager.create_user('admin', 'admin', 'admin', True) - self.project = self.manager.create_project('proj', 'admin', 'proj') - self.context = context.RequestContext(user=self.user, - project=self.project) - - def tearDown(self): - self.manager.delete_project(self.project) - self.manager.delete_user(self.user) - super(DbApiTestCase, self).tearDown() + self.user_id = 'fake' + self.project_id = 'fake' + self.context = context.RequestContext(self.user_id, self.project_id) def test_instance_get_project_vpn(self): - result = db.fixed_ip_get_all(self.context) 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, self.project.id) - self.assertEqual(instance.id, result.id) + result = db.instance_get_project_vpn(self.context.elevated(), + self.project_id) + self.assertEqual(instance['id'], result['id']) def test_instance_get_project_vpn_joins(self): - result = db.fixed_ip_get_all(self.context) 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) - result = db.instance_get_project_vpn(self.context, self.project.id) - self.assertEqual(instance.id, result.id) + _setup_networking(instance['id']) + result = db.instance_get_project_vpn(self.context.elevated(), + self.project_id) + self.assertEqual(instance['id'], result['id']) self.assertEqual(result['fixed_ips'][0]['floating_ips'][0].address, '1.2.1.2') diff --git a/nova/tests/test_quota.py b/nova/tests/test_quota.py index 0ffab0ee1..92393b536 100644 --- a/nova/tests/test_quota.py +++ b/nova/tests/test_quota.py @@ -20,10 +20,8 @@ from nova import compute from nova import context from nova import db from nova import flags -from nova import network from nova import quota from nova import test -from nova import utils from nova import volume from nova.compute import instance_types @@ -267,7 +265,7 @@ class QuotaTestCase(test.TestCase): address = '192.168.0.100' db.floating_ip_create(context.get_admin_context(), {'address': address, - 'project_id': self.project.id}) + 'project_id': self.project_id}) self.assertRaises(quota.QuotaError, self.network.allocate_floating_ip, self.context, diff --git a/nova/tests/test_vmwareapi.py b/nova/tests/test_vmwareapi.py index be5246fdf..3d87d67ad 100644 --- a/nova/tests/test_vmwareapi.py +++ b/nova/tests/test_vmwareapi.py @@ -19,6 +19,7 @@ Test suite for VMWareAPI. """ +from nova import context from nova import db from nova import flags from nova import test @@ -42,7 +43,6 @@ class VMWareAPIVMTestCase(test.TestCase): self.flags(vmwareapi_host_ip='test_url', vmwareapi_host_username='test_username', vmwareapi_host_password='test_pass') - self.manager = manager.AuthManager() self.user_id = 'fake' self.project_id = 'fake' self.context = context.RequestContext(self.user_id, self.project_id) -- cgit From 559b73ed18a271dd35c7b9d00306c8c5b33bd45b Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Thu, 28 Jul 2011 01:36:55 +0000 Subject: remove authman from images/s3.py and replace with flags --- nova/image/s3.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) (limited to 'nova') diff --git a/nova/image/s3.py b/nova/image/s3.py index 8685c96fd..ccbfa89cd 100644 --- a/nova/image/s3.py +++ b/nova/image/s3.py @@ -34,7 +34,6 @@ from nova import flags from nova import image from nova import log as logging from nova import utils -from nova.auth import manager from nova.image import service from nova.api.ec2 import ec2utils @@ -43,6 +42,10 @@ LOG = logging.getLogger("nova.image.s3") FLAGS = flags.FLAGS flags.DEFINE_string('image_decryption_dir', '/tmp', 'parent dir for tempdir used for image decryption') +flags.DEFINE_string('s3_access_key', 'notchecked', + 'access key to use for s3 server for images') +flags.DEFINE_string('s3_secret_key', 'notchecked', + 'secret key to use for s3 server for images') class S3ImageService(service.BaseImageService): @@ -82,11 +85,10 @@ class S3ImageService(service.BaseImageService): @staticmethod def _conn(context): - # TODO(vish): is there a better way to get creds to sign - # for the user? - authman = manager.AuthManager() - access = authman.get_access_key(context.user_id, context.project_id) - secret = str(authman.get_user(context.user_id).secret) + # NOTE(vish): access and secret keys for s3 server are not + # checked in nova-objectstore + access = FLAGS.s3_access_key + secret = FLAGS.s3_secret_key calling = boto.s3.connection.OrdinaryCallingFormat() return boto.s3.connection.S3Connection(aws_access_key_id=access, aws_secret_access_key=secret, -- cgit From 92c8d269a13917de397c1d0ce9fecfaa36195ce9 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Thu, 28 Jul 2011 10:23:44 -0400 Subject: Removed v1_1 from individual tests --- nova/tests/api/openstack/test_server_actions.py | 32 ++++++++++++------------- 1 file changed, 16 insertions(+), 16 deletions(-) (limited to 'nova') diff --git a/nova/tests/api/openstack/test_server_actions.py b/nova/tests/api/openstack/test_server_actions.py index 97c108068..5c23d6d9d 100644 --- a/nova/tests/api/openstack/test_server_actions.py +++ b/nova/tests/api/openstack/test_server_actions.py @@ -350,7 +350,7 @@ class ServerActionsTestV11(test.TestCase): def tearDown(self): self.stubs.UnsetAll() - def test_server_change_password_v1_1(self): + def test_server_change_password(self): mock_method = MockSetAdminPassword() self.stubs.Set(nova.compute.api.API, 'set_admin_password', mock_method) body = {'changePassword': {'adminPass': '1234pass'}} @@ -363,7 +363,7 @@ class ServerActionsTestV11(test.TestCase): self.assertEqual(mock_method.instance_id, '1') self.assertEqual(mock_method.password, '1234pass') - def test_server_change_password_bad_request_v1_1(self): + def test_server_change_password_bad_request(self): body = {'changePassword': {'pass': '12345'}} req = webob.Request.blank('/v1.1/servers/1/action') req.method = 'POST' @@ -372,7 +372,7 @@ class ServerActionsTestV11(test.TestCase): res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 400) - def test_server_change_password_empty_string_v1_1(self): + def test_server_change_password_empty_string(self): body = {'changePassword': {'adminPass': ''}} req = webob.Request.blank('/v1.1/servers/1/action') req.method = 'POST' @@ -381,7 +381,7 @@ class ServerActionsTestV11(test.TestCase): res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 400) - def test_server_change_password_none_v1_1(self): + def test_server_change_password_none(self): body = {'changePassword': {'adminPass': None}} req = webob.Request.blank('/v1.1/servers/1/action') req.method = 'POST' @@ -390,7 +390,7 @@ class ServerActionsTestV11(test.TestCase): res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 400) - def test_server_rebuild_accepted_minimum_v1_1(self): + def test_server_rebuild_accepted_minimum(self): body = { "rebuild": { "imageRef": "http://localhost/images/2", @@ -405,7 +405,7 @@ class ServerActionsTestV11(test.TestCase): res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 202) - def test_server_rebuild_rejected_when_building_v1_1(self): + def test_server_rebuild_rejected_when_building(self): body = { "rebuild": { "imageRef": "http://localhost/images/2", @@ -426,7 +426,7 @@ class ServerActionsTestV11(test.TestCase): res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 409) - def test_server_rebuild_accepted_with_metadata_v1_1(self): + def test_server_rebuild_accepted_with_metadata(self): body = { "rebuild": { "imageRef": "http://localhost/images/2", @@ -444,7 +444,7 @@ class ServerActionsTestV11(test.TestCase): res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 202) - def test_server_rebuild_accepted_with_bad_metadata_v1_1(self): + def test_server_rebuild_accepted_with_bad_metadata(self): body = { "rebuild": { "imageRef": "http://localhost/images/2", @@ -460,7 +460,7 @@ class ServerActionsTestV11(test.TestCase): res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 400) - def test_server_rebuild_bad_entity_v1_1(self): + def test_server_rebuild_bad_entity(self): body = { "rebuild": { "imageId": 2, @@ -475,7 +475,7 @@ class ServerActionsTestV11(test.TestCase): res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 400) - def test_server_rebuild_bad_personality_v1_1(self): + def test_server_rebuild_bad_personality(self): body = { "rebuild": { "imageRef": "http://localhost/images/2", @@ -494,7 +494,7 @@ class ServerActionsTestV11(test.TestCase): res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 400) - def test_server_rebuild_personality_v1_1(self): + def test_server_rebuild_personality(self): body = { "rebuild": { "imageRef": "http://localhost/images/2", @@ -513,7 +513,7 @@ class ServerActionsTestV11(test.TestCase): res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 202) - def test_resize_server_v11(self): + def test_resize_server(self): req = webob.Request.blank('/v1.1/servers/1/action') req.content_type = 'application/json' @@ -532,7 +532,7 @@ class ServerActionsTestV11(test.TestCase): self.assertEqual(res.status_int, 202) self.assertEqual(self.resize_called, True) - def test_create_image_v1_1(self): + def test_create_image(self): body = { 'createImage': { 'name': 'Snapshot 1', @@ -547,7 +547,7 @@ class ServerActionsTestV11(test.TestCase): location = response.headers['Location'] self.assertEqual('http://localhost/v1.1/images/123', location) - def test_create_image_v1_1_with_metadata(self): + def test_create_image_with_metadata(self): body = { 'createImage': { 'name': 'Snapshot 1', @@ -563,7 +563,7 @@ class ServerActionsTestV11(test.TestCase): location = response.headers['Location'] self.assertEqual('http://localhost/v1.1/images/123', location) - def test_create_image_v1_1_no_name(self): + def test_create_image_no_name(self): body = { 'createImage': {}, } @@ -574,7 +574,7 @@ class ServerActionsTestV11(test.TestCase): response = req.get_response(fakes.wsgi_app()) self.assertEqual(400, response.status_int) - def test_create_image_v1_1_bad_metadata(self): + def test_create_image_bad_metadata(self): body = { 'createImage': { 'name': 'geoff', -- cgit From cd065f6669a666387c8f9efb5c0fff7eaf94521c Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Thu, 28 Jul 2011 10:31:06 -0400 Subject: moved test --- nova/tests/api/openstack/test_server_actions.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) (limited to 'nova') diff --git a/nova/tests/api/openstack/test_server_actions.py b/nova/tests/api/openstack/test_server_actions.py index 5c23d6d9d..1651b1645 100644 --- a/nova/tests/api/openstack/test_server_actions.py +++ b/nova/tests/api/openstack/test_server_actions.py @@ -132,15 +132,6 @@ class ServerActionsTest(test.TestCase): # res = req.get_response(fakes.wsgi_app()) # self.assertEqual(res.status_int, 501) - def test_server_change_password_not_a_string_v1_1(self): - body = {'changePassword': {'adminPass': 1234}} - req = webob.Request.blank('/v1.1/servers/1/action') - req.method = 'POST' - req.content_type = 'application/json' - req.body = json.dumps(body) - res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status_int, 400) - def test_server_reboot(self): body = dict(server=dict( name='server_test', imageId=2, flavorId=2, metadata={}, @@ -363,6 +354,15 @@ class ServerActionsTestV11(test.TestCase): self.assertEqual(mock_method.instance_id, '1') self.assertEqual(mock_method.password, '1234pass') + def test_server_change_password_not_a_string(self): + body = {'changePassword': {'adminPass': 1234}} + req = webob.Request.blank('/v1.1/servers/1/action') + req.method = 'POST' + req.content_type = 'application/json' + req.body = json.dumps(body) + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 400) + def test_server_change_password_bad_request(self): body = {'changePassword': {'pass': '12345'}} req = webob.Request.blank('/v1.1/servers/1/action') -- 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 c0bbcb5d9f4deab1acc5ca03e270a5234d114ec5 Mon Sep 17 00:00:00 2001 From: Jason Koelker Date: Thu, 28 Jul 2011 11:01:38 -0500 Subject: trunk infected with non-pep8 code --- nova/tests/api/openstack/test_extensions.py | 38 ++++++++++++++--------------- nova/tests/api/openstack/test_limits.py | 14 +++++------ nova/tests/api/openstack/test_servers.py | 2 +- nova/tests/test_db_api.py | 4 +-- nova/tests/test_libvirt.py | 2 +- 5 files changed, 30 insertions(+), 30 deletions(-) (limited to 'nova') diff --git a/nova/tests/api/openstack/test_extensions.py b/nova/tests/api/openstack/test_extensions.py index d459c694f..bf2d0368f 100644 --- a/nova/tests/api/openstack/test_extensions.py +++ b/nova/tests/api/openstack/test_extensions.py @@ -109,8 +109,8 @@ class ExtensionControllerTest(unittest.TestCase): 'updated': '2011-01-22T13:25:27-06:00', 'description': 'The Fox In Socks Extension', 'alias': 'FOXNSOX', - 'links': [] - } + 'links': [], + }, ) def test_get_extension_json(self): @@ -127,8 +127,8 @@ class ExtensionControllerTest(unittest.TestCase): "updated": "2011-01-22T13:25:27-06:00", "description": "The Fox In Socks Extension", "alias": "FOXNSOX", - "links": [] - } + "links": [], + }, ) def test_list_extensions_xml(self): @@ -342,15 +342,15 @@ class ExtensionsXMLSerializerTest(unittest.TestCase): { 'rel': 'describedby', 'type': 'application/pdf', - 'href': 'http://docs.rack.com/servers/api/ext/cs.pdf' + 'href': 'http://docs.rack.com/servers/api/ext/cs.pdf', }, { 'rel': 'describedby', 'type': 'application/vnd.sun.wadl+xml', - 'href': 'http://docs.rack.com/servers/api/ext/cs.wadl' - } - ] - } + 'href': 'http://docs.rack.com/servers/api/ext/cs.wadl', + }, + ], + }, } xml = serializer.serialize(data, 'show') @@ -382,14 +382,14 @@ class ExtensionsXMLSerializerTest(unittest.TestCase): { "rel": "describedby", "type": "application/pdf", - "href": "http://foo.com/api/ext/cs-pie.pdf" + "href": "http://foo.com/api/ext/cs-pie.pdf", }, { "rel": "describedby", "type": "application/vnd.sun.wadl+xml", - "href": "http://foo.com/api/ext/cs-pie.wadl" - } - ] + "href": "http://foo.com/api/ext/cs-pie.wadl", + }, + ], }, { "name": "Cloud Block Storage", @@ -401,16 +401,16 @@ class ExtensionsXMLSerializerTest(unittest.TestCase): { "rel": "describedby", "type": "application/pdf", - "href": "http://foo.com/api/ext/cs-cbs.pdf" + "href": "http://foo.com/api/ext/cs-cbs.pdf", }, { "rel": "describedby", "type": "application/vnd.sun.wadl+xml", - "href": "http://foo.com/api/ext/cs-cbs.wadl" - } - ] - } - ] + "href": "http://foo.com/api/ext/cs-cbs.wadl", + }, + ], + }, + ], } xml = serializer.serialize(data, 'index') diff --git a/nova/tests/api/openstack/test_limits.py b/nova/tests/api/openstack/test_limits.py index 8a3fe681a..6c3d531e3 100644 --- a/nova/tests/api/openstack/test_limits.py +++ b/nova/tests/api/openstack/test_limits.py @@ -920,7 +920,7 @@ class LimitsViewBuilderV11Test(test.TestCase): "verb": "POST", "remaining": 2, "unit": "MINUTE", - "resetTime": 1311272226 + "resetTime": 1311272226, }, { "URI": "*/servers", @@ -929,7 +929,7 @@ class LimitsViewBuilderV11Test(test.TestCase): "verb": "POST", "remaining": 10, "unit": "DAY", - "resetTime": 1311272226 + "resetTime": 1311272226, }, ] self.absolute_limits = { @@ -954,7 +954,7 @@ class LimitsViewBuilderV11Test(test.TestCase): "verb": "POST", "remaining": 2, "unit": "MINUTE", - "next-available": "2011-07-21T18:17:06Z" + "next-available": "2011-07-21T18:17:06Z", }, ] }, @@ -967,7 +967,7 @@ class LimitsViewBuilderV11Test(test.TestCase): "verb": "POST", "remaining": 10, "unit": "DAY", - "next-available": "2011-07-21T18:17:06Z" + "next-available": "2011-07-21T18:17:06Z", }, ] }, @@ -989,7 +989,7 @@ class LimitsViewBuilderV11Test(test.TestCase): expected_limits = { "limits": { "rate": [], - "absolute": {} + "absolute": {}, } } @@ -1022,7 +1022,7 @@ class LimitsXMLSerializationTest(test.TestCase): "verb": "POST", "remaining": 2, "unit": "MINUTE", - "next-available": "2011-12-15T22:42:45Z" + "next-available": "2011-12-15T22:42:45Z", }, ] }, @@ -1083,7 +1083,7 @@ class LimitsXMLSerializationTest(test.TestCase): fixture = { "limits": { "rate": [], - "absolute": {} + "absolute": {}, } } diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 4ca79434f..712e5e67c 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -2950,7 +2950,7 @@ class ServersViewBuilderV11Test(test.TestCase): address_builder, flavor_builder, image_builder, - base_url + base_url, ) return view_builder diff --git a/nova/tests/test_db_api.py b/nova/tests/test_db_api.py index 107fd03e3..5560b489b 100644 --- a/nova/tests/test_db_api.py +++ b/nova/tests/test_db_api.py @@ -66,7 +66,7 @@ class DbApiTestCase(test.TestCase): result = db.fixed_ip_get_all(self.context) 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, self.project.id) @@ -76,7 +76,7 @@ class DbApiTestCase(test.TestCase): result = db.fixed_ip_get_all(self.context) 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) diff --git a/nova/tests/test_libvirt.py b/nova/tests/test_libvirt.py index bab4d200b..f367688cc 100644 --- a/nova/tests/test_libvirt.py +++ b/nova/tests/test_libvirt.py @@ -239,7 +239,7 @@ class LibvirtConnTestCase(test.TestCase): 'mac_address': 'fake', 'ip_address': 'fake', 'dhcp_server': 'fake', - 'extra_params': 'fake' + 'extra_params': 'fake', } # Creating mocks -- 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 969ba5028d8b07cece4b4c940ce02f661cecbb71 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Thu, 28 Jul 2011 12:04:49 -0700 Subject: simplify if statement --- nova/context.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) (limited to 'nova') diff --git a/nova/context.py b/nova/context.py index db19f136c..5b2776d4e 100644 --- a/nova/context.py +++ b/nova/context.py @@ -38,10 +38,7 @@ class RequestContext(object): self.roles = roles or [] self.is_admin = is_admin if self.is_admin is None: - if 'admin' in self.roles: - self.is_admin = True - else: - self.is_admin = False + self.admin = 'admin' in self.roles self.read_deleted = read_deleted self.remote_address = remote_address if not timestamp: -- cgit From 0e3f66bac655b49329b5e90f23599ba45333543b Mon Sep 17 00:00:00 2001 From: Matthew Hooker Date: Thu, 28 Jul 2011 15:09:02 -0400 Subject: Some tests for resolved pylint errors. --- nova/tests/scheduler/test_zone_aware_scheduler.py | 20 ++++++++++++++++++++ nova/tests/test_xenapi.py | 15 +++++++++++++++ 2 files changed, 35 insertions(+) (limited to 'nova') diff --git a/nova/tests/scheduler/test_zone_aware_scheduler.py b/nova/tests/scheduler/test_zone_aware_scheduler.py index d74b71fb6..14f8191f2 100644 --- a/nova/tests/scheduler/test_zone_aware_scheduler.py +++ b/nova/tests/scheduler/test_zone_aware_scheduler.py @@ -16,6 +16,9 @@ Tests For Zone Aware Scheduler. """ +import json +import mox + import nova.db from nova import exception @@ -327,3 +330,20 @@ class ZoneAwareSchedulerTestCase(test.TestCase): sched._provision_resource_from_blob(None, request_spec, 1, request_spec, {}) self.assertTrue(was_called) + + def test_decrypt_blob(self): + """Test that the decrypt method works.""" + + fixture = FakeZoneAwareScheduler() + test_data = {"foo": "bar"} + + crypto = self.mox.CreateMockAnything() + crypto.decryptor(mox.IgnoreArg()).AndReturn(lambda blob: blob) + """ + def _decryptor(i): + return lambda blob: blob + """ + self.stubs.Set(zone_aware_scheduler, 'crypto', + crypto) + + self.assertEqual(fixture._decrypt_blob(test_data), json.dumps(test_data)) diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index 8b3b5fa28..781a218b6 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -807,6 +807,21 @@ class XenAPIMigrateInstance(test.TestCase): conn.finish_resize(instance, dict(base_copy='hurr', cow='durr'), network_info) +class XenAPIImageTypeTestCase(test.TestCase): + """Test ImageType class.""" + + def test_to_string(self): + """Can convert from type id to type string.""" + self.assertEquals( + vm_utils.ImageType.to_string(vm_utils.ImageType.KERNEL), + vm_utils.ImageType.KERNEL_STR) + + def test_from_string(self): + """Can convert from string to type id.""" + self.assertEquals( + vm_utils.ImageType.to_string(vm_utils.ImageType.KERNEL_STR), + vm_utils.ImageType.KERNEL) + class XenAPIDetermineDiskImageTestCase(test.TestCase): """Unit tests for code that detects the ImageType.""" -- cgit From 89acd0c231b0c92724b188d11e7c1b541d931658 Mon Sep 17 00:00:00 2001 From: Matthew Hooker Date: Thu, 28 Jul 2011 15:11:54 -0400 Subject: Use utils.utcnow. Use True instead of literal 1 --- nova/db/sqlalchemy/api.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'nova') diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 8682fd996..0c3c5af6a 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -34,7 +34,6 @@ from sqlalchemy.orm import joinedload from sqlalchemy.orm import joinedload_all from sqlalchemy.sql import func from sqlalchemy.sql.expression import literal_column -import datetime FLAGS = flags.FLAGS LOG = logging.getLogger("nova.db.sqlalchemy") @@ -3248,8 +3247,8 @@ def agent_build_destroy(context, agent_build_id): with session.begin(): session.query(models.AgentBuild).\ filter_by(id=agent_build_id).\ - update({'deleted': 1, - 'deleted_at': datetime.datetime.utcnow(), + update({'deleted': True, + 'deleted_at': utils.utcnow(), 'updated_at': literal_column('updated_at')}) -- cgit From 782f86b931156bd81c05acabe200e123f6227ae4 Mon Sep 17 00:00:00 2001 From: Matthew Hooker Date: Thu, 28 Jul 2011 15:46:20 -0400 Subject: Fix tests for checking pylint errors. --- nova/tests/scheduler/test_zone_aware_scheduler.py | 12 +++++------- nova/tests/test_xenapi.py | 2 +- 2 files changed, 6 insertions(+), 8 deletions(-) (limited to 'nova') diff --git a/nova/tests/scheduler/test_zone_aware_scheduler.py b/nova/tests/scheduler/test_zone_aware_scheduler.py index 14f8191f2..3781c567e 100644 --- a/nova/tests/scheduler/test_zone_aware_scheduler.py +++ b/nova/tests/scheduler/test_zone_aware_scheduler.py @@ -337,13 +337,11 @@ class ZoneAwareSchedulerTestCase(test.TestCase): fixture = FakeZoneAwareScheduler() test_data = {"foo": "bar"} - crypto = self.mox.CreateMockAnything() - crypto.decryptor(mox.IgnoreArg()).AndReturn(lambda blob: blob) - """ - def _decryptor(i): - return lambda blob: blob - """ + class StubDecryptor(object): + def decryptor(self, key): + return lambda blob: blob + self.stubs.Set(zone_aware_scheduler, 'crypto', - crypto) + StubDecryptor()) self.assertEqual(fixture._decrypt_blob(test_data), json.dumps(test_data)) diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index 781a218b6..e87622451 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -819,7 +819,7 @@ class XenAPIImageTypeTestCase(test.TestCase): def test_from_string(self): """Can convert from string to type id.""" self.assertEquals( - vm_utils.ImageType.to_string(vm_utils.ImageType.KERNEL_STR), + vm_utils.ImageType.from_string(vm_utils.ImageType.KERNEL_STR), vm_utils.ImageType.KERNEL) -- 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 0c9d1fcbdd1701f1206e1f66db47edd419c8901d Mon Sep 17 00:00:00 2001 From: Matthew Hooker Date: Thu, 28 Jul 2011 15:50:09 -0400 Subject: Rewrite ImageType enumeration to be more pythonic --- nova/virt/xenapi/vm_utils.py | 24 ++++-------------------- 1 file changed, 4 insertions(+), 20 deletions(-) (limited to 'nova') diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index f4bd1ee30..2ed83a90a 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -85,38 +85,22 @@ class ImageType: DISK = 2 DISK_RAW = 3 DISK_VHD = 4 + _ids = (KERNEL, RAMDISK, DISK, DISK_RAW, DISK_VHD) KERNEL_STR = "kernel" RAMDISK_STR = "ramdisk" DISK_STR = "os" DISK_RAW_STR = "os_raw" DISK_VHD_STR = "vhd" + _strs = (KERNEL_STR, RAMDISK_STR, DISK_STR, DISK_RAW_STR, DISK_VHD_STR) @classmethod def to_string(cls, image_type): - if image_type == ImageType.KERNEL: - return ImageType.KERNEL_STR - elif image_type == ImageType.RAMDISK: - return ImageType.RAMDISK_STR - elif image_type == ImageType.DISK: - return ImageType.DISK_STR - elif image_type == ImageType.DISK_RAW: - return ImageType.DISK_RAW_STR - elif image_type == ImageType.DISK_VHD: - return ImageType.VHD_STR + return dict(zip(ImageType._ids, ImageType._strs)).get(image_type) @classmethod def from_string(cls, image_type_str): - if image_type_str == ImageType.KERNEL_STR: - return ImageType.KERNEL - elif image_type_str == ImageType.RAMDISK_STR: - return ImageType.RAMDISK - elif image_type_str == ImageType.DISK_STR: - return ImageType.DISK - elif image_type_str == ImageType.DISK_RAW_STR: - return ImageType.DISK_RAW - elif image_type_str == ImageType.DISK_VHD_STR: - return ImageType.VHD + return dict(zip(ImageType._strs, ImageType._ids)).get(image_type_str) class VMHelper(HelperBase): -- cgit From 2e3b199005d16ee3e35cd6c71b8512628e3631bc Mon Sep 17 00:00:00 2001 From: "Dave Walker (Daviey)" Date: Thu, 28 Jul 2011 21:12:03 +0100 Subject: Simplified test cases --- nova/api/ec2/cloud.py | 2 +- nova/tests/test_api.py | 27 ++++++--------------------- 2 files changed, 7 insertions(+), 22 deletions(-) (limited to 'nova') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index ecdbad895..371837d19 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -695,7 +695,7 @@ class CloudController(object): if not re.match('^[a-zA-Z0-9_\- ]+$', str(group_name)): # Some validation to ensure that values match API spec. # - Alphanumeric characters, spaces, dashes, and underscores. - # TODO(Daviey): LP: #813685 extend beyond group_name checking, and + # TODO(Daviey): LP: #813685 extend beyond group_name checking, and # probably create a param validator that can be used elsewhere. err = _("Value (%s) for parameter GroupName is invalid." " Content limited to Alphanumeric characters, " diff --git a/nova/tests/test_api.py b/nova/tests/test_api.py index 5759e7726..40e62ac76 100644 --- a/nova/tests/test_api.py +++ b/nova/tests/test_api.py @@ -365,7 +365,7 @@ class ApiEc2TestCase(test.TestCase): def test_group_name_valid_chars_security_group(self): """ Test that we sanely handle invalid security group names. - API Spec states we should only accept alphanumeric characters, + API Spec states we should only accept alphanumeric characters, spaces, dashes, and underscores. """ self.expect_http() self.mox.ReplayAll() @@ -380,16 +380,8 @@ class ApiEc2TestCase(test.TestCase): # dashes, and underscores. security_group_name = "aa #^% -=99" - try: - self.ec2.create_security_group(security_group_name, 'test group') - except EC2ResponseError, e: - if e.code == 'InvalidParameterValue': - pass - else: - self.fail("Unexpected EC2ResponseError: %s " - "(expected InvalidParameterValue)" % e.code) - else: - self.fail('Exception not raised.') + self.assertRaises(EC2ResponseError, self.ec2.create_security_group, + security_group_name, 'test group') def test_group_name_valid_length_security_group(self): """Test that we sanely handle invalid security group names. @@ -406,16 +398,9 @@ class ApiEc2TestCase(test.TestCase): # Test block group_name > 255 chars security_group_name = "".join(random.choice("poiuytrewqasdfghjklmnbvc") for x in range(random.randint(256, 266))) - try: - self.ec2.create_security_group(security_group_name, 'test group') - except EC2ResponseError, e: - if e.code == 'InvalidParameterValue': - pass - else: - self.fail("Unexpected EC2ResponseError: %s " - "(expected InvalidParameterValue)" % e.code) - else: - self.fail('Exception not raised.') + + self.assertRaises(EC2ResponseError, self.ec2.create_security_group, + security_group_name, 'test group') def test_authorize_revoke_security_group_cidr(self): """ -- 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 7834ab495fa657105a41dc590628981b5d44d8b9 Mon Sep 17 00:00:00 2001 From: Matthew Hooker Date: Thu, 28 Jul 2011 17:41:13 -0400 Subject: fix pep8 errors --- nova/tests/scheduler/test_zone_aware_scheduler.py | 5 +++-- nova/tests/test_xenapi.py | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/tests/scheduler/test_zone_aware_scheduler.py b/nova/tests/scheduler/test_zone_aware_scheduler.py index 3781c567e..043d0d5c8 100644 --- a/nova/tests/scheduler/test_zone_aware_scheduler.py +++ b/nova/tests/scheduler/test_zone_aware_scheduler.py @@ -343,5 +343,6 @@ class ZoneAwareSchedulerTestCase(test.TestCase): self.stubs.Set(zone_aware_scheduler, 'crypto', StubDecryptor()) - - self.assertEqual(fixture._decrypt_blob(test_data), json.dumps(test_data)) + + self.assertEqual(fixture._decrypt_blob(test_data), + json.dumps(test_data)) diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index e87622451..df95f2c60 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -807,6 +807,7 @@ class XenAPIMigrateInstance(test.TestCase): conn.finish_resize(instance, dict(base_copy='hurr', cow='durr'), network_info) + class XenAPIImageTypeTestCase(test.TestCase): """Test ImageType class.""" -- 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 a52b643b18e1bac18b642ecfd781809eb5612763 Mon Sep 17 00:00:00 2001 From: Isaku Yamahata Date: Fri, 29 Jul 2011 10:51:50 +0900 Subject: api/ec2: rename CloudController._get_instance_mapping into _format_instance_mapping This patch renames nova.api.ec2.cloud.CouldController._get_instance_mapping to _format_instance_mapping in order to make it clear that the method is for API formatting, not for internal use. --- nova/api/ec2/cloud.py | 4 ++-- nova/tests/test_cloud.py | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) (limited to 'nova') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 65f18ddbf..9b0ec2fde 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -237,7 +237,7 @@ class CloudController(object): state = 'available' return image['properties'].get('image_state', state) - def _get_instance_mapping(self, ctxt, instance_ref): + def _format_instance_mapping(self, ctxt, instance_ref): root_device_name = instance_ref['root_device_name'] if root_device_name is None: return _DEFAULT_MAPPINGS @@ -287,7 +287,7 @@ class CloudController(object): security_groups = db.security_group_get_by_instance(ctxt, instance_ref['id']) security_groups = [x['name'] for x in security_groups] - mappings = self._get_instance_mapping(ctxt, instance_ref) + mappings = self._format_instance_mapping(ctxt, instance_ref) data = { 'user-data': self._format_user_data(instance_ref), 'meta-data': { diff --git a/nova/tests/test_cloud.py b/nova/tests/test_cloud.py index 507b35d22..ac959bd63 100644 --- a/nova/tests/test_cloud.py +++ b/nova/tests/test_cloud.py @@ -1505,9 +1505,11 @@ class CloudTestCase(test.TestCase): 'ephemeral1': '/dev/sdd', 'ephemeral2': '/dev/sd3'} - self.assertEqual(self.cloud._get_instance_mapping(ctxt, instance_ref0), + self.assertEqual(self.cloud._format_instance_mapping(ctxt, + instance_ref0), cloud._DEFAULT_MAPPINGS) - self.assertEqual(self.cloud._get_instance_mapping(ctxt, instance_ref1), + self.assertEqual(self.cloud._format_instance_mapping(ctxt, + instance_ref1), expected) def test_describe_instance_attribute(self): -- cgit From 1e8a7f2846ce0a3fb3d9e31fc7d4dbf27d54fac2 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Thu, 28 Jul 2011 19:06:48 -0700 Subject: remove extra log statement --- nova/compute/api.py | 1 - 1 file changed, 1 deletion(-) (limited to 'nova') diff --git a/nova/compute/api.py b/nova/compute/api.py index 51c5ae155..8f7b3c3ef 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -689,7 +689,6 @@ class API(base.Base): raise instances = None elif project_id or not context.is_admin: - LOG.info(context.project_id) if not context.project_id: instances = self.db.instance_get_all_by_user( context, context.user_id) -- cgit From 45c3c01f69e1f13ced70942e6c8369098a307c48 Mon Sep 17 00:00:00 2001 From: Naveed Massjouni Date: Fri, 29 Jul 2011 01:54:19 -0400 Subject: Added xml schema validation for extensions resources. Added corresponding xml schemas. Added lxml dep, which is needed for doing xml schema validation. --- nova/api/openstack/extensions.py | 31 +- nova/api/openstack/schemas/atom-link.rng | 141 ++++++ nova/api/openstack/schemas/atom.rng | 597 +++++++++++++++++++++++++ nova/api/openstack/schemas/v1.1/extension.rng | 11 + nova/api/openstack/schemas/v1.1/extensions.rng | 6 + nova/tests/api/openstack/test_extensions.py | 26 +- 6 files changed, 792 insertions(+), 20 deletions(-) create mode 100644 nova/api/openstack/schemas/atom-link.rng create mode 100644 nova/api/openstack/schemas/atom.rng create mode 100644 nova/api/openstack/schemas/v1.1/extension.rng create mode 100644 nova/api/openstack/schemas/v1.1/extensions.rng (limited to 'nova') diff --git a/nova/api/openstack/extensions.py b/nova/api/openstack/extensions.py index cc889703e..6188e274d 100644 --- a/nova/api/openstack/extensions.py +++ b/nova/api/openstack/extensions.py @@ -23,7 +23,7 @@ import sys import routes import webob.dec import webob.exc -from xml.etree import ElementTree +from lxml import etree from nova import exception from nova import flags @@ -32,6 +32,7 @@ from nova import wsgi as base_wsgi from nova.api.openstack import common from nova.api.openstack import faults from nova.api.openstack import wsgi +from nova.api.openstack import xmlutil LOG = logging.getLogger('extensions') @@ -470,36 +471,38 @@ class ResourceExtension(object): class ExtensionsXMLSerializer(wsgi.XMLDictSerializer): + NSMAP = {None: xmlutil.XMLNS_V11, 'atom': xmlutil.XMLNS_ATOM} + def show(self, ext_dict): - ext = self._create_ext_elem(ext_dict['extension']) + ext = etree.Element('extension', nsmap=self.NSMAP) + self._populate_ext(ext, ext_dict['extension']) return self._to_xml(ext) def index(self, exts_dict): - exts = ElementTree.Element('extensions') + exts = etree.Element('extensions', nsmap=self.NSMAP) for ext_dict in exts_dict['extensions']: - exts.append(self._create_ext_elem(ext_dict)) + ext = etree.SubElement(exts, 'extension') + self._populate_ext(ext, ext_dict) return self._to_xml(exts) - def _create_ext_elem(self, ext_dict): - """Create an extension xml element from a dict.""" - ext_elem = ElementTree.Element('extension') + def _populate_ext(self, ext_elem, ext_dict): + """Populate an extension xml element from a dict.""" + ext_elem.set('name', ext_dict['name']) ext_elem.set('namespace', ext_dict['namespace']) ext_elem.set('alias', ext_dict['alias']) ext_elem.set('updated', ext_dict['updated']) - desc = ElementTree.Element('description') + desc = etree.Element('description') desc.text = ext_dict['description'] ext_elem.append(desc) for link in ext_dict.get('links', []): - elem = ElementTree.Element('atom:link') + elem = etree.SubElement(ext_elem, '{%s}link' % xmlutil.XMLNS_ATOM) elem.set('rel', link['rel']) elem.set('href', link['href']) elem.set('type', link['type']) - ext_elem.append(elem) return ext_elem def _to_xml(self, root): - """Convert the xml tree object to an xml string.""" - root.set('xmlns', wsgi.XMLNS_V11) - root.set('xmlns:atom', wsgi.XMLNS_ATOM) - return ElementTree.tostring(root, encoding='UTF-8') + """Convert the xml object to an xml string.""" + + return etree.tostring(root, encoding='UTF-8') diff --git a/nova/api/openstack/schemas/atom-link.rng b/nova/api/openstack/schemas/atom-link.rng new file mode 100644 index 000000000..edba5eee6 --- /dev/null +++ b/nova/api/openstack/schemas/atom-link.rng @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + [^:]* + + + + + + .+/.+ + + + + + + [A-Za-z]{1,8}(-[A-Za-z0-9]{1,8})* + + + + + + + + + + + + xml:base + xml:lang + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/nova/api/openstack/schemas/atom.rng b/nova/api/openstack/schemas/atom.rng new file mode 100644 index 000000000..c2df4e410 --- /dev/null +++ b/nova/api/openstack/schemas/atom.rng @@ -0,0 +1,597 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text + html + + + + + + + + + xhtml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + An atom:feed must have an atom:author unless all of its atom:entry children have an atom:author. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + An atom:entry must have at least one atom:link element with a rel attribute of 'alternate' or an atom:content. + + + An atom:entry must have an atom:author if its feed does not. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text + html + + + + + + + + + + + + + xhtml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + [^:]* + + + + + + .+/.+ + + + + + + [A-Za-z]{1,8}(-[A-Za-z0-9]{1,8})* + + + + + + + + + + .+@.+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + xml:base + xml:lang + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/nova/api/openstack/schemas/v1.1/extension.rng b/nova/api/openstack/schemas/v1.1/extension.rng new file mode 100644 index 000000000..336659755 --- /dev/null +++ b/nova/api/openstack/schemas/v1.1/extension.rng @@ -0,0 +1,11 @@ + + + + + + + + + + diff --git a/nova/api/openstack/schemas/v1.1/extensions.rng b/nova/api/openstack/schemas/v1.1/extensions.rng new file mode 100644 index 000000000..4d8bff646 --- /dev/null +++ b/nova/api/openstack/schemas/v1.1/extensions.rng @@ -0,0 +1,6 @@ + + + + + diff --git a/nova/tests/api/openstack/test_extensions.py b/nova/tests/api/openstack/test_extensions.py index d459c694f..2bc8bbb07 100644 --- a/nova/tests/api/openstack/test_extensions.py +++ b/nova/tests/api/openstack/test_extensions.py @@ -20,16 +20,20 @@ import os.path import stubout import unittest import webob -from xml.etree import ElementTree +from lxml import etree +from StringIO import StringIO from nova import context from nova import flags +from nova import utils from nova.api import openstack from nova.api.openstack import extensions from nova.api.openstack import flavors from nova.api.openstack import wsgi +from nova.api.openstack import xmlutil from nova.tests.api.openstack import fakes + FLAGS = flags.FLAGS NS = "{http://docs.openstack.org/compute/api/v1.1}" ATOMNS = "{http://www.w3.org/2005/Atom}" @@ -140,7 +144,7 @@ class ExtensionControllerTest(unittest.TestCase): self.assertEqual(200, response.status_int) print response.body - root = ElementTree.XML(response.body) + root = etree.XML(response.body) self.assertEqual(root.tag.split('extensions')[0], NS) # Make sure we have all the extensions. @@ -156,6 +160,8 @@ class ExtensionControllerTest(unittest.TestCase): self.assertEqual(fox_ext.findtext('{0}description'.format(NS)), 'The Fox In Socks Extension') + xmlutil.validate_schema(root, 'extensions') + def test_get_extension_xml(self): app = openstack.APIRouterV11() ext_midware = extensions.ExtensionMiddleware(app) @@ -163,9 +169,10 @@ class ExtensionControllerTest(unittest.TestCase): request.accept = "application/xml" response = request.get_response(ext_midware) self.assertEqual(200, response.status_int) - print response.body + xml = response.body + print xml - root = ElementTree.XML(response.body) + root = etree.XML(xml) self.assertEqual(root.tag.split('extension')[0], NS) self.assertEqual(root.get('alias'), 'FOXNSOX') self.assertEqual(root.get('name'), 'Fox In Socks') @@ -175,6 +182,8 @@ class ExtensionControllerTest(unittest.TestCase): self.assertEqual(root.findtext('{0}description'.format(NS)), 'The Fox In Socks Extension') + xmlutil.validate_schema(root, 'extension') + class ResourceExtensionTest(unittest.TestCase): @@ -354,7 +363,8 @@ class ExtensionsXMLSerializerTest(unittest.TestCase): } xml = serializer.serialize(data, 'show') - root = ElementTree.XML(xml) + print xml + root = etree.XML(xml) ext_dict = data['extension'] self.assertEqual(root.findtext('{0}description'.format(NS)), ext_dict['description']) @@ -368,6 +378,8 @@ class ExtensionsXMLSerializerTest(unittest.TestCase): for key, value in link.items(): self.assertEqual(link_nodes[i].get(key), value) + xmlutil.validate_schema(root, 'extension') + def test_serialize_extensions(self): serializer = extensions.ExtensionsXMLSerializer() data = { @@ -415,7 +427,7 @@ class ExtensionsXMLSerializerTest(unittest.TestCase): xml = serializer.serialize(data, 'index') print xml - root = ElementTree.XML(xml) + root = etree.XML(xml) ext_elems = root.findall('{0}extension'.format(NS)) self.assertEqual(len(ext_elems), 2) for i, ext_elem in enumerate(ext_elems): @@ -431,3 +443,5 @@ class ExtensionsXMLSerializerTest(unittest.TestCase): for i, link in enumerate(ext_dict['links']): for key, value in link.items(): self.assertEqual(link_nodes[i].get(key), value) + + xmlutil.validate_schema(root, 'extensions') -- cgit From 22b0e3948beaa2b1b3d61562e453412abb5edcbc Mon Sep 17 00:00:00 2001 From: Naveed Massjouni Date: Fri, 29 Jul 2011 03:42:40 -0400 Subject: Removing unnecessary imports. --- nova/tests/api/openstack/test_extensions.py | 2 -- 1 file changed, 2 deletions(-) (limited to 'nova') diff --git a/nova/tests/api/openstack/test_extensions.py b/nova/tests/api/openstack/test_extensions.py index 2bc8bbb07..ae0dc283f 100644 --- a/nova/tests/api/openstack/test_extensions.py +++ b/nova/tests/api/openstack/test_extensions.py @@ -21,11 +21,9 @@ import stubout import unittest import webob from lxml import etree -from StringIO import StringIO from nova import context from nova import flags -from nova import utils from nova.api import openstack from nova.api.openstack import extensions from nova.api.openstack import flavors -- 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 f91413bec1e6698935e00c323befa0655dea1ab1 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Fri, 29 Jul 2011 10:22:50 -0400 Subject: Fixed changes missed in merge --- nova/tests/api/openstack/test_server_actions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/tests/api/openstack/test_server_actions.py b/nova/tests/api/openstack/test_server_actions.py index 1651b1645..0bc7e4cf3 100644 --- a/nova/tests/api/openstack/test_server_actions.py +++ b/nova/tests/api/openstack/test_server_actions.py @@ -53,8 +53,8 @@ class MockSetAdminPassword(object): def _get_instance(): instance = { "id": 1, - "created_at": "2010-10-10T12:00:00Z", - "updated_at": "2010-11-11T11:00:00Z", + "created_at": "2010-10-10 12:00:00", + "updated_at": "2010-11-11 11:00:00", "admin_pass": "", "user_id": "", "project_id": "", -- cgit From 7a165843aa5c1a98b1dbf13dedf556878a3d0435 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Fri, 29 Jul 2011 11:06:02 -0400 Subject: Updated resize to call compute API with instance_type identifiers instead of flavor identifiers. Updated tests. --- nova/api/openstack/servers.py | 63 ++++++++++++++++++++------------ nova/compute/instance_types.py | 9 ++--- nova/tests/api/openstack/test_servers.py | 61 +++++++++++++++++++++++++++---- 3 files changed, 96 insertions(+), 37 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index f6841318d..096fb229c 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -17,11 +17,10 @@ import base64 import traceback from webob import exc -import webob from xml.dom import minidom +import webob from nova import compute -from nova import db from nova import exception from nova import flags from nova import log as logging @@ -29,13 +28,14 @@ from nova import utils from nova.api.openstack import common from nova.api.openstack import create_instance_helper as helper from nova.api.openstack import ips +from nova.api.openstack import wsgi +from nova.compute import instance_types +from nova.scheduler import api as scheduler_api +import nova.api.openstack import nova.api.openstack.views.addresses import nova.api.openstack.views.flavors import nova.api.openstack.views.images import nova.api.openstack.views.servers -from nova.api.openstack import wsgi -import nova.api.openstack -from nova.scheduler import api as scheduler_api LOG = logging.getLogger('nova.api.openstack.servers') @@ -438,13 +438,21 @@ class ControllerV10(Controller): def _action_resize(self, input_dict, req, id): """ Resizes a given instance to the flavor size requested """ - if 'resize' in input_dict and 'flavorId' in input_dict['resize']: - flavor_id = input_dict['resize']['flavorId'] - self.compute_api.resize(req.environ['nova.context'], id, - flavor_id) - else: - LOG.exception(_("Missing 'flavorId' argument for resize")) - raise exc.HTTPUnprocessableEntity() + try: + flavor_id = input_dict["resize"]["flavorId"] + except (KeyError, TypeError): + 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) def _action_rebuild(self, info, request, instance_id): @@ -555,17 +563,26 @@ class ControllerV11(Controller): def _action_resize(self, input_dict, req, id): """ Resizes a given instance to the flavor size requested """ try: - if 'resize' in input_dict and 'flavorRef' in input_dict['resize']: - flavor_ref = input_dict['resize']['flavorRef'] - flavor_id = common.get_id_from_href(flavor_ref) - self.compute_api.resize(req.environ['nova.context'], id, - flavor_id) - else: - LOG.exception(_("Missing 'flavorRef' argument for resize")) - raise exc.HTTPUnprocessableEntity() - except Exception, e: - LOG.exception(_("Error in resize %s"), e) - raise exc.HTTPBadRequest() + flavor = input_dict["resize"]["flavor"] + except (KeyError, TypeError): + msg = _("Resize requests require a flavor to resize instance.") + raise exc.HTTPBadRequest(explanation=msg) + + try: + flavor_id = flavor["id"] + except (KeyError, TypeError): + msg = _("Resize flavor requires 'id' 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) def _action_rebuild(self, info, request, instance_id): diff --git a/nova/compute/instance_types.py b/nova/compute/instance_types.py index c13a629a9..824416514 100644 --- a/nova/compute/instance_types.py +++ b/nova/compute/instance_types.py @@ -132,11 +132,8 @@ def get_instance_type_by_name(name): # flavors. def get_instance_type_by_flavor_id(flavor_id): """Retrieve instance type by flavor_id.""" - if flavor_id is None: - return get_default_instance_type() + ctxt = context.get_admin_context() try: - ctxt = context.get_admin_context() return db.instance_type_get_by_flavor_id(ctxt, flavor_id) - except exception.DBError, e: - LOG.exception(_('DB error: %s') % e) - raise exception.ApiError(_("Unknown flavor: %s") % flavor_id) + except ValueError: + raise exception.FlavorNotFound(flavor_id=flavor_id) diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 4027ef829..055a0b3ff 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -2167,11 +2167,16 @@ class ServersTest(test.TestCase): self.assertEqual(self.resize_called, True) def test_resize_server_v11(self): - req = webob.Request.blank('/v1.1/servers/1/action') req.content_type = 'application/json' req.method = 'POST' - body_dict = dict(resize=dict(flavorRef="http://localhost/3")) + body_dict = { + "resize": { + "flavor": { + "id": 3, + }, + }, + } req.body = json.dumps(body_dict) self.resize_called = False @@ -2185,8 +2190,8 @@ class ServersTest(test.TestCase): self.assertEqual(res.status_int, 202) self.assertEqual(self.resize_called, True) - def test_resize_bad_flavor_fails(self): - req = self.webreq('/1/action', 'POST', dict(resize=dict(derp=3))) + def test_resize_bad_flavor_data(self): + req = self.webreq('/1/action', 'POST', {"resize": "bad_data"}) self.resize_called = False @@ -2196,14 +2201,54 @@ class ServersTest(test.TestCase): self.stubs.Set(nova.compute.api.API, 'resize', resize_mock) res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status_int, 422) + self.assertEqual(res.status_int, 400) self.assertEqual(self.resize_called, False) + def test_resize_invalid_flavorid(self): + req = self.webreq('/1/action', 'POST', {"resize": {"flavorId": 300}}) + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 400) + + def test_resize_nonint_flavorid(self): + req = self.webreq('/1/action', 'POST', {"resize": {"flavorId": "a"}}) + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 400) + + def test_resize_invalid_flavorid_v1_1(self): + req = webob.Request.blank('/v1.1/servers/1/action') + req.content_type = 'application/json' + req.method = 'POST' + resize_body = { + "resize": { + "image": { + "id": 300, + }, + }, + } + req.body = json.dumps(resize_body) + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 400) + + def test_resize_nonint_flavorid_v1_1(self): + req = webob.Request.blank('/v1.1/servers/1/action') + req.content_type = 'application/json' + req.method = 'POST' + resize_body = { + "resize": { + "image": { + "id": "a", + }, + }, + } + req.body = json.dumps(resize_body) + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 400) + def test_resize_raises_fails(self): req = self.webreq('/1/action', 'POST', dict(resize=dict(flavorId=3))) def resize_mock(*args): - raise Exception('hurr durr') + raise Exception("An error occurred.") self.stubs.Set(nova.compute.api.API, 'resize', resize_mock) @@ -2241,7 +2286,7 @@ class ServersTest(test.TestCase): req = self.webreq('/1/action', 'POST', dict(confirmResize=None)) def confirm_resize_mock(*args): - raise Exception('hurr durr') + raise Exception("An error occurred.") self.stubs.Set(nova.compute.api.API, 'confirm_resize', confirm_resize_mock) @@ -2268,7 +2313,7 @@ class ServersTest(test.TestCase): req = self.webreq('/1/action', 'POST', dict(revertResize=None)) def revert_resize_mock(*args): - raise Exception('hurr durr') + raise Exception("An error occurred.") self.stubs.Set(nova.compute.api.API, 'revert_resize', revert_resize_mock) -- cgit From 50abd79432ff82a23da1934cc4d297c0c5051668 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Fri, 29 Jul 2011 11:57:40 -0400 Subject: Oops, I wasn't actually being compatible with the spec here. --- nova/api/openstack/servers.py | 12 +++--------- nova/tests/api/openstack/test_servers.py | 4 +--- 2 files changed, 4 insertions(+), 12 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 096fb229c..30169d450 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -563,19 +563,13 @@ class ControllerV11(Controller): def _action_resize(self, input_dict, req, id): """ Resizes a given instance to the flavor size requested """ try: - flavor = input_dict["resize"]["flavor"] + flavor_ref = input_dict["resize"]["flavorRef"] except (KeyError, TypeError): - msg = _("Resize requests require a flavor to resize instance.") + msg = _("Resize requests require 'flavorRef' attribute.") raise exc.HTTPBadRequest(explanation=msg) try: - flavor_id = flavor["id"] - except (KeyError, TypeError): - msg = _("Resize flavor requires 'id' attribute.") - raise exc.HTTPBadRequest(explanation=msg) - - try: - i_type = instance_types.get_instance_type_by_flavor_id(flavor_id) + 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) diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 055a0b3ff..221c1f4f7 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -2172,9 +2172,7 @@ class ServersTest(test.TestCase): req.method = 'POST' body_dict = { "resize": { - "flavor": { - "id": 3, - }, + "flavorRef": 3, }, } req.body = json.dumps(body_dict) -- cgit From 14b5036d01cdfecd650755345589424969b675ff Mon Sep 17 00:00:00 2001 From: Zed Shaw Date: Fri, 29 Jul 2011 10:33:58 -0700 Subject: Use the util.import_object to import a module. --- nova/rpc.py | 4 ++-- nova/utils.py | 11 ----------- 2 files changed, 2 insertions(+), 13 deletions(-) (limited to 'nova') diff --git a/nova/rpc.py b/nova/rpc.py index 8b0c6df67..f5dd49982 100644 --- a/nova/rpc.py +++ b/nova/rpc.py @@ -17,7 +17,7 @@ # under the License. -from nova.utils import load_module +from nova.utils import import_object from nova.rpc_backends.common import RemoteError, LOG from nova import flags @@ -26,7 +26,7 @@ flags.DEFINE_string('rpc_backend', 'nova.rpc_backends.amqp', "The messaging module to use, defaults to AMQP.") -RPCIMPL = load_module(FLAGS.rpc_backend) +RPCIMPL = import_object(FLAGS.rpc_backend) def create_connection(new=True): diff --git a/nova/utils.py b/nova/utils.py index ad31f88bd..8784a227d 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -809,14 +809,3 @@ class Bootstrapper(object): for key in FLAGS: value = FLAGS.get(key, None) logging.audit(_("%(key)s : %(value)s" % locals())) - - -def load_module(name): - mod = __import__(name) - - components = name.split('.') - - for comp in components[1:]: - mod = getattr(mod, comp) - - return mod -- cgit From 798dbb567be3e36eb2d6f0fbe27aa2eced0345d4 Mon Sep 17 00:00:00 2001 From: Matthew Hooker Date: Fri, 29 Jul 2011 14:58:49 -0400 Subject: remove unused import --- nova/tests/scheduler/test_zone_aware_scheduler.py | 1 - 1 file changed, 1 deletion(-) (limited to 'nova') diff --git a/nova/tests/scheduler/test_zone_aware_scheduler.py b/nova/tests/scheduler/test_zone_aware_scheduler.py index 043d0d5c8..7833028c3 100644 --- a/nova/tests/scheduler/test_zone_aware_scheduler.py +++ b/nova/tests/scheduler/test_zone_aware_scheduler.py @@ -17,7 +17,6 @@ Tests For Zone Aware Scheduler. """ import json -import mox import nova.db -- cgit From 87f021f73c01806bc1c22106e3a169d60024104f Mon Sep 17 00:00:00 2001 From: Zed Shaw Date: Fri, 29 Jul 2011 12:08:59 -0700 Subject: Reorganize the code to satisfy review comments. --- nova/rpc.py | 66 ----- nova/rpc/__init__.py | 66 +++++ nova/rpc/amqp.py | 591 ++++++++++++++++++++++++++++++++++++++++++ nova/rpc/common.py | 23 ++ nova/rpc_backends/__init__.py | 0 nova/rpc_backends/amqp.py | 591 ------------------------------------------ nova/rpc_backends/common.py | 23 -- nova/tests/test_rpc_amqp.py | 2 +- 8 files changed, 681 insertions(+), 681 deletions(-) delete mode 100644 nova/rpc.py create mode 100644 nova/rpc/__init__.py create mode 100644 nova/rpc/amqp.py create mode 100644 nova/rpc/common.py delete mode 100644 nova/rpc_backends/__init__.py delete mode 100644 nova/rpc_backends/amqp.py delete mode 100644 nova/rpc_backends/common.py (limited to 'nova') diff --git a/nova/rpc.py b/nova/rpc.py deleted file mode 100644 index f5dd49982..000000000 --- a/nova/rpc.py +++ /dev/null @@ -1,66 +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. - - -from nova.utils import import_object -from nova.rpc_backends.common import RemoteError, LOG -from nova import flags - -FLAGS = flags.FLAGS -flags.DEFINE_string('rpc_backend', - 'nova.rpc_backends.amqp', - "The messaging module to use, defaults to AMQP.") - -RPCIMPL = import_object(FLAGS.rpc_backend) - - -def create_connection(new=True): - return RPCIMPL.Connection.instance(new=True) - - -def create_consumer(conn, topic, proxy, fanout=False): - if fanout: - return RPCIMPL.FanoutAdapterConsumer( - connection=conn, - topic=topic, - proxy=proxy) - else: - return RPCIMPL.TopicAdapterConsumer( - connection=conn, - topic=topic, - proxy=proxy) - - -def create_consumer_set(conn, consumers): - return RPCIMPL.ConsumerSet(connection=conn, consumer_list=consumers) - - -def call(context, topic, msg): - return RPCIMPL.call(context, topic, msg) - - -def cast(context, topic, msg): - return RPCIMPL.cast(context, topic, msg) - - -def fanout_cast(context, topic, msg): - return RPCIMPL.fanout_cast(context, topic, msg) - - -def multicall(context, topic, msg): - return RPCIMPL.multicall(context, topic, msg) diff --git a/nova/rpc/__init__.py b/nova/rpc/__init__.py new file mode 100644 index 000000000..bdf7f705b --- /dev/null +++ b/nova/rpc/__init__.py @@ -0,0 +1,66 @@ +# 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. + + +from nova.utils import import_object +from nova.rpc.common import RemoteError, LOG +from nova import flags + +FLAGS = flags.FLAGS +flags.DEFINE_string('rpc_backend', + 'nova.rpc.amqp', + "The messaging module to use, defaults to AMQP.") + +RPCIMPL = import_object(FLAGS.rpc_backend) + + +def create_connection(new=True): + return RPCIMPL.Connection.instance(new=True) + + +def create_consumer(conn, topic, proxy, fanout=False): + if fanout: + return RPCIMPL.FanoutAdapterConsumer( + connection=conn, + topic=topic, + proxy=proxy) + else: + return RPCIMPL.TopicAdapterConsumer( + connection=conn, + topic=topic, + proxy=proxy) + + +def create_consumer_set(conn, consumers): + return RPCIMPL.ConsumerSet(connection=conn, consumer_list=consumers) + + +def call(context, topic, msg): + return RPCIMPL.call(context, topic, msg) + + +def cast(context, topic, msg): + return RPCIMPL.cast(context, topic, msg) + + +def fanout_cast(context, topic, msg): + return RPCIMPL.fanout_cast(context, topic, msg) + + +def multicall(context, topic, msg): + return RPCIMPL.multicall(context, topic, msg) diff --git a/nova/rpc/amqp.py b/nova/rpc/amqp.py new file mode 100644 index 000000000..61555795a --- /dev/null +++ b/nova/rpc/amqp.py @@ -0,0 +1,591 @@ +# 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. + +"""AMQP-based RPC. + +Queues have consumers and publishers. + +No fan-out support yet. + +""" + +import json +import sys +import time +import traceback +import types +import uuid + +from carrot import connection as carrot_connection +from carrot import messaging +from eventlet import greenpool +from eventlet import pools +from eventlet import queue +import greenlet + +from nova import context +from nova import exception +from nova import fakerabbit +from nova import flags +from nova import log as logging +from nova import utils +from nova.rpc.common import RemoteError, LOG + + +FLAGS = flags.FLAGS +flags.DEFINE_integer('rpc_thread_pool_size', 1024, + 'Size of RPC thread pool') +flags.DEFINE_integer('rpc_conn_pool_size', 30, + 'Size of RPC connection pool') + + +class Connection(carrot_connection.BrokerConnection): + """Connection instance object.""" + + @classmethod + def instance(cls, new=True): + """Returns the instance.""" + if new or not hasattr(cls, '_instance'): + params = dict(hostname=FLAGS.rabbit_host, + port=FLAGS.rabbit_port, + ssl=FLAGS.rabbit_use_ssl, + userid=FLAGS.rabbit_userid, + password=FLAGS.rabbit_password, + virtual_host=FLAGS.rabbit_virtual_host) + + if FLAGS.fake_rabbit: + params['backend_cls'] = fakerabbit.Backend + + # NOTE(vish): magic is fun! + # pylint: disable=W0142 + if new: + return cls(**params) + else: + cls._instance = cls(**params) + return cls._instance + + @classmethod + def recreate(cls): + """Recreates the connection instance. + + This is necessary to recover from some network errors/disconnects. + + """ + try: + del cls._instance + except AttributeError, e: + # The _instance stuff is for testing purposes. Usually we don't use + # it. So don't freak out if it doesn't exist. + pass + return cls.instance() + + +class Pool(pools.Pool): + """Class that implements a Pool of Connections.""" + + # TODO(comstud): Timeout connections not used in a while + def create(self): + LOG.debug('Creating new connection') + return Connection.instance(new=True) + +# Create a ConnectionPool to use for RPC calls. We'll order the +# pool as a stack (LIFO), so that we can potentially loop through and +# timeout old unused connections at some point +ConnectionPool = Pool( + max_size=FLAGS.rpc_conn_pool_size, + order_as_stack=True) + + +class Consumer(messaging.Consumer): + """Consumer base class. + + Contains methods for connecting the fetch method to async loops. + + """ + + def __init__(self, *args, **kwargs): + for i in xrange(FLAGS.rabbit_max_retries): + if i > 0: + time.sleep(FLAGS.rabbit_retry_interval) + try: + super(Consumer, self).__init__(*args, **kwargs) + self.failed_connection = False + break + except Exception as e: # Catching all because carrot sucks + fl_host = FLAGS.rabbit_host + fl_port = FLAGS.rabbit_port + fl_intv = FLAGS.rabbit_retry_interval + LOG.error(_('AMQP server on %(fl_host)s:%(fl_port)d is' + ' unreachable: %(e)s. Trying again in %(fl_intv)d' + ' seconds.') % locals()) + self.failed_connection = True + if self.failed_connection: + LOG.error(_('Unable to connect to AMQP server ' + 'after %d tries. Shutting down.'), + FLAGS.rabbit_max_retries) + sys.exit(1) + + def fetch(self, no_ack=None, auto_ack=None, enable_callbacks=False): + """Wraps the parent fetch with some logic for failed connection.""" + # TODO(vish): the logic for failed connections and logging should be + # refactored into some sort of connection manager object + try: + if self.failed_connection: + # NOTE(vish): connection is defined in the parent class, we can + # recreate it as long as we create the backend too + # pylint: disable=W0201 + self.connection = Connection.recreate() + self.backend = self.connection.create_backend() + self.declare() + return super(Consumer, self).fetch(no_ack, + auto_ack, + enable_callbacks) + if self.failed_connection: + LOG.error(_('Reconnected to queue')) + self.failed_connection = False + # NOTE(vish): This is catching all errors because we really don't + # want exceptions to be logged 10 times a second if some + # persistent failure occurs. + except Exception, e: # pylint: disable=W0703 + if not self.failed_connection: + LOG.exception(_('Failed to fetch message from queue: %s' % e)) + self.failed_connection = True + + def attach_to_eventlet(self): + """Only needed for unit tests!""" + timer = utils.LoopingCall(self.fetch, enable_callbacks=True) + timer.start(0.1) + return timer + + +class AdapterConsumer(Consumer): + """Calls methods on a proxy object based on method and args.""" + + def __init__(self, connection=None, topic='broadcast', proxy=None): + LOG.debug(_('Initing the Adapter Consumer for %s') % topic) + self.proxy = proxy + self.pool = greenpool.GreenPool(FLAGS.rpc_thread_pool_size) + super(AdapterConsumer, self).__init__(connection=connection, + topic=topic) + self.register_callback(self.process_data) + + def process_data(self, message_data, message): + """Consumer callback to call a method on a proxy object. + + Parses the message for validity and fires off a thread to call the + proxy object method. + + Message data should be a dictionary with two keys: + method: string representing the method to call + args: dictionary of arg: value + + Example: {'method': 'echo', 'args': {'value': 42}} + + """ + LOG.debug(_('received %s') % message_data) + # This will be popped off in _unpack_context + msg_id = message_data.get('_msg_id', None) + ctxt = _unpack_context(message_data) + + method = message_data.get('method') + args = message_data.get('args', {}) + message.ack() + if not method: + # NOTE(vish): we may not want to ack here, but that means that bad + # messages stay in the queue indefinitely, so for now + # we just log the message and send an error string + # back to the caller + LOG.warn(_('no method for message: %s') % message_data) + if msg_id: + msg_reply(msg_id, + _('No method for message: %s') % message_data) + return + self.pool.spawn_n(self._process_data, msg_id, ctxt, method, args) + + @exception.wrap_exception() + def _process_data(self, msg_id, ctxt, method, args): + """Thread that maigcally looks for a method on the proxy + object and calls it. + """ + + node_func = getattr(self.proxy, str(method)) + node_args = dict((str(k), v) for k, v in args.iteritems()) + # NOTE(vish): magic is fun! + try: + rval = node_func(context=ctxt, **node_args) + if msg_id: + # Check if the result was a generator + if isinstance(rval, types.GeneratorType): + for x in rval: + msg_reply(msg_id, x, None) + else: + msg_reply(msg_id, rval, None) + + # This final None tells multicall that it is done. + msg_reply(msg_id, None, None) + elif isinstance(rval, types.GeneratorType): + # NOTE(vish): this iterates through the generator + list(rval) + except Exception as e: + logging.exception('Exception during message handling') + if msg_id: + msg_reply(msg_id, None, sys.exc_info()) + return + + +class TopicAdapterConsumer(AdapterConsumer): + """Consumes messages on a specific topic.""" + + exchange_type = 'topic' + + def __init__(self, connection=None, topic='broadcast', proxy=None): + self.queue = topic + self.routing_key = topic + self.exchange = FLAGS.control_exchange + self.durable = False + super(TopicAdapterConsumer, self).__init__(connection=connection, + topic=topic, proxy=proxy) + + +class FanoutAdapterConsumer(AdapterConsumer): + """Consumes messages from a fanout exchange.""" + + exchange_type = 'fanout' + + def __init__(self, connection=None, topic='broadcast', proxy=None): + self.exchange = '%s_fanout' % topic + self.routing_key = topic + unique = uuid.uuid4().hex + self.queue = '%s_fanout_%s' % (topic, unique) + self.durable = False + # Fanout creates unique queue names, so we should auto-remove + # them when done, so they're not left around on restart. + # Also, we're the only one that should be consuming. exclusive + # implies auto_delete, so we'll just set that.. + self.exclusive = True + LOG.info(_('Created "%(exchange)s" fanout exchange ' + 'with "%(key)s" routing key'), + dict(exchange=self.exchange, key=self.routing_key)) + super(FanoutAdapterConsumer, self).__init__(connection=connection, + topic=topic, proxy=proxy) + + +class ConsumerSet(object): + """Groups consumers to listen on together on a single connection.""" + + def __init__(self, connection, consumer_list): + self.consumer_list = set(consumer_list) + self.consumer_set = None + self.enabled = True + self.init(connection) + + def init(self, conn): + if not conn: + conn = Connection.instance(new=True) + if self.consumer_set: + self.consumer_set.close() + self.consumer_set = messaging.ConsumerSet(conn) + for consumer in self.consumer_list: + consumer.connection = conn + # consumer.backend is set for us + self.consumer_set.add_consumer(consumer) + + def reconnect(self): + self.init(None) + + def wait(self, limit=None): + running = True + while running: + it = self.consumer_set.iterconsume(limit=limit) + if not it: + break + while True: + try: + it.next() + except StopIteration: + return + except greenlet.GreenletExit: + running = False + break + except Exception as e: + LOG.exception(_("Exception while processing consumer")) + self.reconnect() + # Break to outer loop + break + + def close(self): + self.consumer_set.close() + + +class Publisher(messaging.Publisher): + """Publisher base class.""" + pass + + +class TopicPublisher(Publisher): + """Publishes messages on a specific topic.""" + + exchange_type = 'topic' + + def __init__(self, connection=None, topic='broadcast'): + self.routing_key = topic + self.exchange = FLAGS.control_exchange + self.durable = False + super(TopicPublisher, self).__init__(connection=connection) + + +class FanoutPublisher(Publisher): + """Publishes messages to a fanout exchange.""" + + exchange_type = 'fanout' + + def __init__(self, topic, connection=None): + self.exchange = '%s_fanout' % topic + self.queue = '%s_fanout' % topic + self.durable = False + self.auto_delete = True + LOG.info(_('Creating "%(exchange)s" fanout exchange'), + dict(exchange=self.exchange)) + super(FanoutPublisher, self).__init__(connection=connection) + + +class DirectConsumer(Consumer): + """Consumes messages directly on a channel specified by msg_id.""" + + exchange_type = 'direct' + + def __init__(self, connection=None, msg_id=None): + self.queue = msg_id + self.routing_key = msg_id + self.exchange = msg_id + self.auto_delete = True + self.exclusive = True + super(DirectConsumer, self).__init__(connection=connection) + + +class DirectPublisher(Publisher): + """Publishes messages directly on a channel specified by msg_id.""" + + exchange_type = 'direct' + + def __init__(self, connection=None, msg_id=None): + self.routing_key = msg_id + self.exchange = msg_id + self.auto_delete = True + super(DirectPublisher, self).__init__(connection=connection) + + +def msg_reply(msg_id, reply=None, failure=None): + """Sends a reply or an error on the channel signified by msg_id. + + Failure should be a sys.exc_info() tuple. + + """ + if failure: + message = str(failure[1]) + tb = traceback.format_exception(*failure) + LOG.error(_("Returning exception %s to caller"), message) + LOG.error(tb) + failure = (failure[0].__name__, str(failure[1]), tb) + + with ConnectionPool.item() as conn: + publisher = DirectPublisher(connection=conn, msg_id=msg_id) + try: + publisher.send({'result': reply, 'failure': failure}) + except TypeError: + publisher.send( + {'result': dict((k, repr(v)) + for k, v in reply.__dict__.iteritems()), + 'failure': failure}) + + publisher.close() + + +def _unpack_context(msg): + """Unpack context from msg.""" + context_dict = {} + for key in list(msg.keys()): + # NOTE(vish): Some versions of python don't like unicode keys + # in kwargs. + key = str(key) + if key.startswith('_context_'): + value = msg.pop(key) + context_dict[key[9:]] = value + context_dict['msg_id'] = msg.pop('_msg_id', None) + LOG.debug(_('unpacked context: %s'), context_dict) + return RpcContext.from_dict(context_dict) + + +def _pack_context(msg, context): + """Pack context into msg. + + Values for message keys need to be less than 255 chars, so we pull + context out into a bunch of separate keys. If we want to support + more arguments in rabbit messages, we may want to do the same + for args at some point. + + """ + context_d = dict([('_context_%s' % key, value) + for (key, value) in context.to_dict().iteritems()]) + msg.update(context_d) + + +class RpcContext(context.RequestContext): + def __init__(self, *args, **kwargs): + msg_id = kwargs.pop('msg_id', None) + self.msg_id = msg_id + super(RpcContext, self).__init__(*args, **kwargs) + + def reply(self, *args, **kwargs): + msg_reply(self.msg_id, *args, **kwargs) + + +def multicall(context, topic, msg): + """Make a call that returns multiple times.""" + LOG.debug(_('Making asynchronous call on %s ...'), topic) + msg_id = uuid.uuid4().hex + msg.update({'_msg_id': msg_id}) + LOG.debug(_('MSG_ID is %s') % (msg_id)) + _pack_context(msg, context) + + con_conn = ConnectionPool.get() + consumer = DirectConsumer(connection=con_conn, msg_id=msg_id) + wait_msg = MulticallWaiter(consumer) + consumer.register_callback(wait_msg) + + publisher = TopicPublisher(connection=con_conn, topic=topic) + publisher.send(msg) + publisher.close() + + return wait_msg + + +class MulticallWaiter(object): + def __init__(self, consumer): + self._consumer = consumer + self._results = queue.Queue() + self._closed = False + + def close(self): + self._closed = True + self._consumer.close() + ConnectionPool.put(self._consumer.connection) + + def __call__(self, data, message): + """Acks message and sets result.""" + message.ack() + if data['failure']: + self._results.put(RemoteError(*data['failure'])) + else: + self._results.put(data['result']) + + def __iter__(self): + return self.wait() + + def wait(self): + while True: + rv = None + while rv is None and not self._closed: + try: + rv = self._consumer.fetch(enable_callbacks=True) + except Exception: + self.close() + raise + time.sleep(0.01) + + result = self._results.get() + if isinstance(result, Exception): + self.close() + raise result + if result == None: + self.close() + raise StopIteration + yield result + + +def call(context, topic, msg): + """Sends a message on a topic and wait for a response.""" + rv = multicall(context, topic, msg) + # NOTE(vish): return the last result from the multicall + rv = list(rv) + if not rv: + return + return rv[-1] + + +def cast(context, topic, msg): + """Sends a message on a topic without waiting for a response.""" + LOG.debug(_('Making asynchronous cast on %s...'), topic) + _pack_context(msg, context) + with ConnectionPool.item() as conn: + publisher = TopicPublisher(connection=conn, topic=topic) + publisher.send(msg) + publisher.close() + + +def fanout_cast(context, topic, msg): + """Sends a message on a fanout exchange without waiting for a response.""" + LOG.debug(_('Making asynchronous fanout cast...')) + _pack_context(msg, context) + with ConnectionPool.item() as conn: + publisher = FanoutPublisher(topic, connection=conn) + publisher.send(msg) + publisher.close() + + +def generic_response(message_data, message): + """Logs a result and exits.""" + LOG.debug(_('response %s'), message_data) + message.ack() + sys.exit(0) + + +def send_message(topic, message, wait=True): + """Sends a message for testing.""" + msg_id = uuid.uuid4().hex + message.update({'_msg_id': msg_id}) + LOG.debug(_('topic is %s'), topic) + LOG.debug(_('message %s'), message) + + if wait: + consumer = messaging.Consumer(connection=Connection.instance(), + queue=msg_id, + exchange=msg_id, + auto_delete=True, + exchange_type='direct', + routing_key=msg_id) + consumer.register_callback(generic_response) + + publisher = messaging.Publisher(connection=Connection.instance(), + exchange=FLAGS.control_exchange, + durable=False, + exchange_type='topic', + routing_key=topic) + publisher.send(message) + publisher.close() + + if wait: + consumer.wait() + consumer.close() + + +if __name__ == '__main__': + # You can send messages from the command line using + # topic and a json string representing a dictionary + # for the method + send_message(sys.argv[1], json.loads(sys.argv[2])) diff --git a/nova/rpc/common.py b/nova/rpc/common.py new file mode 100644 index 000000000..1d3065a83 --- /dev/null +++ b/nova/rpc/common.py @@ -0,0 +1,23 @@ +from nova import exception +from nova import log as logging + +LOG = logging.getLogger('nova.rpc') + + +class RemoteError(exception.Error): + """Signifies that a remote class has raised an exception. + + Containes a string representation of the type of the original exception, + the value of the original exception, and the traceback. These are + sent to the parent as a joined string so printing the exception + contains all of the relevent info. + + """ + + def __init__(self, exc_type, value, traceback): + self.exc_type = exc_type + self.value = value + self.traceback = traceback + super(RemoteError, self).__init__('%s %s\n%s' % (exc_type, + value, + traceback)) diff --git a/nova/rpc_backends/__init__.py b/nova/rpc_backends/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/nova/rpc_backends/amqp.py b/nova/rpc_backends/amqp.py deleted file mode 100644 index efa178bd2..000000000 --- a/nova/rpc_backends/amqp.py +++ /dev/null @@ -1,591 +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. - -"""AMQP-based RPC. - -Queues have consumers and publishers. - -No fan-out support yet. - -""" - -import json -import sys -import time -import traceback -import types -import uuid - -from carrot import connection as carrot_connection -from carrot import messaging -from eventlet import greenpool -from eventlet import pools -from eventlet import queue -import greenlet - -from nova import context -from nova import exception -from nova import fakerabbit -from nova import flags -from nova import log as logging -from nova import utils -from nova.rpc_backends.common import RemoteError, LOG - - -FLAGS = flags.FLAGS -flags.DEFINE_integer('rpc_thread_pool_size', 1024, - 'Size of RPC thread pool') -flags.DEFINE_integer('rpc_conn_pool_size', 30, - 'Size of RPC connection pool') - - -class Connection(carrot_connection.BrokerConnection): - """Connection instance object.""" - - @classmethod - def instance(cls, new=True): - """Returns the instance.""" - if new or not hasattr(cls, '_instance'): - params = dict(hostname=FLAGS.rabbit_host, - port=FLAGS.rabbit_port, - ssl=FLAGS.rabbit_use_ssl, - userid=FLAGS.rabbit_userid, - password=FLAGS.rabbit_password, - virtual_host=FLAGS.rabbit_virtual_host) - - if FLAGS.fake_rabbit: - params['backend_cls'] = fakerabbit.Backend - - # NOTE(vish): magic is fun! - # pylint: disable=W0142 - if new: - return cls(**params) - else: - cls._instance = cls(**params) - return cls._instance - - @classmethod - def recreate(cls): - """Recreates the connection instance. - - This is necessary to recover from some network errors/disconnects. - - """ - try: - del cls._instance - except AttributeError, e: - # The _instance stuff is for testing purposes. Usually we don't use - # it. So don't freak out if it doesn't exist. - pass - return cls.instance() - - -class Pool(pools.Pool): - """Class that implements a Pool of Connections.""" - - # TODO(comstud): Timeout connections not used in a while - def create(self): - LOG.debug('Creating new connection') - return Connection.instance(new=True) - -# Create a ConnectionPool to use for RPC calls. We'll order the -# pool as a stack (LIFO), so that we can potentially loop through and -# timeout old unused connections at some point -ConnectionPool = Pool( - max_size=FLAGS.rpc_conn_pool_size, - order_as_stack=True) - - -class Consumer(messaging.Consumer): - """Consumer base class. - - Contains methods for connecting the fetch method to async loops. - - """ - - def __init__(self, *args, **kwargs): - for i in xrange(FLAGS.rabbit_max_retries): - if i > 0: - time.sleep(FLAGS.rabbit_retry_interval) - try: - super(Consumer, self).__init__(*args, **kwargs) - self.failed_connection = False - break - except Exception as e: # Catching all because carrot sucks - fl_host = FLAGS.rabbit_host - fl_port = FLAGS.rabbit_port - fl_intv = FLAGS.rabbit_retry_interval - LOG.error(_('AMQP server on %(fl_host)s:%(fl_port)d is' - ' unreachable: %(e)s. Trying again in %(fl_intv)d' - ' seconds.') % locals()) - self.failed_connection = True - if self.failed_connection: - LOG.error(_('Unable to connect to AMQP server ' - 'after %d tries. Shutting down.'), - FLAGS.rabbit_max_retries) - sys.exit(1) - - def fetch(self, no_ack=None, auto_ack=None, enable_callbacks=False): - """Wraps the parent fetch with some logic for failed connection.""" - # TODO(vish): the logic for failed connections and logging should be - # refactored into some sort of connection manager object - try: - if self.failed_connection: - # NOTE(vish): connection is defined in the parent class, we can - # recreate it as long as we create the backend too - # pylint: disable=W0201 - self.connection = Connection.recreate() - self.backend = self.connection.create_backend() - self.declare() - return super(Consumer, self).fetch(no_ack, - auto_ack, - enable_callbacks) - if self.failed_connection: - LOG.error(_('Reconnected to queue')) - self.failed_connection = False - # NOTE(vish): This is catching all errors because we really don't - # want exceptions to be logged 10 times a second if some - # persistent failure occurs. - except Exception, e: # pylint: disable=W0703 - if not self.failed_connection: - LOG.exception(_('Failed to fetch message from queue: %s' % e)) - self.failed_connection = True - - def attach_to_eventlet(self): - """Only needed for unit tests!""" - timer = utils.LoopingCall(self.fetch, enable_callbacks=True) - timer.start(0.1) - return timer - - -class AdapterConsumer(Consumer): - """Calls methods on a proxy object based on method and args.""" - - def __init__(self, connection=None, topic='broadcast', proxy=None): - LOG.debug(_('Initing the Adapter Consumer for %s') % topic) - self.proxy = proxy - self.pool = greenpool.GreenPool(FLAGS.rpc_thread_pool_size) - super(AdapterConsumer, self).__init__(connection=connection, - topic=topic) - self.register_callback(self.process_data) - - def process_data(self, message_data, message): - """Consumer callback to call a method on a proxy object. - - Parses the message for validity and fires off a thread to call the - proxy object method. - - Message data should be a dictionary with two keys: - method: string representing the method to call - args: dictionary of arg: value - - Example: {'method': 'echo', 'args': {'value': 42}} - - """ - LOG.debug(_('received %s') % message_data) - # This will be popped off in _unpack_context - msg_id = message_data.get('_msg_id', None) - ctxt = _unpack_context(message_data) - - method = message_data.get('method') - args = message_data.get('args', {}) - message.ack() - if not method: - # NOTE(vish): we may not want to ack here, but that means that bad - # messages stay in the queue indefinitely, so for now - # we just log the message and send an error string - # back to the caller - LOG.warn(_('no method for message: %s') % message_data) - if msg_id: - msg_reply(msg_id, - _('No method for message: %s') % message_data) - return - self.pool.spawn_n(self._process_data, msg_id, ctxt, method, args) - - @exception.wrap_exception() - def _process_data(self, msg_id, ctxt, method, args): - """Thread that maigcally looks for a method on the proxy - object and calls it. - """ - - node_func = getattr(self.proxy, str(method)) - node_args = dict((str(k), v) for k, v in args.iteritems()) - # NOTE(vish): magic is fun! - try: - rval = node_func(context=ctxt, **node_args) - if msg_id: - # Check if the result was a generator - if isinstance(rval, types.GeneratorType): - for x in rval: - msg_reply(msg_id, x, None) - else: - msg_reply(msg_id, rval, None) - - # This final None tells multicall that it is done. - msg_reply(msg_id, None, None) - elif isinstance(rval, types.GeneratorType): - # NOTE(vish): this iterates through the generator - list(rval) - except Exception as e: - logging.exception('Exception during message handling') - if msg_id: - msg_reply(msg_id, None, sys.exc_info()) - return - - -class TopicAdapterConsumer(AdapterConsumer): - """Consumes messages on a specific topic.""" - - exchange_type = 'topic' - - def __init__(self, connection=None, topic='broadcast', proxy=None): - self.queue = topic - self.routing_key = topic - self.exchange = FLAGS.control_exchange - self.durable = False - super(TopicAdapterConsumer, self).__init__(connection=connection, - topic=topic, proxy=proxy) - - -class FanoutAdapterConsumer(AdapterConsumer): - """Consumes messages from a fanout exchange.""" - - exchange_type = 'fanout' - - def __init__(self, connection=None, topic='broadcast', proxy=None): - self.exchange = '%s_fanout' % topic - self.routing_key = topic - unique = uuid.uuid4().hex - self.queue = '%s_fanout_%s' % (topic, unique) - self.durable = False - # Fanout creates unique queue names, so we should auto-remove - # them when done, so they're not left around on restart. - # Also, we're the only one that should be consuming. exclusive - # implies auto_delete, so we'll just set that.. - self.exclusive = True - LOG.info(_('Created "%(exchange)s" fanout exchange ' - 'with "%(key)s" routing key'), - dict(exchange=self.exchange, key=self.routing_key)) - super(FanoutAdapterConsumer, self).__init__(connection=connection, - topic=topic, proxy=proxy) - - -class ConsumerSet(object): - """Groups consumers to listen on together on a single connection.""" - - def __init__(self, connection, consumer_list): - self.consumer_list = set(consumer_list) - self.consumer_set = None - self.enabled = True - self.init(connection) - - def init(self, conn): - if not conn: - conn = Connection.instance(new=True) - if self.consumer_set: - self.consumer_set.close() - self.consumer_set = messaging.ConsumerSet(conn) - for consumer in self.consumer_list: - consumer.connection = conn - # consumer.backend is set for us - self.consumer_set.add_consumer(consumer) - - def reconnect(self): - self.init(None) - - def wait(self, limit=None): - running = True - while running: - it = self.consumer_set.iterconsume(limit=limit) - if not it: - break - while True: - try: - it.next() - except StopIteration: - return - except greenlet.GreenletExit: - running = False - break - except Exception as e: - LOG.exception(_("Exception while processing consumer")) - self.reconnect() - # Break to outer loop - break - - def close(self): - self.consumer_set.close() - - -class Publisher(messaging.Publisher): - """Publisher base class.""" - pass - - -class TopicPublisher(Publisher): - """Publishes messages on a specific topic.""" - - exchange_type = 'topic' - - def __init__(self, connection=None, topic='broadcast'): - self.routing_key = topic - self.exchange = FLAGS.control_exchange - self.durable = False - super(TopicPublisher, self).__init__(connection=connection) - - -class FanoutPublisher(Publisher): - """Publishes messages to a fanout exchange.""" - - exchange_type = 'fanout' - - def __init__(self, topic, connection=None): - self.exchange = '%s_fanout' % topic - self.queue = '%s_fanout' % topic - self.durable = False - self.auto_delete = True - LOG.info(_('Creating "%(exchange)s" fanout exchange'), - dict(exchange=self.exchange)) - super(FanoutPublisher, self).__init__(connection=connection) - - -class DirectConsumer(Consumer): - """Consumes messages directly on a channel specified by msg_id.""" - - exchange_type = 'direct' - - def __init__(self, connection=None, msg_id=None): - self.queue = msg_id - self.routing_key = msg_id - self.exchange = msg_id - self.auto_delete = True - self.exclusive = True - super(DirectConsumer, self).__init__(connection=connection) - - -class DirectPublisher(Publisher): - """Publishes messages directly on a channel specified by msg_id.""" - - exchange_type = 'direct' - - def __init__(self, connection=None, msg_id=None): - self.routing_key = msg_id - self.exchange = msg_id - self.auto_delete = True - super(DirectPublisher, self).__init__(connection=connection) - - -def msg_reply(msg_id, reply=None, failure=None): - """Sends a reply or an error on the channel signified by msg_id. - - Failure should be a sys.exc_info() tuple. - - """ - if failure: - message = str(failure[1]) - tb = traceback.format_exception(*failure) - LOG.error(_("Returning exception %s to caller"), message) - LOG.error(tb) - failure = (failure[0].__name__, str(failure[1]), tb) - - with ConnectionPool.item() as conn: - publisher = DirectPublisher(connection=conn, msg_id=msg_id) - try: - publisher.send({'result': reply, 'failure': failure}) - except TypeError: - publisher.send( - {'result': dict((k, repr(v)) - for k, v in reply.__dict__.iteritems()), - 'failure': failure}) - - publisher.close() - - -def _unpack_context(msg): - """Unpack context from msg.""" - context_dict = {} - for key in list(msg.keys()): - # NOTE(vish): Some versions of python don't like unicode keys - # in kwargs. - key = str(key) - if key.startswith('_context_'): - value = msg.pop(key) - context_dict[key[9:]] = value - context_dict['msg_id'] = msg.pop('_msg_id', None) - LOG.debug(_('unpacked context: %s'), context_dict) - return RpcContext.from_dict(context_dict) - - -def _pack_context(msg, context): - """Pack context into msg. - - Values for message keys need to be less than 255 chars, so we pull - context out into a bunch of separate keys. If we want to support - more arguments in rabbit messages, we may want to do the same - for args at some point. - - """ - context_d = dict([('_context_%s' % key, value) - for (key, value) in context.to_dict().iteritems()]) - msg.update(context_d) - - -class RpcContext(context.RequestContext): - def __init__(self, *args, **kwargs): - msg_id = kwargs.pop('msg_id', None) - self.msg_id = msg_id - super(RpcContext, self).__init__(*args, **kwargs) - - def reply(self, *args, **kwargs): - msg_reply(self.msg_id, *args, **kwargs) - - -def multicall(context, topic, msg): - """Make a call that returns multiple times.""" - LOG.debug(_('Making asynchronous call on %s ...'), topic) - msg_id = uuid.uuid4().hex - msg.update({'_msg_id': msg_id}) - LOG.debug(_('MSG_ID is %s') % (msg_id)) - _pack_context(msg, context) - - con_conn = ConnectionPool.get() - consumer = DirectConsumer(connection=con_conn, msg_id=msg_id) - wait_msg = MulticallWaiter(consumer) - consumer.register_callback(wait_msg) - - publisher = TopicPublisher(connection=con_conn, topic=topic) - publisher.send(msg) - publisher.close() - - return wait_msg - - -class MulticallWaiter(object): - def __init__(self, consumer): - self._consumer = consumer - self._results = queue.Queue() - self._closed = False - - def close(self): - self._closed = True - self._consumer.close() - ConnectionPool.put(self._consumer.connection) - - def __call__(self, data, message): - """Acks message and sets result.""" - message.ack() - if data['failure']: - self._results.put(RemoteError(*data['failure'])) - else: - self._results.put(data['result']) - - def __iter__(self): - return self.wait() - - def wait(self): - while True: - rv = None - while rv is None and not self._closed: - try: - rv = self._consumer.fetch(enable_callbacks=True) - except Exception: - self.close() - raise - time.sleep(0.01) - - result = self._results.get() - if isinstance(result, Exception): - self.close() - raise result - if result == None: - self.close() - raise StopIteration - yield result - - -def call(context, topic, msg): - """Sends a message on a topic and wait for a response.""" - rv = multicall(context, topic, msg) - # NOTE(vish): return the last result from the multicall - rv = list(rv) - if not rv: - return - return rv[-1] - - -def cast(context, topic, msg): - """Sends a message on a topic without waiting for a response.""" - LOG.debug(_('Making asynchronous cast on %s...'), topic) - _pack_context(msg, context) - with ConnectionPool.item() as conn: - publisher = TopicPublisher(connection=conn, topic=topic) - publisher.send(msg) - publisher.close() - - -def fanout_cast(context, topic, msg): - """Sends a message on a fanout exchange without waiting for a response.""" - LOG.debug(_('Making asynchronous fanout cast...')) - _pack_context(msg, context) - with ConnectionPool.item() as conn: - publisher = FanoutPublisher(topic, connection=conn) - publisher.send(msg) - publisher.close() - - -def generic_response(message_data, message): - """Logs a result and exits.""" - LOG.debug(_('response %s'), message_data) - message.ack() - sys.exit(0) - - -def send_message(topic, message, wait=True): - """Sends a message for testing.""" - msg_id = uuid.uuid4().hex - message.update({'_msg_id': msg_id}) - LOG.debug(_('topic is %s'), topic) - LOG.debug(_('message %s'), message) - - if wait: - consumer = messaging.Consumer(connection=Connection.instance(), - queue=msg_id, - exchange=msg_id, - auto_delete=True, - exchange_type='direct', - routing_key=msg_id) - consumer.register_callback(generic_response) - - publisher = messaging.Publisher(connection=Connection.instance(), - exchange=FLAGS.control_exchange, - durable=False, - exchange_type='topic', - routing_key=topic) - publisher.send(message) - publisher.close() - - if wait: - consumer.wait() - consumer.close() - - -if __name__ == '__main__': - # You can send messages from the command line using - # topic and a json string representing a dictionary - # for the method - send_message(sys.argv[1], json.loads(sys.argv[2])) diff --git a/nova/rpc_backends/common.py b/nova/rpc_backends/common.py deleted file mode 100644 index 1d3065a83..000000000 --- a/nova/rpc_backends/common.py +++ /dev/null @@ -1,23 +0,0 @@ -from nova import exception -from nova import log as logging - -LOG = logging.getLogger('nova.rpc') - - -class RemoteError(exception.Error): - """Signifies that a remote class has raised an exception. - - Containes a string representation of the type of the original exception, - the value of the original exception, and the traceback. These are - sent to the parent as a joined string so printing the exception - contains all of the relevent info. - - """ - - def __init__(self, exc_type, value, traceback): - self.exc_type = exc_type - self.value = value - self.traceback = traceback - super(RemoteError, self).__init__('%s %s\n%s' % (exc_type, - value, - traceback)) diff --git a/nova/tests/test_rpc_amqp.py b/nova/tests/test_rpc_amqp.py index e3df2393a..d29f7ae32 100644 --- a/nova/tests/test_rpc_amqp.py +++ b/nova/tests/test_rpc_amqp.py @@ -2,7 +2,7 @@ from nova import context from nova import flags from nova import log as logging from nova import rpc -from nova.rpc_backends import amqp +from nova.rpc import amqp from nova import test -- 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 85795ff1f8b6a0ff3de634828208d6debd91692f Mon Sep 17 00:00:00 2001 From: Ed Leafe Date: Mon, 1 Aug 2011 21:06:47 +0000 Subject: Added option for rebooting or shutting down a host. --- nova/api/openstack/contrib/hosts.py | 15 +++++++++++++++ nova/compute/api.py | 5 +++++ nova/compute/manager.py | 8 +++++++- nova/tests/test_hosts.py | 9 +++++++++ nova/virt/driver.py | 4 ++++ nova/virt/fake.py | 4 ++++ nova/virt/hyperv.py | 4 ++++ nova/virt/libvirt/connection.py | 4 ++++ nova/virt/vmwareapi_conn.py | 4 ++++ nova/virt/xenapi/vmops.py | 8 ++++++++ nova/virt/xenapi_conn.py | 4 ++++ 11 files changed, 68 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/api/openstack/contrib/hosts.py b/nova/api/openstack/contrib/hosts.py index 55e57e1a4..b6a4bdb77 100644 --- a/nova/api/openstack/contrib/hosts.py +++ b/nova/api/openstack/contrib/hosts.py @@ -78,6 +78,12 @@ class HostController(object): else: explanation = _("Invalid status: '%s'") % raw_val raise webob.exc.HTTPBadRequest(explanation=explanation) + elif key == "powerstate": + if val in ("reboot", "shutdown"): + return self._set_powerstate(req, id, val) + else: + explanation = _("Invalid powerstate: '%s'") % raw_val + raise webob.exc.HTTPBadRequest(explanation=explanation) else: explanation = _("Invalid update setting: '%s'") % raw_key raise webob.exc.HTTPBadRequest(explanation=explanation) @@ -91,6 +97,15 @@ class HostController(object): enabled=enabled) return {"host": host, "status": result} + def _set_powerstate(self, req, host, state): + """Reboots or shuts down the host.""" + context = req.environ['nova.context'] + LOG.audit(_("Changing powerstate of host %(host)s to %(state)s.") + % locals()) + result = self.compute_api.set_host_powerstate(context, host=host, + state=state) + return {"host": host, "powerstate": result} + class Hosts(extensions.ExtensionDescriptor): def get_name(self): diff --git a/nova/compute/api.py b/nova/compute/api.py index 8f7b3c3ef..bd17fdf31 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -998,6 +998,11 @@ class API(base.Base): return self._call_compute_message("set_host_enabled", context, instance_id=None, host=host, params={"enabled": enabled}) + def set_host_powerstate(self, context, host, state): + """Reboots or shuts down the host.""" + return self._call_compute_message("set_host_powerstate", context, + instance_id=None, host=host, params={"state": state}) + @scheduler_api.reroute_compute("diagnostics") def get_diagnostics(self, context, instance_id): """Retrieve diagnostics for the given instance.""" diff --git a/nova/compute/manager.py b/nova/compute/manager.py index a2d84cd76..d0f9a81f4 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -1,4 +1,4 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 +: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2010 United States Government as represented by the # Administrator of the National Aeronautics and Space Administration. @@ -927,6 +927,12 @@ class ComputeManager(manager.SchedulerDependentManager): instance_id, result)) + @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id()) + def set_host_powerstate(self, context, instance_id=None, host=None, + state=None): + """Reboots or shuts down the host.""" + return self.driver.set_host_powerstate(host, state) + @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id()) def set_host_enabled(self, context, instance_id=None, host=None, enabled=None): diff --git a/nova/tests/test_hosts.py b/nova/tests/test_hosts.py index 548f81f8b..ad057f429 100644 --- a/nova/tests/test_hosts.py +++ b/nova/tests/test_hosts.py @@ -48,6 +48,13 @@ def stub_set_host_enabled(context, host, enabled): return status +def stub_set_host_powerstate(context, host, state): + # We'll simulate success and failure by assuming + # that 'host_c1' always succeeds, and 'host_c2' + # always fails + return state if host == "host_c1" else "running" + + class FakeRequest(object): environ = {"nova.context": context.get_admin_context()} @@ -62,6 +69,8 @@ class HostTestCase(test.TestCase): self.stubs.Set(scheduler_api, 'get_host_list', stub_get_host_list) self.stubs.Set(self.controller.compute_api, 'set_host_enabled', stub_set_host_enabled) + self.stubs.Set(self.controller.compute_api, 'set_host_powerstate', + stub_set_host_powerstate) def test_list_hosts(self): """Verify that the compute hosts are returned.""" diff --git a/nova/virt/driver.py b/nova/virt/driver.py index b219fb2cb..bbb17480d 100644 --- a/nova/virt/driver.py +++ b/nova/virt/driver.py @@ -251,6 +251,10 @@ class ComputeDriver(object): """Poll for rescued instances""" raise NotImplementedError() + def set_host_powerstate(self, host, state): + """Reboots or shuts down the host.""" + raise NotImplementedError() + def set_host_enabled(self, host, enabled): """Sets the specified host's ability to accept new instances.""" raise NotImplementedError() diff --git a/nova/virt/fake.py b/nova/virt/fake.py index 26bc421c0..6dc6552d7 100644 --- a/nova/virt/fake.py +++ b/nova/virt/fake.py @@ -515,6 +515,10 @@ class FakeConnection(driver.ComputeDriver): """Return fake Host Status of ram, disk, network.""" return self.host_status + def set_host_powerstate(self, host, state): + """Reboots or shuts down the host.""" + pass + def set_host_enabled(self, host, enabled): """Sets the specified host's ability to accept new instances.""" pass diff --git a/nova/virt/hyperv.py b/nova/virt/hyperv.py index c26fe108b..119def38b 100644 --- a/nova/virt/hyperv.py +++ b/nova/virt/hyperv.py @@ -498,6 +498,10 @@ class HyperVConnection(driver.ComputeDriver): """See xenapi_conn.py implementation.""" pass + def set_host_powerstate(self, host, state): + """Reboots or shuts down the host.""" + pass + def set_host_enabled(self, host, enabled): """Sets the specified host's ability to accept new instances.""" pass diff --git a/nova/virt/libvirt/connection.py b/nova/virt/libvirt/connection.py index 17c328a83..ae1a16d44 100644 --- a/nova/virt/libvirt/connection.py +++ b/nova/virt/libvirt/connection.py @@ -1583,6 +1583,10 @@ class LibvirtConnection(driver.ComputeDriver): """See xenapi_conn.py implementation.""" pass + def set_host_powerstate(self, host, state): + """Reboots or shuts down the host.""" + pass + def set_host_enabled(self, host, enabled): """Sets the specified host's ability to accept new instances.""" pass diff --git a/nova/virt/vmwareapi_conn.py b/nova/virt/vmwareapi_conn.py index ce57847b2..af547821f 100644 --- a/nova/virt/vmwareapi_conn.py +++ b/nova/virt/vmwareapi_conn.py @@ -190,6 +190,10 @@ class VMWareESXConnection(driver.ComputeDriver): """This method is supported only by libvirt.""" return + def set_host_powerstate(self, host, state): + """Reboots or shuts down the host.""" + pass + def set_host_enabled(self, host, enabled): """Sets the specified host's ability to accept new instances.""" pass diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 7e02e1def..8e57042f9 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -1023,6 +1023,14 @@ class VMOps(object): # TODO: implement this! return 'http://fakeajaxconsole/fake_url' + def set_host_powerstate(self, host, state): + """Reboots or shuts down the host.""" + args = {"state": json.dumps(state)} + methods = {"reboot": "host_reboot", "shutdown": "host_shutdown"} + json_resp = self._call_xenhost(methods[state], args) + resp = json.loads(json_resp) + return resp["powerstate"] + def set_host_enabled(self, host, enabled): """Sets the specified host's ability to accept new instances.""" args = {"enabled": json.dumps(enabled)} diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index cc18ed83c..5ac837a17 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -334,6 +334,10 @@ class XenAPIConnection(driver.ComputeDriver): True, run the update first.""" return self.HostState.get_host_stats(refresh=refresh) + def set_host_powerstate(self, host, state): + """Reboots or shuts down the host.""" + return self._vmops.set_host_powerstate(host, state) + def set_host_enabled(self, host, enabled): """Sets the specified host's ability to accept new instances.""" return self._vmops.set_host_enabled(host, enabled) -- 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 883095f1e69d70173339ccba64f2b2a8a1d48a5f Mon Sep 17 00:00:00 2001 From: Justin Shepherd Date: Mon, 1 Aug 2011 20:45:30 -0500 Subject: Added @test.skip_unless and @test.skip_if functionality. Also created nova/tests/test_skip_examples.py to show the skip cases usage. --- nova/test.py | 31 +++++++++++++++++++++++++++++++ nova/tests/test_skip_examples.py | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 nova/tests/test_skip_examples.py (limited to 'nova') diff --git a/nova/test.py b/nova/test.py index 549aa6fcf..afc118100 100644 --- a/nova/test.py +++ b/nova/test.py @@ -67,6 +67,37 @@ class skip_test(object): _skipper.__doc__ = func.__doc__ return _skipper +class skip_if(object): + """Decorator that skips a test if contition is true.""" + def __init__(self, condition, msg): + self.condition = condition + self.message = msg + + def __call__(self, func): + def _skipper(*args, **kw): + """Wrapped skipper function.""" + if self.condition: + raise nose.SkipTest(self.message) + _skipper.__name__ = func.__name__ + _skipper.__doc__ = func.__doc__ + return _skipper + + +class skip_unless(object): + """Decorator that skips a test if condition is false.""" + def __init__(self, condition, msg): + self.condition = condition + self.message = msg + + def __call__(self, func): + def _skipper(*args, **kw): + """Wrapped skipper function.""" + if not self.condition: + raise nose.SkipTest(self.message) + _skipper.__name__ = func.__name__ + _skipper.__doc__ = func.__doc__ + return _skipper + def skip_if_fake(func): """Decorator that skips a test if running in fake mode.""" diff --git a/nova/tests/test_skip_examples.py b/nova/tests/test_skip_examples.py new file mode 100644 index 000000000..dfbaac74b --- /dev/null +++ b/nova/tests/test_skip_examples.py @@ -0,0 +1,37 @@ +# 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 os + +from nova import test + + +class ExampleSkipTestCase(test.TestCase): + @test.skip_test("testing skipping") + def test_skip_test(self): + x = 1 + + @test.skip_if(os.getenv("USER"), + "Skiping -- Environment variable USER exists") + def test_skip_if_env_user_exists(self): + x = 1 + + @test.skip_unless(os.getenv("BLAH"), + "Skipping -- Environment variable BLAH does not exist") + def test_skip_unless_env_blah_exists(self): + x = 1 -- 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 695afaffaa4de359b306280c252f8f40a3bab5a7 Mon Sep 17 00:00:00 2001 From: William Wolf Date: Tue, 2 Aug 2011 11:48:01 -0400 Subject: cleanup --- nova/api/openstack/server_metadata.py | 2 +- nova/api/openstack/servers.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/server_metadata.py b/nova/api/openstack/server_metadata.py index f25d36535..b0b014f86 100644 --- a/nova/api/openstack/server_metadata.py +++ b/nova/api/openstack/server_metadata.py @@ -53,7 +53,7 @@ class Controller(object): metadata = body['metadata'] except (KeyError, TypeError): msg = _("Malformed request body") - raise exc.HTTPBadRequest(esplanation=msg) + raise exc.HTTPBadRequest(explanation=msg) context = req.environ['nova.context'] diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 1453424ce..30169d450 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -19,7 +19,6 @@ import traceback from webob import exc from xml.dom import minidom import webob -from xml.dom import minidom from nova import compute from nova import exception -- 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 113fe431d61899b8402181d1016ab29479330636 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Tue, 2 Aug 2011 13:34:29 -0500 Subject: Get instance by UUID instead of id. --- nova/compute/manager.py | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index a2d84cd76..c7c88a1fb 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -648,37 +648,38 @@ class ComputeManager(manager.SchedulerDependentManager): @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id()) @checks_instance_lock - def rescue_instance(self, context, instance_id): + def rescue_instance(self, context, instance_uuid): """Rescue an instance on this host.""" context = context.elevated() - instance_ref = self.db.instance_get(context, instance_id) - LOG.audit(_('instance %s: rescuing'), instance_id, context=context) + instance_ref = self.db.instance_get_by_uuid(context, instance_uuid) + LOG.audit(_('instance %s: rescuing'), instance_uuid, context=context) self.db.instance_set_state(context, - instance_id, + instance_uuid, power_state.NOSTATE, 'rescuing') _update_state = lambda result: self._update_state_callback( - self, context, instance_id, result) + self, context, instance_uuid, result) network_info = self._get_instance_nw_info(context, instance_ref) self.driver.rescue(instance_ref, _update_state, network_info) - self._update_state(context, instance_id) + self._update_state(context, instance_uuid) @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id()) @checks_instance_lock - def unrescue_instance(self, context, instance_id): + def unrescue_instance(self, context, instance_uuid): """Rescue an instance on this host.""" context = context.elevated() - instance_ref = self.db.instance_get(context, instance_id) - LOG.audit(_('instance %s: unrescuing'), instance_id, context=context) + instance_ref = self.db.instance_get_by_uuid(context, instance_uuid) + LOG.audit(_('instance %s: unrescuing'), instance_uuid, + context=context) self.db.instance_set_state(context, - instance_id, + instance_uuid, power_state.NOSTATE, 'unrescuing') _update_state = lambda result: self._update_state_callback( - self, context, instance_id, result) + self, context, instance_uuid, result) network_info = self._get_instance_nw_info(context, instance_ref) self.driver.unrescue(instance_ref, _update_state, network_info) - self._update_state(context, instance_id) + self._update_state(context, instance_uuid) @staticmethod def _update_state_callback(self, context, instance_id, result): -- 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 f06dee2b82bd658a57736d94974f431976085400 Mon Sep 17 00:00:00 2001 From: Ed Leafe Date: Tue, 2 Aug 2011 19:02:40 +0000 Subject: Fixed several typos --- nova/api/openstack/contrib/hosts.py | 5 ++--- nova/compute/manager.py | 2 +- nova/tests/test_hosts.py | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/contrib/hosts.py b/nova/api/openstack/contrib/hosts.py index 94fba910c..2d9d494b9 100644 --- a/nova/api/openstack/contrib/hosts.py +++ b/nova/api/openstack/contrib/hosts.py @@ -85,7 +85,7 @@ class HostController(object): # 'startup' option to start up a host, but this is not # technically feasible now, as we run the host on the # XenServer box. - msg = _("Host startup on XenServer is not supported.")) + msg = _("Host startup on XenServer is not supported.") raise webob.exc.HTTPBadRequest(explanation=msg) elif val in ("reboot", "shutdown"): return self._set_powerstate(req, id, val) @@ -106,8 +106,7 @@ class HostController(object): return {"host": host, "status": result} def _set_powerstate(self, req, host, state): - """Reboots or shuts down the host. - """ + """Reboots or shuts down the host.""" context = req.environ['nova.context'] result = self.compute_api.set_host_powerstate(context, host=host, state=state) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index d0f9a81f4..cd05f3f24 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -1,4 +1,4 @@ -: tabstop=4 shiftwidth=4 softtabstop=4 +#: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2010 United States Government as represented by the # Administrator of the National Aeronautics and Space Administration. diff --git a/nova/tests/test_hosts.py b/nova/tests/test_hosts.py index ad057f429..9f54d5ec7 100644 --- a/nova/tests/test_hosts.py +++ b/nova/tests/test_hosts.py @@ -52,7 +52,7 @@ def stub_set_host_powerstate(context, host, state): # We'll simulate success and failure by assuming # that 'host_c1' always succeeds, and 'host_c2' # always fails - return state if host == "host_c1" else "running" + return state if host == "host_c1" else "running" class FakeRequest(object): -- cgit From ded9447aca01d593b0efa91b5ec23ecf8aa97264 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Tue, 2 Aug 2011 15:43:36 -0400 Subject: Fixed merge issues --- nova/api/openstack/images.py | 3 --- nova/api/openstack/servers.py | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index 43d976b8f..0834adfa5 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -197,9 +197,6 @@ class ControllerV11(Controller): def create(self, *args, **kwargs): raise webob.exc.HTTPMethodNotAllowed() - def create(self, *args, **kwargs): - raise webob.exc.HTTPMethodNotAllowed() - class ImageXMLSerializer(wsgi.XMLDictSerializer): diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index dfa150320..7b757143d 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -20,7 +20,6 @@ import traceback from webob import exc from xml.dom import minidom import webob -from xml.dom import minidom from nova import compute from nova import exception @@ -178,6 +177,7 @@ class Controller(object): 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): -- 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 From 4c07cac5b0e79f3911fbcc392c3f9e7f07333968 Mon Sep 17 00:00:00 2001 From: Ed Leafe Date: Tue, 2 Aug 2011 20:39:14 +0000 Subject: Fixed a missing space. --- nova/compute/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/compute/api.py b/nova/compute/api.py index b83c4831c..5ea7a1c01 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -995,7 +995,7 @@ class API(base.Base): return self._call_compute_message("set_host_enabled", context, instance_id=None, host=host, params={"enabled": enabled}) - def set_host_powerstate(self, context, host, state): + def set_host_powerstate(self, context, host, state): """Reboots or shuts down the host.""" return self._call_compute_message("set_host_powerstate", context, instance_id=None, host=host, params={"state": state}) -- cgit From 1d3d1d5fb552f2dc80c39ad15d89d59bfc7f873a Mon Sep 17 00:00:00 2001 From: Ed Leafe Date: Tue, 2 Aug 2011 21:11:12 +0000 Subject: Minor test fixes --- nova/api/openstack/contrib/hosts.py | 4 ++-- nova/tests/test_hosts.py | 11 +++++------ 2 files changed, 7 insertions(+), 8 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/contrib/hosts.py b/nova/api/openstack/contrib/hosts.py index e9f82c75b..c90a889d5 100644 --- a/nova/api/openstack/contrib/hosts.py +++ b/nova/api/openstack/contrib/hosts.py @@ -78,7 +78,7 @@ class HostController(object): else: explanation = _("Invalid status: '%s'") % raw_val raise webob.exc.HTTPBadRequest(explanation=explanation) - elif key == "powerstate": + elif key == "power_state": if val == "startup": # The only valid values for 'state' are 'reboot' or # 'shutdown'. For completeness' sake there is the @@ -113,7 +113,7 @@ class HostController(object): context = req.environ['nova.context'] result = self.compute_api.set_host_powerstate(context, host=host, state=state) - return {"host": host, "powerstate": result} + return {"host": host, "power_state": result} class Hosts(extensions.ExtensionDescriptor): diff --git a/nova/tests/test_hosts.py b/nova/tests/test_hosts.py index 1c7c21a1a..d8f90a109 100644 --- a/nova/tests/test_hosts.py +++ b/nova/tests/test_hosts.py @@ -49,10 +49,7 @@ def stub_set_host_enabled(context, host, enabled): def stub_set_host_powerstate(context, host, state): - # We'll simulate success and failure by assuming - # that 'host_c1' always succeeds, and 'host_c2' - # always fails - return state if host == "host_c1" else "running" + return state class FakeRequest(object): @@ -96,12 +93,14 @@ class HostTestCase(test.TestCase): result_c2 = self.controller.update(self.req, "host_c2", body=en_body) self.assertEqual(result_c2["status"], "disabled") - def test_power_state(self): + def test_host_power_state(self): en_body = {"power_state": "reboot"} result_c1 = self.controller.update(self.req, "host_c1", body=en_body) self.assertEqual(result_c1["power_state"], "reboot") + # Test invalid power_state + en_body = {"power_state": "invalid"} self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update, - self.req, "host_c2", body=en_body) + self.req, "host_c1", body=en_body) def test_bad_power_state_value(self): bad_body = {"power_state": "bad"} -- cgit From 14e8257af4624fa5b056a1b0e94d1b584e080ce9 Mon Sep 17 00:00:00 2001 From: Ed Leafe Date: Tue, 2 Aug 2011 22:48:47 +0000 Subject: Added check for --allow-admin-api to the host API extension code. --- nova/api/openstack/contrib/hosts.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/contrib/hosts.py b/nova/api/openstack/contrib/hosts.py index c90a889d5..78ba66771 100644 --- a/nova/api/openstack/contrib/hosts.py +++ b/nova/api/openstack/contrib/hosts.py @@ -133,6 +133,11 @@ class Hosts(extensions.ExtensionDescriptor): return "2011-06-29T00:00:00+00:00" def get_resources(self): - resources = [extensions.ResourceExtension('os-hosts', HostController(), - collection_actions={'update': 'PUT'}, member_actions={})] + resources = [] + # If we are not in an admin env, don't add the resource. Regular users + # shouldn't have access to the host. + if FLAGS.allow_admin_api: + resources = [extensions.ResourceExtension('os-hosts', + HostController(), collection_actions={'update': 'PUT'}, + member_actions={})] return resources -- cgit From a0ec6a6aa5ebdde1d099c5f6c03cf1dbd28441fa Mon Sep 17 00:00:00 2001 From: Ed Leafe Date: Wed, 3 Aug 2011 00:52:15 +0000 Subject: Removed duplicate methods created by previous merge. --- nova/compute/manager.py | 8 +------- nova/virt/driver.py | 4 ---- nova/virt/fake.py | 4 ---- nova/virt/hyperv.py | 4 ---- nova/virt/libvirt/connection.py | 4 ---- nova/virt/vmwareapi_conn.py | 4 ---- nova/virt/xenapi/vmops.py | 11 ----------- nova/virt/xenapi_conn.py | 4 ---- 8 files changed, 1 insertion(+), 42 deletions(-) (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 37b920074..3c89042c8 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -1,4 +1,4 @@ -#: tabstop=4 shiftwidth=4 softtabstop=4 +# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2010 United States Government as represented by the # Administrator of the National Aeronautics and Space Administration. @@ -939,12 +939,6 @@ class ComputeManager(manager.SchedulerDependentManager): """Sets the specified host's ability to accept new instances.""" return self.driver.set_host_enabled(host, enabled) - @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id()) - def set_power_state(self, context, instance_id=None, host=None, - power_state=None): - """Turns the specified host on/off, or reboots the host.""" - return self.driver.set_power_state(host, power_state) - @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id()) def get_diagnostics(self, context, instance_id): """Retrieve diagnostics for an instance on this host.""" diff --git a/nova/virt/driver.py b/nova/virt/driver.py index b32ed7c54..bbb17480d 100644 --- a/nova/virt/driver.py +++ b/nova/virt/driver.py @@ -259,10 +259,6 @@ class ComputeDriver(object): """Sets the specified host's ability to accept new instances.""" raise NotImplementedError() - def set_power_state(self, host, power_state): - """Reboots, shuts down or starts up the host.""" - raise NotImplementedError() - def plug_vifs(self, instance, network_info): """Plugs in VIFs to networks.""" raise NotImplementedError() diff --git a/nova/virt/fake.py b/nova/virt/fake.py index 135b88ab8..888dca220 100644 --- a/nova/virt/fake.py +++ b/nova/virt/fake.py @@ -518,7 +518,3 @@ class FakeConnection(driver.ComputeDriver): def set_host_enabled(self, host, enabled): """Sets the specified host's ability to accept new instances.""" pass - - def set_power_state(self, host, power_state): - """Reboots, shuts down or starts up the host.""" - pass diff --git a/nova/virt/hyperv.py b/nova/virt/hyperv.py index 553f85265..119def38b 100644 --- a/nova/virt/hyperv.py +++ b/nova/virt/hyperv.py @@ -505,7 +505,3 @@ class HyperVConnection(driver.ComputeDriver): def set_host_enabled(self, host, enabled): """Sets the specified host's ability to accept new instances.""" pass - - def set_power_state(self, host, power_state): - """Reboots, shuts down or starts up the host.""" - pass diff --git a/nova/virt/libvirt/connection.py b/nova/virt/libvirt/connection.py index 150478edf..46f3eef4b 100644 --- a/nova/virt/libvirt/connection.py +++ b/nova/virt/libvirt/connection.py @@ -1586,7 +1586,3 @@ class LibvirtConnection(driver.ComputeDriver): def set_host_enabled(self, host, enabled): """Sets the specified host's ability to accept new instances.""" pass - - def set_power_state(self, host, power_state): - """Reboots, shuts down or starts up the host.""" - pass diff --git a/nova/virt/vmwareapi_conn.py b/nova/virt/vmwareapi_conn.py index 6f68fd726..af547821f 100644 --- a/nova/virt/vmwareapi_conn.py +++ b/nova/virt/vmwareapi_conn.py @@ -198,10 +198,6 @@ class VMWareESXConnection(driver.ComputeDriver): """Sets the specified host's ability to accept new instances.""" pass - def set_power_state(self, host, power_state): - """Reboots, shuts down or starts up the host.""" - pass - def plug_vifs(self, instance, network_info): """Plugs in VIFs to networks.""" self._vmops.plug_vifs(instance, network_info) diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 194c33d54..a90ae0128 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -1042,17 +1042,6 @@ class VMOps(object): return xenapi_resp.details[-1] return resp["status"] - def set_power_state(self, host, power_state): - """Reboots, shuts down or starts up the host.""" - args = {"power_state": power_state} - xenapi_resp = self._call_xenhost("set_power_state", args) - try: - resp = json.loads(xenapi_resp) - except TypeError as e: - # Already logged; return the message - return xenapi_resp.details[-1] - return resp["power_state"] - def _call_xenhost(self, method, arg_dict): """There will be several methods that will need this general handling for interacting with the xenhost plugin, so this abstracts diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index 468e12696..5962b4ff8 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -339,10 +339,6 @@ class XenAPIConnection(driver.ComputeDriver): """Sets the specified host's ability to accept new instances.""" return self._vmops.set_host_enabled(host, enabled) - def set_power_state(self, host, power_state): - """Reboots, shuts down or starts up the host.""" - return self._vmops.set_power_state(host, power_state) - class XenAPISession(object): """The session to invoke XenAPI SDK calls""" -- cgit From 7b69ef4fe1e4aabcf44789455b96492b168ad6f5 Mon Sep 17 00:00:00 2001 From: Ed Leafe Date: Wed, 3 Aug 2011 01:32:08 +0000 Subject: Removed trailing whitespace that somehow made it into trunk. --- 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 a2d18d37e..333994fcc 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -92,7 +92,7 @@ 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 str(image_href).startswith(req.application_url): image_href = image_href.split('/').pop() try: -- cgit From 44964f41ed591c8ece72dbc36f5d668d2e279274 Mon Sep 17 00:00:00 2001 From: Justin Shepherd Date: Tue, 2 Aug 2011 22:22:34 -0500 Subject: renaming test_skip_unless_env_foo_exists() --- nova/tests/test_skip_examples.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'nova') diff --git a/nova/tests/test_skip_examples.py b/nova/tests/test_skip_examples.py index dfbaac74b..b7979d34b 100644 --- a/nova/tests/test_skip_examples.py +++ b/nova/tests/test_skip_examples.py @@ -31,7 +31,7 @@ class ExampleSkipTestCase(test.TestCase): def test_skip_if_env_user_exists(self): x = 1 - @test.skip_unless(os.getenv("BLAH"), - "Skipping -- Environment variable BLAH does not exist") - def test_skip_unless_env_blah_exists(self): + @test.skip_unless(os.getenv("FOO"), + "Skipping -- Environment variable FOO does not exist") + def test_skip_unless_env_foo_exists(self): x = 1 -- cgit From 836c394def93a8589bcb7387862390b24b8fdab8 Mon Sep 17 00:00:00 2001 From: Justin Shepherd Date: Tue, 2 Aug 2011 22:25:23 -0500 Subject: Resolved pep8 errors --- nova/api/openstack/create_instance_helper.py | 2 +- nova/test.py | 3 ++- 2 files changed, 3 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 a2d18d37e..333994fcc 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -92,7 +92,7 @@ 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 str(image_href).startswith(req.application_url): image_href = image_href.split('/').pop() try: diff --git a/nova/test.py b/nova/test.py index afc118100..f1b4050f9 100644 --- a/nova/test.py +++ b/nova/test.py @@ -67,6 +67,7 @@ class skip_test(object): _skipper.__doc__ = func.__doc__ return _skipper + class skip_if(object): """Decorator that skips a test if contition is true.""" def __init__(self, condition, msg): @@ -84,7 +85,7 @@ class skip_if(object): class skip_unless(object): - """Decorator that skips a test if condition is false.""" + """Decorator that skips a test if condition is not true.""" def __init__(self, condition, msg): self.condition = condition self.message = msg -- cgit From 2d2405e35d34c928c87d575bcfc7c74db9de6b1d Mon Sep 17 00:00:00 2001 From: Johannes Erdfelt Date: Wed, 3 Aug 2011 11:31:10 +0000 Subject: Use flavorid only at the API level and use instance_type_id internally --- nova/compute/api.py | 2 +- nova/compute/manager.py | 21 ++++--- .../versions/036_change_flavor_id_in_migrations.py | 71 ++++++++++++++++++++++ nova/db/sqlalchemy/models.py | 6 +- nova/tests/test_compute.py | 4 +- 5 files changed, 89 insertions(+), 15 deletions(-) create mode 100644 nova/db/sqlalchemy/migrate_repo/versions/036_change_flavor_id_in_migrations.py (limited to 'nova') diff --git a/nova/compute/api.py b/nova/compute/api.py index aae16d1da..f42df015a 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -955,7 +955,7 @@ class API(base.Base): {"method": "prep_resize", "args": {"topic": FLAGS.compute_topic, "instance_id": instance_ref['uuid'], - "flavor_id": new_instance_type['id']}}) + "instance_type_id": new_instance_type['id']}}) @scheduler_api.reroute_compute("add_fixed_ip") def add_fixed_ip(self, context, instance_id, network_id): diff --git a/nova/compute/manager.py b/nova/compute/manager.py index a2d84cd76..9b5fac58b 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -736,8 +736,8 @@ class ComputeManager(manager.SchedulerDependentManager): instance_ref = self.db.instance_get_by_uuid(context, migration_ref.instance_uuid) - instance_type = self.db.instance_type_get_by_flavor_id(context, - migration_ref['old_flavor_id']) + instance_type = self.db.instance_type_get(context, + migration_ref['old_instance_type_id']) # Just roll back the record. There's no need to resize down since # the 'old' VM already has the preferred attributes @@ -758,7 +758,7 @@ class ComputeManager(manager.SchedulerDependentManager): @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id()) @checks_instance_lock - def prep_resize(self, context, instance_id, flavor_id): + def prep_resize(self, context, instance_id, instance_type_id): """Initiates the process of moving a running instance to another host. Possibly changes the RAM and disk size in the process. @@ -777,16 +777,16 @@ class ComputeManager(manager.SchedulerDependentManager): old_instance_type = self.db.instance_type_get(context, instance_ref['instance_type_id']) - new_instance_type = self.db.instance_type_get_by_flavor_id(context, - flavor_id) + new_instance_type = self.db.instance_type_get(context, + instance_type_id) migration_ref = self.db.migration_create(context, {'instance_uuid': instance_ref['uuid'], 'source_compute': instance_ref['host'], 'dest_compute': FLAGS.host, 'dest_host': self.driver.get_host_ip_addr(), - 'old_flavor_id': old_instance_type['flavorid'], - 'new_flavor_id': flavor_id, + 'old_instance_type_id': old_instance_type['id'], + 'new_instance_type_id': instance_type_id, 'status': 'pre-migrating'}) LOG.audit(_('instance %s: migrating'), instance_ref['uuid'], @@ -849,9 +849,10 @@ class ComputeManager(manager.SchedulerDependentManager): resize_instance = False instance_ref = self.db.instance_get_by_uuid(context, migration_ref.instance_uuid) - if migration_ref['old_flavor_id'] != migration_ref['new_flavor_id']: - instance_type = self.db.instance_type_get_by_flavor_id(context, - migration_ref['new_flavor_id']) + if migration_ref['old_instance_type_id'] != \ + migration_ref['new_instance_type_id']: + instance_type = self.db.instance_type_get(context, + migration_ref['new_instance_type_id']) self.db.instance_update(context, instance_ref.uuid, dict(instance_type_id=instance_type['id'], memory_mb=instance_type['memory_mb'], diff --git a/nova/db/sqlalchemy/migrate_repo/versions/036_change_flavor_id_in_migrations.py b/nova/db/sqlalchemy/migrate_repo/versions/036_change_flavor_id_in_migrations.py new file mode 100644 index 000000000..7ff709c17 --- /dev/null +++ b/nova/db/sqlalchemy/migrate_repo/versions/036_change_flavor_id_in_migrations.py @@ -0,0 +1,71 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 OpenStack LLC. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License.from sqlalchemy import * + +from sqlalchemy import Column, Integer, MetaData, Table + + +meta = MetaData() + + +# +# Tables to alter +# +# + +old_flavor_id = Column('old_flavor_id', Integer()) +new_flavor_id = Column('new_flavor_id', Integer()) +old_instance_type_id = Column('old_instance_type_id', Integer()) +new_instance_type_id = Column('new_instance_type_id', Integer()) + + +def upgrade(migrate_engine): + meta.bind = migrate_engine + instance_types = Table('instance_types', meta, autoload=True) + migrations = Table('migrations', meta, autoload=True) + migrations.create_column(old_instance_type_id) + migrations.create_column(new_instance_type_id) + + # Convert flavor_id to instance_type_id + for instance_type in migrate_engine.execute(instance_types.select()): + migrate_engine.execute(migrations.update()\ + .where(migrations.c.old_flavor_id == instance_type.flavorid)\ + .values(old_instance_type_id=instance_type.id)) + migrate_engine.execute(migrations.update()\ + .where(migrations.c.new_flavor_id == instance_type.flavorid)\ + .values(new_instance_type_id=instance_type.id)) + + migrations.c.old_flavor_id.drop() + migrations.c.new_flavor_id.drop() + + +def downgrade(migrate_engine): + meta.bind = migrate_engine + migrations = Table('migrations', meta, autoload=True) + migrations.create_column(old_flavor_id) + migrations.create_column(new_flavor_id) + + # Convert instance_type_id to flavor_id + for instance_type in migrate_engine.execute(instance_types.select()): + migrate_engine.execute(migrations.update()\ + .where(migrations.c.old_instance_type_id == instance_type.id)\ + .values(old_flavor_id=instance_type.flavorid)) + migrate_engine.execute(migrations.update()\ + .where(migrations.c.new_instance_type_id == instance_type.id)\ + .values(new_flavor_id=instance_type.flavorid)) + + migrations.c.old_instance_type_id.drop() + migrations.c.new_instance_type_id.drop() diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index 056259539..9f4c7a0aa 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -202,7 +202,7 @@ class Instance(BASE, NovaBase): hostname = Column(String(255)) host = Column(String(255)) # , ForeignKey('hosts.id')) - # aka flavor_id + # *not* flavor_id instance_type_id = Column(Integer) user_data = Column(Text) @@ -511,8 +511,8 @@ class Migration(BASE, NovaBase): source_compute = Column(String(255)) dest_compute = Column(String(255)) dest_host = Column(String(255)) - old_flavor_id = Column(Integer()) - new_flavor_id = Column(Integer()) + old_instance_type_id = Column(Integer()) + new_instance_type_id = Column(Integer()) instance_uuid = Column(String(255), ForeignKey('instances.uuid'), nullable=True) #TODO(_cerberus_): enum diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py index 879e4b9cb..bbf9ddcc6 100644 --- a/nova/tests/test_compute.py +++ b/nova/tests/test_compute.py @@ -535,7 +535,9 @@ class ComputeTestCase(test.TestCase): db.instance_update(self.context, instance_id, {'host': 'foo'}) - self.compute.prep_resize(context, inst_ref['uuid'], 3) + new_instance_type_ref = db.instance_type_get_by_flavor_id(context, 3) + self.compute.prep_resize(context, inst_ref['uuid'], + new_instance_type_ref['id']) migration_ref = db.migration_get_by_instance_and_status(context, inst_ref['uuid'], 'pre-migrating') -- cgit From 8c77b6afa20c443916dd71572f22b52a5ecc88e9 Mon Sep 17 00:00:00 2001 From: Jason Koelker Date: Wed, 3 Aug 2011 08:54:00 -0500 Subject: fumigate non-pep8 code --- nova/api/openstack/create_instance_helper.py | 2 +- nova/api/openstack/versions.py | 16 +++--- nova/api/openstack/views/versions.py | 6 +-- nova/tests/api/openstack/test_versions.py | 80 ++++++++++++++-------------- 4 files changed, 52 insertions(+), 52 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index a2d18d37e..333994fcc 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -92,7 +92,7 @@ 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 str(image_href).startswith(req.application_url): image_href = image_href.split('/').pop() try: diff --git a/nova/api/openstack/versions.py b/nova/api/openstack/versions.py index 3ef72b7f6..e2f892fb6 100644 --- a/nova/api/openstack/versions.py +++ b/nova/api/openstack/versions.py @@ -34,23 +34,23 @@ 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": [ { "base": "application/xml", - "type": "application/vnd.openstack.compute-v1.0+xml" + "type": "application/vnd.openstack.compute-v1.0+xml", }, { "base": "application/json", - "type": "application/vnd.openstack.compute-v1.0+json" + "type": "application/vnd.openstack.compute-v1.0+json", } ], }, @@ -63,23 +63,23 @@ 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": [ { "base": "application/xml", - "type": "application/vnd.openstack.compute-v1.1+xml" + "type": "application/vnd.openstack.compute-v1.1+xml", }, { "base": "application/json", - "type": "application/vnd.openstack.compute-v1.1+json" + "type": "application/vnd.openstack.compute-v1.1+json", } ], }, diff --git a/nova/api/openstack/views/versions.py b/nova/api/openstack/views/versions.py index 547289034..03da80818 100644 --- a/nova/api/openstack/views/versions.py +++ b/nova/api/openstack/views/versions.py @@ -42,10 +42,10 @@ class ViewBuilder(object): "links": [ { "rel": "self", - "href": self.generate_href(version['id'], req.path) - } + "href": self.generate_href(version['id'], req.path), + }, ], - "media-types": version['media-types'] + "media-types": version['media-types'], }) return dict(choices=version_objs) diff --git a/nova/tests/api/openstack/test_versions.py b/nova/tests/api/openstack/test_versions.py index e68455778..7482368f9 100644 --- a/nova/tests/api/openstack/test_versions.py +++ b/nova/tests/api/openstack/test_versions.py @@ -38,24 +38,24 @@ 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": [ { "base": "application/xml", - "type": "application/vnd.openstack.compute-v1.0+xml" + "type": "application/vnd.openstack.compute-v1.0+xml", }, { "base": "application/json", - "type": "application/vnd.openstack.compute-v1.0+json" - } + "type": "application/vnd.openstack.compute-v1.0+json", + }, ], }, "v1.1": { @@ -67,24 +67,24 @@ 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": [ { "base": "application/xml", - "type": "application/vnd.openstack.compute-v1.1+xml" + "type": "application/vnd.openstack.compute-v1.1+xml", }, { "base": "application/json", - "type": "application/vnd.openstack.compute-v1.1+json" - } + "type": "application/vnd.openstack.compute-v1.1+json", + }, ], }, } @@ -150,34 +150,34 @@ class VersionsTest(test.TestCase): "links": [ { "rel": "self", - "href": "http://localhost/v1.0/" + "href": "http://localhost/v1.0/", }, { "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", + }, + ], + }, } self.assertEqual(expected, version) @@ -196,34 +196,34 @@ class VersionsTest(test.TestCase): "links": [ { "rel": "self", - "href": "http://localhost/v1.1/" + "href": "http://localhost/v1.1/", }, { "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" + "type": "application/", + "vnd.openstack.compute-v1.1+xml", }, { "base": "application/json", "type": "application/" - "vnd.openstack.compute-v1.1+json" - } - ] - } + "vnd.openstack.compute-v1.1+json", + }, + ], + }, } self.assertEqual(expected, version) @@ -739,30 +739,30 @@ class VersionsSerializerTests(test.TestCase): "links": [ { "rel": "self", - "href": "http://localhost/v1.0/" + "href": "http://localhost/v1.0/", }, { "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" + "type": "application/vnd.openstack.compute-v1.0+xml", }, { "base": "application/json", - "type": "application/vnd.openstack.compute-v1.0+json" - } + "type": "application/vnd.openstack.compute-v1.0+json", + }, ], }, } @@ -874,29 +874,29 @@ class VersionsSerializerTests(test.TestCase): "links": [ { "rel": "self", - "href": "http://localhost/v1.1/" + "href": "http://localhost/v1.1/", }, { "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" + "type": "application/vnd.openstack.compute-v1.1+xml", }, { "base": "application/json", - "type": "application/vnd.openstack.compute-v1.1+json" + "type": "application/vnd.openstack.compute-v1.1+json", } ], }, -- cgit From 736a18ad49b535ce9d935bbe8c3d8e67f73a4d15 Mon Sep 17 00:00:00 2001 From: Jason Koelker Date: Wed, 3 Aug 2011 08:57:27 -0500 Subject: whoops, got a little comma crazy --- nova/tests/api/openstack/test_versions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/tests/api/openstack/test_versions.py b/nova/tests/api/openstack/test_versions.py index 7482368f9..1269f13c9 100644 --- a/nova/tests/api/openstack/test_versions.py +++ b/nova/tests/api/openstack/test_versions.py @@ -214,7 +214,7 @@ class VersionsTest(test.TestCase): "media-types": [ { "base": "application/xml", - "type": "application/", + "type": "application/" "vnd.openstack.compute-v1.1+xml", }, { -- cgit From cb3bc75b316281866eaf32040dccc3fc3be257c2 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Wed, 3 Aug 2011 10:03:15 -0400 Subject: PEP8 issue --- 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 a2d18d37e..333994fcc 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -92,7 +92,7 @@ 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 str(image_href).startswith(req.application_url): image_href = image_href.split('/').pop() try: -- cgit From d543b9914506b39defbf94ebc63eb95f0d58a8cd Mon Sep 17 00:00:00 2001 From: Justin Shepherd Date: Wed, 3 Aug 2011 09:32:24 -0500 Subject: Removed dependancy on os.getenv. Test cases now raise Exception if they are not properly skipped. --- nova/tests/test_skip_examples.py | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) (limited to 'nova') diff --git a/nova/tests/test_skip_examples.py b/nova/tests/test_skip_examples.py index b7979d34b..e41cd848d 100644 --- a/nova/tests/test_skip_examples.py +++ b/nova/tests/test_skip_examples.py @@ -16,22 +16,18 @@ # License for the specific language governing permissions and limitations # under the License. -import os - from nova import test class ExampleSkipTestCase(test.TestCase): - @test.skip_test("testing skipping") + @test.skip_test("Example usage of @test.skip_test()") def test_skip_test(self): - x = 1 + raise Exception("skip_test failed to work properly.") - @test.skip_if(os.getenv("USER"), - "Skiping -- Environment variable USER exists") + @test.skip_if(True, "Example usage of @test.skip_if()") def test_skip_if_env_user_exists(self): - x = 1 + raise Exception("skip_if failed to work properly.") - @test.skip_unless(os.getenv("FOO"), - "Skipping -- Environment variable FOO does not exist") + @test.skip_unless(False, "Example usage of @test.skip_unless()") def test_skip_unless_env_foo_exists(self): - x = 1 + raise Exception("skip_unless failed to work properly.") -- cgit From c237b762381856b1041be06debf0deed4ae4e2a8 Mon Sep 17 00:00:00 2001 From: Justin Shepherd Date: Wed, 3 Aug 2011 10:44:09 -0500 Subject: replaced raise Exception with self.fail() --- nova/tests/test_skip_examples.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'nova') diff --git a/nova/tests/test_skip_examples.py b/nova/tests/test_skip_examples.py index e41cd848d..41c3b1b5f 100644 --- a/nova/tests/test_skip_examples.py +++ b/nova/tests/test_skip_examples.py @@ -22,12 +22,12 @@ from nova import test class ExampleSkipTestCase(test.TestCase): @test.skip_test("Example usage of @test.skip_test()") def test_skip_test(self): - raise Exception("skip_test failed to work properly.") + self.fail("skip_test failed to work properly.") @test.skip_if(True, "Example usage of @test.skip_if()") def test_skip_if_env_user_exists(self): - raise Exception("skip_if failed to work properly.") + self.fail("skip_if failed to work properly.") @test.skip_unless(False, "Example usage of @test.skip_unless()") def test_skip_unless_env_foo_exists(self): - raise Exception("skip_unless failed to work properly.") + self.fail("skip_unless failed to work properly.") -- cgit From 6166415d03db8f7af93778bdf7a735fb81e63fc5 Mon Sep 17 00:00:00 2001 From: Rick Harris Date: Wed, 3 Aug 2011 15:50:29 +0000 Subject: Default dns to '' if not present --- nova/virt/xenapi/vm_utils.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'nova') diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index 60ef0df43..e08acbedc 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -1092,6 +1092,8 @@ def _prepare_injectables(inst, networks_info): ip_v6 = info['ip6s'][0] if len(info['dns']) > 0: dns = info['dns'][0] + else: + dns = '' interface_info = {'name': 'eth%d' % ifc_num, 'address': ip_v4 and ip_v4['ip'] or '', 'netmask': ip_v4 and ip_v4['netmask'] or '', -- cgit From 5027838724a5adfd0e15c4a10be723f03b6bbbae Mon Sep 17 00:00:00 2001 From: "matt.dietz@rackspace.com" <> Date: Wed, 3 Aug 2011 11:48:50 -0500 Subject: Removes extraneous body argument from server controller methods --- nova/api/openstack/create_instance_helper.py | 2 +- nova/api/openstack/servers.py | 12 ++++++------ nova/tests/api/openstack/test_servers.py | 24 ------------------------ 3 files changed, 7 insertions(+), 31 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index a2d18d37e..333994fcc 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -92,7 +92,7 @@ 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 str(image_href).startswith(req.application_url): image_href = image_href.split('/').pop() try: diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 002b47edb..d17714371 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -328,7 +328,7 @@ class Controller(object): return webob.Response(status_int=202) @scheduler_api.redirect_handler - def reset_network(self, req, id, body): + def reset_network(self, req, id): """ Reset networking on an instance (admin only). @@ -343,7 +343,7 @@ class Controller(object): return webob.Response(status_int=202) @scheduler_api.redirect_handler - def inject_network_info(self, req, id, body): + def inject_network_info(self, req, id): """ Inject network info for an instance (admin only). @@ -358,7 +358,7 @@ class Controller(object): return webob.Response(status_int=202) @scheduler_api.redirect_handler - def pause(self, req, id, body): + def pause(self, req, id): """ Permit Admins to Pause the server. """ ctxt = req.environ['nova.context'] try: @@ -370,7 +370,7 @@ class Controller(object): return webob.Response(status_int=202) @scheduler_api.redirect_handler - def unpause(self, req, id, body): + def unpause(self, req, id): """ Permit Admins to Unpause the server. """ ctxt = req.environ['nova.context'] try: @@ -382,7 +382,7 @@ class Controller(object): return webob.Response(status_int=202) @scheduler_api.redirect_handler - def suspend(self, req, id, body): + def suspend(self, req, id): """permit admins to suspend the server""" context = req.environ['nova.context'] try: @@ -394,7 +394,7 @@ class Controller(object): return webob.Response(status_int=202) @scheduler_api.redirect_handler - def resume(self, req, id, body): + def resume(self, req, id): """permit admins to resume the server from suspend""" context = req.environ['nova.context'] try: diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 4d42972c1..7062b0982 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -1756,74 +1756,50 @@ class ServersTest(test.TestCase): def test_server_pause(self): self.flags(allow_admin_api=True) - body = dict(server=dict( - name='server_test', imageId=2, flavorId=2, metadata={}, - personality={})) req = webob.Request.blank('/v1.0/servers/1/pause') req.method = 'POST' req.content_type = 'application/json' - req.body = json.dumps(body) res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 202) def test_server_unpause(self): self.flags(allow_admin_api=True) - body = dict(server=dict( - name='server_test', imageId=2, flavorId=2, metadata={}, - personality={})) req = webob.Request.blank('/v1.0/servers/1/unpause') req.method = 'POST' req.content_type = 'application/json' - req.body = json.dumps(body) res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 202) def test_server_suspend(self): self.flags(allow_admin_api=True) - body = dict(server=dict( - name='server_test', imageId=2, flavorId=2, metadata={}, - personality={})) req = webob.Request.blank('/v1.0/servers/1/suspend') req.method = 'POST' req.content_type = 'application/json' - req.body = json.dumps(body) res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 202) def test_server_resume(self): self.flags(allow_admin_api=True) - body = dict(server=dict( - name='server_test', imageId=2, flavorId=2, metadata={}, - personality={})) req = webob.Request.blank('/v1.0/servers/1/resume') req.method = 'POST' req.content_type = 'application/json' - req.body = json.dumps(body) res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 202) def test_server_reset_network(self): self.flags(allow_admin_api=True) - body = dict(server=dict( - name='server_test', imageId=2, flavorId=2, metadata={}, - personality={})) req = webob.Request.blank('/v1.0/servers/1/reset_network') req.method = 'POST' req.content_type = 'application/json' - req.body = json.dumps(body) res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 202) def test_server_inject_network_info(self): self.flags(allow_admin_api=True) - body = dict(server=dict( - name='server_test', imageId=2, flavorId=2, metadata={}, - personality={})) req = webob.Request.blank( '/v1.0/servers/1/inject_network_info') req.method = 'POST' req.content_type = 'application/json' - req.body = json.dumps(body) res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 202) -- cgit From 4d500af8bdb4eda2d6040b54c217905cd7ed8bff Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Wed, 3 Aug 2011 09:56:37 -0700 Subject: fix use of FLAGS in openstack API servers tests to use the new way --- nova/tests/api/openstack/test_servers.py | 35 +++++++++++++------------------- 1 file changed, 14 insertions(+), 21 deletions(-) (limited to 'nova') diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 4d42972c1..e927e6612 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -26,7 +26,6 @@ import webob from nova import context from nova import db from nova import exception -from nova import flags from nova import test from nova import utils import nova.api.openstack @@ -46,10 +45,6 @@ from nova.tests.api.openstack import common from nova.tests.api.openstack import fakes -FLAGS = flags.FLAGS -FLAGS.verbose = True - - FAKE_UUID = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa' @@ -269,8 +264,6 @@ class ServersTest(test.TestCase): 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') def test_get_server_by_id(self): @@ -767,7 +760,7 @@ class ServersTest(test.TestCase): self.assertEquals(ip.getAttribute('addr'), private) def test_get_server_by_id_with_addresses_v1_1(self): - FLAGS.use_ipv6 = True + self.flags(use_ipv6=True) interfaces = [ { 'network': {'label': 'network_1'}, @@ -811,7 +804,7 @@ class ServersTest(test.TestCase): self.assertEqual(addresses, expected) def test_get_server_by_id_with_addresses_v1_1_ipv6_disabled(self): - FLAGS.use_ipv6 = False + self.flags(use_ipv6=False) interfaces = [ { 'network': {'label': 'network_1'}, @@ -854,7 +847,7 @@ class ServersTest(test.TestCase): self.assertEqual(addresses, expected) def test_get_server_addresses_v1_1(self): - FLAGS.use_ipv6 = True + self.flags(use_ipv6=True) interfaces = [ { 'network': {'label': 'network_1'}, @@ -905,7 +898,7 @@ class ServersTest(test.TestCase): self.assertEqual(res_dict, expected) def test_get_server_addresses_single_network_v1_1(self): - FLAGS.use_ipv6 = True + self.flags(use_ipv6=True) interfaces = [ { 'network': {'label': 'network_1'}, @@ -2329,7 +2322,7 @@ class ServersTest(test.TestCase): """ req = self.webreq('/1/migrate', 'POST') - FLAGS.allow_admin_api = True + self.flags(allow_admin_api=True) self.resize_called = False def resize_mock(*args): @@ -2344,7 +2337,7 @@ class ServersTest(test.TestCase): def test_migrate_server_no_admin_api_fails(self): req = self.webreq('/1/migrate', 'POST') - FLAGS.allow_admin_api = False + self.flags(allow_admin_api=False) res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 404) @@ -2425,7 +2418,7 @@ class ServersTest(test.TestCase): def test_create_backup(self): """The happy path for creating backups""" - FLAGS.allow_admin_api = True + self.flags(allow_admin_api=True) body = { 'createBackup': { @@ -2445,7 +2438,7 @@ class ServersTest(test.TestCase): def test_create_backup_v1_1(self): """The happy path for creating backups through v1.1 api""" - FLAGS.allow_admin_api = True + self.flags(allow_admin_api=True) body = { 'createBackup': { @@ -2465,7 +2458,7 @@ class ServersTest(test.TestCase): def test_create_backup_admin_api_off(self): """The happy path for creating backups""" - FLAGS.allow_admin_api = False + self.flags(allow_admin_api=False) body = { 'createBackup': { @@ -2483,7 +2476,7 @@ class ServersTest(test.TestCase): self.assertEqual(501, response.status_int) def test_create_backup_with_metadata(self): - FLAGS.allow_admin_api = True + self.flags(allow_admin_api=True) body = { 'createBackup': { @@ -2504,7 +2497,7 @@ class ServersTest(test.TestCase): def test_create_backup_no_name(self): """Name is required for backups""" - FLAGS.allow_admin_api = True + self.flags(allow_admin_api=True) body = { 'createBackup': { @@ -2522,7 +2515,7 @@ class ServersTest(test.TestCase): def test_create_backup_no_rotation(self): """Rotation is required for backup requests""" - FLAGS.allow_admin_api = True + self.flags(allow_admin_api=True) body = { 'createBackup': { @@ -2541,7 +2534,7 @@ class ServersTest(test.TestCase): def test_create_backup_no_backup_type(self): """Backup Type (daily or weekly) is required for backup requests""" - FLAGS.allow_admin_api = True + self.flags(allow_admin_api=True) body = { 'createBackup': { @@ -2558,7 +2551,7 @@ class ServersTest(test.TestCase): self.assertEqual(400, response.status_int) def test_create_backup_bad_entity(self): - FLAGS.allow_admin_api = True + self.flags(allow_admin_api=True) body = {'createBackup': 'go'} req = webob.Request.blank('/v1.0/images') -- cgit From d7f704876f5cfab165c855170468274e57935d15 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Wed, 3 Aug 2011 10:11:20 -0700 Subject: more cleanup of API tests regarding FLAGS --- nova/test.py | 6 ++++++ nova/tests/api/openstack/test_accounts.py | 6 +----- nova/tests/api/openstack/test_adminapi.py | 5 +---- nova/tests/api/openstack/test_extensions.py | 2 -- nova/tests/api/openstack/test_flavors_extra_specs.py | 3 --- nova/tests/api/openstack/test_images.py | 8 +------- nova/tests/api/openstack/test_servers.py | 1 + nova/tests/api/openstack/test_users.py | 7 +------ nova/tests/api/openstack/test_zones.py | 2 +- 9 files changed, 12 insertions(+), 28 deletions(-) (limited to 'nova') diff --git a/nova/test.py b/nova/test.py index 549aa6fcf..804dc8c37 100644 --- a/nova/test.py +++ b/nova/test.py @@ -100,6 +100,7 @@ class TestCase(unittest.TestCase): self.injected = [] self._services = [] self._original_flags = FLAGS.FlagValuesDict() + self._original_flags_verbosity = FLAGS.verbose def tearDown(self): """Runs after each test method to tear down test environment.""" @@ -157,6 +158,11 @@ class TestCase(unittest.TestCase): FLAGS.Reset() for k, v in self._original_flags.iteritems(): setattr(FLAGS, k, v) + FLAGS.verbose = self._original_flags_verbosity + + def set_flags_verbosity(self, verbose=True): + """Set the FLAGS verbosity for a test""" + FLAGS.verbose = verbose def start_service(self, name, host=None, **kwargs): host = host and host or uuid.uuid4().hex diff --git a/nova/tests/api/openstack/test_accounts.py b/nova/tests/api/openstack/test_accounts.py index 89dbf5213..b1ca546d2 100644 --- a/nova/tests/api/openstack/test_accounts.py +++ b/nova/tests/api/openstack/test_accounts.py @@ -18,17 +18,12 @@ import json import webob -from nova import flags from nova import test from nova.api.openstack import accounts from nova.auth.manager import User from nova.tests.api.openstack import fakes -FLAGS = flags.FLAGS -FLAGS.verbose = True - - def fake_init(self): self.manager = fakes.FakeAuthManager() @@ -41,6 +36,7 @@ class AccountsTest(test.TestCase): def setUp(self): super(AccountsTest, self).setUp() self.flags(allow_admin_api=True) + self.set_flags_verbosity(True) self.stubs.Set(accounts.Controller, '__init__', fake_init) self.stubs.Set(accounts.Controller, '_check_admin', diff --git a/nova/tests/api/openstack/test_adminapi.py b/nova/tests/api/openstack/test_adminapi.py index b83de40cf..9d03a7f9d 100644 --- a/nova/tests/api/openstack/test_adminapi.py +++ b/nova/tests/api/openstack/test_adminapi.py @@ -18,12 +18,9 @@ import webob -from nova import flags from nova import test from nova.tests.api.openstack import fakes -FLAGS = flags.FLAGS - class AdminAPITest(test.TestCase): @@ -31,7 +28,7 @@ class AdminAPITest(test.TestCase): super(AdminAPITest, self).setUp() fakes.stub_out_networking(self.stubs) fakes.stub_out_rate_limiting(self.stubs) - self.allow_admin = FLAGS.allow_admin_api + self.set_flags_verbosity(True) def test_admin_enabled(self): self.flags(allow_admin_api=True) diff --git a/nova/tests/api/openstack/test_extensions.py b/nova/tests/api/openstack/test_extensions.py index 47c37225c..409fa0e71 100644 --- a/nova/tests/api/openstack/test_extensions.py +++ b/nova/tests/api/openstack/test_extensions.py @@ -21,7 +21,6 @@ import webob from xml.etree import ElementTree from nova import context -from nova import flags from nova import test from nova.api import openstack from nova.api.openstack import extensions @@ -29,7 +28,6 @@ from nova.api.openstack import flavors from nova.api.openstack import wsgi from nova.tests.api.openstack import fakes -FLAGS = flags.FLAGS NS = "{http://docs.openstack.org/compute/api/v1.1}" ATOMNS = "{http://www.w3.org/2005/Atom}" response_body = "Try to say this Mr. Knox, sir..." diff --git a/nova/tests/api/openstack/test_flavors_extra_specs.py b/nova/tests/api/openstack/test_flavors_extra_specs.py index d386958db..ccd1b0d9f 100644 --- a/nova/tests/api/openstack/test_flavors_extra_specs.py +++ b/nova/tests/api/openstack/test_flavors_extra_specs.py @@ -21,15 +21,12 @@ import webob import os.path -from nova import flags from nova import test from nova.api import openstack from nova.api.openstack import extensions from nova.tests.api.openstack import fakes import nova.wsgi -FLAGS = flags.FLAGS - def return_create_flavor_extra_specs(context, flavor_id, extra_specs): return stub_flavor_extra_specs() diff --git a/nova/tests/api/openstack/test_images.py b/nova/tests/api/openstack/test_images.py index 942c0b333..38cfc287c 100644 --- a/nova/tests/api/openstack/test_images.py +++ b/nova/tests/api/openstack/test_images.py @@ -34,7 +34,6 @@ import webob from glance import client as glance_client from nova import context from nova import exception -from nova import flags from nova import test from nova import utils import nova.api.openstack @@ -42,9 +41,6 @@ from nova.api.openstack import images from nova.tests.api.openstack import fakes -FLAGS = flags.FLAGS - - class _BaseImageServiceTests(test.TestCase): """Tasks to test for all image services""" @@ -328,8 +324,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): def setUp(self): """Run before each test.""" super(ImageControllerWithGlanceServiceTest, self).setUp() - self.orig_image_service = FLAGS.image_service - FLAGS.image_service = 'nova.image.glance.GlanceImageService' + self.flags(image_service='nova.image.glance.GlanceImageService') self.stubs = stubout.StubOutForTesting() fakes.stub_out_networking(self.stubs) fakes.stub_out_rate_limiting(self.stubs) @@ -342,7 +337,6 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): def tearDown(self): """Run after each test.""" self.stubs.UnsetAll() - FLAGS.image_service = self.orig_image_service super(ImageControllerWithGlanceServiceTest, self).tearDown() def _applicable_fixture(self, fixture, user_id): diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index e927e6612..796c494d4 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -230,6 +230,7 @@ class ServersTest(test.TestCase): def setUp(self): self.maxDiff = None super(ServersTest, self).setUp() + self.set_flags_verbosity(True) fakes.stub_out_networking(self.stubs) fakes.stub_out_rate_limiting(self.stubs) fakes.stub_out_key_pair_funcs(self.stubs) diff --git a/nova/tests/api/openstack/test_users.py b/nova/tests/api/openstack/test_users.py index 705c02f6b..649538f11 100644 --- a/nova/tests/api/openstack/test_users.py +++ b/nova/tests/api/openstack/test_users.py @@ -17,7 +17,6 @@ import json import webob -from nova import flags from nova import test from nova import utils from nova.api.openstack import users @@ -25,10 +24,6 @@ from nova.auth.manager import User, Project from nova.tests.api.openstack import fakes -FLAGS = flags.FLAGS -FLAGS.verbose = True - - def fake_init(self): self.manager = fakes.FakeAuthManager() @@ -41,6 +36,7 @@ class UsersTest(test.TestCase): def setUp(self): super(UsersTest, self).setUp() self.flags(allow_admin_api=True) + self.set_flags_verbosity(True) self.stubs.Set(users.Controller, '__init__', fake_init) self.stubs.Set(users.Controller, '_check_admin', @@ -56,7 +52,6 @@ class UsersTest(test.TestCase): fakes.stub_out_rate_limiting(self.stubs) fakes.stub_out_auth(self.stubs) - self.allow_admin = FLAGS.allow_admin_api fakemgr = fakes.FakeAuthManager() fakemgr.add_user(User('id1', 'guy1', 'acc1', 'secret1', False)) fakemgr.add_user(User('id2', 'guy2', 'acc2', 'secret2', True)) diff --git a/nova/tests/api/openstack/test_zones.py b/nova/tests/api/openstack/test_zones.py index 3deb844aa..442ee4313 100644 --- a/nova/tests/api/openstack/test_zones.py +++ b/nova/tests/api/openstack/test_zones.py @@ -29,7 +29,6 @@ from nova.scheduler import api FLAGS = flags.FLAGS -FLAGS.verbose = True def zone_get(context, zone_id): @@ -96,6 +95,7 @@ class ZonesTest(test.TestCase): def setUp(self): super(ZonesTest, self).setUp() self.flags(allow_admin_api=True) + self.set_flags_verbosity(True) fakes.stub_out_networking(self.stubs) fakes.stub_out_rate_limiting(self.stubs) -- cgit From 8efe41aaa4993d0aa9ad381d202ba7b7d025939e Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Wed, 3 Aug 2011 12:22:58 -0700 Subject: switch FLAGS.* = in tests to self.flags(...) remove unused cases of FLAGS from tests modified test.TestCase's flags() to allow multiple overrides added missing license to test_rpc_amqp.py --- nova/test.py | 8 +++----- nova/tests/hyperv_unittest.py | 5 +---- nova/tests/test_auth.py | 4 ++-- nova/tests/test_host_filter.py | 12 +++--------- nova/tests/test_ipv6.py | 3 --- nova/tests/test_libvirt.py | 23 +++++++++-------------- nova/tests/test_network.py | 2 -- nova/tests/test_quota.py | 42 +++++++++++++++++------------------------- nova/tests/test_rpc.py | 2 -- nova/tests/test_rpc_amqp.py | 24 ++++++++++++++++++++++-- nova/tests/test_service.py | 1 - nova/tests/test_xenapi.py | 12 ++++++------ 12 files changed, 63 insertions(+), 75 deletions(-) (limited to 'nova') diff --git a/nova/test.py b/nova/test.py index 804dc8c37..fa5a662fd 100644 --- a/nova/test.py +++ b/nova/test.py @@ -142,11 +142,9 @@ class TestCase(unittest.TestCase): def flags(self, **kw): """Override flag variables for a test.""" for k, v in kw.iteritems(): - if k in self.flag_overrides: - self.reset_flags() - raise Exception( - 'trying to override already overriden flag: %s' % k) - self.flag_overrides[k] = getattr(FLAGS, k) + # Store original flag value if it's not been overriden yet + if k not in self.flag_overrides: + self.flag_overrides[k] = getattr(FLAGS, k) setattr(FLAGS, k, v) def reset_flags(self): diff --git a/nova/tests/hyperv_unittest.py b/nova/tests/hyperv_unittest.py index 0ea196950..d346d0a70 100644 --- a/nova/tests/hyperv_unittest.py +++ b/nova/tests/hyperv_unittest.py @@ -21,13 +21,9 @@ import random from nova import context from nova import db -from nova import flags from nova import test from nova.virt import hyperv -FLAGS = flags.FLAGS -FLAGS.connection_type = 'hyperv' - class HyperVTestCase(test.TestCase): """Test cases for the Hyper-V driver""" @@ -36,6 +32,7 @@ class HyperVTestCase(test.TestCase): self.user_id = 'fake' self.project_id = 'fake' self.context = context.RequestContext(self.user_id, self.project_id) + self.flags(connection_type='hyperv') def test_create_destroy(self): """Create a VM and destroy it""" diff --git a/nova/tests/test_auth.py b/nova/tests/test_auth.py index 7c0f783bb..2e24b7d6e 100644 --- a/nova/tests/test_auth.py +++ b/nova/tests/test_auth.py @@ -83,9 +83,9 @@ class user_and_project_generator(object): class _AuthManagerBaseTestCase(test.TestCase): def setUp(self): - FLAGS.auth_driver = self.auth_driver super(_AuthManagerBaseTestCase, self).setUp() - self.flags(connection_type='fake') + self.flags(auth_driver=self.auth_driver, + connection_type='fake') self.manager = manager.AuthManager(new=True) self.manager.mc.cache = {} diff --git a/nova/tests/test_host_filter.py b/nova/tests/test_host_filter.py index 438f3e522..3a1389a49 100644 --- a/nova/tests/test_host_filter.py +++ b/nova/tests/test_host_filter.py @@ -19,12 +19,9 @@ Tests For Scheduler Host Filters. import json from nova import exception -from nova import flags from nova import test from nova.scheduler import host_filter -FLAGS = flags.FLAGS - class FakeZoneManager: pass @@ -57,9 +54,9 @@ class HostFilterTestCase(test.TestCase): 'host_name-label': 'xs-%s' % multiplier} def setUp(self): - self.old_flag = FLAGS.default_host_filter - FLAGS.default_host_filter = \ - 'nova.scheduler.host_filter.AllHostsFilter' + super(HostFilterTestCase, self).setUp() + default_host_filter = 'nova.scheduler.host_filter.AllHostsFilter' + self.flags(default_host_filter=default_host_filter) self.instance_type = dict(name='tiny', memory_mb=50, vcpus=10, @@ -76,9 +73,6 @@ class HostFilterTestCase(test.TestCase): states['host%02d' % (x + 1)] = {'compute': self._host_caps(x)} self.zone_manager.service_states = states - def tearDown(self): - FLAGS.default_host_filter = self.old_flag - def test_choose_filter(self): # Test default filter ... hf = host_filter.choose_host_filter() diff --git a/nova/tests/test_ipv6.py b/nova/tests/test_ipv6.py index 11dc2ec98..d123df6f1 100644 --- a/nova/tests/test_ipv6.py +++ b/nova/tests/test_ipv6.py @@ -16,15 +16,12 @@ """Test suite for IPv6.""" -from nova import flags from nova import ipv6 from nova import log as logging from nova import test LOG = logging.getLogger('nova.tests.test_ipv6') -FLAGS = flags.FLAGS - import sys diff --git a/nova/tests/test_libvirt.py b/nova/tests/test_libvirt.py index cf25ce215..f8b866985 100644 --- a/nova/tests/test_libvirt.py +++ b/nova/tests/test_libvirt.py @@ -38,7 +38,6 @@ from nova.virt.libvirt import firewall libvirt = None FLAGS = flags.FLAGS -flags.DECLARE('instances_path', 'nova.compute.manager') def _concurrency(wait, done, target): @@ -93,6 +92,7 @@ def _setup_networking(instance_id, ip='1.2.3.4'): class CacheConcurrencyTestCase(test.TestCase): def setUp(self): super(CacheConcurrencyTestCase, self).setUp() + self.flags(instances_path='nova.compute.manager') def fake_exists(fname): basedir = os.path.join(FLAGS.instances_path, '_base') @@ -158,7 +158,7 @@ class LibvirtConnTestCase(test.TestCase): self.context = context.RequestContext(self.user_id, self.project_id) self.network = utils.import_object(FLAGS.network_manager) self.context = context.get_admin_context() - FLAGS.instances_path = '' + self.flags(instances_path='') self.call_libvirt_dependant_setup = False self.test_ip = '10.11.12.13' @@ -322,7 +322,7 @@ class LibvirtConnTestCase(test.TestCase): if not self.lazy_load_library_exists(): return - FLAGS.image_service = 'nova.image.fake.FakeImageService' + self.flags(image_service='nova.image.fake.FakeImageService') # Start test image_service = utils.import_object(FLAGS.image_service) @@ -357,7 +357,7 @@ class LibvirtConnTestCase(test.TestCase): if not self.lazy_load_library_exists(): return - FLAGS.image_service = 'nova.image.fake.FakeImageService' + self.flags(image_service='nova.image.fake.FakeImageService') # Start test image_service = utils.import_object(FLAGS.image_service) @@ -521,7 +521,7 @@ class LibvirtConnTestCase(test.TestCase): 'disk.local')] for (libvirt_type, (expected_uri, checks)) in type_uri_map.iteritems(): - FLAGS.libvirt_type = libvirt_type + self.flags(libvirt_type=libvirt_type) conn = connection.LibvirtConnection(True) uri = conn.get_uri() @@ -546,9 +546,9 @@ class LibvirtConnTestCase(test.TestCase): # checking against that later on. This way we make sure the # implementation doesn't fiddle around with the FLAGS. testuri = 'something completely different' - FLAGS.libvirt_uri = testuri + self.flags(libvirt_uri=testuri) for (libvirt_type, (expected_uri, checks)) in type_uri_map.iteritems(): - FLAGS.libvirt_type = libvirt_type + self.flags(libvirt_type=libvirt_type) conn = connection.LibvirtConnection(True) uri = conn.get_uri() self.assertEquals(uri, testuri) @@ -556,8 +556,7 @@ class LibvirtConnTestCase(test.TestCase): def test_update_available_resource_works_correctly(self): """Confirm compute_node table is updated successfully.""" - org_path = FLAGS.instances_path = '' - FLAGS.instances_path = '.' + self.flags(instances_path='.') # Prepare mocks def getVersion(): @@ -604,12 +603,10 @@ class LibvirtConnTestCase(test.TestCase): self.assertTrue(compute_node['hypervisor_version'] > 0) db.service_destroy(self.context, service_ref['id']) - FLAGS.instances_path = org_path def test_update_resource_info_no_compute_record_found(self): """Raise exception if no recorde found on services table.""" - org_path = FLAGS.instances_path = '' - FLAGS.instances_path = '.' + self.flags(instances_path='.') self.create_fake_libvirt_mock() self.mox.ReplayAll() @@ -618,8 +615,6 @@ class LibvirtConnTestCase(test.TestCase): conn.update_available_resource, self.context, 'dummy') - FLAGS.instances_path = org_path - def test_ensure_filtering_rules_for_instance_timeout(self): """ensure_filtering_fules_for_instance() finishes with timeout.""" # Skip if non-libvirt environment diff --git a/nova/tests/test_network.py b/nova/tests/test_network.py index 28f50d328..2ca8b64f4 100644 --- a/nova/tests/test_network.py +++ b/nova/tests/test_network.py @@ -17,7 +17,6 @@ from nova import db from nova import exception -from nova import flags from nova import log as logging from nova import test from nova.network import manager as network_manager @@ -26,7 +25,6 @@ from nova.network import manager as network_manager import mox -FLAGS = flags.FLAGS LOG = logging.getLogger('nova.tests.network') diff --git a/nova/tests/test_quota.py b/nova/tests/test_quota.py index 92393b536..f4b481ebe 100644 --- a/nova/tests/test_quota.py +++ b/nova/tests/test_quota.py @@ -114,9 +114,7 @@ class QuotaTestCase(test.TestCase): db.quota_destroy_all_by_project(self.context, self.project_id) def test_unlimited_instances(self): - FLAGS.quota_instances = 2 - FLAGS.quota_ram = -1 - FLAGS.quota_cores = -1 + self.flags(quota_instances=2, quota_ram=-1, quota_cores=-1) instance_type = self._get_instance_type('m1.small') num_instances = quota.allowed_instances(self.context, 100, instance_type) @@ -130,9 +128,7 @@ class QuotaTestCase(test.TestCase): self.assertEqual(num_instances, 101) def test_unlimited_ram(self): - FLAGS.quota_instances = -1 - FLAGS.quota_ram = 2 * 2048 - FLAGS.quota_cores = -1 + self.flags(quota_instances=-1, quota_ram=2 * 2048, quota_cores=-1) instance_type = self._get_instance_type('m1.small') num_instances = quota.allowed_instances(self.context, 100, instance_type) @@ -146,9 +142,7 @@ class QuotaTestCase(test.TestCase): self.assertEqual(num_instances, 101) def test_unlimited_cores(self): - FLAGS.quota_instances = -1 - FLAGS.quota_ram = -1 - FLAGS.quota_cores = 2 + self.flags(quota_instances=-1, quota_ram=-1, quota_cores=2) instance_type = self._get_instance_type('m1.small') num_instances = quota.allowed_instances(self.context, 100, instance_type) @@ -162,8 +156,7 @@ class QuotaTestCase(test.TestCase): self.assertEqual(num_instances, 101) def test_unlimited_volumes(self): - FLAGS.quota_volumes = 10 - FLAGS.quota_gigabytes = -1 + self.flags(quota_volumes=10, quota_gigabytes=-1) volumes = quota.allowed_volumes(self.context, 100, 1) self.assertEqual(volumes, 10) db.quota_create(self.context, self.project_id, 'volumes', None) @@ -173,8 +166,7 @@ class QuotaTestCase(test.TestCase): self.assertEqual(volumes, 101) def test_unlimited_gigabytes(self): - FLAGS.quota_volumes = -1 - FLAGS.quota_gigabytes = 10 + self.flags(quota_volumes=-1, quota_gigabytes=10) volumes = quota.allowed_volumes(self.context, 100, 1) self.assertEqual(volumes, 10) db.quota_create(self.context, self.project_id, 'gigabytes', None) @@ -184,7 +176,7 @@ class QuotaTestCase(test.TestCase): self.assertEqual(volumes, 101) def test_unlimited_floating_ips(self): - FLAGS.quota_floating_ips = 10 + self.flags(quota_floating_ips=10) floating_ips = quota.allowed_floating_ips(self.context, 100) self.assertEqual(floating_ips, 10) db.quota_create(self.context, self.project_id, 'floating_ips', None) @@ -194,7 +186,7 @@ class QuotaTestCase(test.TestCase): self.assertEqual(floating_ips, 101) def test_unlimited_metadata_items(self): - FLAGS.quota_metadata_items = 10 + self.flags(quota_metadata_items=10) items = quota.allowed_metadata_items(self.context, 100) self.assertEqual(items, 10) db.quota_create(self.context, self.project_id, 'metadata_items', None) @@ -286,49 +278,49 @@ class QuotaTestCase(test.TestCase): metadata=metadata) def test_default_allowed_injected_files(self): - FLAGS.quota_max_injected_files = 55 + self.flags(quota_max_injected_files=55) self.assertEqual(quota.allowed_injected_files(self.context, 100), 55) def test_overridden_allowed_injected_files(self): - FLAGS.quota_max_injected_files = 5 + self.flags(quota_max_injected_files=5) db.quota_create(self.context, self.project_id, 'injected_files', 77) self.assertEqual(quota.allowed_injected_files(self.context, 100), 77) def test_unlimited_default_allowed_injected_files(self): - FLAGS.quota_max_injected_files = -1 + self.flags(quota_max_injected_files=-1) self.assertEqual(quota.allowed_injected_files(self.context, 100), 100) def test_unlimited_db_allowed_injected_files(self): - FLAGS.quota_max_injected_files = 5 + self.flags(quota_max_injected_files=5) db.quota_create(self.context, self.project_id, 'injected_files', None) self.assertEqual(quota.allowed_injected_files(self.context, 100), 100) def test_default_allowed_injected_file_content_bytes(self): - FLAGS.quota_max_injected_file_content_bytes = 12345 + self.flags(quota_max_injected_file_content_bytes=12345) limit = quota.allowed_injected_file_content_bytes(self.context, 23456) self.assertEqual(limit, 12345) def test_overridden_allowed_injected_file_content_bytes(self): - FLAGS.quota_max_injected_file_content_bytes = 12345 + self.flags(quota_max_injected_file_content_bytes=12345) db.quota_create(self.context, self.project_id, 'injected_file_content_bytes', 5678) limit = quota.allowed_injected_file_content_bytes(self.context, 23456) self.assertEqual(limit, 5678) def test_unlimited_default_allowed_injected_file_content_bytes(self): - FLAGS.quota_max_injected_file_content_bytes = -1 + self.flags(quota_max_injected_file_content_bytes=-1) limit = quota.allowed_injected_file_content_bytes(self.context, 23456) self.assertEqual(limit, 23456) def test_unlimited_db_allowed_injected_file_content_bytes(self): - FLAGS.quota_max_injected_file_content_bytes = 12345 + self.flags(quota_max_injected_file_content_bytes=12345) db.quota_create(self.context, self.project_id, 'injected_file_content_bytes', None) limit = quota.allowed_injected_file_content_bytes(self.context, 23456) self.assertEqual(limit, 23456) def _create_with_injected_files(self, files): - FLAGS.image_service = 'nova.image.fake.FakeImageService' + self.flags(image_service='nova.image.fake.FakeImageService') api = compute.API(image_service=self.StubImageService()) inst_type = instance_types.get_instance_type_by_name('m1.small') api.create(self.context, min_count=1, max_count=1, @@ -336,7 +328,7 @@ class QuotaTestCase(test.TestCase): injected_files=files) def test_no_injected_files(self): - FLAGS.image_service = 'nova.image.fake.FakeImageService' + self.flags(image_service='nova.image.fake.FakeImageService') api = compute.API(image_service=self.StubImageService()) inst_type = instance_types.get_instance_type_by_name('m1.small') api.create(self.context, instance_type=inst_type, image_href='3') diff --git a/nova/tests/test_rpc.py b/nova/tests/test_rpc.py index 2d2436175..ba9c0a859 100644 --- a/nova/tests/test_rpc.py +++ b/nova/tests/test_rpc.py @@ -20,13 +20,11 @@ Unit Tests for remote procedure calls using queue """ from nova import context -from nova import flags from nova import log as logging from nova import rpc from nova import test -FLAGS = flags.FLAGS LOG = logging.getLogger('nova.tests.rpc') diff --git a/nova/tests/test_rpc_amqp.py b/nova/tests/test_rpc_amqp.py index d29f7ae32..2215a908b 100644 --- a/nova/tests/test_rpc_amqp.py +++ b/nova/tests/test_rpc_amqp.py @@ -1,12 +1,32 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright (c) 2010 Openstack, LLC. +# Administrator of the National Aeronautics and Space Administration. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +""" +Tests For RPC AMQP. +""" + from nova import context -from nova import flags from nova import log as logging from nova import rpc from nova.rpc import amqp from nova import test -FLAGS = flags.FLAGS LOG = logging.getLogger('nova.tests.rpc') diff --git a/nova/tests/test_service.py b/nova/tests/test_service.py index bbf47b50f..8f92406ff 100644 --- a/nova/tests/test_service.py +++ b/nova/tests/test_service.py @@ -33,7 +33,6 @@ from nova import manager from nova import wsgi from nova.compute import manager as compute_manager -FLAGS = flags.FLAGS flags.DEFINE_string("fake_manager", "nova.tests.test_service.FakeManager", "Manager for testing") diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index a795b3c74..1ba2bf356 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -71,9 +71,9 @@ class XenAPIVolumeTestCase(test.TestCase): self.user_id = 'fake' self.project_id = 'fake' self.context = context.RequestContext(self.user_id, self.project_id) - FLAGS.target_host = '127.0.0.1' - FLAGS.xenapi_connection_url = 'test_url' - FLAGS.xenapi_connection_password = 'test_pass' + self.flags(target_host='127.0.0.1', + xenapi_connection_url='test_url', + xenapi_connection_password='test_pass') db_fakes.stub_out_db_instance_api(self.stubs) stubs.stub_out_get_target(self.stubs) xenapi_fake.reset() @@ -719,9 +719,9 @@ class XenAPIMigrateInstance(test.TestCase): def setUp(self): super(XenAPIMigrateInstance, self).setUp() self.stubs = stubout.StubOutForTesting() - FLAGS.target_host = '127.0.0.1' - FLAGS.xenapi_connection_url = 'test_url' - FLAGS.xenapi_connection_password = 'test_pass' + self.flags(target_host='127.0.0.1', + xenapi_connection_url='test_url', + xenapi_connection_password='test_pass') db_fakes.stub_out_db_instance_api(self.stubs) stubs.stub_out_get_target(self.stubs) xenapi_fake.reset() -- cgit From 25655e0bcea99ea27108d954104138a09f922f30 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Wed, 3 Aug 2011 12:26:51 -0700 Subject: fix test_s3 FLAGS uses --- nova/tests/api/openstack/__init__.py | 3 --- nova/tests/image/test_s3.py | 10 +--------- 2 files changed, 1 insertion(+), 12 deletions(-) (limited to 'nova') diff --git a/nova/tests/api/openstack/__init__.py b/nova/tests/api/openstack/__init__.py index bfb424afe..458434a81 100644 --- a/nova/tests/api/openstack/__init__.py +++ b/nova/tests/api/openstack/__init__.py @@ -22,14 +22,11 @@ import webob.dec from nova import test from nova import context -from nova import flags from nova.api.openstack.limits import RateLimitingMiddleware from nova.api.openstack.common import limited from nova.tests.api.openstack import fakes from webob import Request -FLAGS = flags.FLAGS - @webob.dec.wsgify def simple_wsgi(req): diff --git a/nova/tests/image/test_s3.py b/nova/tests/image/test_s3.py index 231e109f8..f1ceeb7fe 100644 --- a/nova/tests/image/test_s3.py +++ b/nova/tests/image/test_s3.py @@ -16,12 +16,9 @@ # under the License. from nova import context -from nova import flags from nova import test from nova.image import s3 -FLAGS = flags.FLAGS - ami_manifest_xml = """ @@ -59,15 +56,10 @@ ami_manifest_xml = """ class TestS3ImageService(test.TestCase): def setUp(self): super(TestS3ImageService, self).setUp() - self.orig_image_service = FLAGS.image_service - FLAGS.image_service = 'nova.image.fake.FakeImageService' + self.flags(image_service='nova.image.fake.FakeImageService') self.image_service = s3.S3ImageService() self.context = context.RequestContext(None, None) - def tearDown(self): - super(TestS3ImageService, self).tearDown() - FLAGS.image_service = self.orig_image_service - def _assertEqualList(self, list0, list1, keys): self.assertEqual(len(list0), len(list1)) key = keys[0] -- cgit From ff5dbc6d985d11ef937baa930ee96c93cbd8ccf9 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Wed, 3 Aug 2011 14:06:56 -0700 Subject: fix more tests that use FLAGS setting --- nova/tests/integrated/integrated_helpers.py | 4 +--- nova/tests/integrated/test_extensions.py | 5 ----- nova/tests/integrated/test_login.py | 4 ---- nova/tests/integrated/test_servers.py | 5 ----- nova/tests/integrated/test_volumes.py | 5 ----- nova/tests/integrated/test_xml.py | 5 ----- 6 files changed, 1 insertion(+), 27 deletions(-) (limited to 'nova') diff --git a/nova/tests/integrated/integrated_helpers.py b/nova/tests/integrated/integrated_helpers.py index 47bd8c1e4..049ddbc60 100644 --- a/nova/tests/integrated/integrated_helpers.py +++ b/nova/tests/integrated/integrated_helpers.py @@ -23,7 +23,6 @@ import random import string from nova import exception -from nova import flags from nova import service from nova import test # For the flags from nova.auth import manager @@ -32,8 +31,6 @@ from nova.log import logging from nova.tests.integrated.api import client -FLAGS = flags.FLAGS - LOG = logging.getLogger('nova.tests.integrated') @@ -151,6 +148,7 @@ class _IntegratedTestBase(test.TestCase): f = self._get_flags() self.flags(**f) + self.set_flags_verbosity(True) def fake_get_image_service(image_href): image_id = int(str(image_href).split('/')[-1]) diff --git a/nova/tests/integrated/test_extensions.py b/nova/tests/integrated/test_extensions.py index 0d4ee8cab..c22cf0be0 100644 --- a/nova/tests/integrated/test_extensions.py +++ b/nova/tests/integrated/test_extensions.py @@ -17,7 +17,6 @@ import os -from nova import flags from nova.log import logging from nova.tests.integrated import integrated_helpers @@ -25,10 +24,6 @@ from nova.tests.integrated import integrated_helpers LOG = logging.getLogger('nova.tests.integrated') -FLAGS = flags.FLAGS -FLAGS.verbose = True - - class ExtensionsTest(integrated_helpers._IntegratedTestBase): def _get_flags(self): f = super(ExtensionsTest, self)._get_flags() diff --git a/nova/tests/integrated/test_login.py b/nova/tests/integrated/test_login.py index a5180b6bc..06359a52f 100644 --- a/nova/tests/integrated/test_login.py +++ b/nova/tests/integrated/test_login.py @@ -17,7 +17,6 @@ import unittest -from nova import flags from nova.log import logging from nova.tests.integrated import integrated_helpers from nova.tests.integrated.api import client @@ -25,9 +24,6 @@ from nova.tests.integrated.api import client LOG = logging.getLogger('nova.tests.integrated') -FLAGS = flags.FLAGS -FLAGS.verbose = True - class LoginTest(integrated_helpers._IntegratedTestBase): def test_login(self): diff --git a/nova/tests/integrated/test_servers.py b/nova/tests/integrated/test_servers.py index 67b3c485a..150279a95 100644 --- a/nova/tests/integrated/test_servers.py +++ b/nova/tests/integrated/test_servers.py @@ -18,7 +18,6 @@ import time import unittest -from nova import flags from nova.log import logging from nova.tests.integrated import integrated_helpers from nova.tests.integrated.api import client @@ -27,10 +26,6 @@ from nova.tests.integrated.api import client LOG = logging.getLogger('nova.tests.integrated') -FLAGS = flags.FLAGS -FLAGS.verbose = True - - class ServersTest(integrated_helpers._IntegratedTestBase): def test_get_servers(self): """Simple check that listing servers works.""" diff --git a/nova/tests/integrated/test_volumes.py b/nova/tests/integrated/test_volumes.py index e9fb3c4d1..d3e936462 100644 --- a/nova/tests/integrated/test_volumes.py +++ b/nova/tests/integrated/test_volumes.py @@ -18,7 +18,6 @@ import unittest import time -from nova import flags from nova.log import logging from nova.tests.integrated import integrated_helpers from nova.tests.integrated.api import client @@ -28,10 +27,6 @@ from nova.volume import driver LOG = logging.getLogger('nova.tests.integrated') -FLAGS = flags.FLAGS -FLAGS.verbose = True - - class VolumesTest(integrated_helpers._IntegratedTestBase): def setUp(self): super(VolumesTest, self).setUp() diff --git a/nova/tests/integrated/test_xml.py b/nova/tests/integrated/test_xml.py index fde32f797..74baaacc2 100644 --- a/nova/tests/integrated/test_xml.py +++ b/nova/tests/integrated/test_xml.py @@ -15,7 +15,6 @@ # License for the specific language governing permissions and limitations # under the License. -from nova import flags from nova.log import logging from nova.tests.integrated import integrated_helpers from nova.api.openstack import common @@ -24,10 +23,6 @@ from nova.api.openstack import common LOG = logging.getLogger('nova.tests.integrated') -FLAGS = flags.FLAGS -FLAGS.verbose = True - - class XmlTests(integrated_helpers._IntegratedTestBase): """"Some basic XML sanity checks.""" -- cgit From 4f9f56d8e9917f5db0c5f76653d7ee3a2db52a5f Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Wed, 3 Aug 2011 14:08:22 -0700 Subject: fix scheduler tests that set FLAGS --- nova/tests/scheduler/test_scheduler.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) (limited to 'nova') diff --git a/nova/tests/scheduler/test_scheduler.py b/nova/tests/scheduler/test_scheduler.py index 6a56a57db..f60eb6433 100644 --- a/nova/tests/scheduler/test_scheduler.py +++ b/nova/tests/scheduler/test_scheduler.py @@ -962,13 +962,10 @@ class ZoneRedirectTest(test.TestCase): self.stubs.Set(db, 'zone_get_all', zone_get_all) self.stubs.Set(db, 'instance_get_by_uuid', fake_instance_get_by_uuid) - - self.enable_zone_routing = FLAGS.enable_zone_routing - FLAGS.enable_zone_routing = True + self.flags(enable_zone_routing=True) def tearDown(self): self.stubs.UnsetAll() - FLAGS.enable_zone_routing = self.enable_zone_routing super(ZoneRedirectTest, self).tearDown() def test_trap_found_locally(self): @@ -998,7 +995,7 @@ class ZoneRedirectTest(test.TestCase): self.assertEquals(e.results['magic'], 'found me') def test_routing_flags(self): - FLAGS.enable_zone_routing = False + self.flags(enable_zone_routing=False) decorator = FakeRerouteCompute("foo") self.assertRaises(exception.InstanceNotFound, decorator(go_boom), None, None, 1) -- cgit From 3765fca2cfd323c047141a27df248d9617d71ad0 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Wed, 3 Aug 2011 14:11:37 -0700 Subject: dist scheduler flag setting fixes --- nova/tests/scheduler/test_host_filter.py | 11 ++--------- nova/tests/scheduler/test_least_cost_scheduler.py | 23 +++++++++-------------- 2 files changed, 11 insertions(+), 23 deletions(-) (limited to 'nova') diff --git a/nova/tests/scheduler/test_host_filter.py b/nova/tests/scheduler/test_host_filter.py index b1892dab4..b016923c4 100644 --- a/nova/tests/scheduler/test_host_filter.py +++ b/nova/tests/scheduler/test_host_filter.py @@ -19,12 +19,9 @@ Tests For Scheduler Host Filters. import json from nova import exception -from nova import flags from nova import test from nova.scheduler import host_filter -FLAGS = flags.FLAGS - class FakeZoneManager: pass @@ -57,9 +54,8 @@ class HostFilterTestCase(test.TestCase): 'host_name-label': 'xs-%s' % multiplier} def setUp(self): - self.old_flag = FLAGS.default_host_filter - FLAGS.default_host_filter = \ - 'nova.scheduler.host_filter.AllHostsFilter' + default_host_filter = 'nova.scheduler.host_filter.AllHostsFilter' + self.flags(default_host_filter=default_host_filter) self.instance_type = dict(name='tiny', memory_mb=50, vcpus=10, @@ -98,9 +94,6 @@ class HostFilterTestCase(test.TestCase): host09['xpu_arch'] = 'fermi' host09['xpu_info'] = 'Tesla 2150' - def tearDown(self): - FLAGS.default_host_filter = self.old_flag - def test_choose_filter(self): # Test default filter ... hf = host_filter.choose_host_filter() diff --git a/nova/tests/scheduler/test_least_cost_scheduler.py b/nova/tests/scheduler/test_least_cost_scheduler.py index 49791053e..fbe6b2f77 100644 --- a/nova/tests/scheduler/test_least_cost_scheduler.py +++ b/nova/tests/scheduler/test_least_cost_scheduler.py @@ -16,13 +16,11 @@ Tests For Least Cost Scheduler """ -from nova import flags from nova import test from nova.scheduler import least_cost from nova.tests.scheduler import test_zone_aware_scheduler MB = 1024 * 1024 -FLAGS = flags.FLAGS class FakeHost(object): @@ -95,10 +93,9 @@ class LeastCostSchedulerTestCase(test.TestCase): self.assertWeights(expected, num, request_spec, hosts) def test_noop_cost_fn(self): - FLAGS.least_cost_scheduler_cost_functions = [ - 'nova.scheduler.least_cost.noop_cost_fn', - ] - FLAGS.noop_cost_fn_weight = 1 + self.flags(least_cost_scheduler_cost_functions=[ + 'nova.scheduler.least_cost.noop_cost_fn'], + noop_cost_fn_weight=1) num = 1 request_spec = {} @@ -109,10 +106,9 @@ class LeastCostSchedulerTestCase(test.TestCase): self.assertWeights(expected, num, request_spec, hosts) def test_cost_fn_weights(self): - FLAGS.least_cost_scheduler_cost_functions = [ - 'nova.scheduler.least_cost.noop_cost_fn', - ] - FLAGS.noop_cost_fn_weight = 2 + self.flags(least_cost_scheduler_cost_functions=[ + 'nova.scheduler.least_cost.noop_cost_fn'], + noop_cost_fn_weight=2) num = 1 request_spec = {} @@ -123,10 +119,9 @@ class LeastCostSchedulerTestCase(test.TestCase): self.assertWeights(expected, num, request_spec, hosts) def test_compute_fill_first_cost_fn(self): - FLAGS.least_cost_scheduler_cost_functions = [ - 'nova.scheduler.least_cost.compute_fill_first_cost_fn', - ] - FLAGS.compute_fill_first_cost_fn_weight = 1 + self.flags(least_cost_scheduler_cost_functions=[ + 'nova.scheduler.least_cost.compute_fill_first_cost_fn'], + compute_fill_first_cost_fn_weight=1) num = 1 instance_type = {'memory_mb': 1024} -- cgit From 544eecd0adc2c774e512f5c364f6952fb3fd1155 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Wed, 3 Aug 2011 14:13:37 -0700 Subject: fix test_cloud FLAGS setting --- nova/tests/test_cloud.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'nova') diff --git a/nova/tests/test_cloud.py b/nova/tests/test_cloud.py index 8c1a74c70..e891fa197 100644 --- a/nova/tests/test_cloud.py +++ b/nova/tests/test_cloud.py @@ -99,11 +99,9 @@ class CloudTestCase(test.TestCase): """Makes sure describe regions runs without raising an exception""" result = self.cloud.describe_regions(self.context) self.assertEqual(len(result['regionInfo']), 1) - regions = FLAGS.region_list - FLAGS.region_list = ["one=test_host1", "two=test_host2"] + self.flags(region_list=["one=test_host1", "two=test_host2"]) result = self.cloud.describe_regions(self.context) self.assertEqual(len(result['regionInfo']), 2) - FLAGS.region_list = regions def test_describe_addresses(self): """Makes sure describe addresses runs without raising an exception""" -- cgit From 4fe63486dd44b036b87d0357563afe4396ecaeb3 Mon Sep 17 00:00:00 2001 From: Johannes Erdfelt Date: Wed, 3 Aug 2011 21:43:48 +0000 Subject: Fix trailing whitespace (PEP8) --- 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 a2d18d37e..333994fcc 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -92,7 +92,7 @@ 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 str(image_href).startswith(req.application_url): image_href = image_href.split('/').pop() try: -- cgit From b87a999725c90e1c80ff7c2b8102b1c5921f8bc0 Mon Sep 17 00:00:00 2001 From: Johannes Erdfelt Date: Wed, 3 Aug 2011 21:44:03 +0000 Subject: Load instance_types in downgrade method too --- .../migrate_repo/versions/036_change_flavor_id_in_migrations.py | 1 + 1 file changed, 1 insertion(+) (limited to 'nova') diff --git a/nova/db/sqlalchemy/migrate_repo/versions/036_change_flavor_id_in_migrations.py b/nova/db/sqlalchemy/migrate_repo/versions/036_change_flavor_id_in_migrations.py index 7ff709c17..f3244033b 100644 --- a/nova/db/sqlalchemy/migrate_repo/versions/036_change_flavor_id_in_migrations.py +++ b/nova/db/sqlalchemy/migrate_repo/versions/036_change_flavor_id_in_migrations.py @@ -54,6 +54,7 @@ def upgrade(migrate_engine): def downgrade(migrate_engine): meta.bind = migrate_engine + instance_types = Table('instance_types', meta, autoload=True) migrations = Table('migrations', meta, autoload=True) migrations.create_column(old_flavor_id) migrations.create_column(new_flavor_id) -- cgit From bd0a2ed74764de262a828fd086fa0e08493b2cec Mon Sep 17 00:00:00 2001 From: "matt.dietz@rackspace.com" <> Date: Wed, 3 Aug 2011 17:39:51 -0500 Subject: Fixes lp819397 --- nova/tests/test_xenapi.py | 7 +++++++ nova/virt/xenapi/vmops.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index a795b3c74..26a36b231 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -754,12 +754,18 @@ class XenAPIMigrateInstance(test.TestCase): def test_finish_migrate(self): instance = db.instance_create(self.context, self.values) self.called = False + self.fake_vm_start_called = False + + def fake_vm_start(*args, **kwargs): + self.fake_vm_start_called = True def fake_vdi_resize(*args, **kwargs): self.called = True self.stubs.Set(stubs.FakeSessionForMigrationTests, "VDI_resize_online", fake_vdi_resize) + self.stubs.Set(vmops.VMOps, '_start', fake_vm_start) + stubs.stubout_session(self.stubs, stubs.FakeSessionForMigrationTests) stubs.stubout_loopingcall_start(self.stubs) conn = xenapi_conn.get_connection(False) @@ -781,6 +787,7 @@ class XenAPIMigrateInstance(test.TestCase): dict(base_copy='hurr', cow='durr'), network_info, resize_instance=True) self.assertEqual(self.called, True) + self.assertEqual(self.fake_vm_start_called, True) def test_finish_migrate_no_local_storage(self): tiny_type_id = \ diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index b3b812a48..3b9f35034 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -122,7 +122,7 @@ class VMOps(object): network_info) if resize_instance: self.resize_instance(instance, vdi_uuid) - self._spawn(instance, vm_ref) + self._start(instance, vm_ref=vm_ref) def _start(self, instance, vm_ref=None): """Power on a VM instance""" -- cgit From f34a6fb9efd8d950555431f5e7268dbde8ae78c8 Mon Sep 17 00:00:00 2001 From: Matthew Hooker Date: Wed, 3 Aug 2011 19:17:08 -0400 Subject: Remove instances of the "diaper pattern" Anywhere "except:" occurs, I tried to replace it with an explicit except on known error types. If none were known, Except was used. In the process I've been able to unearth a few evasive bugs and clean up some adjacent code. --- nova/api/direct.py | 5 ++--- nova/api/ec2/__init__.py | 4 ++-- nova/api/ec2/apirequest.py | 2 +- nova/api/openstack/common.py | 27 +++++++++++---------------- nova/api/openstack/servers.py | 22 +++++++++++----------- nova/cloudpipe/pipelib.py | 17 +++++++---------- nova/db/sqlalchemy/api.py | 14 ++++++++------ nova/image/__init__.py | 3 ++- nova/utils.py | 18 +++++++++++++++++- nova/virt/libvirt/connection.py | 6 +++--- nova/virt/libvirt/vif.py | 5 +++-- nova/virt/xenapi_conn.py | 2 +- nova/vnc/proxy.py | 4 ++-- 13 files changed, 70 insertions(+), 59 deletions(-) (limited to 'nova') diff --git a/nova/api/direct.py b/nova/api/direct.py index 993815fc7..139c46d63 100644 --- a/nova/api/direct.py +++ b/nova/api/direct.py @@ -48,7 +48,6 @@ import nova.api.openstack.wsgi # Global storage for registering modules. ROUTES = {} - def register_service(path, handle): """Register a service handle at a given path. @@ -296,8 +295,8 @@ class ServiceWrapper(object): 'application/json': nova.api.openstack.wsgi.JSONDictSerializer(), }[content_type] return serializer.serialize(result) - except: - raise exception.Error("returned non-serializable type: %s" + except Exception, e: + raise exception.Error(_("Returned non-serializable type: %s") % result) diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py index af232edda..804e54ef9 100644 --- a/nova/api/ec2/__init__.py +++ b/nova/api/ec2/__init__.py @@ -147,7 +147,7 @@ class Authenticate(wsgi.Middleware): try: signature = req.params['Signature'] access = req.params['AWSAccessKeyId'] - except: + except KeyError, e: raise webob.exc.HTTPBadRequest() # Make a copy of args for authentication and signature verification. @@ -211,7 +211,7 @@ class Requestify(wsgi.Middleware): for non_arg in non_args: # Remove, but raise KeyError if omitted args.pop(non_arg) - except: + except KeyError, e: raise webob.exc.HTTPBadRequest() LOG.debug(_('action: %s'), action) diff --git a/nova/api/ec2/apirequest.py b/nova/api/ec2/apirequest.py index 7d78c5cfa..9a3e55925 100644 --- a/nova/api/ec2/apirequest.py +++ b/nova/api/ec2/apirequest.py @@ -104,7 +104,7 @@ class APIRequest(object): for key in data.keys(): val = data[key] el.appendChild(self._render_data(xml, key, val)) - except: + except Exception: LOG.debug(data) raise diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py index efa4ab385..1304379a8 100644 --- a/nova/api/openstack/common.py +++ b/nova/api/openstack/common.py @@ -16,7 +16,7 @@ # under the License. import re -from urlparse import urlparse +import urlparse from xml.dom import minidom import webob @@ -137,8 +137,8 @@ def get_id_from_href(href): if re.match(r'\d+$', str(href)): return int(href) try: - return int(urlparse(href).path.split('/')[-1]) - except: + return int(urlparse.urlsplit(href).path.split('/')[-1]) + except ValueError, e: LOG.debug(_("Error extracting id from href: %s") % href) raise ValueError(_('could not parse id from href')) @@ -153,22 +153,17 @@ def remove_version_from_href(href): Returns: 'http://www.nova.com' """ - try: - #removes the first instance that matches /v#.#/ - new_href = re.sub(r'[/][v][0-9]+\.[0-9]+[/]', '/', href, count=1) + parsed_url = urlparse.urlsplit(href) + new_path = re.sub(r'^/v[0-9]+\.[0-9]+(/|$)', r'\1', parsed_url.path, count=1) - #if no version was found, try finding /v#.# at the end of the string - if new_href == href: - new_href = re.sub(r'[/][v][0-9]+\.[0-9]+$', '', href, count=1) - except: - LOG.debug(_("Error removing version from href: %s") % href) - msg = _('could not parse version from href') + if new_path == parsed_url.path: + msg = _('href %s does not contain version') % href + LOG.debug(msg) raise ValueError(msg) - if new_href == href: - msg = _('href does not contain version') - raise ValueError(msg) - return new_href + parsed_url = list(parsed_url) + parsed_url[2] = new_path + return urlparse.urlunsplit(parsed_url) def get_version_from_href(href): diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 002b47edb..493845e8c 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -290,7 +290,7 @@ class Controller(object): context = req.environ['nova.context'] try: self.compute_api.lock(context, id) - except: + except Exception: readable = traceback.format_exc() LOG.exception(_("Compute.api::lock %s"), readable) raise exc.HTTPUnprocessableEntity() @@ -306,7 +306,7 @@ class Controller(object): context = req.environ['nova.context'] try: self.compute_api.unlock(context, id) - except: + except Exception: readable = traceback.format_exc() LOG.exception(_("Compute.api::unlock %s"), readable) raise exc.HTTPUnprocessableEntity() @@ -321,7 +321,7 @@ class Controller(object): context = req.environ['nova.context'] try: self.compute_api.get_lock(context, id) - except: + except Exception: readable = traceback.format_exc() LOG.exception(_("Compute.api::get_lock %s"), readable) raise exc.HTTPUnprocessableEntity() @@ -336,7 +336,7 @@ class Controller(object): context = req.environ['nova.context'] try: self.compute_api.reset_network(context, id) - except: + except Exception: readable = traceback.format_exc() LOG.exception(_("Compute.api::reset_network %s"), readable) raise exc.HTTPUnprocessableEntity() @@ -351,7 +351,7 @@ class Controller(object): context = req.environ['nova.context'] try: self.compute_api.inject_network_info(context, id) - except: + except Exception: readable = traceback.format_exc() LOG.exception(_("Compute.api::inject_network_info %s"), readable) raise exc.HTTPUnprocessableEntity() @@ -363,7 +363,7 @@ class Controller(object): ctxt = req.environ['nova.context'] try: self.compute_api.pause(ctxt, id) - except: + except Exception: readable = traceback.format_exc() LOG.exception(_("Compute.api::pause %s"), readable) raise exc.HTTPUnprocessableEntity() @@ -375,7 +375,7 @@ class Controller(object): ctxt = req.environ['nova.context'] try: self.compute_api.unpause(ctxt, id) - except: + except Exception: readable = traceback.format_exc() LOG.exception(_("Compute.api::unpause %s"), readable) raise exc.HTTPUnprocessableEntity() @@ -387,7 +387,7 @@ class Controller(object): context = req.environ['nova.context'] try: self.compute_api.suspend(context, id) - except: + except Exception: readable = traceback.format_exc() LOG.exception(_("compute.api::suspend %s"), readable) raise exc.HTTPUnprocessableEntity() @@ -399,7 +399,7 @@ class Controller(object): context = req.environ['nova.context'] try: self.compute_api.resume(context, id) - except: + except Exception: readable = traceback.format_exc() LOG.exception(_("compute.api::resume %s"), readable) raise exc.HTTPUnprocessableEntity() @@ -420,7 +420,7 @@ class Controller(object): context = req.environ["nova.context"] try: self.compute_api.rescue(context, id) - except: + except Exception: readable = traceback.format_exc() LOG.exception(_("compute.api::rescue %s"), readable) raise exc.HTTPUnprocessableEntity() @@ -432,7 +432,7 @@ class Controller(object): context = req.environ["nova.context"] try: self.compute_api.unrescue(context, id) - except: + except Exception: readable = traceback.format_exc() LOG.exception(_("compute.api::unrescue %s"), readable) raise exc.HTTPUnprocessableEntity() diff --git a/nova/cloudpipe/pipelib.py b/nova/cloudpipe/pipelib.py index 521525205..46549c9d7 100644 --- a/nova/cloudpipe/pipelib.py +++ b/nova/cloudpipe/pipelib.py @@ -141,15 +141,12 @@ class CloudPipe(object): try: result = cloud._gen_key(context, context.user_id, key_name) private_key = result['private_key'] - try: - key_dir = os.path.join(FLAGS.keys_path, context.user_id) - if not os.path.exists(key_dir): - os.makedirs(key_dir) - key_path = os.path.join(key_dir, '%s.pem' % key_name) - with open(key_path, 'w') as f: - f.write(private_key) - except: - pass - except exception.Duplicate: + key_dir = os.path.join(FLAGS.keys_path, context.user_id) + if not os.path.exists(key_dir): + os.makedirs(key_dir) + key_path = os.path.join(key_dir, '%s.pem' % key_name) + with open(key_path, 'w') as f: + f.write(private_key) + except exception.Duplicate, os.error, IOError: pass return key_name diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 4f1445217..ce12ba4e0 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -3178,8 +3178,9 @@ def instance_metadata_delete_all(context, instance_id): @require_context @require_instance_exists -def instance_metadata_get_item(context, instance_id, key): - session = get_session() +def instance_metadata_get_item(context, instance_id, key, session=None): + if not session: + session = get_session() meta_result = session.query(models.InstanceMetadata).\ filter_by(instance_id=instance_id).\ @@ -3205,7 +3206,7 @@ def instance_metadata_update_or_create(context, instance_id, metadata): try: meta_ref = instance_metadata_get_item(context, instance_id, key, session) - except: + except exception.InstanceMetadataNotFound, e: meta_ref = models.InstanceMetadata() meta_ref.update({"key": key, "value": value, "instance_id": instance_id, @@ -3300,8 +3301,9 @@ def instance_type_extra_specs_delete(context, instance_type_id, key): @require_context -def instance_type_extra_specs_get_item(context, instance_type_id, key): - session = get_session() +def instance_type_extra_specs_get_item(context, instance_type_id, key, session=None): + if not session: + session = get_session() spec_result = session.query(models.InstanceTypeExtraSpecs).\ filter_by(instance_type_id=instance_type_id).\ @@ -3327,7 +3329,7 @@ def instance_type_extra_specs_update_or_create(context, instance_type_id, instance_type_id, key, session) - except: + except exception.InstanceTypeExtraSpecsNotFound, e: spec_ref = models.InstanceTypeExtraSpecs() spec_ref.update({"key": key, "value": value, "instance_type_id": instance_type_id, diff --git a/nova/image/__init__.py b/nova/image/__init__.py index a27d649d4..5447c8a3a 100644 --- a/nova/image/__init__.py +++ b/nova/image/__init__.py @@ -35,6 +35,7 @@ def _parse_image_ref(image_href): :param image_href: href of an image :returns: a tuple of the form (image_id, host, port) + :raises ValueError """ o = urlparse(image_href) @@ -72,7 +73,7 @@ def get_glance_client(image_href): try: (image_id, host, port) = _parse_image_ref(image_href) - except: + except ValueError: raise exception.InvalidImageRef(image_href=image_href) glance_client = GlanceClient(host, port) return (glance_client, image_id) diff --git a/nova/utils.py b/nova/utils.py index 4ea623cc1..1e2dbebb1 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -126,6 +126,22 @@ def fetchfile(url, target): def execute(*cmd, **kwargs): + """ + Helper method to execute command with optional retry. + + :cmd Passed to subprocess.Popen. + :process_input Send to opened process. + :addl_env Added to the processes env. + :check_exit_code Defaults to 0. Raise exception.ProcessExecutionError + unless program exits with this code. + :delay_on_retry True | False. Defaults to True. If set to True, wait a + short amount of time before retrying. + :attempts How many times to retry cmd. + + :raises exception.Error on receiving unknown arguments + :raises exception.ProcessExecutionError + """ + process_input = kwargs.pop('process_input', None) addl_env = kwargs.pop('addl_env', None) check_exit_code = kwargs.pop('check_exit_code', 0) @@ -790,7 +806,7 @@ def parse_server_string(server_str): (address, port) = server_str.split(':') return (address, port) - except: + except Exception: LOG.debug(_('Invalid server_string: %s' % server_str)) return ('', '') diff --git a/nova/virt/libvirt/connection.py b/nova/virt/libvirt/connection.py index 0acf25d28..4dd8f02e5 100644 --- a/nova/virt/libvirt/connection.py +++ b/nova/virt/libvirt/connection.py @@ -370,7 +370,7 @@ class LibvirtConnection(driver.ComputeDriver): """Returns the xml for the disk mounted at device""" try: doc = libxml2.parseDoc(xml) - except: + except Exception: return None ctx = doc.xpathNewContext() try: @@ -1103,7 +1103,7 @@ class LibvirtConnection(driver.ComputeDriver): try: doc = libxml2.parseDoc(xml) - except: + except Exception: return [] ctx = doc.xpathNewContext() @@ -1144,7 +1144,7 @@ class LibvirtConnection(driver.ComputeDriver): try: doc = libxml2.parseDoc(xml) - except: + except Exception: return [] ctx = doc.xpathNewContext() diff --git a/nova/virt/libvirt/vif.py b/nova/virt/libvirt/vif.py index eef582fac..4be60ce8a 100644 --- a/nova/virt/libvirt/vif.py +++ b/nova/virt/libvirt/vif.py @@ -25,6 +25,7 @@ from nova.network import linux_net from nova.virt.libvirt import netutils from nova import utils from nova.virt.vif import VIFDriver +from nova import exception LOG = logging.getLogger('nova.virt.libvirt.vif') @@ -128,7 +129,7 @@ class LibvirtOpenVswitchDriver(VIFDriver): utils.execute('sudo', 'ovs-vsctl', 'del-port', network['bridge'], dev) utils.execute('sudo', 'ip', 'link', 'delete', dev) - except: + except exception.ProcessExecutionError, e: LOG.warning(_("Failed while unplugging vif of instance '%s'"), instance['name']) - raise + raise e diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index 39afbd650..f75d4493c 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -441,7 +441,7 @@ class XenAPISession(object): params = None try: params = eval(exc.details[3]) - except: + except Exception: raise exc raise self.XenAPI.Failure(params) else: diff --git a/nova/vnc/proxy.py b/nova/vnc/proxy.py index c4603803b..2e3e38ca9 100644 --- a/nova/vnc/proxy.py +++ b/nova/vnc/proxy.py @@ -60,7 +60,7 @@ class WebsocketVNCProxy(object): break d = base64.b64encode(d) dest.send(d) - except: + except Exception: source.close() dest.close() @@ -72,7 +72,7 @@ class WebsocketVNCProxy(object): break d = base64.b64decode(d) dest.sendall(d) - except: + except Exception: source.close() dest.close() -- cgit From 27392e9412c7042b67947dbc103704d121d60e12 Mon Sep 17 00:00:00 2001 From: Justin Shepherd Date: Wed, 3 Aug 2011 20:48:17 -0500 Subject: Conditionals were not actually runing the tests when they were supposed to. Renamed example testcases --- nova/test.py | 2 ++ nova/tests/test_skip_examples.py | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) (limited to 'nova') diff --git a/nova/test.py b/nova/test.py index f1b4050f9..97cd3e0d0 100644 --- a/nova/test.py +++ b/nova/test.py @@ -79,6 +79,7 @@ class skip_if(object): """Wrapped skipper function.""" if self.condition: raise nose.SkipTest(self.message) + func(*args, **kw) _skipper.__name__ = func.__name__ _skipper.__doc__ = func.__doc__ return _skipper @@ -95,6 +96,7 @@ class skip_unless(object): """Wrapped skipper function.""" if not self.condition: raise nose.SkipTest(self.message) + func(*args, **kw) _skipper.__name__ = func.__name__ _skipper.__doc__ = func.__doc__ return _skipper diff --git a/nova/tests/test_skip_examples.py b/nova/tests/test_skip_examples.py index 41c3b1b5f..0ae3576f7 100644 --- a/nova/tests/test_skip_examples.py +++ b/nova/tests/test_skip_examples.py @@ -21,13 +21,13 @@ from nova import test class ExampleSkipTestCase(test.TestCase): @test.skip_test("Example usage of @test.skip_test()") - def test_skip_test(self): + def test_skip_test_example(self): self.fail("skip_test failed to work properly.") @test.skip_if(True, "Example usage of @test.skip_if()") - def test_skip_if_env_user_exists(self): + def test_skip_if_example(self): self.fail("skip_if failed to work properly.") @test.skip_unless(False, "Example usage of @test.skip_unless()") - def test_skip_unless_env_foo_exists(self): + def test_skip_unless_example(self): self.fail("skip_unless failed to work properly.") -- cgit From 6f3b63cd51549843b269cc6c138575a5af2337b2 Mon Sep 17 00:00:00 2001 From: Johannes Erdfelt Date: Thu, 4 Aug 2011 08:43:13 +0000 Subject: Revert should be sent to destination node and confirm should be sent to source node --- nova/compute/api.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/compute/api.py b/nova/compute/api.py index aae16d1da..1d09c69f6 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -888,7 +888,7 @@ class API(base.Base): params = {'migration_id': migration_ref['id']} self._cast_compute_message('revert_resize', context, instance_ref['uuid'], - migration_ref['source_compute'], + migration_ref['dest_compute'], params=params) self.db.migration_update(context, migration_ref['id'], @@ -908,7 +908,7 @@ class API(base.Base): params = {'migration_id': migration_ref['id']} self._cast_compute_message('confirm_resize', context, instance_ref['uuid'], - migration_ref['dest_compute'], + migration_ref['source_compute'], params=params) self.db.migration_update(context, migration_ref['id'], -- cgit From 144720ce87b3f0f469ed748780af55e32babc821 Mon Sep 17 00:00:00 2001 From: Justin Shepherd Date: Thu, 4 Aug 2011 09:29:43 -0500 Subject: Added in tests that verify tests are skipped appropriately. --- nova/tests/test_skip_examples.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) (limited to 'nova') diff --git a/nova/tests/test_skip_examples.py b/nova/tests/test_skip_examples.py index 0ae3576f7..033f1ea2e 100644 --- a/nova/tests/test_skip_examples.py +++ b/nova/tests/test_skip_examples.py @@ -20,6 +20,8 @@ from nova import test class ExampleSkipTestCase(test.TestCase): + test_counter = 0 + @test.skip_test("Example usage of @test.skip_test()") def test_skip_test_example(self): self.fail("skip_test failed to work properly.") @@ -31,3 +33,15 @@ class ExampleSkipTestCase(test.TestCase): @test.skip_unless(False, "Example usage of @test.skip_unless()") def test_skip_unless_example(self): self.fail("skip_unless failed to work properly.") + + @test.skip_if(False, "This test case should never be skipped.") + def test_001_increase_test_counter(self): + ExampleSkipTestCase.test_counter += 1 + + @test.skip_unless(True, "This test case should never be skipped.") + def test_002_increase_test_counter(self): + ExampleSkipTestCase.test_counter += 1 + + def test_003_verify_test_counter(self): + self.assertEquals(ExampleSkipTestCase.test_counter, 2, + "Tests were not skipped appropriately") -- cgit From dbbc07750c2624b61af4f824d29c99bffe5f722f Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Thu, 4 Aug 2011 09:40:51 -0500 Subject: Fixed rescue and unrescue. --- nova/compute/manager.py | 25 ++++++++++++------------- nova/exception.py | 3 ++- nova/virt/xenapi/vmops.py | 3 ++- 3 files changed, 16 insertions(+), 15 deletions(-) (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 8780ad921..cf4ee229d 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -648,38 +648,37 @@ class ComputeManager(manager.SchedulerDependentManager): @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id()) @checks_instance_lock - def rescue_instance(self, context, instance_uuid): + def rescue_instance(self, context, instance_id): """Rescue an instance on this host.""" context = context.elevated() - instance_ref = self.db.instance_get_by_uuid(context, instance_uuid) - LOG.audit(_('instance %s: rescuing'), instance_uuid, context=context) + instance_ref = self.db.instance_get(context, instance_id) + LOG.audit(_('instance %s: rescuing'), instance_id, context=context) self.db.instance_set_state(context, - instance_uuid, + instance_id, power_state.NOSTATE, 'rescuing') _update_state = lambda result: self._update_state_callback( - self, context, instance_uuid, result) + self, context, instance_id, result) network_info = self._get_instance_nw_info(context, instance_ref) self.driver.rescue(context, instance_ref, _update_state, network_info) - self._update_state(context, instance_uuid) + self._update_state(context, instance_id) @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id()) @checks_instance_lock - def unrescue_instance(self, context, instance_uuid): + def unrescue_instance(self, context, instance_id): """Rescue an instance on this host.""" context = context.elevated() - instance_ref = self.db.instance_get_by_uuid(context, instance_uuid) - LOG.audit(_('instance %s: unrescuing'), instance_uuid, - context=context) + instance_ref = self.db.instance_get(context, instance_id) + LOG.audit(_('instance %s: unrescuing'), instance_id, context=context) self.db.instance_set_state(context, - instance_uuid, + instance_id, power_state.NOSTATE, 'unrescuing') _update_state = lambda result: self._update_state_callback( - self, context, instance_uuid, result) + self, context, instance_id, result) network_info = self._get_instance_nw_info(context, instance_ref) self.driver.unrescue(instance_ref, _update_state, network_info) - self._update_state(context, instance_uuid) + self._update_state(context, instance_id) @staticmethod def _update_state_callback(self, context, instance_id, result): diff --git a/nova/exception.py b/nova/exception.py index 68e6ac937..8503f8e3e 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -122,7 +122,8 @@ def wrap_exception(notifier=None, publisher_id=None, event_type=None, LOG.exception(_('Uncaught exception')) #logging.error(traceback.extract_stack(exc_traceback)) raise Error(str(e)) - raise + # Don't mask the real exception. + raise Exception(e) return wraps(f)(wrapped) return inner diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index b3b812a48..9979a0165 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -935,7 +935,8 @@ class VMOps(object): 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] + #NOTE(jk0): Find the root partition, not swap. + vbd_ref = self._session.get_xenapi().VM.get_VBDs(vm_ref)[1] vdi_ref = self._session.get_xenapi().VBD.get_record(vbd_ref)["VDI"] rescue_vbd_ref = VMHelper.create_vbd(self._session, rescue_vm_ref, vdi_ref, 1, False) -- cgit From a3abff9c6243ecfddac72167320c74dce7398941 Mon Sep 17 00:00:00 2001 From: Ed Leafe Date: Thu, 4 Aug 2011 15:02:28 +0000 Subject: Changed the definition of the 'action' dict to always occur. --- nova/virt/xenapi_conn.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) (limited to 'nova') diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index 39afbd650..8ddbbd33a 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -394,11 +394,10 @@ class XenAPISession(object): try: name = self._session.xenapi.task.get_name_label(task) status = self._session.xenapi.task.get_status(task) + # Ensure action is never > 255 + action = dict(action=name[:255], error=None) if id: - action = dict( - instance_id=int(id), - action=name[0:255], # Ensure action is never > 255 - error=None) + action["instance_id"] = int(id) if status == "pending": return elif status == "success": -- cgit From 4eed25b0f01b510d0d90e864eef7f285964ab293 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Thu, 4 Aug 2011 11:11:12 -0400 Subject: The OSAPI v1.0 image create POST request should store the instance_id as a Glance property. --- nova/api/openstack/images.py | 8 ++++++-- nova/tests/api/openstack/test_images.py | 3 +++ 2 files changed, 9 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index 0834adfa5..c76738d30 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -116,13 +116,17 @@ class ControllerV10(Controller): try: image_name = image["name"] - server_id = image["serverId"] + instance_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) + props = {'instance_id': instance_id} + image = self._compute_service.snapshot(context, + instance_id, + image_name, + extra_properties=props) return dict(image=self.get_builder(req).build(image, detail=True)) diff --git a/nova/tests/api/openstack/test_images.py b/nova/tests/api/openstack/test_images.py index 942c0b333..6486a069c 100644 --- a/nova/tests/api/openstack/test_images.py +++ b/nova/tests/api/openstack/test_images.py @@ -1031,6 +1031,9 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): req.headers["content-type"] = "application/json" response = req.get_response(fakes.wsgi_app()) self.assertEqual(200, response.status_int) + image_meta = json.loads(response.body)['image'] + self.assertEqual(123, image_meta['serverId']) + self.assertEqual('Snapshot 1', image_meta['name']) def test_create_snapshot_no_name(self): """Name is required for snapshots""" -- cgit From ae6b54cc1748d7b9c7bfa55374e9355665343b82 Mon Sep 17 00:00:00 2001 From: William Wolf Date: Thu, 4 Aug 2011 11:45:24 -0400 Subject: add test for spawning a xenapi instance with an empty dns list --- nova/tests/test_xenapi.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index a795b3c74..5f3aec700 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -397,7 +397,7 @@ class XenAPIVMTestCase(test.TestCase): instance_type_id="3", os_type="linux", architecture="x86-64", instance_id=1, check_injection=False, - create_record=True): + create_record=True, empty_dns=False): stubs.stubout_loopingcall_start(self.stubs) if create_record: values = {'id': instance_id, @@ -426,12 +426,22 @@ class XenAPIVMTestCase(test.TestCase): 'label': 'fake', 'mac': 'DE:AD:BE:EF:00:00', 'rxtx_cap': 3})] + if empty_dns: + network_info[0][1]['dns'] = [] + 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) self.assertTrue(instance.architecture) + def test_spawn_empty_dns(self): + """"Test spawning with an empty dns list""" + self._test_spawn(glance_stubs.FakeGlance.IMAGE_VHD, None, None, + os_type="linux", architecture="x86-64", + empty_dns=True) + self.check_vm_params_for_linux() + def test_spawn_not_enough_memory(self): self.assertRaises(Exception, self._test_spawn, -- cgit From 5826a793e7e05db8ccc14d15326245246fb652d4 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Thu, 4 Aug 2011 12:48:13 -0400 Subject: fixing typo --- nova/api/openstack/create_instance_helper.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index 178685889..2a8e7fd7e 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -332,7 +332,8 @@ class ServerXMLDeserializer(wsgi.XMLDeserializer): if value: data[attribute] = value metadata_node = self.find_first_child_named(node, 'metadata') - data['metadata'] = self.extract_metadata(metadata_node) + metadata = self.metadata_deserializer.extract_metadata(metadata_node) + data['metadata'] = metadata return data def create(self, string): -- cgit From 8fd335e309ea48d1460ee4d04105e429575f47f6 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Thu, 4 Aug 2011 10:03:15 -0700 Subject: fix up new test_server_actions.py file for flags verbosity change --- nova/tests/api/openstack/test_server_actions.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) (limited to 'nova') diff --git a/nova/tests/api/openstack/test_server_actions.py b/nova/tests/api/openstack/test_server_actions.py index fc08a2d53..baec5f3d1 100644 --- a/nova/tests/api/openstack/test_server_actions.py +++ b/nova/tests/api/openstack/test_server_actions.py @@ -8,7 +8,6 @@ import webob from nova import context from nova import db -from nova import flags from nova import utils from nova.api.openstack import create_instance_helper from nova.compute import instance_types @@ -19,10 +18,6 @@ from nova.tests.api.openstack import common from nova.tests.api.openstack import fakes -FLAGS = flags.FLAGS -FLAGS.verbose = True - - def return_server_by_id(context, id): return _get_instance() @@ -102,6 +97,7 @@ class ServerActionsTest(test.TestCase): def setUp(self): self.maxDiff = None super(ServerActionsTest, self).setUp() + self.set_flags_verbosity(True) self.stubs = stubout.StubOutForTesting() fakes.FakeAuthManager.reset_fake_data() fakes.FakeAuthDatabase.data = {} -- cgit From 24b308ebb4f7fff5520383248170bfb7d51c6755 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Thu, 4 Aug 2011 10:17:55 -0700 Subject: test_host_filter setUp needs to call its super --- nova/tests/scheduler/test_host_filter.py | 1 + 1 file changed, 1 insertion(+) (limited to 'nova') diff --git a/nova/tests/scheduler/test_host_filter.py b/nova/tests/scheduler/test_host_filter.py index b016923c4..7e664d3f9 100644 --- a/nova/tests/scheduler/test_host_filter.py +++ b/nova/tests/scheduler/test_host_filter.py @@ -54,6 +54,7 @@ class HostFilterTestCase(test.TestCase): 'host_name-label': 'xs-%s' % multiplier} def setUp(self): + super(HostFilterTestCase, self).setUp() default_host_filter = 'nova.scheduler.host_filter.AllHostsFilter' self.flags(default_host_filter=default_host_filter) self.instance_type = dict(name='tiny', -- cgit From 7f35619377593b5bb30425984999e8bb9ed2bdb2 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Thu, 4 Aug 2011 10:34:36 -0700 Subject: fix pep8 issues that are in trunk --- nova/compute/manager.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 94bac9be4..9f566dea7 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -167,10 +167,12 @@ class ComputeManager(manager.SchedulerDependentManager): 'nova-compute restart.'), locals()) self.reboot_instance(context, instance['id']) elif drv_state == power_state.RUNNING: - try: # Hyper-V and VMWareAPI drivers will raise and exception + # Hyper-V and VMWareAPI drivers will raise and exception + try: self.driver.ensure_filtering_rules_for_instance(instance) except NotImplementedError: - LOG.warning(_('Hypervisor driver does not support firewall rules')) + LOG.warning(_('Hypervisor driver does not ' + 'support firewall rules')) def _update_state(self, context, instance_id, state=None): """Update the state of an instance from the driver info.""" -- cgit From 194f0e4909490c4b626bd211c46121ae37db20dd Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Thu, 4 Aug 2011 10:43:42 -0700 Subject: uses 2.6.0 novaclient (OS API 1.1 support) --- nova/compute/manager.py | 5 +++-- nova/scheduler/api.py | 13 +++++++------ nova/scheduler/zone_aware_scheduler.py | 9 +++++---- nova/scheduler/zone_manager.py | 7 ++++--- nova/tests/scheduler/test_scheduler.py | 12 +++++++----- nova/tests/test_zones.py | 1 - 6 files changed, 26 insertions(+), 21 deletions(-) (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 94bac9be4..843f6d490 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -167,10 +167,11 @@ class ComputeManager(manager.SchedulerDependentManager): 'nova-compute restart.'), locals()) self.reboot_instance(context, instance['id']) elif drv_state == power_state.RUNNING: - try: # Hyper-V and VMWareAPI drivers will raise and exception + try: # Hyper-V and VMWareAPI drivers will raise and exception self.driver.ensure_filtering_rules_for_instance(instance) except NotImplementedError: - LOG.warning(_('Hypervisor driver does not support firewall rules')) + LOG.warning( + _('Hypervisor driver does not support firewall rules')) def _update_state(self, context, instance_id, state=None): """Update the state of an instance from the driver info.""" diff --git a/nova/scheduler/api.py b/nova/scheduler/api.py index 137b671c0..55cea5f8f 100644 --- a/nova/scheduler/api.py +++ b/nova/scheduler/api.py @@ -17,7 +17,8 @@ Handles all requests relating to schedulers. """ -import novaclient +from novaclient import v1_1 as novaclient +from novaclient import exceptions as novaclient_exceptions from nova import db from nova import exception @@ -112,7 +113,7 @@ def _wrap_method(function, self): def _process(func, zone): """Worker stub for green thread pool. Give the worker an authenticated nova client and zone info.""" - nova = novaclient.OpenStack(zone.username, zone.password, None, + nova = novaclient.Client(zone.username, zone.password, None, zone.api_url) nova.authenticate() return func(nova, zone) @@ -132,10 +133,10 @@ def call_zone_method(context, method_name, errors_to_ignore=None, zones = db.zone_get_all(context) for zone in zones: try: - nova = novaclient.OpenStack(zone.username, zone.password, None, + nova = novaclient.Client(zone.username, zone.password, None, zone.api_url) nova.authenticate() - except novaclient.exceptions.BadRequest, e: + except novaclient_exceptions.BadRequest, e: url = zone.api_url LOG.warn(_("Failed request to zone; URL=%(url)s: %(e)s") % locals()) @@ -188,7 +189,7 @@ def _issue_novaclient_command(nova, zone, collection, if method_name in ['find', 'findall']: try: return getattr(manager, method_name)(**kwargs) - except novaclient.NotFound: + except novaclient_exceptions.NotFound: url = zone.api_url LOG.debug(_("%(collection)s.%(method_name)s didn't find " "anything matching '%(kwargs)s' on '%(url)s'" % @@ -200,7 +201,7 @@ def _issue_novaclient_command(nova, zone, collection, item = args.pop(0) try: result = manager.get(item) - except novaclient.NotFound: + except novaclient_exceptions.NotFound: url = zone.api_url LOG.debug(_("%(collection)s '%(item)s' not found on '%(url)s'" % locals())) diff --git a/nova/scheduler/zone_aware_scheduler.py b/nova/scheduler/zone_aware_scheduler.py index d99d7214c..7e813af7e 100644 --- a/nova/scheduler/zone_aware_scheduler.py +++ b/nova/scheduler/zone_aware_scheduler.py @@ -24,7 +24,9 @@ import operator import json import M2Crypto -import novaclient + +from novaclient import v1_1 as novaclient +from novaclient import exceptions as novaclient_exceptions from nova import crypto from nova import db @@ -117,10 +119,9 @@ class ZoneAwareScheduler(driver.Scheduler): % locals()) nova = None try: - nova = novaclient.OpenStack(zone.username, zone.password, None, - url) + nova = novaclient.Client(zone.username, zone.password, None, url) nova.authenticate() - except novaclient.exceptions.BadRequest, e: + except novaclient_exceptions.BadRequest, e: raise exception.NotAuthorized(_("Bad credentials attempting " "to talk to zone at %(url)s.") % locals()) diff --git a/nova/scheduler/zone_manager.py b/nova/scheduler/zone_manager.py index efdac06e1..97bdf3d44 100644 --- a/nova/scheduler/zone_manager.py +++ b/nova/scheduler/zone_manager.py @@ -18,10 +18,11 @@ ZoneManager oversees all communications with child Zones. """ import datetime -import novaclient import thread import traceback +from novaclient import v1_1 as novaclient + from eventlet import greenpool from nova import db @@ -89,8 +90,8 @@ class ZoneState(object): def _call_novaclient(zone): """Call novaclient. Broken out for testing purposes.""" - client = novaclient.OpenStack(zone.username, zone.password, None, - zone.api_url) + client = novaclient.Client(zone.username, zone.password, None, + zone.api_url) return client.zones.info()._info diff --git a/nova/tests/scheduler/test_scheduler.py b/nova/tests/scheduler/test_scheduler.py index 6a56a57db..bd2394adf 100644 --- a/nova/tests/scheduler/test_scheduler.py +++ b/nova/tests/scheduler/test_scheduler.py @@ -21,9 +21,11 @@ Tests For Scheduler import datetime import mox -import novaclient.exceptions import stubout +from novaclient import v1_1 as novaclient +from novaclient import exceptions as novaclient_exceptions + from mox import IgnoreArg from nova import context from nova import db @@ -1039,10 +1041,10 @@ class FakeServerCollection(object): class FakeEmptyServerCollection(object): def get(self, f): - raise novaclient.NotFound(1) + raise novaclient_exceptions.NotFound(1) def find(self, name): - raise novaclient.NotFound(2) + raise novaclient_exceptions.NotFound(2) class FakeNovaClient(object): @@ -1088,7 +1090,7 @@ class FakeZonesProxy(object): raise Exception('testing') -class FakeNovaClientOpenStack(object): +class FakeNovaClientZones(object): def __init__(self, *args, **kwargs): self.zones = FakeZonesProxy() @@ -1101,7 +1103,7 @@ class CallZoneMethodTest(test.TestCase): super(CallZoneMethodTest, self).setUp() self.stubs = stubout.StubOutForTesting() self.stubs.Set(db, 'zone_get_all', zone_get_all) - self.stubs.Set(novaclient, 'OpenStack', FakeNovaClientOpenStack) + self.stubs.Set(novaclient, 'Client', FakeNovaClientZones) def tearDown(self): self.stubs.UnsetAll() diff --git a/nova/tests/test_zones.py b/nova/tests/test_zones.py index a943fee27..9efa23015 100644 --- a/nova/tests/test_zones.py +++ b/nova/tests/test_zones.py @@ -18,7 +18,6 @@ Tests For ZoneManager import datetime import mox -import novaclient from nova import context from nova import db -- cgit From 7e70e5ff7c39862a328b304f13778936e422a212 Mon Sep 17 00:00:00 2001 From: Matthew Hooker Date: Thu, 4 Aug 2011 14:37:24 -0400 Subject: remove unused reference to exception object --- nova/virt/libvirt/vif.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/virt/libvirt/vif.py b/nova/virt/libvirt/vif.py index 4be60ce8a..711b05bae 100644 --- a/nova/virt/libvirt/vif.py +++ b/nova/virt/libvirt/vif.py @@ -129,7 +129,7 @@ class LibvirtOpenVswitchDriver(VIFDriver): utils.execute('sudo', 'ovs-vsctl', 'del-port', network['bridge'], dev) utils.execute('sudo', 'ip', 'link', 'delete', dev) - except exception.ProcessExecutionError, e: + except exception.ProcessExecutionError: LOG.warning(_("Failed while unplugging vif of instance '%s'"), instance['name']) - raise e + raise -- cgit From 7381c55a3549ead494c6bd13dece17f293442940 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Thu, 4 Aug 2011 11:40:07 -0700 Subject: added NOVA_VERSION to novarc --- nova/auth/novarc.template | 1 + 1 file changed, 1 insertion(+) (limited to 'nova') diff --git a/nova/auth/novarc.template b/nova/auth/novarc.template index d05c099d7..978ffb210 100644 --- a/nova/auth/novarc.template +++ b/nova/auth/novarc.template @@ -16,3 +16,4 @@ export NOVA_API_KEY="%(access)s" export NOVA_USERNAME="%(user)s" export NOVA_PROJECT_ID="%(project)s" export NOVA_URL="%(os)s" +export NOVA_VERSION="1.1" -- cgit From bdbf3efcadeda46e66787edee344def84dccef73 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Thu, 4 Aug 2011 11:50:20 -0700 Subject: OS v1.1 is now the default into novarc --- nova/flags.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/flags.py b/nova/flags.py index 12c6d1356..eb6366ed9 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -317,7 +317,7 @@ DEFINE_string('osapi_extensions_path', '/var/lib/nova/extensions', DEFINE_string('osapi_host', '$my_ip', 'ip of api server') DEFINE_string('osapi_scheme', 'http', 'prefix for openstack') DEFINE_integer('osapi_port', 8774, 'OpenStack API port') -DEFINE_string('osapi_path', '/v1.0/', 'suffix for openstack') +DEFINE_string('osapi_path', '/v1.1/', 'suffix for openstack') DEFINE_integer('osapi_max_limit', 1000, 'max number of items returned in a collection response') -- cgit From 7f35b6ee93ea2192a8c81a7c17e2e74694a397a1 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Thu, 4 Aug 2011 12:07:07 -0700 Subject: remove set_flags_verbosity.. it's not needed --- nova/test.py | 5 ----- nova/tests/api/openstack/test_accounts.py | 3 +-- nova/tests/api/openstack/test_adminapi.py | 2 +- nova/tests/api/openstack/test_server_actions.py | 2 +- nova/tests/api/openstack/test_servers.py | 2 +- nova/tests/api/openstack/test_users.py | 3 +-- nova/tests/api/openstack/test_zones.py | 3 +-- nova/tests/integrated/integrated_helpers.py | 2 +- 8 files changed, 7 insertions(+), 15 deletions(-) (limited to 'nova') diff --git a/nova/test.py b/nova/test.py index fa5a662fd..ff85deeda 100644 --- a/nova/test.py +++ b/nova/test.py @@ -156,11 +156,6 @@ class TestCase(unittest.TestCase): FLAGS.Reset() for k, v in self._original_flags.iteritems(): setattr(FLAGS, k, v) - FLAGS.verbose = self._original_flags_verbosity - - def set_flags_verbosity(self, verbose=True): - """Set the FLAGS verbosity for a test""" - FLAGS.verbose = verbose def start_service(self, name, host=None, **kwargs): host = host and host or uuid.uuid4().hex diff --git a/nova/tests/api/openstack/test_accounts.py b/nova/tests/api/openstack/test_accounts.py index b1ca546d2..707a2599f 100644 --- a/nova/tests/api/openstack/test_accounts.py +++ b/nova/tests/api/openstack/test_accounts.py @@ -35,8 +35,7 @@ def fake_admin_check(self, req): class AccountsTest(test.TestCase): def setUp(self): super(AccountsTest, self).setUp() - self.flags(allow_admin_api=True) - self.set_flags_verbosity(True) + self.flags(verbose=True, allow_admin_api=True) self.stubs.Set(accounts.Controller, '__init__', fake_init) self.stubs.Set(accounts.Controller, '_check_admin', diff --git a/nova/tests/api/openstack/test_adminapi.py b/nova/tests/api/openstack/test_adminapi.py index 9d03a7f9d..c9e66dc4c 100644 --- a/nova/tests/api/openstack/test_adminapi.py +++ b/nova/tests/api/openstack/test_adminapi.py @@ -28,7 +28,7 @@ class AdminAPITest(test.TestCase): super(AdminAPITest, self).setUp() fakes.stub_out_networking(self.stubs) fakes.stub_out_rate_limiting(self.stubs) - self.set_flags_verbosity(True) + self.flags(verbose=True) def test_admin_enabled(self): self.flags(allow_admin_api=True) diff --git a/nova/tests/api/openstack/test_server_actions.py b/nova/tests/api/openstack/test_server_actions.py index baec5f3d1..562cefe90 100644 --- a/nova/tests/api/openstack/test_server_actions.py +++ b/nova/tests/api/openstack/test_server_actions.py @@ -97,7 +97,7 @@ class ServerActionsTest(test.TestCase): def setUp(self): self.maxDiff = None super(ServerActionsTest, self).setUp() - self.set_flags_verbosity(True) + self.flags(verbose=True) self.stubs = stubout.StubOutForTesting() fakes.FakeAuthManager.reset_fake_data() fakes.FakeAuthDatabase.data = {} diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index bb411af10..0477f6d92 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -230,7 +230,7 @@ class ServersTest(test.TestCase): def setUp(self): self.maxDiff = None super(ServersTest, self).setUp() - self.set_flags_verbosity(True) + self.flags(verbose=True) fakes.stub_out_networking(self.stubs) fakes.stub_out_rate_limiting(self.stubs) fakes.stub_out_key_pair_funcs(self.stubs) diff --git a/nova/tests/api/openstack/test_users.py b/nova/tests/api/openstack/test_users.py index 649538f11..1d133f9ab 100644 --- a/nova/tests/api/openstack/test_users.py +++ b/nova/tests/api/openstack/test_users.py @@ -35,8 +35,7 @@ def fake_admin_check(self, req): class UsersTest(test.TestCase): def setUp(self): super(UsersTest, self).setUp() - self.flags(allow_admin_api=True) - self.set_flags_verbosity(True) + self.flags(verbose=True, allow_admin_api=True) self.stubs.Set(users.Controller, '__init__', fake_init) self.stubs.Set(users.Controller, '_check_admin', diff --git a/nova/tests/api/openstack/test_zones.py b/nova/tests/api/openstack/test_zones.py index 442ee4313..4a46a5764 100644 --- a/nova/tests/api/openstack/test_zones.py +++ b/nova/tests/api/openstack/test_zones.py @@ -94,8 +94,7 @@ def zone_select(context, specs): class ZonesTest(test.TestCase): def setUp(self): super(ZonesTest, self).setUp() - self.flags(allow_admin_api=True) - self.set_flags_verbosity(True) + self.flags(verbose=True, allow_admin_api=True) fakes.stub_out_networking(self.stubs) fakes.stub_out_rate_limiting(self.stubs) diff --git a/nova/tests/integrated/integrated_helpers.py b/nova/tests/integrated/integrated_helpers.py index 049ddbc60..fb2f88502 100644 --- a/nova/tests/integrated/integrated_helpers.py +++ b/nova/tests/integrated/integrated_helpers.py @@ -148,7 +148,7 @@ class _IntegratedTestBase(test.TestCase): f = self._get_flags() self.flags(**f) - self.set_flags_verbosity(True) + self.flags(verbose=True) def fake_get_image_service(image_href): image_id = int(str(image_href).split('/')[-1]) -- cgit From b02fb7f6a3c35673122d7f409d8c999cc9fbae46 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Thu, 4 Aug 2011 12:21:09 -0700 Subject: remove storing original flags verbosity --- nova/test.py | 1 - 1 file changed, 1 deletion(-) (limited to 'nova') diff --git a/nova/test.py b/nova/test.py index ff85deeda..5760d7a82 100644 --- a/nova/test.py +++ b/nova/test.py @@ -100,7 +100,6 @@ class TestCase(unittest.TestCase): self.injected = [] self._services = [] self._original_flags = FLAGS.FlagValuesDict() - self._original_flags_verbosity = FLAGS.verbose def tearDown(self): """Runs after each test method to tear down test environment.""" -- cgit From cae90fb1d5a798673416e51c5cf1f7db6cbd6c23 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Thu, 4 Aug 2011 14:30:12 -0500 Subject: Fixed rescue and unrescue. --- nova/compute/manager.py | 10 ++++------ nova/virt/xenapi/vmops.py | 4 ++-- nova/virt/xenapi_conn.py | 8 ++++---- 3 files changed, 10 insertions(+), 12 deletions(-) (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index cf4ee229d..c6af0dfbe 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -651,16 +651,15 @@ class ComputeManager(manager.SchedulerDependentManager): def rescue_instance(self, context, instance_id): """Rescue an instance on this host.""" context = context.elevated() + self._update_state(context, instance_id) instance_ref = self.db.instance_get(context, instance_id) LOG.audit(_('instance %s: rescuing'), instance_id, context=context) self.db.instance_set_state(context, instance_id, power_state.NOSTATE, 'rescuing') - _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(context, instance_ref, _update_state, network_info) + self.driver.rescue(context, instance_ref, None, network_info) self._update_state(context, instance_id) @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id()) @@ -668,16 +667,15 @@ class ComputeManager(manager.SchedulerDependentManager): def unrescue_instance(self, context, instance_id): """Rescue an instance on this host.""" context = context.elevated() + self._update_state(context, instance_id) instance_ref = self.db.instance_get(context, instance_id) LOG.audit(_('instance %s: unrescuing'), instance_id, context=context) self.db.instance_set_state(context, instance_id, power_state.NOSTATE, 'unrescuing') - _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.unrescue(instance_ref, _update_state, network_info) + self.driver.unrescue(instance_ref, None, network_info) self._update_state(context, instance_id) @staticmethod diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 9979a0165..5badbac31 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -914,7 +914,7 @@ class VMOps(object): True) self._wait_with_callback(instance.id, task, callback) - def rescue(self, context, instance, callback, network_info): + def rescue(self, context, instance, _callback, network_info): """Rescue the specified instance. - shutdown the instance VM. @@ -943,7 +943,7 @@ class VMOps(object): self._session.call_xenapi("Async.VBD.plug", rescue_vbd_ref) - def unrescue(self, instance, callback): + def unrescue(self, instance, _callback): """Unrescue the specified instance. - unplug the instance VM's disk from the rescue VM. diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index 39afbd650..9dcbb3cf9 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -242,13 +242,13 @@ class XenAPIConnection(driver.ComputeDriver): """resume the specified instance""" self._vmops.resume(instance, callback) - def rescue(self, context, instance, callback, network_info): + def rescue(self, context, instance, _callback, network_info): """Rescue the specified instance""" - self._vmops.rescue(context, instance, callback, network_info) + self._vmops.rescue(context, instance, _callback, network_info) - def unrescue(self, instance, callback, network_info): + def unrescue(self, instance, _callback, network_info): """Unrescue the specified instance""" - self._vmops.unrescue(instance, callback) + self._vmops.unrescue(instance, _callback) def poll_rescued_instances(self, timeout): """Poll for rescued instances""" -- cgit From c56d20383a432aa2c83e7e3c2beebdb49cbe9efc Mon Sep 17 00:00:00 2001 From: Matthew Hooker Date: Thu, 4 Aug 2011 15:30:39 -0400 Subject: fix syntax error --- nova/cloudpipe/pipelib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/cloudpipe/pipelib.py b/nova/cloudpipe/pipelib.py index 46549c9d7..2c4673f9e 100644 --- a/nova/cloudpipe/pipelib.py +++ b/nova/cloudpipe/pipelib.py @@ -147,6 +147,6 @@ class CloudPipe(object): key_path = os.path.join(key_dir, '%s.pem' % key_name) with open(key_path, 'w') as f: f.write(private_key) - except exception.Duplicate, os.error, IOError: + except (exception.Duplicate, os.error, IOError): pass return key_name -- cgit From 12404f486a825dc8afdc45db0b10347a3a782e6d Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Thu, 4 Aug 2011 14:41:54 -0500 Subject: Removed temporary debugging raise. --- nova/exception.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/exception.py b/nova/exception.py index 8503f8e3e..68e6ac937 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -122,8 +122,7 @@ def wrap_exception(notifier=None, publisher_id=None, event_type=None, LOG.exception(_('Uncaught exception')) #logging.error(traceback.extract_stack(exc_traceback)) raise Error(str(e)) - # Don't mask the real exception. - raise Exception(e) + raise return wraps(f)(wrapped) return inner -- cgit From f22c19c6f78451074c33fe8da855755574cb6b49 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Thu, 4 Aug 2011 15:51:41 -0400 Subject: Split serverXMLDeserializers into v1.0 and v1.1 --- nova/api/openstack/create_instance_helper.py | 48 +++++++++++++++++++++++++ nova/api/openstack/servers.py | 7 +++- nova/tests/api/openstack/test_server_actions.py | 4 +-- nova/tests/api/openstack/test_servers.py | 2 +- 4 files changed, 57 insertions(+), 4 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index 2a8e7fd7e..832c890b6 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -304,6 +304,54 @@ class ServerXMLDeserializer(wsgi.XMLDeserializer): metadata_deserializer = common.MetadataXMLDeserializer() + def create(self, string): + """Deserialize an xml-formatted server create request""" + dom = minidom.parseString(string) + server = self._extract_server(dom) + return {'body': {'server': server}} + + def _extract_server(self, node): + """Marshal the server attribute of a parsed request""" + server = {} + server_node = self.find_first_child_named(node, 'server') + + attributes = ["name", "imageId", "flavorId", "adminPass"] + for attr in attributes: + if server_node.getAttribute(attr): + server[attr] = server_node.getAttribute(attr) + + metadata_node = self.find_first_child_named(server_node, "metadata") + server["metadata"] = self.metadata_deserializer.extract_metadata( + metadata_node) + + server["personality"] = self._extract_personality(server_node) + + return server + + def _extract_personality(self, server_node): + """Marshal the personality attribute of a parsed request""" + node = self.find_first_child_named(server_node, "personality") + personality = [] + if node is not None: + for file_node in self.find_children_named(node, "file"): + item = {} + if file_node.hasAttribute("path"): + item["path"] = file_node.getAttribute("path") + item["contents"] = self.extract_text(file_node) + personality.append(item) + return personality + + +class ServerXMLDeserializerV11(wsgi.MetadataXMLDeserializer): + """ + Deserializer to handle xml-formatted server create requests. + + Handles standard server attributes as well as optional metadata + and personality attributes + """ + + metadata_deserializer = common.MetadataXMLDeserializer() + def action(self, string): dom = minidom.parseString(string) action_node = dom.childNodes[0] diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index d17714371..3acc4510e 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -891,8 +891,13 @@ def create_resource(version='1.0'): 'application/xml': xml_serializer, } + xml_deserializer = { + '1.0': helper.ServerXMLDeserializer(), + '1.1': helper.ServerXMLDeserializerV11(), + }[version] + body_deserializers = { - 'application/xml': helper.ServerXMLDeserializer(), + 'application/xml': xml_deserializer, } serializer = wsgi.ResponseSerializer(body_serializers, headers_serializer) diff --git a/nova/tests/api/openstack/test_server_actions.py b/nova/tests/api/openstack/test_server_actions.py index fc08a2d53..8f4cfacf8 100644 --- a/nova/tests/api/openstack/test_server_actions.py +++ b/nova/tests/api/openstack/test_server_actions.py @@ -734,10 +734,10 @@ class ServerActionsTestV11(test.TestCase): self.assertTrue(response.headers['Location']) -class TestServerActionXMLDeserializer(test.TestCase): +class TestServerActionXMLDeserializerV11(test.TestCase): def setUp(self): - self.deserializer = create_instance_helper.ServerXMLDeserializer() + self.deserializer = create_instance_helper.ServerXMLDeserializerV11() def tearDown(self): pass diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 1349b289d..748573cce 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -2181,7 +2181,7 @@ class TestServerCreateRequestXMLDeserializerV11(test.TestCase): def setUp(self): super(TestServerCreateRequestXMLDeserializerV11, self).setUp() - self.deserializer = create_instance_helper.ServerXMLDeserializer() + self.deserializer = create_instance_helper.ServerXMLDeserializerV11() def test_minimal_request(self): serial_request = """ -- cgit From b8d4004e9de28dae47c6a1772a0caac965f7a69f Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Thu, 4 Aug 2011 15:16:42 -0500 Subject: Fixed rescue unit tests. --- nova/tests/test_xenapi.py | 6 ++++++ nova/virt/xenapi/vmops.py | 15 +++++++++------ 2 files changed, 15 insertions(+), 6 deletions(-) (limited to 'nova') diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index a795b3c74..1bc55027a 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -170,6 +170,10 @@ def reset_network(*args): pass +def _find_rescue_vbd_ref(*args): + pass + + class XenAPIVMTestCase(test.TestCase): """Unit tests for VM operations.""" def setUp(self): @@ -189,6 +193,8 @@ class XenAPIVMTestCase(test.TestCase): stubs.stubout_stream_disk(self.stubs) stubs.stubout_is_vdi_pv(self.stubs) self.stubs.Set(vmops.VMOps, 'reset_network', reset_network) + self.stubs.Set(vmops.VMOps, '_find_rescue_vbd_ref', + _find_rescue_vbd_ref) stubs.stub_out_vm_methods(self.stubs) glance_stubs.stubout_glance_client(self.stubs) fake_utils.stub_out_utils_execute(self.stubs) diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 5badbac31..81c6c6967 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -743,6 +743,14 @@ class VMOps(object): except self.XenAPI.Failure, exc: LOG.exception(exc) + def _find_rescue_vbd_ref(self, vm_ref, rescue_vm_ref): + """Find and return the rescue VM's vbd_ref.""" + vbd_ref = self._session.get_xenapi().VM.get_VBDs(vm_ref)[1] + vdi_ref = self._session.get_xenapi().VBD.get_record(vbd_ref)["VDI"] + + return VMHelper.create_vbd(self._session, rescue_vm_ref, vdi_ref, 1, + False) + def _shutdown_rescue(self, rescue_vm_ref): """Shutdown a rescue instance.""" self._session.call_xenapi("Async.VM.hard_shutdown", rescue_vm_ref) @@ -934,12 +942,7 @@ class VMOps(object): instance._rescue = True self.spawn_rescue(context, instance, network_info) rescue_vm_ref = VMHelper.lookup(self._session, instance.name) - - #NOTE(jk0): Find the root partition, not swap. - vbd_ref = self._session.get_xenapi().VM.get_VBDs(vm_ref)[1] - vdi_ref = self._session.get_xenapi().VBD.get_record(vbd_ref)["VDI"] - rescue_vbd_ref = VMHelper.create_vbd(self._session, rescue_vm_ref, - vdi_ref, 1, False) + rescue_vbd_ref = self._find_rescue_vbd_ref(vm_ref, rescue_vm_ref) self._session.call_xenapi("Async.VBD.plug", rescue_vbd_ref) -- cgit From 9ce80fc74b3ea4513369b795d1e6891d6dfa8e03 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Thu, 4 Aug 2011 16:20:37 -0400 Subject: Updated create image server action to respect 1.1 --- nova/api/openstack/create_instance_helper.py | 23 ++++++++++++++--------- nova/tests/api/openstack/test_server_actions.py | 1 - nova/tests/api/openstack/test_servers.py | 10 ---------- 3 files changed, 14 insertions(+), 20 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index 832c890b6..eec861428 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -380,8 +380,10 @@ class ServerXMLDeserializerV11(wsgi.MetadataXMLDeserializer): if value: data[attribute] = value metadata_node = self.find_first_child_named(node, 'metadata') - metadata = self.metadata_deserializer.extract_metadata(metadata_node) - data['metadata'] = metadata + if metadata_node is not None: + metadata = self.metadata_deserializer.extract_metadata( + metadata_node) + data['metadata'] = metadata return data def create(self, string): @@ -395,29 +397,32 @@ class ServerXMLDeserializerV11(wsgi.MetadataXMLDeserializer): server = {} server_node = self.find_first_child_named(node, 'server') - attributes = ["name", "imageId", "flavorId", "imageRef", - "flavorRef", "adminPass"] + attributes = ["name", "imageRef", "flavorRef", "adminPass"] for attr in attributes: if server_node.getAttribute(attr): server[attr] = server_node.getAttribute(attr) metadata_node = self.find_first_child_named(server_node, "metadata") - server["metadata"] = self.metadata_deserializer.extract_metadata( - metadata_node) + if metadata_node is not None: + server["metadata"] = self.extract_metadata(metadata_node) - server["personality"] = self._extract_personality(server_node) + personality = self._extract_personality(server_node) + if personality is not None: + server["personality"] = personality return server def _extract_personality(self, server_node): """Marshal the personality attribute of a parsed request""" node = self.find_first_child_named(server_node, "personality") - personality = [] if node is not None: + personality = [] for file_node in self.find_children_named(node, "file"): item = {} if file_node.hasAttribute("path"): item["path"] = file_node.getAttribute("path") item["contents"] = self.extract_text(file_node) personality.append(item) - return personality + return personality + else: + return None diff --git a/nova/tests/api/openstack/test_server_actions.py b/nova/tests/api/openstack/test_server_actions.py index 8f4cfacf8..fe627987f 100644 --- a/nova/tests/api/openstack/test_server_actions.py +++ b/nova/tests/api/openstack/test_server_actions.py @@ -750,7 +750,6 @@ class TestServerActionXMLDeserializerV11(test.TestCase): expected = { "createImage": { "name": "new-server-test", - "metadata": {}, }, } self.assertEquals(request['body'], expected) diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 748573cce..235554afb 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -2195,8 +2195,6 @@ class TestServerCreateRequestXMLDeserializerV11(test.TestCase): "name": "new-server-test", "imageRef": "1", "flavorRef": "2", - "metadata": {}, - "personality": [], }, } self.assertEquals(request['body'], expected) @@ -2215,8 +2213,6 @@ class TestServerCreateRequestXMLDeserializerV11(test.TestCase): "imageRef": "1", "flavorRef": "2", "adminPass": "1234", - "metadata": {}, - "personality": [], }, } self.assertEquals(request['body'], expected) @@ -2233,8 +2229,6 @@ class TestServerCreateRequestXMLDeserializerV11(test.TestCase): "name": "new-server-test", "imageRef": "http://localhost:8774/v1.1/images/2", "flavorRef": "3", - "metadata": {}, - "personality": [], }, } self.assertEquals(request['body'], expected) @@ -2251,8 +2245,6 @@ class TestServerCreateRequestXMLDeserializerV11(test.TestCase): "name": "new-server-test", "imageRef": "1", "flavorRef": "http://localhost:8774/v1.1/flavors/3", - "metadata": {}, - "personality": [], }, } self.assertEquals(request['body'], expected) @@ -2296,7 +2288,6 @@ class TestServerCreateRequestXMLDeserializerV11(test.TestCase): "imageRef": "1", "flavorRef": "2", "metadata": {"one": "two", "open": "snack"}, - "personality": [], }, } self.assertEquals(request['body'], expected) @@ -2318,7 +2309,6 @@ class TestServerCreateRequestXMLDeserializerV11(test.TestCase): "name": "new-server-test", "imageRef": "1", "flavorRef": "2", - "metadata": {}, "personality": [ {"path": "/etc/banner.txt", "contents": "MQ=="}, {"path": "/etc/hosts", "contents": "Mg=="}, -- cgit From eca23b1ad18a8626dd68cded4d488ca1aa779f69 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Thu, 4 Aug 2011 15:22:36 -0500 Subject: Removed debugging code. --- nova/compute/manager.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index c6af0dfbe..cf4ee229d 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -651,15 +651,16 @@ class ComputeManager(manager.SchedulerDependentManager): def rescue_instance(self, context, instance_id): """Rescue an instance on this host.""" context = context.elevated() - self._update_state(context, instance_id) instance_ref = self.db.instance_get(context, instance_id) LOG.audit(_('instance %s: rescuing'), instance_id, context=context) self.db.instance_set_state(context, instance_id, power_state.NOSTATE, 'rescuing') + _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(context, instance_ref, None, 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()) @@ -667,15 +668,16 @@ class ComputeManager(manager.SchedulerDependentManager): def unrescue_instance(self, context, instance_id): """Rescue an instance on this host.""" context = context.elevated() - self._update_state(context, instance_id) instance_ref = self.db.instance_get(context, instance_id) LOG.audit(_('instance %s: unrescuing'), instance_id, context=context) self.db.instance_set_state(context, instance_id, power_state.NOSTATE, 'unrescuing') + _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.unrescue(instance_ref, None, network_info) + self.driver.unrescue(instance_ref, _update_state, network_info) self._update_state(context, instance_id) @staticmethod -- cgit From 0f515d6d31b2c95ed7f1e3ca8d9d67f98fda9fbe Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Thu, 4 Aug 2011 16:26:31 -0400 Subject: Added XML serialization for server actions --- nova/api/openstack/create_instance_helper.py | 46 ++++++++ nova/tests/api/openstack/test_server_actions.py | 144 ++++++++++++++++++++++++ 2 files changed, 190 insertions(+) (limited to 'nova') diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index eec861428..894d47beb 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -360,6 +360,12 @@ class ServerXMLDeserializerV11(wsgi.MetadataXMLDeserializer): action_deserializer = { 'createImage': self._action_create_image, 'createBackup': self._action_create_backup, + 'changePassword': self._action_change_password, + 'reboot': self._action_reboot, + 'rebuild': self._action_rebuild, + 'resize': self._action_resize, + 'confirmResize': self._action_confirm_resize, + 'revertResize': self._action_revert_resize, }.get(action_name, self.default) action_data = action_deserializer(action_node) @@ -373,6 +379,46 @@ class ServerXMLDeserializerV11(wsgi.MetadataXMLDeserializer): attributes = ('name', 'backup_type', 'rotation') return self._deserialize_image_action(node, attributes) + def _action_change_password(self, node): + if not node.hasAttribute("adminPass"): + raise AttributeError("No adminPass was specified in request") + return {"adminPass": node.getAttribute("adminPass")} + + def _action_reboot(self, node): + if not node.hasAttribute("type"): + raise AttributeError("No reboot type was specified in request") + return {"type": node.getAttribute("type")} + + def _action_rebuild(self, node): + rebuild = {} + if node.hasAttribute("name"): + rebuild['name'] = node.getAttribute("name") + + metadata_node = self.find_first_child_named(node, "metadata") + if metadata_node is not None: + rebuild["metadata"] = self.extract_metadata(metadata_node) + + personality = self._extract_personality(node) + if personality is not None: + rebuild["personality"] = personality + + if not node.hasAttribute("imageRef"): + raise AttributeError("No imageRef was specified in request") + rebuild["imageRef"] = node.getAttribute("imageRef") + + return rebuild + + def _action_resize(self, node): + if not node.hasAttribute("flavorRef"): + raise AttributeError("No flavorRef was specified in request") + return {"flavorRef": node.getAttribute("flavorRef")} + + def _action_confirm_resize(self, node): + return None + + def _action_revert_resize(self, node): + return None + def _deserialize_image_action(self, node, allowed_attributes): data = {} for attribute in allowed_attributes: diff --git a/nova/tests/api/openstack/test_server_actions.py b/nova/tests/api/openstack/test_server_actions.py index fe627987f..b175a7ff2 100644 --- a/nova/tests/api/openstack/test_server_actions.py +++ b/nova/tests/api/openstack/test_server_actions.py @@ -770,3 +770,147 @@ class TestServerActionXMLDeserializerV11(test.TestCase): }, } self.assertEquals(request['body'], expected) + + def test_change_pass(self): + serial_request = """ + """ + request = self.deserializer.deserialize(serial_request, 'action') + expected = { + "changePassword": { + "adminPass": "1234pass", + }, + } + self.assertEquals(request['body'], expected) + + def test_change_pass_no_pass(self): + serial_request = """ + """ + self.assertRaises(AttributeError, + self.deserializer.deserialize, + serial_request, + 'action') + + def test_reboot(self): + serial_request = """ + """ + request = self.deserializer.deserialize(serial_request, 'action') + expected = { + "reboot": { + "type": "HARD", + }, + } + self.assertEquals(request['body'], expected) + + def test_reboot_no_type(self): + serial_request = """ + """ + self.assertRaises(AttributeError, + self.deserializer.deserialize, + serial_request, + 'action') + + def test_resize(self): + serial_request = """ + """ + request = self.deserializer.deserialize(serial_request, 'action') + expected = { + "resize": { + "flavorRef": "http://localhost/flavors/3" + }, + } + self.assertEquals(request['body'], expected) + + def test_resize_no_flavor_ref(self): + serial_request = """ + """ + self.assertRaises(AttributeError, + self.deserializer.deserialize, + serial_request, + 'action') + + def test_confirm_resize(self): + serial_request = """ + """ + request = self.deserializer.deserialize(serial_request, 'action') + expected = { + "confirmResize": None, + } + self.assertEquals(request['body'], expected) + + def test_revert_resize(self): + serial_request = """ + """ + request = self.deserializer.deserialize(serial_request, 'action') + expected = { + "revertResize": None, + } + self.assertEquals(request['body'], expected) + + def test_rebuild(self): + serial_request = """ + + + Apache1 + + + Mg== + + """ + request = self.deserializer.deserialize(serial_request, 'action') + expected = { + "rebuild": { + "name": "new-server-test", + "imageRef": "http://localhost/images/1", + "metadata": { + "My Server Name": "Apache1", + }, + "personality": [ + {"path": "/etc/banner.txt", "contents": "Mg=="}, + ], + }, + } + self.assertDictEqual(request['body'], expected) + + def test_rebuild_minimum(self): + serial_request = """ + """ + request = self.deserializer.deserialize(serial_request, 'action') + expected = { + "rebuild": { + "imageRef": "http://localhost/images/1", + }, + } + self.assertDictEqual(request['body'], expected) + + def test_rebuild_no_imageRef(self): + serial_request = """ + + + Apache1 + + + Mg== + + """ + self.assertRaises(AttributeError, + self.deserializer.deserialize, + serial_request, + 'action') -- cgit From 475963efeae1b12ae7474508559fb904bc9c1675 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Thu, 4 Aug 2011 15:31:18 -0500 Subject: Added more informative docstring. --- nova/virt/xenapi/vmops.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 81c6c6967..69f1af75a 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -744,7 +744,10 @@ class VMOps(object): LOG.exception(exc) def _find_rescue_vbd_ref(self, vm_ref, rescue_vm_ref): - """Find and return the rescue VM's vbd_ref.""" + """Find and return the rescue VM's vbd_ref. + + We use the second VBD here because swap is first with the root file + system coming in second.""" vbd_ref = self._session.get_xenapi().VM.get_VBDs(vm_ref)[1] vdi_ref = self._session.get_xenapi().VBD.get_record(vbd_ref)["VDI"] -- cgit From 5b463f5d14c62f66250d5edc3edbd2ded704e0da Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Thu, 4 Aug 2011 16:38:55 -0400 Subject: Added missing tests for server actions Updated reboot to verify the reboot type is HARD or SOFT Fixed case of having an empty flavorref on resize --- nova/api/openstack/servers.py | 9 +- nova/tests/api/openstack/test_server_actions.py | 107 ++++++++++++++++++++++++ 2 files changed, 115 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 3acc4510e..fa4d11e24 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -268,9 +268,13 @@ class Controller(object): def _action_reboot(self, input_dict, req, id): if 'reboot' in input_dict and 'type' in input_dict['reboot']: reboot_type = input_dict['reboot']['type'] + if (not reboot_type == 'HARD') and (not reboot_type == 'SOFT'): + msg = _("Argument 'type' for reboot is not HARD or SOFT") + LOG.exception(msg) + raise exc.HTTPBadRequest() else: LOG.exception(_("Missing argument 'type' for reboot")) - raise exc.HTTPUnprocessableEntity() + raise exc.HTTPBadRequest() try: # TODO(gundlach): pass reboot_type, support soft reboot in # virt driver @@ -646,6 +650,9 @@ class ControllerV11(Controller): """ Resizes a given instance to the flavor size requested """ try: flavor_ref = input_dict["resize"]["flavorRef"] + if not flavor_ref: + msg = _("Resize request has invalid 'flavorRef' attribute.") + raise exc.HTTPBadRequest(explanation=msg) except (KeyError, TypeError): msg = _("Resize requests require 'flavorRef' attribute.") raise exc.HTTPBadRequest(explanation=msg) diff --git a/nova/tests/api/openstack/test_server_actions.py b/nova/tests/api/openstack/test_server_actions.py index b175a7ff2..b5e4246e9 100644 --- a/nova/tests/api/openstack/test_server_actions.py +++ b/nova/tests/api/openstack/test_server_actions.py @@ -479,6 +479,21 @@ class ServerActionsTestV11(test.TestCase): self.assertEqual(mock_method.instance_id, '1') self.assertEqual(mock_method.password, '1234pass') + def test_server_change_password_xml(self): + mock_method = MockSetAdminPassword() + self.stubs.Set(nova.compute.api.API, 'set_admin_password', mock_method) + req = webob.Request.blank('/v1.1/servers/1/action') + req.method = 'POST' + req.content_type = "application/xml" + req.body = """ + """ + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 202) + self.assertEqual(mock_method.instance_id, '1') + self.assertEqual(mock_method.password, '1234pass') + def test_server_change_password_not_a_string(self): body = {'changePassword': {'adminPass': 1234}} req = webob.Request.blank('/v1.1/servers/1/action') @@ -515,6 +530,42 @@ class ServerActionsTestV11(test.TestCase): res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 400) + def test_server_reboot_hard(self): + body = dict(reboot=dict(type="HARD")) + req = webob.Request.blank('/v1.1/servers/1/action') + req.method = 'POST' + req.content_type = 'application/json' + req.body = json.dumps(body) + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 202) + + def test_server_reboot_soft(self): + body = dict(reboot=dict(type="SOFT")) + req = webob.Request.blank('/v1.1/servers/1/action') + req.method = 'POST' + req.content_type = 'application/json' + req.body = json.dumps(body) + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 202) + + def test_server_reboot_incorrect_type(self): + body = dict(reboot=dict(type="NOT_A_TYPE")) + req = webob.Request.blank('/v1.1/servers/1/action') + req.method = 'POST' + req.content_type = 'application/json' + req.body = json.dumps(body) + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 400) + + def test_server_reboot_missing_type(self): + body = dict(reboot=dict()) + req = webob.Request.blank('/v1.1/servers/1/action') + req.method = 'POST' + req.content_type = 'application/json' + req.body = json.dumps(body) + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 400) + def test_server_rebuild_accepted_minimum(self): body = { "rebuild": { @@ -657,6 +708,62 @@ class ServerActionsTestV11(test.TestCase): self.assertEqual(res.status_int, 202) self.assertEqual(self.resize_called, True) + def test_resize_server_no_flavor(self): + req = webob.Request.blank('/v1.1/servers/1/action') + req.content_type = 'application/json' + req.method = 'POST' + body_dict = dict(resize=dict()) + req.body = json.dumps(body_dict) + + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 400) + + def test_resize_server_no_flavor_ref(self): + req = webob.Request.blank('/v1.1/servers/1/action') + req.content_type = 'application/json' + req.method = 'POST' + body_dict = dict(resize=dict(flavorRef=None)) + req.body = json.dumps(body_dict) + + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 400) + + def test_confirm_resize_server(self): + req = webob.Request.blank('/v1.1/servers/1/action') + req.content_type = 'application/json' + req.method = 'POST' + body_dict = dict(confirmResize=None) + req.body = json.dumps(body_dict) + + self.confirm_resize_called = False + + def cr_mock(*args): + self.confirm_resize_called = True + + self.stubs.Set(nova.compute.api.API, 'confirm_resize', cr_mock) + + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 204) + self.assertEqual(self.confirm_resize_called, True) + + def test_revert_resize_server(self): + req = webob.Request.blank('/v1.1/servers/1/action') + req.content_type = 'application/json' + req.method = 'POST' + body_dict = dict(revertResize=None) + req.body = json.dumps(body_dict) + + self.revert_resize_called = False + + def revert_mock(*args): + self.revert_resize_called = True + + self.stubs.Set(nova.compute.api.API, 'revert_resize', revert_mock) + + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 202) + self.assertEqual(self.revert_resize_called, True) + def test_create_image(self): body = { 'createImage': { -- cgit From 75b110aa451382cce94f10a392597b40df97839c Mon Sep 17 00:00:00 2001 From: Ed Leafe Date: Thu, 4 Aug 2011 20:49:21 +0000 Subject: Changed all references to 'power state' to 'power action' as requested by review. --- nova/api/openstack/contrib/hosts.py | 43 ++++++++++++++++++++----------------- nova/compute/api.py | 6 +++--- nova/compute/manager.py | 6 +++--- nova/tests/test_hosts.py | 31 +++++++++++++------------- nova/virt/driver.py | 2 +- nova/virt/fake.py | 2 +- nova/virt/hyperv.py | 2 +- nova/virt/libvirt/connection.py | 2 +- nova/virt/vmwareapi_conn.py | 2 +- nova/virt/xenapi/vmops.py | 8 +++---- nova/virt/xenapi_conn.py | 4 ++-- 11 files changed, 55 insertions(+), 53 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/contrib/hosts.py b/nova/api/openstack/contrib/hosts.py index 78ba66771..09adbe2f4 100644 --- a/nova/api/openstack/contrib/hosts.py +++ b/nova/api/openstack/contrib/hosts.py @@ -70,7 +70,7 @@ class HostController(object): key = raw_key.lower().strip() val = raw_val.lower().strip() # NOTE: (dabo) Right now only 'status' can be set, but other - # actions may follow. + # settings may follow. if key == "status": if val[:6] in ("enable", "disabl"): return self._set_enabled_status(req, id, @@ -78,20 +78,6 @@ class HostController(object): else: explanation = _("Invalid status: '%s'") % raw_val raise webob.exc.HTTPBadRequest(explanation=explanation) - elif key == "power_state": - if val == "startup": - # The only valid values for 'state' are 'reboot' or - # 'shutdown'. For completeness' sake there is the - # 'startup' option to start up a host, but this is not - # technically feasible now, as we run the host on the - # XenServer box. - msg = _("Host startup on XenServer is not supported.") - raise webob.exc.HTTPBadRequest(explanation=msg) - elif val in ("reboot", "shutdown"): - return self._set_powerstate(req, id, val) - else: - explanation = _("Invalid powerstate: '%s'") % raw_val - raise webob.exc.HTTPBadRequest(explanation=explanation) else: explanation = _("Invalid update setting: '%s'") % raw_key raise webob.exc.HTTPBadRequest(explanation=explanation) @@ -108,12 +94,28 @@ class HostController(object): raise webob.exc.HTTPBadRequest(explanation=result) return {"host": host, "status": result} - def _set_powerstate(self, req, host, state): + def _host_power_action(self, req, host, action): """Reboots or shuts down the host.""" context = req.environ['nova.context'] - result = self.compute_api.set_host_powerstate(context, host=host, - state=state) - return {"host": host, "power_state": result} + result = self.compute_api.host_power_action(context, host=host, + action=action) + return {"host": host, "power_action": result} + + def startup(self, req, id): + """The only valid values for 'action' are 'reboot' or + 'shutdown'. For completeness' sake there is the + 'startup' option to start up a host, but this is not + technically feasible now, as we run the host on the + XenServer box. + """ + msg = _("Host startup on XenServer is not supported.") + raise webob.exc.HTTPBadRequest(explanation=msg) + + def shutdown(self, req, id): + return self._host_power_action(req, host=id, action="shutdown") + + def reboot(self, req, id): + return self._host_power_action(req, host=id, action="reboot") class Hosts(extensions.ExtensionDescriptor): @@ -139,5 +141,6 @@ class Hosts(extensions.ExtensionDescriptor): if FLAGS.allow_admin_api: resources = [extensions.ResourceExtension('os-hosts', HostController(), collection_actions={'update': 'PUT'}, - member_actions={})] + member_actions={"startup": "GET", "shutdown": "GET", + "reboot": "GET"})] return resources diff --git a/nova/compute/api.py b/nova/compute/api.py index 3e0cd7cfa..5f85ca908 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -995,10 +995,10 @@ class API(base.Base): return self._call_compute_message("set_host_enabled", context, instance_id=None, host=host, params={"enabled": enabled}) - def set_host_powerstate(self, context, host, state): + def host_power_action(self, context, host, action): """Reboots or shuts down the host.""" - return self._call_compute_message("set_host_powerstate", context, - instance_id=None, host=host, params={"state": state}) + return self._call_compute_message("host_power_action", context, + instance_id=None, host=host, params={"action": action}) @scheduler_api.reroute_compute("diagnostics") def get_diagnostics(self, context, instance_id): diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 220b09554..c2c12a9a2 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -955,10 +955,10 @@ class ComputeManager(manager.SchedulerDependentManager): result)) @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id()) - def set_host_powerstate(self, context, instance_id=None, host=None, - state=None): + def host_power_action(self, context, instance_id=None, host=None, + action=None): """Reboots or shuts down the host.""" - return self.driver.set_host_powerstate(host, state) + return self.driver.host_power_action(host, action) @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id()) def set_host_enabled(self, context, instance_id=None, host=None, diff --git a/nova/tests/test_hosts.py b/nova/tests/test_hosts.py index d8f90a109..cd22571e6 100644 --- a/nova/tests/test_hosts.py +++ b/nova/tests/test_hosts.py @@ -48,8 +48,8 @@ def stub_set_host_enabled(context, host, enabled): return status -def stub_set_host_powerstate(context, host, state): - return state +def stub_host_power_action(context, host, action): + return action class FakeRequest(object): @@ -66,8 +66,8 @@ class HostTestCase(test.TestCase): self.stubs.Set(scheduler_api, 'get_host_list', stub_get_host_list) self.stubs.Set(self.controller.compute_api, 'set_host_enabled', stub_set_host_enabled) - self.stubs.Set(self.controller.compute_api, 'set_host_powerstate', - stub_set_host_powerstate) + self.stubs.Set(self.controller.compute_api, 'host_power_action', + stub_host_power_action) def test_list_hosts(self): """Verify that the compute hosts are returned.""" @@ -93,19 +93,18 @@ class HostTestCase(test.TestCase): result_c2 = self.controller.update(self.req, "host_c2", body=en_body) self.assertEqual(result_c2["status"], "disabled") - def test_host_power_state(self): - en_body = {"power_state": "reboot"} - result_c1 = self.controller.update(self.req, "host_c1", body=en_body) - self.assertEqual(result_c1["power_state"], "reboot") - # Test invalid power_state - en_body = {"power_state": "invalid"} - self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update, - self.req, "host_c1", body=en_body) + def test_host_startup(self): + self.assertRaises(webob.exc.HTTPBadRequest, self.controller.startup, + self.req, "host_c1") - def test_bad_power_state_value(self): - bad_body = {"power_state": "bad"} - self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update, - self.req, "host_c1", body=bad_body) + def test_host_shutdown(self): + result = self.controller.shutdown(self.req, "host_c1") + print "RES", result + self.assertEqual(result["power_action"], "shutdown") + + def test_host_reboot(self): + result = self.controller.reboot(self.req, "host_c1") + self.assertEqual(result["power_action"], "reboot") def test_bad_status_value(self): bad_body = {"status": "bad"} diff --git a/nova/virt/driver.py b/nova/virt/driver.py index 30f14459a..052c6607e 100644 --- a/nova/virt/driver.py +++ b/nova/virt/driver.py @@ -282,7 +282,7 @@ class ComputeDriver(object): # TODO(Vek): Need to pass context in for access to auth_token raise NotImplementedError() - def set_host_powerstate(self, host, state): + def host_power_action(self, host, action): """Reboots or shuts down the host.""" raise NotImplementedError() diff --git a/nova/virt/fake.py b/nova/virt/fake.py index 7b13f4fbe..db51c258b 100644 --- a/nova/virt/fake.py +++ b/nova/virt/fake.py @@ -512,7 +512,7 @@ class FakeConnection(driver.ComputeDriver): """Return fake Host Status of ram, disk, network.""" return self.host_status - def set_host_powerstate(self, host, state): + def host_power_action(self, host, action): """Reboots or shuts down the host.""" pass diff --git a/nova/virt/hyperv.py b/nova/virt/hyperv.py index 00c4d360b..f0efeb581 100644 --- a/nova/virt/hyperv.py +++ b/nova/virt/hyperv.py @@ -499,7 +499,7 @@ class HyperVConnection(driver.ComputeDriver): """See xenapi_conn.py implementation.""" pass - def set_host_powerstate(self, host, state): + def host_power_action(self, host, action): """Reboots or shuts down the host.""" pass diff --git a/nova/virt/libvirt/connection.py b/nova/virt/libvirt/connection.py index f0b2014ac..14e02c7c4 100644 --- a/nova/virt/libvirt/connection.py +++ b/nova/virt/libvirt/connection.py @@ -1558,7 +1558,7 @@ class LibvirtConnection(driver.ComputeDriver): """See xenapi_conn.py implementation.""" pass - def set_host_powerstate(self, host, state): + def host_power_action(self, host, action): """Reboots or shuts down the host.""" pass diff --git a/nova/virt/vmwareapi_conn.py b/nova/virt/vmwareapi_conn.py index 90d083f10..5937d9585 100644 --- a/nova/virt/vmwareapi_conn.py +++ b/nova/virt/vmwareapi_conn.py @@ -191,7 +191,7 @@ class VMWareESXConnection(driver.ComputeDriver): """This method is supported only by libvirt.""" return - def set_host_powerstate(self, host, state): + def host_power_action(self, host, action): """Reboots or shuts down the host.""" pass diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index dfd85dd2c..509abd767 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -1024,13 +1024,13 @@ class VMOps(object): # TODO: implement this! return 'http://fakeajaxconsole/fake_url' - def set_host_powerstate(self, host, state): + def host_power_action(self, host, action): """Reboots or shuts down the host.""" - args = {"state": json.dumps(state)} + args = {"action": json.dumps(action)} methods = {"reboot": "host_reboot", "shutdown": "host_shutdown"} - json_resp = self._call_xenhost(methods[state], args) + json_resp = self._call_xenhost(methods[action], args) resp = json.loads(json_resp) - return resp["powerstate"] + return resp["power_action"] def set_host_enabled(self, host, enabled): """Sets the specified host's ability to accept new instances.""" diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index fa5b78a2a..3452343c6 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -332,9 +332,9 @@ class XenAPIConnection(driver.ComputeDriver): True, run the update first.""" return self.HostState.get_host_stats(refresh=refresh) - def set_host_powerstate(self, host, state): + def host_power_action(self, host, action): """Reboots or shuts down the host.""" - return self._vmops.set_host_powerstate(host, state) + return self._vmops.host_power_action(host, action) def set_host_enabled(self, host, enabled): """Sets the specified host's ability to accept new instances.""" -- cgit From dcac4bc6c7be9832704e37cca7ce815e083974f5 Mon Sep 17 00:00:00 2001 From: Ed Leafe Date: Thu, 4 Aug 2011 20:55:56 +0000 Subject: Added admin-only decorator --- nova/api/openstack/contrib/admin_only.py | 30 ++++++++++++++++++++++++++++++ nova/api/openstack/contrib/hosts.py | 14 ++++++-------- 2 files changed, 36 insertions(+), 8 deletions(-) create mode 100644 nova/api/openstack/contrib/admin_only.py (limited to 'nova') diff --git a/nova/api/openstack/contrib/admin_only.py b/nova/api/openstack/contrib/admin_only.py new file mode 100644 index 000000000..e821c9e1f --- /dev/null +++ b/nova/api/openstack/contrib/admin_only.py @@ -0,0 +1,30 @@ +# Copyright (c) 2011 Openstack, LLC. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +"""Decorator for limiting extensions that should be admin-only.""" + +from functools import wraps +from nova import flags +FLAGS = flags.FLAGS + + +def admin_only(fnc): + @wraps(fnc) + def _wrapped(self, *args, **kwargs): + if FLAGS.allow_admin_api: + return fnc(self, *args, **kwargs) + return [] + _wrapped.func_name = fnc.func_name + return _wrapped diff --git a/nova/api/openstack/contrib/hosts.py b/nova/api/openstack/contrib/hosts.py index 09adbe2f4..cdf8760d5 100644 --- a/nova/api/openstack/contrib/hosts.py +++ b/nova/api/openstack/contrib/hosts.py @@ -24,6 +24,7 @@ from nova import log as logging from nova.api.openstack import common from nova.api.openstack import extensions from nova.api.openstack import faults +from nova.api.openstack.contrib import admin_only from nova.scheduler import api as scheduler_api @@ -134,13 +135,10 @@ class Hosts(extensions.ExtensionDescriptor): def get_updated(self): return "2011-06-29T00:00:00+00:00" + @admin_only.admin_only def get_resources(self): - resources = [] - # If we are not in an admin env, don't add the resource. Regular users - # shouldn't have access to the host. - if FLAGS.allow_admin_api: - resources = [extensions.ResourceExtension('os-hosts', - HostController(), collection_actions={'update': 'PUT'}, - member_actions={"startup": "GET", "shutdown": "GET", - "reboot": "GET"})] + resources = [extensions.ResourceExtension('os-hosts', + HostController(), collection_actions={'update': 'PUT'}, + member_actions={"startup": "GET", "shutdown": "GET", + "reboot": "GET"})] return resources -- cgit From dae356ad06bb4926ca91fd9a5182271d93c4b7be Mon Sep 17 00:00:00 2001 From: Matt Dietz Date: Thu, 4 Aug 2011 21:18:55 +0000 Subject: Forgot the instance_id parameter in the finish call --- nova/compute/manager.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 94bac9be4..a062cbe91 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -746,7 +746,8 @@ class ComputeManager(manager.SchedulerDependentManager): instance_ref['host']) rpc.cast(context, topic, {'method': 'finish_revert_resize', - 'args': {'migration_id': migration_ref['id']}, + 'args': { 'instance_id': instance_ref['uuid'], + 'migration_id': migration_ref['id']}, }) @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id()) -- cgit From 77a1a63c30a9797f2f729c7b815d6660bed880d5 Mon Sep 17 00:00:00 2001 From: Matt Dietz Date: Thu, 4 Aug 2011 21:26:21 +0000 Subject: Bad method call --- nova/virt/xenapi_conn.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index 91d9d8530..166141e5e 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -191,7 +191,7 @@ class XenAPIConnection(driver.ComputeDriver): def revert_migration(self, instance): """Reverts a resize, powering back on the instance""" - self._vmops.revert_resize(instance) + self._vmops.revert_migration(instance) def finish_migration(self, context, instance, disk_info, network_info, resize_instance=False): -- cgit From 5fe92058d0ee11a7e9ea1c8f56b7e9350cf703e4 Mon Sep 17 00:00:00 2001 From: Matt Dietz Date: Thu, 4 Aug 2011 22:18:52 +0000 Subject: Revert migration now finishes --- nova/compute/manager.py | 4 ++-- nova/tests/test_xenapi.py | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index a062cbe91..0fd9c3c13 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -746,8 +746,8 @@ class ComputeManager(manager.SchedulerDependentManager): instance_ref['host']) rpc.cast(context, topic, {'method': 'finish_revert_resize', - 'args': { 'instance_id': instance_ref['uuid'], - 'migration_id': migration_ref['id']}, + 'args': {'instance_id': instance_ref['uuid'], + 'migration_id': migration_ref['id']}, }) @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id()) diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index 5b29cf537..d269408b9 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -767,6 +767,53 @@ class XenAPIMigrateInstance(test.TestCase): conn = xenapi_conn.get_connection(False) conn.migrate_disk_and_power_off(instance, '127.0.0.1') + + def test_revert_migrate(self): + instance = db.instance_create(self.context, self.values) + self.called = False + self.fake_vm_start_called = False + self.fake_revert_migration_called = False + + def fake_vm_start(*args, **kwargs): + self.fake_vm_start_called = True + + def fake_vdi_resize(*args, **kwargs): + self.called = True + + def fake_revert_migration(*args, **kwargs): + self.fake_revert_migration_called = True + + self.stubs.Set(stubs.FakeSessionForMigrationTests, + "VDI_resize_online", fake_vdi_resize) + self.stubs.Set(vmops.VMOps, '_start', fake_vm_start) + self.stubs.Set(vmops.VMOps, 'revert_migration', fake_revert_migration) + + stubs.stubout_session(self.stubs, stubs.FakeSessionForMigrationTests) + stubs.stubout_loopingcall_start(self.stubs) + conn = xenapi_conn.get_connection(False) + network_info = [({'bridge': 'fa0', 'id': 0, 'injected': False}, + {'broadcast': '192.168.0.255', + 'dns': ['192.168.0.1'], + 'gateway': '192.168.0.1', + 'gateway6': 'dead:beef::1', + 'ip6s': [{'enabled': '1', + 'ip': 'dead:beef::dcad:beff:feef:0', + 'netmask': '64'}], + 'ips': [{'enabled': '1', + 'ip': '192.168.0.100', + 'netmask': '255.255.255.0'}], + 'label': 'fake', + 'mac': 'DE:AD:BE:EF:00:00', + 'rxtx_cap': 3})] + conn.finish_migration(self.context, instance, + dict(base_copy='hurr', cow='durr'), + network_info, resize_instance=True) + self.assertEqual(self.called, True) + self.assertEqual(self.fake_vm_start_called, True) + + conn.revert_migration(instance) + self.assertEqual(self.fake_revert_migration_called, True) + def test_finish_migrate(self): instance = db.instance_create(self.context, self.values) self.called = False -- cgit From d470f82ea09214b6f535995aa551a23eb4ccc4ed Mon Sep 17 00:00:00 2001 From: Adam Gandelman Date: Thu, 4 Aug 2011 17:43:37 -0700 Subject: Properly format mapping['dns'] before handing off to template for injection (Fixes LP Bug #821203) --- nova/virt/libvirt/connection.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/virt/libvirt/connection.py b/nova/virt/libvirt/connection.py index f7aae4112..0f0f62588 100644 --- a/nova/virt/libvirt/connection.py +++ b/nova/virt/libvirt/connection.py @@ -880,7 +880,7 @@ class LibvirtConnection(driver.ComputeDriver): 'netmask': netmask, 'gateway': mapping['gateway'], 'broadcast': mapping['broadcast'], - 'dns': mapping['dns'], + 'dns': ' '.join(mapping['dns']), 'address_v6': address_v6, 'gateway6': gateway_v6, 'netmask_v6': netmask_v6} -- cgit From 79e51d7b138948eddd307747c517be9ad1aa67d1 Mon Sep 17 00:00:00 2001 From: Gabe Westmaas Date: Fri, 5 Aug 2011 01:07:53 +0000 Subject: Adding missing module xmlutil --- nova/api/openstack/xmlutil.py | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 nova/api/openstack/xmlutil.py (limited to 'nova') diff --git a/nova/api/openstack/xmlutil.py b/nova/api/openstack/xmlutil.py new file mode 100644 index 000000000..97ad90ada --- /dev/null +++ b/nova/api/openstack/xmlutil.py @@ -0,0 +1,37 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 OpenStack LLC. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import os.path + +from lxml import etree + +from nova import utils + + +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' + + +def validate_schema(xml, schema_name): + if type(xml) is str: + xml = etree.fromstring(xml) + schema_path = os.path.join(utils.novadir(), + 'nova/api/openstack/schemas/v1.1/%s.rng' % schema_name) + schema_doc = etree.parse(schema_path) + relaxng = etree.RelaxNG(schema_doc) + relaxng.assertValid(xml) -- cgit From 637dfc0f44cbd5bf0c76d80d708a241e562403ac Mon Sep 17 00:00:00 2001 From: Gabe Westmaas Date: Fri, 5 Aug 2011 01:55:53 +0000 Subject: Added explanations to exceptions and cleaned up reboot types --- nova/api/openstack/servers.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index fa4d11e24..9a55af8ea 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -267,14 +267,16 @@ class Controller(object): def _action_reboot(self, input_dict, req, id): if 'reboot' in input_dict and 'type' in input_dict['reboot']: - reboot_type = input_dict['reboot']['type'] - if (not reboot_type == 'HARD') and (not reboot_type == 'SOFT'): + valid_reboot_types = ['HARD', 'SOFT'] + reboot_type = input_dict['reboot']['type'].upper() + if not valid_reboot_types.count(reboot_type): msg = _("Argument 'type' for reboot is not HARD or SOFT") LOG.exception(msg) - raise exc.HTTPBadRequest() + raise exc.HTTPBadRequest(explanation=msg) else: - LOG.exception(_("Missing argument 'type' for reboot")) - raise exc.HTTPBadRequest() + msg = _("Missing argument 'type' for reboot") + LOG.exception(msg) + raise exc.HTTPBadRequest(explanation=msg) try: # TODO(gundlach): pass reboot_type, support soft reboot in # virt driver -- cgit From b068e1edd79a5382ef1237d0b47e60dc141bae35 Mon Sep 17 00:00:00 2001 From: Ilya Alekseyev Date: Fri, 5 Aug 2011 07:55:22 +0400 Subject: added missing tests --- nova/tests/test_nova_manage.py | 79 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 nova/tests/test_nova_manage.py (limited to 'nova') diff --git a/nova/tests/test_nova_manage.py b/nova/tests/test_nova_manage.py new file mode 100644 index 000000000..ca9ffa222 --- /dev/null +++ b/nova/tests/test_nova_manage.py @@ -0,0 +1,79 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 OpenStack LLC +# Copyright 2011 Ilya Alekseyev +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import os + +TOPDIR = os.path.normpath(os.path.join( + os.path.dirname(os.path.abspath(__file__)), + os.pardir, + os.pardir)) +NOVA_MANAGE_PATH = os.path.join(TOPDIR, 'bin', 'nova-manage') + +import imp +nova_manage = imp.load_source('nova_manage', NOVA_MANAGE_PATH) + +import netaddr +from nova import context +from nova import db +from nova import flags +from nova import test + +FLAGS = flags.FLAGS + + +class FixedIpCommandsTestCase(test.TestCase): + def setUp(self): + super(FixedIpCommandsTestCase, self).setUp() + cidr = '10.0.0.0/24' + net = netaddr.IPNetwork(cidr) + net_info = {'bridge': 'fakebr', + 'bridge_interface': 'fakeeth', + 'dns': FLAGS.flat_network_dns, + 'cidr': cidr, + 'netmask': str(net.netmask), + 'gateway': str(net[1]), + 'broadcast': str(net.broadcast), + 'dhcp_start': str(net[2])} + self.network = db.network_create_safe(context.get_admin_context(), + net_info) + num_ips = len(net) + for index in range(num_ips): + address = str(net[index]) + reserved = (index == 1 or index == 2) + db.fixed_ip_create(context.get_admin_context(), + {'network_id': self.network['id'], + 'address': address, + 'reserved': reserved}) + self.commands = nova_manage.FixedIpCommands() + + def tearDown(self): + db.network_delete_safe(context.get_admin_context(), self.network['id']) + super(FixedIpCommandsTestCase, self).tearDown() + + def test_reserve(self): + self.commands.reserve('10.0.0.100') + address = db.fixed_ip_get_by_address(context.get_admin_context(), + '10.0.0.100') + self.assertEqual(address['reserved'], True) + + def test_waste(self): + db.fixed_ip_update(context.get_admin_context(), '10.0.0.100', + {'reserved': True}) + self.commands.waste('10.0.0.100') + address = db.fixed_ip_get_by_address(context.get_admin_context(), + '10.0.0.100') + self.assertEqual(address['reserved'], False) -- cgit From 625330bbc70b7be0d007c2a5ce1fba4dfcc29bf8 Mon Sep 17 00:00:00 2001 From: Justin Shepherd Date: Thu, 4 Aug 2011 22:59:36 -0500 Subject: utilized functools.wraps --- nova/test.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) (limited to 'nova') diff --git a/nova/test.py b/nova/test.py index 97cd3e0d0..d3b9d3ee2 100644 --- a/nova/test.py +++ b/nova/test.py @@ -60,11 +60,10 @@ class skip_test(object): self.message = msg def __call__(self, func): + @functools.wraps(func) def _skipper(*args, **kw): """Wrapped skipper function.""" raise nose.SkipTest(self.message) - _skipper.__name__ = func.__name__ - _skipper.__doc__ = func.__doc__ return _skipper @@ -75,13 +74,12 @@ class skip_if(object): self.message = msg def __call__(self, func): + @functools.wraps(func) def _skipper(*args, **kw): """Wrapped skipper function.""" if self.condition: raise nose.SkipTest(self.message) func(*args, **kw) - _skipper.__name__ = func.__name__ - _skipper.__doc__ = func.__doc__ return _skipper @@ -92,13 +90,12 @@ class skip_unless(object): self.message = msg def __call__(self, func): + @functools.wraps(func) def _skipper(*args, **kw): """Wrapped skipper function.""" if not self.condition: raise nose.SkipTest(self.message) func(*args, **kw) - _skipper.__name__ = func.__name__ - _skipper.__doc__ = func.__doc__ return _skipper -- cgit From 13fb04effa25e860eaa9e4ebaca2b4d906a140b0 Mon Sep 17 00:00:00 2001 From: Justin Shepherd Date: Thu, 4 Aug 2011 23:01:22 -0500 Subject: fixed pep8 issue --- nova/tests/test_skip_examples.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/tests/test_skip_examples.py b/nova/tests/test_skip_examples.py index 033f1ea2e..8ca203442 100644 --- a/nova/tests/test_skip_examples.py +++ b/nova/tests/test_skip_examples.py @@ -37,7 +37,7 @@ class ExampleSkipTestCase(test.TestCase): @test.skip_if(False, "This test case should never be skipped.") def test_001_increase_test_counter(self): ExampleSkipTestCase.test_counter += 1 - + @test.skip_unless(True, "This test case should never be skipped.") def test_002_increase_test_counter(self): ExampleSkipTestCase.test_counter += 1 -- cgit From fe343a30ad5317ac5635667e72f56be775284658 Mon Sep 17 00:00:00 2001 From: Isaku Yamahata Date: Fri, 5 Aug 2011 15:23:54 +0900 Subject: fix mismerge --- nova/virt/libvirt/connection.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/virt/libvirt/connection.py b/nova/virt/libvirt/connection.py index c49f6da5d..7f5bdcf10 100644 --- a/nova/virt/libvirt/connection.py +++ b/nova/virt/libvirt/connection.py @@ -1052,7 +1052,8 @@ class LibvirtConnection(driver.ComputeDriver): # NOTE(yamahata): # for nova.api.ec2.cloud.CloudController.get_metadata() xml_info['root_device'] = self.default_root_device - db.instance_update(context.get_admin_context(), instance['id'], + db.instance_update( + nova_context.get_admin_context(), instance['id'], {'root_device_name': '/dev/' + self.default_root_device}) swap = driver.block_device_info_get_swap(block_device_info) -- cgit From 9742ff588d3ce90b1420eaf95e311f02a4e196da Mon Sep 17 00:00:00 2001 From: Soren Hansen Date: Fri, 5 Aug 2011 14:54:56 +0200 Subject: Add generic image service tests. --- nova/image/fake.py | 41 ++++++++++---- nova/tests/test_image.py | 135 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 165 insertions(+), 11 deletions(-) create mode 100644 nova/tests/test_image.py (limited to 'nova') diff --git a/nova/image/fake.py b/nova/image/fake.py index 28e912534..b29ec6517 100644 --- a/nova/image/fake.py +++ b/nova/image/fake.py @@ -45,9 +45,12 @@ class _FakeImageService(service.BaseImageService): 'name': 'fakeimage123456', 'created_at': timestamp, 'updated_at': timestamp, + 'deleted_at': None, + 'deleted': False, 'status': 'active', - 'container_format': 'ami', - 'disk_format': 'raw', + 'is_public': False, +# 'container_format': 'ami', +# 'disk_format': 'raw', 'properties': {'kernel_id': FLAGS.null_kernel, 'ramdisk_id': FLAGS.null_kernel, 'architecture': 'x86_64'}} @@ -56,9 +59,12 @@ class _FakeImageService(service.BaseImageService): 'name': 'fakeimage123456', 'created_at': timestamp, 'updated_at': timestamp, + 'deleted_at': None, + 'deleted': False, 'status': 'active', - 'container_format': 'ami', - 'disk_format': 'raw', + 'is_public': True, +# 'container_format': 'ami', +# 'disk_format': 'raw', 'properties': {'kernel_id': FLAGS.null_kernel, 'ramdisk_id': FLAGS.null_kernel}} @@ -66,9 +72,12 @@ class _FakeImageService(service.BaseImageService): 'name': 'fakeimage123456', 'created_at': timestamp, 'updated_at': timestamp, + 'deleted_at': None, + 'deleted': False, 'status': 'active', - 'container_format': 'ami', - 'disk_format': 'raw', + 'is_public': True, +# 'container_format': 'ami', +# 'disk_format': 'raw', 'properties': {'kernel_id': FLAGS.null_kernel, 'ramdisk_id': FLAGS.null_kernel}} @@ -76,9 +85,12 @@ class _FakeImageService(service.BaseImageService): 'name': 'fakeimage123456', 'created_at': timestamp, 'updated_at': timestamp, + 'deleted_at': None, + 'deleted': False, 'status': 'active', - 'container_format': 'ami', - 'disk_format': 'raw', + 'is_public': True, +# 'container_format': 'ami', +# 'disk_format': 'raw', 'properties': {'kernel_id': FLAGS.null_kernel, 'ramdisk_id': FLAGS.null_kernel}} @@ -86,9 +98,12 @@ class _FakeImageService(service.BaseImageService): 'name': 'fakeimage123456', 'created_at': timestamp, 'updated_at': timestamp, + 'deleted_at': None, + 'deleted': False, 'status': 'active', - 'container_format': 'ami', - 'disk_format': 'raw', + 'is_public': True, +# 'container_format': 'ami', +# 'disk_format': 'raw', 'properties': {'kernel_id': FLAGS.null_kernel, 'ramdisk_id': FLAGS.null_kernel}} @@ -101,7 +116,11 @@ class _FakeImageService(service.BaseImageService): def index(self, context, filters=None, marker=None, limit=None): """Returns list of images.""" - return copy.deepcopy(self.images.values()) + retval = [] + for img in self.images.values(): + retval += [dict([(k, v) for k, v in img.iteritems() + if k in ['id', 'name']])] + return retval def detail(self, context, filters=None, marker=None, limit=None): """Return list of detailed image information.""" diff --git a/nova/tests/test_image.py b/nova/tests/test_image.py new file mode 100644 index 000000000..ba5b93f9b --- /dev/null +++ b/nova/tests/test_image.py @@ -0,0 +1,135 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 Linux2Go +# Author: Soren Hansen +# +# 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 datetime + +from nova import context +from nova import exception +from nova import test +import nova.image + +class _ImageTestCase(test.TestCase): + def setUp(self): + super(_ImageTestCase, self).setUp() + self.context = context.get_admin_context() + + def test_index(self): + res = self.image_service.index(self.context) + for image in res: + self.assertEquals(set(image.keys()), set(['id', 'name'])) + + def test_detail(self): + res = self.image_service.detail(self.context) + for image in res: + keys = set(image.keys()) + self.assertEquals(keys, set(['id', 'name', 'created_at', + 'updated_at', 'deleted_at', 'deleted', + 'status', 'is_public', 'properties'])) + self.assertTrue(isinstance(image['created_at'], datetime.datetime)) + self.assertTrue(isinstance(image['updated_at'], datetime.datetime)) + + if not (isinstance(image['deleted_at'], datetime.datetime) or + image['deleted_at'] is None): + self.fail('image\'s "deleted_at" attribute was neither a ' + 'datetime object nor None') + + def check_is_bool(image, key): + val = image.get('deleted') + if not isinstance(val, bool): + self.fail('image\'s "%s" attribute wasn\'t ' + 'a bool: %r' % (key, val)) + + check_is_bool(image, 'deleted') + check_is_bool(image, 'is_public') + + def test_index_and_detail_have_same_results(self): + index = self.image_service.index(self.context) + detail = self.image_service.detail(self.context) + index_set = set([(i['id'], i['name']) for i in index]) + detail_set = set([(i['id'], i['name']) for i in detail]) + self.assertEqual(index_set, detail_set) + + def test_show_raises_imagenotfound_for_invalid_id(self): + self.assertRaises(exception.ImageNotFound, + self.image_service.show, + self.context, + 'this image does not exist') + + def test_show_by_name(self): + self.assertRaises(exception.ImageNotFound, + self.image_service.show_by_name, + self.context, + 'this image does not exist') + + def test_create_adds_id(self): + index = self.image_service.index(self.context) + image_count = len(index) + + self.image_service.create(self.context, {}) + + index = self.image_service.index(self.context) + self.assertEquals(len(index), image_count+1) + + self.assertTrue(index[0]['id']) + + + def test_create_keeps_id(self): + self.image_service.create(self.context, {'id': '34'}) + self.image_service.show(self.context, '34') + + + def test_create_rejects_duplicate_ids(self): + self.image_service.create(self.context, {'id': '34'}) + self.assertRaises(exception.Duplicate, + self.image_service.create, + self.context, + {'id': '34'}) + + # Make sure there's still one left + self.image_service.show(self.context, '34') + + def test_update(self): + self.image_service.create(self.context, + {'id': '34', 'foo': 'bar'}) + + self.image_service.update(self.context, '34', + {'id': '34', 'foo': 'baz'}) + + img = self.image_service.show(self.context, '34') + self.assertEquals(img['foo'], 'baz') + + def test_delete(self): + self.image_service.create(self.context, {'id': '34', 'foo': 'bar'}) + self.image_service.delete(self.context, '34') + self.assertRaises(exception.NotFound, + self.image_service.show, + self.context, + '34') + + def test_delete_all(self): + self.image_service.create(self.context, {'id': '32', 'foo': 'bar'}) + self.image_service.create(self.context, {'id': '33', 'foo': 'bar'}) + self.image_service.create(self.context, {'id': '34', 'foo': 'bar'}) + self.image_service.delete_all() + index = self.image_service.index(self.context) + self.assertEquals(len(index), 0) + +class FakeImageTestCase(_ImageTestCase): + def setUp(self): + super(FakeImageTestCase, self).setUp() + self.image_service = nova.image.fake.FakeImageService() + -- cgit From ab1ba7cbcffc92c2c82c468fb0a2a81f93db3f85 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Fri, 5 Aug 2011 06:01:55 -0700 Subject: fixed up zones controller to properly work with 1.1 --- nova/api/openstack/zones.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/api/openstack/zones.py b/nova/api/openstack/zones.py index f7fd87bcd..a2bf267ed 100644 --- a/nova/api/openstack/zones.py +++ b/nova/api/openstack/zones.py @@ -166,7 +166,7 @@ class Controller(object): return self.helper._get_server_admin_password_old_style(server) -class ControllerV11(object): +class ControllerV11(Controller): """Controller for 1.1 Zone resources.""" def _get_server_admin_password(self, server): -- cgit From 1e366d9de180347b47675bf7f811ffd5a293ef10 Mon Sep 17 00:00:00 2001 From: Soren Hansen Date: Fri, 5 Aug 2011 15:37:36 +0200 Subject: pep8 --- nova/image/fake.py | 2 +- nova/tests/test_image.py | 7 ++----- 2 files changed, 3 insertions(+), 6 deletions(-) (limited to 'nova') diff --git a/nova/image/fake.py b/nova/image/fake.py index b29ec6517..97af81711 100644 --- a/nova/image/fake.py +++ b/nova/image/fake.py @@ -118,7 +118,7 @@ class _FakeImageService(service.BaseImageService): """Returns list of images.""" retval = [] for img in self.images.values(): - retval += [dict([(k, v) for k, v in img.iteritems() + retval += [dict([(k, v) for k, v in img.iteritems() if k in ['id', 'name']])] return retval diff --git a/nova/tests/test_image.py b/nova/tests/test_image.py index ba5b93f9b..5ec8812f3 100644 --- a/nova/tests/test_image.py +++ b/nova/tests/test_image.py @@ -1,6 +1,6 @@ # vim: tabstop=4 shiftwidth=4 softtabstop=4 -# Copyright 2011 Linux2Go +# Copyright 2011 OpenStack LLC # Author: Soren Hansen # # Licensed under the Apache License, Version 2.0 (the "License"); you may @@ -82,16 +82,14 @@ class _ImageTestCase(test.TestCase): self.image_service.create(self.context, {}) index = self.image_service.index(self.context) - self.assertEquals(len(index), image_count+1) + self.assertEquals(len(index), image_count + 1) self.assertTrue(index[0]['id']) - def test_create_keeps_id(self): self.image_service.create(self.context, {'id': '34'}) self.image_service.show(self.context, '34') - def test_create_rejects_duplicate_ids(self): self.image_service.create(self.context, {'id': '34'}) self.assertRaises(exception.Duplicate, @@ -132,4 +130,3 @@ class FakeImageTestCase(_ImageTestCase): def setUp(self): super(FakeImageTestCase, self).setUp() self.image_service = nova.image.fake.FakeImageService() - -- cgit From 070d832dad062dfb18439e77b4460e6ebe75bdb0 Mon Sep 17 00:00:00 2001 From: Soren Hansen Date: Fri, 5 Aug 2011 15:38:25 +0200 Subject: Make disk_format and container_format optional for libvirt's snapshot implementation. --- nova/virt/libvirt/connection.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) (limited to 'nova') diff --git a/nova/virt/libvirt/connection.py b/nova/virt/libvirt/connection.py index 0b93a8399..3663337e9 100644 --- a/nova/virt/libvirt/connection.py +++ b/nova/virt/libvirt/connection.py @@ -392,9 +392,7 @@ class LibvirtConnection(driver.ComputeDriver): nova.image.get_image_service(image_href) snapshot = snapshot_image_service.show(context, snapshot_image_id) - metadata = {'disk_format': base['disk_format'], - 'container_format': base['container_format'], - 'is_public': False, + metadata = {'is_public': False, 'status': 'active', 'name': snapshot['name'], 'properties': { @@ -409,6 +407,12 @@ class LibvirtConnection(driver.ComputeDriver): arch = base['properties']['architecture'] metadata['properties']['architecture'] = arch + if 'disk_format' in base: + metadata['disk_format'] = base['disk_format'] + + if 'container_format' in base: + metadata['container_format'] = base['container_format'] + # Make the snapshot snapshot_name = uuid.uuid4().hex snapshot_xml = """ -- cgit From e3433605d77492a58916d2e131eb0701baf849fa Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Fri, 5 Aug 2011 07:19:35 -0700 Subject: pep8 violations sneaking into trunk? --- nova/api/direct.py | 1 + nova/api/openstack/common.py | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/api/direct.py b/nova/api/direct.py index 139c46d63..fdd2943d2 100644 --- a/nova/api/direct.py +++ b/nova/api/direct.py @@ -48,6 +48,7 @@ import nova.api.openstack.wsgi # Global storage for registering modules. ROUTES = {} + def register_service(path, handle): """Register a service handle at a given path. diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py index 715b9e4a4..75e862630 100644 --- a/nova/api/openstack/common.py +++ b/nova/api/openstack/common.py @@ -154,7 +154,8 @@ def remove_version_from_href(href): """ parsed_url = urlparse.urlsplit(href) - new_path = re.sub(r'^/v[0-9]+\.[0-9]+(/|$)', r'\1', parsed_url.path, count=1) + new_path = re.sub(r'^/v[0-9]+\.[0-9]+(/|$)', r'\1', parsed_url.path, + count=1) if new_path == parsed_url.path: msg = _('href %s does not contain version') % href -- cgit From 19e50320e36f02ce11a6aaae8f88a6ddbc132859 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Fri, 5 Aug 2011 07:21:55 -0700 Subject: pep8 violations sneaking into trunk? --- nova/db/sqlalchemy/api.py | 3 ++- nova/tests/test_xenapi.py | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index ce12ba4e0..8cfa94ed7 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -3301,7 +3301,8 @@ def instance_type_extra_specs_delete(context, instance_type_id, key): @require_context -def instance_type_extra_specs_get_item(context, instance_type_id, key, session=None): +def instance_type_extra_specs_get_item(context, instance_type_id, key, + session=None): if not session: session = get_session() diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index 8048e5341..dfc1eeb0a 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -767,7 +767,6 @@ class XenAPIMigrateInstance(test.TestCase): conn = xenapi_conn.get_connection(False) conn.migrate_disk_and_power_off(instance, '127.0.0.1') - def test_revert_migrate(self): instance = db.instance_create(self.context, self.values) self.called = False -- cgit From a77f10ee052fdafeb9d52407695719397c52e68d Mon Sep 17 00:00:00 2001 From: Ilya Alekseyev Date: Fri, 5 Aug 2011 19:45:17 +0400 Subject: methods renamed --- nova/tests/test_nova_manage.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'nova') diff --git a/nova/tests/test_nova_manage.py b/nova/tests/test_nova_manage.py index ca9ffa222..9c6563f14 100644 --- a/nova/tests/test_nova_manage.py +++ b/nova/tests/test_nova_manage.py @@ -16,6 +16,7 @@ # under the License. import os +import sys TOPDIR = os.path.normpath(os.path.join( os.path.dirname(os.path.abspath(__file__)), @@ -23,8 +24,10 @@ TOPDIR = os.path.normpath(os.path.join( os.pardir)) NOVA_MANAGE_PATH = os.path.join(TOPDIR, 'bin', 'nova-manage') +sys.dont_write_bytecode = True import imp -nova_manage = imp.load_source('nova_manage', NOVA_MANAGE_PATH) +nova_manage = imp.load_source('nova_manage.py', NOVA_MANAGE_PATH) +sys.dont_write_bytecode = False import netaddr from nova import context @@ -70,10 +73,10 @@ class FixedIpCommandsTestCase(test.TestCase): '10.0.0.100') self.assertEqual(address['reserved'], True) - def test_waste(self): + def test_unreserve(self): db.fixed_ip_update(context.get_admin_context(), '10.0.0.100', {'reserved': True}) - self.commands.waste('10.0.0.100') + self.commands.unreserve('10.0.0.100') address = db.fixed_ip_get_by_address(context.get_admin_context(), '10.0.0.100') self.assertEqual(address['reserved'], False) -- cgit From 9602a558b6be6e6812626b986c0f9557a3862fe6 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Fri, 5 Aug 2011 11:59:14 -0400 Subject: glance image service pagination --- nova/api/openstack/images.py | 4 +-- nova/exception.py | 4 +++ nova/image/glance.py | 82 +++++++++++++++++++++++++++++--------------- 3 files changed, 61 insertions(+), 29 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index c76738d30..b9bc83fde 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -143,7 +143,7 @@ class ControllerV10(Controller): """ context = req.environ['nova.context'] filters = self._get_filters(req) - images = self._image_service.index(context, filters) + images = self._image_service.index(context, filters=filters) images = common.limited(images, req) builder = self.get_builder(req).build return dict(images=[builder(image, detail=False) for image in images]) @@ -156,7 +156,7 @@ class ControllerV10(Controller): """ context = req.environ['nova.context'] filters = self._get_filters(req) - images = self._image_service.detail(context, filters) + images = self._image_service.detail(context, filters=filters) images = common.limited(images, req) builder = self.get_builder(req).build return dict(images=[builder(image, detail=True) for image in images]) diff --git a/nova/exception.py b/nova/exception.py index 68e6ac937..792e306c1 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -150,6 +150,10 @@ class NovaException(Exception): return self._error_string +class ImagePaginationFailed(NovaException): + message = _("Failed to paginate through images from image service") + + class VirtualInterfaceCreateException(NovaException): message = _("Virtual Interface creation failed") diff --git a/nova/image/glance.py b/nova/image/glance.py index 44a3c6f83..ccb1b7ec0 100644 --- a/nova/image/glance.py +++ b/nova/image/glance.py @@ -87,42 +87,70 @@ class GlanceImageService(service.BaseImageService): """Sets the client's auth token.""" self.client.set_auth_token(context.auth_token) - def index(self, context, filters=None, marker=None, limit=None): + def index(self, context, **kwargs): """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: - # NOTE(vish): don't filter out private images - filters['is_public'] = 'none' - image_metas = self.client.get_images_detailed(filters=filters, - marker=marker, - limit=limit) + params = self._extract_query_params(kwargs) + image_metas = self._get_images(context, **params) + + images = [] for image_meta in image_metas: + # 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 if self._is_image_available(context, image_meta): meta_subset = utils.subset_dict(image_meta, ('id', 'name')) - filtered.append(meta_subset) - return filtered + images.append(meta_subset) + return images - def detail(self, context, filters=None, marker=None, limit=None): + def detail(self, context, **kwargs): """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: - # NOTE(vish): don't filter out private images - filters['is_public'] = 'none' - image_metas = self.client.get_images_detailed(filters=filters, - marker=marker, - limit=limit) + params = self._extract_query_params(kwargs) + image_metas = self._get_images(context, **params) + + images = [] for image_meta in image_metas: if self._is_image_available(context, image_meta): base_image_meta = self._translate_to_base(image_meta) - filtered.append(base_image_meta) - return filtered + images.append(base_image_meta) + return images + + def _extract_query_params(self, params): + _params = {} + accepted_params = ('filters', 'marker', 'limit') + #'sort_key', 'sort_dir') + for param in accepted_params: + _params[param] = params.get(param) + + return _params + + def _get_images(self, context, **kwargs): + """Get image entitites from images service""" + self._set_client_context(context) + + # ensure filters is a dict + kwargs['filters'] = kwargs['filters'] or {} + # NOTE(vish): don't filter out private images + kwargs['filters'].setdefault('is_public', 'none') + + return self._fetch_images(self.client.get_images_detailed, **kwargs) + + def _fetch_images(self, fetch_func, **kwargs): + """Paginate through results from glance server""" + images = fetch_func(**kwargs) + + for image in images: + yield image + else: + # break out of recursive loop to end pagination + return + + try: + # attempt to advance the marker in order to fetch next page + kwargs['marker'] = images[-1]['id'] + except KeyError: + raise exception.ImagePaginationFailed() + + self._fetch_images(fetch_func, **kwargs) def show(self, context, image_id): """Returns a dict with image data for the given opaque image id.""" -- cgit From 149153085027237c343cc325e163979f1cd31a21 Mon Sep 17 00:00:00 2001 From: Gabe Westmaas Date: Fri, 5 Aug 2011 16:22:21 +0000 Subject: Moving from assertDictEqual to assertDictMatch --- nova/tests/api/openstack/test_server_actions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/tests/api/openstack/test_server_actions.py b/nova/tests/api/openstack/test_server_actions.py index 005923f4e..7e24d24fd 100644 --- a/nova/tests/api/openstack/test_server_actions.py +++ b/nova/tests/api/openstack/test_server_actions.py @@ -986,7 +986,7 @@ class TestServerActionXMLDeserializerV11(test.TestCase): ], }, } - self.assertDictEqual(request['body'], expected) + self.assertDictMatch(request['body'], expected) def test_rebuild_minimum(self): serial_request = """ @@ -999,7 +999,7 @@ class TestServerActionXMLDeserializerV11(test.TestCase): "imageRef": "http://localhost/images/1", }, } - self.assertDictEqual(request['body'], expected) + self.assertDictMatch(request['body'], expected) def test_rebuild_no_imageRef(self): serial_request = """ -- cgit From 681d3e2bfac2aa8e19cb393591b99efcbcdd8230 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Fri, 5 Aug 2011 14:15:53 -0400 Subject: re-enabling sort_key/sort_dir and fixing filters line --- nova/image/glance.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'nova') diff --git a/nova/image/glance.py b/nova/image/glance.py index ccb1b7ec0..23749e560 100644 --- a/nova/image/glance.py +++ b/nova/image/glance.py @@ -116,10 +116,11 @@ class GlanceImageService(service.BaseImageService): def _extract_query_params(self, params): _params = {} - accepted_params = ('filters', 'marker', 'limit') - #'sort_key', 'sort_dir') + accepted_params = ('filters', 'marker', 'limit', + 'sort_key', 'sort_dir') for param in accepted_params: - _params[param] = params.get(param) + if param in params: + _params[param] = params.get(param) return _params @@ -128,7 +129,7 @@ class GlanceImageService(service.BaseImageService): self._set_client_context(context) # ensure filters is a dict - kwargs['filters'] = kwargs['filters'] or {} + kwargs['filters'] = kwargs.get('filters', {}) # NOTE(vish): don't filter out private images kwargs['filters'].setdefault('is_public', 'none') -- cgit From a3e618bdc64f5e2cbb62fcf4a5b2df0e437c4fd9 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Fri, 5 Aug 2011 15:02:18 -0400 Subject: fixing filters get --- nova/image/glance.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/image/glance.py b/nova/image/glance.py index 23749e560..da93f0d1c 100644 --- a/nova/image/glance.py +++ b/nova/image/glance.py @@ -129,7 +129,7 @@ class GlanceImageService(service.BaseImageService): self._set_client_context(context) # ensure filters is a dict - kwargs['filters'] = kwargs.get('filters', {}) + kwargs['filters'] = kwargs.get('filters') or {} # NOTE(vish): don't filter out private images kwargs['filters'].setdefault('is_public', 'none') -- cgit From f03c926a7d28ee35789048ea53c36cd452ed3571 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Fri, 5 Aug 2011 14:28:22 -0500 Subject: Allow actions queries by UUID and PEP8 fixes. --- nova/api/direct.py | 1 + nova/api/openstack/common.py | 3 ++- nova/db/sqlalchemy/api.py | 10 ++++++++-- nova/tests/test_image.py | 2 ++ nova/tests/test_xenapi.py | 1 - 5 files changed, 13 insertions(+), 4 deletions(-) (limited to 'nova') diff --git a/nova/api/direct.py b/nova/api/direct.py index 139c46d63..fdd2943d2 100644 --- a/nova/api/direct.py +++ b/nova/api/direct.py @@ -48,6 +48,7 @@ import nova.api.openstack.wsgi # Global storage for registering modules. ROUTES = {} + def register_service(path, handle): """Register a service handle at a given path. diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py index 715b9e4a4..4548c2c75 100644 --- a/nova/api/openstack/common.py +++ b/nova/api/openstack/common.py @@ -154,7 +154,8 @@ def remove_version_from_href(href): """ parsed_url = urlparse.urlsplit(href) - new_path = re.sub(r'^/v[0-9]+\.[0-9]+(/|$)', r'\1', parsed_url.path, count=1) + new_path = re.sub(r'^/v[0-9]+\.[0-9]+(/|$)', r'\1', parsed_url.path, + count=1) if new_path == parsed_url.path: msg = _('href %s does not contain version') % href diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index ce12ba4e0..f469dc0e5 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -1426,9 +1426,14 @@ def instance_action_create(context, values): def instance_get_actions(context, instance_id): """Return the actions associated to the given instance id""" session = get_session() + + if utils.is_uuid_like(instance_id): + instance = instance_get_by_uuid(context, instance_id, session) + instance_id = instance.id + return session.query(models.InstanceActions).\ filter_by(instance_id=instance_id).\ - all() + all() ################### @@ -3301,7 +3306,8 @@ def instance_type_extra_specs_delete(context, instance_type_id, key): @require_context -def instance_type_extra_specs_get_item(context, instance_type_id, key, session=None): +def instance_type_extra_specs_get_item(context, instance_type_id, key, + session=None): if not session: session = get_session() diff --git a/nova/tests/test_image.py b/nova/tests/test_image.py index 5ec8812f3..9680d6f2b 100644 --- a/nova/tests/test_image.py +++ b/nova/tests/test_image.py @@ -22,6 +22,7 @@ from nova import exception from nova import test import nova.image + class _ImageTestCase(test.TestCase): def setUp(self): super(_ImageTestCase, self).setUp() @@ -126,6 +127,7 @@ class _ImageTestCase(test.TestCase): index = self.image_service.index(self.context) self.assertEquals(len(index), 0) + class FakeImageTestCase(_ImageTestCase): def setUp(self): super(FakeImageTestCase, self).setUp() diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index 8048e5341..dfc1eeb0a 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -767,7 +767,6 @@ class XenAPIMigrateInstance(test.TestCase): conn = xenapi_conn.get_connection(False) conn.migrate_disk_and_power_off(instance, '127.0.0.1') - def test_revert_migrate(self): instance = db.instance_create(self.context, self.values) self.called = False -- cgit From 9633e9877c7836c18c30b51c8494abfb025e64ca Mon Sep 17 00:00:00 2001 From: Rick Harris Date: Fri, 5 Aug 2011 22:14:15 +0000 Subject: Adding flag around image-create for v1.0 --- nova/api/openstack/__init__.py | 3 +++ nova/api/openstack/images.py | 6 ++++++ nova/tests/api/openstack/test_images.py | 10 ++++++++++ 3 files changed, 19 insertions(+) (limited to 'nova') diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index d6a98c2cd..4d49df2ad 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -50,6 +50,9 @@ FLAGS = flags.FLAGS flags.DEFINE_bool('allow_admin_api', False, 'When True, this API service will accept admin operations.') +flags.DEFINE_bool('allow_instance_snapshots', + True, + 'When True, this API service will permit instance snapshot operations.') class FaultWrapper(base_wsgi.Middleware): diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index b9bc83fde..7b738e1f3 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -108,6 +108,12 @@ class ControllerV10(Controller): def create(self, req, body): """Snapshot a server instance and save the image.""" + if not FLAGS.allow_instance_snapshots: + LOG.warn(_('Rejecting snapshot request, snapshots currently' + ' disabled')) + msg = _("Instance Snapshots are not permitted at this time.") + raise webob.exc.HTTPBadRequest(explanation=msg) + try: image = body["image"] except (KeyError, TypeError): diff --git a/nova/tests/api/openstack/test_images.py b/nova/tests/api/openstack/test_images.py index 8e2e3f390..38495bbe7 100644 --- a/nova/tests/api/openstack/test_images.py +++ b/nova/tests/api/openstack/test_images.py @@ -1049,6 +1049,16 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): response = req.get_response(fakes.wsgi_app()) self.assertEqual(400, response.status_int) + def test_create_image_snapshots_disabled(self): + self.flags(allow_instance_snapshots=False) + body = dict(image=dict(serverId='123', name='Snapshot 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) + @classmethod def _make_image_fixtures(cls): image_id = 123 -- cgit From c49e99a7fc590c2dde6125843d904895ca8861a3 Mon Sep 17 00:00:00 2001 From: Rick Harris Date: Fri, 5 Aug 2011 22:29:28 +0000 Subject: Disable flag for V1 Openstack API --- nova/api/openstack/servers.py | 6 ++++++ nova/tests/api/openstack/test_server_actions.py | 18 ++++++++++++++++++ 2 files changed, 24 insertions(+) (limited to 'nova') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 1051ba571..391c7d644 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -691,6 +691,12 @@ class ControllerV11(Controller): def _action_create_image(self, input_dict, req, instance_id): """Snapshot a server instance.""" + if not FLAGS.allow_instance_snapshots: + LOG.warn(_('Rejecting snapshot request, snapshots currently' + ' disabled')) + msg = _("Instance Snapshots are not permitted at this time.") + raise webob.exc.HTTPBadRequest(explanation=msg) + entity = input_dict.get("createImage", {}) try: diff --git a/nova/tests/api/openstack/test_server_actions.py b/nova/tests/api/openstack/test_server_actions.py index 7e24d24fd..bf18bc1b0 100644 --- a/nova/tests/api/openstack/test_server_actions.py +++ b/nova/tests/api/openstack/test_server_actions.py @@ -458,6 +458,7 @@ class ServerActionsTestV11(test.TestCase): self.service.delete_all() self.sent_to_glance = {} fakes.stub_out_glance_add_image(self.stubs, self.sent_to_glance) + self.flags(allow_instance_snapshots=True) def tearDown(self): self.stubs.UnsetAll() @@ -775,6 +776,23 @@ class ServerActionsTestV11(test.TestCase): location = response.headers['Location'] self.assertEqual('http://localhost/v1.1/images/123', location) + def test_create_image_snapshots_disabled(self): + """Don't permit a snapshot if the allow_instance_snapshots flag is + False + """ + self.flags(allow_instance_snapshots=False) + 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(400, response.status_int) + def test_create_image_with_metadata(self): body = { 'createImage': { -- cgit From bdabdd50845279cbca11f510dd5da6a5aa110528 Mon Sep 17 00:00:00 2001 From: Rick Harris Date: Fri, 5 Aug 2011 22:56:08 +0000 Subject: Using decorator for snapshots enabled check --- nova/api/openstack/common.py | 14 ++++++++++++++ nova/api/openstack/images.py | 7 +------ nova/api/openstack/servers.py | 7 +------ 3 files changed, 16 insertions(+), 12 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py index 4548c2c75..ec9368140 100644 --- a/nova/api/openstack/common.py +++ b/nova/api/openstack/common.py @@ -15,6 +15,7 @@ # License for the specific language governing permissions and limitations # under the License. +import functools import re import urlparse from xml.dom import minidom @@ -280,3 +281,16 @@ class MetadataXMLSerializer(wsgi.XMLDictSerializer): def default(self, *args, **kwargs): return '' + + +def check_snapshots_enabled(f): + @functools.wraps(f) + def inner(*args, **kwargs): + if not FLAGS.allow_instance_snapshots: + LOG.warn(_('Rejecting snapshot request, snapshots currently' + ' disabled')) + msg = _("Instance Snapshots are not permitted at this time.") + raise webob.exc.HTTPBadRequest(explanation=msg) + return f(*args, **kwargs) + return inner + diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index 7b738e1f3..0aabb9e56 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -106,14 +106,9 @@ class Controller(object): class ControllerV10(Controller): """Version 1.0 specific controller logic.""" + @common.check_snapshots_enabled def create(self, req, body): """Snapshot a server instance and save the image.""" - if not FLAGS.allow_instance_snapshots: - LOG.warn(_('Rejecting snapshot request, snapshots currently' - ' disabled')) - msg = _("Instance Snapshots are not permitted at this time.") - raise webob.exc.HTTPBadRequest(explanation=msg) - try: image = body["image"] except (KeyError, TypeError): diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 391c7d644..4d6518598 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -689,14 +689,9 @@ class ControllerV11(Controller): return webob.Response(status_int=202) + @common.check_snapshots_enabled def _action_create_image(self, input_dict, req, instance_id): """Snapshot a server instance.""" - if not FLAGS.allow_instance_snapshots: - LOG.warn(_('Rejecting snapshot request, snapshots currently' - ' disabled')) - msg = _("Instance Snapshots are not permitted at this time.") - raise webob.exc.HTTPBadRequest(explanation=msg) - entity = input_dict.get("createImage", {}) try: -- cgit From b15535a20b7f717aa23f5bc6d695e574bb86c407 Mon Sep 17 00:00:00 2001 From: Rick Harris Date: Fri, 5 Aug 2011 23:26:08 +0000 Subject: Adding check to stub method --- nova/api/openstack/common.py | 2 +- nova/api/openstack/servers.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py index ec9368140..375304930 100644 --- a/nova/api/openstack/common.py +++ b/nova/api/openstack/common.py @@ -289,7 +289,7 @@ def check_snapshots_enabled(f): if not FLAGS.allow_instance_snapshots: LOG.warn(_('Rejecting snapshot request, snapshots currently' ' disabled')) - msg = _("Instance Snapshots are not permitted at this time.") + msg = _("Instance snapshots are not permitted at this time.") raise webob.exc.HTTPBadRequest(explanation=msg) return f(*args, **kwargs) return inner diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 4d6518598..f1a27a98c 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -240,6 +240,7 @@ class Controller(object): resp.headers['Location'] = image_ref return resp + @common.check_snapshots_enabled def _action_create_image(self, input_dict, req, id): return exc.HTTPNotImplemented() -- cgit From 8c6ccfd51698bffe0d56193bc4137ad80708e6d3 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Fri, 5 Aug 2011 16:41:03 -0700 Subject: zone_aware_scheduler classes couldn't build instances due to a change to compute api's create_db_entry_for_new_instance call. now passing image argument down to the scheduler and through to the call. updated a existing test to cover this --- nova/compute/api.py | 4 ++++ nova/scheduler/zone_aware_scheduler.py | 3 ++- nova/tests/scheduler/test_zone_aware_scheduler.py | 27 ++++++++++++++++++----- 3 files changed, 28 insertions(+), 6 deletions(-) (limited to 'nova') diff --git a/nova/compute/api.py b/nova/compute/api.py index 80d54d029..752f29384 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -360,6 +360,7 @@ class API(base.Base): instance_type, zone_blob, availability_zone, injected_files, admin_password, + image, instance_id=None, num_instances=1): """Send the run_instance request to the schedulers for processing.""" pid = context.project_id @@ -373,6 +374,7 @@ class API(base.Base): filter_class = 'nova.scheduler.host_filter.InstanceTypeFilter' request_spec = { + 'image': image, 'instance_properties': base_options, 'instance_type': instance_type, 'filter': filter_class, @@ -415,6 +417,7 @@ class API(base.Base): instance_type, zone_blob, availability_zone, injected_files, admin_password, + image, num_instances=num_instances) return base_options['reservation_id'] @@ -463,6 +466,7 @@ class API(base.Base): instance_type, zone_blob, availability_zone, injected_files, admin_password, + image, instance_id=instance_id) return [dict(x.iteritems()) for x in instances] diff --git a/nova/scheduler/zone_aware_scheduler.py b/nova/scheduler/zone_aware_scheduler.py index d99d7214c..a47bf7fe7 100644 --- a/nova/scheduler/zone_aware_scheduler.py +++ b/nova/scheduler/zone_aware_scheduler.py @@ -58,12 +58,13 @@ class ZoneAwareScheduler(driver.Scheduler): """Create the requested resource in this Zone.""" host = build_plan_item['hostname'] base_options = request_spec['instance_properties'] + image = request_spec['image'] # TODO(sandy): I guess someone needs to add block_device_mapping # support at some point? Also, OS API has no concept of security # groups. instance = compute_api.API().create_db_entry_for_new_instance(context, - base_options, None, []) + image, base_options, None, []) instance_id = instance['id'] kwargs['instance_id'] = instance_id diff --git a/nova/tests/scheduler/test_zone_aware_scheduler.py b/nova/tests/scheduler/test_zone_aware_scheduler.py index 7833028c3..788efca52 100644 --- a/nova/tests/scheduler/test_zone_aware_scheduler.py +++ b/nova/tests/scheduler/test_zone_aware_scheduler.py @@ -21,7 +21,9 @@ import json import nova.db from nova import exception +from nova import rpc from nova import test +from nova.compute import api as compute_api from nova.scheduler import driver from nova.scheduler import zone_aware_scheduler from nova.scheduler import zone_manager @@ -114,7 +116,7 @@ def fake_provision_resource_from_blob(context, item, instance_id, def fake_decrypt_blob_returns_local_info(blob): - return {'foo': True} # values aren't important. + return {'hostname': 'foooooo'} # values aren't important. def fake_decrypt_blob_returns_child_info(blob): @@ -283,14 +285,29 @@ class ZoneAwareSchedulerTestCase(test.TestCase): global was_called sched = FakeZoneAwareScheduler() was_called = False + + def fake_create_db_entry_for_new_instance(self, context, + image, base_options, security_group, + block_device_mapping, num=1): + global was_called + was_called = True + # return fake instances + return {'id': 1, 'uuid': 'f874093c-7b17-49c0-89c3-22a5348497f9'} + + def fake_rpc_cast(*args, **kwargs): + pass + self.stubs.Set(sched, '_decrypt_blob', fake_decrypt_blob_returns_local_info) - self.stubs.Set(sched, '_provision_resource_locally', - fake_provision_resource_locally) + self.stubs.Set(compute_api.API, + 'create_db_entry_for_new_instance', + fake_create_db_entry_for_new_instance) + self.stubs.Set(rpc, 'cast', fake_rpc_cast) - request_spec = {'blob': "Non-None blob data"} + build_plan_item = {'blob': "Non-None blob data"} + request_spec = {'image': {}, 'instance_properties': {}} - sched._provision_resource_from_blob(None, request_spec, 1, + sched._provision_resource_from_blob(None, build_plan_item, 1, request_spec, {}) self.assertTrue(was_called) -- cgit From 7a5bb39ef11d630df26f2fcfbf249f0c34e9fa55 Mon Sep 17 00:00:00 2001 From: Rick Harris Date: Fri, 5 Aug 2011 18:51:17 -0500 Subject: Pep8 fix --- nova/api/openstack/common.py | 1 - 1 file changed, 1 deletion(-) (limited to 'nova') diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py index 375304930..5226cdf9a 100644 --- a/nova/api/openstack/common.py +++ b/nova/api/openstack/common.py @@ -293,4 +293,3 @@ def check_snapshots_enabled(f): raise webob.exc.HTTPBadRequest(explanation=msg) return f(*args, **kwargs) return inner - -- cgit From 82eb299fd0fa6601d4704836ed7e76369f086ffc Mon Sep 17 00:00:00 2001 From: "Dave Walker (Daviey)" Date: Sat, 6 Aug 2011 20:18:35 +0100 Subject: simplified test cases further, thanks to trunk changes --- nova/tests/test_api.py | 12 ------------ 1 file changed, 12 deletions(-) (limited to 'nova') diff --git a/nova/tests/test_api.py b/nova/tests/test_api.py index 3af1563fa..533447362 100644 --- a/nova/tests/test_api.py +++ b/nova/tests/test_api.py @@ -342,12 +342,6 @@ class ApiEc2TestCase(test.TestCase): spaces, dashes, and underscores. """ self.expect_http() self.mox.ReplayAll() - user = self.manager.create_user('fake', 'fake', 'fake', admin=True) - project = self.manager.create_project('fake', 'fake', 'fake') - - # At the moment, you need both of these to actually be netadmin - self.manager.add_role('fake', 'netadmin') - project.add_role('fake', 'netadmin') # Test block group_name of non alphanumeric characters, spaces, # dashes, and underscores. @@ -361,12 +355,6 @@ class ApiEc2TestCase(test.TestCase): API Spec states that the length should not exceed 255 chars """ self.expect_http() self.mox.ReplayAll() - user = self.manager.create_user('fake', 'fake', 'fake', admin=True) - project = self.manager.create_project('fake', 'fake', 'fake') - - # At the moment, you need both of these to actually be netadmin - self.manager.add_role('fake', 'netadmin') - project.add_role('fake', 'netadmin') # Test block group_name > 255 chars security_group_name = "".join(random.choice("poiuytrewqasdfghjklmnbvc") -- cgit From 8c75de3188fdbec6456fcf7071b6b08b9d1a0d40 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Sun, 7 Aug 2011 16:40:07 -0400 Subject: Set image progress to 100 if the image is active. --- nova/api/openstack/views/images.py | 4 +++- nova/tests/api/openstack/test_images.py | 19 +++++++++++++++++-- 2 files changed, 20 insertions(+), 3 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/views/images.py b/nova/api/openstack/views/images.py index 873ce212a..8539fbcbf 100644 --- a/nova/api/openstack/views/images.py +++ b/nova/api/openstack/views/images.py @@ -77,7 +77,9 @@ class ViewBuilder(object): "status": image_obj.get("status"), }) - if image["status"] == "SAVING": + if image["status"] == "ACTIVE": + image["progress"] = 100 + else: image["progress"] = 0 return image diff --git a/nova/tests/api/openstack/test_images.py b/nova/tests/api/openstack/test_images.py index 8e2e3f390..498e8b14c 100644 --- a/nova/tests/api/openstack/test_images.py +++ b/nova/tests/api/openstack/test_images.py @@ -379,6 +379,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): "updated": self.NOW_API_FORMAT, "created": self.NOW_API_FORMAT, "status": "ACTIVE", + "progress": 100, }, } @@ -402,6 +403,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): "updated": self.NOW_API_FORMAT, "created": self.NOW_API_FORMAT, "status": "QUEUED", + "progress": 0, 'server': { 'id': 42, "links": [{ @@ -444,6 +446,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): updated="%(expected_now)s" created="%(expected_now)s" status="ACTIVE" + progress="100" xmlns="http://docs.rackspacecloud.com/servers/api/v1.0" /> """ % (locals())) @@ -463,6 +466,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): updated="%(expected_now)s" created="%(expected_now)s" status="ACTIVE" + progress="100" xmlns="http://docs.rackspacecloud.com/servers/api/v1.0" /> """ % (locals())) @@ -587,6 +591,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): 'updated': self.NOW_API_FORMAT, 'created': self.NOW_API_FORMAT, 'status': 'ACTIVE', + 'progress': 100, }, { 'id': 124, @@ -594,6 +599,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): 'updated': self.NOW_API_FORMAT, 'created': self.NOW_API_FORMAT, 'status': 'QUEUED', + 'progress': 0, }, { 'id': 125, @@ -608,7 +614,8 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): 'name': 'active snapshot', 'updated': self.NOW_API_FORMAT, 'created': self.NOW_API_FORMAT, - 'status': 'ACTIVE' + 'status': 'ACTIVE', + 'progress': 100, }, { 'id': 127, @@ -616,6 +623,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): 'updated': self.NOW_API_FORMAT, 'created': self.NOW_API_FORMAT, 'status': 'FAILED', + 'progress': 0, }, { 'id': 129, @@ -623,6 +631,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): 'updated': self.NOW_API_FORMAT, 'created': self.NOW_API_FORMAT, 'status': 'ACTIVE', + 'progress': 100, }] self.assertDictListMatch(expected, response_list) @@ -643,6 +652,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): 'updated': self.NOW_API_FORMAT, 'created': self.NOW_API_FORMAT, 'status': 'ACTIVE', + 'progress': 100, "links": [{ "rel": "self", "href": "http://localhost/v1.1/images/123", @@ -662,6 +672,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): 'updated': self.NOW_API_FORMAT, 'created': self.NOW_API_FORMAT, 'status': 'QUEUED', + 'progress': 0, 'server': { 'id': 42, "links": [{ @@ -723,6 +734,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): 'updated': self.NOW_API_FORMAT, 'created': self.NOW_API_FORMAT, 'status': 'ACTIVE', + 'progress': 100, 'server': { 'id': 42, "links": [{ @@ -753,6 +765,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): 'updated': self.NOW_API_FORMAT, 'created': self.NOW_API_FORMAT, 'status': 'FAILED', + 'progress': 0, 'server': { 'id': 42, "links": [{ @@ -780,6 +793,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): 'updated': self.NOW_API_FORMAT, 'created': self.NOW_API_FORMAT, 'status': 'ACTIVE', + 'progress': 100, "links": [{ "rel": "self", "href": "http://localhost/v1.1/images/129", @@ -1001,7 +1015,8 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): image_meta = json.loads(res.body)['image'] expected = {'id': 123, 'name': 'public image', 'updated': self.NOW_API_FORMAT, - 'created': self.NOW_API_FORMAT, 'status': 'ACTIVE'} + 'created': self.NOW_API_FORMAT, 'status': 'ACTIVE', + 'progress': 100} self.assertDictMatch(image_meta, expected) def test_get_image_non_existent(self): -- cgit From 309e49873fd2535fa64b242aea254b72b5cbb4a9 Mon Sep 17 00:00:00 2001 From: Naveed Massjouni Date: Sun, 7 Aug 2011 22:05:01 -0400 Subject: Adding __init__.py files --- nova/api/openstack/schemas/__init__.py | 0 nova/api/openstack/schemas/v1.1/__init__.py | 0 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 nova/api/openstack/schemas/__init__.py create mode 100644 nova/api/openstack/schemas/v1.1/__init__.py (limited to 'nova') diff --git a/nova/api/openstack/schemas/__init__.py b/nova/api/openstack/schemas/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/nova/api/openstack/schemas/v1.1/__init__.py b/nova/api/openstack/schemas/v1.1/__init__.py new file mode 100644 index 000000000..e69de29bb -- cgit From 27a77fbc2651381d9663064a363105f803781924 Mon Sep 17 00:00:00 2001 From: Johannes Erdfelt Date: Mon, 8 Aug 2011 09:30:56 +0000 Subject: Save exception and re-raise that instead of depending on thread local exception that may have been clobbered by intermediate processing --- nova/exception.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/exception.py b/nova/exception.py index 792e306c1..b017c8d87 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -96,6 +96,10 @@ def wrap_exception(notifier=None, publisher_id=None, event_type=None, try: return f(*args, **kw) except Exception, e: + # Save exception since it can be clobbered during processing + # below before we can re-raise + exc_info = sys.exc_info() + if notifier: payload = dict(args=args, exception=e) payload.update(kw) @@ -122,7 +126,9 @@ def wrap_exception(notifier=None, publisher_id=None, event_type=None, LOG.exception(_('Uncaught exception')) #logging.error(traceback.extract_stack(exc_traceback)) raise Error(str(e)) - raise + + # re-raise original exception since it may have been clobbered + raise exc_info[0], exc_info[1], exc_info[2] return wraps(f)(wrapped) return inner -- cgit From e4ee8b54d0e840050357902b78f7e48013be9096 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Mon, 8 Aug 2011 10:12:01 -0400 Subject: upper() is even better. --- nova/api/openstack/views/images.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/api/openstack/views/images.py b/nova/api/openstack/views/images.py index 8539fbcbf..912303d14 100644 --- a/nova/api/openstack/views/images.py +++ b/nova/api/openstack/views/images.py @@ -77,7 +77,7 @@ class ViewBuilder(object): "status": image_obj.get("status"), }) - if image["status"] == "ACTIVE": + if image["status"].upper() == "ACTIVE": image["progress"] = 100 else: image["progress"] = 0 -- cgit From b1a503053cb8cbeb1a4ab18e650b49cc4da15e23 Mon Sep 17 00:00:00 2001 From: Ed Leafe Date: Mon, 8 Aug 2011 14:19:53 +0000 Subject: Moved the restriction on host startup to the xenapi layer.: --- nova/api/openstack/contrib/hosts.py | 18 +++++++----------- nova/virt/xenapi/vmops.py | 2 +- nova/virt/xenapi_conn.py | 13 +++++++++++-- 3 files changed, 19 insertions(+), 14 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/contrib/hosts.py b/nova/api/openstack/contrib/hosts.py index cdf8760d5..d5bd3166b 100644 --- a/nova/api/openstack/contrib/hosts.py +++ b/nova/api/openstack/contrib/hosts.py @@ -96,21 +96,17 @@ class HostController(object): return {"host": host, "status": result} def _host_power_action(self, req, host, action): - """Reboots or shuts down the host.""" + """Reboots, shuts down or powers up the host.""" context = req.environ['nova.context'] - result = self.compute_api.host_power_action(context, host=host, - action=action) + try: + result = self.compute_api.host_power_action(context, host=host, + action=action) + except NotImplementedError as e: + raise webob.exc.HTTPBadRequest(explanation=e.msg) return {"host": host, "power_action": result} def startup(self, req, id): - """The only valid values for 'action' are 'reboot' or - 'shutdown'. For completeness' sake there is the - 'startup' option to start up a host, but this is not - technically feasible now, as we run the host on the - XenServer box. - """ - msg = _("Host startup on XenServer is not supported.") - raise webob.exc.HTTPBadRequest(explanation=msg) + return self._host_power_action(req, host=id, action="startup") def shutdown(self, req, id): return self._host_power_action(req, host=id, action="shutdown") diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index df90b62c4..2a5cf8b5e 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -1032,7 +1032,7 @@ class VMOps(object): return 'http://fakeajaxconsole/fake_url' def host_power_action(self, host, action): - """Reboots, shuts down or powers up the host.""" + """Reboots or shuts down the host.""" args = {"action": json.dumps(action)} methods = {"reboot": "host_reboot", "shutdown": "host_shutdown"} json_resp = self._call_xenhost(methods[action], args) diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index 720c9fd58..2a6a97faf 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -333,8 +333,17 @@ class XenAPIConnection(driver.ComputeDriver): return self.HostState.get_host_stats(refresh=refresh) def host_power_action(self, host, action): - """Reboots, shuts down or powers up the host.""" - return self._vmops.host_power_action(host, action) + """The only valid values for 'action' on XenServer are 'reboot' or + 'shutdown', even though the API also accepts 'startup'. As this is + not technically possible on XenServer, since the host is the same + physical machine as the hypervisor, if this is requested, we need to + raise an exception. + """ + if action in ("reboot", "shutdown"): + return self._vmops.host_power_action(host, action) + else: + msg = _("Host startup on XenServer is not supported.") + raise NotImplementedError(msg) def set_host_enabled(self, host, enabled): """Sets the specified host's ability to accept new instances.""" -- cgit From 973032959ea4b1300cb68f767885dbd3226bebd9 Mon Sep 17 00:00:00 2001 From: Ed Leafe Date: Mon, 8 Aug 2011 14:42:18 +0000 Subject: Fixed some typos from the last refactoring --- nova/api/openstack/contrib/hosts.py | 2 +- nova/tests/test_hosts.py | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/contrib/hosts.py b/nova/api/openstack/contrib/hosts.py index d5bd3166b..ecaa365b7 100644 --- a/nova/api/openstack/contrib/hosts.py +++ b/nova/api/openstack/contrib/hosts.py @@ -133,7 +133,7 @@ class Hosts(extensions.ExtensionDescriptor): @admin_only.admin_only def get_resources(self): - resources = [extensions.ResourceExtension('os-hosts', + resources = [extensions.ResourceExtension('os-hosts', HostController(), collection_actions={'update': 'PUT'}, member_actions={"startup": "GET", "shutdown": "GET", "reboot": "GET"})] diff --git a/nova/tests/test_hosts.py b/nova/tests/test_hosts.py index cd22571e6..a724db9da 100644 --- a/nova/tests/test_hosts.py +++ b/nova/tests/test_hosts.py @@ -94,12 +94,11 @@ class HostTestCase(test.TestCase): self.assertEqual(result_c2["status"], "disabled") def test_host_startup(self): - self.assertRaises(webob.exc.HTTPBadRequest, self.controller.startup, - self.req, "host_c1") + result = self.controller.startup(self.req, "host_c1") + self.assertEqual(result["power_action"], "startup") def test_host_shutdown(self): result = self.controller.shutdown(self.req, "host_c1") - print "RES", result self.assertEqual(result["power_action"], "shutdown") def test_host_reboot(self): -- cgit From 586359f792cb32210f83046e46a0cdb85b319fcd Mon Sep 17 00:00:00 2001 From: Ed Leafe Date: Mon, 8 Aug 2011 14:51:42 +0000 Subject: Cleaned up some old code added by the last merge --- nova/compute/manager.py | 8 +------- nova/virt/fake.py | 4 ---- nova/virt/hyperv.py | 4 ---- nova/virt/libvirt/connection.py | 4 ---- nova/virt/vmwareapi_conn.py | 4 ---- nova/virt/xenapi/vmops.py | 11 ----------- nova/virt/xenapi_conn.py | 4 ---- 7 files changed, 1 insertion(+), 38 deletions(-) (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 4908cba97..ecfbd3908 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -1,4 +1,4 @@ -#: tabstop=4 shiftwidth=4 softtabstop=4 +# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2010 United States Government as represented by the # Administrator of the National Aeronautics and Space Administration. @@ -969,12 +969,6 @@ class ComputeManager(manager.SchedulerDependentManager): """Sets the specified host's ability to accept new instances.""" return self.driver.set_host_enabled(host, enabled) - @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id()) - def set_power_state(self, context, instance_id=None, host=None, - power_state=None): - """Turns the specified host on/off, or reboots the host.""" - return self.driver.set_power_state(host, power_state) - @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id()) def get_diagnostics(self, context, instance_id): """Retrieve diagnostics for an instance on this host.""" diff --git a/nova/virt/fake.py b/nova/virt/fake.py index a811edf7a..89ad20494 100644 --- a/nova/virt/fake.py +++ b/nova/virt/fake.py @@ -519,7 +519,3 @@ class FakeConnection(driver.ComputeDriver): def set_host_enabled(self, host, enabled): """Sets the specified host's ability to accept new instances.""" pass - - def set_power_state(self, host, power_state): - """Reboots, shuts down or starts up the host.""" - pass diff --git a/nova/virt/hyperv.py b/nova/virt/hyperv.py index 6524e1739..ae30c62f0 100644 --- a/nova/virt/hyperv.py +++ b/nova/virt/hyperv.py @@ -506,7 +506,3 @@ class HyperVConnection(driver.ComputeDriver): def set_host_enabled(self, host, enabled): """Sets the specified host's ability to accept new instances.""" pass - - def set_power_state(self, host, power_state): - """Reboots, shuts down or starts up the host.""" - pass diff --git a/nova/virt/libvirt/connection.py b/nova/virt/libvirt/connection.py index af5221f55..7655bf386 100644 --- a/nova/virt/libvirt/connection.py +++ b/nova/virt/libvirt/connection.py @@ -1569,7 +1569,3 @@ class LibvirtConnection(driver.ComputeDriver): def set_host_enabled(self, host, enabled): """Sets the specified host's ability to accept new instances.""" pass - - def set_power_state(self, host, power_state): - """Reboots, shuts down or starts up the host.""" - pass diff --git a/nova/virt/vmwareapi_conn.py b/nova/virt/vmwareapi_conn.py index cfa4cb418..aaa384374 100644 --- a/nova/virt/vmwareapi_conn.py +++ b/nova/virt/vmwareapi_conn.py @@ -199,10 +199,6 @@ class VMWareESXConnection(driver.ComputeDriver): """Sets the specified host's ability to accept new instances.""" pass - def set_power_state(self, host, power_state): - """Reboots, shuts down or starts up the host.""" - pass - def plug_vifs(self, instance, network_info): """Plugs in VIFs to networks.""" self._vmops.plug_vifs(instance, network_info) diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 2a5cf8b5e..b549b33d1 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -1050,17 +1050,6 @@ class VMOps(object): return xenapi_resp.details[-1] return resp["status"] - def set_power_state(self, host, power_state): - """Reboots, shuts down or starts up the host.""" - args = {"power_state": power_state} - xenapi_resp = self._call_xenhost("set_power_state", args) - try: - resp = json.loads(xenapi_resp) - except TypeError as e: - # Already logged; return the message - return xenapi_resp.details[-1] - return resp["power_state"] - def _call_xenhost(self, method, arg_dict): """There will be several methods that will need this general handling for interacting with the xenhost plugin, so this abstracts diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index 2a6a97faf..a1c9a1e30 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -349,10 +349,6 @@ class XenAPIConnection(driver.ComputeDriver): """Sets the specified host's ability to accept new instances.""" return self._vmops.set_host_enabled(host, enabled) - def set_power_state(self, host, power_state): - """Reboots, shuts down or starts up the host.""" - return self._vmops.set_power_state(host, power_state) - class XenAPISession(object): """The session to invoke XenAPI SDK calls""" -- cgit From b23387ef7a0024ac11e0970e3b76fa3441e30a9c Mon Sep 17 00:00:00 2001 From: Ed Leafe Date: Mon, 8 Aug 2011 17:34:42 +0000 Subject: Review fixes --- nova/compute/api.py | 4 ++-- nova/compute/manager.py | 6 ++---- nova/virt/xenapi/vmops.py | 2 +- 3 files changed, 5 insertions(+), 7 deletions(-) (limited to 'nova') diff --git a/nova/compute/api.py b/nova/compute/api.py index d5d66fa57..d2c08678b 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -997,12 +997,12 @@ class API(base.Base): def set_host_enabled(self, context, host, enabled): """Sets the specified host's ability to accept new instances.""" return self._call_compute_message("set_host_enabled", context, - instance_id=None, host=host, params={"enabled": enabled}) + host=host, params={"enabled": enabled}) def host_power_action(self, context, host, action): """Reboots, shuts down or powers up the host.""" return self._call_compute_message("host_power_action", context, - instance_id=None, host=host, params={"action": action}) + host=host, params={"action": action}) @scheduler_api.reroute_compute("diagnostics") def get_diagnostics(self, context, instance_id): diff --git a/nova/compute/manager.py b/nova/compute/manager.py index ecfbd3908..cb6617c33 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -958,14 +958,12 @@ class ComputeManager(manager.SchedulerDependentManager): result)) @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id()) - def host_power_action(self, context, instance_id=None, host=None, - action=None): + def host_power_action(self, context, host=None, action=None): """Reboots, shuts down or powers up the host.""" return self.driver.host_power_action(host, action) @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id()) - def set_host_enabled(self, context, instance_id=None, host=None, - enabled=None): + def set_host_enabled(self, context, host=None, enabled=None): """Sets the specified host's ability to accept new instances.""" return self.driver.set_host_enabled(host, enabled) diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index b549b33d1..b913e764e 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -1045,7 +1045,7 @@ class VMOps(object): xenapi_resp = self._call_xenhost("set_host_enabled", args) try: resp = json.loads(xenapi_resp) - except TypeError as e: + except TypeError as e: # Already logged; return the message return xenapi_resp.details[-1] return resp["status"] -- cgit From 9788cddbf7833a82fc5589dd5f2869a309d1f657 Mon Sep 17 00:00:00 2001 From: Johannes Erdfelt Date: Mon, 8 Aug 2011 19:28:42 +0000 Subject: Import sys as well --- nova/exception.py | 1 + 1 file changed, 1 insertion(+) (limited to 'nova') diff --git a/nova/exception.py b/nova/exception.py index b017c8d87..a87728fff 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -25,6 +25,7 @@ SHOULD include dedicated exception logging. """ from functools import wraps +import sys from nova import log as logging -- cgit From 543a783cefc3b34fa4a5d4ae5b9034090666d182 Mon Sep 17 00:00:00 2001 From: Naveed Massjouni Date: Mon, 8 Aug 2011 20:23:15 -0400 Subject: Fixing a bug in nova.utils.novadir() --- nova/api/openstack/schemas/__init__.py | 0 nova/api/openstack/schemas/v1.1/__init__.py | 0 nova/utils.py | 2 +- 3 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 nova/api/openstack/schemas/__init__.py delete mode 100644 nova/api/openstack/schemas/v1.1/__init__.py (limited to 'nova') diff --git a/nova/api/openstack/schemas/__init__.py b/nova/api/openstack/schemas/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/nova/api/openstack/schemas/v1.1/__init__.py b/nova/api/openstack/schemas/v1.1/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/nova/utils.py b/nova/utils.py index 4ea623cc1..da8826f1f 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -223,7 +223,7 @@ def abspath(s): def novadir(): import nova - return os.path.abspath(nova.__file__).split('nova/__init__.pyc')[0] + return os.path.abspath(nova.__file__).split('nova/__init__.py')[0] def default_flagfile(filename='nova.conf', args=None): -- cgit