From d224b0509273ca8a92c5c2b9abca69038835935c Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Wed, 30 Mar 2011 15:10:40 -0400 Subject: adding v1.0 support for rebuild; adding compute api rebuild support --- nova/api/openstack/servers.py | 19 ++++++++++++++++--- nova/compute/api.py | 9 +++++++++ nova/tests/api/openstack/test_servers.py | 27 +++++++++++++++++++++++---- 3 files changed, 48 insertions(+), 7 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index f7696d918..4b2703549 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -317,9 +317,6 @@ class Controller(wsgi.Controller): return faults.Fault(exc.HTTPBadRequest()) return exc.HTTPAccepted() - def _action_rebuild(self, input_dict, req, id): - return faults.Fault(exc.HTTPNotImplemented()) - def _action_resize(self, input_dict, req, id): """ Resizes a given instance to the flavor size requested """ try: @@ -606,6 +603,19 @@ class ControllerV10(Controller): except exception.TimeoutException: return exc.HTTPRequestTimeout() + def _action_rebuild(self, input_dict, req, id): + context = req.environ['nova.context'] + if (not 'rebuild' in input_dict + or not 'imageId' in input_dict['rebuild']): + msg = _("No imageId was specified") + return faults.Fault(exc.HTTPBadRequest(msg)) + + image_id = input_dict['rebuild']['imageId'] + + self.compute_api.rebuild(context, id, image_id) + + return exc.HTTPAccepted() + class ControllerV11(Controller): def _image_id_from_req_data(self, data): @@ -632,6 +642,9 @@ class ControllerV11(Controller): def _limit_items(self, items, req): return common.limited_by_marker(items, req) + def _action_rebuild(self, input_dict, req, id): + return faults.Fault(exc.HTTPNotImplemented()) + class ServerCreateRequestXMLDeserializer(object): """ diff --git a/nova/compute/api.py b/nova/compute/api.py index 1dbd73f8f..93a5e7855 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -480,6 +480,15 @@ class API(base.Base): """Reboot the given instance.""" self._cast_compute_message('reboot_instance', context, instance_id) + def rebuild(self, context, instance_id, image_id, metadata=None): + """Rebuild the given instance with the provided metadata.""" + return + # default to an empty list + metadata = metadata or [] + #TODO: validate metadata + self._cast_compute_message('rebuild_instance', context, + instance_id, metadata) + def revert_resize(self, context, instance_id): """Reverts a resize, deleting the 'new' instance in the process""" context = context.elevated() diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 130b8c5d5..87abc0067 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -774,15 +774,34 @@ class ServersTest(test.TestCase): req.body = json.dumps(body) res = req.get_response(fakes.wsgi_app()) - def test_server_rebuild(self): - body = dict(server=dict( - name='server_test', imageId=2, flavorId=2, metadata={}, - personality={})) + 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) + + 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_delete_server_instance(self): req = webob.Request.blank('/v1.0/servers/1') -- cgit From e52cdaa75ac4b5c9ea37a8a8c9b1f02e8d0f638f Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Wed, 30 Mar 2011 15:33:52 -0400 Subject: Rough implementation of rebuild_instance in compute manager. --- nova/compute/manager.py | 36 ++++++++++++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 4 deletions(-) (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 08b772517..7366785dd 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -152,6 +152,11 @@ class ComputeManager(manager.SchedulerDependentManager): state = power_state.FAILED self.db.instance_set_state(context, instance_id, state) + def _update_launched_at(self, context, instance_id): + """Update the launched_at parameter of the given instance.""" + data = {'launched_at': datetime.datetime.utcnow()} + self.db.instance_update(context, instance_id, data) + def get_console_topic(self, context, **kwargs): """Retrieves the console host for a project on this host Currently this is just set in the flags for each compute @@ -232,10 +237,7 @@ class ComputeManager(manager.SchedulerDependentManager): try: self.driver.spawn(instance_ref) - now = datetime.datetime.utcnow() - self.db.instance_update(context, - instance_id, - {'launched_at': now}) + self._update_launched_at(context, instance_id) except Exception: # pylint: disable=W0702 LOG.exception(_("Instance '%s' failed to spawn. Is virtualization" " enabled in the BIOS?"), instance_id, @@ -294,6 +296,32 @@ class ComputeManager(manager.SchedulerDependentManager): # TODO(ja): should we keep it in a terminated state for a bit? self.db.instance_destroy(context, instance_id) + @exception.wrap_exception + @checks_instance_lock + def rebuild_instance(self, context, instance_id): + """Destroy and re-make this instance. + + A 'rebuild' effectively purges all existing data from the system and + remakes the VM with given 'metadata' and 'personalities'. + + :param context: `nova.RequestContext` object + :param instance_id: Instance identifier (integer) + """ + context = context.elevated() + + instance_ref = self.db.instance_get(context, instance_id) + LOG.audit(_("Rebuilding instance %s"), instance_id, context=context) + + # TODO(blamar): Detach volumes prior to rebuild. + + # NOTE(blamar): The driver interface seems to indicate `destroy` is an + # async call, but the implementations look sync... + self.driver.destroy(instance_ref) + self.driver.spawn(instance_ref) + + self._update_launched_at(context, instance_id) + self._update_state(context, instance_id) + @exception.wrap_exception @checks_instance_lock def reboot_instance(self, context, instance_id): -- cgit From cee0e90c058c3e50a3388eb4960afeb21b441f6a Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Wed, 30 Mar 2011 16:17:07 -0400 Subject: adding initial v1.1 rebuild action support --- nova/api/openstack/servers.py | 24 ++++++++++-- nova/compute/api.py | 7 +++- nova/tests/api/openstack/test_servers.py | 64 ++++++++++++++++++++++++++++++++ 3 files changed, 90 insertions(+), 5 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 4b2703549..edf98fe93 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -611,9 +611,7 @@ class ControllerV10(Controller): return faults.Fault(exc.HTTPBadRequest(msg)) image_id = input_dict['rebuild']['imageId'] - self.compute_api.rebuild(context, id, image_id) - return exc.HTTPAccepted() @@ -643,7 +641,27 @@ class ControllerV11(Controller): return common.limited_by_marker(items, req) def _action_rebuild(self, input_dict, req, id): - return faults.Fault(exc.HTTPNotImplemented()) + context = req.environ['nova.context'] + if (not 'rebuild' in input_dict + or not 'imageRef' in input_dict['rebuild']): + msg = _("No imageRef was specified") + return faults.Fault(exc.HTTPBadRequest(msg)) + + image_ref = input_dict['rebuild']['imageRef'] + image_id = common.get_id_from_href(image_ref) + + metadata = [] + if 'metadata' in input_dict['rebuild']: + try: + for k, v in input_dict['rebuild']['metadata'].items(): + metadata.append({'key': k, 'value': v}) + + except Exception: + msg = _("Improperly formatted metadata provided") + return exc.HTTPBadRequest(msg) + + self.compute_api.rebuild(context, id, image_id, metadata) + return exc.HTTPAccepted() class ServerCreateRequestXMLDeserializer(object): diff --git a/nova/compute/api.py b/nova/compute/api.py index 93a5e7855..fdfb8103b 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -482,12 +482,15 @@ class API(base.Base): def rebuild(self, context, instance_id, image_id, metadata=None): """Rebuild the given instance with the provided metadata.""" - return # default to an empty list metadata = metadata or [] #TODO: validate metadata + params = { + "image_id": image_id, + "metadata": metadata, + } self._cast_compute_message('rebuild_instance', context, - instance_id, metadata) + instance_id, params=params) def revert_resize(self, context, instance_id): """Reverts a resize, deleting the 'new' instance in the process""" diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 87abc0067..00c6d850a 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -803,6 +803,70 @@ class ServersTest(test.TestCase): res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 400) + def test_server_rebuild_accepted_minimum_v11(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_accepted_with_metadata_v11(self): + body = { + "rebuild": { + "imageRef": "http://localhost/images/2", + "metadata": { + "open": "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, 202) + + def test_server_rebuild_accepted_with_bad_metadata_v11(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_v11(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_delete_server_instance(self): req = webob.Request.blank('/v1.0/servers/1') req.method = 'DELETE' -- cgit From 158d434b9ec3018909f2f90a1808e27e28e4f704 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Thu, 31 Mar 2011 09:49:03 -0400 Subject: Rebuild improvements. --- nova/compute/manager.py | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 7366785dd..0e2a6deb9 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -152,9 +152,14 @@ class ComputeManager(manager.SchedulerDependentManager): state = power_state.FAILED self.db.instance_set_state(context, instance_id, state) - def _update_launched_at(self, context, instance_id): + def _update_launched_at(self, context, instance_id, launched_at=None): """Update the launched_at parameter of the given instance.""" - data = {'launched_at': datetime.datetime.utcnow()} + data = {'launched_at': launched_at or datetime.datetime.utcnow()} + self.db.instance_update(context, instance_id, data) + + def _update_image_id(self, context, instance_id, image_id): + """Update the image_id for the given instance.""" + data = {'image_id': image_id} self.db.instance_update(context, instance_id, data) def get_console_topic(self, context, **kwargs): @@ -298,7 +303,7 @@ class ComputeManager(manager.SchedulerDependentManager): @exception.wrap_exception @checks_instance_lock - def rebuild_instance(self, context, instance_id): + def rebuild_instance(self, context, instance_id, image_id, metadata): """Destroy and re-make this instance. A 'rebuild' effectively purges all existing data from the system and @@ -306,6 +311,8 @@ class ComputeManager(manager.SchedulerDependentManager): :param context: `nova.RequestContext` object :param instance_id: Instance identifier (integer) + :param image_id: Image identifier (integer) + :param metadata: Metadata dictionary """ context = context.elevated() @@ -314,11 +321,14 @@ class ComputeManager(manager.SchedulerDependentManager): # TODO(blamar): Detach volumes prior to rebuild. - # NOTE(blamar): The driver interface seems to indicate `destroy` is an - # async call, but the implementations look sync... self.driver.destroy(instance_ref) + + #self._update_state(context, instance_id) + instance_ref.image_id = image_id + self.driver.spawn(instance_ref) + self._update_image_id(context, instance_id, image_id) self._update_launched_at(context, instance_id) self._update_state(context, instance_id) -- cgit From 2d800666df9cb16e90a47c10dc7d5d7a800088d4 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Thu, 31 Mar 2011 10:18:44 -0400 Subject: adding metadata support for v1.1 --- nova/compute/api.py | 59 +++++++++++++++++--------------- nova/tests/api/openstack/test_servers.py | 2 +- 2 files changed, 33 insertions(+), 28 deletions(-) (limited to 'nova') diff --git a/nova/compute/api.py b/nova/compute/api.py index fdfb8103b..43f972882 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -100,26 +100,7 @@ class API(base.Base): if len(content) > content_limit: raise quota.QuotaError(code="OnsetFileContentLimitExceeded") - def create(self, context, instance_type, - image_id, kernel_id=None, ramdisk_id=None, - min_count=1, max_count=1, - display_name='', display_description='', - key_name=None, key_data=None, security_group='default', - availability_zone=None, user_data=None, metadata=[], - injected_files=None): - """Create the number of instances requested if quota and - other arguments check out ok.""" - - type_data = instance_types.get_instance_type(instance_type) - num_instances = quota.allowed_instances(context, max_count, type_data) - if num_instances < min_count: - pid = context.project_id - LOG.warn(_("Quota exceeeded for %(pid)s," - " tried to run %(min_count)s instances") % locals()) - raise quota.QuotaError(_("Instance quota exceeded. You can only " - "run %s more instances of this type.") % - num_instances, "InstanceLimitExceeded") - + def _check_metadata_quota(self, context, metadata): num_metadata = len(metadata) quota_metadata = quota.allowed_metadata_items(context, num_metadata) if quota_metadata < num_metadata: @@ -130,6 +111,7 @@ class API(base.Base): LOG.warn(msg) raise quota.QuotaError(msg, "MetadataLimitExceeded") + def _check_metadata_item_length(self, context, metadata): # Because metadata is stored in the DB, we hard-code the size limits # In future, we may support more variable length strings, so we act # as if this is quota-controlled for forwards compatibility @@ -144,6 +126,29 @@ class API(base.Base): LOG.warn(msg) raise quota.QuotaError(msg, "MetadataLimitExceeded") + def create(self, context, instance_type, + image_id, kernel_id=None, ramdisk_id=None, + min_count=1, max_count=1, + display_name='', display_description='', + key_name=None, key_data=None, security_group='default', + availability_zone=None, user_data=None, metadata=[], + injected_files=None): + """Create the number of instances requested if quota and + other arguments check out ok.""" + + type_data = instance_types.get_instance_type(instance_type) + num_instances = quota.allowed_instances(context, max_count, type_data) + if num_instances < min_count: + pid = context.project_id + LOG.warn(_("Quota exceeeded for %(pid)s," + " tried to run %(min_count)s instances") % locals()) + raise quota.QuotaError(_("Instance quota exceeded. You can only " + "run %s more instances of this type.") % + num_instances, "InstanceLimitExceeded") + + self._check_metadata_quota(context, metadata) + self._check_metadata_item_length(context, metadata) + self._check_injected_file_quota(context, injected_files) image = self.image_service.show(context, image_id) @@ -482,15 +487,15 @@ class API(base.Base): def rebuild(self, context, instance_id, image_id, metadata=None): """Rebuild the given instance with the provided metadata.""" - # default to an empty list + metadata = metadata or [] - #TODO: validate metadata - params = { - "image_id": image_id, - "metadata": metadata, - } + self._check_metadata_quota(context, metadata) + self._check_metadata_item_length(context, metadata) + self._cast_compute_message('rebuild_instance', context, - instance_id, params=params) + instance_id, params={"image_id": image_id}) + + self.db.instance_update(context, instance_id, {"metadata": metadata}) def revert_resize(self, context, instance_id): """Reverts a resize, deleting the 'new' instance in the process""" diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 00c6d850a..aab89b0b3 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -823,7 +823,7 @@ class ServersTest(test.TestCase): "rebuild": { "imageRef": "http://localhost/images/2", "metadata": { - "open": "stack", + "new": "metadata", }, }, } -- cgit From 8e079f75e6391a3fc181fce7b6d03919b9625737 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Thu, 31 Mar 2011 10:45:53 -0400 Subject: Update state between delete and spawn. --- 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 0e2a6deb9..943a455a6 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -323,7 +323,7 @@ class ComputeManager(manager.SchedulerDependentManager): self.driver.destroy(instance_ref) - #self._update_state(context, instance_id) + self._update_state(context, instance_id) instance_ref.image_id = image_id self.driver.spawn(instance_ref) -- cgit From 1ecd9b3d00f002799a9eab92f0179dcbea8b8c37 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Thu, 31 Mar 2011 14:15:42 -0400 Subject: adding 'building' power state; testing for 409 from OSAPI when rebuild requested on server being rebuild --- nova/api/openstack/servers.py | 15 ++++++++-- nova/compute/api.py | 6 ++++ nova/compute/power_state.py | 21 ++++++++------ nova/exception.py | 4 +++ nova/tests/api/openstack/test_servers.py | 48 ++++++++++++++++++++++++++++++-- 5 files changed, 81 insertions(+), 13 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index edf98fe93..fc33a8257 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -611,7 +611,13 @@ class ControllerV10(Controller): return faults.Fault(exc.HTTPBadRequest(msg)) image_id = input_dict['rebuild']['imageId'] - self.compute_api.rebuild(context, id, image_id) + + try: + self.compute_api.rebuild(context, id, image_id) + except exception.BuildInProgress: + msg = _("Unable to rebuild server that is being rebuilt") + return faults.Fault(exc.HTTPConflict(msg)) + return exc.HTTPAccepted() @@ -660,7 +666,12 @@ class ControllerV11(Controller): msg = _("Improperly formatted metadata provided") return exc.HTTPBadRequest(msg) - self.compute_api.rebuild(context, id, image_id, metadata) + try: + self.compute_api.rebuild(context, id, image_id, metadata) + except exception.BuildInProgress: + msg = _("Unable to rebuild server that is being rebuilt") + return faults.Fault(exc.HTTPConflict(msg)) + return exc.HTTPAccepted() diff --git a/nova/compute/api.py b/nova/compute/api.py index 43f972882..32b283d31 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -34,6 +34,7 @@ from nova import rpc from nova import utils from nova import volume from nova.compute import instance_types +from nova.compute import power_state from nova.scheduler import api as scheduler_api from nova.db import base @@ -488,6 +489,11 @@ class API(base.Base): def rebuild(self, context, instance_id, image_id, metadata=None): """Rebuild the given instance with the provided metadata.""" + instance = db.api.instance_get(context, instance_id) + if instance["state"] == power_state.BUILDING: + msg = _("Instance already building") + raise exception.BuildInProgress(msg) + metadata = metadata or [] self._check_metadata_quota(context, metadata) self._check_metadata_item_length(context, metadata) diff --git a/nova/compute/power_state.py b/nova/compute/power_state.py index ef013b2ef..c468fe6b3 100644 --- a/nova/compute/power_state.py +++ b/nova/compute/power_state.py @@ -30,20 +30,23 @@ SHUTOFF = 0x05 CRASHED = 0x06 SUSPENDED = 0x07 FAILED = 0x08 +BUILDING = 0x09 # TODO(justinsb): Power state really needs to be a proper class, # so that we're not locked into the libvirt status codes and can put mapping # logic here rather than spread throughout the code _STATE_MAP = { - NOSTATE: 'pending', - RUNNING: 'running', - BLOCKED: 'blocked', - PAUSED: 'paused', - SHUTDOWN: 'shutdown', - SHUTOFF: 'shutdown', - CRASHED: 'crashed', - SUSPENDED: 'suspended', - FAILED: 'failed to spawn'} + NOSTATE: 'pending', + RUNNING: 'running', + BLOCKED: 'blocked', + PAUSED: 'paused', + SHUTDOWN: 'shutdown', + SHUTOFF: 'shutdown', + CRASHED: 'crashed', + SUSPENDED: 'suspended', + FAILED: 'failed to spawn', + BUILDING: 'building', +} def name(code): diff --git a/nova/exception.py b/nova/exception.py index 4e2bbdbaf..5fa7fb56e 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -96,6 +96,10 @@ class TimeoutException(Error): pass +class BuildInProgress(Error): + pass + + class DBError(Error): """Wraps an implementation specific exception""" def __init__(self, inner_exception): diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index aab89b0b3..9e77738a6 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -31,6 +31,7 @@ from nova import flags from nova import test import nova.api.openstack from nova.api.openstack import servers +from nova.compute import power_state import nova.compute.api import nova.db.api from nova.db.sqlalchemy.models import Instance @@ -55,6 +56,12 @@ def return_server_with_addresses(private, public): return _return_server +def return_server_with_state(state): + def _return_server(context, id): + return stub_instance(id, state=state) + return _return_server + + def return_servers(context, user_id=1): return [stub_instance(i, user_id) for i in xrange(5)] @@ -71,7 +78,8 @@ def instance_address(context, instance_id): return None -def stub_instance(id, user_id=1, private_address=None, public_addresses=None): +def stub_instance(id, user_id=1, private_address=None, public_addresses=None, + state=0): metadata = [] metadata.append(InstanceMetadata(key='seq', value=id)) @@ -89,7 +97,7 @@ def stub_instance(id, user_id=1, private_address=None, public_addresses=None): "launch_index": 0, "key_name": "", "key_data": "", - "state": 0, + "state": state, "state_description": "", "memory_mb": 0, "vcpus": 0, @@ -789,6 +797,24 @@ class ServersTest(test.TestCase): res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 202) + def test_server_rebuild_rejected_when_building(self): + body = { + "rebuild": { + "imageId": 2, + }, + } + + new_return_server = return_server_with_state(power_state.BUILDING) + self.stubs.Set(nova.db.api, 'instance_get', new_return_server) + + 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": { @@ -818,6 +844,24 @@ class ServersTest(test.TestCase): res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 202) + def test_server_rebuild_rejected_when_building_v11(self): + body = { + "rebuild": { + "imageRef": "http://localhost/images/2", + }, + } + + new_return_server = return_server_with_state(power_state.BUILDING) + self.stubs.Set(nova.db.api, 'instance_get', new_return_server) + + 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_v11(self): body = { "rebuild": { -- cgit From 59e8112994e293fa1155b703abcc3e33d7cfc6c7 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Thu, 31 Mar 2011 14:35:25 -0400 Subject: pep8 --- 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 32b283d31..17bec2545 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -34,7 +34,7 @@ from nova import rpc from nova import utils from nova import volume from nova.compute import instance_types -from nova.compute import power_state +from nova.compute import power_state from nova.scheduler import api as scheduler_api from nova.db import base -- cgit From 600dc802e3775ea6b4b940e03c82e8b8ac40191c Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Thu, 31 Mar 2011 15:34:33 -0400 Subject: adding servers view mapping for BUILDING power state --- nova/api/openstack/views/servers.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'nova') diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index 4e7f62eb3..b74c8d409 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -67,6 +67,8 @@ class ViewBuilder(object): power_state.SHUTOFF: 'active', power_state.CRASHED: 'error', power_state.FAILED: 'error'} + power_state.BUILDING: 'build', + } inst_dict = { 'id': int(inst['id']), -- cgit From 6e6288d6d42bc17508b5df9781c7f04104f34fb2 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Thu, 31 Mar 2011 15:37:38 -0400 Subject: Now using the new power state instead of string. --- nova/compute/manager.py | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 2c5285dff..94d4f1991 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -141,15 +141,21 @@ class ComputeManager(manager.SchedulerDependentManager): """ self.driver.init_host(host=self.host) - def _update_state(self, context, instance_id, desc=None): + def _update_state(self, context, instance_id, state=None): """Update the state of an instance from the driver info.""" instance_ref = self.db.instance_get(context, instance_id) - try: - info = self.driver.get_info(instance_ref['name']) - state = info['state'] - except exception.NotFound: - state = power_state.FAILED - self.db.instance_set_state(context, instance_id, state, desc) + + if state is None: + try: + info = self.driver.get_info(instance_ref['name']) + except exception.NotFound: + info = None + state = power_state.FAILED + + if info is not None: + state = info['state'] + + self.db.instance_set_state(context, instance_id, state) def _update_launched_at(self, context, instance_id, launched_at=None): """Update the launched_at parameter of the given instance.""" @@ -318,7 +324,7 @@ class ComputeManager(manager.SchedulerDependentManager): LOG.audit(_("Rebuilding instance %s"), instance_id, context=context) # TODO(blamar): Detach volumes prior to rebuild. - self._update_state(context, instance_id, "rebuilding") + self._update_state(context, instance_id, power_state.BUILDING) self.driver.destroy(instance_ref) instance_ref.image_id = image_id -- cgit From 29396c4c739b6b0a26a1b24beb86aa2e7e2bc474 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Thu, 31 Mar 2011 15:37:44 -0400 Subject: Didn't run my code. Syntax error :( --- nova/api/openstack/views/servers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index b74c8d409..6496f0ee5 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -66,7 +66,7 @@ class ViewBuilder(object): power_state.SHUTDOWN: 'active', power_state.SHUTOFF: 'active', power_state.CRASHED: 'error', - power_state.FAILED: 'error'} + power_state.FAILED: 'error', power_state.BUILDING: 'build', } -- cgit From f37edcb18d27585ce6a2074a5f35eb7a84454dcf Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Thu, 31 Mar 2011 15:41:18 -0400 Subject: Adding explanation keyword to HTTPConflict --- 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 fc33a8257..563acc59e 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -616,7 +616,7 @@ class ControllerV10(Controller): self.compute_api.rebuild(context, id, image_id) except exception.BuildInProgress: msg = _("Unable to rebuild server that is being rebuilt") - return faults.Fault(exc.HTTPConflict(msg)) + return faults.Fault(exc.HTTPConflict(explanation=msg)) return exc.HTTPAccepted() @@ -670,7 +670,7 @@ class ControllerV11(Controller): self.compute_api.rebuild(context, id, image_id, metadata) except exception.BuildInProgress: msg = _("Unable to rebuild server that is being rebuilt") - return faults.Fault(exc.HTTPConflict(msg)) + return faults.Fault(exc.HTTPConflict(explanation=msg)) return exc.HTTPAccepted() -- cgit From 7688cbb07ffcfd6446dc9ede60fb9eb610809c1d Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Thu, 31 Mar 2011 16:46:08 -0400 Subject: Removal of instance_set_state from driver code, it shouldnt be there, but instead should be in the compute manager. --- nova/compute/manager.py | 15 ++++----------- nova/virt/libvirt_conn.py | 4 ++-- nova/virt/xenapi/vmops.py | 26 ++++++++++---------------- 3 files changed, 16 insertions(+), 29 deletions(-) (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 94d4f1991..db1d6950e 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -240,21 +240,16 @@ class ComputeManager(manager.SchedulerDependentManager): instance_id) # TODO(vish) check to make sure the availability zone matches - self.db.instance_set_state(context, - instance_id, - power_state.NOSTATE, - 'spawning') + self._update_state(context, instance_id, power_state.BUILDING) try: self.driver.spawn(instance_ref) self._update_launched_at(context, instance_id) - except Exception: # pylint: disable=W0702 + except Exception as ex: # pylint: disable=W0702 + LOG.debug(ex) LOG.exception(_("Instance '%s' failed to spawn. Is virtualization" " enabled in the BIOS?"), instance_id, context=context) - self.db.instance_set_state(context, - instance_id, - power_state.SHUTDOWN) self._update_state(context, instance_id) @@ -1133,9 +1128,7 @@ class ComputeManager(manager.SchedulerDependentManager): if vm_state != db_state: LOG.info(_("DB/VM state mismatch. Changing state from " "'%(db_state)s' to '%(vm_state)s'") % locals()) - self.db.instance_set_state(context, - db_instance['id'], - vm_state) + self._update_state(context, db_instance['id'], vm_state) if vm_state == power_state.SHUTOFF: # TODO(soren): This is what the compute manager does when you diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index f998a592b..bc9a031f9 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -597,8 +597,8 @@ class LibvirtConnection(driver.ComputeDriver): try: state = self.get_info(name)['state'] except (exception.NotFound, libvirt.libvirtError) as ex: - msg = _("Error while waiting for VM to run: %s") % ex - LOG.debug(msg) + msg = _("Error while waiting for VM '%(_id)s' to run: %(ex)s") + LOG.debug(msg % locals()) timer.stop() if state == power_state.RUNNING: diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index c96c35a6e..fb3ca5306 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -206,33 +206,27 @@ class VMOps(object): # NOTE(armando): Do we really need to do this in virt? # NOTE(tr3buchet): not sure but wherever we do it, we need to call # reset_network afterwards - timer = utils.LoopingCall(f=None) def _wait_for_boot(): try: state = self.get_info(instance_name)['state'] - db.instance_set_state(context.get_admin_context(), - instance['id'], state) - if state == power_state.RUNNING: - LOG.debug(_('Instance %s: booted'), instance_name) - timer.stop() - _inject_files() - return True - except Exception, exc: - LOG.warn(exc) - LOG.exception(_('instance %s: failed to boot'), - instance_name) - db.instance_set_state(context.get_admin_context(), - instance['id'], - power_state.SHUTDOWN) + except self.XenAPI.Failure as ex: + msg = _("Error while waiting for VM '%(instance_name)s' " + "to boot: %(ex)s") % locals() + LOG.debug(msg) timer.stop() return False - timer.f = _wait_for_boot + if state == power_state.RUNNING: + LOG.debug(_('VM %s is now running.') % name) + timer.stop() + _inject_files() + return True # call to reset network to configure network from xenstore self.reset_network(instance, vm_ref) + timer = utils.LoopingCall(f=_wait_for_boot) return timer.start(interval=0.5, now=True) def _get_vm_opaque_ref(self, instance_or_vm): -- cgit From dcb94be18b1f8b3591d7304208b64a296cdd71f6 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Fri, 1 Apr 2011 14:16:17 -0400 Subject: Poller needs to check for BUILDING not NOSTATE now, since we're being more explict about what is going on. --- 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 db1d6950e..1e0997a97 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -1104,7 +1104,7 @@ class ComputeManager(manager.SchedulerDependentManager): if vm_instance is None: # NOTE(justinsb): We have to be very careful here, because a # concurrent operation could be in progress (e.g. a spawn) - if db_state == power_state.NOSTATE: + if db_state == power_state.BUILDING: # Assume that NOSTATE => spawning # TODO(justinsb): This does mean that if we crash during a # spawn, the machine will never leave the spawning state, -- cgit From e057d7fd01def4db0c77b962fea925177de9a91f Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Mon, 4 Apr 2011 15:20:09 -0400 Subject: fixing log message --- 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 fb3ca5306..1dc5624eb 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -218,7 +218,7 @@ class VMOps(object): return False if state == power_state.RUNNING: - LOG.debug(_('VM %s is now running.') % name) + LOG.debug(_('VM %s is now running.') % instance_name) timer.stop() _inject_files() return True -- cgit From b2f693f63d73e3e51cb3be40b5deae720c773340 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Tue, 12 Apr 2011 09:23:52 -0400 Subject: Reverted some superfluous changes to make MP more concise. --- nova/compute/api.py | 52 ++++++++++++++++++------------------ nova/compute/manager.py | 11 ++++---- nova/virt/libvirt_conn.py | 67 +++++++++-------------------------------------- 3 files changed, 45 insertions(+), 85 deletions(-) (limited to 'nova') diff --git a/nova/compute/api.py b/nova/compute/api.py index 020d3b06d..5ec88adbd 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -105,32 +105,6 @@ class API(base.Base): if len(content) > content_limit: raise quota.QuotaError(code="OnsetFileContentLimitExceeded") - def _check_metadata_quota(self, context, metadata): - num_metadata = len(metadata) - quota_metadata = quota.allowed_metadata_items(context, num_metadata) - if quota_metadata < num_metadata: - pid = context.project_id - msg = (_("Quota exceeeded for %(pid)s," - " tried to set %(num_metadata)s metadata properties") - % locals()) - LOG.warn(msg) - raise quota.QuotaError(msg, "MetadataLimitExceeded") - - def _check_metadata_item_length(self, context, metadata): - # Because metadata is stored in the DB, we hard-code the size limits - # In future, we may support more variable length strings, so we act - # as if this is quota-controlled for forwards compatibility - for metadata_item in metadata: - k = metadata_item['key'] - v = metadata_item['value'] - if len(k) > 255 or len(v) > 255: - pid = context.project_id - msg = (_("Quota exceeeded for %(pid)s," - " metadata property key or value too long") - % locals()) - LOG.warn(msg) - raise quota.QuotaError(msg, "MetadataLimitExceeded") - def create(self, context, instance_type, image_id, kernel_id=None, ramdisk_id=None, min_count=1, max_count=1, @@ -267,6 +241,32 @@ class API(base.Base): return [dict(x.iteritems()) for x in instances] + def _check_metadata_quota(self, context, metadata): + num_metadata = len(metadata) + quota_metadata = quota.allowed_metadata_items(context, num_metadata) + if quota_metadata < num_metadata: + pid = context.project_id + msg = (_("Quota exceeeded for %(pid)s," + " tried to set %(num_metadata)s metadata properties") + % locals()) + LOG.warn(msg) + raise quota.QuotaError(msg, "MetadataLimitExceeded") + + def _check_metadata_item_length(self, context, metadata): + # Because metadata is stored in the DB, we hard-code the size limits + # In future, we may support more variable length strings, so we act + # as if this is quota-controlled for forwards compatibility + for metadata_item in metadata: + k = metadata_item['key'] + v = metadata_item['value'] + if len(k) > 255 or len(v) > 255: + pid = context.project_id + msg = (_("Quota exceeeded for %(pid)s," + " metadata property key or value too long") + % locals()) + LOG.warn(msg) + raise quota.QuotaError(msg, "MetadataLimitExceeded") + def has_finished_migration(self, context, instance_id): """Retrieves whether or not a finished migration exists for an instance""" diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 51f5322cd..04f2abc49 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -150,10 +150,11 @@ class ComputeManager(manager.SchedulerDependentManager): info = self.driver.get_info(instance_ref['name']) except exception.NotFound: info = None - state = power_state.FAILED if info is not None: state = info['state'] + else: + state = power_state.FAILED self.db.instance_set_state(context, instance_id, state) @@ -246,10 +247,10 @@ class ComputeManager(manager.SchedulerDependentManager): self.driver.spawn(instance_ref) self._update_launched_at(context, instance_id) except Exception as ex: # pylint: disable=W0702 - LOG.debug(ex) - LOG.exception(_("Instance '%s' failed to spawn. Is virtualization" - " enabled in the BIOS?"), instance_id, - context=context) + msg = _("Instance '%(instance_id)s' failed to spawn. Is " + "virtualization enabled in the BIOS? Details: " + "%(ex)s") % locals() + LOG.exception(msg) self._update_state(context, instance_id) diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 9714773b2..53382a315 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -309,27 +309,6 @@ class LibvirtConnection(driver.ComputeDriver): return infos def destroy(self, instance, cleanup=True): -<<<<<<< TREE - """Delete the VM instance from the hypervisor. - - :param instance: Object representing the instance to destroy - :param cleanup: Should we erase all of the VM's associated files? - """ - name = instance['name'] - - try: - virt_dom = self._conn.lookupByName(name) - except libvirt.libvirtError as ex: - msg = _("Instance %s not found.") % name - raise exception.NotFound(msg) - - try: - virt_dom.destroy() - except libvirt.libvirtError as ex: - # If the instance is already terminated, we're still happy - msg = _("Error encountered during `libvirt.destroy`: %s") % ex - LOG.debug(msg) -======= instance_name = instance['name'] # TODO(justinsb): Refactor all lookupByName calls for error-handling @@ -624,38 +603,16 @@ class LibvirtConnection(driver.ComputeDriver): # for xenapi(tr3buchet) @exception.wrap_exception def spawn(self, instance, network_info=None): - """Create the given VM instance using the libvirt connection. - - :param instance: Object representing the instance to create - :param network_info: Associated network information - """ - _id = instance['id'] - name = instance['name'] xml = self.to_xml(instance, network_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) -<<<<<<< TREE - - try: - self._conn.createXML(xml, 0) - except libvirt.libvirtError as ex: - msg = _("Error encountered creating VM '%(name)s': %(ex)s") - LOG.error(msg % locals()) - return False - - LOG.debug(_("VM %s successfully created.") % name) - -======= domain = self._create_new_domain(xml) LOG.debug(_("instance %s: is running"), instance['name']) ->>>>>>> MERGE-SOURCE self.firewall_driver.apply_instance_filter(instance) -<<<<<<< TREE -======= if FLAGS.start_guests_on_host_boot: LOG.debug(_("instance %s: setting autostart ON") % instance['name']) @@ -663,21 +620,23 @@ class LibvirtConnection(driver.ComputeDriver): timer = utils.LoopingCall(f=None) ->>>>>>> MERGE-SOURCE def _wait_for_boot(): - """Check to see if the VM is running.""" try: - state = self.get_info(name)['state'] - except (exception.NotFound, libvirt.libvirtError) as ex: - msg = _("Error while waiting for VM '%(_id)s' to run: %(ex)s") - LOG.debug(msg % locals()) - timer.stop() - - if state == power_state.RUNNING: - LOG.debug(_('VM %s is now running.') % name) + state = self.get_info(instance['name'])['state'] + db.instance_set_state(context.get_admin_context(), + instance['id'], state) + if state == power_state.RUNNING: + LOG.debug(_('instance %s: booted'), instance['name']) + timer.stop() + except: + LOG.exception(_('instance %s: failed to boot'), + instance['name']) + db.instance_set_state(context.get_admin_context(), + instance['id'], + power_state.SHUTDOWN) timer.stop() - timer = utils.LoopingCall(f=_wait_for_boot) + timer.f = _wait_for_boot return timer.start(interval=0.5, now=True) def _flush_xen_console(self, virsh_output): -- cgit From 764862180657dbc16b2d57d3b2027c23b86ea649 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Tue, 12 Apr 2011 09:34:52 -0400 Subject: Reverted some superfluous changes to make MP more concise. --- nova/virt/xenapi/vmops.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) (limited to 'nova') diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 6ed065280..135e59a34 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -206,27 +206,27 @@ class VMOps(object): # NOTE(armando): Do we really need to do this in virt? # NOTE(tr3buchet): not sure but wherever we do it, we need to call # reset_network afterwards + timer = utils.LoopingCall(f=None) def _wait_for_boot(): try: state = self.get_info(instance_name)['state'] - except self.XenAPI.Failure as ex: - msg = _("Error while waiting for VM '%(instance_name)s' " - "to boot: %(ex)s") % locals() - LOG.debug(msg) + if state == power_state.RUNNING: + LOG.debug(_('Instance %s: booted'), instance_name) + timer.stop() + _inject_files() + return True + except Exception, exc: + LOG.warn(exc) + LOG.exception(_('Instance %s: failed to boot'), instance_name) timer.stop() return False - if state == power_state.RUNNING: - LOG.debug(_('VM %s is now running.') % instance_name) - timer.stop() - _inject_files() - return True + timer.f = _wait_for_boot # call to reset network to configure network from xenstore self.reset_network(instance, vm_ref) - timer = utils.LoopingCall(f=_wait_for_boot) return timer.start(interval=0.5, now=True) def _get_vm_opaque_ref(self, instance_or_vm): -- cgit From ae30b0a83469b15d1986fdbbef4f1dee52d68c17 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Tue, 12 Apr 2011 09:37:06 -0400 Subject: Removed extra call from try/except. --- 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 04f2abc49..a8bb091ec 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -245,13 +245,13 @@ class ComputeManager(manager.SchedulerDependentManager): try: self.driver.spawn(instance_ref) - self._update_launched_at(context, instance_id) except Exception as ex: # pylint: disable=W0702 msg = _("Instance '%(instance_id)s' failed to spawn. Is " "virtualization enabled in the BIOS? Details: " "%(ex)s") % locals() LOG.exception(msg) + self._update_launched_at(context, instance_id) self._update_state(context, instance_id) @exception.wrap_exception -- cgit From cebc98176926f57016a508d5c59b11f55dfcf2b3 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Tue, 12 Apr 2011 10:19:37 -0400 Subject: Commit for merge of metadata_quotas preq. --- nova/api/openstack/servers.py | 103 ++++++++++++++++++++++--------- nova/compute/api.py | 20 ++++-- nova/tests/api/openstack/test_servers.py | 1 + 3 files changed, 90 insertions(+), 34 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index c9f0e1b1c..61e08211d 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -606,22 +606,32 @@ class ControllerV10(Controller): except exception.TimeoutException: return exc.HTTPRequestTimeout() - def _action_rebuild(self, input_dict, req, id): - context = req.environ['nova.context'] - if (not 'rebuild' in input_dict - or not 'imageId' in input_dict['rebuild']): - msg = _("No imageId was specified") - return faults.Fault(exc.HTTPBadRequest(msg)) + def _action_rebuild(self, info, request, instance_id): + context = request.environ['nova.context'] + instance_id = int(instance_id) - image_id = input_dict['rebuild']['imageId'] + try: + image_id = info["rebuild"]["imageId"] + except (KeyError, TypeError): + msg = _("Could not parse imageId from request.") + LOG.debug(msg) + return faults.Fault(exc.HTTPBadRequest(explanation=msg)) try: - self.compute_api.rebuild(context, id, image_id) + self.compute_api.rebuild(context, instance_id, image_id) except exception.BuildInProgress: - msg = _("Unable to rebuild server that is being rebuilt") + msg = _("Instance %d is currently being rebuilt.") % instance_id + LOG.debug(msg) return faults.Fault(exc.HTTPConflict(explanation=msg)) + except exception.Error as ex: + msg = _("Error encountered attempting to rebuild instance " + "%(instance_id): %(ex)") % locals() + LOG.error(msg) + raise - return exc.HTTPAccepted() + response = exc.HTTPAccepted() + response.empty_body = True + return response class ControllerV11(Controller): @@ -662,33 +672,66 @@ class ControllerV11(Controller): def _limit_items(self, items, req): return common.limited_by_marker(items, req) - def _action_rebuild(self, input_dict, req, id): - context = req.environ['nova.context'] - if (not 'rebuild' in input_dict - or not 'imageRef' in input_dict['rebuild']): - msg = _("No imageRef was specified") - return faults.Fault(exc.HTTPBadRequest(msg)) - - image_ref = input_dict['rebuild']['imageRef'] - image_id = common.get_id_from_href(image_ref) + def _check_metadata(self, metadata): + """Ensure that the metadata given is of the correct type.""" + try: + metadata.iteritems() + except AttributeError as ex: + msg = _("Unable to parse metadata key/value pairs.") + LOG.debug(msg) + raise faults.Fault(exc.HTTPBadRequest(explanation=msg)) + + def _check_personalities(self, personalities): + """Ensure the given personalities have valid paths and contents.""" + for personality in personalities: + try: + path = personality["path"] + contents = personality["contents"] + except (KeyError, TypeError): + msg = _("Unable to parse personality path/contents.") + LOG.info(msg) + raise faults.Fault(exc.HTTPBadRequest(explanation=msg)) - metadata = [] - if 'metadata' in input_dict['rebuild']: try: - for k, v in input_dict['rebuild']['metadata'].items(): - metadata.append({'key': k, 'value': v}) + base64.b64decode(contents) + except TypeError: + msg = _("Personality content could not be Base64 decoded.") + LOG.info(msg) + raise faults.Fault(exc.HTTPBadRequest(explanation=msg)) + + def _action_rebuild(self, info, request, instance_id): + context = request.environ['nova.context'] + instance_id = int(instance_id) + + try: + image_ref = info["rebuild"]["imageRef"] + except (KeyError, TypeError): + msg = _("Could not parse imageRef from request.") + return faults.Fault(exc.HTTPBadRequest(explanation=msg)) - except Exception: - msg = _("Improperly formatted metadata provided") - return exc.HTTPBadRequest(msg) + image_id = common.get_id_from_href(image_ref) + personalities = info["rebuild"].get("personality", []) + metadata = info["rebuild"].get("metadata", {}) + + self._check_metadata(metadata) + self._check_personalities(personalities) try: - self.compute_api.rebuild(context, id, image_id, metadata) + args = [context, instance_id, image_id, metadata, personalities] + self.compute_api.rebuild(*args) except exception.BuildInProgress: - msg = _("Unable to rebuild server that is being rebuilt") + msg = _("Instance %d is currently being rebuilt.") % instance_id + LOG.debug(msg) return faults.Fault(exc.HTTPConflict(explanation=msg)) - - return exc.HTTPAccepted() + except exception.Error as ex: + msg = _("Error encountered attempting to rebuild instance " + "%(instance_id): %(ex)") % locals() + LOG.error(msg) + raise + + response = exc.HTTPAccepted() + response.empty_body = True + return response def get_default_xmlns(self, req): return common.XML_NS_V11 diff --git a/nova/compute/api.py b/nova/compute/api.py index 5ec88adbd..e5065c3a5 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -497,10 +497,11 @@ class API(base.Base): """Reboot the given instance.""" self._cast_compute_message('reboot_instance', context, instance_id) - def rebuild(self, context, instance_id, image_id, metadata=None): + def rebuild(self, context, instance_id, image_id, metadata=None, + files_to_inject=None): """Rebuild the given instance with the provided metadata.""" - instance = db.api.instance_get(context, instance_id) + if instance["state"] == power_state.BUILDING: msg = _("Instance already building") raise exception.BuildInProgress(msg) @@ -509,11 +510,22 @@ class API(base.Base): self._check_metadata_quota(context, metadata) self._check_metadata_item_length(context, metadata) - self._cast_compute_message('rebuild_instance', context, - instance_id, params={"image_id": image_id}) + files_to_inject = files_to_inject or [] + self._check_injected_file_quota(context, files_to_inject) + self._check_injected_file_format(context, files_to_inject) self.db.instance_update(context, instance_id, {"metadata": metadata}) + rebuild_params = { + "image_id": image_id, + "injected_files": files_to_inject, + } + + self._cast_compute_message('rebuild_instance', + context, + instance_id, + params=rebuild_params) + def revert_resize(self, context, instance_id): """Reverts a resize, deleting the 'new' instance in the process""" context = context.elevated() diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 424ed2ec6..ccbdc4b38 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -981,6 +981,7 @@ class ServersTest(test.TestCase): 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 = { -- cgit From 5de1825e2c1d1cdc63790f61e05b1f8b05ded1b3 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Tue, 12 Apr 2011 11:09:31 -0400 Subject: Cleanup after prereq merge. --- nova/compute/api.py | 42 ++++++++++++------------------------------ 1 file changed, 12 insertions(+), 30 deletions(-) (limited to 'nova') diff --git a/nova/compute/api.py b/nova/compute/api.py index effe68d6d..c99d7f828 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -105,6 +105,15 @@ class API(base.Base): if len(content) > content_limit: raise quota.QuotaError(code="OnsetFileContentLimitExceeded") + def _check_injected_file_format(self, injected_files): + """Ensure given injected files are in the correct format.""" + for _, content in injected_files: + try: + base64.b64decode(content) + except TypeError: + msg = _("File contents must be base64 encoded.") + raise exception.Error(msg) + def _check_metadata_properties_quota(self, context, metadata={}): """Enforce quota limits on metadata properties""" num_metadata = len(metadata) @@ -263,32 +272,6 @@ class API(base.Base): return [dict(x.iteritems()) for x in instances] - def _check_metadata_quota(self, context, metadata): - num_metadata = len(metadata) - quota_metadata = quota.allowed_metadata_items(context, num_metadata) - if quota_metadata < num_metadata: - pid = context.project_id - msg = (_("Quota exceeeded for %(pid)s," - " tried to set %(num_metadata)s metadata properties") - % locals()) - LOG.warn(msg) - raise quota.QuotaError(msg, "MetadataLimitExceeded") - - def _check_metadata_item_length(self, context, metadata): - # Because metadata is stored in the DB, we hard-code the size limits - # In future, we may support more variable length strings, so we act - # as if this is quota-controlled for forwards compatibility - for metadata_item in metadata: - k = metadata_item['key'] - v = metadata_item['value'] - if len(k) > 255 or len(v) > 255: - pid = context.project_id - msg = (_("Quota exceeeded for %(pid)s," - " metadata property key or value too long") - % locals()) - LOG.warn(msg) - raise quota.QuotaError(msg, "MetadataLimitExceeded") - def has_finished_migration(self, context, instance_id): """Retrieves whether or not a finished migration exists for an instance""" @@ -528,13 +511,12 @@ class API(base.Base): msg = _("Instance already building") raise exception.BuildInProgress(msg) - metadata = metadata or [] - self._check_metadata_quota(context, metadata) - self._check_metadata_item_length(context, metadata) + metadata = metadata or {} + self._check_metadata_properties_quota(context, metadata) files_to_inject = files_to_inject or [] self._check_injected_file_quota(context, files_to_inject) - self._check_injected_file_format(context, files_to_inject) + self._check_injected_file_format(files_to_inject) self.db.instance_update(context, instance_id, {"metadata": metadata}) -- cgit From 2576c733c05dfd9872423f52319c28a65834ee61 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Tue, 12 Apr 2011 11:13:31 -0400 Subject: Dangerous whitespace mistake! :) --- 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 135e59a34..7f9814a10 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -222,7 +222,7 @@ class VMOps(object): timer.stop() return False - timer.f = _wait_for_boot + timer.f = _wait_for_boot # call to reset network to configure network from xenstore self.reset_network(instance, vm_ref) -- cgit From e5e1863349a1842d3f6ca452a59e574c03102ebf Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Tue, 12 Apr 2011 11:47:08 -0400 Subject: Added some tests. --- nova/compute/api.py | 1 + nova/tests/api/openstack/test_servers.py | 38 ++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+) (limited to 'nova') diff --git a/nova/compute/api.py b/nova/compute/api.py index c99d7f828..3bf18c667 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -20,6 +20,7 @@ Handles all requests relating to instances (guest vms). """ +import base64 import datetime import re import time diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index ccbdc4b38..0aa2ba3ac 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -1097,6 +1097,44 @@ class ServersTest(test.TestCase): res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 400) + def test_server_rebuild_bad_personality_v11(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_v11(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' -- cgit From bee606e08f5ba96a25d02a9358265db4a59ce5cd Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Wed, 20 Apr 2011 11:06:03 -0400 Subject: Removed _ and replaced with real variable name. --- 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 a0f0ff27e..703533f55 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -106,7 +106,7 @@ class API(base.Base): def _check_injected_file_format(self, injected_files): """Ensure given injected files are in the correct format.""" - for _, content in injected_files: + for file_path, content in injected_files: try: base64.b64decode(content) except TypeError: -- cgit From e628007bec0e313f252d8dd15d19297f99dc93f8 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Wed, 20 Apr 2011 11:09:14 -0400 Subject: Removed TODO we don't need. --- nova/compute/manager.py | 1 - 1 file changed, 1 deletion(-) (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 0e5d6c4ff..5a7b4fb11 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -319,7 +319,6 @@ class ComputeManager(manager.SchedulerDependentManager): instance_ref = self.db.instance_get(context, instance_id) LOG.audit(_("Rebuilding instance %s"), instance_id, context=context) - # TODO(blamar): Detach volumes prior to rebuild. self._update_state(context, instance_id, power_state.BUILDING) self.driver.destroy(instance_ref) -- cgit From 6c538b870005464b2bab0510b4e98a71d0d24770 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Wed, 20 Apr 2011 11:11:45 -0400 Subject: Removed no longer relevant comment. --- nova/compute/manager.py | 1 - 1 file changed, 1 deletion(-) (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 5a7b4fb11..46f910b27 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -1103,7 +1103,6 @@ class ComputeManager(manager.SchedulerDependentManager): # NOTE(justinsb): We have to be very careful here, because a # concurrent operation could be in progress (e.g. a spawn) if db_state == power_state.BUILDING: - # Assume that NOSTATE => spawning # TODO(justinsb): This does mean that if we crash during a # spawn, the machine will never leave the spawning state, # but this is just the way nova is; this function isn't -- cgit From bdbfcb49179d32da5fcecd75fb849efe71469b00 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Wed, 20 Apr 2011 11:16:35 -0400 Subject: Reverted bad merge. --- nova/virt/libvirt_conn.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 6b417124e..715512507 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -609,7 +609,7 @@ class LibvirtConnection(driver.ComputeDriver): # for xenapi(tr3buchet) @exception.wrap_exception def spawn(self, instance, network_info=None): - xml = self.to_xml(instance, network_info) + xml = self.to_xml(instance, False, network_info) self.firewall_driver.setup_basic_filtering(instance, network_info) self.firewall_driver.prepare_instance_filter(instance, network_info) -- cgit From 6c037c5c639249556fcadd871d8af91760b50e90 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Thu, 21 Apr 2011 16:17:16 -0400 Subject: Fixes and reworkings based on review. --- nova/api/openstack/servers.py | 28 +++++++++------------------- nova/compute/api.py | 11 ----------- 2 files changed, 9 insertions(+), 30 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 357a279ef..a7e240917 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -623,11 +623,6 @@ class ControllerV10(Controller): msg = _("Instance %d is currently being rebuilt.") % instance_id LOG.debug(msg) return faults.Fault(exc.HTTPConflict(explanation=msg)) - except exception.Error as ex: - msg = _("Error encountered attempting to rebuild instance " - "%(instance_id): %(ex)") % locals() - LOG.error(msg) - raise response = exc.HTTPAccepted() response.empty_body = True @@ -672,8 +667,8 @@ class ControllerV11(Controller): def _limit_items(self, items, req): return common.limited_by_marker(items, req) - def _check_metadata(self, metadata): - """Ensure that the metadata given is of the correct type.""" + def _validate_metadata(self, metadata): + """Ensure that we can work with the metadata given.""" try: metadata.iteritems() except AttributeError as ex: @@ -681,8 +676,8 @@ class ControllerV11(Controller): LOG.debug(msg) raise faults.Fault(exc.HTTPBadRequest(explanation=msg)) - def _check_personalities(self, personalities): - """Ensure the given personalities have valid paths and contents.""" + def _decode_personalities(self, personalities): + """Decode the Base64-encoded personalities.""" for personality in personalities: try: path = personality["path"] @@ -693,7 +688,7 @@ class ControllerV11(Controller): raise faults.Fault(exc.HTTPBadRequest(explanation=msg)) try: - base64.b64decode(contents) + personality["contents"] = base64.b64decode(contents) except TypeError: msg = _("Personality content could not be Base64 decoded.") LOG.info(msg) @@ -713,21 +708,16 @@ class ControllerV11(Controller): personalities = info["rebuild"].get("personality", []) metadata = info["rebuild"].get("metadata", {}) - self._check_metadata(metadata) - self._check_personalities(personalities) + self._validate_metadata(metadata) + self._decode_personalities(personalities) try: - args = [context, instance_id, image_id, metadata, personalities] - self.compute_api.rebuild(*args) + self.compute_api.rebuild(context, instance_id, image_id, metadata, + personalities) except exception.BuildInProgress: msg = _("Instance %d is currently being rebuilt.") % instance_id LOG.debug(msg) return faults.Fault(exc.HTTPConflict(explanation=msg)) - except exception.Error as ex: - msg = _("Error encountered attempting to rebuild instance " - "%(instance_id): %(ex)") % locals() - LOG.error(msg) - raise response = exc.HTTPAccepted() response.empty_body = True diff --git a/nova/compute/api.py b/nova/compute/api.py index 703533f55..4ec840b07 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -18,7 +18,6 @@ """Handles all requests relating to instances (guest vms).""" -import base64 import datetime import re import time @@ -104,15 +103,6 @@ class API(base.Base): if len(content) > content_limit: raise quota.QuotaError(code="OnsetFileContentLimitExceeded") - def _check_injected_file_format(self, injected_files): - """Ensure given injected files are in the correct format.""" - for file_path, content in injected_files: - try: - base64.b64decode(content) - except TypeError: - msg = _("File contents must be base64 encoded.") - raise exception.Error(msg) - def _check_metadata_properties_quota(self, context, metadata={}): """Enforce quota limits on metadata properties.""" num_metadata = len(metadata) @@ -526,7 +516,6 @@ class API(base.Base): files_to_inject = files_to_inject or [] self._check_injected_file_quota(context, files_to_inject) - self._check_injected_file_format(files_to_inject) self.db.instance_update(context, instance_id, {"metadata": metadata}) -- cgit From 29e9aa173ea20a7d5cb816ce7478d6c0c2c38b80 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Tue, 3 May 2011 12:32:40 -0400 Subject: adding debug log message --- nova/api/openstack/servers.py | 1 + 1 file changed, 1 insertion(+) (limited to 'nova') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index a238e6c82..3cf78e32c 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -692,6 +692,7 @@ class ControllerV11(Controller): image_ref = info["rebuild"]["imageRef"] except (KeyError, TypeError): msg = _("Could not parse imageRef from request.") + LOG.debug(msg) return faults.Fault(exc.HTTPBadRequest(explanation=msg)) image_id = common.get_id_from_href(image_ref) -- cgit