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