From 8aea573bd2e44e152fb4ef1627640bab1818dede Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Tue, 28 Dec 2010 23:55:58 -0600 Subject: initial lock functionality commit --- nova/api/openstack/__init__.py | 73 +++++++++++++++++++++++++++++++++++ nova/api/openstack/servers.py | 86 ++++++++++++++++++++++++++++++++++++++++++ nova/compute/api.py | 35 ++++++++++++++++- nova/compute/manager.py | 24 ++++++++++++ nova/db/sqlalchemy/api.py | 22 +++++++++++ nova/db/sqlalchemy/models.py | 2 + 6 files changed, 241 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index bebcdc18c..b3bb65550 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -22,6 +22,7 @@ WSGI middleware for OpenStack API controllers. import json import time +import functools import logging import routes @@ -113,3 +114,75 @@ class APIRouter(wsgi.Router): controller=sharedipgroups.Controller()) super(APIRouter, self).__init__(mapper) + + +#class CheckLock(object): +# """ +# decorator used for preventing action against locked instances +# unless, of course, you happen to be admin +# +# """ +# def __init__(self, function): +# self.function = function +# +# def __getattribute__(self, attr): +# if attr == "function": +# return super(CheckLock, self).__getattribute__(attr) +# return self.function.__getattribute__(attr) +# +# def __call__(self, *args, **kwargs): +# logging.info(_("Calling %s. Checking locks and privileges"), +# self.function.__name__) +# +# # get req +# if 'req' is in kwargs: +# req = kwargs['req'] +# else: +# req = args[1] +# +# # check table for lock +# locked = True +# if(locked): +# # check context for admin +# if(req.environ['nova.context'].is_admin): +# self.function(*args, **kwargs) +# else: +# pass +# # return 404 +# +# def __get__(self, obj, objtype): +# f = functools.partial(self.__call__, obj) +# f.__doc__ = self.function.__doc__ +# return f + + + + +#def checks_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): +# +# # check table for lock +# locked = True +# if(locked): +# try: +# # get context from req and check for admin +# if 'req' is in kwargs: +# req = kwargs['req'] +# else: +# req = args[1] +# if(req.environ['nova.context'].is_admin): +# function(*args, **kwargs) +# else: +# pass +# # return 404 +# except: +# logging.error(_("CheckLock: error getting context")) +# +# return decorated_function diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 10c397384..46e65ca83 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -35,6 +35,40 @@ LOG = logging.getLogger('server') LOG.setLevel(logging.DEBUG) +def checks_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' is in kwargs: + req = kwargs['req'] + else: + req = args[1] + if 'id' is in kwargs: + _id = kwargs['id'] + else: + req = args[2] + context = req.environ['nova.context'] + except: + logging.error(_("CheckLock: argument error")) + + # if locked and admin call function, otherwise 404 + if(compute_api.ComputeAPI().get_lock(context, _id)): + if(req.environ['nova.context'].is_admin): + function(*args, **kwargs) + # return 404 + return faults.Fault(exc.HTTPUnprocessableEntity()) + + return decorated_function + + def _entity_list(entities): """ Coerces a list of servers into proper dictionary format """ return dict(servers=entities) @@ -104,6 +138,7 @@ class Controller(wsgi.Controller): res = [entity_maker(inst)['server'] for inst in limited_list] return _entity_list(res) + @checks_lock def show(self, req, id): """ Returns server details by server id """ try: @@ -113,6 +148,7 @@ class Controller(wsgi.Controller): except exception.NotFound: return faults.Fault(exc.HTTPNotFound()) + @checks_lock def delete(self, req, id): """ Destroys a server """ try: @@ -140,6 +176,7 @@ class Controller(wsgi.Controller): key_data=key_pair['public_key']) return _entity_inst(instances[0]) + @checks_lock def update(self, req, id): """ Updates the server name or password """ inst_dict = self._deserialize(req.body, req) @@ -160,6 +197,7 @@ class Controller(wsgi.Controller): return faults.Fault(exc.HTTPNotFound()) return exc.HTTPNoContent() + @checks_lock def action(self, req, id): """ Multi-purpose method used to reboot, rebuild, and resize a server """ @@ -176,6 +214,51 @@ 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() + + @checks_lock def pause(self, req, id): """ Permit Admins to Pause the server. """ ctxt = req.environ['nova.context'] @@ -187,6 +270,7 @@ class Controller(wsgi.Controller): return faults.Fault(exc.HTTPUnprocessableEntity()) return exc.HTTPAccepted() + @checks_lock def unpause(self, req, id): """ Permit Admins to Unpause the server. """ ctxt = req.environ['nova.context'] @@ -198,6 +282,7 @@ class Controller(wsgi.Controller): return faults.Fault(exc.HTTPUnprocessableEntity()) return exc.HTTPAccepted() + @checks_lock def suspend(self, req, id): """permit admins to suspend the server""" context = req.environ['nova.context'] @@ -209,6 +294,7 @@ class Controller(wsgi.Controller): return faults.Fault(exc.HTTPUnprocessableEntity()) return exc.HTTPAccepted() + @checks_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 a47703461..361ab9914 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -141,7 +141,8 @@ class ComputeAPI(base.Base): 'display_description': description, 'user_data': user_data or '', 'key_name': key_name, - 'key_data': key_data} + 'key_data': key_data, + 'locked': False} elevated = context.elevated() instances = [] @@ -319,3 +320,35 @@ class ComputeAPI(base.Base): self.db.queue_get_for(context, FLAGS.compute_topic, host), {"method": "unrescue_instance", "args": {"instance_id": 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'] diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 70b175e7c..05f1e44a2 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -329,6 +329,30 @@ class ComputeManager(manager.Manager): instance_id, 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_ref['internal_id']) + self.db.instance_set_lock(context, instance_id, 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_ref['internal_id']) + self.db.instance_set_lock(context, instance_id, False) + @exception.wrap_exception def get_console_output(self, context, instance_id): """Send the console output for an instance.""" diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 7e945e4cb..6d774b39c 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -856,6 +856,28 @@ def instance_action_create(context, values): return action_ref +@require_admin_context +def instance_set_lock(context, instance_id, lock): + """ + twiddle the locked bit in the db + lock is a boolean + + """ + db.instance_update(context, + instance_id, + {'locked': lock}) + + +#@require_admin_context +#def instance_is_locked(context, instance_id): +# """ +# return the boolean state of (instance with instance_id)'s lock +# +# """ +# instance_ref = instance_get(context, instance_id) +# return instance_ref['locked'] + + ################### diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index 693db8d23..830ef30a1 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 -- cgit