Statistics
| Branch: | Tag: | Revision:

root / lib / cli.py @ 47099cd1

History | View | Annotate | Download (103 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
import itertools
32
from cStringIO import StringIO
33

    
34
from ganeti import utils
35
from ganeti import errors
36
from ganeti import constants
37
from ganeti import opcodes
38
from ganeti import luxi
39
from ganeti import ssconf
40
from ganeti import rpc
41
from ganeti import ssh
42
from ganeti import compat
43
from ganeti import netutils
44
from ganeti import qlang
45

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

    
49

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

    
240
NO_PREFIX = "no_"
241
UN_PREFIX = "-"
242

    
243
#: Priorities (sorted)
244
_PRIORITY_NAMES = [
245
  ("low", constants.OP_PRIO_LOW),
246
  ("normal", constants.OP_PRIO_NORMAL),
247
  ("high", constants.OP_PRIO_HIGH),
248
  ]
249

    
250
#: Priority dictionary for easier lookup
251
# TODO: Replace this and _PRIORITY_NAMES with a single sorted dictionary once
252
# we migrate to Python 2.6
253
_PRIONAME_TO_VALUE = dict(_PRIORITY_NAMES)
254

    
255
# Query result status for clients
256
(QR_NORMAL,
257
 QR_UNKNOWN,
258
 QR_INCOMPLETE) = range(3)
259

    
260

    
261
class _Argument:
262
  def __init__(self, min=0, max=None): # pylint: disable-msg=W0622
263
    self.min = min
264
    self.max = max
265

    
266
  def __repr__(self):
267
    return ("<%s min=%s max=%s>" %
268
            (self.__class__.__name__, self.min, self.max))
269

    
270

    
271
class ArgSuggest(_Argument):
272
  """Suggesting argument.
273

274
  Value can be any of the ones passed to the constructor.
275

276
  """
277
  # pylint: disable-msg=W0622
278
  def __init__(self, min=0, max=None, choices=None):
279
    _Argument.__init__(self, min=min, max=max)
280
    self.choices = choices
281

    
282
  def __repr__(self):
283
    return ("<%s min=%s max=%s choices=%r>" %
284
            (self.__class__.__name__, self.min, self.max, self.choices))
285

    
286

    
287
class ArgChoice(ArgSuggest):
288
  """Choice argument.
289

290
  Value can be any of the ones passed to the constructor. Like L{ArgSuggest},
291
  but value must be one of the choices.
292

293
  """
294

    
295

    
296
class ArgUnknown(_Argument):
297
  """Unknown argument to program (e.g. determined at runtime).
298

299
  """
300

    
301

    
302
class ArgInstance(_Argument):
303
  """Instances argument.
304

305
  """
306

    
307

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

311
  """
312

    
313

    
314
class ArgGroup(_Argument):
315
  """Node group argument.
316

317
  """
318

    
319

    
320
class ArgJobId(_Argument):
321
  """Job ID argument.
322

323
  """
324

    
325

    
326
class ArgFile(_Argument):
327
  """File path argument.
328

329
  """
330

    
331

    
332
class ArgCommand(_Argument):
333
  """Command argument.
334

335
  """
336

    
337

    
338
class ArgHost(_Argument):
339
  """Host argument.
340

341
  """
342

    
343

    
344
class ArgOs(_Argument):
345
  """OS argument.
346

347
  """
348

    
349

    
350
ARGS_NONE = []
351
ARGS_MANY_INSTANCES = [ArgInstance()]
352
ARGS_MANY_NODES = [ArgNode()]
353
ARGS_MANY_GROUPS = [ArgGroup()]
354
ARGS_ONE_INSTANCE = [ArgInstance(min=1, max=1)]
355
ARGS_ONE_NODE = [ArgNode(min=1, max=1)]
356
# TODO
357
ARGS_ONE_GROUP = [ArgGroup(min=1, max=1)]
358
ARGS_ONE_OS = [ArgOs(min=1, max=1)]
359

    
360

    
361
def _ExtractTagsObject(opts, args):
362
  """Extract the tag type object.
363

364
  Note that this function will modify its args parameter.
365

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

    
383

    
384
def _ExtendTags(opts, args):
385
  """Extend the args if a source file has been given.
386

387
  This function will extend the tags with the contents of the file
388
  passed in the 'tags_source' attribute of the opts parameter. A file
389
  named '-' will be replaced by stdin.
390

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

    
412

    
413
def ListTags(opts, args):
414
  """List the tags on a given object.
415

416
  This is a generic implementation that knows how to deal with all
417
  three cases of tag objects (cluster, node, instance). The opts
418
  argument is expected to contain a tag_type field denoting what
419
  object type we work on.
420

421
  """
422
  kind, name = _ExtractTagsObject(opts, args)
423
  cl = GetClient()
424
  result = cl.QueryTags(kind, name)
425
  result = list(result)
426
  result.sort()
427
  for tag in result:
428
    ToStdout(tag)
429

    
430

    
431
def AddTags(opts, args):
432
  """Add tags on a given object.
433

434
  This is a generic implementation that knows how to deal with all
435
  three cases of tag objects (cluster, node, instance). The opts
436
  argument is expected to contain a tag_type field denoting what
437
  object type we work on.
438

439
  """
440
  kind, name = _ExtractTagsObject(opts, args)
441
  _ExtendTags(opts, args)
442
  if not args:
443
    raise errors.OpPrereqError("No tags to be added")
444
  op = opcodes.OpTagsSet(kind=kind, name=name, tags=args)
445
  SubmitOpCode(op, opts=opts)
446

    
447

    
448
def RemoveTags(opts, args):
449
  """Remove tags from a given object.
450

451
  This is a generic implementation that knows how to deal with all
452
  three cases of tag objects (cluster, node, instance). The opts
453
  argument is expected to contain a tag_type field denoting what
454
  object type we work on.
455

456
  """
457
  kind, name = _ExtractTagsObject(opts, args)
458
  _ExtendTags(opts, args)
459
  if not args:
460
    raise errors.OpPrereqError("No tags to be removed")
461
  op = opcodes.OpTagsDel(kind=kind, name=name, tags=args)
462
  SubmitOpCode(op, opts=opts)
463

    
464

    
465
def check_unit(option, opt, value): # pylint: disable-msg=W0613
466
  """OptParsers custom converter for units.
467

468
  """
469
  try:
470
    return utils.ParseUnit(value)
471
  except errors.UnitParseError, err:
472
    raise OptionValueError("option %s: %s" % (opt, err))
473

    
474

    
475
def _SplitKeyVal(opt, data):
476
  """Convert a KeyVal string into a dict.
477

478
  This function will convert a key=val[,...] string into a dict. Empty
479
  values will be converted specially: keys which have the prefix 'no_'
480
  will have the value=False and the prefix stripped, the others will
481
  have value=True.
482

483
  @type opt: string
484
  @param opt: a string holding the option name for which we process the
485
      data, used in building error messages
486
  @type data: string
487
  @param data: a string of the format key=val,key=val,...
488
  @rtype: dict
489
  @return: {key=val, key=val}
490
  @raises errors.ParameterError: if there are duplicate keys
491

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

    
511

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

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

518
  """
519
  if ":" not in value:
520
    ident, rest = value, ''
521
  else:
522
    ident, rest = value.split(":", 1)
523

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

    
539

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

543
  This will store the parsed values as a dict {key: val}.
544

545
  """
546
  return _SplitKeyVal(opt, value)
547

    
548

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

552
  This will store the parsed value as either True or False.
553

554
  """
555
  value = value.lower()
556
  if value == constants.VALUE_FALSE or value == "no":
557
    return False
558
  elif value == constants.VALUE_TRUE or value == "yes":
559
    return True
560
  else:
561
    raise errors.ParameterError("Invalid boolean value '%s'" % value)
562

    
563

    
564
# completion_suggestion is normally a list. Using numeric values not evaluating
565
# to False for dynamic completion.
566
(OPT_COMPL_MANY_NODES,
567
 OPT_COMPL_ONE_NODE,
568
 OPT_COMPL_ONE_INSTANCE,
569
 OPT_COMPL_ONE_OS,
570
 OPT_COMPL_ONE_IALLOCATOR,
571
 OPT_COMPL_INST_ADD_NODES,
572
 OPT_COMPL_ONE_NODEGROUP) = range(100, 107)
573

    
574
OPT_COMPL_ALL = frozenset([
575
  OPT_COMPL_MANY_NODES,
576
  OPT_COMPL_ONE_NODE,
577
  OPT_COMPL_ONE_INSTANCE,
578
  OPT_COMPL_ONE_OS,
579
  OPT_COMPL_ONE_IALLOCATOR,
580
  OPT_COMPL_INST_ADD_NODES,
581
  OPT_COMPL_ONE_NODEGROUP,
582
  ])
583

    
584

    
585
class CliOption(Option):
586
  """Custom option class for optparse.
587

588
  """
589
  ATTRS = Option.ATTRS + [
590
    "completion_suggest",
591
    ]
592
  TYPES = Option.TYPES + (
593
    "identkeyval",
594
    "keyval",
595
    "unit",
596
    "bool",
597
    )
598
  TYPE_CHECKER = Option.TYPE_CHECKER.copy()
599
  TYPE_CHECKER["identkeyval"] = check_ident_key_val
600
  TYPE_CHECKER["keyval"] = check_key_val
601
  TYPE_CHECKER["unit"] = check_unit
602
  TYPE_CHECKER["bool"] = check_bool
603

    
604

    
605
# optparse.py sets make_option, so we do it for our own option class, too
606
cli_option = CliOption
607

    
608

    
609
_YORNO = "yes|no"
610

    
611
DEBUG_OPT = cli_option("-d", "--debug", default=0, action="count",
612
                       help="Increase debugging level")
613

    
614
NOHDR_OPT = cli_option("--no-headers", default=False,
615
                       action="store_true", dest="no_headers",
616
                       help="Don't display column headers")
617

    
618
SEP_OPT = cli_option("--separator", default=None,
619
                     action="store", dest="separator",
620
                     help=("Separator between output fields"
621
                           " (defaults to one space)"))
622

    
623
USEUNITS_OPT = cli_option("--units", default=None,
624
                          dest="units", choices=('h', 'm', 'g', 't'),
625
                          help="Specify units for output (one of h/m/g/t)")
626

    
627
FIELDS_OPT = cli_option("-o", "--output", dest="output", action="store",
628
                        type="string", metavar="FIELDS",
629
                        help="Comma separated list of output fields")
630

    
631
FORCE_OPT = cli_option("-f", "--force", dest="force", action="store_true",
632
                       default=False, help="Force the operation")
633

    
634
CONFIRM_OPT = cli_option("--yes", dest="confirm", action="store_true",
635
                         default=False, help="Do not require confirmation")
636

    
637
IGNORE_OFFLINE_OPT = cli_option("--ignore-offline", dest="ignore_offline",
638
                                  action="store_true", default=False,
639
                                  help=("Ignore offline nodes and do as much"
640
                                        " as possible"))
641

    
642
TAG_ADD_OPT = cli_option("--tags", dest="tags",
643
                         default=None, help="Comma-separated list of instance"
644
                                            " tags")
645

    
646
TAG_SRC_OPT = cli_option("--from", dest="tags_source",
647
                         default=None, help="File with tag names")
648

    
649
SUBMIT_OPT = cli_option("--submit", dest="submit_only",
650
                        default=False, action="store_true",
651
                        help=("Submit the job and return the job ID, but"
652
                              " don't wait for the job to finish"))
653

    
654
SYNC_OPT = cli_option("--sync", dest="do_locking",
655
                      default=False, action="store_true",
656
                      help=("Grab locks while doing the queries"
657
                            " in order to ensure more consistent results"))
658

    
659
DRY_RUN_OPT = cli_option("--dry-run", default=False,
660
                         action="store_true",
661
                         help=("Do not execute the operation, just run the"
662
                               " check steps and verify it it could be"
663
                               " executed"))
664

    
665
VERBOSE_OPT = cli_option("-v", "--verbose", default=False,
666
                         action="store_true",
667
                         help="Increase the verbosity of the operation")
668

    
669
DEBUG_SIMERR_OPT = cli_option("--debug-simulate-errors", default=False,
670
                              action="store_true", dest="simulate_errors",
671
                              help="Debugging option that makes the operation"
672
                              " treat most runtime checks as failed")
673

    
674
NWSYNC_OPT = cli_option("--no-wait-for-sync", dest="wait_for_sync",
675
                        default=True, action="store_false",
676
                        help="Don't wait for sync (DANGEROUS!)")
677

    
678
DISK_TEMPLATE_OPT = cli_option("-t", "--disk-template", dest="disk_template",
679
                               help=("Custom disk setup (%s)" %
680
                                     utils.CommaJoin(constants.DISK_TEMPLATES)),
681
                               default=None, metavar="TEMPL",
682
                               choices=list(constants.DISK_TEMPLATES))
683

    
684
NONICS_OPT = cli_option("--no-nics", default=False, action="store_true",
685
                        help="Do not create any network cards for"
686
                        " the instance")
687

    
688
FILESTORE_DIR_OPT = cli_option("--file-storage-dir", dest="file_storage_dir",
689
                               help="Relative path under default cluster-wide"
690
                               " file storage dir to store file-based disks",
691
                               default=None, metavar="<DIR>")
692

    
693
FILESTORE_DRIVER_OPT = cli_option("--file-driver", dest="file_driver",
694
                                  help="Driver to use for image files",
695
                                  default="loop", metavar="<DRIVER>",
696
                                  choices=list(constants.FILE_DRIVER))
697

    
698
IALLOCATOR_OPT = cli_option("-I", "--iallocator", metavar="<NAME>",
699
                            help="Select nodes for the instance automatically"
700
                            " using the <NAME> iallocator plugin",
701
                            default=None, type="string",
702
                            completion_suggest=OPT_COMPL_ONE_IALLOCATOR)
703

    
704
DEFAULT_IALLOCATOR_OPT = cli_option("-I", "--default-iallocator",
705
                            metavar="<NAME>",
706
                            help="Set the default instance allocator plugin",
707
                            default=None, type="string",
708
                            completion_suggest=OPT_COMPL_ONE_IALLOCATOR)
709

    
710
OS_OPT = cli_option("-o", "--os-type", dest="os", help="What OS to run",
711
                    metavar="<os>",
712
                    completion_suggest=OPT_COMPL_ONE_OS)
713

    
714
OSPARAMS_OPT = cli_option("-O", "--os-parameters", dest="osparams",
715
                         type="keyval", default={},
716
                         help="OS parameters")
717

    
718
FORCE_VARIANT_OPT = cli_option("--force-variant", dest="force_variant",
719
                               action="store_true", default=False,
720
                               help="Force an unknown variant")
721

    
722
NO_INSTALL_OPT = cli_option("--no-install", dest="no_install",
723
                            action="store_true", default=False,
724
                            help="Do not install the OS (will"
725
                            " enable no-start)")
726

    
727
BACKEND_OPT = cli_option("-B", "--backend-parameters", dest="beparams",
728
                         type="keyval", default={},
729
                         help="Backend parameters")
730

    
731
HVOPTS_OPT =  cli_option("-H", "--hypervisor-parameters", type="keyval",
732
                         default={}, dest="hvparams",
733
                         help="Hypervisor parameters")
734

    
735
HYPERVISOR_OPT = cli_option("-H", "--hypervisor-parameters", dest="hypervisor",
736
                            help="Hypervisor and hypervisor options, in the"
737
                            " format hypervisor:option=value,option=value,...",
738
                            default=None, type="identkeyval")
739

    
740
HVLIST_OPT = cli_option("-H", "--hypervisor-parameters", dest="hvparams",
741
                        help="Hypervisor and hypervisor options, in the"
742
                        " format hypervisor:option=value,option=value,...",
743
                        default=[], action="append", type="identkeyval")
744

    
745
NOIPCHECK_OPT = cli_option("--no-ip-check", dest="ip_check", default=True,
746
                           action="store_false",
747
                           help="Don't check that the instance's IP"
748
                           " is alive")
749

    
750
NONAMECHECK_OPT = cli_option("--no-name-check", dest="name_check",
751
                             default=True, action="store_false",
752
                             help="Don't check that the instance's name"
753
                             " is resolvable")
754

    
755
NET_OPT = cli_option("--net",
756
                     help="NIC parameters", default=[],
757
                     dest="nics", action="append", type="identkeyval")
758

    
759
DISK_OPT = cli_option("--disk", help="Disk parameters", default=[],
760
                      dest="disks", action="append", type="identkeyval")
761

    
762
DISKIDX_OPT = cli_option("--disks", dest="disks", default=None,
763
                         help="Comma-separated list of disks"
764
                         " indices to act on (e.g. 0,2) (optional,"
765
                         " defaults to all disks)")
766

    
767
OS_SIZE_OPT = cli_option("-s", "--os-size", dest="sd_size",
768
                         help="Enforces a single-disk configuration using the"
769
                         " given disk size, in MiB unless a suffix is used",
770
                         default=None, type="unit", metavar="<size>")
771

    
772
IGNORE_CONSIST_OPT = cli_option("--ignore-consistency",
773
                                dest="ignore_consistency",
774
                                action="store_true", default=False,
775
                                help="Ignore the consistency of the disks on"
776
                                " the secondary")
777

    
778
ALLOW_FAILOVER_OPT = cli_option("--allow-failover",
779
                                dest="allow_failover",
780
                                action="store_true", default=False,
781
                                help="If migration is not possible fallback to"
782
                                     " failover")
783

    
784
NONLIVE_OPT = cli_option("--non-live", dest="live",
785
                         default=True, action="store_false",
786
                         help="Do a non-live migration (this usually means"
787
                         " freeze the instance, save the state, transfer and"
788
                         " only then resume running on the secondary node)")
789

    
790
MIGRATION_MODE_OPT = cli_option("--migration-mode", dest="migration_mode",
791
                                default=None,
792
                                choices=list(constants.HT_MIGRATION_MODES),
793
                                help="Override default migration mode (choose"
794
                                " either live or non-live")
795

    
796
NODE_PLACEMENT_OPT = cli_option("-n", "--node", dest="node",
797
                                help="Target node and optional secondary node",
798
                                metavar="<pnode>[:<snode>]",
799
                                completion_suggest=OPT_COMPL_INST_ADD_NODES)
800

    
801
NODE_LIST_OPT = cli_option("-n", "--node", dest="nodes", default=[],
802
                           action="append", metavar="<node>",
803
                           help="Use only this node (can be used multiple"
804
                           " times, if not given defaults to all nodes)",
805
                           completion_suggest=OPT_COMPL_ONE_NODE)
806

    
807
NODEGROUP_OPT = cli_option("-g", "--node-group",
808
                           dest="nodegroup",
809
                           help="Node group (name or uuid)",
810
                           metavar="<nodegroup>",
811
                           default=None, type="string",
812
                           completion_suggest=OPT_COMPL_ONE_NODEGROUP)
813

    
814
SINGLE_NODE_OPT = cli_option("-n", "--node", dest="node", help="Target node",
815
                             metavar="<node>",
816
                             completion_suggest=OPT_COMPL_ONE_NODE)
817

    
818
NOSTART_OPT = cli_option("--no-start", dest="start", default=True,
819
                         action="store_false",
820
                         help="Don't start the instance after creation")
821

    
822
SHOWCMD_OPT = cli_option("--show-cmd", dest="show_command",
823
                         action="store_true", default=False,
824
                         help="Show command instead of executing it")
825

    
826
CLEANUP_OPT = cli_option("--cleanup", dest="cleanup",
827
                         default=False, action="store_true",
828
                         help="Instead of performing the migration, try to"
829
                         " recover from a failed cleanup. This is safe"
830
                         " to run even if the instance is healthy, but it"
831
                         " will create extra replication traffic and "
832
                         " disrupt briefly the replication (like during the"
833
                         " migration")
834

    
835
STATIC_OPT = cli_option("-s", "--static", dest="static",
836
                        action="store_true", default=False,
837
                        help="Only show configuration data, not runtime data")
838

    
839
ALL_OPT = cli_option("--all", dest="show_all",
840
                     default=False, action="store_true",
841
                     help="Show info on all instances on the cluster."
842
                     " This can take a long time to run, use wisely")
843

    
844
SELECT_OS_OPT = cli_option("--select-os", dest="select_os",
845
                           action="store_true", default=False,
846
                           help="Interactive OS reinstall, lists available"
847
                           " OS templates for selection")
848

    
849
IGNORE_FAILURES_OPT = cli_option("--ignore-failures", dest="ignore_failures",
850
                                 action="store_true", default=False,
851
                                 help="Remove the instance from the cluster"
852
                                 " configuration even if there are failures"
853
                                 " during the removal process")
854

    
855
IGNORE_REMOVE_FAILURES_OPT = cli_option("--ignore-remove-failures",
856
                                        dest="ignore_remove_failures",
857
                                        action="store_true", default=False,
858
                                        help="Remove the instance from the"
859
                                        " cluster configuration even if there"
860
                                        " are failures during the removal"
861
                                        " process")
862

    
863
REMOVE_INSTANCE_OPT = cli_option("--remove-instance", dest="remove_instance",
864
                                 action="store_true", default=False,
865
                                 help="Remove the instance from the cluster")
866

    
867
DST_NODE_OPT = cli_option("-n", "--target-node", dest="dst_node",
868
                               help="Specifies the new node for the instance",
869
                               metavar="NODE", default=None,
870
                               completion_suggest=OPT_COMPL_ONE_NODE)
871

    
872
NEW_SECONDARY_OPT = cli_option("-n", "--new-secondary", dest="dst_node",
873
                               help="Specifies the new secondary node",
874
                               metavar="NODE", default=None,
875
                               completion_suggest=OPT_COMPL_ONE_NODE)
876

    
877
ON_PRIMARY_OPT = cli_option("-p", "--on-primary", dest="on_primary",
878
                            default=False, action="store_true",
879
                            help="Replace the disk(s) on the primary"
880
                                 " node (applies only to internally mirrored"
881
                                 " disk templates, e.g. %s)" %
882
                                 utils.CommaJoin(constants.DTS_INT_MIRROR))
883

    
884
ON_SECONDARY_OPT = cli_option("-s", "--on-secondary", dest="on_secondary",
885
                              default=False, action="store_true",
886
                              help="Replace the disk(s) on the secondary"
887
                                   " node (applies only to internally mirrored"
888
                                   " disk templates, e.g. %s)" %
889
                                   utils.CommaJoin(constants.DTS_INT_MIRROR))
890

    
891
AUTO_PROMOTE_OPT = cli_option("--auto-promote", dest="auto_promote",
892
                              default=False, action="store_true",
893
                              help="Lock all nodes and auto-promote as needed"
894
                              " to MC status")
895

    
896
AUTO_REPLACE_OPT = cli_option("-a", "--auto", dest="auto",
897
                              default=False, action="store_true",
898
                              help="Automatically replace faulty disks"
899
                                   " (applies only to internally mirrored"
900
                                   " disk templates, e.g. %s)" %
901
                                   utils.CommaJoin(constants.DTS_INT_MIRROR))
902

    
903
IGNORE_SIZE_OPT = cli_option("--ignore-size", dest="ignore_size",
904
                             default=False, action="store_true",
905
                             help="Ignore current recorded size"
906
                             " (useful for forcing activation when"
907
                             " the recorded size is wrong)")
908

    
909
SRC_NODE_OPT = cli_option("--src-node", dest="src_node", help="Source node",
910
                          metavar="<node>",
911
                          completion_suggest=OPT_COMPL_ONE_NODE)
912

    
913
SRC_DIR_OPT = cli_option("--src-dir", dest="src_dir", help="Source directory",
914
                         metavar="<dir>")
915

    
916
SECONDARY_IP_OPT = cli_option("-s", "--secondary-ip", dest="secondary_ip",
917
                              help="Specify the secondary ip for the node",
918
                              metavar="ADDRESS", default=None)
919

    
920
READD_OPT = cli_option("--readd", dest="readd",
921
                       default=False, action="store_true",
922
                       help="Readd old node after replacing it")
923

    
924
NOSSH_KEYCHECK_OPT = cli_option("--no-ssh-key-check", dest="ssh_key_check",
925
                                default=True, action="store_false",
926
                                help="Disable SSH key fingerprint checking")
927

    
928
NODE_FORCE_JOIN_OPT = cli_option("--force-join", dest="force_join",
929
                                 default=False, action="store_true",
930
                                 help="Force the joining of a node")
931

    
932
MC_OPT = cli_option("-C", "--master-candidate", dest="master_candidate",
933
                    type="bool", default=None, metavar=_YORNO,
934
                    help="Set the master_candidate flag on the node")
935

    
936
OFFLINE_OPT = cli_option("-O", "--offline", dest="offline", metavar=_YORNO,
937
                         type="bool", default=None,
938
                         help=("Set the offline flag on the node"
939
                               " (cluster does not communicate with offline"
940
                               " nodes)"))
941

    
942
DRAINED_OPT = cli_option("-D", "--drained", dest="drained", metavar=_YORNO,
943
                         type="bool", default=None,
944
                         help=("Set the drained flag on the node"
945
                               " (excluded from allocation operations)"))
946

    
947
CAPAB_MASTER_OPT = cli_option("--master-capable", dest="master_capable",
948
                    type="bool", default=None, metavar=_YORNO,
949
                    help="Set the master_capable flag on the node")
950

    
951
CAPAB_VM_OPT = cli_option("--vm-capable", dest="vm_capable",
952
                    type="bool", default=None, metavar=_YORNO,
953
                    help="Set the vm_capable flag on the node")
954

    
955
ALLOCATABLE_OPT = cli_option("--allocatable", dest="allocatable",
956
                             type="bool", default=None, metavar=_YORNO,
957
                             help="Set the allocatable flag on a volume")
958

    
959
NOLVM_STORAGE_OPT = cli_option("--no-lvm-storage", dest="lvm_storage",
960
                               help="Disable support for lvm based instances"
961
                               " (cluster-wide)",
962
                               action="store_false", default=True)
963

    
964
ENABLED_HV_OPT = cli_option("--enabled-hypervisors",
965
                            dest="enabled_hypervisors",
966
                            help="Comma-separated list of hypervisors",
967
                            type="string", default=None)
968

    
969
NIC_PARAMS_OPT = cli_option("-N", "--nic-parameters", dest="nicparams",
970
                            type="keyval", default={},
971
                            help="NIC parameters")
972

    
973
CP_SIZE_OPT = cli_option("-C", "--candidate-pool-size", default=None,
974
                         dest="candidate_pool_size", type="int",
975
                         help="Set the candidate pool size")
976

    
977
VG_NAME_OPT = cli_option("--vg-name", dest="vg_name",
978
                         help=("Enables LVM and specifies the volume group"
979
                               " name (cluster-wide) for disk allocation"
980
                               " [%s]" % constants.DEFAULT_VG),
981
                         metavar="VG", default=None)
982

    
983
YES_DOIT_OPT = cli_option("--yes-do-it", dest="yes_do_it",
984
                          help="Destroy cluster", action="store_true")
985

    
986
NOVOTING_OPT = cli_option("--no-voting", dest="no_voting",
987
                          help="Skip node agreement check (dangerous)",
988
                          action="store_true", default=False)
989

    
990
MAC_PREFIX_OPT = cli_option("-m", "--mac-prefix", dest="mac_prefix",
991
                            help="Specify the mac prefix for the instance IP"
992
                            " addresses, in the format XX:XX:XX",
993
                            metavar="PREFIX",
994
                            default=None)
995

    
996
MASTER_NETDEV_OPT = cli_option("--master-netdev", dest="master_netdev",
997
                               help="Specify the node interface (cluster-wide)"
998
                               " on which the master IP address will be added"
999
                               " (cluster init default: %s)" %
1000
                               constants.DEFAULT_BRIDGE,
1001
                               metavar="NETDEV",
1002
                               default=None)
1003

    
1004
GLOBAL_FILEDIR_OPT = cli_option("--file-storage-dir", dest="file_storage_dir",
1005
                                help="Specify the default directory (cluster-"
1006
                                "wide) for storing the file-based disks [%s]" %
1007
                                constants.DEFAULT_FILE_STORAGE_DIR,
1008
                                metavar="DIR",
1009
                                default=constants.DEFAULT_FILE_STORAGE_DIR)
1010

    
1011
GLOBAL_SHARED_FILEDIR_OPT = cli_option("--shared-file-storage-dir",
1012
                            dest="shared_file_storage_dir",
1013
                            help="Specify the default directory (cluster-"
1014
                            "wide) for storing the shared file-based"
1015
                            " disks [%s]" %
1016
                            constants.DEFAULT_SHARED_FILE_STORAGE_DIR,
1017
                            metavar="SHAREDDIR",
1018
                            default=constants.DEFAULT_SHARED_FILE_STORAGE_DIR)
1019

    
1020
NOMODIFY_ETCHOSTS_OPT = cli_option("--no-etc-hosts", dest="modify_etc_hosts",
1021
                                   help="Don't modify /etc/hosts",
1022
                                   action="store_false", default=True)
1023

    
1024
NOMODIFY_SSH_SETUP_OPT = cli_option("--no-ssh-init", dest="modify_ssh_setup",
1025
                                    help="Don't initialize SSH keys",
1026
                                    action="store_false", default=True)
1027

    
1028
ERROR_CODES_OPT = cli_option("--error-codes", dest="error_codes",
1029
                             help="Enable parseable error messages",
1030
                             action="store_true", default=False)
1031

    
1032
NONPLUS1_OPT = cli_option("--no-nplus1-mem", dest="skip_nplusone_mem",
1033
                          help="Skip N+1 memory redundancy tests",
1034
                          action="store_true", default=False)
1035

    
1036
REBOOT_TYPE_OPT = cli_option("-t", "--type", dest="reboot_type",
1037
                             help="Type of reboot: soft/hard/full",
1038
                             default=constants.INSTANCE_REBOOT_HARD,
1039
                             metavar="<REBOOT>",
1040
                             choices=list(constants.REBOOT_TYPES))
1041

    
1042
IGNORE_SECONDARIES_OPT = cli_option("--ignore-secondaries",
1043
                                    dest="ignore_secondaries",
1044
                                    default=False, action="store_true",
1045
                                    help="Ignore errors from secondaries")
1046

    
1047
NOSHUTDOWN_OPT = cli_option("--noshutdown", dest="shutdown",
1048
                            action="store_false", default=True,
1049
                            help="Don't shutdown the instance (unsafe)")
1050

    
1051
TIMEOUT_OPT = cli_option("--timeout", dest="timeout", type="int",
1052
                         default=constants.DEFAULT_SHUTDOWN_TIMEOUT,
1053
                         help="Maximum time to wait")
1054

    
1055
SHUTDOWN_TIMEOUT_OPT = cli_option("--shutdown-timeout",
1056
                         dest="shutdown_timeout", type="int",
1057
                         default=constants.DEFAULT_SHUTDOWN_TIMEOUT,
1058
                         help="Maximum time to wait for instance shutdown")
1059

    
1060
INTERVAL_OPT = cli_option("--interval", dest="interval", type="int",
1061
                          default=None,
1062
                          help=("Number of seconds between repetions of the"
1063
                                " command"))
1064

    
1065
EARLY_RELEASE_OPT = cli_option("--early-release",
1066
                               dest="early_release", default=False,
1067
                               action="store_true",
1068
                               help="Release the locks on the secondary"
1069
                               " node(s) early")
1070

    
1071
NEW_CLUSTER_CERT_OPT = cli_option("--new-cluster-certificate",
1072
                                  dest="new_cluster_cert",
1073
                                  default=False, action="store_true",
1074
                                  help="Generate a new cluster certificate")
1075

    
1076
RAPI_CERT_OPT = cli_option("--rapi-certificate", dest="rapi_cert",
1077
                           default=None,
1078
                           help="File containing new RAPI certificate")
1079

    
1080
NEW_RAPI_CERT_OPT = cli_option("--new-rapi-certificate", dest="new_rapi_cert",
1081
                               default=None, action="store_true",
1082
                               help=("Generate a new self-signed RAPI"
1083
                                     " certificate"))
1084

    
1085
NEW_CONFD_HMAC_KEY_OPT = cli_option("--new-confd-hmac-key",
1086
                                    dest="new_confd_hmac_key",
1087
                                    default=False, action="store_true",
1088
                                    help=("Create a new HMAC key for %s" %
1089
                                          constants.CONFD))
1090

    
1091
CLUSTER_DOMAIN_SECRET_OPT = cli_option("--cluster-domain-secret",
1092
                                       dest="cluster_domain_secret",
1093
                                       default=None,
1094
                                       help=("Load new new cluster domain"
1095
                                             " secret from file"))
1096

    
1097
NEW_CLUSTER_DOMAIN_SECRET_OPT = cli_option("--new-cluster-domain-secret",
1098
                                           dest="new_cluster_domain_secret",
1099
                                           default=False, action="store_true",
1100
                                           help=("Create a new cluster domain"
1101
                                                 " secret"))
1102

    
1103
USE_REPL_NET_OPT = cli_option("--use-replication-network",
1104
                              dest="use_replication_network",
1105
                              help="Whether to use the replication network"
1106
                              " for talking to the nodes",
1107
                              action="store_true", default=False)
1108

    
1109
MAINTAIN_NODE_HEALTH_OPT = \
1110
    cli_option("--maintain-node-health", dest="maintain_node_health",
1111
               metavar=_YORNO, default=None, type="bool",
1112
               help="Configure the cluster to automatically maintain node"
1113
               " health, by shutting down unknown instances, shutting down"
1114
               " unknown DRBD devices, etc.")
1115

    
1116
IDENTIFY_DEFAULTS_OPT = \
1117
    cli_option("--identify-defaults", dest="identify_defaults",
1118
               default=False, action="store_true",
1119
               help="Identify which saved instance parameters are equal to"
1120
               " the current cluster defaults and set them as such, instead"
1121
               " of marking them as overridden")
1122

    
1123
UIDPOOL_OPT = cli_option("--uid-pool", default=None,
1124
                         action="store", dest="uid_pool",
1125
                         help=("A list of user-ids or user-id"
1126
                               " ranges separated by commas"))
1127

    
1128
ADD_UIDS_OPT = cli_option("--add-uids", default=None,
1129
                          action="store", dest="add_uids",
1130
                          help=("A list of user-ids or user-id"
1131
                                " ranges separated by commas, to be"
1132
                                " added to the user-id pool"))
1133

    
1134
REMOVE_UIDS_OPT = cli_option("--remove-uids", default=None,
1135
                             action="store", dest="remove_uids",
1136
                             help=("A list of user-ids or user-id"
1137
                                   " ranges separated by commas, to be"
1138
                                   " removed from the user-id pool"))
1139

    
1140
RESERVED_LVS_OPT = cli_option("--reserved-lvs", default=None,
1141
                             action="store", dest="reserved_lvs",
1142
                             help=("A comma-separated list of reserved"
1143
                                   " logical volumes names, that will be"
1144
                                   " ignored by cluster verify"))
1145

    
1146
ROMAN_OPT = cli_option("--roman",
1147
                       dest="roman_integers", default=False,
1148
                       action="store_true",
1149
                       help="Use roman numbers for positive integers")
1150

    
1151
DRBD_HELPER_OPT = cli_option("--drbd-usermode-helper", dest="drbd_helper",
1152
                             action="store", default=None,
1153
                             help="Specifies usermode helper for DRBD")
1154

    
1155
NODRBD_STORAGE_OPT = cli_option("--no-drbd-storage", dest="drbd_storage",
1156
                                action="store_false", default=True,
1157
                                help="Disable support for DRBD")
1158

    
1159
PRIMARY_IP_VERSION_OPT = \
1160
    cli_option("--primary-ip-version", default=constants.IP4_VERSION,
1161
               action="store", dest="primary_ip_version",
1162
               metavar="%d|%d" % (constants.IP4_VERSION,
1163
                                  constants.IP6_VERSION),
1164
               help="Cluster-wide IP version for primary IP")
1165

    
1166
PRIORITY_OPT = cli_option("--priority", default=None, dest="priority",
1167
                          metavar="|".join(name for name, _ in _PRIORITY_NAMES),
1168
                          choices=_PRIONAME_TO_VALUE.keys(),
1169
                          help="Priority for opcode processing")
1170

    
1171
HID_OS_OPT = cli_option("--hidden", dest="hidden",
1172
                        type="bool", default=None, metavar=_YORNO,
1173
                        help="Sets the hidden flag on the OS")
1174

    
1175
BLK_OS_OPT = cli_option("--blacklisted", dest="blacklisted",
1176
                        type="bool", default=None, metavar=_YORNO,
1177
                        help="Sets the blacklisted flag on the OS")
1178

    
1179
PREALLOC_WIPE_DISKS_OPT = cli_option("--prealloc-wipe-disks", default=None,
1180
                                     type="bool", metavar=_YORNO,
1181
                                     dest="prealloc_wipe_disks",
1182
                                     help=("Wipe disks prior to instance"
1183
                                           " creation"))
1184

    
1185
NODE_PARAMS_OPT = cli_option("--node-parameters", dest="ndparams",
1186
                             type="keyval", default=None,
1187
                             help="Node parameters")
1188

    
1189
ALLOC_POLICY_OPT = cli_option("--alloc-policy", dest="alloc_policy",
1190
                              action="store", metavar="POLICY", default=None,
1191
                              help="Allocation policy for the node group")
1192

    
1193
NODE_POWERED_OPT = cli_option("--node-powered", default=None,
1194
                              type="bool", metavar=_YORNO,
1195
                              dest="node_powered",
1196
                              help="Specify if the SoR for node is powered")
1197

    
1198
OOB_TIMEOUT_OPT = cli_option("--oob-timeout", dest="oob_timeout", type="int",
1199
                         default=constants.OOB_TIMEOUT,
1200
                         help="Maximum time to wait for out-of-band helper")
1201

    
1202
POWER_DELAY_OPT = cli_option("--power-delay", dest="power_delay", type="float",
1203
                             default=constants.OOB_POWER_DELAY,
1204
                             help="Time in seconds to wait between power-ons")
1205

    
1206
FORCE_FILTER_OPT = cli_option("-F", "--filter", dest="force_filter",
1207
                              action="store_true", default=False,
1208
                              help=("Whether command argument should be treated"
1209
                                    " as filter"))
1210

    
1211
NO_REMEMBER_OPT = cli_option("--no-remember",
1212
                             dest="no_remember",
1213
                             action="store_true", default=False,
1214
                             help="Perform but do not record the change"
1215
                             " in the configuration")
1216

    
1217
PRIMARY_ONLY_OPT = cli_option("-p", "--primary-only",
1218
                              default=False, action="store_true",
1219
                              help="Evacuate primary instances only")
1220

    
1221
SECONDARY_ONLY_OPT = cli_option("-s", "--secondary-only",
1222
                                default=False, action="store_true",
1223
                                help="Evacuate secondary instances only"
1224
                                     " (applies only to internally mirrored"
1225
                                     " disk templates, e.g. %s)" %
1226
                                     utils.CommaJoin(constants.DTS_INT_MIRROR))
1227

    
1228
STARTUP_PAUSED_OPT = cli_option("--paused", dest="startup_paused",
1229
                                action="store_true", default=False,
1230
                                help="Pause instance at startup")
1231

    
1232
TO_GROUP_OPT = cli_option("--to", dest="to", metavar="<group>",
1233
                          help="Destination node group (name or uuid)",
1234
                          default=None, action="append",
1235
                          completion_suggest=OPT_COMPL_ONE_NODEGROUP)
1236

    
1237

    
1238
#: Options provided by all commands
1239
COMMON_OPTS = [DEBUG_OPT]
1240

    
1241
# common options for creating instances. add and import then add their own
1242
# specific ones.
1243
COMMON_CREATE_OPTS = [
1244
  BACKEND_OPT,
1245
  DISK_OPT,
1246
  DISK_TEMPLATE_OPT,
1247
  FILESTORE_DIR_OPT,
1248
  FILESTORE_DRIVER_OPT,
1249
  HYPERVISOR_OPT,
1250
  IALLOCATOR_OPT,
1251
  NET_OPT,
1252
  NODE_PLACEMENT_OPT,
1253
  NOIPCHECK_OPT,
1254
  NONAMECHECK_OPT,
1255
  NONICS_OPT,
1256
  NWSYNC_OPT,
1257
  OSPARAMS_OPT,
1258
  OS_SIZE_OPT,
1259
  SUBMIT_OPT,
1260
  TAG_ADD_OPT,
1261
  DRY_RUN_OPT,
1262
  PRIORITY_OPT,
1263
  ]
1264

    
1265

    
1266
def _ParseArgs(argv, commands, aliases):
1267
  """Parser for the command line arguments.
1268

1269
  This function parses the arguments and returns the function which
1270
  must be executed together with its (modified) arguments.
1271

1272
  @param argv: the command line
1273
  @param commands: dictionary with special contents, see the design
1274
      doc for cmdline handling
1275
  @param aliases: dictionary with command aliases {'alias': 'target, ...}
1276

1277
  """
1278
  if len(argv) == 0:
1279
    binary = "<command>"
1280
  else:
1281
    binary = argv[0].split("/")[-1]
1282

    
1283
  if len(argv) > 1 and argv[1] == "--version":
1284
    ToStdout("%s (ganeti %s) %s", binary, constants.VCS_VERSION,
1285
             constants.RELEASE_VERSION)
1286
    # Quit right away. That way we don't have to care about this special
1287
    # argument. optparse.py does it the same.
1288
    sys.exit(0)
1289

    
1290
  if len(argv) < 2 or not (argv[1] in commands or
1291
                           argv[1] in aliases):
1292
    # let's do a nice thing
1293
    sortedcmds = commands.keys()
1294
    sortedcmds.sort()
1295

    
1296
    ToStdout("Usage: %s {command} [options...] [argument...]", binary)
1297
    ToStdout("%s <command> --help to see details, or man %s", binary, binary)
1298
    ToStdout("")
1299

    
1300
    # compute the max line length for cmd + usage
1301
    mlen = max([len(" %s" % cmd) for cmd in commands])
1302
    mlen = min(60, mlen) # should not get here...
1303

    
1304
    # and format a nice command list
1305
    ToStdout("Commands:")
1306
    for cmd in sortedcmds:
1307
      cmdstr = " %s" % (cmd,)
1308
      help_text = commands[cmd][4]
1309
      help_lines = textwrap.wrap(help_text, 79 - 3 - mlen)
1310
      ToStdout("%-*s - %s", mlen, cmdstr, help_lines.pop(0))
1311
      for line in help_lines:
1312
        ToStdout("%-*s   %s", mlen, "", line)
1313

    
1314
    ToStdout("")
1315

    
1316
    return None, None, None
1317

    
1318
  # get command, unalias it, and look it up in commands
1319
  cmd = argv.pop(1)
1320
  if cmd in aliases:
1321
    if cmd in commands:
1322
      raise errors.ProgrammerError("Alias '%s' overrides an existing"
1323
                                   " command" % cmd)
1324

    
1325
    if aliases[cmd] not in commands:
1326
      raise errors.ProgrammerError("Alias '%s' maps to non-existing"
1327
                                   " command '%s'" % (cmd, aliases[cmd]))
1328

    
1329
    cmd = aliases[cmd]
1330

    
1331
  func, args_def, parser_opts, usage, description = commands[cmd]
1332
  parser = OptionParser(option_list=parser_opts + COMMON_OPTS,
1333
                        description=description,
1334
                        formatter=TitledHelpFormatter(),
1335
                        usage="%%prog %s %s" % (cmd, usage))
1336
  parser.disable_interspersed_args()
1337
  options, args = parser.parse_args()
1338

    
1339
  if not _CheckArguments(cmd, args_def, args):
1340
    return None, None, None
1341

    
1342
  return func, options, args
1343

    
1344

    
1345
def _CheckArguments(cmd, args_def, args):
1346
  """Verifies the arguments using the argument definition.
1347

1348
  Algorithm:
1349

1350
    1. Abort with error if values specified by user but none expected.
1351

1352
    1. For each argument in definition
1353

1354
      1. Keep running count of minimum number of values (min_count)
1355
      1. Keep running count of maximum number of values (max_count)
1356
      1. If it has an unlimited number of values
1357

1358
        1. Abort with error if it's not the last argument in the definition
1359

1360
    1. If last argument has limited number of values
1361

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

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

1366
  """
1367
  if args and not args_def:
1368
    ToStderr("Error: Command %s expects no arguments", cmd)
1369
    return False
1370

    
1371
  min_count = None
1372
  max_count = None
1373
  check_max = None
1374

    
1375
  last_idx = len(args_def) - 1
1376

    
1377
  for idx, arg in enumerate(args_def):
1378
    if min_count is None:
1379
      min_count = arg.min
1380
    elif arg.min is not None:
1381
      min_count += arg.min
1382

    
1383
    if max_count is None:
1384
      max_count = arg.max
1385
    elif arg.max is not None:
1386
      max_count += arg.max
1387

    
1388
    if idx == last_idx:
1389
      check_max = (arg.max is not None)
1390

    
1391
    elif arg.max is None:
1392
      raise errors.ProgrammerError("Only the last argument can have max=None")
1393

    
1394
  if check_max:
1395
    # Command with exact number of arguments
1396
    if (min_count is not None and max_count is not None and
1397
        min_count == max_count and len(args) != min_count):
1398
      ToStderr("Error: Command %s expects %d argument(s)", cmd, min_count)
1399
      return False
1400

    
1401
    # Command with limited number of arguments
1402
    if max_count is not None and len(args) > max_count:
1403
      ToStderr("Error: Command %s expects only %d argument(s)",
1404
               cmd, max_count)
1405
      return False
1406

    
1407
  # Command with some required arguments
1408
  if min_count is not None and len(args) < min_count:
1409
    ToStderr("Error: Command %s expects at least %d argument(s)",
1410
             cmd, min_count)
1411
    return False
1412

    
1413
  return True
1414

    
1415

    
1416
def SplitNodeOption(value):
1417
  """Splits the value of a --node option.
1418

1419
  """
1420
  if value and ':' in value:
1421
    return value.split(':', 1)
1422
  else:
1423
    return (value, None)
1424

    
1425

    
1426
def CalculateOSNames(os_name, os_variants):
1427
  """Calculates all the names an OS can be called, according to its variants.
1428

1429
  @type os_name: string
1430
  @param os_name: base name of the os
1431
  @type os_variants: list or None
1432
  @param os_variants: list of supported variants
1433
  @rtype: list
1434
  @return: list of valid names
1435

1436
  """
1437
  if os_variants:
1438
    return ['%s+%s' % (os_name, v) for v in os_variants]
1439
  else:
1440
    return [os_name]
1441

    
1442

    
1443
def ParseFields(selected, default):
1444
  """Parses the values of "--field"-like options.
1445

1446
  @type selected: string or None
1447
  @param selected: User-selected options
1448
  @type default: list
1449
  @param default: Default fields
1450

1451
  """
1452
  if selected is None:
1453
    return default
1454

    
1455
  if selected.startswith("+"):
1456
    return default + selected[1:].split(",")
1457

    
1458
  return selected.split(",")
1459

    
1460

    
1461
UsesRPC = rpc.RunWithRPC
1462

    
1463

    
1464
def AskUser(text, choices=None):
1465
  """Ask the user a question.
1466

1467
  @param text: the question to ask
1468

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

1474
  @return: one of the return values from the choices list; if input is
1475
      not possible (i.e. not running with a tty, we return the last
1476
      entry from the list
1477

1478
  """
1479
  if choices is None:
1480
    choices = [('y', True, 'Perform the operation'),
1481
               ('n', False, 'Do not perform the operation')]
1482
  if not choices or not isinstance(choices, list):
1483
    raise errors.ProgrammerError("Invalid choices argument to AskUser")
1484
  for entry in choices:
1485
    if not isinstance(entry, tuple) or len(entry) < 3 or entry[0] == '?':
1486
      raise errors.ProgrammerError("Invalid choices element to AskUser")
1487

    
1488
  answer = choices[-1][1]
1489
  new_text = []
1490
  for line in text.splitlines():
1491
    new_text.append(textwrap.fill(line, 70, replace_whitespace=False))
1492
  text = "\n".join(new_text)
1493
  try:
1494
    f = file("/dev/tty", "a+")
1495
  except IOError:
1496
    return answer
1497
  try:
1498
    chars = [entry[0] for entry in choices]
1499
    chars[-1] = "[%s]" % chars[-1]
1500
    chars.append('?')
1501
    maps = dict([(entry[0], entry[1]) for entry in choices])
1502
    while True:
1503
      f.write(text)
1504
      f.write('\n')
1505
      f.write("/".join(chars))
1506
      f.write(": ")
1507
      line = f.readline(2).strip().lower()
1508
      if line in maps:
1509
        answer = maps[line]
1510
        break
1511
      elif line == '?':
1512
        for entry in choices:
1513
          f.write(" %s - %s\n" % (entry[0], entry[2]))
1514
        f.write("\n")
1515
        continue
1516
  finally:
1517
    f.close()
1518
  return answer
1519

    
1520

    
1521
class JobSubmittedException(Exception):
1522
  """Job was submitted, client should exit.
1523

1524
  This exception has one argument, the ID of the job that was
1525
  submitted. The handler should print this ID.
1526

1527
  This is not an error, just a structured way to exit from clients.
1528

1529
  """
1530

    
1531

    
1532
def SendJob(ops, cl=None):
1533
  """Function to submit an opcode without waiting for the results.
1534

1535
  @type ops: list
1536
  @param ops: list of opcodes
1537
  @type cl: luxi.Client
1538
  @param cl: the luxi client to use for communicating with the master;
1539
             if None, a new client will be created
1540

1541
  """
1542
  if cl is None:
1543
    cl = GetClient()
1544

    
1545
  job_id = cl.SubmitJob(ops)
1546

    
1547
  return job_id
1548

    
1549

    
1550
def GenericPollJob(job_id, cbs, report_cbs):
1551
  """Generic job-polling function.
1552

1553
  @type job_id: number
1554
  @param job_id: Job ID
1555
  @type cbs: Instance of L{JobPollCbBase}
1556
  @param cbs: Data callbacks
1557
  @type report_cbs: Instance of L{JobPollReportCbBase}
1558
  @param report_cbs: Reporting callbacks
1559

1560
  """
1561
  prev_job_info = None
1562
  prev_logmsg_serial = None
1563

    
1564
  status = None
1565

    
1566
  while True:
1567
    result = cbs.WaitForJobChangeOnce(job_id, ["status"], prev_job_info,
1568
                                      prev_logmsg_serial)
1569
    if not result:
1570
      # job not found, go away!
1571
      raise errors.JobLost("Job with id %s lost" % job_id)
1572

    
1573
    if result == constants.JOB_NOTCHANGED:
1574
      report_cbs.ReportNotChanged(job_id, status)
1575

    
1576
      # Wait again
1577
      continue
1578

    
1579
    # Split result, a tuple of (field values, log entries)
1580
    (job_info, log_entries) = result
1581
    (status, ) = job_info
1582

    
1583
    if log_entries:
1584
      for log_entry in log_entries:
1585
        (serial, timestamp, log_type, message) = log_entry
1586
        report_cbs.ReportLogMessage(job_id, serial, timestamp,
1587
                                    log_type, message)
1588
        prev_logmsg_serial = max(prev_logmsg_serial, serial)
1589

    
1590
    # TODO: Handle canceled and archived jobs
1591
    elif status in (constants.JOB_STATUS_SUCCESS,
1592
                    constants.JOB_STATUS_ERROR,
1593
                    constants.JOB_STATUS_CANCELING,
1594
                    constants.JOB_STATUS_CANCELED):
1595
      break
1596

    
1597
    prev_job_info = job_info
1598

    
1599
  jobs = cbs.QueryJobs([job_id], ["status", "opstatus", "opresult"])
1600
  if not jobs:
1601
    raise errors.JobLost("Job with id %s lost" % job_id)
1602

    
1603
  status, opstatus, result = jobs[0]
1604

    
1605
  if status == constants.JOB_STATUS_SUCCESS:
1606
    return result
1607

    
1608
  if status in (constants.JOB_STATUS_CANCELING, constants.JOB_STATUS_CANCELED):
1609
    raise errors.OpExecError("Job was canceled")
1610

    
1611
  has_ok = False
1612
  for idx, (status, msg) in enumerate(zip(opstatus, result)):
1613
    if status == constants.OP_STATUS_SUCCESS:
1614
      has_ok = True
1615
    elif status == constants.OP_STATUS_ERROR:
1616
      errors.MaybeRaise(msg)
1617

    
1618
      if has_ok:
1619
        raise errors.OpExecError("partial failure (opcode %d): %s" %
1620
                                 (idx, msg))
1621

    
1622
      raise errors.OpExecError(str(msg))
1623

    
1624
  # default failure mode
1625
  raise errors.OpExecError(result)
1626

    
1627

    
1628
class JobPollCbBase:
1629
  """Base class for L{GenericPollJob} callbacks.
1630

1631
  """
1632
  def __init__(self):
1633
    """Initializes this class.
1634

1635
    """
1636

    
1637
  def WaitForJobChangeOnce(self, job_id, fields,
1638
                           prev_job_info, prev_log_serial):
1639
    """Waits for changes on a job.
1640

1641
    """
1642
    raise NotImplementedError()
1643

    
1644
  def QueryJobs(self, job_ids, fields):
1645
    """Returns the selected fields for the selected job IDs.
1646

1647
    @type job_ids: list of numbers
1648
    @param job_ids: Job IDs
1649
    @type fields: list of strings
1650
    @param fields: Fields
1651

1652
    """
1653
    raise NotImplementedError()
1654

    
1655

    
1656
class JobPollReportCbBase:
1657
  """Base class for L{GenericPollJob} reporting callbacks.
1658

1659
  """
1660
  def __init__(self):
1661
    """Initializes this class.
1662

1663
    """
1664

    
1665
  def ReportLogMessage(self, job_id, serial, timestamp, log_type, log_msg):
1666
    """Handles a log message.
1667

1668
    """
1669
    raise NotImplementedError()
1670

    
1671
  def ReportNotChanged(self, job_id, status):
1672
    """Called for if a job hasn't changed in a while.
1673

1674
    @type job_id: number
1675
    @param job_id: Job ID
1676
    @type status: string or None
1677
    @param status: Job status if available
1678

1679
    """
1680
    raise NotImplementedError()
1681

    
1682

    
1683
class _LuxiJobPollCb(JobPollCbBase):
1684
  def __init__(self, cl):
1685
    """Initializes this class.
1686

1687
    """
1688
    JobPollCbBase.__init__(self)
1689
    self.cl = cl
1690

    
1691
  def WaitForJobChangeOnce(self, job_id, fields,
1692
                           prev_job_info, prev_log_serial):
1693
    """Waits for changes on a job.
1694

1695
    """
1696
    return self.cl.WaitForJobChangeOnce(job_id, fields,
1697
                                        prev_job_info, prev_log_serial)
1698

    
1699
  def QueryJobs(self, job_ids, fields):
1700
    """Returns the selected fields for the selected job IDs.
1701

1702
    """
1703
    return self.cl.QueryJobs(job_ids, fields)
1704

    
1705

    
1706
class FeedbackFnJobPollReportCb(JobPollReportCbBase):
1707
  def __init__(self, feedback_fn):
1708
    """Initializes this class.
1709

1710
    """
1711
    JobPollReportCbBase.__init__(self)
1712

    
1713
    self.feedback_fn = feedback_fn
1714

    
1715
    assert callable(feedback_fn)
1716

    
1717
  def ReportLogMessage(self, job_id, serial, timestamp, log_type, log_msg):
1718
    """Handles a log message.
1719

1720
    """
1721
    self.feedback_fn((timestamp, log_type, log_msg))
1722

    
1723
  def ReportNotChanged(self, job_id, status):
1724
    """Called if a job hasn't changed in a while.
1725

1726
    """
1727
    # Ignore
1728

    
1729

    
1730
class StdioJobPollReportCb(JobPollReportCbBase):
1731
  def __init__(self):
1732
    """Initializes this class.
1733

1734
    """
1735
    JobPollReportCbBase.__init__(self)
1736

    
1737
    self.notified_queued = False
1738
    self.notified_waitlock = False
1739

    
1740
  def ReportLogMessage(self, job_id, serial, timestamp, log_type, log_msg):
1741
    """Handles a log message.
1742

1743
    """
1744
    ToStdout("%s %s", time.ctime(utils.MergeTime(timestamp)),
1745
             FormatLogMessage(log_type, log_msg))
1746

    
1747
  def ReportNotChanged(self, job_id, status):
1748
    """Called if a job hasn't changed in a while.
1749

1750
    """
1751
    if status is None:
1752
      return
1753

    
1754
    if status == constants.JOB_STATUS_QUEUED and not self.notified_queued:
1755
      ToStderr("Job %s is waiting in queue", job_id)
1756
      self.notified_queued = True
1757

    
1758
    elif status == constants.JOB_STATUS_WAITING and not self.notified_waitlock:
1759
      ToStderr("Job %s is trying to acquire all necessary locks", job_id)
1760
      self.notified_waitlock = True
1761

    
1762

    
1763
def FormatLogMessage(log_type, log_msg):
1764
  """Formats a job message according to its type.
1765

1766
  """
1767
  if log_type != constants.ELOG_MESSAGE:
1768
    log_msg = str(log_msg)
1769

    
1770
  return utils.SafeEncode(log_msg)
1771

    
1772

    
1773
def PollJob(job_id, cl=None, feedback_fn=None, reporter=None):
1774
  """Function to poll for the result of a job.
1775

1776
  @type job_id: job identified
1777
  @param job_id: the job to poll for results
1778
  @type cl: luxi.Client
1779
  @param cl: the luxi client to use for communicating with the master;
1780
             if None, a new client will be created
1781

1782
  """
1783
  if cl is None:
1784
    cl = GetClient()
1785

    
1786
  if reporter is None:
1787
    if feedback_fn:
1788
      reporter = FeedbackFnJobPollReportCb(feedback_fn)
1789
    else:
1790
      reporter = StdioJobPollReportCb()
1791
  elif feedback_fn:
1792
    raise errors.ProgrammerError("Can't specify reporter and feedback function")
1793

    
1794
  return GenericPollJob(job_id, _LuxiJobPollCb(cl), reporter)
1795

    
1796

    
1797
def SubmitOpCode(op, cl=None, feedback_fn=None, opts=None, reporter=None):
1798
  """Legacy function to submit an opcode.
1799

1800
  This is just a simple wrapper over the construction of the processor
1801
  instance. It should be extended to better handle feedback and
1802
  interaction functions.
1803

1804
  """
1805
  if cl is None:
1806
    cl = GetClient()
1807

    
1808
  SetGenericOpcodeOpts([op], opts)
1809

    
1810
  job_id = SendJob([op], cl=cl)
1811

    
1812
  op_results = PollJob(job_id, cl=cl, feedback_fn=feedback_fn,
1813
                       reporter=reporter)
1814

    
1815
  return op_results[0]
1816

    
1817

    
1818
def SubmitOrSend(op, opts, cl=None, feedback_fn=None):
1819
  """Wrapper around SubmitOpCode or SendJob.
1820

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

1826
  It will also process the opcodes if we're sending the via SendJob
1827
  (otherwise SubmitOpCode does it).
1828

1829
  """
1830
  if opts and opts.submit_only:
1831
    job = [op]
1832
    SetGenericOpcodeOpts(job, opts)
1833
    job_id = SendJob(job, cl=cl)
1834
    raise JobSubmittedException(job_id)
1835
  else:
1836
    return SubmitOpCode(op, cl=cl, feedback_fn=feedback_fn, opts=opts)
1837

    
1838

    
1839
def SetGenericOpcodeOpts(opcode_list, options):
1840
  """Processor for generic options.
1841

1842
  This function updates the given opcodes based on generic command
1843
  line options (like debug, dry-run, etc.).
1844

1845
  @param opcode_list: list of opcodes
1846
  @param options: command line options or None
1847
  @return: None (in-place modification)
1848

1849
  """
1850
  if not options:
1851
    return
1852
  for op in opcode_list:
1853
    op.debug_level = options.debug
1854
    if hasattr(options, "dry_run"):
1855
      op.dry_run = options.dry_run
1856
    if getattr(options, "priority", None) is not None:
1857
      op.priority = _PRIONAME_TO_VALUE[options.priority]
1858

    
1859

    
1860
def GetClient():
1861
  # TODO: Cache object?
1862
  try:
1863
    client = luxi.Client()
1864
  except luxi.NoMasterError:
1865
    ss = ssconf.SimpleStore()
1866

    
1867
    # Try to read ssconf file
1868
    try:
1869
      ss.GetMasterNode()
1870
    except errors.ConfigurationError:
1871
      raise errors.OpPrereqError("Cluster not initialized or this machine is"
1872
                                 " not part of a cluster")
1873

    
1874
    master, myself = ssconf.GetMasterAndMyself(ss=ss)
1875
    if master != myself:
1876
      raise errors.OpPrereqError("This is not the master node, please connect"
1877
                                 " to node '%s' and rerun the command" %
1878
                                 master)
1879
    raise
1880
  return client
1881

    
1882

    
1883
def FormatError(err):
1884
  """Return a formatted error message for a given error.
1885

1886
  This function takes an exception instance and returns a tuple
1887
  consisting of two values: first, the recommended exit code, and
1888
  second, a string describing the error message (not
1889
  newline-terminated).
1890

1891
  """
1892
  retcode = 1
1893
  obuf = StringIO()
1894
  msg = str(err)
1895
  if isinstance(err, errors.ConfigurationError):
1896
    txt = "Corrupt configuration file: %s" % msg
1897
    logging.error(txt)
1898
    obuf.write(txt + "\n")
1899
    obuf.write("Aborting.")
1900
    retcode = 2
1901
  elif isinstance(err, errors.HooksAbort):
1902
    obuf.write("Failure: hooks execution failed:\n")
1903
    for node, script, out in err.args[0]:
1904
      if out:
1905
        obuf.write("  node: %s, script: %s, output: %s\n" %
1906
                   (node, script, out))
1907
      else:
1908
        obuf.write("  node: %s, script: %s (no output)\n" %
1909
                   (node, script))
1910
  elif isinstance(err, errors.HooksFailure):
1911
    obuf.write("Failure: hooks general failure: %s" % msg)
1912
  elif isinstance(err, errors.ResolverError):
1913
    this_host = netutils.Hostname.GetSysName()
1914
    if err.args[0] == this_host:
1915
      msg = "Failure: can't resolve my own hostname ('%s')"
1916
    else:
1917
      msg = "Failure: can't resolve hostname '%s'"
1918
    obuf.write(msg % err.args[0])
1919
  elif isinstance(err, errors.OpPrereqError):
1920
    if len(err.args) == 2:
1921
      obuf.write("Failure: prerequisites not met for this"
1922
               " operation:\nerror type: %s, error details:\n%s" %
1923
                 (err.args[1], err.args[0]))
1924
    else:
1925
      obuf.write("Failure: prerequisites not met for this"
1926
                 " operation:\n%s" % msg)
1927
  elif isinstance(err, errors.OpExecError):
1928
    obuf.write("Failure: command execution error:\n%s" % msg)
1929
  elif isinstance(err, errors.TagError):
1930
    obuf.write("Failure: invalid tag(s) given:\n%s" % msg)
1931
  elif isinstance(err, errors.JobQueueDrainError):
1932
    obuf.write("Failure: the job queue is marked for drain and doesn't"
1933
               " accept new requests\n")
1934
  elif isinstance(err, errors.JobQueueFull):
1935
    obuf.write("Failure: the job queue is full and doesn't accept new"
1936
               " job submissions until old jobs are archived\n")
1937
  elif isinstance(err, errors.TypeEnforcementError):
1938
    obuf.write("Parameter Error: %s" % msg)
1939
  elif isinstance(err, errors.ParameterError):
1940
    obuf.write("Failure: unknown/wrong parameter name '%s'" % msg)
1941
  elif isinstance(err, luxi.NoMasterError):
1942
    obuf.write("Cannot communicate with the master daemon.\nIs it running"
1943
               " and listening for connections?")
1944
  elif isinstance(err, luxi.TimeoutError):
1945
    obuf.write("Timeout while talking to the master daemon. Jobs might have"
1946
               " been submitted and will continue to run even if the call"
1947
               " timed out. Useful commands in this situation are \"gnt-job"
1948
               " list\", \"gnt-job cancel\" and \"gnt-job watch\". Error:\n")
1949
    obuf.write(msg)
1950
  elif isinstance(err, luxi.PermissionError):
1951
    obuf.write("It seems you don't have permissions to connect to the"
1952
               " master daemon.\nPlease retry as a different user.")
1953
  elif isinstance(err, luxi.ProtocolError):
1954
    obuf.write("Unhandled protocol error while talking to the master daemon:\n"
1955
               "%s" % msg)
1956
  elif isinstance(err, errors.JobLost):
1957
    obuf.write("Error checking job status: %s" % msg)
1958
  elif isinstance(err, errors.QueryFilterParseError):
1959
    obuf.write("Error while parsing query filter: %s\n" % err.args[0])
1960
    obuf.write("\n".join(err.GetDetails()))
1961
  elif isinstance(err, errors.GenericError):
1962
    obuf.write("Unhandled Ganeti error: %s" % msg)
1963
  elif isinstance(err, JobSubmittedException):
1964
    obuf.write("JobID: %s\n" % err.args[0])
1965
    retcode = 0
1966
  else:
1967
    obuf.write("Unhandled exception: %s" % msg)
1968
  return retcode, obuf.getvalue().rstrip('\n')
1969

    
1970

    
1971
def GenericMain(commands, override=None, aliases=None):
1972
  """Generic main function for all the gnt-* commands.
1973

1974
  Arguments:
1975
    - commands: a dictionary with a special structure, see the design doc
1976
                for command line handling.
1977
    - override: if not None, we expect a dictionary with keys that will
1978
                override command line options; this can be used to pass
1979
                options from the scripts to generic functions
1980
    - aliases: dictionary with command aliases {'alias': 'target, ...}
1981

1982
  """
1983
  # save the program name and the entire command line for later logging
1984
  if sys.argv:
1985
    binary = os.path.basename(sys.argv[0]) or sys.argv[0]
1986
    if len(sys.argv) >= 2:
1987
      binary += " " + sys.argv[1]
1988
      old_cmdline = " ".join(sys.argv[2:])
1989
    else:
1990
      old_cmdline = ""
1991
  else:
1992
    binary = "<unknown program>"
1993
    old_cmdline = ""
1994

    
1995
  if aliases is None:
1996
    aliases = {}
1997

    
1998
  try:
1999
    func, options, args = _ParseArgs(sys.argv, commands, aliases)
2000
  except errors.ParameterError, err:
2001
    result, err_msg = FormatError(err)
2002
    ToStderr(err_msg)
2003
    return 1
2004

    
2005
  if func is None: # parse error
2006
    return 1
2007

    
2008
  if override is not None:
2009
    for key, val in override.iteritems():
2010
      setattr(options, key, val)
2011

    
2012
  utils.SetupLogging(constants.LOG_COMMANDS, binary, debug=options.debug,
2013
                     stderr_logging=True)
2014

    
2015
  if old_cmdline:
2016
    logging.info("run with arguments '%s'", old_cmdline)
2017
  else:
2018
    logging.info("run with no arguments")
2019

    
2020
  try:
2021
    result = func(options, args)
2022
  except (errors.GenericError, luxi.ProtocolError,
2023
          JobSubmittedException), err:
2024
    result, err_msg = FormatError(err)
2025
    logging.exception("Error during command processing")
2026
    ToStderr(err_msg)
2027
  except KeyboardInterrupt:
2028
    result = constants.EXIT_FAILURE
2029
    ToStderr("Aborted. Note that if the operation created any jobs, they"
2030
             " might have been submitted and"
2031
             " will continue to run in the background.")
2032
  except IOError, err:
2033
    if err.errno == errno.EPIPE:
2034
      # our terminal went away, we'll exit
2035
      sys.exit(constants.EXIT_FAILURE)
2036
    else:
2037
      raise
2038

    
2039
  return result
2040

    
2041

    
2042
def ParseNicOption(optvalue):
2043
  """Parses the value of the --net option(s).
2044

2045
  """
2046
  try:
2047
    nic_max = max(int(nidx[0]) + 1 for nidx in optvalue)
2048
  except (TypeError, ValueError), err:
2049
    raise errors.OpPrereqError("Invalid NIC index passed: %s" % str(err))
2050

    
2051
  nics = [{}] * nic_max
2052
  for nidx, ndict in optvalue:
2053
    nidx = int(nidx)
2054

    
2055
    if not isinstance(ndict, dict):
2056
      raise errors.OpPrereqError("Invalid nic/%d value: expected dict,"
2057
                                 " got %s" % (nidx, ndict))
2058

    
2059
    utils.ForceDictType(ndict, constants.INIC_PARAMS_TYPES)
2060

    
2061
    nics[nidx] = ndict
2062

    
2063
  return nics
2064

    
2065

    
2066
def GenericInstanceCreate(mode, opts, args):
2067
  """Add an instance to the cluster via either creation or import.
2068

2069
  @param mode: constants.INSTANCE_CREATE or constants.INSTANCE_IMPORT
2070
  @param opts: the command line options selected by the user
2071
  @type args: list
2072
  @param args: should contain only one element, the new instance name
2073
  @rtype: int
2074
  @return: the desired exit code
2075

2076
  """
2077
  instance = args[0]
2078

    
2079
  (pnode, snode) = SplitNodeOption(opts.node)
2080

    
2081
  hypervisor = None
2082
  hvparams = {}
2083
  if opts.hypervisor:
2084
    hypervisor, hvparams = opts.hypervisor
2085

    
2086
  if opts.nics:
2087
    nics = ParseNicOption(opts.nics)
2088
  elif opts.no_nics:
2089
    # no nics
2090
    nics = []
2091
  elif mode == constants.INSTANCE_CREATE:
2092
    # default of one nic, all auto
2093
    nics = [{}]
2094
  else:
2095
    # mode == import
2096
    nics = []
2097

    
2098
  if opts.disk_template == constants.DT_DISKLESS:
2099
    if opts.disks or opts.sd_size is not None:
2100
      raise errors.OpPrereqError("Diskless instance but disk"
2101
                                 " information passed")
2102
    disks = []
2103
  else:
2104
    if (not opts.disks and not opts.sd_size
2105
        and mode == constants.INSTANCE_CREATE):
2106
      raise errors.OpPrereqError("No disk information specified")
2107
    if opts.disks and opts.sd_size is not None:
2108
      raise errors.OpPrereqError("Please use either the '--disk' or"
2109
                                 " '-s' option")
2110
    if opts.sd_size is not None:
2111
      opts.disks = [(0, {constants.IDISK_SIZE: opts.sd_size})]
2112

    
2113
    if opts.disks:
2114
      try:
2115
        disk_max = max(int(didx[0]) + 1 for didx in opts.disks)
2116
      except ValueError, err:
2117
        raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err))
2118
      disks = [{}] * disk_max
2119
    else:
2120
      disks = []
2121
    for didx, ddict in opts.disks:
2122
      didx = int(didx)
2123
      if not isinstance(ddict, dict):
2124
        msg = "Invalid disk/%d value: expected dict, got %s" % (didx, ddict)
2125
        raise errors.OpPrereqError(msg)
2126
      elif constants.IDISK_SIZE in ddict:
2127
        if constants.IDISK_ADOPT in ddict:
2128
          raise errors.OpPrereqError("Only one of 'size' and 'adopt' allowed"
2129
                                     " (disk %d)" % didx)
2130
        try:
2131
          ddict[constants.IDISK_SIZE] = \
2132
            utils.ParseUnit(ddict[constants.IDISK_SIZE])
2133
        except ValueError, err:
2134
          raise errors.OpPrereqError("Invalid disk size for disk %d: %s" %
2135
                                     (didx, err))
2136
      elif constants.IDISK_ADOPT in ddict:
2137
        if mode == constants.INSTANCE_IMPORT:
2138
          raise errors.OpPrereqError("Disk adoption not allowed for instance"
2139
                                     " import")
2140
        ddict[constants.IDISK_SIZE] = 0
2141
      else:
2142
        raise errors.OpPrereqError("Missing size or adoption source for"
2143
                                   " disk %d" % didx)
2144
      disks[didx] = ddict
2145

    
2146
  if opts.tags is not None:
2147
    tags = opts.tags.split(",")
2148
  else:
2149
    tags = []
2150

    
2151
  utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_TYPES)
2152
  utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
2153

    
2154
  if mode == constants.INSTANCE_CREATE:
2155
    start = opts.start
2156
    os_type = opts.os
2157
    force_variant = opts.force_variant
2158
    src_node = None
2159
    src_path = None
2160
    no_install = opts.no_install
2161
    identify_defaults = False
2162
  elif mode == constants.INSTANCE_IMPORT:
2163
    start = False
2164
    os_type = None
2165
    force_variant = False
2166
    src_node = opts.src_node
2167
    src_path = opts.src_dir
2168
    no_install = None
2169
    identify_defaults = opts.identify_defaults
2170
  else:
2171
    raise errors.ProgrammerError("Invalid creation mode %s" % mode)
2172

    
2173
  op = opcodes.OpInstanceCreate(instance_name=instance,
2174
                                disks=disks,
2175
                                disk_template=opts.disk_template,
2176
                                nics=nics,
2177
                                pnode=pnode, snode=snode,
2178
                                ip_check=opts.ip_check,
2179
                                name_check=opts.name_check,
2180
                                wait_for_sync=opts.wait_for_sync,
2181
                                file_storage_dir=opts.file_storage_dir,
2182
                                file_driver=opts.file_driver,
2183
                                iallocator=opts.iallocator,
2184
                                hypervisor=hypervisor,
2185
                                hvparams=hvparams,
2186
                                beparams=opts.beparams,
2187
                                osparams=opts.osparams,
2188
                                mode=mode,
2189
                                start=start,
2190
                                os_type=os_type,
2191
                                force_variant=force_variant,
2192
                                src_node=src_node,
2193
                                src_path=src_path,
2194
                                tags=tags,
2195
                                no_install=no_install,
2196
                                identify_defaults=identify_defaults)
2197

    
2198
  SubmitOrSend(op, opts)
2199
  return 0
2200

    
2201

    
2202
class _RunWhileClusterStoppedHelper:
2203
  """Helper class for L{RunWhileClusterStopped} to simplify state management
2204

2205
  """
2206
  def __init__(self, feedback_fn, cluster_name, master_node, online_nodes):
2207
    """Initializes this class.
2208

2209
    @type feedback_fn: callable
2210
    @param feedback_fn: Feedback function
2211
    @type cluster_name: string
2212
    @param cluster_name: Cluster name
2213
    @type master_node: string
2214
    @param master_node Master node name
2215
    @type online_nodes: list
2216
    @param online_nodes: List of names of online nodes
2217

2218
    """
2219
    self.feedback_fn = feedback_fn
2220
    self.cluster_name = cluster_name
2221
    self.master_node = master_node
2222
    self.online_nodes = online_nodes
2223

    
2224
    self.ssh = ssh.SshRunner(self.cluster_name)
2225

    
2226
    self.nonmaster_nodes = [name for name in online_nodes
2227
                            if name != master_node]
2228

    
2229
    assert self.master_node not in self.nonmaster_nodes
2230

    
2231
  def _RunCmd(self, node_name, cmd):
2232
    """Runs a command on the local or a remote machine.
2233

2234
    @type node_name: string
2235
    @param node_name: Machine name
2236
    @type cmd: list
2237
    @param cmd: Command
2238

2239
    """
2240
    if node_name is None or node_name == self.master_node:
2241
      # No need to use SSH
2242
      result = utils.RunCmd(cmd)
2243
    else:
2244
      result = self.ssh.Run(node_name, "root", utils.ShellQuoteArgs(cmd))
2245

    
2246
    if result.failed:
2247
      errmsg = ["Failed to run command %s" % result.cmd]
2248
      if node_name:
2249
        errmsg.append("on node %s" % node_name)
2250
      errmsg.append(": exitcode %s and error %s" %
2251
                    (result.exit_code, result.output))
2252
      raise errors.OpExecError(" ".join(errmsg))
2253

    
2254
  def Call(self, fn, *args):
2255
    """Call function while all daemons are stopped.
2256

2257
    @type fn: callable
2258
    @param fn: Function to be called
2259

2260
    """
2261
    # Pause watcher by acquiring an exclusive lock on watcher state file
2262
    self.feedback_fn("Blocking watcher")
2263
    watcher_block = utils.FileLock.Open(constants.WATCHER_STATEFILE)
2264
    try:
2265
      # TODO: Currently, this just blocks. There's no timeout.
2266
      # TODO: Should it be a shared lock?
2267
      watcher_block.Exclusive(blocking=True)
2268

    
2269
      # Stop master daemons, so that no new jobs can come in and all running
2270
      # ones are finished
2271
      self.feedback_fn("Stopping master daemons")
2272
      self._RunCmd(None, [constants.DAEMON_UTIL, "stop-master"])
2273
      try:
2274
        # Stop daemons on all nodes
2275
        for node_name in self.online_nodes:
2276
          self.feedback_fn("Stopping daemons on %s" % node_name)
2277
          self._RunCmd(node_name, [constants.DAEMON_UTIL, "stop-all"])
2278

    
2279
        # All daemons are shut down now
2280
        try:
2281
          return fn(self, *args)
2282
        except Exception, err:
2283
          _, errmsg = FormatError(err)
2284
          logging.exception("Caught exception")
2285
          self.feedback_fn(errmsg)
2286
          raise
2287
      finally:
2288
        # Start cluster again, master node last
2289
        for node_name in self.nonmaster_nodes + [self.master_node]:
2290
          self.feedback_fn("Starting daemons on %s" % node_name)
2291
          self._RunCmd(node_name, [constants.DAEMON_UTIL, "start-all"])
2292
    finally:
2293
      # Resume watcher
2294
      watcher_block.Close()
2295

    
2296

    
2297
def RunWhileClusterStopped(feedback_fn, fn, *args):
2298
  """Calls a function while all cluster daemons are stopped.
2299

2300
  @type feedback_fn: callable
2301
  @param feedback_fn: Feedback function
2302
  @type fn: callable
2303
  @param fn: Function to be called when daemons are stopped
2304

2305
  """
2306
  feedback_fn("Gathering cluster information")
2307

    
2308
  # This ensures we're running on the master daemon
2309
  cl = GetClient()
2310

    
2311
  (cluster_name, master_node) = \
2312
    cl.QueryConfigValues(["cluster_name", "master_node"])
2313

    
2314
  online_nodes = GetOnlineNodes([], cl=cl)
2315

    
2316
  # Don't keep a reference to the client. The master daemon will go away.
2317
  del cl
2318

    
2319
  assert master_node in online_nodes
2320

    
2321
  return _RunWhileClusterStoppedHelper(feedback_fn, cluster_name, master_node,
2322
                                       online_nodes).Call(fn, *args)
2323

    
2324

    
2325
def GenerateTable(headers, fields, separator, data,
2326
                  numfields=None, unitfields=None,
2327
                  units=None):
2328
  """Prints a table with headers and different fields.
2329

2330
  @type headers: dict
2331
  @param headers: dictionary mapping field names to headers for
2332
      the table
2333
  @type fields: list
2334
  @param fields: the field names corresponding to each row in
2335
      the data field
2336
  @param separator: the separator to be used; if this is None,
2337
      the default 'smart' algorithm is used which computes optimal
2338
      field width, otherwise just the separator is used between
2339
      each field
2340
  @type data: list
2341
  @param data: a list of lists, each sublist being one row to be output
2342
  @type numfields: list
2343
  @param numfields: a list with the fields that hold numeric
2344
      values and thus should be right-aligned
2345
  @type unitfields: list
2346
  @param unitfields: a list with the fields that hold numeric
2347
      values that should be formatted with the units field
2348
  @type units: string or None
2349
  @param units: the units we should use for formatting, or None for
2350
      automatic choice (human-readable for non-separator usage, otherwise
2351
      megabytes); this is a one-letter string
2352

2353
  """
2354
  if units is None:
2355
    if separator:
2356
      units = "m"
2357
    else:
2358
      units = "h"
2359

    
2360
  if numfields is None:
2361
    numfields = []
2362
  if unitfields is None:
2363
    unitfields = []
2364

    
2365
  numfields = utils.FieldSet(*numfields)   # pylint: disable-msg=W0142
2366
  unitfields = utils.FieldSet(*unitfields) # pylint: disable-msg=W0142
2367

    
2368
  format_fields = []
2369
  for field in fields:
2370
    if headers and field not in headers:
2371
      # TODO: handle better unknown fields (either revert to old
2372
      # style of raising exception, or deal more intelligently with
2373
      # variable fields)
2374
      headers[field] = field
2375
    if separator is not None:
2376
      format_fields.append("%s")
2377
    elif numfields.Matches(field):
2378
      format_fields.append("%*s")
2379
    else:
2380
      format_fields.append("%-*s")
2381

    
2382
  if separator is None:
2383
    mlens = [0 for name in fields]
2384
    format_str = ' '.join(format_fields)
2385
  else:
2386
    format_str = separator.replace("%", "%%").join(format_fields)
2387

    
2388
  for row in data:
2389
    if row is None:
2390
      continue
2391
    for idx, val in enumerate(row):
2392
      if unitfields.Matches(fields[idx]):
2393
        try:
2394
          val = int(val)
2395
        except (TypeError, ValueError):
2396
          pass
2397
        else:
2398
          val = row[idx] = utils.FormatUnit(val, units)
2399
      val = row[idx] = str(val)
2400
      if separator is None:
2401
        mlens[idx] = max(mlens[idx], len(val))
2402

    
2403
  result = []
2404
  if headers:
2405
    args = []
2406
    for idx, name in enumerate(fields):
2407
      hdr = headers[name]
2408
      if separator is None:
2409
        mlens[idx] = max(mlens[idx], len(hdr))
2410
        args.append(mlens[idx])
2411
      args.append(hdr)
2412
    result.append(format_str % tuple(args))
2413

    
2414
  if separator is None:
2415
    assert len(mlens) == len(fields)
2416

    
2417
    if fields and not numfields.Matches(fields[-1]):
2418
      mlens[-1] = 0
2419

    
2420
  for line in data:
2421
    args = []
2422
    if line is None:
2423
      line = ['-' for _ in fields]
2424
    for idx in range(len(fields)):
2425
      if separator is None:
2426
        args.append(mlens[idx])
2427
      args.append(line[idx])
2428
    result.append(format_str % tuple(args))
2429

    
2430
  return result
2431

    
2432

    
2433
def _FormatBool(value):
2434
  """Formats a boolean value as a string.
2435

2436
  """
2437
  if value:
2438
    return "Y"
2439
  return "N"
2440

    
2441

    
2442
#: Default formatting for query results; (callback, align right)
2443
_DEFAULT_FORMAT_QUERY = {
2444
  constants.QFT_TEXT: (str, False),
2445
  constants.QFT_BOOL: (_FormatBool, False),
2446
  constants.QFT_NUMBER: (str, True),
2447
  constants.QFT_TIMESTAMP: (utils.FormatTime, False),
2448
  constants.QFT_OTHER: (str, False),
2449
  constants.QFT_UNKNOWN: (str, False),
2450
  }
2451

    
2452

    
2453
def _GetColumnFormatter(fdef, override, unit):
2454
  """Returns formatting function for a field.
2455

2456
  @type fdef: L{objects.QueryFieldDefinition}
2457
  @type override: dict
2458
  @param override: Dictionary for overriding field formatting functions,
2459
    indexed by field name, contents like L{_DEFAULT_FORMAT_QUERY}
2460
  @type unit: string
2461
  @param unit: Unit used for formatting fields of type L{constants.QFT_UNIT}
2462
  @rtype: tuple; (callable, bool)
2463
  @return: Returns the function to format a value (takes one parameter) and a
2464
    boolean for aligning the value on the right-hand side
2465

2466
  """
2467
  fmt = override.get(fdef.name, None)
2468
  if fmt is not None:
2469
    return fmt
2470

    
2471
  assert constants.QFT_UNIT not in _DEFAULT_FORMAT_QUERY
2472

    
2473
  if fdef.kind == constants.QFT_UNIT:
2474
    # Can't keep this information in the static dictionary
2475
    return (lambda value: utils.FormatUnit(value, unit), True)
2476

    
2477
  fmt = _DEFAULT_FORMAT_QUERY.get(fdef.kind, None)
2478
  if fmt is not None:
2479
    return fmt
2480

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

    
2483

    
2484
class _QueryColumnFormatter:
2485
  """Callable class for formatting fields of a query.
2486

2487
  """
2488
  def __init__(self, fn, status_fn, verbose):
2489
    """Initializes this class.
2490

2491
    @type fn: callable
2492
    @param fn: Formatting function
2493
    @type status_fn: callable
2494
    @param status_fn: Function to report fields' status
2495
    @type verbose: boolean
2496
    @param verbose: whether to use verbose field descriptions or not
2497

2498
    """
2499
    self._fn = fn
2500
    self._status_fn = status_fn
2501
    self._verbose = verbose
2502

    
2503
  def __call__(self, data):
2504
    """Returns a field's string representation.
2505

2506
    """
2507
    (status, value) = data
2508

    
2509
    # Report status
2510
    self._status_fn(status)
2511

    
2512
    if status == constants.RS_NORMAL:
2513
      return self._fn(value)
2514

    
2515
    assert value is None, \
2516
           "Found value %r for abnormal status %s" % (value, status)
2517

    
2518
    return FormatResultError(status, self._verbose)
2519

    
2520

    
2521
def FormatResultError(status, verbose):
2522
  """Formats result status other than L{constants.RS_NORMAL}.
2523

2524
  @param status: The result status
2525
  @type verbose: boolean
2526
  @param verbose: Whether to return the verbose text
2527
  @return: Text of result status
2528

2529
  """
2530
  assert status != constants.RS_NORMAL, \
2531
         "FormatResultError called with status equal to constants.RS_NORMAL"
2532
  try:
2533
    (verbose_text, normal_text) = constants.RSS_DESCRIPTION[status]
2534
  except KeyError:
2535
    raise NotImplementedError("Unknown status %s" % status)
2536
  else:
2537
    if verbose:
2538
      return verbose_text
2539
    return normal_text
2540

    
2541

    
2542
def FormatQueryResult(result, unit=None, format_override=None, separator=None,
2543
                      header=False, verbose=False):
2544
  """Formats data in L{objects.QueryResponse}.
2545

2546
  @type result: L{objects.QueryResponse}
2547
  @param result: result of query operation
2548
  @type unit: string
2549
  @param unit: Unit used for formatting fields of type L{constants.QFT_UNIT},
2550
    see L{utils.text.FormatUnit}
2551
  @type format_override: dict
2552
  @param format_override: Dictionary for overriding field formatting functions,
2553
    indexed by field name, contents like L{_DEFAULT_FORMAT_QUERY}
2554
  @type separator: string or None
2555
  @param separator: String used to separate fields
2556
  @type header: bool
2557
  @param header: Whether to output header row
2558
  @type verbose: boolean
2559
  @param verbose: whether to use verbose field descriptions or not
2560

2561
  """
2562
  if unit is None:
2563
    if separator:
2564
      unit = "m"
2565
    else:
2566
      unit = "h"
2567

    
2568
  if format_override is None:
2569
    format_override = {}
2570

    
2571
  stats = dict.fromkeys(constants.RS_ALL, 0)
2572

    
2573
  def _RecordStatus(status):
2574
    if status in stats:
2575
      stats[status] += 1
2576

    
2577
  columns = []
2578
  for fdef in result.fields:
2579
    assert fdef.title and fdef.name
2580
    (fn, align_right) = _GetColumnFormatter(fdef, format_override, unit)
2581
    columns.append(TableColumn(fdef.title,
2582
                               _QueryColumnFormatter(fn, _RecordStatus,
2583
                                                     verbose),
2584
                               align_right))
2585

    
2586
  table = FormatTable(result.data, columns, header, separator)
2587

    
2588
  # Collect statistics
2589
  assert len(stats) == len(constants.RS_ALL)
2590
  assert compat.all(count >= 0 for count in stats.values())
2591

    
2592
  # Determine overall status. If there was no data, unknown fields must be
2593
  # detected via the field definitions.
2594
  if (stats[constants.RS_UNKNOWN] or
2595
      (not result.data and _GetUnknownFields(result.fields))):
2596
    status = QR_UNKNOWN
2597
  elif compat.any(count > 0 for key, count in stats.items()
2598
                  if key != constants.RS_NORMAL):
2599
    status = QR_INCOMPLETE
2600
  else:
2601
    status = QR_NORMAL
2602

    
2603
  return (status, table)
2604

    
2605

    
2606
def _GetUnknownFields(fdefs):
2607
  """Returns list of unknown fields included in C{fdefs}.
2608

2609
  @type fdefs: list of L{objects.QueryFieldDefinition}
2610

2611
  """
2612
  return [fdef for fdef in fdefs
2613
          if fdef.kind == constants.QFT_UNKNOWN]
2614

    
2615

    
2616
def _WarnUnknownFields(fdefs):
2617
  """Prints a warning to stderr if a query included unknown fields.
2618

2619
  @type fdefs: list of L{objects.QueryFieldDefinition}
2620

2621
  """
2622
  unknown = _GetUnknownFields(fdefs)
2623
  if unknown:
2624
    ToStderr("Warning: Queried for unknown fields %s",
2625
             utils.CommaJoin(fdef.name for fdef in unknown))
2626
    return True
2627

    
2628
  return False
2629

    
2630

    
2631
def GenericList(resource, fields, names, unit, separator, header, cl=None,
2632
                format_override=None, verbose=False, force_filter=False):
2633
  """Generic implementation for listing all items of a resource.
2634

2635
  @param resource: One of L{constants.QR_VIA_LUXI}
2636
  @type fields: list of strings
2637
  @param fields: List of fields to query for
2638
  @type names: list of strings
2639
  @param names: Names of items to query for
2640
  @type unit: string or None
2641
  @param unit: Unit used for formatting fields of type L{constants.QFT_UNIT} or
2642
    None for automatic choice (human-readable for non-separator usage,
2643
    otherwise megabytes); this is a one-letter string
2644
  @type separator: string or None
2645
  @param separator: String used to separate fields
2646
  @type header: bool
2647
  @param header: Whether to show header row
2648
  @type force_filter: bool
2649
  @param force_filter: Whether to always treat names as filter
2650
  @type format_override: dict
2651
  @param format_override: Dictionary for overriding field formatting functions,
2652
    indexed by field name, contents like L{_DEFAULT_FORMAT_QUERY}
2653
  @type verbose: boolean
2654
  @param verbose: whether to use verbose field descriptions or not
2655

2656
  """
2657
  if cl is None:
2658
    cl = GetClient()
2659

    
2660
  if not names:
2661
    names = None
2662

    
2663
  if (force_filter or
2664
      (names and len(names) == 1 and qlang.MaybeFilter(names[0]))):
2665
    try:
2666
      (filter_text, ) = names
2667
    except ValueError:
2668
      raise errors.OpPrereqError("Exactly one argument must be given as a"
2669
                                 " filter")
2670

    
2671
    logging.debug("Parsing '%s' as filter", filter_text)
2672
    filter_ = qlang.ParseFilter(filter_text)
2673
  else:
2674
    filter_ = qlang.MakeSimpleFilter("name", names)
2675

    
2676
  response = cl.Query(resource, fields, filter_)
2677

    
2678
  found_unknown = _WarnUnknownFields(response.fields)
2679

    
2680
  (status, data) = FormatQueryResult(response, unit=unit, separator=separator,
2681
                                     header=header,
2682
                                     format_override=format_override,
2683
                                     verbose=verbose)
2684

    
2685
  for line in data:
2686
    ToStdout(line)
2687

    
2688
  assert ((found_unknown and status == QR_UNKNOWN) or
2689
          (not found_unknown and status != QR_UNKNOWN))
2690

    
2691
  if status == QR_UNKNOWN:
2692
    return constants.EXIT_UNKNOWN_FIELD
2693

    
2694
  # TODO: Should the list command fail if not all data could be collected?
2695
  return constants.EXIT_SUCCESS
2696

    
2697

    
2698
def GenericListFields(resource, fields, separator, header, cl=None):
2699
  """Generic implementation for listing fields for a resource.
2700

2701
  @param resource: One of L{constants.QR_VIA_LUXI}
2702
  @type fields: list of strings
2703
  @param fields: List of fields to query for
2704
  @type separator: string or None
2705
  @param separator: String used to separate fields
2706
  @type header: bool
2707
  @param header: Whether to show header row
2708

2709
  """
2710
  if cl is None:
2711
    cl = GetClient()
2712

    
2713
  if not fields:
2714
    fields = None
2715

    
2716
  response = cl.QueryFields(resource, fields)
2717

    
2718
  found_unknown = _WarnUnknownFields(response.fields)
2719

    
2720
  columns = [
2721
    TableColumn("Name", str, False),
2722
    TableColumn("Title", str, False),
2723
    TableColumn("Description", str, False),
2724
    ]
2725

    
2726
  rows = [[fdef.name, fdef.title, fdef.doc] for fdef in response.fields]
2727

    
2728
  for line in FormatTable(rows, columns, header, separator):
2729
    ToStdout(line)
2730

    
2731
  if found_unknown:
2732
    return constants.EXIT_UNKNOWN_FIELD
2733

    
2734
  return constants.EXIT_SUCCESS
2735

    
2736

    
2737
class TableColumn:
2738
  """Describes a column for L{FormatTable}.
2739

2740
  """
2741
  def __init__(self, title, fn, align_right):
2742
    """Initializes this class.
2743

2744
    @type title: string
2745
    @param title: Column title
2746
    @type fn: callable
2747
    @param fn: Formatting function
2748
    @type align_right: bool
2749
    @param align_right: Whether to align values on the right-hand side
2750

2751
    """
2752
    self.title = title
2753
    self.format = fn
2754
    self.align_right = align_right
2755

    
2756

    
2757
def _GetColFormatString(width, align_right):
2758
  """Returns the format string for a field.
2759

2760
  """
2761
  if align_right:
2762
    sign = ""
2763
  else:
2764
    sign = "-"
2765

    
2766
  return "%%%s%ss" % (sign, width)
2767

    
2768

    
2769
def FormatTable(rows, columns, header, separator):
2770
  """Formats data as a table.
2771

2772
  @type rows: list of lists
2773
  @param rows: Row data, one list per row
2774
  @type columns: list of L{TableColumn}
2775
  @param columns: Column descriptions
2776
  @type header: bool
2777
  @param header: Whether to show header row
2778
  @type separator: string or None
2779
  @param separator: String used to separate columns
2780

2781
  """
2782
  if header:
2783
    data = [[col.title for col in columns]]
2784
    colwidth = [len(col.title) for col in columns]
2785
  else:
2786
    data = []
2787
    colwidth = [0 for _ in columns]
2788

    
2789
  # Format row data
2790
  for row in rows:
2791
    assert len(row) == len(columns)
2792

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

    
2795
    if separator is None:
2796
      # Update column widths
2797
      for idx, (oldwidth, value) in enumerate(zip(colwidth, formatted)):
2798
        # Modifying a list's items while iterating is fine
2799
        colwidth[idx] = max(oldwidth, len(value))
2800

    
2801
    data.append(formatted)
2802

    
2803
  if separator is not None:
2804
    # Return early if a separator is used
2805
    return [separator.join(row) for row in data]
2806

    
2807
  if columns and not columns[-1].align_right:
2808
    # Avoid unnecessary spaces at end of line
2809
    colwidth[-1] = 0
2810

    
2811
  # Build format string
2812
  fmt = " ".join([_GetColFormatString(width, col.align_right)
2813
                  for col, width in zip(columns, colwidth)])
2814

    
2815
  return [fmt % tuple(row) for row in data]
2816

    
2817

    
2818
def FormatTimestamp(ts):
2819
  """Formats a given timestamp.
2820

2821
  @type ts: timestamp
2822
  @param ts: a timeval-type timestamp, a tuple of seconds and microseconds
2823

2824
  @rtype: string
2825
  @return: a string with the formatted timestamp
2826

2827
  """
2828
  if not isinstance (ts, (tuple, list)) or len(ts) != 2:
2829
    return '?'
2830
  sec, usec = ts
2831
  return time.strftime("%F %T", time.localtime(sec)) + ".%06d" % usec
2832

    
2833

    
2834
def ParseTimespec(value):
2835
  """Parse a time specification.
2836

2837
  The following suffixed will be recognized:
2838

2839
    - s: seconds
2840
    - m: minutes
2841
    - h: hours
2842
    - d: day
2843
    - w: weeks
2844

2845
  Without any suffix, the value will be taken to be in seconds.
2846

2847
  """
2848
  value = str(value)
2849
  if not value:
2850
    raise errors.OpPrereqError("Empty time specification passed")
2851
  suffix_map = {
2852
    's': 1,
2853
    'm': 60,
2854
    'h': 3600,
2855
    'd': 86400,
2856
    'w': 604800,
2857
    }
2858
  if value[-1] not in suffix_map:
2859
    try:
2860
      value = int(value)
2861
    except (TypeError, ValueError):
2862
      raise errors.OpPrereqError("Invalid time specification '%s'" % value)
2863
  else:
2864
    multiplier = suffix_map[value[-1]]
2865
    value = value[:-1]
2866
    if not value: # no data left after stripping the suffix
2867
      raise errors.OpPrereqError("Invalid time specification (only"
2868
                                 " suffix passed)")
2869
    try:
2870
      value = int(value) * multiplier
2871
    except (TypeError, ValueError):
2872
      raise errors.OpPrereqError("Invalid time specification '%s'" % value)
2873
  return value
2874

    
2875

    
2876
def GetOnlineNodes(nodes, cl=None, nowarn=False, secondary_ips=False,
2877
                   filter_master=False, nodegroup=None):
2878
  """Returns the names of online nodes.
2879

2880
  This function will also log a warning on stderr with the names of
2881
  the online nodes.
2882

2883
  @param nodes: if not empty, use only this subset of nodes (minus the
2884
      offline ones)
2885
  @param cl: if not None, luxi client to use
2886
  @type nowarn: boolean
2887
  @param nowarn: by default, this function will output a note with the
2888
      offline nodes that are skipped; if this parameter is True the
2889
      note is not displayed
2890
  @type secondary_ips: boolean
2891
  @param secondary_ips: if True, return the secondary IPs instead of the
2892
      names, useful for doing network traffic over the replication interface
2893
      (if any)
2894
  @type filter_master: boolean
2895
  @param filter_master: if True, do not return the master node in the list
2896
      (useful in coordination with secondary_ips where we cannot check our
2897
      node name against the list)
2898
  @type nodegroup: string
2899
  @param nodegroup: If set, only return nodes in this node group
2900

2901
  """
2902
  if cl is None:
2903
    cl = GetClient()
2904

    
2905
  filter_ = []
2906

    
2907
  if nodes:
2908
    filter_.append(qlang.MakeSimpleFilter("name", nodes))
2909

    
2910
  if nodegroup is not None:
2911
    filter_.append([qlang.OP_OR, [qlang.OP_EQUAL, "group", nodegroup],
2912
                                 [qlang.OP_EQUAL, "group.uuid", nodegroup]])
2913

    
2914
  if filter_master:
2915
    filter_.append([qlang.OP_NOT, [qlang.OP_TRUE, "master"]])
2916

    
2917
  if filter_:
2918
    if len(filter_) > 1:
2919
      final_filter = [qlang.OP_AND] + filter_
2920
    else:
2921
      assert len(filter_) == 1
2922
      final_filter = filter_[0]
2923
  else:
2924
    final_filter = None
2925

    
2926
  result = cl.Query(constants.QR_NODE, ["name", "offline", "sip"], final_filter)
2927

    
2928
  def _IsOffline(row):
2929
    (_, (_, offline), _) = row
2930
    return offline
2931

    
2932
  def _GetName(row):
2933
    ((_, name), _, _) = row
2934
    return name
2935

    
2936
  def _GetSip(row):
2937
    (_, _, (_, sip)) = row
2938
    return sip
2939

    
2940
  (offline, online) = compat.partition(result.data, _IsOffline)
2941

    
2942
  if offline and not nowarn:
2943
    ToStderr("Note: skipping offline node(s): %s" %
2944
             utils.CommaJoin(map(_GetName, offline)))
2945

    
2946
  if secondary_ips:
2947
    fn = _GetSip
2948
  else:
2949
    fn = _GetName
2950

    
2951
  return map(fn, online)
2952

    
2953

    
2954
def _ToStream(stream, txt, *args):
2955
  """Write a message to a stream, bypassing the logging system
2956

2957
  @type stream: file object
2958
  @param stream: the file to which we should write
2959
  @type txt: str
2960
  @param txt: the message
2961

2962
  """
2963
  try:
2964
    if args:
2965
      args = tuple(args)
2966
      stream.write(txt % args)
2967
    else:
2968
      stream.write(txt)
2969
    stream.write('\n')
2970
    stream.flush()
2971
  except IOError, err:
2972
    if err.errno == errno.EPIPE:
2973
      # our terminal went away, we'll exit
2974
      sys.exit(constants.EXIT_FAILURE)
2975
    else:
2976
      raise
2977

    
2978

    
2979
def ToStdout(txt, *args):
2980
  """Write a message to stdout only, bypassing the logging system
2981

2982
  This is just a wrapper over _ToStream.
2983

2984
  @type txt: str
2985
  @param txt: the message
2986

2987
  """
2988
  _ToStream(sys.stdout, txt, *args)
2989

    
2990

    
2991
def ToStderr(txt, *args):
2992
  """Write a message to stderr only, bypassing the logging system
2993

2994
  This is just a wrapper over _ToStream.
2995

2996
  @type txt: str
2997
  @param txt: the message
2998

2999
  """
3000
  _ToStream(sys.stderr, txt, *args)
3001

    
3002

    
3003
class JobExecutor(object):
3004
  """Class which manages the submission and execution of multiple jobs.
3005

3006
  Note that instances of this class should not be reused between
3007
  GetResults() calls.
3008

3009
  """
3010
  def __init__(self, cl=None, verbose=True, opts=None, feedback_fn=None):
3011
    self.queue = []
3012
    if cl is None:
3013
      cl = GetClient()
3014
    self.cl = cl
3015
    self.verbose = verbose
3016
    self.jobs = []
3017
    self.opts = opts
3018
    self.feedback_fn = feedback_fn
3019
    self._counter = itertools.count()
3020

    
3021
  @staticmethod
3022
  def _IfName(name, fmt):
3023
    """Helper function for formatting name.
3024

3025
    """
3026
    if name:
3027
      return fmt % name
3028

    
3029
    return ""
3030

    
3031
  def QueueJob(self, name, *ops):
3032
    """Record a job for later submit.
3033

3034
    @type name: string
3035
    @param name: a description of the job, will be used in WaitJobSet
3036

3037
    """
3038
    SetGenericOpcodeOpts(ops, self.opts)
3039
    self.queue.append((self._counter.next(), name, ops))
3040

    
3041
  def AddJobId(self, name, status, job_id):
3042
    """Adds a job ID to the internal queue.
3043

3044
    """
3045
    self.jobs.append((self._counter.next(), status, job_id, name))
3046

    
3047
  def SubmitPending(self, each=False):
3048
    """Submit all pending jobs.
3049

3050
    """
3051
    if each:
3052
      results = []
3053
      for (_, _, ops) in self.queue:
3054
        # SubmitJob will remove the success status, but raise an exception if
3055
        # the submission fails, so we'll notice that anyway.
3056
        results.append([True, self.cl.SubmitJob(ops)])
3057
    else:
3058
      results = self.cl.SubmitManyJobs([ops for (_, _, ops) in self.queue])
3059
    for ((status, data), (idx, name, _)) in zip(results, self.queue):
3060
      self.jobs.append((idx, status, data, name))
3061

    
3062
  def _ChooseJob(self):
3063
    """Choose a non-waiting/queued job to poll next.
3064

3065
    """
3066
    assert self.jobs, "_ChooseJob called with empty job list"
3067

    
3068
    result = self.cl.QueryJobs([i[2] for i in self.jobs], ["status"])
3069
    assert result
3070

    
3071
    for job_data, status in zip(self.jobs, result):
3072
      if (isinstance(status, list) and status and
3073
          status[0] in (constants.JOB_STATUS_QUEUED,
3074
                        constants.JOB_STATUS_WAITING,
3075
                        constants.JOB_STATUS_CANCELING)):
3076
        # job is still present and waiting
3077
        continue
3078
      # good candidate found (either running job or lost job)
3079
      self.jobs.remove(job_data)
3080
      return job_data
3081

    
3082
    # no job found
3083
    return self.jobs.pop(0)
3084

    
3085
  def GetResults(self):
3086
    """Wait for and return the results of all jobs.
3087

3088
    @rtype: list
3089
    @return: list of tuples (success, job results), in the same order
3090
        as the submitted jobs; if a job has failed, instead of the result
3091
        there will be the error message
3092

3093
    """
3094
    if not self.jobs:
3095
      self.SubmitPending()
3096
    results = []
3097
    if self.verbose:
3098
      ok_jobs = [row[2] for row in self.jobs if row[1]]
3099
      if ok_jobs:
3100
        ToStdout("Submitted jobs %s", utils.CommaJoin(ok_jobs))
3101

    
3102
    # first, remove any non-submitted jobs
3103
    self.jobs, failures = compat.partition(self.jobs, lambda x: x[1])
3104
    for idx, _, jid, name in failures:
3105
      ToStderr("Failed to submit job%s: %s", self._IfName(name, " for %s"), jid)
3106
      results.append((idx, False, jid))
3107

    
3108
    while self.jobs:
3109
      (idx, _, jid, name) = self._ChooseJob()
3110
      ToStdout("Waiting for job %s%s ...", jid, self._IfName(name, " for %s"))
3111
      try:
3112
        job_result = PollJob(jid, cl=self.cl, feedback_fn=self.feedback_fn)
3113
        success = True
3114
      except errors.JobLost, err:
3115
        _, job_result = FormatError(err)
3116
        ToStderr("Job %s%s has been archived, cannot check its result",
3117
                 jid, self._IfName(name, " for %s"))
3118
        success = False
3119
      except (errors.GenericError, luxi.ProtocolError), err:
3120
        _, job_result = FormatError(err)
3121
        success = False
3122
        # the error message will always be shown, verbose or not
3123
        ToStderr("Job %s%s has failed: %s",
3124
                 jid, self._IfName(name, " for %s"), job_result)
3125

    
3126
      results.append((idx, success, job_result))
3127

    
3128
    # sort based on the index, then drop it
3129
    results.sort()
3130
    results = [i[1:] for i in results]
3131

    
3132
    return results
3133

    
3134
  def WaitOrShow(self, wait):
3135
    """Wait for job results or only print the job IDs.
3136

3137
    @type wait: boolean
3138
    @param wait: whether to wait or not
3139

3140
    """
3141
    if wait:
3142
      return self.GetResults()
3143
    else:
3144
      if not self.jobs:
3145
        self.SubmitPending()
3146
      for _, status, result, name in self.jobs:
3147
        if status:
3148
          ToStdout("%s: %s", result, name)
3149
        else:
3150
          ToStderr("Failure for %s: %s", name, result)
3151
      return [row[1:3] for row in self.jobs]
3152

    
3153

    
3154
def FormatParameterDict(buf, param_dict, actual, level=1):
3155
  """Formats a parameter dictionary.
3156

3157
  @type buf: L{StringIO}
3158
  @param buf: the buffer into which to write
3159
  @type param_dict: dict
3160
  @param param_dict: the own parameters
3161
  @type actual: dict
3162
  @param actual: the current parameter set (including defaults)
3163
  @param level: Level of indent
3164

3165
  """
3166
  indent = "  " * level
3167
  for key in sorted(actual):
3168
    val = param_dict.get(key, "default (%s)" % actual[key])
3169
    buf.write("%s- %s: %s\n" % (indent, key, val))
3170

    
3171

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

3175
  This function is used to request confirmation for doing an operation
3176
  on a given list of list_type.
3177

3178
  @type names: list
3179
  @param names: the list of names that we display when
3180
      we ask for confirmation
3181
  @type list_type: str
3182
  @param list_type: Human readable name for elements in the list (e.g. nodes)
3183
  @type text: str
3184
  @param text: the operation that the user should confirm
3185
  @rtype: boolean
3186
  @return: True or False depending on user's confirmation.
3187

3188
  """
3189
  count = len(names)
3190
  msg = ("The %s will operate on %d %s.\n%s"
3191
         "Do you want to continue?" % (text, count, list_type, extra))
3192
  affected = (("\nAffected %s:\n" % list_type) +
3193
              "\n".join(["  %s" % name for name in names]))
3194

    
3195
  choices = [("y", True, "Yes, execute the %s" % text),
3196
             ("n", False, "No, abort the %s" % text)]
3197

    
3198
  if count > 20:
3199
    choices.insert(1, ("v", "v", "View the list of affected %s" % list_type))
3200
    question = msg
3201
  else:
3202
    question = msg + affected
3203

    
3204
  choice = AskUser(question, choices)
3205
  if choice == "v":
3206
    choices.pop(1)
3207
    choice = AskUser(msg + affected, choices)
3208
  return choice