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