Statistics
| Branch: | Tag: | Revision:

root / lib / cli.py @ 235407ba

History | View | Annotate | Download (100.2 kB)

1
#
2
#
3

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

    
21

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

    
24

    
25
import sys
26
import textwrap
27
import os.path
28
import time
29
import logging
30
import errno
31
from cStringIO import StringIO
32

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

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

    
48

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

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

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

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

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

    
253

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

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

    
263

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

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

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

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

    
279

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

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

286
  """
287

    
288

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

292
  """
293

    
294

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

298
  """
299

    
300

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

304
  """
305

    
306

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

310
  """
311

    
312

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

316
  """
317

    
318

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

322
  """
323

    
324

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

328
  """
329

    
330

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

334
  """
335

    
336

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

340
  """
341

    
342

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

    
353

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

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

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

    
376

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

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

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

    
405

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

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

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

    
423

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

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

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

    
440

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

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

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

    
457

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

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

    
467

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

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

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

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

    
504

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

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

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

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

    
532

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

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

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

    
541

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

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

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

    
556

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

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

    
577

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

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

    
597

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

    
601

    
602
_YORNO = "yes|no"
603

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1200

    
1201
#: Options provided by all commands
1202
COMMON_OPTS = [DEBUG_OPT]
1203

    
1204
# common options for creating instances. add and import then add their own
1205
# specific ones.
1206
COMMON_CREATE_OPTS = [
1207
  BACKEND_OPT,
1208
  DISK_OPT,
1209
  DISK_TEMPLATE_OPT,
1210
  FILESTORE_DIR_OPT,
1211
  FILESTORE_DRIVER_OPT,
1212
  HYPERVISOR_OPT,
1213
  IALLOCATOR_OPT,
1214
  NET_OPT,
1215
  NODE_PLACEMENT_OPT,
1216
  NOIPCHECK_OPT,
1217
  NONAMECHECK_OPT,
1218
  NONICS_OPT,
1219
  NWSYNC_OPT,
1220
  OSPARAMS_OPT,
1221
  OS_SIZE_OPT,
1222
  SUBMIT_OPT,
1223
  DRY_RUN_OPT,
1224
  PRIORITY_OPT,
1225
  ]
1226

    
1227

    
1228
def _ParseArgs(argv, commands, aliases):
1229
  """Parser for the command line arguments.
1230

1231
  This function parses the arguments and returns the function which
1232
  must be executed together with its (modified) arguments.
1233

1234
  @param argv: the command line
1235
  @param commands: dictionary with special contents, see the design
1236
      doc for cmdline handling
1237
  @param aliases: dictionary with command aliases {'alias': 'target, ...}
1238

1239
  """
1240
  if len(argv) == 0:
1241
    binary = "<command>"
1242
  else:
1243
    binary = argv[0].split("/")[-1]
1244

    
1245
  if len(argv) > 1 and argv[1] == "--version":
1246
    ToStdout("%s (ganeti %s) %s", binary, constants.VCS_VERSION,
1247
             constants.RELEASE_VERSION)
1248
    # Quit right away. That way we don't have to care about this special
1249
    # argument. optparse.py does it the same.
1250
    sys.exit(0)
1251

    
1252
  if len(argv) < 2 or not (argv[1] in commands or
1253
                           argv[1] in aliases):
1254
    # let's do a nice thing
1255
    sortedcmds = commands.keys()
1256
    sortedcmds.sort()
1257

    
1258
    ToStdout("Usage: %s {command} [options...] [argument...]", binary)
1259
    ToStdout("%s <command> --help to see details, or man %s", binary, binary)
1260
    ToStdout("")
1261

    
1262
    # compute the max line length for cmd + usage
1263
    mlen = max([len(" %s" % cmd) for cmd in commands])
1264
    mlen = min(60, mlen) # should not get here...
1265

    
1266
    # and format a nice command list
1267
    ToStdout("Commands:")
1268
    for cmd in sortedcmds:
1269
      cmdstr = " %s" % (cmd,)
1270
      help_text = commands[cmd][4]
1271
      help_lines = textwrap.wrap(help_text, 79 - 3 - mlen)
1272
      ToStdout("%-*s - %s", mlen, cmdstr, help_lines.pop(0))
1273
      for line in help_lines:
1274
        ToStdout("%-*s   %s", mlen, "", line)
1275

    
1276
    ToStdout("")
1277

    
1278
    return None, None, None
1279

    
1280
  # get command, unalias it, and look it up in commands
1281
  cmd = argv.pop(1)
1282
  if cmd in aliases:
1283
    if cmd in commands:
1284
      raise errors.ProgrammerError("Alias '%s' overrides an existing"
1285
                                   " command" % cmd)
1286

    
1287
    if aliases[cmd] not in commands:
1288
      raise errors.ProgrammerError("Alias '%s' maps to non-existing"
1289
                                   " command '%s'" % (cmd, aliases[cmd]))
1290

    
1291
    cmd = aliases[cmd]
1292

    
1293
  func, args_def, parser_opts, usage, description = commands[cmd]
1294
  parser = OptionParser(option_list=parser_opts + COMMON_OPTS,
1295
                        description=description,
1296
                        formatter=TitledHelpFormatter(),
1297
                        usage="%%prog %s %s" % (cmd, usage))
1298
  parser.disable_interspersed_args()
1299
  options, args = parser.parse_args()
1300

    
1301
  if not _CheckArguments(cmd, args_def, args):
1302
    return None, None, None
1303

    
1304
  return func, options, args
1305

    
1306

    
1307
def _CheckArguments(cmd, args_def, args):
1308
  """Verifies the arguments using the argument definition.
1309

1310
  Algorithm:
1311

1312
    1. Abort with error if values specified by user but none expected.
1313

1314
    1. For each argument in definition
1315

1316
      1. Keep running count of minimum number of values (min_count)
1317
      1. Keep running count of maximum number of values (max_count)
1318
      1. If it has an unlimited number of values
1319

1320
        1. Abort with error if it's not the last argument in the definition
1321

1322
    1. If last argument has limited number of values
1323

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

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

1328
  """
1329
  if args and not args_def:
1330
    ToStderr("Error: Command %s expects no arguments", cmd)
1331
    return False
1332

    
1333
  min_count = None
1334
  max_count = None
1335
  check_max = None
1336

    
1337
  last_idx = len(args_def) - 1
1338

    
1339
  for idx, arg in enumerate(args_def):
1340
    if min_count is None:
1341
      min_count = arg.min
1342
    elif arg.min is not None:
1343
      min_count += arg.min
1344

    
1345
    if max_count is None:
1346
      max_count = arg.max
1347
    elif arg.max is not None:
1348
      max_count += arg.max
1349

    
1350
    if idx == last_idx:
1351
      check_max = (arg.max is not None)
1352

    
1353
    elif arg.max is None:
1354
      raise errors.ProgrammerError("Only the last argument can have max=None")
1355

    
1356
  if check_max:
1357
    # Command with exact number of arguments
1358
    if (min_count is not None and max_count is not None and
1359
        min_count == max_count and len(args) != min_count):
1360
      ToStderr("Error: Command %s expects %d argument(s)", cmd, min_count)
1361
      return False
1362

    
1363
    # Command with limited number of arguments
1364
    if max_count is not None and len(args) > max_count:
1365
      ToStderr("Error: Command %s expects only %d argument(s)",
1366
               cmd, max_count)
1367
      return False
1368

    
1369
  # Command with some required arguments
1370
  if min_count is not None and len(args) < min_count:
1371
    ToStderr("Error: Command %s expects at least %d argument(s)",
1372
             cmd, min_count)
1373
    return False
1374

    
1375
  return True
1376

    
1377

    
1378
def SplitNodeOption(value):
1379
  """Splits the value of a --node option.
1380

1381
  """
1382
  if value and ':' in value:
1383
    return value.split(':', 1)
1384
  else:
1385
    return (value, None)
1386

    
1387

    
1388
def CalculateOSNames(os_name, os_variants):
1389
  """Calculates all the names an OS can be called, according to its variants.
1390

1391
  @type os_name: string
1392
  @param os_name: base name of the os
1393
  @type os_variants: list or None
1394
  @param os_variants: list of supported variants
1395
  @rtype: list
1396
  @return: list of valid names
1397

1398
  """
1399
  if os_variants:
1400
    return ['%s+%s' % (os_name, v) for v in os_variants]
1401
  else:
1402
    return [os_name]
1403

    
1404

    
1405
def ParseFields(selected, default):
1406
  """Parses the values of "--field"-like options.
1407

1408
  @type selected: string or None
1409
  @param selected: User-selected options
1410
  @type default: list
1411
  @param default: Default fields
1412

1413
  """
1414
  if selected is None:
1415
    return default
1416

    
1417
  if selected.startswith("+"):
1418
    return default + selected[1:].split(",")
1419

    
1420
  return selected.split(",")
1421

    
1422

    
1423
UsesRPC = rpc.RunWithRPC
1424

    
1425

    
1426
def AskUser(text, choices=None):
1427
  """Ask the user a question.
1428

1429
  @param text: the question to ask
1430

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

1436
  @return: one of the return values from the choices list; if input is
1437
      not possible (i.e. not running with a tty, we return the last
1438
      entry from the list
1439

1440
  """
1441
  if choices is None:
1442
    choices = [('y', True, 'Perform the operation'),
1443
               ('n', False, 'Do not perform the operation')]
1444
  if not choices or not isinstance(choices, list):
1445
    raise errors.ProgrammerError("Invalid choices argument to AskUser")
1446
  for entry in choices:
1447
    if not isinstance(entry, tuple) or len(entry) < 3 or entry[0] == '?':
1448
      raise errors.ProgrammerError("Invalid choices element to AskUser")
1449

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

    
1482

    
1483
class JobSubmittedException(Exception):
1484
  """Job was submitted, client should exit.
1485

1486
  This exception has one argument, the ID of the job that was
1487
  submitted. The handler should print this ID.
1488

1489
  This is not an error, just a structured way to exit from clients.
1490

1491
  """
1492

    
1493

    
1494
def SendJob(ops, cl=None):
1495
  """Function to submit an opcode without waiting for the results.
1496

1497
  @type ops: list
1498
  @param ops: list of opcodes
1499
  @type cl: luxi.Client
1500
  @param cl: the luxi client to use for communicating with the master;
1501
             if None, a new client will be created
1502

1503
  """
1504
  if cl is None:
1505
    cl = GetClient()
1506

    
1507
  job_id = cl.SubmitJob(ops)
1508

    
1509
  return job_id
1510

    
1511

    
1512
def GenericPollJob(job_id, cbs, report_cbs):
1513
  """Generic job-polling function.
1514

1515
  @type job_id: number
1516
  @param job_id: Job ID
1517
  @type cbs: Instance of L{JobPollCbBase}
1518
  @param cbs: Data callbacks
1519
  @type report_cbs: Instance of L{JobPollReportCbBase}
1520
  @param report_cbs: Reporting callbacks
1521

1522
  """
1523
  prev_job_info = None
1524
  prev_logmsg_serial = None
1525

    
1526
  status = None
1527

    
1528
  while True:
1529
    result = cbs.WaitForJobChangeOnce(job_id, ["status"], prev_job_info,
1530
                                      prev_logmsg_serial)
1531
    if not result:
1532
      # job not found, go away!
1533
      raise errors.JobLost("Job with id %s lost" % job_id)
1534

    
1535
    if result == constants.JOB_NOTCHANGED:
1536
      report_cbs.ReportNotChanged(job_id, status)
1537

    
1538
      # Wait again
1539
      continue
1540

    
1541
    # Split result, a tuple of (field values, log entries)
1542
    (job_info, log_entries) = result
1543
    (status, ) = job_info
1544

    
1545
    if log_entries:
1546
      for log_entry in log_entries:
1547
        (serial, timestamp, log_type, message) = log_entry
1548
        report_cbs.ReportLogMessage(job_id, serial, timestamp,
1549
                                    log_type, message)
1550
        prev_logmsg_serial = max(prev_logmsg_serial, serial)
1551

    
1552
    # TODO: Handle canceled and archived jobs
1553
    elif status in (constants.JOB_STATUS_SUCCESS,
1554
                    constants.JOB_STATUS_ERROR,
1555
                    constants.JOB_STATUS_CANCELING,
1556
                    constants.JOB_STATUS_CANCELED):
1557
      break
1558

    
1559
    prev_job_info = job_info
1560

    
1561
  jobs = cbs.QueryJobs([job_id], ["status", "opstatus", "opresult"])
1562
  if not jobs:
1563
    raise errors.JobLost("Job with id %s lost" % job_id)
1564

    
1565
  status, opstatus, result = jobs[0]
1566

    
1567
  if status == constants.JOB_STATUS_SUCCESS:
1568
    return result
1569

    
1570
  if status in (constants.JOB_STATUS_CANCELING, constants.JOB_STATUS_CANCELED):
1571
    raise errors.OpExecError("Job was canceled")
1572

    
1573
  has_ok = False
1574
  for idx, (status, msg) in enumerate(zip(opstatus, result)):
1575
    if status == constants.OP_STATUS_SUCCESS:
1576
      has_ok = True
1577
    elif status == constants.OP_STATUS_ERROR:
1578
      errors.MaybeRaise(msg)
1579

    
1580
      if has_ok:
1581
        raise errors.OpExecError("partial failure (opcode %d): %s" %
1582
                                 (idx, msg))
1583

    
1584
      raise errors.OpExecError(str(msg))
1585

    
1586
  # default failure mode
1587
  raise errors.OpExecError(result)
1588

    
1589

    
1590
class JobPollCbBase:
1591
  """Base class for L{GenericPollJob} callbacks.
1592

1593
  """
1594
  def __init__(self):
1595
    """Initializes this class.
1596

1597
    """
1598

    
1599
  def WaitForJobChangeOnce(self, job_id, fields,
1600
                           prev_job_info, prev_log_serial):
1601
    """Waits for changes on a job.
1602

1603
    """
1604
    raise NotImplementedError()
1605

    
1606
  def QueryJobs(self, job_ids, fields):
1607
    """Returns the selected fields for the selected job IDs.
1608

1609
    @type job_ids: list of numbers
1610
    @param job_ids: Job IDs
1611
    @type fields: list of strings
1612
    @param fields: Fields
1613

1614
    """
1615
    raise NotImplementedError()
1616

    
1617

    
1618
class JobPollReportCbBase:
1619
  """Base class for L{GenericPollJob} reporting callbacks.
1620

1621
  """
1622
  def __init__(self):
1623
    """Initializes this class.
1624

1625
    """
1626

    
1627
  def ReportLogMessage(self, job_id, serial, timestamp, log_type, log_msg):
1628
    """Handles a log message.
1629

1630
    """
1631
    raise NotImplementedError()
1632

    
1633
  def ReportNotChanged(self, job_id, status):
1634
    """Called for if a job hasn't changed in a while.
1635

1636
    @type job_id: number
1637
    @param job_id: Job ID
1638
    @type status: string or None
1639
    @param status: Job status if available
1640

1641
    """
1642
    raise NotImplementedError()
1643

    
1644

    
1645
class _LuxiJobPollCb(JobPollCbBase):
1646
  def __init__(self, cl):
1647
    """Initializes this class.
1648

1649
    """
1650
    JobPollCbBase.__init__(self)
1651
    self.cl = cl
1652

    
1653
  def WaitForJobChangeOnce(self, job_id, fields,
1654
                           prev_job_info, prev_log_serial):
1655
    """Waits for changes on a job.
1656

1657
    """
1658
    return self.cl.WaitForJobChangeOnce(job_id, fields,
1659
                                        prev_job_info, prev_log_serial)
1660

    
1661
  def QueryJobs(self, job_ids, fields):
1662
    """Returns the selected fields for the selected job IDs.
1663

1664
    """
1665
    return self.cl.QueryJobs(job_ids, fields)
1666

    
1667

    
1668
class FeedbackFnJobPollReportCb(JobPollReportCbBase):
1669
  def __init__(self, feedback_fn):
1670
    """Initializes this class.
1671

1672
    """
1673
    JobPollReportCbBase.__init__(self)
1674

    
1675
    self.feedback_fn = feedback_fn
1676

    
1677
    assert callable(feedback_fn)
1678

    
1679
  def ReportLogMessage(self, job_id, serial, timestamp, log_type, log_msg):
1680
    """Handles a log message.
1681

1682
    """
1683
    self.feedback_fn((timestamp, log_type, log_msg))
1684

    
1685
  def ReportNotChanged(self, job_id, status):
1686
    """Called if a job hasn't changed in a while.
1687

1688
    """
1689
    # Ignore
1690

    
1691

    
1692
class StdioJobPollReportCb(JobPollReportCbBase):
1693
  def __init__(self):
1694
    """Initializes this class.
1695

1696
    """
1697
    JobPollReportCbBase.__init__(self)
1698

    
1699
    self.notified_queued = False
1700
    self.notified_waitlock = False
1701

    
1702
  def ReportLogMessage(self, job_id, serial, timestamp, log_type, log_msg):
1703
    """Handles a log message.
1704

1705
    """
1706
    ToStdout("%s %s", time.ctime(utils.MergeTime(timestamp)),
1707
             FormatLogMessage(log_type, log_msg))
1708

    
1709
  def ReportNotChanged(self, job_id, status):
1710
    """Called if a job hasn't changed in a while.
1711

1712
    """
1713
    if status is None:
1714
      return
1715

    
1716
    if status == constants.JOB_STATUS_QUEUED and not self.notified_queued:
1717
      ToStderr("Job %s is waiting in queue", job_id)
1718
      self.notified_queued = True
1719

    
1720
    elif status == constants.JOB_STATUS_WAITLOCK and not self.notified_waitlock:
1721
      ToStderr("Job %s is trying to acquire all necessary locks", job_id)
1722
      self.notified_waitlock = True
1723

    
1724

    
1725
def FormatLogMessage(log_type, log_msg):
1726
  """Formats a job message according to its type.
1727

1728
  """
1729
  if log_type != constants.ELOG_MESSAGE:
1730
    log_msg = str(log_msg)
1731

    
1732
  return utils.SafeEncode(log_msg)
1733

    
1734

    
1735
def PollJob(job_id, cl=None, feedback_fn=None, reporter=None):
1736
  """Function to poll for the result of a job.
1737

1738
  @type job_id: job identified
1739
  @param job_id: the job to poll for results
1740
  @type cl: luxi.Client
1741
  @param cl: the luxi client to use for communicating with the master;
1742
             if None, a new client will be created
1743

1744
  """
1745
  if cl is None:
1746
    cl = GetClient()
1747

    
1748
  if reporter is None:
1749
    if feedback_fn:
1750
      reporter = FeedbackFnJobPollReportCb(feedback_fn)
1751
    else:
1752
      reporter = StdioJobPollReportCb()
1753
  elif feedback_fn:
1754
    raise errors.ProgrammerError("Can't specify reporter and feedback function")
1755

    
1756
  return GenericPollJob(job_id, _LuxiJobPollCb(cl), reporter)
1757

    
1758

    
1759
def SubmitOpCode(op, cl=None, feedback_fn=None, opts=None, reporter=None):
1760
  """Legacy function to submit an opcode.
1761

1762
  This is just a simple wrapper over the construction of the processor
1763
  instance. It should be extended to better handle feedback and
1764
  interaction functions.
1765

1766
  """
1767
  if cl is None:
1768
    cl = GetClient()
1769

    
1770
  SetGenericOpcodeOpts([op], opts)
1771

    
1772
  job_id = SendJob([op], cl=cl)
1773

    
1774
  op_results = PollJob(job_id, cl=cl, feedback_fn=feedback_fn,
1775
                       reporter=reporter)
1776

    
1777
  return op_results[0]
1778

    
1779

    
1780
def SubmitOrSend(op, opts, cl=None, feedback_fn=None):
1781
  """Wrapper around SubmitOpCode or SendJob.
1782

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

1788
  It will also process the opcodes if we're sending the via SendJob
1789
  (otherwise SubmitOpCode does it).
1790

1791
  """
1792
  if opts and opts.submit_only:
1793
    job = [op]
1794
    SetGenericOpcodeOpts(job, opts)
1795
    job_id = SendJob(job, cl=cl)
1796
    raise JobSubmittedException(job_id)
1797
  else:
1798
    return SubmitOpCode(op, cl=cl, feedback_fn=feedback_fn, opts=opts)
1799

    
1800

    
1801
def SetGenericOpcodeOpts(opcode_list, options):
1802
  """Processor for generic options.
1803

1804
  This function updates the given opcodes based on generic command
1805
  line options (like debug, dry-run, etc.).
1806

1807
  @param opcode_list: list of opcodes
1808
  @param options: command line options or None
1809
  @return: None (in-place modification)
1810

1811
  """
1812
  if not options:
1813
    return
1814
  for op in opcode_list:
1815
    op.debug_level = options.debug
1816
    if hasattr(options, "dry_run"):
1817
      op.dry_run = options.dry_run
1818
    if getattr(options, "priority", None) is not None:
1819
      op.priority = _PRIONAME_TO_VALUE[options.priority]
1820

    
1821

    
1822
def GetClient():
1823
  # TODO: Cache object?
1824
  try:
1825
    client = luxi.Client()
1826
  except luxi.NoMasterError:
1827
    ss = ssconf.SimpleStore()
1828

    
1829
    # Try to read ssconf file
1830
    try:
1831
      ss.GetMasterNode()
1832
    except errors.ConfigurationError:
1833
      raise errors.OpPrereqError("Cluster not initialized or this machine is"
1834
                                 " not part of a cluster")
1835

    
1836
    master, myself = ssconf.GetMasterAndMyself(ss=ss)
1837
    if master != myself:
1838
      raise errors.OpPrereqError("This is not the master node, please connect"
1839
                                 " to node '%s' and rerun the command" %
1840
                                 master)
1841
    raise
1842
  return client
1843

    
1844

    
1845
def FormatError(err):
1846
  """Return a formatted error message for a given error.
1847

1848
  This function takes an exception instance and returns a tuple
1849
  consisting of two values: first, the recommended exit code, and
1850
  second, a string describing the error message (not
1851
  newline-terminated).
1852

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

    
1932

    
1933
def GenericMain(commands, override=None, aliases=None):
1934
  """Generic main function for all the gnt-* commands.
1935

1936
  Arguments:
1937
    - commands: a dictionary with a special structure, see the design doc
1938
                for command line handling.
1939
    - override: if not None, we expect a dictionary with keys that will
1940
                override command line options; this can be used to pass
1941
                options from the scripts to generic functions
1942
    - aliases: dictionary with command aliases {'alias': 'target, ...}
1943

1944
  """
1945
  # save the program name and the entire command line for later logging
1946
  if sys.argv:
1947
    binary = os.path.basename(sys.argv[0]) or sys.argv[0]
1948
    if len(sys.argv) >= 2:
1949
      binary += " " + sys.argv[1]
1950
      old_cmdline = " ".join(sys.argv[2:])
1951
    else:
1952
      old_cmdline = ""
1953
  else:
1954
    binary = "<unknown program>"
1955
    old_cmdline = ""
1956

    
1957
  if aliases is None:
1958
    aliases = {}
1959

    
1960
  try:
1961
    func, options, args = _ParseArgs(sys.argv, commands, aliases)
1962
  except errors.ParameterError, err:
1963
    result, err_msg = FormatError(err)
1964
    ToStderr(err_msg)
1965
    return 1
1966

    
1967
  if func is None: # parse error
1968
    return 1
1969

    
1970
  if override is not None:
1971
    for key, val in override.iteritems():
1972
      setattr(options, key, val)
1973

    
1974
  utils.SetupLogging(constants.LOG_COMMANDS, binary, debug=options.debug,
1975
                     stderr_logging=True)
1976

    
1977
  if old_cmdline:
1978
    logging.info("run with arguments '%s'", old_cmdline)
1979
  else:
1980
    logging.info("run with no arguments")
1981

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

    
2001
  return result
2002

    
2003

    
2004
def ParseNicOption(optvalue):
2005
  """Parses the value of the --net option(s).
2006

2007
  """
2008
  try:
2009
    nic_max = max(int(nidx[0]) + 1 for nidx in optvalue)
2010
  except (TypeError, ValueError), err:
2011
    raise errors.OpPrereqError("Invalid NIC index passed: %s" % str(err))
2012

    
2013
  nics = [{}] * nic_max
2014
  for nidx, ndict in optvalue:
2015
    nidx = int(nidx)
2016

    
2017
    if not isinstance(ndict, dict):
2018
      raise errors.OpPrereqError("Invalid nic/%d value: expected dict,"
2019
                                 " got %s" % (nidx, ndict))
2020

    
2021
    utils.ForceDictType(ndict, constants.INIC_PARAMS_TYPES)
2022

    
2023
    nics[nidx] = ndict
2024

    
2025
  return nics
2026

    
2027

    
2028
def GenericInstanceCreate(mode, opts, args):
2029
  """Add an instance to the cluster via either creation or import.
2030

2031
  @param mode: constants.INSTANCE_CREATE or constants.INSTANCE_IMPORT
2032
  @param opts: the command line options selected by the user
2033
  @type args: list
2034
  @param args: should contain only one element, the new instance name
2035
  @rtype: int
2036
  @return: the desired exit code
2037

2038
  """
2039
  instance = args[0]
2040

    
2041
  (pnode, snode) = SplitNodeOption(opts.node)
2042

    
2043
  hypervisor = None
2044
  hvparams = {}
2045
  if opts.hypervisor:
2046
    hypervisor, hvparams = opts.hypervisor
2047

    
2048
  if opts.nics:
2049
    nics = ParseNicOption(opts.nics)
2050
  elif opts.no_nics:
2051
    # no nics
2052
    nics = []
2053
  elif mode == constants.INSTANCE_CREATE:
2054
    # default of one nic, all auto
2055
    nics = [{}]
2056
  else:
2057
    # mode == import
2058
    nics = []
2059

    
2060
  if opts.disk_template == constants.DT_DISKLESS:
2061
    if opts.disks or opts.sd_size is not None:
2062
      raise errors.OpPrereqError("Diskless instance but disk"
2063
                                 " information passed")
2064
    disks = []
2065
  else:
2066
    if (not opts.disks and not opts.sd_size
2067
        and mode == constants.INSTANCE_CREATE):
2068
      raise errors.OpPrereqError("No disk information specified")
2069
    if opts.disks and opts.sd_size is not None:
2070
      raise errors.OpPrereqError("Please use either the '--disk' or"
2071
                                 " '-s' option")
2072
    if opts.sd_size is not None:
2073
      opts.disks = [(0, {constants.IDISK_SIZE: opts.sd_size})]
2074

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

    
2108
  utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_TYPES)
2109
  utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
2110

    
2111
  if mode == constants.INSTANCE_CREATE:
2112
    start = opts.start
2113
    os_type = opts.os
2114
    force_variant = opts.force_variant
2115
    src_node = None
2116
    src_path = None
2117
    no_install = opts.no_install
2118
    identify_defaults = False
2119
  elif mode == constants.INSTANCE_IMPORT:
2120
    start = False
2121
    os_type = None
2122
    force_variant = False
2123
    src_node = opts.src_node
2124
    src_path = opts.src_dir
2125
    no_install = None
2126
    identify_defaults = opts.identify_defaults
2127
  else:
2128
    raise errors.ProgrammerError("Invalid creation mode %s" % mode)
2129

    
2130
  op = opcodes.OpInstanceCreate(instance_name=instance,
2131
                                disks=disks,
2132
                                disk_template=opts.disk_template,
2133
                                nics=nics,
2134
                                pnode=pnode, snode=snode,
2135
                                ip_check=opts.ip_check,
2136
                                name_check=opts.name_check,
2137
                                wait_for_sync=opts.wait_for_sync,
2138
                                file_storage_dir=opts.file_storage_dir,
2139
                                file_driver=opts.file_driver,
2140
                                iallocator=opts.iallocator,
2141
                                hypervisor=hypervisor,
2142
                                hvparams=hvparams,
2143
                                beparams=opts.beparams,
2144
                                osparams=opts.osparams,
2145
                                mode=mode,
2146
                                start=start,
2147
                                os_type=os_type,
2148
                                force_variant=force_variant,
2149
                                src_node=src_node,
2150
                                src_path=src_path,
2151
                                no_install=no_install,
2152
                                identify_defaults=identify_defaults)
2153

    
2154
  SubmitOrSend(op, opts)
2155
  return 0
2156

    
2157

    
2158
class _RunWhileClusterStoppedHelper:
2159
  """Helper class for L{RunWhileClusterStopped} to simplify state management
2160

2161
  """
2162
  def __init__(self, feedback_fn, cluster_name, master_node, online_nodes):
2163
    """Initializes this class.
2164

2165
    @type feedback_fn: callable
2166
    @param feedback_fn: Feedback function
2167
    @type cluster_name: string
2168
    @param cluster_name: Cluster name
2169
    @type master_node: string
2170
    @param master_node Master node name
2171
    @type online_nodes: list
2172
    @param online_nodes: List of names of online nodes
2173

2174
    """
2175
    self.feedback_fn = feedback_fn
2176
    self.cluster_name = cluster_name
2177
    self.master_node = master_node
2178
    self.online_nodes = online_nodes
2179

    
2180
    self.ssh = ssh.SshRunner(self.cluster_name)
2181

    
2182
    self.nonmaster_nodes = [name for name in online_nodes
2183
                            if name != master_node]
2184

    
2185
    assert self.master_node not in self.nonmaster_nodes
2186

    
2187
  def _RunCmd(self, node_name, cmd):
2188
    """Runs a command on the local or a remote machine.
2189

2190
    @type node_name: string
2191
    @param node_name: Machine name
2192
    @type cmd: list
2193
    @param cmd: Command
2194

2195
    """
2196
    if node_name is None or node_name == self.master_node:
2197
      # No need to use SSH
2198
      result = utils.RunCmd(cmd)
2199
    else:
2200
      result = self.ssh.Run(node_name, "root", utils.ShellQuoteArgs(cmd))
2201

    
2202
    if result.failed:
2203
      errmsg = ["Failed to run command %s" % result.cmd]
2204
      if node_name:
2205
        errmsg.append("on node %s" % node_name)
2206
      errmsg.append(": exitcode %s and error %s" %
2207
                    (result.exit_code, result.output))
2208
      raise errors.OpExecError(" ".join(errmsg))
2209

    
2210
  def Call(self, fn, *args):
2211
    """Call function while all daemons are stopped.
2212

2213
    @type fn: callable
2214
    @param fn: Function to be called
2215

2216
    """
2217
    # Pause watcher by acquiring an exclusive lock on watcher state file
2218
    self.feedback_fn("Blocking watcher")
2219
    watcher_block = utils.FileLock.Open(constants.WATCHER_STATEFILE)
2220
    try:
2221
      # TODO: Currently, this just blocks. There's no timeout.
2222
      # TODO: Should it be a shared lock?
2223
      watcher_block.Exclusive(blocking=True)
2224

    
2225
      # Stop master daemons, so that no new jobs can come in and all running
2226
      # ones are finished
2227
      self.feedback_fn("Stopping master daemons")
2228
      self._RunCmd(None, [constants.DAEMON_UTIL, "stop-master"])
2229
      try:
2230
        # Stop daemons on all nodes
2231
        for node_name in self.online_nodes:
2232
          self.feedback_fn("Stopping daemons on %s" % node_name)
2233
          self._RunCmd(node_name, [constants.DAEMON_UTIL, "stop-all"])
2234

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

    
2252

    
2253
def RunWhileClusterStopped(feedback_fn, fn, *args):
2254
  """Calls a function while all cluster daemons are stopped.
2255

2256
  @type feedback_fn: callable
2257
  @param feedback_fn: Feedback function
2258
  @type fn: callable
2259
  @param fn: Function to be called when daemons are stopped
2260

2261
  """
2262
  feedback_fn("Gathering cluster information")
2263

    
2264
  # This ensures we're running on the master daemon
2265
  cl = GetClient()
2266

    
2267
  (cluster_name, master_node) = \
2268
    cl.QueryConfigValues(["cluster_name", "master_node"])
2269

    
2270
  online_nodes = GetOnlineNodes([], cl=cl)
2271

    
2272
  # Don't keep a reference to the client. The master daemon will go away.
2273
  del cl
2274

    
2275
  assert master_node in online_nodes
2276

    
2277
  return _RunWhileClusterStoppedHelper(feedback_fn, cluster_name, master_node,
2278
                                       online_nodes).Call(fn, *args)
2279

    
2280

    
2281
def GenerateTable(headers, fields, separator, data,
2282
                  numfields=None, unitfields=None,
2283
                  units=None):
2284
  """Prints a table with headers and different fields.
2285

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

2309
  """
2310
  if units is None:
2311
    if separator:
2312
      units = "m"
2313
    else:
2314
      units = "h"
2315

    
2316
  if numfields is None:
2317
    numfields = []
2318
  if unitfields is None:
2319
    unitfields = []
2320

    
2321
  numfields = utils.FieldSet(*numfields)   # pylint: disable-msg=W0142
2322
  unitfields = utils.FieldSet(*unitfields) # pylint: disable-msg=W0142
2323

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

    
2338
  if separator is None:
2339
    mlens = [0 for name in fields]
2340
    format_str = ' '.join(format_fields)
2341
  else:
2342
    format_str = separator.replace("%", "%%").join(format_fields)
2343

    
2344
  for row in data:
2345
    if row is None:
2346
      continue
2347
    for idx, val in enumerate(row):
2348
      if unitfields.Matches(fields[idx]):
2349
        try:
2350
          val = int(val)
2351
        except (TypeError, ValueError):
2352
          pass
2353
        else:
2354
          val = row[idx] = utils.FormatUnit(val, units)
2355
      val = row[idx] = str(val)
2356
      if separator is None:
2357
        mlens[idx] = max(mlens[idx], len(val))
2358

    
2359
  result = []
2360
  if headers:
2361
    args = []
2362
    for idx, name in enumerate(fields):
2363
      hdr = headers[name]
2364
      if separator is None:
2365
        mlens[idx] = max(mlens[idx], len(hdr))
2366
        args.append(mlens[idx])
2367
      args.append(hdr)
2368
    result.append(format_str % tuple(args))
2369

    
2370
  if separator is None:
2371
    assert len(mlens) == len(fields)
2372

    
2373
    if fields and not numfields.Matches(fields[-1]):
2374
      mlens[-1] = 0
2375

    
2376
  for line in data:
2377
    args = []
2378
    if line is None:
2379
      line = ['-' for _ in fields]
2380
    for idx in range(len(fields)):
2381
      if separator is None:
2382
        args.append(mlens[idx])
2383
      args.append(line[idx])
2384
    result.append(format_str % tuple(args))
2385

    
2386
  return result
2387

    
2388

    
2389
def _FormatBool(value):
2390
  """Formats a boolean value as a string.
2391

2392
  """
2393
  if value:
2394
    return "Y"
2395
  return "N"
2396

    
2397

    
2398
#: Default formatting for query results; (callback, align right)
2399
_DEFAULT_FORMAT_QUERY = {
2400
  constants.QFT_TEXT: (str, False),
2401
  constants.QFT_BOOL: (_FormatBool, False),
2402
  constants.QFT_NUMBER: (str, True),
2403
  constants.QFT_TIMESTAMP: (utils.FormatTime, False),
2404
  constants.QFT_OTHER: (str, False),
2405
  constants.QFT_UNKNOWN: (str, False),
2406
  }
2407

    
2408

    
2409
def _GetColumnFormatter(fdef, override, unit):
2410
  """Returns formatting function for a field.
2411

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

2422
  """
2423
  fmt = override.get(fdef.name, None)
2424
  if fmt is not None:
2425
    return fmt
2426

    
2427
  assert constants.QFT_UNIT not in _DEFAULT_FORMAT_QUERY
2428

    
2429
  if fdef.kind == constants.QFT_UNIT:
2430
    # Can't keep this information in the static dictionary
2431
    return (lambda value: utils.FormatUnit(value, unit), True)
2432

    
2433
  fmt = _DEFAULT_FORMAT_QUERY.get(fdef.kind, None)
2434
  if fmt is not None:
2435
    return fmt
2436

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

    
2439

    
2440
class _QueryColumnFormatter:
2441
  """Callable class for formatting fields of a query.
2442

2443
  """
2444
  def __init__(self, fn, status_fn, verbose):
2445
    """Initializes this class.
2446

2447
    @type fn: callable
2448
    @param fn: Formatting function
2449
    @type status_fn: callable
2450
    @param status_fn: Function to report fields' status
2451
    @type verbose: boolean
2452
    @param verbose: whether to use verbose field descriptions or not
2453

2454
    """
2455
    self._fn = fn
2456
    self._status_fn = status_fn
2457
    self._verbose = verbose
2458

    
2459
  def __call__(self, data):
2460
    """Returns a field's string representation.
2461

2462
    """
2463
    (status, value) = data
2464

    
2465
    # Report status
2466
    self._status_fn(status)
2467

    
2468
    if status == constants.RS_NORMAL:
2469
      return self._fn(value)
2470

    
2471
    assert value is None, \
2472
           "Found value %r for abnormal status %s" % (value, status)
2473

    
2474
    return FormatResultError(status, self._verbose)
2475

    
2476

    
2477
def FormatResultError(status, verbose):
2478
  """Formats result status other than L{constants.RS_NORMAL}.
2479

2480
  @param status: The result status
2481
  @type verbose: boolean
2482
  @param verbose: Whether to return the verbose text
2483
  @return: Text of result status
2484

2485
  """
2486
  assert status != constants.RS_NORMAL, \
2487
         "FormatResultError called with status equal to constants.RS_NORMAL"
2488
  try:
2489
    (verbose_text, normal_text) = constants.RSS_DESCRIPTION[status]
2490
  except KeyError:
2491
    raise NotImplementedError("Unknown status %s" % status)
2492
  else:
2493
    if verbose:
2494
      return verbose_text
2495
    return normal_text
2496

    
2497

    
2498
def FormatQueryResult(result, unit=None, format_override=None, separator=None,
2499
                      header=False, verbose=False):
2500
  """Formats data in L{objects.QueryResponse}.
2501

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

2517
  """
2518
  if unit is None:
2519
    if separator:
2520
      unit = "m"
2521
    else:
2522
      unit = "h"
2523

    
2524
  if format_override is None:
2525
    format_override = {}
2526

    
2527
  stats = dict.fromkeys(constants.RS_ALL, 0)
2528

    
2529
  def _RecordStatus(status):
2530
    if status in stats:
2531
      stats[status] += 1
2532

    
2533
  columns = []
2534
  for fdef in result.fields:
2535
    assert fdef.title and fdef.name
2536
    (fn, align_right) = _GetColumnFormatter(fdef, format_override, unit)
2537
    columns.append(TableColumn(fdef.title,
2538
                               _QueryColumnFormatter(fn, _RecordStatus,
2539
                                                     verbose),
2540
                               align_right))
2541

    
2542
  table = FormatTable(result.data, columns, header, separator)
2543

    
2544
  # Collect statistics
2545
  assert len(stats) == len(constants.RS_ALL)
2546
  assert compat.all(count >= 0 for count in stats.values())
2547

    
2548
  # Determine overall status. If there was no data, unknown fields must be
2549
  # detected via the field definitions.
2550
  if (stats[constants.RS_UNKNOWN] or
2551
      (not result.data and _GetUnknownFields(result.fields))):
2552
    status = QR_UNKNOWN
2553
  elif compat.any(count > 0 for key, count in stats.items()
2554
                  if key != constants.RS_NORMAL):
2555
    status = QR_INCOMPLETE
2556
  else:
2557
    status = QR_NORMAL
2558

    
2559
  return (status, table)
2560

    
2561

    
2562
def _GetUnknownFields(fdefs):
2563
  """Returns list of unknown fields included in C{fdefs}.
2564

2565
  @type fdefs: list of L{objects.QueryFieldDefinition}
2566

2567
  """
2568
  return [fdef for fdef in fdefs
2569
          if fdef.kind == constants.QFT_UNKNOWN]
2570

    
2571

    
2572
def _WarnUnknownFields(fdefs):
2573
  """Prints a warning to stderr if a query included unknown fields.
2574

2575
  @type fdefs: list of L{objects.QueryFieldDefinition}
2576

2577
  """
2578
  unknown = _GetUnknownFields(fdefs)
2579
  if unknown:
2580
    ToStderr("Warning: Queried for unknown fields %s",
2581
             utils.CommaJoin(fdef.name for fdef in unknown))
2582
    return True
2583

    
2584
  return False
2585

    
2586

    
2587
def GenericList(resource, fields, names, unit, separator, header, cl=None,
2588
                format_override=None, verbose=False, force_filter=False):
2589
  """Generic implementation for listing all items of a resource.
2590

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

2612
  """
2613
  if cl is None:
2614
    cl = GetClient()
2615

    
2616
  if not names:
2617
    names = None
2618

    
2619
  if (force_filter or
2620
      (names and len(names) == 1 and qlang.MaybeFilter(names[0]))):
2621
    try:
2622
      (filter_text, ) = names
2623
    except ValueError:
2624
      raise errors.OpPrereqError("Exactly one argument must be given as a"
2625
                                 " filter")
2626

    
2627
    logging.debug("Parsing '%s' as filter", filter_text)
2628
    filter_ = qlang.ParseFilter(filter_text)
2629
  else:
2630
    filter_ = qlang.MakeSimpleFilter("name", names)
2631

    
2632
  response = cl.Query(resource, fields, filter_)
2633

    
2634
  found_unknown = _WarnUnknownFields(response.fields)
2635

    
2636
  (status, data) = FormatQueryResult(response, unit=unit, separator=separator,
2637
                                     header=header,
2638
                                     format_override=format_override,
2639
                                     verbose=verbose)
2640

    
2641
  for line in data:
2642
    ToStdout(line)
2643

    
2644
  assert ((found_unknown and status == QR_UNKNOWN) or
2645
          (not found_unknown and status != QR_UNKNOWN))
2646

    
2647
  if status == QR_UNKNOWN:
2648
    return constants.EXIT_UNKNOWN_FIELD
2649

    
2650
  # TODO: Should the list command fail if not all data could be collected?
2651
  return constants.EXIT_SUCCESS
2652

    
2653

    
2654
def GenericListFields(resource, fields, separator, header, cl=None):
2655
  """Generic implementation for listing fields for a resource.
2656

2657
  @param resource: One of L{constants.QR_VIA_LUXI}
2658
  @type fields: list of strings
2659
  @param fields: List of fields to query for
2660
  @type separator: string or None
2661
  @param separator: String used to separate fields
2662
  @type header: bool
2663
  @param header: Whether to show header row
2664

2665
  """
2666
  if cl is None:
2667
    cl = GetClient()
2668

    
2669
  if not fields:
2670
    fields = None
2671

    
2672
  response = cl.QueryFields(resource, fields)
2673

    
2674
  found_unknown = _WarnUnknownFields(response.fields)
2675

    
2676
  columns = [
2677
    TableColumn("Name", str, False),
2678
    TableColumn("Title", str, False),
2679
    TableColumn("Description", str, False),
2680
    ]
2681

    
2682
  rows = [[fdef.name, fdef.title, fdef.doc] for fdef in response.fields]
2683

    
2684
  for line in FormatTable(rows, columns, header, separator):
2685
    ToStdout(line)
2686

    
2687
  if found_unknown:
2688
    return constants.EXIT_UNKNOWN_FIELD
2689

    
2690
  return constants.EXIT_SUCCESS
2691

    
2692

    
2693
class TableColumn:
2694
  """Describes a column for L{FormatTable}.
2695

2696
  """
2697
  def __init__(self, title, fn, align_right):
2698
    """Initializes this class.
2699

2700
    @type title: string
2701
    @param title: Column title
2702
    @type fn: callable
2703
    @param fn: Formatting function
2704
    @type align_right: bool
2705
    @param align_right: Whether to align values on the right-hand side
2706

2707
    """
2708
    self.title = title
2709
    self.format = fn
2710
    self.align_right = align_right
2711

    
2712

    
2713
def _GetColFormatString(width, align_right):
2714
  """Returns the format string for a field.
2715

2716
  """
2717
  if align_right:
2718
    sign = ""
2719
  else:
2720
    sign = "-"
2721

    
2722
  return "%%%s%ss" % (sign, width)
2723

    
2724

    
2725
def FormatTable(rows, columns, header, separator):
2726
  """Formats data as a table.
2727

2728
  @type rows: list of lists
2729
  @param rows: Row data, one list per row
2730
  @type columns: list of L{TableColumn}
2731
  @param columns: Column descriptions
2732
  @type header: bool
2733
  @param header: Whether to show header row
2734
  @type separator: string or None
2735
  @param separator: String used to separate columns
2736

2737
  """
2738
  if header:
2739
    data = [[col.title for col in columns]]
2740
    colwidth = [len(col.title) for col in columns]
2741
  else:
2742
    data = []
2743
    colwidth = [0 for _ in columns]
2744

    
2745
  # Format row data
2746
  for row in rows:
2747
    assert len(row) == len(columns)
2748

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

    
2751
    if separator is None:
2752
      # Update column widths
2753
      for idx, (oldwidth, value) in enumerate(zip(colwidth, formatted)):
2754
        # Modifying a list's items while iterating is fine
2755
        colwidth[idx] = max(oldwidth, len(value))
2756

    
2757
    data.append(formatted)
2758

    
2759
  if separator is not None:
2760
    # Return early if a separator is used
2761
    return [separator.join(row) for row in data]
2762

    
2763
  if columns and not columns[-1].align_right:
2764
    # Avoid unnecessary spaces at end of line
2765
    colwidth[-1] = 0
2766

    
2767
  # Build format string
2768
  fmt = " ".join([_GetColFormatString(width, col.align_right)
2769
                  for col, width in zip(columns, colwidth)])
2770

    
2771
  return [fmt % tuple(row) for row in data]
2772

    
2773

    
2774
def FormatTimestamp(ts):
2775
  """Formats a given timestamp.
2776

2777
  @type ts: timestamp
2778
  @param ts: a timeval-type timestamp, a tuple of seconds and microseconds
2779

2780
  @rtype: string
2781
  @return: a string with the formatted timestamp
2782

2783
  """
2784
  if not isinstance (ts, (tuple, list)) or len(ts) != 2:
2785
    return '?'
2786
  sec, usec = ts
2787
  return time.strftime("%F %T", time.localtime(sec)) + ".%06d" % usec
2788

    
2789

    
2790
def ParseTimespec(value):
2791
  """Parse a time specification.
2792

2793
  The following suffixed will be recognized:
2794

2795
    - s: seconds
2796
    - m: minutes
2797
    - h: hours
2798
    - d: day
2799
    - w: weeks
2800

2801
  Without any suffix, the value will be taken to be in seconds.
2802

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

    
2831

    
2832
def GetOnlineNodes(nodes, cl=None, nowarn=False, secondary_ips=False,
2833
                   filter_master=False):
2834
  """Returns the names of online nodes.
2835

2836
  This function will also log a warning on stderr with the names of
2837
  the online nodes.
2838

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

2855
  """
2856
  if cl is None:
2857
    cl = GetClient()
2858

    
2859
  if secondary_ips:
2860
    name_idx = 2
2861
  else:
2862
    name_idx = 0
2863

    
2864
  if filter_master:
2865
    master_node = cl.QueryConfigValues(["master_node"])[0]
2866
    filter_fn = lambda x: x != master_node
2867
  else:
2868
    filter_fn = lambda _: True
2869

    
2870
  result = cl.QueryNodes(names=nodes, fields=["name", "offline", "sip"],
2871
                         use_locking=False)
2872
  offline = [row[0] for row in result if row[1]]
2873
  if offline and not nowarn:
2874
    ToStderr("Note: skipping offline node(s): %s" % utils.CommaJoin(offline))
2875
  return [row[name_idx] for row in result if not row[1] and filter_fn(row[0])]
2876

    
2877

    
2878
def _ToStream(stream, txt, *args):
2879
  """Write a message to a stream, bypassing the logging system
2880

2881
  @type stream: file object
2882
  @param stream: the file to which we should write
2883
  @type txt: str
2884
  @param txt: the message
2885

2886
  """
2887
  try:
2888
    if args:
2889
      args = tuple(args)
2890
      stream.write(txt % args)
2891
    else:
2892
      stream.write(txt)
2893
    stream.write('\n')
2894
    stream.flush()
2895
  except IOError, err:
2896
    if err.errno == errno.EPIPE:
2897
      # our terminal went away, we'll exit
2898
      sys.exit(constants.EXIT_FAILURE)
2899
    else:
2900
      raise
2901

    
2902

    
2903
def ToStdout(txt, *args):
2904
  """Write a message to stdout only, bypassing the logging system
2905

2906
  This is just a wrapper over _ToStream.
2907

2908
  @type txt: str
2909
  @param txt: the message
2910

2911
  """
2912
  _ToStream(sys.stdout, txt, *args)
2913

    
2914

    
2915
def ToStderr(txt, *args):
2916
  """Write a message to stderr only, bypassing the logging system
2917

2918
  This is just a wrapper over _ToStream.
2919

2920
  @type txt: str
2921
  @param txt: the message
2922

2923
  """
2924
  _ToStream(sys.stderr, txt, *args)
2925

    
2926

    
2927
class JobExecutor(object):
2928
  """Class which manages the submission and execution of multiple jobs.
2929

2930
  Note that instances of this class should not be reused between
2931
  GetResults() calls.
2932

2933
  """
2934
  def __init__(self, cl=None, verbose=True, opts=None, feedback_fn=None):
2935
    self.queue = []
2936
    if cl is None:
2937
      cl = GetClient()
2938
    self.cl = cl
2939
    self.verbose = verbose
2940
    self.jobs = []
2941
    self.opts = opts
2942
    self.feedback_fn = feedback_fn
2943

    
2944
  def QueueJob(self, name, *ops):
2945
    """Record a job for later submit.
2946

2947
    @type name: string
2948
    @param name: a description of the job, will be used in WaitJobSet
2949
    """
2950
    SetGenericOpcodeOpts(ops, self.opts)
2951
    self.queue.append((name, ops))
2952

    
2953
  def SubmitPending(self, each=False):
2954
    """Submit all pending jobs.
2955

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

    
2969
  def _ChooseJob(self):
2970
    """Choose a non-waiting/queued job to poll next.
2971

2972
    """
2973
    assert self.jobs, "_ChooseJob called with empty job list"
2974

    
2975
    result = self.cl.QueryJobs([i[2] for i in self.jobs], ["status"])
2976
    assert result
2977

    
2978
    for job_data, status in zip(self.jobs, result):
2979
      if (isinstance(status, list) and status and
2980
          status[0] in (constants.JOB_STATUS_QUEUED,
2981
                        constants.JOB_STATUS_WAITLOCK,
2982
                        constants.JOB_STATUS_CANCELING)):
2983
        # job is still present and waiting
2984
        continue
2985
      # good candidate found (either running job or lost job)
2986
      self.jobs.remove(job_data)
2987
      return job_data
2988

    
2989
    # no job found
2990
    return self.jobs.pop(0)
2991

    
2992
  def GetResults(self):
2993
    """Wait for and return the results of all jobs.
2994

2995
    @rtype: list
2996
    @return: list of tuples (success, job results), in the same order
2997
        as the submitted jobs; if a job has failed, instead of the result
2998
        there will be the error message
2999

3000
    """
3001
    if not self.jobs:
3002
      self.SubmitPending()
3003
    results = []
3004
    if self.verbose:
3005
      ok_jobs = [row[2] for row in self.jobs if row[1]]
3006
      if ok_jobs:
3007
        ToStdout("Submitted jobs %s", utils.CommaJoin(ok_jobs))
3008

    
3009
    # first, remove any non-submitted jobs
3010
    self.jobs, failures = compat.partition(self.jobs, lambda x: x[1])
3011
    for idx, _, jid, name in failures:
3012
      ToStderr("Failed to submit job for %s: %s", name, jid)
3013
      results.append((idx, False, jid))
3014

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

    
3032
      results.append((idx, success, job_result))
3033

    
3034
    # sort based on the index, then drop it
3035
    results.sort()
3036
    results = [i[1:] for i in results]
3037

    
3038
    return results
3039

    
3040
  def WaitOrShow(self, wait):
3041
    """Wait for job results or only print the job IDs.
3042

3043
    @type wait: boolean
3044
    @param wait: whether to wait or not
3045

3046
    """
3047
    if wait:
3048
      return self.GetResults()
3049
    else:
3050
      if not self.jobs:
3051
        self.SubmitPending()
3052
      for _, status, result, name in self.jobs:
3053
        if status:
3054
          ToStdout("%s: %s", result, name)
3055
        else:
3056
          ToStderr("Failure for %s: %s", name, result)
3057
      return [row[1:3] for row in self.jobs]
3058

    
3059

    
3060
def FormatParameterDict(buf, param_dict, actual, level=1):
3061
  """Formats a parameter dictionary.
3062

3063
  @type buf: L{StringIO}
3064
  @param buf: the buffer into which to write
3065
  @type param_dict: dict
3066
  @param param_dict: the own parameters
3067
  @type actual: dict
3068
  @param actual: the current parameter set (including defaults)
3069
  @param level: Level of indent
3070

3071
  """
3072
  indent = "  " * level
3073
  for key in sorted(actual):
3074
    val = param_dict.get(key, "default (%s)" % actual[key])
3075
    buf.write("%s- %s: %s\n" % (indent, key, val))
3076

    
3077

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

3081
  This function is used to request confirmation for doing an operation
3082
  on a given list of list_type.
3083

3084
  @type names: list
3085
  @param names: the list of names that we display when
3086
      we ask for confirmation
3087
  @type list_type: str
3088
  @param list_type: Human readable name for elements in the list (e.g. nodes)
3089
  @type text: str
3090
  @param text: the operation that the user should confirm
3091
  @rtype: boolean
3092
  @return: True or False depending on user's confirmation.
3093

3094
  """
3095
  count = len(names)
3096
  msg = ("The %s will operate on %d %s.\n%s"
3097
         "Do you want to continue?" % (text, count, list_type, extra))
3098
  affected = (("\nAffected %s:\n" % list_type) +
3099
              "\n".join(["  %s" % name for name in names]))
3100

    
3101
  choices = [("y", True, "Yes, execute the %s" % text),
3102
             ("n", False, "No, abort the %s" % text)]
3103

    
3104
  if count > 20:
3105
    choices.insert(1, ("v", "v", "View the list of affected %s" % list_type))
3106
    question = msg
3107
  else:
3108
    question = msg + affected
3109

    
3110
  choice = AskUser(question, choices)
3111
  if choice == "v":
3112
    choices.pop(1)
3113
    choice = AskUser(msg + affected, choices)
3114
  return choice