root / lib / cmdlib / base.py @ 87ed6b79
History | View | Annotate | Download (21.6 kB)
1 |
#
|
---|---|
2 |
#
|
3 |
|
4 |
# Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 Google Inc.
|
5 |
#
|
6 |
# This program is free software; you can redistribute it and/or modify
|
7 |
# it under the terms of the GNU General Public License as published by
|
8 |
# the Free Software Foundation; either version 2 of the License, or
|
9 |
# (at your option) any later version.
|
10 |
#
|
11 |
# This program is distributed in the hope that it will be useful, but
|
12 |
# WITHOUT ANY WARRANTY; without even the implied warranty of
|
13 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
14 |
# General Public License for more details.
|
15 |
#
|
16 |
# You should have received a copy of the GNU General Public License
|
17 |
# along with this program; if not, write to the Free Software
|
18 |
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
19 |
# 02110-1301, USA.
|
20 |
|
21 |
|
22 |
"""Base classes and functions for cmdlib."""
|
23 |
|
24 |
import logging |
25 |
|
26 |
from ganeti import errors |
27 |
from ganeti import constants |
28 |
from ganeti import locking |
29 |
from ganeti import query |
30 |
from ganeti import utils |
31 |
from ganeti.cmdlib.common import ExpandInstanceUuidAndName |
32 |
|
33 |
|
34 |
class ResultWithJobs: |
35 |
"""Data container for LU results with jobs.
|
36 |
|
37 |
Instances of this class returned from L{LogicalUnit.Exec} will be recognized
|
38 |
by L{mcpu._ProcessResult}. The latter will then submit the jobs
|
39 |
contained in the C{jobs} attribute and include the job IDs in the opcode
|
40 |
result.
|
41 |
|
42 |
"""
|
43 |
def __init__(self, jobs, **kwargs): |
44 |
"""Initializes this class.
|
45 |
|
46 |
Additional return values can be specified as keyword arguments.
|
47 |
|
48 |
@type jobs: list of lists of L{opcode.OpCode}
|
49 |
@param jobs: A list of lists of opcode objects
|
50 |
|
51 |
"""
|
52 |
self.jobs = jobs
|
53 |
self.other = kwargs
|
54 |
|
55 |
|
56 |
class LUWConfdClient(object): |
57 |
"""Wrapper class for wconfd client calls from LUs.
|
58 |
|
59 |
Correctly updates the cache of the LU's owned locks
|
60 |
when leaving. Also transparently adds the context
|
61 |
for resource requests.
|
62 |
|
63 |
"""
|
64 |
def __init__(self, lu): |
65 |
self.lu = lu
|
66 |
|
67 |
def TryUpdateLocks(self, req): |
68 |
jid, livelockfile = self.lu.wconfdcontext
|
69 |
self.lu.wconfd.Client().TryUpdateLocks(jid, livelockfile, req)
|
70 |
self.lu.wconfdlocks = self.lu.wconfd.Client().ListLocks(jid, livelockfile) |
71 |
|
72 |
def DownGradeLocksLevel(self, level): |
73 |
jid, livelockfile = self.lu.wconfdcontext
|
74 |
self.lu.wconfd.Client().DownGradeLocksLevel(jid, livelockfile, level)
|
75 |
self.lu.wconfdlocks = self.lu.wconfd.Client().ListLocks(jid, livelockfile) |
76 |
|
77 |
def FreeLocksLevel(self, level): |
78 |
jid, livelockfile = self.lu.wconfdcontext
|
79 |
self.lu.wconfd.Client().FreeLocksLevel(jid, livelockfile, level)
|
80 |
self.lu.wconfdlocks = self.lu.wconfd.Client().ListLocks(jid, livelockfile) |
81 |
|
82 |
|
83 |
class LogicalUnit(object): |
84 |
"""Logical Unit base class.
|
85 |
|
86 |
Subclasses must follow these rules:
|
87 |
- implement ExpandNames
|
88 |
- implement CheckPrereq (except when tasklets are used)
|
89 |
- implement Exec (except when tasklets are used)
|
90 |
- implement BuildHooksEnv
|
91 |
- implement BuildHooksNodes
|
92 |
- redefine HPATH and HTYPE
|
93 |
- optionally redefine their run requirements:
|
94 |
REQ_BGL: the LU needs to hold the Big Ganeti Lock exclusively
|
95 |
|
96 |
Note that all commands require root permissions.
|
97 |
|
98 |
@ivar dry_run_result: the value (if any) that will be returned to the caller
|
99 |
in dry-run mode (signalled by opcode dry_run parameter)
|
100 |
|
101 |
"""
|
102 |
HPATH = None
|
103 |
HTYPE = None
|
104 |
REQ_BGL = True
|
105 |
|
106 |
def __init__(self, processor, op, context, rpc_runner, wconfdcontext, wconfd): |
107 |
"""Constructor for LogicalUnit.
|
108 |
|
109 |
This needs to be overridden in derived classes in order to check op
|
110 |
validity.
|
111 |
|
112 |
@type wconfdcontext: (int, string)
|
113 |
@param wconfdcontext: the identity of the logical unit to represent itself
|
114 |
to wconfd when asking for resources; it is given as job id and livelock
|
115 |
file.
|
116 |
@param wconfd: the wconfd class to use; dependency injection to allow
|
117 |
testability.
|
118 |
|
119 |
"""
|
120 |
self.proc = processor
|
121 |
self.op = op
|
122 |
self.cfg = context.cfg
|
123 |
self.wconfdlocks = []
|
124 |
self.wconfdcontext = wconfdcontext
|
125 |
self.context = context
|
126 |
self.rpc = rpc_runner
|
127 |
self.wconfd = wconfd # wconfd module to use, for testing |
128 |
|
129 |
# Dictionaries used to declare locking needs to mcpu
|
130 |
self.needed_locks = None |
131 |
self.share_locks = dict.fromkeys(locking.LEVELS, 0) |
132 |
self.opportunistic_locks = dict.fromkeys(locking.LEVELS, False) |
133 |
|
134 |
self.add_locks = {}
|
135 |
self.remove_locks = {}
|
136 |
|
137 |
# Used to force good behavior when calling helper functions
|
138 |
self.recalculate_locks = {}
|
139 |
|
140 |
# logging
|
141 |
self.Log = processor.Log # pylint: disable=C0103 |
142 |
self.LogWarning = processor.LogWarning # pylint: disable=C0103 |
143 |
self.LogInfo = processor.LogInfo # pylint: disable=C0103 |
144 |
self.LogStep = processor.LogStep # pylint: disable=C0103 |
145 |
# support for dry-run
|
146 |
self.dry_run_result = None |
147 |
# support for generic debug attribute
|
148 |
if (not hasattr(self.op, "debug_level") or |
149 |
not isinstance(self.op.debug_level, int)): |
150 |
self.op.debug_level = 0 |
151 |
|
152 |
# Tasklets
|
153 |
self.tasklets = None |
154 |
|
155 |
# Validate opcode parameters and set defaults
|
156 |
self.op.Validate(True) |
157 |
|
158 |
self.CheckArguments()
|
159 |
|
160 |
def WConfdClient(self): |
161 |
return LUWConfdClient(self) |
162 |
|
163 |
def owned_locks(self, level): |
164 |
"""Return the list of locks owned by the LU at a given level.
|
165 |
|
166 |
This method assumes that is field wconfdlocks is set correctly
|
167 |
by mcpu.
|
168 |
|
169 |
"""
|
170 |
levelprefix = "%s/" % (locking.LEVEL_NAMES[level],)
|
171 |
locks = set([lock[0][len(levelprefix):] |
172 |
for lock in self.wconfdlocks |
173 |
if lock[0].startswith(levelprefix)]) |
174 |
expand_fns = { |
175 |
locking.LEVEL_CLUSTER: (lambda: [locking.BGL]),
|
176 |
locking.LEVEL_INSTANCE: self.cfg.GetInstanceList,
|
177 |
locking.LEVEL_NODE_ALLOC: (lambda: [locking.NAL]),
|
178 |
locking.LEVEL_NODEGROUP: self.cfg.GetNodeGroupList,
|
179 |
locking.LEVEL_NODE: self.cfg.GetNodeList,
|
180 |
locking.LEVEL_NODE_RES: self.cfg.GetNodeList,
|
181 |
locking.LEVEL_NETWORK: self.cfg.GetNetworkList,
|
182 |
} |
183 |
if locking.LOCKSET_NAME in locks: |
184 |
return expand_fns[level]()
|
185 |
else:
|
186 |
return locks
|
187 |
|
188 |
def release_request(self, level, names): |
189 |
"""Return a request to release the specified locks of the given level.
|
190 |
|
191 |
Correctly break up the group lock to do so.
|
192 |
|
193 |
"""
|
194 |
levelprefix = "%s/" % (locking.LEVEL_NAMES[level],)
|
195 |
release = [[levelprefix + lock, "release"] for lock in names] |
196 |
|
197 |
# if we break up the set-lock, make sure we ask for the rest of it.
|
198 |
setlock = levelprefix + locking.LOCKSET_NAME |
199 |
if [setlock, "exclusive"] in self.wconfdlocks: |
200 |
owned = self.owned_locks(level)
|
201 |
request = [[levelprefix + lock, "exclusive"]
|
202 |
for lock in owned |
203 |
if lock not in names] |
204 |
elif [setlock, "shared"] in self.wconfdlocks: |
205 |
owned = self.owned_locks(level)
|
206 |
request = [[levelprefix + lock, "shared"]
|
207 |
for lock in owned |
208 |
if lock not in names] |
209 |
else:
|
210 |
request = [] |
211 |
|
212 |
return release + [[setlock, "release"]] + request |
213 |
|
214 |
def CheckArguments(self): |
215 |
"""Check syntactic validity for the opcode arguments.
|
216 |
|
217 |
This method is for doing a simple syntactic check and ensure
|
218 |
validity of opcode parameters, without any cluster-related
|
219 |
checks. While the same can be accomplished in ExpandNames and/or
|
220 |
CheckPrereq, doing these separate is better because:
|
221 |
|
222 |
- ExpandNames is left as as purely a lock-related function
|
223 |
- CheckPrereq is run after we have acquired locks (and possible
|
224 |
waited for them)
|
225 |
|
226 |
The function is allowed to change the self.op attribute so that
|
227 |
later methods can no longer worry about missing parameters.
|
228 |
|
229 |
"""
|
230 |
pass
|
231 |
|
232 |
def ExpandNames(self): |
233 |
"""Expand names for this LU.
|
234 |
|
235 |
This method is called before starting to execute the opcode, and it should
|
236 |
update all the parameters of the opcode to their canonical form (e.g. a
|
237 |
short node name must be fully expanded after this method has successfully
|
238 |
completed). This way locking, hooks, logging, etc. can work correctly.
|
239 |
|
240 |
LUs which implement this method must also populate the self.needed_locks
|
241 |
member, as a dict with lock levels as keys, and a list of needed lock names
|
242 |
as values. Rules:
|
243 |
|
244 |
- use an empty dict if you don't need any lock
|
245 |
- if you don't need any lock at a particular level omit that
|
246 |
level (note that in this case C{DeclareLocks} won't be called
|
247 |
at all for that level)
|
248 |
- if you need locks at a level, but you can't calculate it in
|
249 |
this function, initialise that level with an empty list and do
|
250 |
further processing in L{LogicalUnit.DeclareLocks} (see that
|
251 |
function's docstring)
|
252 |
- don't put anything for the BGL level
|
253 |
- if you want all locks at a level use L{locking.ALL_SET} as a value
|
254 |
|
255 |
If you need to share locks (rather than acquire them exclusively) at one
|
256 |
level you can modify self.share_locks, setting a true value (usually 1) for
|
257 |
that level. By default locks are not shared.
|
258 |
|
259 |
This function can also define a list of tasklets, which then will be
|
260 |
executed in order instead of the usual LU-level CheckPrereq and Exec
|
261 |
functions, if those are not defined by the LU.
|
262 |
|
263 |
Examples::
|
264 |
|
265 |
# Acquire all nodes and one instance
|
266 |
self.needed_locks = {
|
267 |
locking.LEVEL_NODE: locking.ALL_SET,
|
268 |
locking.LEVEL_INSTANCE: ['instance1.example.com'],
|
269 |
}
|
270 |
# Acquire just two nodes
|
271 |
self.needed_locks = {
|
272 |
locking.LEVEL_NODE: ['node1-uuid', 'node2-uuid'],
|
273 |
}
|
274 |
# Acquire no locks
|
275 |
self.needed_locks = {} # No, you can't leave it to the default value None
|
276 |
|
277 |
"""
|
278 |
# The implementation of this method is mandatory only if the new LU is
|
279 |
# concurrent, so that old LUs don't need to be changed all at the same
|
280 |
# time.
|
281 |
if self.REQ_BGL: |
282 |
self.needed_locks = {} # Exclusive LUs don't need locks. |
283 |
else:
|
284 |
raise NotImplementedError |
285 |
|
286 |
def DeclareLocks(self, level): |
287 |
"""Declare LU locking needs for a level
|
288 |
|
289 |
While most LUs can just declare their locking needs at ExpandNames time,
|
290 |
sometimes there's the need to calculate some locks after having acquired
|
291 |
the ones before. This function is called just before acquiring locks at a
|
292 |
particular level, but after acquiring the ones at lower levels, and permits
|
293 |
such calculations. It can be used to modify self.needed_locks, and by
|
294 |
default it does nothing.
|
295 |
|
296 |
This function is only called if you have something already set in
|
297 |
self.needed_locks for the level.
|
298 |
|
299 |
@param level: Locking level which is going to be locked
|
300 |
@type level: member of L{ganeti.locking.LEVELS}
|
301 |
|
302 |
"""
|
303 |
|
304 |
def CheckPrereq(self): |
305 |
"""Check prerequisites for this LU.
|
306 |
|
307 |
This method should check that the prerequisites for the execution
|
308 |
of this LU are fulfilled. It can do internode communication, but
|
309 |
it should be idempotent - no cluster or system changes are
|
310 |
allowed.
|
311 |
|
312 |
The method should raise errors.OpPrereqError in case something is
|
313 |
not fulfilled. Its return value is ignored.
|
314 |
|
315 |
This method should also update all the parameters of the opcode to
|
316 |
their canonical form if it hasn't been done by ExpandNames before.
|
317 |
|
318 |
"""
|
319 |
if self.tasklets is not None: |
320 |
for (idx, tl) in enumerate(self.tasklets): |
321 |
logging.debug("Checking prerequisites for tasklet %s/%s",
|
322 |
idx + 1, len(self.tasklets)) |
323 |
tl.CheckPrereq() |
324 |
else:
|
325 |
pass
|
326 |
|
327 |
def Exec(self, feedback_fn): |
328 |
"""Execute the LU.
|
329 |
|
330 |
This method should implement the actual work. It should raise
|
331 |
errors.OpExecError for failures that are somewhat dealt with in
|
332 |
code, or expected.
|
333 |
|
334 |
"""
|
335 |
if self.tasklets is not None: |
336 |
for (idx, tl) in enumerate(self.tasklets): |
337 |
logging.debug("Executing tasklet %s/%s", idx + 1, len(self.tasklets)) |
338 |
tl.Exec(feedback_fn) |
339 |
else:
|
340 |
raise NotImplementedError |
341 |
|
342 |
def BuildHooksEnv(self): |
343 |
"""Build hooks environment for this LU.
|
344 |
|
345 |
@rtype: dict
|
346 |
@return: Dictionary containing the environment that will be used for
|
347 |
running the hooks for this LU. The keys of the dict must not be prefixed
|
348 |
with "GANETI_"--that'll be added by the hooks runner. The hooks runner
|
349 |
will extend the environment with additional variables. If no environment
|
350 |
should be defined, an empty dictionary should be returned (not C{None}).
|
351 |
@note: If the C{HPATH} attribute of the LU class is C{None}, this function
|
352 |
will not be called.
|
353 |
|
354 |
"""
|
355 |
raise NotImplementedError |
356 |
|
357 |
def BuildHooksNodes(self): |
358 |
"""Build list of nodes to run LU's hooks.
|
359 |
|
360 |
@rtype: tuple; (list, list)
|
361 |
@return: Tuple containing a list of node UUIDs on which the hook
|
362 |
should run before the execution and a list of node UUIDs on which the
|
363 |
hook should run after the execution.
|
364 |
No nodes should be returned as an empty list (and not None).
|
365 |
@note: If the C{HPATH} attribute of the LU class is C{None}, this function
|
366 |
will not be called.
|
367 |
|
368 |
"""
|
369 |
raise NotImplementedError |
370 |
|
371 |
def PreparePostHookNodes(self, post_hook_node_uuids): |
372 |
"""Extend list of nodes to run the post LU hook.
|
373 |
|
374 |
This method allows LUs to change the list of node UUIDs on which the
|
375 |
post hook should run after the LU has been executed but before the post
|
376 |
hook is run.
|
377 |
|
378 |
@type post_hook_node_uuids: list
|
379 |
@param post_hook_node_uuids: The initial list of node UUIDs to run the
|
380 |
post hook on, as returned by L{BuildHooksNodes}.
|
381 |
@rtype: list
|
382 |
@return: list of node UUIDs on which the post hook should run. The default
|
383 |
implementation returns the passed in C{post_hook_node_uuids}, but
|
384 |
custom implementations can choose to alter the list.
|
385 |
|
386 |
"""
|
387 |
# For consistency with HooksCallBack we ignore the "could be a function"
|
388 |
# warning
|
389 |
# pylint: disable=R0201
|
390 |
return post_hook_node_uuids
|
391 |
|
392 |
def HooksCallBack(self, phase, hook_results, feedback_fn, lu_result): |
393 |
"""Notify the LU about the results of its hooks.
|
394 |
|
395 |
This method is called every time a hooks phase is executed, and notifies
|
396 |
the Logical Unit about the hooks' result. The LU can then use it to alter
|
397 |
its result based on the hooks. By default the method does nothing and the
|
398 |
previous result is passed back unchanged but any LU can define it if it
|
399 |
wants to use the local cluster hook-scripts somehow.
|
400 |
|
401 |
@param phase: one of L{constants.HOOKS_PHASE_POST} or
|
402 |
L{constants.HOOKS_PHASE_PRE}; it denotes the hooks phase
|
403 |
@param hook_results: the results of the multi-node hooks rpc call
|
404 |
@param feedback_fn: function used send feedback back to the caller
|
405 |
@param lu_result: the previous Exec result this LU had, or None
|
406 |
in the PRE phase
|
407 |
@return: the new Exec result, based on the previous result
|
408 |
and hook results
|
409 |
|
410 |
"""
|
411 |
# API must be kept, thus we ignore the unused argument and could
|
412 |
# be a function warnings
|
413 |
# pylint: disable=W0613,R0201
|
414 |
return lu_result
|
415 |
|
416 |
def _ExpandAndLockInstance(self): |
417 |
"""Helper function to expand and lock an instance.
|
418 |
|
419 |
Many LUs that work on an instance take its name in self.op.instance_name
|
420 |
and need to expand it and then declare the expanded name for locking. This
|
421 |
function does it, and then updates self.op.instance_name to the expanded
|
422 |
name. It also initializes needed_locks as a dict, if this hasn't been done
|
423 |
before.
|
424 |
|
425 |
"""
|
426 |
if self.needed_locks is None: |
427 |
self.needed_locks = {}
|
428 |
else:
|
429 |
assert locking.LEVEL_INSTANCE not in self.needed_locks, \ |
430 |
"_ExpandAndLockInstance called with instance-level locks set"
|
431 |
(self.op.instance_uuid, self.op.instance_name) = \ |
432 |
ExpandInstanceUuidAndName(self.cfg, self.op.instance_uuid, |
433 |
self.op.instance_name)
|
434 |
self.needed_locks[locking.LEVEL_INSTANCE] = self.op.instance_name |
435 |
|
436 |
def _LockInstancesNodes(self, primary_only=False, |
437 |
level=locking.LEVEL_NODE): |
438 |
"""Helper function to declare instances' nodes for locking.
|
439 |
|
440 |
This function should be called after locking one or more instances to lock
|
441 |
their nodes. Its effect is populating self.needed_locks[locking.LEVEL_NODE]
|
442 |
with all primary or secondary nodes for instances already locked and
|
443 |
present in self.needed_locks[locking.LEVEL_INSTANCE].
|
444 |
|
445 |
It should be called from DeclareLocks, and for safety only works if
|
446 |
self.recalculate_locks[locking.LEVEL_NODE] is set.
|
447 |
|
448 |
In the future it may grow parameters to just lock some instance's nodes, or
|
449 |
to just lock primaries or secondary nodes, if needed.
|
450 |
|
451 |
If should be called in DeclareLocks in a way similar to::
|
452 |
|
453 |
if level == locking.LEVEL_NODE:
|
454 |
self._LockInstancesNodes()
|
455 |
|
456 |
@type primary_only: boolean
|
457 |
@param primary_only: only lock primary nodes of locked instances
|
458 |
@param level: Which lock level to use for locking nodes
|
459 |
|
460 |
"""
|
461 |
assert level in self.recalculate_locks, \ |
462 |
"_LockInstancesNodes helper function called with no nodes to recalculate"
|
463 |
|
464 |
# TODO: check if we're really been called with the instance locks held
|
465 |
|
466 |
# For now we'll replace self.needed_locks[locking.LEVEL_NODE], but in the
|
467 |
# future we might want to have different behaviors depending on the value
|
468 |
# of self.recalculate_locks[locking.LEVEL_NODE]
|
469 |
wanted_node_uuids = [] |
470 |
locked_i = self.owned_locks(locking.LEVEL_INSTANCE)
|
471 |
for _, instance in self.cfg.GetMultiInstanceInfoByName(locked_i): |
472 |
wanted_node_uuids.append(instance.primary_node) |
473 |
if not primary_only: |
474 |
wanted_node_uuids.extend(instance.secondary_nodes) |
475 |
|
476 |
if self.recalculate_locks[level] == constants.LOCKS_REPLACE: |
477 |
self.needed_locks[level] = wanted_node_uuids
|
478 |
elif self.recalculate_locks[level] == constants.LOCKS_APPEND: |
479 |
self.needed_locks[level].extend(wanted_node_uuids)
|
480 |
else:
|
481 |
raise errors.ProgrammerError("Unknown recalculation mode") |
482 |
|
483 |
del self.recalculate_locks[level] |
484 |
|
485 |
|
486 |
class NoHooksLU(LogicalUnit): # pylint: disable=W0223 |
487 |
"""Simple LU which runs no hooks.
|
488 |
|
489 |
This LU is intended as a parent for other LogicalUnits which will
|
490 |
run no hooks, in order to reduce duplicate code.
|
491 |
|
492 |
"""
|
493 |
HPATH = None
|
494 |
HTYPE = None
|
495 |
|
496 |
def BuildHooksEnv(self): |
497 |
"""Empty BuildHooksEnv for NoHooksLu.
|
498 |
|
499 |
This just raises an error.
|
500 |
|
501 |
"""
|
502 |
raise AssertionError("BuildHooksEnv called for NoHooksLUs") |
503 |
|
504 |
def BuildHooksNodes(self): |
505 |
"""Empty BuildHooksNodes for NoHooksLU.
|
506 |
|
507 |
"""
|
508 |
raise AssertionError("BuildHooksNodes called for NoHooksLU") |
509 |
|
510 |
def PreparePostHookNodes(self, post_hook_node_uuids): |
511 |
"""Empty PreparePostHookNodes for NoHooksLU.
|
512 |
|
513 |
"""
|
514 |
raise AssertionError("PreparePostHookNodes called for NoHooksLU") |
515 |
|
516 |
|
517 |
class Tasklet: |
518 |
"""Tasklet base class.
|
519 |
|
520 |
Tasklets are subcomponents for LUs. LUs can consist entirely of tasklets or
|
521 |
they can mix legacy code with tasklets. Locking needs to be done in the LU,
|
522 |
tasklets know nothing about locks.
|
523 |
|
524 |
Subclasses must follow these rules:
|
525 |
- Implement CheckPrereq
|
526 |
- Implement Exec
|
527 |
|
528 |
"""
|
529 |
def __init__(self, lu): |
530 |
self.lu = lu
|
531 |
|
532 |
# Shortcuts
|
533 |
self.cfg = lu.cfg
|
534 |
self.rpc = lu.rpc
|
535 |
|
536 |
def CheckPrereq(self): |
537 |
"""Check prerequisites for this tasklets.
|
538 |
|
539 |
This method should check whether the prerequisites for the execution of
|
540 |
this tasklet are fulfilled. It can do internode communication, but it
|
541 |
should be idempotent - no cluster or system changes are allowed.
|
542 |
|
543 |
The method should raise errors.OpPrereqError in case something is not
|
544 |
fulfilled. Its return value is ignored.
|
545 |
|
546 |
This method should also update all parameters to their canonical form if it
|
547 |
hasn't been done before.
|
548 |
|
549 |
"""
|
550 |
pass
|
551 |
|
552 |
def Exec(self, feedback_fn): |
553 |
"""Execute the tasklet.
|
554 |
|
555 |
This method should implement the actual work. It should raise
|
556 |
errors.OpExecError for failures that are somewhat dealt with in code, or
|
557 |
expected.
|
558 |
|
559 |
"""
|
560 |
raise NotImplementedError |
561 |
|
562 |
|
563 |
class QueryBase: |
564 |
"""Base for query utility classes.
|
565 |
|
566 |
"""
|
567 |
#: Attribute holding field definitions
|
568 |
FIELDS = None
|
569 |
|
570 |
#: Field to sort by
|
571 |
SORT_FIELD = "name"
|
572 |
|
573 |
def __init__(self, qfilter, fields, use_locking): |
574 |
"""Initializes this class.
|
575 |
|
576 |
"""
|
577 |
self.use_locking = use_locking
|
578 |
|
579 |
self.query = query.Query(self.FIELDS, fields, qfilter=qfilter, |
580 |
namefield=self.SORT_FIELD)
|
581 |
self.requested_data = self.query.RequestedData() |
582 |
self.names = self.query.RequestedNames() |
583 |
|
584 |
# Sort only if no names were requested
|
585 |
self.sort_by_name = not self.names |
586 |
|
587 |
self.do_locking = None |
588 |
self.wanted = None |
589 |
|
590 |
def _GetNames(self, lu, all_names, lock_level): |
591 |
"""Helper function to determine names asked for in the query.
|
592 |
|
593 |
"""
|
594 |
if self.do_locking: |
595 |
names = lu.owned_locks(lock_level) |
596 |
else:
|
597 |
names = all_names |
598 |
|
599 |
if self.wanted == locking.ALL_SET: |
600 |
assert not self.names |
601 |
# caller didn't specify names, so ordering is not important
|
602 |
return utils.NiceSort(names)
|
603 |
|
604 |
# caller specified names and we must keep the same order
|
605 |
assert self.names |
606 |
|
607 |
missing = set(self.wanted).difference(names) |
608 |
if missing:
|
609 |
raise errors.OpExecError("Some items were removed before retrieving" |
610 |
" their data: %s" % missing)
|
611 |
|
612 |
# Return expanded names
|
613 |
return self.wanted |
614 |
|
615 |
def ExpandNames(self, lu): |
616 |
"""Expand names for this query.
|
617 |
|
618 |
See L{LogicalUnit.ExpandNames}.
|
619 |
|
620 |
"""
|
621 |
raise NotImplementedError() |
622 |
|
623 |
def DeclareLocks(self, lu, level): |
624 |
"""Declare locks for this query.
|
625 |
|
626 |
See L{LogicalUnit.DeclareLocks}.
|
627 |
|
628 |
"""
|
629 |
raise NotImplementedError() |
630 |
|
631 |
def _GetQueryData(self, lu): |
632 |
"""Collects all data for this query.
|
633 |
|
634 |
@return: Query data object
|
635 |
|
636 |
"""
|
637 |
raise NotImplementedError() |
638 |
|
639 |
def NewStyleQuery(self, lu): |
640 |
"""Collect data and execute query.
|
641 |
|
642 |
"""
|
643 |
return query.GetQueryResponse(self.query, self._GetQueryData(lu), |
644 |
sort_by_name=self.sort_by_name)
|
645 |
|
646 |
def OldStyleQuery(self, lu): |
647 |
"""Collect data and execute query.
|
648 |
|
649 |
"""
|
650 |
return self.query.OldStyleQuery(self._GetQueryData(lu), |
651 |
sort_by_name=self.sort_by_name)
|