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