root / lib / cmdlib / base.py @ c42be2c0
History | View | Annotate | Download (17.8 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 LogicalUnit(object): |
57 |
"""Logical Unit base class.
|
58 |
|
59 |
Subclasses must follow these rules:
|
60 |
- implement ExpandNames
|
61 |
- implement CheckPrereq (except when tasklets are used)
|
62 |
- implement Exec (except when tasklets are used)
|
63 |
- implement BuildHooksEnv
|
64 |
- implement BuildHooksNodes
|
65 |
- redefine HPATH and HTYPE
|
66 |
- optionally redefine their run requirements:
|
67 |
REQ_BGL: the LU needs to hold the Big Ganeti Lock exclusively
|
68 |
|
69 |
Note that all commands require root permissions.
|
70 |
|
71 |
@ivar dry_run_result: the value (if any) that will be returned to the caller
|
72 |
in dry-run mode (signalled by opcode dry_run parameter)
|
73 |
|
74 |
"""
|
75 |
HPATH = None
|
76 |
HTYPE = None
|
77 |
REQ_BGL = True
|
78 |
|
79 |
def __init__(self, processor, op, context, rpc_runner): |
80 |
"""Constructor for LogicalUnit.
|
81 |
|
82 |
This needs to be overridden in derived classes in order to check op
|
83 |
validity.
|
84 |
|
85 |
"""
|
86 |
self.proc = processor
|
87 |
self.op = op
|
88 |
self.cfg = context.cfg
|
89 |
self.glm = context.glm
|
90 |
# readability alias
|
91 |
self.owned_locks = context.glm.list_owned
|
92 |
self.context = context
|
93 |
self.rpc = rpc_runner
|
94 |
|
95 |
# Dictionaries used to declare locking needs to mcpu
|
96 |
self.needed_locks = None |
97 |
self.share_locks = dict.fromkeys(locking.LEVELS, 0) |
98 |
self.opportunistic_locks = dict.fromkeys(locking.LEVELS, False) |
99 |
|
100 |
self.add_locks = {}
|
101 |
self.remove_locks = {}
|
102 |
|
103 |
# Used to force good behavior when calling helper functions
|
104 |
self.recalculate_locks = {}
|
105 |
|
106 |
# logging
|
107 |
self.Log = processor.Log # pylint: disable=C0103 |
108 |
self.LogWarning = processor.LogWarning # pylint: disable=C0103 |
109 |
self.LogInfo = processor.LogInfo # pylint: disable=C0103 |
110 |
self.LogStep = processor.LogStep # pylint: disable=C0103 |
111 |
# support for dry-run
|
112 |
self.dry_run_result = None |
113 |
# support for generic debug attribute
|
114 |
if (not hasattr(self.op, "debug_level") or |
115 |
not isinstance(self.op.debug_level, int)): |
116 |
self.op.debug_level = 0 |
117 |
|
118 |
# Tasklets
|
119 |
self.tasklets = None |
120 |
|
121 |
# Validate opcode parameters and set defaults
|
122 |
self.op.Validate(True) |
123 |
|
124 |
self.CheckArguments()
|
125 |
|
126 |
def CheckArguments(self): |
127 |
"""Check syntactic validity for the opcode arguments.
|
128 |
|
129 |
This method is for doing a simple syntactic check and ensure
|
130 |
validity of opcode parameters, without any cluster-related
|
131 |
checks. While the same can be accomplished in ExpandNames and/or
|
132 |
CheckPrereq, doing these separate is better because:
|
133 |
|
134 |
- ExpandNames is left as as purely a lock-related function
|
135 |
- CheckPrereq is run after we have acquired locks (and possible
|
136 |
waited for them)
|
137 |
|
138 |
The function is allowed to change the self.op attribute so that
|
139 |
later methods can no longer worry about missing parameters.
|
140 |
|
141 |
"""
|
142 |
pass
|
143 |
|
144 |
def ExpandNames(self): |
145 |
"""Expand names for this LU.
|
146 |
|
147 |
This method is called before starting to execute the opcode, and it should
|
148 |
update all the parameters of the opcode to their canonical form (e.g. a
|
149 |
short node name must be fully expanded after this method has successfully
|
150 |
completed). This way locking, hooks, logging, etc. can work correctly.
|
151 |
|
152 |
LUs which implement this method must also populate the self.needed_locks
|
153 |
member, as a dict with lock levels as keys, and a list of needed lock names
|
154 |
as values. Rules:
|
155 |
|
156 |
- use an empty dict if you don't need any lock
|
157 |
- if you don't need any lock at a particular level omit that
|
158 |
level (note that in this case C{DeclareLocks} won't be called
|
159 |
at all for that level)
|
160 |
- if you need locks at a level, but you can't calculate it in
|
161 |
this function, initialise that level with an empty list and do
|
162 |
further processing in L{LogicalUnit.DeclareLocks} (see that
|
163 |
function's docstring)
|
164 |
- don't put anything for the BGL level
|
165 |
- if you want all locks at a level use L{locking.ALL_SET} as a value
|
166 |
|
167 |
If you need to share locks (rather than acquire them exclusively) at one
|
168 |
level you can modify self.share_locks, setting a true value (usually 1) for
|
169 |
that level. By default locks are not shared.
|
170 |
|
171 |
This function can also define a list of tasklets, which then will be
|
172 |
executed in order instead of the usual LU-level CheckPrereq and Exec
|
173 |
functions, if those are not defined by the LU.
|
174 |
|
175 |
Examples::
|
176 |
|
177 |
# Acquire all nodes and one instance
|
178 |
self.needed_locks = {
|
179 |
locking.LEVEL_NODE: locking.ALL_SET,
|
180 |
locking.LEVEL_INSTANCE: ['instance1.example.com'],
|
181 |
}
|
182 |
# Acquire just two nodes
|
183 |
self.needed_locks = {
|
184 |
locking.LEVEL_NODE: ['node1-uuid', 'node2-uuid'],
|
185 |
}
|
186 |
# Acquire no locks
|
187 |
self.needed_locks = {} # No, you can't leave it to the default value None
|
188 |
|
189 |
"""
|
190 |
# The implementation of this method is mandatory only if the new LU is
|
191 |
# concurrent, so that old LUs don't need to be changed all at the same
|
192 |
# time.
|
193 |
if self.REQ_BGL: |
194 |
self.needed_locks = {} # Exclusive LUs don't need locks. |
195 |
else:
|
196 |
raise NotImplementedError |
197 |
|
198 |
def DeclareLocks(self, level): |
199 |
"""Declare LU locking needs for a level
|
200 |
|
201 |
While most LUs can just declare their locking needs at ExpandNames time,
|
202 |
sometimes there's the need to calculate some locks after having acquired
|
203 |
the ones before. This function is called just before acquiring locks at a
|
204 |
particular level, but after acquiring the ones at lower levels, and permits
|
205 |
such calculations. It can be used to modify self.needed_locks, and by
|
206 |
default it does nothing.
|
207 |
|
208 |
This function is only called if you have something already set in
|
209 |
self.needed_locks for the level.
|
210 |
|
211 |
@param level: Locking level which is going to be locked
|
212 |
@type level: member of L{ganeti.locking.LEVELS}
|
213 |
|
214 |
"""
|
215 |
|
216 |
def CheckPrereq(self): |
217 |
"""Check prerequisites for this LU.
|
218 |
|
219 |
This method should check that the prerequisites for the execution
|
220 |
of this LU are fulfilled. It can do internode communication, but
|
221 |
it should be idempotent - no cluster or system changes are
|
222 |
allowed.
|
223 |
|
224 |
The method should raise errors.OpPrereqError in case something is
|
225 |
not fulfilled. Its return value is ignored.
|
226 |
|
227 |
This method should also update all the parameters of the opcode to
|
228 |
their canonical form if it hasn't been done by ExpandNames before.
|
229 |
|
230 |
"""
|
231 |
if self.tasklets is not None: |
232 |
for (idx, tl) in enumerate(self.tasklets): |
233 |
logging.debug("Checking prerequisites for tasklet %s/%s",
|
234 |
idx + 1, len(self.tasklets)) |
235 |
tl.CheckPrereq() |
236 |
else:
|
237 |
pass
|
238 |
|
239 |
def Exec(self, feedback_fn): |
240 |
"""Execute the LU.
|
241 |
|
242 |
This method should implement the actual work. It should raise
|
243 |
errors.OpExecError for failures that are somewhat dealt with in
|
244 |
code, or expected.
|
245 |
|
246 |
"""
|
247 |
if self.tasklets is not None: |
248 |
for (idx, tl) in enumerate(self.tasklets): |
249 |
logging.debug("Executing tasklet %s/%s", idx + 1, len(self.tasklets)) |
250 |
tl.Exec(feedback_fn) |
251 |
else:
|
252 |
raise NotImplementedError |
253 |
|
254 |
def BuildHooksEnv(self): |
255 |
"""Build hooks environment for this LU.
|
256 |
|
257 |
@rtype: dict
|
258 |
@return: Dictionary containing the environment that will be used for
|
259 |
running the hooks for this LU. The keys of the dict must not be prefixed
|
260 |
with "GANETI_"--that'll be added by the hooks runner. The hooks runner
|
261 |
will extend the environment with additional variables. If no environment
|
262 |
should be defined, an empty dictionary should be returned (not C{None}).
|
263 |
@note: If the C{HPATH} attribute of the LU class is C{None}, this function
|
264 |
will not be called.
|
265 |
|
266 |
"""
|
267 |
raise NotImplementedError |
268 |
|
269 |
def BuildHooksNodes(self): |
270 |
"""Build list of nodes to run LU's hooks.
|
271 |
|
272 |
@rtype: tuple; (list, list) or (list, list, list)
|
273 |
@return: Tuple containing a list of node UUIDs on which the hook
|
274 |
should run before the execution and a list of node UUIDs on which the
|
275 |
hook should run after the execution. As it might be possible that the
|
276 |
node UUID is not known at the time this method is invoked, an optional
|
277 |
third list can be added which contains node names on which the hook
|
278 |
should run after the execution (in case of node add, for instance).
|
279 |
No nodes should be returned as an empty list (and not None).
|
280 |
@note: If the C{HPATH} attribute of the LU class is C{None}, this function
|
281 |
will not be called.
|
282 |
|
283 |
"""
|
284 |
raise NotImplementedError |
285 |
|
286 |
def HooksCallBack(self, phase, hook_results, feedback_fn, lu_result): |
287 |
"""Notify the LU about the results of its hooks.
|
288 |
|
289 |
This method is called every time a hooks phase is executed, and notifies
|
290 |
the Logical Unit about the hooks' result. The LU can then use it to alter
|
291 |
its result based on the hooks. By default the method does nothing and the
|
292 |
previous result is passed back unchanged but any LU can define it if it
|
293 |
wants to use the local cluster hook-scripts somehow.
|
294 |
|
295 |
@param phase: one of L{constants.HOOKS_PHASE_POST} or
|
296 |
L{constants.HOOKS_PHASE_PRE}; it denotes the hooks phase
|
297 |
@param hook_results: the results of the multi-node hooks rpc call
|
298 |
@param feedback_fn: function used send feedback back to the caller
|
299 |
@param lu_result: the previous Exec result this LU had, or None
|
300 |
in the PRE phase
|
301 |
@return: the new Exec result, based on the previous result
|
302 |
and hook results
|
303 |
|
304 |
"""
|
305 |
# API must be kept, thus we ignore the unused argument and could
|
306 |
# be a function warnings
|
307 |
# pylint: disable=W0613,R0201
|
308 |
return lu_result
|
309 |
|
310 |
def _ExpandAndLockInstance(self): |
311 |
"""Helper function to expand and lock an instance.
|
312 |
|
313 |
Many LUs that work on an instance take its name in self.op.instance_name
|
314 |
and need to expand it and then declare the expanded name for locking. This
|
315 |
function does it, and then updates self.op.instance_name to the expanded
|
316 |
name. It also initializes needed_locks as a dict, if this hasn't been done
|
317 |
before.
|
318 |
|
319 |
"""
|
320 |
if self.needed_locks is None: |
321 |
self.needed_locks = {}
|
322 |
else:
|
323 |
assert locking.LEVEL_INSTANCE not in self.needed_locks, \ |
324 |
"_ExpandAndLockInstance called with instance-level locks set"
|
325 |
(self.op.instance_uuid, self.op.instance_name) = \ |
326 |
ExpandInstanceUuidAndName(self.cfg, self.op.instance_uuid, |
327 |
self.op.instance_name)
|
328 |
self.needed_locks[locking.LEVEL_INSTANCE] = self.op.instance_name |
329 |
|
330 |
def _LockInstancesNodes(self, primary_only=False, |
331 |
level=locking.LEVEL_NODE): |
332 |
"""Helper function to declare instances' nodes for locking.
|
333 |
|
334 |
This function should be called after locking one or more instances to lock
|
335 |
their nodes. Its effect is populating self.needed_locks[locking.LEVEL_NODE]
|
336 |
with all primary or secondary nodes for instances already locked and
|
337 |
present in self.needed_locks[locking.LEVEL_INSTANCE].
|
338 |
|
339 |
It should be called from DeclareLocks, and for safety only works if
|
340 |
self.recalculate_locks[locking.LEVEL_NODE] is set.
|
341 |
|
342 |
In the future it may grow parameters to just lock some instance's nodes, or
|
343 |
to just lock primaries or secondary nodes, if needed.
|
344 |
|
345 |
If should be called in DeclareLocks in a way similar to::
|
346 |
|
347 |
if level == locking.LEVEL_NODE:
|
348 |
self._LockInstancesNodes()
|
349 |
|
350 |
@type primary_only: boolean
|
351 |
@param primary_only: only lock primary nodes of locked instances
|
352 |
@param level: Which lock level to use for locking nodes
|
353 |
|
354 |
"""
|
355 |
assert level in self.recalculate_locks, \ |
356 |
"_LockInstancesNodes helper function called with no nodes to recalculate"
|
357 |
|
358 |
# TODO: check if we're really been called with the instance locks held
|
359 |
|
360 |
# For now we'll replace self.needed_locks[locking.LEVEL_NODE], but in the
|
361 |
# future we might want to have different behaviors depending on the value
|
362 |
# of self.recalculate_locks[locking.LEVEL_NODE]
|
363 |
wanted_node_uuids = [] |
364 |
locked_i = self.owned_locks(locking.LEVEL_INSTANCE)
|
365 |
for _, instance in self.cfg.GetMultiInstanceInfoByName(locked_i): |
366 |
wanted_node_uuids.append(instance.primary_node) |
367 |
if not primary_only: |
368 |
wanted_node_uuids.extend(instance.secondary_nodes) |
369 |
|
370 |
if self.recalculate_locks[level] == constants.LOCKS_REPLACE: |
371 |
self.needed_locks[level] = wanted_node_uuids
|
372 |
elif self.recalculate_locks[level] == constants.LOCKS_APPEND: |
373 |
self.needed_locks[level].extend(wanted_node_uuids)
|
374 |
else:
|
375 |
raise errors.ProgrammerError("Unknown recalculation mode") |
376 |
|
377 |
del self.recalculate_locks[level] |
378 |
|
379 |
|
380 |
class NoHooksLU(LogicalUnit): # pylint: disable=W0223 |
381 |
"""Simple LU which runs no hooks.
|
382 |
|
383 |
This LU is intended as a parent for other LogicalUnits which will
|
384 |
run no hooks, in order to reduce duplicate code.
|
385 |
|
386 |
"""
|
387 |
HPATH = None
|
388 |
HTYPE = None
|
389 |
|
390 |
def BuildHooksEnv(self): |
391 |
"""Empty BuildHooksEnv for NoHooksLu.
|
392 |
|
393 |
This just raises an error.
|
394 |
|
395 |
"""
|
396 |
raise AssertionError("BuildHooksEnv called for NoHooksLUs") |
397 |
|
398 |
def BuildHooksNodes(self): |
399 |
"""Empty BuildHooksNodes for NoHooksLU.
|
400 |
|
401 |
"""
|
402 |
raise AssertionError("BuildHooksNodes called for NoHooksLU") |
403 |
|
404 |
|
405 |
class Tasklet: |
406 |
"""Tasklet base class.
|
407 |
|
408 |
Tasklets are subcomponents for LUs. LUs can consist entirely of tasklets or
|
409 |
they can mix legacy code with tasklets. Locking needs to be done in the LU,
|
410 |
tasklets know nothing about locks.
|
411 |
|
412 |
Subclasses must follow these rules:
|
413 |
- Implement CheckPrereq
|
414 |
- Implement Exec
|
415 |
|
416 |
"""
|
417 |
def __init__(self, lu): |
418 |
self.lu = lu
|
419 |
|
420 |
# Shortcuts
|
421 |
self.cfg = lu.cfg
|
422 |
self.rpc = lu.rpc
|
423 |
|
424 |
def CheckPrereq(self): |
425 |
"""Check prerequisites for this tasklets.
|
426 |
|
427 |
This method should check whether the prerequisites for the execution of
|
428 |
this tasklet are fulfilled. It can do internode communication, but it
|
429 |
should be idempotent - no cluster or system changes are allowed.
|
430 |
|
431 |
The method should raise errors.OpPrereqError in case something is not
|
432 |
fulfilled. Its return value is ignored.
|
433 |
|
434 |
This method should also update all parameters to their canonical form if it
|
435 |
hasn't been done before.
|
436 |
|
437 |
"""
|
438 |
pass
|
439 |
|
440 |
def Exec(self, feedback_fn): |
441 |
"""Execute the tasklet.
|
442 |
|
443 |
This method should implement the actual work. It should raise
|
444 |
errors.OpExecError for failures that are somewhat dealt with in code, or
|
445 |
expected.
|
446 |
|
447 |
"""
|
448 |
raise NotImplementedError |
449 |
|
450 |
|
451 |
class QueryBase: |
452 |
"""Base for query utility classes.
|
453 |
|
454 |
"""
|
455 |
#: Attribute holding field definitions
|
456 |
FIELDS = None
|
457 |
|
458 |
#: Field to sort by
|
459 |
SORT_FIELD = "name"
|
460 |
|
461 |
def __init__(self, qfilter, fields, use_locking): |
462 |
"""Initializes this class.
|
463 |
|
464 |
"""
|
465 |
self.use_locking = use_locking
|
466 |
|
467 |
self.query = query.Query(self.FIELDS, fields, qfilter=qfilter, |
468 |
namefield=self.SORT_FIELD)
|
469 |
self.requested_data = self.query.RequestedData() |
470 |
self.names = self.query.RequestedNames() |
471 |
|
472 |
# Sort only if no names were requested
|
473 |
self.sort_by_name = not self.names |
474 |
|
475 |
self.do_locking = None |
476 |
self.wanted = None |
477 |
|
478 |
def _GetNames(self, lu, all_names, lock_level): |
479 |
"""Helper function to determine names asked for in the query.
|
480 |
|
481 |
"""
|
482 |
if self.do_locking: |
483 |
names = lu.owned_locks(lock_level) |
484 |
else:
|
485 |
names = all_names |
486 |
|
487 |
if self.wanted == locking.ALL_SET: |
488 |
assert not self.names |
489 |
# caller didn't specify names, so ordering is not important
|
490 |
return utils.NiceSort(names)
|
491 |
|
492 |
# caller specified names and we must keep the same order
|
493 |
assert self.names |
494 |
assert not self.do_locking or lu.glm.is_owned(lock_level) |
495 |
|
496 |
missing = set(self.wanted).difference(names) |
497 |
if missing:
|
498 |
raise errors.OpExecError("Some items were removed before retrieving" |
499 |
" their data: %s" % missing)
|
500 |
|
501 |
# Return expanded names
|
502 |
return self.wanted |
503 |
|
504 |
def ExpandNames(self, lu): |
505 |
"""Expand names for this query.
|
506 |
|
507 |
See L{LogicalUnit.ExpandNames}.
|
508 |
|
509 |
"""
|
510 |
raise NotImplementedError() |
511 |
|
512 |
def DeclareLocks(self, lu, level): |
513 |
"""Declare locks for this query.
|
514 |
|
515 |
See L{LogicalUnit.DeclareLocks}.
|
516 |
|
517 |
"""
|
518 |
raise NotImplementedError() |
519 |
|
520 |
def _GetQueryData(self, lu): |
521 |
"""Collects all data for this query.
|
522 |
|
523 |
@return: Query data object
|
524 |
|
525 |
"""
|
526 |
raise NotImplementedError() |
527 |
|
528 |
def NewStyleQuery(self, lu): |
529 |
"""Collect data and execute query.
|
530 |
|
531 |
"""
|
532 |
return query.GetQueryResponse(self.query, self._GetQueryData(lu), |
533 |
sort_by_name=self.sort_by_name)
|
534 |
|
535 |
def OldStyleQuery(self, lu): |
536 |
"""Collect data and execute query.
|
537 |
|
538 |
"""
|
539 |
return self.query.OldStyleQuery(self._GetQueryData(lu), |
540 |
sort_by_name=self.sort_by_name)
|