Statistics
| Branch: | Tag: | Revision:

root / lib / cli.py @ 323f9095

History | View | Annotate | Download (102.1 kB)

1
#
2
#
3

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

    
21

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

    
24

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

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

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

    
49

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

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

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

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

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

    
259

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

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

    
269

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

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

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

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

    
285

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

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

292
  """
293

    
294

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

298
  """
299

    
300

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

304
  """
305

    
306

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

310
  """
311

    
312

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

316
  """
317

    
318

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

322
  """
323

    
324

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

328
  """
329

    
330

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

334
  """
335

    
336

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

340
  """
341

    
342

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

346
  """
347

    
348

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

    
359

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

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

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

    
382

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

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

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

    
411

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

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

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

    
429

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

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

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

    
446

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

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

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

    
463

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

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

    
473

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

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

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

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

    
510

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

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

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

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

    
538

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

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

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

    
547

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

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

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

    
562

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

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

    
583

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

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

    
603

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

    
607

    
608
_YORNO = "yes|no"
609

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1231

    
1232
#: Options provided by all commands
1233
COMMON_OPTS = [DEBUG_OPT]
1234

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

    
1259

    
1260
def _ParseArgs(argv, commands, aliases):
1261
  """Parser for the command line arguments.
1262

1263
  This function parses the arguments and returns the function which
1264
  must be executed together with its (modified) arguments.
1265

1266
  @param argv: the command line
1267
  @param commands: dictionary with special contents, see the design
1268
      doc for cmdline handling
1269
  @param aliases: dictionary with command aliases {'alias': 'target, ...}
1270

1271
  """
1272
  if len(argv) == 0:
1273
    binary = "<command>"
1274
  else:
1275
    binary = argv[0].split("/")[-1]
1276

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

    
1284
  if len(argv) < 2 or not (argv[1] in commands or
1285
                           argv[1] in aliases):
1286
    # let's do a nice thing
1287
    sortedcmds = commands.keys()
1288
    sortedcmds.sort()
1289

    
1290
    ToStdout("Usage: %s {command} [options...] [argument...]", binary)
1291
    ToStdout("%s <command> --help to see details, or man %s", binary, binary)
1292
    ToStdout("")
1293

    
1294
    # compute the max line length for cmd + usage
1295
    mlen = max([len(" %s" % cmd) for cmd in commands])
1296
    mlen = min(60, mlen) # should not get here...
1297

    
1298
    # and format a nice command list
1299
    ToStdout("Commands:")
1300
    for cmd in sortedcmds:
1301
      cmdstr = " %s" % (cmd,)
1302
      help_text = commands[cmd][4]
1303
      help_lines = textwrap.wrap(help_text, 79 - 3 - mlen)
1304
      ToStdout("%-*s - %s", mlen, cmdstr, help_lines.pop(0))
1305
      for line in help_lines:
1306
        ToStdout("%-*s   %s", mlen, "", line)
1307

    
1308
    ToStdout("")
1309

    
1310
    return None, None, None
1311

    
1312
  # get command, unalias it, and look it up in commands
1313
  cmd = argv.pop(1)
1314
  if cmd in aliases:
1315
    if cmd in commands:
1316
      raise errors.ProgrammerError("Alias '%s' overrides an existing"
1317
                                   " command" % cmd)
1318

    
1319
    if aliases[cmd] not in commands:
1320
      raise errors.ProgrammerError("Alias '%s' maps to non-existing"
1321
                                   " command '%s'" % (cmd, aliases[cmd]))
1322

    
1323
    cmd = aliases[cmd]
1324

    
1325
  func, args_def, parser_opts, usage, description = commands[cmd]
1326
  parser = OptionParser(option_list=parser_opts + COMMON_OPTS,
1327
                        description=description,
1328
                        formatter=TitledHelpFormatter(),
1329
                        usage="%%prog %s %s" % (cmd, usage))
1330
  parser.disable_interspersed_args()
1331
  options, args = parser.parse_args()
1332

    
1333
  if not _CheckArguments(cmd, args_def, args):
1334
    return None, None, None
1335

    
1336
  return func, options, args
1337

    
1338

    
1339
def _CheckArguments(cmd, args_def, args):
1340
  """Verifies the arguments using the argument definition.
1341

1342
  Algorithm:
1343

1344
    1. Abort with error if values specified by user but none expected.
1345

1346
    1. For each argument in definition
1347

1348
      1. Keep running count of minimum number of values (min_count)
1349
      1. Keep running count of maximum number of values (max_count)
1350
      1. If it has an unlimited number of values
1351

1352
        1. Abort with error if it's not the last argument in the definition
1353

1354
    1. If last argument has limited number of values
1355

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

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

1360
  """
1361
  if args and not args_def:
1362
    ToStderr("Error: Command %s expects no arguments", cmd)
1363
    return False
1364

    
1365
  min_count = None
1366
  max_count = None
1367
  check_max = None
1368

    
1369
  last_idx = len(args_def) - 1
1370

    
1371
  for idx, arg in enumerate(args_def):
1372
    if min_count is None:
1373
      min_count = arg.min
1374
    elif arg.min is not None:
1375
      min_count += arg.min
1376

    
1377
    if max_count is None:
1378
      max_count = arg.max
1379
    elif arg.max is not None:
1380
      max_count += arg.max
1381

    
1382
    if idx == last_idx:
1383
      check_max = (arg.max is not None)
1384

    
1385
    elif arg.max is None:
1386
      raise errors.ProgrammerError("Only the last argument can have max=None")
1387

    
1388
  if check_max:
1389
    # Command with exact number of arguments
1390
    if (min_count is not None and max_count is not None and
1391
        min_count == max_count and len(args) != min_count):
1392
      ToStderr("Error: Command %s expects %d argument(s)", cmd, min_count)
1393
      return False
1394

    
1395
    # Command with limited number of arguments
1396
    if max_count is not None and len(args) > max_count:
1397
      ToStderr("Error: Command %s expects only %d argument(s)",
1398
               cmd, max_count)
1399
      return False
1400

    
1401
  # Command with some required arguments
1402
  if min_count is not None and len(args) < min_count:
1403
    ToStderr("Error: Command %s expects at least %d argument(s)",
1404
             cmd, min_count)
1405
    return False
1406

    
1407
  return True
1408

    
1409

    
1410
def SplitNodeOption(value):
1411
  """Splits the value of a --node option.
1412

1413
  """
1414
  if value and ':' in value:
1415
    return value.split(':', 1)
1416
  else:
1417
    return (value, None)
1418

    
1419

    
1420
def CalculateOSNames(os_name, os_variants):
1421
  """Calculates all the names an OS can be called, according to its variants.
1422

1423
  @type os_name: string
1424
  @param os_name: base name of the os
1425
  @type os_variants: list or None
1426
  @param os_variants: list of supported variants
1427
  @rtype: list
1428
  @return: list of valid names
1429

1430
  """
1431
  if os_variants:
1432
    return ['%s+%s' % (os_name, v) for v in os_variants]
1433
  else:
1434
    return [os_name]
1435

    
1436

    
1437
def ParseFields(selected, default):
1438
  """Parses the values of "--field"-like options.
1439

1440
  @type selected: string or None
1441
  @param selected: User-selected options
1442
  @type default: list
1443
  @param default: Default fields
1444

1445
  """
1446
  if selected is None:
1447
    return default
1448

    
1449
  if selected.startswith("+"):
1450
    return default + selected[1:].split(",")
1451

    
1452
  return selected.split(",")
1453

    
1454

    
1455
UsesRPC = rpc.RunWithRPC
1456

    
1457

    
1458
def AskUser(text, choices=None):
1459
  """Ask the user a question.
1460

1461
  @param text: the question to ask
1462

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

1468
  @return: one of the return values from the choices list; if input is
1469
      not possible (i.e. not running with a tty, we return the last
1470
      entry from the list
1471

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

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

    
1514

    
1515
class JobSubmittedException(Exception):
1516
  """Job was submitted, client should exit.
1517

1518
  This exception has one argument, the ID of the job that was
1519
  submitted. The handler should print this ID.
1520

1521
  This is not an error, just a structured way to exit from clients.
1522

1523
  """
1524

    
1525

    
1526
def SendJob(ops, cl=None):
1527
  """Function to submit an opcode without waiting for the results.
1528

1529
  @type ops: list
1530
  @param ops: list of opcodes
1531
  @type cl: luxi.Client
1532
  @param cl: the luxi client to use for communicating with the master;
1533
             if None, a new client will be created
1534

1535
  """
1536
  if cl is None:
1537
    cl = GetClient()
1538

    
1539
  job_id = cl.SubmitJob(ops)
1540

    
1541
  return job_id
1542

    
1543

    
1544
def GenericPollJob(job_id, cbs, report_cbs):
1545
  """Generic job-polling function.
1546

1547
  @type job_id: number
1548
  @param job_id: Job ID
1549
  @type cbs: Instance of L{JobPollCbBase}
1550
  @param cbs: Data callbacks
1551
  @type report_cbs: Instance of L{JobPollReportCbBase}
1552
  @param report_cbs: Reporting callbacks
1553

1554
  """
1555
  prev_job_info = None
1556
  prev_logmsg_serial = None
1557

    
1558
  status = None
1559

    
1560
  while True:
1561
    result = cbs.WaitForJobChangeOnce(job_id, ["status"], prev_job_info,
1562
                                      prev_logmsg_serial)
1563
    if not result:
1564
      # job not found, go away!
1565
      raise errors.JobLost("Job with id %s lost" % job_id)
1566

    
1567
    if result == constants.JOB_NOTCHANGED:
1568
      report_cbs.ReportNotChanged(job_id, status)
1569

    
1570
      # Wait again
1571
      continue
1572

    
1573
    # Split result, a tuple of (field values, log entries)
1574
    (job_info, log_entries) = result
1575
    (status, ) = job_info
1576

    
1577
    if log_entries:
1578
      for log_entry in log_entries:
1579
        (serial, timestamp, log_type, message) = log_entry
1580
        report_cbs.ReportLogMessage(job_id, serial, timestamp,
1581
                                    log_type, message)
1582
        prev_logmsg_serial = max(prev_logmsg_serial, serial)
1583

    
1584
    # TODO: Handle canceled and archived jobs
1585
    elif status in (constants.JOB_STATUS_SUCCESS,
1586
                    constants.JOB_STATUS_ERROR,
1587
                    constants.JOB_STATUS_CANCELING,
1588
                    constants.JOB_STATUS_CANCELED):
1589
      break
1590

    
1591
    prev_job_info = job_info
1592

    
1593
  jobs = cbs.QueryJobs([job_id], ["status", "opstatus", "opresult"])
1594
  if not jobs:
1595
    raise errors.JobLost("Job with id %s lost" % job_id)
1596

    
1597
  status, opstatus, result = jobs[0]
1598

    
1599
  if status == constants.JOB_STATUS_SUCCESS:
1600
    return result
1601

    
1602
  if status in (constants.JOB_STATUS_CANCELING, constants.JOB_STATUS_CANCELED):
1603
    raise errors.OpExecError("Job was canceled")
1604

    
1605
  has_ok = False
1606
  for idx, (status, msg) in enumerate(zip(opstatus, result)):
1607
    if status == constants.OP_STATUS_SUCCESS:
1608
      has_ok = True
1609
    elif status == constants.OP_STATUS_ERROR:
1610
      errors.MaybeRaise(msg)
1611

    
1612
      if has_ok:
1613
        raise errors.OpExecError("partial failure (opcode %d): %s" %
1614
                                 (idx, msg))
1615

    
1616
      raise errors.OpExecError(str(msg))
1617

    
1618
  # default failure mode
1619
  raise errors.OpExecError(result)
1620

    
1621

    
1622
class JobPollCbBase:
1623
  """Base class for L{GenericPollJob} callbacks.
1624

1625
  """
1626
  def __init__(self):
1627
    """Initializes this class.
1628

1629
    """
1630

    
1631
  def WaitForJobChangeOnce(self, job_id, fields,
1632
                           prev_job_info, prev_log_serial):
1633
    """Waits for changes on a job.
1634

1635
    """
1636
    raise NotImplementedError()
1637

    
1638
  def QueryJobs(self, job_ids, fields):
1639
    """Returns the selected fields for the selected job IDs.
1640

1641
    @type job_ids: list of numbers
1642
    @param job_ids: Job IDs
1643
    @type fields: list of strings
1644
    @param fields: Fields
1645

1646
    """
1647
    raise NotImplementedError()
1648

    
1649

    
1650
class JobPollReportCbBase:
1651
  """Base class for L{GenericPollJob} reporting callbacks.
1652

1653
  """
1654
  def __init__(self):
1655
    """Initializes this class.
1656

1657
    """
1658

    
1659
  def ReportLogMessage(self, job_id, serial, timestamp, log_type, log_msg):
1660
    """Handles a log message.
1661

1662
    """
1663
    raise NotImplementedError()
1664

    
1665
  def ReportNotChanged(self, job_id, status):
1666
    """Called for if a job hasn't changed in a while.
1667

1668
    @type job_id: number
1669
    @param job_id: Job ID
1670
    @type status: string or None
1671
    @param status: Job status if available
1672

1673
    """
1674
    raise NotImplementedError()
1675

    
1676

    
1677
class _LuxiJobPollCb(JobPollCbBase):
1678
  def __init__(self, cl):
1679
    """Initializes this class.
1680

1681
    """
1682
    JobPollCbBase.__init__(self)
1683
    self.cl = cl
1684

    
1685
  def WaitForJobChangeOnce(self, job_id, fields,
1686
                           prev_job_info, prev_log_serial):
1687
    """Waits for changes on a job.
1688

1689
    """
1690
    return self.cl.WaitForJobChangeOnce(job_id, fields,
1691
                                        prev_job_info, prev_log_serial)
1692

    
1693
  def QueryJobs(self, job_ids, fields):
1694
    """Returns the selected fields for the selected job IDs.
1695

1696
    """
1697
    return self.cl.QueryJobs(job_ids, fields)
1698

    
1699

    
1700
class FeedbackFnJobPollReportCb(JobPollReportCbBase):
1701
  def __init__(self, feedback_fn):
1702
    """Initializes this class.
1703

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

    
1707
    self.feedback_fn = feedback_fn
1708

    
1709
    assert callable(feedback_fn)
1710

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

1714
    """
1715
    self.feedback_fn((timestamp, log_type, log_msg))
1716

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

1720
    """
1721
    # Ignore
1722

    
1723

    
1724
class StdioJobPollReportCb(JobPollReportCbBase):
1725
  def __init__(self):
1726
    """Initializes this class.
1727

1728
    """
1729
    JobPollReportCbBase.__init__(self)
1730

    
1731
    self.notified_queued = False
1732
    self.notified_waitlock = False
1733

    
1734
  def ReportLogMessage(self, job_id, serial, timestamp, log_type, log_msg):
1735
    """Handles a log message.
1736

1737
    """
1738
    ToStdout("%s %s", time.ctime(utils.MergeTime(timestamp)),
1739
             FormatLogMessage(log_type, log_msg))
1740

    
1741
  def ReportNotChanged(self, job_id, status):
1742
    """Called if a job hasn't changed in a while.
1743

1744
    """
1745
    if status is None:
1746
      return
1747

    
1748
    if status == constants.JOB_STATUS_QUEUED and not self.notified_queued:
1749
      ToStderr("Job %s is waiting in queue", job_id)
1750
      self.notified_queued = True
1751

    
1752
    elif status == constants.JOB_STATUS_WAITLOCK and not self.notified_waitlock:
1753
      ToStderr("Job %s is trying to acquire all necessary locks", job_id)
1754
      self.notified_waitlock = True
1755

    
1756

    
1757
def FormatLogMessage(log_type, log_msg):
1758
  """Formats a job message according to its type.
1759

1760
  """
1761
  if log_type != constants.ELOG_MESSAGE:
1762
    log_msg = str(log_msg)
1763

    
1764
  return utils.SafeEncode(log_msg)
1765

    
1766

    
1767
def PollJob(job_id, cl=None, feedback_fn=None, reporter=None):
1768
  """Function to poll for the result of a job.
1769

1770
  @type job_id: job identified
1771
  @param job_id: the job to poll for results
1772
  @type cl: luxi.Client
1773
  @param cl: the luxi client to use for communicating with the master;
1774
             if None, a new client will be created
1775

1776
  """
1777
  if cl is None:
1778
    cl = GetClient()
1779

    
1780
  if reporter is None:
1781
    if feedback_fn:
1782
      reporter = FeedbackFnJobPollReportCb(feedback_fn)
1783
    else:
1784
      reporter = StdioJobPollReportCb()
1785
  elif feedback_fn:
1786
    raise errors.ProgrammerError("Can't specify reporter and feedback function")
1787

    
1788
  return GenericPollJob(job_id, _LuxiJobPollCb(cl), reporter)
1789

    
1790

    
1791
def SubmitOpCode(op, cl=None, feedback_fn=None, opts=None, reporter=None):
1792
  """Legacy function to submit an opcode.
1793

1794
  This is just a simple wrapper over the construction of the processor
1795
  instance. It should be extended to better handle feedback and
1796
  interaction functions.
1797

1798
  """
1799
  if cl is None:
1800
    cl = GetClient()
1801

    
1802
  SetGenericOpcodeOpts([op], opts)
1803

    
1804
  job_id = SendJob([op], cl=cl)
1805

    
1806
  op_results = PollJob(job_id, cl=cl, feedback_fn=feedback_fn,
1807
                       reporter=reporter)
1808

    
1809
  return op_results[0]
1810

    
1811

    
1812
def SubmitOrSend(op, opts, cl=None, feedback_fn=None):
1813
  """Wrapper around SubmitOpCode or SendJob.
1814

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

1820
  It will also process the opcodes if we're sending the via SendJob
1821
  (otherwise SubmitOpCode does it).
1822

1823
  """
1824
  if opts and opts.submit_only:
1825
    job = [op]
1826
    SetGenericOpcodeOpts(job, opts)
1827
    job_id = SendJob(job, cl=cl)
1828
    raise JobSubmittedException(job_id)
1829
  else:
1830
    return SubmitOpCode(op, cl=cl, feedback_fn=feedback_fn, opts=opts)
1831

    
1832

    
1833
def SetGenericOpcodeOpts(opcode_list, options):
1834
  """Processor for generic options.
1835

1836
  This function updates the given opcodes based on generic command
1837
  line options (like debug, dry-run, etc.).
1838

1839
  @param opcode_list: list of opcodes
1840
  @param options: command line options or None
1841
  @return: None (in-place modification)
1842

1843
  """
1844
  if not options:
1845
    return
1846
  for op in opcode_list:
1847
    op.debug_level = options.debug
1848
    if hasattr(options, "dry_run"):
1849
      op.dry_run = options.dry_run
1850
    if getattr(options, "priority", None) is not None:
1851
      op.priority = _PRIONAME_TO_VALUE[options.priority]
1852

    
1853

    
1854
def GetClient():
1855
  # TODO: Cache object?
1856
  try:
1857
    client = luxi.Client()
1858
  except luxi.NoMasterError:
1859
    ss = ssconf.SimpleStore()
1860

    
1861
    # Try to read ssconf file
1862
    try:
1863
      ss.GetMasterNode()
1864
    except errors.ConfigurationError:
1865
      raise errors.OpPrereqError("Cluster not initialized or this machine is"
1866
                                 " not part of a cluster")
1867

    
1868
    master, myself = ssconf.GetMasterAndMyself(ss=ss)
1869
    if master != myself:
1870
      raise errors.OpPrereqError("This is not the master node, please connect"
1871
                                 " to node '%s' and rerun the command" %
1872
                                 master)
1873
    raise
1874
  return client
1875

    
1876

    
1877
def FormatError(err):
1878
  """Return a formatted error message for a given error.
1879

1880
  This function takes an exception instance and returns a tuple
1881
  consisting of two values: first, the recommended exit code, and
1882
  second, a string describing the error message (not
1883
  newline-terminated).
1884

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

    
1964

    
1965
def GenericMain(commands, override=None, aliases=None):
1966
  """Generic main function for all the gnt-* commands.
1967

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

1976
  """
1977
  # save the program name and the entire command line for later logging
1978
  if sys.argv:
1979
    binary = os.path.basename(sys.argv[0]) or sys.argv[0]
1980
    if len(sys.argv) >= 2:
1981
      binary += " " + sys.argv[1]
1982
      old_cmdline = " ".join(sys.argv[2:])
1983
    else:
1984
      old_cmdline = ""
1985
  else:
1986
    binary = "<unknown program>"
1987
    old_cmdline = ""
1988

    
1989
  if aliases is None:
1990
    aliases = {}
1991

    
1992
  try:
1993
    func, options, args = _ParseArgs(sys.argv, commands, aliases)
1994
  except errors.ParameterError, err:
1995
    result, err_msg = FormatError(err)
1996
    ToStderr(err_msg)
1997
    return 1
1998

    
1999
  if func is None: # parse error
2000
    return 1
2001

    
2002
  if override is not None:
2003
    for key, val in override.iteritems():
2004
      setattr(options, key, val)
2005

    
2006
  utils.SetupLogging(constants.LOG_COMMANDS, binary, debug=options.debug,
2007
                     stderr_logging=True)
2008

    
2009
  if old_cmdline:
2010
    logging.info("run with arguments '%s'", old_cmdline)
2011
  else:
2012
    logging.info("run with no arguments")
2013

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

    
2033
  return result
2034

    
2035

    
2036
def ParseNicOption(optvalue):
2037
  """Parses the value of the --net option(s).
2038

2039
  """
2040
  try:
2041
    nic_max = max(int(nidx[0]) + 1 for nidx in optvalue)
2042
  except (TypeError, ValueError), err:
2043
    raise errors.OpPrereqError("Invalid NIC index passed: %s" % str(err))
2044

    
2045
  nics = [{}] * nic_max
2046
  for nidx, ndict in optvalue:
2047
    nidx = int(nidx)
2048

    
2049
    if not isinstance(ndict, dict):
2050
      raise errors.OpPrereqError("Invalid nic/%d value: expected dict,"
2051
                                 " got %s" % (nidx, ndict))
2052

    
2053
    utils.ForceDictType(ndict, constants.INIC_PARAMS_TYPES)
2054

    
2055
    nics[nidx] = ndict
2056

    
2057
  return nics
2058

    
2059

    
2060
def GenericInstanceCreate(mode, opts, args):
2061
  """Add an instance to the cluster via either creation or import.
2062

2063
  @param mode: constants.INSTANCE_CREATE or constants.INSTANCE_IMPORT
2064
  @param opts: the command line options selected by the user
2065
  @type args: list
2066
  @param args: should contain only one element, the new instance name
2067
  @rtype: int
2068
  @return: the desired exit code
2069

2070
  """
2071
  instance = args[0]
2072

    
2073
  (pnode, snode) = SplitNodeOption(opts.node)
2074

    
2075
  hypervisor = None
2076
  hvparams = {}
2077
  if opts.hypervisor:
2078
    hypervisor, hvparams = opts.hypervisor
2079

    
2080
  if opts.nics:
2081
    nics = ParseNicOption(opts.nics)
2082
  elif opts.no_nics:
2083
    # no nics
2084
    nics = []
2085
  elif mode == constants.INSTANCE_CREATE:
2086
    # default of one nic, all auto
2087
    nics = [{}]
2088
  else:
2089
    # mode == import
2090
    nics = []
2091

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

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

    
2140
  if opts.tags is not None:
2141
    tags = opts.tags.split(",")
2142
  else:
2143
    tags = []
2144

    
2145
  utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_TYPES)
2146
  utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
2147

    
2148
  if mode == constants.INSTANCE_CREATE:
2149
    start = opts.start
2150
    os_type = opts.os
2151
    force_variant = opts.force_variant
2152
    src_node = None
2153
    src_path = None
2154
    no_install = opts.no_install
2155
    identify_defaults = False
2156
  elif mode == constants.INSTANCE_IMPORT:
2157
    start = False
2158
    os_type = None
2159
    force_variant = False
2160
    src_node = opts.src_node
2161
    src_path = opts.src_dir
2162
    no_install = None
2163
    identify_defaults = opts.identify_defaults
2164
  else:
2165
    raise errors.ProgrammerError("Invalid creation mode %s" % mode)
2166

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

    
2192
  SubmitOrSend(op, opts)
2193
  return 0
2194

    
2195

    
2196
class _RunWhileClusterStoppedHelper:
2197
  """Helper class for L{RunWhileClusterStopped} to simplify state management
2198

2199
  """
2200
  def __init__(self, feedback_fn, cluster_name, master_node, online_nodes):
2201
    """Initializes this class.
2202

2203
    @type feedback_fn: callable
2204
    @param feedback_fn: Feedback function
2205
    @type cluster_name: string
2206
    @param cluster_name: Cluster name
2207
    @type master_node: string
2208
    @param master_node Master node name
2209
    @type online_nodes: list
2210
    @param online_nodes: List of names of online nodes
2211

2212
    """
2213
    self.feedback_fn = feedback_fn
2214
    self.cluster_name = cluster_name
2215
    self.master_node = master_node
2216
    self.online_nodes = online_nodes
2217

    
2218
    self.ssh = ssh.SshRunner(self.cluster_name)
2219

    
2220
    self.nonmaster_nodes = [name for name in online_nodes
2221
                            if name != master_node]
2222

    
2223
    assert self.master_node not in self.nonmaster_nodes
2224

    
2225
  def _RunCmd(self, node_name, cmd):
2226
    """Runs a command on the local or a remote machine.
2227

2228
    @type node_name: string
2229
    @param node_name: Machine name
2230
    @type cmd: list
2231
    @param cmd: Command
2232

2233
    """
2234
    if node_name is None or node_name == self.master_node:
2235
      # No need to use SSH
2236
      result = utils.RunCmd(cmd)
2237
    else:
2238
      result = self.ssh.Run(node_name, "root", utils.ShellQuoteArgs(cmd))
2239

    
2240
    if result.failed:
2241
      errmsg = ["Failed to run command %s" % result.cmd]
2242
      if node_name:
2243
        errmsg.append("on node %s" % node_name)
2244
      errmsg.append(": exitcode %s and error %s" %
2245
                    (result.exit_code, result.output))
2246
      raise errors.OpExecError(" ".join(errmsg))
2247

    
2248
  def Call(self, fn, *args):
2249
    """Call function while all daemons are stopped.
2250

2251
    @type fn: callable
2252
    @param fn: Function to be called
2253

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

    
2263
      # Stop master daemons, so that no new jobs can come in and all running
2264
      # ones are finished
2265
      self.feedback_fn("Stopping master daemons")
2266
      self._RunCmd(None, [constants.DAEMON_UTIL, "stop-master"])
2267
      try:
2268
        # Stop daemons on all nodes
2269
        for node_name in self.online_nodes:
2270
          self.feedback_fn("Stopping daemons on %s" % node_name)
2271
          self._RunCmd(node_name, [constants.DAEMON_UTIL, "stop-all"])
2272

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

    
2290

    
2291
def RunWhileClusterStopped(feedback_fn, fn, *args):
2292
  """Calls a function while all cluster daemons are stopped.
2293

2294
  @type feedback_fn: callable
2295
  @param feedback_fn: Feedback function
2296
  @type fn: callable
2297
  @param fn: Function to be called when daemons are stopped
2298

2299
  """
2300
  feedback_fn("Gathering cluster information")
2301

    
2302
  # This ensures we're running on the master daemon
2303
  cl = GetClient()
2304

    
2305
  (cluster_name, master_node) = \
2306
    cl.QueryConfigValues(["cluster_name", "master_node"])
2307

    
2308
  online_nodes = GetOnlineNodes([], cl=cl)
2309

    
2310
  # Don't keep a reference to the client. The master daemon will go away.
2311
  del cl
2312

    
2313
  assert master_node in online_nodes
2314

    
2315
  return _RunWhileClusterStoppedHelper(feedback_fn, cluster_name, master_node,
2316
                                       online_nodes).Call(fn, *args)
2317

    
2318

    
2319
def GenerateTable(headers, fields, separator, data,
2320
                  numfields=None, unitfields=None,
2321
                  units=None):
2322
  """Prints a table with headers and different fields.
2323

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

2347
  """
2348
  if units is None:
2349
    if separator:
2350
      units = "m"
2351
    else:
2352
      units = "h"
2353

    
2354
  if numfields is None:
2355
    numfields = []
2356
  if unitfields is None:
2357
    unitfields = []
2358

    
2359
  numfields = utils.FieldSet(*numfields)   # pylint: disable-msg=W0142
2360
  unitfields = utils.FieldSet(*unitfields) # pylint: disable-msg=W0142
2361

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

    
2376
  if separator is None:
2377
    mlens = [0 for name in fields]
2378
    format_str = ' '.join(format_fields)
2379
  else:
2380
    format_str = separator.replace("%", "%%").join(format_fields)
2381

    
2382
  for row in data:
2383
    if row is None:
2384
      continue
2385
    for idx, val in enumerate(row):
2386
      if unitfields.Matches(fields[idx]):
2387
        try:
2388
          val = int(val)
2389
        except (TypeError, ValueError):
2390
          pass
2391
        else:
2392
          val = row[idx] = utils.FormatUnit(val, units)
2393
      val = row[idx] = str(val)
2394
      if separator is None:
2395
        mlens[idx] = max(mlens[idx], len(val))
2396

    
2397
  result = []
2398
  if headers:
2399
    args = []
2400
    for idx, name in enumerate(fields):
2401
      hdr = headers[name]
2402
      if separator is None:
2403
        mlens[idx] = max(mlens[idx], len(hdr))
2404
        args.append(mlens[idx])
2405
      args.append(hdr)
2406
    result.append(format_str % tuple(args))
2407

    
2408
  if separator is None:
2409
    assert len(mlens) == len(fields)
2410

    
2411
    if fields and not numfields.Matches(fields[-1]):
2412
      mlens[-1] = 0
2413

    
2414
  for line in data:
2415
    args = []
2416
    if line is None:
2417
      line = ['-' for _ in fields]
2418
    for idx in range(len(fields)):
2419
      if separator is None:
2420
        args.append(mlens[idx])
2421
      args.append(line[idx])
2422
    result.append(format_str % tuple(args))
2423

    
2424
  return result
2425

    
2426

    
2427
def _FormatBool(value):
2428
  """Formats a boolean value as a string.
2429

2430
  """
2431
  if value:
2432
    return "Y"
2433
  return "N"
2434

    
2435

    
2436
#: Default formatting for query results; (callback, align right)
2437
_DEFAULT_FORMAT_QUERY = {
2438
  constants.QFT_TEXT: (str, False),
2439
  constants.QFT_BOOL: (_FormatBool, False),
2440
  constants.QFT_NUMBER: (str, True),
2441
  constants.QFT_TIMESTAMP: (utils.FormatTime, False),
2442
  constants.QFT_OTHER: (str, False),
2443
  constants.QFT_UNKNOWN: (str, False),
2444
  }
2445

    
2446

    
2447
def _GetColumnFormatter(fdef, override, unit):
2448
  """Returns formatting function for a field.
2449

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

2460
  """
2461
  fmt = override.get(fdef.name, None)
2462
  if fmt is not None:
2463
    return fmt
2464

    
2465
  assert constants.QFT_UNIT not in _DEFAULT_FORMAT_QUERY
2466

    
2467
  if fdef.kind == constants.QFT_UNIT:
2468
    # Can't keep this information in the static dictionary
2469
    return (lambda value: utils.FormatUnit(value, unit), True)
2470

    
2471
  fmt = _DEFAULT_FORMAT_QUERY.get(fdef.kind, None)
2472
  if fmt is not None:
2473
    return fmt
2474

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

    
2477

    
2478
class _QueryColumnFormatter:
2479
  """Callable class for formatting fields of a query.
2480

2481
  """
2482
  def __init__(self, fn, status_fn, verbose):
2483
    """Initializes this class.
2484

2485
    @type fn: callable
2486
    @param fn: Formatting function
2487
    @type status_fn: callable
2488
    @param status_fn: Function to report fields' status
2489
    @type verbose: boolean
2490
    @param verbose: whether to use verbose field descriptions or not
2491

2492
    """
2493
    self._fn = fn
2494
    self._status_fn = status_fn
2495
    self._verbose = verbose
2496

    
2497
  def __call__(self, data):
2498
    """Returns a field's string representation.
2499

2500
    """
2501
    (status, value) = data
2502

    
2503
    # Report status
2504
    self._status_fn(status)
2505

    
2506
    if status == constants.RS_NORMAL:
2507
      return self._fn(value)
2508

    
2509
    assert value is None, \
2510
           "Found value %r for abnormal status %s" % (value, status)
2511

    
2512
    return FormatResultError(status, self._verbose)
2513

    
2514

    
2515
def FormatResultError(status, verbose):
2516
  """Formats result status other than L{constants.RS_NORMAL}.
2517

2518
  @param status: The result status
2519
  @type verbose: boolean
2520
  @param verbose: Whether to return the verbose text
2521
  @return: Text of result status
2522

2523
  """
2524
  assert status != constants.RS_NORMAL, \
2525
         "FormatResultError called with status equal to constants.RS_NORMAL"
2526
  try:
2527
    (verbose_text, normal_text) = constants.RSS_DESCRIPTION[status]
2528
  except KeyError:
2529
    raise NotImplementedError("Unknown status %s" % status)
2530
  else:
2531
    if verbose:
2532
      return verbose_text
2533
    return normal_text
2534

    
2535

    
2536
def FormatQueryResult(result, unit=None, format_override=None, separator=None,
2537
                      header=False, verbose=False):
2538
  """Formats data in L{objects.QueryResponse}.
2539

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

2555
  """
2556
  if unit is None:
2557
    if separator:
2558
      unit = "m"
2559
    else:
2560
      unit = "h"
2561

    
2562
  if format_override is None:
2563
    format_override = {}
2564

    
2565
  stats = dict.fromkeys(constants.RS_ALL, 0)
2566

    
2567
  def _RecordStatus(status):
2568
    if status in stats:
2569
      stats[status] += 1
2570

    
2571
  columns = []
2572
  for fdef in result.fields:
2573
    assert fdef.title and fdef.name
2574
    (fn, align_right) = _GetColumnFormatter(fdef, format_override, unit)
2575
    columns.append(TableColumn(fdef.title,
2576
                               _QueryColumnFormatter(fn, _RecordStatus,
2577
                                                     verbose),
2578
                               align_right))
2579

    
2580
  table = FormatTable(result.data, columns, header, separator)
2581

    
2582
  # Collect statistics
2583
  assert len(stats) == len(constants.RS_ALL)
2584
  assert compat.all(count >= 0 for count in stats.values())
2585

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

    
2597
  return (status, table)
2598

    
2599

    
2600
def _GetUnknownFields(fdefs):
2601
  """Returns list of unknown fields included in C{fdefs}.
2602

2603
  @type fdefs: list of L{objects.QueryFieldDefinition}
2604

2605
  """
2606
  return [fdef for fdef in fdefs
2607
          if fdef.kind == constants.QFT_UNKNOWN]
2608

    
2609

    
2610
def _WarnUnknownFields(fdefs):
2611
  """Prints a warning to stderr if a query included unknown fields.
2612

2613
  @type fdefs: list of L{objects.QueryFieldDefinition}
2614

2615
  """
2616
  unknown = _GetUnknownFields(fdefs)
2617
  if unknown:
2618
    ToStderr("Warning: Queried for unknown fields %s",
2619
             utils.CommaJoin(fdef.name for fdef in unknown))
2620
    return True
2621

    
2622
  return False
2623

    
2624

    
2625
def GenericList(resource, fields, names, unit, separator, header, cl=None,
2626
                format_override=None, verbose=False, force_filter=False):
2627
  """Generic implementation for listing all items of a resource.
2628

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

2650
  """
2651
  if cl is None:
2652
    cl = GetClient()
2653

    
2654
  if not names:
2655
    names = None
2656

    
2657
  if (force_filter or
2658
      (names and len(names) == 1 and qlang.MaybeFilter(names[0]))):
2659
    try:
2660
      (filter_text, ) = names
2661
    except ValueError:
2662
      raise errors.OpPrereqError("Exactly one argument must be given as a"
2663
                                 " filter")
2664

    
2665
    logging.debug("Parsing '%s' as filter", filter_text)
2666
    filter_ = qlang.ParseFilter(filter_text)
2667
  else:
2668
    filter_ = qlang.MakeSimpleFilter("name", names)
2669

    
2670
  response = cl.Query(resource, fields, filter_)
2671

    
2672
  found_unknown = _WarnUnknownFields(response.fields)
2673

    
2674
  (status, data) = FormatQueryResult(response, unit=unit, separator=separator,
2675
                                     header=header,
2676
                                     format_override=format_override,
2677
                                     verbose=verbose)
2678

    
2679
  for line in data:
2680
    ToStdout(line)
2681

    
2682
  assert ((found_unknown and status == QR_UNKNOWN) or
2683
          (not found_unknown and status != QR_UNKNOWN))
2684

    
2685
  if status == QR_UNKNOWN:
2686
    return constants.EXIT_UNKNOWN_FIELD
2687

    
2688
  # TODO: Should the list command fail if not all data could be collected?
2689
  return constants.EXIT_SUCCESS
2690

    
2691

    
2692
def GenericListFields(resource, fields, separator, header, cl=None):
2693
  """Generic implementation for listing fields for a resource.
2694

2695
  @param resource: One of L{constants.QR_VIA_LUXI}
2696
  @type fields: list of strings
2697
  @param fields: List of fields to query for
2698
  @type separator: string or None
2699
  @param separator: String used to separate fields
2700
  @type header: bool
2701
  @param header: Whether to show header row
2702

2703
  """
2704
  if cl is None:
2705
    cl = GetClient()
2706

    
2707
  if not fields:
2708
    fields = None
2709

    
2710
  response = cl.QueryFields(resource, fields)
2711

    
2712
  found_unknown = _WarnUnknownFields(response.fields)
2713

    
2714
  columns = [
2715
    TableColumn("Name", str, False),
2716
    TableColumn("Title", str, False),
2717
    TableColumn("Description", str, False),
2718
    ]
2719

    
2720
  rows = [[fdef.name, fdef.title, fdef.doc] for fdef in response.fields]
2721

    
2722
  for line in FormatTable(rows, columns, header, separator):
2723
    ToStdout(line)
2724

    
2725
  if found_unknown:
2726
    return constants.EXIT_UNKNOWN_FIELD
2727

    
2728
  return constants.EXIT_SUCCESS
2729

    
2730

    
2731
class TableColumn:
2732
  """Describes a column for L{FormatTable}.
2733

2734
  """
2735
  def __init__(self, title, fn, align_right):
2736
    """Initializes this class.
2737

2738
    @type title: string
2739
    @param title: Column title
2740
    @type fn: callable
2741
    @param fn: Formatting function
2742
    @type align_right: bool
2743
    @param align_right: Whether to align values on the right-hand side
2744

2745
    """
2746
    self.title = title
2747
    self.format = fn
2748
    self.align_right = align_right
2749

    
2750

    
2751
def _GetColFormatString(width, align_right):
2752
  """Returns the format string for a field.
2753

2754
  """
2755
  if align_right:
2756
    sign = ""
2757
  else:
2758
    sign = "-"
2759

    
2760
  return "%%%s%ss" % (sign, width)
2761

    
2762

    
2763
def FormatTable(rows, columns, header, separator):
2764
  """Formats data as a table.
2765

2766
  @type rows: list of lists
2767
  @param rows: Row data, one list per row
2768
  @type columns: list of L{TableColumn}
2769
  @param columns: Column descriptions
2770
  @type header: bool
2771
  @param header: Whether to show header row
2772
  @type separator: string or None
2773
  @param separator: String used to separate columns
2774

2775
  """
2776
  if header:
2777
    data = [[col.title for col in columns]]
2778
    colwidth = [len(col.title) for col in columns]
2779
  else:
2780
    data = []
2781
    colwidth = [0 for _ in columns]
2782

    
2783
  # Format row data
2784
  for row in rows:
2785
    assert len(row) == len(columns)
2786

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

    
2789
    if separator is None:
2790
      # Update column widths
2791
      for idx, (oldwidth, value) in enumerate(zip(colwidth, formatted)):
2792
        # Modifying a list's items while iterating is fine
2793
        colwidth[idx] = max(oldwidth, len(value))
2794

    
2795
    data.append(formatted)
2796

    
2797
  if separator is not None:
2798
    # Return early if a separator is used
2799
    return [separator.join(row) for row in data]
2800

    
2801
  if columns and not columns[-1].align_right:
2802
    # Avoid unnecessary spaces at end of line
2803
    colwidth[-1] = 0
2804

    
2805
  # Build format string
2806
  fmt = " ".join([_GetColFormatString(width, col.align_right)
2807
                  for col, width in zip(columns, colwidth)])
2808

    
2809
  return [fmt % tuple(row) for row in data]
2810

    
2811

    
2812
def FormatTimestamp(ts):
2813
  """Formats a given timestamp.
2814

2815
  @type ts: timestamp
2816
  @param ts: a timeval-type timestamp, a tuple of seconds and microseconds
2817

2818
  @rtype: string
2819
  @return: a string with the formatted timestamp
2820

2821
  """
2822
  if not isinstance (ts, (tuple, list)) or len(ts) != 2:
2823
    return '?'
2824
  sec, usec = ts
2825
  return time.strftime("%F %T", time.localtime(sec)) + ".%06d" % usec
2826

    
2827

    
2828
def ParseTimespec(value):
2829
  """Parse a time specification.
2830

2831
  The following suffixed will be recognized:
2832

2833
    - s: seconds
2834
    - m: minutes
2835
    - h: hours
2836
    - d: day
2837
    - w: weeks
2838

2839
  Without any suffix, the value will be taken to be in seconds.
2840

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

    
2869

    
2870
def GetOnlineNodes(nodes, cl=None, nowarn=False, secondary_ips=False,
2871
                   filter_master=False):
2872
  """Returns the names of online nodes.
2873

2874
  This function will also log a warning on stderr with the names of
2875
  the online nodes.
2876

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

2893
  """
2894
  if cl is None:
2895
    cl = GetClient()
2896

    
2897
  if secondary_ips:
2898
    name_idx = 2
2899
  else:
2900
    name_idx = 0
2901

    
2902
  if filter_master:
2903
    master_node = cl.QueryConfigValues(["master_node"])[0]
2904
    filter_fn = lambda x: x != master_node
2905
  else:
2906
    filter_fn = lambda _: True
2907

    
2908
  result = cl.QueryNodes(names=nodes, fields=["name", "offline", "sip"],
2909
                         use_locking=False)
2910
  offline = [row[0] for row in result if row[1]]
2911
  if offline and not nowarn:
2912
    ToStderr("Note: skipping offline node(s): %s" % utils.CommaJoin(offline))
2913
  return [row[name_idx] for row in result if not row[1] and filter_fn(row[0])]
2914

    
2915

    
2916
def _ToStream(stream, txt, *args):
2917
  """Write a message to a stream, bypassing the logging system
2918

2919
  @type stream: file object
2920
  @param stream: the file to which we should write
2921
  @type txt: str
2922
  @param txt: the message
2923

2924
  """
2925
  try:
2926
    if args:
2927
      args = tuple(args)
2928
      stream.write(txt % args)
2929
    else:
2930
      stream.write(txt)
2931
    stream.write('\n')
2932
    stream.flush()
2933
  except IOError, err:
2934
    if err.errno == errno.EPIPE:
2935
      # our terminal went away, we'll exit
2936
      sys.exit(constants.EXIT_FAILURE)
2937
    else:
2938
      raise
2939

    
2940

    
2941
def ToStdout(txt, *args):
2942
  """Write a message to stdout only, bypassing the logging system
2943

2944
  This is just a wrapper over _ToStream.
2945

2946
  @type txt: str
2947
  @param txt: the message
2948

2949
  """
2950
  _ToStream(sys.stdout, txt, *args)
2951

    
2952

    
2953
def ToStderr(txt, *args):
2954
  """Write a message to stderr only, bypassing the logging system
2955

2956
  This is just a wrapper over _ToStream.
2957

2958
  @type txt: str
2959
  @param txt: the message
2960

2961
  """
2962
  _ToStream(sys.stderr, txt, *args)
2963

    
2964

    
2965
class JobExecutor(object):
2966
  """Class which manages the submission and execution of multiple jobs.
2967

2968
  Note that instances of this class should not be reused between
2969
  GetResults() calls.
2970

2971
  """
2972
  def __init__(self, cl=None, verbose=True, opts=None, feedback_fn=None):
2973
    self.queue = []
2974
    if cl is None:
2975
      cl = GetClient()
2976
    self.cl = cl
2977
    self.verbose = verbose
2978
    self.jobs = []
2979
    self.opts = opts
2980
    self.feedback_fn = feedback_fn
2981
    self._counter = itertools.count()
2982

    
2983
  @staticmethod
2984
  def _IfName(name, fmt):
2985
    """Helper function for formatting name.
2986

2987
    """
2988
    if name:
2989
      return fmt % name
2990

    
2991
    return ""
2992

    
2993
  def QueueJob(self, name, *ops):
2994
    """Record a job for later submit.
2995

2996
    @type name: string
2997
    @param name: a description of the job, will be used in WaitJobSet
2998

2999
    """
3000
    SetGenericOpcodeOpts(ops, self.opts)
3001
    self.queue.append((self._counter.next(), name, ops))
3002

    
3003
  def AddJobId(self, name, status, job_id):
3004
    """Adds a job ID to the internal queue.
3005

3006
    """
3007
    self.jobs.append((self._counter.next(), status, job_id, name))
3008

    
3009
  def SubmitPending(self, each=False):
3010
    """Submit all pending jobs.
3011

3012
    """
3013
    if each:
3014
      results = []
3015
      for (_, _, ops) in self.queue:
3016
        # SubmitJob will remove the success status, but raise an exception if
3017
        # the submission fails, so we'll notice that anyway.
3018
        results.append([True, self.cl.SubmitJob(ops)])
3019
    else:
3020
      results = self.cl.SubmitManyJobs([ops for (_, _, ops) in self.queue])
3021
    for ((status, data), (idx, name, _)) in zip(results, self.queue):
3022
      self.jobs.append((idx, status, data, name))
3023

    
3024
  def _ChooseJob(self):
3025
    """Choose a non-waiting/queued job to poll next.
3026

3027
    """
3028
    assert self.jobs, "_ChooseJob called with empty job list"
3029

    
3030
    result = self.cl.QueryJobs([i[2] for i in self.jobs], ["status"])
3031
    assert result
3032

    
3033
    for job_data, status in zip(self.jobs, result):
3034
      if (isinstance(status, list) and status and
3035
          status[0] in (constants.JOB_STATUS_QUEUED,
3036
                        constants.JOB_STATUS_WAITLOCK,
3037
                        constants.JOB_STATUS_CANCELING)):
3038
        # job is still present and waiting
3039
        continue
3040
      # good candidate found (either running job or lost job)
3041
      self.jobs.remove(job_data)
3042
      return job_data
3043

    
3044
    # no job found
3045
    return self.jobs.pop(0)
3046

    
3047
  def GetResults(self):
3048
    """Wait for and return the results of all jobs.
3049

3050
    @rtype: list
3051
    @return: list of tuples (success, job results), in the same order
3052
        as the submitted jobs; if a job has failed, instead of the result
3053
        there will be the error message
3054

3055
    """
3056
    if not self.jobs:
3057
      self.SubmitPending()
3058
    results = []
3059
    if self.verbose:
3060
      ok_jobs = [row[2] for row in self.jobs if row[1]]
3061
      if ok_jobs:
3062
        ToStdout("Submitted jobs %s", utils.CommaJoin(ok_jobs))
3063

    
3064
    # first, remove any non-submitted jobs
3065
    self.jobs, failures = compat.partition(self.jobs, lambda x: x[1])
3066
    for idx, _, jid, name in failures:
3067
      ToStderr("Failed to submit job%s: %s", self._IfName(name, " for %s"), jid)
3068
      results.append((idx, False, jid))
3069

    
3070
    while self.jobs:
3071
      (idx, _, jid, name) = self._ChooseJob()
3072
      ToStdout("Waiting for job %s%s ...", jid, self._IfName(name, " for %s"))
3073
      try:
3074
        job_result = PollJob(jid, cl=self.cl, feedback_fn=self.feedback_fn)
3075
        success = True
3076
      except errors.JobLost, err:
3077
        _, job_result = FormatError(err)
3078
        ToStderr("Job %s%s has been archived, cannot check its result",
3079
                 jid, self._IfName(name, " for %s"))
3080
        success = False
3081
      except (errors.GenericError, luxi.ProtocolError), err:
3082
        _, job_result = FormatError(err)
3083
        success = False
3084
        # the error message will always be shown, verbose or not
3085
        ToStderr("Job %s%s has failed: %s",
3086
                 jid, self._IfName(name, " for %s"), job_result)
3087

    
3088
      results.append((idx, success, job_result))
3089

    
3090
    # sort based on the index, then drop it
3091
    results.sort()
3092
    results = [i[1:] for i in results]
3093

    
3094
    return results
3095

    
3096
  def WaitOrShow(self, wait):
3097
    """Wait for job results or only print the job IDs.
3098

3099
    @type wait: boolean
3100
    @param wait: whether to wait or not
3101

3102
    """
3103
    if wait:
3104
      return self.GetResults()
3105
    else:
3106
      if not self.jobs:
3107
        self.SubmitPending()
3108
      for _, status, result, name in self.jobs:
3109
        if status:
3110
          ToStdout("%s: %s", result, name)
3111
        else:
3112
          ToStderr("Failure for %s: %s", name, result)
3113
      return [row[1:3] for row in self.jobs]
3114

    
3115

    
3116
def FormatParameterDict(buf, param_dict, actual, level=1):
3117
  """Formats a parameter dictionary.
3118

3119
  @type buf: L{StringIO}
3120
  @param buf: the buffer into which to write
3121
  @type param_dict: dict
3122
  @param param_dict: the own parameters
3123
  @type actual: dict
3124
  @param actual: the current parameter set (including defaults)
3125
  @param level: Level of indent
3126

3127
  """
3128
  indent = "  " * level
3129
  for key in sorted(actual):
3130
    val = param_dict.get(key, "default (%s)" % actual[key])
3131
    buf.write("%s- %s: %s\n" % (indent, key, val))
3132

    
3133

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

3137
  This function is used to request confirmation for doing an operation
3138
  on a given list of list_type.
3139

3140
  @type names: list
3141
  @param names: the list of names that we display when
3142
      we ask for confirmation
3143
  @type list_type: str
3144
  @param list_type: Human readable name for elements in the list (e.g. nodes)
3145
  @type text: str
3146
  @param text: the operation that the user should confirm
3147
  @rtype: boolean
3148
  @return: True or False depending on user's confirmation.
3149

3150
  """
3151
  count = len(names)
3152
  msg = ("The %s will operate on %d %s.\n%s"
3153
         "Do you want to continue?" % (text, count, list_type, extra))
3154
  affected = (("\nAffected %s:\n" % list_type) +
3155
              "\n".join(["  %s" % name for name in names]))
3156

    
3157
  choices = [("y", True, "Yes, execute the %s" % text),
3158
             ("n", False, "No, abort the %s" % text)]
3159

    
3160
  if count > 20:
3161
    choices.insert(1, ("v", "v", "View the list of affected %s" % list_type))
3162
    question = msg
3163
  else:
3164
    question = msg + affected
3165

    
3166
  choice = AskUser(question, choices)
3167
  if choice == "v":
3168
    choices.pop(1)
3169
    choice = AskUser(msg + affected, choices)
3170
  return choice