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