Statistics
| Branch: | Tag: | Revision:

root / lib / cli.py @ 064c21f8

History | View | Annotate | Download (51.6 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_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)