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