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