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