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