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