Merge branch 'devel-2.1'
[ganeti-local] / lib / cli.py
1 #
2 #
3
4 # Copyright (C) 2006, 2007 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
40 from optparse import (OptionParser, TitledHelpFormatter,
41                       Option, OptionValueError)
42
43
44 __all__ = [
45   # Command line options
46   "ALLOCATABLE_OPT",
47   "ALL_OPT",
48   "AUTO_REPLACE_OPT",
49   "BACKEND_OPT",
50   "CLEANUP_OPT",
51   "CONFIRM_OPT",
52   "CP_SIZE_OPT",
53   "DEBUG_OPT",
54   "DEBUG_SIMERR_OPT",
55   "DISKIDX_OPT",
56   "DISK_OPT",
57   "DISK_TEMPLATE_OPT",
58   "DRAINED_OPT",
59   "EARLY_RELEASE_OPT",
60   "ENABLED_HV_OPT",
61   "ERROR_CODES_OPT",
62   "FIELDS_OPT",
63   "FILESTORE_DIR_OPT",
64   "FILESTORE_DRIVER_OPT",
65   "FORCE_OPT",
66   "FORCE_VARIANT_OPT",
67   "GLOBAL_FILEDIR_OPT",
68   "HVLIST_OPT",
69   "HVOPTS_OPT",
70   "HYPERVISOR_OPT",
71   "IALLOCATOR_OPT",
72   "IGNORE_CONSIST_OPT",
73   "IGNORE_FAILURES_OPT",
74   "IGNORE_REMOVE_FAILURES_OPT",
75   "IGNORE_SECONDARIES_OPT",
76   "IGNORE_SIZE_OPT",
77   "MAC_PREFIX_OPT",
78   "MASTER_NETDEV_OPT",
79   "MC_OPT",
80   "NET_OPT",
81   "NEW_SECONDARY_OPT",
82   "NIC_PARAMS_OPT",
83   "NODE_LIST_OPT",
84   "NODE_PLACEMENT_OPT",
85   "NOHDR_OPT",
86   "NOIPCHECK_OPT",
87   "NONAMECHECK_OPT",
88   "NOLVM_STORAGE_OPT",
89   "NOMODIFY_ETCHOSTS_OPT",
90   "NOMODIFY_SSH_SETUP_OPT",
91   "NONICS_OPT",
92   "NONLIVE_OPT",
93   "NONPLUS1_OPT",
94   "NOSHUTDOWN_OPT",
95   "NOSTART_OPT",
96   "NOSSH_KEYCHECK_OPT",
97   "NOVOTING_OPT",
98   "NWSYNC_OPT",
99   "ON_PRIMARY_OPT",
100   "ON_SECONDARY_OPT",
101   "OFFLINE_OPT",
102   "OS_OPT",
103   "OS_SIZE_OPT",
104   "READD_OPT",
105   "REBOOT_TYPE_OPT",
106   "REMOVE_INSTANCE_OPT",
107   "SECONDARY_IP_OPT",
108   "SELECT_OS_OPT",
109   "SEP_OPT",
110   "SHOWCMD_OPT",
111   "SHUTDOWN_TIMEOUT_OPT",
112   "SINGLE_NODE_OPT",
113   "SRC_DIR_OPT",
114   "SRC_NODE_OPT",
115   "SUBMIT_OPT",
116   "STATIC_OPT",
117   "SYNC_OPT",
118   "TAG_SRC_OPT",
119   "TIMEOUT_OPT",
120   "USEUNITS_OPT",
121   "VERBOSE_OPT",
122   "VG_NAME_OPT",
123   "YES_DOIT_OPT",
124   # Generic functions for CLI programs
125   "GenericMain",
126   "GenericInstanceCreate",
127   "GetClient",
128   "GetOnlineNodes",
129   "JobExecutor",
130   "JobSubmittedException",
131   "ParseTimespec",
132   "SubmitOpCode",
133   "SubmitOrSend",
134   "UsesRPC",
135   # Formatting functions
136   "ToStderr", "ToStdout",
137   "FormatError",
138   "GenerateTable",
139   "AskUser",
140   "FormatTimestamp",
141   # Tags functions
142   "ListTags",
143   "AddTags",
144   "RemoveTags",
145   # command line options support infrastructure
146   "ARGS_MANY_INSTANCES",
147   "ARGS_MANY_NODES",
148   "ARGS_NONE",
149   "ARGS_ONE_INSTANCE",
150   "ARGS_ONE_NODE",
151   "ArgChoice",
152   "ArgCommand",
153   "ArgFile",
154   "ArgHost",
155   "ArgInstance",
156   "ArgJobId",
157   "ArgNode",
158   "ArgSuggest",
159   "ArgUnknown",
160   "OPT_COMPL_INST_ADD_NODES",
161   "OPT_COMPL_MANY_NODES",
162   "OPT_COMPL_ONE_IALLOCATOR",
163   "OPT_COMPL_ONE_INSTANCE",
164   "OPT_COMPL_ONE_NODE",
165   "OPT_COMPL_ONE_OS",
166   "cli_option",
167   "SplitNodeOption",
168   "CalculateOSNames",
169   ]
170
171 NO_PREFIX = "no_"
172 UN_PREFIX = "-"
173
174
175 class _Argument:
176   def __init__(self, min=0, max=None): # pylint: disable-msg=W0622
177     self.min = min
178     self.max = max
179
180   def __repr__(self):
181     return ("<%s min=%s max=%s>" %
182             (self.__class__.__name__, self.min, self.max))
183
184
185 class ArgSuggest(_Argument):
186   """Suggesting argument.
187
188   Value can be any of the ones passed to the constructor.
189
190   """
191   # pylint: disable-msg=W0622
192   def __init__(self, min=0, max=None, choices=None):
193     _Argument.__init__(self, min=min, max=max)
194     self.choices = choices
195
196   def __repr__(self):
197     return ("<%s min=%s max=%s choices=%r>" %
198             (self.__class__.__name__, self.min, self.max, self.choices))
199
200
201 class ArgChoice(ArgSuggest):
202   """Choice argument.
203
204   Value can be any of the ones passed to the constructor. Like L{ArgSuggest},
205   but value must be one of the choices.
206
207   """
208
209
210 class ArgUnknown(_Argument):
211   """Unknown argument to program (e.g. determined at runtime).
212
213   """
214
215
216 class ArgInstance(_Argument):
217   """Instances argument.
218
219   """
220
221
222 class ArgNode(_Argument):
223   """Node argument.
224
225   """
226
227 class ArgJobId(_Argument):
228   """Job ID argument.
229
230   """
231
232
233 class ArgFile(_Argument):
234   """File path argument.
235
236   """
237
238
239 class ArgCommand(_Argument):
240   """Command argument.
241
242   """
243
244
245 class ArgHost(_Argument):
246   """Host argument.
247
248   """
249
250
251 ARGS_NONE = []
252 ARGS_MANY_INSTANCES = [ArgInstance()]
253 ARGS_MANY_NODES = [ArgNode()]
254 ARGS_ONE_INSTANCE = [ArgInstance(min=1, max=1)]
255 ARGS_ONE_NODE = [ArgNode(min=1, max=1)]
256
257
258 def _ExtractTagsObject(opts, args):
259   """Extract the tag type object.
260
261   Note that this function will modify its args parameter.
262
263   """
264   if not hasattr(opts, "tag_type"):
265     raise errors.ProgrammerError("tag_type not passed to _ExtractTagsObject")
266   kind = opts.tag_type
267   if kind == constants.TAG_CLUSTER:
268     retval = kind, kind
269   elif kind == constants.TAG_NODE or kind == constants.TAG_INSTANCE:
270     if not args:
271       raise errors.OpPrereqError("no arguments passed to the command")
272     name = args.pop(0)
273     retval = kind, name
274   else:
275     raise errors.ProgrammerError("Unhandled tag type '%s'" % kind)
276   return retval
277
278
279 def _ExtendTags(opts, args):
280   """Extend the args if a source file has been given.
281
282   This function will extend the tags with the contents of the file
283   passed in the 'tags_source' attribute of the opts parameter. A file
284   named '-' will be replaced by stdin.
285
286   """
287   fname = opts.tags_source
288   if fname is None:
289     return
290   if fname == "-":
291     new_fh = sys.stdin
292   else:
293     new_fh = open(fname, "r")
294   new_data = []
295   try:
296     # we don't use the nice 'new_data = [line.strip() for line in fh]'
297     # because of python bug 1633941
298     while True:
299       line = new_fh.readline()
300       if not line:
301         break
302       new_data.append(line.strip())
303   finally:
304     new_fh.close()
305   args.extend(new_data)
306
307
308 def ListTags(opts, args):
309   """List the tags on a given object.
310
311   This is a generic implementation that knows how to deal with all
312   three cases of tag objects (cluster, node, instance). The opts
313   argument is expected to contain a tag_type field denoting what
314   object type we work on.
315
316   """
317   kind, name = _ExtractTagsObject(opts, args)
318   cl = GetClient()
319   result = cl.QueryTags(kind, name)
320   result = list(result)
321   result.sort()
322   for tag in result:
323     ToStdout(tag)
324
325
326 def AddTags(opts, args):
327   """Add tags on a given object.
328
329   This is a generic implementation that knows how to deal with all
330   three cases of tag objects (cluster, node, instance). The opts
331   argument is expected to contain a tag_type field denoting what
332   object type we work on.
333
334   """
335   kind, name = _ExtractTagsObject(opts, args)
336   _ExtendTags(opts, args)
337   if not args:
338     raise errors.OpPrereqError("No tags to be added")
339   op = opcodes.OpAddTags(kind=kind, name=name, tags=args)
340   SubmitOpCode(op)
341
342
343 def RemoveTags(opts, args):
344   """Remove tags from a given object.
345
346   This is a generic implementation that knows how to deal with all
347   three cases of tag objects (cluster, node, instance). The opts
348   argument is expected to contain a tag_type field denoting what
349   object type we work on.
350
351   """
352   kind, name = _ExtractTagsObject(opts, args)
353   _ExtendTags(opts, args)
354   if not args:
355     raise errors.OpPrereqError("No tags to be removed")
356   op = opcodes.OpDelTags(kind=kind, name=name, tags=args)
357   SubmitOpCode(op)
358
359
360 def check_unit(option, opt, value): # pylint: disable-msg=W0613
361   """OptParsers custom converter for units.
362
363   """
364   try:
365     return utils.ParseUnit(value)
366   except errors.UnitParseError, err:
367     raise OptionValueError("option %s: %s" % (opt, err))
368
369
370 def _SplitKeyVal(opt, data):
371   """Convert a KeyVal string into a dict.
372
373   This function will convert a key=val[,...] string into a dict. Empty
374   values will be converted specially: keys which have the prefix 'no_'
375   will have the value=False and the prefix stripped, the others will
376   have value=True.
377
378   @type opt: string
379   @param opt: a string holding the option name for which we process the
380       data, used in building error messages
381   @type data: string
382   @param data: a string of the format key=val,key=val,...
383   @rtype: dict
384   @return: {key=val, key=val}
385   @raises errors.ParameterError: if there are duplicate keys
386
387   """
388   kv_dict = {}
389   if data:
390     for elem in utils.UnescapeAndSplit(data, sep=","):
391       if "=" in elem:
392         key, val = elem.split("=", 1)
393       else:
394         if elem.startswith(NO_PREFIX):
395           key, val = elem[len(NO_PREFIX):], False
396         elif elem.startswith(UN_PREFIX):
397           key, val = elem[len(UN_PREFIX):], None
398         else:
399           key, val = elem, True
400       if key in kv_dict:
401         raise errors.ParameterError("Duplicate key '%s' in option %s" %
402                                     (key, opt))
403       kv_dict[key] = val
404   return kv_dict
405
406
407 def check_ident_key_val(option, opt, value):  # pylint: disable-msg=W0613
408   """Custom parser for ident:key=val,key=val options.
409
410   This will store the parsed values as a tuple (ident, {key: val}). As such,
411   multiple uses of this option via action=append is possible.
412
413   """
414   if ":" not in value:
415     ident, rest = value, ''
416   else:
417     ident, rest = value.split(":", 1)
418
419   if ident.startswith(NO_PREFIX):
420     if rest:
421       msg = "Cannot pass options when removing parameter groups: %s" % value
422       raise errors.ParameterError(msg)
423     retval = (ident[len(NO_PREFIX):], False)
424   elif ident.startswith(UN_PREFIX):
425     if rest:
426       msg = "Cannot pass options when removing parameter groups: %s" % value
427       raise errors.ParameterError(msg)
428     retval = (ident[len(UN_PREFIX):], None)
429   else:
430     kv_dict = _SplitKeyVal(opt, rest)
431     retval = (ident, kv_dict)
432   return retval
433
434
435 def check_key_val(option, opt, value):  # pylint: disable-msg=W0613
436   """Custom parser class for key=val,key=val options.
437
438   This will store the parsed values as a dict {key: val}.
439
440   """
441   return _SplitKeyVal(opt, value)
442
443
444 # completion_suggestion is normally a list. Using numeric values not evaluating
445 # to False for dynamic completion.
446 (OPT_COMPL_MANY_NODES,
447  OPT_COMPL_ONE_NODE,
448  OPT_COMPL_ONE_INSTANCE,
449  OPT_COMPL_ONE_OS,
450  OPT_COMPL_ONE_IALLOCATOR,
451  OPT_COMPL_INST_ADD_NODES) = range(100, 106)
452
453 OPT_COMPL_ALL = frozenset([
454   OPT_COMPL_MANY_NODES,
455   OPT_COMPL_ONE_NODE,
456   OPT_COMPL_ONE_INSTANCE,
457   OPT_COMPL_ONE_OS,
458   OPT_COMPL_ONE_IALLOCATOR,
459   OPT_COMPL_INST_ADD_NODES,
460   ])
461
462
463 class CliOption(Option):
464   """Custom option class for optparse.
465
466   """
467   ATTRS = Option.ATTRS + [
468     "completion_suggest",
469     ]
470   TYPES = Option.TYPES + (
471     "identkeyval",
472     "keyval",
473     "unit",
474     )
475   TYPE_CHECKER = Option.TYPE_CHECKER.copy()
476   TYPE_CHECKER["identkeyval"] = check_ident_key_val
477   TYPE_CHECKER["keyval"] = check_key_val
478   TYPE_CHECKER["unit"] = check_unit
479
480
481 # optparse.py sets make_option, so we do it for our own option class, too
482 cli_option = CliOption
483
484
485 _YESNO = ("yes", "no")
486 _YORNO = "yes|no"
487
488 DEBUG_OPT = cli_option("-d", "--debug", default=0, action="count",
489                        help="Increase debugging level")
490
491 NOHDR_OPT = cli_option("--no-headers", default=False,
492                        action="store_true", dest="no_headers",
493                        help="Don't display column headers")
494
495 SEP_OPT = cli_option("--separator", default=None,
496                      action="store", dest="separator",
497                      help=("Separator between output fields"
498                            " (defaults to one space)"))
499
500 USEUNITS_OPT = cli_option("--units", default=None,
501                           dest="units", choices=('h', 'm', 'g', 't'),
502                           help="Specify units for output (one of hmgt)")
503
504 FIELDS_OPT = cli_option("-o", "--output", dest="output", action="store",
505                         type="string", metavar="FIELDS",
506                         help="Comma separated list of output fields")
507
508 FORCE_OPT = cli_option("-f", "--force", dest="force", action="store_true",
509                        default=False, help="Force the operation")
510
511 CONFIRM_OPT = cli_option("--yes", dest="confirm", action="store_true",
512                          default=False, help="Do not require confirmation")
513
514 TAG_SRC_OPT = cli_option("--from", dest="tags_source",
515                          default=None, help="File with tag names")
516
517 SUBMIT_OPT = cli_option("--submit", dest="submit_only",
518                         default=False, action="store_true",
519                         help=("Submit the job and return the job ID, but"
520                               " don't wait for the job to finish"))
521
522 SYNC_OPT = cli_option("--sync", dest="do_locking",
523                       default=False, action="store_true",
524                       help=("Grab locks while doing the queries"
525                             " in order to ensure more consistent results"))
526
527 _DRY_RUN_OPT = cli_option("--dry-run", default=False,
528                           action="store_true",
529                           help=("Do not execute the operation, just run the"
530                                 " check steps and verify it it could be"
531                                 " executed"))
532
533 VERBOSE_OPT = cli_option("-v", "--verbose", default=False,
534                          action="store_true",
535                          help="Increase the verbosity of the operation")
536
537 DEBUG_SIMERR_OPT = cli_option("--debug-simulate-errors", default=False,
538                               action="store_true", dest="simulate_errors",
539                               help="Debugging option that makes the operation"
540                               " treat most runtime checks as failed")
541
542 NWSYNC_OPT = cli_option("--no-wait-for-sync", dest="wait_for_sync",
543                         default=True, action="store_false",
544                         help="Don't wait for sync (DANGEROUS!)")
545
546 DISK_TEMPLATE_OPT = cli_option("-t", "--disk-template", dest="disk_template",
547                                help="Custom disk setup (diskless, file,"
548                                " plain or drbd)",
549                                default=None, metavar="TEMPL",
550                                choices=list(constants.DISK_TEMPLATES))
551
552 NONICS_OPT = cli_option("--no-nics", default=False, action="store_true",
553                         help="Do not create any network cards for"
554                         " the instance")
555
556 FILESTORE_DIR_OPT = cli_option("--file-storage-dir", dest="file_storage_dir",
557                                help="Relative path under default cluster-wide"
558                                " file storage dir to store file-based disks",
559                                default=None, metavar="<DIR>")
560
561 FILESTORE_DRIVER_OPT = cli_option("--file-driver", dest="file_driver",
562                                   help="Driver to use for image files",
563                                   default="loop", metavar="<DRIVER>",
564                                   choices=list(constants.FILE_DRIVER))
565
566 IALLOCATOR_OPT = cli_option("-I", "--iallocator", metavar="<NAME>",
567                             help="Select nodes for the instance automatically"
568                             " using the <NAME> iallocator plugin",
569                             default=None, type="string",
570                             completion_suggest=OPT_COMPL_ONE_IALLOCATOR)
571
572 OS_OPT = cli_option("-o", "--os-type", dest="os", help="What OS to run",
573                     metavar="<os>",
574                     completion_suggest=OPT_COMPL_ONE_OS)
575
576 FORCE_VARIANT_OPT = cli_option("--force-variant", dest="force_variant",
577                                action="store_true", default=False,
578                                help="Force an unknown variant")
579
580 BACKEND_OPT = cli_option("-B", "--backend-parameters", dest="beparams",
581                          type="keyval", default={},
582                          help="Backend parameters")
583
584 HVOPTS_OPT =  cli_option("-H", "--hypervisor-parameters", type="keyval",
585                          default={}, dest="hvparams",
586                          help="Hypervisor parameters")
587
588 HYPERVISOR_OPT = cli_option("-H", "--hypervisor-parameters", dest="hypervisor",
589                             help="Hypervisor and hypervisor options, in the"
590                             " format hypervisor:option=value,option=value,...",
591                             default=None, type="identkeyval")
592
593 HVLIST_OPT = cli_option("-H", "--hypervisor-parameters", dest="hvparams",
594                         help="Hypervisor and hypervisor options, in the"
595                         " format hypervisor:option=value,option=value,...",
596                         default=[], action="append", type="identkeyval")
597
598 NOIPCHECK_OPT = cli_option("--no-ip-check", dest="ip_check", default=True,
599                            action="store_false",
600                            help="Don't check that the instance's IP"
601                            " is alive")
602
603 NONAMECHECK_OPT = cli_option("--no-name-check", dest="name_check",
604                              default=True, action="store_false",
605                              help="Don't check that the instance's name"
606                              " is resolvable")
607
608 NET_OPT = cli_option("--net",
609                      help="NIC parameters", default=[],
610                      dest="nics", action="append", type="identkeyval")
611
612 DISK_OPT = cli_option("--disk", help="Disk parameters", default=[],
613                       dest="disks", action="append", type="identkeyval")
614
615 DISKIDX_OPT = cli_option("--disks", dest="disks", default=None,
616                          help="Comma-separated list of disks"
617                          " indices to act on (e.g. 0,2) (optional,"
618                          " defaults to all disks)")
619
620 OS_SIZE_OPT = cli_option("-s", "--os-size", dest="sd_size",
621                          help="Enforces a single-disk configuration using the"
622                          " given disk size, in MiB unless a suffix is used",
623                          default=None, type="unit", metavar="<size>")
624
625 IGNORE_CONSIST_OPT = cli_option("--ignore-consistency",
626                                 dest="ignore_consistency",
627                                 action="store_true", default=False,
628                                 help="Ignore the consistency of the disks on"
629                                 " the secondary")
630
631 NONLIVE_OPT = cli_option("--non-live", dest="live",
632                          default=True, action="store_false",
633                          help="Do a non-live migration (this usually means"
634                          " freeze the instance, save the state, transfer and"
635                          " only then resume running on the secondary node)")
636
637 NODE_PLACEMENT_OPT = cli_option("-n", "--node", dest="node",
638                                 help="Target node and optional secondary node",
639                                 metavar="<pnode>[:<snode>]",
640                                 completion_suggest=OPT_COMPL_INST_ADD_NODES)
641
642 NODE_LIST_OPT = cli_option("-n", "--node", dest="nodes", default=[],
643                            action="append", metavar="<node>",
644                            help="Use only this node (can be used multiple"
645                            " times, if not given defaults to all nodes)",
646                            completion_suggest=OPT_COMPL_ONE_NODE)
647
648 SINGLE_NODE_OPT = cli_option("-n", "--node", dest="node", help="Target node",
649                              metavar="<node>",
650                              completion_suggest=OPT_COMPL_ONE_NODE)
651
652 NOSTART_OPT = cli_option("--no-start", dest="start", default=True,
653                          action="store_false",
654                          help="Don't start the instance after creation")
655
656 SHOWCMD_OPT = cli_option("--show-cmd", dest="show_command",
657                          action="store_true", default=False,
658                          help="Show command instead of executing it")
659
660 CLEANUP_OPT = cli_option("--cleanup", dest="cleanup",
661                          default=False, action="store_true",
662                          help="Instead of performing the migration, try to"
663                          " recover from a failed cleanup. This is safe"
664                          " to run even if the instance is healthy, but it"
665                          " will create extra replication traffic and "
666                          " disrupt briefly the replication (like during the"
667                          " migration")
668
669 STATIC_OPT = cli_option("-s", "--static", dest="static",
670                         action="store_true", default=False,
671                         help="Only show configuration data, not runtime data")
672
673 ALL_OPT = cli_option("--all", dest="show_all",
674                      default=False, action="store_true",
675                      help="Show info on all instances on the cluster."
676                      " This can take a long time to run, use wisely")
677
678 SELECT_OS_OPT = cli_option("--select-os", dest="select_os",
679                            action="store_true", default=False,
680                            help="Interactive OS reinstall, lists available"
681                            " OS templates for selection")
682
683 IGNORE_FAILURES_OPT = cli_option("--ignore-failures", dest="ignore_failures",
684                                  action="store_true", default=False,
685                                  help="Remove the instance from the cluster"
686                                  " configuration even if there are failures"
687                                  " during the removal process")
688
689 IGNORE_REMOVE_FAILURES_OPT = cli_option("--ignore-remove-failures",
690                                         dest="ignore_remove_failures",
691                                         action="store_true", default=False,
692                                         help="Remove the instance from the"
693                                         " cluster configuration even if there"
694                                         " are failures during the removal"
695                                         " process")
696
697 REMOVE_INSTANCE_OPT = cli_option("--remove-instance", dest="remove_instance",
698                                  action="store_true", default=False,
699                                  help="Remove the instance from the cluster")
700
701 NEW_SECONDARY_OPT = cli_option("-n", "--new-secondary", dest="dst_node",
702                                help="Specifies the new secondary node",
703                                metavar="NODE", default=None,
704                                completion_suggest=OPT_COMPL_ONE_NODE)
705
706 ON_PRIMARY_OPT = cli_option("-p", "--on-primary", dest="on_primary",
707                             default=False, action="store_true",
708                             help="Replace the disk(s) on the primary"
709                             " node (only for the drbd template)")
710
711 ON_SECONDARY_OPT = cli_option("-s", "--on-secondary", dest="on_secondary",
712                               default=False, action="store_true",
713                               help="Replace the disk(s) on the secondary"
714                               " node (only for the drbd template)")
715
716 AUTO_REPLACE_OPT = cli_option("-a", "--auto", dest="auto",
717                               default=False, action="store_true",
718                               help="Automatically replace faulty disks"
719                               " (only for the drbd template)")
720
721 IGNORE_SIZE_OPT = cli_option("--ignore-size", dest="ignore_size",
722                              default=False, action="store_true",
723                              help="Ignore current recorded size"
724                              " (useful for forcing activation when"
725                              " the recorded size is wrong)")
726
727 SRC_NODE_OPT = cli_option("--src-node", dest="src_node", help="Source node",
728                           metavar="<node>",
729                           completion_suggest=OPT_COMPL_ONE_NODE)
730
731 SRC_DIR_OPT = cli_option("--src-dir", dest="src_dir", help="Source directory",
732                          metavar="<dir>")
733
734 SECONDARY_IP_OPT = cli_option("-s", "--secondary-ip", dest="secondary_ip",
735                               help="Specify the secondary ip for the node",
736                               metavar="ADDRESS", default=None)
737
738 READD_OPT = cli_option("--readd", dest="readd",
739                        default=False, action="store_true",
740                        help="Readd old node after replacing it")
741
742 NOSSH_KEYCHECK_OPT = cli_option("--no-ssh-key-check", dest="ssh_key_check",
743                                 default=True, action="store_false",
744                                 help="Disable SSH key fingerprint checking")
745
746
747 MC_OPT = cli_option("-C", "--master-candidate", dest="master_candidate",
748                     choices=_YESNO, default=None, metavar=_YORNO,
749                     help="Set the master_candidate flag on the node")
750
751 OFFLINE_OPT = cli_option("-O", "--offline", dest="offline", metavar=_YORNO,
752                          choices=_YESNO, default=None,
753                          help="Set the offline flag on the node")
754
755 DRAINED_OPT = cli_option("-D", "--drained", dest="drained", metavar=_YORNO,
756                          choices=_YESNO, default=None,
757                          help="Set the drained flag on the node")
758
759 ALLOCATABLE_OPT = cli_option("--allocatable", dest="allocatable",
760                              choices=_YESNO, default=None, metavar=_YORNO,
761                              help="Set the allocatable flag on a volume")
762
763 NOLVM_STORAGE_OPT = cli_option("--no-lvm-storage", dest="lvm_storage",
764                                help="Disable support for lvm based instances"
765                                " (cluster-wide)",
766                                action="store_false", default=True)
767
768 ENABLED_HV_OPT = cli_option("--enabled-hypervisors",
769                             dest="enabled_hypervisors",
770                             help="Comma-separated list of hypervisors",
771                             type="string", default=None)
772
773 NIC_PARAMS_OPT = cli_option("-N", "--nic-parameters", dest="nicparams",
774                             type="keyval", default={},
775                             help="NIC parameters")
776
777 CP_SIZE_OPT = cli_option("-C", "--candidate-pool-size", default=None,
778                          dest="candidate_pool_size", type="int",
779                          help="Set the candidate pool size")
780
781 VG_NAME_OPT = cli_option("-g", "--vg-name", dest="vg_name",
782                          help="Enables LVM and specifies the volume group"
783                          " name (cluster-wide) for disk allocation [xenvg]",
784                          metavar="VG", default=None)
785
786 YES_DOIT_OPT = cli_option("--yes-do-it", dest="yes_do_it",
787                           help="Destroy cluster", action="store_true")
788
789 NOVOTING_OPT = cli_option("--no-voting", dest="no_voting",
790                           help="Skip node agreement check (dangerous)",
791                           action="store_true", default=False)
792
793 MAC_PREFIX_OPT = cli_option("-m", "--mac-prefix", dest="mac_prefix",
794                             help="Specify the mac prefix for the instance IP"
795                             " addresses, in the format XX:XX:XX",
796                             metavar="PREFIX",
797                             default=None)
798
799 MASTER_NETDEV_OPT = cli_option("--master-netdev", dest="master_netdev",
800                                help="Specify the node interface (cluster-wide)"
801                                " on which the master IP address will be added "
802                                " [%s]" % constants.DEFAULT_BRIDGE,
803                                metavar="NETDEV",
804                                default=constants.DEFAULT_BRIDGE)
805
806
807 GLOBAL_FILEDIR_OPT = cli_option("--file-storage-dir", dest="file_storage_dir",
808                                 help="Specify the default directory (cluster-"
809                                 "wide) for storing the file-based disks [%s]" %
810                                 constants.DEFAULT_FILE_STORAGE_DIR,
811                                 metavar="DIR",
812                                 default=constants.DEFAULT_FILE_STORAGE_DIR)
813
814 NOMODIFY_ETCHOSTS_OPT = cli_option("--no-etc-hosts", dest="modify_etc_hosts",
815                                    help="Don't modify /etc/hosts",
816                                    action="store_false", default=True)
817
818 NOMODIFY_SSH_SETUP_OPT = cli_option("--no-ssh-init", dest="modify_ssh_setup",
819                                     help="Don't initialize SSH keys",
820                                     action="store_false", default=True)
821
822 ERROR_CODES_OPT = cli_option("--error-codes", dest="error_codes",
823                              help="Enable parseable error messages",
824                              action="store_true", default=False)
825
826 NONPLUS1_OPT = cli_option("--no-nplus1-mem", dest="skip_nplusone_mem",
827                           help="Skip N+1 memory redundancy tests",
828                           action="store_true", default=False)
829
830 REBOOT_TYPE_OPT = cli_option("-t", "--type", dest="reboot_type",
831                              help="Type of reboot: soft/hard/full",
832                              default=constants.INSTANCE_REBOOT_HARD,
833                              metavar="<REBOOT>",
834                              choices=list(constants.REBOOT_TYPES))
835
836 IGNORE_SECONDARIES_OPT = cli_option("--ignore-secondaries",
837                                     dest="ignore_secondaries",
838                                     default=False, action="store_true",
839                                     help="Ignore errors from secondaries")
840
841 NOSHUTDOWN_OPT = cli_option("--noshutdown", dest="shutdown",
842                             action="store_false", default=True,
843                             help="Don't shutdown the instance (unsafe)")
844
845 TIMEOUT_OPT = cli_option("--timeout", dest="timeout", type="int",
846                          default=constants.DEFAULT_SHUTDOWN_TIMEOUT,
847                          help="Maximum time to wait")
848
849 SHUTDOWN_TIMEOUT_OPT = cli_option("--shutdown-timeout",
850                          dest="shutdown_timeout", type="int",
851                          default=constants.DEFAULT_SHUTDOWN_TIMEOUT,
852                          help="Maximum time to wait for instance shutdown")
853
854 EARLY_RELEASE_OPT = cli_option("--early-release",
855                                dest="early_release", default=False,
856                                action="store_true",
857                                help="Release the locks on the secondary"
858                                " node(s) early")
859
860
861 def _ParseArgs(argv, commands, aliases):
862   """Parser for the command line arguments.
863
864   This function parses the arguments and returns the function which
865   must be executed together with its (modified) arguments.
866
867   @param argv: the command line
868   @param commands: dictionary with special contents, see the design
869       doc for cmdline handling
870   @param aliases: dictionary with command aliases {'alias': 'target, ...}
871
872   """
873   if len(argv) == 0:
874     binary = "<command>"
875   else:
876     binary = argv[0].split("/")[-1]
877
878   if len(argv) > 1 and argv[1] == "--version":
879     ToStdout("%s (ganeti) %s", binary, constants.RELEASE_VERSION)
880     # Quit right away. That way we don't have to care about this special
881     # argument. optparse.py does it the same.
882     sys.exit(0)
883
884   if len(argv) < 2 or not (argv[1] in commands or
885                            argv[1] in aliases):
886     # let's do a nice thing
887     sortedcmds = commands.keys()
888     sortedcmds.sort()
889
890     ToStdout("Usage: %s {command} [options...] [argument...]", binary)
891     ToStdout("%s <command> --help to see details, or man %s", binary, binary)
892     ToStdout("")
893
894     # compute the max line length for cmd + usage
895     mlen = max([len(" %s" % cmd) for cmd in commands])
896     mlen = min(60, mlen) # should not get here...
897
898     # and format a nice command list
899     ToStdout("Commands:")
900     for cmd in sortedcmds:
901       cmdstr = " %s" % (cmd,)
902       help_text = commands[cmd][4]
903       help_lines = textwrap.wrap(help_text, 79 - 3 - mlen)
904       ToStdout("%-*s - %s", mlen, cmdstr, help_lines.pop(0))
905       for line in help_lines:
906         ToStdout("%-*s   %s", mlen, "", line)
907
908     ToStdout("")
909
910     return None, None, None
911
912   # get command, unalias it, and look it up in commands
913   cmd = argv.pop(1)
914   if cmd in aliases:
915     if cmd in commands:
916       raise errors.ProgrammerError("Alias '%s' overrides an existing"
917                                    " command" % cmd)
918
919     if aliases[cmd] not in commands:
920       raise errors.ProgrammerError("Alias '%s' maps to non-existing"
921                                    " command '%s'" % (cmd, aliases[cmd]))
922
923     cmd = aliases[cmd]
924
925   func, args_def, parser_opts, usage, description = commands[cmd]
926   parser = OptionParser(option_list=parser_opts + [_DRY_RUN_OPT, DEBUG_OPT],
927                         description=description,
928                         formatter=TitledHelpFormatter(),
929                         usage="%%prog %s %s" % (cmd, usage))
930   parser.disable_interspersed_args()
931   options, args = parser.parse_args()
932
933   if not _CheckArguments(cmd, args_def, args):
934     return None, None, None
935
936   return func, options, args
937
938
939 def _CheckArguments(cmd, args_def, args):
940   """Verifies the arguments using the argument definition.
941
942   Algorithm:
943
944     1. Abort with error if values specified by user but none expected.
945
946     1. For each argument in definition
947
948       1. Keep running count of minimum number of values (min_count)
949       1. Keep running count of maximum number of values (max_count)
950       1. If it has an unlimited number of values
951
952         1. Abort with error if it's not the last argument in the definition
953
954     1. If last argument has limited number of values
955
956       1. Abort with error if number of values doesn't match or is too large
957
958     1. Abort with error if user didn't pass enough values (min_count)
959
960   """
961   if args and not args_def:
962     ToStderr("Error: Command %s expects no arguments", cmd)
963     return False
964
965   min_count = None
966   max_count = None
967   check_max = None
968
969   last_idx = len(args_def) - 1
970
971   for idx, arg in enumerate(args_def):
972     if min_count is None:
973       min_count = arg.min
974     elif arg.min is not None:
975       min_count += arg.min
976
977     if max_count is None:
978       max_count = arg.max
979     elif arg.max is not None:
980       max_count += arg.max
981
982     if idx == last_idx:
983       check_max = (arg.max is not None)
984
985     elif arg.max is None:
986       raise errors.ProgrammerError("Only the last argument can have max=None")
987
988   if check_max:
989     # Command with exact number of arguments
990     if (min_count is not None and max_count is not None and
991         min_count == max_count and len(args) != min_count):
992       ToStderr("Error: Command %s expects %d argument(s)", cmd, min_count)
993       return False
994
995     # Command with limited number of arguments
996     if max_count is not None and len(args) > max_count:
997       ToStderr("Error: Command %s expects only %d argument(s)",
998                cmd, max_count)
999       return False
1000
1001   # Command with some required arguments
1002   if min_count is not None and len(args) < min_count:
1003     ToStderr("Error: Command %s expects at least %d argument(s)",
1004              cmd, min_count)
1005     return False
1006
1007   return True
1008
1009
1010 def SplitNodeOption(value):
1011   """Splits the value of a --node option.
1012
1013   """
1014   if value and ':' in value:
1015     return value.split(':', 1)
1016   else:
1017     return (value, None)
1018
1019
1020 def CalculateOSNames(os_name, os_variants):
1021   """Calculates all the names an OS can be called, according to its variants.
1022
1023   @type os_name: string
1024   @param os_name: base name of the os
1025   @type os_variants: list or None
1026   @param os_variants: list of supported variants
1027   @rtype: list
1028   @return: list of valid names
1029
1030   """
1031   if os_variants:
1032     return ['%s+%s' % (os_name, v) for v in os_variants]
1033   else:
1034     return [os_name]
1035
1036
1037 def UsesRPC(fn):
1038   def wrapper(*args, **kwargs):
1039     rpc.Init()
1040     try:
1041       return fn(*args, **kwargs)
1042     finally:
1043       rpc.Shutdown()
1044   return wrapper
1045
1046
1047 def AskUser(text, choices=None):
1048   """Ask the user a question.
1049
1050   @param text: the question to ask
1051
1052   @param choices: list with elements tuples (input_char, return_value,
1053       description); if not given, it will default to: [('y', True,
1054       'Perform the operation'), ('n', False, 'Do no do the operation')];
1055       note that the '?' char is reserved for help
1056
1057   @return: one of the return values from the choices list; if input is
1058       not possible (i.e. not running with a tty, we return the last
1059       entry from the list
1060
1061   """
1062   if choices is None:
1063     choices = [('y', True, 'Perform the operation'),
1064                ('n', False, 'Do not perform the operation')]
1065   if not choices or not isinstance(choices, list):
1066     raise errors.ProgrammerError("Invalid choices argument to AskUser")
1067   for entry in choices:
1068     if not isinstance(entry, tuple) or len(entry) < 3 or entry[0] == '?':
1069       raise errors.ProgrammerError("Invalid choices element to AskUser")
1070
1071   answer = choices[-1][1]
1072   new_text = []
1073   for line in text.splitlines():
1074     new_text.append(textwrap.fill(line, 70, replace_whitespace=False))
1075   text = "\n".join(new_text)
1076   try:
1077     f = file("/dev/tty", "a+")
1078   except IOError:
1079     return answer
1080   try:
1081     chars = [entry[0] for entry in choices]
1082     chars[-1] = "[%s]" % chars[-1]
1083     chars.append('?')
1084     maps = dict([(entry[0], entry[1]) for entry in choices])
1085     while True:
1086       f.write(text)
1087       f.write('\n')
1088       f.write("/".join(chars))
1089       f.write(": ")
1090       line = f.readline(2).strip().lower()
1091       if line in maps:
1092         answer = maps[line]
1093         break
1094       elif line == '?':
1095         for entry in choices:
1096           f.write(" %s - %s\n" % (entry[0], entry[2]))
1097         f.write("\n")
1098         continue
1099   finally:
1100     f.close()
1101   return answer
1102
1103
1104 class JobSubmittedException(Exception):
1105   """Job was submitted, client should exit.
1106
1107   This exception has one argument, the ID of the job that was
1108   submitted. The handler should print this ID.
1109
1110   This is not an error, just a structured way to exit from clients.
1111
1112   """
1113
1114
1115 def SendJob(ops, cl=None):
1116   """Function to submit an opcode without waiting for the results.
1117
1118   @type ops: list
1119   @param ops: list of opcodes
1120   @type cl: luxi.Client
1121   @param cl: the luxi client to use for communicating with the master;
1122              if None, a new client will be created
1123
1124   """
1125   if cl is None:
1126     cl = GetClient()
1127
1128   job_id = cl.SubmitJob(ops)
1129
1130   return job_id
1131
1132
1133 def PollJob(job_id, cl=None, feedback_fn=None):
1134   """Function to poll for the result of a job.
1135
1136   @type job_id: job identified
1137   @param job_id: the job to poll for results
1138   @type cl: luxi.Client
1139   @param cl: the luxi client to use for communicating with the master;
1140              if None, a new client will be created
1141
1142   """
1143   if cl is None:
1144     cl = GetClient()
1145
1146   prev_job_info = None
1147   prev_logmsg_serial = None
1148
1149   while True:
1150     result = cl.WaitForJobChange(job_id, ["status"], prev_job_info,
1151                                  prev_logmsg_serial)
1152     if not result:
1153       # job not found, go away!
1154       raise errors.JobLost("Job with id %s lost" % job_id)
1155
1156     # Split result, a tuple of (field values, log entries)
1157     (job_info, log_entries) = result
1158     (status, ) = job_info
1159
1160     if log_entries:
1161       for log_entry in log_entries:
1162         (serial, timestamp, _, message) = log_entry
1163         if callable(feedback_fn):
1164           feedback_fn(log_entry[1:])
1165         else:
1166           encoded = utils.SafeEncode(message)
1167           ToStdout("%s %s", time.ctime(utils.MergeTime(timestamp)), encoded)
1168         prev_logmsg_serial = max(prev_logmsg_serial, serial)
1169
1170     # TODO: Handle canceled and archived jobs
1171     elif status in (constants.JOB_STATUS_SUCCESS,
1172                     constants.JOB_STATUS_ERROR,
1173                     constants.JOB_STATUS_CANCELING,
1174                     constants.JOB_STATUS_CANCELED):
1175       break
1176
1177     prev_job_info = job_info
1178
1179   jobs = cl.QueryJobs([job_id], ["status", "opstatus", "opresult"])
1180   if not jobs:
1181     raise errors.JobLost("Job with id %s lost" % job_id)
1182
1183   status, opstatus, result = jobs[0]
1184   if status == constants.JOB_STATUS_SUCCESS:
1185     return result
1186   elif status in (constants.JOB_STATUS_CANCELING,
1187                   constants.JOB_STATUS_CANCELED):
1188     raise errors.OpExecError("Job was canceled")
1189   else:
1190     has_ok = False
1191     for idx, (status, msg) in enumerate(zip(opstatus, result)):
1192       if status == constants.OP_STATUS_SUCCESS:
1193         has_ok = True
1194       elif status == constants.OP_STATUS_ERROR:
1195         errors.MaybeRaise(msg)
1196         if has_ok:
1197           raise errors.OpExecError("partial failure (opcode %d): %s" %
1198                                    (idx, msg))
1199         else:
1200           raise errors.OpExecError(str(msg))
1201     # default failure mode
1202     raise errors.OpExecError(result)
1203
1204
1205 def SubmitOpCode(op, cl=None, feedback_fn=None, opts=None):
1206   """Legacy function to submit an opcode.
1207
1208   This is just a simple wrapper over the construction of the processor
1209   instance. It should be extended to better handle feedback and
1210   interaction functions.
1211
1212   """
1213   if cl is None:
1214     cl = GetClient()
1215
1216   SetGenericOpcodeOpts([op], opts)
1217
1218   job_id = SendJob([op], cl)
1219
1220   op_results = PollJob(job_id, cl=cl, feedback_fn=feedback_fn)
1221
1222   return op_results[0]
1223
1224
1225 def SubmitOrSend(op, opts, cl=None, feedback_fn=None):
1226   """Wrapper around SubmitOpCode or SendJob.
1227
1228   This function will decide, based on the 'opts' parameter, whether to
1229   submit and wait for the result of the opcode (and return it), or
1230   whether to just send the job and print its identifier. It is used in
1231   order to simplify the implementation of the '--submit' option.
1232
1233   It will also process the opcodes if we're sending the via SendJob
1234   (otherwise SubmitOpCode does it).
1235
1236   """
1237   if opts and opts.submit_only:
1238     job = [op]
1239     SetGenericOpcodeOpts(job, opts)
1240     job_id = SendJob(job, cl=cl)
1241     raise JobSubmittedException(job_id)
1242   else:
1243     return SubmitOpCode(op, cl=cl, feedback_fn=feedback_fn, opts=opts)
1244
1245
1246 def SetGenericOpcodeOpts(opcode_list, options):
1247   """Processor for generic options.
1248
1249   This function updates the given opcodes based on generic command
1250   line options (like debug, dry-run, etc.).
1251
1252   @param opcode_list: list of opcodes
1253   @param options: command line options or None
1254   @return: None (in-place modification)
1255
1256   """
1257   if not options:
1258     return
1259   for op in opcode_list:
1260     op.dry_run = options.dry_run
1261     op.debug_level = options.debug
1262
1263
1264 def GetClient():
1265   # TODO: Cache object?
1266   try:
1267     client = luxi.Client()
1268   except luxi.NoMasterError:
1269     ss = ssconf.SimpleStore()
1270
1271     # Try to read ssconf file
1272     try:
1273       ss.GetMasterNode()
1274     except errors.ConfigurationError:
1275       raise errors.OpPrereqError("Cluster not initialized or this machine is"
1276                                  " not part of a cluster")
1277
1278     master, myself = ssconf.GetMasterAndMyself(ss=ss)
1279     if master != myself:
1280       raise errors.OpPrereqError("This is not the master node, please connect"
1281                                  " to node '%s' and rerun the command" %
1282                                  master)
1283     raise
1284   return client
1285
1286
1287 def FormatError(err):
1288   """Return a formatted error message for a given error.
1289
1290   This function takes an exception instance and returns a tuple
1291   consisting of two values: first, the recommended exit code, and
1292   second, a string describing the error message (not
1293   newline-terminated).
1294
1295   """
1296   retcode = 1
1297   obuf = StringIO()
1298   msg = str(err)
1299   if isinstance(err, errors.ConfigurationError):
1300     txt = "Corrupt configuration file: %s" % msg
1301     logging.error(txt)
1302     obuf.write(txt + "\n")
1303     obuf.write("Aborting.")
1304     retcode = 2
1305   elif isinstance(err, errors.HooksAbort):
1306     obuf.write("Failure: hooks execution failed:\n")
1307     for node, script, out in err.args[0]:
1308       if out:
1309         obuf.write("  node: %s, script: %s, output: %s\n" %
1310                    (node, script, out))
1311       else:
1312         obuf.write("  node: %s, script: %s (no output)\n" %
1313                    (node, script))
1314   elif isinstance(err, errors.HooksFailure):
1315     obuf.write("Failure: hooks general failure: %s" % msg)
1316   elif isinstance(err, errors.ResolverError):
1317     this_host = utils.HostInfo.SysName()
1318     if err.args[0] == this_host:
1319       msg = "Failure: can't resolve my own hostname ('%s')"
1320     else:
1321       msg = "Failure: can't resolve hostname '%s'"
1322     obuf.write(msg % err.args[0])
1323   elif isinstance(err, errors.OpPrereqError):
1324     if len(err.args) == 2:
1325       obuf.write("Failure: prerequisites not met for this"
1326                " operation:\nerror type: %s, error details:\n%s" %
1327                  (err.args[1], err.args[0]))
1328     else:
1329       obuf.write("Failure: prerequisites not met for this"
1330                  " operation:\n%s" % msg)
1331   elif isinstance(err, errors.OpExecError):
1332     obuf.write("Failure: command execution error:\n%s" % msg)
1333   elif isinstance(err, errors.TagError):
1334     obuf.write("Failure: invalid tag(s) given:\n%s" % msg)
1335   elif isinstance(err, errors.JobQueueDrainError):
1336     obuf.write("Failure: the job queue is marked for drain and doesn't"
1337                " accept new requests\n")
1338   elif isinstance(err, errors.JobQueueFull):
1339     obuf.write("Failure: the job queue is full and doesn't accept new"
1340                " job submissions until old jobs are archived\n")
1341   elif isinstance(err, errors.TypeEnforcementError):
1342     obuf.write("Parameter Error: %s" % msg)
1343   elif isinstance(err, errors.ParameterError):
1344     obuf.write("Failure: unknown/wrong parameter name '%s'" % msg)
1345   elif isinstance(err, luxi.NoMasterError):
1346     obuf.write("Cannot communicate with the master daemon.\nIs it running"
1347                " and listening for connections?")
1348   elif isinstance(err, luxi.TimeoutError):
1349     obuf.write("Timeout while talking to the master daemon. Error:\n"
1350                "%s" % msg)
1351   elif isinstance(err, luxi.ProtocolError):
1352     obuf.write("Unhandled protocol error while talking to the master daemon:\n"
1353                "%s" % msg)
1354   elif isinstance(err, errors.GenericError):
1355     obuf.write("Unhandled Ganeti error: %s" % msg)
1356   elif isinstance(err, JobSubmittedException):
1357     obuf.write("JobID: %s\n" % err.args[0])
1358     retcode = 0
1359   else:
1360     obuf.write("Unhandled exception: %s" % msg)
1361   return retcode, obuf.getvalue().rstrip('\n')
1362
1363
1364 def GenericMain(commands, override=None, aliases=None):
1365   """Generic main function for all the gnt-* commands.
1366
1367   Arguments:
1368     - commands: a dictionary with a special structure, see the design doc
1369                 for command line handling.
1370     - override: if not None, we expect a dictionary with keys that will
1371                 override command line options; this can be used to pass
1372                 options from the scripts to generic functions
1373     - aliases: dictionary with command aliases {'alias': 'target, ...}
1374
1375   """
1376   # save the program name and the entire command line for later logging
1377   if sys.argv:
1378     binary = os.path.basename(sys.argv[0]) or sys.argv[0]
1379     if len(sys.argv) >= 2:
1380       binary += " " + sys.argv[1]
1381       old_cmdline = " ".join(sys.argv[2:])
1382     else:
1383       old_cmdline = ""
1384   else:
1385     binary = "<unknown program>"
1386     old_cmdline = ""
1387
1388   if aliases is None:
1389     aliases = {}
1390
1391   try:
1392     func, options, args = _ParseArgs(sys.argv, commands, aliases)
1393   except errors.ParameterError, err:
1394     result, err_msg = FormatError(err)
1395     ToStderr(err_msg)
1396     return 1
1397
1398   if func is None: # parse error
1399     return 1
1400
1401   if override is not None:
1402     for key, val in override.iteritems():
1403       setattr(options, key, val)
1404
1405   utils.SetupLogging(constants.LOG_COMMANDS, debug=options.debug,
1406                      stderr_logging=True, program=binary)
1407
1408   if old_cmdline:
1409     logging.info("run with arguments '%s'", old_cmdline)
1410   else:
1411     logging.info("run with no arguments")
1412
1413   try:
1414     result = func(options, args)
1415   except (errors.GenericError, luxi.ProtocolError,
1416           JobSubmittedException), err:
1417     result, err_msg = FormatError(err)
1418     logging.exception("Error during command processing")
1419     ToStderr(err_msg)
1420
1421   return result
1422
1423
1424 def GenericInstanceCreate(mode, opts, args):
1425   """Add an instance to the cluster via either creation or import.
1426
1427   @param mode: constants.INSTANCE_CREATE or constants.INSTANCE_IMPORT
1428   @param opts: the command line options selected by the user
1429   @type args: list
1430   @param args: should contain only one element, the new instance name
1431   @rtype: int
1432   @return: the desired exit code
1433
1434   """
1435   instance = args[0]
1436
1437   (pnode, snode) = SplitNodeOption(opts.node)
1438
1439   hypervisor = None
1440   hvparams = {}
1441   if opts.hypervisor:
1442     hypervisor, hvparams = opts.hypervisor
1443
1444   if opts.nics:
1445     try:
1446       nic_max = max(int(nidx[0]) + 1 for nidx in opts.nics)
1447     except ValueError, err:
1448       raise errors.OpPrereqError("Invalid NIC index passed: %s" % str(err))
1449     nics = [{}] * nic_max
1450     for nidx, ndict in opts.nics:
1451       nidx = int(nidx)
1452       if not isinstance(ndict, dict):
1453         msg = "Invalid nic/%d value: expected dict, got %s" % (nidx, ndict)
1454         raise errors.OpPrereqError(msg)
1455       nics[nidx] = ndict
1456   elif opts.no_nics:
1457     # no nics
1458     nics = []
1459   else:
1460     # default of one nic, all auto
1461     nics = [{}]
1462
1463   if opts.disk_template == constants.DT_DISKLESS:
1464     if opts.disks or opts.sd_size is not None:
1465       raise errors.OpPrereqError("Diskless instance but disk"
1466                                  " information passed")
1467     disks = []
1468   else:
1469     if not opts.disks and not opts.sd_size:
1470       raise errors.OpPrereqError("No disk information specified")
1471     if opts.disks and opts.sd_size is not None:
1472       raise errors.OpPrereqError("Please use either the '--disk' or"
1473                                  " '-s' option")
1474     if opts.sd_size is not None:
1475       opts.disks = [(0, {"size": opts.sd_size})]
1476     try:
1477       disk_max = max(int(didx[0]) + 1 for didx in opts.disks)
1478     except ValueError, err:
1479       raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err))
1480     disks = [{}] * disk_max
1481     for didx, ddict in opts.disks:
1482       didx = int(didx)
1483       if not isinstance(ddict, dict):
1484         msg = "Invalid disk/%d value: expected dict, got %s" % (didx, ddict)
1485         raise errors.OpPrereqError(msg)
1486       elif "size" not in ddict:
1487         raise errors.OpPrereqError("Missing size for disk %d" % didx)
1488       try:
1489         ddict["size"] = utils.ParseUnit(ddict["size"])
1490       except ValueError, err:
1491         raise errors.OpPrereqError("Invalid disk size for disk %d: %s" %
1492                                    (didx, err))
1493       disks[didx] = ddict
1494
1495   utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_TYPES)
1496   utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
1497
1498   if mode == constants.INSTANCE_CREATE:
1499     start = opts.start
1500     os_type = opts.os
1501     src_node = None
1502     src_path = None
1503   elif mode == constants.INSTANCE_IMPORT:
1504     start = False
1505     os_type = None
1506     src_node = opts.src_node
1507     src_path = opts.src_dir
1508   else:
1509     raise errors.ProgrammerError("Invalid creation mode %s" % mode)
1510
1511   op = opcodes.OpCreateInstance(instance_name=instance,
1512                                 disks=disks,
1513                                 disk_template=opts.disk_template,
1514                                 nics=nics,
1515                                 pnode=pnode, snode=snode,
1516                                 ip_check=opts.ip_check,
1517                                 name_check=opts.name_check,
1518                                 wait_for_sync=opts.wait_for_sync,
1519                                 file_storage_dir=opts.file_storage_dir,
1520                                 file_driver=opts.file_driver,
1521                                 iallocator=opts.iallocator,
1522                                 hypervisor=hypervisor,
1523                                 hvparams=hvparams,
1524                                 beparams=opts.beparams,
1525                                 mode=mode,
1526                                 start=start,
1527                                 os_type=os_type,
1528                                 src_node=src_node,
1529                                 src_path=src_path)
1530
1531   SubmitOrSend(op, opts)
1532   return 0
1533
1534
1535 def GenerateTable(headers, fields, separator, data,
1536                   numfields=None, unitfields=None,
1537                   units=None):
1538   """Prints a table with headers and different fields.
1539
1540   @type headers: dict
1541   @param headers: dictionary mapping field names to headers for
1542       the table
1543   @type fields: list
1544   @param fields: the field names corresponding to each row in
1545       the data field
1546   @param separator: the separator to be used; if this is None,
1547       the default 'smart' algorithm is used which computes optimal
1548       field width, otherwise just the separator is used between
1549       each field
1550   @type data: list
1551   @param data: a list of lists, each sublist being one row to be output
1552   @type numfields: list
1553   @param numfields: a list with the fields that hold numeric
1554       values and thus should be right-aligned
1555   @type unitfields: list
1556   @param unitfields: a list with the fields that hold numeric
1557       values that should be formatted with the units field
1558   @type units: string or None
1559   @param units: the units we should use for formatting, or None for
1560       automatic choice (human-readable for non-separator usage, otherwise
1561       megabytes); this is a one-letter string
1562
1563   """
1564   if units is None:
1565     if separator:
1566       units = "m"
1567     else:
1568       units = "h"
1569
1570   if numfields is None:
1571     numfields = []
1572   if unitfields is None:
1573     unitfields = []
1574
1575   numfields = utils.FieldSet(*numfields)   # pylint: disable-msg=W0142
1576   unitfields = utils.FieldSet(*unitfields) # pylint: disable-msg=W0142
1577
1578   format_fields = []
1579   for field in fields:
1580     if headers and field not in headers:
1581       # TODO: handle better unknown fields (either revert to old
1582       # style of raising exception, or deal more intelligently with
1583       # variable fields)
1584       headers[field] = field
1585     if separator is not None:
1586       format_fields.append("%s")
1587     elif numfields.Matches(field):
1588       format_fields.append("%*s")
1589     else:
1590       format_fields.append("%-*s")
1591
1592   if separator is None:
1593     mlens = [0 for name in fields]
1594     format = ' '.join(format_fields)
1595   else:
1596     format = separator.replace("%", "%%").join(format_fields)
1597
1598   for row in data:
1599     if row is None:
1600       continue
1601     for idx, val in enumerate(row):
1602       if unitfields.Matches(fields[idx]):
1603         try:
1604           val = int(val)
1605         except (TypeError, ValueError):
1606           pass
1607         else:
1608           val = row[idx] = utils.FormatUnit(val, units)
1609       val = row[idx] = str(val)
1610       if separator is None:
1611         mlens[idx] = max(mlens[idx], len(val))
1612
1613   result = []
1614   if headers:
1615     args = []
1616     for idx, name in enumerate(fields):
1617       hdr = headers[name]
1618       if separator is None:
1619         mlens[idx] = max(mlens[idx], len(hdr))
1620         args.append(mlens[idx])
1621       args.append(hdr)
1622     result.append(format % tuple(args))
1623
1624   if separator is None:
1625     assert len(mlens) == len(fields)
1626
1627     if fields and not numfields.Matches(fields[-1]):
1628       mlens[-1] = 0
1629
1630   for line in data:
1631     args = []
1632     if line is None:
1633       line = ['-' for _ in fields]
1634     for idx in range(len(fields)):
1635       if separator is None:
1636         args.append(mlens[idx])
1637       args.append(line[idx])
1638     result.append(format % tuple(args))
1639
1640   return result
1641
1642
1643 def FormatTimestamp(ts):
1644   """Formats a given timestamp.
1645
1646   @type ts: timestamp
1647   @param ts: a timeval-type timestamp, a tuple of seconds and microseconds
1648
1649   @rtype: string
1650   @return: a string with the formatted timestamp
1651
1652   """
1653   if not isinstance (ts, (tuple, list)) or len(ts) != 2:
1654     return '?'
1655   sec, usec = ts
1656   return time.strftime("%F %T", time.localtime(sec)) + ".%06d" % usec
1657
1658
1659 def ParseTimespec(value):
1660   """Parse a time specification.
1661
1662   The following suffixed will be recognized:
1663
1664     - s: seconds
1665     - m: minutes
1666     - h: hours
1667     - d: day
1668     - w: weeks
1669
1670   Without any suffix, the value will be taken to be in seconds.
1671
1672   """
1673   value = str(value)
1674   if not value:
1675     raise errors.OpPrereqError("Empty time specification passed")
1676   suffix_map = {
1677     's': 1,
1678     'm': 60,
1679     'h': 3600,
1680     'd': 86400,
1681     'w': 604800,
1682     }
1683   if value[-1] not in suffix_map:
1684     try:
1685       value = int(value)
1686     except (TypeError, ValueError):
1687       raise errors.OpPrereqError("Invalid time specification '%s'" % value)
1688   else:
1689     multiplier = suffix_map[value[-1]]
1690     value = value[:-1]
1691     if not value: # no data left after stripping the suffix
1692       raise errors.OpPrereqError("Invalid time specification (only"
1693                                  " suffix passed)")
1694     try:
1695       value = int(value) * multiplier
1696     except (TypeError, ValueError):
1697       raise errors.OpPrereqError("Invalid time specification '%s'" % value)
1698   return value
1699
1700
1701 def GetOnlineNodes(nodes, cl=None, nowarn=False):
1702   """Returns the names of online nodes.
1703
1704   This function will also log a warning on stderr with the names of
1705   the online nodes.
1706
1707   @param nodes: if not empty, use only this subset of nodes (minus the
1708       offline ones)
1709   @param cl: if not None, luxi client to use
1710   @type nowarn: boolean
1711   @param nowarn: by default, this function will output a note with the
1712       offline nodes that are skipped; if this parameter is True the
1713       note is not displayed
1714
1715   """
1716   if cl is None:
1717     cl = GetClient()
1718
1719   result = cl.QueryNodes(names=nodes, fields=["name", "offline"],
1720                          use_locking=False)
1721   offline = [row[0] for row in result if row[1]]
1722   if offline and not nowarn:
1723     ToStderr("Note: skipping offline node(s): %s" % utils.CommaJoin(offline))
1724   return [row[0] for row in result if not row[1]]
1725
1726
1727 def _ToStream(stream, txt, *args):
1728   """Write a message to a stream, bypassing the logging system
1729
1730   @type stream: file object
1731   @param stream: the file to which we should write
1732   @type txt: str
1733   @param txt: the message
1734
1735   """
1736   if args:
1737     args = tuple(args)
1738     stream.write(txt % args)
1739   else:
1740     stream.write(txt)
1741   stream.write('\n')
1742   stream.flush()
1743
1744
1745 def ToStdout(txt, *args):
1746   """Write a message to stdout only, bypassing the logging system
1747
1748   This is just a wrapper over _ToStream.
1749
1750   @type txt: str
1751   @param txt: the message
1752
1753   """
1754   _ToStream(sys.stdout, txt, *args)
1755
1756
1757 def ToStderr(txt, *args):
1758   """Write a message to stderr only, bypassing the logging system
1759
1760   This is just a wrapper over _ToStream.
1761
1762   @type txt: str
1763   @param txt: the message
1764
1765   """
1766   _ToStream(sys.stderr, txt, *args)
1767
1768
1769 class JobExecutor(object):
1770   """Class which manages the submission and execution of multiple jobs.
1771
1772   Note that instances of this class should not be reused between
1773   GetResults() calls.
1774
1775   """
1776   def __init__(self, cl=None, verbose=True, opts=None):
1777     self.queue = []
1778     if cl is None:
1779       cl = GetClient()
1780     self.cl = cl
1781     self.verbose = verbose
1782     self.jobs = []
1783     self.opts = opts
1784
1785   def QueueJob(self, name, *ops):
1786     """Record a job for later submit.
1787
1788     @type name: string
1789     @param name: a description of the job, will be used in WaitJobSet
1790     """
1791     SetGenericOpcodeOpts(ops, self.opts)
1792     self.queue.append((name, ops))
1793
1794   def SubmitPending(self):
1795     """Submit all pending jobs.
1796
1797     """
1798     results = self.cl.SubmitManyJobs([row[1] for row in self.queue])
1799     for ((status, data), (name, _)) in zip(results, self.queue):
1800       self.jobs.append((status, data, name))
1801
1802   def GetResults(self):
1803     """Wait for and return the results of all jobs.
1804
1805     @rtype: list
1806     @return: list of tuples (success, job results), in the same order
1807         as the submitted jobs; if a job has failed, instead of the result
1808         there will be the error message
1809
1810     """
1811     if not self.jobs:
1812       self.SubmitPending()
1813     results = []
1814     if self.verbose:
1815       ok_jobs = [row[1] for row in self.jobs if row[0]]
1816       if ok_jobs:
1817         ToStdout("Submitted jobs %s", utils.CommaJoin(ok_jobs))
1818     for submit_status, jid, name in self.jobs:
1819       if not submit_status:
1820         ToStderr("Failed to submit job for %s: %s", name, jid)
1821         results.append((False, jid))
1822         continue
1823       if self.verbose:
1824         ToStdout("Waiting for job %s for %s...", jid, name)
1825       try:
1826         job_result = PollJob(jid, cl=self.cl)
1827         success = True
1828       except (errors.GenericError, luxi.ProtocolError), err:
1829         _, job_result = FormatError(err)
1830         success = False
1831         # the error message will always be shown, verbose or not
1832         ToStderr("Job %s for %s has failed: %s", jid, name, job_result)
1833
1834       results.append((success, job_result))
1835     return results
1836
1837   def WaitOrShow(self, wait):
1838     """Wait for job results or only print the job IDs.
1839
1840     @type wait: boolean
1841     @param wait: whether to wait or not
1842
1843     """
1844     if wait:
1845       return self.GetResults()
1846     else:
1847       if not self.jobs:
1848         self.SubmitPending()
1849       for status, result, name in self.jobs:
1850         if status:
1851           ToStdout("%s: %s", result, name)
1852         else:
1853           ToStderr("Failure for %s: %s", name, result)