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