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