Statistics
| Branch: | Tag: | Revision:

root / lib / cli.py @ cf692cd0

History | View | Annotate | Download (100.5 kB)

1
#
2
#
3

    
4
# Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011 Google Inc.
5
#
6
# This program is free software; you can redistribute it and/or modify
7
# it under the terms of the GNU General Public License as published by
8
# the Free Software Foundation; either version 2 of the License, or
9
# (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful, but
12
# WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14
# General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19
# 02110-1301, USA.
20

    
21

    
22
"""Module dealing with command line parsing"""
23

    
24

    
25
import sys
26
import textwrap
27
import os.path
28
import time
29
import logging
30
import errno
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
from ganeti import ssh
41
from ganeti import compat
42
from ganeti import netutils
43
from ganeti import qlang
44

    
45
from optparse import (OptionParser, TitledHelpFormatter,
46
                      Option, OptionValueError)
47

    
48

    
49
__all__ = [
50
  # Command line options
51
  "ADD_UIDS_OPT",
52
  "ALLOCATABLE_OPT",
53
  "ALLOC_POLICY_OPT",
54
  "ALL_OPT",
55
  "ALLOW_FAILOVER_OPT",
56
  "AUTO_PROMOTE_OPT",
57
  "AUTO_REPLACE_OPT",
58
  "BACKEND_OPT",
59
  "BLK_OS_OPT",
60
  "CAPAB_MASTER_OPT",
61
  "CAPAB_VM_OPT",
62
  "CLEANUP_OPT",
63
  "CLUSTER_DOMAIN_SECRET_OPT",
64
  "CONFIRM_OPT",
65
  "CP_SIZE_OPT",
66
  "DEBUG_OPT",
67
  "DEBUG_SIMERR_OPT",
68
  "DISKIDX_OPT",
69
  "DISK_OPT",
70
  "DISK_TEMPLATE_OPT",
71
  "DRAINED_OPT",
72
  "DRY_RUN_OPT",
73
  "DRBD_HELPER_OPT",
74
  "DST_NODE_OPT",
75
  "EARLY_RELEASE_OPT",
76
  "ENABLED_HV_OPT",
77
  "ERROR_CODES_OPT",
78
  "FIELDS_OPT",
79
  "FILESTORE_DIR_OPT",
80
  "FILESTORE_DRIVER_OPT",
81
  "FORCE_FILTER_OPT",
82
  "FORCE_OPT",
83
  "FORCE_VARIANT_OPT",
84
  "GLOBAL_FILEDIR_OPT",
85
  "HID_OS_OPT",
86
  "GLOBAL_SHARED_FILEDIR_OPT",
87
  "HVLIST_OPT",
88
  "HVOPTS_OPT",
89
  "HYPERVISOR_OPT",
90
  "IALLOCATOR_OPT",
91
  "DEFAULT_IALLOCATOR_OPT",
92
  "IDENTIFY_DEFAULTS_OPT",
93
  "IGNORE_CONSIST_OPT",
94
  "IGNORE_FAILURES_OPT",
95
  "IGNORE_OFFLINE_OPT",
96
  "IGNORE_REMOVE_FAILURES_OPT",
97
  "IGNORE_SECONDARIES_OPT",
98
  "IGNORE_SIZE_OPT",
99
  "INTERVAL_OPT",
100
  "MAC_PREFIX_OPT",
101
  "MAINTAIN_NODE_HEALTH_OPT",
102
  "MASTER_NETDEV_OPT",
103
  "MC_OPT",
104
  "MIGRATION_MODE_OPT",
105
  "NET_OPT",
106
  "NEW_CLUSTER_CERT_OPT",
107
  "NEW_CLUSTER_DOMAIN_SECRET_OPT",
108
  "NEW_CONFD_HMAC_KEY_OPT",
109
  "NEW_RAPI_CERT_OPT",
110
  "NEW_SECONDARY_OPT",
111
  "NIC_PARAMS_OPT",
112
  "NODE_FORCE_JOIN_OPT",
113
  "NODE_LIST_OPT",
114
  "NODE_PLACEMENT_OPT",
115
  "NODEGROUP_OPT",
116
  "NODE_PARAMS_OPT",
117
  "NODE_POWERED_OPT",
118
  "NODRBD_STORAGE_OPT",
119
  "NOHDR_OPT",
120
  "NOIPCHECK_OPT",
121
  "NO_INSTALL_OPT",
122
  "NONAMECHECK_OPT",
123
  "NOLVM_STORAGE_OPT",
124
  "NOMODIFY_ETCHOSTS_OPT",
125
  "NOMODIFY_SSH_SETUP_OPT",
126
  "NONICS_OPT",
127
  "NONLIVE_OPT",
128
  "NONPLUS1_OPT",
129
  "NOSHUTDOWN_OPT",
130
  "NOSTART_OPT",
131
  "NOSSH_KEYCHECK_OPT",
132
  "NOVOTING_OPT",
133
  "NO_REMEMBER_OPT",
134
  "NWSYNC_OPT",
135
  "ON_PRIMARY_OPT",
136
  "ON_SECONDARY_OPT",
137
  "OFFLINE_OPT",
138
  "OSPARAMS_OPT",
139
  "OS_OPT",
140
  "OS_SIZE_OPT",
141
  "OOB_TIMEOUT_OPT",
142
  "POWER_DELAY_OPT",
143
  "PREALLOC_WIPE_DISKS_OPT",
144
  "PRIMARY_IP_VERSION_OPT",
145
  "PRIORITY_OPT",
146
  "RAPI_CERT_OPT",
147
  "READD_OPT",
148
  "REBOOT_TYPE_OPT",
149
  "REMOVE_INSTANCE_OPT",
150
  "REMOVE_UIDS_OPT",
151
  "RESERVED_LVS_OPT",
152
  "ROMAN_OPT",
153
  "SECONDARY_IP_OPT",
154
  "SELECT_OS_OPT",
155
  "SEP_OPT",
156
  "SHOWCMD_OPT",
157
  "SHUTDOWN_TIMEOUT_OPT",
158
  "SINGLE_NODE_OPT",
159
  "SRC_DIR_OPT",
160
  "SRC_NODE_OPT",
161
  "SUBMIT_OPT",
162
  "STATIC_OPT",
163
  "SYNC_OPT",
164
  "TAG_SRC_OPT",
165
  "TIMEOUT_OPT",
166
  "UIDPOOL_OPT",
167
  "USEUNITS_OPT",
168
  "USE_REPL_NET_OPT",
169
  "VERBOSE_OPT",
170
  "VG_NAME_OPT",
171
  "YES_DOIT_OPT",
172
  # Generic functions for CLI programs
173
  "ConfirmOperation",
174
  "GenericMain",
175
  "GenericInstanceCreate",
176
  "GenericList",
177
  "GenericListFields",
178
  "GetClient",
179
  "GetOnlineNodes",
180
  "JobExecutor",
181
  "JobSubmittedException",
182
  "ParseTimespec",
183
  "RunWhileClusterStopped",
184
  "SubmitOpCode",
185
  "SubmitOrSend",
186
  "UsesRPC",
187
  # Formatting functions
188
  "ToStderr", "ToStdout",
189
  "FormatError",
190
  "FormatQueryResult",
191
  "FormatParameterDict",
192
  "GenerateTable",
193
  "AskUser",
194
  "FormatTimestamp",
195
  "FormatLogMessage",
196
  # Tags functions
197
  "ListTags",
198
  "AddTags",
199
  "RemoveTags",
200
  # command line options support infrastructure
201
  "ARGS_MANY_INSTANCES",
202
  "ARGS_MANY_NODES",
203
  "ARGS_MANY_GROUPS",
204
  "ARGS_NONE",
205
  "ARGS_ONE_INSTANCE",
206
  "ARGS_ONE_NODE",
207
  "ARGS_ONE_GROUP",
208
  "ARGS_ONE_OS",
209
  "ArgChoice",
210
  "ArgCommand",
211
  "ArgFile",
212
  "ArgGroup",
213
  "ArgHost",
214
  "ArgInstance",
215
  "ArgJobId",
216
  "ArgNode",
217
  "ArgOs",
218
  "ArgSuggest",
219
  "ArgUnknown",
220
  "OPT_COMPL_INST_ADD_NODES",
221
  "OPT_COMPL_MANY_NODES",
222
  "OPT_COMPL_ONE_IALLOCATOR",
223
  "OPT_COMPL_ONE_INSTANCE",
224
  "OPT_COMPL_ONE_NODE",
225
  "OPT_COMPL_ONE_NODEGROUP",
226
  "OPT_COMPL_ONE_OS",
227
  "cli_option",
228
  "SplitNodeOption",
229
  "CalculateOSNames",
230
  "ParseFields",
231
  "COMMON_CREATE_OPTS",
232
  ]
233

    
234
NO_PREFIX = "no_"
235
UN_PREFIX = "-"
236

    
237
#: Priorities (sorted)
238
_PRIORITY_NAMES = [
239
  ("low", constants.OP_PRIO_LOW),
240
  ("normal", constants.OP_PRIO_NORMAL),
241
  ("high", constants.OP_PRIO_HIGH),
242
  ]
243

    
244
#: Priority dictionary for easier lookup
245
# TODO: Replace this and _PRIORITY_NAMES with a single sorted dictionary once
246
# we migrate to Python 2.6
247
_PRIONAME_TO_VALUE = dict(_PRIORITY_NAMES)
248

    
249
# Query result status for clients
250
(QR_NORMAL,
251
 QR_UNKNOWN,
252
 QR_INCOMPLETE) = range(3)
253

    
254

    
255
class _Argument:
256
  def __init__(self, min=0, max=None): # pylint: disable-msg=W0622
257
    self.min = min
258
    self.max = max
259

    
260
  def __repr__(self):
261
    return ("<%s min=%s max=%s>" %
262
            (self.__class__.__name__, self.min, self.max))
263

    
264

    
265
class ArgSuggest(_Argument):
266
  """Suggesting argument.
267

268
  Value can be any of the ones passed to the constructor.
269

270
  """
271
  # pylint: disable-msg=W0622
272
  def __init__(self, min=0, max=None, choices=None):
273
    _Argument.__init__(self, min=min, max=max)
274
    self.choices = choices
275

    
276
  def __repr__(self):
277
    return ("<%s min=%s max=%s choices=%r>" %
278
            (self.__class__.__name__, self.min, self.max, self.choices))
279

    
280

    
281
class ArgChoice(ArgSuggest):
282
  """Choice argument.
283

284
  Value can be any of the ones passed to the constructor. Like L{ArgSuggest},
285
  but value must be one of the choices.
286

287
  """
288

    
289

    
290
class ArgUnknown(_Argument):
291
  """Unknown argument to program (e.g. determined at runtime).
292

293
  """
294

    
295

    
296
class ArgInstance(_Argument):
297
  """Instances argument.
298

299
  """
300

    
301

    
302
class ArgNode(_Argument):
303
  """Node argument.
304

305
  """
306

    
307

    
308
class ArgGroup(_Argument):
309
  """Node group argument.
310

311
  """
312

    
313

    
314
class ArgJobId(_Argument):
315
  """Job ID argument.
316

317
  """
318

    
319

    
320
class ArgFile(_Argument):
321
  """File path argument.
322

323
  """
324

    
325

    
326
class ArgCommand(_Argument):
327
  """Command argument.
328

329
  """
330

    
331

    
332
class ArgHost(_Argument):
333
  """Host argument.
334

335
  """
336

    
337

    
338
class ArgOs(_Argument):
339
  """OS argument.
340

341
  """
342

    
343

    
344
ARGS_NONE = []
345
ARGS_MANY_INSTANCES = [ArgInstance()]
346
ARGS_MANY_NODES = [ArgNode()]
347
ARGS_MANY_GROUPS = [ArgGroup()]
348
ARGS_ONE_INSTANCE = [ArgInstance(min=1, max=1)]
349
ARGS_ONE_NODE = [ArgNode(min=1, max=1)]
350
# TODO
351
ARGS_ONE_GROUP = [ArgGroup(min=1, max=1)]
352
ARGS_ONE_OS = [ArgOs(min=1, max=1)]
353

    
354

    
355
def _ExtractTagsObject(opts, args):
356
  """Extract the tag type object.
357

358
  Note that this function will modify its args parameter.
359

360
  """
361
  if not hasattr(opts, "tag_type"):
362
    raise errors.ProgrammerError("tag_type not passed to _ExtractTagsObject")
363
  kind = opts.tag_type
364
  if kind == constants.TAG_CLUSTER:
365
    retval = kind, kind
366
  elif kind in (constants.TAG_NODEGROUP,
367
                constants.TAG_NODE,
368
                constants.TAG_INSTANCE):
369
    if not args:
370
      raise errors.OpPrereqError("no arguments passed to the command")
371
    name = args.pop(0)
372
    retval = kind, name
373
  else:
374
    raise errors.ProgrammerError("Unhandled tag type '%s'" % kind)
375
  return retval
376

    
377

    
378
def _ExtendTags(opts, args):
379
  """Extend the args if a source file has been given.
380

381
  This function will extend the tags with the contents of the file
382
  passed in the 'tags_source' attribute of the opts parameter. A file
383
  named '-' will be replaced by stdin.
384

385
  """
386
  fname = opts.tags_source
387
  if fname is None:
388
    return
389
  if fname == "-":
390
    new_fh = sys.stdin
391
  else:
392
    new_fh = open(fname, "r")
393
  new_data = []
394
  try:
395
    # we don't use the nice 'new_data = [line.strip() for line in fh]'
396
    # because of python bug 1633941
397
    while True:
398
      line = new_fh.readline()
399
      if not line:
400
        break
401
      new_data.append(line.strip())
402
  finally:
403
    new_fh.close()
404
  args.extend(new_data)
405

    
406

    
407
def ListTags(opts, args):
408
  """List the tags on a given object.
409

410
  This is a generic implementation that knows how to deal with all
411
  three cases of tag objects (cluster, node, instance). The opts
412
  argument is expected to contain a tag_type field denoting what
413
  object type we work on.
414

415
  """
416
  kind, name = _ExtractTagsObject(opts, args)
417
  cl = GetClient()
418
  result = cl.QueryTags(kind, name)
419
  result = list(result)
420
  result.sort()
421
  for tag in result:
422
    ToStdout(tag)
423

    
424

    
425
def AddTags(opts, args):
426
  """Add tags on a given object.
427

428
  This is a generic implementation that knows how to deal with all
429
  three cases of tag objects (cluster, node, instance). The opts
430
  argument is expected to contain a tag_type field denoting what
431
  object type we work on.
432

433
  """
434
  kind, name = _ExtractTagsObject(opts, args)
435
  _ExtendTags(opts, args)
436
  if not args:
437
    raise errors.OpPrereqError("No tags to be added")
438
  op = opcodes.OpTagsSet(kind=kind, name=name, tags=args)
439
  SubmitOpCode(op, opts=opts)
440

    
441

    
442
def RemoveTags(opts, args):
443
  """Remove tags from a given object.
444

445
  This is a generic implementation that knows how to deal with all
446
  three cases of tag objects (cluster, node, instance). The opts
447
  argument is expected to contain a tag_type field denoting what
448
  object type we work on.
449

450
  """
451
  kind, name = _ExtractTagsObject(opts, args)
452
  _ExtendTags(opts, args)
453
  if not args:
454
    raise errors.OpPrereqError("No tags to be removed")
455
  op = opcodes.OpTagsDel(kind=kind, name=name, tags=args)
456
  SubmitOpCode(op, opts=opts)
457

    
458

    
459
def check_unit(option, opt, value): # pylint: disable-msg=W0613
460
  """OptParsers custom converter for units.
461

462
  """
463
  try:
464
    return utils.ParseUnit(value)
465
  except errors.UnitParseError, err:
466
    raise OptionValueError("option %s: %s" % (opt, err))
467

    
468

    
469
def _SplitKeyVal(opt, data):
470
  """Convert a KeyVal string into a dict.
471

472
  This function will convert a key=val[,...] string into a dict. Empty
473
  values will be converted specially: keys which have the prefix 'no_'
474
  will have the value=False and the prefix stripped, the others will
475
  have value=True.
476

477
  @type opt: string
478
  @param opt: a string holding the option name for which we process the
479
      data, used in building error messages
480
  @type data: string
481
  @param data: a string of the format key=val,key=val,...
482
  @rtype: dict
483
  @return: {key=val, key=val}
484
  @raises errors.ParameterError: if there are duplicate keys
485

486
  """
487
  kv_dict = {}
488
  if data:
489
    for elem in utils.UnescapeAndSplit(data, sep=","):
490
      if "=" in elem:
491
        key, val = elem.split("=", 1)
492
      else:
493
        if elem.startswith(NO_PREFIX):
494
          key, val = elem[len(NO_PREFIX):], False
495
        elif elem.startswith(UN_PREFIX):
496
          key, val = elem[len(UN_PREFIX):], None
497
        else:
498
          key, val = elem, True
499
      if key in kv_dict:
500
        raise errors.ParameterError("Duplicate key '%s' in option %s" %
501
                                    (key, opt))
502
      kv_dict[key] = val
503
  return kv_dict
504

    
505

    
506
def check_ident_key_val(option, opt, value):  # pylint: disable-msg=W0613
507
  """Custom parser for ident:key=val,key=val options.
508

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

512
  """
513
  if ":" not in value:
514
    ident, rest = value, ''
515
  else:
516
    ident, rest = value.split(":", 1)
517

    
518
  if ident.startswith(NO_PREFIX):
519
    if rest:
520
      msg = "Cannot pass options when removing parameter groups: %s" % value
521
      raise errors.ParameterError(msg)
522
    retval = (ident[len(NO_PREFIX):], False)
523
  elif ident.startswith(UN_PREFIX):
524
    if rest:
525
      msg = "Cannot pass options when removing parameter groups: %s" % value
526
      raise errors.ParameterError(msg)
527
    retval = (ident[len(UN_PREFIX):], None)
528
  else:
529
    kv_dict = _SplitKeyVal(opt, rest)
530
    retval = (ident, kv_dict)
531
  return retval
532

    
533

    
534
def check_key_val(option, opt, value):  # pylint: disable-msg=W0613
535
  """Custom parser class for key=val,key=val options.
536

537
  This will store the parsed values as a dict {key: val}.
538

539
  """
540
  return _SplitKeyVal(opt, value)
541

    
542

    
543
def check_bool(option, opt, value): # pylint: disable-msg=W0613
544
  """Custom parser for yes/no options.
545

546
  This will store the parsed value as either True or False.
547

548
  """
549
  value = value.lower()
550
  if value == constants.VALUE_FALSE or value == "no":
551
    return False
552
  elif value == constants.VALUE_TRUE or value == "yes":
553
    return True
554
  else:
555
    raise errors.ParameterError("Invalid boolean value '%s'" % value)
556

    
557

    
558
# completion_suggestion is normally a list. Using numeric values not evaluating
559
# to False for dynamic completion.
560
(OPT_COMPL_MANY_NODES,
561
 OPT_COMPL_ONE_NODE,
562
 OPT_COMPL_ONE_INSTANCE,
563
 OPT_COMPL_ONE_OS,
564
 OPT_COMPL_ONE_IALLOCATOR,
565
 OPT_COMPL_INST_ADD_NODES,
566
 OPT_COMPL_ONE_NODEGROUP) = range(100, 107)
567

    
568
OPT_COMPL_ALL = frozenset([
569
  OPT_COMPL_MANY_NODES,
570
  OPT_COMPL_ONE_NODE,
571
  OPT_COMPL_ONE_INSTANCE,
572
  OPT_COMPL_ONE_OS,
573
  OPT_COMPL_ONE_IALLOCATOR,
574
  OPT_COMPL_INST_ADD_NODES,
575
  OPT_COMPL_ONE_NODEGROUP,
576
  ])
577

    
578

    
579
class CliOption(Option):
580
  """Custom option class for optparse.
581

582
  """
583
  ATTRS = Option.ATTRS + [
584
    "completion_suggest",
585
    ]
586
  TYPES = Option.TYPES + (
587
    "identkeyval",
588
    "keyval",
589
    "unit",
590
    "bool",
591
    )
592
  TYPE_CHECKER = Option.TYPE_CHECKER.copy()
593
  TYPE_CHECKER["identkeyval"] = check_ident_key_val
594
  TYPE_CHECKER["keyval"] = check_key_val
595
  TYPE_CHECKER["unit"] = check_unit
596
  TYPE_CHECKER["bool"] = check_bool
597

    
598

    
599
# optparse.py sets make_option, so we do it for our own option class, too
600
cli_option = CliOption
601

    
602

    
603
_YORNO = "yes|no"
604

    
605
DEBUG_OPT = cli_option("-d", "--debug", default=0, action="count",
606
                       help="Increase debugging level")
607

    
608
NOHDR_OPT = cli_option("--no-headers", default=False,
609
                       action="store_true", dest="no_headers",
610
                       help="Don't display column headers")
611

    
612
SEP_OPT = cli_option("--separator", default=None,
613
                     action="store", dest="separator",
614
                     help=("Separator between output fields"
615
                           " (defaults to one space)"))
616

    
617
USEUNITS_OPT = cli_option("--units", default=None,
618
                          dest="units", choices=('h', 'm', 'g', 't'),
619
                          help="Specify units for output (one of h/m/g/t)")
620

    
621
FIELDS_OPT = cli_option("-o", "--output", dest="output", action="store",
622
                        type="string", metavar="FIELDS",
623
                        help="Comma separated list of output fields")
624

    
625
FORCE_OPT = cli_option("-f", "--force", dest="force", action="store_true",
626
                       default=False, help="Force the operation")
627

    
628
CONFIRM_OPT = cli_option("--yes", dest="confirm", action="store_true",
629
                         default=False, help="Do not require confirmation")
630

    
631
IGNORE_OFFLINE_OPT = cli_option("--ignore-offline", dest="ignore_offline",
632
                                  action="store_true", default=False,
633
                                  help=("Ignore offline nodes and do as much"
634
                                        " as possible"))
635

    
636
TAG_SRC_OPT = cli_option("--from", dest="tags_source",
637
                         default=None, help="File with tag names")
638

    
639
SUBMIT_OPT = cli_option("--submit", dest="submit_only",
640
                        default=False, action="store_true",
641
                        help=("Submit the job and return the job ID, but"
642
                              " don't wait for the job to finish"))
643

    
644
SYNC_OPT = cli_option("--sync", dest="do_locking",
645
                      default=False, action="store_true",
646
                      help=("Grab locks while doing the queries"
647
                            " in order to ensure more consistent results"))
648

    
649
DRY_RUN_OPT = cli_option("--dry-run", default=False,
650
                         action="store_true",
651
                         help=("Do not execute the operation, just run the"
652
                               " check steps and verify it it could be"
653
                               " executed"))
654

    
655
VERBOSE_OPT = cli_option("-v", "--verbose", default=False,
656
                         action="store_true",
657
                         help="Increase the verbosity of the operation")
658

    
659
DEBUG_SIMERR_OPT = cli_option("--debug-simulate-errors", default=False,
660
                              action="store_true", dest="simulate_errors",
661
                              help="Debugging option that makes the operation"
662
                              " treat most runtime checks as failed")
663

    
664
NWSYNC_OPT = cli_option("--no-wait-for-sync", dest="wait_for_sync",
665
                        default=True, action="store_false",
666
                        help="Don't wait for sync (DANGEROUS!)")
667

    
668
DISK_TEMPLATE_OPT = cli_option("-t", "--disk-template", dest="disk_template",
669
                               help=("Custom disk setup (%s)" %
670
                                     utils.CommaJoin(constants.DISK_TEMPLATES)),
671
                               default=None, metavar="TEMPL",
672
                               choices=list(constants.DISK_TEMPLATES))
673

    
674
NONICS_OPT = cli_option("--no-nics", default=False, action="store_true",
675
                        help="Do not create any network cards for"
676
                        " the instance")
677

    
678
FILESTORE_DIR_OPT = cli_option("--file-storage-dir", dest="file_storage_dir",
679
                               help="Relative path under default cluster-wide"
680
                               " file storage dir to store file-based disks",
681
                               default=None, metavar="<DIR>")
682

    
683
FILESTORE_DRIVER_OPT = cli_option("--file-driver", dest="file_driver",
684
                                  help="Driver to use for image files",
685
                                  default="loop", metavar="<DRIVER>",
686
                                  choices=list(constants.FILE_DRIVER))
687

    
688
IALLOCATOR_OPT = cli_option("-I", "--iallocator", metavar="<NAME>",
689
                            help="Select nodes for the instance automatically"
690
                            " using the <NAME> iallocator plugin",
691
                            default=None, type="string",
692
                            completion_suggest=OPT_COMPL_ONE_IALLOCATOR)
693

    
694
DEFAULT_IALLOCATOR_OPT = cli_option("-I", "--default-iallocator",
695
                            metavar="<NAME>",
696
                            help="Set the default instance allocator plugin",
697
                            default=None, type="string",
698
                            completion_suggest=OPT_COMPL_ONE_IALLOCATOR)
699

    
700
OS_OPT = cli_option("-o", "--os-type", dest="os", help="What OS to run",
701
                    metavar="<os>",
702
                    completion_suggest=OPT_COMPL_ONE_OS)
703

    
704
OSPARAMS_OPT = cli_option("-O", "--os-parameters", dest="osparams",
705
                         type="keyval", default={},
706
                         help="OS parameters")
707

    
708
FORCE_VARIANT_OPT = cli_option("--force-variant", dest="force_variant",
709
                               action="store_true", default=False,
710
                               help="Force an unknown variant")
711

    
712
NO_INSTALL_OPT = cli_option("--no-install", dest="no_install",
713
                            action="store_true", default=False,
714
                            help="Do not install the OS (will"
715
                            " enable no-start)")
716

    
717
BACKEND_OPT = cli_option("-B", "--backend-parameters", dest="beparams",
718
                         type="keyval", default={},
719
                         help="Backend parameters")
720

    
721
HVOPTS_OPT =  cli_option("-H", "--hypervisor-parameters", type="keyval",
722
                         default={}, dest="hvparams",
723
                         help="Hypervisor parameters")
724

    
725
HYPERVISOR_OPT = cli_option("-H", "--hypervisor-parameters", dest="hypervisor",
726
                            help="Hypervisor and hypervisor options, in the"
727
                            " format hypervisor:option=value,option=value,...",
728
                            default=None, type="identkeyval")
729

    
730
HVLIST_OPT = cli_option("-H", "--hypervisor-parameters", dest="hvparams",
731
                        help="Hypervisor and hypervisor options, in the"
732
                        " format hypervisor:option=value,option=value,...",
733
                        default=[], action="append", type="identkeyval")
734

    
735
NOIPCHECK_OPT = cli_option("--no-ip-check", dest="ip_check", default=True,
736
                           action="store_false",
737
                           help="Don't check that the instance's IP"
738
                           " is alive")
739

    
740
NONAMECHECK_OPT = cli_option("--no-name-check", dest="name_check",
741
                             default=True, action="store_false",
742
                             help="Don't check that the instance's name"
743
                             " is resolvable")
744

    
745
NET_OPT = cli_option("--net",
746
                     help="NIC parameters", default=[],
747
                     dest="nics", action="append", type="identkeyval")
748

    
749
DISK_OPT = cli_option("--disk", help="Disk parameters", default=[],
750
                      dest="disks", action="append", type="identkeyval")
751

    
752
DISKIDX_OPT = cli_option("--disks", dest="disks", default=None,
753
                         help="Comma-separated list of disks"
754
                         " indices to act on (e.g. 0,2) (optional,"
755
                         " defaults to all disks)")
756

    
757
OS_SIZE_OPT = cli_option("-s", "--os-size", dest="sd_size",
758
                         help="Enforces a single-disk configuration using the"
759
                         " given disk size, in MiB unless a suffix is used",
760
                         default=None, type="unit", metavar="<size>")
761

    
762
IGNORE_CONSIST_OPT = cli_option("--ignore-consistency",
763
                                dest="ignore_consistency",
764
                                action="store_true", default=False,
765
                                help="Ignore the consistency of the disks on"
766
                                " the secondary")
767

    
768
ALLOW_FAILOVER_OPT = cli_option("--allow-failover",
769
                                dest="allow_failover",
770
                                action="store_true", default=False,
771
                                help="If migration is not possible fallback to"
772
                                     " failover")
773

    
774
NONLIVE_OPT = cli_option("--non-live", dest="live",
775
                         default=True, action="store_false",
776
                         help="Do a non-live migration (this usually means"
777
                         " freeze the instance, save the state, transfer and"
778
                         " only then resume running on the secondary node)")
779

    
780
MIGRATION_MODE_OPT = cli_option("--migration-mode", dest="migration_mode",
781
                                default=None,
782
                                choices=list(constants.HT_MIGRATION_MODES),
783
                                help="Override default migration mode (choose"
784
                                " either live or non-live")
785

    
786
NODE_PLACEMENT_OPT = cli_option("-n", "--node", dest="node",
787
                                help="Target node and optional secondary node",
788
                                metavar="<pnode>[:<snode>]",
789
                                completion_suggest=OPT_COMPL_INST_ADD_NODES)
790

    
791
NODE_LIST_OPT = cli_option("-n", "--node", dest="nodes", default=[],
792
                           action="append", metavar="<node>",
793
                           help="Use only this node (can be used multiple"
794
                           " times, if not given defaults to all nodes)",
795
                           completion_suggest=OPT_COMPL_ONE_NODE)
796

    
797
NODEGROUP_OPT = cli_option("-g", "--node-group",
798
                           dest="nodegroup",
799
                           help="Node group (name or uuid)",
800
                           metavar="<nodegroup>",
801
                           default=None, type="string",
802
                           completion_suggest=OPT_COMPL_ONE_NODEGROUP)
803

    
804
SINGLE_NODE_OPT = cli_option("-n", "--node", dest="node", help="Target node",
805
                             metavar="<node>",
806
                             completion_suggest=OPT_COMPL_ONE_NODE)
807

    
808
NOSTART_OPT = cli_option("--no-start", dest="start", default=True,
809
                         action="store_false",
810
                         help="Don't start the instance after creation")
811

    
812
SHOWCMD_OPT = cli_option("--show-cmd", dest="show_command",
813
                         action="store_true", default=False,
814
                         help="Show command instead of executing it")
815

    
816
CLEANUP_OPT = cli_option("--cleanup", dest="cleanup",
817
                         default=False, action="store_true",
818
                         help="Instead of performing the migration, try to"
819
                         " recover from a failed cleanup. This is safe"
820
                         " to run even if the instance is healthy, but it"
821
                         " will create extra replication traffic and "
822
                         " disrupt briefly the replication (like during the"
823
                         " migration")
824

    
825
STATIC_OPT = cli_option("-s", "--static", dest="static",
826
                        action="store_true", default=False,
827
                        help="Only show configuration data, not runtime data")
828

    
829
ALL_OPT = cli_option("--all", dest="show_all",
830
                     default=False, action="store_true",
831
                     help="Show info on all instances on the cluster."
832
                     " This can take a long time to run, use wisely")
833

    
834
SELECT_OS_OPT = cli_option("--select-os", dest="select_os",
835
                           action="store_true", default=False,
836
                           help="Interactive OS reinstall, lists available"
837
                           " OS templates for selection")
838

    
839
IGNORE_FAILURES_OPT = cli_option("--ignore-failures", dest="ignore_failures",
840
                                 action="store_true", default=False,
841
                                 help="Remove the instance from the cluster"
842
                                 " configuration even if there are failures"
843
                                 " during the removal process")
844

    
845
IGNORE_REMOVE_FAILURES_OPT = cli_option("--ignore-remove-failures",
846
                                        dest="ignore_remove_failures",
847
                                        action="store_true", default=False,
848
                                        help="Remove the instance from the"
849
                                        " cluster configuration even if there"
850
                                        " are failures during the removal"
851
                                        " process")
852

    
853
REMOVE_INSTANCE_OPT = cli_option("--remove-instance", dest="remove_instance",
854
                                 action="store_true", default=False,
855
                                 help="Remove the instance from the cluster")
856

    
857
DST_NODE_OPT = cli_option("-n", "--target-node", dest="dst_node",
858
                               help="Specifies the new node for the instance",
859
                               metavar="NODE", default=None,
860
                               completion_suggest=OPT_COMPL_ONE_NODE)
861

    
862
NEW_SECONDARY_OPT = cli_option("-n", "--new-secondary", dest="dst_node",
863
                               help="Specifies the new secondary node",
864
                               metavar="NODE", default=None,
865
                               completion_suggest=OPT_COMPL_ONE_NODE)
866

    
867
ON_PRIMARY_OPT = cli_option("-p", "--on-primary", dest="on_primary",
868
                            default=False, action="store_true",
869
                            help="Replace the disk(s) on the primary"
870
                                 " node (applies only to internally mirrored"
871
                                 " disk templates, e.g. %s)" %
872
                                 utils.CommaJoin(constants.DTS_INT_MIRROR))
873

    
874
ON_SECONDARY_OPT = cli_option("-s", "--on-secondary", dest="on_secondary",
875
                              default=False, action="store_true",
876
                              help="Replace the disk(s) on the secondary"
877
                                   " node (applies only to internally mirrored"
878
                                   " disk templates, e.g. %s)" %
879
                                   utils.CommaJoin(constants.DTS_INT_MIRROR))
880

    
881
AUTO_PROMOTE_OPT = cli_option("--auto-promote", dest="auto_promote",
882
                              default=False, action="store_true",
883
                              help="Lock all nodes and auto-promote as needed"
884
                              " to MC status")
885

    
886
AUTO_REPLACE_OPT = cli_option("-a", "--auto", dest="auto",
887
                              default=False, action="store_true",
888
                              help="Automatically replace faulty disks"
889
                                   " (applies only to internally mirrored"
890
                                   " disk templates, e.g. %s)" %
891
                                   utils.CommaJoin(constants.DTS_INT_MIRROR))
892

    
893
IGNORE_SIZE_OPT = cli_option("--ignore-size", dest="ignore_size",
894
                             default=False, action="store_true",
895
                             help="Ignore current recorded size"
896
                             " (useful for forcing activation when"
897
                             " the recorded size is wrong)")
898

    
899
SRC_NODE_OPT = cli_option("--src-node", dest="src_node", help="Source node",
900
                          metavar="<node>",
901
                          completion_suggest=OPT_COMPL_ONE_NODE)
902

    
903
SRC_DIR_OPT = cli_option("--src-dir", dest="src_dir", help="Source directory",
904
                         metavar="<dir>")
905

    
906
SECONDARY_IP_OPT = cli_option("-s", "--secondary-ip", dest="secondary_ip",
907
                              help="Specify the secondary ip for the node",
908
                              metavar="ADDRESS", default=None)
909

    
910
READD_OPT = cli_option("--readd", dest="readd",
911
                       default=False, action="store_true",
912
                       help="Readd old node after replacing it")
913

    
914
NOSSH_KEYCHECK_OPT = cli_option("--no-ssh-key-check", dest="ssh_key_check",
915
                                default=True, action="store_false",
916
                                help="Disable SSH key fingerprint checking")
917

    
918
NODE_FORCE_JOIN_OPT = cli_option("--force-join", dest="force_join",
919
                                 default=False, action="store_true",
920
                                 help="Force the joining of a node")
921

    
922
MC_OPT = cli_option("-C", "--master-candidate", dest="master_candidate",
923
                    type="bool", default=None, metavar=_YORNO,
924
                    help="Set the master_candidate flag on the node")
925

    
926
OFFLINE_OPT = cli_option("-O", "--offline", dest="offline", metavar=_YORNO,
927
                         type="bool", default=None,
928
                         help=("Set the offline flag on the node"
929
                               " (cluster does not communicate with offline"
930
                               " nodes)"))
931

    
932
DRAINED_OPT = cli_option("-D", "--drained", dest="drained", metavar=_YORNO,
933
                         type="bool", default=None,
934
                         help=("Set the drained flag on the node"
935
                               " (excluded from allocation operations)"))
936

    
937
CAPAB_MASTER_OPT = cli_option("--master-capable", dest="master_capable",
938
                    type="bool", default=None, metavar=_YORNO,
939
                    help="Set the master_capable flag on the node")
940

    
941
CAPAB_VM_OPT = cli_option("--vm-capable", dest="vm_capable",
942
                    type="bool", default=None, metavar=_YORNO,
943
                    help="Set the vm_capable flag on the node")
944

    
945
ALLOCATABLE_OPT = cli_option("--allocatable", dest="allocatable",
946
                             type="bool", default=None, metavar=_YORNO,
947
                             help="Set the allocatable flag on a volume")
948

    
949
NOLVM_STORAGE_OPT = cli_option("--no-lvm-storage", dest="lvm_storage",
950
                               help="Disable support for lvm based instances"
951
                               " (cluster-wide)",
952
                               action="store_false", default=True)
953

    
954
ENABLED_HV_OPT = cli_option("--enabled-hypervisors",
955
                            dest="enabled_hypervisors",
956
                            help="Comma-separated list of hypervisors",
957
                            type="string", default=None)
958

    
959
NIC_PARAMS_OPT = cli_option("-N", "--nic-parameters", dest="nicparams",
960
                            type="keyval", default={},
961
                            help="NIC parameters")
962

    
963
CP_SIZE_OPT = cli_option("-C", "--candidate-pool-size", default=None,
964
                         dest="candidate_pool_size", type="int",
965
                         help="Set the candidate pool size")
966

    
967
VG_NAME_OPT = cli_option("--vg-name", dest="vg_name",
968
                         help=("Enables LVM and specifies the volume group"
969
                               " name (cluster-wide) for disk allocation"
970
                               " [%s]" % constants.DEFAULT_VG),
971
                         metavar="VG", default=None)
972

    
973
YES_DOIT_OPT = cli_option("--yes-do-it", dest="yes_do_it",
974
                          help="Destroy cluster", action="store_true")
975

    
976
NOVOTING_OPT = cli_option("--no-voting", dest="no_voting",
977
                          help="Skip node agreement check (dangerous)",
978
                          action="store_true", default=False)
979

    
980
MAC_PREFIX_OPT = cli_option("-m", "--mac-prefix", dest="mac_prefix",
981
                            help="Specify the mac prefix for the instance IP"
982
                            " addresses, in the format XX:XX:XX",
983
                            metavar="PREFIX",
984
                            default=None)
985

    
986
MASTER_NETDEV_OPT = cli_option("--master-netdev", dest="master_netdev",
987
                               help="Specify the node interface (cluster-wide)"
988
                               " on which the master IP address will be added"
989
                               " (cluster init default: %s)" %
990
                               constants.DEFAULT_BRIDGE,
991
                               metavar="NETDEV",
992
                               default=None)
993

    
994
GLOBAL_FILEDIR_OPT = cli_option("--file-storage-dir", dest="file_storage_dir",
995
                                help="Specify the default directory (cluster-"
996
                                "wide) for storing the file-based disks [%s]" %
997
                                constants.DEFAULT_FILE_STORAGE_DIR,
998
                                metavar="DIR",
999
                                default=constants.DEFAULT_FILE_STORAGE_DIR)
1000

    
1001
GLOBAL_SHARED_FILEDIR_OPT = cli_option("--shared-file-storage-dir",
1002
                            dest="shared_file_storage_dir",
1003
                            help="Specify the default directory (cluster-"
1004
                            "wide) for storing the shared file-based"
1005
                            " disks [%s]" %
1006
                            constants.DEFAULT_SHARED_FILE_STORAGE_DIR,
1007
                            metavar="SHAREDDIR",
1008
                            default=constants.DEFAULT_SHARED_FILE_STORAGE_DIR)
1009

    
1010
NOMODIFY_ETCHOSTS_OPT = cli_option("--no-etc-hosts", dest="modify_etc_hosts",
1011
                                   help="Don't modify /etc/hosts",
1012
                                   action="store_false", default=True)
1013

    
1014
NOMODIFY_SSH_SETUP_OPT = cli_option("--no-ssh-init", dest="modify_ssh_setup",
1015
                                    help="Don't initialize SSH keys",
1016
                                    action="store_false", default=True)
1017

    
1018
ERROR_CODES_OPT = cli_option("--error-codes", dest="error_codes",
1019
                             help="Enable parseable error messages",
1020
                             action="store_true", default=False)
1021

    
1022
NONPLUS1_OPT = cli_option("--no-nplus1-mem", dest="skip_nplusone_mem",
1023
                          help="Skip N+1 memory redundancy tests",
1024
                          action="store_true", default=False)
1025

    
1026
REBOOT_TYPE_OPT = cli_option("-t", "--type", dest="reboot_type",
1027
                             help="Type of reboot: soft/hard/full",
1028
                             default=constants.INSTANCE_REBOOT_HARD,
1029
                             metavar="<REBOOT>",
1030
                             choices=list(constants.REBOOT_TYPES))
1031

    
1032
IGNORE_SECONDARIES_OPT = cli_option("--ignore-secondaries",
1033
                                    dest="ignore_secondaries",
1034
                                    default=False, action="store_true",
1035
                                    help="Ignore errors from secondaries")
1036

    
1037
NOSHUTDOWN_OPT = cli_option("--noshutdown", dest="shutdown",
1038
                            action="store_false", default=True,
1039
                            help="Don't shutdown the instance (unsafe)")
1040

    
1041
TIMEOUT_OPT = cli_option("--timeout", dest="timeout", type="int",
1042
                         default=constants.DEFAULT_SHUTDOWN_TIMEOUT,
1043
                         help="Maximum time to wait")
1044

    
1045
SHUTDOWN_TIMEOUT_OPT = cli_option("--shutdown-timeout",
1046
                         dest="shutdown_timeout", type="int",
1047
                         default=constants.DEFAULT_SHUTDOWN_TIMEOUT,
1048
                         help="Maximum time to wait for instance shutdown")
1049

    
1050
INTERVAL_OPT = cli_option("--interval", dest="interval", type="int",
1051
                          default=None,
1052
                          help=("Number of seconds between repetions of the"
1053
                                " command"))
1054

    
1055
EARLY_RELEASE_OPT = cli_option("--early-release",
1056
                               dest="early_release", default=False,
1057
                               action="store_true",
1058
                               help="Release the locks on the secondary"
1059
                               " node(s) early")
1060

    
1061
NEW_CLUSTER_CERT_OPT = cli_option("--new-cluster-certificate",
1062
                                  dest="new_cluster_cert",
1063
                                  default=False, action="store_true",
1064
                                  help="Generate a new cluster certificate")
1065

    
1066
RAPI_CERT_OPT = cli_option("--rapi-certificate", dest="rapi_cert",
1067
                           default=None,
1068
                           help="File containing new RAPI certificate")
1069

    
1070
NEW_RAPI_CERT_OPT = cli_option("--new-rapi-certificate", dest="new_rapi_cert",
1071
                               default=None, action="store_true",
1072
                               help=("Generate a new self-signed RAPI"
1073
                                     " certificate"))
1074

    
1075
NEW_CONFD_HMAC_KEY_OPT = cli_option("--new-confd-hmac-key",
1076
                                    dest="new_confd_hmac_key",
1077
                                    default=False, action="store_true",
1078
                                    help=("Create a new HMAC key for %s" %
1079
                                          constants.CONFD))
1080

    
1081
CLUSTER_DOMAIN_SECRET_OPT = cli_option("--cluster-domain-secret",
1082
                                       dest="cluster_domain_secret",
1083
                                       default=None,
1084
                                       help=("Load new new cluster domain"
1085
                                             " secret from file"))
1086

    
1087
NEW_CLUSTER_DOMAIN_SECRET_OPT = cli_option("--new-cluster-domain-secret",
1088
                                           dest="new_cluster_domain_secret",
1089
                                           default=False, action="store_true",
1090
                                           help=("Create a new cluster domain"
1091
                                                 " secret"))
1092

    
1093
USE_REPL_NET_OPT = cli_option("--use-replication-network",
1094
                              dest="use_replication_network",
1095
                              help="Whether to use the replication network"
1096
                              " for talking to the nodes",
1097
                              action="store_true", default=False)
1098

    
1099
MAINTAIN_NODE_HEALTH_OPT = \
1100
    cli_option("--maintain-node-health", dest="maintain_node_health",
1101
               metavar=_YORNO, default=None, type="bool",
1102
               help="Configure the cluster to automatically maintain node"
1103
               " health, by shutting down unknown instances, shutting down"
1104
               " unknown DRBD devices, etc.")
1105

    
1106
IDENTIFY_DEFAULTS_OPT = \
1107
    cli_option("--identify-defaults", dest="identify_defaults",
1108
               default=False, action="store_true",
1109
               help="Identify which saved instance parameters are equal to"
1110
               " the current cluster defaults and set them as such, instead"
1111
               " of marking them as overridden")
1112

    
1113
UIDPOOL_OPT = cli_option("--uid-pool", default=None,
1114
                         action="store", dest="uid_pool",
1115
                         help=("A list of user-ids or user-id"
1116
                               " ranges separated by commas"))
1117

    
1118
ADD_UIDS_OPT = cli_option("--add-uids", default=None,
1119
                          action="store", dest="add_uids",
1120
                          help=("A list of user-ids or user-id"
1121
                                " ranges separated by commas, to be"
1122
                                " added to the user-id pool"))
1123

    
1124
REMOVE_UIDS_OPT = cli_option("--remove-uids", default=None,
1125
                             action="store", dest="remove_uids",
1126
                             help=("A list of user-ids or user-id"
1127
                                   " ranges separated by commas, to be"
1128
                                   " removed from the user-id pool"))
1129

    
1130
RESERVED_LVS_OPT = cli_option("--reserved-lvs", default=None,
1131
                             action="store", dest="reserved_lvs",
1132
                             help=("A comma-separated list of reserved"
1133
                                   " logical volumes names, that will be"
1134
                                   " ignored by cluster verify"))
1135

    
1136
ROMAN_OPT = cli_option("--roman",
1137
                       dest="roman_integers", default=False,
1138
                       action="store_true",
1139
                       help="Use roman numbers for positive integers")
1140

    
1141
DRBD_HELPER_OPT = cli_option("--drbd-usermode-helper", dest="drbd_helper",
1142
                             action="store", default=None,
1143
                             help="Specifies usermode helper for DRBD")
1144

    
1145
NODRBD_STORAGE_OPT = cli_option("--no-drbd-storage", dest="drbd_storage",
1146
                                action="store_false", default=True,
1147
                                help="Disable support for DRBD")
1148

    
1149
PRIMARY_IP_VERSION_OPT = \
1150
    cli_option("--primary-ip-version", default=constants.IP4_VERSION,
1151
               action="store", dest="primary_ip_version",
1152
               metavar="%d|%d" % (constants.IP4_VERSION,
1153
                                  constants.IP6_VERSION),
1154
               help="Cluster-wide IP version for primary IP")
1155

    
1156
PRIORITY_OPT = cli_option("--priority", default=None, dest="priority",
1157
                          metavar="|".join(name for name, _ in _PRIORITY_NAMES),
1158
                          choices=_PRIONAME_TO_VALUE.keys(),
1159
                          help="Priority for opcode processing")
1160

    
1161
HID_OS_OPT = cli_option("--hidden", dest="hidden",
1162
                        type="bool", default=None, metavar=_YORNO,
1163
                        help="Sets the hidden flag on the OS")
1164

    
1165
BLK_OS_OPT = cli_option("--blacklisted", dest="blacklisted",
1166
                        type="bool", default=None, metavar=_YORNO,
1167
                        help="Sets the blacklisted flag on the OS")
1168

    
1169
PREALLOC_WIPE_DISKS_OPT = cli_option("--prealloc-wipe-disks", default=None,
1170
                                     type="bool", metavar=_YORNO,
1171
                                     dest="prealloc_wipe_disks",
1172
                                     help=("Wipe disks prior to instance"
1173
                                           " creation"))
1174

    
1175
NODE_PARAMS_OPT = cli_option("--node-parameters", dest="ndparams",
1176
                             type="keyval", default=None,
1177
                             help="Node parameters")
1178

    
1179
ALLOC_POLICY_OPT = cli_option("--alloc-policy", dest="alloc_policy",
1180
                              action="store", metavar="POLICY", default=None,
1181
                              help="Allocation policy for the node group")
1182

    
1183
NODE_POWERED_OPT = cli_option("--node-powered", default=None,
1184
                              type="bool", metavar=_YORNO,
1185
                              dest="node_powered",
1186
                              help="Specify if the SoR for node is powered")
1187

    
1188
OOB_TIMEOUT_OPT = cli_option("--oob-timeout", dest="oob_timeout", type="int",
1189
                         default=constants.OOB_TIMEOUT,
1190
                         help="Maximum time to wait for out-of-band helper")
1191

    
1192
POWER_DELAY_OPT = cli_option("--power-delay", dest="power_delay", type="float",
1193
                             default=constants.OOB_POWER_DELAY,
1194
                             help="Time in seconds to wait between power-ons")
1195

    
1196
FORCE_FILTER_OPT = cli_option("-F", "--filter", dest="force_filter",
1197
                              action="store_true", default=False,
1198
                              help=("Whether command argument should be treated"
1199
                                    " as filter"))
1200

    
1201
NO_REMEMBER_OPT = cli_option("--no-remember",
1202
                             dest="no_remember",
1203
                             action="store_true", default=False,
1204
                             help="Perform but do not record the change"
1205
                             " in the configuration")
1206

    
1207

    
1208
#: Options provided by all commands
1209
COMMON_OPTS = [DEBUG_OPT]
1210

    
1211
# common options for creating instances. add and import then add their own
1212
# specific ones.
1213
COMMON_CREATE_OPTS = [
1214
  BACKEND_OPT,
1215
  DISK_OPT,
1216
  DISK_TEMPLATE_OPT,
1217
  FILESTORE_DIR_OPT,
1218
  FILESTORE_DRIVER_OPT,
1219
  HYPERVISOR_OPT,
1220
  IALLOCATOR_OPT,
1221
  NET_OPT,
1222
  NODE_PLACEMENT_OPT,
1223
  NOIPCHECK_OPT,
1224
  NONAMECHECK_OPT,
1225
  NONICS_OPT,
1226
  NWSYNC_OPT,
1227
  OSPARAMS_OPT,
1228
  OS_SIZE_OPT,
1229
  SUBMIT_OPT,
1230
  DRY_RUN_OPT,
1231
  PRIORITY_OPT,
1232
  ]
1233

    
1234

    
1235
def _ParseArgs(argv, commands, aliases):
1236
  """Parser for the command line arguments.
1237

1238
  This function parses the arguments and returns the function which
1239
  must be executed together with its (modified) arguments.
1240

1241
  @param argv: the command line
1242
  @param commands: dictionary with special contents, see the design
1243
      doc for cmdline handling
1244
  @param aliases: dictionary with command aliases {'alias': 'target, ...}
1245

1246
  """
1247
  if len(argv) == 0:
1248
    binary = "<command>"
1249
  else:
1250
    binary = argv[0].split("/")[-1]
1251

    
1252
  if len(argv) > 1 and argv[1] == "--version":
1253
    ToStdout("%s (ganeti %s) %s", binary, constants.VCS_VERSION,
1254
             constants.RELEASE_VERSION)
1255
    # Quit right away. That way we don't have to care about this special
1256
    # argument. optparse.py does it the same.
1257
    sys.exit(0)
1258

    
1259
  if len(argv) < 2 or not (argv[1] in commands or
1260
                           argv[1] in aliases):
1261
    # let's do a nice thing
1262
    sortedcmds = commands.keys()
1263
    sortedcmds.sort()
1264

    
1265
    ToStdout("Usage: %s {command} [options...] [argument...]", binary)
1266
    ToStdout("%s <command> --help to see details, or man %s", binary, binary)
1267
    ToStdout("")
1268

    
1269
    # compute the max line length for cmd + usage
1270
    mlen = max([len(" %s" % cmd) for cmd in commands])
1271
    mlen = min(60, mlen) # should not get here...
1272

    
1273
    # and format a nice command list
1274
    ToStdout("Commands:")
1275
    for cmd in sortedcmds:
1276
      cmdstr = " %s" % (cmd,)
1277
      help_text = commands[cmd][4]
1278
      help_lines = textwrap.wrap(help_text, 79 - 3 - mlen)
1279
      ToStdout("%-*s - %s", mlen, cmdstr, help_lines.pop(0))
1280
      for line in help_lines:
1281
        ToStdout("%-*s   %s", mlen, "", line)
1282

    
1283
    ToStdout("")
1284

    
1285
    return None, None, None
1286

    
1287
  # get command, unalias it, and look it up in commands
1288
  cmd = argv.pop(1)
1289
  if cmd in aliases:
1290
    if cmd in commands:
1291
      raise errors.ProgrammerError("Alias '%s' overrides an existing"
1292
                                   " command" % cmd)
1293

    
1294
    if aliases[cmd] not in commands:
1295
      raise errors.ProgrammerError("Alias '%s' maps to non-existing"
1296
                                   " command '%s'" % (cmd, aliases[cmd]))
1297

    
1298
    cmd = aliases[cmd]
1299

    
1300
  func, args_def, parser_opts, usage, description = commands[cmd]
1301
  parser = OptionParser(option_list=parser_opts + COMMON_OPTS,
1302
                        description=description,
1303
                        formatter=TitledHelpFormatter(),
1304
                        usage="%%prog %s %s" % (cmd, usage))
1305
  parser.disable_interspersed_args()
1306
  options, args = parser.parse_args()
1307

    
1308
  if not _CheckArguments(cmd, args_def, args):
1309
    return None, None, None
1310

    
1311
  return func, options, args
1312

    
1313

    
1314
def _CheckArguments(cmd, args_def, args):
1315
  """Verifies the arguments using the argument definition.
1316

1317
  Algorithm:
1318

1319
    1. Abort with error if values specified by user but none expected.
1320

1321
    1. For each argument in definition
1322

1323
      1. Keep running count of minimum number of values (min_count)
1324
      1. Keep running count of maximum number of values (max_count)
1325
      1. If it has an unlimited number of values
1326

1327
        1. Abort with error if it's not the last argument in the definition
1328

1329
    1. If last argument has limited number of values
1330

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

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

1335
  """
1336
  if args and not args_def:
1337
    ToStderr("Error: Command %s expects no arguments", cmd)
1338
    return False
1339

    
1340
  min_count = None
1341
  max_count = None
1342
  check_max = None
1343

    
1344
  last_idx = len(args_def) - 1
1345

    
1346
  for idx, arg in enumerate(args_def):
1347
    if min_count is None:
1348
      min_count = arg.min
1349
    elif arg.min is not None:
1350
      min_count += arg.min
1351

    
1352
    if max_count is None:
1353
      max_count = arg.max
1354
    elif arg.max is not None:
1355
      max_count += arg.max
1356

    
1357
    if idx == last_idx:
1358
      check_max = (arg.max is not None)
1359

    
1360
    elif arg.max is None:
1361
      raise errors.ProgrammerError("Only the last argument can have max=None")
1362

    
1363
  if check_max:
1364
    # Command with exact number of arguments
1365
    if (min_count is not None and max_count is not None and
1366
        min_count == max_count and len(args) != min_count):
1367
      ToStderr("Error: Command %s expects %d argument(s)", cmd, min_count)
1368
      return False
1369

    
1370
    # Command with limited number of arguments
1371
    if max_count is not None and len(args) > max_count:
1372
      ToStderr("Error: Command %s expects only %d argument(s)",
1373
               cmd, max_count)
1374
      return False
1375

    
1376
  # Command with some required arguments
1377
  if min_count is not None and len(args) < min_count:
1378
    ToStderr("Error: Command %s expects at least %d argument(s)",
1379
             cmd, min_count)
1380
    return False
1381

    
1382
  return True
1383

    
1384

    
1385
def SplitNodeOption(value):
1386
  """Splits the value of a --node option.
1387

1388
  """
1389
  if value and ':' in value:
1390
    return value.split(':', 1)
1391
  else:
1392
    return (value, None)
1393

    
1394

    
1395
def CalculateOSNames(os_name, os_variants):
1396
  """Calculates all the names an OS can be called, according to its variants.
1397

1398
  @type os_name: string
1399
  @param os_name: base name of the os
1400
  @type os_variants: list or None
1401
  @param os_variants: list of supported variants
1402
  @rtype: list
1403
  @return: list of valid names
1404

1405
  """
1406
  if os_variants:
1407
    return ['%s+%s' % (os_name, v) for v in os_variants]
1408
  else:
1409
    return [os_name]
1410

    
1411

    
1412
def ParseFields(selected, default):
1413
  """Parses the values of "--field"-like options.
1414

1415
  @type selected: string or None
1416
  @param selected: User-selected options
1417
  @type default: list
1418
  @param default: Default fields
1419

1420
  """
1421
  if selected is None:
1422
    return default
1423

    
1424
  if selected.startswith("+"):
1425
    return default + selected[1:].split(",")
1426

    
1427
  return selected.split(",")
1428

    
1429

    
1430
UsesRPC = rpc.RunWithRPC
1431

    
1432

    
1433
def AskUser(text, choices=None):
1434
  """Ask the user a question.
1435

1436
  @param text: the question to ask
1437

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

1443
  @return: one of the return values from the choices list; if input is
1444
      not possible (i.e. not running with a tty, we return the last
1445
      entry from the list
1446

1447
  """
1448
  if choices is None:
1449
    choices = [('y', True, 'Perform the operation'),
1450
               ('n', False, 'Do not perform the operation')]
1451
  if not choices or not isinstance(choices, list):
1452
    raise errors.ProgrammerError("Invalid choices argument to AskUser")
1453
  for entry in choices:
1454
    if not isinstance(entry, tuple) or len(entry) < 3 or entry[0] == '?':
1455
      raise errors.ProgrammerError("Invalid choices element to AskUser")
1456

    
1457
  answer = choices[-1][1]
1458
  new_text = []
1459
  for line in text.splitlines():
1460
    new_text.append(textwrap.fill(line, 70, replace_whitespace=False))
1461
  text = "\n".join(new_text)
1462
  try:
1463
    f = file("/dev/tty", "a+")
1464
  except IOError:
1465
    return answer
1466
  try:
1467
    chars = [entry[0] for entry in choices]
1468
    chars[-1] = "[%s]" % chars[-1]
1469
    chars.append('?')
1470
    maps = dict([(entry[0], entry[1]) for entry in choices])
1471
    while True:
1472
      f.write(text)
1473
      f.write('\n')
1474
      f.write("/".join(chars))
1475
      f.write(": ")
1476
      line = f.readline(2).strip().lower()
1477
      if line in maps:
1478
        answer = maps[line]
1479
        break
1480
      elif line == '?':
1481
        for entry in choices:
1482
          f.write(" %s - %s\n" % (entry[0], entry[2]))
1483
        f.write("\n")
1484
        continue
1485
  finally:
1486
    f.close()
1487
  return answer
1488

    
1489

    
1490
class JobSubmittedException(Exception):
1491
  """Job was submitted, client should exit.
1492

1493
  This exception has one argument, the ID of the job that was
1494
  submitted. The handler should print this ID.
1495

1496
  This is not an error, just a structured way to exit from clients.
1497

1498
  """
1499

    
1500

    
1501
def SendJob(ops, cl=None):
1502
  """Function to submit an opcode without waiting for the results.
1503

1504
  @type ops: list
1505
  @param ops: list of opcodes
1506
  @type cl: luxi.Client
1507
  @param cl: the luxi client to use for communicating with the master;
1508
             if None, a new client will be created
1509

1510
  """
1511
  if cl is None:
1512
    cl = GetClient()
1513

    
1514
  job_id = cl.SubmitJob(ops)
1515

    
1516
  return job_id
1517

    
1518

    
1519
def GenericPollJob(job_id, cbs, report_cbs):
1520
  """Generic job-polling function.
1521

1522
  @type job_id: number
1523
  @param job_id: Job ID
1524
  @type cbs: Instance of L{JobPollCbBase}
1525
  @param cbs: Data callbacks
1526
  @type report_cbs: Instance of L{JobPollReportCbBase}
1527
  @param report_cbs: Reporting callbacks
1528

1529
  """
1530
  prev_job_info = None
1531
  prev_logmsg_serial = None
1532

    
1533
  status = None
1534

    
1535
  while True:
1536
    result = cbs.WaitForJobChangeOnce(job_id, ["status"], prev_job_info,
1537
                                      prev_logmsg_serial)
1538
    if not result:
1539
      # job not found, go away!
1540
      raise errors.JobLost("Job with id %s lost" % job_id)
1541

    
1542
    if result == constants.JOB_NOTCHANGED:
1543
      report_cbs.ReportNotChanged(job_id, status)
1544

    
1545
      # Wait again
1546
      continue
1547

    
1548
    # Split result, a tuple of (field values, log entries)
1549
    (job_info, log_entries) = result
1550
    (status, ) = job_info
1551

    
1552
    if log_entries:
1553
      for log_entry in log_entries:
1554
        (serial, timestamp, log_type, message) = log_entry
1555
        report_cbs.ReportLogMessage(job_id, serial, timestamp,
1556
                                    log_type, message)
1557
        prev_logmsg_serial = max(prev_logmsg_serial, serial)
1558

    
1559
    # TODO: Handle canceled and archived jobs
1560
    elif status in (constants.JOB_STATUS_SUCCESS,
1561
                    constants.JOB_STATUS_ERROR,
1562
                    constants.JOB_STATUS_CANCELING,
1563
                    constants.JOB_STATUS_CANCELED):
1564
      break
1565

    
1566
    prev_job_info = job_info
1567

    
1568
  jobs = cbs.QueryJobs([job_id], ["status", "opstatus", "opresult"])
1569
  if not jobs:
1570
    raise errors.JobLost("Job with id %s lost" % job_id)
1571

    
1572
  status, opstatus, result = jobs[0]
1573

    
1574
  if status == constants.JOB_STATUS_SUCCESS:
1575
    return result
1576

    
1577
  if status in (constants.JOB_STATUS_CANCELING, constants.JOB_STATUS_CANCELED):
1578
    raise errors.OpExecError("Job was canceled")
1579

    
1580
  has_ok = False
1581
  for idx, (status, msg) in enumerate(zip(opstatus, result)):
1582
    if status == constants.OP_STATUS_SUCCESS:
1583
      has_ok = True
1584
    elif status == constants.OP_STATUS_ERROR:
1585
      errors.MaybeRaise(msg)
1586

    
1587
      if has_ok:
1588
        raise errors.OpExecError("partial failure (opcode %d): %s" %
1589
                                 (idx, msg))
1590

    
1591
      raise errors.OpExecError(str(msg))
1592

    
1593
  # default failure mode
1594
  raise errors.OpExecError(result)
1595

    
1596

    
1597
class JobPollCbBase:
1598
  """Base class for L{GenericPollJob} callbacks.
1599

1600
  """
1601
  def __init__(self):
1602
    """Initializes this class.
1603

1604
    """
1605

    
1606
  def WaitForJobChangeOnce(self, job_id, fields,
1607
                           prev_job_info, prev_log_serial):
1608
    """Waits for changes on a job.
1609

1610
    """
1611
    raise NotImplementedError()
1612

    
1613
  def QueryJobs(self, job_ids, fields):
1614
    """Returns the selected fields for the selected job IDs.
1615

1616
    @type job_ids: list of numbers
1617
    @param job_ids: Job IDs
1618
    @type fields: list of strings
1619
    @param fields: Fields
1620

1621
    """
1622
    raise NotImplementedError()
1623

    
1624

    
1625
class JobPollReportCbBase:
1626
  """Base class for L{GenericPollJob} reporting callbacks.
1627

1628
  """
1629
  def __init__(self):
1630
    """Initializes this class.
1631

1632
    """
1633

    
1634
  def ReportLogMessage(self, job_id, serial, timestamp, log_type, log_msg):
1635
    """Handles a log message.
1636

1637
    """
1638
    raise NotImplementedError()
1639

    
1640
  def ReportNotChanged(self, job_id, status):
1641
    """Called for if a job hasn't changed in a while.
1642

1643
    @type job_id: number
1644
    @param job_id: Job ID
1645
    @type status: string or None
1646
    @param status: Job status if available
1647

1648
    """
1649
    raise NotImplementedError()
1650

    
1651

    
1652
class _LuxiJobPollCb(JobPollCbBase):
1653
  def __init__(self, cl):
1654
    """Initializes this class.
1655

1656
    """
1657
    JobPollCbBase.__init__(self)
1658
    self.cl = cl
1659

    
1660
  def WaitForJobChangeOnce(self, job_id, fields,
1661
                           prev_job_info, prev_log_serial):
1662
    """Waits for changes on a job.
1663

1664
    """
1665
    return self.cl.WaitForJobChangeOnce(job_id, fields,
1666
                                        prev_job_info, prev_log_serial)
1667

    
1668
  def QueryJobs(self, job_ids, fields):
1669
    """Returns the selected fields for the selected job IDs.
1670

1671
    """
1672
    return self.cl.QueryJobs(job_ids, fields)
1673

    
1674

    
1675
class FeedbackFnJobPollReportCb(JobPollReportCbBase):
1676
  def __init__(self, feedback_fn):
1677
    """Initializes this class.
1678

1679
    """
1680
    JobPollReportCbBase.__init__(self)
1681

    
1682
    self.feedback_fn = feedback_fn
1683

    
1684
    assert callable(feedback_fn)
1685

    
1686
  def ReportLogMessage(self, job_id, serial, timestamp, log_type, log_msg):
1687
    """Handles a log message.
1688

1689
    """
1690
    self.feedback_fn((timestamp, log_type, log_msg))
1691

    
1692
  def ReportNotChanged(self, job_id, status):
1693
    """Called if a job hasn't changed in a while.
1694

1695
    """
1696
    # Ignore
1697

    
1698

    
1699
class StdioJobPollReportCb(JobPollReportCbBase):
1700
  def __init__(self):
1701
    """Initializes this class.
1702

1703
    """
1704
    JobPollReportCbBase.__init__(self)
1705

    
1706
    self.notified_queued = False
1707
    self.notified_waitlock = False
1708

    
1709
  def ReportLogMessage(self, job_id, serial, timestamp, log_type, log_msg):
1710
    """Handles a log message.
1711

1712
    """
1713
    ToStdout("%s %s", time.ctime(utils.MergeTime(timestamp)),
1714
             FormatLogMessage(log_type, log_msg))
1715

    
1716
  def ReportNotChanged(self, job_id, status):
1717
    """Called if a job hasn't changed in a while.
1718

1719
    """
1720
    if status is None:
1721
      return
1722

    
1723
    if status == constants.JOB_STATUS_QUEUED and not self.notified_queued:
1724
      ToStderr("Job %s is waiting in queue", job_id)
1725
      self.notified_queued = True
1726

    
1727
    elif status == constants.JOB_STATUS_WAITLOCK and not self.notified_waitlock:
1728
      ToStderr("Job %s is trying to acquire all necessary locks", job_id)
1729
      self.notified_waitlock = True
1730

    
1731

    
1732
def FormatLogMessage(log_type, log_msg):
1733
  """Formats a job message according to its type.
1734

1735
  """
1736
  if log_type != constants.ELOG_MESSAGE:
1737
    log_msg = str(log_msg)
1738

    
1739
  return utils.SafeEncode(log_msg)
1740

    
1741

    
1742
def PollJob(job_id, cl=None, feedback_fn=None, reporter=None):
1743
  """Function to poll for the result of a job.
1744

1745
  @type job_id: job identified
1746
  @param job_id: the job to poll for results
1747
  @type cl: luxi.Client
1748
  @param cl: the luxi client to use for communicating with the master;
1749
             if None, a new client will be created
1750

1751
  """
1752
  if cl is None:
1753
    cl = GetClient()
1754

    
1755
  if reporter is None:
1756
    if feedback_fn:
1757
      reporter = FeedbackFnJobPollReportCb(feedback_fn)
1758
    else:
1759
      reporter = StdioJobPollReportCb()
1760
  elif feedback_fn:
1761
    raise errors.ProgrammerError("Can't specify reporter and feedback function")
1762

    
1763
  return GenericPollJob(job_id, _LuxiJobPollCb(cl), reporter)
1764

    
1765

    
1766
def SubmitOpCode(op, cl=None, feedback_fn=None, opts=None, reporter=None):
1767
  """Legacy function to submit an opcode.
1768

1769
  This is just a simple wrapper over the construction of the processor
1770
  instance. It should be extended to better handle feedback and
1771
  interaction functions.
1772

1773
  """
1774
  if cl is None:
1775
    cl = GetClient()
1776

    
1777
  SetGenericOpcodeOpts([op], opts)
1778

    
1779
  job_id = SendJob([op], cl=cl)
1780

    
1781
  op_results = PollJob(job_id, cl=cl, feedback_fn=feedback_fn,
1782
                       reporter=reporter)
1783

    
1784
  return op_results[0]
1785

    
1786

    
1787
def SubmitOrSend(op, opts, cl=None, feedback_fn=None):
1788
  """Wrapper around SubmitOpCode or SendJob.
1789

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

1795
  It will also process the opcodes if we're sending the via SendJob
1796
  (otherwise SubmitOpCode does it).
1797

1798
  """
1799
  if opts and opts.submit_only:
1800
    job = [op]
1801
    SetGenericOpcodeOpts(job, opts)
1802
    job_id = SendJob(job, cl=cl)
1803
    raise JobSubmittedException(job_id)
1804
  else:
1805
    return SubmitOpCode(op, cl=cl, feedback_fn=feedback_fn, opts=opts)
1806

    
1807

    
1808
def SetGenericOpcodeOpts(opcode_list, options):
1809
  """Processor for generic options.
1810

1811
  This function updates the given opcodes based on generic command
1812
  line options (like debug, dry-run, etc.).
1813

1814
  @param opcode_list: list of opcodes
1815
  @param options: command line options or None
1816
  @return: None (in-place modification)
1817

1818
  """
1819
  if not options:
1820
    return
1821
  for op in opcode_list:
1822
    op.debug_level = options.debug
1823
    if hasattr(options, "dry_run"):
1824
      op.dry_run = options.dry_run
1825
    if getattr(options, "priority", None) is not None:
1826
      op.priority = _PRIONAME_TO_VALUE[options.priority]
1827

    
1828

    
1829
def GetClient():
1830
  # TODO: Cache object?
1831
  try:
1832
    client = luxi.Client()
1833
  except luxi.NoMasterError:
1834
    ss = ssconf.SimpleStore()
1835

    
1836
    # Try to read ssconf file
1837
    try:
1838
      ss.GetMasterNode()
1839
    except errors.ConfigurationError:
1840
      raise errors.OpPrereqError("Cluster not initialized or this machine is"
1841
                                 " not part of a cluster")
1842

    
1843
    master, myself = ssconf.GetMasterAndMyself(ss=ss)
1844
    if master != myself:
1845
      raise errors.OpPrereqError("This is not the master node, please connect"
1846
                                 " to node '%s' and rerun the command" %
1847
                                 master)
1848
    raise
1849
  return client
1850

    
1851

    
1852
def FormatError(err):
1853
  """Return a formatted error message for a given error.
1854

1855
  This function takes an exception instance and returns a tuple
1856
  consisting of two values: first, the recommended exit code, and
1857
  second, a string describing the error message (not
1858
  newline-terminated).
1859

1860
  """
1861
  retcode = 1
1862
  obuf = StringIO()
1863
  msg = str(err)
1864
  if isinstance(err, errors.ConfigurationError):
1865
    txt = "Corrupt configuration file: %s" % msg
1866
    logging.error(txt)
1867
    obuf.write(txt + "\n")
1868
    obuf.write("Aborting.")
1869
    retcode = 2
1870
  elif isinstance(err, errors.HooksAbort):
1871
    obuf.write("Failure: hooks execution failed:\n")
1872
    for node, script, out in err.args[0]:
1873
      if out:
1874
        obuf.write("  node: %s, script: %s, output: %s\n" %
1875
                   (node, script, out))
1876
      else:
1877
        obuf.write("  node: %s, script: %s (no output)\n" %
1878
                   (node, script))
1879
  elif isinstance(err, errors.HooksFailure):
1880
    obuf.write("Failure: hooks general failure: %s" % msg)
1881
  elif isinstance(err, errors.ResolverError):
1882
    this_host = netutils.Hostname.GetSysName()
1883
    if err.args[0] == this_host:
1884
      msg = "Failure: can't resolve my own hostname ('%s')"
1885
    else:
1886
      msg = "Failure: can't resolve hostname '%s'"
1887
    obuf.write(msg % err.args[0])
1888
  elif isinstance(err, errors.OpPrereqError):
1889
    if len(err.args) == 2:
1890
      obuf.write("Failure: prerequisites not met for this"
1891
               " operation:\nerror type: %s, error details:\n%s" %
1892
                 (err.args[1], err.args[0]))
1893
    else:
1894
      obuf.write("Failure: prerequisites not met for this"
1895
                 " operation:\n%s" % msg)
1896
  elif isinstance(err, errors.OpExecError):
1897
    obuf.write("Failure: command execution error:\n%s" % msg)
1898
  elif isinstance(err, errors.TagError):
1899
    obuf.write("Failure: invalid tag(s) given:\n%s" % msg)
1900
  elif isinstance(err, errors.JobQueueDrainError):
1901
    obuf.write("Failure: the job queue is marked for drain and doesn't"
1902
               " accept new requests\n")
1903
  elif isinstance(err, errors.JobQueueFull):
1904
    obuf.write("Failure: the job queue is full and doesn't accept new"
1905
               " job submissions until old jobs are archived\n")
1906
  elif isinstance(err, errors.TypeEnforcementError):
1907
    obuf.write("Parameter Error: %s" % msg)
1908
  elif isinstance(err, errors.ParameterError):
1909
    obuf.write("Failure: unknown/wrong parameter name '%s'" % msg)
1910
  elif isinstance(err, luxi.NoMasterError):
1911
    obuf.write("Cannot communicate with the master daemon.\nIs it running"
1912
               " and listening for connections?")
1913
  elif isinstance(err, luxi.TimeoutError):
1914
    obuf.write("Timeout while talking to the master daemon. Jobs might have"
1915
               " been submitted and will continue to run even if the call"
1916
               " timed out. Useful commands in this situation are \"gnt-job"
1917
               " list\", \"gnt-job cancel\" and \"gnt-job watch\". Error:\n")
1918
    obuf.write(msg)
1919
  elif isinstance(err, luxi.PermissionError):
1920
    obuf.write("It seems you don't have permissions to connect to the"
1921
               " master daemon.\nPlease retry as a different user.")
1922
  elif isinstance(err, luxi.ProtocolError):
1923
    obuf.write("Unhandled protocol error while talking to the master daemon:\n"
1924
               "%s" % msg)
1925
  elif isinstance(err, errors.JobLost):
1926
    obuf.write("Error checking job status: %s" % msg)
1927
  elif isinstance(err, errors.QueryFilterParseError):
1928
    obuf.write("Error while parsing query filter: %s\n" % err.args[0])
1929
    obuf.write("\n".join(err.GetDetails()))
1930
  elif isinstance(err, errors.GenericError):
1931
    obuf.write("Unhandled Ganeti error: %s" % msg)
1932
  elif isinstance(err, JobSubmittedException):
1933
    obuf.write("JobID: %s\n" % err.args[0])
1934
    retcode = 0
1935
  else:
1936
    obuf.write("Unhandled exception: %s" % msg)
1937
  return retcode, obuf.getvalue().rstrip('\n')
1938

    
1939

    
1940
def GenericMain(commands, override=None, aliases=None):
1941
  """Generic main function for all the gnt-* commands.
1942

1943
  Arguments:
1944
    - commands: a dictionary with a special structure, see the design doc
1945
                for command line handling.
1946
    - override: if not None, we expect a dictionary with keys that will
1947
                override command line options; this can be used to pass
1948
                options from the scripts to generic functions
1949
    - aliases: dictionary with command aliases {'alias': 'target, ...}
1950

1951
  """
1952
  # save the program name and the entire command line for later logging
1953
  if sys.argv:
1954
    binary = os.path.basename(sys.argv[0]) or sys.argv[0]
1955
    if len(sys.argv) >= 2:
1956
      binary += " " + sys.argv[1]
1957
      old_cmdline = " ".join(sys.argv[2:])
1958
    else:
1959
      old_cmdline = ""
1960
  else:
1961
    binary = "<unknown program>"
1962
    old_cmdline = ""
1963

    
1964
  if aliases is None:
1965
    aliases = {}
1966

    
1967
  try:
1968
    func, options, args = _ParseArgs(sys.argv, commands, aliases)
1969
  except errors.ParameterError, err:
1970
    result, err_msg = FormatError(err)
1971
    ToStderr(err_msg)
1972
    return 1
1973

    
1974
  if func is None: # parse error
1975
    return 1
1976

    
1977
  if override is not None:
1978
    for key, val in override.iteritems():
1979
      setattr(options, key, val)
1980

    
1981
  utils.SetupLogging(constants.LOG_COMMANDS, binary, debug=options.debug,
1982
                     stderr_logging=True)
1983

    
1984
  if old_cmdline:
1985
    logging.info("run with arguments '%s'", old_cmdline)
1986
  else:
1987
    logging.info("run with no arguments")
1988

    
1989
  try:
1990
    result = func(options, args)
1991
  except (errors.GenericError, luxi.ProtocolError,
1992
          JobSubmittedException), err:
1993
    result, err_msg = FormatError(err)
1994
    logging.exception("Error during command processing")
1995
    ToStderr(err_msg)
1996
  except KeyboardInterrupt:
1997
    result = constants.EXIT_FAILURE
1998
    ToStderr("Aborted. Note that if the operation created any jobs, they"
1999
             " might have been submitted and"
2000
             " will continue to run in the background.")
2001
  except IOError, err:
2002
    if err.errno == errno.EPIPE:
2003
      # our terminal went away, we'll exit
2004
      sys.exit(constants.EXIT_FAILURE)
2005
    else:
2006
      raise
2007

    
2008
  return result
2009

    
2010

    
2011
def ParseNicOption(optvalue):
2012
  """Parses the value of the --net option(s).
2013

2014
  """
2015
  try:
2016
    nic_max = max(int(nidx[0]) + 1 for nidx in optvalue)
2017
  except (TypeError, ValueError), err:
2018
    raise errors.OpPrereqError("Invalid NIC index passed: %s" % str(err))
2019

    
2020
  nics = [{}] * nic_max
2021
  for nidx, ndict in optvalue:
2022
    nidx = int(nidx)
2023

    
2024
    if not isinstance(ndict, dict):
2025
      raise errors.OpPrereqError("Invalid nic/%d value: expected dict,"
2026
                                 " got %s" % (nidx, ndict))
2027

    
2028
    utils.ForceDictType(ndict, constants.INIC_PARAMS_TYPES)
2029

    
2030
    nics[nidx] = ndict
2031

    
2032
  return nics
2033

    
2034

    
2035
def GenericInstanceCreate(mode, opts, args):
2036
  """Add an instance to the cluster via either creation or import.
2037

2038
  @param mode: constants.INSTANCE_CREATE or constants.INSTANCE_IMPORT
2039
  @param opts: the command line options selected by the user
2040
  @type args: list
2041
  @param args: should contain only one element, the new instance name
2042
  @rtype: int
2043
  @return: the desired exit code
2044

2045
  """
2046
  instance = args[0]
2047

    
2048
  (pnode, snode) = SplitNodeOption(opts.node)
2049

    
2050
  hypervisor = None
2051
  hvparams = {}
2052
  if opts.hypervisor:
2053
    hypervisor, hvparams = opts.hypervisor
2054

    
2055
  if opts.nics:
2056
    nics = ParseNicOption(opts.nics)
2057
  elif opts.no_nics:
2058
    # no nics
2059
    nics = []
2060
  elif mode == constants.INSTANCE_CREATE:
2061
    # default of one nic, all auto
2062
    nics = [{}]
2063
  else:
2064
    # mode == import
2065
    nics = []
2066

    
2067
  if opts.disk_template == constants.DT_DISKLESS:
2068
    if opts.disks or opts.sd_size is not None:
2069
      raise errors.OpPrereqError("Diskless instance but disk"
2070
                                 " information passed")
2071
    disks = []
2072
  else:
2073
    if (not opts.disks and not opts.sd_size
2074
        and mode == constants.INSTANCE_CREATE):
2075
      raise errors.OpPrereqError("No disk information specified")
2076
    if opts.disks and opts.sd_size is not None:
2077
      raise errors.OpPrereqError("Please use either the '--disk' or"
2078
                                 " '-s' option")
2079
    if opts.sd_size is not None:
2080
      opts.disks = [(0, {constants.IDISK_SIZE: opts.sd_size})]
2081

    
2082
    if opts.disks:
2083
      try:
2084
        disk_max = max(int(didx[0]) + 1 for didx in opts.disks)
2085
      except ValueError, err:
2086
        raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err))
2087
      disks = [{}] * disk_max
2088
    else:
2089
      disks = []
2090
    for didx, ddict in opts.disks:
2091
      didx = int(didx)
2092
      if not isinstance(ddict, dict):
2093
        msg = "Invalid disk/%d value: expected dict, got %s" % (didx, ddict)
2094
        raise errors.OpPrereqError(msg)
2095
      elif constants.IDISK_SIZE in ddict:
2096
        if constants.IDISK_ADOPT in ddict:
2097
          raise errors.OpPrereqError("Only one of 'size' and 'adopt' allowed"
2098
                                     " (disk %d)" % didx)
2099
        try:
2100
          ddict[constants.IDISK_SIZE] = \
2101
            utils.ParseUnit(ddict[constants.IDISK_SIZE])
2102
        except ValueError, err:
2103
          raise errors.OpPrereqError("Invalid disk size for disk %d: %s" %
2104
                                     (didx, err))
2105
      elif constants.IDISK_ADOPT in ddict:
2106
        if mode == constants.INSTANCE_IMPORT:
2107
          raise errors.OpPrereqError("Disk adoption not allowed for instance"
2108
                                     " import")
2109
        ddict[constants.IDISK_SIZE] = 0
2110
      else:
2111
        raise errors.OpPrereqError("Missing size or adoption source for"
2112
                                   " disk %d" % didx)
2113
      disks[didx] = ddict
2114

    
2115
  utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_TYPES)
2116
  utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
2117

    
2118
  if mode == constants.INSTANCE_CREATE:
2119
    start = opts.start
2120
    os_type = opts.os
2121
    force_variant = opts.force_variant
2122
    src_node = None
2123
    src_path = None
2124
    no_install = opts.no_install
2125
    identify_defaults = False
2126
  elif mode == constants.INSTANCE_IMPORT:
2127
    start = False
2128
    os_type = None
2129
    force_variant = False
2130
    src_node = opts.src_node
2131
    src_path = opts.src_dir
2132
    no_install = None
2133
    identify_defaults = opts.identify_defaults
2134
  else:
2135
    raise errors.ProgrammerError("Invalid creation mode %s" % mode)
2136

    
2137
  op = opcodes.OpInstanceCreate(instance_name=instance,
2138
                                disks=disks,
2139
                                disk_template=opts.disk_template,
2140
                                nics=nics,
2141
                                pnode=pnode, snode=snode,
2142
                                ip_check=opts.ip_check,
2143
                                name_check=opts.name_check,
2144
                                wait_for_sync=opts.wait_for_sync,
2145
                                file_storage_dir=opts.file_storage_dir,
2146
                                file_driver=opts.file_driver,
2147
                                iallocator=opts.iallocator,
2148
                                hypervisor=hypervisor,
2149
                                hvparams=hvparams,
2150
                                beparams=opts.beparams,
2151
                                osparams=opts.osparams,
2152
                                mode=mode,
2153
                                start=start,
2154
                                os_type=os_type,
2155
                                force_variant=force_variant,
2156
                                src_node=src_node,
2157
                                src_path=src_path,
2158
                                no_install=no_install,
2159
                                identify_defaults=identify_defaults)
2160

    
2161
  SubmitOrSend(op, opts)
2162
  return 0
2163

    
2164

    
2165
class _RunWhileClusterStoppedHelper:
2166
  """Helper class for L{RunWhileClusterStopped} to simplify state management
2167

2168
  """
2169
  def __init__(self, feedback_fn, cluster_name, master_node, online_nodes):
2170
    """Initializes this class.
2171

2172
    @type feedback_fn: callable
2173
    @param feedback_fn: Feedback function
2174
    @type cluster_name: string
2175
    @param cluster_name: Cluster name
2176
    @type master_node: string
2177
    @param master_node Master node name
2178
    @type online_nodes: list
2179
    @param online_nodes: List of names of online nodes
2180

2181
    """
2182
    self.feedback_fn = feedback_fn
2183
    self.cluster_name = cluster_name
2184
    self.master_node = master_node
2185
    self.online_nodes = online_nodes
2186

    
2187
    self.ssh = ssh.SshRunner(self.cluster_name)
2188

    
2189
    self.nonmaster_nodes = [name for name in online_nodes
2190
                            if name != master_node]
2191

    
2192
    assert self.master_node not in self.nonmaster_nodes
2193

    
2194
  def _RunCmd(self, node_name, cmd):
2195
    """Runs a command on the local or a remote machine.
2196

2197
    @type node_name: string
2198
    @param node_name: Machine name
2199
    @type cmd: list
2200
    @param cmd: Command
2201

2202
    """
2203
    if node_name is None or node_name == self.master_node:
2204
      # No need to use SSH
2205
      result = utils.RunCmd(cmd)
2206
    else:
2207
      result = self.ssh.Run(node_name, "root", utils.ShellQuoteArgs(cmd))
2208

    
2209
    if result.failed:
2210
      errmsg = ["Failed to run command %s" % result.cmd]
2211
      if node_name:
2212
        errmsg.append("on node %s" % node_name)
2213
      errmsg.append(": exitcode %s and error %s" %
2214
                    (result.exit_code, result.output))
2215
      raise errors.OpExecError(" ".join(errmsg))
2216

    
2217
  def Call(self, fn, *args):
2218
    """Call function while all daemons are stopped.
2219

2220
    @type fn: callable
2221
    @param fn: Function to be called
2222

2223
    """
2224
    # Pause watcher by acquiring an exclusive lock on watcher state file
2225
    self.feedback_fn("Blocking watcher")
2226
    watcher_block = utils.FileLock.Open(constants.WATCHER_STATEFILE)
2227
    try:
2228
      # TODO: Currently, this just blocks. There's no timeout.
2229
      # TODO: Should it be a shared lock?
2230
      watcher_block.Exclusive(blocking=True)
2231

    
2232
      # Stop master daemons, so that no new jobs can come in and all running
2233
      # ones are finished
2234
      self.feedback_fn("Stopping master daemons")
2235
      self._RunCmd(None, [constants.DAEMON_UTIL, "stop-master"])
2236
      try:
2237
        # Stop daemons on all nodes
2238
        for node_name in self.online_nodes:
2239
          self.feedback_fn("Stopping daemons on %s" % node_name)
2240
          self._RunCmd(node_name, [constants.DAEMON_UTIL, "stop-all"])
2241

    
2242
        # All daemons are shut down now
2243
        try:
2244
          return fn(self, *args)
2245
        except Exception, err:
2246
          _, errmsg = FormatError(err)
2247
          logging.exception("Caught exception")
2248
          self.feedback_fn(errmsg)
2249
          raise
2250
      finally:
2251
        # Start cluster again, master node last
2252
        for node_name in self.nonmaster_nodes + [self.master_node]:
2253
          self.feedback_fn("Starting daemons on %s" % node_name)
2254
          self._RunCmd(node_name, [constants.DAEMON_UTIL, "start-all"])
2255
    finally:
2256
      # Resume watcher
2257
      watcher_block.Close()
2258

    
2259

    
2260
def RunWhileClusterStopped(feedback_fn, fn, *args):
2261
  """Calls a function while all cluster daemons are stopped.
2262

2263
  @type feedback_fn: callable
2264
  @param feedback_fn: Feedback function
2265
  @type fn: callable
2266
  @param fn: Function to be called when daemons are stopped
2267

2268
  """
2269
  feedback_fn("Gathering cluster information")
2270

    
2271
  # This ensures we're running on the master daemon
2272
  cl = GetClient()
2273

    
2274
  (cluster_name, master_node) = \
2275
    cl.QueryConfigValues(["cluster_name", "master_node"])
2276

    
2277
  online_nodes = GetOnlineNodes([], cl=cl)
2278

    
2279
  # Don't keep a reference to the client. The master daemon will go away.
2280
  del cl
2281

    
2282
  assert master_node in online_nodes
2283

    
2284
  return _RunWhileClusterStoppedHelper(feedback_fn, cluster_name, master_node,
2285
                                       online_nodes).Call(fn, *args)
2286

    
2287

    
2288
def GenerateTable(headers, fields, separator, data,
2289
                  numfields=None, unitfields=None,
2290
                  units=None):
2291
  """Prints a table with headers and different fields.
2292

2293
  @type headers: dict
2294
  @param headers: dictionary mapping field names to headers for
2295
      the table
2296
  @type fields: list
2297
  @param fields: the field names corresponding to each row in
2298
      the data field
2299
  @param separator: the separator to be used; if this is None,
2300
      the default 'smart' algorithm is used which computes optimal
2301
      field width, otherwise just the separator is used between
2302
      each field
2303
  @type data: list
2304
  @param data: a list of lists, each sublist being one row to be output
2305
  @type numfields: list
2306
  @param numfields: a list with the fields that hold numeric
2307
      values and thus should be right-aligned
2308
  @type unitfields: list
2309
  @param unitfields: a list with the fields that hold numeric
2310
      values that should be formatted with the units field
2311
  @type units: string or None
2312
  @param units: the units we should use for formatting, or None for
2313
      automatic choice (human-readable for non-separator usage, otherwise
2314
      megabytes); this is a one-letter string
2315

2316
  """
2317
  if units is None:
2318
    if separator:
2319
      units = "m"
2320
    else:
2321
      units = "h"
2322

    
2323
  if numfields is None:
2324
    numfields = []
2325
  if unitfields is None:
2326
    unitfields = []
2327

    
2328
  numfields = utils.FieldSet(*numfields)   # pylint: disable-msg=W0142
2329
  unitfields = utils.FieldSet(*unitfields) # pylint: disable-msg=W0142
2330

    
2331
  format_fields = []
2332
  for field in fields:
2333
    if headers and field not in headers:
2334
      # TODO: handle better unknown fields (either revert to old
2335
      # style of raising exception, or deal more intelligently with
2336
      # variable fields)
2337
      headers[field] = field
2338
    if separator is not None:
2339
      format_fields.append("%s")
2340
    elif numfields.Matches(field):
2341
      format_fields.append("%*s")
2342
    else:
2343
      format_fields.append("%-*s")
2344

    
2345
  if separator is None:
2346
    mlens = [0 for name in fields]
2347
    format_str = ' '.join(format_fields)
2348
  else:
2349
    format_str = separator.replace("%", "%%").join(format_fields)
2350

    
2351
  for row in data:
2352
    if row is None:
2353
      continue
2354
    for idx, val in enumerate(row):
2355
      if unitfields.Matches(fields[idx]):
2356
        try:
2357
          val = int(val)
2358
        except (TypeError, ValueError):
2359
          pass
2360
        else:
2361
          val = row[idx] = utils.FormatUnit(val, units)
2362
      val = row[idx] = str(val)
2363
      if separator is None:
2364
        mlens[idx] = max(mlens[idx], len(val))
2365

    
2366
  result = []
2367
  if headers:
2368
    args = []
2369
    for idx, name in enumerate(fields):
2370
      hdr = headers[name]
2371
      if separator is None:
2372
        mlens[idx] = max(mlens[idx], len(hdr))
2373
        args.append(mlens[idx])
2374
      args.append(hdr)
2375
    result.append(format_str % tuple(args))
2376

    
2377
  if separator is None:
2378
    assert len(mlens) == len(fields)
2379

    
2380
    if fields and not numfields.Matches(fields[-1]):
2381
      mlens[-1] = 0
2382

    
2383
  for line in data:
2384
    args = []
2385
    if line is None:
2386
      line = ['-' for _ in fields]
2387
    for idx in range(len(fields)):
2388
      if separator is None:
2389
        args.append(mlens[idx])
2390
      args.append(line[idx])
2391
    result.append(format_str % tuple(args))
2392

    
2393
  return result
2394

    
2395

    
2396
def _FormatBool(value):
2397
  """Formats a boolean value as a string.
2398

2399
  """
2400
  if value:
2401
    return "Y"
2402
  return "N"
2403

    
2404

    
2405
#: Default formatting for query results; (callback, align right)
2406
_DEFAULT_FORMAT_QUERY = {
2407
  constants.QFT_TEXT: (str, False),
2408
  constants.QFT_BOOL: (_FormatBool, False),
2409
  constants.QFT_NUMBER: (str, True),
2410
  constants.QFT_TIMESTAMP: (utils.FormatTime, False),
2411
  constants.QFT_OTHER: (str, False),
2412
  constants.QFT_UNKNOWN: (str, False),
2413
  }
2414

    
2415

    
2416
def _GetColumnFormatter(fdef, override, unit):
2417
  """Returns formatting function for a field.
2418

2419
  @type fdef: L{objects.QueryFieldDefinition}
2420
  @type override: dict
2421
  @param override: Dictionary for overriding field formatting functions,
2422
    indexed by field name, contents like L{_DEFAULT_FORMAT_QUERY}
2423
  @type unit: string
2424
  @param unit: Unit used for formatting fields of type L{constants.QFT_UNIT}
2425
  @rtype: tuple; (callable, bool)
2426
  @return: Returns the function to format a value (takes one parameter) and a
2427
    boolean for aligning the value on the right-hand side
2428

2429
  """
2430
  fmt = override.get(fdef.name, None)
2431
  if fmt is not None:
2432
    return fmt
2433

    
2434
  assert constants.QFT_UNIT not in _DEFAULT_FORMAT_QUERY
2435

    
2436
  if fdef.kind == constants.QFT_UNIT:
2437
    # Can't keep this information in the static dictionary
2438
    return (lambda value: utils.FormatUnit(value, unit), True)
2439

    
2440
  fmt = _DEFAULT_FORMAT_QUERY.get(fdef.kind, None)
2441
  if fmt is not None:
2442
    return fmt
2443

    
2444
  raise NotImplementedError("Can't format column type '%s'" % fdef.kind)
2445

    
2446

    
2447
class _QueryColumnFormatter:
2448
  """Callable class for formatting fields of a query.
2449

2450
  """
2451
  def __init__(self, fn, status_fn, verbose):
2452
    """Initializes this class.
2453

2454
    @type fn: callable
2455
    @param fn: Formatting function
2456
    @type status_fn: callable
2457
    @param status_fn: Function to report fields' status
2458
    @type verbose: boolean
2459
    @param verbose: whether to use verbose field descriptions or not
2460

2461
    """
2462
    self._fn = fn
2463
    self._status_fn = status_fn
2464
    self._verbose = verbose
2465

    
2466
  def __call__(self, data):
2467
    """Returns a field's string representation.
2468

2469
    """
2470
    (status, value) = data
2471

    
2472
    # Report status
2473
    self._status_fn(status)
2474

    
2475
    if status == constants.RS_NORMAL:
2476
      return self._fn(value)
2477

    
2478
    assert value is None, \
2479
           "Found value %r for abnormal status %s" % (value, status)
2480

    
2481
    return FormatResultError(status, self._verbose)
2482

    
2483

    
2484
def FormatResultError(status, verbose):
2485
  """Formats result status other than L{constants.RS_NORMAL}.
2486

2487
  @param status: The result status
2488
  @type verbose: boolean
2489
  @param verbose: Whether to return the verbose text
2490
  @return: Text of result status
2491

2492
  """
2493
  assert status != constants.RS_NORMAL, \
2494
         "FormatResultError called with status equal to constants.RS_NORMAL"
2495
  try:
2496
    (verbose_text, normal_text) = constants.RSS_DESCRIPTION[status]
2497
  except KeyError:
2498
    raise NotImplementedError("Unknown status %s" % status)
2499
  else:
2500
    if verbose:
2501
      return verbose_text
2502
    return normal_text
2503

    
2504

    
2505
def FormatQueryResult(result, unit=None, format_override=None, separator=None,
2506
                      header=False, verbose=False):
2507
  """Formats data in L{objects.QueryResponse}.
2508

2509
  @type result: L{objects.QueryResponse}
2510
  @param result: result of query operation
2511
  @type unit: string
2512
  @param unit: Unit used for formatting fields of type L{constants.QFT_UNIT},
2513
    see L{utils.text.FormatUnit}
2514
  @type format_override: dict
2515
  @param format_override: Dictionary for overriding field formatting functions,
2516
    indexed by field name, contents like L{_DEFAULT_FORMAT_QUERY}
2517
  @type separator: string or None
2518
  @param separator: String used to separate fields
2519
  @type header: bool
2520
  @param header: Whether to output header row
2521
  @type verbose: boolean
2522
  @param verbose: whether to use verbose field descriptions or not
2523

2524
  """
2525
  if unit is None:
2526
    if separator:
2527
      unit = "m"
2528
    else:
2529
      unit = "h"
2530

    
2531
  if format_override is None:
2532
    format_override = {}
2533

    
2534
  stats = dict.fromkeys(constants.RS_ALL, 0)
2535

    
2536
  def _RecordStatus(status):
2537
    if status in stats:
2538
      stats[status] += 1
2539

    
2540
  columns = []
2541
  for fdef in result.fields:
2542
    assert fdef.title and fdef.name
2543
    (fn, align_right) = _GetColumnFormatter(fdef, format_override, unit)
2544
    columns.append(TableColumn(fdef.title,
2545
                               _QueryColumnFormatter(fn, _RecordStatus,
2546
                                                     verbose),
2547
                               align_right))
2548

    
2549
  table = FormatTable(result.data, columns, header, separator)
2550

    
2551
  # Collect statistics
2552
  assert len(stats) == len(constants.RS_ALL)
2553
  assert compat.all(count >= 0 for count in stats.values())
2554

    
2555
  # Determine overall status. If there was no data, unknown fields must be
2556
  # detected via the field definitions.
2557
  if (stats[constants.RS_UNKNOWN] or
2558
      (not result.data and _GetUnknownFields(result.fields))):
2559
    status = QR_UNKNOWN
2560
  elif compat.any(count > 0 for key, count in stats.items()
2561
                  if key != constants.RS_NORMAL):
2562
    status = QR_INCOMPLETE
2563
  else:
2564
    status = QR_NORMAL
2565

    
2566
  return (status, table)
2567

    
2568

    
2569
def _GetUnknownFields(fdefs):
2570
  """Returns list of unknown fields included in C{fdefs}.
2571

2572
  @type fdefs: list of L{objects.QueryFieldDefinition}
2573

2574
  """
2575
  return [fdef for fdef in fdefs
2576
          if fdef.kind == constants.QFT_UNKNOWN]
2577

    
2578

    
2579
def _WarnUnknownFields(fdefs):
2580
  """Prints a warning to stderr if a query included unknown fields.
2581

2582
  @type fdefs: list of L{objects.QueryFieldDefinition}
2583

2584
  """
2585
  unknown = _GetUnknownFields(fdefs)
2586
  if unknown:
2587
    ToStderr("Warning: Queried for unknown fields %s",
2588
             utils.CommaJoin(fdef.name for fdef in unknown))
2589
    return True
2590

    
2591
  return False
2592

    
2593

    
2594
def GenericList(resource, fields, names, unit, separator, header, cl=None,
2595
                format_override=None, verbose=False, force_filter=False):
2596
  """Generic implementation for listing all items of a resource.
2597

2598
  @param resource: One of L{constants.QR_VIA_LUXI}
2599
  @type fields: list of strings
2600
  @param fields: List of fields to query for
2601
  @type names: list of strings
2602
  @param names: Names of items to query for
2603
  @type unit: string or None
2604
  @param unit: Unit used for formatting fields of type L{constants.QFT_UNIT} or
2605
    None for automatic choice (human-readable for non-separator usage,
2606
    otherwise megabytes); this is a one-letter string
2607
  @type separator: string or None
2608
  @param separator: String used to separate fields
2609
  @type header: bool
2610
  @param header: Whether to show header row
2611
  @type force_filter: bool
2612
  @param force_filter: Whether to always treat names as filter
2613
  @type format_override: dict
2614
  @param format_override: Dictionary for overriding field formatting functions,
2615
    indexed by field name, contents like L{_DEFAULT_FORMAT_QUERY}
2616
  @type verbose: boolean
2617
  @param verbose: whether to use verbose field descriptions or not
2618

2619
  """
2620
  if cl is None:
2621
    cl = GetClient()
2622

    
2623
  if not names:
2624
    names = None
2625

    
2626
  if (force_filter or
2627
      (names and len(names) == 1 and qlang.MaybeFilter(names[0]))):
2628
    try:
2629
      (filter_text, ) = names
2630
    except ValueError:
2631
      raise errors.OpPrereqError("Exactly one argument must be given as a"
2632
                                 " filter")
2633

    
2634
    logging.debug("Parsing '%s' as filter", filter_text)
2635
    filter_ = qlang.ParseFilter(filter_text)
2636
  else:
2637
    filter_ = qlang.MakeSimpleFilter("name", names)
2638

    
2639
  response = cl.Query(resource, fields, filter_)
2640

    
2641
  found_unknown = _WarnUnknownFields(response.fields)
2642

    
2643
  (status, data) = FormatQueryResult(response, unit=unit, separator=separator,
2644
                                     header=header,
2645
                                     format_override=format_override,
2646
                                     verbose=verbose)
2647

    
2648
  for line in data:
2649
    ToStdout(line)
2650

    
2651
  assert ((found_unknown and status == QR_UNKNOWN) or
2652
          (not found_unknown and status != QR_UNKNOWN))
2653

    
2654
  if status == QR_UNKNOWN:
2655
    return constants.EXIT_UNKNOWN_FIELD
2656

    
2657
  # TODO: Should the list command fail if not all data could be collected?
2658
  return constants.EXIT_SUCCESS
2659

    
2660

    
2661
def GenericListFields(resource, fields, separator, header, cl=None):
2662
  """Generic implementation for listing fields for a resource.
2663

2664
  @param resource: One of L{constants.QR_VIA_LUXI}
2665
  @type fields: list of strings
2666
  @param fields: List of fields to query for
2667
  @type separator: string or None
2668
  @param separator: String used to separate fields
2669
  @type header: bool
2670
  @param header: Whether to show header row
2671

2672
  """
2673
  if cl is None:
2674
    cl = GetClient()
2675

    
2676
  if not fields:
2677
    fields = None
2678

    
2679
  response = cl.QueryFields(resource, fields)
2680

    
2681
  found_unknown = _WarnUnknownFields(response.fields)
2682

    
2683
  columns = [
2684
    TableColumn("Name", str, False),
2685
    TableColumn("Title", str, False),
2686
    TableColumn("Description", str, False),
2687
    ]
2688

    
2689
  rows = [[fdef.name, fdef.title, fdef.doc] for fdef in response.fields]
2690

    
2691
  for line in FormatTable(rows, columns, header, separator):
2692
    ToStdout(line)
2693

    
2694
  if found_unknown:
2695
    return constants.EXIT_UNKNOWN_FIELD
2696

    
2697
  return constants.EXIT_SUCCESS
2698

    
2699

    
2700
class TableColumn:
2701
  """Describes a column for L{FormatTable}.
2702

2703
  """
2704
  def __init__(self, title, fn, align_right):
2705
    """Initializes this class.
2706

2707
    @type title: string
2708
    @param title: Column title
2709
    @type fn: callable
2710
    @param fn: Formatting function
2711
    @type align_right: bool
2712
    @param align_right: Whether to align values on the right-hand side
2713

2714
    """
2715
    self.title = title
2716
    self.format = fn
2717
    self.align_right = align_right
2718

    
2719

    
2720
def _GetColFormatString(width, align_right):
2721
  """Returns the format string for a field.
2722

2723
  """
2724
  if align_right:
2725
    sign = ""
2726
  else:
2727
    sign = "-"
2728

    
2729
  return "%%%s%ss" % (sign, width)
2730

    
2731

    
2732
def FormatTable(rows, columns, header, separator):
2733
  """Formats data as a table.
2734

2735
  @type rows: list of lists
2736
  @param rows: Row data, one list per row
2737
  @type columns: list of L{TableColumn}
2738
  @param columns: Column descriptions
2739
  @type header: bool
2740
  @param header: Whether to show header row
2741
  @type separator: string or None
2742
  @param separator: String used to separate columns
2743

2744
  """
2745
  if header:
2746
    data = [[col.title for col in columns]]
2747
    colwidth = [len(col.title) for col in columns]
2748
  else:
2749
    data = []
2750
    colwidth = [0 for _ in columns]
2751

    
2752
  # Format row data
2753
  for row in rows:
2754
    assert len(row) == len(columns)
2755

    
2756
    formatted = [col.format(value) for value, col in zip(row, columns)]
2757

    
2758
    if separator is None:
2759
      # Update column widths
2760
      for idx, (oldwidth, value) in enumerate(zip(colwidth, formatted)):
2761
        # Modifying a list's items while iterating is fine
2762
        colwidth[idx] = max(oldwidth, len(value))
2763

    
2764
    data.append(formatted)
2765

    
2766
  if separator is not None:
2767
    # Return early if a separator is used
2768
    return [separator.join(row) for row in data]
2769

    
2770
  if columns and not columns[-1].align_right:
2771
    # Avoid unnecessary spaces at end of line
2772
    colwidth[-1] = 0
2773

    
2774
  # Build format string
2775
  fmt = " ".join([_GetColFormatString(width, col.align_right)
2776
                  for col, width in zip(columns, colwidth)])
2777

    
2778
  return [fmt % tuple(row) for row in data]
2779

    
2780

    
2781
def FormatTimestamp(ts):
2782
  """Formats a given timestamp.
2783

2784
  @type ts: timestamp
2785
  @param ts: a timeval-type timestamp, a tuple of seconds and microseconds
2786

2787
  @rtype: string
2788
  @return: a string with the formatted timestamp
2789

2790
  """
2791
  if not isinstance (ts, (tuple, list)) or len(ts) != 2:
2792
    return '?'
2793
  sec, usec = ts
2794
  return time.strftime("%F %T", time.localtime(sec)) + ".%06d" % usec
2795

    
2796

    
2797
def ParseTimespec(value):
2798
  """Parse a time specification.
2799

2800
  The following suffixed will be recognized:
2801

2802
    - s: seconds
2803
    - m: minutes
2804
    - h: hours
2805
    - d: day
2806
    - w: weeks
2807

2808
  Without any suffix, the value will be taken to be in seconds.
2809

2810
  """
2811
  value = str(value)
2812
  if not value:
2813
    raise errors.OpPrereqError("Empty time specification passed")
2814
  suffix_map = {
2815
    's': 1,
2816
    'm': 60,
2817
    'h': 3600,
2818
    'd': 86400,
2819
    'w': 604800,
2820
    }
2821
  if value[-1] not in suffix_map:
2822
    try:
2823
      value = int(value)
2824
    except (TypeError, ValueError):
2825
      raise errors.OpPrereqError("Invalid time specification '%s'" % value)
2826
  else:
2827
    multiplier = suffix_map[value[-1]]
2828
    value = value[:-1]
2829
    if not value: # no data left after stripping the suffix
2830
      raise errors.OpPrereqError("Invalid time specification (only"
2831
                                 " suffix passed)")
2832
    try:
2833
      value = int(value) * multiplier
2834
    except (TypeError, ValueError):
2835
      raise errors.OpPrereqError("Invalid time specification '%s'" % value)
2836
  return value
2837

    
2838

    
2839
def GetOnlineNodes(nodes, cl=None, nowarn=False, secondary_ips=False,
2840
                   filter_master=False):
2841
  """Returns the names of online nodes.
2842

2843
  This function will also log a warning on stderr with the names of
2844
  the online nodes.
2845

2846
  @param nodes: if not empty, use only this subset of nodes (minus the
2847
      offline ones)
2848
  @param cl: if not None, luxi client to use
2849
  @type nowarn: boolean
2850
  @param nowarn: by default, this function will output a note with the
2851
      offline nodes that are skipped; if this parameter is True the
2852
      note is not displayed
2853
  @type secondary_ips: boolean
2854
  @param secondary_ips: if True, return the secondary IPs instead of the
2855
      names, useful for doing network traffic over the replication interface
2856
      (if any)
2857
  @type filter_master: boolean
2858
  @param filter_master: if True, do not return the master node in the list
2859
      (useful in coordination with secondary_ips where we cannot check our
2860
      node name against the list)
2861

2862
  """
2863
  if cl is None:
2864
    cl = GetClient()
2865

    
2866
  if secondary_ips:
2867
    name_idx = 2
2868
  else:
2869
    name_idx = 0
2870

    
2871
  if filter_master:
2872
    master_node = cl.QueryConfigValues(["master_node"])[0]
2873
    filter_fn = lambda x: x != master_node
2874
  else:
2875
    filter_fn = lambda _: True
2876

    
2877
  result = cl.QueryNodes(names=nodes, fields=["name", "offline", "sip"],
2878
                         use_locking=False)
2879
  offline = [row[0] for row in result if row[1]]
2880
  if offline and not nowarn:
2881
    ToStderr("Note: skipping offline node(s): %s" % utils.CommaJoin(offline))
2882
  return [row[name_idx] for row in result if not row[1] and filter_fn(row[0])]
2883

    
2884

    
2885
def _ToStream(stream, txt, *args):
2886
  """Write a message to a stream, bypassing the logging system
2887

2888
  @type stream: file object
2889
  @param stream: the file to which we should write
2890
  @type txt: str
2891
  @param txt: the message
2892

2893
  """
2894
  try:
2895
    if args:
2896
      args = tuple(args)
2897
      stream.write(txt % args)
2898
    else:
2899
      stream.write(txt)
2900
    stream.write('\n')
2901
    stream.flush()
2902
  except IOError, err:
2903
    if err.errno == errno.EPIPE:
2904
      # our terminal went away, we'll exit
2905
      sys.exit(constants.EXIT_FAILURE)
2906
    else:
2907
      raise
2908

    
2909

    
2910
def ToStdout(txt, *args):
2911
  """Write a message to stdout only, bypassing the logging system
2912

2913
  This is just a wrapper over _ToStream.
2914

2915
  @type txt: str
2916
  @param txt: the message
2917

2918
  """
2919
  _ToStream(sys.stdout, txt, *args)
2920

    
2921

    
2922
def ToStderr(txt, *args):
2923
  """Write a message to stderr only, bypassing the logging system
2924

2925
  This is just a wrapper over _ToStream.
2926

2927
  @type txt: str
2928
  @param txt: the message
2929

2930
  """
2931
  _ToStream(sys.stderr, txt, *args)
2932

    
2933

    
2934
class JobExecutor(object):
2935
  """Class which manages the submission and execution of multiple jobs.
2936

2937
  Note that instances of this class should not be reused between
2938
  GetResults() calls.
2939

2940
  """
2941
  def __init__(self, cl=None, verbose=True, opts=None, feedback_fn=None):
2942
    self.queue = []
2943
    if cl is None:
2944
      cl = GetClient()
2945
    self.cl = cl
2946
    self.verbose = verbose
2947
    self.jobs = []
2948
    self.opts = opts
2949
    self.feedback_fn = feedback_fn
2950

    
2951
  def QueueJob(self, name, *ops):
2952
    """Record a job for later submit.
2953

2954
    @type name: string
2955
    @param name: a description of the job, will be used in WaitJobSet
2956
    """
2957
    SetGenericOpcodeOpts(ops, self.opts)
2958
    self.queue.append((name, ops))
2959

    
2960
  def SubmitPending(self, each=False):
2961
    """Submit all pending jobs.
2962

2963
    """
2964
    if each:
2965
      results = []
2966
      for row in self.queue:
2967
        # SubmitJob will remove the success status, but raise an exception if
2968
        # the submission fails, so we'll notice that anyway.
2969
        results.append([True, self.cl.SubmitJob(row[1])])
2970
    else:
2971
      results = self.cl.SubmitManyJobs([row[1] for row in self.queue])
2972
    for (idx, ((status, data), (name, _))) in enumerate(zip(results,
2973
                                                            self.queue)):
2974
      self.jobs.append((idx, status, data, name))
2975

    
2976
  def _ChooseJob(self):
2977
    """Choose a non-waiting/queued job to poll next.
2978

2979
    """
2980
    assert self.jobs, "_ChooseJob called with empty job list"
2981

    
2982
    result = self.cl.QueryJobs([i[2] for i in self.jobs], ["status"])
2983
    assert result
2984

    
2985
    for job_data, status in zip(self.jobs, result):
2986
      if (isinstance(status, list) and status and
2987
          status[0] in (constants.JOB_STATUS_QUEUED,
2988
                        constants.JOB_STATUS_WAITLOCK,
2989
                        constants.JOB_STATUS_CANCELING)):
2990
        # job is still present and waiting
2991
        continue
2992
      # good candidate found (either running job or lost job)
2993
      self.jobs.remove(job_data)
2994
      return job_data
2995

    
2996
    # no job found
2997
    return self.jobs.pop(0)
2998

    
2999
  def GetResults(self):
3000
    """Wait for and return the results of all jobs.
3001

3002
    @rtype: list
3003
    @return: list of tuples (success, job results), in the same order
3004
        as the submitted jobs; if a job has failed, instead of the result
3005
        there will be the error message
3006

3007
    """
3008
    if not self.jobs:
3009
      self.SubmitPending()
3010
    results = []
3011
    if self.verbose:
3012
      ok_jobs = [row[2] for row in self.jobs if row[1]]
3013
      if ok_jobs:
3014
        ToStdout("Submitted jobs %s", utils.CommaJoin(ok_jobs))
3015

    
3016
    # first, remove any non-submitted jobs
3017
    self.jobs, failures = compat.partition(self.jobs, lambda x: x[1])
3018
    for idx, _, jid, name in failures:
3019
      ToStderr("Failed to submit job for %s: %s", name, jid)
3020
      results.append((idx, False, jid))
3021

    
3022
    while self.jobs:
3023
      (idx, _, jid, name) = self._ChooseJob()
3024
      ToStdout("Waiting for job %s for %s...", jid, name)
3025
      try:
3026
        job_result = PollJob(jid, cl=self.cl, feedback_fn=self.feedback_fn)
3027
        success = True
3028
      except errors.JobLost, err:
3029
        _, job_result = FormatError(err)
3030
        ToStderr("Job %s for %s has been archived, cannot check its result",
3031
                 jid, name)
3032
        success = False
3033
      except (errors.GenericError, luxi.ProtocolError), err:
3034
        _, job_result = FormatError(err)
3035
        success = False
3036
        # the error message will always be shown, verbose or not
3037
        ToStderr("Job %s for %s has failed: %s", jid, name, job_result)
3038

    
3039
      results.append((idx, success, job_result))
3040

    
3041
    # sort based on the index, then drop it
3042
    results.sort()
3043
    results = [i[1:] for i in results]
3044

    
3045
    return results
3046

    
3047
  def WaitOrShow(self, wait):
3048
    """Wait for job results or only print the job IDs.
3049

3050
    @type wait: boolean
3051
    @param wait: whether to wait or not
3052

3053
    """
3054
    if wait:
3055
      return self.GetResults()
3056
    else:
3057
      if not self.jobs:
3058
        self.SubmitPending()
3059
      for _, status, result, name in self.jobs:
3060
        if status:
3061
          ToStdout("%s: %s", result, name)
3062
        else:
3063
          ToStderr("Failure for %s: %s", name, result)
3064
      return [row[1:3] for row in self.jobs]
3065

    
3066

    
3067
def FormatParameterDict(buf, param_dict, actual, level=1):
3068
  """Formats a parameter dictionary.
3069

3070
  @type buf: L{StringIO}
3071
  @param buf: the buffer into which to write
3072
  @type param_dict: dict
3073
  @param param_dict: the own parameters
3074
  @type actual: dict
3075
  @param actual: the current parameter set (including defaults)
3076
  @param level: Level of indent
3077

3078
  """
3079
  indent = "  " * level
3080
  for key in sorted(actual):
3081
    val = param_dict.get(key, "default (%s)" % actual[key])
3082
    buf.write("%s- %s: %s\n" % (indent, key, val))
3083

    
3084

    
3085
def ConfirmOperation(names, list_type, text, extra=""):
3086
  """Ask the user to confirm an operation on a list of list_type.
3087

3088
  This function is used to request confirmation for doing an operation
3089
  on a given list of list_type.
3090

3091
  @type names: list
3092
  @param names: the list of names that we display when
3093
      we ask for confirmation
3094
  @type list_type: str
3095
  @param list_type: Human readable name for elements in the list (e.g. nodes)
3096
  @type text: str
3097
  @param text: the operation that the user should confirm
3098
  @rtype: boolean
3099
  @return: True or False depending on user's confirmation.
3100

3101
  """
3102
  count = len(names)
3103
  msg = ("The %s will operate on %d %s.\n%s"
3104
         "Do you want to continue?" % (text, count, list_type, extra))
3105
  affected = (("\nAffected %s:\n" % list_type) +
3106
              "\n".join(["  %s" % name for name in names]))
3107

    
3108
  choices = [("y", True, "Yes, execute the %s" % text),
3109
             ("n", False, "No, abort the %s" % text)]
3110

    
3111
  if count > 20:
3112
    choices.insert(1, ("v", "v", "View the list of affected %s" % list_type))
3113
    question = msg
3114
  else:
3115
    question = msg + affected
3116

    
3117
  choice = AskUser(question, choices)
3118
  if choice == "v":
3119
    choices.pop(1)
3120
    choice = AskUser(msg + affected, choices)
3121
  return choice