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