diff options
| author | Ed Leafe <ed@leafe.com> | 2011-01-07 07:28:30 -0600 |
|---|---|---|
| committer | Ed Leafe <ed@leafe.com> | 2011-01-07 07:28:30 -0600 |
| commit | b024dcf6f0c1e5a2735e84d21d6edef5ff38d1cf (patch) | |
| tree | 72a98ad437b83e6f24e0848739f5502c52dacdae | |
| parent | e66f3017373dcf9135c53ae4d510b0b2a5dcecf0 (diff) | |
| parent | e33102d23ec8f357c08e2583f8d9e3c1753bab4d (diff) | |
| download | nova-b024dcf6f0c1e5a2735e84d21d6edef5ff38d1cf.tar.gz nova-b024dcf6f0c1e5a2735e84d21d6edef5ff38d1cf.tar.xz nova-b024dcf6f0c1e5a2735e84d21d6edef5ff38d1cf.zip | |
merged changes from trunk
| -rw-r--r-- | nova/api/openstack/servers.py | 55 | ||||
| -rw-r--r-- | nova/compute/api.py | 33 | ||||
| -rw-r--r-- | nova/compute/manager.py | 88 | ||||
| -rw-r--r-- | nova/db/sqlalchemy/api.py | 7 | ||||
| -rw-r--r-- | nova/db/sqlalchemy/models.py | 2 | ||||
| -rw-r--r-- | nova/tests/test_compute.py | 19 |
6 files changed, 196 insertions, 8 deletions
diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index bc89f696c..a426a721d 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -180,6 +180,50 @@ class Controller(wsgi.Controller): return faults.Fault(exc.HTTPUnprocessableEntity()) return exc.HTTPAccepted() + def lock(self, req, id): + """ + lock the instance with id + admin only operation + + """ + context = req.environ['nova.context'] + try: + self.compute_api.lock(context, id) + except: + readable = traceback.format_exc() + logging.error(_("Compute.api::lock %s"), readable) + return faults.Fault(exc.HTTPUnprocessableEntity()) + return exc.HTTPAccepted() + + def unlock(self, req, id): + """ + unlock the instance with id + admin only operation + + """ + context = req.environ['nova.context'] + try: + self.compute_api.unlock(context, id) + except: + readable = traceback.format_exc() + logging.error(_("Compute.api::unlock %s"), readable) + return faults.Fault(exc.HTTPUnprocessableEntity()) + return exc.HTTPAccepted() + + def get_lock(self, req, id): + """ + return the boolean state of (instance with id)'s lock + + """ + context = req.environ['nova.context'] + try: + self.compute_api.get_lock(context, id) + except: + readable = traceback.format_exc() + logging.error(_("Compute.api::get_lock %s"), readable) + return faults.Fault(exc.HTTPUnprocessableEntity()) + return exc.HTTPAccepted() + def pause(self, req, id): """ Permit Admins to Pause the server. """ ctxt = req.environ['nova.context'] @@ -232,4 +276,13 @@ class Controller(wsgi.Controller): def actions(self, req, id): """Permit Admins to retrieve server actions.""" ctxt = req.environ["nova.context"] - return self.compute_api.get_actions(ctxt, id_val) + items = self.compute_api.get_actions(ctxt, id) + actions = [] + # TODO(jk0): Do not do pre-serialization here once the default + # serializer is updated + for item in items: + actions.append(dict( + created_at=str(item.created_at), + action=item.action, + error=item.error)) + return dict(actions=actions) diff --git a/nova/compute/api.py b/nova/compute/api.py index a4345f337..78ffcca7a 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -147,6 +147,7 @@ class API(base.Base): 'user_data': user_data or '', 'key_name': key_name, 'key_data': key_data, + 'locked': False, 'availability_zone': availability_zone} elevated = context.elevated() @@ -322,6 +323,38 @@ class API(base.Base): """Set the root/admin password for the given instance.""" self._cast_compute_message('set_admin_password', context, instance_id) + def lock(self, context, instance_id): + """ + lock the instance with instance_id + + """ + instance = self.get_instance(context, instance_id) + host = instance['host'] + rpc.cast(context, + self.db.queue_get_for(context, FLAGS.compute_topic, host), + {"method": "lock_instance", + "args": {"instance_id": instance['id']}}) + + def unlock(self, context, instance_id): + """ + unlock the instance with instance_id + + """ + instance = self.get_instance(context, instance_id) + host = instance['host'] + rpc.cast(context, + self.db.queue_get_for(context, FLAGS.compute_topic, host), + {"method": "unlock_instance", + "args": {"instance_id": instance['id']}}) + + def get_lock(self, context, instance_id): + """ + return the boolean state of (instance with instance_id)'s lock + + """ + instance = self.get_instance(context, instance_id) + return instance['locked'] + def attach_volume(self, context, instance_id, volume_id, device): if not re.match("^/dev/[a-z]d[a-z]+$", device): raise exception.ApiError(_("Invalid device specified: %s. " diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 52acfebea..10219833b 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -38,6 +38,7 @@ import datetime import logging import random import string +import functools from nova import exception from nova import flags @@ -57,6 +58,38 @@ flags.DEFINE_integer('password_length', 12, 'Length of generated admin passwords') +def checks_instance_lock(function): + """ + decorator used for preventing action against locked instances + unless, of course, you happen to be admin + + """ + + @functools.wraps(function) + def decorated_function(self, context, instance_id, *args, **kwargs): + + logging.info(_("check_instance_lock: decorating: |%s|"), function) + logging.info(_("check_instance_lock: arguments: |%s| |%s| |%s|"), + self, + context, + instance_id) + locked = self.get_lock(context, instance_id) + admin = context.is_admin + logging.info(_("check_instance_lock: locked: |%s|"), locked) + logging.info(_("check_instance_lock: admin: |%s|"), admin) + + # if admin or unlocked call function otherwise log error + if admin or not locked: + logging.info(_("check_instance_lock: executing: |%s|"), function) + function(self, context, instance_id, *args, **kwargs) + else: + logging.error(_("check_instance_lock: not executing |%s|"), + function) + return False + + return decorated_function + + class ComputeManager(manager.Manager): """Manages the running instances from creation to destruction.""" @@ -162,6 +195,7 @@ class ComputeManager(manager.Manager): self._update_state(context, instance_id) @exception.wrap_exception + @checks_instance_lock def terminate_instance(self, context, instance_id): """Terminate an instance on this machine.""" context = context.elevated() @@ -206,6 +240,7 @@ class ComputeManager(manager.Manager): self.db.instance_destroy(context, instance_id) @exception.wrap_exception + @checks_instance_lock def reboot_instance(self, context, instance_id): """Reboot an instance on this server.""" context = context.elevated() @@ -279,6 +314,7 @@ class ComputeManager(manager.Manager): return "".join([random.choice(chrs) for i in xrange(length)]) @exception.wrap_exception + @checks_instance_lock def rescue_instance(self, context, instance_id): """Rescue an instance on this server.""" context = context.elevated() @@ -294,6 +330,7 @@ class ComputeManager(manager.Manager): self._update_state(context, instance_id) @exception.wrap_exception + @checks_instance_lock def unrescue_instance(self, context, instance_id): """Rescue an instance on this server.""" context = context.elevated() @@ -313,6 +350,7 @@ class ComputeManager(manager.Manager): self._update_state(context, instance_id) @exception.wrap_exception + @checks_instance_lock def pause_instance(self, context, instance_id): """Pause an instance on this server.""" context = context.elevated() @@ -330,6 +368,7 @@ class ComputeManager(manager.Manager): result)) @exception.wrap_exception + @checks_instance_lock def unpause_instance(self, context, instance_id): """Unpause a paused instance on this server.""" context = context.elevated() @@ -357,8 +396,12 @@ class ComputeManager(manager.Manager): return self.driver.get_diagnostics(instance_ref) @exception.wrap_exception + @checks_instance_lock def suspend_instance(self, context, instance_id): - """suspend the instance with instance_id""" + """ + suspend the instance with instance_id + + """ context = context.elevated() instance_ref = self.db.instance_get(context, instance_id) @@ -373,8 +416,12 @@ class ComputeManager(manager.Manager): result)) @exception.wrap_exception + @checks_instance_lock def resume_instance(self, context, instance_id): - """resume the suspended instance with instance_id""" + """ + resume the suspended instance with instance_id + + """ context = context.elevated() instance_ref = self.db.instance_get(context, instance_id) @@ -389,6 +436,41 @@ class ComputeManager(manager.Manager): result)) @exception.wrap_exception + def lock_instance(self, context, instance_id): + """ + lock the instance with instance_id + + """ + context = context.elevated() + instance_ref = self.db.instance_get(context, instance_id) + + logging.debug(_('instance %s: locking'), instance_id) + self.db.instance_update(context, instance_id, {'locked': True}) + + @exception.wrap_exception + def unlock_instance(self, context, instance_id): + """ + unlock the instance with instance_id + + """ + context = context.elevated() + instance_ref = self.db.instance_get(context, instance_id) + + logging.debug(_('instance %s: unlocking'), instance_id) + self.db.instance_update(context, instance_id, {'locked': False}) + + @exception.wrap_exception + def get_lock(self, context, instance_id): + """ + return the boolean state of (instance with instance_id)'s lock + + """ + context = context.elevated() + logging.debug(_('instance %s: getting locked state'), instance_id) + instance_ref = self.db.instance_get(context, instance_id) + return instance_ref['locked'] + + @exception.wrap_exception def get_console_output(self, context, instance_id): """Send the console output for an instance.""" context = context.elevated() @@ -398,6 +480,7 @@ class ComputeManager(manager.Manager): return self.driver.get_console_output(instance_ref) @exception.wrap_exception + @checks_instance_lock def attach_volume(self, context, instance_id, volume_id, mountpoint): """Attach a volume to an instance.""" context = context.elevated() @@ -427,6 +510,7 @@ class ComputeManager(manager.Manager): return True @exception.wrap_exception + @checks_instance_lock def detach_volume(self, context, instance_id, volume_id): """Detach a volume from an instance.""" context = context.elevated() diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 34c73490e..0e5c14275 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -863,12 +863,9 @@ 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() - actions = {} - for action in session.query(models.InstanceActions).\ + return session.query(models.InstanceActions).\ filter_by(instance_id=instance_id).\ - all(): - actions[action.action] = action.error - return actions + all() ################### diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index 62bb1780d..1ed366127 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -224,6 +224,8 @@ class Instance(BASE, NovaBase): display_name = Column(String(255)) display_description = Column(String(255)) + locked = Column(Boolean) + # TODO(vish): see Ewan's email about state improvements, probably # should be in a driver base class or some such # vmstate_state = running, halted, suspended, paused diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py index 31e251d5e..41900a2db 100644 --- a/nova/tests/test_compute.py +++ b/nova/tests/test_compute.py @@ -185,3 +185,22 @@ class ComputeTestCase(test.TestCase): self.context, instance_id) self.compute.terminate_instance(self.context, instance_id) + + def test_lock(self): + """ensure locked instance cannot be changed""" + instance_id = self._create_instance() + self.compute.run_instance(self.context, instance_id) + + non_admin_context = context.RequestContext(None, None, False, False) + + # decorator should return False (fail) with locked nonadmin context + self.compute.lock_instance(self.context, instance_id) + ret_val = self.compute.reboot_instance(non_admin_context, instance_id) + self.assertEqual(ret_val, False) + + # decorator should return None (success) with unlocked nonadmin context + self.compute.unlock_instance(self.context, instance_id) + ret_val = self.compute.reboot_instance(non_admin_context, instance_id) + self.assertEqual(ret_val, None) + + self.compute.terminate_instance(self.context, instance_id) |
