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