Statistics
| Branch: | Tag: | Revision:

root / lib / cli.py @ 499eb088

History | View | Annotate | Download (108.4 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 shlex
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 (OptionParser, 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_PARAMS_OPT",
73
  "DISK_TEMPLATE_OPT",
74
  "DRAINED_OPT",
75
  "DRY_RUN_OPT",
76
  "DRBD_HELPER_OPT",
77
  "DST_NODE_OPT",
78
  "EARLY_RELEASE_OPT",
79
  "ENABLED_HV_OPT",
80
  "ERROR_CODES_OPT",
81
  "FIELDS_OPT",
82
  "FILESTORE_DIR_OPT",
83
  "FILESTORE_DRIVER_OPT",
84
  "FORCE_FILTER_OPT",
85
  "FORCE_OPT",
86
  "FORCE_VARIANT_OPT",
87
  "GLOBAL_FILEDIR_OPT",
88
  "HID_OS_OPT",
89
  "GLOBAL_SHARED_FILEDIR_OPT",
90
  "HVLIST_OPT",
91
  "HVOPTS_OPT",
92
  "HYPERVISOR_OPT",
93
  "IALLOCATOR_OPT",
94
  "DEFAULT_IALLOCATOR_OPT",
95
  "IDENTIFY_DEFAULTS_OPT",
96
  "IGNORE_CONSIST_OPT",
97
  "IGNORE_ERRORS_OPT",
98
  "IGNORE_FAILURES_OPT",
99
  "IGNORE_OFFLINE_OPT",
100
  "IGNORE_REMOVE_FAILURES_OPT",
101
  "IGNORE_SECONDARIES_OPT",
102
  "IGNORE_SIZE_OPT",
103
  "INTERVAL_OPT",
104
  "MAC_PREFIX_OPT",
105
  "MAINTAIN_NODE_HEALTH_OPT",
106
  "MASTER_NETDEV_OPT",
107
  "MASTER_NETMASK_OPT",
108
  "MC_OPT",
109
  "MIGRATION_MODE_OPT",
110
  "NET_OPT",
111
  "NEW_CLUSTER_CERT_OPT",
112
  "NEW_CLUSTER_DOMAIN_SECRET_OPT",
113
  "NEW_CONFD_HMAC_KEY_OPT",
114
  "NEW_RAPI_CERT_OPT",
115
  "NEW_SECONDARY_OPT",
116
  "NEW_SPICE_CERT_OPT",
117
  "NIC_PARAMS_OPT",
118
  "NODE_FORCE_JOIN_OPT",
119
  "NODE_LIST_OPT",
120
  "NODE_PLACEMENT_OPT",
121
  "NODEGROUP_OPT",
122
  "NODE_PARAMS_OPT",
123
  "NODE_POWERED_OPT",
124
  "NODRBD_STORAGE_OPT",
125
  "NOHDR_OPT",
126
  "NOIPCHECK_OPT",
127
  "NO_INSTALL_OPT",
128
  "NONAMECHECK_OPT",
129
  "NOLVM_STORAGE_OPT",
130
  "NOMODIFY_ETCHOSTS_OPT",
131
  "NOMODIFY_SSH_SETUP_OPT",
132
  "NONICS_OPT",
133
  "NONLIVE_OPT",
134
  "NONPLUS1_OPT",
135
  "NOSHUTDOWN_OPT",
136
  "NOSTART_OPT",
137
  "NOSSH_KEYCHECK_OPT",
138
  "NOVOTING_OPT",
139
  "NO_REMEMBER_OPT",
140
  "NWSYNC_OPT",
141
  "OFFLINE_INST_OPT",
142
  "ONLINE_INST_OPT",
143
  "ON_PRIMARY_OPT",
144
  "ON_SECONDARY_OPT",
145
  "OFFLINE_OPT",
146
  "OSPARAMS_OPT",
147
  "OS_OPT",
148
  "OS_SIZE_OPT",
149
  "OOB_TIMEOUT_OPT",
150
  "POWER_DELAY_OPT",
151
  "PREALLOC_WIPE_DISKS_OPT",
152
  "PRIMARY_IP_VERSION_OPT",
153
  "PRIMARY_ONLY_OPT",
154
  "PRIORITY_OPT",
155
  "RAPI_CERT_OPT",
156
  "READD_OPT",
157
  "REBOOT_TYPE_OPT",
158
  "REMOVE_INSTANCE_OPT",
159
  "REMOVE_UIDS_OPT",
160
  "RESERVED_LVS_OPT",
161
  "ROMAN_OPT",
162
  "SECONDARY_IP_OPT",
163
  "SECONDARY_ONLY_OPT",
164
  "SELECT_OS_OPT",
165
  "SEP_OPT",
166
  "SHOWCMD_OPT",
167
  "SHUTDOWN_TIMEOUT_OPT",
168
  "SINGLE_NODE_OPT",
169
  "SPECS_CPU_COUNT_OPT",
170
  "SPECS_DISK_COUNT_OPT",
171
  "SPECS_DISK_SIZE_OPT",
172
  "SPECS_MEM_SIZE_OPT",
173
  "SPECS_NIC_COUNT_OPT",
174
  "SPICE_CACERT_OPT",
175
  "SPICE_CERT_OPT",
176
  "SRC_DIR_OPT",
177
  "SRC_NODE_OPT",
178
  "SUBMIT_OPT",
179
  "STARTUP_PAUSED_OPT",
180
  "STATIC_OPT",
181
  "SYNC_OPT",
182
  "TAG_ADD_OPT",
183
  "TAG_SRC_OPT",
184
  "TIMEOUT_OPT",
185
  "TO_GROUP_OPT",
186
  "UIDPOOL_OPT",
187
  "USEUNITS_OPT",
188
  "USE_EXTERNAL_MIP_SCRIPT",
189
  "USE_REPL_NET_OPT",
190
  "VERBOSE_OPT",
191
  "VG_NAME_OPT",
192
  "YES_DOIT_OPT",
193
  "DISK_STATE_OPT",
194
  "HV_STATE_OPT",
195
  "IGNORE_IPOLICY_OPT",
196
  # Generic functions for CLI programs
197
  "ConfirmOperation",
198
  "GenericMain",
199
  "GenericInstanceCreate",
200
  "GenericList",
201
  "GenericListFields",
202
  "GetClient",
203
  "GetOnlineNodes",
204
  "JobExecutor",
205
  "JobSubmittedException",
206
  "ParseTimespec",
207
  "RunWhileClusterStopped",
208
  "SubmitOpCode",
209
  "SubmitOrSend",
210
  "UsesRPC",
211
  # Formatting functions
212
  "ToStderr", "ToStdout",
213
  "FormatError",
214
  "FormatQueryResult",
215
  "FormatParameterDict",
216
  "GenerateTable",
217
  "AskUser",
218
  "FormatTimestamp",
219
  "FormatLogMessage",
220
  # Tags functions
221
  "ListTags",
222
  "AddTags",
223
  "RemoveTags",
224
  # command line options support infrastructure
225
  "ARGS_MANY_INSTANCES",
226
  "ARGS_MANY_NODES",
227
  "ARGS_MANY_GROUPS",
228
  "ARGS_NONE",
229
  "ARGS_ONE_INSTANCE",
230
  "ARGS_ONE_NODE",
231
  "ARGS_ONE_GROUP",
232
  "ARGS_ONE_OS",
233
  "ArgChoice",
234
  "ArgCommand",
235
  "ArgFile",
236
  "ArgGroup",
237
  "ArgHost",
238
  "ArgInstance",
239
  "ArgJobId",
240
  "ArgNode",
241
  "ArgOs",
242
  "ArgSuggest",
243
  "ArgUnknown",
244
  "OPT_COMPL_INST_ADD_NODES",
245
  "OPT_COMPL_MANY_NODES",
246
  "OPT_COMPL_ONE_IALLOCATOR",
247
  "OPT_COMPL_ONE_INSTANCE",
248
  "OPT_COMPL_ONE_NODE",
249
  "OPT_COMPL_ONE_NODEGROUP",
250
  "OPT_COMPL_ONE_OS",
251
  "cli_option",
252
  "SplitNodeOption",
253
  "CalculateOSNames",
254
  "ParseFields",
255
  "COMMON_CREATE_OPTS",
256
  ]
257

    
258
NO_PREFIX = "no_"
259
UN_PREFIX = "-"
260

    
261
#: Priorities (sorted)
262
_PRIORITY_NAMES = [
263
  ("low", constants.OP_PRIO_LOW),
264
  ("normal", constants.OP_PRIO_NORMAL),
265
  ("high", constants.OP_PRIO_HIGH),
266
  ]
267

    
268
#: Priority dictionary for easier lookup
269
# TODO: Replace this and _PRIORITY_NAMES with a single sorted dictionary once
270
# we migrate to Python 2.6
271
_PRIONAME_TO_VALUE = dict(_PRIORITY_NAMES)
272

    
273
# Query result status for clients
274
(QR_NORMAL,
275
 QR_UNKNOWN,
276
 QR_INCOMPLETE) = range(3)
277

    
278
#: Maximum batch size for ChooseJob
279
_CHOOSE_BATCH = 25
280

    
281

    
282
class _Argument:
283
  def __init__(self, min=0, max=None): # pylint: disable=W0622
284
    self.min = min
285
    self.max = max
286

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

    
291

    
292
class ArgSuggest(_Argument):
293
  """Suggesting argument.
294

295
  Value can be any of the ones passed to the constructor.
296

297
  """
298
  # pylint: disable=W0622
299
  def __init__(self, min=0, max=None, choices=None):
300
    _Argument.__init__(self, min=min, max=max)
301
    self.choices = choices
302

    
303
  def __repr__(self):
304
    return ("<%s min=%s max=%s choices=%r>" %
305
            (self.__class__.__name__, self.min, self.max, self.choices))
306

    
307

    
308
class ArgChoice(ArgSuggest):
309
  """Choice argument.
310

311
  Value can be any of the ones passed to the constructor. Like L{ArgSuggest},
312
  but value must be one of the choices.
313

314
  """
315

    
316

    
317
class ArgUnknown(_Argument):
318
  """Unknown argument to program (e.g. determined at runtime).
319

320
  """
321

    
322

    
323
class ArgInstance(_Argument):
324
  """Instances argument.
325

326
  """
327

    
328

    
329
class ArgNode(_Argument):
330
  """Node argument.
331

332
  """
333

    
334

    
335
class ArgGroup(_Argument):
336
  """Node group argument.
337

338
  """
339

    
340

    
341
class ArgJobId(_Argument):
342
  """Job ID argument.
343

344
  """
345

    
346

    
347
class ArgFile(_Argument):
348
  """File path argument.
349

350
  """
351

    
352

    
353
class ArgCommand(_Argument):
354
  """Command argument.
355

356
  """
357

    
358

    
359
class ArgHost(_Argument):
360
  """Host argument.
361

362
  """
363

    
364

    
365
class ArgOs(_Argument):
366
  """OS argument.
367

368
  """
369

    
370

    
371
ARGS_NONE = []
372
ARGS_MANY_INSTANCES = [ArgInstance()]
373
ARGS_MANY_NODES = [ArgNode()]
374
ARGS_MANY_GROUPS = [ArgGroup()]
375
ARGS_ONE_INSTANCE = [ArgInstance(min=1, max=1)]
376
ARGS_ONE_NODE = [ArgNode(min=1, max=1)]
377
# TODO
378
ARGS_ONE_GROUP = [ArgGroup(min=1, max=1)]
379
ARGS_ONE_OS = [ArgOs(min=1, max=1)]
380

    
381

    
382
def _ExtractTagsObject(opts, args):
383
  """Extract the tag type object.
384

385
  Note that this function will modify its args parameter.
386

387
  """
388
  if not hasattr(opts, "tag_type"):
389
    raise errors.ProgrammerError("tag_type not passed to _ExtractTagsObject")
390
  kind = opts.tag_type
391
  if kind == constants.TAG_CLUSTER:
392
    retval = kind, kind
393
  elif kind in (constants.TAG_NODEGROUP,
394
                constants.TAG_NODE,
395
                constants.TAG_INSTANCE):
396
    if not args:
397
      raise errors.OpPrereqError("no arguments passed to the command")
398
    name = args.pop(0)
399
    retval = kind, name
400
  else:
401
    raise errors.ProgrammerError("Unhandled tag type '%s'" % kind)
402
  return retval
403

    
404

    
405
def _ExtendTags(opts, args):
406
  """Extend the args if a source file has been given.
407

408
  This function will extend the tags with the contents of the file
409
  passed in the 'tags_source' attribute of the opts parameter. A file
410
  named '-' will be replaced by stdin.
411

412
  """
413
  fname = opts.tags_source
414
  if fname is None:
415
    return
416
  if fname == "-":
417
    new_fh = sys.stdin
418
  else:
419
    new_fh = open(fname, "r")
420
  new_data = []
421
  try:
422
    # we don't use the nice 'new_data = [line.strip() for line in fh]'
423
    # because of python bug 1633941
424
    while True:
425
      line = new_fh.readline()
426
      if not line:
427
        break
428
      new_data.append(line.strip())
429
  finally:
430
    new_fh.close()
431
  args.extend(new_data)
432

    
433

    
434
def ListTags(opts, args):
435
  """List the tags on a given object.
436

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

442
  """
443
  kind, name = _ExtractTagsObject(opts, args)
444
  cl = GetClient()
445
  result = cl.QueryTags(kind, name)
446
  result = list(result)
447
  result.sort()
448
  for tag in result:
449
    ToStdout(tag)
450

    
451

    
452
def AddTags(opts, args):
453
  """Add tags on 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 added")
465
  op = opcodes.OpTagsSet(kind=kind, name=name, tags=args)
466
  SubmitOpCode(op, opts=opts)
467

    
468

    
469
def RemoveTags(opts, args):
470
  """Remove tags from a given object.
471

472
  This is a generic implementation that knows how to deal with all
473
  three cases of tag objects (cluster, node, instance). The opts
474
  argument is expected to contain a tag_type field denoting what
475
  object type we work on.
476

477
  """
478
  kind, name = _ExtractTagsObject(opts, args)
479
  _ExtendTags(opts, args)
480
  if not args:
481
    raise errors.OpPrereqError("No tags to be removed")
482
  op = opcodes.OpTagsDel(kind=kind, name=name, tags=args)
483
  SubmitOpCode(op, opts=opts)
484

    
485

    
486
def check_unit(option, opt, value): # pylint: disable=W0613
487
  """OptParsers custom converter for units.
488

489
  """
490
  try:
491
    return utils.ParseUnit(value)
492
  except errors.UnitParseError, err:
493
    raise OptionValueError("option %s: %s" % (opt, err))
494

    
495

    
496
def _SplitKeyVal(opt, data):
497
  """Convert a KeyVal string into a dict.
498

499
  This function will convert a key=val[,...] string into a dict. Empty
500
  values will be converted specially: keys which have the prefix 'no_'
501
  will have the value=False and the prefix stripped, the others will
502
  have value=True.
503

504
  @type opt: string
505
  @param opt: a string holding the option name for which we process the
506
      data, used in building error messages
507
  @type data: string
508
  @param data: a string of the format key=val,key=val,...
509
  @rtype: dict
510
  @return: {key=val, key=val}
511
  @raises errors.ParameterError: if there are duplicate keys
512

513
  """
514
  kv_dict = {}
515
  if data:
516
    for elem in utils.UnescapeAndSplit(data, sep=","):
517
      if "=" in elem:
518
        key, val = elem.split("=", 1)
519
      else:
520
        if elem.startswith(NO_PREFIX):
521
          key, val = elem[len(NO_PREFIX):], False
522
        elif elem.startswith(UN_PREFIX):
523
          key, val = elem[len(UN_PREFIX):], None
524
        else:
525
          key, val = elem, True
526
      if key in kv_dict:
527
        raise errors.ParameterError("Duplicate key '%s' in option %s" %
528
                                    (key, opt))
529
      kv_dict[key] = val
530
  return kv_dict
531

    
532

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

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

539
  """
540
  if ":" not in value:
541
    ident, rest = value, ""
542
  else:
543
    ident, rest = value.split(":", 1)
544

    
545
  if ident.startswith(NO_PREFIX):
546
    if rest:
547
      msg = "Cannot pass options when removing parameter groups: %s" % value
548
      raise errors.ParameterError(msg)
549
    retval = (ident[len(NO_PREFIX):], False)
550
  elif ident.startswith(UN_PREFIX):
551
    if rest:
552
      msg = "Cannot pass options when removing parameter groups: %s" % value
553
      raise errors.ParameterError(msg)
554
    retval = (ident[len(UN_PREFIX):], None)
555
  else:
556
    kv_dict = _SplitKeyVal(opt, rest)
557
    retval = (ident, kv_dict)
558
  return retval
559

    
560

    
561
def check_key_val(option, opt, value):  # pylint: disable=W0613
562
  """Custom parser class for key=val,key=val options.
563

564
  This will store the parsed values as a dict {key: val}.
565

566
  """
567
  return _SplitKeyVal(opt, value)
568

    
569

    
570
def check_bool(option, opt, value): # pylint: disable=W0613
571
  """Custom parser for yes/no options.
572

573
  This will store the parsed value as either True or False.
574

575
  """
576
  value = value.lower()
577
  if value == constants.VALUE_FALSE or value == "no":
578
    return False
579
  elif value == constants.VALUE_TRUE or value == "yes":
580
    return True
581
  else:
582
    raise errors.ParameterError("Invalid boolean value '%s'" % value)
583

    
584

    
585
def check_list(option, opt, value): # pylint: disable=W0613
586
  """Custom parser for comma-separated lists.
587

588
  """
589
  # we have to make this explicit check since "".split(",") is [""],
590
  # not an empty list :(
591
  if not value:
592
    return []
593
  else:
594
    return utils.UnescapeAndSplit(value)
595

    
596

    
597
# completion_suggestion is normally a list. Using numeric values not evaluating
598
# to False for dynamic completion.
599
(OPT_COMPL_MANY_NODES,
600
 OPT_COMPL_ONE_NODE,
601
 OPT_COMPL_ONE_INSTANCE,
602
 OPT_COMPL_ONE_OS,
603
 OPT_COMPL_ONE_IALLOCATOR,
604
 OPT_COMPL_INST_ADD_NODES,
605
 OPT_COMPL_ONE_NODEGROUP) = range(100, 107)
606

    
607
OPT_COMPL_ALL = frozenset([
608
  OPT_COMPL_MANY_NODES,
609
  OPT_COMPL_ONE_NODE,
610
  OPT_COMPL_ONE_INSTANCE,
611
  OPT_COMPL_ONE_OS,
612
  OPT_COMPL_ONE_IALLOCATOR,
613
  OPT_COMPL_INST_ADD_NODES,
614
  OPT_COMPL_ONE_NODEGROUP,
615
  ])
616

    
617

    
618
class CliOption(Option):
619
  """Custom option class for optparse.
620

621
  """
622
  ATTRS = Option.ATTRS + [
623
    "completion_suggest",
624
    ]
625
  TYPES = Option.TYPES + (
626
    "identkeyval",
627
    "keyval",
628
    "unit",
629
    "bool",
630
    "list",
631
    )
632
  TYPE_CHECKER = Option.TYPE_CHECKER.copy()
633
  TYPE_CHECKER["identkeyval"] = check_ident_key_val
634
  TYPE_CHECKER["keyval"] = check_key_val
635
  TYPE_CHECKER["unit"] = check_unit
636
  TYPE_CHECKER["bool"] = check_bool
637
  TYPE_CHECKER["list"] = check_list
638

    
639

    
640
# optparse.py sets make_option, so we do it for our own option class, too
641
cli_option = CliOption
642

    
643

    
644
_YORNO = "yes|no"
645

    
646
DEBUG_OPT = cli_option("-d", "--debug", default=0, action="count",
647
                       help="Increase debugging level")
648

    
649
NOHDR_OPT = cli_option("--no-headers", default=False,
650
                       action="store_true", dest="no_headers",
651
                       help="Don't display column headers")
652

    
653
SEP_OPT = cli_option("--separator", default=None,
654
                     action="store", dest="separator",
655
                     help=("Separator between output fields"
656
                           " (defaults to one space)"))
657

    
658
USEUNITS_OPT = cli_option("--units", default=None,
659
                          dest="units", choices=("h", "m", "g", "t"),
660
                          help="Specify units for output (one of h/m/g/t)")
661

    
662
FIELDS_OPT = cli_option("-o", "--output", dest="output", action="store",
663
                        type="string", metavar="FIELDS",
664
                        help="Comma separated list of output fields")
665

    
666
FORCE_OPT = cli_option("-f", "--force", dest="force", action="store_true",
667
                       default=False, help="Force the operation")
668

    
669
CONFIRM_OPT = cli_option("--yes", dest="confirm", action="store_true",
670
                         default=False, help="Do not require confirmation")
671

    
672
IGNORE_OFFLINE_OPT = cli_option("--ignore-offline", dest="ignore_offline",
673
                                  action="store_true", default=False,
674
                                  help=("Ignore offline nodes and do as much"
675
                                        " as possible"))
676

    
677
TAG_ADD_OPT = cli_option("--tags", dest="tags",
678
                         default=None, help="Comma-separated list of instance"
679
                                            " tags")
680

    
681
TAG_SRC_OPT = cli_option("--from", dest="tags_source",
682
                         default=None, help="File with tag names")
683

    
684
SUBMIT_OPT = cli_option("--submit", dest="submit_only",
685
                        default=False, action="store_true",
686
                        help=("Submit the job and return the job ID, but"
687
                              " don't wait for the job to finish"))
688

    
689
SYNC_OPT = cli_option("--sync", dest="do_locking",
690
                      default=False, action="store_true",
691
                      help=("Grab locks while doing the queries"
692
                            " in order to ensure more consistent results"))
693

    
694
DRY_RUN_OPT = cli_option("--dry-run", default=False,
695
                         action="store_true",
696
                         help=("Do not execute the operation, just run the"
697
                               " check steps and verify it it could be"
698
                               " executed"))
699

    
700
VERBOSE_OPT = cli_option("-v", "--verbose", default=False,
701
                         action="store_true",
702
                         help="Increase the verbosity of the operation")
703

    
704
DEBUG_SIMERR_OPT = cli_option("--debug-simulate-errors", default=False,
705
                              action="store_true", dest="simulate_errors",
706
                              help="Debugging option that makes the operation"
707
                              " treat most runtime checks as failed")
708

    
709
NWSYNC_OPT = cli_option("--no-wait-for-sync", dest="wait_for_sync",
710
                        default=True, action="store_false",
711
                        help="Don't wait for sync (DANGEROUS!)")
712

    
713
ONLINE_INST_OPT = cli_option("--online", dest="online_inst",
714
                             action="store_true", default=False,
715
                             help="Enable offline instance")
716

    
717
OFFLINE_INST_OPT = cli_option("--offline", dest="offline_inst",
718
                              action="store_true", default=False,
719
                              help="Disable down instance")
720

    
721
DISK_TEMPLATE_OPT = cli_option("-t", "--disk-template", dest="disk_template",
722
                               help=("Custom disk setup (%s)" %
723
                                     utils.CommaJoin(constants.DISK_TEMPLATES)),
724
                               default=None, metavar="TEMPL",
725
                               choices=list(constants.DISK_TEMPLATES))
726

    
727
NONICS_OPT = cli_option("--no-nics", default=False, action="store_true",
728
                        help="Do not create any network cards for"
729
                        " the instance")
730

    
731
FILESTORE_DIR_OPT = cli_option("--file-storage-dir", dest="file_storage_dir",
732
                               help="Relative path under default cluster-wide"
733
                               " file storage dir to store file-based disks",
734
                               default=None, metavar="<DIR>")
735

    
736
FILESTORE_DRIVER_OPT = cli_option("--file-driver", dest="file_driver",
737
                                  help="Driver to use for image files",
738
                                  default="loop", metavar="<DRIVER>",
739
                                  choices=list(constants.FILE_DRIVER))
740

    
741
IALLOCATOR_OPT = cli_option("-I", "--iallocator", metavar="<NAME>",
742
                            help="Select nodes for the instance automatically"
743
                            " using the <NAME> iallocator plugin",
744
                            default=None, type="string",
745
                            completion_suggest=OPT_COMPL_ONE_IALLOCATOR)
746

    
747
DEFAULT_IALLOCATOR_OPT = cli_option("-I", "--default-iallocator",
748
                            metavar="<NAME>",
749
                            help="Set the default instance allocator plugin",
750
                            default=None, type="string",
751
                            completion_suggest=OPT_COMPL_ONE_IALLOCATOR)
752

    
753
OS_OPT = cli_option("-o", "--os-type", dest="os", help="What OS to run",
754
                    metavar="<os>",
755
                    completion_suggest=OPT_COMPL_ONE_OS)
756

    
757
OSPARAMS_OPT = cli_option("-O", "--os-parameters", dest="osparams",
758
                         type="keyval", default={},
759
                         help="OS parameters")
760

    
761
FORCE_VARIANT_OPT = cli_option("--force-variant", dest="force_variant",
762
                               action="store_true", default=False,
763
                               help="Force an unknown variant")
764

    
765
NO_INSTALL_OPT = cli_option("--no-install", dest="no_install",
766
                            action="store_true", default=False,
767
                            help="Do not install the OS (will"
768
                            " enable no-start)")
769

    
770
BACKEND_OPT = cli_option("-B", "--backend-parameters", dest="beparams",
771
                         type="keyval", default={},
772
                         help="Backend parameters")
773

    
774
HVOPTS_OPT = cli_option("-H", "--hypervisor-parameters", type="keyval",
775
                        default={}, dest="hvparams",
776
                        help="Hypervisor parameters")
777

    
778
DISK_PARAMS_OPT = cli_option("-D", "--disk-parameters", dest="diskparams",
779
                             help="Disk template parameters, in the format"
780
                             " template:option=value,option=value,...",
781
                             type="identkeyval", action="append", default=[])
782

    
783
SPECS_MEM_SIZE_OPT = cli_option("--specs-mem-size", dest="ispecs_mem_size",
784
                                 type="keyval", default={},
785
                                 help="Memory count specs: min, max, std"
786
                                 " (in MB)")
787

    
788
SPECS_CPU_COUNT_OPT = cli_option("--specs-cpu-count", dest="ispecs_cpu_count",
789
                                 type="keyval", default={},
790
                                 help="CPU count specs: min, max, std")
791

    
792
SPECS_DISK_COUNT_OPT = cli_option("--specs-disk-count",
793
                                  dest="ispecs_disk_count",
794
                                  type="keyval", default={},
795
                                  help="Disk count specs: min, max, std")
796

    
797
SPECS_DISK_SIZE_OPT = cli_option("--specs-disk-size", dest="ispecs_disk_size",
798
                                 type="keyval", default={},
799
                                 help="Disk size specs: min, max, std (in MB)")
800

    
801
SPECS_NIC_COUNT_OPT = cli_option("--specs-nic-count", dest="ispecs_nic_count",
802
                                 type="keyval", default={},
803
                                 help="NIC count specs: min, max, std")
804

    
805
HYPERVISOR_OPT = cli_option("-H", "--hypervisor-parameters", dest="hypervisor",
806
                            help="Hypervisor and hypervisor options, in the"
807
                            " format hypervisor:option=value,option=value,...",
808
                            default=None, type="identkeyval")
809

    
810
HVLIST_OPT = cli_option("-H", "--hypervisor-parameters", dest="hvparams",
811
                        help="Hypervisor and hypervisor options, in the"
812
                        " format hypervisor:option=value,option=value,...",
813
                        default=[], action="append", type="identkeyval")
814

    
815
NOIPCHECK_OPT = cli_option("--no-ip-check", dest="ip_check", default=True,
816
                           action="store_false",
817
                           help="Don't check that the instance's IP"
818
                           " is alive")
819

    
820
NONAMECHECK_OPT = cli_option("--no-name-check", dest="name_check",
821
                             default=True, action="store_false",
822
                             help="Don't check that the instance's name"
823
                             " is resolvable")
824

    
825
NET_OPT = cli_option("--net",
826
                     help="NIC parameters", default=[],
827
                     dest="nics", action="append", type="identkeyval")
828

    
829
DISK_OPT = cli_option("--disk", help="Disk parameters", default=[],
830
                      dest="disks", action="append", type="identkeyval")
831

    
832
DISKIDX_OPT = cli_option("--disks", dest="disks", default=None,
833
                         help="Comma-separated list of disks"
834
                         " indices to act on (e.g. 0,2) (optional,"
835
                         " defaults to all disks)")
836

    
837
OS_SIZE_OPT = cli_option("-s", "--os-size", dest="sd_size",
838
                         help="Enforces a single-disk configuration using the"
839
                         " given disk size, in MiB unless a suffix is used",
840
                         default=None, type="unit", metavar="<size>")
841

    
842
IGNORE_CONSIST_OPT = cli_option("--ignore-consistency",
843
                                dest="ignore_consistency",
844
                                action="store_true", default=False,
845
                                help="Ignore the consistency of the disks on"
846
                                " the secondary")
847

    
848
ALLOW_FAILOVER_OPT = cli_option("--allow-failover",
849
                                dest="allow_failover",
850
                                action="store_true", default=False,
851
                                help="If migration is not possible fallback to"
852
                                     " failover")
853

    
854
NONLIVE_OPT = cli_option("--non-live", dest="live",
855
                         default=True, action="store_false",
856
                         help="Do a non-live migration (this usually means"
857
                         " freeze the instance, save the state, transfer and"
858
                         " only then resume running on the secondary node)")
859

    
860
MIGRATION_MODE_OPT = cli_option("--migration-mode", dest="migration_mode",
861
                                default=None,
862
                                choices=list(constants.HT_MIGRATION_MODES),
863
                                help="Override default migration mode (choose"
864
                                " either live or non-live")
865

    
866
NODE_PLACEMENT_OPT = cli_option("-n", "--node", dest="node",
867
                                help="Target node and optional secondary node",
868
                                metavar="<pnode>[:<snode>]",
869
                                completion_suggest=OPT_COMPL_INST_ADD_NODES)
870

    
871
NODE_LIST_OPT = cli_option("-n", "--node", dest="nodes", default=[],
872
                           action="append", metavar="<node>",
873
                           help="Use only this node (can be used multiple"
874
                           " times, if not given defaults to all nodes)",
875
                           completion_suggest=OPT_COMPL_ONE_NODE)
876

    
877
NODEGROUP_OPT_NAME = "--node-group"
878
NODEGROUP_OPT = cli_option("-g", NODEGROUP_OPT_NAME,
879
                           dest="nodegroup",
880
                           help="Node group (name or uuid)",
881
                           metavar="<nodegroup>",
882
                           default=None, type="string",
883
                           completion_suggest=OPT_COMPL_ONE_NODEGROUP)
884

    
885
SINGLE_NODE_OPT = cli_option("-n", "--node", dest="node", help="Target node",
886
                             metavar="<node>",
887
                             completion_suggest=OPT_COMPL_ONE_NODE)
888

    
889
NOSTART_OPT = cli_option("--no-start", dest="start", default=True,
890
                         action="store_false",
891
                         help="Don't start the instance after creation")
892

    
893
SHOWCMD_OPT = cli_option("--show-cmd", dest="show_command",
894
                         action="store_true", default=False,
895
                         help="Show command instead of executing it")
896

    
897
CLEANUP_OPT = cli_option("--cleanup", dest="cleanup",
898
                         default=False, action="store_true",
899
                         help="Instead of performing the migration, try to"
900
                         " recover from a failed cleanup. This is safe"
901
                         " to run even if the instance is healthy, but it"
902
                         " will create extra replication traffic and "
903
                         " disrupt briefly the replication (like during the"
904
                         " migration")
905

    
906
STATIC_OPT = cli_option("-s", "--static", dest="static",
907
                        action="store_true", default=False,
908
                        help="Only show configuration data, not runtime data")
909

    
910
ALL_OPT = cli_option("--all", dest="show_all",
911
                     default=False, action="store_true",
912
                     help="Show info on all instances on the cluster."
913
                     " This can take a long time to run, use wisely")
914

    
915
SELECT_OS_OPT = cli_option("--select-os", dest="select_os",
916
                           action="store_true", default=False,
917
                           help="Interactive OS reinstall, lists available"
918
                           " OS templates for selection")
919

    
920
IGNORE_FAILURES_OPT = cli_option("--ignore-failures", dest="ignore_failures",
921
                                 action="store_true", default=False,
922
                                 help="Remove the instance from the cluster"
923
                                 " configuration even if there are failures"
924
                                 " during the removal process")
925

    
926
IGNORE_REMOVE_FAILURES_OPT = cli_option("--ignore-remove-failures",
927
                                        dest="ignore_remove_failures",
928
                                        action="store_true", default=False,
929
                                        help="Remove the instance from the"
930
                                        " cluster configuration even if there"
931
                                        " are failures during the removal"
932
                                        " process")
933

    
934
REMOVE_INSTANCE_OPT = cli_option("--remove-instance", dest="remove_instance",
935
                                 action="store_true", default=False,
936
                                 help="Remove the instance from the cluster")
937

    
938
DST_NODE_OPT = cli_option("-n", "--target-node", dest="dst_node",
939
                               help="Specifies the new node for the instance",
940
                               metavar="NODE", default=None,
941
                               completion_suggest=OPT_COMPL_ONE_NODE)
942

    
943
NEW_SECONDARY_OPT = cli_option("-n", "--new-secondary", dest="dst_node",
944
                               help="Specifies the new secondary node",
945
                               metavar="NODE", default=None,
946
                               completion_suggest=OPT_COMPL_ONE_NODE)
947

    
948
ON_PRIMARY_OPT = cli_option("-p", "--on-primary", dest="on_primary",
949
                            default=False, action="store_true",
950
                            help="Replace the disk(s) on the primary"
951
                                 " node (applies only to internally mirrored"
952
                                 " disk templates, e.g. %s)" %
953
                                 utils.CommaJoin(constants.DTS_INT_MIRROR))
954

    
955
ON_SECONDARY_OPT = cli_option("-s", "--on-secondary", dest="on_secondary",
956
                              default=False, action="store_true",
957
                              help="Replace the disk(s) on the secondary"
958
                                   " node (applies only to internally mirrored"
959
                                   " disk templates, e.g. %s)" %
960
                                   utils.CommaJoin(constants.DTS_INT_MIRROR))
961

    
962
AUTO_PROMOTE_OPT = cli_option("--auto-promote", dest="auto_promote",
963
                              default=False, action="store_true",
964
                              help="Lock all nodes and auto-promote as needed"
965
                              " to MC status")
966

    
967
AUTO_REPLACE_OPT = cli_option("-a", "--auto", dest="auto",
968
                              default=False, action="store_true",
969
                              help="Automatically replace faulty disks"
970
                                   " (applies only to internally mirrored"
971
                                   " disk templates, e.g. %s)" %
972
                                   utils.CommaJoin(constants.DTS_INT_MIRROR))
973

    
974
IGNORE_SIZE_OPT = cli_option("--ignore-size", dest="ignore_size",
975
                             default=False, action="store_true",
976
                             help="Ignore current recorded size"
977
                             " (useful for forcing activation when"
978
                             " the recorded size is wrong)")
979

    
980
SRC_NODE_OPT = cli_option("--src-node", dest="src_node", help="Source node",
981
                          metavar="<node>",
982
                          completion_suggest=OPT_COMPL_ONE_NODE)
983

    
984
SRC_DIR_OPT = cli_option("--src-dir", dest="src_dir", help="Source directory",
985
                         metavar="<dir>")
986

    
987
SECONDARY_IP_OPT = cli_option("-s", "--secondary-ip", dest="secondary_ip",
988
                              help="Specify the secondary ip for the node",
989
                              metavar="ADDRESS", default=None)
990

    
991
READD_OPT = cli_option("--readd", dest="readd",
992
                       default=False, action="store_true",
993
                       help="Readd old node after replacing it")
994

    
995
NOSSH_KEYCHECK_OPT = cli_option("--no-ssh-key-check", dest="ssh_key_check",
996
                                default=True, action="store_false",
997
                                help="Disable SSH key fingerprint checking")
998

    
999
NODE_FORCE_JOIN_OPT = cli_option("--force-join", dest="force_join",
1000
                                 default=False, action="store_true",
1001
                                 help="Force the joining of a node")
1002

    
1003
MC_OPT = cli_option("-C", "--master-candidate", dest="master_candidate",
1004
                    type="bool", default=None, metavar=_YORNO,
1005
                    help="Set the master_candidate flag on the node")
1006

    
1007
OFFLINE_OPT = cli_option("-O", "--offline", dest="offline", metavar=_YORNO,
1008
                         type="bool", default=None,
1009
                         help=("Set the offline flag on the node"
1010
                               " (cluster does not communicate with offline"
1011
                               " nodes)"))
1012

    
1013
DRAINED_OPT = cli_option("-D", "--drained", dest="drained", metavar=_YORNO,
1014
                         type="bool", default=None,
1015
                         help=("Set the drained flag on the node"
1016
                               " (excluded from allocation operations)"))
1017

    
1018
CAPAB_MASTER_OPT = cli_option("--master-capable", dest="master_capable",
1019
                    type="bool", default=None, metavar=_YORNO,
1020
                    help="Set the master_capable flag on the node")
1021

    
1022
CAPAB_VM_OPT = cli_option("--vm-capable", dest="vm_capable",
1023
                    type="bool", default=None, metavar=_YORNO,
1024
                    help="Set the vm_capable flag on the node")
1025

    
1026
ALLOCATABLE_OPT = cli_option("--allocatable", dest="allocatable",
1027
                             type="bool", default=None, metavar=_YORNO,
1028
                             help="Set the allocatable flag on a volume")
1029

    
1030
NOLVM_STORAGE_OPT = cli_option("--no-lvm-storage", dest="lvm_storage",
1031
                               help="Disable support for lvm based instances"
1032
                               " (cluster-wide)",
1033
                               action="store_false", default=True)
1034

    
1035
ENABLED_HV_OPT = cli_option("--enabled-hypervisors",
1036
                            dest="enabled_hypervisors",
1037
                            help="Comma-separated list of hypervisors",
1038
                            type="string", default=None)
1039

    
1040
NIC_PARAMS_OPT = cli_option("-N", "--nic-parameters", dest="nicparams",
1041
                            type="keyval", default={},
1042
                            help="NIC parameters")
1043

    
1044
CP_SIZE_OPT = cli_option("-C", "--candidate-pool-size", default=None,
1045
                         dest="candidate_pool_size", type="int",
1046
                         help="Set the candidate pool size")
1047

    
1048
VG_NAME_OPT = cli_option("--vg-name", dest="vg_name",
1049
                         help=("Enables LVM and specifies the volume group"
1050
                               " name (cluster-wide) for disk allocation"
1051
                               " [%s]" % constants.DEFAULT_VG),
1052
                         metavar="VG", default=None)
1053

    
1054
YES_DOIT_OPT = cli_option("--yes-do-it", "--ya-rly", dest="yes_do_it",
1055
                          help="Destroy cluster", action="store_true")
1056

    
1057
NOVOTING_OPT = cli_option("--no-voting", dest="no_voting",
1058
                          help="Skip node agreement check (dangerous)",
1059
                          action="store_true", default=False)
1060

    
1061
MAC_PREFIX_OPT = cli_option("-m", "--mac-prefix", dest="mac_prefix",
1062
                            help="Specify the mac prefix for the instance IP"
1063
                            " addresses, in the format XX:XX:XX",
1064
                            metavar="PREFIX",
1065
                            default=None)
1066

    
1067
MASTER_NETDEV_OPT = cli_option("--master-netdev", dest="master_netdev",
1068
                               help="Specify the node interface (cluster-wide)"
1069
                               " on which the master IP address will be added"
1070
                               " (cluster init default: %s)" %
1071
                               constants.DEFAULT_BRIDGE,
1072
                               metavar="NETDEV",
1073
                               default=None)
1074

    
1075
MASTER_NETMASK_OPT = cli_option("--master-netmask", dest="master_netmask",
1076
                                help="Specify the netmask of the master IP",
1077
                                metavar="NETMASK",
1078
                                default=None)
1079

    
1080
USE_EXTERNAL_MIP_SCRIPT = cli_option("--use-external-mip-script",
1081
                                dest="use_external_mip_script",
1082
                                help="Specify whether to run a user-provided"
1083
                                " script for the master IP address turnup and"
1084
                                " turndown operations",
1085
                                type="bool", metavar=_YORNO, default=None)
1086

    
1087
GLOBAL_FILEDIR_OPT = cli_option("--file-storage-dir", dest="file_storage_dir",
1088
                                help="Specify the default directory (cluster-"
1089
                                "wide) for storing the file-based disks [%s]" %
1090
                                constants.DEFAULT_FILE_STORAGE_DIR,
1091
                                metavar="DIR",
1092
                                default=constants.DEFAULT_FILE_STORAGE_DIR)
1093

    
1094
GLOBAL_SHARED_FILEDIR_OPT = cli_option("--shared-file-storage-dir",
1095
                            dest="shared_file_storage_dir",
1096
                            help="Specify the default directory (cluster-"
1097
                            "wide) for storing the shared file-based"
1098
                            " disks [%s]" %
1099
                            constants.DEFAULT_SHARED_FILE_STORAGE_DIR,
1100
                            metavar="SHAREDDIR",
1101
                            default=constants.DEFAULT_SHARED_FILE_STORAGE_DIR)
1102

    
1103
NOMODIFY_ETCHOSTS_OPT = cli_option("--no-etc-hosts", dest="modify_etc_hosts",
1104
                                   help="Don't modify /etc/hosts",
1105
                                   action="store_false", default=True)
1106

    
1107
NOMODIFY_SSH_SETUP_OPT = cli_option("--no-ssh-init", dest="modify_ssh_setup",
1108
                                    help="Don't initialize SSH keys",
1109
                                    action="store_false", default=True)
1110

    
1111
ERROR_CODES_OPT = cli_option("--error-codes", dest="error_codes",
1112
                             help="Enable parseable error messages",
1113
                             action="store_true", default=False)
1114

    
1115
NONPLUS1_OPT = cli_option("--no-nplus1-mem", dest="skip_nplusone_mem",
1116
                          help="Skip N+1 memory redundancy tests",
1117
                          action="store_true", default=False)
1118

    
1119
REBOOT_TYPE_OPT = cli_option("-t", "--type", dest="reboot_type",
1120
                             help="Type of reboot: soft/hard/full",
1121
                             default=constants.INSTANCE_REBOOT_HARD,
1122
                             metavar="<REBOOT>",
1123
                             choices=list(constants.REBOOT_TYPES))
1124

    
1125
IGNORE_SECONDARIES_OPT = cli_option("--ignore-secondaries",
1126
                                    dest="ignore_secondaries",
1127
                                    default=False, action="store_true",
1128
                                    help="Ignore errors from secondaries")
1129

    
1130
NOSHUTDOWN_OPT = cli_option("--noshutdown", dest="shutdown",
1131
                            action="store_false", default=True,
1132
                            help="Don't shutdown the instance (unsafe)")
1133

    
1134
TIMEOUT_OPT = cli_option("--timeout", dest="timeout", type="int",
1135
                         default=constants.DEFAULT_SHUTDOWN_TIMEOUT,
1136
                         help="Maximum time to wait")
1137

    
1138
SHUTDOWN_TIMEOUT_OPT = cli_option("--shutdown-timeout",
1139
                         dest="shutdown_timeout", type="int",
1140
                         default=constants.DEFAULT_SHUTDOWN_TIMEOUT,
1141
                         help="Maximum time to wait for instance shutdown")
1142

    
1143
INTERVAL_OPT = cli_option("--interval", dest="interval", type="int",
1144
                          default=None,
1145
                          help=("Number of seconds between repetions of the"
1146
                                " command"))
1147

    
1148
EARLY_RELEASE_OPT = cli_option("--early-release",
1149
                               dest="early_release", default=False,
1150
                               action="store_true",
1151
                               help="Release the locks on the secondary"
1152
                               " node(s) early")
1153

    
1154
NEW_CLUSTER_CERT_OPT = cli_option("--new-cluster-certificate",
1155
                                  dest="new_cluster_cert",
1156
                                  default=False, action="store_true",
1157
                                  help="Generate a new cluster certificate")
1158

    
1159
RAPI_CERT_OPT = cli_option("--rapi-certificate", dest="rapi_cert",
1160
                           default=None,
1161
                           help="File containing new RAPI certificate")
1162

    
1163
NEW_RAPI_CERT_OPT = cli_option("--new-rapi-certificate", dest="new_rapi_cert",
1164
                               default=None, action="store_true",
1165
                               help=("Generate a new self-signed RAPI"
1166
                                     " certificate"))
1167

    
1168
SPICE_CERT_OPT = cli_option("--spice-certificate", dest="spice_cert",
1169
                           default=None,
1170
                           help="File containing new SPICE certificate")
1171

    
1172
SPICE_CACERT_OPT = cli_option("--spice-ca-certificate", dest="spice_cacert",
1173
                           default=None,
1174
                           help="File containing the certificate of the CA"
1175
                                " which signed the SPICE certificate")
1176

    
1177
NEW_SPICE_CERT_OPT = cli_option("--new-spice-certificate",
1178
                               dest="new_spice_cert", default=None,
1179
                               action="store_true",
1180
                               help=("Generate a new self-signed SPICE"
1181
                                     " certificate"))
1182

    
1183
NEW_CONFD_HMAC_KEY_OPT = cli_option("--new-confd-hmac-key",
1184
                                    dest="new_confd_hmac_key",
1185
                                    default=False, action="store_true",
1186
                                    help=("Create a new HMAC key for %s" %
1187
                                          constants.CONFD))
1188

    
1189
CLUSTER_DOMAIN_SECRET_OPT = cli_option("--cluster-domain-secret",
1190
                                       dest="cluster_domain_secret",
1191
                                       default=None,
1192
                                       help=("Load new new cluster domain"
1193
                                             " secret from file"))
1194

    
1195
NEW_CLUSTER_DOMAIN_SECRET_OPT = cli_option("--new-cluster-domain-secret",
1196
                                           dest="new_cluster_domain_secret",
1197
                                           default=False, action="store_true",
1198
                                           help=("Create a new cluster domain"
1199
                                                 " secret"))
1200

    
1201
USE_REPL_NET_OPT = cli_option("--use-replication-network",
1202
                              dest="use_replication_network",
1203
                              help="Whether to use the replication network"
1204
                              " for talking to the nodes",
1205
                              action="store_true", default=False)
1206

    
1207
MAINTAIN_NODE_HEALTH_OPT = \
1208
    cli_option("--maintain-node-health", dest="maintain_node_health",
1209
               metavar=_YORNO, default=None, type="bool",
1210
               help="Configure the cluster to automatically maintain node"
1211
               " health, by shutting down unknown instances, shutting down"
1212
               " unknown DRBD devices, etc.")
1213

    
1214
IDENTIFY_DEFAULTS_OPT = \
1215
    cli_option("--identify-defaults", dest="identify_defaults",
1216
               default=False, action="store_true",
1217
               help="Identify which saved instance parameters are equal to"
1218
               " the current cluster defaults and set them as such, instead"
1219
               " of marking them as overridden")
1220

    
1221
UIDPOOL_OPT = cli_option("--uid-pool", default=None,
1222
                         action="store", dest="uid_pool",
1223
                         help=("A list of user-ids or user-id"
1224
                               " ranges separated by commas"))
1225

    
1226
ADD_UIDS_OPT = cli_option("--add-uids", default=None,
1227
                          action="store", dest="add_uids",
1228
                          help=("A list of user-ids or user-id"
1229
                                " ranges separated by commas, to be"
1230
                                " added to the user-id pool"))
1231

    
1232
REMOVE_UIDS_OPT = cli_option("--remove-uids", default=None,
1233
                             action="store", dest="remove_uids",
1234
                             help=("A list of user-ids or user-id"
1235
                                   " ranges separated by commas, to be"
1236
                                   " removed from the user-id pool"))
1237

    
1238
RESERVED_LVS_OPT = cli_option("--reserved-lvs", default=None,
1239
                             action="store", dest="reserved_lvs",
1240
                             help=("A comma-separated list of reserved"
1241
                                   " logical volumes names, that will be"
1242
                                   " ignored by cluster verify"))
1243

    
1244
ROMAN_OPT = cli_option("--roman",
1245
                       dest="roman_integers", default=False,
1246
                       action="store_true",
1247
                       help="Use roman numbers for positive integers")
1248

    
1249
DRBD_HELPER_OPT = cli_option("--drbd-usermode-helper", dest="drbd_helper",
1250
                             action="store", default=None,
1251
                             help="Specifies usermode helper for DRBD")
1252

    
1253
NODRBD_STORAGE_OPT = cli_option("--no-drbd-storage", dest="drbd_storage",
1254
                                action="store_false", default=True,
1255
                                help="Disable support for DRBD")
1256

    
1257
PRIMARY_IP_VERSION_OPT = \
1258
    cli_option("--primary-ip-version", default=constants.IP4_VERSION,
1259
               action="store", dest="primary_ip_version",
1260
               metavar="%d|%d" % (constants.IP4_VERSION,
1261
                                  constants.IP6_VERSION),
1262
               help="Cluster-wide IP version for primary IP")
1263

    
1264
PRIORITY_OPT = cli_option("--priority", default=None, dest="priority",
1265
                          metavar="|".join(name for name, _ in _PRIORITY_NAMES),
1266
                          choices=_PRIONAME_TO_VALUE.keys(),
1267
                          help="Priority for opcode processing")
1268

    
1269
HID_OS_OPT = cli_option("--hidden", dest="hidden",
1270
                        type="bool", default=None, metavar=_YORNO,
1271
                        help="Sets the hidden flag on the OS")
1272

    
1273
BLK_OS_OPT = cli_option("--blacklisted", dest="blacklisted",
1274
                        type="bool", default=None, metavar=_YORNO,
1275
                        help="Sets the blacklisted flag on the OS")
1276

    
1277
PREALLOC_WIPE_DISKS_OPT = cli_option("--prealloc-wipe-disks", default=None,
1278
                                     type="bool", metavar=_YORNO,
1279
                                     dest="prealloc_wipe_disks",
1280
                                     help=("Wipe disks prior to instance"
1281
                                           " creation"))
1282

    
1283
NODE_PARAMS_OPT = cli_option("--node-parameters", dest="ndparams",
1284
                             type="keyval", default=None,
1285
                             help="Node parameters")
1286

    
1287
ALLOC_POLICY_OPT = cli_option("--alloc-policy", dest="alloc_policy",
1288
                              action="store", metavar="POLICY", default=None,
1289
                              help="Allocation policy for the node group")
1290

    
1291
NODE_POWERED_OPT = cli_option("--node-powered", default=None,
1292
                              type="bool", metavar=_YORNO,
1293
                              dest="node_powered",
1294
                              help="Specify if the SoR for node is powered")
1295

    
1296
OOB_TIMEOUT_OPT = cli_option("--oob-timeout", dest="oob_timeout", type="int",
1297
                         default=constants.OOB_TIMEOUT,
1298
                         help="Maximum time to wait for out-of-band helper")
1299

    
1300
POWER_DELAY_OPT = cli_option("--power-delay", dest="power_delay", type="float",
1301
                             default=constants.OOB_POWER_DELAY,
1302
                             help="Time in seconds to wait between power-ons")
1303

    
1304
FORCE_FILTER_OPT = cli_option("-F", "--filter", dest="force_filter",
1305
                              action="store_true", default=False,
1306
                              help=("Whether command argument should be treated"
1307
                                    " as filter"))
1308

    
1309
NO_REMEMBER_OPT = cli_option("--no-remember",
1310
                             dest="no_remember",
1311
                             action="store_true", default=False,
1312
                             help="Perform but do not record the change"
1313
                             " in the configuration")
1314

    
1315
PRIMARY_ONLY_OPT = cli_option("-p", "--primary-only",
1316
                              default=False, action="store_true",
1317
                              help="Evacuate primary instances only")
1318

    
1319
SECONDARY_ONLY_OPT = cli_option("-s", "--secondary-only",
1320
                                default=False, action="store_true",
1321
                                help="Evacuate secondary instances only"
1322
                                     " (applies only to internally mirrored"
1323
                                     " disk templates, e.g. %s)" %
1324
                                     utils.CommaJoin(constants.DTS_INT_MIRROR))
1325

    
1326
STARTUP_PAUSED_OPT = cli_option("--paused", dest="startup_paused",
1327
                                action="store_true", default=False,
1328
                                help="Pause instance at startup")
1329

    
1330
TO_GROUP_OPT = cli_option("--to", dest="to", metavar="<group>",
1331
                          help="Destination node group (name or uuid)",
1332
                          default=None, action="append",
1333
                          completion_suggest=OPT_COMPL_ONE_NODEGROUP)
1334

    
1335
IGNORE_ERRORS_OPT = cli_option("-I", "--ignore-errors", default=[],
1336
                               action="append", dest="ignore_errors",
1337
                               choices=list(constants.CV_ALL_ECODES_STRINGS),
1338
                               help="Error code to be ignored")
1339

    
1340
DISK_STATE_OPT = cli_option("--disk-state", default=[], dest="disk_state",
1341
                            action="append",
1342
                            help=("Specify disk state information in the format"
1343
                                  " storage_type/identifier:option=value,..."),
1344
                            type="identkeyval")
1345

    
1346
HV_STATE_OPT = cli_option("--hypervisor-state", default=[], dest="hv_state",
1347
                          action="append",
1348
                          help=("Specify hypervisor state information in the"
1349
                                " format hypervisor:option=value,..."),
1350
                          type="identkeyval")
1351

    
1352
IGNORE_IPOLICY_OPT = cli_option("--ignore-ipolicy", dest="ignore_ipolicy",
1353
                                action="store_true", default=False,
1354
                                help="Ignore instance policy violations")
1355

    
1356

    
1357
#: Options provided by all commands
1358
COMMON_OPTS = [DEBUG_OPT]
1359

    
1360
# common options for creating instances. add and import then add their own
1361
# specific ones.
1362
COMMON_CREATE_OPTS = [
1363
  BACKEND_OPT,
1364
  DISK_OPT,
1365
  DISK_TEMPLATE_OPT,
1366
  FILESTORE_DIR_OPT,
1367
  FILESTORE_DRIVER_OPT,
1368
  HYPERVISOR_OPT,
1369
  IALLOCATOR_OPT,
1370
  NET_OPT,
1371
  NODE_PLACEMENT_OPT,
1372
  NOIPCHECK_OPT,
1373
  NONAMECHECK_OPT,
1374
  NONICS_OPT,
1375
  NWSYNC_OPT,
1376
  OSPARAMS_OPT,
1377
  OS_SIZE_OPT,
1378
  SUBMIT_OPT,
1379
  TAG_ADD_OPT,
1380
  DRY_RUN_OPT,
1381
  PRIORITY_OPT,
1382
  ]
1383

    
1384

    
1385
def _ParseArgs(argv, commands, aliases, env_override):
1386
  """Parser for the command line arguments.
1387

1388
  This function parses the arguments and returns the function which
1389
  must be executed together with its (modified) arguments.
1390

1391
  @param argv: the command line
1392
  @param commands: dictionary with special contents, see the design
1393
      doc for cmdline handling
1394
  @param aliases: dictionary with command aliases {'alias': 'target, ...}
1395
  @param env_override: list of env variables allowed for default args
1396

1397
  """
1398
  assert not (env_override - set(commands))
1399

    
1400
  if len(argv) == 0:
1401
    binary = "<command>"
1402
  else:
1403
    binary = argv[0].split("/")[-1]
1404

    
1405
  if len(argv) > 1 and argv[1] == "--version":
1406
    ToStdout("%s (ganeti %s) %s", binary, constants.VCS_VERSION,
1407
             constants.RELEASE_VERSION)
1408
    # Quit right away. That way we don't have to care about this special
1409
    # argument. optparse.py does it the same.
1410
    sys.exit(0)
1411

    
1412
  if len(argv) < 2 or not (argv[1] in commands or
1413
                           argv[1] in aliases):
1414
    # let's do a nice thing
1415
    sortedcmds = commands.keys()
1416
    sortedcmds.sort()
1417

    
1418
    ToStdout("Usage: %s {command} [options...] [argument...]", binary)
1419
    ToStdout("%s <command> --help to see details, or man %s", binary, binary)
1420
    ToStdout("")
1421

    
1422
    # compute the max line length for cmd + usage
1423
    mlen = max([len(" %s" % cmd) for cmd in commands])
1424
    mlen = min(60, mlen) # should not get here...
1425

    
1426
    # and format a nice command list
1427
    ToStdout("Commands:")
1428
    for cmd in sortedcmds:
1429
      cmdstr = " %s" % (cmd,)
1430
      help_text = commands[cmd][4]
1431
      help_lines = textwrap.wrap(help_text, 79 - 3 - mlen)
1432
      ToStdout("%-*s - %s", mlen, cmdstr, help_lines.pop(0))
1433
      for line in help_lines:
1434
        ToStdout("%-*s   %s", mlen, "", line)
1435

    
1436
    ToStdout("")
1437

    
1438
    return None, None, None
1439

    
1440
  # get command, unalias it, and look it up in commands
1441
  cmd = argv.pop(1)
1442
  if cmd in aliases:
1443
    if cmd in commands:
1444
      raise errors.ProgrammerError("Alias '%s' overrides an existing"
1445
                                   " command" % cmd)
1446

    
1447
    if aliases[cmd] not in commands:
1448
      raise errors.ProgrammerError("Alias '%s' maps to non-existing"
1449
                                   " command '%s'" % (cmd, aliases[cmd]))
1450

    
1451
    cmd = aliases[cmd]
1452

    
1453
  if cmd in env_override:
1454
    args_env_name = ("%s_%s" % (binary.replace("-", "_"), cmd)).upper()
1455
    env_args = os.environ.get(args_env_name)
1456
    if env_args:
1457
      argv = utils.InsertAtPos(argv, 1, shlex.split(env_args))
1458

    
1459
  func, args_def, parser_opts, usage, description = commands[cmd]
1460
  parser = OptionParser(option_list=parser_opts + COMMON_OPTS,
1461
                        description=description,
1462
                        formatter=TitledHelpFormatter(),
1463
                        usage="%%prog %s %s" % (cmd, usage))
1464
  parser.disable_interspersed_args()
1465
  options, args = parser.parse_args(args=argv[1:])
1466

    
1467
  if not _CheckArguments(cmd, args_def, args):
1468
    return None, None, None
1469

    
1470
  return func, options, args
1471

    
1472

    
1473
def _CheckArguments(cmd, args_def, args):
1474
  """Verifies the arguments using the argument definition.
1475

1476
  Algorithm:
1477

1478
    1. Abort with error if values specified by user but none expected.
1479

1480
    1. For each argument in definition
1481

1482
      1. Keep running count of minimum number of values (min_count)
1483
      1. Keep running count of maximum number of values (max_count)
1484
      1. If it has an unlimited number of values
1485

1486
        1. Abort with error if it's not the last argument in the definition
1487

1488
    1. If last argument has limited number of values
1489

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

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

1494
  """
1495
  if args and not args_def:
1496
    ToStderr("Error: Command %s expects no arguments", cmd)
1497
    return False
1498

    
1499
  min_count = None
1500
  max_count = None
1501
  check_max = None
1502

    
1503
  last_idx = len(args_def) - 1
1504

    
1505
  for idx, arg in enumerate(args_def):
1506
    if min_count is None:
1507
      min_count = arg.min
1508
    elif arg.min is not None:
1509
      min_count += arg.min
1510

    
1511
    if max_count is None:
1512
      max_count = arg.max
1513
    elif arg.max is not None:
1514
      max_count += arg.max
1515

    
1516
    if idx == last_idx:
1517
      check_max = (arg.max is not None)
1518

    
1519
    elif arg.max is None:
1520
      raise errors.ProgrammerError("Only the last argument can have max=None")
1521

    
1522
  if check_max:
1523
    # Command with exact number of arguments
1524
    if (min_count is not None and max_count is not None and
1525
        min_count == max_count and len(args) != min_count):
1526
      ToStderr("Error: Command %s expects %d argument(s)", cmd, min_count)
1527
      return False
1528

    
1529
    # Command with limited number of arguments
1530
    if max_count is not None and len(args) > max_count:
1531
      ToStderr("Error: Command %s expects only %d argument(s)",
1532
               cmd, max_count)
1533
      return False
1534

    
1535
  # Command with some required arguments
1536
  if min_count is not None and len(args) < min_count:
1537
    ToStderr("Error: Command %s expects at least %d argument(s)",
1538
             cmd, min_count)
1539
    return False
1540

    
1541
  return True
1542

    
1543

    
1544
def SplitNodeOption(value):
1545
  """Splits the value of a --node option.
1546

1547
  """
1548
  if value and ":" in value:
1549
    return value.split(":", 1)
1550
  else:
1551
    return (value, None)
1552

    
1553

    
1554
def CalculateOSNames(os_name, os_variants):
1555
  """Calculates all the names an OS can be called, according to its variants.
1556

1557
  @type os_name: string
1558
  @param os_name: base name of the os
1559
  @type os_variants: list or None
1560
  @param os_variants: list of supported variants
1561
  @rtype: list
1562
  @return: list of valid names
1563

1564
  """
1565
  if os_variants:
1566
    return ["%s+%s" % (os_name, v) for v in os_variants]
1567
  else:
1568
    return [os_name]
1569

    
1570

    
1571
def ParseFields(selected, default):
1572
  """Parses the values of "--field"-like options.
1573

1574
  @type selected: string or None
1575
  @param selected: User-selected options
1576
  @type default: list
1577
  @param default: Default fields
1578

1579
  """
1580
  if selected is None:
1581
    return default
1582

    
1583
  if selected.startswith("+"):
1584
    return default + selected[1:].split(",")
1585

    
1586
  return selected.split(",")
1587

    
1588

    
1589
UsesRPC = rpc.RunWithRPC
1590

    
1591

    
1592
def AskUser(text, choices=None):
1593
  """Ask the user a question.
1594

1595
  @param text: the question to ask
1596

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

1602
  @return: one of the return values from the choices list; if input is
1603
      not possible (i.e. not running with a tty, we return the last
1604
      entry from the list
1605

1606
  """
1607
  if choices is None:
1608
    choices = [("y", True, "Perform the operation"),
1609
               ("n", False, "Do not perform the operation")]
1610
  if not choices or not isinstance(choices, list):
1611
    raise errors.ProgrammerError("Invalid choices argument to AskUser")
1612
  for entry in choices:
1613
    if not isinstance(entry, tuple) or len(entry) < 3 or entry[0] == "?":
1614
      raise errors.ProgrammerError("Invalid choices element to AskUser")
1615

    
1616
  answer = choices[-1][1]
1617
  new_text = []
1618
  for line in text.splitlines():
1619
    new_text.append(textwrap.fill(line, 70, replace_whitespace=False))
1620
  text = "\n".join(new_text)
1621
  try:
1622
    f = file("/dev/tty", "a+")
1623
  except IOError:
1624
    return answer
1625
  try:
1626
    chars = [entry[0] for entry in choices]
1627
    chars[-1] = "[%s]" % chars[-1]
1628
    chars.append("?")
1629
    maps = dict([(entry[0], entry[1]) for entry in choices])
1630
    while True:
1631
      f.write(text)
1632
      f.write("\n")
1633
      f.write("/".join(chars))
1634
      f.write(": ")
1635
      line = f.readline(2).strip().lower()
1636
      if line in maps:
1637
        answer = maps[line]
1638
        break
1639
      elif line == "?":
1640
        for entry in choices:
1641
          f.write(" %s - %s\n" % (entry[0], entry[2]))
1642
        f.write("\n")
1643
        continue
1644
  finally:
1645
    f.close()
1646
  return answer
1647

    
1648

    
1649
class JobSubmittedException(Exception):
1650
  """Job was submitted, client should exit.
1651

1652
  This exception has one argument, the ID of the job that was
1653
  submitted. The handler should print this ID.
1654

1655
  This is not an error, just a structured way to exit from clients.
1656

1657
  """
1658

    
1659

    
1660
def SendJob(ops, cl=None):
1661
  """Function to submit an opcode without waiting for the results.
1662

1663
  @type ops: list
1664
  @param ops: list of opcodes
1665
  @type cl: luxi.Client
1666
  @param cl: the luxi client to use for communicating with the master;
1667
             if None, a new client will be created
1668

1669
  """
1670
  if cl is None:
1671
    cl = GetClient()
1672

    
1673
  job_id = cl.SubmitJob(ops)
1674

    
1675
  return job_id
1676

    
1677

    
1678
def GenericPollJob(job_id, cbs, report_cbs):
1679
  """Generic job-polling function.
1680

1681
  @type job_id: number
1682
  @param job_id: Job ID
1683
  @type cbs: Instance of L{JobPollCbBase}
1684
  @param cbs: Data callbacks
1685
  @type report_cbs: Instance of L{JobPollReportCbBase}
1686
  @param report_cbs: Reporting callbacks
1687

1688
  """
1689
  prev_job_info = None
1690
  prev_logmsg_serial = None
1691

    
1692
  status = None
1693

    
1694
  while True:
1695
    result = cbs.WaitForJobChangeOnce(job_id, ["status"], prev_job_info,
1696
                                      prev_logmsg_serial)
1697
    if not result:
1698
      # job not found, go away!
1699
      raise errors.JobLost("Job with id %s lost" % job_id)
1700

    
1701
    if result == constants.JOB_NOTCHANGED:
1702
      report_cbs.ReportNotChanged(job_id, status)
1703

    
1704
      # Wait again
1705
      continue
1706

    
1707
    # Split result, a tuple of (field values, log entries)
1708
    (job_info, log_entries) = result
1709
    (status, ) = job_info
1710

    
1711
    if log_entries:
1712
      for log_entry in log_entries:
1713
        (serial, timestamp, log_type, message) = log_entry
1714
        report_cbs.ReportLogMessage(job_id, serial, timestamp,
1715
                                    log_type, message)
1716
        prev_logmsg_serial = max(prev_logmsg_serial, serial)
1717

    
1718
    # TODO: Handle canceled and archived jobs
1719
    elif status in (constants.JOB_STATUS_SUCCESS,
1720
                    constants.JOB_STATUS_ERROR,
1721
                    constants.JOB_STATUS_CANCELING,
1722
                    constants.JOB_STATUS_CANCELED):
1723
      break
1724

    
1725
    prev_job_info = job_info
1726

    
1727
  jobs = cbs.QueryJobs([job_id], ["status", "opstatus", "opresult"])
1728
  if not jobs:
1729
    raise errors.JobLost("Job with id %s lost" % job_id)
1730

    
1731
  status, opstatus, result = jobs[0]
1732

    
1733
  if status == constants.JOB_STATUS_SUCCESS:
1734
    return result
1735

    
1736
  if status in (constants.JOB_STATUS_CANCELING, constants.JOB_STATUS_CANCELED):
1737
    raise errors.OpExecError("Job was canceled")
1738

    
1739
  has_ok = False
1740
  for idx, (status, msg) in enumerate(zip(opstatus, result)):
1741
    if status == constants.OP_STATUS_SUCCESS:
1742
      has_ok = True
1743
    elif status == constants.OP_STATUS_ERROR:
1744
      errors.MaybeRaise(msg)
1745

    
1746
      if has_ok:
1747
        raise errors.OpExecError("partial failure (opcode %d): %s" %
1748
                                 (idx, msg))
1749

    
1750
      raise errors.OpExecError(str(msg))
1751

    
1752
  # default failure mode
1753
  raise errors.OpExecError(result)
1754

    
1755

    
1756
class JobPollCbBase:
1757
  """Base class for L{GenericPollJob} callbacks.
1758

1759
  """
1760
  def __init__(self):
1761
    """Initializes this class.
1762

1763
    """
1764

    
1765
  def WaitForJobChangeOnce(self, job_id, fields,
1766
                           prev_job_info, prev_log_serial):
1767
    """Waits for changes on a job.
1768

1769
    """
1770
    raise NotImplementedError()
1771

    
1772
  def QueryJobs(self, job_ids, fields):
1773
    """Returns the selected fields for the selected job IDs.
1774

1775
    @type job_ids: list of numbers
1776
    @param job_ids: Job IDs
1777
    @type fields: list of strings
1778
    @param fields: Fields
1779

1780
    """
1781
    raise NotImplementedError()
1782

    
1783

    
1784
class JobPollReportCbBase:
1785
  """Base class for L{GenericPollJob} reporting callbacks.
1786

1787
  """
1788
  def __init__(self):
1789
    """Initializes this class.
1790

1791
    """
1792

    
1793
  def ReportLogMessage(self, job_id, serial, timestamp, log_type, log_msg):
1794
    """Handles a log message.
1795

1796
    """
1797
    raise NotImplementedError()
1798

    
1799
  def ReportNotChanged(self, job_id, status):
1800
    """Called for if a job hasn't changed in a while.
1801

1802
    @type job_id: number
1803
    @param job_id: Job ID
1804
    @type status: string or None
1805
    @param status: Job status if available
1806

1807
    """
1808
    raise NotImplementedError()
1809

    
1810

    
1811
class _LuxiJobPollCb(JobPollCbBase):
1812
  def __init__(self, cl):
1813
    """Initializes this class.
1814

1815
    """
1816
    JobPollCbBase.__init__(self)
1817
    self.cl = cl
1818

    
1819
  def WaitForJobChangeOnce(self, job_id, fields,
1820
                           prev_job_info, prev_log_serial):
1821
    """Waits for changes on a job.
1822

1823
    """
1824
    return self.cl.WaitForJobChangeOnce(job_id, fields,
1825
                                        prev_job_info, prev_log_serial)
1826

    
1827
  def QueryJobs(self, job_ids, fields):
1828
    """Returns the selected fields for the selected job IDs.
1829

1830
    """
1831
    return self.cl.QueryJobs(job_ids, fields)
1832

    
1833

    
1834
class FeedbackFnJobPollReportCb(JobPollReportCbBase):
1835
  def __init__(self, feedback_fn):
1836
    """Initializes this class.
1837

1838
    """
1839
    JobPollReportCbBase.__init__(self)
1840

    
1841
    self.feedback_fn = feedback_fn
1842

    
1843
    assert callable(feedback_fn)
1844

    
1845
  def ReportLogMessage(self, job_id, serial, timestamp, log_type, log_msg):
1846
    """Handles a log message.
1847

1848
    """
1849
    self.feedback_fn((timestamp, log_type, log_msg))
1850

    
1851
  def ReportNotChanged(self, job_id, status):
1852
    """Called if a job hasn't changed in a while.
1853

1854
    """
1855
    # Ignore
1856

    
1857

    
1858
class StdioJobPollReportCb(JobPollReportCbBase):
1859
  def __init__(self):
1860
    """Initializes this class.
1861

1862
    """
1863
    JobPollReportCbBase.__init__(self)
1864

    
1865
    self.notified_queued = False
1866
    self.notified_waitlock = False
1867

    
1868
  def ReportLogMessage(self, job_id, serial, timestamp, log_type, log_msg):
1869
    """Handles a log message.
1870

1871
    """
1872
    ToStdout("%s %s", time.ctime(utils.MergeTime(timestamp)),
1873
             FormatLogMessage(log_type, log_msg))
1874

    
1875
  def ReportNotChanged(self, job_id, status):
1876
    """Called if a job hasn't changed in a while.
1877

1878
    """
1879
    if status is None:
1880
      return
1881

    
1882
    if status == constants.JOB_STATUS_QUEUED and not self.notified_queued:
1883
      ToStderr("Job %s is waiting in queue", job_id)
1884
      self.notified_queued = True
1885

    
1886
    elif status == constants.JOB_STATUS_WAITING and not self.notified_waitlock:
1887
      ToStderr("Job %s is trying to acquire all necessary locks", job_id)
1888
      self.notified_waitlock = True
1889

    
1890

    
1891
def FormatLogMessage(log_type, log_msg):
1892
  """Formats a job message according to its type.
1893

1894
  """
1895
  if log_type != constants.ELOG_MESSAGE:
1896
    log_msg = str(log_msg)
1897

    
1898
  return utils.SafeEncode(log_msg)
1899

    
1900

    
1901
def PollJob(job_id, cl=None, feedback_fn=None, reporter=None):
1902
  """Function to poll for the result of a job.
1903

1904
  @type job_id: job identified
1905
  @param job_id: the job to poll for results
1906
  @type cl: luxi.Client
1907
  @param cl: the luxi client to use for communicating with the master;
1908
             if None, a new client will be created
1909

1910
  """
1911
  if cl is None:
1912
    cl = GetClient()
1913

    
1914
  if reporter is None:
1915
    if feedback_fn:
1916
      reporter = FeedbackFnJobPollReportCb(feedback_fn)
1917
    else:
1918
      reporter = StdioJobPollReportCb()
1919
  elif feedback_fn:
1920
    raise errors.ProgrammerError("Can't specify reporter and feedback function")
1921

    
1922
  return GenericPollJob(job_id, _LuxiJobPollCb(cl), reporter)
1923

    
1924

    
1925
def SubmitOpCode(op, cl=None, feedback_fn=None, opts=None, reporter=None):
1926
  """Legacy function to submit an opcode.
1927

1928
  This is just a simple wrapper over the construction of the processor
1929
  instance. It should be extended to better handle feedback and
1930
  interaction functions.
1931

1932
  """
1933
  if cl is None:
1934
    cl = GetClient()
1935

    
1936
  SetGenericOpcodeOpts([op], opts)
1937

    
1938
  job_id = SendJob([op], cl=cl)
1939

    
1940
  op_results = PollJob(job_id, cl=cl, feedback_fn=feedback_fn,
1941
                       reporter=reporter)
1942

    
1943
  return op_results[0]
1944

    
1945

    
1946
def SubmitOrSend(op, opts, cl=None, feedback_fn=None):
1947
  """Wrapper around SubmitOpCode or SendJob.
1948

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

1954
  It will also process the opcodes if we're sending the via SendJob
1955
  (otherwise SubmitOpCode does it).
1956

1957
  """
1958
  if opts and opts.submit_only:
1959
    job = [op]
1960
    SetGenericOpcodeOpts(job, opts)
1961
    job_id = SendJob(job, cl=cl)
1962
    raise JobSubmittedException(job_id)
1963
  else:
1964
    return SubmitOpCode(op, cl=cl, feedback_fn=feedback_fn, opts=opts)
1965

    
1966

    
1967
def SetGenericOpcodeOpts(opcode_list, options):
1968
  """Processor for generic options.
1969

1970
  This function updates the given opcodes based on generic command
1971
  line options (like debug, dry-run, etc.).
1972

1973
  @param opcode_list: list of opcodes
1974
  @param options: command line options or None
1975
  @return: None (in-place modification)
1976

1977
  """
1978
  if not options:
1979
    return
1980
  for op in opcode_list:
1981
    op.debug_level = options.debug
1982
    if hasattr(options, "dry_run"):
1983
      op.dry_run = options.dry_run
1984
    if getattr(options, "priority", None) is not None:
1985
      op.priority = _PRIONAME_TO_VALUE[options.priority]
1986

    
1987

    
1988
def GetClient():
1989
  # TODO: Cache object?
1990
  try:
1991
    client = luxi.Client()
1992
  except luxi.NoMasterError:
1993
    ss = ssconf.SimpleStore()
1994

    
1995
    # Try to read ssconf file
1996
    try:
1997
      ss.GetMasterNode()
1998
    except errors.ConfigurationError:
1999
      raise errors.OpPrereqError("Cluster not initialized or this machine is"
2000
                                 " not part of a cluster")
2001

    
2002
    master, myself = ssconf.GetMasterAndMyself(ss=ss)
2003
    if master != myself:
2004
      raise errors.OpPrereqError("This is not the master node, please connect"
2005
                                 " to node '%s' and rerun the command" %
2006
                                 master)
2007
    raise
2008
  return client
2009

    
2010

    
2011
def FormatError(err):
2012
  """Return a formatted error message for a given error.
2013

2014
  This function takes an exception instance and returns a tuple
2015
  consisting of two values: first, the recommended exit code, and
2016
  second, a string describing the error message (not
2017
  newline-terminated).
2018

2019
  """
2020
  retcode = 1
2021
  obuf = StringIO()
2022
  msg = str(err)
2023
  if isinstance(err, errors.ConfigurationError):
2024
    txt = "Corrupt configuration file: %s" % msg
2025
    logging.error(txt)
2026
    obuf.write(txt + "\n")
2027
    obuf.write("Aborting.")
2028
    retcode = 2
2029
  elif isinstance(err, errors.HooksAbort):
2030
    obuf.write("Failure: hooks execution failed:\n")
2031
    for node, script, out in err.args[0]:
2032
      if out:
2033
        obuf.write("  node: %s, script: %s, output: %s\n" %
2034
                   (node, script, out))
2035
      else:
2036
        obuf.write("  node: %s, script: %s (no output)\n" %
2037
                   (node, script))
2038
  elif isinstance(err, errors.HooksFailure):
2039
    obuf.write("Failure: hooks general failure: %s" % msg)
2040
  elif isinstance(err, errors.ResolverError):
2041
    this_host = netutils.Hostname.GetSysName()
2042
    if err.args[0] == this_host:
2043
      msg = "Failure: can't resolve my own hostname ('%s')"
2044
    else:
2045
      msg = "Failure: can't resolve hostname '%s'"
2046
    obuf.write(msg % err.args[0])
2047
  elif isinstance(err, errors.OpPrereqError):
2048
    if len(err.args) == 2:
2049
      obuf.write("Failure: prerequisites not met for this"
2050
               " operation:\nerror type: %s, error details:\n%s" %
2051
                 (err.args[1], err.args[0]))
2052
    else:
2053
      obuf.write("Failure: prerequisites not met for this"
2054
                 " operation:\n%s" % msg)
2055
  elif isinstance(err, errors.OpExecError):
2056
    obuf.write("Failure: command execution error:\n%s" % msg)
2057
  elif isinstance(err, errors.TagError):
2058
    obuf.write("Failure: invalid tag(s) given:\n%s" % msg)
2059
  elif isinstance(err, errors.JobQueueDrainError):
2060
    obuf.write("Failure: the job queue is marked for drain and doesn't"
2061
               " accept new requests\n")
2062
  elif isinstance(err, errors.JobQueueFull):
2063
    obuf.write("Failure: the job queue is full and doesn't accept new"
2064
               " job submissions until old jobs are archived\n")
2065
  elif isinstance(err, errors.TypeEnforcementError):
2066
    obuf.write("Parameter Error: %s" % msg)
2067
  elif isinstance(err, errors.ParameterError):
2068
    obuf.write("Failure: unknown/wrong parameter name '%s'" % msg)
2069
  elif isinstance(err, luxi.NoMasterError):
2070
    obuf.write("Cannot communicate with the master daemon.\nIs it running"
2071
               " and listening for connections?")
2072
  elif isinstance(err, luxi.TimeoutError):
2073
    obuf.write("Timeout while talking to the master daemon. Jobs might have"
2074
               " been submitted and will continue to run even if the call"
2075
               " timed out. Useful commands in this situation are \"gnt-job"
2076
               " list\", \"gnt-job cancel\" and \"gnt-job watch\". Error:\n")
2077
    obuf.write(msg)
2078
  elif isinstance(err, luxi.PermissionError):
2079
    obuf.write("It seems you don't have permissions to connect to the"
2080
               " master daemon.\nPlease retry as a different user.")
2081
  elif isinstance(err, luxi.ProtocolError):
2082
    obuf.write("Unhandled protocol error while talking to the master daemon:\n"
2083
               "%s" % msg)
2084
  elif isinstance(err, errors.JobLost):
2085
    obuf.write("Error checking job status: %s" % msg)
2086
  elif isinstance(err, errors.QueryFilterParseError):
2087
    obuf.write("Error while parsing query filter: %s\n" % err.args[0])
2088
    obuf.write("\n".join(err.GetDetails()))
2089
  elif isinstance(err, errors.GenericError):
2090
    obuf.write("Unhandled Ganeti error: %s" % msg)
2091
  elif isinstance(err, JobSubmittedException):
2092
    obuf.write("JobID: %s\n" % err.args[0])
2093
    retcode = 0
2094
  else:
2095
    obuf.write("Unhandled exception: %s" % msg)
2096
  return retcode, obuf.getvalue().rstrip("\n")
2097

    
2098

    
2099
def GenericMain(commands, override=None, aliases=None,
2100
                env_override=frozenset()):
2101
  """Generic main function for all the gnt-* commands.
2102

2103
  @param commands: a dictionary with a special structure, see the design doc
2104
                   for command line handling.
2105
  @param override: if not None, we expect a dictionary with keys that will
2106
                   override command line options; this can be used to pass
2107
                   options from the scripts to generic functions
2108
  @param aliases: dictionary with command aliases {'alias': 'target, ...}
2109
  @param env_override: list of environment names which are allowed to submit
2110
                       default args for commands
2111

2112
  """
2113
  # save the program name and the entire command line for later logging
2114
  if sys.argv:
2115
    binary = os.path.basename(sys.argv[0]) or sys.argv[0]
2116
    if len(sys.argv) >= 2:
2117
      binary += " " + sys.argv[1]
2118
      old_cmdline = " ".join(sys.argv[2:])
2119
    else:
2120
      old_cmdline = ""
2121
  else:
2122
    binary = "<unknown program>"
2123
    old_cmdline = ""
2124

    
2125
  if aliases is None:
2126
    aliases = {}
2127

    
2128
  try:
2129
    func, options, args = _ParseArgs(sys.argv, commands, aliases, env_override)
2130
  except errors.ParameterError, err:
2131
    result, err_msg = FormatError(err)
2132
    ToStderr(err_msg)
2133
    return 1
2134

    
2135
  if func is None: # parse error
2136
    return 1
2137

    
2138
  if override is not None:
2139
    for key, val in override.iteritems():
2140
      setattr(options, key, val)
2141

    
2142
  utils.SetupLogging(constants.LOG_COMMANDS, binary, debug=options.debug,
2143
                     stderr_logging=True)
2144

    
2145
  if old_cmdline:
2146
    logging.info("run with arguments '%s'", old_cmdline)
2147
  else:
2148
    logging.info("run with no arguments")
2149

    
2150
  try:
2151
    result = func(options, args)
2152
  except (errors.GenericError, luxi.ProtocolError,
2153
          JobSubmittedException), err:
2154
    result, err_msg = FormatError(err)
2155
    logging.exception("Error during command processing")
2156
    ToStderr(err_msg)
2157
  except KeyboardInterrupt:
2158
    result = constants.EXIT_FAILURE
2159
    ToStderr("Aborted. Note that if the operation created any jobs, they"
2160
             " might have been submitted and"
2161
             " will continue to run in the background.")
2162
  except IOError, err:
2163
    if err.errno == errno.EPIPE:
2164
      # our terminal went away, we'll exit
2165
      sys.exit(constants.EXIT_FAILURE)
2166
    else:
2167
      raise
2168

    
2169
  return result
2170

    
2171

    
2172
def ParseNicOption(optvalue):
2173
  """Parses the value of the --net option(s).
2174

2175
  """
2176
  try:
2177
    nic_max = max(int(nidx[0]) + 1 for nidx in optvalue)
2178
  except (TypeError, ValueError), err:
2179
    raise errors.OpPrereqError("Invalid NIC index passed: %s" % str(err))
2180

    
2181
  nics = [{}] * nic_max
2182
  for nidx, ndict in optvalue:
2183
    nidx = int(nidx)
2184

    
2185
    if not isinstance(ndict, dict):
2186
      raise errors.OpPrereqError("Invalid nic/%d value: expected dict,"
2187
                                 " got %s" % (nidx, ndict))
2188

    
2189
    utils.ForceDictType(ndict, constants.INIC_PARAMS_TYPES)
2190

    
2191
    nics[nidx] = ndict
2192

    
2193
  return nics
2194

    
2195

    
2196
def GenericInstanceCreate(mode, opts, args):
2197
  """Add an instance to the cluster via either creation or import.
2198

2199
  @param mode: constants.INSTANCE_CREATE or constants.INSTANCE_IMPORT
2200
  @param opts: the command line options selected by the user
2201
  @type args: list
2202
  @param args: should contain only one element, the new instance name
2203
  @rtype: int
2204
  @return: the desired exit code
2205

2206
  """
2207
  instance = args[0]
2208

    
2209
  (pnode, snode) = SplitNodeOption(opts.node)
2210

    
2211
  hypervisor = None
2212
  hvparams = {}
2213
  if opts.hypervisor:
2214
    hypervisor, hvparams = opts.hypervisor
2215

    
2216
  if opts.nics:
2217
    nics = ParseNicOption(opts.nics)
2218
  elif opts.no_nics:
2219
    # no nics
2220
    nics = []
2221
  elif mode == constants.INSTANCE_CREATE:
2222
    # default of one nic, all auto
2223
    nics = [{}]
2224
  else:
2225
    # mode == import
2226
    nics = []
2227

    
2228
  if opts.disk_template == constants.DT_DISKLESS:
2229
    if opts.disks or opts.sd_size is not None:
2230
      raise errors.OpPrereqError("Diskless instance but disk"
2231
                                 " information passed")
2232
    disks = []
2233
  else:
2234
    if (not opts.disks and not opts.sd_size
2235
        and mode == constants.INSTANCE_CREATE):
2236
      raise errors.OpPrereqError("No disk information specified")
2237
    if opts.disks and opts.sd_size is not None:
2238
      raise errors.OpPrereqError("Please use either the '--disk' or"
2239
                                 " '-s' option")
2240
    if opts.sd_size is not None:
2241
      opts.disks = [(0, {constants.IDISK_SIZE: opts.sd_size})]
2242

    
2243
    if opts.disks:
2244
      try:
2245
        disk_max = max(int(didx[0]) + 1 for didx in opts.disks)
2246
      except ValueError, err:
2247
        raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err))
2248
      disks = [{}] * disk_max
2249
    else:
2250
      disks = []
2251
    for didx, ddict in opts.disks:
2252
      didx = int(didx)
2253
      if not isinstance(ddict, dict):
2254
        msg = "Invalid disk/%d value: expected dict, got %s" % (didx, ddict)
2255
        raise errors.OpPrereqError(msg)
2256
      elif constants.IDISK_SIZE in ddict:
2257
        if constants.IDISK_ADOPT in ddict:
2258
          raise errors.OpPrereqError("Only one of 'size' and 'adopt' allowed"
2259
                                     " (disk %d)" % didx)
2260
        try:
2261
          ddict[constants.IDISK_SIZE] = \
2262
            utils.ParseUnit(ddict[constants.IDISK_SIZE])
2263
        except ValueError, err:
2264
          raise errors.OpPrereqError("Invalid disk size for disk %d: %s" %
2265
                                     (didx, err))
2266
      elif constants.IDISK_ADOPT in ddict:
2267
        if mode == constants.INSTANCE_IMPORT:
2268
          raise errors.OpPrereqError("Disk adoption not allowed for instance"
2269
                                     " import")
2270
        ddict[constants.IDISK_SIZE] = 0
2271
      else:
2272
        raise errors.OpPrereqError("Missing size or adoption source for"
2273
                                   " disk %d" % didx)
2274
      disks[didx] = ddict
2275

    
2276
  if opts.tags is not None:
2277
    tags = opts.tags.split(",")
2278
  else:
2279
    tags = []
2280

    
2281
  utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_COMPAT)
2282
  utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
2283

    
2284
  if mode == constants.INSTANCE_CREATE:
2285
    start = opts.start
2286
    os_type = opts.os
2287
    force_variant = opts.force_variant
2288
    src_node = None
2289
    src_path = None
2290
    no_install = opts.no_install
2291
    identify_defaults = False
2292
  elif mode == constants.INSTANCE_IMPORT:
2293
    start = False
2294
    os_type = None
2295
    force_variant = False
2296
    src_node = opts.src_node
2297
    src_path = opts.src_dir
2298
    no_install = None
2299
    identify_defaults = opts.identify_defaults
2300
  else:
2301
    raise errors.ProgrammerError("Invalid creation mode %s" % mode)
2302

    
2303
  op = opcodes.OpInstanceCreate(instance_name=instance,
2304
                                disks=disks,
2305
                                disk_template=opts.disk_template,
2306
                                nics=nics,
2307
                                pnode=pnode, snode=snode,
2308
                                ip_check=opts.ip_check,
2309
                                name_check=opts.name_check,
2310
                                wait_for_sync=opts.wait_for_sync,
2311
                                file_storage_dir=opts.file_storage_dir,
2312
                                file_driver=opts.file_driver,
2313
                                iallocator=opts.iallocator,
2314
                                hypervisor=hypervisor,
2315
                                hvparams=hvparams,
2316
                                beparams=opts.beparams,
2317
                                osparams=opts.osparams,
2318
                                mode=mode,
2319
                                start=start,
2320
                                os_type=os_type,
2321
                                force_variant=force_variant,
2322
                                src_node=src_node,
2323
                                src_path=src_path,
2324
                                tags=tags,
2325
                                no_install=no_install,
2326
                                identify_defaults=identify_defaults,
2327
                                ignore_ipolicy=opts.ignore_ipolicy)
2328

    
2329
  SubmitOrSend(op, opts)
2330
  return 0
2331

    
2332

    
2333
class _RunWhileClusterStoppedHelper:
2334
  """Helper class for L{RunWhileClusterStopped} to simplify state management
2335

2336
  """
2337
  def __init__(self, feedback_fn, cluster_name, master_node, online_nodes):
2338
    """Initializes this class.
2339

2340
    @type feedback_fn: callable
2341
    @param feedback_fn: Feedback function
2342
    @type cluster_name: string
2343
    @param cluster_name: Cluster name
2344
    @type master_node: string
2345
    @param master_node Master node name
2346
    @type online_nodes: list
2347
    @param online_nodes: List of names of online nodes
2348

2349
    """
2350
    self.feedback_fn = feedback_fn
2351
    self.cluster_name = cluster_name
2352
    self.master_node = master_node
2353
    self.online_nodes = online_nodes
2354

    
2355
    self.ssh = ssh.SshRunner(self.cluster_name)
2356

    
2357
    self.nonmaster_nodes = [name for name in online_nodes
2358
                            if name != master_node]
2359

    
2360
    assert self.master_node not in self.nonmaster_nodes
2361

    
2362
  def _RunCmd(self, node_name, cmd):
2363
    """Runs a command on the local or a remote machine.
2364

2365
    @type node_name: string
2366
    @param node_name: Machine name
2367
    @type cmd: list
2368
    @param cmd: Command
2369

2370
    """
2371
    if node_name is None or node_name == self.master_node:
2372
      # No need to use SSH
2373
      result = utils.RunCmd(cmd)
2374
    else:
2375
      result = self.ssh.Run(node_name, "root", utils.ShellQuoteArgs(cmd))
2376

    
2377
    if result.failed:
2378
      errmsg = ["Failed to run command %s" % result.cmd]
2379
      if node_name:
2380
        errmsg.append("on node %s" % node_name)
2381
      errmsg.append(": exitcode %s and error %s" %
2382
                    (result.exit_code, result.output))
2383
      raise errors.OpExecError(" ".join(errmsg))
2384

    
2385
  def Call(self, fn, *args):
2386
    """Call function while all daemons are stopped.
2387

2388
    @type fn: callable
2389
    @param fn: Function to be called
2390

2391
    """
2392
    # Pause watcher by acquiring an exclusive lock on watcher state file
2393
    self.feedback_fn("Blocking watcher")
2394
    watcher_block = utils.FileLock.Open(constants.WATCHER_LOCK_FILE)
2395
    try:
2396
      # TODO: Currently, this just blocks. There's no timeout.
2397
      # TODO: Should it be a shared lock?
2398
      watcher_block.Exclusive(blocking=True)
2399

    
2400
      # Stop master daemons, so that no new jobs can come in and all running
2401
      # ones are finished
2402
      self.feedback_fn("Stopping master daemons")
2403
      self._RunCmd(None, [constants.DAEMON_UTIL, "stop-master"])
2404
      try:
2405
        # Stop daemons on all nodes
2406
        for node_name in self.online_nodes:
2407
          self.feedback_fn("Stopping daemons on %s" % node_name)
2408
          self._RunCmd(node_name, [constants.DAEMON_UTIL, "stop-all"])
2409

    
2410
        # All daemons are shut down now
2411
        try:
2412
          return fn(self, *args)
2413
        except Exception, err:
2414
          _, errmsg = FormatError(err)
2415
          logging.exception("Caught exception")
2416
          self.feedback_fn(errmsg)
2417
          raise
2418
      finally:
2419
        # Start cluster again, master node last
2420
        for node_name in self.nonmaster_nodes + [self.master_node]:
2421
          self.feedback_fn("Starting daemons on %s" % node_name)
2422
          self._RunCmd(node_name, [constants.DAEMON_UTIL, "start-all"])
2423
    finally:
2424
      # Resume watcher
2425
      watcher_block.Close()
2426

    
2427

    
2428
def RunWhileClusterStopped(feedback_fn, fn, *args):
2429
  """Calls a function while all cluster daemons are stopped.
2430

2431
  @type feedback_fn: callable
2432
  @param feedback_fn: Feedback function
2433
  @type fn: callable
2434
  @param fn: Function to be called when daemons are stopped
2435

2436
  """
2437
  feedback_fn("Gathering cluster information")
2438

    
2439
  # This ensures we're running on the master daemon
2440
  cl = GetClient()
2441

    
2442
  (cluster_name, master_node) = \
2443
    cl.QueryConfigValues(["cluster_name", "master_node"])
2444

    
2445
  online_nodes = GetOnlineNodes([], cl=cl)
2446

    
2447
  # Don't keep a reference to the client. The master daemon will go away.
2448
  del cl
2449

    
2450
  assert master_node in online_nodes
2451

    
2452
  return _RunWhileClusterStoppedHelper(feedback_fn, cluster_name, master_node,
2453
                                       online_nodes).Call(fn, *args)
2454

    
2455

    
2456
def GenerateTable(headers, fields, separator, data,
2457
                  numfields=None, unitfields=None,
2458
                  units=None):
2459
  """Prints a table with headers and different fields.
2460

2461
  @type headers: dict
2462
  @param headers: dictionary mapping field names to headers for
2463
      the table
2464
  @type fields: list
2465
  @param fields: the field names corresponding to each row in
2466
      the data field
2467
  @param separator: the separator to be used; if this is None,
2468
      the default 'smart' algorithm is used which computes optimal
2469
      field width, otherwise just the separator is used between
2470
      each field
2471
  @type data: list
2472
  @param data: a list of lists, each sublist being one row to be output
2473
  @type numfields: list
2474
  @param numfields: a list with the fields that hold numeric
2475
      values and thus should be right-aligned
2476
  @type unitfields: list
2477
  @param unitfields: a list with the fields that hold numeric
2478
      values that should be formatted with the units field
2479
  @type units: string or None
2480
  @param units: the units we should use for formatting, or None for
2481
      automatic choice (human-readable for non-separator usage, otherwise
2482
      megabytes); this is a one-letter string
2483

2484
  """
2485
  if units is None:
2486
    if separator:
2487
      units = "m"
2488
    else:
2489
      units = "h"
2490

    
2491
  if numfields is None:
2492
    numfields = []
2493
  if unitfields is None:
2494
    unitfields = []
2495

    
2496
  numfields = utils.FieldSet(*numfields)   # pylint: disable=W0142
2497
  unitfields = utils.FieldSet(*unitfields) # pylint: disable=W0142
2498

    
2499
  format_fields = []
2500
  for field in fields:
2501
    if headers and field not in headers:
2502
      # TODO: handle better unknown fields (either revert to old
2503
      # style of raising exception, or deal more intelligently with
2504
      # variable fields)
2505
      headers[field] = field
2506
    if separator is not None:
2507
      format_fields.append("%s")
2508
    elif numfields.Matches(field):
2509
      format_fields.append("%*s")
2510
    else:
2511
      format_fields.append("%-*s")
2512

    
2513
  if separator is None:
2514
    mlens = [0 for name in fields]
2515
    format_str = " ".join(format_fields)
2516
  else:
2517
    format_str = separator.replace("%", "%%").join(format_fields)
2518

    
2519
  for row in data:
2520
    if row is None:
2521
      continue
2522
    for idx, val in enumerate(row):
2523
      if unitfields.Matches(fields[idx]):
2524
        try:
2525
          val = int(val)
2526
        except (TypeError, ValueError):
2527
          pass
2528
        else:
2529
          val = row[idx] = utils.FormatUnit(val, units)
2530
      val = row[idx] = str(val)
2531
      if separator is None:
2532
        mlens[idx] = max(mlens[idx], len(val))
2533

    
2534
  result = []
2535
  if headers:
2536
    args = []
2537
    for idx, name in enumerate(fields):
2538
      hdr = headers[name]
2539
      if separator is None:
2540
        mlens[idx] = max(mlens[idx], len(hdr))
2541
        args.append(mlens[idx])
2542
      args.append(hdr)
2543
    result.append(format_str % tuple(args))
2544

    
2545
  if separator is None:
2546
    assert len(mlens) == len(fields)
2547

    
2548
    if fields and not numfields.Matches(fields[-1]):
2549
      mlens[-1] = 0
2550

    
2551
  for line in data:
2552
    args = []
2553
    if line is None:
2554
      line = ["-" for _ in fields]
2555
    for idx in range(len(fields)):
2556
      if separator is None:
2557
        args.append(mlens[idx])
2558
      args.append(line[idx])
2559
    result.append(format_str % tuple(args))
2560

    
2561
  return result
2562

    
2563

    
2564
def _FormatBool(value):
2565
  """Formats a boolean value as a string.
2566

2567
  """
2568
  if value:
2569
    return "Y"
2570
  return "N"
2571

    
2572

    
2573
#: Default formatting for query results; (callback, align right)
2574
_DEFAULT_FORMAT_QUERY = {
2575
  constants.QFT_TEXT: (str, False),
2576
  constants.QFT_BOOL: (_FormatBool, False),
2577
  constants.QFT_NUMBER: (str, True),
2578
  constants.QFT_TIMESTAMP: (utils.FormatTime, False),
2579
  constants.QFT_OTHER: (str, False),
2580
  constants.QFT_UNKNOWN: (str, False),
2581
  }
2582

    
2583

    
2584
def _GetColumnFormatter(fdef, override, unit):
2585
  """Returns formatting function for a field.
2586

2587
  @type fdef: L{objects.QueryFieldDefinition}
2588
  @type override: dict
2589
  @param override: Dictionary for overriding field formatting functions,
2590
    indexed by field name, contents like L{_DEFAULT_FORMAT_QUERY}
2591
  @type unit: string
2592
  @param unit: Unit used for formatting fields of type L{constants.QFT_UNIT}
2593
  @rtype: tuple; (callable, bool)
2594
  @return: Returns the function to format a value (takes one parameter) and a
2595
    boolean for aligning the value on the right-hand side
2596

2597
  """
2598
  fmt = override.get(fdef.name, None)
2599
  if fmt is not None:
2600
    return fmt
2601

    
2602
  assert constants.QFT_UNIT not in _DEFAULT_FORMAT_QUERY
2603

    
2604
  if fdef.kind == constants.QFT_UNIT:
2605
    # Can't keep this information in the static dictionary
2606
    return (lambda value: utils.FormatUnit(value, unit), True)
2607

    
2608
  fmt = _DEFAULT_FORMAT_QUERY.get(fdef.kind, None)
2609
  if fmt is not None:
2610
    return fmt
2611

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

    
2614

    
2615
class _QueryColumnFormatter:
2616
  """Callable class for formatting fields of a query.
2617

2618
  """
2619
  def __init__(self, fn, status_fn, verbose):
2620
    """Initializes this class.
2621

2622
    @type fn: callable
2623
    @param fn: Formatting function
2624
    @type status_fn: callable
2625
    @param status_fn: Function to report fields' status
2626
    @type verbose: boolean
2627
    @param verbose: whether to use verbose field descriptions or not
2628

2629
    """
2630
    self._fn = fn
2631
    self._status_fn = status_fn
2632
    self._verbose = verbose
2633

    
2634
  def __call__(self, data):
2635
    """Returns a field's string representation.
2636

2637
    """
2638
    (status, value) = data
2639

    
2640
    # Report status
2641
    self._status_fn(status)
2642

    
2643
    if status == constants.RS_NORMAL:
2644
      return self._fn(value)
2645

    
2646
    assert value is None, \
2647
           "Found value %r for abnormal status %s" % (value, status)
2648

    
2649
    return FormatResultError(status, self._verbose)
2650

    
2651

    
2652
def FormatResultError(status, verbose):
2653
  """Formats result status other than L{constants.RS_NORMAL}.
2654

2655
  @param status: The result status
2656
  @type verbose: boolean
2657
  @param verbose: Whether to return the verbose text
2658
  @return: Text of result status
2659

2660
  """
2661
  assert status != constants.RS_NORMAL, \
2662
         "FormatResultError called with status equal to constants.RS_NORMAL"
2663
  try:
2664
    (verbose_text, normal_text) = constants.RSS_DESCRIPTION[status]
2665
  except KeyError:
2666
    raise NotImplementedError("Unknown status %s" % status)
2667
  else:
2668
    if verbose:
2669
      return verbose_text
2670
    return normal_text
2671

    
2672

    
2673
def FormatQueryResult(result, unit=None, format_override=None, separator=None,
2674
                      header=False, verbose=False):
2675
  """Formats data in L{objects.QueryResponse}.
2676

2677
  @type result: L{objects.QueryResponse}
2678
  @param result: result of query operation
2679
  @type unit: string
2680
  @param unit: Unit used for formatting fields of type L{constants.QFT_UNIT},
2681
    see L{utils.text.FormatUnit}
2682
  @type format_override: dict
2683
  @param format_override: Dictionary for overriding field formatting functions,
2684
    indexed by field name, contents like L{_DEFAULT_FORMAT_QUERY}
2685
  @type separator: string or None
2686
  @param separator: String used to separate fields
2687
  @type header: bool
2688
  @param header: Whether to output header row
2689
  @type verbose: boolean
2690
  @param verbose: whether to use verbose field descriptions or not
2691

2692
  """
2693
  if unit is None:
2694
    if separator:
2695
      unit = "m"
2696
    else:
2697
      unit = "h"
2698

    
2699
  if format_override is None:
2700
    format_override = {}
2701

    
2702
  stats = dict.fromkeys(constants.RS_ALL, 0)
2703

    
2704
  def _RecordStatus(status):
2705
    if status in stats:
2706
      stats[status] += 1
2707

    
2708
  columns = []
2709
  for fdef in result.fields:
2710
    assert fdef.title and fdef.name
2711
    (fn, align_right) = _GetColumnFormatter(fdef, format_override, unit)
2712
    columns.append(TableColumn(fdef.title,
2713
                               _QueryColumnFormatter(fn, _RecordStatus,
2714
                                                     verbose),
2715
                               align_right))
2716

    
2717
  table = FormatTable(result.data, columns, header, separator)
2718

    
2719
  # Collect statistics
2720
  assert len(stats) == len(constants.RS_ALL)
2721
  assert compat.all(count >= 0 for count in stats.values())
2722

    
2723
  # Determine overall status. If there was no data, unknown fields must be
2724
  # detected via the field definitions.
2725
  if (stats[constants.RS_UNKNOWN] or
2726
      (not result.data and _GetUnknownFields(result.fields))):
2727
    status = QR_UNKNOWN
2728
  elif compat.any(count > 0 for key, count in stats.items()
2729
                  if key != constants.RS_NORMAL):
2730
    status = QR_INCOMPLETE
2731
  else:
2732
    status = QR_NORMAL
2733

    
2734
  return (status, table)
2735

    
2736

    
2737
def _GetUnknownFields(fdefs):
2738
  """Returns list of unknown fields included in C{fdefs}.
2739

2740
  @type fdefs: list of L{objects.QueryFieldDefinition}
2741

2742
  """
2743
  return [fdef for fdef in fdefs
2744
          if fdef.kind == constants.QFT_UNKNOWN]
2745

    
2746

    
2747
def _WarnUnknownFields(fdefs):
2748
  """Prints a warning to stderr if a query included unknown fields.
2749

2750
  @type fdefs: list of L{objects.QueryFieldDefinition}
2751

2752
  """
2753
  unknown = _GetUnknownFields(fdefs)
2754
  if unknown:
2755
    ToStderr("Warning: Queried for unknown fields %s",
2756
             utils.CommaJoin(fdef.name for fdef in unknown))
2757
    return True
2758

    
2759
  return False
2760

    
2761

    
2762
def GenericList(resource, fields, names, unit, separator, header, cl=None,
2763
                format_override=None, verbose=False, force_filter=False):
2764
  """Generic implementation for listing all items of a resource.
2765

2766
  @param resource: One of L{constants.QR_VIA_LUXI}
2767
  @type fields: list of strings
2768
  @param fields: List of fields to query for
2769
  @type names: list of strings
2770
  @param names: Names of items to query for
2771
  @type unit: string or None
2772
  @param unit: Unit used for formatting fields of type L{constants.QFT_UNIT} or
2773
    None for automatic choice (human-readable for non-separator usage,
2774
    otherwise megabytes); this is a one-letter string
2775
  @type separator: string or None
2776
  @param separator: String used to separate fields
2777
  @type header: bool
2778
  @param header: Whether to show header row
2779
  @type force_filter: bool
2780
  @param force_filter: Whether to always treat names as filter
2781
  @type format_override: dict
2782
  @param format_override: Dictionary for overriding field formatting functions,
2783
    indexed by field name, contents like L{_DEFAULT_FORMAT_QUERY}
2784
  @type verbose: boolean
2785
  @param verbose: whether to use verbose field descriptions or not
2786

2787
  """
2788
  if not names:
2789
    names = None
2790

    
2791
  qfilter = qlang.MakeFilter(names, force_filter)
2792

    
2793
  if cl is None:
2794
    cl = GetClient()
2795

    
2796
  response = cl.Query(resource, fields, qfilter)
2797

    
2798
  found_unknown = _WarnUnknownFields(response.fields)
2799

    
2800
  (status, data) = FormatQueryResult(response, unit=unit, separator=separator,
2801
                                     header=header,
2802
                                     format_override=format_override,
2803
                                     verbose=verbose)
2804

    
2805
  for line in data:
2806
    ToStdout(line)
2807

    
2808
  assert ((found_unknown and status == QR_UNKNOWN) or
2809
          (not found_unknown and status != QR_UNKNOWN))
2810

    
2811
  if status == QR_UNKNOWN:
2812
    return constants.EXIT_UNKNOWN_FIELD
2813

    
2814
  # TODO: Should the list command fail if not all data could be collected?
2815
  return constants.EXIT_SUCCESS
2816

    
2817

    
2818
def GenericListFields(resource, fields, separator, header, cl=None):
2819
  """Generic implementation for listing fields for a resource.
2820

2821
  @param resource: One of L{constants.QR_VIA_LUXI}
2822
  @type fields: list of strings
2823
  @param fields: List of fields to query for
2824
  @type separator: string or None
2825
  @param separator: String used to separate fields
2826
  @type header: bool
2827
  @param header: Whether to show header row
2828

2829
  """
2830
  if cl is None:
2831
    cl = GetClient()
2832

    
2833
  if not fields:
2834
    fields = None
2835

    
2836
  response = cl.QueryFields(resource, fields)
2837

    
2838
  found_unknown = _WarnUnknownFields(response.fields)
2839

    
2840
  columns = [
2841
    TableColumn("Name", str, False),
2842
    TableColumn("Title", str, False),
2843
    TableColumn("Description", str, False),
2844
    ]
2845

    
2846
  rows = [[fdef.name, fdef.title, fdef.doc] for fdef in response.fields]
2847

    
2848
  for line in FormatTable(rows, columns, header, separator):
2849
    ToStdout(line)
2850

    
2851
  if found_unknown:
2852
    return constants.EXIT_UNKNOWN_FIELD
2853

    
2854
  return constants.EXIT_SUCCESS
2855

    
2856

    
2857
class TableColumn:
2858
  """Describes a column for L{FormatTable}.
2859

2860
  """
2861
  def __init__(self, title, fn, align_right):
2862
    """Initializes this class.
2863

2864
    @type title: string
2865
    @param title: Column title
2866
    @type fn: callable
2867
    @param fn: Formatting function
2868
    @type align_right: bool
2869
    @param align_right: Whether to align values on the right-hand side
2870

2871
    """
2872
    self.title = title
2873
    self.format = fn
2874
    self.align_right = align_right
2875

    
2876

    
2877
def _GetColFormatString(width, align_right):
2878
  """Returns the format string for a field.
2879

2880
  """
2881
  if align_right:
2882
    sign = ""
2883
  else:
2884
    sign = "-"
2885

    
2886
  return "%%%s%ss" % (sign, width)
2887

    
2888

    
2889
def FormatTable(rows, columns, header, separator):
2890
  """Formats data as a table.
2891

2892
  @type rows: list of lists
2893
  @param rows: Row data, one list per row
2894
  @type columns: list of L{TableColumn}
2895
  @param columns: Column descriptions
2896
  @type header: bool
2897
  @param header: Whether to show header row
2898
  @type separator: string or None
2899
  @param separator: String used to separate columns
2900

2901
  """
2902
  if header:
2903
    data = [[col.title for col in columns]]
2904
    colwidth = [len(col.title) for col in columns]
2905
  else:
2906
    data = []
2907
    colwidth = [0 for _ in columns]
2908

    
2909
  # Format row data
2910
  for row in rows:
2911
    assert len(row) == len(columns)
2912

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

    
2915
    if separator is None:
2916
      # Update column widths
2917
      for idx, (oldwidth, value) in enumerate(zip(colwidth, formatted)):
2918
        # Modifying a list's items while iterating is fine
2919
        colwidth[idx] = max(oldwidth, len(value))
2920

    
2921
    data.append(formatted)
2922

    
2923
  if separator is not None:
2924
    # Return early if a separator is used
2925
    return [separator.join(row) for row in data]
2926

    
2927
  if columns and not columns[-1].align_right:
2928
    # Avoid unnecessary spaces at end of line
2929
    colwidth[-1] = 0
2930

    
2931
  # Build format string
2932
  fmt = " ".join([_GetColFormatString(width, col.align_right)
2933
                  for col, width in zip(columns, colwidth)])
2934

    
2935
  return [fmt % tuple(row) for row in data]
2936

    
2937

    
2938
def FormatTimestamp(ts):
2939
  """Formats a given timestamp.
2940

2941
  @type ts: timestamp
2942
  @param ts: a timeval-type timestamp, a tuple of seconds and microseconds
2943

2944
  @rtype: string
2945
  @return: a string with the formatted timestamp
2946

2947
  """
2948
  if not isinstance(ts, (tuple, list)) or len(ts) != 2:
2949
    return "?"
2950
  sec, usec = ts
2951
  return time.strftime("%F %T", time.localtime(sec)) + ".%06d" % usec
2952

    
2953

    
2954
def ParseTimespec(value):
2955
  """Parse a time specification.
2956

2957
  The following suffixed will be recognized:
2958

2959
    - s: seconds
2960
    - m: minutes
2961
    - h: hours
2962
    - d: day
2963
    - w: weeks
2964

2965
  Without any suffix, the value will be taken to be in seconds.
2966

2967
  """
2968
  value = str(value)
2969
  if not value:
2970
    raise errors.OpPrereqError("Empty time specification passed")
2971
  suffix_map = {
2972
    "s": 1,
2973
    "m": 60,
2974
    "h": 3600,
2975
    "d": 86400,
2976
    "w": 604800,
2977
    }
2978
  if value[-1] not in suffix_map:
2979
    try:
2980
      value = int(value)
2981
    except (TypeError, ValueError):
2982
      raise errors.OpPrereqError("Invalid time specification '%s'" % value)
2983
  else:
2984
    multiplier = suffix_map[value[-1]]
2985
    value = value[:-1]
2986
    if not value: # no data left after stripping the suffix
2987
      raise errors.OpPrereqError("Invalid time specification (only"
2988
                                 " suffix passed)")
2989
    try:
2990
      value = int(value) * multiplier
2991
    except (TypeError, ValueError):
2992
      raise errors.OpPrereqError("Invalid time specification '%s'" % value)
2993
  return value
2994

    
2995

    
2996
def GetOnlineNodes(nodes, cl=None, nowarn=False, secondary_ips=False,
2997
                   filter_master=False, nodegroup=None):
2998
  """Returns the names of online nodes.
2999

3000
  This function will also log a warning on stderr with the names of
3001
  the online nodes.
3002

3003
  @param nodes: if not empty, use only this subset of nodes (minus the
3004
      offline ones)
3005
  @param cl: if not None, luxi client to use
3006
  @type nowarn: boolean
3007
  @param nowarn: by default, this function will output a note with the
3008
      offline nodes that are skipped; if this parameter is True the
3009
      note is not displayed
3010
  @type secondary_ips: boolean
3011
  @param secondary_ips: if True, return the secondary IPs instead of the
3012
      names, useful for doing network traffic over the replication interface
3013
      (if any)
3014
  @type filter_master: boolean
3015
  @param filter_master: if True, do not return the master node in the list
3016
      (useful in coordination with secondary_ips where we cannot check our
3017
      node name against the list)
3018
  @type nodegroup: string
3019
  @param nodegroup: If set, only return nodes in this node group
3020

3021
  """
3022
  if cl is None:
3023
    cl = GetClient()
3024

    
3025
  qfilter = []
3026

    
3027
  if nodes:
3028
    qfilter.append(qlang.MakeSimpleFilter("name", nodes))
3029

    
3030
  if nodegroup is not None:
3031
    qfilter.append([qlang.OP_OR, [qlang.OP_EQUAL, "group", nodegroup],
3032
                                 [qlang.OP_EQUAL, "group.uuid", nodegroup]])
3033

    
3034
  if filter_master:
3035
    qfilter.append([qlang.OP_NOT, [qlang.OP_TRUE, "master"]])
3036

    
3037
  if qfilter:
3038
    if len(qfilter) > 1:
3039
      final_filter = [qlang.OP_AND] + qfilter
3040
    else:
3041
      assert len(qfilter) == 1
3042
      final_filter = qfilter[0]
3043
  else:
3044
    final_filter = None
3045

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

    
3048
  def _IsOffline(row):
3049
    (_, (_, offline), _) = row
3050
    return offline
3051

    
3052
  def _GetName(row):
3053
    ((_, name), _, _) = row
3054
    return name
3055

    
3056
  def _GetSip(row):
3057
    (_, _, (_, sip)) = row
3058
    return sip
3059

    
3060
  (offline, online) = compat.partition(result.data, _IsOffline)
3061

    
3062
  if offline and not nowarn:
3063
    ToStderr("Note: skipping offline node(s): %s" %
3064
             utils.CommaJoin(map(_GetName, offline)))
3065

    
3066
  if secondary_ips:
3067
    fn = _GetSip
3068
  else:
3069
    fn = _GetName
3070

    
3071
  return map(fn, online)
3072

    
3073

    
3074
def _ToStream(stream, txt, *args):
3075
  """Write a message to a stream, bypassing the logging system
3076

3077
  @type stream: file object
3078
  @param stream: the file to which we should write
3079
  @type txt: str
3080
  @param txt: the message
3081

3082
  """
3083
  try:
3084
    if args:
3085
      args = tuple(args)
3086
      stream.write(txt % args)
3087
    else:
3088
      stream.write(txt)
3089
    stream.write("\n")
3090
    stream.flush()
3091
  except IOError, err:
3092
    if err.errno == errno.EPIPE:
3093
      # our terminal went away, we'll exit
3094
      sys.exit(constants.EXIT_FAILURE)
3095
    else:
3096
      raise
3097

    
3098

    
3099
def ToStdout(txt, *args):
3100
  """Write a message to stdout only, bypassing the logging system
3101

3102
  This is just a wrapper over _ToStream.
3103

3104
  @type txt: str
3105
  @param txt: the message
3106

3107
  """
3108
  _ToStream(sys.stdout, txt, *args)
3109

    
3110

    
3111
def ToStderr(txt, *args):
3112
  """Write a message to stderr only, bypassing the logging system
3113

3114
  This is just a wrapper over _ToStream.
3115

3116
  @type txt: str
3117
  @param txt: the message
3118

3119
  """
3120
  _ToStream(sys.stderr, txt, *args)
3121

    
3122

    
3123
class JobExecutor(object):
3124
  """Class which manages the submission and execution of multiple jobs.
3125

3126
  Note that instances of this class should not be reused between
3127
  GetResults() calls.
3128

3129
  """
3130
  def __init__(self, cl=None, verbose=True, opts=None, feedback_fn=None):
3131
    self.queue = []
3132
    if cl is None:
3133
      cl = GetClient()
3134
    self.cl = cl
3135
    self.verbose = verbose
3136
    self.jobs = []
3137
    self.opts = opts
3138
    self.feedback_fn = feedback_fn
3139
    self._counter = itertools.count()
3140

    
3141
  @staticmethod
3142
  def _IfName(name, fmt):
3143
    """Helper function for formatting name.
3144

3145
    """
3146
    if name:
3147
      return fmt % name
3148

    
3149
    return ""
3150

    
3151
  def QueueJob(self, name, *ops):
3152
    """Record a job for later submit.
3153

3154
    @type name: string
3155
    @param name: a description of the job, will be used in WaitJobSet
3156

3157
    """
3158
    SetGenericOpcodeOpts(ops, self.opts)
3159
    self.queue.append((self._counter.next(), name, ops))
3160

    
3161
  def AddJobId(self, name, status, job_id):
3162
    """Adds a job ID to the internal queue.
3163

3164
    """
3165
    self.jobs.append((self._counter.next(), status, job_id, name))
3166

    
3167
  def SubmitPending(self, each=False):
3168
    """Submit all pending jobs.
3169

3170
    """
3171
    if each:
3172
      results = []
3173
      for (_, _, ops) in self.queue:
3174
        # SubmitJob will remove the success status, but raise an exception if
3175
        # the submission fails, so we'll notice that anyway.
3176
        results.append([True, self.cl.SubmitJob(ops)[0]])
3177
    else:
3178
      results = self.cl.SubmitManyJobs([ops for (_, _, ops) in self.queue])
3179
    for ((status, data), (idx, name, _)) in zip(results, self.queue):
3180
      self.jobs.append((idx, status, data, name))
3181

    
3182
  def _ChooseJob(self):
3183
    """Choose a non-waiting/queued job to poll next.
3184

3185
    """
3186
    assert self.jobs, "_ChooseJob called with empty job list"
3187

    
3188
    result = self.cl.QueryJobs([i[2] for i in self.jobs[:_CHOOSE_BATCH]],
3189
                               ["status"])
3190
    assert result
3191

    
3192
    for job_data, status in zip(self.jobs, result):
3193
      if (isinstance(status, list) and status and
3194
          status[0] in (constants.JOB_STATUS_QUEUED,
3195
                        constants.JOB_STATUS_WAITING,
3196
                        constants.JOB_STATUS_CANCELING)):
3197
        # job is still present and waiting
3198
        continue
3199
      # good candidate found (either running job or lost job)
3200
      self.jobs.remove(job_data)
3201
      return job_data
3202

    
3203
    # no job found
3204
    return self.jobs.pop(0)
3205

    
3206
  def GetResults(self):
3207
    """Wait for and return the results of all jobs.
3208

3209
    @rtype: list
3210
    @return: list of tuples (success, job results), in the same order
3211
        as the submitted jobs; if a job has failed, instead of the result
3212
        there will be the error message
3213

3214
    """
3215
    if not self.jobs:
3216
      self.SubmitPending()
3217
    results = []
3218
    if self.verbose:
3219
      ok_jobs = [row[2] for row in self.jobs if row[1]]
3220
      if ok_jobs:
3221
        ToStdout("Submitted jobs %s", utils.CommaJoin(ok_jobs))
3222

    
3223
    # first, remove any non-submitted jobs
3224
    self.jobs, failures = compat.partition(self.jobs, lambda x: x[1])
3225
    for idx, _, jid, name in failures:
3226
      ToStderr("Failed to submit job%s: %s", self._IfName(name, " for %s"), jid)
3227
      results.append((idx, False, jid))
3228

    
3229
    while self.jobs:
3230
      (idx, _, jid, name) = self._ChooseJob()
3231
      ToStdout("Waiting for job %s%s ...", jid, self._IfName(name, " for %s"))
3232
      try:
3233
        job_result = PollJob(jid, cl=self.cl, feedback_fn=self.feedback_fn)
3234
        success = True
3235
      except errors.JobLost, err:
3236
        _, job_result = FormatError(err)
3237
        ToStderr("Job %s%s has been archived, cannot check its result",
3238
                 jid, self._IfName(name, " for %s"))
3239
        success = False
3240
      except (errors.GenericError, luxi.ProtocolError), err:
3241
        _, job_result = FormatError(err)
3242
        success = False
3243
        # the error message will always be shown, verbose or not
3244
        ToStderr("Job %s%s has failed: %s",
3245
                 jid, self._IfName(name, " for %s"), job_result)
3246

    
3247
      results.append((idx, success, job_result))
3248

    
3249
    # sort based on the index, then drop it
3250
    results.sort()
3251
    results = [i[1:] for i in results]
3252

    
3253
    return results
3254

    
3255
  def WaitOrShow(self, wait):
3256
    """Wait for job results or only print the job IDs.
3257

3258
    @type wait: boolean
3259
    @param wait: whether to wait or not
3260

3261
    """
3262
    if wait:
3263
      return self.GetResults()
3264
    else:
3265
      if not self.jobs:
3266
        self.SubmitPending()
3267
      for _, status, result, name in self.jobs:
3268
        if status:
3269
          ToStdout("%s: %s", result, name)
3270
        else:
3271
          ToStderr("Failure for %s: %s", name, result)
3272
      return [row[1:3] for row in self.jobs]
3273

    
3274

    
3275
def FormatParameterDict(buf, param_dict, actual, level=1):
3276
  """Formats a parameter dictionary.
3277

3278
  @type buf: L{StringIO}
3279
  @param buf: the buffer into which to write
3280
  @type param_dict: dict
3281
  @param param_dict: the own parameters
3282
  @type actual: dict
3283
  @param actual: the current parameter set (including defaults)
3284
  @param level: Level of indent
3285

3286
  """
3287
  indent = "  " * level
3288
  for key in sorted(actual):
3289
    val = param_dict.get(key, "default (%s)" % actual[key])
3290
    buf.write("%s- %s: %s\n" % (indent, key, val))
3291

    
3292

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

3296
  This function is used to request confirmation for doing an operation
3297
  on a given list of list_type.
3298

3299
  @type names: list
3300
  @param names: the list of names that we display when
3301
      we ask for confirmation
3302
  @type list_type: str
3303
  @param list_type: Human readable name for elements in the list (e.g. nodes)
3304
  @type text: str
3305
  @param text: the operation that the user should confirm
3306
  @rtype: boolean
3307
  @return: True or False depending on user's confirmation.
3308

3309
  """
3310
  count = len(names)
3311
  msg = ("The %s will operate on %d %s.\n%s"
3312
         "Do you want to continue?" % (text, count, list_type, extra))
3313
  affected = (("\nAffected %s:\n" % list_type) +
3314
              "\n".join(["  %s" % name for name in names]))
3315

    
3316
  choices = [("y", True, "Yes, execute the %s" % text),
3317
             ("n", False, "No, abort the %s" % text)]
3318

    
3319
  if count > 20:
3320
    choices.insert(1, ("v", "v", "View the list of affected %s" % list_type))
3321
    question = msg
3322
  else:
3323
    question = msg + affected
3324

    
3325
  choice = AskUser(question, choices)
3326
  if choice == "v":
3327
    choices.pop(1)
3328
    choice = AskUser(msg + affected, choices)
3329
  return choice