Statistics
| Branch: | Tag: | Revision:

root / lib / cli.py @ 14e9e7f3

History | View | Annotate | Download (50.8 kB)

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_SIZE_OPT",
73
  "FORCE_OPT",
74
  "MAC_PREFIX_OPT",
75
  "MASTER_NETDEV_OPT",
76
  "MC_OPT",
77
  "NET_OPT",
78
  "NEW_SECONDARY_OPT",
79
  "NIC_PARAMS_OPT",
80
  "NODE_LIST_OPT",
81
  "NODE_PLACEMENT_OPT",
82
  "NOHDR_OPT",
83
  "NOIPCHECK_OPT",
84
  "NOLVM_STORAGE_OPT",
85
  "NOMODIFY_ETCHOSTS_OPT",
86
  "NONICS_OPT",
87
  "NONLIVE_OPT",
88
  "NONPLUS1_OPT",
89
  "NOSTART_OPT",
90
  "NOSSH_KEYCHECK_OPT",
91
  "NOVOTING_OPT",
92
  "NWSYNC_OPT",
93
  "ON_PRIMARY_OPT",
94
  "ON_SECONDARY_OPT",
95
  "OFFLINE_OPT",
96
  "OS_OPT",
97
  "OS_SIZE_OPT",
98
  "READD_OPT",
99
  "SECONDARY_IP_OPT",
100
  "SELECT_OS_OPT",
101
  "SEP_OPT",
102
  "SHOWCMD_OPT",
103
  "SINGLE_NODE_OPT",
104
  "SRC_DIR_OPT",
105
  "SRC_NODE_OPT",
106
  "SUBMIT_OPT",
107
  "STATIC_OPT",
108
  "SYNC_OPT",
109
  "TAG_SRC_OPT",
110
  "USEUNITS_OPT",
111
  "VERBOSE_OPT",
112
  "VG_NAME_OPT",
113
  "YES_DOIT_OPT",
114
  # Generic functions for CLI programs
115
  "GenericMain",
116
  "GetClient",
117
  "GetOnlineNodes",
118
  "JobExecutor",
119
  "JobSubmittedException",
120
  "ParseTimespec",
121
  "SubmitOpCode",
122
  "SubmitOrSend",
123
  "UsesRPC",
124
  # Formatting functions
125
  "ToStderr", "ToStdout",
126
  "FormatError",
127
  "GenerateTable",
128
  "AskUser",
129
  "FormatTimestamp",
130
  # Tags functions
131
  "ListTags",
132
  "AddTags",
133
  "RemoveTags",
134
  # command line options support infrastructure
135
  "ARGS_MANY_INSTANCES",
136
  "ARGS_MANY_NODES",
137
  "ARGS_NONE",
138
  "ARGS_ONE_INSTANCE",
139
  "ARGS_ONE_NODE",
140
  "ArgChoice",
141
  "ArgCommand",
142
  "ArgFile",
143
  "ArgHost",
144
  "ArgInstance",
145
  "ArgJobId",
146
  "ArgNode",
147
  "ArgSuggest",
148
  "ArgUnknown",
149
  "OPT_COMPL_INST_ADD_NODES",
150
  "OPT_COMPL_MANY_NODES",
151
  "OPT_COMPL_ONE_IALLOCATOR",
152
  "OPT_COMPL_ONE_INSTANCE",
153
  "OPT_COMPL_ONE_NODE",
154
  "OPT_COMPL_ONE_OS",
155
  "cli_option",
156
  "SplitNodeOption",
157
  ]
158

    
159
NO_PREFIX = "no_"
160
UN_PREFIX = "-"
161

    
162

    
163
class _Argument:
164
  def __init__(self, min=0, max=None):
165
    self.min = min
166
    self.max = max
167

    
168
  def __repr__(self):
169
    return ("<%s min=%s max=%s>" %
170
            (self.__class__.__name__, self.min, self.max))
171

    
172

    
173
class ArgSuggest(_Argument):
174
  """Suggesting argument.
175

176
  Value can be any of the ones passed to the constructor.
177

178
  """
179
  def __init__(self, min=0, max=None, choices=None):
180
    _Argument.__init__(self, min=min, max=max)
181
    self.choices = choices
182

    
183
  def __repr__(self):
184
    return ("<%s min=%s max=%s choices=%r>" %
185
            (self.__class__.__name__, self.min, self.max, self.choices))
186

    
187

    
188
class ArgChoice(ArgSuggest):
189
  """Choice argument.
190

191
  Value can be any of the ones passed to the constructor. Like L{ArgSuggest},
192
  but value must be one of the choices.
193

194
  """
195

    
196

    
197
class ArgUnknown(_Argument):
198
  """Unknown argument to program (e.g. determined at runtime).
199

200
  """
201

    
202

    
203
class ArgInstance(_Argument):
204
  """Instances argument.
205

206
  """
207

    
208

    
209
class ArgNode(_Argument):
210
  """Node argument.
211

212
  """
213

    
214
class ArgJobId(_Argument):
215
  """Job ID argument.
216

217
  """
218

    
219

    
220
class ArgFile(_Argument):
221
  """File path argument.
222

223
  """
224

    
225

    
226
class ArgCommand(_Argument):
227
  """Command argument.
228

229
  """
230

    
231

    
232
class ArgHost(_Argument):
233
  """Host argument.
234

235
  """
236

    
237

    
238
ARGS_NONE = []
239
ARGS_MANY_INSTANCES = [ArgInstance()]
240
ARGS_MANY_NODES = [ArgNode()]
241
ARGS_ONE_INSTANCE = [ArgInstance(min=1, max=1)]
242
ARGS_ONE_NODE = [ArgNode(min=1, max=1)]
243

    
244

    
245

    
246
def _ExtractTagsObject(opts, args):
247
  """Extract the tag type object.
248

249
  Note that this function will modify its args parameter.
250

251
  """
252
  if not hasattr(opts, "tag_type"):
253
    raise errors.ProgrammerError("tag_type not passed to _ExtractTagsObject")
254
  kind = opts.tag_type
255
  if kind == constants.TAG_CLUSTER:
256
    retval = kind, kind
257
  elif kind == constants.TAG_NODE or kind == constants.TAG_INSTANCE:
258
    if not args:
259
      raise errors.OpPrereqError("no arguments passed to the command")
260
    name = args.pop(0)
261
    retval = kind, name
262
  else:
263
    raise errors.ProgrammerError("Unhandled tag type '%s'" % kind)
264
  return retval
265

    
266

    
267
def _ExtendTags(opts, args):
268
  """Extend the args if a source file has been given.
269

270
  This function will extend the tags with the contents of the file
271
  passed in the 'tags_source' attribute of the opts parameter. A file
272
  named '-' will be replaced by stdin.
273

274
  """
275
  fname = opts.tags_source
276
  if fname is None:
277
    return
278
  if fname == "-":
279
    new_fh = sys.stdin
280
  else:
281
    new_fh = open(fname, "r")
282
  new_data = []
283
  try:
284
    # we don't use the nice 'new_data = [line.strip() for line in fh]'
285
    # because of python bug 1633941
286
    while True:
287
      line = new_fh.readline()
288
      if not line:
289
        break
290
      new_data.append(line.strip())
291
  finally:
292
    new_fh.close()
293
  args.extend(new_data)
294

    
295

    
296
def ListTags(opts, args):
297
  """List the tags on a given object.
298

299
  This is a generic implementation that knows how to deal with all
300
  three cases of tag objects (cluster, node, instance). The opts
301
  argument is expected to contain a tag_type field denoting what
302
  object type we work on.
303

304
  """
305
  kind, name = _ExtractTagsObject(opts, args)
306
  op = opcodes.OpGetTags(kind=kind, name=name)
307
  result = SubmitOpCode(op)
308
  result = list(result)
309
  result.sort()
310
  for tag in result:
311
    ToStdout(tag)
312

    
313

    
314
def AddTags(opts, args):
315
  """Add tags on a given object.
316

317
  This is a generic implementation that knows how to deal with all
318
  three cases of tag objects (cluster, node, instance). The opts
319
  argument is expected to contain a tag_type field denoting what
320
  object type we work on.
321

322
  """
323
  kind, name = _ExtractTagsObject(opts, args)
324
  _ExtendTags(opts, args)
325
  if not args:
326
    raise errors.OpPrereqError("No tags to be added")
327
  op = opcodes.OpAddTags(kind=kind, name=name, tags=args)
328
  SubmitOpCode(op)
329

    
330

    
331
def RemoveTags(opts, args):
332
  """Remove tags from a given object.
333

334
  This is a generic implementation that knows how to deal with all
335
  three cases of tag objects (cluster, node, instance). The opts
336
  argument is expected to contain a tag_type field denoting what
337
  object type we work on.
338

339
  """
340
  kind, name = _ExtractTagsObject(opts, args)
341
  _ExtendTags(opts, args)
342
  if not args:
343
    raise errors.OpPrereqError("No tags to be removed")
344
  op = opcodes.OpDelTags(kind=kind, name=name, tags=args)
345
  SubmitOpCode(op)
346

    
347

    
348
def check_unit(option, opt, value):
349
  """OptParsers custom converter for units.
350

351
  """
352
  try:
353
    return utils.ParseUnit(value)
354
  except errors.UnitParseError, err:
355
    raise OptionValueError("option %s: %s" % (opt, err))
356

    
357

    
358
def _SplitKeyVal(opt, data):
359
  """Convert a KeyVal string into a dict.
360

361
  This function will convert a key=val[,...] string into a dict. Empty
362
  values will be converted specially: keys which have the prefix 'no_'
363
  will have the value=False and the prefix stripped, the others will
364
  have value=True.
365

366
  @type opt: string
367
  @param opt: a string holding the option name for which we process the
368
      data, used in building error messages
369
  @type data: string
370
  @param data: a string of the format key=val,key=val,...
371
  @rtype: dict
372
  @return: {key=val, key=val}
373
  @raises errors.ParameterError: if there are duplicate keys
374

375
  """
376
  kv_dict = {}
377
  if data:
378
    for elem in data.split(","):
379
      if "=" in elem:
380
        key, val = elem.split("=", 1)
381
      else:
382
        if elem.startswith(NO_PREFIX):
383
          key, val = elem[len(NO_PREFIX):], False
384
        elif elem.startswith(UN_PREFIX):
385
          key, val = elem[len(UN_PREFIX):], None
386
        else:
387
          key, val = elem, True
388
      if key in kv_dict:
389
        raise errors.ParameterError("Duplicate key '%s' in option %s" %
390
                                    (key, opt))
391
      kv_dict[key] = val
392
  return kv_dict
393

    
394

    
395
def check_ident_key_val(option, opt, value):
396
  """Custom parser for ident:key=val,key=val options.
397

398
  This will store the parsed values as a tuple (ident, {key: val}). As such,
399
  multiple uses of this option via action=append is possible.
400

401
  """
402
  if ":" not in value:
403
    ident, rest = value, ''
404
  else:
405
    ident, rest = value.split(":", 1)
406

    
407
  if ident.startswith(NO_PREFIX):
408
    if rest:
409
      msg = "Cannot pass options when removing parameter groups: %s" % value
410
      raise errors.ParameterError(msg)
411
    retval = (ident[len(NO_PREFIX):], False)
412
  elif ident.startswith(UN_PREFIX):
413
    if rest:
414
      msg = "Cannot pass options when removing parameter groups: %s" % value
415
      raise errors.ParameterError(msg)
416
    retval = (ident[len(UN_PREFIX):], None)
417
  else:
418
    kv_dict = _SplitKeyVal(opt, rest)
419
    retval = (ident, kv_dict)
420
  return retval
421

    
422

    
423
def check_key_val(option, opt, value):
424
  """Custom parser class for key=val,key=val options.
425

426
  This will store the parsed values as a dict {key: val}.
427

428
  """
429
  return _SplitKeyVal(opt, value)
430

    
431

    
432
# completion_suggestion is normally a list. Using numeric values not evaluating
433
# to False for dynamic completion.
434
(OPT_COMPL_MANY_NODES,
435
 OPT_COMPL_ONE_NODE,
436
 OPT_COMPL_ONE_INSTANCE,
437
 OPT_COMPL_ONE_OS,
438
 OPT_COMPL_ONE_IALLOCATOR,
439
 OPT_COMPL_INST_ADD_NODES) = range(100, 106)
440

    
441
OPT_COMPL_ALL = frozenset([
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,
448
  ])
449

    
450

    
451
class CliOption(Option):
452
  """Custom option class for optparse.
453

454
  """
455
  ATTRS = Option.ATTRS + [
456
    "completion_suggest",
457
    ]
458
  TYPES = Option.TYPES + (
459
    "identkeyval",
460
    "keyval",
461
    "unit",
462
    )
463
  TYPE_CHECKER = Option.TYPE_CHECKER.copy()
464
  TYPE_CHECKER["identkeyval"] = check_ident_key_val
465
  TYPE_CHECKER["keyval"] = check_key_val
466
  TYPE_CHECKER["unit"] = check_unit
467

    
468

    
469
# optparse.py sets make_option, so we do it for our own option class, too
470
cli_option = CliOption
471

    
472

    
473
_YESNO = ("yes", "no")
474
_YORNO = "yes|no"
475

    
476
DEBUG_OPT = cli_option("-d", "--debug", default=False,
477
                       action="store_true",
478
                       help="Turn debugging on")
479

    
480
NOHDR_OPT = cli_option("--no-headers", default=False,
481
                       action="store_true", dest="no_headers",
482
                       help="Don't display column headers")
483

    
484
SEP_OPT = cli_option("--separator", default=None,
485
                     action="store", dest="separator",
486
                     help=("Separator between output fields"
487
                           " (defaults to one space)"))
488

    
489
USEUNITS_OPT = cli_option("--units", default=None,
490
                          dest="units", choices=('h', 'm', 'g', 't'),
491
                          help="Specify units for output (one of hmgt)")
492

    
493
FIELDS_OPT = cli_option("-o", "--output", dest="output", action="store",
494
                        type="string", metavar="FIELDS",
495
                        help="Comma separated list of output fields")
496

    
497
FORCE_OPT = cli_option("-f", "--force", dest="force", action="store_true",
498
                       default=False, help="Force the operation")
499

    
500
CONFIRM_OPT = cli_option("--yes", dest="confirm", action="store_true",
501
                         default=False, help="Do not require confirmation")
502

    
503
TAG_SRC_OPT = cli_option("--from", dest="tags_source",
504
                         default=None, help="File with tag names")
505

    
506
SUBMIT_OPT = cli_option("--submit", dest="submit_only",
507
                        default=False, action="store_true",
508
                        help=("Submit the job and return the job ID, but"
509
                              " don't wait for the job to finish"))
510

    
511
SYNC_OPT = cli_option("--sync", dest="do_locking",
512
                      default=False, action="store_true",
513
                      help=("Grab locks while doing the queries"
514
                            " in order to ensure more consistent results"))
515

    
516
_DRY_RUN_OPT = cli_option("--dry-run", default=False,
517
                          action="store_true",
518
                          help=("Do not execute the operation, just run the"
519
                                " check steps and verify it it could be"
520
                                " executed"))
521

    
522
VERBOSE_OPT = cli_option("-v", "--verbose", default=False,
523
                         action="store_true",
524
                         help="Increase the verbosity of the operation")
525

    
526
DEBUG_SIMERR_OPT = cli_option("--debug-simulate-errors", default=False,
527
                              action="store_true", dest="simulate_errors",
528
                              help="Debugging option that makes the operation"
529
                              " treat most runtime checks as failed")
530

    
531
NWSYNC_OPT = cli_option("--no-wait-for-sync", dest="wait_for_sync",
532
                        default=True, action="store_false",
533
                        help="Don't wait for sync (DANGEROUS!)")
534

    
535
DISK_TEMPLATE_OPT = cli_option("-t", "--disk-template", dest="disk_template",
536
                               help="Custom disk setup (diskless, file,"
537
                               " plain or drbd)",
538
                               default=None, metavar="TEMPL",
539
                               choices=list(constants.DISK_TEMPLATES))
540

    
541
NONICS_OPT = cli_option("--no-nics", default=False, action="store_true",
542
                        help="Do not create any network cards for"
543
                        " the instance")
544

    
545
FILESTORE_DIR_OPT = cli_option("--file-storage-dir", dest="file_storage_dir",
546
                               help="Relative path under default cluster-wide"
547
                               " file storage dir to store file-based disks",
548
                               default=None, metavar="<DIR>")
549

    
550
FILESTORE_DRIVER_OPT = cli_option("--file-driver", dest="file_driver",
551
                                  help="Driver to use for image files",
552
                                  default="loop", metavar="<DRIVER>",
553
                                  choices=list(constants.FILE_DRIVER))
554

    
555
IALLOCATOR_OPT = cli_option("-I", "--iallocator", metavar="<NAME>",
556
                            help="Select nodes for the instance automatically"
557
                            " using the <NAME> iallocator plugin",
558
                            default=None, type="string",
559
                            completion_suggest=OPT_COMPL_ONE_IALLOCATOR)
560

    
561
OS_OPT = cli_option("-o", "--os-type", dest="os", help="What OS to run",
562
                    metavar="<os>",
563
                    completion_suggest=OPT_COMPL_ONE_OS)
564

    
565
BACKEND_OPT = cli_option("-B", "--backend-parameters", dest="beparams",
566
                         type="keyval", default={},
567
                         help="Backend parameters")
568

    
569
HVOPTS_OPT =  cli_option("-H", "--hypervisor-parameters", type="keyval",
570
                         default={}, dest="hvparams",
571
                         help="Hypervisor parameters")
572

    
573
HYPERVISOR_OPT = cli_option("-H", "--hypervisor-parameters", dest="hypervisor",
574
                            help="Hypervisor and hypervisor options, in the"
575
                            " format hypervisor:option=value,option=value,...",
576
                            default=None, type="identkeyval")
577

    
578
HVLIST_OPT = cli_option("-H", "--hypervisor-parameters", dest="hvparams",
579
                        help="Hypervisor and hypervisor options, in the"
580
                        " format hypervisor:option=value,option=value,...",
581
                        default=[], action="append", type="identkeyval")
582

    
583
NOIPCHECK_OPT = cli_option("--no-ip-check", dest="ip_check", default=True,
584
                           action="store_false",
585
                           help="Don't check that the instance's IP"
586
                           " is alive")
587

    
588
NET_OPT = cli_option("--net",
589
                     help="NIC parameters", default=[],
590
                     dest="nics", action="append", type="identkeyval")
591

    
592
DISK_OPT = cli_option("--disk", help="Disk parameters", default=[],
593
                      dest="disks", action="append", type="identkeyval")
594

    
595
DISKIDX_OPT = cli_option("--disks", dest="disks", default=None,
596
                         help="Comma-separated list of disks"
597
                         " indices to act on (e.g. 0,2) (optional,"
598
                         " defaults to all disks)")
599

    
600
OS_SIZE_OPT = cli_option("-s", "--os-size", dest="sd_size",
601
                         help="Enforces a single-disk configuration using the"
602
                         " given disk size, in MiB unless a suffix is used",
603
                         default=None, type="unit", metavar="<size>")
604

    
605
IGNORE_CONSIST_OPT = cli_option("--ignore-consistency",
606
                                dest="ignore_consistency",
607
                                action="store_true", default=False,
608
                                help="Ignore the consistency of the disks on"
609
                                " the secondary")
610

    
611
NONLIVE_OPT = cli_option("--non-live", dest="live",
612
                         default=True, action="store_false",
613
                         help="Do a non-live migration (this usually means"
614
                         " freeze the instance, save the state, transfer and"
615
                         " only then resume running on the secondary node)")
616

    
617
NODE_PLACEMENT_OPT = cli_option("-n", "--node", dest="node",
618
                                help="Target node and optional secondary node",
619
                                metavar="<pnode>[:<snode>]",
620
                                completion_suggest=OPT_COMPL_INST_ADD_NODES)
621

    
622
NODE_LIST_OPT = cli_option("-n", "--node", dest="nodes", default=[],
623
                           action="append", metavar="<node>",
624
                           help="Use only this node (can be used multiple"
625
                           " times, if not given defaults to all nodes)",
626
                           completion_suggest=OPT_COMPL_ONE_NODE)
627

    
628
SINGLE_NODE_OPT = cli_option("-n", "--node", dest="node", help="Target node",
629
                             metavar="<node>",
630
                             completion_suggest=OPT_COMPL_ONE_NODE)
631

    
632
NOSTART_OPT = cli_option("--no-start", dest="start", default=True,
633
                         action="store_false",
634
                         help="Don't start the instance after creation")
635

    
636
SHOWCMD_OPT = cli_option("--show-cmd", dest="show_command",
637
                         action="store_true", default=False,
638
                         help="Show command instead of executing it")
639

    
640
CLEANUP_OPT = cli_option("--cleanup", dest="cleanup",
641
                         default=False, action="store_true",
642
                         help="Instead of performing the migration, try to"
643
                         " recover from a failed cleanup. This is safe"
644
                         " to run even if the instance is healthy, but it"
645
                         " will create extra replication traffic and "
646
                         " disrupt briefly the replication (like during the"
647
                         " migration")
648

    
649
STATIC_OPT = cli_option("-s", "--static", dest="static",
650
                        action="store_true", default=False,
651
                        help="Only show configuration data, not runtime data")
652

    
653
ALL_OPT = cli_option("--all", dest="show_all",
654
                     default=False, action="store_true",
655
                     help="Show info on all instances on the cluster."
656
                     " This can take a long time to run, use wisely")
657

    
658
SELECT_OS_OPT = cli_option("--select-os", dest="select_os",
659
                           action="store_true", default=False,
660
                           help="Interactive OS reinstall, lists available"
661
                           " OS templates for selection")
662

    
663
IGNORE_FAILURES_OPT = cli_option("--ignore-failures", dest="ignore_failures",
664
                                 action="store_true", default=False,
665
                                 help="Remove the instance from the cluster"
666
                                 " configuration even if there are failures"
667
                                 " during the removal process")
668

    
669
NEW_SECONDARY_OPT = cli_option("-n", "--new-secondary", dest="dst_node",
670
                               help="Specifies the new secondary node",
671
                               metavar="NODE", default=None,
672
                               completion_suggest=OPT_COMPL_ONE_NODE)
673

    
674
ON_PRIMARY_OPT = cli_option("-p", "--on-primary", dest="on_primary",
675
                            default=False, action="store_true",
676
                            help="Replace the disk(s) on the primary"
677
                            " node (only for the drbd template)")
678

    
679
ON_SECONDARY_OPT = cli_option("-s", "--on-secondary", dest="on_secondary",
680
                              default=False, action="store_true",
681
                              help="Replace the disk(s) on the secondary"
682
                              " node (only for the drbd template)")
683

    
684
AUTO_REPLACE_OPT = cli_option("-a", "--auto", dest="auto",
685
                              default=False, action="store_true",
686
                              help="Automatically replace faulty disks"
687
                              " (only for the drbd template)")
688

    
689
IGNORE_SIZE_OPT = cli_option("--ignore-size", dest="ignore_size",
690
                             default=False, action="store_true",
691
                             help="Ignore current recorded size"
692
                             " (useful for forcing activation when"
693
                             " the recorded size is wrong)")
694

    
695
SRC_NODE_OPT = cli_option("--src-node", dest="src_node", help="Source node",
696
                          metavar="<node>",
697
                          completion_suggest=OPT_COMPL_ONE_NODE)
698

    
699
SRC_DIR_OPT = cli_option("--src-dir", dest="src_dir", help="Source directory",
700
                         metavar="<dir>")
701

    
702
SECONDARY_IP_OPT = cli_option("-s", "--secondary-ip", dest="secondary_ip",
703
                              help="Specify the secondary ip for the node",
704
                              metavar="ADDRESS", default=None)
705

    
706
READD_OPT = cli_option("--readd", dest="readd",
707
                       default=False, action="store_true",
708
                       help="Readd old node after replacing it")
709

    
710
NOSSH_KEYCHECK_OPT = cli_option("--no-ssh-key-check", dest="ssh_key_check",
711
                                default=True, action="store_false",
712
                                help="Disable SSH key fingerprint checking")
713

    
714

    
715
MC_OPT = cli_option("-C", "--master-candidate", dest="master_candidate",
716
                    choices=_YESNO, default=None, metavar=_YORNO,
717
                    help="Set the master_candidate flag on the node")
718

    
719
OFFLINE_OPT = cli_option("-O", "--offline", dest="offline", metavar=_YORNO,
720
                         choices=_YESNO, default=None,
721
                         help="Set the offline flag on the node")
722

    
723
DRAINED_OPT = cli_option("-D", "--drained", dest="drained", metavar=_YORNO,
724
                         choices=_YESNO, default=None,
725
                         help="Set the drained flag on the node")
726

    
727
ALLOCATABLE_OPT = cli_option("--allocatable", dest="allocatable",
728
                             choices=_YESNO, default=None, metavar=_YORNO,
729
                             help="Set the allocatable flag on a volume")
730

    
731
NOLVM_STORAGE_OPT = cli_option("--no-lvm-storage", dest="lvm_storage",
732
                               help="Disable support for lvm based instances"
733
                               " (cluster-wide)",
734
                               action="store_false", default=True)
735

    
736
ENABLED_HV_OPT = cli_option("--enabled-hypervisors",
737
                            dest="enabled_hypervisors",
738
                            help="Comma-separated list of hypervisors",
739
                            type="string", default=None)
740

    
741
NIC_PARAMS_OPT = cli_option("-N", "--nic-parameters", dest="nicparams",
742
                            type="keyval", default={},
743
                            help="NIC parameters")
744

    
745
CP_SIZE_OPT = cli_option("-C", "--candidate-pool-size", default=None,
746
                         dest="candidate_pool_size", type="int",
747
                         help="Set the candidate pool size")
748

    
749
VG_NAME_OPT = cli_option("-g", "--vg-name", dest="vg_name",
750
                         help="Enables LVM and specifies the volume group"
751
                         " name (cluster-wide) for disk allocation [xenvg]",
752
                         metavar="VG", default=None)
753

    
754
YES_DOIT_OPT = cli_option("--yes-do-it", dest="yes_do_it",
755
                          help="Destroy cluster", action="store_true")
756

    
757
NOVOTING_OPT = cli_option("--no-voting", dest="no_voting",
758
                          help="Skip node agreement check (dangerous)",
759
                          action="store_true", default=False)
760

    
761
MAC_PREFIX_OPT = cli_option("-m", "--mac-prefix", dest="mac_prefix",
762
                            help="Specify the mac prefix for the instance IP"
763
                            " addresses, in the format XX:XX:XX",
764
                            metavar="PREFIX",
765
                            default=None)
766

    
767
MASTER_NETDEV_OPT = cli_option("--master-netdev", dest="master_netdev",
768
                               help="Specify the node interface (cluster-wide)"
769
                               " on which the master IP address will be added "
770
                               " [%s]" % constants.DEFAULT_BRIDGE,
771
                               metavar="NETDEV",
772
                               default=constants.DEFAULT_BRIDGE)
773

    
774

    
775
GLOBAL_FILEDIR_OPT = cli_option("--file-storage-dir", dest="file_storage_dir",
776
                                help="Specify the default directory (cluster-"
777
                                "wide) for storing the file-based disks [%s]" %
778
                                constants.DEFAULT_FILE_STORAGE_DIR,
779
                                metavar="DIR",
780
                                default=constants.DEFAULT_FILE_STORAGE_DIR)
781

    
782
NOMODIFY_ETCHOSTS_OPT = cli_option("--no-etc-hosts", dest="modify_etc_hosts",
783
                                   help="Don't modify /etc/hosts",
784
                                   action="store_false", default=True)
785

    
786
ERROR_CODES_OPT = cli_option("--error-codes", dest="error_codes",
787
                             help="Enable parseable error messages",
788
                             action="store_true", default=False)
789

    
790
NONPLUS1_OPT = cli_option("--no-nplus1-mem", dest="skip_nplusone_mem",
791
                          help="Skip N+1 memory redundancy tests",
792
                          action="store_true", default=False)
793

    
794

    
795
def _ParseArgs(argv, commands, aliases):
796
  """Parser for the command line arguments.
797

798
  This function parses the arguments and returns the function which
799
  must be executed together with its (modified) arguments.
800

801
  @param argv: the command line
802
  @param commands: dictionary with special contents, see the design
803
      doc for cmdline handling
804
  @param aliases: dictionary with command aliases {'alias': 'target, ...}
805

806
  """
807
  if len(argv) == 0:
808
    binary = "<command>"
809
  else:
810
    binary = argv[0].split("/")[-1]
811

    
812
  if len(argv) > 1 and argv[1] == "--version":
813
    ToStdout("%s (ganeti) %s", binary, constants.RELEASE_VERSION)
814
    # Quit right away. That way we don't have to care about this special
815
    # argument. optparse.py does it the same.
816
    sys.exit(0)
817

    
818
  if len(argv) < 2 or not (argv[1] in commands or
819
                           argv[1] in aliases):
820
    # let's do a nice thing
821
    sortedcmds = commands.keys()
822
    sortedcmds.sort()
823

    
824
    ToStdout("Usage: %s {command} [options...] [argument...]", binary)
825
    ToStdout("%s <command> --help to see details, or man %s", binary, binary)
826
    ToStdout("")
827

    
828
    # compute the max line length for cmd + usage
829
    mlen = max([len(" %s" % cmd) for cmd in commands])
830
    mlen = min(60, mlen) # should not get here...
831

    
832
    # and format a nice command list
833
    ToStdout("Commands:")
834
    for cmd in sortedcmds:
835
      cmdstr = " %s" % (cmd,)
836
      help_text = commands[cmd][4]
837
      help_lines = textwrap.wrap(help_text, 79 - 3 - mlen)
838
      ToStdout("%-*s - %s", mlen, cmdstr, help_lines.pop(0))
839
      for line in help_lines:
840
        ToStdout("%-*s   %s", mlen, "", line)
841

    
842
    ToStdout("")
843

    
844
    return None, None, None
845

    
846
  # get command, unalias it, and look it up in commands
847
  cmd = argv.pop(1)
848
  if cmd in aliases:
849
    if cmd in commands:
850
      raise errors.ProgrammerError("Alias '%s' overrides an existing"
851
                                   " command" % cmd)
852

    
853
    if aliases[cmd] not in commands:
854
      raise errors.ProgrammerError("Alias '%s' maps to non-existing"
855
                                   " command '%s'" % (cmd, aliases[cmd]))
856

    
857
    cmd = aliases[cmd]
858

    
859
  func, args_def, parser_opts, usage, description = commands[cmd]
860
  parser = OptionParser(option_list=parser_opts + [_DRY_RUN_OPT],
861
                        description=description,
862
                        formatter=TitledHelpFormatter(),
863
                        usage="%%prog %s %s" % (cmd, usage))
864
  parser.disable_interspersed_args()
865
  options, args = parser.parse_args()
866

    
867
  if not _CheckArguments(cmd, args_def, args):
868
    return None, None, None
869

    
870
  return func, options, args
871

    
872

    
873
def _CheckArguments(cmd, args_def, args):
874
  """Verifies the arguments using the argument definition.
875

876
  Algorithm:
877

878
    1. Abort with error if values specified by user but none expected.
879

880
    1. For each argument in definition
881

882
      1. Keep running count of minimum number of values (min_count)
883
      1. Keep running count of maximum number of values (max_count)
884
      1. If it has an unlimited number of values
885

886
        1. Abort with error if it's not the last argument in the definition
887

888
    1. If last argument has limited number of values
889

890
      1. Abort with error if number of values doesn't match or is too large
891

892
    1. Abort with error if user didn't pass enough values (min_count)
893

894
  """
895
  if args and not args_def:
896
    ToStderr("Error: Command %s expects no arguments", cmd)
897
    return False
898

    
899
  min_count = None
900
  max_count = None
901
  check_max = None
902

    
903
  last_idx = len(args_def) - 1
904

    
905
  for idx, arg in enumerate(args_def):
906
    if min_count is None:
907
      min_count = arg.min
908
    elif arg.min is not None:
909
      min_count += arg.min
910

    
911
    if max_count is None:
912
      max_count = arg.max
913
    elif arg.max is not None:
914
      max_count += arg.max
915

    
916
    if idx == last_idx:
917
      check_max = (arg.max is not None)
918

    
919
    elif arg.max is None:
920
      raise errors.ProgrammerError("Only the last argument can have max=None")
921

    
922
  if check_max:
923
    # Command with exact number of arguments
924
    if (min_count is not None and max_count is not None and
925
        min_count == max_count and len(args) != min_count):
926
      ToStderr("Error: Command %s expects %d argument(s)", cmd, min_count)
927
      return False
928

    
929
    # Command with limited number of arguments
930
    if max_count is not None and len(args) > max_count:
931
      ToStderr("Error: Command %s expects only %d argument(s)",
932
               cmd, max_count)
933
      return False
934

    
935
  # Command with some required arguments
936
  if min_count is not None and len(args) < min_count:
937
    ToStderr("Error: Command %s expects at least %d argument(s)",
938
             cmd, min_count)
939
    return False
940

    
941
  return True
942

    
943

    
944
def SplitNodeOption(value):
945
  """Splits the value of a --node option.
946

947
  """
948
  if value and ':' in value:
949
    return value.split(':', 1)
950
  else:
951
    return (value, None)
952

    
953

    
954
def UsesRPC(fn):
955
  def wrapper(*args, **kwargs):
956
    rpc.Init()
957
    try:
958
      return fn(*args, **kwargs)
959
    finally:
960
      rpc.Shutdown()
961
  return wrapper
962

    
963

    
964
def AskUser(text, choices=None):
965
  """Ask the user a question.
966

967
  @param text: the question to ask
968

969
  @param choices: list with elements tuples (input_char, return_value,
970
      description); if not given, it will default to: [('y', True,
971
      'Perform the operation'), ('n', False, 'Do no do the operation')];
972
      note that the '?' char is reserved for help
973

974
  @return: one of the return values from the choices list; if input is
975
      not possible (i.e. not running with a tty, we return the last
976
      entry from the list
977

978
  """
979
  if choices is None:
980
    choices = [('y', True, 'Perform the operation'),
981
               ('n', False, 'Do not perform the operation')]
982
  if not choices or not isinstance(choices, list):
983
    raise errors.ProgrammerError("Invalid choices argument to AskUser")
984
  for entry in choices:
985
    if not isinstance(entry, tuple) or len(entry) < 3 or entry[0] == '?':
986
      raise errors.ProgrammerError("Invalid choices element to AskUser")
987

    
988
  answer = choices[-1][1]
989
  new_text = []
990
  for line in text.splitlines():
991
    new_text.append(textwrap.fill(line, 70, replace_whitespace=False))
992
  text = "\n".join(new_text)
993
  try:
994
    f = file("/dev/tty", "a+")
995
  except IOError:
996
    return answer
997
  try:
998
    chars = [entry[0] for entry in choices]
999
    chars[-1] = "[%s]" % chars[-1]
1000
    chars.append('?')
1001
    maps = dict([(entry[0], entry[1]) for entry in choices])
1002
    while True:
1003
      f.write(text)
1004
      f.write('\n')
1005
      f.write("/".join(chars))
1006
      f.write(": ")
1007
      line = f.readline(2).strip().lower()
1008
      if line in maps:
1009
        answer = maps[line]
1010
        break
1011
      elif line == '?':
1012
        for entry in choices:
1013
          f.write(" %s - %s\n" % (entry[0], entry[2]))
1014
        f.write("\n")
1015
        continue
1016
  finally:
1017
    f.close()
1018
  return answer
1019

    
1020

    
1021
class JobSubmittedException(Exception):
1022
  """Job was submitted, client should exit.
1023

1024
  This exception has one argument, the ID of the job that was
1025
  submitted. The handler should print this ID.
1026

1027
  This is not an error, just a structured way to exit from clients.
1028

1029
  """
1030

    
1031

    
1032
def SendJob(ops, cl=None):
1033
  """Function to submit an opcode without waiting for the results.
1034

1035
  @type ops: list
1036
  @param ops: list of opcodes
1037
  @type cl: luxi.Client
1038
  @param cl: the luxi client to use for communicating with the master;
1039
             if None, a new client will be created
1040

1041
  """
1042
  if cl is None:
1043
    cl = GetClient()
1044

    
1045
  job_id = cl.SubmitJob(ops)
1046

    
1047
  return job_id
1048

    
1049

    
1050
def PollJob(job_id, cl=None, feedback_fn=None):
1051
  """Function to poll for the result of a job.
1052

1053
  @type job_id: job identified
1054
  @param job_id: the job to poll for results
1055
  @type cl: luxi.Client
1056
  @param cl: the luxi client to use for communicating with the master;
1057
             if None, a new client will be created
1058

1059
  """
1060
  if cl is None:
1061
    cl = GetClient()
1062

    
1063
  prev_job_info = None
1064
  prev_logmsg_serial = None
1065

    
1066
  while True:
1067
    result = cl.WaitForJobChange(job_id, ["status"], prev_job_info,
1068
                                 prev_logmsg_serial)
1069
    if not result:
1070
      # job not found, go away!
1071
      raise errors.JobLost("Job with id %s lost" % job_id)
1072

    
1073
    # Split result, a tuple of (field values, log entries)
1074
    (job_info, log_entries) = result
1075
    (status, ) = job_info
1076

    
1077
    if log_entries:
1078
      for log_entry in log_entries:
1079
        (serial, timestamp, _, message) = log_entry
1080
        if callable(feedback_fn):
1081
          feedback_fn(log_entry[1:])
1082
        else:
1083
          encoded = utils.SafeEncode(message)
1084
          ToStdout("%s %s", time.ctime(utils.MergeTime(timestamp)), encoded)
1085
        prev_logmsg_serial = max(prev_logmsg_serial, serial)
1086

    
1087
    # TODO: Handle canceled and archived jobs
1088
    elif status in (constants.JOB_STATUS_SUCCESS,
1089
                    constants.JOB_STATUS_ERROR,
1090
                    constants.JOB_STATUS_CANCELING,
1091
                    constants.JOB_STATUS_CANCELED):
1092
      break
1093

    
1094
    prev_job_info = job_info
1095

    
1096
  jobs = cl.QueryJobs([job_id], ["status", "opstatus", "opresult"])
1097
  if not jobs:
1098
    raise errors.JobLost("Job with id %s lost" % job_id)
1099

    
1100
  status, opstatus, result = jobs[0]
1101
  if status == constants.JOB_STATUS_SUCCESS:
1102
    return result
1103
  elif status in (constants.JOB_STATUS_CANCELING,
1104
                  constants.JOB_STATUS_CANCELED):
1105
    raise errors.OpExecError("Job was canceled")
1106
  else:
1107
    has_ok = False
1108
    for idx, (status, msg) in enumerate(zip(opstatus, result)):
1109
      if status == constants.OP_STATUS_SUCCESS:
1110
        has_ok = True
1111
      elif status == constants.OP_STATUS_ERROR:
1112
        errors.MaybeRaise(msg)
1113
        if has_ok:
1114
          raise errors.OpExecError("partial failure (opcode %d): %s" %
1115
                                   (idx, msg))
1116
        else:
1117
          raise errors.OpExecError(str(msg))
1118
    # default failure mode
1119
    raise errors.OpExecError(result)
1120

    
1121

    
1122
def SubmitOpCode(op, cl=None, feedback_fn=None):
1123
  """Legacy function to submit an opcode.
1124

1125
  This is just a simple wrapper over the construction of the processor
1126
  instance. It should be extended to better handle feedback and
1127
  interaction functions.
1128

1129
  """
1130
  if cl is None:
1131
    cl = GetClient()
1132

    
1133
  job_id = SendJob([op], cl)
1134

    
1135
  op_results = PollJob(job_id, cl=cl, feedback_fn=feedback_fn)
1136

    
1137
  return op_results[0]
1138

    
1139

    
1140
def SubmitOrSend(op, opts, cl=None, feedback_fn=None):
1141
  """Wrapper around SubmitOpCode or SendJob.
1142

1143
  This function will decide, based on the 'opts' parameter, whether to
1144
  submit and wait for the result of the opcode (and return it), or
1145
  whether to just send the job and print its identifier. It is used in
1146
  order to simplify the implementation of the '--submit' option.
1147

1148
  It will also add the dry-run parameter from the options passed, if true.
1149

1150
  """
1151
  if opts and opts.dry_run:
1152
    op.dry_run = opts.dry_run
1153
  if opts and opts.submit_only:
1154
    job_id = SendJob([op], cl=cl)
1155
    raise JobSubmittedException(job_id)
1156
  else:
1157
    return SubmitOpCode(op, cl=cl, feedback_fn=feedback_fn)
1158

    
1159

    
1160
def GetClient():
1161
  # TODO: Cache object?
1162
  try:
1163
    client = luxi.Client()
1164
  except luxi.NoMasterError:
1165
    master, myself = ssconf.GetMasterAndMyself()
1166
    if master != myself:
1167
      raise errors.OpPrereqError("This is not the master node, please connect"
1168
                                 " to node '%s' and rerun the command" %
1169
                                 master)
1170
    else:
1171
      raise
1172
  return client
1173

    
1174

    
1175
def FormatError(err):
1176
  """Return a formatted error message for a given error.
1177

1178
  This function takes an exception instance and returns a tuple
1179
  consisting of two values: first, the recommended exit code, and
1180
  second, a string describing the error message (not
1181
  newline-terminated).
1182

1183
  """
1184
  retcode = 1
1185
  obuf = StringIO()
1186
  msg = str(err)
1187
  if isinstance(err, errors.ConfigurationError):
1188
    txt = "Corrupt configuration file: %s" % msg
1189
    logging.error(txt)
1190
    obuf.write(txt + "\n")
1191
    obuf.write("Aborting.")
1192
    retcode = 2
1193
  elif isinstance(err, errors.HooksAbort):
1194
    obuf.write("Failure: hooks execution failed:\n")
1195
    for node, script, out in err.args[0]:
1196
      if out:
1197
        obuf.write("  node: %s, script: %s, output: %s\n" %
1198
                   (node, script, out))
1199
      else:
1200
        obuf.write("  node: %s, script: %s (no output)\n" %
1201
                   (node, script))
1202
  elif isinstance(err, errors.HooksFailure):
1203
    obuf.write("Failure: hooks general failure: %s" % msg)
1204
  elif isinstance(err, errors.ResolverError):
1205
    this_host = utils.HostInfo.SysName()
1206
    if err.args[0] == this_host:
1207
      msg = "Failure: can't resolve my own hostname ('%s')"
1208
    else:
1209
      msg = "Failure: can't resolve hostname '%s'"
1210
    obuf.write(msg % err.args[0])
1211
  elif isinstance(err, errors.OpPrereqError):
1212
    obuf.write("Failure: prerequisites not met for this"
1213
               " operation:\n%s" % msg)
1214
  elif isinstance(err, errors.OpExecError):
1215
    obuf.write("Failure: command execution error:\n%s" % msg)
1216
  elif isinstance(err, errors.TagError):
1217
    obuf.write("Failure: invalid tag(s) given:\n%s" % msg)
1218
  elif isinstance(err, errors.JobQueueDrainError):
1219
    obuf.write("Failure: the job queue is marked for drain and doesn't"
1220
               " accept new requests\n")
1221
  elif isinstance(err, errors.JobQueueFull):
1222
    obuf.write("Failure: the job queue is full and doesn't accept new"
1223
               " job submissions until old jobs are archived\n")
1224
  elif isinstance(err, errors.TypeEnforcementError):
1225
    obuf.write("Parameter Error: %s" % msg)
1226
  elif isinstance(err, errors.ParameterError):
1227
    obuf.write("Failure: unknown/wrong parameter name '%s'" % msg)
1228
  elif isinstance(err, errors.GenericError):
1229
    obuf.write("Unhandled Ganeti error: %s" % msg)
1230
  elif isinstance(err, luxi.NoMasterError):
1231
    obuf.write("Cannot communicate with the master daemon.\nIs it running"
1232
               " and listening for connections?")
1233
  elif isinstance(err, luxi.TimeoutError):
1234
    obuf.write("Timeout while talking to the master daemon. Error:\n"
1235
               "%s" % msg)
1236
  elif isinstance(err, luxi.ProtocolError):
1237
    obuf.write("Unhandled protocol error while talking to the master daemon:\n"
1238
               "%s" % msg)
1239
  elif isinstance(err, JobSubmittedException):
1240
    obuf.write("JobID: %s\n" % err.args[0])
1241
    retcode = 0
1242
  else:
1243
    obuf.write("Unhandled exception: %s" % msg)
1244
  return retcode, obuf.getvalue().rstrip('\n')
1245

    
1246

    
1247
def GenericMain(commands, override=None, aliases=None):
1248
  """Generic main function for all the gnt-* commands.
1249

1250
  Arguments:
1251
    - commands: a dictionary with a special structure, see the design doc
1252
                for command line handling.
1253
    - override: if not None, we expect a dictionary with keys that will
1254
                override command line options; this can be used to pass
1255
                options from the scripts to generic functions
1256
    - aliases: dictionary with command aliases {'alias': 'target, ...}
1257

1258
  """
1259
  # save the program name and the entire command line for later logging
1260
  if sys.argv:
1261
    binary = os.path.basename(sys.argv[0]) or sys.argv[0]
1262
    if len(sys.argv) >= 2:
1263
      binary += " " + sys.argv[1]
1264
      old_cmdline = " ".join(sys.argv[2:])
1265
    else:
1266
      old_cmdline = ""
1267
  else:
1268
    binary = "<unknown program>"
1269
    old_cmdline = ""
1270

    
1271
  if aliases is None:
1272
    aliases = {}
1273

    
1274
  try:
1275
    func, options, args = _ParseArgs(sys.argv, commands, aliases)
1276
  except errors.ParameterError, err:
1277
    result, err_msg = FormatError(err)
1278
    ToStderr(err_msg)
1279
    return 1
1280

    
1281
  if func is None: # parse error
1282
    return 1
1283

    
1284
  if override is not None:
1285
    for key, val in override.iteritems():
1286
      setattr(options, key, val)
1287

    
1288
  utils.SetupLogging(constants.LOG_COMMANDS, debug=options.debug,
1289
                     stderr_logging=True, program=binary)
1290

    
1291
  if old_cmdline:
1292
    logging.info("run with arguments '%s'", old_cmdline)
1293
  else:
1294
    logging.info("run with no arguments")
1295

    
1296
  try:
1297
    result = func(options, args)
1298
  except (errors.GenericError, luxi.ProtocolError,
1299
          JobSubmittedException), err:
1300
    result, err_msg = FormatError(err)
1301
    logging.exception("Error during command processing")
1302
    ToStderr(err_msg)
1303

    
1304
  return result
1305

    
1306

    
1307
def GenerateTable(headers, fields, separator, data,
1308
                  numfields=None, unitfields=None,
1309
                  units=None):
1310
  """Prints a table with headers and different fields.
1311

1312
  @type headers: dict
1313
  @param headers: dictionary mapping field names to headers for
1314
      the table
1315
  @type fields: list
1316
  @param fields: the field names corresponding to each row in
1317
      the data field
1318
  @param separator: the separator to be used; if this is None,
1319
      the default 'smart' algorithm is used which computes optimal
1320
      field width, otherwise just the separator is used between
1321
      each field
1322
  @type data: list
1323
  @param data: a list of lists, each sublist being one row to be output
1324
  @type numfields: list
1325
  @param numfields: a list with the fields that hold numeric
1326
      values and thus should be right-aligned
1327
  @type unitfields: list
1328
  @param unitfields: a list with the fields that hold numeric
1329
      values that should be formatted with the units field
1330
  @type units: string or None
1331
  @param units: the units we should use for formatting, or None for
1332
      automatic choice (human-readable for non-separator usage, otherwise
1333
      megabytes); this is a one-letter string
1334

1335
  """
1336
  if units is None:
1337
    if separator:
1338
      units = "m"
1339
    else:
1340
      units = "h"
1341

    
1342
  if numfields is None:
1343
    numfields = []
1344
  if unitfields is None:
1345
    unitfields = []
1346

    
1347
  numfields = utils.FieldSet(*numfields)
1348
  unitfields = utils.FieldSet(*unitfields)
1349

    
1350
  format_fields = []
1351
  for field in fields:
1352
    if headers and field not in headers:
1353
      # TODO: handle better unknown fields (either revert to old
1354
      # style of raising exception, or deal more intelligently with
1355
      # variable fields)
1356
      headers[field] = field
1357
    if separator is not None:
1358
      format_fields.append("%s")
1359
    elif numfields.Matches(field):
1360
      format_fields.append("%*s")
1361
    else:
1362
      format_fields.append("%-*s")
1363

    
1364
  if separator is None:
1365
    mlens = [0 for name in fields]
1366
    format = ' '.join(format_fields)
1367
  else:
1368
    format = separator.replace("%", "%%").join(format_fields)
1369

    
1370
  for row in data:
1371
    if row is None:
1372
      continue
1373
    for idx, val in enumerate(row):
1374
      if unitfields.Matches(fields[idx]):
1375
        try:
1376
          val = int(val)
1377
        except ValueError:
1378
          pass
1379
        else:
1380
          val = row[idx] = utils.FormatUnit(val, units)
1381
      val = row[idx] = str(val)
1382
      if separator is None:
1383
        mlens[idx] = max(mlens[idx], len(val))
1384

    
1385
  result = []
1386
  if headers:
1387
    args = []
1388
    for idx, name in enumerate(fields):
1389
      hdr = headers[name]
1390
      if separator is None:
1391
        mlens[idx] = max(mlens[idx], len(hdr))
1392
        args.append(mlens[idx])
1393
      args.append(hdr)
1394
    result.append(format % tuple(args))
1395

    
1396
  for line in data:
1397
    args = []
1398
    if line is None:
1399
      line = ['-' for _ in fields]
1400
    for idx in xrange(len(fields)):
1401
      if separator is None:
1402
        args.append(mlens[idx])
1403
      args.append(line[idx])
1404
    result.append(format % tuple(args))
1405

    
1406
  return result
1407

    
1408

    
1409
def FormatTimestamp(ts):
1410
  """Formats a given timestamp.
1411

1412
  @type ts: timestamp
1413
  @param ts: a timeval-type timestamp, a tuple of seconds and microseconds
1414

1415
  @rtype: string
1416
  @return: a string with the formatted timestamp
1417

1418
  """
1419
  if not isinstance (ts, (tuple, list)) or len(ts) != 2:
1420
    return '?'
1421
  sec, usec = ts
1422
  return time.strftime("%F %T", time.localtime(sec)) + ".%06d" % usec
1423

    
1424

    
1425
def ParseTimespec(value):
1426
  """Parse a time specification.
1427

1428
  The following suffixed will be recognized:
1429

1430
    - s: seconds
1431
    - m: minutes
1432
    - h: hours
1433
    - d: day
1434
    - w: weeks
1435

1436
  Without any suffix, the value will be taken to be in seconds.
1437

1438
  """
1439
  value = str(value)
1440
  if not value:
1441
    raise errors.OpPrereqError("Empty time specification passed")
1442
  suffix_map = {
1443
    's': 1,
1444
    'm': 60,
1445
    'h': 3600,
1446
    'd': 86400,
1447
    'w': 604800,
1448
    }
1449
  if value[-1] not in suffix_map:
1450
    try:
1451
      value = int(value)
1452
    except ValueError:
1453
      raise errors.OpPrereqError("Invalid time specification '%s'" % value)
1454
  else:
1455
    multiplier = suffix_map[value[-1]]
1456
    value = value[:-1]
1457
    if not value: # no data left after stripping the suffix
1458
      raise errors.OpPrereqError("Invalid time specification (only"
1459
                                 " suffix passed)")
1460
    try:
1461
      value = int(value) * multiplier
1462
    except ValueError:
1463
      raise errors.OpPrereqError("Invalid time specification '%s'" % value)
1464
  return value
1465

    
1466

    
1467
def GetOnlineNodes(nodes, cl=None, nowarn=False):
1468
  """Returns the names of online nodes.
1469

1470
  This function will also log a warning on stderr with the names of
1471
  the online nodes.
1472

1473
  @param nodes: if not empty, use only this subset of nodes (minus the
1474
      offline ones)
1475
  @param cl: if not None, luxi client to use
1476
  @type nowarn: boolean
1477
  @param nowarn: by default, this function will output a note with the
1478
      offline nodes that are skipped; if this parameter is True the
1479
      note is not displayed
1480

1481
  """
1482
  if cl is None:
1483
    cl = GetClient()
1484

    
1485
  result = cl.QueryNodes(names=nodes, fields=["name", "offline"],
1486
                         use_locking=False)
1487
  offline = [row[0] for row in result if row[1]]
1488
  if offline and not nowarn:
1489
    ToStderr("Note: skipping offline node(s): %s" % ", ".join(offline))
1490
  return [row[0] for row in result if not row[1]]
1491

    
1492

    
1493
def _ToStream(stream, txt, *args):
1494
  """Write a message to a stream, bypassing the logging system
1495

1496
  @type stream: file object
1497
  @param stream: the file to which we should write
1498
  @type txt: str
1499
  @param txt: the message
1500

1501
  """
1502
  if args:
1503
    args = tuple(args)
1504
    stream.write(txt % args)
1505
  else:
1506
    stream.write(txt)
1507
  stream.write('\n')
1508
  stream.flush()
1509

    
1510

    
1511
def ToStdout(txt, *args):
1512
  """Write a message to stdout only, bypassing the logging system
1513

1514
  This is just a wrapper over _ToStream.
1515

1516
  @type txt: str
1517
  @param txt: the message
1518

1519
  """
1520
  _ToStream(sys.stdout, txt, *args)
1521

    
1522

    
1523
def ToStderr(txt, *args):
1524
  """Write a message to stderr only, bypassing the logging system
1525

1526
  This is just a wrapper over _ToStream.
1527

1528
  @type txt: str
1529
  @param txt: the message
1530

1531
  """
1532
  _ToStream(sys.stderr, txt, *args)
1533

    
1534

    
1535
class JobExecutor(object):
1536
  """Class which manages the submission and execution of multiple jobs.
1537

1538
  Note that instances of this class should not be reused between
1539
  GetResults() calls.
1540

1541
  """
1542
  def __init__(self, cl=None, verbose=True):
1543
    self.queue = []
1544
    if cl is None:
1545
      cl = GetClient()
1546
    self.cl = cl
1547
    self.verbose = verbose
1548
    self.jobs = []
1549

    
1550
  def QueueJob(self, name, *ops):
1551
    """Record a job for later submit.
1552

1553
    @type name: string
1554
    @param name: a description of the job, will be used in WaitJobSet
1555
    """
1556
    self.queue.append((name, ops))
1557

    
1558
  def SubmitPending(self):
1559
    """Submit all pending jobs.
1560

1561
    """
1562
    results = self.cl.SubmitManyJobs([row[1] for row in self.queue])
1563
    for ((status, data), (name, _)) in zip(results, self.queue):
1564
      self.jobs.append((status, data, name))
1565

    
1566
  def GetResults(self):
1567
    """Wait for and return the results of all jobs.
1568

1569
    @rtype: list
1570
    @return: list of tuples (success, job results), in the same order
1571
        as the submitted jobs; if a job has failed, instead of the result
1572
        there will be the error message
1573

1574
    """
1575
    if not self.jobs:
1576
      self.SubmitPending()
1577
    results = []
1578
    if self.verbose:
1579
      ok_jobs = [row[1] for row in self.jobs if row[0]]
1580
      if ok_jobs:
1581
        ToStdout("Submitted jobs %s", ", ".join(ok_jobs))
1582
    for submit_status, jid, name in self.jobs:
1583
      if not submit_status:
1584
        ToStderr("Failed to submit job for %s: %s", name, jid)
1585
        results.append((False, jid))
1586
        continue
1587
      if self.verbose:
1588
        ToStdout("Waiting for job %s for %s...", jid, name)
1589
      try:
1590
        job_result = PollJob(jid, cl=self.cl)
1591
        success = True
1592
      except (errors.GenericError, luxi.ProtocolError), err:
1593
        _, job_result = FormatError(err)
1594
        success = False
1595
        # the error message will always be shown, verbose or not
1596
        ToStderr("Job %s for %s has failed: %s", jid, name, job_result)
1597

    
1598
      results.append((success, job_result))
1599
    return results
1600

    
1601
  def WaitOrShow(self, wait):
1602
    """Wait for job results or only print the job IDs.
1603

1604
    @type wait: boolean
1605
    @param wait: whether to wait or not
1606

1607
    """
1608
    if wait:
1609
      return self.GetResults()
1610
    else:
1611
      if not self.jobs:
1612
        self.SubmitPending()
1613
      for status, result, name in self.jobs:
1614
        if status:
1615
          ToStdout("%s: %s", result, name)
1616
        else:
1617
          ToStderr("Failure for %s: %s", name, result)