Statistics
| Branch: | Tag: | Revision:

root / lib / cli.py @ 232aab3f

History | View | Annotate | Download (103.2 kB)

1
#
2
#
3

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

    
21

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

    
24

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

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

    
47
from optparse import (TitledHelpFormatter,
48
                      Option, OptionValueError)
49

    
50

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

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

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

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

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

    
261
#: Maximum batch size for ChooseJob
262
_CHOOSE_BATCH = 25
263

    
264

    
265
class _Argument:
266
  def __init__(self, min=0, max=None): # pylint: disable=W0622
267
    self.min = min
268
    self.max = max
269

    
270
  def __repr__(self):
271
    return ("<%s min=%s max=%s>" %
272
            (self.__class__.__name__, self.min, self.max))
273

    
274

    
275
class ArgSuggest(_Argument):
276
  """Suggesting argument.
277

278
  Value can be any of the ones passed to the constructor.
279

280
  """
281
  # pylint: disable=W0622
282
  def __init__(self, min=0, max=None, choices=None):
283
    _Argument.__init__(self, min=min, max=max)
284
    self.choices = choices
285

    
286
  def __repr__(self):
287
    return ("<%s min=%s max=%s choices=%r>" %
288
            (self.__class__.__name__, self.min, self.max, self.choices))
289

    
290

    
291
class ArgChoice(ArgSuggest):
292
  """Choice argument.
293

294
  Value can be any of the ones passed to the constructor. Like L{ArgSuggest},
295
  but value must be one of the choices.
296

297
  """
298

    
299

    
300
class ArgUnknown(_Argument):
301
  """Unknown argument to program (e.g. determined at runtime).
302

303
  """
304

    
305

    
306
class ArgInstance(_Argument):
307
  """Instances argument.
308

309
  """
310

    
311

    
312
class ArgNode(_Argument):
313
  """Node argument.
314

315
  """
316

    
317

    
318
class ArgGroup(_Argument):
319
  """Node group argument.
320

321
  """
322

    
323

    
324
class ArgJobId(_Argument):
325
  """Job ID argument.
326

327
  """
328

    
329

    
330
class ArgFile(_Argument):
331
  """File path argument.
332

333
  """
334

    
335

    
336
class ArgCommand(_Argument):
337
  """Command argument.
338

339
  """
340

    
341

    
342
class ArgHost(_Argument):
343
  """Host argument.
344

345
  """
346

    
347

    
348
class ArgOs(_Argument):
349
  """OS argument.
350

351
  """
352

    
353

    
354
ARGS_NONE = []
355
ARGS_MANY_INSTANCES = [ArgInstance()]
356
ARGS_MANY_NODES = [ArgNode()]
357
ARGS_MANY_GROUPS = [ArgGroup()]
358
ARGS_ONE_INSTANCE = [ArgInstance(min=1, max=1)]
359
ARGS_ONE_NODE = [ArgNode(min=1, max=1)]
360
# TODO
361
ARGS_ONE_GROUP = [ArgGroup(min=1, max=1)]
362
ARGS_ONE_OS = [ArgOs(min=1, max=1)]
363

    
364

    
365
def _ExtractTagsObject(opts, args):
366
  """Extract the tag type object.
367

368
  Note that this function will modify its args parameter.
369

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

    
387

    
388
def _ExtendTags(opts, args):
389
  """Extend the args if a source file has been given.
390

391
  This function will extend the tags with the contents of the file
392
  passed in the 'tags_source' attribute of the opts parameter. A file
393
  named '-' will be replaced by stdin.
394

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

    
416

    
417
def ListTags(opts, args):
418
  """List the tags on a given object.
419

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

425
  """
426
  kind, name = _ExtractTagsObject(opts, args)
427
  cl = GetClient()
428
  result = cl.QueryTags(kind, name)
429
  result = list(result)
430
  result.sort()
431
  for tag in result:
432
    ToStdout(tag)
433

    
434

    
435
def AddTags(opts, args):
436
  """Add tags on a given object.
437

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

443
  """
444
  kind, name = _ExtractTagsObject(opts, args)
445
  _ExtendTags(opts, args)
446
  if not args:
447
    raise errors.OpPrereqError("No tags to be added")
448
  op = opcodes.OpTagsSet(kind=kind, name=name, tags=args)
449
  SubmitOpCode(op, opts=opts)
450

    
451

    
452
def RemoveTags(opts, args):
453
  """Remove tags from a given object.
454

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

460
  """
461
  kind, name = _ExtractTagsObject(opts, args)
462
  _ExtendTags(opts, args)
463
  if not args:
464
    raise errors.OpPrereqError("No tags to be removed")
465
  op = opcodes.OpTagsDel(kind=kind, name=name, tags=args)
466
  SubmitOpCode(op, opts=opts)
467

    
468

    
469
def check_unit(option, opt, value): # pylint: disable=W0613
470
  """OptParsers custom converter for units.
471

472
  """
473
  try:
474
    return utils.ParseUnit(value)
475
  except errors.UnitParseError, err:
476
    raise OptionValueError("option %s: %s" % (opt, err))
477

    
478

    
479
def _SplitKeyVal(opt, data):
480
  """Convert a KeyVal string into a dict.
481

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

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

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

    
515

    
516
def check_ident_key_val(option, opt, value):  # pylint: disable=W0613
517
  """Custom parser for ident:key=val,key=val options.
518

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

522
  """
523
  if ":" not in value:
524
    ident, rest = value, ""
525
  else:
526
    ident, rest = value.split(":", 1)
527

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

    
543

    
544
def check_key_val(option, opt, value):  # pylint: disable=W0613
545
  """Custom parser class for key=val,key=val options.
546

547
  This will store the parsed values as a dict {key: val}.
548

549
  """
550
  return _SplitKeyVal(opt, value)
551

    
552

    
553
def check_bool(option, opt, value): # pylint: disable=W0613
554
  """Custom parser for yes/no options.
555

556
  This will store the parsed value as either True or False.
557

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

    
567

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

    
578
OPT_COMPL_ALL = frozenset([
579
  OPT_COMPL_MANY_NODES,
580
  OPT_COMPL_ONE_NODE,
581
  OPT_COMPL_ONE_INSTANCE,
582
  OPT_COMPL_ONE_OS,
583
  OPT_COMPL_ONE_IALLOCATOR,
584
  OPT_COMPL_INST_ADD_NODES,
585
  OPT_COMPL_ONE_NODEGROUP,
586
  ])
587

    
588

    
589
class CliOption(Option):
590
  """Custom option class for optparse.
591

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

    
608

    
609
# optparse.py sets make_option, so we do it for our own option class, too
610
cli_option = CliOption
611

    
612

    
613
_YORNO = "yes|no"
614

    
615
DEBUG_OPT = cli_option("-d", "--debug", default=0, action="count",
616
                       help="Increase debugging level")
617

    
618
NOHDR_OPT = cli_option("--no-headers", default=False,
619
                       action="store_true", dest="no_headers",
620
                       help="Don't display column headers")
621

    
622
SEP_OPT = cli_option("--separator", default=None,
623
                     action="store", dest="separator",
624
                     help=("Separator between output fields"
625
                           " (defaults to one space)"))
626

    
627
USEUNITS_OPT = cli_option("--units", default=None,
628
                          dest="units", choices=("h", "m", "g", "t"),
629
                          help="Specify units for output (one of h/m/g/t)")
630

    
631
FIELDS_OPT = cli_option("-o", "--output", dest="output", action="store",
632
                        type="string", metavar="FIELDS",
633
                        help="Comma separated list of output fields")
634

    
635
FORCE_OPT = cli_option("-f", "--force", dest="force", action="store_true",
636
                       default=False, help="Force the operation")
637

    
638
CONFIRM_OPT = cli_option("--yes", dest="confirm", action="store_true",
639
                         default=False, help="Do not require confirmation")
640

    
641
IGNORE_OFFLINE_OPT = cli_option("--ignore-offline", dest="ignore_offline",
642
                                  action="store_true", default=False,
643
                                  help=("Ignore offline nodes and do as much"
644
                                        " as possible"))
645

    
646
TAG_ADD_OPT = cli_option("--tags", dest="tags",
647
                         default=None, help="Comma-separated list of instance"
648
                                            " tags")
649

    
650
TAG_SRC_OPT = cli_option("--from", dest="tags_source",
651
                         default=None, help="File with tag names")
652

    
653
SUBMIT_OPT = cli_option("--submit", dest="submit_only",
654
                        default=False, action="store_true",
655
                        help=("Submit the job and return the job ID, but"
656
                              " don't wait for the job to finish"))
657

    
658
SYNC_OPT = cli_option("--sync", dest="do_locking",
659
                      default=False, action="store_true",
660
                      help=("Grab locks while doing the queries"
661
                            " in order to ensure more consistent results"))
662

    
663
DRY_RUN_OPT = cli_option("--dry-run", default=False,
664
                         action="store_true",
665
                         help=("Do not execute the operation, just run the"
666
                               " check steps and verify it it could be"
667
                               " executed"))
668

    
669
VERBOSE_OPT = cli_option("-v", "--verbose", default=False,
670
                         action="store_true",
671
                         help="Increase the verbosity of the operation")
672

    
673
DEBUG_SIMERR_OPT = cli_option("--debug-simulate-errors", default=False,
674
                              action="store_true", dest="simulate_errors",
675
                              help="Debugging option that makes the operation"
676
                              " treat most runtime checks as failed")
677

    
678
NWSYNC_OPT = cli_option("--no-wait-for-sync", dest="wait_for_sync",
679
                        default=True, action="store_false",
680
                        help="Don't wait for sync (DANGEROUS!)")
681

    
682
DISK_TEMPLATE_OPT = cli_option("-t", "--disk-template", dest="disk_template",
683
                               help=("Custom disk setup (%s)" %
684
                                     utils.CommaJoin(constants.DISK_TEMPLATES)),
685
                               default=None, metavar="TEMPL",
686
                               choices=list(constants.DISK_TEMPLATES))
687

    
688
NONICS_OPT = cli_option("--no-nics", default=False, action="store_true",
689
                        help="Do not create any network cards for"
690
                        " the instance")
691

    
692
FILESTORE_DIR_OPT = cli_option("--file-storage-dir", dest="file_storage_dir",
693
                               help="Relative path under default cluster-wide"
694
                               " file storage dir to store file-based disks",
695
                               default=None, metavar="<DIR>")
696

    
697
FILESTORE_DRIVER_OPT = cli_option("--file-driver", dest="file_driver",
698
                                  help="Driver to use for image files",
699
                                  default="loop", metavar="<DRIVER>",
700
                                  choices=list(constants.FILE_DRIVER))
701

    
702
IALLOCATOR_OPT = cli_option("-I", "--iallocator", metavar="<NAME>",
703
                            help="Select nodes for the instance automatically"
704
                            " using the <NAME> iallocator plugin",
705
                            default=None, type="string",
706
                            completion_suggest=OPT_COMPL_ONE_IALLOCATOR)
707

    
708
DEFAULT_IALLOCATOR_OPT = cli_option("-I", "--default-iallocator",
709
                            metavar="<NAME>",
710
                            help="Set the default instance allocator plugin",
711
                            default=None, type="string",
712
                            completion_suggest=OPT_COMPL_ONE_IALLOCATOR)
713

    
714
OS_OPT = cli_option("-o", "--os-type", dest="os", help="What OS to run",
715
                    metavar="<os>",
716
                    completion_suggest=OPT_COMPL_ONE_OS)
717

    
718
OSPARAMS_OPT = cli_option("-O", "--os-parameters", dest="osparams",
719
                         type="keyval", default={},
720
                         help="OS parameters")
721

    
722
FORCE_VARIANT_OPT = cli_option("--force-variant", dest="force_variant",
723
                               action="store_true", default=False,
724
                               help="Force an unknown variant")
725

    
726
NO_INSTALL_OPT = cli_option("--no-install", dest="no_install",
727
                            action="store_true", default=False,
728
                            help="Do not install the OS (will"
729
                            " enable no-start)")
730

    
731
BACKEND_OPT = cli_option("-B", "--backend-parameters", dest="beparams",
732
                         type="keyval", default={},
733
                         help="Backend parameters")
734

    
735
HVOPTS_OPT = cli_option("-H", "--hypervisor-parameters", type="keyval",
736
                        default={}, dest="hvparams",
737
                        help="Hypervisor parameters")
738

    
739
HYPERVISOR_OPT = cli_option("-H", "--hypervisor-parameters", dest="hypervisor",
740
                            help="Hypervisor and hypervisor options, in the"
741
                            " format hypervisor:option=value,option=value,...",
742
                            default=None, type="identkeyval")
743

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

    
749
NOIPCHECK_OPT = cli_option("--no-ip-check", dest="ip_check", default=True,
750
                           action="store_false",
751
                           help="Don't check that the instance's IP"
752
                           " is alive")
753

    
754
NONAMECHECK_OPT = cli_option("--no-name-check", dest="name_check",
755
                             default=True, action="store_false",
756
                             help="Don't check that the instance's name"
757
                             " is resolvable")
758

    
759
NET_OPT = cli_option("--net",
760
                     help="NIC parameters", default=[],
761
                     dest="nics", action="append", type="identkeyval")
762

    
763
DISK_OPT = cli_option("--disk", help="Disk parameters", default=[],
764
                      dest="disks", action="append", type="identkeyval")
765

    
766
DISKIDX_OPT = cli_option("--disks", dest="disks", default=None,
767
                         help="Comma-separated list of disks"
768
                         " indices to act on (e.g. 0,2) (optional,"
769
                         " defaults to all disks)")
770

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

    
776
IGNORE_CONSIST_OPT = cli_option("--ignore-consistency",
777
                                dest="ignore_consistency",
778
                                action="store_true", default=False,
779
                                help="Ignore the consistency of the disks on"
780
                                " the secondary")
781

    
782
ALLOW_FAILOVER_OPT = cli_option("--allow-failover",
783
                                dest="allow_failover",
784
                                action="store_true", default=False,
785
                                help="If migration is not possible fallback to"
786
                                     " failover")
787

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

    
794
MIGRATION_MODE_OPT = cli_option("--migration-mode", dest="migration_mode",
795
                                default=None,
796
                                choices=list(constants.HT_MIGRATION_MODES),
797
                                help="Override default migration mode (choose"
798
                                " either live or non-live")
799

    
800
NODE_PLACEMENT_OPT = cli_option("-n", "--node", dest="node",
801
                                help="Target node and optional secondary node",
802
                                metavar="<pnode>[:<snode>]",
803
                                completion_suggest=OPT_COMPL_INST_ADD_NODES)
804

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

    
811
NODEGROUP_OPT_NAME = "--node-group"
812
NODEGROUP_OPT = cli_option("-g", NODEGROUP_OPT_NAME,
813
                           dest="nodegroup",
814
                           help="Node group (name or uuid)",
815
                           metavar="<nodegroup>",
816
                           default=None, type="string",
817
                           completion_suggest=OPT_COMPL_ONE_NODEGROUP)
818

    
819
SINGLE_NODE_OPT = cli_option("-n", "--node", dest="node", help="Target node",
820
                             metavar="<node>",
821
                             completion_suggest=OPT_COMPL_ONE_NODE)
822

    
823
NOSTART_OPT = cli_option("--no-start", dest="start", default=True,
824
                         action="store_false",
825
                         help="Don't start the instance after creation")
826

    
827
SHOWCMD_OPT = cli_option("--show-cmd", dest="show_command",
828
                         action="store_true", default=False,
829
                         help="Show command instead of executing it")
830

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

    
840
STATIC_OPT = cli_option("-s", "--static", dest="static",
841
                        action="store_true", default=False,
842
                        help="Only show configuration data, not runtime data")
843

    
844
ALL_OPT = cli_option("--all", dest="show_all",
845
                     default=False, action="store_true",
846
                     help="Show info on all instances on the cluster."
847
                     " This can take a long time to run, use wisely")
848

    
849
SELECT_OS_OPT = cli_option("--select-os", dest="select_os",
850
                           action="store_true", default=False,
851
                           help="Interactive OS reinstall, lists available"
852
                           " OS templates for selection")
853

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

    
860
IGNORE_REMOVE_FAILURES_OPT = cli_option("--ignore-remove-failures",
861
                                        dest="ignore_remove_failures",
862
                                        action="store_true", default=False,
863
                                        help="Remove the instance from the"
864
                                        " cluster configuration even if there"
865
                                        " are failures during the removal"
866
                                        " process")
867

    
868
REMOVE_INSTANCE_OPT = cli_option("--remove-instance", dest="remove_instance",
869
                                 action="store_true", default=False,
870
                                 help="Remove the instance from the cluster")
871

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

    
877
NEW_SECONDARY_OPT = cli_option("-n", "--new-secondary", dest="dst_node",
878
                               help="Specifies the new secondary node",
879
                               metavar="NODE", default=None,
880
                               completion_suggest=OPT_COMPL_ONE_NODE)
881

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

    
889
ON_SECONDARY_OPT = cli_option("-s", "--on-secondary", dest="on_secondary",
890
                              default=False, action="store_true",
891
                              help="Replace the disk(s) on the secondary"
892
                                   " node (applies only to internally mirrored"
893
                                   " disk templates, e.g. %s)" %
894
                                   utils.CommaJoin(constants.DTS_INT_MIRROR))
895

    
896
AUTO_PROMOTE_OPT = cli_option("--auto-promote", dest="auto_promote",
897
                              default=False, action="store_true",
898
                              help="Lock all nodes and auto-promote as needed"
899
                              " to MC status")
900

    
901
AUTO_REPLACE_OPT = cli_option("-a", "--auto", dest="auto",
902
                              default=False, action="store_true",
903
                              help="Automatically replace faulty disks"
904
                                   " (applies only to internally mirrored"
905
                                   " disk templates, e.g. %s)" %
906
                                   utils.CommaJoin(constants.DTS_INT_MIRROR))
907

    
908
IGNORE_SIZE_OPT = cli_option("--ignore-size", dest="ignore_size",
909
                             default=False, action="store_true",
910
                             help="Ignore current recorded size"
911
                             " (useful for forcing activation when"
912
                             " the recorded size is wrong)")
913

    
914
SRC_NODE_OPT = cli_option("--src-node", dest="src_node", help="Source node",
915
                          metavar="<node>",
916
                          completion_suggest=OPT_COMPL_ONE_NODE)
917

    
918
SRC_DIR_OPT = cli_option("--src-dir", dest="src_dir", help="Source directory",
919
                         metavar="<dir>")
920

    
921
SECONDARY_IP_OPT = cli_option("-s", "--secondary-ip", dest="secondary_ip",
922
                              help="Specify the secondary ip for the node",
923
                              metavar="ADDRESS", default=None)
924

    
925
READD_OPT = cli_option("--readd", dest="readd",
926
                       default=False, action="store_true",
927
                       help="Readd old node after replacing it")
928

    
929
NOSSH_KEYCHECK_OPT = cli_option("--no-ssh-key-check", dest="ssh_key_check",
930
                                default=True, action="store_false",
931
                                help="Disable SSH key fingerprint checking")
932

    
933
NODE_FORCE_JOIN_OPT = cli_option("--force-join", dest="force_join",
934
                                 default=False, action="store_true",
935
                                 help="Force the joining of a node")
936

    
937
MC_OPT = cli_option("-C", "--master-candidate", dest="master_candidate",
938
                    type="bool", default=None, metavar=_YORNO,
939
                    help="Set the master_candidate flag on the node")
940

    
941
OFFLINE_OPT = cli_option("-O", "--offline", dest="offline", metavar=_YORNO,
942
                         type="bool", default=None,
943
                         help=("Set the offline flag on the node"
944
                               " (cluster does not communicate with offline"
945
                               " nodes)"))
946

    
947
DRAINED_OPT = cli_option("-D", "--drained", dest="drained", metavar=_YORNO,
948
                         type="bool", default=None,
949
                         help=("Set the drained flag on the node"
950
                               " (excluded from allocation operations)"))
951

    
952
CAPAB_MASTER_OPT = cli_option("--master-capable", dest="master_capable",
953
                    type="bool", default=None, metavar=_YORNO,
954
                    help="Set the master_capable flag on the node")
955

    
956
CAPAB_VM_OPT = cli_option("--vm-capable", dest="vm_capable",
957
                    type="bool", default=None, metavar=_YORNO,
958
                    help="Set the vm_capable flag on the node")
959

    
960
ALLOCATABLE_OPT = cli_option("--allocatable", dest="allocatable",
961
                             type="bool", default=None, metavar=_YORNO,
962
                             help="Set the allocatable flag on a volume")
963

    
964
NOLVM_STORAGE_OPT = cli_option("--no-lvm-storage", dest="lvm_storage",
965
                               help="Disable support for lvm based instances"
966
                               " (cluster-wide)",
967
                               action="store_false", default=True)
968

    
969
ENABLED_HV_OPT = cli_option("--enabled-hypervisors",
970
                            dest="enabled_hypervisors",
971
                            help="Comma-separated list of hypervisors",
972
                            type="string", default=None)
973

    
974
NIC_PARAMS_OPT = cli_option("-N", "--nic-parameters", dest="nicparams",
975
                            type="keyval", default={},
976
                            help="NIC parameters")
977

    
978
CP_SIZE_OPT = cli_option("-C", "--candidate-pool-size", default=None,
979
                         dest="candidate_pool_size", type="int",
980
                         help="Set the candidate pool size")
981

    
982
VG_NAME_OPT = cli_option("--vg-name", dest="vg_name",
983
                         help=("Enables LVM and specifies the volume group"
984
                               " name (cluster-wide) for disk allocation"
985
                               " [%s]" % constants.DEFAULT_VG),
986
                         metavar="VG", default=None)
987

    
988
YES_DOIT_OPT = cli_option("--yes-do-it", "--ya-rly", dest="yes_do_it",
989
                          help="Destroy cluster", action="store_true")
990

    
991
NOVOTING_OPT = cli_option("--no-voting", dest="no_voting",
992
                          help="Skip node agreement check (dangerous)",
993
                          action="store_true", default=False)
994

    
995
MAC_PREFIX_OPT = cli_option("-m", "--mac-prefix", dest="mac_prefix",
996
                            help="Specify the mac prefix for the instance IP"
997
                            " addresses, in the format XX:XX:XX",
998
                            metavar="PREFIX",
999
                            default=None)
1000

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

    
1009
GLOBAL_FILEDIR_OPT = cli_option("--file-storage-dir", dest="file_storage_dir",
1010
                                help="Specify the default directory (cluster-"
1011
                                "wide) for storing the file-based disks [%s]" %
1012
                                constants.DEFAULT_FILE_STORAGE_DIR,
1013
                                metavar="DIR",
1014
                                default=constants.DEFAULT_FILE_STORAGE_DIR)
1015

    
1016
GLOBAL_SHARED_FILEDIR_OPT = cli_option("--shared-file-storage-dir",
1017
                            dest="shared_file_storage_dir",
1018
                            help="Specify the default directory (cluster-"
1019
                            "wide) for storing the shared file-based"
1020
                            " disks [%s]" %
1021
                            constants.DEFAULT_SHARED_FILE_STORAGE_DIR,
1022
                            metavar="SHAREDDIR",
1023
                            default=constants.DEFAULT_SHARED_FILE_STORAGE_DIR)
1024

    
1025
NOMODIFY_ETCHOSTS_OPT = cli_option("--no-etc-hosts", dest="modify_etc_hosts",
1026
                                   help="Don't modify /etc/hosts",
1027
                                   action="store_false", default=True)
1028

    
1029
NOMODIFY_SSH_SETUP_OPT = cli_option("--no-ssh-init", dest="modify_ssh_setup",
1030
                                    help="Don't initialize SSH keys",
1031
                                    action="store_false", default=True)
1032

    
1033
ERROR_CODES_OPT = cli_option("--error-codes", dest="error_codes",
1034
                             help="Enable parseable error messages",
1035
                             action="store_true", default=False)
1036

    
1037
NONPLUS1_OPT = cli_option("--no-nplus1-mem", dest="skip_nplusone_mem",
1038
                          help="Skip N+1 memory redundancy tests",
1039
                          action="store_true", default=False)
1040

    
1041
REBOOT_TYPE_OPT = cli_option("-t", "--type", dest="reboot_type",
1042
                             help="Type of reboot: soft/hard/full",
1043
                             default=constants.INSTANCE_REBOOT_HARD,
1044
                             metavar="<REBOOT>",
1045
                             choices=list(constants.REBOOT_TYPES))
1046

    
1047
IGNORE_SECONDARIES_OPT = cli_option("--ignore-secondaries",
1048
                                    dest="ignore_secondaries",
1049
                                    default=False, action="store_true",
1050
                                    help="Ignore errors from secondaries")
1051

    
1052
NOSHUTDOWN_OPT = cli_option("--noshutdown", dest="shutdown",
1053
                            action="store_false", default=True,
1054
                            help="Don't shutdown the instance (unsafe)")
1055

    
1056
TIMEOUT_OPT = cli_option("--timeout", dest="timeout", type="int",
1057
                         default=constants.DEFAULT_SHUTDOWN_TIMEOUT,
1058
                         help="Maximum time to wait")
1059

    
1060
SHUTDOWN_TIMEOUT_OPT = cli_option("--shutdown-timeout",
1061
                         dest="shutdown_timeout", type="int",
1062
                         default=constants.DEFAULT_SHUTDOWN_TIMEOUT,
1063
                         help="Maximum time to wait for instance shutdown")
1064

    
1065
INTERVAL_OPT = cli_option("--interval", dest="interval", type="int",
1066
                          default=None,
1067
                          help=("Number of seconds between repetions of the"
1068
                                " command"))
1069

    
1070
EARLY_RELEASE_OPT = cli_option("--early-release",
1071
                               dest="early_release", default=False,
1072
                               action="store_true",
1073
                               help="Release the locks on the secondary"
1074
                               " node(s) early")
1075

    
1076
NEW_CLUSTER_CERT_OPT = cli_option("--new-cluster-certificate",
1077
                                  dest="new_cluster_cert",
1078
                                  default=False, action="store_true",
1079
                                  help="Generate a new cluster certificate")
1080

    
1081
RAPI_CERT_OPT = cli_option("--rapi-certificate", dest="rapi_cert",
1082
                           default=None,
1083
                           help="File containing new RAPI certificate")
1084

    
1085
NEW_RAPI_CERT_OPT = cli_option("--new-rapi-certificate", dest="new_rapi_cert",
1086
                               default=None, action="store_true",
1087
                               help=("Generate a new self-signed RAPI"
1088
                                     " certificate"))
1089

    
1090
NEW_CONFD_HMAC_KEY_OPT = cli_option("--new-confd-hmac-key",
1091
                                    dest="new_confd_hmac_key",
1092
                                    default=False, action="store_true",
1093
                                    help=("Create a new HMAC key for %s" %
1094
                                          constants.CONFD))
1095

    
1096
CLUSTER_DOMAIN_SECRET_OPT = cli_option("--cluster-domain-secret",
1097
                                       dest="cluster_domain_secret",
1098
                                       default=None,
1099
                                       help=("Load new new cluster domain"
1100
                                             " secret from file"))
1101

    
1102
NEW_CLUSTER_DOMAIN_SECRET_OPT = cli_option("--new-cluster-domain-secret",
1103
                                           dest="new_cluster_domain_secret",
1104
                                           default=False, action="store_true",
1105
                                           help=("Create a new cluster domain"
1106
                                                 " secret"))
1107

    
1108
USE_REPL_NET_OPT = cli_option("--use-replication-network",
1109
                              dest="use_replication_network",
1110
                              help="Whether to use the replication network"
1111
                              " for talking to the nodes",
1112
                              action="store_true", default=False)
1113

    
1114
MAINTAIN_NODE_HEALTH_OPT = \
1115
    cli_option("--maintain-node-health", dest="maintain_node_health",
1116
               metavar=_YORNO, default=None, type="bool",
1117
               help="Configure the cluster to automatically maintain node"
1118
               " health, by shutting down unknown instances, shutting down"
1119
               " unknown DRBD devices, etc.")
1120

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

    
1128
UIDPOOL_OPT = cli_option("--uid-pool", default=None,
1129
                         action="store", dest="uid_pool",
1130
                         help=("A list of user-ids or user-id"
1131
                               " ranges separated by commas"))
1132

    
1133
ADD_UIDS_OPT = cli_option("--add-uids", default=None,
1134
                          action="store", dest="add_uids",
1135
                          help=("A list of user-ids or user-id"
1136
                                " ranges separated by commas, to be"
1137
                                " added to the user-id pool"))
1138

    
1139
REMOVE_UIDS_OPT = cli_option("--remove-uids", default=None,
1140
                             action="store", dest="remove_uids",
1141
                             help=("A list of user-ids or user-id"
1142
                                   " ranges separated by commas, to be"
1143
                                   " removed from the user-id pool"))
1144

    
1145
RESERVED_LVS_OPT = cli_option("--reserved-lvs", default=None,
1146
                             action="store", dest="reserved_lvs",
1147
                             help=("A comma-separated list of reserved"
1148
                                   " logical volumes names, that will be"
1149
                                   " ignored by cluster verify"))
1150

    
1151
ROMAN_OPT = cli_option("--roman",
1152
                       dest="roman_integers", default=False,
1153
                       action="store_true",
1154
                       help="Use roman numbers for positive integers")
1155

    
1156
DRBD_HELPER_OPT = cli_option("--drbd-usermode-helper", dest="drbd_helper",
1157
                             action="store", default=None,
1158
                             help="Specifies usermode helper for DRBD")
1159

    
1160
NODRBD_STORAGE_OPT = cli_option("--no-drbd-storage", dest="drbd_storage",
1161
                                action="store_false", default=True,
1162
                                help="Disable support for DRBD")
1163

    
1164
PRIMARY_IP_VERSION_OPT = \
1165
    cli_option("--primary-ip-version", default=constants.IP4_VERSION,
1166
               action="store", dest="primary_ip_version",
1167
               metavar="%d|%d" % (constants.IP4_VERSION,
1168
                                  constants.IP6_VERSION),
1169
               help="Cluster-wide IP version for primary IP")
1170

    
1171
PRIORITY_OPT = cli_option("--priority", default=None, dest="priority",
1172
                          metavar="|".join(name for name, _ in _PRIORITY_NAMES),
1173
                          choices=_PRIONAME_TO_VALUE.keys(),
1174
                          help="Priority for opcode processing")
1175

    
1176
HID_OS_OPT = cli_option("--hidden", dest="hidden",
1177
                        type="bool", default=None, metavar=_YORNO,
1178
                        help="Sets the hidden flag on the OS")
1179

    
1180
BLK_OS_OPT = cli_option("--blacklisted", dest="blacklisted",
1181
                        type="bool", default=None, metavar=_YORNO,
1182
                        help="Sets the blacklisted flag on the OS")
1183

    
1184
PREALLOC_WIPE_DISKS_OPT = cli_option("--prealloc-wipe-disks", default=None,
1185
                                     type="bool", metavar=_YORNO,
1186
                                     dest="prealloc_wipe_disks",
1187
                                     help=("Wipe disks prior to instance"
1188
                                           " creation"))
1189

    
1190
NODE_PARAMS_OPT = cli_option("--node-parameters", dest="ndparams",
1191
                             type="keyval", default=None,
1192
                             help="Node parameters")
1193

    
1194
ALLOC_POLICY_OPT = cli_option("--alloc-policy", dest="alloc_policy",
1195
                              action="store", metavar="POLICY", default=None,
1196
                              help="Allocation policy for the node group")
1197

    
1198
NODE_POWERED_OPT = cli_option("--node-powered", default=None,
1199
                              type="bool", metavar=_YORNO,
1200
                              dest="node_powered",
1201
                              help="Specify if the SoR for node is powered")
1202

    
1203
OOB_TIMEOUT_OPT = cli_option("--oob-timeout", dest="oob_timeout", type="int",
1204
                         default=constants.OOB_TIMEOUT,
1205
                         help="Maximum time to wait for out-of-band helper")
1206

    
1207
POWER_DELAY_OPT = cli_option("--power-delay", dest="power_delay", type="float",
1208
                             default=constants.OOB_POWER_DELAY,
1209
                             help="Time in seconds to wait between power-ons")
1210

    
1211
FORCE_FILTER_OPT = cli_option("-F", "--filter", dest="force_filter",
1212
                              action="store_true", default=False,
1213
                              help=("Whether command argument should be treated"
1214
                                    " as filter"))
1215

    
1216
NO_REMEMBER_OPT = cli_option("--no-remember",
1217
                             dest="no_remember",
1218
                             action="store_true", default=False,
1219
                             help="Perform but do not record the change"
1220
                             " in the configuration")
1221

    
1222
PRIMARY_ONLY_OPT = cli_option("-p", "--primary-only",
1223
                              default=False, action="store_true",
1224
                              help="Evacuate primary instances only")
1225

    
1226
SECONDARY_ONLY_OPT = cli_option("-s", "--secondary-only",
1227
                                default=False, action="store_true",
1228
                                help="Evacuate secondary instances only"
1229
                                     " (applies only to internally mirrored"
1230
                                     " disk templates, e.g. %s)" %
1231
                                     utils.CommaJoin(constants.DTS_INT_MIRROR))
1232

    
1233
STARTUP_PAUSED_OPT = cli_option("--paused", dest="startup_paused",
1234
                                action="store_true", default=False,
1235
                                help="Pause instance at startup")
1236

    
1237
TO_GROUP_OPT = cli_option("--to", dest="to", metavar="<group>",
1238
                          help="Destination node group (name or uuid)",
1239
                          default=None, action="append",
1240
                          completion_suggest=OPT_COMPL_ONE_NODEGROUP)
1241

    
1242

    
1243
#: Options provided by all commands
1244
COMMON_OPTS = [DEBUG_OPT]
1245

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

    
1270

    
1271
def _ParseArgs(argv, commands, aliases):
1272
  """Parser for the command line arguments.
1273

1274
  This function parses the arguments and returns the function which
1275
  must be executed together with its (modified) arguments.
1276

1277
  @param argv: the command line
1278
  @param commands: dictionary with special contents, see the design
1279
      doc for cmdline handling
1280
  @param aliases: dictionary with command aliases {'alias': 'target, ...}
1281

1282
  """
1283
  if len(argv) == 0:
1284
    binary = "<command>"
1285
  else:
1286
    binary = argv[0].split("/")[-1]
1287

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

    
1295
  if len(argv) < 2 or not (argv[1] in commands or
1296
                           argv[1] in aliases):
1297
    # let's do a nice thing
1298
    sortedcmds = commands.keys()
1299
    sortedcmds.sort()
1300

    
1301
    ToStdout("Usage: %s {command} [options...] [argument...]", binary)
1302
    ToStdout("%s <command> --help to see details, or man %s", binary, binary)
1303
    ToStdout("")
1304

    
1305
    # compute the max line length for cmd + usage
1306
    mlen = max([len(" %s" % cmd) for cmd in commands])
1307
    mlen = min(60, mlen) # should not get here...
1308

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

    
1319
    ToStdout("")
1320

    
1321
    return None, None, None
1322

    
1323
  # get command, unalias it, and look it up in commands
1324
  cmd = argv.pop(1)
1325
  if cmd in aliases:
1326
    if cmd in commands:
1327
      raise errors.ProgrammerError("Alias '%s' overrides an existing"
1328
                                   " command" % cmd)
1329

    
1330
    if aliases[cmd] not in commands:
1331
      raise errors.ProgrammerError("Alias '%s' maps to non-existing"
1332
                                   " command '%s'" % (cmd, aliases[cmd]))
1333

    
1334
    cmd = aliases[cmd]
1335

    
1336
  func, args_def, parser_opts, usage, description = commands[cmd]
1337
  parser = CustomOptionParser(option_list=parser_opts + COMMON_OPTS,
1338
                              description=description,
1339
                              formatter=TitledHelpFormatter(),
1340
                              usage="%%prog %s %s" % (cmd, usage))
1341
  parser.disable_interspersed_args()
1342
  options, args = parser.parse_args()
1343

    
1344
  if not _CheckArguments(cmd, args_def, args):
1345
    return None, None, None
1346

    
1347
  return func, options, args
1348

    
1349

    
1350
class CustomOptionParser(optparse.OptionParser):
1351
  def _match_long_opt(self, opt):
1352
    """Override C{OptionParser}'s function for matching long options.
1353

1354
    The default implementation does prefix-based abbreviation matching. We
1355
    disable such behaviour as it can can lead to confusing conflicts (e.g.
1356
    C{--force} and C{--force-multi}).
1357

1358
    """
1359
    if opt in self._long_opt:
1360
      return opt
1361
    else:
1362
      raise optparse.BadOptionError(opt)
1363

    
1364

    
1365
def _CheckArguments(cmd, args_def, args):
1366
  """Verifies the arguments using the argument definition.
1367

1368
  Algorithm:
1369

1370
    1. Abort with error if values specified by user but none expected.
1371

1372
    1. For each argument in definition
1373

1374
      1. Keep running count of minimum number of values (min_count)
1375
      1. Keep running count of maximum number of values (max_count)
1376
      1. If it has an unlimited number of values
1377

1378
        1. Abort with error if it's not the last argument in the definition
1379

1380
    1. If last argument has limited number of values
1381

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

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

1386
  """
1387
  if args and not args_def:
1388
    ToStderr("Error: Command %s expects no arguments", cmd)
1389
    return False
1390

    
1391
  min_count = None
1392
  max_count = None
1393
  check_max = None
1394

    
1395
  last_idx = len(args_def) - 1
1396

    
1397
  for idx, arg in enumerate(args_def):
1398
    if min_count is None:
1399
      min_count = arg.min
1400
    elif arg.min is not None:
1401
      min_count += arg.min
1402

    
1403
    if max_count is None:
1404
      max_count = arg.max
1405
    elif arg.max is not None:
1406
      max_count += arg.max
1407

    
1408
    if idx == last_idx:
1409
      check_max = (arg.max is not None)
1410

    
1411
    elif arg.max is None:
1412
      raise errors.ProgrammerError("Only the last argument can have max=None")
1413

    
1414
  if check_max:
1415
    # Command with exact number of arguments
1416
    if (min_count is not None and max_count is not None and
1417
        min_count == max_count and len(args) != min_count):
1418
      ToStderr("Error: Command %s expects %d argument(s)", cmd, min_count)
1419
      return False
1420

    
1421
    # Command with limited number of arguments
1422
    if max_count is not None and len(args) > max_count:
1423
      ToStderr("Error: Command %s expects only %d argument(s)",
1424
               cmd, max_count)
1425
      return False
1426

    
1427
  # Command with some required arguments
1428
  if min_count is not None and len(args) < min_count:
1429
    ToStderr("Error: Command %s expects at least %d argument(s)",
1430
             cmd, min_count)
1431
    return False
1432

    
1433
  return True
1434

    
1435

    
1436
def SplitNodeOption(value):
1437
  """Splits the value of a --node option.
1438

1439
  """
1440
  if value and ":" in value:
1441
    return value.split(":", 1)
1442
  else:
1443
    return (value, None)
1444

    
1445

    
1446
def CalculateOSNames(os_name, os_variants):
1447
  """Calculates all the names an OS can be called, according to its variants.
1448

1449
  @type os_name: string
1450
  @param os_name: base name of the os
1451
  @type os_variants: list or None
1452
  @param os_variants: list of supported variants
1453
  @rtype: list
1454
  @return: list of valid names
1455

1456
  """
1457
  if os_variants:
1458
    return ["%s+%s" % (os_name, v) for v in os_variants]
1459
  else:
1460
    return [os_name]
1461

    
1462

    
1463
def ParseFields(selected, default):
1464
  """Parses the values of "--field"-like options.
1465

1466
  @type selected: string or None
1467
  @param selected: User-selected options
1468
  @type default: list
1469
  @param default: Default fields
1470

1471
  """
1472
  if selected is None:
1473
    return default
1474

    
1475
  if selected.startswith("+"):
1476
    return default + selected[1:].split(",")
1477

    
1478
  return selected.split(",")
1479

    
1480

    
1481
UsesRPC = rpc.RunWithRPC
1482

    
1483

    
1484
def AskUser(text, choices=None):
1485
  """Ask the user a question.
1486

1487
  @param text: the question to ask
1488

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

1494
  @return: one of the return values from the choices list; if input is
1495
      not possible (i.e. not running with a tty, we return the last
1496
      entry from the list
1497

1498
  """
1499
  if choices is None:
1500
    choices = [("y", True, "Perform the operation"),
1501
               ("n", False, "Do not perform the operation")]
1502
  if not choices or not isinstance(choices, list):
1503
    raise errors.ProgrammerError("Invalid choices argument to AskUser")
1504
  for entry in choices:
1505
    if not isinstance(entry, tuple) or len(entry) < 3 or entry[0] == "?":
1506
      raise errors.ProgrammerError("Invalid choices element to AskUser")
1507

    
1508
  answer = choices[-1][1]
1509
  new_text = []
1510
  for line in text.splitlines():
1511
    new_text.append(textwrap.fill(line, 70, replace_whitespace=False))
1512
  text = "\n".join(new_text)
1513
  try:
1514
    f = file("/dev/tty", "a+")
1515
  except IOError:
1516
    return answer
1517
  try:
1518
    chars = [entry[0] for entry in choices]
1519
    chars[-1] = "[%s]" % chars[-1]
1520
    chars.append("?")
1521
    maps = dict([(entry[0], entry[1]) for entry in choices])
1522
    while True:
1523
      f.write(text)
1524
      f.write("\n")
1525
      f.write("/".join(chars))
1526
      f.write(": ")
1527
      line = f.readline(2).strip().lower()
1528
      if line in maps:
1529
        answer = maps[line]
1530
        break
1531
      elif line == "?":
1532
        for entry in choices:
1533
          f.write(" %s - %s\n" % (entry[0], entry[2]))
1534
        f.write("\n")
1535
        continue
1536
  finally:
1537
    f.close()
1538
  return answer
1539

    
1540

    
1541
class JobSubmittedException(Exception):
1542
  """Job was submitted, client should exit.
1543

1544
  This exception has one argument, the ID of the job that was
1545
  submitted. The handler should print this ID.
1546

1547
  This is not an error, just a structured way to exit from clients.
1548

1549
  """
1550

    
1551

    
1552
def SendJob(ops, cl=None):
1553
  """Function to submit an opcode without waiting for the results.
1554

1555
  @type ops: list
1556
  @param ops: list of opcodes
1557
  @type cl: luxi.Client
1558
  @param cl: the luxi client to use for communicating with the master;
1559
             if None, a new client will be created
1560

1561
  """
1562
  if cl is None:
1563
    cl = GetClient()
1564

    
1565
  job_id = cl.SubmitJob(ops)
1566

    
1567
  return job_id
1568

    
1569

    
1570
def GenericPollJob(job_id, cbs, report_cbs):
1571
  """Generic job-polling function.
1572

1573
  @type job_id: number
1574
  @param job_id: Job ID
1575
  @type cbs: Instance of L{JobPollCbBase}
1576
  @param cbs: Data callbacks
1577
  @type report_cbs: Instance of L{JobPollReportCbBase}
1578
  @param report_cbs: Reporting callbacks
1579

1580
  """
1581
  prev_job_info = None
1582
  prev_logmsg_serial = None
1583

    
1584
  status = None
1585

    
1586
  while True:
1587
    result = cbs.WaitForJobChangeOnce(job_id, ["status"], prev_job_info,
1588
                                      prev_logmsg_serial)
1589
    if not result:
1590
      # job not found, go away!
1591
      raise errors.JobLost("Job with id %s lost" % job_id)
1592

    
1593
    if result == constants.JOB_NOTCHANGED:
1594
      report_cbs.ReportNotChanged(job_id, status)
1595

    
1596
      # Wait again
1597
      continue
1598

    
1599
    # Split result, a tuple of (field values, log entries)
1600
    (job_info, log_entries) = result
1601
    (status, ) = job_info
1602

    
1603
    if log_entries:
1604
      for log_entry in log_entries:
1605
        (serial, timestamp, log_type, message) = log_entry
1606
        report_cbs.ReportLogMessage(job_id, serial, timestamp,
1607
                                    log_type, message)
1608
        prev_logmsg_serial = max(prev_logmsg_serial, serial)
1609

    
1610
    # TODO: Handle canceled and archived jobs
1611
    elif status in (constants.JOB_STATUS_SUCCESS,
1612
                    constants.JOB_STATUS_ERROR,
1613
                    constants.JOB_STATUS_CANCELING,
1614
                    constants.JOB_STATUS_CANCELED):
1615
      break
1616

    
1617
    prev_job_info = job_info
1618

    
1619
  jobs = cbs.QueryJobs([job_id], ["status", "opstatus", "opresult"])
1620
  if not jobs:
1621
    raise errors.JobLost("Job with id %s lost" % job_id)
1622

    
1623
  status, opstatus, result = jobs[0]
1624

    
1625
  if status == constants.JOB_STATUS_SUCCESS:
1626
    return result
1627

    
1628
  if status in (constants.JOB_STATUS_CANCELING, constants.JOB_STATUS_CANCELED):
1629
    raise errors.OpExecError("Job was canceled")
1630

    
1631
  has_ok = False
1632
  for idx, (status, msg) in enumerate(zip(opstatus, result)):
1633
    if status == constants.OP_STATUS_SUCCESS:
1634
      has_ok = True
1635
    elif status == constants.OP_STATUS_ERROR:
1636
      errors.MaybeRaise(msg)
1637

    
1638
      if has_ok:
1639
        raise errors.OpExecError("partial failure (opcode %d): %s" %
1640
                                 (idx, msg))
1641

    
1642
      raise errors.OpExecError(str(msg))
1643

    
1644
  # default failure mode
1645
  raise errors.OpExecError(result)
1646

    
1647

    
1648
class JobPollCbBase:
1649
  """Base class for L{GenericPollJob} callbacks.
1650

1651
  """
1652
  def __init__(self):
1653
    """Initializes this class.
1654

1655
    """
1656

    
1657
  def WaitForJobChangeOnce(self, job_id, fields,
1658
                           prev_job_info, prev_log_serial):
1659
    """Waits for changes on a job.
1660

1661
    """
1662
    raise NotImplementedError()
1663

    
1664
  def QueryJobs(self, job_ids, fields):
1665
    """Returns the selected fields for the selected job IDs.
1666

1667
    @type job_ids: list of numbers
1668
    @param job_ids: Job IDs
1669
    @type fields: list of strings
1670
    @param fields: Fields
1671

1672
    """
1673
    raise NotImplementedError()
1674

    
1675

    
1676
class JobPollReportCbBase:
1677
  """Base class for L{GenericPollJob} reporting callbacks.
1678

1679
  """
1680
  def __init__(self):
1681
    """Initializes this class.
1682

1683
    """
1684

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

1688
    """
1689
    raise NotImplementedError()
1690

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

1694
    @type job_id: number
1695
    @param job_id: Job ID
1696
    @type status: string or None
1697
    @param status: Job status if available
1698

1699
    """
1700
    raise NotImplementedError()
1701

    
1702

    
1703
class _LuxiJobPollCb(JobPollCbBase):
1704
  def __init__(self, cl):
1705
    """Initializes this class.
1706

1707
    """
1708
    JobPollCbBase.__init__(self)
1709
    self.cl = cl
1710

    
1711
  def WaitForJobChangeOnce(self, job_id, fields,
1712
                           prev_job_info, prev_log_serial):
1713
    """Waits for changes on a job.
1714

1715
    """
1716
    return self.cl.WaitForJobChangeOnce(job_id, fields,
1717
                                        prev_job_info, prev_log_serial)
1718

    
1719
  def QueryJobs(self, job_ids, fields):
1720
    """Returns the selected fields for the selected job IDs.
1721

1722
    """
1723
    return self.cl.QueryJobs(job_ids, fields)
1724

    
1725

    
1726
class FeedbackFnJobPollReportCb(JobPollReportCbBase):
1727
  def __init__(self, feedback_fn):
1728
    """Initializes this class.
1729

1730
    """
1731
    JobPollReportCbBase.__init__(self)
1732

    
1733
    self.feedback_fn = feedback_fn
1734

    
1735
    assert callable(feedback_fn)
1736

    
1737
  def ReportLogMessage(self, job_id, serial, timestamp, log_type, log_msg):
1738
    """Handles a log message.
1739

1740
    """
1741
    self.feedback_fn((timestamp, log_type, log_msg))
1742

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

1746
    """
1747
    # Ignore
1748

    
1749

    
1750
class StdioJobPollReportCb(JobPollReportCbBase):
1751
  def __init__(self):
1752
    """Initializes this class.
1753

1754
    """
1755
    JobPollReportCbBase.__init__(self)
1756

    
1757
    self.notified_queued = False
1758
    self.notified_waitlock = False
1759

    
1760
  def ReportLogMessage(self, job_id, serial, timestamp, log_type, log_msg):
1761
    """Handles a log message.
1762

1763
    """
1764
    ToStdout("%s %s", time.ctime(utils.MergeTime(timestamp)),
1765
             FormatLogMessage(log_type, log_msg))
1766

    
1767
  def ReportNotChanged(self, job_id, status):
1768
    """Called if a job hasn't changed in a while.
1769

1770
    """
1771
    if status is None:
1772
      return
1773

    
1774
    if status == constants.JOB_STATUS_QUEUED and not self.notified_queued:
1775
      ToStderr("Job %s is waiting in queue", job_id)
1776
      self.notified_queued = True
1777

    
1778
    elif status == constants.JOB_STATUS_WAITING and not self.notified_waitlock:
1779
      ToStderr("Job %s is trying to acquire all necessary locks", job_id)
1780
      self.notified_waitlock = True
1781

    
1782

    
1783
def FormatLogMessage(log_type, log_msg):
1784
  """Formats a job message according to its type.
1785

1786
  """
1787
  if log_type != constants.ELOG_MESSAGE:
1788
    log_msg = str(log_msg)
1789

    
1790
  return utils.SafeEncode(log_msg)
1791

    
1792

    
1793
def PollJob(job_id, cl=None, feedback_fn=None, reporter=None):
1794
  """Function to poll for the result of a job.
1795

1796
  @type job_id: job identified
1797
  @param job_id: the job to poll for results
1798
  @type cl: luxi.Client
1799
  @param cl: the luxi client to use for communicating with the master;
1800
             if None, a new client will be created
1801

1802
  """
1803
  if cl is None:
1804
    cl = GetClient()
1805

    
1806
  if reporter is None:
1807
    if feedback_fn:
1808
      reporter = FeedbackFnJobPollReportCb(feedback_fn)
1809
    else:
1810
      reporter = StdioJobPollReportCb()
1811
  elif feedback_fn:
1812
    raise errors.ProgrammerError("Can't specify reporter and feedback function")
1813

    
1814
  return GenericPollJob(job_id, _LuxiJobPollCb(cl), reporter)
1815

    
1816

    
1817
def SubmitOpCode(op, cl=None, feedback_fn=None, opts=None, reporter=None):
1818
  """Legacy function to submit an opcode.
1819

1820
  This is just a simple wrapper over the construction of the processor
1821
  instance. It should be extended to better handle feedback and
1822
  interaction functions.
1823

1824
  """
1825
  if cl is None:
1826
    cl = GetClient()
1827

    
1828
  SetGenericOpcodeOpts([op], opts)
1829

    
1830
  job_id = SendJob([op], cl=cl)
1831

    
1832
  op_results = PollJob(job_id, cl=cl, feedback_fn=feedback_fn,
1833
                       reporter=reporter)
1834

    
1835
  return op_results[0]
1836

    
1837

    
1838
def SubmitOrSend(op, opts, cl=None, feedback_fn=None):
1839
  """Wrapper around SubmitOpCode or SendJob.
1840

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

1846
  It will also process the opcodes if we're sending the via SendJob
1847
  (otherwise SubmitOpCode does it).
1848

1849
  """
1850
  if opts and opts.submit_only:
1851
    job = [op]
1852
    SetGenericOpcodeOpts(job, opts)
1853
    job_id = SendJob(job, cl=cl)
1854
    raise JobSubmittedException(job_id)
1855
  else:
1856
    return SubmitOpCode(op, cl=cl, feedback_fn=feedback_fn, opts=opts)
1857

    
1858

    
1859
def SetGenericOpcodeOpts(opcode_list, options):
1860
  """Processor for generic options.
1861

1862
  This function updates the given opcodes based on generic command
1863
  line options (like debug, dry-run, etc.).
1864

1865
  @param opcode_list: list of opcodes
1866
  @param options: command line options or None
1867
  @return: None (in-place modification)
1868

1869
  """
1870
  if not options:
1871
    return
1872
  for op in opcode_list:
1873
    op.debug_level = options.debug
1874
    if hasattr(options, "dry_run"):
1875
      op.dry_run = options.dry_run
1876
    if getattr(options, "priority", None) is not None:
1877
      op.priority = _PRIONAME_TO_VALUE[options.priority]
1878

    
1879

    
1880
def GetClient():
1881
  # TODO: Cache object?
1882
  try:
1883
    client = luxi.Client()
1884
  except luxi.NoMasterError:
1885
    ss = ssconf.SimpleStore()
1886

    
1887
    # Try to read ssconf file
1888
    try:
1889
      ss.GetMasterNode()
1890
    except errors.ConfigurationError:
1891
      raise errors.OpPrereqError("Cluster not initialized or this machine is"
1892
                                 " not part of a cluster")
1893

    
1894
    master, myself = ssconf.GetMasterAndMyself(ss=ss)
1895
    if master != myself:
1896
      raise errors.OpPrereqError("This is not the master node, please connect"
1897
                                 " to node '%s' and rerun the command" %
1898
                                 master)
1899
    raise
1900
  return client
1901

    
1902

    
1903
def FormatError(err):
1904
  """Return a formatted error message for a given error.
1905

1906
  This function takes an exception instance and returns a tuple
1907
  consisting of two values: first, the recommended exit code, and
1908
  second, a string describing the error message (not
1909
  newline-terminated).
1910

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

    
1990

    
1991
def GenericMain(commands, override=None, aliases=None):
1992
  """Generic main function for all the gnt-* commands.
1993

1994
  Arguments:
1995
    - commands: a dictionary with a special structure, see the design doc
1996
                for command line handling.
1997
    - override: if not None, we expect a dictionary with keys that will
1998
                override command line options; this can be used to pass
1999
                options from the scripts to generic functions
2000
    - aliases: dictionary with command aliases {'alias': 'target, ...}
2001

2002
  """
2003
  # save the program name and the entire command line for later logging
2004
  if sys.argv:
2005
    binary = os.path.basename(sys.argv[0]) or sys.argv[0]
2006
    if len(sys.argv) >= 2:
2007
      binary += " " + sys.argv[1]
2008
      old_cmdline = " ".join(sys.argv[2:])
2009
    else:
2010
      old_cmdline = ""
2011
  else:
2012
    binary = "<unknown program>"
2013
    old_cmdline = ""
2014

    
2015
  if aliases is None:
2016
    aliases = {}
2017

    
2018
  try:
2019
    func, options, args = _ParseArgs(sys.argv, commands, aliases)
2020
  except errors.ParameterError, err:
2021
    result, err_msg = FormatError(err)
2022
    ToStderr(err_msg)
2023
    return 1
2024

    
2025
  if func is None: # parse error
2026
    return 1
2027

    
2028
  if override is not None:
2029
    for key, val in override.iteritems():
2030
      setattr(options, key, val)
2031

    
2032
  utils.SetupLogging(constants.LOG_COMMANDS, binary, debug=options.debug,
2033
                     stderr_logging=True)
2034

    
2035
  if old_cmdline:
2036
    logging.info("run with arguments '%s'", old_cmdline)
2037
  else:
2038
    logging.info("run with no arguments")
2039

    
2040
  try:
2041
    result = func(options, args)
2042
  except (errors.GenericError, luxi.ProtocolError,
2043
          JobSubmittedException), err:
2044
    result, err_msg = FormatError(err)
2045
    logging.exception("Error during command processing")
2046
    ToStderr(err_msg)
2047
  except KeyboardInterrupt:
2048
    result = constants.EXIT_FAILURE
2049
    ToStderr("Aborted. Note that if the operation created any jobs, they"
2050
             " might have been submitted and"
2051
             " will continue to run in the background.")
2052
  except IOError, err:
2053
    if err.errno == errno.EPIPE:
2054
      # our terminal went away, we'll exit
2055
      sys.exit(constants.EXIT_FAILURE)
2056
    else:
2057
      raise
2058

    
2059
  return result
2060

    
2061

    
2062
def ParseNicOption(optvalue):
2063
  """Parses the value of the --net option(s).
2064

2065
  """
2066
  try:
2067
    nic_max = max(int(nidx[0]) + 1 for nidx in optvalue)
2068
  except (TypeError, ValueError), err:
2069
    raise errors.OpPrereqError("Invalid NIC index passed: %s" % str(err))
2070

    
2071
  nics = [{}] * nic_max
2072
  for nidx, ndict in optvalue:
2073
    nidx = int(nidx)
2074

    
2075
    if not isinstance(ndict, dict):
2076
      raise errors.OpPrereqError("Invalid nic/%d value: expected dict,"
2077
                                 " got %s" % (nidx, ndict))
2078

    
2079
    utils.ForceDictType(ndict, constants.INIC_PARAMS_TYPES)
2080

    
2081
    nics[nidx] = ndict
2082

    
2083
  return nics
2084

    
2085

    
2086
def GenericInstanceCreate(mode, opts, args):
2087
  """Add an instance to the cluster via either creation or import.
2088

2089
  @param mode: constants.INSTANCE_CREATE or constants.INSTANCE_IMPORT
2090
  @param opts: the command line options selected by the user
2091
  @type args: list
2092
  @param args: should contain only one element, the new instance name
2093
  @rtype: int
2094
  @return: the desired exit code
2095

2096
  """
2097
  instance = args[0]
2098

    
2099
  (pnode, snode) = SplitNodeOption(opts.node)
2100

    
2101
  hypervisor = None
2102
  hvparams = {}
2103
  if opts.hypervisor:
2104
    hypervisor, hvparams = opts.hypervisor
2105

    
2106
  if opts.nics:
2107
    nics = ParseNicOption(opts.nics)
2108
  elif opts.no_nics:
2109
    # no nics
2110
    nics = []
2111
  elif mode == constants.INSTANCE_CREATE:
2112
    # default of one nic, all auto
2113
    nics = [{}]
2114
  else:
2115
    # mode == import
2116
    nics = []
2117

    
2118
  if opts.disk_template == constants.DT_DISKLESS:
2119
    if opts.disks or opts.sd_size is not None:
2120
      raise errors.OpPrereqError("Diskless instance but disk"
2121
                                 " information passed")
2122
    disks = []
2123
  else:
2124
    if (not opts.disks and not opts.sd_size
2125
        and mode == constants.INSTANCE_CREATE):
2126
      raise errors.OpPrereqError("No disk information specified")
2127
    if opts.disks and opts.sd_size is not None:
2128
      raise errors.OpPrereqError("Please use either the '--disk' or"
2129
                                 " '-s' option")
2130
    if opts.sd_size is not None:
2131
      opts.disks = [(0, {constants.IDISK_SIZE: opts.sd_size})]
2132

    
2133
    if opts.disks:
2134
      try:
2135
        disk_max = max(int(didx[0]) + 1 for didx in opts.disks)
2136
      except ValueError, err:
2137
        raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err))
2138
      disks = [{}] * disk_max
2139
    else:
2140
      disks = []
2141
    for didx, ddict in opts.disks:
2142
      didx = int(didx)
2143
      if not isinstance(ddict, dict):
2144
        msg = "Invalid disk/%d value: expected dict, got %s" % (didx, ddict)
2145
        raise errors.OpPrereqError(msg)
2146
      elif constants.IDISK_SIZE in ddict:
2147
        if constants.IDISK_ADOPT in ddict:
2148
          raise errors.OpPrereqError("Only one of 'size' and 'adopt' allowed"
2149
                                     " (disk %d)" % didx)
2150
        try:
2151
          ddict[constants.IDISK_SIZE] = \
2152
            utils.ParseUnit(ddict[constants.IDISK_SIZE])
2153
        except ValueError, err:
2154
          raise errors.OpPrereqError("Invalid disk size for disk %d: %s" %
2155
                                     (didx, err))
2156
      elif constants.IDISK_ADOPT in ddict:
2157
        if mode == constants.INSTANCE_IMPORT:
2158
          raise errors.OpPrereqError("Disk adoption not allowed for instance"
2159
                                     " import")
2160
        ddict[constants.IDISK_SIZE] = 0
2161
      else:
2162
        raise errors.OpPrereqError("Missing size or adoption source for"
2163
                                   " disk %d" % didx)
2164
      disks[didx] = ddict
2165

    
2166
  if opts.tags is not None:
2167
    tags = opts.tags.split(",")
2168
  else:
2169
    tags = []
2170

    
2171
  utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_TYPES)
2172
  utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
2173

    
2174
  if mode == constants.INSTANCE_CREATE:
2175
    start = opts.start
2176
    os_type = opts.os
2177
    force_variant = opts.force_variant
2178
    src_node = None
2179
    src_path = None
2180
    no_install = opts.no_install
2181
    identify_defaults = False
2182
  elif mode == constants.INSTANCE_IMPORT:
2183
    start = False
2184
    os_type = None
2185
    force_variant = False
2186
    src_node = opts.src_node
2187
    src_path = opts.src_dir
2188
    no_install = None
2189
    identify_defaults = opts.identify_defaults
2190
  else:
2191
    raise errors.ProgrammerError("Invalid creation mode %s" % mode)
2192

    
2193
  op = opcodes.OpInstanceCreate(instance_name=instance,
2194
                                disks=disks,
2195
                                disk_template=opts.disk_template,
2196
                                nics=nics,
2197
                                pnode=pnode, snode=snode,
2198
                                ip_check=opts.ip_check,
2199
                                name_check=opts.name_check,
2200
                                wait_for_sync=opts.wait_for_sync,
2201
                                file_storage_dir=opts.file_storage_dir,
2202
                                file_driver=opts.file_driver,
2203
                                iallocator=opts.iallocator,
2204
                                hypervisor=hypervisor,
2205
                                hvparams=hvparams,
2206
                                beparams=opts.beparams,
2207
                                osparams=opts.osparams,
2208
                                mode=mode,
2209
                                start=start,
2210
                                os_type=os_type,
2211
                                force_variant=force_variant,
2212
                                src_node=src_node,
2213
                                src_path=src_path,
2214
                                tags=tags,
2215
                                no_install=no_install,
2216
                                identify_defaults=identify_defaults)
2217

    
2218
  SubmitOrSend(op, opts)
2219
  return 0
2220

    
2221

    
2222
class _RunWhileClusterStoppedHelper:
2223
  """Helper class for L{RunWhileClusterStopped} to simplify state management
2224

2225
  """
2226
  def __init__(self, feedback_fn, cluster_name, master_node, online_nodes):
2227
    """Initializes this class.
2228

2229
    @type feedback_fn: callable
2230
    @param feedback_fn: Feedback function
2231
    @type cluster_name: string
2232
    @param cluster_name: Cluster name
2233
    @type master_node: string
2234
    @param master_node Master node name
2235
    @type online_nodes: list
2236
    @param online_nodes: List of names of online nodes
2237

2238
    """
2239
    self.feedback_fn = feedback_fn
2240
    self.cluster_name = cluster_name
2241
    self.master_node = master_node
2242
    self.online_nodes = online_nodes
2243

    
2244
    self.ssh = ssh.SshRunner(self.cluster_name)
2245

    
2246
    self.nonmaster_nodes = [name for name in online_nodes
2247
                            if name != master_node]
2248

    
2249
    assert self.master_node not in self.nonmaster_nodes
2250

    
2251
  def _RunCmd(self, node_name, cmd):
2252
    """Runs a command on the local or a remote machine.
2253

2254
    @type node_name: string
2255
    @param node_name: Machine name
2256
    @type cmd: list
2257
    @param cmd: Command
2258

2259
    """
2260
    if node_name is None or node_name == self.master_node:
2261
      # No need to use SSH
2262
      result = utils.RunCmd(cmd)
2263
    else:
2264
      result = self.ssh.Run(node_name, "root", utils.ShellQuoteArgs(cmd))
2265

    
2266
    if result.failed:
2267
      errmsg = ["Failed to run command %s" % result.cmd]
2268
      if node_name:
2269
        errmsg.append("on node %s" % node_name)
2270
      errmsg.append(": exitcode %s and error %s" %
2271
                    (result.exit_code, result.output))
2272
      raise errors.OpExecError(" ".join(errmsg))
2273

    
2274
  def Call(self, fn, *args):
2275
    """Call function while all daemons are stopped.
2276

2277
    @type fn: callable
2278
    @param fn: Function to be called
2279

2280
    """
2281
    # Pause watcher by acquiring an exclusive lock on watcher state file
2282
    self.feedback_fn("Blocking watcher")
2283
    watcher_block = utils.FileLock.Open(constants.WATCHER_LOCK_FILE)
2284
    try:
2285
      # TODO: Currently, this just blocks. There's no timeout.
2286
      # TODO: Should it be a shared lock?
2287
      watcher_block.Exclusive(blocking=True)
2288

    
2289
      # Stop master daemons, so that no new jobs can come in and all running
2290
      # ones are finished
2291
      self.feedback_fn("Stopping master daemons")
2292
      self._RunCmd(None, [constants.DAEMON_UTIL, "stop-master"])
2293
      try:
2294
        # Stop daemons on all nodes
2295
        for node_name in self.online_nodes:
2296
          self.feedback_fn("Stopping daemons on %s" % node_name)
2297
          self._RunCmd(node_name, [constants.DAEMON_UTIL, "stop-all"])
2298

    
2299
        # All daemons are shut down now
2300
        try:
2301
          return fn(self, *args)
2302
        except Exception, err:
2303
          _, errmsg = FormatError(err)
2304
          logging.exception("Caught exception")
2305
          self.feedback_fn(errmsg)
2306
          raise
2307
      finally:
2308
        # Start cluster again, master node last
2309
        for node_name in self.nonmaster_nodes + [self.master_node]:
2310
          self.feedback_fn("Starting daemons on %s" % node_name)
2311
          self._RunCmd(node_name, [constants.DAEMON_UTIL, "start-all"])
2312
    finally:
2313
      # Resume watcher
2314
      watcher_block.Close()
2315

    
2316

    
2317
def RunWhileClusterStopped(feedback_fn, fn, *args):
2318
  """Calls a function while all cluster daemons are stopped.
2319

2320
  @type feedback_fn: callable
2321
  @param feedback_fn: Feedback function
2322
  @type fn: callable
2323
  @param fn: Function to be called when daemons are stopped
2324

2325
  """
2326
  feedback_fn("Gathering cluster information")
2327

    
2328
  # This ensures we're running on the master daemon
2329
  cl = GetClient()
2330

    
2331
  (cluster_name, master_node) = \
2332
    cl.QueryConfigValues(["cluster_name", "master_node"])
2333

    
2334
  online_nodes = GetOnlineNodes([], cl=cl)
2335

    
2336
  # Don't keep a reference to the client. The master daemon will go away.
2337
  del cl
2338

    
2339
  assert master_node in online_nodes
2340

    
2341
  return _RunWhileClusterStoppedHelper(feedback_fn, cluster_name, master_node,
2342
                                       online_nodes).Call(fn, *args)
2343

    
2344

    
2345
def GenerateTable(headers, fields, separator, data,
2346
                  numfields=None, unitfields=None,
2347
                  units=None):
2348
  """Prints a table with headers and different fields.
2349

2350
  @type headers: dict
2351
  @param headers: dictionary mapping field names to headers for
2352
      the table
2353
  @type fields: list
2354
  @param fields: the field names corresponding to each row in
2355
      the data field
2356
  @param separator: the separator to be used; if this is None,
2357
      the default 'smart' algorithm is used which computes optimal
2358
      field width, otherwise just the separator is used between
2359
      each field
2360
  @type data: list
2361
  @param data: a list of lists, each sublist being one row to be output
2362
  @type numfields: list
2363
  @param numfields: a list with the fields that hold numeric
2364
      values and thus should be right-aligned
2365
  @type unitfields: list
2366
  @param unitfields: a list with the fields that hold numeric
2367
      values that should be formatted with the units field
2368
  @type units: string or None
2369
  @param units: the units we should use for formatting, or None for
2370
      automatic choice (human-readable for non-separator usage, otherwise
2371
      megabytes); this is a one-letter string
2372

2373
  """
2374
  if units is None:
2375
    if separator:
2376
      units = "m"
2377
    else:
2378
      units = "h"
2379

    
2380
  if numfields is None:
2381
    numfields = []
2382
  if unitfields is None:
2383
    unitfields = []
2384

    
2385
  numfields = utils.FieldSet(*numfields)   # pylint: disable=W0142
2386
  unitfields = utils.FieldSet(*unitfields) # pylint: disable=W0142
2387

    
2388
  format_fields = []
2389
  for field in fields:
2390
    if headers and field not in headers:
2391
      # TODO: handle better unknown fields (either revert to old
2392
      # style of raising exception, or deal more intelligently with
2393
      # variable fields)
2394
      headers[field] = field
2395
    if separator is not None:
2396
      format_fields.append("%s")
2397
    elif numfields.Matches(field):
2398
      format_fields.append("%*s")
2399
    else:
2400
      format_fields.append("%-*s")
2401

    
2402
  if separator is None:
2403
    mlens = [0 for name in fields]
2404
    format_str = " ".join(format_fields)
2405
  else:
2406
    format_str = separator.replace("%", "%%").join(format_fields)
2407

    
2408
  for row in data:
2409
    if row is None:
2410
      continue
2411
    for idx, val in enumerate(row):
2412
      if unitfields.Matches(fields[idx]):
2413
        try:
2414
          val = int(val)
2415
        except (TypeError, ValueError):
2416
          pass
2417
        else:
2418
          val = row[idx] = utils.FormatUnit(val, units)
2419
      val = row[idx] = str(val)
2420
      if separator is None:
2421
        mlens[idx] = max(mlens[idx], len(val))
2422

    
2423
  result = []
2424
  if headers:
2425
    args = []
2426
    for idx, name in enumerate(fields):
2427
      hdr = headers[name]
2428
      if separator is None:
2429
        mlens[idx] = max(mlens[idx], len(hdr))
2430
        args.append(mlens[idx])
2431
      args.append(hdr)
2432
    result.append(format_str % tuple(args))
2433

    
2434
  if separator is None:
2435
    assert len(mlens) == len(fields)
2436

    
2437
    if fields and not numfields.Matches(fields[-1]):
2438
      mlens[-1] = 0
2439

    
2440
  for line in data:
2441
    args = []
2442
    if line is None:
2443
      line = ["-" for _ in fields]
2444
    for idx in range(len(fields)):
2445
      if separator is None:
2446
        args.append(mlens[idx])
2447
      args.append(line[idx])
2448
    result.append(format_str % tuple(args))
2449

    
2450
  return result
2451

    
2452

    
2453
def _FormatBool(value):
2454
  """Formats a boolean value as a string.
2455

2456
  """
2457
  if value:
2458
    return "Y"
2459
  return "N"
2460

    
2461

    
2462
#: Default formatting for query results; (callback, align right)
2463
_DEFAULT_FORMAT_QUERY = {
2464
  constants.QFT_TEXT: (str, False),
2465
  constants.QFT_BOOL: (_FormatBool, False),
2466
  constants.QFT_NUMBER: (str, True),
2467
  constants.QFT_TIMESTAMP: (utils.FormatTime, False),
2468
  constants.QFT_OTHER: (str, False),
2469
  constants.QFT_UNKNOWN: (str, False),
2470
  }
2471

    
2472

    
2473
def _GetColumnFormatter(fdef, override, unit):
2474
  """Returns formatting function for a field.
2475

2476
  @type fdef: L{objects.QueryFieldDefinition}
2477
  @type override: dict
2478
  @param override: Dictionary for overriding field formatting functions,
2479
    indexed by field name, contents like L{_DEFAULT_FORMAT_QUERY}
2480
  @type unit: string
2481
  @param unit: Unit used for formatting fields of type L{constants.QFT_UNIT}
2482
  @rtype: tuple; (callable, bool)
2483
  @return: Returns the function to format a value (takes one parameter) and a
2484
    boolean for aligning the value on the right-hand side
2485

2486
  """
2487
  fmt = override.get(fdef.name, None)
2488
  if fmt is not None:
2489
    return fmt
2490

    
2491
  assert constants.QFT_UNIT not in _DEFAULT_FORMAT_QUERY
2492

    
2493
  if fdef.kind == constants.QFT_UNIT:
2494
    # Can't keep this information in the static dictionary
2495
    return (lambda value: utils.FormatUnit(value, unit), True)
2496

    
2497
  fmt = _DEFAULT_FORMAT_QUERY.get(fdef.kind, None)
2498
  if fmt is not None:
2499
    return fmt
2500

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

    
2503

    
2504
class _QueryColumnFormatter:
2505
  """Callable class for formatting fields of a query.
2506

2507
  """
2508
  def __init__(self, fn, status_fn, verbose):
2509
    """Initializes this class.
2510

2511
    @type fn: callable
2512
    @param fn: Formatting function
2513
    @type status_fn: callable
2514
    @param status_fn: Function to report fields' status
2515
    @type verbose: boolean
2516
    @param verbose: whether to use verbose field descriptions or not
2517

2518
    """
2519
    self._fn = fn
2520
    self._status_fn = status_fn
2521
    self._verbose = verbose
2522

    
2523
  def __call__(self, data):
2524
    """Returns a field's string representation.
2525

2526
    """
2527
    (status, value) = data
2528

    
2529
    # Report status
2530
    self._status_fn(status)
2531

    
2532
    if status == constants.RS_NORMAL:
2533
      return self._fn(value)
2534

    
2535
    assert value is None, \
2536
           "Found value %r for abnormal status %s" % (value, status)
2537

    
2538
    return FormatResultError(status, self._verbose)
2539

    
2540

    
2541
def FormatResultError(status, verbose):
2542
  """Formats result status other than L{constants.RS_NORMAL}.
2543

2544
  @param status: The result status
2545
  @type verbose: boolean
2546
  @param verbose: Whether to return the verbose text
2547
  @return: Text of result status
2548

2549
  """
2550
  assert status != constants.RS_NORMAL, \
2551
         "FormatResultError called with status equal to constants.RS_NORMAL"
2552
  try:
2553
    (verbose_text, normal_text) = constants.RSS_DESCRIPTION[status]
2554
  except KeyError:
2555
    raise NotImplementedError("Unknown status %s" % status)
2556
  else:
2557
    if verbose:
2558
      return verbose_text
2559
    return normal_text
2560

    
2561

    
2562
def FormatQueryResult(result, unit=None, format_override=None, separator=None,
2563
                      header=False, verbose=False):
2564
  """Formats data in L{objects.QueryResponse}.
2565

2566
  @type result: L{objects.QueryResponse}
2567
  @param result: result of query operation
2568
  @type unit: string
2569
  @param unit: Unit used for formatting fields of type L{constants.QFT_UNIT},
2570
    see L{utils.text.FormatUnit}
2571
  @type format_override: dict
2572
  @param format_override: Dictionary for overriding field formatting functions,
2573
    indexed by field name, contents like L{_DEFAULT_FORMAT_QUERY}
2574
  @type separator: string or None
2575
  @param separator: String used to separate fields
2576
  @type header: bool
2577
  @param header: Whether to output header row
2578
  @type verbose: boolean
2579
  @param verbose: whether to use verbose field descriptions or not
2580

2581
  """
2582
  if unit is None:
2583
    if separator:
2584
      unit = "m"
2585
    else:
2586
      unit = "h"
2587

    
2588
  if format_override is None:
2589
    format_override = {}
2590

    
2591
  stats = dict.fromkeys(constants.RS_ALL, 0)
2592

    
2593
  def _RecordStatus(status):
2594
    if status in stats:
2595
      stats[status] += 1
2596

    
2597
  columns = []
2598
  for fdef in result.fields:
2599
    assert fdef.title and fdef.name
2600
    (fn, align_right) = _GetColumnFormatter(fdef, format_override, unit)
2601
    columns.append(TableColumn(fdef.title,
2602
                               _QueryColumnFormatter(fn, _RecordStatus,
2603
                                                     verbose),
2604
                               align_right))
2605

    
2606
  table = FormatTable(result.data, columns, header, separator)
2607

    
2608
  # Collect statistics
2609
  assert len(stats) == len(constants.RS_ALL)
2610
  assert compat.all(count >= 0 for count in stats.values())
2611

    
2612
  # Determine overall status. If there was no data, unknown fields must be
2613
  # detected via the field definitions.
2614
  if (stats[constants.RS_UNKNOWN] or
2615
      (not result.data and _GetUnknownFields(result.fields))):
2616
    status = QR_UNKNOWN
2617
  elif compat.any(count > 0 for key, count in stats.items()
2618
                  if key != constants.RS_NORMAL):
2619
    status = QR_INCOMPLETE
2620
  else:
2621
    status = QR_NORMAL
2622

    
2623
  return (status, table)
2624

    
2625

    
2626
def _GetUnknownFields(fdefs):
2627
  """Returns list of unknown fields included in C{fdefs}.
2628

2629
  @type fdefs: list of L{objects.QueryFieldDefinition}
2630

2631
  """
2632
  return [fdef for fdef in fdefs
2633
          if fdef.kind == constants.QFT_UNKNOWN]
2634

    
2635

    
2636
def _WarnUnknownFields(fdefs):
2637
  """Prints a warning to stderr if a query included unknown fields.
2638

2639
  @type fdefs: list of L{objects.QueryFieldDefinition}
2640

2641
  """
2642
  unknown = _GetUnknownFields(fdefs)
2643
  if unknown:
2644
    ToStderr("Warning: Queried for unknown fields %s",
2645
             utils.CommaJoin(fdef.name for fdef in unknown))
2646
    return True
2647

    
2648
  return False
2649

    
2650

    
2651
def GenericList(resource, fields, names, unit, separator, header, cl=None,
2652
                format_override=None, verbose=False, force_filter=False):
2653
  """Generic implementation for listing all items of a resource.
2654

2655
  @param resource: One of L{constants.QR_VIA_LUXI}
2656
  @type fields: list of strings
2657
  @param fields: List of fields to query for
2658
  @type names: list of strings
2659
  @param names: Names of items to query for
2660
  @type unit: string or None
2661
  @param unit: Unit used for formatting fields of type L{constants.QFT_UNIT} or
2662
    None for automatic choice (human-readable for non-separator usage,
2663
    otherwise megabytes); this is a one-letter string
2664
  @type separator: string or None
2665
  @param separator: String used to separate fields
2666
  @type header: bool
2667
  @param header: Whether to show header row
2668
  @type force_filter: bool
2669
  @param force_filter: Whether to always treat names as filter
2670
  @type format_override: dict
2671
  @param format_override: Dictionary for overriding field formatting functions,
2672
    indexed by field name, contents like L{_DEFAULT_FORMAT_QUERY}
2673
  @type verbose: boolean
2674
  @param verbose: whether to use verbose field descriptions or not
2675

2676
  """
2677
  if cl is None:
2678
    cl = GetClient()
2679

    
2680
  if not names:
2681
    names = None
2682

    
2683
  filter_ = qlang.MakeFilter(names, force_filter)
2684

    
2685
  response = cl.Query(resource, fields, filter_)
2686

    
2687
  found_unknown = _WarnUnknownFields(response.fields)
2688

    
2689
  (status, data) = FormatQueryResult(response, unit=unit, separator=separator,
2690
                                     header=header,
2691
                                     format_override=format_override,
2692
                                     verbose=verbose)
2693

    
2694
  for line in data:
2695
    ToStdout(line)
2696

    
2697
  assert ((found_unknown and status == QR_UNKNOWN) or
2698
          (not found_unknown and status != QR_UNKNOWN))
2699

    
2700
  if status == QR_UNKNOWN:
2701
    return constants.EXIT_UNKNOWN_FIELD
2702

    
2703
  # TODO: Should the list command fail if not all data could be collected?
2704
  return constants.EXIT_SUCCESS
2705

    
2706

    
2707
def GenericListFields(resource, fields, separator, header, cl=None):
2708
  """Generic implementation for listing fields for a resource.
2709

2710
  @param resource: One of L{constants.QR_VIA_LUXI}
2711
  @type fields: list of strings
2712
  @param fields: List of fields to query for
2713
  @type separator: string or None
2714
  @param separator: String used to separate fields
2715
  @type header: bool
2716
  @param header: Whether to show header row
2717

2718
  """
2719
  if cl is None:
2720
    cl = GetClient()
2721

    
2722
  if not fields:
2723
    fields = None
2724

    
2725
  response = cl.QueryFields(resource, fields)
2726

    
2727
  found_unknown = _WarnUnknownFields(response.fields)
2728

    
2729
  columns = [
2730
    TableColumn("Name", str, False),
2731
    TableColumn("Title", str, False),
2732
    TableColumn("Description", str, False),
2733
    ]
2734

    
2735
  rows = [[fdef.name, fdef.title, fdef.doc] for fdef in response.fields]
2736

    
2737
  for line in FormatTable(rows, columns, header, separator):
2738
    ToStdout(line)
2739

    
2740
  if found_unknown:
2741
    return constants.EXIT_UNKNOWN_FIELD
2742

    
2743
  return constants.EXIT_SUCCESS
2744

    
2745

    
2746
class TableColumn:
2747
  """Describes a column for L{FormatTable}.
2748

2749
  """
2750
  def __init__(self, title, fn, align_right):
2751
    """Initializes this class.
2752

2753
    @type title: string
2754
    @param title: Column title
2755
    @type fn: callable
2756
    @param fn: Formatting function
2757
    @type align_right: bool
2758
    @param align_right: Whether to align values on the right-hand side
2759

2760
    """
2761
    self.title = title
2762
    self.format = fn
2763
    self.align_right = align_right
2764

    
2765

    
2766
def _GetColFormatString(width, align_right):
2767
  """Returns the format string for a field.
2768

2769
  """
2770
  if align_right:
2771
    sign = ""
2772
  else:
2773
    sign = "-"
2774

    
2775
  return "%%%s%ss" % (sign, width)
2776

    
2777

    
2778
def FormatTable(rows, columns, header, separator):
2779
  """Formats data as a table.
2780

2781
  @type rows: list of lists
2782
  @param rows: Row data, one list per row
2783
  @type columns: list of L{TableColumn}
2784
  @param columns: Column descriptions
2785
  @type header: bool
2786
  @param header: Whether to show header row
2787
  @type separator: string or None
2788
  @param separator: String used to separate columns
2789

2790
  """
2791
  if header:
2792
    data = [[col.title for col in columns]]
2793
    colwidth = [len(col.title) for col in columns]
2794
  else:
2795
    data = []
2796
    colwidth = [0 for _ in columns]
2797

    
2798
  # Format row data
2799
  for row in rows:
2800
    assert len(row) == len(columns)
2801

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

    
2804
    if separator is None:
2805
      # Update column widths
2806
      for idx, (oldwidth, value) in enumerate(zip(colwidth, formatted)):
2807
        # Modifying a list's items while iterating is fine
2808
        colwidth[idx] = max(oldwidth, len(value))
2809

    
2810
    data.append(formatted)
2811

    
2812
  if separator is not None:
2813
    # Return early if a separator is used
2814
    return [separator.join(row) for row in data]
2815

    
2816
  if columns and not columns[-1].align_right:
2817
    # Avoid unnecessary spaces at end of line
2818
    colwidth[-1] = 0
2819

    
2820
  # Build format string
2821
  fmt = " ".join([_GetColFormatString(width, col.align_right)
2822
                  for col, width in zip(columns, colwidth)])
2823

    
2824
  return [fmt % tuple(row) for row in data]
2825

    
2826

    
2827
def FormatTimestamp(ts):
2828
  """Formats a given timestamp.
2829

2830
  @type ts: timestamp
2831
  @param ts: a timeval-type timestamp, a tuple of seconds and microseconds
2832

2833
  @rtype: string
2834
  @return: a string with the formatted timestamp
2835

2836
  """
2837
  if not isinstance(ts, (tuple, list)) or len(ts) != 2:
2838
    return "?"
2839
  sec, usec = ts
2840
  return time.strftime("%F %T", time.localtime(sec)) + ".%06d" % usec
2841

    
2842

    
2843
def ParseTimespec(value):
2844
  """Parse a time specification.
2845

2846
  The following suffixed will be recognized:
2847

2848
    - s: seconds
2849
    - m: minutes
2850
    - h: hours
2851
    - d: day
2852
    - w: weeks
2853

2854
  Without any suffix, the value will be taken to be in seconds.
2855

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

    
2884

    
2885
def GetOnlineNodes(nodes, cl=None, nowarn=False, secondary_ips=False,
2886
                   filter_master=False, nodegroup=None):
2887
  """Returns the names of online nodes.
2888

2889
  This function will also log a warning on stderr with the names of
2890
  the online nodes.
2891

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

2910
  """
2911
  if cl is None:
2912
    cl = GetClient()
2913

    
2914
  filter_ = []
2915

    
2916
  if nodes:
2917
    filter_.append(qlang.MakeSimpleFilter("name", nodes))
2918

    
2919
  if nodegroup is not None:
2920
    filter_.append([qlang.OP_OR, [qlang.OP_EQUAL, "group", nodegroup],
2921
                                 [qlang.OP_EQUAL, "group.uuid", nodegroup]])
2922

    
2923
  if filter_master:
2924
    filter_.append([qlang.OP_NOT, [qlang.OP_TRUE, "master"]])
2925

    
2926
  if filter_:
2927
    if len(filter_) > 1:
2928
      final_filter = [qlang.OP_AND] + filter_
2929
    else:
2930
      assert len(filter_) == 1
2931
      final_filter = filter_[0]
2932
  else:
2933
    final_filter = None
2934

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

    
2937
  def _IsOffline(row):
2938
    (_, (_, offline), _) = row
2939
    return offline
2940

    
2941
  def _GetName(row):
2942
    ((_, name), _, _) = row
2943
    return name
2944

    
2945
  def _GetSip(row):
2946
    (_, _, (_, sip)) = row
2947
    return sip
2948

    
2949
  (offline, online) = compat.partition(result.data, _IsOffline)
2950

    
2951
  if offline and not nowarn:
2952
    ToStderr("Note: skipping offline node(s): %s" %
2953
             utils.CommaJoin(map(_GetName, offline)))
2954

    
2955
  if secondary_ips:
2956
    fn = _GetSip
2957
  else:
2958
    fn = _GetName
2959

    
2960
  return map(fn, online)
2961

    
2962

    
2963
def _ToStream(stream, txt, *args):
2964
  """Write a message to a stream, bypassing the logging system
2965

2966
  @type stream: file object
2967
  @param stream: the file to which we should write
2968
  @type txt: str
2969
  @param txt: the message
2970

2971
  """
2972
  try:
2973
    if args:
2974
      args = tuple(args)
2975
      stream.write(txt % args)
2976
    else:
2977
      stream.write(txt)
2978
    stream.write("\n")
2979
    stream.flush()
2980
  except IOError, err:
2981
    if err.errno == errno.EPIPE:
2982
      # our terminal went away, we'll exit
2983
      sys.exit(constants.EXIT_FAILURE)
2984
    else:
2985
      raise
2986

    
2987

    
2988
def ToStdout(txt, *args):
2989
  """Write a message to stdout only, bypassing the logging system
2990

2991
  This is just a wrapper over _ToStream.
2992

2993
  @type txt: str
2994
  @param txt: the message
2995

2996
  """
2997
  _ToStream(sys.stdout, txt, *args)
2998

    
2999

    
3000
def ToStderr(txt, *args):
3001
  """Write a message to stderr only, bypassing the logging system
3002

3003
  This is just a wrapper over _ToStream.
3004

3005
  @type txt: str
3006
  @param txt: the message
3007

3008
  """
3009
  _ToStream(sys.stderr, txt, *args)
3010

    
3011

    
3012
class JobExecutor(object):
3013
  """Class which manages the submission and execution of multiple jobs.
3014

3015
  Note that instances of this class should not be reused between
3016
  GetResults() calls.
3017

3018
  """
3019
  def __init__(self, cl=None, verbose=True, opts=None, feedback_fn=None):
3020
    self.queue = []
3021
    if cl is None:
3022
      cl = GetClient()
3023
    self.cl = cl
3024
    self.verbose = verbose
3025
    self.jobs = []
3026
    self.opts = opts
3027
    self.feedback_fn = feedback_fn
3028
    self._counter = itertools.count()
3029

    
3030
  @staticmethod
3031
  def _IfName(name, fmt):
3032
    """Helper function for formatting name.
3033

3034
    """
3035
    if name:
3036
      return fmt % name
3037

    
3038
    return ""
3039

    
3040
  def QueueJob(self, name, *ops):
3041
    """Record a job for later submit.
3042

3043
    @type name: string
3044
    @param name: a description of the job, will be used in WaitJobSet
3045

3046
    """
3047
    SetGenericOpcodeOpts(ops, self.opts)
3048
    self.queue.append((self._counter.next(), name, ops))
3049

    
3050
  def AddJobId(self, name, status, job_id):
3051
    """Adds a job ID to the internal queue.
3052

3053
    """
3054
    self.jobs.append((self._counter.next(), status, job_id, name))
3055

    
3056
  def SubmitPending(self, each=False):
3057
    """Submit all pending jobs.
3058

3059
    """
3060
    if each:
3061
      results = []
3062
      for (_, _, ops) in self.queue:
3063
        # SubmitJob will remove the success status, but raise an exception if
3064
        # the submission fails, so we'll notice that anyway.
3065
        results.append([True, self.cl.SubmitJob(ops)])
3066
    else:
3067
      results = self.cl.SubmitManyJobs([ops for (_, _, ops) in self.queue])
3068
    for ((status, data), (idx, name, _)) in zip(results, self.queue):
3069
      self.jobs.append((idx, status, data, name))
3070

    
3071
  def _ChooseJob(self):
3072
    """Choose a non-waiting/queued job to poll next.
3073

3074
    """
3075
    assert self.jobs, "_ChooseJob called with empty job list"
3076

    
3077
    result = self.cl.QueryJobs([i[2] for i in self.jobs[:_CHOOSE_BATCH]],
3078
                               ["status"])
3079
    assert result
3080

    
3081
    for job_data, status in zip(self.jobs, result):
3082
      if (isinstance(status, list) and status and
3083
          status[0] in (constants.JOB_STATUS_QUEUED,
3084
                        constants.JOB_STATUS_WAITING,
3085
                        constants.JOB_STATUS_CANCELING)):
3086
        # job is still present and waiting
3087
        continue
3088
      # good candidate found (either running job or lost job)
3089
      self.jobs.remove(job_data)
3090
      return job_data
3091

    
3092
    # no job found
3093
    return self.jobs.pop(0)
3094

    
3095
  def GetResults(self):
3096
    """Wait for and return the results of all jobs.
3097

3098
    @rtype: list
3099
    @return: list of tuples (success, job results), in the same order
3100
        as the submitted jobs; if a job has failed, instead of the result
3101
        there will be the error message
3102

3103
    """
3104
    if not self.jobs:
3105
      self.SubmitPending()
3106
    results = []
3107
    if self.verbose:
3108
      ok_jobs = [row[2] for row in self.jobs if row[1]]
3109
      if ok_jobs:
3110
        ToStdout("Submitted jobs %s", utils.CommaJoin(ok_jobs))
3111

    
3112
    # first, remove any non-submitted jobs
3113
    self.jobs, failures = compat.partition(self.jobs, lambda x: x[1])
3114
    for idx, _, jid, name in failures:
3115
      ToStderr("Failed to submit job%s: %s", self._IfName(name, " for %s"), jid)
3116
      results.append((idx, False, jid))
3117

    
3118
    while self.jobs:
3119
      (idx, _, jid, name) = self._ChooseJob()
3120
      ToStdout("Waiting for job %s%s ...", jid, self._IfName(name, " for %s"))
3121
      try:
3122
        job_result = PollJob(jid, cl=self.cl, feedback_fn=self.feedback_fn)
3123
        success = True
3124
      except errors.JobLost, err:
3125
        _, job_result = FormatError(err)
3126
        ToStderr("Job %s%s has been archived, cannot check its result",
3127
                 jid, self._IfName(name, " for %s"))
3128
        success = False
3129
      except (errors.GenericError, luxi.ProtocolError), err:
3130
        _, job_result = FormatError(err)
3131
        success = False
3132
        # the error message will always be shown, verbose or not
3133
        ToStderr("Job %s%s has failed: %s",
3134
                 jid, self._IfName(name, " for %s"), job_result)
3135

    
3136
      results.append((idx, success, job_result))
3137

    
3138
    # sort based on the index, then drop it
3139
    results.sort()
3140
    results = [i[1:] for i in results]
3141

    
3142
    return results
3143

    
3144
  def WaitOrShow(self, wait):
3145
    """Wait for job results or only print the job IDs.
3146

3147
    @type wait: boolean
3148
    @param wait: whether to wait or not
3149

3150
    """
3151
    if wait:
3152
      return self.GetResults()
3153
    else:
3154
      if not self.jobs:
3155
        self.SubmitPending()
3156
      for _, status, result, name in self.jobs:
3157
        if status:
3158
          ToStdout("%s: %s", result, name)
3159
        else:
3160
          ToStderr("Failure for %s: %s", name, result)
3161
      return [row[1:3] for row in self.jobs]
3162

    
3163

    
3164
def FormatParameterDict(buf, param_dict, actual, level=1):
3165
  """Formats a parameter dictionary.
3166

3167
  @type buf: L{StringIO}
3168
  @param buf: the buffer into which to write
3169
  @type param_dict: dict
3170
  @param param_dict: the own parameters
3171
  @type actual: dict
3172
  @param actual: the current parameter set (including defaults)
3173
  @param level: Level of indent
3174

3175
  """
3176
  indent = "  " * level
3177
  for key in sorted(actual):
3178
    val = param_dict.get(key, "default (%s)" % actual[key])
3179
    buf.write("%s- %s: %s\n" % (indent, key, val))
3180

    
3181

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

3185
  This function is used to request confirmation for doing an operation
3186
  on a given list of list_type.
3187

3188
  @type names: list
3189
  @param names: the list of names that we display when
3190
      we ask for confirmation
3191
  @type list_type: str
3192
  @param list_type: Human readable name for elements in the list (e.g. nodes)
3193
  @type text: str
3194
  @param text: the operation that the user should confirm
3195
  @rtype: boolean
3196
  @return: True or False depending on user's confirmation.
3197

3198
  """
3199
  count = len(names)
3200
  msg = ("The %s will operate on %d %s.\n%s"
3201
         "Do you want to continue?" % (text, count, list_type, extra))
3202
  affected = (("\nAffected %s:\n" % list_type) +
3203
              "\n".join(["  %s" % name for name in names]))
3204

    
3205
  choices = [("y", True, "Yes, execute the %s" % text),
3206
             ("n", False, "No, abort the %s" % text)]
3207

    
3208
  if count > 20:
3209
    choices.insert(1, ("v", "v", "View the list of affected %s" % list_type))
3210
    question = msg
3211
  else:
3212
    question = msg + affected
3213

    
3214
  choice = AskUser(question, choices)
3215
  if choice == "v":
3216
    choices.pop(1)
3217
    choice = AskUser(msg + affected, choices)
3218
  return choice