hv_xen: Simplify writing configuration
[ganeti-local] / lib / cli.py
1 #
2 #
3
4 # Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012 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 """Module dealing with command line parsing"""
23
24
25 import sys
26 import textwrap
27 import os.path
28 import time
29 import logging
30 import errno
31 import itertools
32 import shlex
33 from cStringIO import StringIO
34
35 from ganeti import utils
36 from ganeti import errors
37 from ganeti import constants
38 from ganeti import opcodes
39 from ganeti import luxi
40 from ganeti import ssconf
41 from ganeti import rpc
42 from ganeti import ssh
43 from ganeti import compat
44 from ganeti import netutils
45 from ganeti import qlang
46 from ganeti import objects
47 from ganeti import pathutils
48
49 from optparse import (OptionParser, TitledHelpFormatter,
50                       Option, OptionValueError)
51
52
53 __all__ = [
54   # Command line options
55   "ABSOLUTE_OPT",
56   "ADD_UIDS_OPT",
57   "ADD_RESERVED_IPS_OPT",
58   "ALLOCATABLE_OPT",
59   "ALLOC_POLICY_OPT",
60   "ALL_OPT",
61   "ALLOW_FAILOVER_OPT",
62   "AUTO_PROMOTE_OPT",
63   "AUTO_REPLACE_OPT",
64   "BACKEND_OPT",
65   "BLK_OS_OPT",
66   "CAPAB_MASTER_OPT",
67   "CAPAB_VM_OPT",
68   "CLEANUP_OPT",
69   "CLUSTER_DOMAIN_SECRET_OPT",
70   "CONFIRM_OPT",
71   "CP_SIZE_OPT",
72   "DEBUG_OPT",
73   "DEBUG_SIMERR_OPT",
74   "DISKIDX_OPT",
75   "DISK_OPT",
76   "DISK_PARAMS_OPT",
77   "DISK_TEMPLATE_OPT",
78   "DRAINED_OPT",
79   "DRY_RUN_OPT",
80   "DRBD_HELPER_OPT",
81   "DST_NODE_OPT",
82   "EARLY_RELEASE_OPT",
83   "ENABLED_HV_OPT",
84   "ERROR_CODES_OPT",
85   "FAILURE_ONLY_OPT",
86   "FIELDS_OPT",
87   "FILESTORE_DIR_OPT",
88   "FILESTORE_DRIVER_OPT",
89   "FORCE_FILTER_OPT",
90   "FORCE_OPT",
91   "FORCE_VARIANT_OPT",
92   "GATEWAY_OPT",
93   "GATEWAY6_OPT",
94   "GLOBAL_FILEDIR_OPT",
95   "HID_OS_OPT",
96   "GLOBAL_SHARED_FILEDIR_OPT",
97   "HVLIST_OPT",
98   "HVOPTS_OPT",
99   "HYPERVISOR_OPT",
100   "IALLOCATOR_OPT",
101   "DEFAULT_IALLOCATOR_OPT",
102   "IDENTIFY_DEFAULTS_OPT",
103   "IGNORE_CONSIST_OPT",
104   "IGNORE_ERRORS_OPT",
105   "IGNORE_FAILURES_OPT",
106   "IGNORE_OFFLINE_OPT",
107   "IGNORE_REMOVE_FAILURES_OPT",
108   "IGNORE_SECONDARIES_OPT",
109   "IGNORE_SIZE_OPT",
110   "INTERVAL_OPT",
111   "MAC_PREFIX_OPT",
112   "MAINTAIN_NODE_HEALTH_OPT",
113   "MASTER_NETDEV_OPT",
114   "MASTER_NETMASK_OPT",
115   "MC_OPT",
116   "MIGRATION_MODE_OPT",
117   "NET_OPT",
118   "NETWORK_OPT",
119   "NETWORK6_OPT",
120   "NETWORK_TYPE_OPT",
121   "NEW_CLUSTER_CERT_OPT",
122   "NEW_CLUSTER_DOMAIN_SECRET_OPT",
123   "NEW_CONFD_HMAC_KEY_OPT",
124   "NEW_RAPI_CERT_OPT",
125   "NEW_SECONDARY_OPT",
126   "NEW_SPICE_CERT_OPT",
127   "NIC_PARAMS_OPT",
128   "NOCONFLICTSCHECK_OPT",
129   "NODE_FORCE_JOIN_OPT",
130   "NODE_LIST_OPT",
131   "NODE_PLACEMENT_OPT",
132   "NODEGROUP_OPT",
133   "NODE_PARAMS_OPT",
134   "NODE_POWERED_OPT",
135   "NODRBD_STORAGE_OPT",
136   "NOHDR_OPT",
137   "NOIPCHECK_OPT",
138   "NO_INSTALL_OPT",
139   "NONAMECHECK_OPT",
140   "NOLVM_STORAGE_OPT",
141   "NOMODIFY_ETCHOSTS_OPT",
142   "NOMODIFY_SSH_SETUP_OPT",
143   "NONICS_OPT",
144   "NONLIVE_OPT",
145   "NONPLUS1_OPT",
146   "NORUNTIME_CHGS_OPT",
147   "NOSHUTDOWN_OPT",
148   "NOSTART_OPT",
149   "NOSSH_KEYCHECK_OPT",
150   "NOVOTING_OPT",
151   "NO_REMEMBER_OPT",
152   "NWSYNC_OPT",
153   "OFFLINE_INST_OPT",
154   "ONLINE_INST_OPT",
155   "ON_PRIMARY_OPT",
156   "ON_SECONDARY_OPT",
157   "OFFLINE_OPT",
158   "OSPARAMS_OPT",
159   "OS_OPT",
160   "OS_SIZE_OPT",
161   "OOB_TIMEOUT_OPT",
162   "POWER_DELAY_OPT",
163   "PREALLOC_WIPE_DISKS_OPT",
164   "PRIMARY_IP_VERSION_OPT",
165   "PRIMARY_ONLY_OPT",
166   "PRIORITY_OPT",
167   "RAPI_CERT_OPT",
168   "READD_OPT",
169   "REBOOT_TYPE_OPT",
170   "REMOVE_INSTANCE_OPT",
171   "REMOVE_RESERVED_IPS_OPT",
172   "REMOVE_UIDS_OPT",
173   "RESERVED_LVS_OPT",
174   "RUNTIME_MEM_OPT",
175   "ROMAN_OPT",
176   "SECONDARY_IP_OPT",
177   "SECONDARY_ONLY_OPT",
178   "SELECT_OS_OPT",
179   "SEP_OPT",
180   "SHOWCMD_OPT",
181   "SHOW_MACHINE_OPT",
182   "SHUTDOWN_TIMEOUT_OPT",
183   "SINGLE_NODE_OPT",
184   "SPECS_CPU_COUNT_OPT",
185   "SPECS_DISK_COUNT_OPT",
186   "SPECS_DISK_SIZE_OPT",
187   "SPECS_MEM_SIZE_OPT",
188   "SPECS_NIC_COUNT_OPT",
189   "IPOLICY_DISK_TEMPLATES",
190   "IPOLICY_VCPU_RATIO",
191   "SPICE_CACERT_OPT",
192   "SPICE_CERT_OPT",
193   "SRC_DIR_OPT",
194   "SRC_NODE_OPT",
195   "SUBMIT_OPT",
196   "STARTUP_PAUSED_OPT",
197   "STATIC_OPT",
198   "SYNC_OPT",
199   "TAG_ADD_OPT",
200   "TAG_SRC_OPT",
201   "TIMEOUT_OPT",
202   "TO_GROUP_OPT",
203   "UIDPOOL_OPT",
204   "USEUNITS_OPT",
205   "USE_EXTERNAL_MIP_SCRIPT",
206   "USE_REPL_NET_OPT",
207   "VERBOSE_OPT",
208   "VG_NAME_OPT",
209   "WFSYNC_OPT",
210   "YES_DOIT_OPT",
211   "DISK_STATE_OPT",
212   "HV_STATE_OPT",
213   "IGNORE_IPOLICY_OPT",
214   "INSTANCE_POLICY_OPTS",
215   # Generic functions for CLI programs
216   "ConfirmOperation",
217   "CreateIPolicyFromOpts",
218   "GenericMain",
219   "GenericInstanceCreate",
220   "GenericList",
221   "GenericListFields",
222   "GetClient",
223   "GetOnlineNodes",
224   "JobExecutor",
225   "JobSubmittedException",
226   "ParseTimespec",
227   "RunWhileClusterStopped",
228   "SubmitOpCode",
229   "SubmitOrSend",
230   "UsesRPC",
231   # Formatting functions
232   "ToStderr", "ToStdout",
233   "FormatError",
234   "FormatQueryResult",
235   "FormatParameterDict",
236   "GenerateTable",
237   "AskUser",
238   "FormatTimestamp",
239   "FormatLogMessage",
240   # Tags functions
241   "ListTags",
242   "AddTags",
243   "RemoveTags",
244   # command line options support infrastructure
245   "ARGS_MANY_INSTANCES",
246   "ARGS_MANY_NODES",
247   "ARGS_MANY_GROUPS",
248   "ARGS_MANY_NETWORKS",
249   "ARGS_NONE",
250   "ARGS_ONE_INSTANCE",
251   "ARGS_ONE_NODE",
252   "ARGS_ONE_GROUP",
253   "ARGS_ONE_OS",
254   "ARGS_ONE_NETWORK",
255   "ArgChoice",
256   "ArgCommand",
257   "ArgFile",
258   "ArgGroup",
259   "ArgHost",
260   "ArgInstance",
261   "ArgJobId",
262   "ArgNetwork",
263   "ArgNode",
264   "ArgOs",
265   "ArgExtStorage",
266   "ArgSuggest",
267   "ArgUnknown",
268   "OPT_COMPL_INST_ADD_NODES",
269   "OPT_COMPL_MANY_NODES",
270   "OPT_COMPL_ONE_IALLOCATOR",
271   "OPT_COMPL_ONE_INSTANCE",
272   "OPT_COMPL_ONE_NODE",
273   "OPT_COMPL_ONE_NODEGROUP",
274   "OPT_COMPL_ONE_NETWORK",
275   "OPT_COMPL_ONE_OS",
276   "OPT_COMPL_ONE_EXTSTORAGE",
277   "cli_option",
278   "SplitNodeOption",
279   "CalculateOSNames",
280   "ParseFields",
281   "COMMON_CREATE_OPTS",
282   ]
283
284 NO_PREFIX = "no_"
285 UN_PREFIX = "-"
286
287 #: Priorities (sorted)
288 _PRIORITY_NAMES = [
289   ("low", constants.OP_PRIO_LOW),
290   ("normal", constants.OP_PRIO_NORMAL),
291   ("high", constants.OP_PRIO_HIGH),
292   ]
293
294 #: Priority dictionary for easier lookup
295 # TODO: Replace this and _PRIORITY_NAMES with a single sorted dictionary once
296 # we migrate to Python 2.6
297 _PRIONAME_TO_VALUE = dict(_PRIORITY_NAMES)
298
299 # Query result status for clients
300 (QR_NORMAL,
301  QR_UNKNOWN,
302  QR_INCOMPLETE) = range(3)
303
304 #: Maximum batch size for ChooseJob
305 _CHOOSE_BATCH = 25
306
307
308 # constants used to create InstancePolicy dictionary
309 TISPECS_GROUP_TYPES = {
310   constants.ISPECS_MIN: constants.VTYPE_INT,
311   constants.ISPECS_MAX: constants.VTYPE_INT,
312   }
313
314 TISPECS_CLUSTER_TYPES = {
315   constants.ISPECS_MIN: constants.VTYPE_INT,
316   constants.ISPECS_MAX: constants.VTYPE_INT,
317   constants.ISPECS_STD: constants.VTYPE_INT,
318   }
319
320 #: User-friendly names for query2 field types
321 _QFT_NAMES = {
322   constants.QFT_UNKNOWN: "Unknown",
323   constants.QFT_TEXT: "Text",
324   constants.QFT_BOOL: "Boolean",
325   constants.QFT_NUMBER: "Number",
326   constants.QFT_UNIT: "Storage size",
327   constants.QFT_TIMESTAMP: "Timestamp",
328   constants.QFT_OTHER: "Custom",
329   }
330
331
332 class _Argument:
333   def __init__(self, min=0, max=None): # pylint: disable=W0622
334     self.min = min
335     self.max = max
336
337   def __repr__(self):
338     return ("<%s min=%s max=%s>" %
339             (self.__class__.__name__, self.min, self.max))
340
341
342 class ArgSuggest(_Argument):
343   """Suggesting argument.
344
345   Value can be any of the ones passed to the constructor.
346
347   """
348   # pylint: disable=W0622
349   def __init__(self, min=0, max=None, choices=None):
350     _Argument.__init__(self, min=min, max=max)
351     self.choices = choices
352
353   def __repr__(self):
354     return ("<%s min=%s max=%s choices=%r>" %
355             (self.__class__.__name__, self.min, self.max, self.choices))
356
357
358 class ArgChoice(ArgSuggest):
359   """Choice argument.
360
361   Value can be any of the ones passed to the constructor. Like L{ArgSuggest},
362   but value must be one of the choices.
363
364   """
365
366
367 class ArgUnknown(_Argument):
368   """Unknown argument to program (e.g. determined at runtime).
369
370   """
371
372
373 class ArgInstance(_Argument):
374   """Instances argument.
375
376   """
377
378
379 class ArgNode(_Argument):
380   """Node argument.
381
382   """
383
384
385 class ArgNetwork(_Argument):
386   """Network argument.
387
388   """
389
390
391 class ArgGroup(_Argument):
392   """Node group argument.
393
394   """
395
396
397 class ArgJobId(_Argument):
398   """Job ID argument.
399
400   """
401
402
403 class ArgFile(_Argument):
404   """File path argument.
405
406   """
407
408
409 class ArgCommand(_Argument):
410   """Command argument.
411
412   """
413
414
415 class ArgHost(_Argument):
416   """Host argument.
417
418   """
419
420
421 class ArgOs(_Argument):
422   """OS argument.
423
424   """
425
426
427 class ArgExtStorage(_Argument):
428   """ExtStorage argument.
429
430   """
431
432
433 ARGS_NONE = []
434 ARGS_MANY_INSTANCES = [ArgInstance()]
435 ARGS_MANY_NETWORKS = [ArgNetwork()]
436 ARGS_MANY_NODES = [ArgNode()]
437 ARGS_MANY_GROUPS = [ArgGroup()]
438 ARGS_ONE_INSTANCE = [ArgInstance(min=1, max=1)]
439 ARGS_ONE_NETWORK = [ArgNetwork(min=1, max=1)]
440 ARGS_ONE_NODE = [ArgNode(min=1, max=1)]
441 # TODO
442 ARGS_ONE_GROUP = [ArgGroup(min=1, max=1)]
443 ARGS_ONE_OS = [ArgOs(min=1, max=1)]
444
445
446 def _ExtractTagsObject(opts, args):
447   """Extract the tag type object.
448
449   Note that this function will modify its args parameter.
450
451   """
452   if not hasattr(opts, "tag_type"):
453     raise errors.ProgrammerError("tag_type not passed to _ExtractTagsObject")
454   kind = opts.tag_type
455   if kind == constants.TAG_CLUSTER:
456     retval = kind, None
457   elif kind in (constants.TAG_NODEGROUP,
458                 constants.TAG_NODE,
459                 constants.TAG_NETWORK,
460                 constants.TAG_INSTANCE):
461     if not args:
462       raise errors.OpPrereqError("no arguments passed to the command",
463                                  errors.ECODE_INVAL)
464     name = args.pop(0)
465     retval = kind, name
466   else:
467     raise errors.ProgrammerError("Unhandled tag type '%s'" % kind)
468   return retval
469
470
471 def _ExtendTags(opts, args):
472   """Extend the args if a source file has been given.
473
474   This function will extend the tags with the contents of the file
475   passed in the 'tags_source' attribute of the opts parameter. A file
476   named '-' will be replaced by stdin.
477
478   """
479   fname = opts.tags_source
480   if fname is None:
481     return
482   if fname == "-":
483     new_fh = sys.stdin
484   else:
485     new_fh = open(fname, "r")
486   new_data = []
487   try:
488     # we don't use the nice 'new_data = [line.strip() for line in fh]'
489     # because of python bug 1633941
490     while True:
491       line = new_fh.readline()
492       if not line:
493         break
494       new_data.append(line.strip())
495   finally:
496     new_fh.close()
497   args.extend(new_data)
498
499
500 def ListTags(opts, args):
501   """List the tags on a given object.
502
503   This is a generic implementation that knows how to deal with all
504   three cases of tag objects (cluster, node, instance). The opts
505   argument is expected to contain a tag_type field denoting what
506   object type we work on.
507
508   """
509   kind, name = _ExtractTagsObject(opts, args)
510   cl = GetClient(query=True)
511   result = cl.QueryTags(kind, name)
512   result = list(result)
513   result.sort()
514   for tag in result:
515     ToStdout(tag)
516
517
518 def AddTags(opts, args):
519   """Add tags on a given object.
520
521   This is a generic implementation that knows how to deal with all
522   three cases of tag objects (cluster, node, instance). The opts
523   argument is expected to contain a tag_type field denoting what
524   object type we work on.
525
526   """
527   kind, name = _ExtractTagsObject(opts, args)
528   _ExtendTags(opts, args)
529   if not args:
530     raise errors.OpPrereqError("No tags to be added", errors.ECODE_INVAL)
531   op = opcodes.OpTagsSet(kind=kind, name=name, tags=args)
532   SubmitOrSend(op, opts)
533
534
535 def RemoveTags(opts, args):
536   """Remove tags from a given object.
537
538   This is a generic implementation that knows how to deal with all
539   three cases of tag objects (cluster, node, instance). The opts
540   argument is expected to contain a tag_type field denoting what
541   object type we work on.
542
543   """
544   kind, name = _ExtractTagsObject(opts, args)
545   _ExtendTags(opts, args)
546   if not args:
547     raise errors.OpPrereqError("No tags to be removed", errors.ECODE_INVAL)
548   op = opcodes.OpTagsDel(kind=kind, name=name, tags=args)
549   SubmitOrSend(op, opts)
550
551
552 def check_unit(option, opt, value): # pylint: disable=W0613
553   """OptParsers custom converter for units.
554
555   """
556   try:
557     return utils.ParseUnit(value)
558   except errors.UnitParseError, err:
559     raise OptionValueError("option %s: %s" % (opt, err))
560
561
562 def _SplitKeyVal(opt, data):
563   """Convert a KeyVal string into a dict.
564
565   This function will convert a key=val[,...] string into a dict. Empty
566   values will be converted specially: keys which have the prefix 'no_'
567   will have the value=False and the prefix stripped, the others will
568   have value=True.
569
570   @type opt: string
571   @param opt: a string holding the option name for which we process the
572       data, used in building error messages
573   @type data: string
574   @param data: a string of the format key=val,key=val,...
575   @rtype: dict
576   @return: {key=val, key=val}
577   @raises errors.ParameterError: if there are duplicate keys
578
579   """
580   kv_dict = {}
581   if data:
582     for elem in utils.UnescapeAndSplit(data, sep=","):
583       if "=" in elem:
584         key, val = elem.split("=", 1)
585       else:
586         if elem.startswith(NO_PREFIX):
587           key, val = elem[len(NO_PREFIX):], False
588         elif elem.startswith(UN_PREFIX):
589           key, val = elem[len(UN_PREFIX):], None
590         else:
591           key, val = elem, True
592       if key in kv_dict:
593         raise errors.ParameterError("Duplicate key '%s' in option %s" %
594                                     (key, opt))
595       kv_dict[key] = val
596   return kv_dict
597
598
599 def check_ident_key_val(option, opt, value):  # pylint: disable=W0613
600   """Custom parser for ident:key=val,key=val options.
601
602   This will store the parsed values as a tuple (ident, {key: val}). As such,
603   multiple uses of this option via action=append is possible.
604
605   """
606   if ":" not in value:
607     ident, rest = value, ""
608   else:
609     ident, rest = value.split(":", 1)
610
611   if ident.startswith(NO_PREFIX):
612     if rest:
613       msg = "Cannot pass options when removing parameter groups: %s" % value
614       raise errors.ParameterError(msg)
615     retval = (ident[len(NO_PREFIX):], False)
616   elif (ident.startswith(UN_PREFIX) and
617         (len(ident) <= len(UN_PREFIX) or
618          not ident[len(UN_PREFIX)][0].isdigit())):
619     if rest:
620       msg = "Cannot pass options when removing parameter groups: %s" % value
621       raise errors.ParameterError(msg)
622     retval = (ident[len(UN_PREFIX):], None)
623   else:
624     kv_dict = _SplitKeyVal(opt, rest)
625     retval = (ident, kv_dict)
626   return retval
627
628
629 def check_key_val(option, opt, value):  # pylint: disable=W0613
630   """Custom parser class for key=val,key=val options.
631
632   This will store the parsed values as a dict {key: val}.
633
634   """
635   return _SplitKeyVal(opt, value)
636
637
638 def check_bool(option, opt, value): # pylint: disable=W0613
639   """Custom parser for yes/no options.
640
641   This will store the parsed value as either True or False.
642
643   """
644   value = value.lower()
645   if value == constants.VALUE_FALSE or value == "no":
646     return False
647   elif value == constants.VALUE_TRUE or value == "yes":
648     return True
649   else:
650     raise errors.ParameterError("Invalid boolean value '%s'" % value)
651
652
653 def check_list(option, opt, value): # pylint: disable=W0613
654   """Custom parser for comma-separated lists.
655
656   """
657   # we have to make this explicit check since "".split(",") is [""],
658   # not an empty list :(
659   if not value:
660     return []
661   else:
662     return utils.UnescapeAndSplit(value)
663
664
665 def check_maybefloat(option, opt, value): # pylint: disable=W0613
666   """Custom parser for float numbers which might be also defaults.
667
668   """
669   value = value.lower()
670
671   if value == constants.VALUE_DEFAULT:
672     return value
673   else:
674     return float(value)
675
676
677 # completion_suggestion is normally a list. Using numeric values not evaluating
678 # to False for dynamic completion.
679 (OPT_COMPL_MANY_NODES,
680  OPT_COMPL_ONE_NODE,
681  OPT_COMPL_ONE_INSTANCE,
682  OPT_COMPL_ONE_OS,
683  OPT_COMPL_ONE_EXTSTORAGE,
684  OPT_COMPL_ONE_IALLOCATOR,
685  OPT_COMPL_ONE_NETWORK,
686  OPT_COMPL_INST_ADD_NODES,
687  OPT_COMPL_ONE_NODEGROUP) = range(100, 109)
688
689 OPT_COMPL_ALL = compat.UniqueFrozenset([
690   OPT_COMPL_MANY_NODES,
691   OPT_COMPL_ONE_NODE,
692   OPT_COMPL_ONE_INSTANCE,
693   OPT_COMPL_ONE_OS,
694   OPT_COMPL_ONE_EXTSTORAGE,
695   OPT_COMPL_ONE_IALLOCATOR,
696   OPT_COMPL_ONE_NETWORK,
697   OPT_COMPL_INST_ADD_NODES,
698   OPT_COMPL_ONE_NODEGROUP,
699   ])
700
701
702 class CliOption(Option):
703   """Custom option class for optparse.
704
705   """
706   ATTRS = Option.ATTRS + [
707     "completion_suggest",
708     ]
709   TYPES = Option.TYPES + (
710     "identkeyval",
711     "keyval",
712     "unit",
713     "bool",
714     "list",
715     "maybefloat",
716     )
717   TYPE_CHECKER = Option.TYPE_CHECKER.copy()
718   TYPE_CHECKER["identkeyval"] = check_ident_key_val
719   TYPE_CHECKER["keyval"] = check_key_val
720   TYPE_CHECKER["unit"] = check_unit
721   TYPE_CHECKER["bool"] = check_bool
722   TYPE_CHECKER["list"] = check_list
723   TYPE_CHECKER["maybefloat"] = check_maybefloat
724
725
726 # optparse.py sets make_option, so we do it for our own option class, too
727 cli_option = CliOption
728
729
730 _YORNO = "yes|no"
731
732 DEBUG_OPT = cli_option("-d", "--debug", default=0, action="count",
733                        help="Increase debugging level")
734
735 NOHDR_OPT = cli_option("--no-headers", default=False,
736                        action="store_true", dest="no_headers",
737                        help="Don't display column headers")
738
739 SEP_OPT = cli_option("--separator", default=None,
740                      action="store", dest="separator",
741                      help=("Separator between output fields"
742                            " (defaults to one space)"))
743
744 USEUNITS_OPT = cli_option("--units", default=None,
745                           dest="units", choices=("h", "m", "g", "t"),
746                           help="Specify units for output (one of h/m/g/t)")
747
748 FIELDS_OPT = cli_option("-o", "--output", dest="output", action="store",
749                         type="string", metavar="FIELDS",
750                         help="Comma separated list of output fields")
751
752 FORCE_OPT = cli_option("-f", "--force", dest="force", action="store_true",
753                        default=False, help="Force the operation")
754
755 CONFIRM_OPT = cli_option("--yes", dest="confirm", action="store_true",
756                          default=False, help="Do not require confirmation")
757
758 IGNORE_OFFLINE_OPT = cli_option("--ignore-offline", dest="ignore_offline",
759                                   action="store_true", default=False,
760                                   help=("Ignore offline nodes and do as much"
761                                         " as possible"))
762
763 TAG_ADD_OPT = cli_option("--tags", dest="tags",
764                          default=None, help="Comma-separated list of instance"
765                                             " tags")
766
767 TAG_SRC_OPT = cli_option("--from", dest="tags_source",
768                          default=None, help="File with tag names")
769
770 SUBMIT_OPT = cli_option("--submit", dest="submit_only",
771                         default=False, action="store_true",
772                         help=("Submit the job and return the job ID, but"
773                               " don't wait for the job to finish"))
774
775 SYNC_OPT = cli_option("--sync", dest="do_locking",
776                       default=False, action="store_true",
777                       help=("Grab locks while doing the queries"
778                             " in order to ensure more consistent results"))
779
780 DRY_RUN_OPT = cli_option("--dry-run", default=False,
781                          action="store_true",
782                          help=("Do not execute the operation, just run the"
783                                " check steps and verify if it could be"
784                                " executed"))
785
786 VERBOSE_OPT = cli_option("-v", "--verbose", default=False,
787                          action="store_true",
788                          help="Increase the verbosity of the operation")
789
790 DEBUG_SIMERR_OPT = cli_option("--debug-simulate-errors", default=False,
791                               action="store_true", dest="simulate_errors",
792                               help="Debugging option that makes the operation"
793                               " treat most runtime checks as failed")
794
795 NWSYNC_OPT = cli_option("--no-wait-for-sync", dest="wait_for_sync",
796                         default=True, action="store_false",
797                         help="Don't wait for sync (DANGEROUS!)")
798
799 WFSYNC_OPT = cli_option("--wait-for-sync", dest="wait_for_sync",
800                         default=False, action="store_true",
801                         help="Wait for disks to sync")
802
803 ONLINE_INST_OPT = cli_option("--online", dest="online_inst",
804                              action="store_true", default=False,
805                              help="Enable offline instance")
806
807 OFFLINE_INST_OPT = cli_option("--offline", dest="offline_inst",
808                               action="store_true", default=False,
809                               help="Disable down instance")
810
811 DISK_TEMPLATE_OPT = cli_option("-t", "--disk-template", dest="disk_template",
812                                help=("Custom disk setup (%s)" %
813                                      utils.CommaJoin(constants.DISK_TEMPLATES)),
814                                default=None, metavar="TEMPL",
815                                choices=list(constants.DISK_TEMPLATES))
816
817 NONICS_OPT = cli_option("--no-nics", default=False, action="store_true",
818                         help="Do not create any network cards for"
819                         " the instance")
820
821 FILESTORE_DIR_OPT = cli_option("--file-storage-dir", dest="file_storage_dir",
822                                help="Relative path under default cluster-wide"
823                                " file storage dir to store file-based disks",
824                                default=None, metavar="<DIR>")
825
826 FILESTORE_DRIVER_OPT = cli_option("--file-driver", dest="file_driver",
827                                   help="Driver to use for image files",
828                                   default="loop", metavar="<DRIVER>",
829                                   choices=list(constants.FILE_DRIVER))
830
831 IALLOCATOR_OPT = cli_option("-I", "--iallocator", metavar="<NAME>",
832                             help="Select nodes for the instance automatically"
833                             " using the <NAME> iallocator plugin",
834                             default=None, type="string",
835                             completion_suggest=OPT_COMPL_ONE_IALLOCATOR)
836
837 DEFAULT_IALLOCATOR_OPT = cli_option("-I", "--default-iallocator",
838                                     metavar="<NAME>",
839                                     help="Set the default instance"
840                                     " allocator plugin",
841                                     default=None, type="string",
842                                     completion_suggest=OPT_COMPL_ONE_IALLOCATOR)
843
844 OS_OPT = cli_option("-o", "--os-type", dest="os", help="What OS to run",
845                     metavar="<os>",
846                     completion_suggest=OPT_COMPL_ONE_OS)
847
848 OSPARAMS_OPT = cli_option("-O", "--os-parameters", dest="osparams",
849                           type="keyval", default={},
850                           help="OS parameters")
851
852 FORCE_VARIANT_OPT = cli_option("--force-variant", dest="force_variant",
853                                action="store_true", default=False,
854                                help="Force an unknown variant")
855
856 NO_INSTALL_OPT = cli_option("--no-install", dest="no_install",
857                             action="store_true", default=False,
858                             help="Do not install the OS (will"
859                             " enable no-start)")
860
861 NORUNTIME_CHGS_OPT = cli_option("--no-runtime-changes",
862                                 dest="allow_runtime_chgs",
863                                 default=True, action="store_false",
864                                 help="Don't allow runtime changes")
865
866 BACKEND_OPT = cli_option("-B", "--backend-parameters", dest="beparams",
867                          type="keyval", default={},
868                          help="Backend parameters")
869
870 HVOPTS_OPT = cli_option("-H", "--hypervisor-parameters", type="keyval",
871                         default={}, dest="hvparams",
872                         help="Hypervisor parameters")
873
874 DISK_PARAMS_OPT = cli_option("-D", "--disk-parameters", dest="diskparams",
875                              help="Disk template parameters, in the format"
876                              " template:option=value,option=value,...",
877                              type="identkeyval", action="append", default=[])
878
879 SPECS_MEM_SIZE_OPT = cli_option("--specs-mem-size", dest="ispecs_mem_size",
880                                  type="keyval", default={},
881                                  help="Memory size specs: list of key=value,"
882                                 " where key is one of min, max, std"
883                                  " (in MB or using a unit)")
884
885 SPECS_CPU_COUNT_OPT = cli_option("--specs-cpu-count", dest="ispecs_cpu_count",
886                                  type="keyval", default={},
887                                  help="CPU count specs: list of key=value,"
888                                  " where key is one of min, max, std")
889
890 SPECS_DISK_COUNT_OPT = cli_option("--specs-disk-count",
891                                   dest="ispecs_disk_count",
892                                   type="keyval", default={},
893                                   help="Disk count specs: list of key=value,"
894                                   " where key is one of min, max, std")
895
896 SPECS_DISK_SIZE_OPT = cli_option("--specs-disk-size", dest="ispecs_disk_size",
897                                  type="keyval", default={},
898                                  help="Disk size specs: list of key=value,"
899                                  " where key is one of min, max, std"
900                                  " (in MB or using a unit)")
901
902 SPECS_NIC_COUNT_OPT = cli_option("--specs-nic-count", dest="ispecs_nic_count",
903                                  type="keyval", default={},
904                                  help="NIC count specs: list of key=value,"
905                                  " where key is one of min, max, std")
906
907 IPOLICY_DISK_TEMPLATES = cli_option("--ipolicy-disk-templates",
908                                     dest="ipolicy_disk_templates",
909                                     type="list", default=None,
910                                     help="Comma-separated list of"
911                                     " enabled disk templates")
912
913 IPOLICY_VCPU_RATIO = cli_option("--ipolicy-vcpu-ratio",
914                                  dest="ipolicy_vcpu_ratio",
915                                  type="maybefloat", default=None,
916                                  help="The maximum allowed vcpu-to-cpu ratio")
917
918 IPOLICY_SPINDLE_RATIO = cli_option("--ipolicy-spindle-ratio",
919                                    dest="ipolicy_spindle_ratio",
920                                    type="maybefloat", default=None,
921                                    help=("The maximum allowed instances to"
922                                          " spindle ratio"))
923
924 HYPERVISOR_OPT = cli_option("-H", "--hypervisor-parameters", dest="hypervisor",
925                             help="Hypervisor and hypervisor options, in the"
926                             " format hypervisor:option=value,option=value,...",
927                             default=None, type="identkeyval")
928
929 HVLIST_OPT = cli_option("-H", "--hypervisor-parameters", dest="hvparams",
930                         help="Hypervisor and hypervisor options, in the"
931                         " format hypervisor:option=value,option=value,...",
932                         default=[], action="append", type="identkeyval")
933
934 NOIPCHECK_OPT = cli_option("--no-ip-check", dest="ip_check", default=True,
935                            action="store_false",
936                            help="Don't check that the instance's IP"
937                            " is alive")
938
939 NONAMECHECK_OPT = cli_option("--no-name-check", dest="name_check",
940                              default=True, action="store_false",
941                              help="Don't check that the instance's name"
942                              " is resolvable")
943
944 NET_OPT = cli_option("--net",
945                      help="NIC parameters", default=[],
946                      dest="nics", action="append", type="identkeyval")
947
948 DISK_OPT = cli_option("--disk", help="Disk parameters", default=[],
949                       dest="disks", action="append", type="identkeyval")
950
951 DISKIDX_OPT = cli_option("--disks", dest="disks", default=None,
952                          help="Comma-separated list of disks"
953                          " indices to act on (e.g. 0,2) (optional,"
954                          " defaults to all disks)")
955
956 OS_SIZE_OPT = cli_option("-s", "--os-size", dest="sd_size",
957                          help="Enforces a single-disk configuration using the"
958                          " given disk size, in MiB unless a suffix is used",
959                          default=None, type="unit", metavar="<size>")
960
961 IGNORE_CONSIST_OPT = cli_option("--ignore-consistency",
962                                 dest="ignore_consistency",
963                                 action="store_true", default=False,
964                                 help="Ignore the consistency of the disks on"
965                                 " the secondary")
966
967 ALLOW_FAILOVER_OPT = cli_option("--allow-failover",
968                                 dest="allow_failover",
969                                 action="store_true", default=False,
970                                 help="If migration is not possible fallback to"
971                                      " failover")
972
973 NONLIVE_OPT = cli_option("--non-live", dest="live",
974                          default=True, action="store_false",
975                          help="Do a non-live migration (this usually means"
976                          " freeze the instance, save the state, transfer and"
977                          " only then resume running on the secondary node)")
978
979 MIGRATION_MODE_OPT = cli_option("--migration-mode", dest="migration_mode",
980                                 default=None,
981                                 choices=list(constants.HT_MIGRATION_MODES),
982                                 help="Override default migration mode (choose"
983                                 " either live or non-live")
984
985 NODE_PLACEMENT_OPT = cli_option("-n", "--node", dest="node",
986                                 help="Target node and optional secondary node",
987                                 metavar="<pnode>[:<snode>]",
988                                 completion_suggest=OPT_COMPL_INST_ADD_NODES)
989
990 NODE_LIST_OPT = cli_option("-n", "--node", dest="nodes", default=[],
991                            action="append", metavar="<node>",
992                            help="Use only this node (can be used multiple"
993                            " times, if not given defaults to all nodes)",
994                            completion_suggest=OPT_COMPL_ONE_NODE)
995
996 NODEGROUP_OPT_NAME = "--node-group"
997 NODEGROUP_OPT = cli_option("-g", NODEGROUP_OPT_NAME,
998                            dest="nodegroup",
999                            help="Node group (name or uuid)",
1000                            metavar="<nodegroup>",
1001                            default=None, type="string",
1002                            completion_suggest=OPT_COMPL_ONE_NODEGROUP)
1003
1004 SINGLE_NODE_OPT = cli_option("-n", "--node", dest="node", help="Target node",
1005                              metavar="<node>",
1006                              completion_suggest=OPT_COMPL_ONE_NODE)
1007
1008 NOSTART_OPT = cli_option("--no-start", dest="start", default=True,
1009                          action="store_false",
1010                          help="Don't start the instance after creation")
1011
1012 SHOWCMD_OPT = cli_option("--show-cmd", dest="show_command",
1013                          action="store_true", default=False,
1014                          help="Show command instead of executing it")
1015
1016 CLEANUP_OPT = cli_option("--cleanup", dest="cleanup",
1017                          default=False, action="store_true",
1018                          help="Instead of performing the migration, try to"
1019                          " recover from a failed cleanup. This is safe"
1020                          " to run even if the instance is healthy, but it"
1021                          " will create extra replication traffic and "
1022                          " disrupt briefly the replication (like during the"
1023                          " migration")
1024
1025 STATIC_OPT = cli_option("-s", "--static", dest="static",
1026                         action="store_true", default=False,
1027                         help="Only show configuration data, not runtime data")
1028
1029 ALL_OPT = cli_option("--all", dest="show_all",
1030                      default=False, action="store_true",
1031                      help="Show info on all instances on the cluster."
1032                      " This can take a long time to run, use wisely")
1033
1034 SELECT_OS_OPT = cli_option("--select-os", dest="select_os",
1035                            action="store_true", default=False,
1036                            help="Interactive OS reinstall, lists available"
1037                            " OS templates for selection")
1038
1039 IGNORE_FAILURES_OPT = cli_option("--ignore-failures", dest="ignore_failures",
1040                                  action="store_true", default=False,
1041                                  help="Remove the instance from the cluster"
1042                                  " configuration even if there are failures"
1043                                  " during the removal process")
1044
1045 IGNORE_REMOVE_FAILURES_OPT = cli_option("--ignore-remove-failures",
1046                                         dest="ignore_remove_failures",
1047                                         action="store_true", default=False,
1048                                         help="Remove the instance from the"
1049                                         " cluster configuration even if there"
1050                                         " are failures during the removal"
1051                                         " process")
1052
1053 REMOVE_INSTANCE_OPT = cli_option("--remove-instance", dest="remove_instance",
1054                                  action="store_true", default=False,
1055                                  help="Remove the instance from the cluster")
1056
1057 DST_NODE_OPT = cli_option("-n", "--target-node", dest="dst_node",
1058                                help="Specifies the new node for the instance",
1059                                metavar="NODE", default=None,
1060                                completion_suggest=OPT_COMPL_ONE_NODE)
1061
1062 NEW_SECONDARY_OPT = cli_option("-n", "--new-secondary", dest="dst_node",
1063                                help="Specifies the new secondary node",
1064                                metavar="NODE", default=None,
1065                                completion_suggest=OPT_COMPL_ONE_NODE)
1066
1067 ON_PRIMARY_OPT = cli_option("-p", "--on-primary", dest="on_primary",
1068                             default=False, action="store_true",
1069                             help="Replace the disk(s) on the primary"
1070                                  " node (applies only to internally mirrored"
1071                                  " disk templates, e.g. %s)" %
1072                                  utils.CommaJoin(constants.DTS_INT_MIRROR))
1073
1074 ON_SECONDARY_OPT = cli_option("-s", "--on-secondary", dest="on_secondary",
1075                               default=False, action="store_true",
1076                               help="Replace the disk(s) on the secondary"
1077                                    " node (applies only to internally mirrored"
1078                                    " disk templates, e.g. %s)" %
1079                                    utils.CommaJoin(constants.DTS_INT_MIRROR))
1080
1081 AUTO_PROMOTE_OPT = cli_option("--auto-promote", dest="auto_promote",
1082                               default=False, action="store_true",
1083                               help="Lock all nodes and auto-promote as needed"
1084                               " to MC status")
1085
1086 AUTO_REPLACE_OPT = cli_option("-a", "--auto", dest="auto",
1087                               default=False, action="store_true",
1088                               help="Automatically replace faulty disks"
1089                                    " (applies only to internally mirrored"
1090                                    " disk templates, e.g. %s)" %
1091                                    utils.CommaJoin(constants.DTS_INT_MIRROR))
1092
1093 IGNORE_SIZE_OPT = cli_option("--ignore-size", dest="ignore_size",
1094                              default=False, action="store_true",
1095                              help="Ignore current recorded size"
1096                              " (useful for forcing activation when"
1097                              " the recorded size is wrong)")
1098
1099 SRC_NODE_OPT = cli_option("--src-node", dest="src_node", help="Source node",
1100                           metavar="<node>",
1101                           completion_suggest=OPT_COMPL_ONE_NODE)
1102
1103 SRC_DIR_OPT = cli_option("--src-dir", dest="src_dir", help="Source directory",
1104                          metavar="<dir>")
1105
1106 SECONDARY_IP_OPT = cli_option("-s", "--secondary-ip", dest="secondary_ip",
1107                               help="Specify the secondary ip for the node",
1108                               metavar="ADDRESS", default=None)
1109
1110 READD_OPT = cli_option("--readd", dest="readd",
1111                        default=False, action="store_true",
1112                        help="Readd old node after replacing it")
1113
1114 NOSSH_KEYCHECK_OPT = cli_option("--no-ssh-key-check", dest="ssh_key_check",
1115                                 default=True, action="store_false",
1116                                 help="Disable SSH key fingerprint checking")
1117
1118 NODE_FORCE_JOIN_OPT = cli_option("--force-join", dest="force_join",
1119                                  default=False, action="store_true",
1120                                  help="Force the joining of a node")
1121
1122 MC_OPT = cli_option("-C", "--master-candidate", dest="master_candidate",
1123                     type="bool", default=None, metavar=_YORNO,
1124                     help="Set the master_candidate flag on the node")
1125
1126 OFFLINE_OPT = cli_option("-O", "--offline", dest="offline", metavar=_YORNO,
1127                          type="bool", default=None,
1128                          help=("Set the offline flag on the node"
1129                                " (cluster does not communicate with offline"
1130                                " nodes)"))
1131
1132 DRAINED_OPT = cli_option("-D", "--drained", dest="drained", metavar=_YORNO,
1133                          type="bool", default=None,
1134                          help=("Set the drained flag on the node"
1135                                " (excluded from allocation operations)"))
1136
1137 CAPAB_MASTER_OPT = cli_option("--master-capable", dest="master_capable",
1138                               type="bool", default=None, metavar=_YORNO,
1139                               help="Set the master_capable flag on the node")
1140
1141 CAPAB_VM_OPT = cli_option("--vm-capable", dest="vm_capable",
1142                           type="bool", default=None, metavar=_YORNO,
1143                           help="Set the vm_capable flag on the node")
1144
1145 ALLOCATABLE_OPT = cli_option("--allocatable", dest="allocatable",
1146                              type="bool", default=None, metavar=_YORNO,
1147                              help="Set the allocatable flag on a volume")
1148
1149 NOLVM_STORAGE_OPT = cli_option("--no-lvm-storage", dest="lvm_storage",
1150                                help="Disable support for lvm based instances"
1151                                " (cluster-wide)",
1152                                action="store_false", default=True)
1153
1154 ENABLED_HV_OPT = cli_option("--enabled-hypervisors",
1155                             dest="enabled_hypervisors",
1156                             help="Comma-separated list of hypervisors",
1157                             type="string", default=None)
1158
1159 NIC_PARAMS_OPT = cli_option("-N", "--nic-parameters", dest="nicparams",
1160                             type="keyval", default={},
1161                             help="NIC parameters")
1162
1163 CP_SIZE_OPT = cli_option("-C", "--candidate-pool-size", default=None,
1164                          dest="candidate_pool_size", type="int",
1165                          help="Set the candidate pool size")
1166
1167 VG_NAME_OPT = cli_option("--vg-name", dest="vg_name",
1168                          help=("Enables LVM and specifies the volume group"
1169                                " name (cluster-wide) for disk allocation"
1170                                " [%s]" % constants.DEFAULT_VG),
1171                          metavar="VG", default=None)
1172
1173 YES_DOIT_OPT = cli_option("--yes-do-it", "--ya-rly", dest="yes_do_it",
1174                           help="Destroy cluster", action="store_true")
1175
1176 NOVOTING_OPT = cli_option("--no-voting", dest="no_voting",
1177                           help="Skip node agreement check (dangerous)",
1178                           action="store_true", default=False)
1179
1180 MAC_PREFIX_OPT = cli_option("-m", "--mac-prefix", dest="mac_prefix",
1181                             help="Specify the mac prefix for the instance IP"
1182                             " addresses, in the format XX:XX:XX",
1183                             metavar="PREFIX",
1184                             default=None)
1185
1186 MASTER_NETDEV_OPT = cli_option("--master-netdev", dest="master_netdev",
1187                                help="Specify the node interface (cluster-wide)"
1188                                " on which the master IP address will be added"
1189                                " (cluster init default: %s)" %
1190                                constants.DEFAULT_BRIDGE,
1191                                metavar="NETDEV",
1192                                default=None)
1193
1194 MASTER_NETMASK_OPT = cli_option("--master-netmask", dest="master_netmask",
1195                                 help="Specify the netmask of the master IP",
1196                                 metavar="NETMASK",
1197                                 default=None)
1198
1199 USE_EXTERNAL_MIP_SCRIPT = cli_option("--use-external-mip-script",
1200                                      dest="use_external_mip_script",
1201                                      help="Specify whether to run a"
1202                                      " user-provided script for the master"
1203                                      " IP address turnup and"
1204                                      " turndown operations",
1205                                      type="bool", metavar=_YORNO, default=None)
1206
1207 GLOBAL_FILEDIR_OPT = cli_option("--file-storage-dir", dest="file_storage_dir",
1208                                 help="Specify the default directory (cluster-"
1209                                 "wide) for storing the file-based disks [%s]" %
1210                                 pathutils.DEFAULT_FILE_STORAGE_DIR,
1211                                 metavar="DIR",
1212                                 default=pathutils.DEFAULT_FILE_STORAGE_DIR)
1213
1214 GLOBAL_SHARED_FILEDIR_OPT = cli_option(
1215   "--shared-file-storage-dir",
1216   dest="shared_file_storage_dir",
1217   help="Specify the default directory (cluster-wide) for storing the"
1218   " shared file-based disks [%s]" %
1219   pathutils.DEFAULT_SHARED_FILE_STORAGE_DIR,
1220   metavar="SHAREDDIR", default=pathutils.DEFAULT_SHARED_FILE_STORAGE_DIR)
1221
1222 NOMODIFY_ETCHOSTS_OPT = cli_option("--no-etc-hosts", dest="modify_etc_hosts",
1223                                    help="Don't modify %s" % pathutils.ETC_HOSTS,
1224                                    action="store_false", default=True)
1225
1226 NOMODIFY_SSH_SETUP_OPT = cli_option("--no-ssh-init", dest="modify_ssh_setup",
1227                                     help="Don't initialize SSH keys",
1228                                     action="store_false", default=True)
1229
1230 ERROR_CODES_OPT = cli_option("--error-codes", dest="error_codes",
1231                              help="Enable parseable error messages",
1232                              action="store_true", default=False)
1233
1234 NONPLUS1_OPT = cli_option("--no-nplus1-mem", dest="skip_nplusone_mem",
1235                           help="Skip N+1 memory redundancy tests",
1236                           action="store_true", default=False)
1237
1238 REBOOT_TYPE_OPT = cli_option("-t", "--type", dest="reboot_type",
1239                              help="Type of reboot: soft/hard/full",
1240                              default=constants.INSTANCE_REBOOT_HARD,
1241                              metavar="<REBOOT>",
1242                              choices=list(constants.REBOOT_TYPES))
1243
1244 IGNORE_SECONDARIES_OPT = cli_option("--ignore-secondaries",
1245                                     dest="ignore_secondaries",
1246                                     default=False, action="store_true",
1247                                     help="Ignore errors from secondaries")
1248
1249 NOSHUTDOWN_OPT = cli_option("--noshutdown", dest="shutdown",
1250                             action="store_false", default=True,
1251                             help="Don't shutdown the instance (unsafe)")
1252
1253 TIMEOUT_OPT = cli_option("--timeout", dest="timeout", type="int",
1254                          default=constants.DEFAULT_SHUTDOWN_TIMEOUT,
1255                          help="Maximum time to wait")
1256
1257 SHUTDOWN_TIMEOUT_OPT = cli_option("--shutdown-timeout",
1258                                   dest="shutdown_timeout", type="int",
1259                                   default=constants.DEFAULT_SHUTDOWN_TIMEOUT,
1260                                   help="Maximum time to wait for instance"
1261                                   " shutdown")
1262
1263 INTERVAL_OPT = cli_option("--interval", dest="interval", type="int",
1264                           default=None,
1265                           help=("Number of seconds between repetions of the"
1266                                 " command"))
1267
1268 EARLY_RELEASE_OPT = cli_option("--early-release",
1269                                dest="early_release", default=False,
1270                                action="store_true",
1271                                help="Release the locks on the secondary"
1272                                " node(s) early")
1273
1274 NEW_CLUSTER_CERT_OPT = cli_option("--new-cluster-certificate",
1275                                   dest="new_cluster_cert",
1276                                   default=False, action="store_true",
1277                                   help="Generate a new cluster certificate")
1278
1279 RAPI_CERT_OPT = cli_option("--rapi-certificate", dest="rapi_cert",
1280                            default=None,
1281                            help="File containing new RAPI certificate")
1282
1283 NEW_RAPI_CERT_OPT = cli_option("--new-rapi-certificate", dest="new_rapi_cert",
1284                                default=None, action="store_true",
1285                                help=("Generate a new self-signed RAPI"
1286                                      " certificate"))
1287
1288 SPICE_CERT_OPT = cli_option("--spice-certificate", dest="spice_cert",
1289                             default=None,
1290                             help="File containing new SPICE certificate")
1291
1292 SPICE_CACERT_OPT = cli_option("--spice-ca-certificate", dest="spice_cacert",
1293                               default=None,
1294                               help="File containing the certificate of the CA"
1295                               " which signed the SPICE certificate")
1296
1297 NEW_SPICE_CERT_OPT = cli_option("--new-spice-certificate",
1298                                 dest="new_spice_cert", default=None,
1299                                 action="store_true",
1300                                 help=("Generate a new self-signed SPICE"
1301                                       " certificate"))
1302
1303 NEW_CONFD_HMAC_KEY_OPT = cli_option("--new-confd-hmac-key",
1304                                     dest="new_confd_hmac_key",
1305                                     default=False, action="store_true",
1306                                     help=("Create a new HMAC key for %s" %
1307                                           constants.CONFD))
1308
1309 CLUSTER_DOMAIN_SECRET_OPT = cli_option("--cluster-domain-secret",
1310                                        dest="cluster_domain_secret",
1311                                        default=None,
1312                                        help=("Load new new cluster domain"
1313                                              " secret from file"))
1314
1315 NEW_CLUSTER_DOMAIN_SECRET_OPT = cli_option("--new-cluster-domain-secret",
1316                                            dest="new_cluster_domain_secret",
1317                                            default=False, action="store_true",
1318                                            help=("Create a new cluster domain"
1319                                                  " secret"))
1320
1321 USE_REPL_NET_OPT = cli_option("--use-replication-network",
1322                               dest="use_replication_network",
1323                               help="Whether to use the replication network"
1324                               " for talking to the nodes",
1325                               action="store_true", default=False)
1326
1327 MAINTAIN_NODE_HEALTH_OPT = \
1328     cli_option("--maintain-node-health", dest="maintain_node_health",
1329                metavar=_YORNO, default=None, type="bool",
1330                help="Configure the cluster to automatically maintain node"
1331                " health, by shutting down unknown instances, shutting down"
1332                " unknown DRBD devices, etc.")
1333
1334 IDENTIFY_DEFAULTS_OPT = \
1335     cli_option("--identify-defaults", dest="identify_defaults",
1336                default=False, action="store_true",
1337                help="Identify which saved instance parameters are equal to"
1338                " the current cluster defaults and set them as such, instead"
1339                " of marking them as overridden")
1340
1341 UIDPOOL_OPT = cli_option("--uid-pool", default=None,
1342                          action="store", dest="uid_pool",
1343                          help=("A list of user-ids or user-id"
1344                                " ranges separated by commas"))
1345
1346 ADD_UIDS_OPT = cli_option("--add-uids", default=None,
1347                           action="store", dest="add_uids",
1348                           help=("A list of user-ids or user-id"
1349                                 " ranges separated by commas, to be"
1350                                 " added to the user-id pool"))
1351
1352 REMOVE_UIDS_OPT = cli_option("--remove-uids", default=None,
1353                              action="store", dest="remove_uids",
1354                              help=("A list of user-ids or user-id"
1355                                    " ranges separated by commas, to be"
1356                                    " removed from the user-id pool"))
1357
1358 RESERVED_LVS_OPT = cli_option("--reserved-lvs", default=None,
1359                               action="store", dest="reserved_lvs",
1360                               help=("A comma-separated list of reserved"
1361                                     " logical volumes names, that will be"
1362                                     " ignored by cluster verify"))
1363
1364 ROMAN_OPT = cli_option("--roman",
1365                        dest="roman_integers", default=False,
1366                        action="store_true",
1367                        help="Use roman numbers for positive integers")
1368
1369 DRBD_HELPER_OPT = cli_option("--drbd-usermode-helper", dest="drbd_helper",
1370                              action="store", default=None,
1371                              help="Specifies usermode helper for DRBD")
1372
1373 NODRBD_STORAGE_OPT = cli_option("--no-drbd-storage", dest="drbd_storage",
1374                                 action="store_false", default=True,
1375                                 help="Disable support for DRBD")
1376
1377 PRIMARY_IP_VERSION_OPT = \
1378     cli_option("--primary-ip-version", default=constants.IP4_VERSION,
1379                action="store", dest="primary_ip_version",
1380                metavar="%d|%d" % (constants.IP4_VERSION,
1381                                   constants.IP6_VERSION),
1382                help="Cluster-wide IP version for primary IP")
1383
1384 SHOW_MACHINE_OPT = cli_option("-M", "--show-machine-names", default=False,
1385                               action="store_true",
1386                               help="Show machine name for every line in output")
1387
1388 FAILURE_ONLY_OPT = cli_option("--failure-only", default=False,
1389                               action="store_true",
1390                               help=("Hide successful results and show failures"
1391                                     " only (determined by the exit code)"))
1392
1393
1394 def _PriorityOptionCb(option, _, value, parser):
1395   """Callback for processing C{--priority} option.
1396
1397   """
1398   value = _PRIONAME_TO_VALUE[value]
1399
1400   setattr(parser.values, option.dest, value)
1401
1402
1403 PRIORITY_OPT = cli_option("--priority", default=None, dest="priority",
1404                           metavar="|".join(name for name, _ in _PRIORITY_NAMES),
1405                           choices=_PRIONAME_TO_VALUE.keys(),
1406                           action="callback", type="choice",
1407                           callback=_PriorityOptionCb,
1408                           help="Priority for opcode processing")
1409
1410 HID_OS_OPT = cli_option("--hidden", dest="hidden",
1411                         type="bool", default=None, metavar=_YORNO,
1412                         help="Sets the hidden flag on the OS")
1413
1414 BLK_OS_OPT = cli_option("--blacklisted", dest="blacklisted",
1415                         type="bool", default=None, metavar=_YORNO,
1416                         help="Sets the blacklisted flag on the OS")
1417
1418 PREALLOC_WIPE_DISKS_OPT = cli_option("--prealloc-wipe-disks", default=None,
1419                                      type="bool", metavar=_YORNO,
1420                                      dest="prealloc_wipe_disks",
1421                                      help=("Wipe disks prior to instance"
1422                                            " creation"))
1423
1424 NODE_PARAMS_OPT = cli_option("--node-parameters", dest="ndparams",
1425                              type="keyval", default=None,
1426                              help="Node parameters")
1427
1428 ALLOC_POLICY_OPT = cli_option("--alloc-policy", dest="alloc_policy",
1429                               action="store", metavar="POLICY", default=None,
1430                               help="Allocation policy for the node group")
1431
1432 NODE_POWERED_OPT = cli_option("--node-powered", default=None,
1433                               type="bool", metavar=_YORNO,
1434                               dest="node_powered",
1435                               help="Specify if the SoR for node is powered")
1436
1437 OOB_TIMEOUT_OPT = cli_option("--oob-timeout", dest="oob_timeout", type="int",
1438                              default=constants.OOB_TIMEOUT,
1439                              help="Maximum time to wait for out-of-band helper")
1440
1441 POWER_DELAY_OPT = cli_option("--power-delay", dest="power_delay", type="float",
1442                              default=constants.OOB_POWER_DELAY,
1443                              help="Time in seconds to wait between power-ons")
1444
1445 FORCE_FILTER_OPT = cli_option("-F", "--filter", dest="force_filter",
1446                               action="store_true", default=False,
1447                               help=("Whether command argument should be treated"
1448                                     " as filter"))
1449
1450 NO_REMEMBER_OPT = cli_option("--no-remember",
1451                              dest="no_remember",
1452                              action="store_true", default=False,
1453                              help="Perform but do not record the change"
1454                              " in the configuration")
1455
1456 PRIMARY_ONLY_OPT = cli_option("-p", "--primary-only",
1457                               default=False, action="store_true",
1458                               help="Evacuate primary instances only")
1459
1460 SECONDARY_ONLY_OPT = cli_option("-s", "--secondary-only",
1461                                 default=False, action="store_true",
1462                                 help="Evacuate secondary instances only"
1463                                      " (applies only to internally mirrored"
1464                                      " disk templates, e.g. %s)" %
1465                                      utils.CommaJoin(constants.DTS_INT_MIRROR))
1466
1467 STARTUP_PAUSED_OPT = cli_option("--paused", dest="startup_paused",
1468                                 action="store_true", default=False,
1469                                 help="Pause instance at startup")
1470
1471 TO_GROUP_OPT = cli_option("--to", dest="to", metavar="<group>",
1472                           help="Destination node group (name or uuid)",
1473                           default=None, action="append",
1474                           completion_suggest=OPT_COMPL_ONE_NODEGROUP)
1475
1476 IGNORE_ERRORS_OPT = cli_option("-I", "--ignore-errors", default=[],
1477                                action="append", dest="ignore_errors",
1478                                choices=list(constants.CV_ALL_ECODES_STRINGS),
1479                                help="Error code to be ignored")
1480
1481 DISK_STATE_OPT = cli_option("--disk-state", default=[], dest="disk_state",
1482                             action="append",
1483                             help=("Specify disk state information in the"
1484                                   " format"
1485                                   " storage_type/identifier:option=value,...;"
1486                                   " note this is unused for now"),
1487                             type="identkeyval")
1488
1489 HV_STATE_OPT = cli_option("--hypervisor-state", default=[], dest="hv_state",
1490                           action="append",
1491                           help=("Specify hypervisor state information in the"
1492                                 " format hypervisor:option=value,...;"
1493                                 " note this is unused for now"),
1494                           type="identkeyval")
1495
1496 IGNORE_IPOLICY_OPT = cli_option("--ignore-ipolicy", dest="ignore_ipolicy",
1497                                 action="store_true", default=False,
1498                                 help="Ignore instance policy violations")
1499
1500 RUNTIME_MEM_OPT = cli_option("-m", "--runtime-memory", dest="runtime_mem",
1501                              help="Sets the instance's runtime memory,"
1502                              " ballooning it up or down to the new value",
1503                              default=None, type="unit", metavar="<size>")
1504
1505 ABSOLUTE_OPT = cli_option("--absolute", dest="absolute",
1506                           action="store_true", default=False,
1507                           help="Marks the grow as absolute instead of the"
1508                           " (default) relative mode")
1509
1510 NETWORK_OPT = cli_option("--network",
1511                          action="store", default=None, dest="network",
1512                          help="IP network in CIDR notation")
1513
1514 GATEWAY_OPT = cli_option("--gateway",
1515                          action="store", default=None, dest="gateway",
1516                          help="IP address of the router (gateway)")
1517
1518 ADD_RESERVED_IPS_OPT = cli_option("--add-reserved-ips",
1519                                   action="store", default=None,
1520                                   dest="add_reserved_ips",
1521                                   help="Comma-separated list of"
1522                                   " reserved IPs to add")
1523
1524 REMOVE_RESERVED_IPS_OPT = cli_option("--remove-reserved-ips",
1525                                      action="store", default=None,
1526                                      dest="remove_reserved_ips",
1527                                      help="Comma-delimited list of"
1528                                      " reserved IPs to remove")
1529
1530 NETWORK_TYPE_OPT = cli_option("--network-type",
1531                               action="store", default=None, dest="network_type",
1532                               help="Network type: private, public, None")
1533
1534 NETWORK6_OPT = cli_option("--network6",
1535                           action="store", default=None, dest="network6",
1536                           help="IP network in CIDR notation")
1537
1538 GATEWAY6_OPT = cli_option("--gateway6",
1539                           action="store", default=None, dest="gateway6",
1540                           help="IP6 address of the router (gateway)")
1541
1542 NOCONFLICTSCHECK_OPT = cli_option("--no-conflicts-check",
1543                                   dest="conflicts_check",
1544                                   default=True,
1545                                   action="store_false",
1546                                   help="Don't check for conflicting IPs")
1547
1548 #: Options provided by all commands
1549 COMMON_OPTS = [DEBUG_OPT]
1550
1551 # common options for creating instances. add and import then add their own
1552 # specific ones.
1553 COMMON_CREATE_OPTS = [
1554   BACKEND_OPT,
1555   DISK_OPT,
1556   DISK_TEMPLATE_OPT,
1557   FILESTORE_DIR_OPT,
1558   FILESTORE_DRIVER_OPT,
1559   HYPERVISOR_OPT,
1560   IALLOCATOR_OPT,
1561   NET_OPT,
1562   NODE_PLACEMENT_OPT,
1563   NOIPCHECK_OPT,
1564   NOCONFLICTSCHECK_OPT,
1565   NONAMECHECK_OPT,
1566   NONICS_OPT,
1567   NWSYNC_OPT,
1568   OSPARAMS_OPT,
1569   OS_SIZE_OPT,
1570   SUBMIT_OPT,
1571   TAG_ADD_OPT,
1572   DRY_RUN_OPT,
1573   PRIORITY_OPT,
1574   ]
1575
1576 # common instance policy options
1577 INSTANCE_POLICY_OPTS = [
1578   SPECS_CPU_COUNT_OPT,
1579   SPECS_DISK_COUNT_OPT,
1580   SPECS_DISK_SIZE_OPT,
1581   SPECS_MEM_SIZE_OPT,
1582   SPECS_NIC_COUNT_OPT,
1583   IPOLICY_DISK_TEMPLATES,
1584   IPOLICY_VCPU_RATIO,
1585   IPOLICY_SPINDLE_RATIO,
1586   ]
1587
1588
1589 class _ShowUsage(Exception):
1590   """Exception class for L{_ParseArgs}.
1591
1592   """
1593   def __init__(self, exit_error):
1594     """Initializes instances of this class.
1595
1596     @type exit_error: bool
1597     @param exit_error: Whether to report failure on exit
1598
1599     """
1600     Exception.__init__(self)
1601     self.exit_error = exit_error
1602
1603
1604 class _ShowVersion(Exception):
1605   """Exception class for L{_ParseArgs}.
1606
1607   """
1608
1609
1610 def _ParseArgs(binary, argv, commands, aliases, env_override):
1611   """Parser for the command line arguments.
1612
1613   This function parses the arguments and returns the function which
1614   must be executed together with its (modified) arguments.
1615
1616   @param binary: Script name
1617   @param argv: Command line arguments
1618   @param commands: Dictionary containing command definitions
1619   @param aliases: dictionary with command aliases {"alias": "target", ...}
1620   @param env_override: list of env variables allowed for default args
1621   @raise _ShowUsage: If usage description should be shown
1622   @raise _ShowVersion: If version should be shown
1623
1624   """
1625   assert not (env_override - set(commands))
1626   assert not (set(aliases.keys()) & set(commands.keys()))
1627
1628   if len(argv) > 1:
1629     cmd = argv[1]
1630   else:
1631     # No option or command given
1632     raise _ShowUsage(exit_error=True)
1633
1634   if cmd == "--version":
1635     raise _ShowVersion()
1636   elif cmd == "--help":
1637     raise _ShowUsage(exit_error=False)
1638   elif not (cmd in commands or cmd in aliases):
1639     raise _ShowUsage(exit_error=True)
1640
1641   # get command, unalias it, and look it up in commands
1642   if cmd in aliases:
1643     if aliases[cmd] not in commands:
1644       raise errors.ProgrammerError("Alias '%s' maps to non-existing"
1645                                    " command '%s'" % (cmd, aliases[cmd]))
1646
1647     cmd = aliases[cmd]
1648
1649   if cmd in env_override:
1650     args_env_name = ("%s_%s" % (binary.replace("-", "_"), cmd)).upper()
1651     env_args = os.environ.get(args_env_name)
1652     if env_args:
1653       argv = utils.InsertAtPos(argv, 2, shlex.split(env_args))
1654
1655   func, args_def, parser_opts, usage, description = commands[cmd]
1656   parser = OptionParser(option_list=parser_opts + COMMON_OPTS,
1657                         description=description,
1658                         formatter=TitledHelpFormatter(),
1659                         usage="%%prog %s %s" % (cmd, usage))
1660   parser.disable_interspersed_args()
1661   options, args = parser.parse_args(args=argv[2:])
1662
1663   if not _CheckArguments(cmd, args_def, args):
1664     return None, None, None
1665
1666   return func, options, args
1667
1668
1669 def _FormatUsage(binary, commands):
1670   """Generates a nice description of all commands.
1671
1672   @param binary: Script name
1673   @param commands: Dictionary containing command definitions
1674
1675   """
1676   # compute the max line length for cmd + usage
1677   mlen = min(60, max(map(len, commands)))
1678
1679   yield "Usage: %s {command} [options...] [argument...]" % binary
1680   yield "%s <command> --help to see details, or man %s" % (binary, binary)
1681   yield ""
1682   yield "Commands:"
1683
1684   # and format a nice command list
1685   for (cmd, (_, _, _, _, help_text)) in sorted(commands.items()):
1686     help_lines = textwrap.wrap(help_text, 79 - 3 - mlen)
1687     yield " %-*s - %s" % (mlen, cmd, help_lines.pop(0))
1688     for line in help_lines:
1689       yield " %-*s   %s" % (mlen, "", line)
1690
1691   yield ""
1692
1693
1694 def _CheckArguments(cmd, args_def, args):
1695   """Verifies the arguments using the argument definition.
1696
1697   Algorithm:
1698
1699     1. Abort with error if values specified by user but none expected.
1700
1701     1. For each argument in definition
1702
1703       1. Keep running count of minimum number of values (min_count)
1704       1. Keep running count of maximum number of values (max_count)
1705       1. If it has an unlimited number of values
1706
1707         1. Abort with error if it's not the last argument in the definition
1708
1709     1. If last argument has limited number of values
1710
1711       1. Abort with error if number of values doesn't match or is too large
1712
1713     1. Abort with error if user didn't pass enough values (min_count)
1714
1715   """
1716   if args and not args_def:
1717     ToStderr("Error: Command %s expects no arguments", cmd)
1718     return False
1719
1720   min_count = None
1721   max_count = None
1722   check_max = None
1723
1724   last_idx = len(args_def) - 1
1725
1726   for idx, arg in enumerate(args_def):
1727     if min_count is None:
1728       min_count = arg.min
1729     elif arg.min is not None:
1730       min_count += arg.min
1731
1732     if max_count is None:
1733       max_count = arg.max
1734     elif arg.max is not None:
1735       max_count += arg.max
1736
1737     if idx == last_idx:
1738       check_max = (arg.max is not None)
1739
1740     elif arg.max is None:
1741       raise errors.ProgrammerError("Only the last argument can have max=None")
1742
1743   if check_max:
1744     # Command with exact number of arguments
1745     if (min_count is not None and max_count is not None and
1746         min_count == max_count and len(args) != min_count):
1747       ToStderr("Error: Command %s expects %d argument(s)", cmd, min_count)
1748       return False
1749
1750     # Command with limited number of arguments
1751     if max_count is not None and len(args) > max_count:
1752       ToStderr("Error: Command %s expects only %d argument(s)",
1753                cmd, max_count)
1754       return False
1755
1756   # Command with some required arguments
1757   if min_count is not None and len(args) < min_count:
1758     ToStderr("Error: Command %s expects at least %d argument(s)",
1759              cmd, min_count)
1760     return False
1761
1762   return True
1763
1764
1765 def SplitNodeOption(value):
1766   """Splits the value of a --node option.
1767
1768   """
1769   if value and ":" in value:
1770     return value.split(":", 1)
1771   else:
1772     return (value, None)
1773
1774
1775 def CalculateOSNames(os_name, os_variants):
1776   """Calculates all the names an OS can be called, according to its variants.
1777
1778   @type os_name: string
1779   @param os_name: base name of the os
1780   @type os_variants: list or None
1781   @param os_variants: list of supported variants
1782   @rtype: list
1783   @return: list of valid names
1784
1785   """
1786   if os_variants:
1787     return ["%s+%s" % (os_name, v) for v in os_variants]
1788   else:
1789     return [os_name]
1790
1791
1792 def ParseFields(selected, default):
1793   """Parses the values of "--field"-like options.
1794
1795   @type selected: string or None
1796   @param selected: User-selected options
1797   @type default: list
1798   @param default: Default fields
1799
1800   """
1801   if selected is None:
1802     return default
1803
1804   if selected.startswith("+"):
1805     return default + selected[1:].split(",")
1806
1807   return selected.split(",")
1808
1809
1810 UsesRPC = rpc.RunWithRPC
1811
1812
1813 def AskUser(text, choices=None):
1814   """Ask the user a question.
1815
1816   @param text: the question to ask
1817
1818   @param choices: list with elements tuples (input_char, return_value,
1819       description); if not given, it will default to: [('y', True,
1820       'Perform the operation'), ('n', False, 'Do no do the operation')];
1821       note that the '?' char is reserved for help
1822
1823   @return: one of the return values from the choices list; if input is
1824       not possible (i.e. not running with a tty, we return the last
1825       entry from the list
1826
1827   """
1828   if choices is None:
1829     choices = [("y", True, "Perform the operation"),
1830                ("n", False, "Do not perform the operation")]
1831   if not choices or not isinstance(choices, list):
1832     raise errors.ProgrammerError("Invalid choices argument to AskUser")
1833   for entry in choices:
1834     if not isinstance(entry, tuple) or len(entry) < 3 or entry[0] == "?":
1835       raise errors.ProgrammerError("Invalid choices element to AskUser")
1836
1837   answer = choices[-1][1]
1838   new_text = []
1839   for line in text.splitlines():
1840     new_text.append(textwrap.fill(line, 70, replace_whitespace=False))
1841   text = "\n".join(new_text)
1842   try:
1843     f = file("/dev/tty", "a+")
1844   except IOError:
1845     return answer
1846   try:
1847     chars = [entry[0] for entry in choices]
1848     chars[-1] = "[%s]" % chars[-1]
1849     chars.append("?")
1850     maps = dict([(entry[0], entry[1]) for entry in choices])
1851     while True:
1852       f.write(text)
1853       f.write("\n")
1854       f.write("/".join(chars))
1855       f.write(": ")
1856       line = f.readline(2).strip().lower()
1857       if line in maps:
1858         answer = maps[line]
1859         break
1860       elif line == "?":
1861         for entry in choices:
1862           f.write(" %s - %s\n" % (entry[0], entry[2]))
1863         f.write("\n")
1864         continue
1865   finally:
1866     f.close()
1867   return answer
1868
1869
1870 class JobSubmittedException(Exception):
1871   """Job was submitted, client should exit.
1872
1873   This exception has one argument, the ID of the job that was
1874   submitted. The handler should print this ID.
1875
1876   This is not an error, just a structured way to exit from clients.
1877
1878   """
1879
1880
1881 def SendJob(ops, cl=None):
1882   """Function to submit an opcode without waiting for the results.
1883
1884   @type ops: list
1885   @param ops: list of opcodes
1886   @type cl: luxi.Client
1887   @param cl: the luxi client to use for communicating with the master;
1888              if None, a new client will be created
1889
1890   """
1891   if cl is None:
1892     cl = GetClient()
1893
1894   job_id = cl.SubmitJob(ops)
1895
1896   return job_id
1897
1898
1899 def GenericPollJob(job_id, cbs, report_cbs):
1900   """Generic job-polling function.
1901
1902   @type job_id: number
1903   @param job_id: Job ID
1904   @type cbs: Instance of L{JobPollCbBase}
1905   @param cbs: Data callbacks
1906   @type report_cbs: Instance of L{JobPollReportCbBase}
1907   @param report_cbs: Reporting callbacks
1908
1909   """
1910   prev_job_info = None
1911   prev_logmsg_serial = None
1912
1913   status = None
1914
1915   while True:
1916     result = cbs.WaitForJobChangeOnce(job_id, ["status"], prev_job_info,
1917                                       prev_logmsg_serial)
1918     if not result:
1919       # job not found, go away!
1920       raise errors.JobLost("Job with id %s lost" % job_id)
1921
1922     if result == constants.JOB_NOTCHANGED:
1923       report_cbs.ReportNotChanged(job_id, status)
1924
1925       # Wait again
1926       continue
1927
1928     # Split result, a tuple of (field values, log entries)
1929     (job_info, log_entries) = result
1930     (status, ) = job_info
1931
1932     if log_entries:
1933       for log_entry in log_entries:
1934         (serial, timestamp, log_type, message) = log_entry
1935         report_cbs.ReportLogMessage(job_id, serial, timestamp,
1936                                     log_type, message)
1937         prev_logmsg_serial = max(prev_logmsg_serial, serial)
1938
1939     # TODO: Handle canceled and archived jobs
1940     elif status in (constants.JOB_STATUS_SUCCESS,
1941                     constants.JOB_STATUS_ERROR,
1942                     constants.JOB_STATUS_CANCELING,
1943                     constants.JOB_STATUS_CANCELED):
1944       break
1945
1946     prev_job_info = job_info
1947
1948   jobs = cbs.QueryJobs([job_id], ["status", "opstatus", "opresult"])
1949   if not jobs:
1950     raise errors.JobLost("Job with id %s lost" % job_id)
1951
1952   status, opstatus, result = jobs[0]
1953
1954   if status == constants.JOB_STATUS_SUCCESS:
1955     return result
1956
1957   if status in (constants.JOB_STATUS_CANCELING, constants.JOB_STATUS_CANCELED):
1958     raise errors.OpExecError("Job was canceled")
1959
1960   has_ok = False
1961   for idx, (status, msg) in enumerate(zip(opstatus, result)):
1962     if status == constants.OP_STATUS_SUCCESS:
1963       has_ok = True
1964     elif status == constants.OP_STATUS_ERROR:
1965       errors.MaybeRaise(msg)
1966
1967       if has_ok:
1968         raise errors.OpExecError("partial failure (opcode %d): %s" %
1969                                  (idx, msg))
1970
1971       raise errors.OpExecError(str(msg))
1972
1973   # default failure mode
1974   raise errors.OpExecError(result)
1975
1976
1977 class JobPollCbBase:
1978   """Base class for L{GenericPollJob} callbacks.
1979
1980   """
1981   def __init__(self):
1982     """Initializes this class.
1983
1984     """
1985
1986   def WaitForJobChangeOnce(self, job_id, fields,
1987                            prev_job_info, prev_log_serial):
1988     """Waits for changes on a job.
1989
1990     """
1991     raise NotImplementedError()
1992
1993   def QueryJobs(self, job_ids, fields):
1994     """Returns the selected fields for the selected job IDs.
1995
1996     @type job_ids: list of numbers
1997     @param job_ids: Job IDs
1998     @type fields: list of strings
1999     @param fields: Fields
2000
2001     """
2002     raise NotImplementedError()
2003
2004
2005 class JobPollReportCbBase:
2006   """Base class for L{GenericPollJob} reporting callbacks.
2007
2008   """
2009   def __init__(self):
2010     """Initializes this class.
2011
2012     """
2013
2014   def ReportLogMessage(self, job_id, serial, timestamp, log_type, log_msg):
2015     """Handles a log message.
2016
2017     """
2018     raise NotImplementedError()
2019
2020   def ReportNotChanged(self, job_id, status):
2021     """Called for if a job hasn't changed in a while.
2022
2023     @type job_id: number
2024     @param job_id: Job ID
2025     @type status: string or None
2026     @param status: Job status if available
2027
2028     """
2029     raise NotImplementedError()
2030
2031
2032 class _LuxiJobPollCb(JobPollCbBase):
2033   def __init__(self, cl):
2034     """Initializes this class.
2035
2036     """
2037     JobPollCbBase.__init__(self)
2038     self.cl = cl
2039
2040   def WaitForJobChangeOnce(self, job_id, fields,
2041                            prev_job_info, prev_log_serial):
2042     """Waits for changes on a job.
2043
2044     """
2045     return self.cl.WaitForJobChangeOnce(job_id, fields,
2046                                         prev_job_info, prev_log_serial)
2047
2048   def QueryJobs(self, job_ids, fields):
2049     """Returns the selected fields for the selected job IDs.
2050
2051     """
2052     return self.cl.QueryJobs(job_ids, fields)
2053
2054
2055 class FeedbackFnJobPollReportCb(JobPollReportCbBase):
2056   def __init__(self, feedback_fn):
2057     """Initializes this class.
2058
2059     """
2060     JobPollReportCbBase.__init__(self)
2061
2062     self.feedback_fn = feedback_fn
2063
2064     assert callable(feedback_fn)
2065
2066   def ReportLogMessage(self, job_id, serial, timestamp, log_type, log_msg):
2067     """Handles a log message.
2068
2069     """
2070     self.feedback_fn((timestamp, log_type, log_msg))
2071
2072   def ReportNotChanged(self, job_id, status):
2073     """Called if a job hasn't changed in a while.
2074
2075     """
2076     # Ignore
2077
2078
2079 class StdioJobPollReportCb(JobPollReportCbBase):
2080   def __init__(self):
2081     """Initializes this class.
2082
2083     """
2084     JobPollReportCbBase.__init__(self)
2085
2086     self.notified_queued = False
2087     self.notified_waitlock = False
2088
2089   def ReportLogMessage(self, job_id, serial, timestamp, log_type, log_msg):
2090     """Handles a log message.
2091
2092     """
2093     ToStdout("%s %s", time.ctime(utils.MergeTime(timestamp)),
2094              FormatLogMessage(log_type, log_msg))
2095
2096   def ReportNotChanged(self, job_id, status):
2097     """Called if a job hasn't changed in a while.
2098
2099     """
2100     if status is None:
2101       return
2102
2103     if status == constants.JOB_STATUS_QUEUED and not self.notified_queued:
2104       ToStderr("Job %s is waiting in queue", job_id)
2105       self.notified_queued = True
2106
2107     elif status == constants.JOB_STATUS_WAITING and not self.notified_waitlock:
2108       ToStderr("Job %s is trying to acquire all necessary locks", job_id)
2109       self.notified_waitlock = True
2110
2111
2112 def FormatLogMessage(log_type, log_msg):
2113   """Formats a job message according to its type.
2114
2115   """
2116   if log_type != constants.ELOG_MESSAGE:
2117     log_msg = str(log_msg)
2118
2119   return utils.SafeEncode(log_msg)
2120
2121
2122 def PollJob(job_id, cl=None, feedback_fn=None, reporter=None):
2123   """Function to poll for the result of a job.
2124
2125   @type job_id: job identified
2126   @param job_id: the job to poll for results
2127   @type cl: luxi.Client
2128   @param cl: the luxi client to use for communicating with the master;
2129              if None, a new client will be created
2130
2131   """
2132   if cl is None:
2133     cl = GetClient()
2134
2135   if reporter is None:
2136     if feedback_fn:
2137       reporter = FeedbackFnJobPollReportCb(feedback_fn)
2138     else:
2139       reporter = StdioJobPollReportCb()
2140   elif feedback_fn:
2141     raise errors.ProgrammerError("Can't specify reporter and feedback function")
2142
2143   return GenericPollJob(job_id, _LuxiJobPollCb(cl), reporter)
2144
2145
2146 def SubmitOpCode(op, cl=None, feedback_fn=None, opts=None, reporter=None):
2147   """Legacy function to submit an opcode.
2148
2149   This is just a simple wrapper over the construction of the processor
2150   instance. It should be extended to better handle feedback and
2151   interaction functions.
2152
2153   """
2154   if cl is None:
2155     cl = GetClient()
2156
2157   SetGenericOpcodeOpts([op], opts)
2158
2159   job_id = SendJob([op], cl=cl)
2160
2161   op_results = PollJob(job_id, cl=cl, feedback_fn=feedback_fn,
2162                        reporter=reporter)
2163
2164   return op_results[0]
2165
2166
2167 def SubmitOrSend(op, opts, cl=None, feedback_fn=None):
2168   """Wrapper around SubmitOpCode or SendJob.
2169
2170   This function will decide, based on the 'opts' parameter, whether to
2171   submit and wait for the result of the opcode (and return it), or
2172   whether to just send the job and print its identifier. It is used in
2173   order to simplify the implementation of the '--submit' option.
2174
2175   It will also process the opcodes if we're sending the via SendJob
2176   (otherwise SubmitOpCode does it).
2177
2178   """
2179   if opts and opts.submit_only:
2180     job = [op]
2181     SetGenericOpcodeOpts(job, opts)
2182     job_id = SendJob(job, cl=cl)
2183     raise JobSubmittedException(job_id)
2184   else:
2185     return SubmitOpCode(op, cl=cl, feedback_fn=feedback_fn, opts=opts)
2186
2187
2188 def SetGenericOpcodeOpts(opcode_list, options):
2189   """Processor for generic options.
2190
2191   This function updates the given opcodes based on generic command
2192   line options (like debug, dry-run, etc.).
2193
2194   @param opcode_list: list of opcodes
2195   @param options: command line options or None
2196   @return: None (in-place modification)
2197
2198   """
2199   if not options:
2200     return
2201   for op in opcode_list:
2202     op.debug_level = options.debug
2203     if hasattr(options, "dry_run"):
2204       op.dry_run = options.dry_run
2205     if getattr(options, "priority", None) is not None:
2206       op.priority = options.priority
2207
2208
2209 def GetClient(query=False):
2210   """Connects to the a luxi socket and returns a client.
2211
2212   @type query: boolean
2213   @param query: this signifies that the client will only be
2214       used for queries; if the build-time parameter
2215       enable-split-queries is enabled, then the client will be
2216       connected to the query socket instead of the masterd socket
2217
2218   """
2219   if query and constants.ENABLE_SPLIT_QUERY:
2220     address = pathutils.QUERY_SOCKET
2221   else:
2222     address = None
2223   # TODO: Cache object?
2224   try:
2225     client = luxi.Client(address=address)
2226   except luxi.NoMasterError:
2227     ss = ssconf.SimpleStore()
2228
2229     # Try to read ssconf file
2230     try:
2231       ss.GetMasterNode()
2232     except errors.ConfigurationError:
2233       raise errors.OpPrereqError("Cluster not initialized or this machine is"
2234                                  " not part of a cluster",
2235                                  errors.ECODE_INVAL)
2236
2237     master, myself = ssconf.GetMasterAndMyself(ss=ss)
2238     if master != myself:
2239       raise errors.OpPrereqError("This is not the master node, please connect"
2240                                  " to node '%s' and rerun the command" %
2241                                  master, errors.ECODE_INVAL)
2242     raise
2243   return client
2244
2245
2246 def FormatError(err):
2247   """Return a formatted error message for a given error.
2248
2249   This function takes an exception instance and returns a tuple
2250   consisting of two values: first, the recommended exit code, and
2251   second, a string describing the error message (not
2252   newline-terminated).
2253
2254   """
2255   retcode = 1
2256   obuf = StringIO()
2257   msg = str(err)
2258   if isinstance(err, errors.ConfigurationError):
2259     txt = "Corrupt configuration file: %s" % msg
2260     logging.error(txt)
2261     obuf.write(txt + "\n")
2262     obuf.write("Aborting.")
2263     retcode = 2
2264   elif isinstance(err, errors.HooksAbort):
2265     obuf.write("Failure: hooks execution failed:\n")
2266     for node, script, out in err.args[0]:
2267       if out:
2268         obuf.write("  node: %s, script: %s, output: %s\n" %
2269                    (node, script, out))
2270       else:
2271         obuf.write("  node: %s, script: %s (no output)\n" %
2272                    (node, script))
2273   elif isinstance(err, errors.HooksFailure):
2274     obuf.write("Failure: hooks general failure: %s" % msg)
2275   elif isinstance(err, errors.ResolverError):
2276     this_host = netutils.Hostname.GetSysName()
2277     if err.args[0] == this_host:
2278       msg = "Failure: can't resolve my own hostname ('%s')"
2279     else:
2280       msg = "Failure: can't resolve hostname '%s'"
2281     obuf.write(msg % err.args[0])
2282   elif isinstance(err, errors.OpPrereqError):
2283     if len(err.args) == 2:
2284       obuf.write("Failure: prerequisites not met for this"
2285                  " operation:\nerror type: %s, error details:\n%s" %
2286                  (err.args[1], err.args[0]))
2287     else:
2288       obuf.write("Failure: prerequisites not met for this"
2289                  " operation:\n%s" % msg)
2290   elif isinstance(err, errors.OpExecError):
2291     obuf.write("Failure: command execution error:\n%s" % msg)
2292   elif isinstance(err, errors.TagError):
2293     obuf.write("Failure: invalid tag(s) given:\n%s" % msg)
2294   elif isinstance(err, errors.JobQueueDrainError):
2295     obuf.write("Failure: the job queue is marked for drain and doesn't"
2296                " accept new requests\n")
2297   elif isinstance(err, errors.JobQueueFull):
2298     obuf.write("Failure: the job queue is full and doesn't accept new"
2299                " job submissions until old jobs are archived\n")
2300   elif isinstance(err, errors.TypeEnforcementError):
2301     obuf.write("Parameter Error: %s" % msg)
2302   elif isinstance(err, errors.ParameterError):
2303     obuf.write("Failure: unknown/wrong parameter name '%s'" % msg)
2304   elif isinstance(err, luxi.NoMasterError):
2305     obuf.write("Cannot communicate with the master daemon.\nIs it running"
2306                " and listening for connections?")
2307   elif isinstance(err, luxi.TimeoutError):
2308     obuf.write("Timeout while talking to the master daemon. Jobs might have"
2309                " been submitted and will continue to run even if the call"
2310                " timed out. Useful commands in this situation are \"gnt-job"
2311                " list\", \"gnt-job cancel\" and \"gnt-job watch\". Error:\n")
2312     obuf.write(msg)
2313   elif isinstance(err, luxi.PermissionError):
2314     obuf.write("It seems you don't have permissions to connect to the"
2315                " master daemon.\nPlease retry as a different user.")
2316   elif isinstance(err, luxi.ProtocolError):
2317     obuf.write("Unhandled protocol error while talking to the master daemon:\n"
2318                "%s" % msg)
2319   elif isinstance(err, errors.JobLost):
2320     obuf.write("Error checking job status: %s" % msg)
2321   elif isinstance(err, errors.QueryFilterParseError):
2322     obuf.write("Error while parsing query filter: %s\n" % err.args[0])
2323     obuf.write("\n".join(err.GetDetails()))
2324   elif isinstance(err, errors.GenericError):
2325     obuf.write("Unhandled Ganeti error: %s" % msg)
2326   elif isinstance(err, JobSubmittedException):
2327     obuf.write("JobID: %s\n" % err.args[0])
2328     retcode = 0
2329   else:
2330     obuf.write("Unhandled exception: %s" % msg)
2331   return retcode, obuf.getvalue().rstrip("\n")
2332
2333
2334 def GenericMain(commands, override=None, aliases=None,
2335                 env_override=frozenset()):
2336   """Generic main function for all the gnt-* commands.
2337
2338   @param commands: a dictionary with a special structure, see the design doc
2339                    for command line handling.
2340   @param override: if not None, we expect a dictionary with keys that will
2341                    override command line options; this can be used to pass
2342                    options from the scripts to generic functions
2343   @param aliases: dictionary with command aliases {'alias': 'target, ...}
2344   @param env_override: list of environment names which are allowed to submit
2345                        default args for commands
2346
2347   """
2348   # save the program name and the entire command line for later logging
2349   if sys.argv:
2350     binary = os.path.basename(sys.argv[0])
2351     if not binary:
2352       binary = sys.argv[0]
2353
2354     if len(sys.argv) >= 2:
2355       logname = utils.ShellQuoteArgs([binary, sys.argv[1]])
2356     else:
2357       logname = binary
2358
2359     cmdline = utils.ShellQuoteArgs([binary] + sys.argv[1:])
2360   else:
2361     binary = "<unknown program>"
2362     cmdline = "<unknown>"
2363
2364   if aliases is None:
2365     aliases = {}
2366
2367   try:
2368     (func, options, args) = _ParseArgs(binary, sys.argv, commands, aliases,
2369                                        env_override)
2370   except _ShowVersion:
2371     ToStdout("%s (ganeti %s) %s", binary, constants.VCS_VERSION,
2372              constants.RELEASE_VERSION)
2373     return constants.EXIT_SUCCESS
2374   except _ShowUsage, err:
2375     for line in _FormatUsage(binary, commands):
2376       ToStdout(line)
2377
2378     if err.exit_error:
2379       return constants.EXIT_FAILURE
2380     else:
2381       return constants.EXIT_SUCCESS
2382   except errors.ParameterError, err:
2383     result, err_msg = FormatError(err)
2384     ToStderr(err_msg)
2385     return 1
2386
2387   if func is None: # parse error
2388     return 1
2389
2390   if override is not None:
2391     for key, val in override.iteritems():
2392       setattr(options, key, val)
2393
2394   utils.SetupLogging(pathutils.LOG_COMMANDS, logname, debug=options.debug,
2395                      stderr_logging=True)
2396
2397   logging.info("Command line: %s", cmdline)
2398
2399   try:
2400     result = func(options, args)
2401   except (errors.GenericError, luxi.ProtocolError,
2402           JobSubmittedException), err:
2403     result, err_msg = FormatError(err)
2404     logging.exception("Error during command processing")
2405     ToStderr(err_msg)
2406   except KeyboardInterrupt:
2407     result = constants.EXIT_FAILURE
2408     ToStderr("Aborted. Note that if the operation created any jobs, they"
2409              " might have been submitted and"
2410              " will continue to run in the background.")
2411   except IOError, err:
2412     if err.errno == errno.EPIPE:
2413       # our terminal went away, we'll exit
2414       sys.exit(constants.EXIT_FAILURE)
2415     else:
2416       raise
2417
2418   return result
2419
2420
2421 def ParseNicOption(optvalue):
2422   """Parses the value of the --net option(s).
2423
2424   """
2425   try:
2426     nic_max = max(int(nidx[0]) + 1 for nidx in optvalue)
2427   except (TypeError, ValueError), err:
2428     raise errors.OpPrereqError("Invalid NIC index passed: %s" % str(err),
2429                                errors.ECODE_INVAL)
2430
2431   nics = [{}] * nic_max
2432   for nidx, ndict in optvalue:
2433     nidx = int(nidx)
2434
2435     if not isinstance(ndict, dict):
2436       raise errors.OpPrereqError("Invalid nic/%d value: expected dict,"
2437                                  " got %s" % (nidx, ndict), errors.ECODE_INVAL)
2438
2439     utils.ForceDictType(ndict, constants.INIC_PARAMS_TYPES)
2440
2441     nics[nidx] = ndict
2442
2443   return nics
2444
2445
2446 def GenericInstanceCreate(mode, opts, args):
2447   """Add an instance to the cluster via either creation or import.
2448
2449   @param mode: constants.INSTANCE_CREATE or constants.INSTANCE_IMPORT
2450   @param opts: the command line options selected by the user
2451   @type args: list
2452   @param args: should contain only one element, the new instance name
2453   @rtype: int
2454   @return: the desired exit code
2455
2456   """
2457   instance = args[0]
2458
2459   (pnode, snode) = SplitNodeOption(opts.node)
2460
2461   hypervisor = None
2462   hvparams = {}
2463   if opts.hypervisor:
2464     hypervisor, hvparams = opts.hypervisor
2465
2466   if opts.nics:
2467     nics = ParseNicOption(opts.nics)
2468   elif opts.no_nics:
2469     # no nics
2470     nics = []
2471   elif mode == constants.INSTANCE_CREATE:
2472     # default of one nic, all auto
2473     nics = [{}]
2474   else:
2475     # mode == import
2476     nics = []
2477
2478   if opts.disk_template == constants.DT_DISKLESS:
2479     if opts.disks or opts.sd_size is not None:
2480       raise errors.OpPrereqError("Diskless instance but disk"
2481                                  " information passed", errors.ECODE_INVAL)
2482     disks = []
2483   else:
2484     if (not opts.disks and not opts.sd_size
2485         and mode == constants.INSTANCE_CREATE):
2486       raise errors.OpPrereqError("No disk information specified",
2487                                  errors.ECODE_INVAL)
2488     if opts.disks and opts.sd_size is not None:
2489       raise errors.OpPrereqError("Please use either the '--disk' or"
2490                                  " '-s' option", errors.ECODE_INVAL)
2491     if opts.sd_size is not None:
2492       opts.disks = [(0, {constants.IDISK_SIZE: opts.sd_size})]
2493
2494     if opts.disks:
2495       try:
2496         disk_max = max(int(didx[0]) + 1 for didx in opts.disks)
2497       except ValueError, err:
2498         raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err),
2499                                    errors.ECODE_INVAL)
2500       disks = [{}] * disk_max
2501     else:
2502       disks = []
2503     for didx, ddict in opts.disks:
2504       didx = int(didx)
2505       if not isinstance(ddict, dict):
2506         msg = "Invalid disk/%d value: expected dict, got %s" % (didx, ddict)
2507         raise errors.OpPrereqError(msg, errors.ECODE_INVAL)
2508       elif constants.IDISK_SIZE in ddict:
2509         if constants.IDISK_ADOPT in ddict:
2510           raise errors.OpPrereqError("Only one of 'size' and 'adopt' allowed"
2511                                      " (disk %d)" % didx, errors.ECODE_INVAL)
2512         try:
2513           ddict[constants.IDISK_SIZE] = \
2514             utils.ParseUnit(ddict[constants.IDISK_SIZE])
2515         except ValueError, err:
2516           raise errors.OpPrereqError("Invalid disk size for disk %d: %s" %
2517                                      (didx, err), errors.ECODE_INVAL)
2518       elif constants.IDISK_ADOPT in ddict:
2519         if mode == constants.INSTANCE_IMPORT:
2520           raise errors.OpPrereqError("Disk adoption not allowed for instance"
2521                                      " import", errors.ECODE_INVAL)
2522         ddict[constants.IDISK_SIZE] = 0
2523       else:
2524         raise errors.OpPrereqError("Missing size or adoption source for"
2525                                    " disk %d" % didx, errors.ECODE_INVAL)
2526       disks[didx] = ddict
2527
2528   if opts.tags is not None:
2529     tags = opts.tags.split(",")
2530   else:
2531     tags = []
2532
2533   utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_COMPAT)
2534   utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
2535
2536   if mode == constants.INSTANCE_CREATE:
2537     start = opts.start
2538     os_type = opts.os
2539     force_variant = opts.force_variant
2540     src_node = None
2541     src_path = None
2542     no_install = opts.no_install
2543     identify_defaults = False
2544   elif mode == constants.INSTANCE_IMPORT:
2545     start = False
2546     os_type = None
2547     force_variant = False
2548     src_node = opts.src_node
2549     src_path = opts.src_dir
2550     no_install = None
2551     identify_defaults = opts.identify_defaults
2552   else:
2553     raise errors.ProgrammerError("Invalid creation mode %s" % mode)
2554
2555   op = opcodes.OpInstanceCreate(instance_name=instance,
2556                                 disks=disks,
2557                                 disk_template=opts.disk_template,
2558                                 nics=nics,
2559                                 conflicts_check=opts.conflicts_check,
2560                                 pnode=pnode, snode=snode,
2561                                 ip_check=opts.ip_check,
2562                                 name_check=opts.name_check,
2563                                 wait_for_sync=opts.wait_for_sync,
2564                                 file_storage_dir=opts.file_storage_dir,
2565                                 file_driver=opts.file_driver,
2566                                 iallocator=opts.iallocator,
2567                                 hypervisor=hypervisor,
2568                                 hvparams=hvparams,
2569                                 beparams=opts.beparams,
2570                                 osparams=opts.osparams,
2571                                 mode=mode,
2572                                 start=start,
2573                                 os_type=os_type,
2574                                 force_variant=force_variant,
2575                                 src_node=src_node,
2576                                 src_path=src_path,
2577                                 tags=tags,
2578                                 no_install=no_install,
2579                                 identify_defaults=identify_defaults,
2580                                 ignore_ipolicy=opts.ignore_ipolicy)
2581
2582   SubmitOrSend(op, opts)
2583   return 0
2584
2585
2586 class _RunWhileClusterStoppedHelper:
2587   """Helper class for L{RunWhileClusterStopped} to simplify state management
2588
2589   """
2590   def __init__(self, feedback_fn, cluster_name, master_node, online_nodes):
2591     """Initializes this class.
2592
2593     @type feedback_fn: callable
2594     @param feedback_fn: Feedback function
2595     @type cluster_name: string
2596     @param cluster_name: Cluster name
2597     @type master_node: string
2598     @param master_node Master node name
2599     @type online_nodes: list
2600     @param online_nodes: List of names of online nodes
2601
2602     """
2603     self.feedback_fn = feedback_fn
2604     self.cluster_name = cluster_name
2605     self.master_node = master_node
2606     self.online_nodes = online_nodes
2607
2608     self.ssh = ssh.SshRunner(self.cluster_name)
2609
2610     self.nonmaster_nodes = [name for name in online_nodes
2611                             if name != master_node]
2612
2613     assert self.master_node not in self.nonmaster_nodes
2614
2615   def _RunCmd(self, node_name, cmd):
2616     """Runs a command on the local or a remote machine.
2617
2618     @type node_name: string
2619     @param node_name: Machine name
2620     @type cmd: list
2621     @param cmd: Command
2622
2623     """
2624     if node_name is None or node_name == self.master_node:
2625       # No need to use SSH
2626       result = utils.RunCmd(cmd)
2627     else:
2628       result = self.ssh.Run(node_name, constants.SSH_LOGIN_USER,
2629                             utils.ShellQuoteArgs(cmd))
2630
2631     if result.failed:
2632       errmsg = ["Failed to run command %s" % result.cmd]
2633       if node_name:
2634         errmsg.append("on node %s" % node_name)
2635       errmsg.append(": exitcode %s and error %s" %
2636                     (result.exit_code, result.output))
2637       raise errors.OpExecError(" ".join(errmsg))
2638
2639   def Call(self, fn, *args):
2640     """Call function while all daemons are stopped.
2641
2642     @type fn: callable
2643     @param fn: Function to be called
2644
2645     """
2646     # Pause watcher by acquiring an exclusive lock on watcher state file
2647     self.feedback_fn("Blocking watcher")
2648     watcher_block = utils.FileLock.Open(pathutils.WATCHER_LOCK_FILE)
2649     try:
2650       # TODO: Currently, this just blocks. There's no timeout.
2651       # TODO: Should it be a shared lock?
2652       watcher_block.Exclusive(blocking=True)
2653
2654       # Stop master daemons, so that no new jobs can come in and all running
2655       # ones are finished
2656       self.feedback_fn("Stopping master daemons")
2657       self._RunCmd(None, [pathutils.DAEMON_UTIL, "stop-master"])
2658       try:
2659         # Stop daemons on all nodes
2660         for node_name in self.online_nodes:
2661           self.feedback_fn("Stopping daemons on %s" % node_name)
2662           self._RunCmd(node_name, [pathutils.DAEMON_UTIL, "stop-all"])
2663
2664         # All daemons are shut down now
2665         try:
2666           return fn(self, *args)
2667         except Exception, err:
2668           _, errmsg = FormatError(err)
2669           logging.exception("Caught exception")
2670           self.feedback_fn(errmsg)
2671           raise
2672       finally:
2673         # Start cluster again, master node last
2674         for node_name in self.nonmaster_nodes + [self.master_node]:
2675           self.feedback_fn("Starting daemons on %s" % node_name)
2676           self._RunCmd(node_name, [pathutils.DAEMON_UTIL, "start-all"])
2677     finally:
2678       # Resume watcher
2679       watcher_block.Close()
2680
2681
2682 def RunWhileClusterStopped(feedback_fn, fn, *args):
2683   """Calls a function while all cluster daemons are stopped.
2684
2685   @type feedback_fn: callable
2686   @param feedback_fn: Feedback function
2687   @type fn: callable
2688   @param fn: Function to be called when daemons are stopped
2689
2690   """
2691   feedback_fn("Gathering cluster information")
2692
2693   # This ensures we're running on the master daemon
2694   cl = GetClient()
2695
2696   (cluster_name, master_node) = \
2697     cl.QueryConfigValues(["cluster_name", "master_node"])
2698
2699   online_nodes = GetOnlineNodes([], cl=cl)
2700
2701   # Don't keep a reference to the client. The master daemon will go away.
2702   del cl
2703
2704   assert master_node in online_nodes
2705
2706   return _RunWhileClusterStoppedHelper(feedback_fn, cluster_name, master_node,
2707                                        online_nodes).Call(fn, *args)
2708
2709
2710 def GenerateTable(headers, fields, separator, data,
2711                   numfields=None, unitfields=None,
2712                   units=None):
2713   """Prints a table with headers and different fields.
2714
2715   @type headers: dict
2716   @param headers: dictionary mapping field names to headers for
2717       the table
2718   @type fields: list
2719   @param fields: the field names corresponding to each row in
2720       the data field
2721   @param separator: the separator to be used; if this is None,
2722       the default 'smart' algorithm is used which computes optimal
2723       field width, otherwise just the separator is used between
2724       each field
2725   @type data: list
2726   @param data: a list of lists, each sublist being one row to be output
2727   @type numfields: list
2728   @param numfields: a list with the fields that hold numeric
2729       values and thus should be right-aligned
2730   @type unitfields: list
2731   @param unitfields: a list with the fields that hold numeric
2732       values that should be formatted with the units field
2733   @type units: string or None
2734   @param units: the units we should use for formatting, or None for
2735       automatic choice (human-readable for non-separator usage, otherwise
2736       megabytes); this is a one-letter string
2737
2738   """
2739   if units is None:
2740     if separator:
2741       units = "m"
2742     else:
2743       units = "h"
2744
2745   if numfields is None:
2746     numfields = []
2747   if unitfields is None:
2748     unitfields = []
2749
2750   numfields = utils.FieldSet(*numfields)   # pylint: disable=W0142
2751   unitfields = utils.FieldSet(*unitfields) # pylint: disable=W0142
2752
2753   format_fields = []
2754   for field in fields:
2755     if headers and field not in headers:
2756       # TODO: handle better unknown fields (either revert to old
2757       # style of raising exception, or deal more intelligently with
2758       # variable fields)
2759       headers[field] = field
2760     if separator is not None:
2761       format_fields.append("%s")
2762     elif numfields.Matches(field):
2763       format_fields.append("%*s")
2764     else:
2765       format_fields.append("%-*s")
2766
2767   if separator is None:
2768     mlens = [0 for name in fields]
2769     format_str = " ".join(format_fields)
2770   else:
2771     format_str = separator.replace("%", "%%").join(format_fields)
2772
2773   for row in data:
2774     if row is None:
2775       continue
2776     for idx, val in enumerate(row):
2777       if unitfields.Matches(fields[idx]):
2778         try:
2779           val = int(val)
2780         except (TypeError, ValueError):
2781           pass
2782         else:
2783           val = row[idx] = utils.FormatUnit(val, units)
2784       val = row[idx] = str(val)
2785       if separator is None:
2786         mlens[idx] = max(mlens[idx], len(val))
2787
2788   result = []
2789   if headers:
2790     args = []
2791     for idx, name in enumerate(fields):
2792       hdr = headers[name]
2793       if separator is None:
2794         mlens[idx] = max(mlens[idx], len(hdr))
2795         args.append(mlens[idx])
2796       args.append(hdr)
2797     result.append(format_str % tuple(args))
2798
2799   if separator is None:
2800     assert len(mlens) == len(fields)
2801
2802     if fields and not numfields.Matches(fields[-1]):
2803       mlens[-1] = 0
2804
2805   for line in data:
2806     args = []
2807     if line is None:
2808       line = ["-" for _ in fields]
2809     for idx in range(len(fields)):
2810       if separator is None:
2811         args.append(mlens[idx])
2812       args.append(line[idx])
2813     result.append(format_str % tuple(args))
2814
2815   return result
2816
2817
2818 def _FormatBool(value):
2819   """Formats a boolean value as a string.
2820
2821   """
2822   if value:
2823     return "Y"
2824   return "N"
2825
2826
2827 #: Default formatting for query results; (callback, align right)
2828 _DEFAULT_FORMAT_QUERY = {
2829   constants.QFT_TEXT: (str, False),
2830   constants.QFT_BOOL: (_FormatBool, False),
2831   constants.QFT_NUMBER: (str, True),
2832   constants.QFT_TIMESTAMP: (utils.FormatTime, False),
2833   constants.QFT_OTHER: (str, False),
2834   constants.QFT_UNKNOWN: (str, False),
2835   }
2836
2837
2838 def _GetColumnFormatter(fdef, override, unit):
2839   """Returns formatting function for a field.
2840
2841   @type fdef: L{objects.QueryFieldDefinition}
2842   @type override: dict
2843   @param override: Dictionary for overriding field formatting functions,
2844     indexed by field name, contents like L{_DEFAULT_FORMAT_QUERY}
2845   @type unit: string
2846   @param unit: Unit used for formatting fields of type L{constants.QFT_UNIT}
2847   @rtype: tuple; (callable, bool)
2848   @return: Returns the function to format a value (takes one parameter) and a
2849     boolean for aligning the value on the right-hand side
2850
2851   """
2852   fmt = override.get(fdef.name, None)
2853   if fmt is not None:
2854     return fmt
2855
2856   assert constants.QFT_UNIT not in _DEFAULT_FORMAT_QUERY
2857
2858   if fdef.kind == constants.QFT_UNIT:
2859     # Can't keep this information in the static dictionary
2860     return (lambda value: utils.FormatUnit(value, unit), True)
2861
2862   fmt = _DEFAULT_FORMAT_QUERY.get(fdef.kind, None)
2863   if fmt is not None:
2864     return fmt
2865
2866   raise NotImplementedError("Can't format column type '%s'" % fdef.kind)
2867
2868
2869 class _QueryColumnFormatter:
2870   """Callable class for formatting fields of a query.
2871
2872   """
2873   def __init__(self, fn, status_fn, verbose):
2874     """Initializes this class.
2875
2876     @type fn: callable
2877     @param fn: Formatting function
2878     @type status_fn: callable
2879     @param status_fn: Function to report fields' status
2880     @type verbose: boolean
2881     @param verbose: whether to use verbose field descriptions or not
2882
2883     """
2884     self._fn = fn
2885     self._status_fn = status_fn
2886     self._verbose = verbose
2887
2888   def __call__(self, data):
2889     """Returns a field's string representation.
2890
2891     """
2892     (status, value) = data
2893
2894     # Report status
2895     self._status_fn(status)
2896
2897     if status == constants.RS_NORMAL:
2898       return self._fn(value)
2899
2900     assert value is None, \
2901            "Found value %r for abnormal status %s" % (value, status)
2902
2903     return FormatResultError(status, self._verbose)
2904
2905
2906 def FormatResultError(status, verbose):
2907   """Formats result status other than L{constants.RS_NORMAL}.
2908
2909   @param status: The result status
2910   @type verbose: boolean
2911   @param verbose: Whether to return the verbose text
2912   @return: Text of result status
2913
2914   """
2915   assert status != constants.RS_NORMAL, \
2916          "FormatResultError called with status equal to constants.RS_NORMAL"
2917   try:
2918     (verbose_text, normal_text) = constants.RSS_DESCRIPTION[status]
2919   except KeyError:
2920     raise NotImplementedError("Unknown status %s" % status)
2921   else:
2922     if verbose:
2923       return verbose_text
2924     return normal_text
2925
2926
2927 def FormatQueryResult(result, unit=None, format_override=None, separator=None,
2928                       header=False, verbose=False):
2929   """Formats data in L{objects.QueryResponse}.
2930
2931   @type result: L{objects.QueryResponse}
2932   @param result: result of query operation
2933   @type unit: string
2934   @param unit: Unit used for formatting fields of type L{constants.QFT_UNIT},
2935     see L{utils.text.FormatUnit}
2936   @type format_override: dict
2937   @param format_override: Dictionary for overriding field formatting functions,
2938     indexed by field name, contents like L{_DEFAULT_FORMAT_QUERY}
2939   @type separator: string or None
2940   @param separator: String used to separate fields
2941   @type header: bool
2942   @param header: Whether to output header row
2943   @type verbose: boolean
2944   @param verbose: whether to use verbose field descriptions or not
2945
2946   """
2947   if unit is None:
2948     if separator:
2949       unit = "m"
2950     else:
2951       unit = "h"
2952
2953   if format_override is None:
2954     format_override = {}
2955
2956   stats = dict.fromkeys(constants.RS_ALL, 0)
2957
2958   def _RecordStatus(status):
2959     if status in stats:
2960       stats[status] += 1
2961
2962   columns = []
2963   for fdef in result.fields:
2964     assert fdef.title and fdef.name
2965     (fn, align_right) = _GetColumnFormatter(fdef, format_override, unit)
2966     columns.append(TableColumn(fdef.title,
2967                                _QueryColumnFormatter(fn, _RecordStatus,
2968                                                      verbose),
2969                                align_right))
2970
2971   table = FormatTable(result.data, columns, header, separator)
2972
2973   # Collect statistics
2974   assert len(stats) == len(constants.RS_ALL)
2975   assert compat.all(count >= 0 for count in stats.values())
2976
2977   # Determine overall status. If there was no data, unknown fields must be
2978   # detected via the field definitions.
2979   if (stats[constants.RS_UNKNOWN] or
2980       (not result.data and _GetUnknownFields(result.fields))):
2981     status = QR_UNKNOWN
2982   elif compat.any(count > 0 for key, count in stats.items()
2983                   if key != constants.RS_NORMAL):
2984     status = QR_INCOMPLETE
2985   else:
2986     status = QR_NORMAL
2987
2988   return (status, table)
2989
2990
2991 def _GetUnknownFields(fdefs):
2992   """Returns list of unknown fields included in C{fdefs}.
2993
2994   @type fdefs: list of L{objects.QueryFieldDefinition}
2995
2996   """
2997   return [fdef for fdef in fdefs
2998           if fdef.kind == constants.QFT_UNKNOWN]
2999
3000
3001 def _WarnUnknownFields(fdefs):
3002   """Prints a warning to stderr if a query included unknown fields.
3003
3004   @type fdefs: list of L{objects.QueryFieldDefinition}
3005
3006   """
3007   unknown = _GetUnknownFields(fdefs)
3008   if unknown:
3009     ToStderr("Warning: Queried for unknown fields %s",
3010              utils.CommaJoin(fdef.name for fdef in unknown))
3011     return True
3012
3013   return False
3014
3015
3016 def GenericList(resource, fields, names, unit, separator, header, cl=None,
3017                 format_override=None, verbose=False, force_filter=False,
3018                 namefield=None, qfilter=None, isnumeric=False):
3019   """Generic implementation for listing all items of a resource.
3020
3021   @param resource: One of L{constants.QR_VIA_LUXI}
3022   @type fields: list of strings
3023   @param fields: List of fields to query for
3024   @type names: list of strings
3025   @param names: Names of items to query for
3026   @type unit: string or None
3027   @param unit: Unit used for formatting fields of type L{constants.QFT_UNIT} or
3028     None for automatic choice (human-readable for non-separator usage,
3029     otherwise megabytes); this is a one-letter string
3030   @type separator: string or None
3031   @param separator: String used to separate fields
3032   @type header: bool
3033   @param header: Whether to show header row
3034   @type force_filter: bool
3035   @param force_filter: Whether to always treat names as filter
3036   @type format_override: dict
3037   @param format_override: Dictionary for overriding field formatting functions,
3038     indexed by field name, contents like L{_DEFAULT_FORMAT_QUERY}
3039   @type verbose: boolean
3040   @param verbose: whether to use verbose field descriptions or not
3041   @type namefield: string
3042   @param namefield: Name of field to use for simple filters (see
3043     L{qlang.MakeFilter} for details)
3044   @type qfilter: list or None
3045   @param qfilter: Query filter (in addition to names)
3046   @param isnumeric: bool
3047   @param isnumeric: Whether the namefield's type is numeric, and therefore
3048     any simple filters built by namefield should use integer values to
3049     reflect that
3050
3051   """
3052   if not names:
3053     names = None
3054
3055   namefilter = qlang.MakeFilter(names, force_filter, namefield=namefield,
3056                                 isnumeric=isnumeric)
3057
3058   if qfilter is None:
3059     qfilter = namefilter
3060   elif namefilter is not None:
3061     qfilter = [qlang.OP_AND, namefilter, qfilter]
3062
3063   if cl is None:
3064     cl = GetClient()
3065
3066   response = cl.Query(resource, fields, qfilter)
3067
3068   found_unknown = _WarnUnknownFields(response.fields)
3069
3070   (status, data) = FormatQueryResult(response, unit=unit, separator=separator,
3071                                      header=header,
3072                                      format_override=format_override,
3073                                      verbose=verbose)
3074
3075   for line in data:
3076     ToStdout(line)
3077
3078   assert ((found_unknown and status == QR_UNKNOWN) or
3079           (not found_unknown and status != QR_UNKNOWN))
3080
3081   if status == QR_UNKNOWN:
3082     return constants.EXIT_UNKNOWN_FIELD
3083
3084   # TODO: Should the list command fail if not all data could be collected?
3085   return constants.EXIT_SUCCESS
3086
3087
3088 def _FieldDescValues(fdef):
3089   """Helper function for L{GenericListFields} to get query field description.
3090
3091   @type fdef: L{objects.QueryFieldDefinition}
3092   @rtype: list
3093
3094   """
3095   return [
3096     fdef.name,
3097     _QFT_NAMES.get(fdef.kind, fdef.kind),
3098     fdef.title,
3099     fdef.doc,
3100     ]
3101
3102
3103 def GenericListFields(resource, fields, separator, header, cl=None):
3104   """Generic implementation for listing fields for a resource.
3105
3106   @param resource: One of L{constants.QR_VIA_LUXI}
3107   @type fields: list of strings
3108   @param fields: List of fields to query for
3109   @type separator: string or None
3110   @param separator: String used to separate fields
3111   @type header: bool
3112   @param header: Whether to show header row
3113
3114   """
3115   if cl is None:
3116     cl = GetClient()
3117
3118   if not fields:
3119     fields = None
3120
3121   response = cl.QueryFields(resource, fields)
3122
3123   found_unknown = _WarnUnknownFields(response.fields)
3124
3125   columns = [
3126     TableColumn("Name", str, False),
3127     TableColumn("Type", str, False),
3128     TableColumn("Title", str, False),
3129     TableColumn("Description", str, False),
3130     ]
3131
3132   rows = map(_FieldDescValues, response.fields)
3133
3134   for line in FormatTable(rows, columns, header, separator):
3135     ToStdout(line)
3136
3137   if found_unknown:
3138     return constants.EXIT_UNKNOWN_FIELD
3139
3140   return constants.EXIT_SUCCESS
3141
3142
3143 class TableColumn:
3144   """Describes a column for L{FormatTable}.
3145
3146   """
3147   def __init__(self, title, fn, align_right):
3148     """Initializes this class.
3149
3150     @type title: string
3151     @param title: Column title
3152     @type fn: callable
3153     @param fn: Formatting function
3154     @type align_right: bool
3155     @param align_right: Whether to align values on the right-hand side
3156
3157     """
3158     self.title = title
3159     self.format = fn
3160     self.align_right = align_right
3161
3162
3163 def _GetColFormatString(width, align_right):
3164   """Returns the format string for a field.
3165
3166   """
3167   if align_right:
3168     sign = ""
3169   else:
3170     sign = "-"
3171
3172   return "%%%s%ss" % (sign, width)
3173
3174
3175 def FormatTable(rows, columns, header, separator):
3176   """Formats data as a table.
3177
3178   @type rows: list of lists
3179   @param rows: Row data, one list per row
3180   @type columns: list of L{TableColumn}
3181   @param columns: Column descriptions
3182   @type header: bool
3183   @param header: Whether to show header row
3184   @type separator: string or None
3185   @param separator: String used to separate columns
3186
3187   """
3188   if header:
3189     data = [[col.title for col in columns]]
3190     colwidth = [len(col.title) for col in columns]
3191   else:
3192     data = []
3193     colwidth = [0 for _ in columns]
3194
3195   # Format row data
3196   for row in rows:
3197     assert len(row) == len(columns)
3198
3199     formatted = [col.format(value) for value, col in zip(row, columns)]
3200
3201     if separator is None:
3202       # Update column widths
3203       for idx, (oldwidth, value) in enumerate(zip(colwidth, formatted)):
3204         # Modifying a list's items while iterating is fine
3205         colwidth[idx] = max(oldwidth, len(value))
3206
3207     data.append(formatted)
3208
3209   if separator is not None:
3210     # Return early if a separator is used
3211     return [separator.join(row) for row in data]
3212
3213   if columns and not columns[-1].align_right:
3214     # Avoid unnecessary spaces at end of line
3215     colwidth[-1] = 0
3216
3217   # Build format string
3218   fmt = " ".join([_GetColFormatString(width, col.align_right)
3219                   for col, width in zip(columns, colwidth)])
3220
3221   return [fmt % tuple(row) for row in data]
3222
3223
3224 def FormatTimestamp(ts):
3225   """Formats a given timestamp.
3226
3227   @type ts: timestamp
3228   @param ts: a timeval-type timestamp, a tuple of seconds and microseconds
3229
3230   @rtype: string
3231   @return: a string with the formatted timestamp
3232
3233   """
3234   if not isinstance(ts, (tuple, list)) or len(ts) != 2:
3235     return "?"
3236
3237   (sec, usecs) = ts
3238   return utils.FormatTime(sec, usecs=usecs)
3239
3240
3241 def ParseTimespec(value):
3242   """Parse a time specification.
3243
3244   The following suffixed will be recognized:
3245
3246     - s: seconds
3247     - m: minutes
3248     - h: hours
3249     - d: day
3250     - w: weeks
3251
3252   Without any suffix, the value will be taken to be in seconds.
3253
3254   """
3255   value = str(value)
3256   if not value:
3257     raise errors.OpPrereqError("Empty time specification passed",
3258                                errors.ECODE_INVAL)
3259   suffix_map = {
3260     "s": 1,
3261     "m": 60,
3262     "h": 3600,
3263     "d": 86400,
3264     "w": 604800,
3265     }
3266   if value[-1] not in suffix_map:
3267     try:
3268       value = int(value)
3269     except (TypeError, ValueError):
3270       raise errors.OpPrereqError("Invalid time specification '%s'" % value,
3271                                  errors.ECODE_INVAL)
3272   else:
3273     multiplier = suffix_map[value[-1]]
3274     value = value[:-1]
3275     if not value: # no data left after stripping the suffix
3276       raise errors.OpPrereqError("Invalid time specification (only"
3277                                  " suffix passed)", errors.ECODE_INVAL)
3278     try:
3279       value = int(value) * multiplier
3280     except (TypeError, ValueError):
3281       raise errors.OpPrereqError("Invalid time specification '%s'" % value,
3282                                  errors.ECODE_INVAL)
3283   return value
3284
3285
3286 def GetOnlineNodes(nodes, cl=None, nowarn=False, secondary_ips=False,
3287                    filter_master=False, nodegroup=None):
3288   """Returns the names of online nodes.
3289
3290   This function will also log a warning on stderr with the names of
3291   the online nodes.
3292
3293   @param nodes: if not empty, use only this subset of nodes (minus the
3294       offline ones)
3295   @param cl: if not None, luxi client to use
3296   @type nowarn: boolean
3297   @param nowarn: by default, this function will output a note with the
3298       offline nodes that are skipped; if this parameter is True the
3299       note is not displayed
3300   @type secondary_ips: boolean
3301   @param secondary_ips: if True, return the secondary IPs instead of the
3302       names, useful for doing network traffic over the replication interface
3303       (if any)
3304   @type filter_master: boolean
3305   @param filter_master: if True, do not return the master node in the list
3306       (useful in coordination with secondary_ips where we cannot check our
3307       node name against the list)
3308   @type nodegroup: string
3309   @param nodegroup: If set, only return nodes in this node group
3310
3311   """
3312   if cl is None:
3313     cl = GetClient()
3314
3315   qfilter = []
3316
3317   if nodes:
3318     qfilter.append(qlang.MakeSimpleFilter("name", nodes))
3319
3320   if nodegroup is not None:
3321     qfilter.append([qlang.OP_OR, [qlang.OP_EQUAL, "group", nodegroup],
3322                                  [qlang.OP_EQUAL, "group.uuid", nodegroup]])
3323
3324   if filter_master:
3325     qfilter.append([qlang.OP_NOT, [qlang.OP_TRUE, "master"]])
3326
3327   if qfilter:
3328     if len(qfilter) > 1:
3329       final_filter = [qlang.OP_AND] + qfilter
3330     else:
3331       assert len(qfilter) == 1
3332       final_filter = qfilter[0]
3333   else:
3334     final_filter = None
3335
3336   result = cl.Query(constants.QR_NODE, ["name", "offline", "sip"], final_filter)
3337
3338   def _IsOffline(row):
3339     (_, (_, offline), _) = row
3340     return offline
3341
3342   def _GetName(row):
3343     ((_, name), _, _) = row
3344     return name
3345
3346   def _GetSip(row):
3347     (_, _, (_, sip)) = row
3348     return sip
3349
3350   (offline, online) = compat.partition(result.data, _IsOffline)
3351
3352   if offline and not nowarn:
3353     ToStderr("Note: skipping offline node(s): %s" %
3354              utils.CommaJoin(map(_GetName, offline)))
3355
3356   if secondary_ips:
3357     fn = _GetSip
3358   else:
3359     fn = _GetName
3360
3361   return map(fn, online)
3362
3363
3364 def _ToStream(stream, txt, *args):
3365   """Write a message to a stream, bypassing the logging system
3366
3367   @type stream: file object
3368   @param stream: the file to which we should write
3369   @type txt: str
3370   @param txt: the message
3371
3372   """
3373   try:
3374     if args:
3375       args = tuple(args)
3376       stream.write(txt % args)
3377     else:
3378       stream.write(txt)
3379     stream.write("\n")
3380     stream.flush()
3381   except IOError, err:
3382     if err.errno == errno.EPIPE:
3383       # our terminal went away, we'll exit
3384       sys.exit(constants.EXIT_FAILURE)
3385     else:
3386       raise
3387
3388
3389 def ToStdout(txt, *args):
3390   """Write a message to stdout only, bypassing the logging system
3391
3392   This is just a wrapper over _ToStream.
3393
3394   @type txt: str
3395   @param txt: the message
3396
3397   """
3398   _ToStream(sys.stdout, txt, *args)
3399
3400
3401 def ToStderr(txt, *args):
3402   """Write a message to stderr only, bypassing the logging system
3403
3404   This is just a wrapper over _ToStream.
3405
3406   @type txt: str
3407   @param txt: the message
3408
3409   """
3410   _ToStream(sys.stderr, txt, *args)
3411
3412
3413 class JobExecutor(object):
3414   """Class which manages the submission and execution of multiple jobs.
3415
3416   Note that instances of this class should not be reused between
3417   GetResults() calls.
3418
3419   """
3420   def __init__(self, cl=None, verbose=True, opts=None, feedback_fn=None):
3421     self.queue = []
3422     if cl is None:
3423       cl = GetClient()
3424     self.cl = cl
3425     self.verbose = verbose
3426     self.jobs = []
3427     self.opts = opts
3428     self.feedback_fn = feedback_fn
3429     self._counter = itertools.count()
3430
3431   @staticmethod
3432   def _IfName(name, fmt):
3433     """Helper function for formatting name.
3434
3435     """
3436     if name:
3437       return fmt % name
3438
3439     return ""
3440
3441   def QueueJob(self, name, *ops):
3442     """Record a job for later submit.
3443
3444     @type name: string
3445     @param name: a description of the job, will be used in WaitJobSet
3446
3447     """
3448     SetGenericOpcodeOpts(ops, self.opts)
3449     self.queue.append((self._counter.next(), name, ops))
3450
3451   def AddJobId(self, name, status, job_id):
3452     """Adds a job ID to the internal queue.
3453
3454     """
3455     self.jobs.append((self._counter.next(), status, job_id, name))
3456
3457   def SubmitPending(self, each=False):
3458     """Submit all pending jobs.
3459
3460     """
3461     if each:
3462       results = []
3463       for (_, _, ops) in self.queue:
3464         # SubmitJob will remove the success status, but raise an exception if
3465         # the submission fails, so we'll notice that anyway.
3466         results.append([True, self.cl.SubmitJob(ops)[0]])
3467     else:
3468       results = self.cl.SubmitManyJobs([ops for (_, _, ops) in self.queue])
3469     for ((status, data), (idx, name, _)) in zip(results, self.queue):
3470       self.jobs.append((idx, status, data, name))
3471
3472   def _ChooseJob(self):
3473     """Choose a non-waiting/queued job to poll next.
3474
3475     """
3476     assert self.jobs, "_ChooseJob called with empty job list"
3477
3478     result = self.cl.QueryJobs([i[2] for i in self.jobs[:_CHOOSE_BATCH]],
3479                                ["status"])
3480     assert result
3481
3482     for job_data, status in zip(self.jobs, result):
3483       if (isinstance(status, list) and status and
3484           status[0] in (constants.JOB_STATUS_QUEUED,
3485                         constants.JOB_STATUS_WAITING,
3486                         constants.JOB_STATUS_CANCELING)):
3487         # job is still present and waiting
3488         continue
3489       # good candidate found (either running job or lost job)
3490       self.jobs.remove(job_data)
3491       return job_data
3492
3493     # no job found
3494     return self.jobs.pop(0)
3495
3496   def GetResults(self):
3497     """Wait for and return the results of all jobs.
3498
3499     @rtype: list
3500     @return: list of tuples (success, job results), in the same order
3501         as the submitted jobs; if a job has failed, instead of the result
3502         there will be the error message
3503
3504     """
3505     if not self.jobs:
3506       self.SubmitPending()
3507     results = []
3508     if self.verbose:
3509       ok_jobs = [row[2] for row in self.jobs if row[1]]
3510       if ok_jobs:
3511         ToStdout("Submitted jobs %s", utils.CommaJoin(ok_jobs))
3512
3513     # first, remove any non-submitted jobs
3514     self.jobs, failures = compat.partition(self.jobs, lambda x: x[1])
3515     for idx, _, jid, name in failures:
3516       ToStderr("Failed to submit job%s: %s", self._IfName(name, " for %s"), jid)
3517       results.append((idx, False, jid))
3518
3519     while self.jobs:
3520       (idx, _, jid, name) = self._ChooseJob()
3521       ToStdout("Waiting for job %s%s ...", jid, self._IfName(name, " for %s"))
3522       try:
3523         job_result = PollJob(jid, cl=self.cl, feedback_fn=self.feedback_fn)
3524         success = True
3525       except errors.JobLost, err:
3526         _, job_result = FormatError(err)
3527         ToStderr("Job %s%s has been archived, cannot check its result",
3528                  jid, self._IfName(name, " for %s"))
3529         success = False
3530       except (errors.GenericError, luxi.ProtocolError), err:
3531         _, job_result = FormatError(err)
3532         success = False
3533         # the error message will always be shown, verbose or not
3534         ToStderr("Job %s%s has failed: %s",
3535                  jid, self._IfName(name, " for %s"), job_result)
3536
3537       results.append((idx, success, job_result))
3538
3539     # sort based on the index, then drop it
3540     results.sort()
3541     results = [i[1:] for i in results]
3542
3543     return results
3544
3545   def WaitOrShow(self, wait):
3546     """Wait for job results or only print the job IDs.
3547
3548     @type wait: boolean
3549     @param wait: whether to wait or not
3550
3551     """
3552     if wait:
3553       return self.GetResults()
3554     else:
3555       if not self.jobs:
3556         self.SubmitPending()
3557       for _, status, result, name in self.jobs:
3558         if status:
3559           ToStdout("%s: %s", result, name)
3560         else:
3561           ToStderr("Failure for %s: %s", name, result)
3562       return [row[1:3] for row in self.jobs]
3563
3564
3565 def FormatParameterDict(buf, param_dict, actual, level=1):
3566   """Formats a parameter dictionary.
3567
3568   @type buf: L{StringIO}
3569   @param buf: the buffer into which to write
3570   @type param_dict: dict
3571   @param param_dict: the own parameters
3572   @type actual: dict
3573   @param actual: the current parameter set (including defaults)
3574   @param level: Level of indent
3575
3576   """
3577   indent = "  " * level
3578
3579   for key in sorted(actual):
3580     data = actual[key]
3581     buf.write("%s- %s:" % (indent, key))
3582
3583     if isinstance(data, dict) and data:
3584       buf.write("\n")
3585       FormatParameterDict(buf, param_dict.get(key, {}), data,
3586                           level=level + 1)
3587     else:
3588       val = param_dict.get(key, "default (%s)" % data)
3589       buf.write(" %s\n" % val)
3590
3591
3592 def ConfirmOperation(names, list_type, text, extra=""):
3593   """Ask the user to confirm an operation on a list of list_type.
3594
3595   This function is used to request confirmation for doing an operation
3596   on a given list of list_type.
3597
3598   @type names: list
3599   @param names: the list of names that we display when
3600       we ask for confirmation
3601   @type list_type: str
3602   @param list_type: Human readable name for elements in the list (e.g. nodes)
3603   @type text: str
3604   @param text: the operation that the user should confirm
3605   @rtype: boolean
3606   @return: True or False depending on user's confirmation.
3607
3608   """
3609   count = len(names)
3610   msg = ("The %s will operate on %d %s.\n%s"
3611          "Do you want to continue?" % (text, count, list_type, extra))
3612   affected = (("\nAffected %s:\n" % list_type) +
3613               "\n".join(["  %s" % name for name in names]))
3614
3615   choices = [("y", True, "Yes, execute the %s" % text),
3616              ("n", False, "No, abort the %s" % text)]
3617
3618   if count > 20:
3619     choices.insert(1, ("v", "v", "View the list of affected %s" % list_type))
3620     question = msg
3621   else:
3622     question = msg + affected
3623
3624   choice = AskUser(question, choices)
3625   if choice == "v":
3626     choices.pop(1)
3627     choice = AskUser(msg + affected, choices)
3628   return choice
3629
3630
3631 def _MaybeParseUnit(elements):
3632   """Parses and returns an array of potential values with units.
3633
3634   """
3635   parsed = {}
3636   for k, v in elements.items():
3637     if v == constants.VALUE_DEFAULT:
3638       parsed[k] = v
3639     else:
3640       parsed[k] = utils.ParseUnit(v)
3641   return parsed
3642
3643
3644 def CreateIPolicyFromOpts(ispecs_mem_size=None,
3645                           ispecs_cpu_count=None,
3646                           ispecs_disk_count=None,
3647                           ispecs_disk_size=None,
3648                           ispecs_nic_count=None,
3649                           ipolicy_disk_templates=None,
3650                           ipolicy_vcpu_ratio=None,
3651                           ipolicy_spindle_ratio=None,
3652                           group_ipolicy=False,
3653                           allowed_values=None,
3654                           fill_all=False):
3655   """Creation of instance policy based on command line options.
3656
3657   @param fill_all: whether for cluster policies we should ensure that
3658     all values are filled
3659
3660
3661   """
3662   try:
3663     if ispecs_mem_size:
3664       ispecs_mem_size = _MaybeParseUnit(ispecs_mem_size)
3665     if ispecs_disk_size:
3666       ispecs_disk_size = _MaybeParseUnit(ispecs_disk_size)
3667   except (TypeError, ValueError, errors.UnitParseError), err:
3668     raise errors.OpPrereqError("Invalid disk (%s) or memory (%s) size"
3669                                " in policy: %s" %
3670                                (ispecs_disk_size, ispecs_mem_size, err),
3671                                errors.ECODE_INVAL)
3672
3673   # prepare ipolicy dict
3674   ipolicy_transposed = {
3675     constants.ISPEC_MEM_SIZE: ispecs_mem_size,
3676     constants.ISPEC_CPU_COUNT: ispecs_cpu_count,
3677     constants.ISPEC_DISK_COUNT: ispecs_disk_count,
3678     constants.ISPEC_DISK_SIZE: ispecs_disk_size,
3679     constants.ISPEC_NIC_COUNT: ispecs_nic_count,
3680     }
3681
3682   # first, check that the values given are correct
3683   if group_ipolicy:
3684     forced_type = TISPECS_GROUP_TYPES
3685   else:
3686     forced_type = TISPECS_CLUSTER_TYPES
3687
3688   for specs in ipolicy_transposed.values():
3689     utils.ForceDictType(specs, forced_type, allowed_values=allowed_values)
3690
3691   # then transpose
3692   ipolicy_out = objects.MakeEmptyIPolicy()
3693   for name, specs in ipolicy_transposed.iteritems():
3694     assert name in constants.ISPECS_PARAMETERS
3695     for key, val in specs.items(): # {min: .. ,max: .., std: ..}
3696       ipolicy_out[key][name] = val
3697
3698   # no filldict for non-dicts
3699   if not group_ipolicy and fill_all:
3700     if ipolicy_disk_templates is None:
3701       ipolicy_disk_templates = constants.DISK_TEMPLATES
3702     if ipolicy_vcpu_ratio is None:
3703       ipolicy_vcpu_ratio = \
3704         constants.IPOLICY_DEFAULTS[constants.IPOLICY_VCPU_RATIO]
3705     if ipolicy_spindle_ratio is None:
3706       ipolicy_spindle_ratio = \
3707         constants.IPOLICY_DEFAULTS[constants.IPOLICY_SPINDLE_RATIO]
3708   if ipolicy_disk_templates is not None:
3709     ipolicy_out[constants.IPOLICY_DTS] = list(ipolicy_disk_templates)
3710   if ipolicy_vcpu_ratio is not None:
3711     ipolicy_out[constants.IPOLICY_VCPU_RATIO] = ipolicy_vcpu_ratio
3712   if ipolicy_spindle_ratio is not None:
3713     ipolicy_out[constants.IPOLICY_SPINDLE_RATIO] = ipolicy_spindle_ratio
3714
3715   assert not (frozenset(ipolicy_out.keys()) - constants.IPOLICY_ALL_KEYS)
3716
3717   return ipolicy_out