Statistics
| Branch: | Tag: | Revision:

root / lib / cli.py @ 12c3d3f6

History | View | Annotate | Download (108.7 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
  # Generic functions for CLI programs
198
  "ConfirmOperation",
199
  "GenericMain",
200
  "GenericInstanceCreate",
201
  "GenericList",
202
  "GenericListFields",
203
  "GetClient",
204
  "GetOnlineNodes",
205
  "JobExecutor",
206
  "JobSubmittedException",
207
  "ParseTimespec",
208
  "RunWhileClusterStopped",
209
  "SubmitOpCode",
210
  "SubmitOrSend",
211
  "UsesRPC",
212
  # Formatting functions
213
  "ToStderr", "ToStdout",
214
  "FormatError",
215
  "FormatQueryResult",
216
  "FormatParameterDict",
217
  "GenerateTable",
218
  "AskUser",
219
  "FormatTimestamp",
220
  "FormatLogMessage",
221
  # Tags functions
222
  "ListTags",
223
  "AddTags",
224
  "RemoveTags",
225
  # command line options support infrastructure
226
  "ARGS_MANY_INSTANCES",
227
  "ARGS_MANY_NODES",
228
  "ARGS_MANY_GROUPS",
229
  "ARGS_NONE",
230
  "ARGS_ONE_INSTANCE",
231
  "ARGS_ONE_NODE",
232
  "ARGS_ONE_GROUP",
233
  "ARGS_ONE_OS",
234
  "ArgChoice",
235
  "ArgCommand",
236
  "ArgFile",
237
  "ArgGroup",
238
  "ArgHost",
239
  "ArgInstance",
240
  "ArgJobId",
241
  "ArgNode",
242
  "ArgOs",
243
  "ArgSuggest",
244
  "ArgUnknown",
245
  "OPT_COMPL_INST_ADD_NODES",
246
  "OPT_COMPL_MANY_NODES",
247
  "OPT_COMPL_ONE_IALLOCATOR",
248
  "OPT_COMPL_ONE_INSTANCE",
249
  "OPT_COMPL_ONE_NODE",
250
  "OPT_COMPL_ONE_NODEGROUP",
251
  "OPT_COMPL_ONE_OS",
252
  "cli_option",
253
  "SplitNodeOption",
254
  "CalculateOSNames",
255
  "ParseFields",
256
  "COMMON_CREATE_OPTS",
257
  ]
258

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

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

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

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

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

    
282

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

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

    
292

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

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

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

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

    
308

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

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

315
  """
316

    
317

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

321
  """
322

    
323

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

327
  """
328

    
329

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

333
  """
334

    
335

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

339
  """
340

    
341

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

345
  """
346

    
347

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

351
  """
352

    
353

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

357
  """
358

    
359

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

363
  """
364

    
365

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

369
  """
370

    
371

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

    
382

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

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

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

    
405

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

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

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

    
434

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

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

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

    
452

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

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

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

    
469

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

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

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

    
486

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

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

    
496

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

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

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

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

    
533

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

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

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

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

    
561

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

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

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

    
570

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

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

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

    
585

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

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

    
597

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

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

    
618

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

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

    
640

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

    
644

    
645
_YORNO = "yes|no"
646

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1363

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

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

    
1391

    
1392
def _ParseArgs(argv, commands, aliases, env_override):
1393
  """Parser for the command line arguments.
1394

1395
  This function parses the arguments and returns the function which
1396
  must be executed together with its (modified) arguments.
1397

1398
  @param argv: the command line
1399
  @param commands: dictionary with special contents, see the design
1400
      doc for cmdline handling
1401
  @param aliases: dictionary with command aliases {'alias': 'target, ...}
1402
  @param env_override: list of env variables allowed for default args
1403

1404
  """
1405
  assert not (env_override - set(commands))
1406

    
1407
  if len(argv) == 0:
1408
    binary = "<command>"
1409
  else:
1410
    binary = argv[0].split("/")[-1]
1411

    
1412
  if len(argv) > 1 and argv[1] == "--version":
1413
    ToStdout("%s (ganeti %s) %s", binary, constants.VCS_VERSION,
1414
             constants.RELEASE_VERSION)
1415
    # Quit right away. That way we don't have to care about this special
1416
    # argument. optparse.py does it the same.
1417
    sys.exit(0)
1418

    
1419
  if len(argv) < 2 or not (argv[1] in commands or
1420
                           argv[1] in aliases):
1421
    # let's do a nice thing
1422
    sortedcmds = commands.keys()
1423
    sortedcmds.sort()
1424

    
1425
    ToStdout("Usage: %s {command} [options...] [argument...]", binary)
1426
    ToStdout("%s <command> --help to see details, or man %s", binary, binary)
1427
    ToStdout("")
1428

    
1429
    # compute the max line length for cmd + usage
1430
    mlen = max([len(" %s" % cmd) for cmd in commands])
1431
    mlen = min(60, mlen) # should not get here...
1432

    
1433
    # and format a nice command list
1434
    ToStdout("Commands:")
1435
    for cmd in sortedcmds:
1436
      cmdstr = " %s" % (cmd,)
1437
      help_text = commands[cmd][4]
1438
      help_lines = textwrap.wrap(help_text, 79 - 3 - mlen)
1439
      ToStdout("%-*s - %s", mlen, cmdstr, help_lines.pop(0))
1440
      for line in help_lines:
1441
        ToStdout("%-*s   %s", mlen, "", line)
1442

    
1443
    ToStdout("")
1444

    
1445
    return None, None, None
1446

    
1447
  # get command, unalias it, and look it up in commands
1448
  cmd = argv.pop(1)
1449
  if cmd in aliases:
1450
    if cmd in commands:
1451
      raise errors.ProgrammerError("Alias '%s' overrides an existing"
1452
                                   " command" % cmd)
1453

    
1454
    if aliases[cmd] not in commands:
1455
      raise errors.ProgrammerError("Alias '%s' maps to non-existing"
1456
                                   " command '%s'" % (cmd, aliases[cmd]))
1457

    
1458
    cmd = aliases[cmd]
1459

    
1460
  if cmd in env_override:
1461
    args_env_name = ("%s_%s" % (binary.replace("-", "_"), cmd)).upper()
1462
    env_args = os.environ.get(args_env_name)
1463
    if env_args:
1464
      argv = utils.InsertAtPos(argv, 1, shlex.split(env_args))
1465

    
1466
  func, args_def, parser_opts, usage, description = commands[cmd]
1467
  parser = OptionParser(option_list=parser_opts + COMMON_OPTS,
1468
                        description=description,
1469
                        formatter=TitledHelpFormatter(),
1470
                        usage="%%prog %s %s" % (cmd, usage))
1471
  parser.disable_interspersed_args()
1472
  options, args = parser.parse_args(args=argv[1:])
1473

    
1474
  if not _CheckArguments(cmd, args_def, args):
1475
    return None, None, None
1476

    
1477
  return func, options, args
1478

    
1479

    
1480
def _CheckArguments(cmd, args_def, args):
1481
  """Verifies the arguments using the argument definition.
1482

1483
  Algorithm:
1484

1485
    1. Abort with error if values specified by user but none expected.
1486

1487
    1. For each argument in definition
1488

1489
      1. Keep running count of minimum number of values (min_count)
1490
      1. Keep running count of maximum number of values (max_count)
1491
      1. If it has an unlimited number of values
1492

1493
        1. Abort with error if it's not the last argument in the definition
1494

1495
    1. If last argument has limited number of values
1496

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

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

1501
  """
1502
  if args and not args_def:
1503
    ToStderr("Error: Command %s expects no arguments", cmd)
1504
    return False
1505

    
1506
  min_count = None
1507
  max_count = None
1508
  check_max = None
1509

    
1510
  last_idx = len(args_def) - 1
1511

    
1512
  for idx, arg in enumerate(args_def):
1513
    if min_count is None:
1514
      min_count = arg.min
1515
    elif arg.min is not None:
1516
      min_count += arg.min
1517

    
1518
    if max_count is None:
1519
      max_count = arg.max
1520
    elif arg.max is not None:
1521
      max_count += arg.max
1522

    
1523
    if idx == last_idx:
1524
      check_max = (arg.max is not None)
1525

    
1526
    elif arg.max is None:
1527
      raise errors.ProgrammerError("Only the last argument can have max=None")
1528

    
1529
  if check_max:
1530
    # Command with exact number of arguments
1531
    if (min_count is not None and max_count is not None and
1532
        min_count == max_count and len(args) != min_count):
1533
      ToStderr("Error: Command %s expects %d argument(s)", cmd, min_count)
1534
      return False
1535

    
1536
    # Command with limited number of arguments
1537
    if max_count is not None and len(args) > max_count:
1538
      ToStderr("Error: Command %s expects only %d argument(s)",
1539
               cmd, max_count)
1540
      return False
1541

    
1542
  # Command with some required arguments
1543
  if min_count is not None and len(args) < min_count:
1544
    ToStderr("Error: Command %s expects at least %d argument(s)",
1545
             cmd, min_count)
1546
    return False
1547

    
1548
  return True
1549

    
1550

    
1551
def SplitNodeOption(value):
1552
  """Splits the value of a --node option.
1553

1554
  """
1555
  if value and ":" in value:
1556
    return value.split(":", 1)
1557
  else:
1558
    return (value, None)
1559

    
1560

    
1561
def CalculateOSNames(os_name, os_variants):
1562
  """Calculates all the names an OS can be called, according to its variants.
1563

1564
  @type os_name: string
1565
  @param os_name: base name of the os
1566
  @type os_variants: list or None
1567
  @param os_variants: list of supported variants
1568
  @rtype: list
1569
  @return: list of valid names
1570

1571
  """
1572
  if os_variants:
1573
    return ["%s+%s" % (os_name, v) for v in os_variants]
1574
  else:
1575
    return [os_name]
1576

    
1577

    
1578
def ParseFields(selected, default):
1579
  """Parses the values of "--field"-like options.
1580

1581
  @type selected: string or None
1582
  @param selected: User-selected options
1583
  @type default: list
1584
  @param default: Default fields
1585

1586
  """
1587
  if selected is None:
1588
    return default
1589

    
1590
  if selected.startswith("+"):
1591
    return default + selected[1:].split(",")
1592

    
1593
  return selected.split(",")
1594

    
1595

    
1596
UsesRPC = rpc.RunWithRPC
1597

    
1598

    
1599
def AskUser(text, choices=None):
1600
  """Ask the user a question.
1601

1602
  @param text: the question to ask
1603

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

1609
  @return: one of the return values from the choices list; if input is
1610
      not possible (i.e. not running with a tty, we return the last
1611
      entry from the list
1612

1613
  """
1614
  if choices is None:
1615
    choices = [("y", True, "Perform the operation"),
1616
               ("n", False, "Do not perform the operation")]
1617
  if not choices or not isinstance(choices, list):
1618
    raise errors.ProgrammerError("Invalid choices argument to AskUser")
1619
  for entry in choices:
1620
    if not isinstance(entry, tuple) or len(entry) < 3 or entry[0] == "?":
1621
      raise errors.ProgrammerError("Invalid choices element to AskUser")
1622

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

    
1655

    
1656
class JobSubmittedException(Exception):
1657
  """Job was submitted, client should exit.
1658

1659
  This exception has one argument, the ID of the job that was
1660
  submitted. The handler should print this ID.
1661

1662
  This is not an error, just a structured way to exit from clients.
1663

1664
  """
1665

    
1666

    
1667
def SendJob(ops, cl=None):
1668
  """Function to submit an opcode without waiting for the results.
1669

1670
  @type ops: list
1671
  @param ops: list of opcodes
1672
  @type cl: luxi.Client
1673
  @param cl: the luxi client to use for communicating with the master;
1674
             if None, a new client will be created
1675

1676
  """
1677
  if cl is None:
1678
    cl = GetClient()
1679

    
1680
  job_id = cl.SubmitJob(ops)
1681

    
1682
  return job_id
1683

    
1684

    
1685
def GenericPollJob(job_id, cbs, report_cbs):
1686
  """Generic job-polling function.
1687

1688
  @type job_id: number
1689
  @param job_id: Job ID
1690
  @type cbs: Instance of L{JobPollCbBase}
1691
  @param cbs: Data callbacks
1692
  @type report_cbs: Instance of L{JobPollReportCbBase}
1693
  @param report_cbs: Reporting callbacks
1694

1695
  """
1696
  prev_job_info = None
1697
  prev_logmsg_serial = None
1698

    
1699
  status = None
1700

    
1701
  while True:
1702
    result = cbs.WaitForJobChangeOnce(job_id, ["status"], prev_job_info,
1703
                                      prev_logmsg_serial)
1704
    if not result:
1705
      # job not found, go away!
1706
      raise errors.JobLost("Job with id %s lost" % job_id)
1707

    
1708
    if result == constants.JOB_NOTCHANGED:
1709
      report_cbs.ReportNotChanged(job_id, status)
1710

    
1711
      # Wait again
1712
      continue
1713

    
1714
    # Split result, a tuple of (field values, log entries)
1715
    (job_info, log_entries) = result
1716
    (status, ) = job_info
1717

    
1718
    if log_entries:
1719
      for log_entry in log_entries:
1720
        (serial, timestamp, log_type, message) = log_entry
1721
        report_cbs.ReportLogMessage(job_id, serial, timestamp,
1722
                                    log_type, message)
1723
        prev_logmsg_serial = max(prev_logmsg_serial, serial)
1724

    
1725
    # TODO: Handle canceled and archived jobs
1726
    elif status in (constants.JOB_STATUS_SUCCESS,
1727
                    constants.JOB_STATUS_ERROR,
1728
                    constants.JOB_STATUS_CANCELING,
1729
                    constants.JOB_STATUS_CANCELED):
1730
      break
1731

    
1732
    prev_job_info = job_info
1733

    
1734
  jobs = cbs.QueryJobs([job_id], ["status", "opstatus", "opresult"])
1735
  if not jobs:
1736
    raise errors.JobLost("Job with id %s lost" % job_id)
1737

    
1738
  status, opstatus, result = jobs[0]
1739

    
1740
  if status == constants.JOB_STATUS_SUCCESS:
1741
    return result
1742

    
1743
  if status in (constants.JOB_STATUS_CANCELING, constants.JOB_STATUS_CANCELED):
1744
    raise errors.OpExecError("Job was canceled")
1745

    
1746
  has_ok = False
1747
  for idx, (status, msg) in enumerate(zip(opstatus, result)):
1748
    if status == constants.OP_STATUS_SUCCESS:
1749
      has_ok = True
1750
    elif status == constants.OP_STATUS_ERROR:
1751
      errors.MaybeRaise(msg)
1752

    
1753
      if has_ok:
1754
        raise errors.OpExecError("partial failure (opcode %d): %s" %
1755
                                 (idx, msg))
1756

    
1757
      raise errors.OpExecError(str(msg))
1758

    
1759
  # default failure mode
1760
  raise errors.OpExecError(result)
1761

    
1762

    
1763
class JobPollCbBase:
1764
  """Base class for L{GenericPollJob} callbacks.
1765

1766
  """
1767
  def __init__(self):
1768
    """Initializes this class.
1769

1770
    """
1771

    
1772
  def WaitForJobChangeOnce(self, job_id, fields,
1773
                           prev_job_info, prev_log_serial):
1774
    """Waits for changes on a job.
1775

1776
    """
1777
    raise NotImplementedError()
1778

    
1779
  def QueryJobs(self, job_ids, fields):
1780
    """Returns the selected fields for the selected job IDs.
1781

1782
    @type job_ids: list of numbers
1783
    @param job_ids: Job IDs
1784
    @type fields: list of strings
1785
    @param fields: Fields
1786

1787
    """
1788
    raise NotImplementedError()
1789

    
1790

    
1791
class JobPollReportCbBase:
1792
  """Base class for L{GenericPollJob} reporting callbacks.
1793

1794
  """
1795
  def __init__(self):
1796
    """Initializes this class.
1797

1798
    """
1799

    
1800
  def ReportLogMessage(self, job_id, serial, timestamp, log_type, log_msg):
1801
    """Handles a log message.
1802

1803
    """
1804
    raise NotImplementedError()
1805

    
1806
  def ReportNotChanged(self, job_id, status):
1807
    """Called for if a job hasn't changed in a while.
1808

1809
    @type job_id: number
1810
    @param job_id: Job ID
1811
    @type status: string or None
1812
    @param status: Job status if available
1813

1814
    """
1815
    raise NotImplementedError()
1816

    
1817

    
1818
class _LuxiJobPollCb(JobPollCbBase):
1819
  def __init__(self, cl):
1820
    """Initializes this class.
1821

1822
    """
1823
    JobPollCbBase.__init__(self)
1824
    self.cl = cl
1825

    
1826
  def WaitForJobChangeOnce(self, job_id, fields,
1827
                           prev_job_info, prev_log_serial):
1828
    """Waits for changes on a job.
1829

1830
    """
1831
    return self.cl.WaitForJobChangeOnce(job_id, fields,
1832
                                        prev_job_info, prev_log_serial)
1833

    
1834
  def QueryJobs(self, job_ids, fields):
1835
    """Returns the selected fields for the selected job IDs.
1836

1837
    """
1838
    return self.cl.QueryJobs(job_ids, fields)
1839

    
1840

    
1841
class FeedbackFnJobPollReportCb(JobPollReportCbBase):
1842
  def __init__(self, feedback_fn):
1843
    """Initializes this class.
1844

1845
    """
1846
    JobPollReportCbBase.__init__(self)
1847

    
1848
    self.feedback_fn = feedback_fn
1849

    
1850
    assert callable(feedback_fn)
1851

    
1852
  def ReportLogMessage(self, job_id, serial, timestamp, log_type, log_msg):
1853
    """Handles a log message.
1854

1855
    """
1856
    self.feedback_fn((timestamp, log_type, log_msg))
1857

    
1858
  def ReportNotChanged(self, job_id, status):
1859
    """Called if a job hasn't changed in a while.
1860

1861
    """
1862
    # Ignore
1863

    
1864

    
1865
class StdioJobPollReportCb(JobPollReportCbBase):
1866
  def __init__(self):
1867
    """Initializes this class.
1868

1869
    """
1870
    JobPollReportCbBase.__init__(self)
1871

    
1872
    self.notified_queued = False
1873
    self.notified_waitlock = False
1874

    
1875
  def ReportLogMessage(self, job_id, serial, timestamp, log_type, log_msg):
1876
    """Handles a log message.
1877

1878
    """
1879
    ToStdout("%s %s", time.ctime(utils.MergeTime(timestamp)),
1880
             FormatLogMessage(log_type, log_msg))
1881

    
1882
  def ReportNotChanged(self, job_id, status):
1883
    """Called if a job hasn't changed in a while.
1884

1885
    """
1886
    if status is None:
1887
      return
1888

    
1889
    if status == constants.JOB_STATUS_QUEUED and not self.notified_queued:
1890
      ToStderr("Job %s is waiting in queue", job_id)
1891
      self.notified_queued = True
1892

    
1893
    elif status == constants.JOB_STATUS_WAITING and not self.notified_waitlock:
1894
      ToStderr("Job %s is trying to acquire all necessary locks", job_id)
1895
      self.notified_waitlock = True
1896

    
1897

    
1898
def FormatLogMessage(log_type, log_msg):
1899
  """Formats a job message according to its type.
1900

1901
  """
1902
  if log_type != constants.ELOG_MESSAGE:
1903
    log_msg = str(log_msg)
1904

    
1905
  return utils.SafeEncode(log_msg)
1906

    
1907

    
1908
def PollJob(job_id, cl=None, feedback_fn=None, reporter=None):
1909
  """Function to poll for the result of a job.
1910

1911
  @type job_id: job identified
1912
  @param job_id: the job to poll for results
1913
  @type cl: luxi.Client
1914
  @param cl: the luxi client to use for communicating with the master;
1915
             if None, a new client will be created
1916

1917
  """
1918
  if cl is None:
1919
    cl = GetClient()
1920

    
1921
  if reporter is None:
1922
    if feedback_fn:
1923
      reporter = FeedbackFnJobPollReportCb(feedback_fn)
1924
    else:
1925
      reporter = StdioJobPollReportCb()
1926
  elif feedback_fn:
1927
    raise errors.ProgrammerError("Can't specify reporter and feedback function")
1928

    
1929
  return GenericPollJob(job_id, _LuxiJobPollCb(cl), reporter)
1930

    
1931

    
1932
def SubmitOpCode(op, cl=None, feedback_fn=None, opts=None, reporter=None):
1933
  """Legacy function to submit an opcode.
1934

1935
  This is just a simple wrapper over the construction of the processor
1936
  instance. It should be extended to better handle feedback and
1937
  interaction functions.
1938

1939
  """
1940
  if cl is None:
1941
    cl = GetClient()
1942

    
1943
  SetGenericOpcodeOpts([op], opts)
1944

    
1945
  job_id = SendJob([op], cl=cl)
1946

    
1947
  op_results = PollJob(job_id, cl=cl, feedback_fn=feedback_fn,
1948
                       reporter=reporter)
1949

    
1950
  return op_results[0]
1951

    
1952

    
1953
def SubmitOrSend(op, opts, cl=None, feedback_fn=None):
1954
  """Wrapper around SubmitOpCode or SendJob.
1955

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

1961
  It will also process the opcodes if we're sending the via SendJob
1962
  (otherwise SubmitOpCode does it).
1963

1964
  """
1965
  if opts and opts.submit_only:
1966
    job = [op]
1967
    SetGenericOpcodeOpts(job, opts)
1968
    job_id = SendJob(job, cl=cl)
1969
    raise JobSubmittedException(job_id)
1970
  else:
1971
    return SubmitOpCode(op, cl=cl, feedback_fn=feedback_fn, opts=opts)
1972

    
1973

    
1974
def SetGenericOpcodeOpts(opcode_list, options):
1975
  """Processor for generic options.
1976

1977
  This function updates the given opcodes based on generic command
1978
  line options (like debug, dry-run, etc.).
1979

1980
  @param opcode_list: list of opcodes
1981
  @param options: command line options or None
1982
  @return: None (in-place modification)
1983

1984
  """
1985
  if not options:
1986
    return
1987
  for op in opcode_list:
1988
    op.debug_level = options.debug
1989
    if hasattr(options, "dry_run"):
1990
      op.dry_run = options.dry_run
1991
    if getattr(options, "priority", None) is not None:
1992
      op.priority = _PRIONAME_TO_VALUE[options.priority]
1993

    
1994

    
1995
def GetClient():
1996
  # TODO: Cache object?
1997
  try:
1998
    client = luxi.Client()
1999
  except luxi.NoMasterError:
2000
    ss = ssconf.SimpleStore()
2001

    
2002
    # Try to read ssconf file
2003
    try:
2004
      ss.GetMasterNode()
2005
    except errors.ConfigurationError:
2006
      raise errors.OpPrereqError("Cluster not initialized or this machine is"
2007
                                 " not part of a cluster")
2008

    
2009
    master, myself = ssconf.GetMasterAndMyself(ss=ss)
2010
    if master != myself:
2011
      raise errors.OpPrereqError("This is not the master node, please connect"
2012
                                 " to node '%s' and rerun the command" %
2013
                                 master)
2014
    raise
2015
  return client
2016

    
2017

    
2018
def FormatError(err):
2019
  """Return a formatted error message for a given error.
2020

2021
  This function takes an exception instance and returns a tuple
2022
  consisting of two values: first, the recommended exit code, and
2023
  second, a string describing the error message (not
2024
  newline-terminated).
2025

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

    
2105

    
2106
def GenericMain(commands, override=None, aliases=None,
2107
                env_override=frozenset()):
2108
  """Generic main function for all the gnt-* commands.
2109

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

2119
  """
2120
  # save the program name and the entire command line for later logging
2121
  if sys.argv:
2122
    binary = os.path.basename(sys.argv[0]) or sys.argv[0]
2123
    if len(sys.argv) >= 2:
2124
      binary += " " + sys.argv[1]
2125
      old_cmdline = " ".join(sys.argv[2:])
2126
    else:
2127
      old_cmdline = ""
2128
  else:
2129
    binary = "<unknown program>"
2130
    old_cmdline = ""
2131

    
2132
  if aliases is None:
2133
    aliases = {}
2134

    
2135
  try:
2136
    func, options, args = _ParseArgs(sys.argv, commands, aliases, env_override)
2137
  except errors.ParameterError, err:
2138
    result, err_msg = FormatError(err)
2139
    ToStderr(err_msg)
2140
    return 1
2141

    
2142
  if func is None: # parse error
2143
    return 1
2144

    
2145
  if override is not None:
2146
    for key, val in override.iteritems():
2147
      setattr(options, key, val)
2148

    
2149
  utils.SetupLogging(constants.LOG_COMMANDS, binary, debug=options.debug,
2150
                     stderr_logging=True)
2151

    
2152
  if old_cmdline:
2153
    logging.info("run with arguments '%s'", old_cmdline)
2154
  else:
2155
    logging.info("run with no arguments")
2156

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

    
2176
  return result
2177

    
2178

    
2179
def ParseNicOption(optvalue):
2180
  """Parses the value of the --net option(s).
2181

2182
  """
2183
  try:
2184
    nic_max = max(int(nidx[0]) + 1 for nidx in optvalue)
2185
  except (TypeError, ValueError), err:
2186
    raise errors.OpPrereqError("Invalid NIC index passed: %s" % str(err))
2187

    
2188
  nics = [{}] * nic_max
2189
  for nidx, ndict in optvalue:
2190
    nidx = int(nidx)
2191

    
2192
    if not isinstance(ndict, dict):
2193
      raise errors.OpPrereqError("Invalid nic/%d value: expected dict,"
2194
                                 " got %s" % (nidx, ndict))
2195

    
2196
    utils.ForceDictType(ndict, constants.INIC_PARAMS_TYPES)
2197

    
2198
    nics[nidx] = ndict
2199

    
2200
  return nics
2201

    
2202

    
2203
def GenericInstanceCreate(mode, opts, args):
2204
  """Add an instance to the cluster via either creation or import.
2205

2206
  @param mode: constants.INSTANCE_CREATE or constants.INSTANCE_IMPORT
2207
  @param opts: the command line options selected by the user
2208
  @type args: list
2209
  @param args: should contain only one element, the new instance name
2210
  @rtype: int
2211
  @return: the desired exit code
2212

2213
  """
2214
  instance = args[0]
2215

    
2216
  (pnode, snode) = SplitNodeOption(opts.node)
2217

    
2218
  hypervisor = None
2219
  hvparams = {}
2220
  if opts.hypervisor:
2221
    hypervisor, hvparams = opts.hypervisor
2222

    
2223
  if opts.nics:
2224
    nics = ParseNicOption(opts.nics)
2225
  elif opts.no_nics:
2226
    # no nics
2227
    nics = []
2228
  elif mode == constants.INSTANCE_CREATE:
2229
    # default of one nic, all auto
2230
    nics = [{}]
2231
  else:
2232
    # mode == import
2233
    nics = []
2234

    
2235
  if opts.disk_template == constants.DT_DISKLESS:
2236
    if opts.disks or opts.sd_size is not None:
2237
      raise errors.OpPrereqError("Diskless instance but disk"
2238
                                 " information passed")
2239
    disks = []
2240
  else:
2241
    if (not opts.disks and not opts.sd_size
2242
        and mode == constants.INSTANCE_CREATE):
2243
      raise errors.OpPrereqError("No disk information specified")
2244
    if opts.disks and opts.sd_size is not None:
2245
      raise errors.OpPrereqError("Please use either the '--disk' or"
2246
                                 " '-s' option")
2247
    if opts.sd_size is not None:
2248
      opts.disks = [(0, {constants.IDISK_SIZE: opts.sd_size})]
2249

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

    
2283
  if opts.tags is not None:
2284
    tags = opts.tags.split(",")
2285
  else:
2286
    tags = []
2287

    
2288
  utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_COMPAT)
2289
  utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
2290

    
2291
  if mode == constants.INSTANCE_CREATE:
2292
    start = opts.start
2293
    os_type = opts.os
2294
    force_variant = opts.force_variant
2295
    src_node = None
2296
    src_path = None
2297
    no_install = opts.no_install
2298
    identify_defaults = False
2299
  elif mode == constants.INSTANCE_IMPORT:
2300
    start = False
2301
    os_type = None
2302
    force_variant = False
2303
    src_node = opts.src_node
2304
    src_path = opts.src_dir
2305
    no_install = None
2306
    identify_defaults = opts.identify_defaults
2307
  else:
2308
    raise errors.ProgrammerError("Invalid creation mode %s" % mode)
2309

    
2310
  op = opcodes.OpInstanceCreate(instance_name=instance,
2311
                                disks=disks,
2312
                                disk_template=opts.disk_template,
2313
                                nics=nics,
2314
                                pnode=pnode, snode=snode,
2315
                                ip_check=opts.ip_check,
2316
                                name_check=opts.name_check,
2317
                                wait_for_sync=opts.wait_for_sync,
2318
                                file_storage_dir=opts.file_storage_dir,
2319
                                file_driver=opts.file_driver,
2320
                                iallocator=opts.iallocator,
2321
                                hypervisor=hypervisor,
2322
                                hvparams=hvparams,
2323
                                beparams=opts.beparams,
2324
                                osparams=opts.osparams,
2325
                                mode=mode,
2326
                                start=start,
2327
                                os_type=os_type,
2328
                                force_variant=force_variant,
2329
                                src_node=src_node,
2330
                                src_path=src_path,
2331
                                tags=tags,
2332
                                no_install=no_install,
2333
                                identify_defaults=identify_defaults,
2334
                                ignore_ipolicy=opts.ignore_ipolicy)
2335

    
2336
  SubmitOrSend(op, opts)
2337
  return 0
2338

    
2339

    
2340
class _RunWhileClusterStoppedHelper:
2341
  """Helper class for L{RunWhileClusterStopped} to simplify state management
2342

2343
  """
2344
  def __init__(self, feedback_fn, cluster_name, master_node, online_nodes):
2345
    """Initializes this class.
2346

2347
    @type feedback_fn: callable
2348
    @param feedback_fn: Feedback function
2349
    @type cluster_name: string
2350
    @param cluster_name: Cluster name
2351
    @type master_node: string
2352
    @param master_node Master node name
2353
    @type online_nodes: list
2354
    @param online_nodes: List of names of online nodes
2355

2356
    """
2357
    self.feedback_fn = feedback_fn
2358
    self.cluster_name = cluster_name
2359
    self.master_node = master_node
2360
    self.online_nodes = online_nodes
2361

    
2362
    self.ssh = ssh.SshRunner(self.cluster_name)
2363

    
2364
    self.nonmaster_nodes = [name for name in online_nodes
2365
                            if name != master_node]
2366

    
2367
    assert self.master_node not in self.nonmaster_nodes
2368

    
2369
  def _RunCmd(self, node_name, cmd):
2370
    """Runs a command on the local or a remote machine.
2371

2372
    @type node_name: string
2373
    @param node_name: Machine name
2374
    @type cmd: list
2375
    @param cmd: Command
2376

2377
    """
2378
    if node_name is None or node_name == self.master_node:
2379
      # No need to use SSH
2380
      result = utils.RunCmd(cmd)
2381
    else:
2382
      result = self.ssh.Run(node_name, "root", utils.ShellQuoteArgs(cmd))
2383

    
2384
    if result.failed:
2385
      errmsg = ["Failed to run command %s" % result.cmd]
2386
      if node_name:
2387
        errmsg.append("on node %s" % node_name)
2388
      errmsg.append(": exitcode %s and error %s" %
2389
                    (result.exit_code, result.output))
2390
      raise errors.OpExecError(" ".join(errmsg))
2391

    
2392
  def Call(self, fn, *args):
2393
    """Call function while all daemons are stopped.
2394

2395
    @type fn: callable
2396
    @param fn: Function to be called
2397

2398
    """
2399
    # Pause watcher by acquiring an exclusive lock on watcher state file
2400
    self.feedback_fn("Blocking watcher")
2401
    watcher_block = utils.FileLock.Open(constants.WATCHER_LOCK_FILE)
2402
    try:
2403
      # TODO: Currently, this just blocks. There's no timeout.
2404
      # TODO: Should it be a shared lock?
2405
      watcher_block.Exclusive(blocking=True)
2406

    
2407
      # Stop master daemons, so that no new jobs can come in and all running
2408
      # ones are finished
2409
      self.feedback_fn("Stopping master daemons")
2410
      self._RunCmd(None, [constants.DAEMON_UTIL, "stop-master"])
2411
      try:
2412
        # Stop daemons on all nodes
2413
        for node_name in self.online_nodes:
2414
          self.feedback_fn("Stopping daemons on %s" % node_name)
2415
          self._RunCmd(node_name, [constants.DAEMON_UTIL, "stop-all"])
2416

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

    
2434

    
2435
def RunWhileClusterStopped(feedback_fn, fn, *args):
2436
  """Calls a function while all cluster daemons are stopped.
2437

2438
  @type feedback_fn: callable
2439
  @param feedback_fn: Feedback function
2440
  @type fn: callable
2441
  @param fn: Function to be called when daemons are stopped
2442

2443
  """
2444
  feedback_fn("Gathering cluster information")
2445

    
2446
  # This ensures we're running on the master daemon
2447
  cl = GetClient()
2448

    
2449
  (cluster_name, master_node) = \
2450
    cl.QueryConfigValues(["cluster_name", "master_node"])
2451

    
2452
  online_nodes = GetOnlineNodes([], cl=cl)
2453

    
2454
  # Don't keep a reference to the client. The master daemon will go away.
2455
  del cl
2456

    
2457
  assert master_node in online_nodes
2458

    
2459
  return _RunWhileClusterStoppedHelper(feedback_fn, cluster_name, master_node,
2460
                                       online_nodes).Call(fn, *args)
2461

    
2462

    
2463
def GenerateTable(headers, fields, separator, data,
2464
                  numfields=None, unitfields=None,
2465
                  units=None):
2466
  """Prints a table with headers and different fields.
2467

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

2491
  """
2492
  if units is None:
2493
    if separator:
2494
      units = "m"
2495
    else:
2496
      units = "h"
2497

    
2498
  if numfields is None:
2499
    numfields = []
2500
  if unitfields is None:
2501
    unitfields = []
2502

    
2503
  numfields = utils.FieldSet(*numfields)   # pylint: disable=W0142
2504
  unitfields = utils.FieldSet(*unitfields) # pylint: disable=W0142
2505

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

    
2520
  if separator is None:
2521
    mlens = [0 for name in fields]
2522
    format_str = " ".join(format_fields)
2523
  else:
2524
    format_str = separator.replace("%", "%%").join(format_fields)
2525

    
2526
  for row in data:
2527
    if row is None:
2528
      continue
2529
    for idx, val in enumerate(row):
2530
      if unitfields.Matches(fields[idx]):
2531
        try:
2532
          val = int(val)
2533
        except (TypeError, ValueError):
2534
          pass
2535
        else:
2536
          val = row[idx] = utils.FormatUnit(val, units)
2537
      val = row[idx] = str(val)
2538
      if separator is None:
2539
        mlens[idx] = max(mlens[idx], len(val))
2540

    
2541
  result = []
2542
  if headers:
2543
    args = []
2544
    for idx, name in enumerate(fields):
2545
      hdr = headers[name]
2546
      if separator is None:
2547
        mlens[idx] = max(mlens[idx], len(hdr))
2548
        args.append(mlens[idx])
2549
      args.append(hdr)
2550
    result.append(format_str % tuple(args))
2551

    
2552
  if separator is None:
2553
    assert len(mlens) == len(fields)
2554

    
2555
    if fields and not numfields.Matches(fields[-1]):
2556
      mlens[-1] = 0
2557

    
2558
  for line in data:
2559
    args = []
2560
    if line is None:
2561
      line = ["-" for _ in fields]
2562
    for idx in range(len(fields)):
2563
      if separator is None:
2564
        args.append(mlens[idx])
2565
      args.append(line[idx])
2566
    result.append(format_str % tuple(args))
2567

    
2568
  return result
2569

    
2570

    
2571
def _FormatBool(value):
2572
  """Formats a boolean value as a string.
2573

2574
  """
2575
  if value:
2576
    return "Y"
2577
  return "N"
2578

    
2579

    
2580
#: Default formatting for query results; (callback, align right)
2581
_DEFAULT_FORMAT_QUERY = {
2582
  constants.QFT_TEXT: (str, False),
2583
  constants.QFT_BOOL: (_FormatBool, False),
2584
  constants.QFT_NUMBER: (str, True),
2585
  constants.QFT_TIMESTAMP: (utils.FormatTime, False),
2586
  constants.QFT_OTHER: (str, False),
2587
  constants.QFT_UNKNOWN: (str, False),
2588
  }
2589

    
2590

    
2591
def _GetColumnFormatter(fdef, override, unit):
2592
  """Returns formatting function for a field.
2593

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

2604
  """
2605
  fmt = override.get(fdef.name, None)
2606
  if fmt is not None:
2607
    return fmt
2608

    
2609
  assert constants.QFT_UNIT not in _DEFAULT_FORMAT_QUERY
2610

    
2611
  if fdef.kind == constants.QFT_UNIT:
2612
    # Can't keep this information in the static dictionary
2613
    return (lambda value: utils.FormatUnit(value, unit), True)
2614

    
2615
  fmt = _DEFAULT_FORMAT_QUERY.get(fdef.kind, None)
2616
  if fmt is not None:
2617
    return fmt
2618

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

    
2621

    
2622
class _QueryColumnFormatter:
2623
  """Callable class for formatting fields of a query.
2624

2625
  """
2626
  def __init__(self, fn, status_fn, verbose):
2627
    """Initializes this class.
2628

2629
    @type fn: callable
2630
    @param fn: Formatting function
2631
    @type status_fn: callable
2632
    @param status_fn: Function to report fields' status
2633
    @type verbose: boolean
2634
    @param verbose: whether to use verbose field descriptions or not
2635

2636
    """
2637
    self._fn = fn
2638
    self._status_fn = status_fn
2639
    self._verbose = verbose
2640

    
2641
  def __call__(self, data):
2642
    """Returns a field's string representation.
2643

2644
    """
2645
    (status, value) = data
2646

    
2647
    # Report status
2648
    self._status_fn(status)
2649

    
2650
    if status == constants.RS_NORMAL:
2651
      return self._fn(value)
2652

    
2653
    assert value is None, \
2654
           "Found value %r for abnormal status %s" % (value, status)
2655

    
2656
    return FormatResultError(status, self._verbose)
2657

    
2658

    
2659
def FormatResultError(status, verbose):
2660
  """Formats result status other than L{constants.RS_NORMAL}.
2661

2662
  @param status: The result status
2663
  @type verbose: boolean
2664
  @param verbose: Whether to return the verbose text
2665
  @return: Text of result status
2666

2667
  """
2668
  assert status != constants.RS_NORMAL, \
2669
         "FormatResultError called with status equal to constants.RS_NORMAL"
2670
  try:
2671
    (verbose_text, normal_text) = constants.RSS_DESCRIPTION[status]
2672
  except KeyError:
2673
    raise NotImplementedError("Unknown status %s" % status)
2674
  else:
2675
    if verbose:
2676
      return verbose_text
2677
    return normal_text
2678

    
2679

    
2680
def FormatQueryResult(result, unit=None, format_override=None, separator=None,
2681
                      header=False, verbose=False):
2682
  """Formats data in L{objects.QueryResponse}.
2683

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

2699
  """
2700
  if unit is None:
2701
    if separator:
2702
      unit = "m"
2703
    else:
2704
      unit = "h"
2705

    
2706
  if format_override is None:
2707
    format_override = {}
2708

    
2709
  stats = dict.fromkeys(constants.RS_ALL, 0)
2710

    
2711
  def _RecordStatus(status):
2712
    if status in stats:
2713
      stats[status] += 1
2714

    
2715
  columns = []
2716
  for fdef in result.fields:
2717
    assert fdef.title and fdef.name
2718
    (fn, align_right) = _GetColumnFormatter(fdef, format_override, unit)
2719
    columns.append(TableColumn(fdef.title,
2720
                               _QueryColumnFormatter(fn, _RecordStatus,
2721
                                                     verbose),
2722
                               align_right))
2723

    
2724
  table = FormatTable(result.data, columns, header, separator)
2725

    
2726
  # Collect statistics
2727
  assert len(stats) == len(constants.RS_ALL)
2728
  assert compat.all(count >= 0 for count in stats.values())
2729

    
2730
  # Determine overall status. If there was no data, unknown fields must be
2731
  # detected via the field definitions.
2732
  if (stats[constants.RS_UNKNOWN] or
2733
      (not result.data and _GetUnknownFields(result.fields))):
2734
    status = QR_UNKNOWN
2735
  elif compat.any(count > 0 for key, count in stats.items()
2736
                  if key != constants.RS_NORMAL):
2737
    status = QR_INCOMPLETE
2738
  else:
2739
    status = QR_NORMAL
2740

    
2741
  return (status, table)
2742

    
2743

    
2744
def _GetUnknownFields(fdefs):
2745
  """Returns list of unknown fields included in C{fdefs}.
2746

2747
  @type fdefs: list of L{objects.QueryFieldDefinition}
2748

2749
  """
2750
  return [fdef for fdef in fdefs
2751
          if fdef.kind == constants.QFT_UNKNOWN]
2752

    
2753

    
2754
def _WarnUnknownFields(fdefs):
2755
  """Prints a warning to stderr if a query included unknown fields.
2756

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

2759
  """
2760
  unknown = _GetUnknownFields(fdefs)
2761
  if unknown:
2762
    ToStderr("Warning: Queried for unknown fields %s",
2763
             utils.CommaJoin(fdef.name for fdef in unknown))
2764
    return True
2765

    
2766
  return False
2767

    
2768

    
2769
def GenericList(resource, fields, names, unit, separator, header, cl=None,
2770
                format_override=None, verbose=False, force_filter=False):
2771
  """Generic implementation for listing all items of a resource.
2772

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

2794
  """
2795
  if not names:
2796
    names = None
2797

    
2798
  qfilter = qlang.MakeFilter(names, force_filter)
2799

    
2800
  if cl is None:
2801
    cl = GetClient()
2802

    
2803
  response = cl.Query(resource, fields, qfilter)
2804

    
2805
  found_unknown = _WarnUnknownFields(response.fields)
2806

    
2807
  (status, data) = FormatQueryResult(response, unit=unit, separator=separator,
2808
                                     header=header,
2809
                                     format_override=format_override,
2810
                                     verbose=verbose)
2811

    
2812
  for line in data:
2813
    ToStdout(line)
2814

    
2815
  assert ((found_unknown and status == QR_UNKNOWN) or
2816
          (not found_unknown and status != QR_UNKNOWN))
2817

    
2818
  if status == QR_UNKNOWN:
2819
    return constants.EXIT_UNKNOWN_FIELD
2820

    
2821
  # TODO: Should the list command fail if not all data could be collected?
2822
  return constants.EXIT_SUCCESS
2823

    
2824

    
2825
def GenericListFields(resource, fields, separator, header, cl=None):
2826
  """Generic implementation for listing fields for a resource.
2827

2828
  @param resource: One of L{constants.QR_VIA_LUXI}
2829
  @type fields: list of strings
2830
  @param fields: List of fields to query for
2831
  @type separator: string or None
2832
  @param separator: String used to separate fields
2833
  @type header: bool
2834
  @param header: Whether to show header row
2835

2836
  """
2837
  if cl is None:
2838
    cl = GetClient()
2839

    
2840
  if not fields:
2841
    fields = None
2842

    
2843
  response = cl.QueryFields(resource, fields)
2844

    
2845
  found_unknown = _WarnUnknownFields(response.fields)
2846

    
2847
  columns = [
2848
    TableColumn("Name", str, False),
2849
    TableColumn("Title", str, False),
2850
    TableColumn("Description", str, False),
2851
    ]
2852

    
2853
  rows = [[fdef.name, fdef.title, fdef.doc] for fdef in response.fields]
2854

    
2855
  for line in FormatTable(rows, columns, header, separator):
2856
    ToStdout(line)
2857

    
2858
  if found_unknown:
2859
    return constants.EXIT_UNKNOWN_FIELD
2860

    
2861
  return constants.EXIT_SUCCESS
2862

    
2863

    
2864
class TableColumn:
2865
  """Describes a column for L{FormatTable}.
2866

2867
  """
2868
  def __init__(self, title, fn, align_right):
2869
    """Initializes this class.
2870

2871
    @type title: string
2872
    @param title: Column title
2873
    @type fn: callable
2874
    @param fn: Formatting function
2875
    @type align_right: bool
2876
    @param align_right: Whether to align values on the right-hand side
2877

2878
    """
2879
    self.title = title
2880
    self.format = fn
2881
    self.align_right = align_right
2882

    
2883

    
2884
def _GetColFormatString(width, align_right):
2885
  """Returns the format string for a field.
2886

2887
  """
2888
  if align_right:
2889
    sign = ""
2890
  else:
2891
    sign = "-"
2892

    
2893
  return "%%%s%ss" % (sign, width)
2894

    
2895

    
2896
def FormatTable(rows, columns, header, separator):
2897
  """Formats data as a table.
2898

2899
  @type rows: list of lists
2900
  @param rows: Row data, one list per row
2901
  @type columns: list of L{TableColumn}
2902
  @param columns: Column descriptions
2903
  @type header: bool
2904
  @param header: Whether to show header row
2905
  @type separator: string or None
2906
  @param separator: String used to separate columns
2907

2908
  """
2909
  if header:
2910
    data = [[col.title for col in columns]]
2911
    colwidth = [len(col.title) for col in columns]
2912
  else:
2913
    data = []
2914
    colwidth = [0 for _ in columns]
2915

    
2916
  # Format row data
2917
  for row in rows:
2918
    assert len(row) == len(columns)
2919

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

    
2922
    if separator is None:
2923
      # Update column widths
2924
      for idx, (oldwidth, value) in enumerate(zip(colwidth, formatted)):
2925
        # Modifying a list's items while iterating is fine
2926
        colwidth[idx] = max(oldwidth, len(value))
2927

    
2928
    data.append(formatted)
2929

    
2930
  if separator is not None:
2931
    # Return early if a separator is used
2932
    return [separator.join(row) for row in data]
2933

    
2934
  if columns and not columns[-1].align_right:
2935
    # Avoid unnecessary spaces at end of line
2936
    colwidth[-1] = 0
2937

    
2938
  # Build format string
2939
  fmt = " ".join([_GetColFormatString(width, col.align_right)
2940
                  for col, width in zip(columns, colwidth)])
2941

    
2942
  return [fmt % tuple(row) for row in data]
2943

    
2944

    
2945
def FormatTimestamp(ts):
2946
  """Formats a given timestamp.
2947

2948
  @type ts: timestamp
2949
  @param ts: a timeval-type timestamp, a tuple of seconds and microseconds
2950

2951
  @rtype: string
2952
  @return: a string with the formatted timestamp
2953

2954
  """
2955
  if not isinstance(ts, (tuple, list)) or len(ts) != 2:
2956
    return "?"
2957
  sec, usec = ts
2958
  return time.strftime("%F %T", time.localtime(sec)) + ".%06d" % usec
2959

    
2960

    
2961
def ParseTimespec(value):
2962
  """Parse a time specification.
2963

2964
  The following suffixed will be recognized:
2965

2966
    - s: seconds
2967
    - m: minutes
2968
    - h: hours
2969
    - d: day
2970
    - w: weeks
2971

2972
  Without any suffix, the value will be taken to be in seconds.
2973

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

    
3002

    
3003
def GetOnlineNodes(nodes, cl=None, nowarn=False, secondary_ips=False,
3004
                   filter_master=False, nodegroup=None):
3005
  """Returns the names of online nodes.
3006

3007
  This function will also log a warning on stderr with the names of
3008
  the online nodes.
3009

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

3028
  """
3029
  if cl is None:
3030
    cl = GetClient()
3031

    
3032
  qfilter = []
3033

    
3034
  if nodes:
3035
    qfilter.append(qlang.MakeSimpleFilter("name", nodes))
3036

    
3037
  if nodegroup is not None:
3038
    qfilter.append([qlang.OP_OR, [qlang.OP_EQUAL, "group", nodegroup],
3039
                                 [qlang.OP_EQUAL, "group.uuid", nodegroup]])
3040

    
3041
  if filter_master:
3042
    qfilter.append([qlang.OP_NOT, [qlang.OP_TRUE, "master"]])
3043

    
3044
  if qfilter:
3045
    if len(qfilter) > 1:
3046
      final_filter = [qlang.OP_AND] + qfilter
3047
    else:
3048
      assert len(qfilter) == 1
3049
      final_filter = qfilter[0]
3050
  else:
3051
    final_filter = None
3052

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

    
3055
  def _IsOffline(row):
3056
    (_, (_, offline), _) = row
3057
    return offline
3058

    
3059
  def _GetName(row):
3060
    ((_, name), _, _) = row
3061
    return name
3062

    
3063
  def _GetSip(row):
3064
    (_, _, (_, sip)) = row
3065
    return sip
3066

    
3067
  (offline, online) = compat.partition(result.data, _IsOffline)
3068

    
3069
  if offline and not nowarn:
3070
    ToStderr("Note: skipping offline node(s): %s" %
3071
             utils.CommaJoin(map(_GetName, offline)))
3072

    
3073
  if secondary_ips:
3074
    fn = _GetSip
3075
  else:
3076
    fn = _GetName
3077

    
3078
  return map(fn, online)
3079

    
3080

    
3081
def _ToStream(stream, txt, *args):
3082
  """Write a message to a stream, bypassing the logging system
3083

3084
  @type stream: file object
3085
  @param stream: the file to which we should write
3086
  @type txt: str
3087
  @param txt: the message
3088

3089
  """
3090
  try:
3091
    if args:
3092
      args = tuple(args)
3093
      stream.write(txt % args)
3094
    else:
3095
      stream.write(txt)
3096
    stream.write("\n")
3097
    stream.flush()
3098
  except IOError, err:
3099
    if err.errno == errno.EPIPE:
3100
      # our terminal went away, we'll exit
3101
      sys.exit(constants.EXIT_FAILURE)
3102
    else:
3103
      raise
3104

    
3105

    
3106
def ToStdout(txt, *args):
3107
  """Write a message to stdout only, bypassing the logging system
3108

3109
  This is just a wrapper over _ToStream.
3110

3111
  @type txt: str
3112
  @param txt: the message
3113

3114
  """
3115
  _ToStream(sys.stdout, txt, *args)
3116

    
3117

    
3118
def ToStderr(txt, *args):
3119
  """Write a message to stderr only, bypassing the logging system
3120

3121
  This is just a wrapper over _ToStream.
3122

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

3126
  """
3127
  _ToStream(sys.stderr, txt, *args)
3128

    
3129

    
3130
class JobExecutor(object):
3131
  """Class which manages the submission and execution of multiple jobs.
3132

3133
  Note that instances of this class should not be reused between
3134
  GetResults() calls.
3135

3136
  """
3137
  def __init__(self, cl=None, verbose=True, opts=None, feedback_fn=None):
3138
    self.queue = []
3139
    if cl is None:
3140
      cl = GetClient()
3141
    self.cl = cl
3142
    self.verbose = verbose
3143
    self.jobs = []
3144
    self.opts = opts
3145
    self.feedback_fn = feedback_fn
3146
    self._counter = itertools.count()
3147

    
3148
  @staticmethod
3149
  def _IfName(name, fmt):
3150
    """Helper function for formatting name.
3151

3152
    """
3153
    if name:
3154
      return fmt % name
3155

    
3156
    return ""
3157

    
3158
  def QueueJob(self, name, *ops):
3159
    """Record a job for later submit.
3160

3161
    @type name: string
3162
    @param name: a description of the job, will be used in WaitJobSet
3163

3164
    """
3165
    SetGenericOpcodeOpts(ops, self.opts)
3166
    self.queue.append((self._counter.next(), name, ops))
3167

    
3168
  def AddJobId(self, name, status, job_id):
3169
    """Adds a job ID to the internal queue.
3170

3171
    """
3172
    self.jobs.append((self._counter.next(), status, job_id, name))
3173

    
3174
  def SubmitPending(self, each=False):
3175
    """Submit all pending jobs.
3176

3177
    """
3178
    if each:
3179
      results = []
3180
      for (_, _, ops) in self.queue:
3181
        # SubmitJob will remove the success status, but raise an exception if
3182
        # the submission fails, so we'll notice that anyway.
3183
        results.append([True, self.cl.SubmitJob(ops)[0]])
3184
    else:
3185
      results = self.cl.SubmitManyJobs([ops for (_, _, ops) in self.queue])
3186
    for ((status, data), (idx, name, _)) in zip(results, self.queue):
3187
      self.jobs.append((idx, status, data, name))
3188

    
3189
  def _ChooseJob(self):
3190
    """Choose a non-waiting/queued job to poll next.
3191

3192
    """
3193
    assert self.jobs, "_ChooseJob called with empty job list"
3194

    
3195
    result = self.cl.QueryJobs([i[2] for i in self.jobs[:_CHOOSE_BATCH]],
3196
                               ["status"])
3197
    assert result
3198

    
3199
    for job_data, status in zip(self.jobs, result):
3200
      if (isinstance(status, list) and status and
3201
          status[0] in (constants.JOB_STATUS_QUEUED,
3202
                        constants.JOB_STATUS_WAITING,
3203
                        constants.JOB_STATUS_CANCELING)):
3204
        # job is still present and waiting
3205
        continue
3206
      # good candidate found (either running job or lost job)
3207
      self.jobs.remove(job_data)
3208
      return job_data
3209

    
3210
    # no job found
3211
    return self.jobs.pop(0)
3212

    
3213
  def GetResults(self):
3214
    """Wait for and return the results of all jobs.
3215

3216
    @rtype: list
3217
    @return: list of tuples (success, job results), in the same order
3218
        as the submitted jobs; if a job has failed, instead of the result
3219
        there will be the error message
3220

3221
    """
3222
    if not self.jobs:
3223
      self.SubmitPending()
3224
    results = []
3225
    if self.verbose:
3226
      ok_jobs = [row[2] for row in self.jobs if row[1]]
3227
      if ok_jobs:
3228
        ToStdout("Submitted jobs %s", utils.CommaJoin(ok_jobs))
3229

    
3230
    # first, remove any non-submitted jobs
3231
    self.jobs, failures = compat.partition(self.jobs, lambda x: x[1])
3232
    for idx, _, jid, name in failures:
3233
      ToStderr("Failed to submit job%s: %s", self._IfName(name, " for %s"), jid)
3234
      results.append((idx, False, jid))
3235

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

    
3254
      results.append((idx, success, job_result))
3255

    
3256
    # sort based on the index, then drop it
3257
    results.sort()
3258
    results = [i[1:] for i in results]
3259

    
3260
    return results
3261

    
3262
  def WaitOrShow(self, wait):
3263
    """Wait for job results or only print the job IDs.
3264

3265
    @type wait: boolean
3266
    @param wait: whether to wait or not
3267

3268
    """
3269
    if wait:
3270
      return self.GetResults()
3271
    else:
3272
      if not self.jobs:
3273
        self.SubmitPending()
3274
      for _, status, result, name in self.jobs:
3275
        if status:
3276
          ToStdout("%s: %s", result, name)
3277
        else:
3278
          ToStderr("Failure for %s: %s", name, result)
3279
      return [row[1:3] for row in self.jobs]
3280

    
3281

    
3282
def FormatParameterDict(buf, param_dict, actual, level=1):
3283
  """Formats a parameter dictionary.
3284

3285
  @type buf: L{StringIO}
3286
  @param buf: the buffer into which to write
3287
  @type param_dict: dict
3288
  @param param_dict: the own parameters
3289
  @type actual: dict
3290
  @param actual: the current parameter set (including defaults)
3291
  @param level: Level of indent
3292

3293
  """
3294
  indent = "  " * level
3295
  for key in sorted(actual):
3296
    val = param_dict.get(key, "default (%s)" % actual[key])
3297
    buf.write("%s- %s: %s\n" % (indent, key, val))
3298

    
3299

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

3303
  This function is used to request confirmation for doing an operation
3304
  on a given list of list_type.
3305

3306
  @type names: list
3307
  @param names: the list of names that we display when
3308
      we ask for confirmation
3309
  @type list_type: str
3310
  @param list_type: Human readable name for elements in the list (e.g. nodes)
3311
  @type text: str
3312
  @param text: the operation that the user should confirm
3313
  @rtype: boolean
3314
  @return: True or False depending on user's confirmation.
3315

3316
  """
3317
  count = len(names)
3318
  msg = ("The %s will operate on %d %s.\n%s"
3319
         "Do you want to continue?" % (text, count, list_type, extra))
3320
  affected = (("\nAffected %s:\n" % list_type) +
3321
              "\n".join(["  %s" % name for name in names]))
3322

    
3323
  choices = [("y", True, "Yes, execute the %s" % text),
3324
             ("n", False, "No, abort the %s" % text)]
3325

    
3326
  if count > 20:
3327
    choices.insert(1, ("v", "v", "View the list of affected %s" % list_type))
3328
    question = msg
3329
  else:
3330
    question = msg + affected
3331

    
3332
  choice = AskUser(question, choices)
3333
  if choice == "v":
3334
    choices.pop(1)
3335
    choice = AskUser(msg + affected, choices)
3336
  return choice