From 24e253a1feaa0a39e4095f447f62f7ea9b43c8bb Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Wed, 29 Dec 2010 18:30:01 -0600 Subject: moved check lock decorator to compute api level. altered openstack.test_servers according and wrote test for lock in tests.test_compute --- nova/api/openstack/servers.py | 43 ------------------------------- nova/compute/api.py | 44 ++++++++++++++++++++++++++++++++ nova/tests/api/openstack/test_servers.py | 35 ++++++++----------------- nova/tests/test_compute.py | 17 ++++++++++++ 4 files changed, 71 insertions(+), 68 deletions(-) diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 24fd5000c..497a04ae3 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -17,7 +17,6 @@ import logging import traceback -import functools from webob import exc @@ -36,41 +35,6 @@ LOG = logging.getLogger('server') LOG.setLevel(logging.DEBUG) -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(*args, **kwargs): - - # grab args to function - try: - if 'req' in kwargs: - req = kwargs['req'] - else: - req = args[1] - if 'id' in kwargs: - _id = kwargs['id'] - else: - req = args[2] - context = req.environ['nova.context'] - except: - logging.error(_("check_lock: argument error: |%s|, |%s|"), args, - kwargs) - # if admin or unlocked call function, otherwise 404 - locked = compute_api.ComputeAPI().get_lock(context, _id) - admin = req.environ['nova.context'].is_admin - if admin or not locked: - return function(*args, **kwargs) - - return faults.Fault(exc.HTTPMethodNotAllowed()) - - return decorated_function - - def _translate_detail_keys(inst): """ Coerces into dictionary format, mapping everything to Rackspace-like attributes for return""" @@ -147,7 +111,6 @@ class Controller(wsgi.Controller): except exception.NotFound: return faults.Fault(exc.HTTPNotFound()) - @checks_instance_lock def delete(self, req, id): """ Destroys a server """ try: @@ -175,7 +138,6 @@ class Controller(wsgi.Controller): key_data=key_pair['public_key']) return _translate_keys(instances[0]) - @checks_instance_lock def update(self, req, id): """ Updates the server name or password """ inst_dict = self._deserialize(req.body, req) @@ -197,7 +159,6 @@ class Controller(wsgi.Controller): return faults.Fault(exc.HTTPNotFound()) return exc.HTTPNoContent() - @checks_instance_lock def action(self, req, id): """ Multi-purpose method used to reboot, rebuild, and resize a server """ @@ -258,7 +219,6 @@ class Controller(wsgi.Controller): return faults.Fault(exc.HTTPUnprocessableEntity()) return exc.HTTPAccepted() - @checks_instance_lock def pause(self, req, id): """ Permit Admins to Pause the server. """ ctxt = req.environ['nova.context'] @@ -270,7 +230,6 @@ class Controller(wsgi.Controller): return faults.Fault(exc.HTTPUnprocessableEntity()) return exc.HTTPAccepted() - @checks_instance_lock def unpause(self, req, id): """ Permit Admins to Unpause the server. """ ctxt = req.environ['nova.context'] @@ -282,7 +241,6 @@ class Controller(wsgi.Controller): return faults.Fault(exc.HTTPUnprocessableEntity()) return exc.HTTPAccepted() - @checks_instance_lock def suspend(self, req, id): """permit admins to suspend the server""" context = req.environ['nova.context'] @@ -294,7 +252,6 @@ class Controller(wsgi.Controller): return faults.Fault(exc.HTTPUnprocessableEntity()) return exc.HTTPAccepted() - @checks_instance_lock def resume(self, req, id): """permit admins to resume the server from suspend""" context = req.environ['nova.context'] diff --git a/nova/compute/api.py b/nova/compute/api.py index 361ab9914..d720a8f2c 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -23,6 +23,7 @@ Handles all API requests relating to instances (guest vms). import datetime import logging import time +import functools from nova import db from nova import exception @@ -36,6 +37,40 @@ from nova.db import base FLAGS = flags.FLAGS +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(*args, **kwargs): + + # grab args to function + try: + if 'context' in kwargs: + context = kwargs['context'] + else: + context = args[1] + if 'instance_id' in kwargs: + instance_id = kwargs['instance_id'] + else: + instance_id = args[2] + locked = ComputeAPI().get_lock(context, instance_id) + admin = context.is_admin + except: + logging.error(_("check_instance_lock: argument error: |%s|, |%s|"), + args, + kwargs) + # if admin or unlocked call function, otherwise 405 + if admin or not locked: + return function(*args, **kwargs) + raise Exception(_("Instance is locked, cannot execute |%s|"), function) + + return decorated_function + + def generate_default_hostname(internal_id): """Default function to generate a hostname given an instance reference.""" return str(internal_id) @@ -198,6 +233,7 @@ class ComputeAPI(base.Base): 'project_id': context.project_id} db.security_group_create(context, values) + @checks_instance_lock def update_instance(self, context, instance_id, **kwargs): """Updates the instance in the datastore. @@ -212,6 +248,7 @@ class ComputeAPI(base.Base): """ return self.db.instance_update(context, instance_id, kwargs) + @checks_instance_lock def delete_instance(self, context, instance_id): logging.debug("Going to try and terminate %d" % instance_id) try: @@ -258,6 +295,7 @@ class ComputeAPI(base.Base): def get_instance(self, context, instance_id): return self.db.instance_get_by_internal_id(context, instance_id) + @checks_instance_lock def reboot(self, context, instance_id): """Reboot the given instance.""" instance = self.db.instance_get_by_internal_id(context, instance_id) @@ -267,6 +305,7 @@ class ComputeAPI(base.Base): {"method": "reboot_instance", "args": {"instance_id": instance['id']}}) + @checks_instance_lock def pause(self, context, instance_id): """Pause the given instance.""" instance = self.db.instance_get_by_internal_id(context, instance_id) @@ -276,6 +315,7 @@ class ComputeAPI(base.Base): {"method": "pause_instance", "args": {"instance_id": instance['id']}}) + @checks_instance_lock def unpause(self, context, instance_id): """Unpause the given instance.""" instance = self.db.instance_get_by_internal_id(context, instance_id) @@ -285,6 +325,7 @@ class ComputeAPI(base.Base): {"method": "unpause_instance", "args": {"instance_id": instance['id']}}) + @checks_instance_lock def suspend(self, context, instance_id): """suspend the instance with instance_id""" instance = self.db.instance_get_by_internal_id(context, instance_id) @@ -294,6 +335,7 @@ class ComputeAPI(base.Base): {"method": "suspend_instance", "args": {"instance_id": instance['id']}}) + @checks_instance_lock def resume(self, context, instance_id): """resume the instance with instance_id""" instance = self.db.instance_get_by_internal_id(context, instance_id) @@ -303,6 +345,7 @@ class ComputeAPI(base.Base): {"method": "resume_instance", "args": {"instance_id": instance['id']}}) + @checks_instance_lock def rescue(self, context, instance_id): """Rescue the given instance.""" instance = self.db.instance_get_by_internal_id(context, instance_id) @@ -312,6 +355,7 @@ class ComputeAPI(base.Base): {"method": "rescue_instance", "args": {"instance_id": instance['id']}}) + @checks_instance_lock def unrescue(self, context, instance_id): """Unrescue the given instance.""" instance = self.db.instance_get_by_internal_id(context, instance_id) diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 56a5a9b27..05419fb70 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -321,14 +321,6 @@ class ServersTest(unittest.TestCase): self.assertEqual(self.server_delete_called, True) def test_lock(self): - # part one: stubs it to be locked and test pause - def get_locked(self, context, id): - return True - - # set get to return locked - self.stubs.Set(nova.compute.api.ComputeAPI, 'get_lock', get_locked) - - # attempt to pause FLAGS.allow_admin_api = True body = dict(server=dict( name='server_test', imageId=2, flavorId=2, metadata={}, @@ -337,31 +329,24 @@ class ServersTest(unittest.TestCase): req.method = 'POST' req.content_type = 'application/json' req.body = json.dumps(body) - res = req.get_response(nova.api.API('os')) - # expect a 404 since it was locked - self.assertEqual(res.status_int, 405) + # part one: stubs it to be locked and attempt pause expecting exception + def get_locked(self, context, id): + return True + self.stubs.Set(nova.compute.api.ComputeAPI, 'get_lock', get_locked) - # Part two: stubs it to be unlocked and test pause + # pause should raise exception on locked instance + self.assertRaises(Exception, req.get_response, nova.api.API('os')) + + # Part two: stubs it to be unlocked and attempt pause expecting success def get_unlocked(self, context, id): return False - - # set get to return locked self.stubs.Set(nova.compute.api.ComputeAPI, 'get_lock', get_unlocked) - # attempt to pause - 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(nova.api.API('os')) - # expect a 202 since it was unlocked - self.assertEqual(res.status_int, 202) + # expecting no exception, test will fail if exception is raised + res = req.get_response(nova.api.API('os')) if __name__ == "__main__": diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py index bcb8a1526..422d59da0 100644 --- a/nova/tests/test_compute.py +++ b/nova/tests/test_compute.py @@ -170,3 +170,20 @@ 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) + self.compute.pause_instance(self.context, instance_id) + self.compute.lock_instance(self.context, instance_id) + + # pause should raise exception on locked instance + self.assertRaises(Exception, self.compute.unpause_instance, + self.context, instance_id) + + # test will fail if exception is raised + self.compute.unlock_instance(self.context, instance_id) + self.compute.unpause_instance(self.context, instance_id) + + self.compute.terminate_instance(self.context, instance_id) -- cgit