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