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