backend: Disable compression in export info file
[ganeti-local] / lib / cli.py
1 #
2 #
3
4 # Copyright (C) 2006, 2007, 2008, 2009, 2010 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 from cStringIO import StringIO
31
32 from ganeti import utils
33 from ganeti import errors
34 from ganeti import constants
35 from ganeti import opcodes
36 from ganeti import luxi
37 from ganeti import ssconf
38 from ganeti import rpc
39 from ganeti import ssh
40 from ganeti import compat
41 from ganeti import netutils
42
43 from optparse import (OptionParser, TitledHelpFormatter,
44                       Option, OptionValueError)
45
46
47 __all__ = [
48   # Command line options
49   "ADD_UIDS_OPT",
50   "ALLOCATABLE_OPT",
51   "ALL_OPT",
52   "AUTO_PROMOTE_OPT",
53   "AUTO_REPLACE_OPT",
54   "BACKEND_OPT",
55   "BLK_OS_OPT",
56   "CAPAB_MASTER_OPT",
57   "CAPAB_VM_OPT",
58   "CLEANUP_OPT",
59   "CLUSTER_DOMAIN_SECRET_OPT",
60   "CONFIRM_OPT",
61   "CP_SIZE_OPT",
62   "DEBUG_OPT",
63   "DEBUG_SIMERR_OPT",
64   "DISKIDX_OPT",
65   "DISK_OPT",
66   "DISK_TEMPLATE_OPT",
67   "DRAINED_OPT",
68   "DRY_RUN_OPT",
69   "DRBD_HELPER_OPT",
70   "EARLY_RELEASE_OPT",
71   "ENABLED_HV_OPT",
72   "ERROR_CODES_OPT",
73   "FIELDS_OPT",
74   "FILESTORE_DIR_OPT",
75   "FILESTORE_DRIVER_OPT",
76   "FORCE_OPT",
77   "FORCE_VARIANT_OPT",
78   "GLOBAL_FILEDIR_OPT",
79   "HID_OS_OPT",
80   "HVLIST_OPT",
81   "HVOPTS_OPT",
82   "HYPERVISOR_OPT",
83   "IALLOCATOR_OPT",
84   "DEFAULT_IALLOCATOR_OPT",
85   "IDENTIFY_DEFAULTS_OPT",
86   "IGNORE_CONSIST_OPT",
87   "IGNORE_FAILURES_OPT",
88   "IGNORE_OFFLINE_OPT",
89   "IGNORE_REMOVE_FAILURES_OPT",
90   "IGNORE_SECONDARIES_OPT",
91   "IGNORE_SIZE_OPT",
92   "INTERVAL_OPT",
93   "MAC_PREFIX_OPT",
94   "MAINTAIN_NODE_HEALTH_OPT",
95   "MASTER_NETDEV_OPT",
96   "MC_OPT",
97   "MIGRATION_MODE_OPT",
98   "NET_OPT",
99   "NEW_CLUSTER_CERT_OPT",
100   "NEW_CLUSTER_DOMAIN_SECRET_OPT",
101   "NEW_CONFD_HMAC_KEY_OPT",
102   "NEW_RAPI_CERT_OPT",
103   "NEW_SECONDARY_OPT",
104   "NIC_PARAMS_OPT",
105   "NODE_LIST_OPT",
106   "NODE_PLACEMENT_OPT",
107   "NODEGROUP_OPT",
108   "NODRBD_STORAGE_OPT",
109   "NOHDR_OPT",
110   "NOIPCHECK_OPT",
111   "NO_INSTALL_OPT",
112   "NONAMECHECK_OPT",
113   "NOLVM_STORAGE_OPT",
114   "NOMODIFY_ETCHOSTS_OPT",
115   "NOMODIFY_SSH_SETUP_OPT",
116   "NONICS_OPT",
117   "NONLIVE_OPT",
118   "NONPLUS1_OPT",
119   "NOSHUTDOWN_OPT",
120   "NOSTART_OPT",
121   "NOSSH_KEYCHECK_OPT",
122   "NOVOTING_OPT",
123   "NWSYNC_OPT",
124   "ON_PRIMARY_OPT",
125   "ON_SECONDARY_OPT",
126   "OFFLINE_OPT",
127   "OSPARAMS_OPT",
128   "OS_OPT",
129   "OS_SIZE_OPT",
130   "PREALLOC_WIPE_DISKS_OPT",
131   "PRIMARY_IP_VERSION_OPT",
132   "PRIORITY_OPT",
133   "RAPI_CERT_OPT",
134   "READD_OPT",
135   "REBOOT_TYPE_OPT",
136   "REMOVE_INSTANCE_OPT",
137   "REMOVE_UIDS_OPT",
138   "RESERVED_LVS_OPT",
139   "ROMAN_OPT",
140   "SECONDARY_IP_OPT",
141   "SELECT_OS_OPT",
142   "SEP_OPT",
143   "SHOWCMD_OPT",
144   "SHUTDOWN_TIMEOUT_OPT",
145   "SINGLE_NODE_OPT",
146   "SRC_DIR_OPT",
147   "SRC_NODE_OPT",
148   "SUBMIT_OPT",
149   "STATIC_OPT",
150   "SYNC_OPT",
151   "TAG_SRC_OPT",
152   "TIMEOUT_OPT",
153   "UIDPOOL_OPT",
154   "USEUNITS_OPT",
155   "USE_REPL_NET_OPT",
156   "VERBOSE_OPT",
157   "VG_NAME_OPT",
158   "YES_DOIT_OPT",
159   # Generic functions for CLI programs
160   "GenericMain",
161   "GenericInstanceCreate",
162   "GetClient",
163   "GetOnlineNodes",
164   "JobExecutor",
165   "JobSubmittedException",
166   "ParseTimespec",
167   "RunWhileClusterStopped",
168   "SubmitOpCode",
169   "SubmitOrSend",
170   "UsesRPC",
171   # Formatting functions
172   "ToStderr", "ToStdout",
173   "FormatError",
174   "GenerateTable",
175   "AskUser",
176   "FormatTimestamp",
177   "FormatLogMessage",
178   # Tags functions
179   "ListTags",
180   "AddTags",
181   "RemoveTags",
182   # command line options support infrastructure
183   "ARGS_MANY_INSTANCES",
184   "ARGS_MANY_NODES",
185   "ARGS_NONE",
186   "ARGS_ONE_INSTANCE",
187   "ARGS_ONE_NODE",
188   "ARGS_ONE_OS",
189   "ArgChoice",
190   "ArgCommand",
191   "ArgFile",
192   "ArgHost",
193   "ArgInstance",
194   "ArgJobId",
195   "ArgNode",
196   "ArgOs",
197   "ArgSuggest",
198   "ArgUnknown",
199   "OPT_COMPL_INST_ADD_NODES",
200   "OPT_COMPL_MANY_NODES",
201   "OPT_COMPL_ONE_IALLOCATOR",
202   "OPT_COMPL_ONE_INSTANCE",
203   "OPT_COMPL_ONE_NODE",
204   "OPT_COMPL_ONE_NODEGROUP",
205   "OPT_COMPL_ONE_OS",
206   "cli_option",
207   "SplitNodeOption",
208   "CalculateOSNames",
209   "ParseFields",
210   "COMMON_CREATE_OPTS",
211   ]
212
213 NO_PREFIX = "no_"
214 UN_PREFIX = "-"
215
216 #: Priorities (sorted)
217 _PRIORITY_NAMES = [
218   ("low", constants.OP_PRIO_LOW),
219   ("normal", constants.OP_PRIO_NORMAL),
220   ("high", constants.OP_PRIO_HIGH),
221   ]
222
223 #: Priority dictionary for easier lookup
224 # TODO: Replace this and _PRIORITY_NAMES with a single sorted dictionary once
225 # we migrate to Python 2.6
226 _PRIONAME_TO_VALUE = dict(_PRIORITY_NAMES)
227
228
229 class _Argument:
230   def __init__(self, min=0, max=None): # pylint: disable-msg=W0622
231     self.min = min
232     self.max = max
233
234   def __repr__(self):
235     return ("<%s min=%s max=%s>" %
236             (self.__class__.__name__, self.min, self.max))
237
238
239 class ArgSuggest(_Argument):
240   """Suggesting argument.
241
242   Value can be any of the ones passed to the constructor.
243
244   """
245   # pylint: disable-msg=W0622
246   def __init__(self, min=0, max=None, choices=None):
247     _Argument.__init__(self, min=min, max=max)
248     self.choices = choices
249
250   def __repr__(self):
251     return ("<%s min=%s max=%s choices=%r>" %
252             (self.__class__.__name__, self.min, self.max, self.choices))
253
254
255 class ArgChoice(ArgSuggest):
256   """Choice argument.
257
258   Value can be any of the ones passed to the constructor. Like L{ArgSuggest},
259   but value must be one of the choices.
260
261   """
262
263
264 class ArgUnknown(_Argument):
265   """Unknown argument to program (e.g. determined at runtime).
266
267   """
268
269
270 class ArgInstance(_Argument):
271   """Instances argument.
272
273   """
274
275
276 class ArgNode(_Argument):
277   """Node argument.
278
279   """
280
281 class ArgJobId(_Argument):
282   """Job ID argument.
283
284   """
285
286
287 class ArgFile(_Argument):
288   """File path argument.
289
290   """
291
292
293 class ArgCommand(_Argument):
294   """Command argument.
295
296   """
297
298
299 class ArgHost(_Argument):
300   """Host argument.
301
302   """
303
304
305 class ArgOs(_Argument):
306   """OS argument.
307
308   """
309
310
311 ARGS_NONE = []
312 ARGS_MANY_INSTANCES = [ArgInstance()]
313 ARGS_MANY_NODES = [ArgNode()]
314 ARGS_ONE_INSTANCE = [ArgInstance(min=1, max=1)]
315 ARGS_ONE_NODE = [ArgNode(min=1, max=1)]
316 ARGS_ONE_OS = [ArgOs(min=1, max=1)]
317
318
319 def _ExtractTagsObject(opts, args):
320   """Extract the tag type object.
321
322   Note that this function will modify its args parameter.
323
324   """
325   if not hasattr(opts, "tag_type"):
326     raise errors.ProgrammerError("tag_type not passed to _ExtractTagsObject")
327   kind = opts.tag_type
328   if kind == constants.TAG_CLUSTER:
329     retval = kind, kind
330   elif kind == constants.TAG_NODE or kind == constants.TAG_INSTANCE:
331     if not args:
332       raise errors.OpPrereqError("no arguments passed to the command")
333     name = args.pop(0)
334     retval = kind, name
335   else:
336     raise errors.ProgrammerError("Unhandled tag type '%s'" % kind)
337   return retval
338
339
340 def _ExtendTags(opts, args):
341   """Extend the args if a source file has been given.
342
343   This function will extend the tags with the contents of the file
344   passed in the 'tags_source' attribute of the opts parameter. A file
345   named '-' will be replaced by stdin.
346
347   """
348   fname = opts.tags_source
349   if fname is None:
350     return
351   if fname == "-":
352     new_fh = sys.stdin
353   else:
354     new_fh = open(fname, "r")
355   new_data = []
356   try:
357     # we don't use the nice 'new_data = [line.strip() for line in fh]'
358     # because of python bug 1633941
359     while True:
360       line = new_fh.readline()
361       if not line:
362         break
363       new_data.append(line.strip())
364   finally:
365     new_fh.close()
366   args.extend(new_data)
367
368
369 def ListTags(opts, args):
370   """List the tags on a given object.
371
372   This is a generic implementation that knows how to deal with all
373   three cases of tag objects (cluster, node, instance). The opts
374   argument is expected to contain a tag_type field denoting what
375   object type we work on.
376
377   """
378   kind, name = _ExtractTagsObject(opts, args)
379   cl = GetClient()
380   result = cl.QueryTags(kind, name)
381   result = list(result)
382   result.sort()
383   for tag in result:
384     ToStdout(tag)
385
386
387 def AddTags(opts, args):
388   """Add tags on a given object.
389
390   This is a generic implementation that knows how to deal with all
391   three cases of tag objects (cluster, node, instance). The opts
392   argument is expected to contain a tag_type field denoting what
393   object type we work on.
394
395   """
396   kind, name = _ExtractTagsObject(opts, args)
397   _ExtendTags(opts, args)
398   if not args:
399     raise errors.OpPrereqError("No tags to be added")
400   op = opcodes.OpAddTags(kind=kind, name=name, tags=args)
401   SubmitOpCode(op, opts=opts)
402
403
404 def RemoveTags(opts, args):
405   """Remove tags from a given object.
406
407   This is a generic implementation that knows how to deal with all
408   three cases of tag objects (cluster, node, instance). The opts
409   argument is expected to contain a tag_type field denoting what
410   object type we work on.
411
412   """
413   kind, name = _ExtractTagsObject(opts, args)
414   _ExtendTags(opts, args)
415   if not args:
416     raise errors.OpPrereqError("No tags to be removed")
417   op = opcodes.OpDelTags(kind=kind, name=name, tags=args)
418   SubmitOpCode(op, opts=opts)
419
420
421 def check_unit(option, opt, value): # pylint: disable-msg=W0613
422   """OptParsers custom converter for units.
423
424   """
425   try:
426     return utils.ParseUnit(value)
427   except errors.UnitParseError, err:
428     raise OptionValueError("option %s: %s" % (opt, err))
429
430
431 def _SplitKeyVal(opt, data):
432   """Convert a KeyVal string into a dict.
433
434   This function will convert a key=val[,...] string into a dict. Empty
435   values will be converted specially: keys which have the prefix 'no_'
436   will have the value=False and the prefix stripped, the others will
437   have value=True.
438
439   @type opt: string
440   @param opt: a string holding the option name for which we process the
441       data, used in building error messages
442   @type data: string
443   @param data: a string of the format key=val,key=val,...
444   @rtype: dict
445   @return: {key=val, key=val}
446   @raises errors.ParameterError: if there are duplicate keys
447
448   """
449   kv_dict = {}
450   if data:
451     for elem in utils.UnescapeAndSplit(data, sep=","):
452       if "=" in elem:
453         key, val = elem.split("=", 1)
454       else:
455         if elem.startswith(NO_PREFIX):
456           key, val = elem[len(NO_PREFIX):], False
457         elif elem.startswith(UN_PREFIX):
458           key, val = elem[len(UN_PREFIX):], None
459         else:
460           key, val = elem, True
461       if key in kv_dict:
462         raise errors.ParameterError("Duplicate key '%s' in option %s" %
463                                     (key, opt))
464       kv_dict[key] = val
465   return kv_dict
466
467
468 def check_ident_key_val(option, opt, value):  # pylint: disable-msg=W0613
469   """Custom parser for ident:key=val,key=val options.
470
471   This will store the parsed values as a tuple (ident, {key: val}). As such,
472   multiple uses of this option via action=append is possible.
473
474   """
475   if ":" not in value:
476     ident, rest = value, ''
477   else:
478     ident, rest = value.split(":", 1)
479
480   if ident.startswith(NO_PREFIX):
481     if rest:
482       msg = "Cannot pass options when removing parameter groups: %s" % value
483       raise errors.ParameterError(msg)
484     retval = (ident[len(NO_PREFIX):], False)
485   elif ident.startswith(UN_PREFIX):
486     if rest:
487       msg = "Cannot pass options when removing parameter groups: %s" % value
488       raise errors.ParameterError(msg)
489     retval = (ident[len(UN_PREFIX):], None)
490   else:
491     kv_dict = _SplitKeyVal(opt, rest)
492     retval = (ident, kv_dict)
493   return retval
494
495
496 def check_key_val(option, opt, value):  # pylint: disable-msg=W0613
497   """Custom parser class for key=val,key=val options.
498
499   This will store the parsed values as a dict {key: val}.
500
501   """
502   return _SplitKeyVal(opt, value)
503
504
505 def check_bool(option, opt, value): # pylint: disable-msg=W0613
506   """Custom parser for yes/no options.
507
508   This will store the parsed value as either True or False.
509
510   """
511   value = value.lower()
512   if value == constants.VALUE_FALSE or value == "no":
513     return False
514   elif value == constants.VALUE_TRUE or value == "yes":
515     return True
516   else:
517     raise errors.ParameterError("Invalid boolean value '%s'" % value)
518
519
520 # completion_suggestion is normally a list. Using numeric values not evaluating
521 # to False for dynamic completion.
522 (OPT_COMPL_MANY_NODES,
523  OPT_COMPL_ONE_NODE,
524  OPT_COMPL_ONE_INSTANCE,
525  OPT_COMPL_ONE_OS,
526  OPT_COMPL_ONE_IALLOCATOR,
527  OPT_COMPL_INST_ADD_NODES,
528  OPT_COMPL_ONE_NODEGROUP) = range(100, 107)
529
530 OPT_COMPL_ALL = frozenset([
531   OPT_COMPL_MANY_NODES,
532   OPT_COMPL_ONE_NODE,
533   OPT_COMPL_ONE_INSTANCE,
534   OPT_COMPL_ONE_OS,
535   OPT_COMPL_ONE_IALLOCATOR,
536   OPT_COMPL_INST_ADD_NODES,
537   OPT_COMPL_ONE_NODEGROUP,
538   ])
539
540
541 class CliOption(Option):
542   """Custom option class for optparse.
543
544   """
545   ATTRS = Option.ATTRS + [
546     "completion_suggest",
547     ]
548   TYPES = Option.TYPES + (
549     "identkeyval",
550     "keyval",
551     "unit",
552     "bool",
553     )
554   TYPE_CHECKER = Option.TYPE_CHECKER.copy()
555   TYPE_CHECKER["identkeyval"] = check_ident_key_val
556   TYPE_CHECKER["keyval"] = check_key_val
557   TYPE_CHECKER["unit"] = check_unit
558   TYPE_CHECKER["bool"] = check_bool
559
560
561 # optparse.py sets make_option, so we do it for our own option class, too
562 cli_option = CliOption
563
564
565 _YORNO = "yes|no"
566
567 DEBUG_OPT = cli_option("-d", "--debug", default=0, action="count",
568                        help="Increase debugging level")
569
570 NOHDR_OPT = cli_option("--no-headers", default=False,
571                        action="store_true", dest="no_headers",
572                        help="Don't display column headers")
573
574 SEP_OPT = cli_option("--separator", default=None,
575                      action="store", dest="separator",
576                      help=("Separator between output fields"
577                            " (defaults to one space)"))
578
579 USEUNITS_OPT = cli_option("--units", default=None,
580                           dest="units", choices=('h', 'm', 'g', 't'),
581                           help="Specify units for output (one of hmgt)")
582
583 FIELDS_OPT = cli_option("-o", "--output", dest="output", action="store",
584                         type="string", metavar="FIELDS",
585                         help="Comma separated list of output fields")
586
587 FORCE_OPT = cli_option("-f", "--force", dest="force", action="store_true",
588                        default=False, help="Force the operation")
589
590 CONFIRM_OPT = cli_option("--yes", dest="confirm", action="store_true",
591                          default=False, help="Do not require confirmation")
592
593 IGNORE_OFFLINE_OPT = cli_option("--ignore-offline", dest="ignore_offline",
594                                   action="store_true", default=False,
595                                   help=("Ignore offline nodes and do as much"
596                                         " as possible"))
597
598 TAG_SRC_OPT = cli_option("--from", dest="tags_source",
599                          default=None, help="File with tag names")
600
601 SUBMIT_OPT = cli_option("--submit", dest="submit_only",
602                         default=False, action="store_true",
603                         help=("Submit the job and return the job ID, but"
604                               " don't wait for the job to finish"))
605
606 SYNC_OPT = cli_option("--sync", dest="do_locking",
607                       default=False, action="store_true",
608                       help=("Grab locks while doing the queries"
609                             " in order to ensure more consistent results"))
610
611 DRY_RUN_OPT = cli_option("--dry-run", default=False,
612                          action="store_true",
613                          help=("Do not execute the operation, just run the"
614                                " check steps and verify it it could be"
615                                " executed"))
616
617 VERBOSE_OPT = cli_option("-v", "--verbose", default=False,
618                          action="store_true",
619                          help="Increase the verbosity of the operation")
620
621 DEBUG_SIMERR_OPT = cli_option("--debug-simulate-errors", default=False,
622                               action="store_true", dest="simulate_errors",
623                               help="Debugging option that makes the operation"
624                               " treat most runtime checks as failed")
625
626 NWSYNC_OPT = cli_option("--no-wait-for-sync", dest="wait_for_sync",
627                         default=True, action="store_false",
628                         help="Don't wait for sync (DANGEROUS!)")
629
630 DISK_TEMPLATE_OPT = cli_option("-t", "--disk-template", dest="disk_template",
631                                help="Custom disk setup (diskless, file,"
632                                " plain or drbd)",
633                                default=None, metavar="TEMPL",
634                                choices=list(constants.DISK_TEMPLATES))
635
636 NONICS_OPT = cli_option("--no-nics", default=False, action="store_true",
637                         help="Do not create any network cards for"
638                         " the instance")
639
640 FILESTORE_DIR_OPT = cli_option("--file-storage-dir", dest="file_storage_dir",
641                                help="Relative path under default cluster-wide"
642                                " file storage dir to store file-based disks",
643                                default=None, metavar="<DIR>")
644
645 FILESTORE_DRIVER_OPT = cli_option("--file-driver", dest="file_driver",
646                                   help="Driver to use for image files",
647                                   default="loop", metavar="<DRIVER>",
648                                   choices=list(constants.FILE_DRIVER))
649
650 IALLOCATOR_OPT = cli_option("-I", "--iallocator", metavar="<NAME>",
651                             help="Select nodes for the instance automatically"
652                             " using the <NAME> iallocator plugin",
653                             default=None, type="string",
654                             completion_suggest=OPT_COMPL_ONE_IALLOCATOR)
655
656 DEFAULT_IALLOCATOR_OPT = cli_option("-I", "--default-iallocator",
657                             metavar="<NAME>",
658                             help="Set the default instance allocator plugin",
659                             default=None, type="string",
660                             completion_suggest=OPT_COMPL_ONE_IALLOCATOR)
661
662 OS_OPT = cli_option("-o", "--os-type", dest="os", help="What OS to run",
663                     metavar="<os>",
664                     completion_suggest=OPT_COMPL_ONE_OS)
665
666 OSPARAMS_OPT = cli_option("-O", "--os-parameters", dest="osparams",
667                          type="keyval", default={},
668                          help="OS parameters")
669
670 FORCE_VARIANT_OPT = cli_option("--force-variant", dest="force_variant",
671                                action="store_true", default=False,
672                                help="Force an unknown variant")
673
674 NO_INSTALL_OPT = cli_option("--no-install", dest="no_install",
675                             action="store_true", default=False,
676                             help="Do not install the OS (will"
677                             " enable no-start)")
678
679 BACKEND_OPT = cli_option("-B", "--backend-parameters", dest="beparams",
680                          type="keyval", default={},
681                          help="Backend parameters")
682
683 HVOPTS_OPT =  cli_option("-H", "--hypervisor-parameters", type="keyval",
684                          default={}, dest="hvparams",
685                          help="Hypervisor parameters")
686
687 HYPERVISOR_OPT = cli_option("-H", "--hypervisor-parameters", dest="hypervisor",
688                             help="Hypervisor and hypervisor options, in the"
689                             " format hypervisor:option=value,option=value,...",
690                             default=None, type="identkeyval")
691
692 HVLIST_OPT = cli_option("-H", "--hypervisor-parameters", dest="hvparams",
693                         help="Hypervisor and hypervisor options, in the"
694                         " format hypervisor:option=value,option=value,...",
695                         default=[], action="append", type="identkeyval")
696
697 NOIPCHECK_OPT = cli_option("--no-ip-check", dest="ip_check", default=True,
698                            action="store_false",
699                            help="Don't check that the instance's IP"
700                            " is alive")
701
702 NONAMECHECK_OPT = cli_option("--no-name-check", dest="name_check",
703                              default=True, action="store_false",
704                              help="Don't check that the instance's name"
705                              " is resolvable")
706
707 NET_OPT = cli_option("--net",
708                      help="NIC parameters", default=[],
709                      dest="nics", action="append", type="identkeyval")
710
711 DISK_OPT = cli_option("--disk", help="Disk parameters", default=[],
712                       dest="disks", action="append", type="identkeyval")
713
714 DISKIDX_OPT = cli_option("--disks", dest="disks", default=None,
715                          help="Comma-separated list of disks"
716                          " indices to act on (e.g. 0,2) (optional,"
717                          " defaults to all disks)")
718
719 OS_SIZE_OPT = cli_option("-s", "--os-size", dest="sd_size",
720                          help="Enforces a single-disk configuration using the"
721                          " given disk size, in MiB unless a suffix is used",
722                          default=None, type="unit", metavar="<size>")
723
724 IGNORE_CONSIST_OPT = cli_option("--ignore-consistency",
725                                 dest="ignore_consistency",
726                                 action="store_true", default=False,
727                                 help="Ignore the consistency of the disks on"
728                                 " the secondary")
729
730 NONLIVE_OPT = cli_option("--non-live", dest="live",
731                          default=True, action="store_false",
732                          help="Do a non-live migration (this usually means"
733                          " freeze the instance, save the state, transfer and"
734                          " only then resume running on the secondary node)")
735
736 MIGRATION_MODE_OPT = cli_option("--migration-mode", dest="migration_mode",
737                                 default=None,
738                                 choices=list(constants.HT_MIGRATION_MODES),
739                                 help="Override default migration mode (choose"
740                                 " either live or non-live")
741
742 NODE_PLACEMENT_OPT = cli_option("-n", "--node", dest="node",
743                                 help="Target node and optional secondary node",
744                                 metavar="<pnode>[:<snode>]",
745                                 completion_suggest=OPT_COMPL_INST_ADD_NODES)
746
747 NODE_LIST_OPT = cli_option("-n", "--node", dest="nodes", default=[],
748                            action="append", metavar="<node>",
749                            help="Use only this node (can be used multiple"
750                            " times, if not given defaults to all nodes)",
751                            completion_suggest=OPT_COMPL_ONE_NODE)
752
753 NODEGROUP_OPT = cli_option("-g", "--node-group",
754                            dest="nodegroup",
755                            help="Node group (name or uuid)",
756                            metavar="<nodegroup>",
757                            default=None, type="string",
758                            completion_suggest=OPT_COMPL_ONE_NODEGROUP)
759
760 SINGLE_NODE_OPT = cli_option("-n", "--node", dest="node", help="Target node",
761                              metavar="<node>",
762                              completion_suggest=OPT_COMPL_ONE_NODE)
763
764 NOSTART_OPT = cli_option("--no-start", dest="start", default=True,
765                          action="store_false",
766                          help="Don't start the instance after creation")
767
768 SHOWCMD_OPT = cli_option("--show-cmd", dest="show_command",
769                          action="store_true", default=False,
770                          help="Show command instead of executing it")
771
772 CLEANUP_OPT = cli_option("--cleanup", dest="cleanup",
773                          default=False, action="store_true",
774                          help="Instead of performing the migration, try to"
775                          " recover from a failed cleanup. This is safe"
776                          " to run even if the instance is healthy, but it"
777                          " will create extra replication traffic and "
778                          " disrupt briefly the replication (like during the"
779                          " migration")
780
781 STATIC_OPT = cli_option("-s", "--static", dest="static",
782                         action="store_true", default=False,
783                         help="Only show configuration data, not runtime data")
784
785 ALL_OPT = cli_option("--all", dest="show_all",
786                      default=False, action="store_true",
787                      help="Show info on all instances on the cluster."
788                      " This can take a long time to run, use wisely")
789
790 SELECT_OS_OPT = cli_option("--select-os", dest="select_os",
791                            action="store_true", default=False,
792                            help="Interactive OS reinstall, lists available"
793                            " OS templates for selection")
794
795 IGNORE_FAILURES_OPT = cli_option("--ignore-failures", dest="ignore_failures",
796                                  action="store_true", default=False,
797                                  help="Remove the instance from the cluster"
798                                  " configuration even if there are failures"
799                                  " during the removal process")
800
801 IGNORE_REMOVE_FAILURES_OPT = cli_option("--ignore-remove-failures",
802                                         dest="ignore_remove_failures",
803                                         action="store_true", default=False,
804                                         help="Remove the instance from the"
805                                         " cluster configuration even if there"
806                                         " are failures during the removal"
807                                         " process")
808
809 REMOVE_INSTANCE_OPT = cli_option("--remove-instance", dest="remove_instance",
810                                  action="store_true", default=False,
811                                  help="Remove the instance from the cluster")
812
813 NEW_SECONDARY_OPT = cli_option("-n", "--new-secondary", dest="dst_node",
814                                help="Specifies the new secondary node",
815                                metavar="NODE", default=None,
816                                completion_suggest=OPT_COMPL_ONE_NODE)
817
818 ON_PRIMARY_OPT = cli_option("-p", "--on-primary", dest="on_primary",
819                             default=False, action="store_true",
820                             help="Replace the disk(s) on the primary"
821                             " node (only for the drbd template)")
822
823 ON_SECONDARY_OPT = cli_option("-s", "--on-secondary", dest="on_secondary",
824                               default=False, action="store_true",
825                               help="Replace the disk(s) on the secondary"
826                               " node (only for the drbd template)")
827
828 AUTO_PROMOTE_OPT = cli_option("--auto-promote", dest="auto_promote",
829                               default=False, action="store_true",
830                               help="Lock all nodes and auto-promote as needed"
831                               " to MC status")
832
833 AUTO_REPLACE_OPT = cli_option("-a", "--auto", dest="auto",
834                               default=False, action="store_true",
835                               help="Automatically replace faulty disks"
836                               " (only for the drbd template)")
837
838 IGNORE_SIZE_OPT = cli_option("--ignore-size", dest="ignore_size",
839                              default=False, action="store_true",
840                              help="Ignore current recorded size"
841                              " (useful for forcing activation when"
842                              " the recorded size is wrong)")
843
844 SRC_NODE_OPT = cli_option("--src-node", dest="src_node", help="Source node",
845                           metavar="<node>",
846                           completion_suggest=OPT_COMPL_ONE_NODE)
847
848 SRC_DIR_OPT = cli_option("--src-dir", dest="src_dir", help="Source directory",
849                          metavar="<dir>")
850
851 SECONDARY_IP_OPT = cli_option("-s", "--secondary-ip", dest="secondary_ip",
852                               help="Specify the secondary ip for the node",
853                               metavar="ADDRESS", default=None)
854
855 READD_OPT = cli_option("--readd", dest="readd",
856                        default=False, action="store_true",
857                        help="Readd old node after replacing it")
858
859 NOSSH_KEYCHECK_OPT = cli_option("--no-ssh-key-check", dest="ssh_key_check",
860                                 default=True, action="store_false",
861                                 help="Disable SSH key fingerprint checking")
862
863 MC_OPT = cli_option("-C", "--master-candidate", dest="master_candidate",
864                     type="bool", default=None, metavar=_YORNO,
865                     help="Set the master_candidate flag on the node")
866
867 OFFLINE_OPT = cli_option("-O", "--offline", dest="offline", metavar=_YORNO,
868                          type="bool", default=None,
869                          help=("Set the offline flag on the node"
870                                " (cluster does not communicate with offline"
871                                " nodes)"))
872
873 DRAINED_OPT = cli_option("-D", "--drained", dest="drained", metavar=_YORNO,
874                          type="bool", default=None,
875                          help=("Set the drained flag on the node"
876                                " (excluded from allocation operations)"))
877
878 CAPAB_MASTER_OPT = cli_option("--master-capable", dest="master_capable",
879                     type="bool", default=None, metavar=_YORNO,
880                     help="Set the master_capable flag on the node")
881
882 CAPAB_VM_OPT = cli_option("--vm-capable", dest="vm_capable",
883                     type="bool", default=None, metavar=_YORNO,
884                     help="Set the vm_capable flag on the node")
885
886 ALLOCATABLE_OPT = cli_option("--allocatable", dest="allocatable",
887                              type="bool", default=None, metavar=_YORNO,
888                              help="Set the allocatable flag on a volume")
889
890 NOLVM_STORAGE_OPT = cli_option("--no-lvm-storage", dest="lvm_storage",
891                                help="Disable support for lvm based instances"
892                                " (cluster-wide)",
893                                action="store_false", default=True)
894
895 ENABLED_HV_OPT = cli_option("--enabled-hypervisors",
896                             dest="enabled_hypervisors",
897                             help="Comma-separated list of hypervisors",
898                             type="string", default=None)
899
900 NIC_PARAMS_OPT = cli_option("-N", "--nic-parameters", dest="nicparams",
901                             type="keyval", default={},
902                             help="NIC parameters")
903
904 CP_SIZE_OPT = cli_option("-C", "--candidate-pool-size", default=None,
905                          dest="candidate_pool_size", type="int",
906                          help="Set the candidate pool size")
907
908 VG_NAME_OPT = cli_option("--vg-name", dest="vg_name",
909                          help=("Enables LVM and specifies the volume group"
910                                " name (cluster-wide) for disk allocation"
911                                " [%s]" % constants.DEFAULT_VG),
912                          metavar="VG", default=None)
913
914 YES_DOIT_OPT = cli_option("--yes-do-it", dest="yes_do_it",
915                           help="Destroy cluster", action="store_true")
916
917 NOVOTING_OPT = cli_option("--no-voting", dest="no_voting",
918                           help="Skip node agreement check (dangerous)",
919                           action="store_true", default=False)
920
921 MAC_PREFIX_OPT = cli_option("-m", "--mac-prefix", dest="mac_prefix",
922                             help="Specify the mac prefix for the instance IP"
923                             " addresses, in the format XX:XX:XX",
924                             metavar="PREFIX",
925                             default=None)
926
927 MASTER_NETDEV_OPT = cli_option("--master-netdev", dest="master_netdev",
928                                help="Specify the node interface (cluster-wide)"
929                                " on which the master IP address will be added "
930                                " [%s]" % constants.DEFAULT_BRIDGE,
931                                metavar="NETDEV",
932                                default=constants.DEFAULT_BRIDGE)
933
934 GLOBAL_FILEDIR_OPT = cli_option("--file-storage-dir", dest="file_storage_dir",
935                                 help="Specify the default directory (cluster-"
936                                 "wide) for storing the file-based disks [%s]" %
937                                 constants.DEFAULT_FILE_STORAGE_DIR,
938                                 metavar="DIR",
939                                 default=constants.DEFAULT_FILE_STORAGE_DIR)
940
941 NOMODIFY_ETCHOSTS_OPT = cli_option("--no-etc-hosts", dest="modify_etc_hosts",
942                                    help="Don't modify /etc/hosts",
943                                    action="store_false", default=True)
944
945 NOMODIFY_SSH_SETUP_OPT = cli_option("--no-ssh-init", dest="modify_ssh_setup",
946                                     help="Don't initialize SSH keys",
947                                     action="store_false", default=True)
948
949 ERROR_CODES_OPT = cli_option("--error-codes", dest="error_codes",
950                              help="Enable parseable error messages",
951                              action="store_true", default=False)
952
953 NONPLUS1_OPT = cli_option("--no-nplus1-mem", dest="skip_nplusone_mem",
954                           help="Skip N+1 memory redundancy tests",
955                           action="store_true", default=False)
956
957 REBOOT_TYPE_OPT = cli_option("-t", "--type", dest="reboot_type",
958                              help="Type of reboot: soft/hard/full",
959                              default=constants.INSTANCE_REBOOT_HARD,
960                              metavar="<REBOOT>",
961                              choices=list(constants.REBOOT_TYPES))
962
963 IGNORE_SECONDARIES_OPT = cli_option("--ignore-secondaries",
964                                     dest="ignore_secondaries",
965                                     default=False, action="store_true",
966                                     help="Ignore errors from secondaries")
967
968 NOSHUTDOWN_OPT = cli_option("--noshutdown", dest="shutdown",
969                             action="store_false", default=True,
970                             help="Don't shutdown the instance (unsafe)")
971
972 TIMEOUT_OPT = cli_option("--timeout", dest="timeout", type="int",
973                          default=constants.DEFAULT_SHUTDOWN_TIMEOUT,
974                          help="Maximum time to wait")
975
976 SHUTDOWN_TIMEOUT_OPT = cli_option("--shutdown-timeout",
977                          dest="shutdown_timeout", type="int",
978                          default=constants.DEFAULT_SHUTDOWN_TIMEOUT,
979                          help="Maximum time to wait for instance shutdown")
980
981 INTERVAL_OPT = cli_option("--interval", dest="interval", type="int",
982                           default=None,
983                           help=("Number of seconds between repetions of the"
984                                 " command"))
985
986 EARLY_RELEASE_OPT = cli_option("--early-release",
987                                dest="early_release", default=False,
988                                action="store_true",
989                                help="Release the locks on the secondary"
990                                " node(s) early")
991
992 NEW_CLUSTER_CERT_OPT = cli_option("--new-cluster-certificate",
993                                   dest="new_cluster_cert",
994                                   default=False, action="store_true",
995                                   help="Generate a new cluster certificate")
996
997 RAPI_CERT_OPT = cli_option("--rapi-certificate", dest="rapi_cert",
998                            default=None,
999                            help="File containing new RAPI certificate")
1000
1001 NEW_RAPI_CERT_OPT = cli_option("--new-rapi-certificate", dest="new_rapi_cert",
1002                                default=None, action="store_true",
1003                                help=("Generate a new self-signed RAPI"
1004                                      " certificate"))
1005
1006 NEW_CONFD_HMAC_KEY_OPT = cli_option("--new-confd-hmac-key",
1007                                     dest="new_confd_hmac_key",
1008                                     default=False, action="store_true",
1009                                     help=("Create a new HMAC key for %s" %
1010                                           constants.CONFD))
1011
1012 CLUSTER_DOMAIN_SECRET_OPT = cli_option("--cluster-domain-secret",
1013                                        dest="cluster_domain_secret",
1014                                        default=None,
1015                                        help=("Load new new cluster domain"
1016                                              " secret from file"))
1017
1018 NEW_CLUSTER_DOMAIN_SECRET_OPT = cli_option("--new-cluster-domain-secret",
1019                                            dest="new_cluster_domain_secret",
1020                                            default=False, action="store_true",
1021                                            help=("Create a new cluster domain"
1022                                                  " secret"))
1023
1024 USE_REPL_NET_OPT = cli_option("--use-replication-network",
1025                               dest="use_replication_network",
1026                               help="Whether to use the replication network"
1027                               " for talking to the nodes",
1028                               action="store_true", default=False)
1029
1030 MAINTAIN_NODE_HEALTH_OPT = \
1031     cli_option("--maintain-node-health", dest="maintain_node_health",
1032                metavar=_YORNO, default=None, type="bool",
1033                help="Configure the cluster to automatically maintain node"
1034                " health, by shutting down unknown instances, shutting down"
1035                " unknown DRBD devices, etc.")
1036
1037 IDENTIFY_DEFAULTS_OPT = \
1038     cli_option("--identify-defaults", dest="identify_defaults",
1039                default=False, action="store_true",
1040                help="Identify which saved instance parameters are equal to"
1041                " the current cluster defaults and set them as such, instead"
1042                " of marking them as overridden")
1043
1044 UIDPOOL_OPT = cli_option("--uid-pool", default=None,
1045                          action="store", dest="uid_pool",
1046                          help=("A list of user-ids or user-id"
1047                                " ranges separated by commas"))
1048
1049 ADD_UIDS_OPT = cli_option("--add-uids", default=None,
1050                           action="store", dest="add_uids",
1051                           help=("A list of user-ids or user-id"
1052                                 " ranges separated by commas, to be"
1053                                 " added to the user-id pool"))
1054
1055 REMOVE_UIDS_OPT = cli_option("--remove-uids", default=None,
1056                              action="store", dest="remove_uids",
1057                              help=("A list of user-ids or user-id"
1058                                    " ranges separated by commas, to be"
1059                                    " removed from the user-id pool"))
1060
1061 RESERVED_LVS_OPT = cli_option("--reserved-lvs", default=None,
1062                              action="store", dest="reserved_lvs",
1063                              help=("A comma-separated list of reserved"
1064                                    " logical volumes names, that will be"
1065                                    " ignored by cluster verify"))
1066
1067 ROMAN_OPT = cli_option("--roman",
1068                        dest="roman_integers", default=False,
1069                        action="store_true",
1070                        help="Use roman numbers for positive integers")
1071
1072 DRBD_HELPER_OPT = cli_option("--drbd-usermode-helper", dest="drbd_helper",
1073                              action="store", default=None,
1074                              help="Specifies usermode helper for DRBD")
1075
1076 NODRBD_STORAGE_OPT = cli_option("--no-drbd-storage", dest="drbd_storage",
1077                                 action="store_false", default=True,
1078                                 help="Disable support for DRBD")
1079
1080 PRIMARY_IP_VERSION_OPT = \
1081     cli_option("--primary-ip-version", default=constants.IP4_VERSION,
1082                action="store", dest="primary_ip_version",
1083                metavar="%d|%d" % (constants.IP4_VERSION,
1084                                   constants.IP6_VERSION),
1085                help="Cluster-wide IP version for primary IP")
1086
1087 PRIORITY_OPT = cli_option("--priority", default=None, dest="priority",
1088                           metavar="|".join(name for name, _ in _PRIORITY_NAMES),
1089                           choices=_PRIONAME_TO_VALUE.keys(),
1090                           help="Priority for opcode processing")
1091
1092 HID_OS_OPT = cli_option("--hidden", dest="hidden",
1093                         type="bool", default=None, metavar=_YORNO,
1094                         help="Sets the hidden flag on the OS")
1095
1096 BLK_OS_OPT = cli_option("--blacklisted", dest="blacklisted",
1097                         type="bool", default=None, metavar=_YORNO,
1098                         help="Sets the blacklisted flag on the OS")
1099
1100 PREALLOC_WIPE_DISKS_OPT = cli_option("--prealloc-wipe-disks", default=None,
1101                                      type="bool", metavar=_YORNO,
1102                                      dest="prealloc_wipe_disks",
1103                                      help=("Wipe disks prior to instance"
1104                                            " creation"))
1105
1106
1107 #: Options provided by all commands
1108 COMMON_OPTS = [DEBUG_OPT]
1109
1110 # common options for creating instances. add and import then add their own
1111 # specific ones.
1112 COMMON_CREATE_OPTS = [
1113   BACKEND_OPT,
1114   DISK_OPT,
1115   DISK_TEMPLATE_OPT,
1116   FILESTORE_DIR_OPT,
1117   FILESTORE_DRIVER_OPT,
1118   HYPERVISOR_OPT,
1119   IALLOCATOR_OPT,
1120   NET_OPT,
1121   NODE_PLACEMENT_OPT,
1122   NOIPCHECK_OPT,
1123   NONAMECHECK_OPT,
1124   NONICS_OPT,
1125   NWSYNC_OPT,
1126   OSPARAMS_OPT,
1127   OS_SIZE_OPT,
1128   SUBMIT_OPT,
1129   DRY_RUN_OPT,
1130   PRIORITY_OPT,
1131   ]
1132
1133
1134 def _ParseArgs(argv, commands, aliases):
1135   """Parser for the command line arguments.
1136
1137   This function parses the arguments and returns the function which
1138   must be executed together with its (modified) arguments.
1139
1140   @param argv: the command line
1141   @param commands: dictionary with special contents, see the design
1142       doc for cmdline handling
1143   @param aliases: dictionary with command aliases {'alias': 'target, ...}
1144
1145   """
1146   if len(argv) == 0:
1147     binary = "<command>"
1148   else:
1149     binary = argv[0].split("/")[-1]
1150
1151   if len(argv) > 1 and argv[1] == "--version":
1152     ToStdout("%s (ganeti %s) %s", binary, constants.VCS_VERSION,
1153              constants.RELEASE_VERSION)
1154     # Quit right away. That way we don't have to care about this special
1155     # argument. optparse.py does it the same.
1156     sys.exit(0)
1157
1158   if len(argv) < 2 or not (argv[1] in commands or
1159                            argv[1] in aliases):
1160     # let's do a nice thing
1161     sortedcmds = commands.keys()
1162     sortedcmds.sort()
1163
1164     ToStdout("Usage: %s {command} [options...] [argument...]", binary)
1165     ToStdout("%s <command> --help to see details, or man %s", binary, binary)
1166     ToStdout("")
1167
1168     # compute the max line length for cmd + usage
1169     mlen = max([len(" %s" % cmd) for cmd in commands])
1170     mlen = min(60, mlen) # should not get here...
1171
1172     # and format a nice command list
1173     ToStdout("Commands:")
1174     for cmd in sortedcmds:
1175       cmdstr = " %s" % (cmd,)
1176       help_text = commands[cmd][4]
1177       help_lines = textwrap.wrap(help_text, 79 - 3 - mlen)
1178       ToStdout("%-*s - %s", mlen, cmdstr, help_lines.pop(0))
1179       for line in help_lines:
1180         ToStdout("%-*s   %s", mlen, "", line)
1181
1182     ToStdout("")
1183
1184     return None, None, None
1185
1186   # get command, unalias it, and look it up in commands
1187   cmd = argv.pop(1)
1188   if cmd in aliases:
1189     if cmd in commands:
1190       raise errors.ProgrammerError("Alias '%s' overrides an existing"
1191                                    " command" % cmd)
1192
1193     if aliases[cmd] not in commands:
1194       raise errors.ProgrammerError("Alias '%s' maps to non-existing"
1195                                    " command '%s'" % (cmd, aliases[cmd]))
1196
1197     cmd = aliases[cmd]
1198
1199   func, args_def, parser_opts, usage, description = commands[cmd]
1200   parser = OptionParser(option_list=parser_opts + COMMON_OPTS,
1201                         description=description,
1202                         formatter=TitledHelpFormatter(),
1203                         usage="%%prog %s %s" % (cmd, usage))
1204   parser.disable_interspersed_args()
1205   options, args = parser.parse_args()
1206
1207   if not _CheckArguments(cmd, args_def, args):
1208     return None, None, None
1209
1210   return func, options, args
1211
1212
1213 def _CheckArguments(cmd, args_def, args):
1214   """Verifies the arguments using the argument definition.
1215
1216   Algorithm:
1217
1218     1. Abort with error if values specified by user but none expected.
1219
1220     1. For each argument in definition
1221
1222       1. Keep running count of minimum number of values (min_count)
1223       1. Keep running count of maximum number of values (max_count)
1224       1. If it has an unlimited number of values
1225
1226         1. Abort with error if it's not the last argument in the definition
1227
1228     1. If last argument has limited number of values
1229
1230       1. Abort with error if number of values doesn't match or is too large
1231
1232     1. Abort with error if user didn't pass enough values (min_count)
1233
1234   """
1235   if args and not args_def:
1236     ToStderr("Error: Command %s expects no arguments", cmd)
1237     return False
1238
1239   min_count = None
1240   max_count = None
1241   check_max = None
1242
1243   last_idx = len(args_def) - 1
1244
1245   for idx, arg in enumerate(args_def):
1246     if min_count is None:
1247       min_count = arg.min
1248     elif arg.min is not None:
1249       min_count += arg.min
1250
1251     if max_count is None:
1252       max_count = arg.max
1253     elif arg.max is not None:
1254       max_count += arg.max
1255
1256     if idx == last_idx:
1257       check_max = (arg.max is not None)
1258
1259     elif arg.max is None:
1260       raise errors.ProgrammerError("Only the last argument can have max=None")
1261
1262   if check_max:
1263     # Command with exact number of arguments
1264     if (min_count is not None and max_count is not None and
1265         min_count == max_count and len(args) != min_count):
1266       ToStderr("Error: Command %s expects %d argument(s)", cmd, min_count)
1267       return False
1268
1269     # Command with limited number of arguments
1270     if max_count is not None and len(args) > max_count:
1271       ToStderr("Error: Command %s expects only %d argument(s)",
1272                cmd, max_count)
1273       return False
1274
1275   # Command with some required arguments
1276   if min_count is not None and len(args) < min_count:
1277     ToStderr("Error: Command %s expects at least %d argument(s)",
1278              cmd, min_count)
1279     return False
1280
1281   return True
1282
1283
1284 def SplitNodeOption(value):
1285   """Splits the value of a --node option.
1286
1287   """
1288   if value and ':' in value:
1289     return value.split(':', 1)
1290   else:
1291     return (value, None)
1292
1293
1294 def CalculateOSNames(os_name, os_variants):
1295   """Calculates all the names an OS can be called, according to its variants.
1296
1297   @type os_name: string
1298   @param os_name: base name of the os
1299   @type os_variants: list or None
1300   @param os_variants: list of supported variants
1301   @rtype: list
1302   @return: list of valid names
1303
1304   """
1305   if os_variants:
1306     return ['%s+%s' % (os_name, v) for v in os_variants]
1307   else:
1308     return [os_name]
1309
1310
1311 def ParseFields(selected, default):
1312   """Parses the values of "--field"-like options.
1313
1314   @type selected: string or None
1315   @param selected: User-selected options
1316   @type default: list
1317   @param default: Default fields
1318
1319   """
1320   if selected is None:
1321     return default
1322
1323   if selected.startswith("+"):
1324     return default + selected[1:].split(",")
1325
1326   return selected.split(",")
1327
1328
1329 UsesRPC = rpc.RunWithRPC
1330
1331
1332 def AskUser(text, choices=None):
1333   """Ask the user a question.
1334
1335   @param text: the question to ask
1336
1337   @param choices: list with elements tuples (input_char, return_value,
1338       description); if not given, it will default to: [('y', True,
1339       'Perform the operation'), ('n', False, 'Do no do the operation')];
1340       note that the '?' char is reserved for help
1341
1342   @return: one of the return values from the choices list; if input is
1343       not possible (i.e. not running with a tty, we return the last
1344       entry from the list
1345
1346   """
1347   if choices is None:
1348     choices = [('y', True, 'Perform the operation'),
1349                ('n', False, 'Do not perform the operation')]
1350   if not choices or not isinstance(choices, list):
1351     raise errors.ProgrammerError("Invalid choices argument to AskUser")
1352   for entry in choices:
1353     if not isinstance(entry, tuple) or len(entry) < 3 or entry[0] == '?':
1354       raise errors.ProgrammerError("Invalid choices element to AskUser")
1355
1356   answer = choices[-1][1]
1357   new_text = []
1358   for line in text.splitlines():
1359     new_text.append(textwrap.fill(line, 70, replace_whitespace=False))
1360   text = "\n".join(new_text)
1361   try:
1362     f = file("/dev/tty", "a+")
1363   except IOError:
1364     return answer
1365   try:
1366     chars = [entry[0] for entry in choices]
1367     chars[-1] = "[%s]" % chars[-1]
1368     chars.append('?')
1369     maps = dict([(entry[0], entry[1]) for entry in choices])
1370     while True:
1371       f.write(text)
1372       f.write('\n')
1373       f.write("/".join(chars))
1374       f.write(": ")
1375       line = f.readline(2).strip().lower()
1376       if line in maps:
1377         answer = maps[line]
1378         break
1379       elif line == '?':
1380         for entry in choices:
1381           f.write(" %s - %s\n" % (entry[0], entry[2]))
1382         f.write("\n")
1383         continue
1384   finally:
1385     f.close()
1386   return answer
1387
1388
1389 class JobSubmittedException(Exception):
1390   """Job was submitted, client should exit.
1391
1392   This exception has one argument, the ID of the job that was
1393   submitted. The handler should print this ID.
1394
1395   This is not an error, just a structured way to exit from clients.
1396
1397   """
1398
1399
1400 def SendJob(ops, cl=None):
1401   """Function to submit an opcode without waiting for the results.
1402
1403   @type ops: list
1404   @param ops: list of opcodes
1405   @type cl: luxi.Client
1406   @param cl: the luxi client to use for communicating with the master;
1407              if None, a new client will be created
1408
1409   """
1410   if cl is None:
1411     cl = GetClient()
1412
1413   job_id = cl.SubmitJob(ops)
1414
1415   return job_id
1416
1417
1418 def GenericPollJob(job_id, cbs, report_cbs):
1419   """Generic job-polling function.
1420
1421   @type job_id: number
1422   @param job_id: Job ID
1423   @type cbs: Instance of L{JobPollCbBase}
1424   @param cbs: Data callbacks
1425   @type report_cbs: Instance of L{JobPollReportCbBase}
1426   @param report_cbs: Reporting callbacks
1427
1428   """
1429   prev_job_info = None
1430   prev_logmsg_serial = None
1431
1432   status = None
1433
1434   while True:
1435     result = cbs.WaitForJobChangeOnce(job_id, ["status"], prev_job_info,
1436                                       prev_logmsg_serial)
1437     if not result:
1438       # job not found, go away!
1439       raise errors.JobLost("Job with id %s lost" % job_id)
1440
1441     if result == constants.JOB_NOTCHANGED:
1442       report_cbs.ReportNotChanged(job_id, status)
1443
1444       # Wait again
1445       continue
1446
1447     # Split result, a tuple of (field values, log entries)
1448     (job_info, log_entries) = result
1449     (status, ) = job_info
1450
1451     if log_entries:
1452       for log_entry in log_entries:
1453         (serial, timestamp, log_type, message) = log_entry
1454         report_cbs.ReportLogMessage(job_id, serial, timestamp,
1455                                     log_type, message)
1456         prev_logmsg_serial = max(prev_logmsg_serial, serial)
1457
1458     # TODO: Handle canceled and archived jobs
1459     elif status in (constants.JOB_STATUS_SUCCESS,
1460                     constants.JOB_STATUS_ERROR,
1461                     constants.JOB_STATUS_CANCELING,
1462                     constants.JOB_STATUS_CANCELED):
1463       break
1464
1465     prev_job_info = job_info
1466
1467   jobs = cbs.QueryJobs([job_id], ["status", "opstatus", "opresult"])
1468   if not jobs:
1469     raise errors.JobLost("Job with id %s lost" % job_id)
1470
1471   status, opstatus, result = jobs[0]
1472
1473   if status == constants.JOB_STATUS_SUCCESS:
1474     return result
1475
1476   if status in (constants.JOB_STATUS_CANCELING, constants.JOB_STATUS_CANCELED):
1477     raise errors.OpExecError("Job was canceled")
1478
1479   has_ok = False
1480   for idx, (status, msg) in enumerate(zip(opstatus, result)):
1481     if status == constants.OP_STATUS_SUCCESS:
1482       has_ok = True
1483     elif status == constants.OP_STATUS_ERROR:
1484       errors.MaybeRaise(msg)
1485
1486       if has_ok:
1487         raise errors.OpExecError("partial failure (opcode %d): %s" %
1488                                  (idx, msg))
1489
1490       raise errors.OpExecError(str(msg))
1491
1492   # default failure mode
1493   raise errors.OpExecError(result)
1494
1495
1496 class JobPollCbBase:
1497   """Base class for L{GenericPollJob} callbacks.
1498
1499   """
1500   def __init__(self):
1501     """Initializes this class.
1502
1503     """
1504
1505   def WaitForJobChangeOnce(self, job_id, fields,
1506                            prev_job_info, prev_log_serial):
1507     """Waits for changes on a job.
1508
1509     """
1510     raise NotImplementedError()
1511
1512   def QueryJobs(self, job_ids, fields):
1513     """Returns the selected fields for the selected job IDs.
1514
1515     @type job_ids: list of numbers
1516     @param job_ids: Job IDs
1517     @type fields: list of strings
1518     @param fields: Fields
1519
1520     """
1521     raise NotImplementedError()
1522
1523
1524 class JobPollReportCbBase:
1525   """Base class for L{GenericPollJob} reporting callbacks.
1526
1527   """
1528   def __init__(self):
1529     """Initializes this class.
1530
1531     """
1532
1533   def ReportLogMessage(self, job_id, serial, timestamp, log_type, log_msg):
1534     """Handles a log message.
1535
1536     """
1537     raise NotImplementedError()
1538
1539   def ReportNotChanged(self, job_id, status):
1540     """Called for if a job hasn't changed in a while.
1541
1542     @type job_id: number
1543     @param job_id: Job ID
1544     @type status: string or None
1545     @param status: Job status if available
1546
1547     """
1548     raise NotImplementedError()
1549
1550
1551 class _LuxiJobPollCb(JobPollCbBase):
1552   def __init__(self, cl):
1553     """Initializes this class.
1554
1555     """
1556     JobPollCbBase.__init__(self)
1557     self.cl = cl
1558
1559   def WaitForJobChangeOnce(self, job_id, fields,
1560                            prev_job_info, prev_log_serial):
1561     """Waits for changes on a job.
1562
1563     """
1564     return self.cl.WaitForJobChangeOnce(job_id, fields,
1565                                         prev_job_info, prev_log_serial)
1566
1567   def QueryJobs(self, job_ids, fields):
1568     """Returns the selected fields for the selected job IDs.
1569
1570     """
1571     return self.cl.QueryJobs(job_ids, fields)
1572
1573
1574 class FeedbackFnJobPollReportCb(JobPollReportCbBase):
1575   def __init__(self, feedback_fn):
1576     """Initializes this class.
1577
1578     """
1579     JobPollReportCbBase.__init__(self)
1580
1581     self.feedback_fn = feedback_fn
1582
1583     assert callable(feedback_fn)
1584
1585   def ReportLogMessage(self, job_id, serial, timestamp, log_type, log_msg):
1586     """Handles a log message.
1587
1588     """
1589     self.feedback_fn((timestamp, log_type, log_msg))
1590
1591   def ReportNotChanged(self, job_id, status):
1592     """Called if a job hasn't changed in a while.
1593
1594     """
1595     # Ignore
1596
1597
1598 class StdioJobPollReportCb(JobPollReportCbBase):
1599   def __init__(self):
1600     """Initializes this class.
1601
1602     """
1603     JobPollReportCbBase.__init__(self)
1604
1605     self.notified_queued = False
1606     self.notified_waitlock = False
1607
1608   def ReportLogMessage(self, job_id, serial, timestamp, log_type, log_msg):
1609     """Handles a log message.
1610
1611     """
1612     ToStdout("%s %s", time.ctime(utils.MergeTime(timestamp)),
1613              FormatLogMessage(log_type, log_msg))
1614
1615   def ReportNotChanged(self, job_id, status):
1616     """Called if a job hasn't changed in a while.
1617
1618     """
1619     if status is None:
1620       return
1621
1622     if status == constants.JOB_STATUS_QUEUED and not self.notified_queued:
1623       ToStderr("Job %s is waiting in queue", job_id)
1624       self.notified_queued = True
1625
1626     elif status == constants.JOB_STATUS_WAITLOCK and not self.notified_waitlock:
1627       ToStderr("Job %s is trying to acquire all necessary locks", job_id)
1628       self.notified_waitlock = True
1629
1630
1631 def FormatLogMessage(log_type, log_msg):
1632   """Formats a job message according to its type.
1633
1634   """
1635   if log_type != constants.ELOG_MESSAGE:
1636     log_msg = str(log_msg)
1637
1638   return utils.SafeEncode(log_msg)
1639
1640
1641 def PollJob(job_id, cl=None, feedback_fn=None, reporter=None):
1642   """Function to poll for the result of a job.
1643
1644   @type job_id: job identified
1645   @param job_id: the job to poll for results
1646   @type cl: luxi.Client
1647   @param cl: the luxi client to use for communicating with the master;
1648              if None, a new client will be created
1649
1650   """
1651   if cl is None:
1652     cl = GetClient()
1653
1654   if reporter is None:
1655     if feedback_fn:
1656       reporter = FeedbackFnJobPollReportCb(feedback_fn)
1657     else:
1658       reporter = StdioJobPollReportCb()
1659   elif feedback_fn:
1660     raise errors.ProgrammerError("Can't specify reporter and feedback function")
1661
1662   return GenericPollJob(job_id, _LuxiJobPollCb(cl), reporter)
1663
1664
1665 def SubmitOpCode(op, cl=None, feedback_fn=None, opts=None, reporter=None):
1666   """Legacy function to submit an opcode.
1667
1668   This is just a simple wrapper over the construction of the processor
1669   instance. It should be extended to better handle feedback and
1670   interaction functions.
1671
1672   """
1673   if cl is None:
1674     cl = GetClient()
1675
1676   SetGenericOpcodeOpts([op], opts)
1677
1678   job_id = SendJob([op], cl=cl)
1679
1680   op_results = PollJob(job_id, cl=cl, feedback_fn=feedback_fn,
1681                        reporter=reporter)
1682
1683   return op_results[0]
1684
1685
1686 def SubmitOrSend(op, opts, cl=None, feedback_fn=None):
1687   """Wrapper around SubmitOpCode or SendJob.
1688
1689   This function will decide, based on the 'opts' parameter, whether to
1690   submit and wait for the result of the opcode (and return it), or
1691   whether to just send the job and print its identifier. It is used in
1692   order to simplify the implementation of the '--submit' option.
1693
1694   It will also process the opcodes if we're sending the via SendJob
1695   (otherwise SubmitOpCode does it).
1696
1697   """
1698   if opts and opts.submit_only:
1699     job = [op]
1700     SetGenericOpcodeOpts(job, opts)
1701     job_id = SendJob(job, cl=cl)
1702     raise JobSubmittedException(job_id)
1703   else:
1704     return SubmitOpCode(op, cl=cl, feedback_fn=feedback_fn, opts=opts)
1705
1706
1707 def SetGenericOpcodeOpts(opcode_list, options):
1708   """Processor for generic options.
1709
1710   This function updates the given opcodes based on generic command
1711   line options (like debug, dry-run, etc.).
1712
1713   @param opcode_list: list of opcodes
1714   @param options: command line options or None
1715   @return: None (in-place modification)
1716
1717   """
1718   if not options:
1719     return
1720   for op in opcode_list:
1721     op.debug_level = options.debug
1722     if hasattr(options, "dry_run"):
1723       op.dry_run = options.dry_run
1724     if getattr(options, "priority", None) is not None:
1725       op.priority = _PRIONAME_TO_VALUE[options.priority]
1726
1727
1728 def GetClient():
1729   # TODO: Cache object?
1730   try:
1731     client = luxi.Client()
1732   except luxi.NoMasterError:
1733     ss = ssconf.SimpleStore()
1734
1735     # Try to read ssconf file
1736     try:
1737       ss.GetMasterNode()
1738     except errors.ConfigurationError:
1739       raise errors.OpPrereqError("Cluster not initialized or this machine is"
1740                                  " not part of a cluster")
1741
1742     master, myself = ssconf.GetMasterAndMyself(ss=ss)
1743     if master != myself:
1744       raise errors.OpPrereqError("This is not the master node, please connect"
1745                                  " to node '%s' and rerun the command" %
1746                                  master)
1747     raise
1748   return client
1749
1750
1751 def FormatError(err):
1752   """Return a formatted error message for a given error.
1753
1754   This function takes an exception instance and returns a tuple
1755   consisting of two values: first, the recommended exit code, and
1756   second, a string describing the error message (not
1757   newline-terminated).
1758
1759   """
1760   retcode = 1
1761   obuf = StringIO()
1762   msg = str(err)
1763   if isinstance(err, errors.ConfigurationError):
1764     txt = "Corrupt configuration file: %s" % msg
1765     logging.error(txt)
1766     obuf.write(txt + "\n")
1767     obuf.write("Aborting.")
1768     retcode = 2
1769   elif isinstance(err, errors.HooksAbort):
1770     obuf.write("Failure: hooks execution failed:\n")
1771     for node, script, out in err.args[0]:
1772       if out:
1773         obuf.write("  node: %s, script: %s, output: %s\n" %
1774                    (node, script, out))
1775       else:
1776         obuf.write("  node: %s, script: %s (no output)\n" %
1777                    (node, script))
1778   elif isinstance(err, errors.HooksFailure):
1779     obuf.write("Failure: hooks general failure: %s" % msg)
1780   elif isinstance(err, errors.ResolverError):
1781     this_host = netutils.Hostname.GetSysName()
1782     if err.args[0] == this_host:
1783       msg = "Failure: can't resolve my own hostname ('%s')"
1784     else:
1785       msg = "Failure: can't resolve hostname '%s'"
1786     obuf.write(msg % err.args[0])
1787   elif isinstance(err, errors.OpPrereqError):
1788     if len(err.args) == 2:
1789       obuf.write("Failure: prerequisites not met for this"
1790                " operation:\nerror type: %s, error details:\n%s" %
1791                  (err.args[1], err.args[0]))
1792     else:
1793       obuf.write("Failure: prerequisites not met for this"
1794                  " operation:\n%s" % msg)
1795   elif isinstance(err, errors.OpExecError):
1796     obuf.write("Failure: command execution error:\n%s" % msg)
1797   elif isinstance(err, errors.TagError):
1798     obuf.write("Failure: invalid tag(s) given:\n%s" % msg)
1799   elif isinstance(err, errors.JobQueueDrainError):
1800     obuf.write("Failure: the job queue is marked for drain and doesn't"
1801                " accept new requests\n")
1802   elif isinstance(err, errors.JobQueueFull):
1803     obuf.write("Failure: the job queue is full and doesn't accept new"
1804                " job submissions until old jobs are archived\n")
1805   elif isinstance(err, errors.TypeEnforcementError):
1806     obuf.write("Parameter Error: %s" % msg)
1807   elif isinstance(err, errors.ParameterError):
1808     obuf.write("Failure: unknown/wrong parameter name '%s'" % msg)
1809   elif isinstance(err, luxi.NoMasterError):
1810     obuf.write("Cannot communicate with the master daemon.\nIs it running"
1811                " and listening for connections?")
1812   elif isinstance(err, luxi.TimeoutError):
1813     obuf.write("Timeout while talking to the master daemon. Jobs might have"
1814                " been submitted and will continue to run even if the call"
1815                " timed out. Useful commands in this situation are \"gnt-job"
1816                " list\", \"gnt-job cancel\" and \"gnt-job watch\". Error:\n")
1817     obuf.write(msg)
1818   elif isinstance(err, luxi.PermissionError):
1819     obuf.write("It seems you don't have permissions to connect to the"
1820                " master daemon.\nPlease retry as a different user.")
1821   elif isinstance(err, luxi.ProtocolError):
1822     obuf.write("Unhandled protocol error while talking to the master daemon:\n"
1823                "%s" % msg)
1824   elif isinstance(err, errors.JobLost):
1825     obuf.write("Error checking job status: %s" % msg)
1826   elif isinstance(err, errors.GenericError):
1827     obuf.write("Unhandled Ganeti error: %s" % msg)
1828   elif isinstance(err, JobSubmittedException):
1829     obuf.write("JobID: %s\n" % err.args[0])
1830     retcode = 0
1831   else:
1832     obuf.write("Unhandled exception: %s" % msg)
1833   return retcode, obuf.getvalue().rstrip('\n')
1834
1835
1836 def GenericMain(commands, override=None, aliases=None):
1837   """Generic main function for all the gnt-* commands.
1838
1839   Arguments:
1840     - commands: a dictionary with a special structure, see the design doc
1841                 for command line handling.
1842     - override: if not None, we expect a dictionary with keys that will
1843                 override command line options; this can be used to pass
1844                 options from the scripts to generic functions
1845     - aliases: dictionary with command aliases {'alias': 'target, ...}
1846
1847   """
1848   # save the program name and the entire command line for later logging
1849   if sys.argv:
1850     binary = os.path.basename(sys.argv[0]) or sys.argv[0]
1851     if len(sys.argv) >= 2:
1852       binary += " " + sys.argv[1]
1853       old_cmdline = " ".join(sys.argv[2:])
1854     else:
1855       old_cmdline = ""
1856   else:
1857     binary = "<unknown program>"
1858     old_cmdline = ""
1859
1860   if aliases is None:
1861     aliases = {}
1862
1863   try:
1864     func, options, args = _ParseArgs(sys.argv, commands, aliases)
1865   except errors.ParameterError, err:
1866     result, err_msg = FormatError(err)
1867     ToStderr(err_msg)
1868     return 1
1869
1870   if func is None: # parse error
1871     return 1
1872
1873   if override is not None:
1874     for key, val in override.iteritems():
1875       setattr(options, key, val)
1876
1877   utils.SetupLogging(constants.LOG_COMMANDS, debug=options.debug,
1878                      stderr_logging=True, program=binary)
1879
1880   if old_cmdline:
1881     logging.info("run with arguments '%s'", old_cmdline)
1882   else:
1883     logging.info("run with no arguments")
1884
1885   try:
1886     result = func(options, args)
1887   except (errors.GenericError, luxi.ProtocolError,
1888           JobSubmittedException), err:
1889     result, err_msg = FormatError(err)
1890     logging.exception("Error during command processing")
1891     ToStderr(err_msg)
1892
1893   return result
1894
1895
1896 def ParseNicOption(optvalue):
1897   """Parses the value of the --net option(s).
1898
1899   """
1900   try:
1901     nic_max = max(int(nidx[0]) + 1 for nidx in optvalue)
1902   except (TypeError, ValueError), err:
1903     raise errors.OpPrereqError("Invalid NIC index passed: %s" % str(err))
1904
1905   nics = [{}] * nic_max
1906   for nidx, ndict in optvalue:
1907     nidx = int(nidx)
1908
1909     if not isinstance(ndict, dict):
1910       raise errors.OpPrereqError("Invalid nic/%d value: expected dict,"
1911                                  " got %s" % (nidx, ndict))
1912
1913     utils.ForceDictType(ndict, constants.INIC_PARAMS_TYPES)
1914
1915     nics[nidx] = ndict
1916
1917   return nics
1918
1919
1920 def GenericInstanceCreate(mode, opts, args):
1921   """Add an instance to the cluster via either creation or import.
1922
1923   @param mode: constants.INSTANCE_CREATE or constants.INSTANCE_IMPORT
1924   @param opts: the command line options selected by the user
1925   @type args: list
1926   @param args: should contain only one element, the new instance name
1927   @rtype: int
1928   @return: the desired exit code
1929
1930   """
1931   instance = args[0]
1932
1933   (pnode, snode) = SplitNodeOption(opts.node)
1934
1935   hypervisor = None
1936   hvparams = {}
1937   if opts.hypervisor:
1938     hypervisor, hvparams = opts.hypervisor
1939
1940   if opts.nics:
1941     nics = ParseNicOption(opts.nics)
1942   elif opts.no_nics:
1943     # no nics
1944     nics = []
1945   elif mode == constants.INSTANCE_CREATE:
1946     # default of one nic, all auto
1947     nics = [{}]
1948   else:
1949     # mode == import
1950     nics = []
1951
1952   if opts.disk_template == constants.DT_DISKLESS:
1953     if opts.disks or opts.sd_size is not None:
1954       raise errors.OpPrereqError("Diskless instance but disk"
1955                                  " information passed")
1956     disks = []
1957   else:
1958     if (not opts.disks and not opts.sd_size
1959         and mode == constants.INSTANCE_CREATE):
1960       raise errors.OpPrereqError("No disk information specified")
1961     if opts.disks and opts.sd_size is not None:
1962       raise errors.OpPrereqError("Please use either the '--disk' or"
1963                                  " '-s' option")
1964     if opts.sd_size is not None:
1965       opts.disks = [(0, {"size": opts.sd_size})]
1966
1967     if opts.disks:
1968       try:
1969         disk_max = max(int(didx[0]) + 1 for didx in opts.disks)
1970       except ValueError, err:
1971         raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err))
1972       disks = [{}] * disk_max
1973     else:
1974       disks = []
1975     for didx, ddict in opts.disks:
1976       didx = int(didx)
1977       if not isinstance(ddict, dict):
1978         msg = "Invalid disk/%d value: expected dict, got %s" % (didx, ddict)
1979         raise errors.OpPrereqError(msg)
1980       elif "size" in ddict:
1981         if "adopt" in ddict:
1982           raise errors.OpPrereqError("Only one of 'size' and 'adopt' allowed"
1983                                      " (disk %d)" % didx)
1984         try:
1985           ddict["size"] = utils.ParseUnit(ddict["size"])
1986         except ValueError, err:
1987           raise errors.OpPrereqError("Invalid disk size for disk %d: %s" %
1988                                      (didx, err))
1989       elif "adopt" in ddict:
1990         if mode == constants.INSTANCE_IMPORT:
1991           raise errors.OpPrereqError("Disk adoption not allowed for instance"
1992                                      " import")
1993         ddict["size"] = 0
1994       else:
1995         raise errors.OpPrereqError("Missing size or adoption source for"
1996                                    " disk %d" % didx)
1997       disks[didx] = ddict
1998
1999   utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_TYPES)
2000   utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
2001
2002   if mode == constants.INSTANCE_CREATE:
2003     start = opts.start
2004     os_type = opts.os
2005     force_variant = opts.force_variant
2006     src_node = None
2007     src_path = None
2008     no_install = opts.no_install
2009     identify_defaults = False
2010   elif mode == constants.INSTANCE_IMPORT:
2011     start = False
2012     os_type = None
2013     force_variant = False
2014     src_node = opts.src_node
2015     src_path = opts.src_dir
2016     no_install = None
2017     identify_defaults = opts.identify_defaults
2018   else:
2019     raise errors.ProgrammerError("Invalid creation mode %s" % mode)
2020
2021   op = opcodes.OpCreateInstance(instance_name=instance,
2022                                 disks=disks,
2023                                 disk_template=opts.disk_template,
2024                                 nics=nics,
2025                                 pnode=pnode, snode=snode,
2026                                 ip_check=opts.ip_check,
2027                                 name_check=opts.name_check,
2028                                 wait_for_sync=opts.wait_for_sync,
2029                                 file_storage_dir=opts.file_storage_dir,
2030                                 file_driver=opts.file_driver,
2031                                 iallocator=opts.iallocator,
2032                                 hypervisor=hypervisor,
2033                                 hvparams=hvparams,
2034                                 beparams=opts.beparams,
2035                                 osparams=opts.osparams,
2036                                 mode=mode,
2037                                 start=start,
2038                                 os_type=os_type,
2039                                 force_variant=force_variant,
2040                                 src_node=src_node,
2041                                 src_path=src_path,
2042                                 no_install=no_install,
2043                                 identify_defaults=identify_defaults)
2044
2045   SubmitOrSend(op, opts)
2046   return 0
2047
2048
2049 class _RunWhileClusterStoppedHelper:
2050   """Helper class for L{RunWhileClusterStopped} to simplify state management
2051
2052   """
2053   def __init__(self, feedback_fn, cluster_name, master_node, online_nodes):
2054     """Initializes this class.
2055
2056     @type feedback_fn: callable
2057     @param feedback_fn: Feedback function
2058     @type cluster_name: string
2059     @param cluster_name: Cluster name
2060     @type master_node: string
2061     @param master_node Master node name
2062     @type online_nodes: list
2063     @param online_nodes: List of names of online nodes
2064
2065     """
2066     self.feedback_fn = feedback_fn
2067     self.cluster_name = cluster_name
2068     self.master_node = master_node
2069     self.online_nodes = online_nodes
2070
2071     self.ssh = ssh.SshRunner(self.cluster_name)
2072
2073     self.nonmaster_nodes = [name for name in online_nodes
2074                             if name != master_node]
2075
2076     assert self.master_node not in self.nonmaster_nodes
2077
2078   def _RunCmd(self, node_name, cmd):
2079     """Runs a command on the local or a remote machine.
2080
2081     @type node_name: string
2082     @param node_name: Machine name
2083     @type cmd: list
2084     @param cmd: Command
2085
2086     """
2087     if node_name is None or node_name == self.master_node:
2088       # No need to use SSH
2089       result = utils.RunCmd(cmd)
2090     else:
2091       result = self.ssh.Run(node_name, "root", utils.ShellQuoteArgs(cmd))
2092
2093     if result.failed:
2094       errmsg = ["Failed to run command %s" % result.cmd]
2095       if node_name:
2096         errmsg.append("on node %s" % node_name)
2097       errmsg.append(": exitcode %s and error %s" %
2098                     (result.exit_code, result.output))
2099       raise errors.OpExecError(" ".join(errmsg))
2100
2101   def Call(self, fn, *args):
2102     """Call function while all daemons are stopped.
2103
2104     @type fn: callable
2105     @param fn: Function to be called
2106
2107     """
2108     # Pause watcher by acquiring an exclusive lock on watcher state file
2109     self.feedback_fn("Blocking watcher")
2110     watcher_block = utils.FileLock.Open(constants.WATCHER_STATEFILE)
2111     try:
2112       # TODO: Currently, this just blocks. There's no timeout.
2113       # TODO: Should it be a shared lock?
2114       watcher_block.Exclusive(blocking=True)
2115
2116       # Stop master daemons, so that no new jobs can come in and all running
2117       # ones are finished
2118       self.feedback_fn("Stopping master daemons")
2119       self._RunCmd(None, [constants.DAEMON_UTIL, "stop-master"])
2120       try:
2121         # Stop daemons on all nodes
2122         for node_name in self.online_nodes:
2123           self.feedback_fn("Stopping daemons on %s" % node_name)
2124           self._RunCmd(node_name, [constants.DAEMON_UTIL, "stop-all"])
2125
2126         # All daemons are shut down now
2127         try:
2128           return fn(self, *args)
2129         except Exception, err:
2130           _, errmsg = FormatError(err)
2131           logging.exception("Caught exception")
2132           self.feedback_fn(errmsg)
2133           raise
2134       finally:
2135         # Start cluster again, master node last
2136         for node_name in self.nonmaster_nodes + [self.master_node]:
2137           self.feedback_fn("Starting daemons on %s" % node_name)
2138           self._RunCmd(node_name, [constants.DAEMON_UTIL, "start-all"])
2139     finally:
2140       # Resume watcher
2141       watcher_block.Close()
2142
2143
2144 def RunWhileClusterStopped(feedback_fn, fn, *args):
2145   """Calls a function while all cluster daemons are stopped.
2146
2147   @type feedback_fn: callable
2148   @param feedback_fn: Feedback function
2149   @type fn: callable
2150   @param fn: Function to be called when daemons are stopped
2151
2152   """
2153   feedback_fn("Gathering cluster information")
2154
2155   # This ensures we're running on the master daemon
2156   cl = GetClient()
2157
2158   (cluster_name, master_node) = \
2159     cl.QueryConfigValues(["cluster_name", "master_node"])
2160
2161   online_nodes = GetOnlineNodes([], cl=cl)
2162
2163   # Don't keep a reference to the client. The master daemon will go away.
2164   del cl
2165
2166   assert master_node in online_nodes
2167
2168   return _RunWhileClusterStoppedHelper(feedback_fn, cluster_name, master_node,
2169                                        online_nodes).Call(fn, *args)
2170
2171
2172 def GenerateTable(headers, fields, separator, data,
2173                   numfields=None, unitfields=None,
2174                   units=None):
2175   """Prints a table with headers and different fields.
2176
2177   @type headers: dict
2178   @param headers: dictionary mapping field names to headers for
2179       the table
2180   @type fields: list
2181   @param fields: the field names corresponding to each row in
2182       the data field
2183   @param separator: the separator to be used; if this is None,
2184       the default 'smart' algorithm is used which computes optimal
2185       field width, otherwise just the separator is used between
2186       each field
2187   @type data: list
2188   @param data: a list of lists, each sublist being one row to be output
2189   @type numfields: list
2190   @param numfields: a list with the fields that hold numeric
2191       values and thus should be right-aligned
2192   @type unitfields: list
2193   @param unitfields: a list with the fields that hold numeric
2194       values that should be formatted with the units field
2195   @type units: string or None
2196   @param units: the units we should use for formatting, or None for
2197       automatic choice (human-readable for non-separator usage, otherwise
2198       megabytes); this is a one-letter string
2199
2200   """
2201   if units is None:
2202     if separator:
2203       units = "m"
2204     else:
2205       units = "h"
2206
2207   if numfields is None:
2208     numfields = []
2209   if unitfields is None:
2210     unitfields = []
2211
2212   numfields = utils.FieldSet(*numfields)   # pylint: disable-msg=W0142
2213   unitfields = utils.FieldSet(*unitfields) # pylint: disable-msg=W0142
2214
2215   format_fields = []
2216   for field in fields:
2217     if headers and field not in headers:
2218       # TODO: handle better unknown fields (either revert to old
2219       # style of raising exception, or deal more intelligently with
2220       # variable fields)
2221       headers[field] = field
2222     if separator is not None:
2223       format_fields.append("%s")
2224     elif numfields.Matches(field):
2225       format_fields.append("%*s")
2226     else:
2227       format_fields.append("%-*s")
2228
2229   if separator is None:
2230     mlens = [0 for name in fields]
2231     format_str = ' '.join(format_fields)
2232   else:
2233     format_str = separator.replace("%", "%%").join(format_fields)
2234
2235   for row in data:
2236     if row is None:
2237       continue
2238     for idx, val in enumerate(row):
2239       if unitfields.Matches(fields[idx]):
2240         try:
2241           val = int(val)
2242         except (TypeError, ValueError):
2243           pass
2244         else:
2245           val = row[idx] = utils.FormatUnit(val, units)
2246       val = row[idx] = str(val)
2247       if separator is None:
2248         mlens[idx] = max(mlens[idx], len(val))
2249
2250   result = []
2251   if headers:
2252     args = []
2253     for idx, name in enumerate(fields):
2254       hdr = headers[name]
2255       if separator is None:
2256         mlens[idx] = max(mlens[idx], len(hdr))
2257         args.append(mlens[idx])
2258       args.append(hdr)
2259     result.append(format_str % tuple(args))
2260
2261   if separator is None:
2262     assert len(mlens) == len(fields)
2263
2264     if fields and not numfields.Matches(fields[-1]):
2265       mlens[-1] = 0
2266
2267   for line in data:
2268     args = []
2269     if line is None:
2270       line = ['-' for _ in fields]
2271     for idx in range(len(fields)):
2272       if separator is None:
2273         args.append(mlens[idx])
2274       args.append(line[idx])
2275     result.append(format_str % tuple(args))
2276
2277   return result
2278
2279
2280 def FormatTimestamp(ts):
2281   """Formats a given timestamp.
2282
2283   @type ts: timestamp
2284   @param ts: a timeval-type timestamp, a tuple of seconds and microseconds
2285
2286   @rtype: string
2287   @return: a string with the formatted timestamp
2288
2289   """
2290   if not isinstance (ts, (tuple, list)) or len(ts) != 2:
2291     return '?'
2292   sec, usec = ts
2293   return time.strftime("%F %T", time.localtime(sec)) + ".%06d" % usec
2294
2295
2296 def ParseTimespec(value):
2297   """Parse a time specification.
2298
2299   The following suffixed will be recognized:
2300
2301     - s: seconds
2302     - m: minutes
2303     - h: hours
2304     - d: day
2305     - w: weeks
2306
2307   Without any suffix, the value will be taken to be in seconds.
2308
2309   """
2310   value = str(value)
2311   if not value:
2312     raise errors.OpPrereqError("Empty time specification passed")
2313   suffix_map = {
2314     's': 1,
2315     'm': 60,
2316     'h': 3600,
2317     'd': 86400,
2318     'w': 604800,
2319     }
2320   if value[-1] not in suffix_map:
2321     try:
2322       value = int(value)
2323     except (TypeError, ValueError):
2324       raise errors.OpPrereqError("Invalid time specification '%s'" % value)
2325   else:
2326     multiplier = suffix_map[value[-1]]
2327     value = value[:-1]
2328     if not value: # no data left after stripping the suffix
2329       raise errors.OpPrereqError("Invalid time specification (only"
2330                                  " suffix passed)")
2331     try:
2332       value = int(value) * multiplier
2333     except (TypeError, ValueError):
2334       raise errors.OpPrereqError("Invalid time specification '%s'" % value)
2335   return value
2336
2337
2338 def GetOnlineNodes(nodes, cl=None, nowarn=False, secondary_ips=False,
2339                    filter_master=False):
2340   """Returns the names of online nodes.
2341
2342   This function will also log a warning on stderr with the names of
2343   the online nodes.
2344
2345   @param nodes: if not empty, use only this subset of nodes (minus the
2346       offline ones)
2347   @param cl: if not None, luxi client to use
2348   @type nowarn: boolean
2349   @param nowarn: by default, this function will output a note with the
2350       offline nodes that are skipped; if this parameter is True the
2351       note is not displayed
2352   @type secondary_ips: boolean
2353   @param secondary_ips: if True, return the secondary IPs instead of the
2354       names, useful for doing network traffic over the replication interface
2355       (if any)
2356   @type filter_master: boolean
2357   @param filter_master: if True, do not return the master node in the list
2358       (useful in coordination with secondary_ips where we cannot check our
2359       node name against the list)
2360
2361   """
2362   if cl is None:
2363     cl = GetClient()
2364
2365   if secondary_ips:
2366     name_idx = 2
2367   else:
2368     name_idx = 0
2369
2370   if filter_master:
2371     master_node = cl.QueryConfigValues(["master_node"])[0]
2372     filter_fn = lambda x: x != master_node
2373   else:
2374     filter_fn = lambda _: True
2375
2376   result = cl.QueryNodes(names=nodes, fields=["name", "offline", "sip"],
2377                          use_locking=False)
2378   offline = [row[0] for row in result if row[1]]
2379   if offline and not nowarn:
2380     ToStderr("Note: skipping offline node(s): %s" % utils.CommaJoin(offline))
2381   return [row[name_idx] for row in result if not row[1] and filter_fn(row[0])]
2382
2383
2384 def _ToStream(stream, txt, *args):
2385   """Write a message to a stream, bypassing the logging system
2386
2387   @type stream: file object
2388   @param stream: the file to which we should write
2389   @type txt: str
2390   @param txt: the message
2391
2392   """
2393   if args:
2394     args = tuple(args)
2395     stream.write(txt % args)
2396   else:
2397     stream.write(txt)
2398   stream.write('\n')
2399   stream.flush()
2400
2401
2402 def ToStdout(txt, *args):
2403   """Write a message to stdout only, bypassing the logging system
2404
2405   This is just a wrapper over _ToStream.
2406
2407   @type txt: str
2408   @param txt: the message
2409
2410   """
2411   _ToStream(sys.stdout, txt, *args)
2412
2413
2414 def ToStderr(txt, *args):
2415   """Write a message to stderr only, bypassing the logging system
2416
2417   This is just a wrapper over _ToStream.
2418
2419   @type txt: str
2420   @param txt: the message
2421
2422   """
2423   _ToStream(sys.stderr, txt, *args)
2424
2425
2426 class JobExecutor(object):
2427   """Class which manages the submission and execution of multiple jobs.
2428
2429   Note that instances of this class should not be reused between
2430   GetResults() calls.
2431
2432   """
2433   def __init__(self, cl=None, verbose=True, opts=None, feedback_fn=None):
2434     self.queue = []
2435     if cl is None:
2436       cl = GetClient()
2437     self.cl = cl
2438     self.verbose = verbose
2439     self.jobs = []
2440     self.opts = opts
2441     self.feedback_fn = feedback_fn
2442
2443   def QueueJob(self, name, *ops):
2444     """Record a job for later submit.
2445
2446     @type name: string
2447     @param name: a description of the job, will be used in WaitJobSet
2448     """
2449     SetGenericOpcodeOpts(ops, self.opts)
2450     self.queue.append((name, ops))
2451
2452   def SubmitPending(self, each=False):
2453     """Submit all pending jobs.
2454
2455     """
2456     if each:
2457       results = []
2458       for row in self.queue:
2459         # SubmitJob will remove the success status, but raise an exception if
2460         # the submission fails, so we'll notice that anyway.
2461         results.append([True, self.cl.SubmitJob(row[1])])
2462     else:
2463       results = self.cl.SubmitManyJobs([row[1] for row in self.queue])
2464     for (idx, ((status, data), (name, _))) in enumerate(zip(results,
2465                                                             self.queue)):
2466       self.jobs.append((idx, status, data, name))
2467
2468   def _ChooseJob(self):
2469     """Choose a non-waiting/queued job to poll next.
2470
2471     """
2472     assert self.jobs, "_ChooseJob called with empty job list"
2473
2474     result = self.cl.QueryJobs([i[2] for i in self.jobs], ["status"])
2475     assert result
2476
2477     for job_data, status in zip(self.jobs, result):
2478       if (isinstance(status, list) and status and
2479           status[0] in (constants.JOB_STATUS_QUEUED,
2480                         constants.JOB_STATUS_WAITLOCK,
2481                         constants.JOB_STATUS_CANCELING)):
2482         # job is still present and waiting
2483         continue
2484       # good candidate found (either running job or lost job)
2485       self.jobs.remove(job_data)
2486       return job_data
2487
2488     # no job found
2489     return self.jobs.pop(0)
2490
2491   def GetResults(self):
2492     """Wait for and return the results of all jobs.
2493
2494     @rtype: list
2495     @return: list of tuples (success, job results), in the same order
2496         as the submitted jobs; if a job has failed, instead of the result
2497         there will be the error message
2498
2499     """
2500     if not self.jobs:
2501       self.SubmitPending()
2502     results = []
2503     if self.verbose:
2504       ok_jobs = [row[2] for row in self.jobs if row[1]]
2505       if ok_jobs:
2506         ToStdout("Submitted jobs %s", utils.CommaJoin(ok_jobs))
2507
2508     # first, remove any non-submitted jobs
2509     self.jobs, failures = compat.partition(self.jobs, lambda x: x[1])
2510     for idx, _, jid, name in failures:
2511       ToStderr("Failed to submit job for %s: %s", name, jid)
2512       results.append((idx, False, jid))
2513
2514     while self.jobs:
2515       (idx, _, jid, name) = self._ChooseJob()
2516       ToStdout("Waiting for job %s for %s...", jid, name)
2517       try:
2518         job_result = PollJob(jid, cl=self.cl, feedback_fn=self.feedback_fn)
2519         success = True
2520       except errors.JobLost, err:
2521         _, job_result = FormatError(err)
2522         ToStderr("Job %s for %s has been archived, cannot check its result",
2523                  jid, name)
2524         success = False
2525       except (errors.GenericError, luxi.ProtocolError), err:
2526         _, job_result = FormatError(err)
2527         success = False
2528         # the error message will always be shown, verbose or not
2529         ToStderr("Job %s for %s has failed: %s", jid, name, job_result)
2530
2531       results.append((idx, success, job_result))
2532
2533     # sort based on the index, then drop it
2534     results.sort()
2535     results = [i[1:] for i in results]
2536
2537     return results
2538
2539   def WaitOrShow(self, wait):
2540     """Wait for job results or only print the job IDs.
2541
2542     @type wait: boolean
2543     @param wait: whether to wait or not
2544
2545     """
2546     if wait:
2547       return self.GetResults()
2548     else:
2549       if not self.jobs:
2550         self.SubmitPending()
2551       for _, status, result, name in self.jobs:
2552         if status:
2553           ToStdout("%s: %s", result, name)
2554         else:
2555           ToStderr("Failure for %s: %s", name, result)
2556       return [row[1:3] for row in self.jobs]