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