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