Skip to content

Commit 8aea573

Browse files
committed
initial lock functionality commit
1 parent 675ca7c commit 8aea573

File tree

6 files changed

+241
-1
lines changed

6 files changed

+241
-1
lines changed

nova/api/openstack/__init__.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
import json
2424
import time
25+
import functools
2526

2627
import logging
2728
import routes
@@ -113,3 +114,75 @@ def __init__(self):
113114
controller=sharedipgroups.Controller())
114115

115116
super(APIRouter, self).__init__(mapper)
117+
118+
119+
#class CheckLock(object):
120+
# """
121+
# decorator used for preventing action against locked instances
122+
# unless, of course, you happen to be admin
123+
#
124+
# """
125+
# def __init__(self, function):
126+
# self.function = function
127+
#
128+
# def __getattribute__(self, attr):
129+
# if attr == "function":
130+
# return super(CheckLock, self).__getattribute__(attr)
131+
# return self.function.__getattribute__(attr)
132+
#
133+
# def __call__(self, *args, **kwargs):
134+
# logging.info(_("Calling %s. Checking locks and privileges"),
135+
# self.function.__name__)
136+
#
137+
# # get req
138+
# if 'req' is in kwargs:
139+
# req = kwargs['req']
140+
# else:
141+
# req = args[1]
142+
#
143+
# # check table for lock
144+
# locked = True
145+
# if(locked):
146+
# # check context for admin
147+
# if(req.environ['nova.context'].is_admin):
148+
# self.function(*args, **kwargs)
149+
# else:
150+
# pass
151+
# # return 404
152+
#
153+
# def __get__(self, obj, objtype):
154+
# f = functools.partial(self.__call__, obj)
155+
# f.__doc__ = self.function.__doc__
156+
# return f
157+
158+
159+
160+
161+
#def checks_lock(function):
162+
# """
163+
# decorator used for preventing action against locked instances
164+
# unless, of course, you happen to be admin
165+
#
166+
# """
167+
#
168+
# @functools.wraps(function)
169+
# def decorated_function(*args, **kwargs):
170+
#
171+
# # check table for lock
172+
# locked = True
173+
# if(locked):
174+
# try:
175+
# # get context from req and check for admin
176+
# if 'req' is in kwargs:
177+
# req = kwargs['req']
178+
# else:
179+
# req = args[1]
180+
# if(req.environ['nova.context'].is_admin):
181+
# function(*args, **kwargs)
182+
# else:
183+
# pass
184+
# # return 404
185+
# except:
186+
# logging.error(_("CheckLock: error getting context"))
187+
#
188+
# return decorated_function

nova/api/openstack/servers.py

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,40 @@
3535
LOG.setLevel(logging.DEBUG)
3636

3737

38+
def checks_lock(function):
39+
"""
40+
decorator used for preventing action against locked instances
41+
unless, of course, you happen to be admin
42+
43+
"""
44+
45+
@functools.wraps(function)
46+
def decorated_function(*args, **kwargs):
47+
48+
# grab args to function
49+
try:
50+
if 'req' is in kwargs:
51+
req = kwargs['req']
52+
else:
53+
req = args[1]
54+
if 'id' is in kwargs:
55+
_id = kwargs['id']
56+
else:
57+
req = args[2]
58+
context = req.environ['nova.context']
59+
except:
60+
logging.error(_("CheckLock: argument error"))
61+
62+
# if locked and admin call function, otherwise 404
63+
if(compute_api.ComputeAPI().get_lock(context, _id)):
64+
if(req.environ['nova.context'].is_admin):
65+
function(*args, **kwargs)
66+
# return 404
67+
return faults.Fault(exc.HTTPUnprocessableEntity())
68+
69+
return decorated_function
70+
71+
3872
def _entity_list(entities):
3973
""" Coerces a list of servers into proper dictionary format """
4074
return dict(servers=entities)
@@ -104,6 +138,7 @@ def _items(self, req, entity_maker):
104138
res = [entity_maker(inst)['server'] for inst in limited_list]
105139
return _entity_list(res)
106140

141+
@checks_lock
107142
def show(self, req, id):
108143
""" Returns server details by server id """
109144
try:
@@ -113,6 +148,7 @@ def show(self, req, id):
113148
except exception.NotFound:
114149
return faults.Fault(exc.HTTPNotFound())
115150

151+
@checks_lock
116152
def delete(self, req, id):
117153
""" Destroys a server """
118154
try:
@@ -140,6 +176,7 @@ def create(self, req):
140176
key_data=key_pair['public_key'])
141177
return _entity_inst(instances[0])
142178

179+
@checks_lock
143180
def update(self, req, id):
144181
""" Updates the server name or password """
145182
inst_dict = self._deserialize(req.body, req)
@@ -160,6 +197,7 @@ def update(self, req, id):
160197
return faults.Fault(exc.HTTPNotFound())
161198
return exc.HTTPNoContent()
162199

200+
@checks_lock
163201
def action(self, req, id):
164202
""" Multi-purpose method used to reboot, rebuild, and
165203
resize a server """
@@ -176,6 +214,51 @@ def action(self, req, id):
176214
return faults.Fault(exc.HTTPUnprocessableEntity())
177215
return exc.HTTPAccepted()
178216

217+
def lock(self, req, id):
218+
"""
219+
lock the instance with id
220+
admin only operation
221+
222+
"""
223+
context = req.environ['nova.context']
224+
try:
225+
self.compute_api.lock(context, id)
226+
except:
227+
readable = traceback.format_exc()
228+
logging.error(_("Compute.api::lock %s"), readable)
229+
return faults.Fault(exc.HTTPUnprocessableEntity())
230+
return exc.HTTPAccepted()
231+
232+
def unlock(self, req, id):
233+
"""
234+
unlock the instance with id
235+
admin only operation
236+
237+
"""
238+
context = req.environ['nova.context']
239+
try:
240+
self.compute_api.unlock(context, id)
241+
except:
242+
readable = traceback.format_exc()
243+
logging.error(_("Compute.api::unlock %s"), readable)
244+
return faults.Fault(exc.HTTPUnprocessableEntity())
245+
return exc.HTTPAccepted()
246+
247+
def get_lock(self, req, id):
248+
"""
249+
return the boolean state of (instance with id)'s lock
250+
251+
"""
252+
context = req.environ['nova.context']
253+
try:
254+
self.compute_api.get_lock(context, id)
255+
except:
256+
readable = traceback.format_exc()
257+
logging.error(_("Compute.api::get_lock %s"), readable)
258+
return faults.Fault(exc.HTTPUnprocessableEntity())
259+
return exc.HTTPAccepted()
260+
261+
@checks_lock
179262
def pause(self, req, id):
180263
""" Permit Admins to Pause the server. """
181264
ctxt = req.environ['nova.context']
@@ -187,6 +270,7 @@ def pause(self, req, id):
187270
return faults.Fault(exc.HTTPUnprocessableEntity())
188271
return exc.HTTPAccepted()
189272

273+
@checks_lock
190274
def unpause(self, req, id):
191275
""" Permit Admins to Unpause the server. """
192276
ctxt = req.environ['nova.context']
@@ -198,6 +282,7 @@ def unpause(self, req, id):
198282
return faults.Fault(exc.HTTPUnprocessableEntity())
199283
return exc.HTTPAccepted()
200284

285+
@checks_lock
201286
def suspend(self, req, id):
202287
"""permit admins to suspend the server"""
203288
context = req.environ['nova.context']
@@ -209,6 +294,7 @@ def suspend(self, req, id):
209294
return faults.Fault(exc.HTTPUnprocessableEntity())
210295
return exc.HTTPAccepted()
211296

297+
@checks_lock
212298
def resume(self, req, id):
213299
"""permit admins to resume the server from suspend"""
214300
context = req.environ['nova.context']

nova/compute/api.py

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,8 @@ def create_instances(self, context, instance_type, image_id, min_count=1,
141141
'display_description': description,
142142
'user_data': user_data or '',
143143
'key_name': key_name,
144-
'key_data': key_data}
144+
'key_data': key_data,
145+
'locked': False}
145146

146147
elevated = context.elevated()
147148
instances = []
@@ -319,3 +320,35 @@ def unrescue(self, context, instance_id):
319320
self.db.queue_get_for(context, FLAGS.compute_topic, host),
320321
{"method": "unrescue_instance",
321322
"args": {"instance_id": instance['id']}})
323+
324+
def lock(self, context, instance_id):
325+
"""
326+
lock the instance with instance_id
327+
328+
"""
329+
instance = self.get_instance(context, instance_id)
330+
host = instance['host']
331+
rpc.cast(context,
332+
self.db.queue_get_for(context, FLAGS.compute_topic, host),
333+
{"method": "lock_instance",
334+
"args": {"instance_id": instance['id']}})
335+
336+
def unlock(self, context, instance_id):
337+
"""
338+
unlock the instance with instance_id
339+
340+
"""
341+
instance = self.get_instance(context, instance_id)
342+
host = instance['host']
343+
rpc.cast(context,
344+
self.db.queue_get_for(context, FLAGS.compute_topic, host),
345+
{"method": "unlock_instance",
346+
"args": {"instance_id": instance['id']}})
347+
348+
def get_lock(self, context, instance_id):
349+
"""
350+
return the boolean state of (instance with instance_id)'s lock
351+
352+
"""
353+
instance = self.get_instance(context, instance_id)
354+
return instance['locked']

nova/compute/manager.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,30 @@ def resume_instance(self, context, instance_id):
329329
instance_id,
330330
result))
331331

332+
@exception.wrap_exception
333+
def lock_instance(self, context, instance_id):
334+
"""
335+
lock the instance with instance_id
336+
337+
"""
338+
context = context.elevated()
339+
instance_ref = self.db.instance_get(context, instance_id)
340+
341+
logging.debug(_('instance %s: locking'), instance_ref['internal_id'])
342+
self.db.instance_set_lock(context, instance_id, True)
343+
344+
@exception.wrap_exception
345+
def unlock_instance(self, context, instance_id):
346+
"""
347+
unlock the instance with instance_id
348+
349+
"""
350+
context = context.elevated()
351+
instance_ref = self.db.instance_get(context, instance_id)
352+
353+
logging.debug(_('instance %s: unlocking'), instance_ref['internal_id'])
354+
self.db.instance_set_lock(context, instance_id, False)
355+
332356
@exception.wrap_exception
333357
def get_console_output(self, context, instance_id):
334358
"""Send the console output for an instance."""

nova/db/sqlalchemy/api.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -856,6 +856,28 @@ def instance_action_create(context, values):
856856
return action_ref
857857

858858

859+
@require_admin_context
860+
def instance_set_lock(context, instance_id, lock):
861+
"""
862+
twiddle the locked bit in the db
863+
lock is a boolean
864+
865+
"""
866+
db.instance_update(context,
867+
instance_id,
868+
{'locked': lock})
869+
870+
871+
#@require_admin_context
872+
#def instance_is_locked(context, instance_id):
873+
# """
874+
# return the boolean state of (instance with instance_id)'s lock
875+
#
876+
# """
877+
# instance_ref = instance_get(context, instance_id)
878+
# return instance_ref['locked']
879+
880+
859881
###################
860882

861883

nova/db/sqlalchemy/models.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,8 @@ def name(self):
224224
display_name = Column(String(255))
225225
display_description = Column(String(255))
226226

227+
locked = Column(Boolean)
228+
227229
# TODO(vish): see Ewan's email about state improvements, probably
228230
# should be in a driver base class or some such
229231
# vmstate_state = running, halted, suspended, paused

0 commit comments

Comments
 (0)