Statistics
| Branch: | Tag: | Revision:

root / lib / cli.py @ 0ce212e5

History | View | Annotate | Download (108.9 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
  "SPECS_DISK_TEMPLATES",
175
  "SPICE_CACERT_OPT",
176
  "SPICE_CERT_OPT",
177
  "SRC_DIR_OPT",
178
  "SRC_NODE_OPT",
179
  "SUBMIT_OPT",
180
  "STARTUP_PAUSED_OPT",
181
  "STATIC_OPT",
182
  "SYNC_OPT",
183
  "TAG_ADD_OPT",
184
  "TAG_SRC_OPT",
185
  "TIMEOUT_OPT",
186
  "TO_GROUP_OPT",
187
  "UIDPOOL_OPT",
188
  "USEUNITS_OPT",
189
  "USE_EXTERNAL_MIP_SCRIPT",
190
  "USE_REPL_NET_OPT",
191
  "VERBOSE_OPT",
192
  "VG_NAME_OPT",
193
  "YES_DOIT_OPT",
194
  "DISK_STATE_OPT",
195
  "HV_STATE_OPT",
196
  "IGNORE_IPOLICY_OPT",
197
  "INSTANCE_POLICY_OPTS",
198
  # Generic functions for CLI programs
199
  "ConfirmOperation",
200
  "GenericMain",
201
  "GenericInstanceCreate",
202
  "GenericList",
203
  "GenericListFields",
204
  "GetClient",
205
  "GetOnlineNodes",
206
  "JobExecutor",
207
  "JobSubmittedException",
208
  "ParseTimespec",
209
  "RunWhileClusterStopped",
210
  "SubmitOpCode",
211
  "SubmitOrSend",
212
  "UsesRPC",
213
  # Formatting functions
214
  "ToStderr", "ToStdout",
215
  "FormatError",
216
  "FormatQueryResult",
217
  "FormatParameterDict",
218
  "GenerateTable",
219
  "AskUser",
220
  "FormatTimestamp",
221
  "FormatLogMessage",
222
  # Tags functions
223
  "ListTags",
224
  "AddTags",
225
  "RemoveTags",
226
  # command line options support infrastructure
227
  "ARGS_MANY_INSTANCES",
228
  "ARGS_MANY_NODES",
229
  "ARGS_MANY_GROUPS",
230
  "ARGS_NONE",
231
  "ARGS_ONE_INSTANCE",
232
  "ARGS_ONE_NODE",
233
  "ARGS_ONE_GROUP",
234
  "ARGS_ONE_OS",
235
  "ArgChoice",
236
  "ArgCommand",
237
  "ArgFile",
238
  "ArgGroup",
239
  "ArgHost",
240
  "ArgInstance",
241
  "ArgJobId",
242
  "ArgNode",
243
  "ArgOs",
244
  "ArgSuggest",
245
  "ArgUnknown",
246
  "OPT_COMPL_INST_ADD_NODES",
247
  "OPT_COMPL_MANY_NODES",
248
  "OPT_COMPL_ONE_IALLOCATOR",
249
  "OPT_COMPL_ONE_INSTANCE",
250
  "OPT_COMPL_ONE_NODE",
251
  "OPT_COMPL_ONE_NODEGROUP",
252
  "OPT_COMPL_ONE_OS",
253
  "cli_option",
254
  "SplitNodeOption",
255
  "CalculateOSNames",
256
  "ParseFields",
257
  "COMMON_CREATE_OPTS",
258
  ]
259

    
260
NO_PREFIX = "no_"
261
UN_PREFIX = "-"
262

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

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

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

    
280
#: Maximum batch size for ChooseJob
281
_CHOOSE_BATCH = 25
282

    
283

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

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

    
293

    
294
class ArgSuggest(_Argument):
295
  """Suggesting argument.
296

297
  Value can be any of the ones passed to the constructor.
298

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

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

    
309

    
310
class ArgChoice(ArgSuggest):
311
  """Choice argument.
312

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

316
  """
317

    
318

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

322
  """
323

    
324

    
325
class ArgInstance(_Argument):
326
  """Instances argument.
327

328
  """
329

    
330

    
331
class ArgNode(_Argument):
332
  """Node argument.
333

334
  """
335

    
336

    
337
class ArgGroup(_Argument):
338
  """Node group argument.
339

340
  """
341

    
342

    
343
class ArgJobId(_Argument):
344
  """Job ID argument.
345

346
  """
347

    
348

    
349
class ArgFile(_Argument):
350
  """File path argument.
351

352
  """
353

    
354

    
355
class ArgCommand(_Argument):
356
  """Command argument.
357

358
  """
359

    
360

    
361
class ArgHost(_Argument):
362
  """Host argument.
363

364
  """
365

    
366

    
367
class ArgOs(_Argument):
368
  """OS argument.
369

370
  """
371

    
372

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

    
383

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

387
  Note that this function will modify its args parameter.
388

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

    
406

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

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

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

    
435

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

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

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

    
453

    
454
def AddTags(opts, args):
455
  """Add tags on a given object.
456

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

462
  """
463
  kind, name = _ExtractTagsObject(opts, args)
464
  _ExtendTags(opts, args)
465
  if not args:
466
    raise errors.OpPrereqError("No tags to be added")
467
  op = opcodes.OpTagsSet(kind=kind, name=name, tags=args)
468
  SubmitOpCode(op, opts=opts)
469

    
470

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

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

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

    
487

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

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

    
497

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

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

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

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

    
534

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

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

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

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

    
562

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

566
  This will store the parsed values as a dict {key: val}.
567

568
  """
569
  return _SplitKeyVal(opt, value)
570

    
571

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

575
  This will store the parsed value as either True or False.
576

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

    
586

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

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

    
598

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

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

    
619

    
620
class CliOption(Option):
621
  """Custom option class for optparse.
622

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

    
641

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

    
645

    
646
_YORNO = "yes|no"
647

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
807
SPECS_DISK_TEMPLATES = cli_option("--specs-disk-templates",
808
                                  dest="ispecs_disk_templates",
809
                                  type="list", default=None,
810
                                  help="Comma-separated list of"
811
                                  " enabled disk templates")
812

    
813
HYPERVISOR_OPT = cli_option("-H", "--hypervisor-parameters", dest="hypervisor",
814
                            help="Hypervisor and hypervisor options, in the"
815
                            " format hypervisor:option=value,option=value,...",
816
                            default=None, type="identkeyval")
817

    
818
HVLIST_OPT = cli_option("-H", "--hypervisor-parameters", dest="hvparams",
819
                        help="Hypervisor and hypervisor options, in the"
820
                        " format hypervisor:option=value,option=value,...",
821
                        default=[], action="append", type="identkeyval")
822

    
823
NOIPCHECK_OPT = cli_option("--no-ip-check", dest="ip_check", default=True,
824
                           action="store_false",
825
                           help="Don't check that the instance's IP"
826
                           " is alive")
827

    
828
NONAMECHECK_OPT = cli_option("--no-name-check", dest="name_check",
829
                             default=True, action="store_false",
830
                             help="Don't check that the instance's name"
831
                             " is resolvable")
832

    
833
NET_OPT = cli_option("--net",
834
                     help="NIC parameters", default=[],
835
                     dest="nics", action="append", type="identkeyval")
836

    
837
DISK_OPT = cli_option("--disk", help="Disk parameters", default=[],
838
                      dest="disks", action="append", type="identkeyval")
839

    
840
DISKIDX_OPT = cli_option("--disks", dest="disks", default=None,
841
                         help="Comma-separated list of disks"
842
                         " indices to act on (e.g. 0,2) (optional,"
843
                         " defaults to all disks)")
844

    
845
OS_SIZE_OPT = cli_option("-s", "--os-size", dest="sd_size",
846
                         help="Enforces a single-disk configuration using the"
847
                         " given disk size, in MiB unless a suffix is used",
848
                         default=None, type="unit", metavar="<size>")
849

    
850
IGNORE_CONSIST_OPT = cli_option("--ignore-consistency",
851
                                dest="ignore_consistency",
852
                                action="store_true", default=False,
853
                                help="Ignore the consistency of the disks on"
854
                                " the secondary")
855

    
856
ALLOW_FAILOVER_OPT = cli_option("--allow-failover",
857
                                dest="allow_failover",
858
                                action="store_true", default=False,
859
                                help="If migration is not possible fallback to"
860
                                     " failover")
861

    
862
NONLIVE_OPT = cli_option("--non-live", dest="live",
863
                         default=True, action="store_false",
864
                         help="Do a non-live migration (this usually means"
865
                         " freeze the instance, save the state, transfer and"
866
                         " only then resume running on the secondary node)")
867

    
868
MIGRATION_MODE_OPT = cli_option("--migration-mode", dest="migration_mode",
869
                                default=None,
870
                                choices=list(constants.HT_MIGRATION_MODES),
871
                                help="Override default migration mode (choose"
872
                                " either live or non-live")
873

    
874
NODE_PLACEMENT_OPT = cli_option("-n", "--node", dest="node",
875
                                help="Target node and optional secondary node",
876
                                metavar="<pnode>[:<snode>]",
877
                                completion_suggest=OPT_COMPL_INST_ADD_NODES)
878

    
879
NODE_LIST_OPT = cli_option("-n", "--node", dest="nodes", default=[],
880
                           action="append", metavar="<node>",
881
                           help="Use only this node (can be used multiple"
882
                           " times, if not given defaults to all nodes)",
883
                           completion_suggest=OPT_COMPL_ONE_NODE)
884

    
885
NODEGROUP_OPT_NAME = "--node-group"
886
NODEGROUP_OPT = cli_option("-g", NODEGROUP_OPT_NAME,
887
                           dest="nodegroup",
888
                           help="Node group (name or uuid)",
889
                           metavar="<nodegroup>",
890
                           default=None, type="string",
891
                           completion_suggest=OPT_COMPL_ONE_NODEGROUP)
892

    
893
SINGLE_NODE_OPT = cli_option("-n", "--node", dest="node", help="Target node",
894
                             metavar="<node>",
895
                             completion_suggest=OPT_COMPL_ONE_NODE)
896

    
897
NOSTART_OPT = cli_option("--no-start", dest="start", default=True,
898
                         action="store_false",
899
                         help="Don't start the instance after creation")
900

    
901
SHOWCMD_OPT = cli_option("--show-cmd", dest="show_command",
902
                         action="store_true", default=False,
903
                         help="Show command instead of executing it")
904

    
905
CLEANUP_OPT = cli_option("--cleanup", dest="cleanup",
906
                         default=False, action="store_true",
907
                         help="Instead of performing the migration, try to"
908
                         " recover from a failed cleanup. This is safe"
909
                         " to run even if the instance is healthy, but it"
910
                         " will create extra replication traffic and "
911
                         " disrupt briefly the replication (like during the"
912
                         " migration")
913

    
914
STATIC_OPT = cli_option("-s", "--static", dest="static",
915
                        action="store_true", default=False,
916
                        help="Only show configuration data, not runtime data")
917

    
918
ALL_OPT = cli_option("--all", dest="show_all",
919
                     default=False, action="store_true",
920
                     help="Show info on all instances on the cluster."
921
                     " This can take a long time to run, use wisely")
922

    
923
SELECT_OS_OPT = cli_option("--select-os", dest="select_os",
924
                           action="store_true", default=False,
925
                           help="Interactive OS reinstall, lists available"
926
                           " OS templates for selection")
927

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

    
934
IGNORE_REMOVE_FAILURES_OPT = cli_option("--ignore-remove-failures",
935
                                        dest="ignore_remove_failures",
936
                                        action="store_true", default=False,
937
                                        help="Remove the instance from the"
938
                                        " cluster configuration even if there"
939
                                        " are failures during the removal"
940
                                        " process")
941

    
942
REMOVE_INSTANCE_OPT = cli_option("--remove-instance", dest="remove_instance",
943
                                 action="store_true", default=False,
944
                                 help="Remove the instance from the cluster")
945

    
946
DST_NODE_OPT = cli_option("-n", "--target-node", dest="dst_node",
947
                               help="Specifies the new node for the instance",
948
                               metavar="NODE", default=None,
949
                               completion_suggest=OPT_COMPL_ONE_NODE)
950

    
951
NEW_SECONDARY_OPT = cli_option("-n", "--new-secondary", dest="dst_node",
952
                               help="Specifies the new secondary node",
953
                               metavar="NODE", default=None,
954
                               completion_suggest=OPT_COMPL_ONE_NODE)
955

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

    
963
ON_SECONDARY_OPT = cli_option("-s", "--on-secondary", dest="on_secondary",
964
                              default=False, action="store_true",
965
                              help="Replace the disk(s) on the secondary"
966
                                   " node (applies only to internally mirrored"
967
                                   " disk templates, e.g. %s)" %
968
                                   utils.CommaJoin(constants.DTS_INT_MIRROR))
969

    
970
AUTO_PROMOTE_OPT = cli_option("--auto-promote", dest="auto_promote",
971
                              default=False, action="store_true",
972
                              help="Lock all nodes and auto-promote as needed"
973
                              " to MC status")
974

    
975
AUTO_REPLACE_OPT = cli_option("-a", "--auto", dest="auto",
976
                              default=False, action="store_true",
977
                              help="Automatically replace faulty disks"
978
                                   " (applies only to internally mirrored"
979
                                   " disk templates, e.g. %s)" %
980
                                   utils.CommaJoin(constants.DTS_INT_MIRROR))
981

    
982
IGNORE_SIZE_OPT = cli_option("--ignore-size", dest="ignore_size",
983
                             default=False, action="store_true",
984
                             help="Ignore current recorded size"
985
                             " (useful for forcing activation when"
986
                             " the recorded size is wrong)")
987

    
988
SRC_NODE_OPT = cli_option("--src-node", dest="src_node", help="Source node",
989
                          metavar="<node>",
990
                          completion_suggest=OPT_COMPL_ONE_NODE)
991

    
992
SRC_DIR_OPT = cli_option("--src-dir", dest="src_dir", help="Source directory",
993
                         metavar="<dir>")
994

    
995
SECONDARY_IP_OPT = cli_option("-s", "--secondary-ip", dest="secondary_ip",
996
                              help="Specify the secondary ip for the node",
997
                              metavar="ADDRESS", default=None)
998

    
999
READD_OPT = cli_option("--readd", dest="readd",
1000
                       default=False, action="store_true",
1001
                       help="Readd old node after replacing it")
1002

    
1003
NOSSH_KEYCHECK_OPT = cli_option("--no-ssh-key-check", dest="ssh_key_check",
1004
                                default=True, action="store_false",
1005
                                help="Disable SSH key fingerprint checking")
1006

    
1007
NODE_FORCE_JOIN_OPT = cli_option("--force-join", dest="force_join",
1008
                                 default=False, action="store_true",
1009
                                 help="Force the joining of a node")
1010

    
1011
MC_OPT = cli_option("-C", "--master-candidate", dest="master_candidate",
1012
                    type="bool", default=None, metavar=_YORNO,
1013
                    help="Set the master_candidate flag on the node")
1014

    
1015
OFFLINE_OPT = cli_option("-O", "--offline", dest="offline", metavar=_YORNO,
1016
                         type="bool", default=None,
1017
                         help=("Set the offline flag on the node"
1018
                               " (cluster does not communicate with offline"
1019
                               " nodes)"))
1020

    
1021
DRAINED_OPT = cli_option("-D", "--drained", dest="drained", metavar=_YORNO,
1022
                         type="bool", default=None,
1023
                         help=("Set the drained flag on the node"
1024
                               " (excluded from allocation operations)"))
1025

    
1026
CAPAB_MASTER_OPT = cli_option("--master-capable", dest="master_capable",
1027
                    type="bool", default=None, metavar=_YORNO,
1028
                    help="Set the master_capable flag on the node")
1029

    
1030
CAPAB_VM_OPT = cli_option("--vm-capable", dest="vm_capable",
1031
                    type="bool", default=None, metavar=_YORNO,
1032
                    help="Set the vm_capable flag on the node")
1033

    
1034
ALLOCATABLE_OPT = cli_option("--allocatable", dest="allocatable",
1035
                             type="bool", default=None, metavar=_YORNO,
1036
                             help="Set the allocatable flag on a volume")
1037

    
1038
NOLVM_STORAGE_OPT = cli_option("--no-lvm-storage", dest="lvm_storage",
1039
                               help="Disable support for lvm based instances"
1040
                               " (cluster-wide)",
1041
                               action="store_false", default=True)
1042

    
1043
ENABLED_HV_OPT = cli_option("--enabled-hypervisors",
1044
                            dest="enabled_hypervisors",
1045
                            help="Comma-separated list of hypervisors",
1046
                            type="string", default=None)
1047

    
1048
NIC_PARAMS_OPT = cli_option("-N", "--nic-parameters", dest="nicparams",
1049
                            type="keyval", default={},
1050
                            help="NIC parameters")
1051

    
1052
CP_SIZE_OPT = cli_option("-C", "--candidate-pool-size", default=None,
1053
                         dest="candidate_pool_size", type="int",
1054
                         help="Set the candidate pool size")
1055

    
1056
VG_NAME_OPT = cli_option("--vg-name", dest="vg_name",
1057
                         help=("Enables LVM and specifies the volume group"
1058
                               " name (cluster-wide) for disk allocation"
1059
                               " [%s]" % constants.DEFAULT_VG),
1060
                         metavar="VG", default=None)
1061

    
1062
YES_DOIT_OPT = cli_option("--yes-do-it", "--ya-rly", dest="yes_do_it",
1063
                          help="Destroy cluster", action="store_true")
1064

    
1065
NOVOTING_OPT = cli_option("--no-voting", dest="no_voting",
1066
                          help="Skip node agreement check (dangerous)",
1067
                          action="store_true", default=False)
1068

    
1069
MAC_PREFIX_OPT = cli_option("-m", "--mac-prefix", dest="mac_prefix",
1070
                            help="Specify the mac prefix for the instance IP"
1071
                            " addresses, in the format XX:XX:XX",
1072
                            metavar="PREFIX",
1073
                            default=None)
1074

    
1075
MASTER_NETDEV_OPT = cli_option("--master-netdev", dest="master_netdev",
1076
                               help="Specify the node interface (cluster-wide)"
1077
                               " on which the master IP address will be added"
1078
                               " (cluster init default: %s)" %
1079
                               constants.DEFAULT_BRIDGE,
1080
                               metavar="NETDEV",
1081
                               default=None)
1082

    
1083
MASTER_NETMASK_OPT = cli_option("--master-netmask", dest="master_netmask",
1084
                                help="Specify the netmask of the master IP",
1085
                                metavar="NETMASK",
1086
                                default=None)
1087

    
1088
USE_EXTERNAL_MIP_SCRIPT = cli_option("--use-external-mip-script",
1089
                                dest="use_external_mip_script",
1090
                                help="Specify whether to run a user-provided"
1091
                                " script for the master IP address turnup and"
1092
                                " turndown operations",
1093
                                type="bool", metavar=_YORNO, default=None)
1094

    
1095
GLOBAL_FILEDIR_OPT = cli_option("--file-storage-dir", dest="file_storage_dir",
1096
                                help="Specify the default directory (cluster-"
1097
                                "wide) for storing the file-based disks [%s]" %
1098
                                constants.DEFAULT_FILE_STORAGE_DIR,
1099
                                metavar="DIR",
1100
                                default=constants.DEFAULT_FILE_STORAGE_DIR)
1101

    
1102
GLOBAL_SHARED_FILEDIR_OPT = cli_option("--shared-file-storage-dir",
1103
                            dest="shared_file_storage_dir",
1104
                            help="Specify the default directory (cluster-"
1105
                            "wide) for storing the shared file-based"
1106
                            " disks [%s]" %
1107
                            constants.DEFAULT_SHARED_FILE_STORAGE_DIR,
1108
                            metavar="SHAREDDIR",
1109
                            default=constants.DEFAULT_SHARED_FILE_STORAGE_DIR)
1110

    
1111
NOMODIFY_ETCHOSTS_OPT = cli_option("--no-etc-hosts", dest="modify_etc_hosts",
1112
                                   help="Don't modify /etc/hosts",
1113
                                   action="store_false", default=True)
1114

    
1115
NOMODIFY_SSH_SETUP_OPT = cli_option("--no-ssh-init", dest="modify_ssh_setup",
1116
                                    help="Don't initialize SSH keys",
1117
                                    action="store_false", default=True)
1118

    
1119
ERROR_CODES_OPT = cli_option("--error-codes", dest="error_codes",
1120
                             help="Enable parseable error messages",
1121
                             action="store_true", default=False)
1122

    
1123
NONPLUS1_OPT = cli_option("--no-nplus1-mem", dest="skip_nplusone_mem",
1124
                          help="Skip N+1 memory redundancy tests",
1125
                          action="store_true", default=False)
1126

    
1127
REBOOT_TYPE_OPT = cli_option("-t", "--type", dest="reboot_type",
1128
                             help="Type of reboot: soft/hard/full",
1129
                             default=constants.INSTANCE_REBOOT_HARD,
1130
                             metavar="<REBOOT>",
1131
                             choices=list(constants.REBOOT_TYPES))
1132

    
1133
IGNORE_SECONDARIES_OPT = cli_option("--ignore-secondaries",
1134
                                    dest="ignore_secondaries",
1135
                                    default=False, action="store_true",
1136
                                    help="Ignore errors from secondaries")
1137

    
1138
NOSHUTDOWN_OPT = cli_option("--noshutdown", dest="shutdown",
1139
                            action="store_false", default=True,
1140
                            help="Don't shutdown the instance (unsafe)")
1141

    
1142
TIMEOUT_OPT = cli_option("--timeout", dest="timeout", type="int",
1143
                         default=constants.DEFAULT_SHUTDOWN_TIMEOUT,
1144
                         help="Maximum time to wait")
1145

    
1146
SHUTDOWN_TIMEOUT_OPT = cli_option("--shutdown-timeout",
1147
                         dest="shutdown_timeout", type="int",
1148
                         default=constants.DEFAULT_SHUTDOWN_TIMEOUT,
1149
                         help="Maximum time to wait for instance shutdown")
1150

    
1151
INTERVAL_OPT = cli_option("--interval", dest="interval", type="int",
1152
                          default=None,
1153
                          help=("Number of seconds between repetions of the"
1154
                                " command"))
1155

    
1156
EARLY_RELEASE_OPT = cli_option("--early-release",
1157
                               dest="early_release", default=False,
1158
                               action="store_true",
1159
                               help="Release the locks on the secondary"
1160
                               " node(s) early")
1161

    
1162
NEW_CLUSTER_CERT_OPT = cli_option("--new-cluster-certificate",
1163
                                  dest="new_cluster_cert",
1164
                                  default=False, action="store_true",
1165
                                  help="Generate a new cluster certificate")
1166

    
1167
RAPI_CERT_OPT = cli_option("--rapi-certificate", dest="rapi_cert",
1168
                           default=None,
1169
                           help="File containing new RAPI certificate")
1170

    
1171
NEW_RAPI_CERT_OPT = cli_option("--new-rapi-certificate", dest="new_rapi_cert",
1172
                               default=None, action="store_true",
1173
                               help=("Generate a new self-signed RAPI"
1174
                                     " certificate"))
1175

    
1176
SPICE_CERT_OPT = cli_option("--spice-certificate", dest="spice_cert",
1177
                           default=None,
1178
                           help="File containing new SPICE certificate")
1179

    
1180
SPICE_CACERT_OPT = cli_option("--spice-ca-certificate", dest="spice_cacert",
1181
                           default=None,
1182
                           help="File containing the certificate of the CA"
1183
                                " which signed the SPICE certificate")
1184

    
1185
NEW_SPICE_CERT_OPT = cli_option("--new-spice-certificate",
1186
                               dest="new_spice_cert", default=None,
1187
                               action="store_true",
1188
                               help=("Generate a new self-signed SPICE"
1189
                                     " certificate"))
1190

    
1191
NEW_CONFD_HMAC_KEY_OPT = cli_option("--new-confd-hmac-key",
1192
                                    dest="new_confd_hmac_key",
1193
                                    default=False, action="store_true",
1194
                                    help=("Create a new HMAC key for %s" %
1195
                                          constants.CONFD))
1196

    
1197
CLUSTER_DOMAIN_SECRET_OPT = cli_option("--cluster-domain-secret",
1198
                                       dest="cluster_domain_secret",
1199
                                       default=None,
1200
                                       help=("Load new new cluster domain"
1201
                                             " secret from file"))
1202

    
1203
NEW_CLUSTER_DOMAIN_SECRET_OPT = cli_option("--new-cluster-domain-secret",
1204
                                           dest="new_cluster_domain_secret",
1205
                                           default=False, action="store_true",
1206
                                           help=("Create a new cluster domain"
1207
                                                 " secret"))
1208

    
1209
USE_REPL_NET_OPT = cli_option("--use-replication-network",
1210
                              dest="use_replication_network",
1211
                              help="Whether to use the replication network"
1212
                              " for talking to the nodes",
1213
                              action="store_true", default=False)
1214

    
1215
MAINTAIN_NODE_HEALTH_OPT = \
1216
    cli_option("--maintain-node-health", dest="maintain_node_health",
1217
               metavar=_YORNO, default=None, type="bool",
1218
               help="Configure the cluster to automatically maintain node"
1219
               " health, by shutting down unknown instances, shutting down"
1220
               " unknown DRBD devices, etc.")
1221

    
1222
IDENTIFY_DEFAULTS_OPT = \
1223
    cli_option("--identify-defaults", dest="identify_defaults",
1224
               default=False, action="store_true",
1225
               help="Identify which saved instance parameters are equal to"
1226
               " the current cluster defaults and set them as such, instead"
1227
               " of marking them as overridden")
1228

    
1229
UIDPOOL_OPT = cli_option("--uid-pool", default=None,
1230
                         action="store", dest="uid_pool",
1231
                         help=("A list of user-ids or user-id"
1232
                               " ranges separated by commas"))
1233

    
1234
ADD_UIDS_OPT = cli_option("--add-uids", default=None,
1235
                          action="store", dest="add_uids",
1236
                          help=("A list of user-ids or user-id"
1237
                                " ranges separated by commas, to be"
1238
                                " added to the user-id pool"))
1239

    
1240
REMOVE_UIDS_OPT = cli_option("--remove-uids", default=None,
1241
                             action="store", dest="remove_uids",
1242
                             help=("A list of user-ids or user-id"
1243
                                   " ranges separated by commas, to be"
1244
                                   " removed from the user-id pool"))
1245

    
1246
RESERVED_LVS_OPT = cli_option("--reserved-lvs", default=None,
1247
                             action="store", dest="reserved_lvs",
1248
                             help=("A comma-separated list of reserved"
1249
                                   " logical volumes names, that will be"
1250
                                   " ignored by cluster verify"))
1251

    
1252
ROMAN_OPT = cli_option("--roman",
1253
                       dest="roman_integers", default=False,
1254
                       action="store_true",
1255
                       help="Use roman numbers for positive integers")
1256

    
1257
DRBD_HELPER_OPT = cli_option("--drbd-usermode-helper", dest="drbd_helper",
1258
                             action="store", default=None,
1259
                             help="Specifies usermode helper for DRBD")
1260

    
1261
NODRBD_STORAGE_OPT = cli_option("--no-drbd-storage", dest="drbd_storage",
1262
                                action="store_false", default=True,
1263
                                help="Disable support for DRBD")
1264

    
1265
PRIMARY_IP_VERSION_OPT = \
1266
    cli_option("--primary-ip-version", default=constants.IP4_VERSION,
1267
               action="store", dest="primary_ip_version",
1268
               metavar="%d|%d" % (constants.IP4_VERSION,
1269
                                  constants.IP6_VERSION),
1270
               help="Cluster-wide IP version for primary IP")
1271

    
1272
PRIORITY_OPT = cli_option("--priority", default=None, dest="priority",
1273
                          metavar="|".join(name for name, _ in _PRIORITY_NAMES),
1274
                          choices=_PRIONAME_TO_VALUE.keys(),
1275
                          help="Priority for opcode processing")
1276

    
1277
HID_OS_OPT = cli_option("--hidden", dest="hidden",
1278
                        type="bool", default=None, metavar=_YORNO,
1279
                        help="Sets the hidden flag on the OS")
1280

    
1281
BLK_OS_OPT = cli_option("--blacklisted", dest="blacklisted",
1282
                        type="bool", default=None, metavar=_YORNO,
1283
                        help="Sets the blacklisted flag on the OS")
1284

    
1285
PREALLOC_WIPE_DISKS_OPT = cli_option("--prealloc-wipe-disks", default=None,
1286
                                     type="bool", metavar=_YORNO,
1287
                                     dest="prealloc_wipe_disks",
1288
                                     help=("Wipe disks prior to instance"
1289
                                           " creation"))
1290

    
1291
NODE_PARAMS_OPT = cli_option("--node-parameters", dest="ndparams",
1292
                             type="keyval", default=None,
1293
                             help="Node parameters")
1294

    
1295
ALLOC_POLICY_OPT = cli_option("--alloc-policy", dest="alloc_policy",
1296
                              action="store", metavar="POLICY", default=None,
1297
                              help="Allocation policy for the node group")
1298

    
1299
NODE_POWERED_OPT = cli_option("--node-powered", default=None,
1300
                              type="bool", metavar=_YORNO,
1301
                              dest="node_powered",
1302
                              help="Specify if the SoR for node is powered")
1303

    
1304
OOB_TIMEOUT_OPT = cli_option("--oob-timeout", dest="oob_timeout", type="int",
1305
                         default=constants.OOB_TIMEOUT,
1306
                         help="Maximum time to wait for out-of-band helper")
1307

    
1308
POWER_DELAY_OPT = cli_option("--power-delay", dest="power_delay", type="float",
1309
                             default=constants.OOB_POWER_DELAY,
1310
                             help="Time in seconds to wait between power-ons")
1311

    
1312
FORCE_FILTER_OPT = cli_option("-F", "--filter", dest="force_filter",
1313
                              action="store_true", default=False,
1314
                              help=("Whether command argument should be treated"
1315
                                    " as filter"))
1316

    
1317
NO_REMEMBER_OPT = cli_option("--no-remember",
1318
                             dest="no_remember",
1319
                             action="store_true", default=False,
1320
                             help="Perform but do not record the change"
1321
                             " in the configuration")
1322

    
1323
PRIMARY_ONLY_OPT = cli_option("-p", "--primary-only",
1324
                              default=False, action="store_true",
1325
                              help="Evacuate primary instances only")
1326

    
1327
SECONDARY_ONLY_OPT = cli_option("-s", "--secondary-only",
1328
                                default=False, action="store_true",
1329
                                help="Evacuate secondary instances only"
1330
                                     " (applies only to internally mirrored"
1331
                                     " disk templates, e.g. %s)" %
1332
                                     utils.CommaJoin(constants.DTS_INT_MIRROR))
1333

    
1334
STARTUP_PAUSED_OPT = cli_option("--paused", dest="startup_paused",
1335
                                action="store_true", default=False,
1336
                                help="Pause instance at startup")
1337

    
1338
TO_GROUP_OPT = cli_option("--to", dest="to", metavar="<group>",
1339
                          help="Destination node group (name or uuid)",
1340
                          default=None, action="append",
1341
                          completion_suggest=OPT_COMPL_ONE_NODEGROUP)
1342

    
1343
IGNORE_ERRORS_OPT = cli_option("-I", "--ignore-errors", default=[],
1344
                               action="append", dest="ignore_errors",
1345
                               choices=list(constants.CV_ALL_ECODES_STRINGS),
1346
                               help="Error code to be ignored")
1347

    
1348
DISK_STATE_OPT = cli_option("--disk-state", default=[], dest="disk_state",
1349
                            action="append",
1350
                            help=("Specify disk state information in the format"
1351
                                  " storage_type/identifier:option=value,..."),
1352
                            type="identkeyval")
1353

    
1354
HV_STATE_OPT = cli_option("--hypervisor-state", default=[], dest="hv_state",
1355
                          action="append",
1356
                          help=("Specify hypervisor state information in the"
1357
                                " format hypervisor:option=value,..."),
1358
                          type="identkeyval")
1359

    
1360
IGNORE_IPOLICY_OPT = cli_option("--ignore-ipolicy", dest="ignore_ipolicy",
1361
                                action="store_true", default=False,
1362
                                help="Ignore instance policy violations")
1363

    
1364

    
1365
#: Options provided by all commands
1366
COMMON_OPTS = [DEBUG_OPT]
1367

    
1368
# common options for creating instances. add and import then add their own
1369
# specific ones.
1370
COMMON_CREATE_OPTS = [
1371
  BACKEND_OPT,
1372
  DISK_OPT,
1373
  DISK_TEMPLATE_OPT,
1374
  FILESTORE_DIR_OPT,
1375
  FILESTORE_DRIVER_OPT,
1376
  HYPERVISOR_OPT,
1377
  IALLOCATOR_OPT,
1378
  NET_OPT,
1379
  NODE_PLACEMENT_OPT,
1380
  NOIPCHECK_OPT,
1381
  NONAMECHECK_OPT,
1382
  NONICS_OPT,
1383
  NWSYNC_OPT,
1384
  OSPARAMS_OPT,
1385
  OS_SIZE_OPT,
1386
  SUBMIT_OPT,
1387
  TAG_ADD_OPT,
1388
  DRY_RUN_OPT,
1389
  PRIORITY_OPT,
1390
  ]
1391

    
1392
# common instance policy options
1393
INSTANCE_POLICY_OPTS = [
1394
  SPECS_CPU_COUNT_OPT,
1395
  SPECS_DISK_COUNT_OPT,
1396
  SPECS_DISK_SIZE_OPT,
1397
  SPECS_MEM_SIZE_OPT,
1398
  SPECS_NIC_COUNT_OPT,
1399
  SPECS_DISK_TEMPLATES,
1400
  ]
1401

    
1402

    
1403
def _ParseArgs(argv, commands, aliases, env_override):
1404
  """Parser for the command line arguments.
1405

1406
  This function parses the arguments and returns the function which
1407
  must be executed together with its (modified) arguments.
1408

1409
  @param argv: the command line
1410
  @param commands: dictionary with special contents, see the design
1411
      doc for cmdline handling
1412
  @param aliases: dictionary with command aliases {'alias': 'target, ...}
1413
  @param env_override: list of env variables allowed for default args
1414

1415
  """
1416
  assert not (env_override - set(commands))
1417

    
1418
  if len(argv) == 0:
1419
    binary = "<command>"
1420
  else:
1421
    binary = argv[0].split("/")[-1]
1422

    
1423
  if len(argv) > 1 and argv[1] == "--version":
1424
    ToStdout("%s (ganeti %s) %s", binary, constants.VCS_VERSION,
1425
             constants.RELEASE_VERSION)
1426
    # Quit right away. That way we don't have to care about this special
1427
    # argument. optparse.py does it the same.
1428
    sys.exit(0)
1429

    
1430
  if len(argv) < 2 or not (argv[1] in commands or
1431
                           argv[1] in aliases):
1432
    # let's do a nice thing
1433
    sortedcmds = commands.keys()
1434
    sortedcmds.sort()
1435

    
1436
    ToStdout("Usage: %s {command} [options...] [argument...]", binary)
1437
    ToStdout("%s <command> --help to see details, or man %s", binary, binary)
1438
    ToStdout("")
1439

    
1440
    # compute the max line length for cmd + usage
1441
    mlen = max([len(" %s" % cmd) for cmd in commands])
1442
    mlen = min(60, mlen) # should not get here...
1443

    
1444
    # and format a nice command list
1445
    ToStdout("Commands:")
1446
    for cmd in sortedcmds:
1447
      cmdstr = " %s" % (cmd,)
1448
      help_text = commands[cmd][4]
1449
      help_lines = textwrap.wrap(help_text, 79 - 3 - mlen)
1450
      ToStdout("%-*s - %s", mlen, cmdstr, help_lines.pop(0))
1451
      for line in help_lines:
1452
        ToStdout("%-*s   %s", mlen, "", line)
1453

    
1454
    ToStdout("")
1455

    
1456
    return None, None, None
1457

    
1458
  # get command, unalias it, and look it up in commands
1459
  cmd = argv.pop(1)
1460
  if cmd in aliases:
1461
    if cmd in commands:
1462
      raise errors.ProgrammerError("Alias '%s' overrides an existing"
1463
                                   " command" % cmd)
1464

    
1465
    if aliases[cmd] not in commands:
1466
      raise errors.ProgrammerError("Alias '%s' maps to non-existing"
1467
                                   " command '%s'" % (cmd, aliases[cmd]))
1468

    
1469
    cmd = aliases[cmd]
1470

    
1471
  if cmd in env_override:
1472
    args_env_name = ("%s_%s" % (binary.replace("-", "_"), cmd)).upper()
1473
    env_args = os.environ.get(args_env_name)
1474
    if env_args:
1475
      argv = utils.InsertAtPos(argv, 1, shlex.split(env_args))
1476

    
1477
  func, args_def, parser_opts, usage, description = commands[cmd]
1478
  parser = OptionParser(option_list=parser_opts + COMMON_OPTS,
1479
                        description=description,
1480
                        formatter=TitledHelpFormatter(),
1481
                        usage="%%prog %s %s" % (cmd, usage))
1482
  parser.disable_interspersed_args()
1483
  options, args = parser.parse_args(args=argv[1:])
1484

    
1485
  if not _CheckArguments(cmd, args_def, args):
1486
    return None, None, None
1487

    
1488
  return func, options, args
1489

    
1490

    
1491
def _CheckArguments(cmd, args_def, args):
1492
  """Verifies the arguments using the argument definition.
1493

1494
  Algorithm:
1495

1496
    1. Abort with error if values specified by user but none expected.
1497

1498
    1. For each argument in definition
1499

1500
      1. Keep running count of minimum number of values (min_count)
1501
      1. Keep running count of maximum number of values (max_count)
1502
      1. If it has an unlimited number of values
1503

1504
        1. Abort with error if it's not the last argument in the definition
1505

1506
    1. If last argument has limited number of values
1507

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

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

1512
  """
1513
  if args and not args_def:
1514
    ToStderr("Error: Command %s expects no arguments", cmd)
1515
    return False
1516

    
1517
  min_count = None
1518
  max_count = None
1519
  check_max = None
1520

    
1521
  last_idx = len(args_def) - 1
1522

    
1523
  for idx, arg in enumerate(args_def):
1524
    if min_count is None:
1525
      min_count = arg.min
1526
    elif arg.min is not None:
1527
      min_count += arg.min
1528

    
1529
    if max_count is None:
1530
      max_count = arg.max
1531
    elif arg.max is not None:
1532
      max_count += arg.max
1533

    
1534
    if idx == last_idx:
1535
      check_max = (arg.max is not None)
1536

    
1537
    elif arg.max is None:
1538
      raise errors.ProgrammerError("Only the last argument can have max=None")
1539

    
1540
  if check_max:
1541
    # Command with exact number of arguments
1542
    if (min_count is not None and max_count is not None and
1543
        min_count == max_count and len(args) != min_count):
1544
      ToStderr("Error: Command %s expects %d argument(s)", cmd, min_count)
1545
      return False
1546

    
1547
    # Command with limited number of arguments
1548
    if max_count is not None and len(args) > max_count:
1549
      ToStderr("Error: Command %s expects only %d argument(s)",
1550
               cmd, max_count)
1551
      return False
1552

    
1553
  # Command with some required arguments
1554
  if min_count is not None and len(args) < min_count:
1555
    ToStderr("Error: Command %s expects at least %d argument(s)",
1556
             cmd, min_count)
1557
    return False
1558

    
1559
  return True
1560

    
1561

    
1562
def SplitNodeOption(value):
1563
  """Splits the value of a --node option.
1564

1565
  """
1566
  if value and ":" in value:
1567
    return value.split(":", 1)
1568
  else:
1569
    return (value, None)
1570

    
1571

    
1572
def CalculateOSNames(os_name, os_variants):
1573
  """Calculates all the names an OS can be called, according to its variants.
1574

1575
  @type os_name: string
1576
  @param os_name: base name of the os
1577
  @type os_variants: list or None
1578
  @param os_variants: list of supported variants
1579
  @rtype: list
1580
  @return: list of valid names
1581

1582
  """
1583
  if os_variants:
1584
    return ["%s+%s" % (os_name, v) for v in os_variants]
1585
  else:
1586
    return [os_name]
1587

    
1588

    
1589
def ParseFields(selected, default):
1590
  """Parses the values of "--field"-like options.
1591

1592
  @type selected: string or None
1593
  @param selected: User-selected options
1594
  @type default: list
1595
  @param default: Default fields
1596

1597
  """
1598
  if selected is None:
1599
    return default
1600

    
1601
  if selected.startswith("+"):
1602
    return default + selected[1:].split(",")
1603

    
1604
  return selected.split(",")
1605

    
1606

    
1607
UsesRPC = rpc.RunWithRPC
1608

    
1609

    
1610
def AskUser(text, choices=None):
1611
  """Ask the user a question.
1612

1613
  @param text: the question to ask
1614

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

1620
  @return: one of the return values from the choices list; if input is
1621
      not possible (i.e. not running with a tty, we return the last
1622
      entry from the list
1623

1624
  """
1625
  if choices is None:
1626
    choices = [("y", True, "Perform the operation"),
1627
               ("n", False, "Do not perform the operation")]
1628
  if not choices or not isinstance(choices, list):
1629
    raise errors.ProgrammerError("Invalid choices argument to AskUser")
1630
  for entry in choices:
1631
    if not isinstance(entry, tuple) or len(entry) < 3 or entry[0] == "?":
1632
      raise errors.ProgrammerError("Invalid choices element to AskUser")
1633

    
1634
  answer = choices[-1][1]
1635
  new_text = []
1636
  for line in text.splitlines():
1637
    new_text.append(textwrap.fill(line, 70, replace_whitespace=False))
1638
  text = "\n".join(new_text)
1639
  try:
1640
    f = file("/dev/tty", "a+")
1641
  except IOError:
1642
    return answer
1643
  try:
1644
    chars = [entry[0] for entry in choices]
1645
    chars[-1] = "[%s]" % chars[-1]
1646
    chars.append("?")
1647
    maps = dict([(entry[0], entry[1]) for entry in choices])
1648
    while True:
1649
      f.write(text)
1650
      f.write("\n")
1651
      f.write("/".join(chars))
1652
      f.write(": ")
1653
      line = f.readline(2).strip().lower()
1654
      if line in maps:
1655
        answer = maps[line]
1656
        break
1657
      elif line == "?":
1658
        for entry in choices:
1659
          f.write(" %s - %s\n" % (entry[0], entry[2]))
1660
        f.write("\n")
1661
        continue
1662
  finally:
1663
    f.close()
1664
  return answer
1665

    
1666

    
1667
class JobSubmittedException(Exception):
1668
  """Job was submitted, client should exit.
1669

1670
  This exception has one argument, the ID of the job that was
1671
  submitted. The handler should print this ID.
1672

1673
  This is not an error, just a structured way to exit from clients.
1674

1675
  """
1676

    
1677

    
1678
def SendJob(ops, cl=None):
1679
  """Function to submit an opcode without waiting for the results.
1680

1681
  @type ops: list
1682
  @param ops: list of opcodes
1683
  @type cl: luxi.Client
1684
  @param cl: the luxi client to use for communicating with the master;
1685
             if None, a new client will be created
1686

1687
  """
1688
  if cl is None:
1689
    cl = GetClient()
1690

    
1691
  job_id = cl.SubmitJob(ops)
1692

    
1693
  return job_id
1694

    
1695

    
1696
def GenericPollJob(job_id, cbs, report_cbs):
1697
  """Generic job-polling function.
1698

1699
  @type job_id: number
1700
  @param job_id: Job ID
1701
  @type cbs: Instance of L{JobPollCbBase}
1702
  @param cbs: Data callbacks
1703
  @type report_cbs: Instance of L{JobPollReportCbBase}
1704
  @param report_cbs: Reporting callbacks
1705

1706
  """
1707
  prev_job_info = None
1708
  prev_logmsg_serial = None
1709

    
1710
  status = None
1711

    
1712
  while True:
1713
    result = cbs.WaitForJobChangeOnce(job_id, ["status"], prev_job_info,
1714
                                      prev_logmsg_serial)
1715
    if not result:
1716
      # job not found, go away!
1717
      raise errors.JobLost("Job with id %s lost" % job_id)
1718

    
1719
    if result == constants.JOB_NOTCHANGED:
1720
      report_cbs.ReportNotChanged(job_id, status)
1721

    
1722
      # Wait again
1723
      continue
1724

    
1725
    # Split result, a tuple of (field values, log entries)
1726
    (job_info, log_entries) = result
1727
    (status, ) = job_info
1728

    
1729
    if log_entries:
1730
      for log_entry in log_entries:
1731
        (serial, timestamp, log_type, message) = log_entry
1732
        report_cbs.ReportLogMessage(job_id, serial, timestamp,
1733
                                    log_type, message)
1734
        prev_logmsg_serial = max(prev_logmsg_serial, serial)
1735

    
1736
    # TODO: Handle canceled and archived jobs
1737
    elif status in (constants.JOB_STATUS_SUCCESS,
1738
                    constants.JOB_STATUS_ERROR,
1739
                    constants.JOB_STATUS_CANCELING,
1740
                    constants.JOB_STATUS_CANCELED):
1741
      break
1742

    
1743
    prev_job_info = job_info
1744

    
1745
  jobs = cbs.QueryJobs([job_id], ["status", "opstatus", "opresult"])
1746
  if not jobs:
1747
    raise errors.JobLost("Job with id %s lost" % job_id)
1748

    
1749
  status, opstatus, result = jobs[0]
1750

    
1751
  if status == constants.JOB_STATUS_SUCCESS:
1752
    return result
1753

    
1754
  if status in (constants.JOB_STATUS_CANCELING, constants.JOB_STATUS_CANCELED):
1755
    raise errors.OpExecError("Job was canceled")
1756

    
1757
  has_ok = False
1758
  for idx, (status, msg) in enumerate(zip(opstatus, result)):
1759
    if status == constants.OP_STATUS_SUCCESS:
1760
      has_ok = True
1761
    elif status == constants.OP_STATUS_ERROR:
1762
      errors.MaybeRaise(msg)
1763

    
1764
      if has_ok:
1765
        raise errors.OpExecError("partial failure (opcode %d): %s" %
1766
                                 (idx, msg))
1767

    
1768
      raise errors.OpExecError(str(msg))
1769

    
1770
  # default failure mode
1771
  raise errors.OpExecError(result)
1772

    
1773

    
1774
class JobPollCbBase:
1775
  """Base class for L{GenericPollJob} callbacks.
1776

1777
  """
1778
  def __init__(self):
1779
    """Initializes this class.
1780

1781
    """
1782

    
1783
  def WaitForJobChangeOnce(self, job_id, fields,
1784
                           prev_job_info, prev_log_serial):
1785
    """Waits for changes on a job.
1786

1787
    """
1788
    raise NotImplementedError()
1789

    
1790
  def QueryJobs(self, job_ids, fields):
1791
    """Returns the selected fields for the selected job IDs.
1792

1793
    @type job_ids: list of numbers
1794
    @param job_ids: Job IDs
1795
    @type fields: list of strings
1796
    @param fields: Fields
1797

1798
    """
1799
    raise NotImplementedError()
1800

    
1801

    
1802
class JobPollReportCbBase:
1803
  """Base class for L{GenericPollJob} reporting callbacks.
1804

1805
  """
1806
  def __init__(self):
1807
    """Initializes this class.
1808

1809
    """
1810

    
1811
  def ReportLogMessage(self, job_id, serial, timestamp, log_type, log_msg):
1812
    """Handles a log message.
1813

1814
    """
1815
    raise NotImplementedError()
1816

    
1817
  def ReportNotChanged(self, job_id, status):
1818
    """Called for if a job hasn't changed in a while.
1819

1820
    @type job_id: number
1821
    @param job_id: Job ID
1822
    @type status: string or None
1823
    @param status: Job status if available
1824

1825
    """
1826
    raise NotImplementedError()
1827

    
1828

    
1829
class _LuxiJobPollCb(JobPollCbBase):
1830
  def __init__(self, cl):
1831
    """Initializes this class.
1832

1833
    """
1834
    JobPollCbBase.__init__(self)
1835
    self.cl = cl
1836

    
1837
  def WaitForJobChangeOnce(self, job_id, fields,
1838
                           prev_job_info, prev_log_serial):
1839
    """Waits for changes on a job.
1840

1841
    """
1842
    return self.cl.WaitForJobChangeOnce(job_id, fields,
1843
                                        prev_job_info, prev_log_serial)
1844

    
1845
  def QueryJobs(self, job_ids, fields):
1846
    """Returns the selected fields for the selected job IDs.
1847

1848
    """
1849
    return self.cl.QueryJobs(job_ids, fields)
1850

    
1851

    
1852
class FeedbackFnJobPollReportCb(JobPollReportCbBase):
1853
  def __init__(self, feedback_fn):
1854
    """Initializes this class.
1855

1856
    """
1857
    JobPollReportCbBase.__init__(self)
1858

    
1859
    self.feedback_fn = feedback_fn
1860

    
1861
    assert callable(feedback_fn)
1862

    
1863
  def ReportLogMessage(self, job_id, serial, timestamp, log_type, log_msg):
1864
    """Handles a log message.
1865

1866
    """
1867
    self.feedback_fn((timestamp, log_type, log_msg))
1868

    
1869
  def ReportNotChanged(self, job_id, status):
1870
    """Called if a job hasn't changed in a while.
1871

1872
    """
1873
    # Ignore
1874

    
1875

    
1876
class StdioJobPollReportCb(JobPollReportCbBase):
1877
  def __init__(self):
1878
    """Initializes this class.
1879

1880
    """
1881
    JobPollReportCbBase.__init__(self)
1882

    
1883
    self.notified_queued = False
1884
    self.notified_waitlock = False
1885

    
1886
  def ReportLogMessage(self, job_id, serial, timestamp, log_type, log_msg):
1887
    """Handles a log message.
1888

1889
    """
1890
    ToStdout("%s %s", time.ctime(utils.MergeTime(timestamp)),
1891
             FormatLogMessage(log_type, log_msg))
1892

    
1893
  def ReportNotChanged(self, job_id, status):
1894
    """Called if a job hasn't changed in a while.
1895

1896
    """
1897
    if status is None:
1898
      return
1899

    
1900
    if status == constants.JOB_STATUS_QUEUED and not self.notified_queued:
1901
      ToStderr("Job %s is waiting in queue", job_id)
1902
      self.notified_queued = True
1903

    
1904
    elif status == constants.JOB_STATUS_WAITING and not self.notified_waitlock:
1905
      ToStderr("Job %s is trying to acquire all necessary locks", job_id)
1906
      self.notified_waitlock = True
1907

    
1908

    
1909
def FormatLogMessage(log_type, log_msg):
1910
  """Formats a job message according to its type.
1911

1912
  """
1913
  if log_type != constants.ELOG_MESSAGE:
1914
    log_msg = str(log_msg)
1915

    
1916
  return utils.SafeEncode(log_msg)
1917

    
1918

    
1919
def PollJob(job_id, cl=None, feedback_fn=None, reporter=None):
1920
  """Function to poll for the result of a job.
1921

1922
  @type job_id: job identified
1923
  @param job_id: the job to poll for results
1924
  @type cl: luxi.Client
1925
  @param cl: the luxi client to use for communicating with the master;
1926
             if None, a new client will be created
1927

1928
  """
1929
  if cl is None:
1930
    cl = GetClient()
1931

    
1932
  if reporter is None:
1933
    if feedback_fn:
1934
      reporter = FeedbackFnJobPollReportCb(feedback_fn)
1935
    else:
1936
      reporter = StdioJobPollReportCb()
1937
  elif feedback_fn:
1938
    raise errors.ProgrammerError("Can't specify reporter and feedback function")
1939

    
1940
  return GenericPollJob(job_id, _LuxiJobPollCb(cl), reporter)
1941

    
1942

    
1943
def SubmitOpCode(op, cl=None, feedback_fn=None, opts=None, reporter=None):
1944
  """Legacy function to submit an opcode.
1945

1946
  This is just a simple wrapper over the construction of the processor
1947
  instance. It should be extended to better handle feedback and
1948
  interaction functions.
1949

1950
  """
1951
  if cl is None:
1952
    cl = GetClient()
1953

    
1954
  SetGenericOpcodeOpts([op], opts)
1955

    
1956
  job_id = SendJob([op], cl=cl)
1957

    
1958
  op_results = PollJob(job_id, cl=cl, feedback_fn=feedback_fn,
1959
                       reporter=reporter)
1960

    
1961
  return op_results[0]
1962

    
1963

    
1964
def SubmitOrSend(op, opts, cl=None, feedback_fn=None):
1965
  """Wrapper around SubmitOpCode or SendJob.
1966

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

1972
  It will also process the opcodes if we're sending the via SendJob
1973
  (otherwise SubmitOpCode does it).
1974

1975
  """
1976
  if opts and opts.submit_only:
1977
    job = [op]
1978
    SetGenericOpcodeOpts(job, opts)
1979
    job_id = SendJob(job, cl=cl)
1980
    raise JobSubmittedException(job_id)
1981
  else:
1982
    return SubmitOpCode(op, cl=cl, feedback_fn=feedback_fn, opts=opts)
1983

    
1984

    
1985
def SetGenericOpcodeOpts(opcode_list, options):
1986
  """Processor for generic options.
1987

1988
  This function updates the given opcodes based on generic command
1989
  line options (like debug, dry-run, etc.).
1990

1991
  @param opcode_list: list of opcodes
1992
  @param options: command line options or None
1993
  @return: None (in-place modification)
1994

1995
  """
1996
  if not options:
1997
    return
1998
  for op in opcode_list:
1999
    op.debug_level = options.debug
2000
    if hasattr(options, "dry_run"):
2001
      op.dry_run = options.dry_run
2002
    if getattr(options, "priority", None) is not None:
2003
      op.priority = _PRIONAME_TO_VALUE[options.priority]
2004

    
2005

    
2006
def GetClient():
2007
  # TODO: Cache object?
2008
  try:
2009
    client = luxi.Client()
2010
  except luxi.NoMasterError:
2011
    ss = ssconf.SimpleStore()
2012

    
2013
    # Try to read ssconf file
2014
    try:
2015
      ss.GetMasterNode()
2016
    except errors.ConfigurationError:
2017
      raise errors.OpPrereqError("Cluster not initialized or this machine is"
2018
                                 " not part of a cluster")
2019

    
2020
    master, myself = ssconf.GetMasterAndMyself(ss=ss)
2021
    if master != myself:
2022
      raise errors.OpPrereqError("This is not the master node, please connect"
2023
                                 " to node '%s' and rerun the command" %
2024
                                 master)
2025
    raise
2026
  return client
2027

    
2028

    
2029
def FormatError(err):
2030
  """Return a formatted error message for a given error.
2031

2032
  This function takes an exception instance and returns a tuple
2033
  consisting of two values: first, the recommended exit code, and
2034
  second, a string describing the error message (not
2035
  newline-terminated).
2036

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

    
2116

    
2117
def GenericMain(commands, override=None, aliases=None,
2118
                env_override=frozenset()):
2119
  """Generic main function for all the gnt-* commands.
2120

2121
  @param commands: a dictionary with a special structure, see the design doc
2122
                   for command line handling.
2123
  @param override: if not None, we expect a dictionary with keys that will
2124
                   override command line options; this can be used to pass
2125
                   options from the scripts to generic functions
2126
  @param aliases: dictionary with command aliases {'alias': 'target, ...}
2127
  @param env_override: list of environment names which are allowed to submit
2128
                       default args for commands
2129

2130
  """
2131
  # save the program name and the entire command line for later logging
2132
  if sys.argv:
2133
    binary = os.path.basename(sys.argv[0]) or sys.argv[0]
2134
    if len(sys.argv) >= 2:
2135
      binary += " " + sys.argv[1]
2136
      old_cmdline = " ".join(sys.argv[2:])
2137
    else:
2138
      old_cmdline = ""
2139
  else:
2140
    binary = "<unknown program>"
2141
    old_cmdline = ""
2142

    
2143
  if aliases is None:
2144
    aliases = {}
2145

    
2146
  try:
2147
    func, options, args = _ParseArgs(sys.argv, commands, aliases, env_override)
2148
  except errors.ParameterError, err:
2149
    result, err_msg = FormatError(err)
2150
    ToStderr(err_msg)
2151
    return 1
2152

    
2153
  if func is None: # parse error
2154
    return 1
2155

    
2156
  if override is not None:
2157
    for key, val in override.iteritems():
2158
      setattr(options, key, val)
2159

    
2160
  utils.SetupLogging(constants.LOG_COMMANDS, binary, debug=options.debug,
2161
                     stderr_logging=True)
2162

    
2163
  if old_cmdline:
2164
    logging.info("run with arguments '%s'", old_cmdline)
2165
  else:
2166
    logging.info("run with no arguments")
2167

    
2168
  try:
2169
    result = func(options, args)
2170
  except (errors.GenericError, luxi.ProtocolError,
2171
          JobSubmittedException), err:
2172
    result, err_msg = FormatError(err)
2173
    logging.exception("Error during command processing")
2174
    ToStderr(err_msg)
2175
  except KeyboardInterrupt:
2176
    result = constants.EXIT_FAILURE
2177
    ToStderr("Aborted. Note that if the operation created any jobs, they"
2178
             " might have been submitted and"
2179
             " will continue to run in the background.")
2180
  except IOError, err:
2181
    if err.errno == errno.EPIPE:
2182
      # our terminal went away, we'll exit
2183
      sys.exit(constants.EXIT_FAILURE)
2184
    else:
2185
      raise
2186

    
2187
  return result
2188

    
2189

    
2190
def ParseNicOption(optvalue):
2191
  """Parses the value of the --net option(s).
2192

2193
  """
2194
  try:
2195
    nic_max = max(int(nidx[0]) + 1 for nidx in optvalue)
2196
  except (TypeError, ValueError), err:
2197
    raise errors.OpPrereqError("Invalid NIC index passed: %s" % str(err))
2198

    
2199
  nics = [{}] * nic_max
2200
  for nidx, ndict in optvalue:
2201
    nidx = int(nidx)
2202

    
2203
    if not isinstance(ndict, dict):
2204
      raise errors.OpPrereqError("Invalid nic/%d value: expected dict,"
2205
                                 " got %s" % (nidx, ndict))
2206

    
2207
    utils.ForceDictType(ndict, constants.INIC_PARAMS_TYPES)
2208

    
2209
    nics[nidx] = ndict
2210

    
2211
  return nics
2212

    
2213

    
2214
def GenericInstanceCreate(mode, opts, args):
2215
  """Add an instance to the cluster via either creation or import.
2216

2217
  @param mode: constants.INSTANCE_CREATE or constants.INSTANCE_IMPORT
2218
  @param opts: the command line options selected by the user
2219
  @type args: list
2220
  @param args: should contain only one element, the new instance name
2221
  @rtype: int
2222
  @return: the desired exit code
2223

2224
  """
2225
  instance = args[0]
2226

    
2227
  (pnode, snode) = SplitNodeOption(opts.node)
2228

    
2229
  hypervisor = None
2230
  hvparams = {}
2231
  if opts.hypervisor:
2232
    hypervisor, hvparams = opts.hypervisor
2233

    
2234
  if opts.nics:
2235
    nics = ParseNicOption(opts.nics)
2236
  elif opts.no_nics:
2237
    # no nics
2238
    nics = []
2239
  elif mode == constants.INSTANCE_CREATE:
2240
    # default of one nic, all auto
2241
    nics = [{}]
2242
  else:
2243
    # mode == import
2244
    nics = []
2245

    
2246
  if opts.disk_template == constants.DT_DISKLESS:
2247
    if opts.disks or opts.sd_size is not None:
2248
      raise errors.OpPrereqError("Diskless instance but disk"
2249
                                 " information passed")
2250
    disks = []
2251
  else:
2252
    if (not opts.disks and not opts.sd_size
2253
        and mode == constants.INSTANCE_CREATE):
2254
      raise errors.OpPrereqError("No disk information specified")
2255
    if opts.disks and opts.sd_size is not None:
2256
      raise errors.OpPrereqError("Please use either the '--disk' or"
2257
                                 " '-s' option")
2258
    if opts.sd_size is not None:
2259
      opts.disks = [(0, {constants.IDISK_SIZE: opts.sd_size})]
2260

    
2261
    if opts.disks:
2262
      try:
2263
        disk_max = max(int(didx[0]) + 1 for didx in opts.disks)
2264
      except ValueError, err:
2265
        raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err))
2266
      disks = [{}] * disk_max
2267
    else:
2268
      disks = []
2269
    for didx, ddict in opts.disks:
2270
      didx = int(didx)
2271
      if not isinstance(ddict, dict):
2272
        msg = "Invalid disk/%d value: expected dict, got %s" % (didx, ddict)
2273
        raise errors.OpPrereqError(msg)
2274
      elif constants.IDISK_SIZE in ddict:
2275
        if constants.IDISK_ADOPT in ddict:
2276
          raise errors.OpPrereqError("Only one of 'size' and 'adopt' allowed"
2277
                                     " (disk %d)" % didx)
2278
        try:
2279
          ddict[constants.IDISK_SIZE] = \
2280
            utils.ParseUnit(ddict[constants.IDISK_SIZE])
2281
        except ValueError, err:
2282
          raise errors.OpPrereqError("Invalid disk size for disk %d: %s" %
2283
                                     (didx, err))
2284
      elif constants.IDISK_ADOPT in ddict:
2285
        if mode == constants.INSTANCE_IMPORT:
2286
          raise errors.OpPrereqError("Disk adoption not allowed for instance"
2287
                                     " import")
2288
        ddict[constants.IDISK_SIZE] = 0
2289
      else:
2290
        raise errors.OpPrereqError("Missing size or adoption source for"
2291
                                   " disk %d" % didx)
2292
      disks[didx] = ddict
2293

    
2294
  if opts.tags is not None:
2295
    tags = opts.tags.split(",")
2296
  else:
2297
    tags = []
2298

    
2299
  utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_COMPAT)
2300
  utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
2301

    
2302
  if mode == constants.INSTANCE_CREATE:
2303
    start = opts.start
2304
    os_type = opts.os
2305
    force_variant = opts.force_variant
2306
    src_node = None
2307
    src_path = None
2308
    no_install = opts.no_install
2309
    identify_defaults = False
2310
  elif mode == constants.INSTANCE_IMPORT:
2311
    start = False
2312
    os_type = None
2313
    force_variant = False
2314
    src_node = opts.src_node
2315
    src_path = opts.src_dir
2316
    no_install = None
2317
    identify_defaults = opts.identify_defaults
2318
  else:
2319
    raise errors.ProgrammerError("Invalid creation mode %s" % mode)
2320

    
2321
  op = opcodes.OpInstanceCreate(instance_name=instance,
2322
                                disks=disks,
2323
                                disk_template=opts.disk_template,
2324
                                nics=nics,
2325
                                pnode=pnode, snode=snode,
2326
                                ip_check=opts.ip_check,
2327
                                name_check=opts.name_check,
2328
                                wait_for_sync=opts.wait_for_sync,
2329
                                file_storage_dir=opts.file_storage_dir,
2330
                                file_driver=opts.file_driver,
2331
                                iallocator=opts.iallocator,
2332
                                hypervisor=hypervisor,
2333
                                hvparams=hvparams,
2334
                                beparams=opts.beparams,
2335
                                osparams=opts.osparams,
2336
                                mode=mode,
2337
                                start=start,
2338
                                os_type=os_type,
2339
                                force_variant=force_variant,
2340
                                src_node=src_node,
2341
                                src_path=src_path,
2342
                                tags=tags,
2343
                                no_install=no_install,
2344
                                identify_defaults=identify_defaults,
2345
                                ignore_ipolicy=opts.ignore_ipolicy)
2346

    
2347
  SubmitOrSend(op, opts)
2348
  return 0
2349

    
2350

    
2351
class _RunWhileClusterStoppedHelper:
2352
  """Helper class for L{RunWhileClusterStopped} to simplify state management
2353

2354
  """
2355
  def __init__(self, feedback_fn, cluster_name, master_node, online_nodes):
2356
    """Initializes this class.
2357

2358
    @type feedback_fn: callable
2359
    @param feedback_fn: Feedback function
2360
    @type cluster_name: string
2361
    @param cluster_name: Cluster name
2362
    @type master_node: string
2363
    @param master_node Master node name
2364
    @type online_nodes: list
2365
    @param online_nodes: List of names of online nodes
2366

2367
    """
2368
    self.feedback_fn = feedback_fn
2369
    self.cluster_name = cluster_name
2370
    self.master_node = master_node
2371
    self.online_nodes = online_nodes
2372

    
2373
    self.ssh = ssh.SshRunner(self.cluster_name)
2374

    
2375
    self.nonmaster_nodes = [name for name in online_nodes
2376
                            if name != master_node]
2377

    
2378
    assert self.master_node not in self.nonmaster_nodes
2379

    
2380
  def _RunCmd(self, node_name, cmd):
2381
    """Runs a command on the local or a remote machine.
2382

2383
    @type node_name: string
2384
    @param node_name: Machine name
2385
    @type cmd: list
2386
    @param cmd: Command
2387

2388
    """
2389
    if node_name is None or node_name == self.master_node:
2390
      # No need to use SSH
2391
      result = utils.RunCmd(cmd)
2392
    else:
2393
      result = self.ssh.Run(node_name, "root", utils.ShellQuoteArgs(cmd))
2394

    
2395
    if result.failed:
2396
      errmsg = ["Failed to run command %s" % result.cmd]
2397
      if node_name:
2398
        errmsg.append("on node %s" % node_name)
2399
      errmsg.append(": exitcode %s and error %s" %
2400
                    (result.exit_code, result.output))
2401
      raise errors.OpExecError(" ".join(errmsg))
2402

    
2403
  def Call(self, fn, *args):
2404
    """Call function while all daemons are stopped.
2405

2406
    @type fn: callable
2407
    @param fn: Function to be called
2408

2409
    """
2410
    # Pause watcher by acquiring an exclusive lock on watcher state file
2411
    self.feedback_fn("Blocking watcher")
2412
    watcher_block = utils.FileLock.Open(constants.WATCHER_LOCK_FILE)
2413
    try:
2414
      # TODO: Currently, this just blocks. There's no timeout.
2415
      # TODO: Should it be a shared lock?
2416
      watcher_block.Exclusive(blocking=True)
2417

    
2418
      # Stop master daemons, so that no new jobs can come in and all running
2419
      # ones are finished
2420
      self.feedback_fn("Stopping master daemons")
2421
      self._RunCmd(None, [constants.DAEMON_UTIL, "stop-master"])
2422
      try:
2423
        # Stop daemons on all nodes
2424
        for node_name in self.online_nodes:
2425
          self.feedback_fn("Stopping daemons on %s" % node_name)
2426
          self._RunCmd(node_name, [constants.DAEMON_UTIL, "stop-all"])
2427

    
2428
        # All daemons are shut down now
2429
        try:
2430
          return fn(self, *args)
2431
        except Exception, err:
2432
          _, errmsg = FormatError(err)
2433
          logging.exception("Caught exception")
2434
          self.feedback_fn(errmsg)
2435
          raise
2436
      finally:
2437
        # Start cluster again, master node last
2438
        for node_name in self.nonmaster_nodes + [self.master_node]:
2439
          self.feedback_fn("Starting daemons on %s" % node_name)
2440
          self._RunCmd(node_name, [constants.DAEMON_UTIL, "start-all"])
2441
    finally:
2442
      # Resume watcher
2443
      watcher_block.Close()
2444

    
2445

    
2446
def RunWhileClusterStopped(feedback_fn, fn, *args):
2447
  """Calls a function while all cluster daemons are stopped.
2448

2449
  @type feedback_fn: callable
2450
  @param feedback_fn: Feedback function
2451
  @type fn: callable
2452
  @param fn: Function to be called when daemons are stopped
2453

2454
  """
2455
  feedback_fn("Gathering cluster information")
2456

    
2457
  # This ensures we're running on the master daemon
2458
  cl = GetClient()
2459

    
2460
  (cluster_name, master_node) = \
2461
    cl.QueryConfigValues(["cluster_name", "master_node"])
2462

    
2463
  online_nodes = GetOnlineNodes([], cl=cl)
2464

    
2465
  # Don't keep a reference to the client. The master daemon will go away.
2466
  del cl
2467

    
2468
  assert master_node in online_nodes
2469

    
2470
  return _RunWhileClusterStoppedHelper(feedback_fn, cluster_name, master_node,
2471
                                       online_nodes).Call(fn, *args)
2472

    
2473

    
2474
def GenerateTable(headers, fields, separator, data,
2475
                  numfields=None, unitfields=None,
2476
                  units=None):
2477
  """Prints a table with headers and different fields.
2478

2479
  @type headers: dict
2480
  @param headers: dictionary mapping field names to headers for
2481
      the table
2482
  @type fields: list
2483
  @param fields: the field names corresponding to each row in
2484
      the data field
2485
  @param separator: the separator to be used; if this is None,
2486
      the default 'smart' algorithm is used which computes optimal
2487
      field width, otherwise just the separator is used between
2488
      each field
2489
  @type data: list
2490
  @param data: a list of lists, each sublist being one row to be output
2491
  @type numfields: list
2492
  @param numfields: a list with the fields that hold numeric
2493
      values and thus should be right-aligned
2494
  @type unitfields: list
2495
  @param unitfields: a list with the fields that hold numeric
2496
      values that should be formatted with the units field
2497
  @type units: string or None
2498
  @param units: the units we should use for formatting, or None for
2499
      automatic choice (human-readable for non-separator usage, otherwise
2500
      megabytes); this is a one-letter string
2501

2502
  """
2503
  if units is None:
2504
    if separator:
2505
      units = "m"
2506
    else:
2507
      units = "h"
2508

    
2509
  if numfields is None:
2510
    numfields = []
2511
  if unitfields is None:
2512
    unitfields = []
2513

    
2514
  numfields = utils.FieldSet(*numfields)   # pylint: disable=W0142
2515
  unitfields = utils.FieldSet(*unitfields) # pylint: disable=W0142
2516

    
2517
  format_fields = []
2518
  for field in fields:
2519
    if headers and field not in headers:
2520
      # TODO: handle better unknown fields (either revert to old
2521
      # style of raising exception, or deal more intelligently with
2522
      # variable fields)
2523
      headers[field] = field
2524
    if separator is not None:
2525
      format_fields.append("%s")
2526
    elif numfields.Matches(field):
2527
      format_fields.append("%*s")
2528
    else:
2529
      format_fields.append("%-*s")
2530

    
2531
  if separator is None:
2532
    mlens = [0 for name in fields]
2533
    format_str = " ".join(format_fields)
2534
  else:
2535
    format_str = separator.replace("%", "%%").join(format_fields)
2536

    
2537
  for row in data:
2538
    if row is None:
2539
      continue
2540
    for idx, val in enumerate(row):
2541
      if unitfields.Matches(fields[idx]):
2542
        try:
2543
          val = int(val)
2544
        except (TypeError, ValueError):
2545
          pass
2546
        else:
2547
          val = row[idx] = utils.FormatUnit(val, units)
2548
      val = row[idx] = str(val)
2549
      if separator is None:
2550
        mlens[idx] = max(mlens[idx], len(val))
2551

    
2552
  result = []
2553
  if headers:
2554
    args = []
2555
    for idx, name in enumerate(fields):
2556
      hdr = headers[name]
2557
      if separator is None:
2558
        mlens[idx] = max(mlens[idx], len(hdr))
2559
        args.append(mlens[idx])
2560
      args.append(hdr)
2561
    result.append(format_str % tuple(args))
2562

    
2563
  if separator is None:
2564
    assert len(mlens) == len(fields)
2565

    
2566
    if fields and not numfields.Matches(fields[-1]):
2567
      mlens[-1] = 0
2568

    
2569
  for line in data:
2570
    args = []
2571
    if line is None:
2572
      line = ["-" for _ in fields]
2573
    for idx in range(len(fields)):
2574
      if separator is None:
2575
        args.append(mlens[idx])
2576
      args.append(line[idx])
2577
    result.append(format_str % tuple(args))
2578

    
2579
  return result
2580

    
2581

    
2582
def _FormatBool(value):
2583
  """Formats a boolean value as a string.
2584

2585
  """
2586
  if value:
2587
    return "Y"
2588
  return "N"
2589

    
2590

    
2591
#: Default formatting for query results; (callback, align right)
2592
_DEFAULT_FORMAT_QUERY = {
2593
  constants.QFT_TEXT: (str, False),
2594
  constants.QFT_BOOL: (_FormatBool, False),
2595
  constants.QFT_NUMBER: (str, True),
2596
  constants.QFT_TIMESTAMP: (utils.FormatTime, False),
2597
  constants.QFT_OTHER: (str, False),
2598
  constants.QFT_UNKNOWN: (str, False),
2599
  }
2600

    
2601

    
2602
def _GetColumnFormatter(fdef, override, unit):
2603
  """Returns formatting function for a field.
2604

2605
  @type fdef: L{objects.QueryFieldDefinition}
2606
  @type override: dict
2607
  @param override: Dictionary for overriding field formatting functions,
2608
    indexed by field name, contents like L{_DEFAULT_FORMAT_QUERY}
2609
  @type unit: string
2610
  @param unit: Unit used for formatting fields of type L{constants.QFT_UNIT}
2611
  @rtype: tuple; (callable, bool)
2612
  @return: Returns the function to format a value (takes one parameter) and a
2613
    boolean for aligning the value on the right-hand side
2614

2615
  """
2616
  fmt = override.get(fdef.name, None)
2617
  if fmt is not None:
2618
    return fmt
2619

    
2620
  assert constants.QFT_UNIT not in _DEFAULT_FORMAT_QUERY
2621

    
2622
  if fdef.kind == constants.QFT_UNIT:
2623
    # Can't keep this information in the static dictionary
2624
    return (lambda value: utils.FormatUnit(value, unit), True)
2625

    
2626
  fmt = _DEFAULT_FORMAT_QUERY.get(fdef.kind, None)
2627
  if fmt is not None:
2628
    return fmt
2629

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

    
2632

    
2633
class _QueryColumnFormatter:
2634
  """Callable class for formatting fields of a query.
2635

2636
  """
2637
  def __init__(self, fn, status_fn, verbose):
2638
    """Initializes this class.
2639

2640
    @type fn: callable
2641
    @param fn: Formatting function
2642
    @type status_fn: callable
2643
    @param status_fn: Function to report fields' status
2644
    @type verbose: boolean
2645
    @param verbose: whether to use verbose field descriptions or not
2646

2647
    """
2648
    self._fn = fn
2649
    self._status_fn = status_fn
2650
    self._verbose = verbose
2651

    
2652
  def __call__(self, data):
2653
    """Returns a field's string representation.
2654

2655
    """
2656
    (status, value) = data
2657

    
2658
    # Report status
2659
    self._status_fn(status)
2660

    
2661
    if status == constants.RS_NORMAL:
2662
      return self._fn(value)
2663

    
2664
    assert value is None, \
2665
           "Found value %r for abnormal status %s" % (value, status)
2666

    
2667
    return FormatResultError(status, self._verbose)
2668

    
2669

    
2670
def FormatResultError(status, verbose):
2671
  """Formats result status other than L{constants.RS_NORMAL}.
2672

2673
  @param status: The result status
2674
  @type verbose: boolean
2675
  @param verbose: Whether to return the verbose text
2676
  @return: Text of result status
2677

2678
  """
2679
  assert status != constants.RS_NORMAL, \
2680
         "FormatResultError called with status equal to constants.RS_NORMAL"
2681
  try:
2682
    (verbose_text, normal_text) = constants.RSS_DESCRIPTION[status]
2683
  except KeyError:
2684
    raise NotImplementedError("Unknown status %s" % status)
2685
  else:
2686
    if verbose:
2687
      return verbose_text
2688
    return normal_text
2689

    
2690

    
2691
def FormatQueryResult(result, unit=None, format_override=None, separator=None,
2692
                      header=False, verbose=False):
2693
  """Formats data in L{objects.QueryResponse}.
2694

2695
  @type result: L{objects.QueryResponse}
2696
  @param result: result of query operation
2697
  @type unit: string
2698
  @param unit: Unit used for formatting fields of type L{constants.QFT_UNIT},
2699
    see L{utils.text.FormatUnit}
2700
  @type format_override: dict
2701
  @param format_override: Dictionary for overriding field formatting functions,
2702
    indexed by field name, contents like L{_DEFAULT_FORMAT_QUERY}
2703
  @type separator: string or None
2704
  @param separator: String used to separate fields
2705
  @type header: bool
2706
  @param header: Whether to output header row
2707
  @type verbose: boolean
2708
  @param verbose: whether to use verbose field descriptions or not
2709

2710
  """
2711
  if unit is None:
2712
    if separator:
2713
      unit = "m"
2714
    else:
2715
      unit = "h"
2716

    
2717
  if format_override is None:
2718
    format_override = {}
2719

    
2720
  stats = dict.fromkeys(constants.RS_ALL, 0)
2721

    
2722
  def _RecordStatus(status):
2723
    if status in stats:
2724
      stats[status] += 1
2725

    
2726
  columns = []
2727
  for fdef in result.fields:
2728
    assert fdef.title and fdef.name
2729
    (fn, align_right) = _GetColumnFormatter(fdef, format_override, unit)
2730
    columns.append(TableColumn(fdef.title,
2731
                               _QueryColumnFormatter(fn, _RecordStatus,
2732
                                                     verbose),
2733
                               align_right))
2734

    
2735
  table = FormatTable(result.data, columns, header, separator)
2736

    
2737
  # Collect statistics
2738
  assert len(stats) == len(constants.RS_ALL)
2739
  assert compat.all(count >= 0 for count in stats.values())
2740

    
2741
  # Determine overall status. If there was no data, unknown fields must be
2742
  # detected via the field definitions.
2743
  if (stats[constants.RS_UNKNOWN] or
2744
      (not result.data and _GetUnknownFields(result.fields))):
2745
    status = QR_UNKNOWN
2746
  elif compat.any(count > 0 for key, count in stats.items()
2747
                  if key != constants.RS_NORMAL):
2748
    status = QR_INCOMPLETE
2749
  else:
2750
    status = QR_NORMAL
2751

    
2752
  return (status, table)
2753

    
2754

    
2755
def _GetUnknownFields(fdefs):
2756
  """Returns list of unknown fields included in C{fdefs}.
2757

2758
  @type fdefs: list of L{objects.QueryFieldDefinition}
2759

2760
  """
2761
  return [fdef for fdef in fdefs
2762
          if fdef.kind == constants.QFT_UNKNOWN]
2763

    
2764

    
2765
def _WarnUnknownFields(fdefs):
2766
  """Prints a warning to stderr if a query included unknown fields.
2767

2768
  @type fdefs: list of L{objects.QueryFieldDefinition}
2769

2770
  """
2771
  unknown = _GetUnknownFields(fdefs)
2772
  if unknown:
2773
    ToStderr("Warning: Queried for unknown fields %s",
2774
             utils.CommaJoin(fdef.name for fdef in unknown))
2775
    return True
2776

    
2777
  return False
2778

    
2779

    
2780
def GenericList(resource, fields, names, unit, separator, header, cl=None,
2781
                format_override=None, verbose=False, force_filter=False):
2782
  """Generic implementation for listing all items of a resource.
2783

2784
  @param resource: One of L{constants.QR_VIA_LUXI}
2785
  @type fields: list of strings
2786
  @param fields: List of fields to query for
2787
  @type names: list of strings
2788
  @param names: Names of items to query for
2789
  @type unit: string or None
2790
  @param unit: Unit used for formatting fields of type L{constants.QFT_UNIT} or
2791
    None for automatic choice (human-readable for non-separator usage,
2792
    otherwise megabytes); this is a one-letter string
2793
  @type separator: string or None
2794
  @param separator: String used to separate fields
2795
  @type header: bool
2796
  @param header: Whether to show header row
2797
  @type force_filter: bool
2798
  @param force_filter: Whether to always treat names as filter
2799
  @type format_override: dict
2800
  @param format_override: Dictionary for overriding field formatting functions,
2801
    indexed by field name, contents like L{_DEFAULT_FORMAT_QUERY}
2802
  @type verbose: boolean
2803
  @param verbose: whether to use verbose field descriptions or not
2804

2805
  """
2806
  if not names:
2807
    names = None
2808

    
2809
  qfilter = qlang.MakeFilter(names, force_filter)
2810

    
2811
  if cl is None:
2812
    cl = GetClient()
2813

    
2814
  response = cl.Query(resource, fields, qfilter)
2815

    
2816
  found_unknown = _WarnUnknownFields(response.fields)
2817

    
2818
  (status, data) = FormatQueryResult(response, unit=unit, separator=separator,
2819
                                     header=header,
2820
                                     format_override=format_override,
2821
                                     verbose=verbose)
2822

    
2823
  for line in data:
2824
    ToStdout(line)
2825

    
2826
  assert ((found_unknown and status == QR_UNKNOWN) or
2827
          (not found_unknown and status != QR_UNKNOWN))
2828

    
2829
  if status == QR_UNKNOWN:
2830
    return constants.EXIT_UNKNOWN_FIELD
2831

    
2832
  # TODO: Should the list command fail if not all data could be collected?
2833
  return constants.EXIT_SUCCESS
2834

    
2835

    
2836
def GenericListFields(resource, fields, separator, header, cl=None):
2837
  """Generic implementation for listing fields for a resource.
2838

2839
  @param resource: One of L{constants.QR_VIA_LUXI}
2840
  @type fields: list of strings
2841
  @param fields: List of fields to query for
2842
  @type separator: string or None
2843
  @param separator: String used to separate fields
2844
  @type header: bool
2845
  @param header: Whether to show header row
2846

2847
  """
2848
  if cl is None:
2849
    cl = GetClient()
2850

    
2851
  if not fields:
2852
    fields = None
2853

    
2854
  response = cl.QueryFields(resource, fields)
2855

    
2856
  found_unknown = _WarnUnknownFields(response.fields)
2857

    
2858
  columns = [
2859
    TableColumn("Name", str, False),
2860
    TableColumn("Title", str, False),
2861
    TableColumn("Description", str, False),
2862
    ]
2863

    
2864
  rows = [[fdef.name, fdef.title, fdef.doc] for fdef in response.fields]
2865

    
2866
  for line in FormatTable(rows, columns, header, separator):
2867
    ToStdout(line)
2868

    
2869
  if found_unknown:
2870
    return constants.EXIT_UNKNOWN_FIELD
2871

    
2872
  return constants.EXIT_SUCCESS
2873

    
2874

    
2875
class TableColumn:
2876
  """Describes a column for L{FormatTable}.
2877

2878
  """
2879
  def __init__(self, title, fn, align_right):
2880
    """Initializes this class.
2881

2882
    @type title: string
2883
    @param title: Column title
2884
    @type fn: callable
2885
    @param fn: Formatting function
2886
    @type align_right: bool
2887
    @param align_right: Whether to align values on the right-hand side
2888

2889
    """
2890
    self.title = title
2891
    self.format = fn
2892
    self.align_right = align_right
2893

    
2894

    
2895
def _GetColFormatString(width, align_right):
2896
  """Returns the format string for a field.
2897

2898
  """
2899
  if align_right:
2900
    sign = ""
2901
  else:
2902
    sign = "-"
2903

    
2904
  return "%%%s%ss" % (sign, width)
2905

    
2906

    
2907
def FormatTable(rows, columns, header, separator):
2908
  """Formats data as a table.
2909

2910
  @type rows: list of lists
2911
  @param rows: Row data, one list per row
2912
  @type columns: list of L{TableColumn}
2913
  @param columns: Column descriptions
2914
  @type header: bool
2915
  @param header: Whether to show header row
2916
  @type separator: string or None
2917
  @param separator: String used to separate columns
2918

2919
  """
2920
  if header:
2921
    data = [[col.title for col in columns]]
2922
    colwidth = [len(col.title) for col in columns]
2923
  else:
2924
    data = []
2925
    colwidth = [0 for _ in columns]
2926

    
2927
  # Format row data
2928
  for row in rows:
2929
    assert len(row) == len(columns)
2930

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

    
2933
    if separator is None:
2934
      # Update column widths
2935
      for idx, (oldwidth, value) in enumerate(zip(colwidth, formatted)):
2936
        # Modifying a list's items while iterating is fine
2937
        colwidth[idx] = max(oldwidth, len(value))
2938

    
2939
    data.append(formatted)
2940

    
2941
  if separator is not None:
2942
    # Return early if a separator is used
2943
    return [separator.join(row) for row in data]
2944

    
2945
  if columns and not columns[-1].align_right:
2946
    # Avoid unnecessary spaces at end of line
2947
    colwidth[-1] = 0
2948

    
2949
  # Build format string
2950
  fmt = " ".join([_GetColFormatString(width, col.align_right)
2951
                  for col, width in zip(columns, colwidth)])
2952

    
2953
  return [fmt % tuple(row) for row in data]
2954

    
2955

    
2956
def FormatTimestamp(ts):
2957
  """Formats a given timestamp.
2958

2959
  @type ts: timestamp
2960
  @param ts: a timeval-type timestamp, a tuple of seconds and microseconds
2961

2962
  @rtype: string
2963
  @return: a string with the formatted timestamp
2964

2965
  """
2966
  if not isinstance(ts, (tuple, list)) or len(ts) != 2:
2967
    return "?"
2968
  sec, usec = ts
2969
  return time.strftime("%F %T", time.localtime(sec)) + ".%06d" % usec
2970

    
2971

    
2972
def ParseTimespec(value):
2973
  """Parse a time specification.
2974

2975
  The following suffixed will be recognized:
2976

2977
    - s: seconds
2978
    - m: minutes
2979
    - h: hours
2980
    - d: day
2981
    - w: weeks
2982

2983
  Without any suffix, the value will be taken to be in seconds.
2984

2985
  """
2986
  value = str(value)
2987
  if not value:
2988
    raise errors.OpPrereqError("Empty time specification passed")
2989
  suffix_map = {
2990
    "s": 1,
2991
    "m": 60,
2992
    "h": 3600,
2993
    "d": 86400,
2994
    "w": 604800,
2995
    }
2996
  if value[-1] not in suffix_map:
2997
    try:
2998
      value = int(value)
2999
    except (TypeError, ValueError):
3000
      raise errors.OpPrereqError("Invalid time specification '%s'" % value)
3001
  else:
3002
    multiplier = suffix_map[value[-1]]
3003
    value = value[:-1]
3004
    if not value: # no data left after stripping the suffix
3005
      raise errors.OpPrereqError("Invalid time specification (only"
3006
                                 " suffix passed)")
3007
    try:
3008
      value = int(value) * multiplier
3009
    except (TypeError, ValueError):
3010
      raise errors.OpPrereqError("Invalid time specification '%s'" % value)
3011
  return value
3012

    
3013

    
3014
def GetOnlineNodes(nodes, cl=None, nowarn=False, secondary_ips=False,
3015
                   filter_master=False, nodegroup=None):
3016
  """Returns the names of online nodes.
3017

3018
  This function will also log a warning on stderr with the names of
3019
  the online nodes.
3020

3021
  @param nodes: if not empty, use only this subset of nodes (minus the
3022
      offline ones)
3023
  @param cl: if not None, luxi client to use
3024
  @type nowarn: boolean
3025
  @param nowarn: by default, this function will output a note with the
3026
      offline nodes that are skipped; if this parameter is True the
3027
      note is not displayed
3028
  @type secondary_ips: boolean
3029
  @param secondary_ips: if True, return the secondary IPs instead of the
3030
      names, useful for doing network traffic over the replication interface
3031
      (if any)
3032
  @type filter_master: boolean
3033
  @param filter_master: if True, do not return the master node in the list
3034
      (useful in coordination with secondary_ips where we cannot check our
3035
      node name against the list)
3036
  @type nodegroup: string
3037
  @param nodegroup: If set, only return nodes in this node group
3038

3039
  """
3040
  if cl is None:
3041
    cl = GetClient()
3042

    
3043
  qfilter = []
3044

    
3045
  if nodes:
3046
    qfilter.append(qlang.MakeSimpleFilter("name", nodes))
3047

    
3048
  if nodegroup is not None:
3049
    qfilter.append([qlang.OP_OR, [qlang.OP_EQUAL, "group", nodegroup],
3050
                                 [qlang.OP_EQUAL, "group.uuid", nodegroup]])
3051

    
3052
  if filter_master:
3053
    qfilter.append([qlang.OP_NOT, [qlang.OP_TRUE, "master"]])
3054

    
3055
  if qfilter:
3056
    if len(qfilter) > 1:
3057
      final_filter = [qlang.OP_AND] + qfilter
3058
    else:
3059
      assert len(qfilter) == 1
3060
      final_filter = qfilter[0]
3061
  else:
3062
    final_filter = None
3063

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

    
3066
  def _IsOffline(row):
3067
    (_, (_, offline), _) = row
3068
    return offline
3069

    
3070
  def _GetName(row):
3071
    ((_, name), _, _) = row
3072
    return name
3073

    
3074
  def _GetSip(row):
3075
    (_, _, (_, sip)) = row
3076
    return sip
3077

    
3078
  (offline, online) = compat.partition(result.data, _IsOffline)
3079

    
3080
  if offline and not nowarn:
3081
    ToStderr("Note: skipping offline node(s): %s" %
3082
             utils.CommaJoin(map(_GetName, offline)))
3083

    
3084
  if secondary_ips:
3085
    fn = _GetSip
3086
  else:
3087
    fn = _GetName
3088

    
3089
  return map(fn, online)
3090

    
3091

    
3092
def _ToStream(stream, txt, *args):
3093
  """Write a message to a stream, bypassing the logging system
3094

3095
  @type stream: file object
3096
  @param stream: the file to which we should write
3097
  @type txt: str
3098
  @param txt: the message
3099

3100
  """
3101
  try:
3102
    if args:
3103
      args = tuple(args)
3104
      stream.write(txt % args)
3105
    else:
3106
      stream.write(txt)
3107
    stream.write("\n")
3108
    stream.flush()
3109
  except IOError, err:
3110
    if err.errno == errno.EPIPE:
3111
      # our terminal went away, we'll exit
3112
      sys.exit(constants.EXIT_FAILURE)
3113
    else:
3114
      raise
3115

    
3116

    
3117
def ToStdout(txt, *args):
3118
  """Write a message to stdout only, bypassing the logging system
3119

3120
  This is just a wrapper over _ToStream.
3121

3122
  @type txt: str
3123
  @param txt: the message
3124

3125
  """
3126
  _ToStream(sys.stdout, txt, *args)
3127

    
3128

    
3129
def ToStderr(txt, *args):
3130
  """Write a message to stderr only, bypassing the logging system
3131

3132
  This is just a wrapper over _ToStream.
3133

3134
  @type txt: str
3135
  @param txt: the message
3136

3137
  """
3138
  _ToStream(sys.stderr, txt, *args)
3139

    
3140

    
3141
class JobExecutor(object):
3142
  """Class which manages the submission and execution of multiple jobs.
3143

3144
  Note that instances of this class should not be reused between
3145
  GetResults() calls.
3146

3147
  """
3148
  def __init__(self, cl=None, verbose=True, opts=None, feedback_fn=None):
3149
    self.queue = []
3150
    if cl is None:
3151
      cl = GetClient()
3152
    self.cl = cl
3153
    self.verbose = verbose
3154
    self.jobs = []
3155
    self.opts = opts
3156
    self.feedback_fn = feedback_fn
3157
    self._counter = itertools.count()
3158

    
3159
  @staticmethod
3160
  def _IfName(name, fmt):
3161
    """Helper function for formatting name.
3162

3163
    """
3164
    if name:
3165
      return fmt % name
3166

    
3167
    return ""
3168

    
3169
  def QueueJob(self, name, *ops):
3170
    """Record a job for later submit.
3171

3172
    @type name: string
3173
    @param name: a description of the job, will be used in WaitJobSet
3174

3175
    """
3176
    SetGenericOpcodeOpts(ops, self.opts)
3177
    self.queue.append((self._counter.next(), name, ops))
3178

    
3179
  def AddJobId(self, name, status, job_id):
3180
    """Adds a job ID to the internal queue.
3181

3182
    """
3183
    self.jobs.append((self._counter.next(), status, job_id, name))
3184

    
3185
  def SubmitPending(self, each=False):
3186
    """Submit all pending jobs.
3187

3188
    """
3189
    if each:
3190
      results = []
3191
      for (_, _, ops) in self.queue:
3192
        # SubmitJob will remove the success status, but raise an exception if
3193
        # the submission fails, so we'll notice that anyway.
3194
        results.append([True, self.cl.SubmitJob(ops)[0]])
3195
    else:
3196
      results = self.cl.SubmitManyJobs([ops for (_, _, ops) in self.queue])
3197
    for ((status, data), (idx, name, _)) in zip(results, self.queue):
3198
      self.jobs.append((idx, status, data, name))
3199

    
3200
  def _ChooseJob(self):
3201
    """Choose a non-waiting/queued job to poll next.
3202

3203
    """
3204
    assert self.jobs, "_ChooseJob called with empty job list"
3205

    
3206
    result = self.cl.QueryJobs([i[2] for i in self.jobs[:_CHOOSE_BATCH]],
3207
                               ["status"])
3208
    assert result
3209

    
3210
    for job_data, status in zip(self.jobs, result):
3211
      if (isinstance(status, list) and status and
3212
          status[0] in (constants.JOB_STATUS_QUEUED,
3213
                        constants.JOB_STATUS_WAITING,
3214
                        constants.JOB_STATUS_CANCELING)):
3215
        # job is still present and waiting
3216
        continue
3217
      # good candidate found (either running job or lost job)
3218
      self.jobs.remove(job_data)
3219
      return job_data
3220

    
3221
    # no job found
3222
    return self.jobs.pop(0)
3223

    
3224
  def GetResults(self):
3225
    """Wait for and return the results of all jobs.
3226

3227
    @rtype: list
3228
    @return: list of tuples (success, job results), in the same order
3229
        as the submitted jobs; if a job has failed, instead of the result
3230
        there will be the error message
3231

3232
    """
3233
    if not self.jobs:
3234
      self.SubmitPending()
3235
    results = []
3236
    if self.verbose:
3237
      ok_jobs = [row[2] for row in self.jobs if row[1]]
3238
      if ok_jobs:
3239
        ToStdout("Submitted jobs %s", utils.CommaJoin(ok_jobs))
3240

    
3241
    # first, remove any non-submitted jobs
3242
    self.jobs, failures = compat.partition(self.jobs, lambda x: x[1])
3243
    for idx, _, jid, name in failures:
3244
      ToStderr("Failed to submit job%s: %s", self._IfName(name, " for %s"), jid)
3245
      results.append((idx, False, jid))
3246

    
3247
    while self.jobs:
3248
      (idx, _, jid, name) = self._ChooseJob()
3249
      ToStdout("Waiting for job %s%s ...", jid, self._IfName(name, " for %s"))
3250
      try:
3251
        job_result = PollJob(jid, cl=self.cl, feedback_fn=self.feedback_fn)
3252
        success = True
3253
      except errors.JobLost, err:
3254
        _, job_result = FormatError(err)
3255
        ToStderr("Job %s%s has been archived, cannot check its result",
3256
                 jid, self._IfName(name, " for %s"))
3257
        success = False
3258
      except (errors.GenericError, luxi.ProtocolError), err:
3259
        _, job_result = FormatError(err)
3260
        success = False
3261
        # the error message will always be shown, verbose or not
3262
        ToStderr("Job %s%s has failed: %s",
3263
                 jid, self._IfName(name, " for %s"), job_result)
3264

    
3265
      results.append((idx, success, job_result))
3266

    
3267
    # sort based on the index, then drop it
3268
    results.sort()
3269
    results = [i[1:] for i in results]
3270

    
3271
    return results
3272

    
3273
  def WaitOrShow(self, wait):
3274
    """Wait for job results or only print the job IDs.
3275

3276
    @type wait: boolean
3277
    @param wait: whether to wait or not
3278

3279
    """
3280
    if wait:
3281
      return self.GetResults()
3282
    else:
3283
      if not self.jobs:
3284
        self.SubmitPending()
3285
      for _, status, result, name in self.jobs:
3286
        if status:
3287
          ToStdout("%s: %s", result, name)
3288
        else:
3289
          ToStderr("Failure for %s: %s", name, result)
3290
      return [row[1:3] for row in self.jobs]
3291

    
3292

    
3293
def FormatParameterDict(buf, param_dict, actual, level=1):
3294
  """Formats a parameter dictionary.
3295

3296
  @type buf: L{StringIO}
3297
  @param buf: the buffer into which to write
3298
  @type param_dict: dict
3299
  @param param_dict: the own parameters
3300
  @type actual: dict
3301
  @param actual: the current parameter set (including defaults)
3302
  @param level: Level of indent
3303

3304
  """
3305
  indent = "  " * level
3306
  for key in sorted(actual):
3307
    val = param_dict.get(key, "default (%s)" % actual[key])
3308
    buf.write("%s- %s: %s\n" % (indent, key, val))
3309

    
3310

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

3314
  This function is used to request confirmation for doing an operation
3315
  on a given list of list_type.
3316

3317
  @type names: list
3318
  @param names: the list of names that we display when
3319
      we ask for confirmation
3320
  @type list_type: str
3321
  @param list_type: Human readable name for elements in the list (e.g. nodes)
3322
  @type text: str
3323
  @param text: the operation that the user should confirm
3324
  @rtype: boolean
3325
  @return: True or False depending on user's confirmation.
3326

3327
  """
3328
  count = len(names)
3329
  msg = ("The %s will operate on %d %s.\n%s"
3330
         "Do you want to continue?" % (text, count, list_type, extra))
3331
  affected = (("\nAffected %s:\n" % list_type) +
3332
              "\n".join(["  %s" % name for name in names]))
3333

    
3334
  choices = [("y", True, "Yes, execute the %s" % text),
3335
             ("n", False, "No, abort the %s" % text)]
3336

    
3337
  if count > 20:
3338
    choices.insert(1, ("v", "v", "View the list of affected %s" % list_type))
3339
    question = msg
3340
  else:
3341
    question = msg + affected
3342

    
3343
  choice = AskUser(question, choices)
3344
  if choice == "v":
3345
    choices.pop(1)
3346
    choice = AskUser(msg + affected, choices)
3347
  return choice