Statistics
| Branch: | Tag: | Revision:

root / lib / cli.py @ c1f19851

History | View | Annotate | Download (109.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
import optparse
34
from cStringIO import StringIO
35

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

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

    
51

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

    
262
NO_PREFIX = "no_"
263
UN_PREFIX = "-"
264

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

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

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

    
282
#: Maximum batch size for ChooseJob
283
_CHOOSE_BATCH = 25
284

    
285

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

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

    
295

    
296
class ArgSuggest(_Argument):
297
  """Suggesting argument.
298

299
  Value can be any of the ones passed to the constructor.
300

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

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

    
311

    
312
class ArgChoice(ArgSuggest):
313
  """Choice argument.
314

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

318
  """
319

    
320

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

324
  """
325

    
326

    
327
class ArgInstance(_Argument):
328
  """Instances argument.
329

330
  """
331

    
332

    
333
class ArgNode(_Argument):
334
  """Node argument.
335

336
  """
337

    
338

    
339
class ArgGroup(_Argument):
340
  """Node group argument.
341

342
  """
343

    
344

    
345
class ArgJobId(_Argument):
346
  """Job ID argument.
347

348
  """
349

    
350

    
351
class ArgFile(_Argument):
352
  """File path argument.
353

354
  """
355

    
356

    
357
class ArgCommand(_Argument):
358
  """Command argument.
359

360
  """
361

    
362

    
363
class ArgHost(_Argument):
364
  """Host argument.
365

366
  """
367

    
368

    
369
class ArgOs(_Argument):
370
  """OS argument.
371

372
  """
373

    
374

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

    
385

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

389
  Note that this function will modify its args parameter.
390

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

    
408

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

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

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

    
437

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

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

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

    
455

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

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

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

    
472

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

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

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

    
489

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

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

    
499

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

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

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

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

    
536

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

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

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

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

    
564

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

568
  This will store the parsed values as a dict {key: val}.
569

570
  """
571
  return _SplitKeyVal(opt, value)
572

    
573

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

577
  This will store the parsed value as either True or False.
578

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

    
588

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

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

    
600

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

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

    
621

    
622
class CliOption(Option):
623
  """Custom option class for optparse.
624

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

    
643

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

    
647

    
648
_YORNO = "yes|no"
649

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1366
RUNTIME_MEM_OPT = cli_option("-m", "--runtime-memory", dest="runtime_mem",
1367
                             help="Sets the instance's runtime memory,"
1368
                             " ballooning it up or down to the new value",
1369
                             default=None, type="unit", metavar="<size>")
1370

    
1371
#: Options provided by all commands
1372
COMMON_OPTS = [DEBUG_OPT]
1373

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

    
1398
# common instance policy options
1399
INSTANCE_POLICY_OPTS = [
1400
  SPECS_CPU_COUNT_OPT,
1401
  SPECS_DISK_COUNT_OPT,
1402
  SPECS_DISK_SIZE_OPT,
1403
  SPECS_MEM_SIZE_OPT,
1404
  SPECS_NIC_COUNT_OPT,
1405
  SPECS_DISK_TEMPLATES,
1406
  ]
1407

    
1408

    
1409
def _ParseArgs(argv, commands, aliases, env_override):
1410
  """Parser for the command line arguments.
1411

1412
  This function parses the arguments and returns the function which
1413
  must be executed together with its (modified) arguments.
1414

1415
  @param argv: the command line
1416
  @param commands: dictionary with special contents, see the design
1417
      doc for cmdline handling
1418
  @param aliases: dictionary with command aliases {'alias': 'target, ...}
1419
  @param env_override: list of env variables allowed for default args
1420

1421
  """
1422
  assert not (env_override - set(commands))
1423

    
1424
  if len(argv) == 0:
1425
    binary = "<command>"
1426
  else:
1427
    binary = argv[0].split("/")[-1]
1428

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

    
1436
  if len(argv) < 2 or not (argv[1] in commands or
1437
                           argv[1] in aliases):
1438
    # let's do a nice thing
1439
    sortedcmds = commands.keys()
1440
    sortedcmds.sort()
1441

    
1442
    ToStdout("Usage: %s {command} [options...] [argument...]", binary)
1443
    ToStdout("%s <command> --help to see details, or man %s", binary, binary)
1444
    ToStdout("")
1445

    
1446
    # compute the max line length for cmd + usage
1447
    mlen = max([len(" %s" % cmd) for cmd in commands])
1448
    mlen = min(60, mlen) # should not get here...
1449

    
1450
    # and format a nice command list
1451
    ToStdout("Commands:")
1452
    for cmd in sortedcmds:
1453
      cmdstr = " %s" % (cmd,)
1454
      help_text = commands[cmd][4]
1455
      help_lines = textwrap.wrap(help_text, 79 - 3 - mlen)
1456
      ToStdout("%-*s - %s", mlen, cmdstr, help_lines.pop(0))
1457
      for line in help_lines:
1458
        ToStdout("%-*s   %s", mlen, "", line)
1459

    
1460
    ToStdout("")
1461

    
1462
    return None, None, None
1463

    
1464
  # get command, unalias it, and look it up in commands
1465
  cmd = argv.pop(1)
1466
  if cmd in aliases:
1467
    if cmd in commands:
1468
      raise errors.ProgrammerError("Alias '%s' overrides an existing"
1469
                                   " command" % cmd)
1470

    
1471
    if aliases[cmd] not in commands:
1472
      raise errors.ProgrammerError("Alias '%s' maps to non-existing"
1473
                                   " command '%s'" % (cmd, aliases[cmd]))
1474

    
1475
    cmd = aliases[cmd]
1476

    
1477
  if cmd in env_override:
1478
    args_env_name = ("%s_%s" % (binary.replace("-", "_"), cmd)).upper()
1479
    env_args = os.environ.get(args_env_name)
1480
    if env_args:
1481
      argv = utils.InsertAtPos(argv, 1, shlex.split(env_args))
1482

    
1483
  func, args_def, parser_opts, usage, description = commands[cmd]
1484
  parser = CustomOptionParser(option_list=parser_opts + COMMON_OPTS,
1485
                              description=description,
1486
                              formatter=TitledHelpFormatter(),
1487
                              usage="%%prog %s %s" % (cmd, usage))
1488
  parser.disable_interspersed_args()
1489
  options, args = parser.parse_args(args=argv[1:])
1490

    
1491
  if not _CheckArguments(cmd, args_def, args):
1492
    return None, None, None
1493

    
1494
  return func, options, args
1495

    
1496

    
1497
class CustomOptionParser(optparse.OptionParser):
1498
  def _match_long_opt(self, opt):
1499
    """Override C{OptionParser}'s function for matching long options.
1500

1501
    The default implementation does prefix-based abbreviation matching. We
1502
    disable such behaviour as it can can lead to confusing conflicts (e.g.
1503
    C{--force} and C{--force-multi}).
1504

1505
    """
1506
    if opt in self._long_opt:
1507
      return opt
1508
    else:
1509
      raise optparse.BadOptionError(opt)
1510

    
1511

    
1512
def _CheckArguments(cmd, args_def, args):
1513
  """Verifies the arguments using the argument definition.
1514

1515
  Algorithm:
1516

1517
    1. Abort with error if values specified by user but none expected.
1518

1519
    1. For each argument in definition
1520

1521
      1. Keep running count of minimum number of values (min_count)
1522
      1. Keep running count of maximum number of values (max_count)
1523
      1. If it has an unlimited number of values
1524

1525
        1. Abort with error if it's not the last argument in the definition
1526

1527
    1. If last argument has limited number of values
1528

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

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

1533
  """
1534
  if args and not args_def:
1535
    ToStderr("Error: Command %s expects no arguments", cmd)
1536
    return False
1537

    
1538
  min_count = None
1539
  max_count = None
1540
  check_max = None
1541

    
1542
  last_idx = len(args_def) - 1
1543

    
1544
  for idx, arg in enumerate(args_def):
1545
    if min_count is None:
1546
      min_count = arg.min
1547
    elif arg.min is not None:
1548
      min_count += arg.min
1549

    
1550
    if max_count is None:
1551
      max_count = arg.max
1552
    elif arg.max is not None:
1553
      max_count += arg.max
1554

    
1555
    if idx == last_idx:
1556
      check_max = (arg.max is not None)
1557

    
1558
    elif arg.max is None:
1559
      raise errors.ProgrammerError("Only the last argument can have max=None")
1560

    
1561
  if check_max:
1562
    # Command with exact number of arguments
1563
    if (min_count is not None and max_count is not None and
1564
        min_count == max_count and len(args) != min_count):
1565
      ToStderr("Error: Command %s expects %d argument(s)", cmd, min_count)
1566
      return False
1567

    
1568
    # Command with limited number of arguments
1569
    if max_count is not None and len(args) > max_count:
1570
      ToStderr("Error: Command %s expects only %d argument(s)",
1571
               cmd, max_count)
1572
      return False
1573

    
1574
  # Command with some required arguments
1575
  if min_count is not None and len(args) < min_count:
1576
    ToStderr("Error: Command %s expects at least %d argument(s)",
1577
             cmd, min_count)
1578
    return False
1579

    
1580
  return True
1581

    
1582

    
1583
def SplitNodeOption(value):
1584
  """Splits the value of a --node option.
1585

1586
  """
1587
  if value and ":" in value:
1588
    return value.split(":", 1)
1589
  else:
1590
    return (value, None)
1591

    
1592

    
1593
def CalculateOSNames(os_name, os_variants):
1594
  """Calculates all the names an OS can be called, according to its variants.
1595

1596
  @type os_name: string
1597
  @param os_name: base name of the os
1598
  @type os_variants: list or None
1599
  @param os_variants: list of supported variants
1600
  @rtype: list
1601
  @return: list of valid names
1602

1603
  """
1604
  if os_variants:
1605
    return ["%s+%s" % (os_name, v) for v in os_variants]
1606
  else:
1607
    return [os_name]
1608

    
1609

    
1610
def ParseFields(selected, default):
1611
  """Parses the values of "--field"-like options.
1612

1613
  @type selected: string or None
1614
  @param selected: User-selected options
1615
  @type default: list
1616
  @param default: Default fields
1617

1618
  """
1619
  if selected is None:
1620
    return default
1621

    
1622
  if selected.startswith("+"):
1623
    return default + selected[1:].split(",")
1624

    
1625
  return selected.split(",")
1626

    
1627

    
1628
UsesRPC = rpc.RunWithRPC
1629

    
1630

    
1631
def AskUser(text, choices=None):
1632
  """Ask the user a question.
1633

1634
  @param text: the question to ask
1635

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

1641
  @return: one of the return values from the choices list; if input is
1642
      not possible (i.e. not running with a tty, we return the last
1643
      entry from the list
1644

1645
  """
1646
  if choices is None:
1647
    choices = [("y", True, "Perform the operation"),
1648
               ("n", False, "Do not perform the operation")]
1649
  if not choices or not isinstance(choices, list):
1650
    raise errors.ProgrammerError("Invalid choices argument to AskUser")
1651
  for entry in choices:
1652
    if not isinstance(entry, tuple) or len(entry) < 3 or entry[0] == "?":
1653
      raise errors.ProgrammerError("Invalid choices element to AskUser")
1654

    
1655
  answer = choices[-1][1]
1656
  new_text = []
1657
  for line in text.splitlines():
1658
    new_text.append(textwrap.fill(line, 70, replace_whitespace=False))
1659
  text = "\n".join(new_text)
1660
  try:
1661
    f = file("/dev/tty", "a+")
1662
  except IOError:
1663
    return answer
1664
  try:
1665
    chars = [entry[0] for entry in choices]
1666
    chars[-1] = "[%s]" % chars[-1]
1667
    chars.append("?")
1668
    maps = dict([(entry[0], entry[1]) for entry in choices])
1669
    while True:
1670
      f.write(text)
1671
      f.write("\n")
1672
      f.write("/".join(chars))
1673
      f.write(": ")
1674
      line = f.readline(2).strip().lower()
1675
      if line in maps:
1676
        answer = maps[line]
1677
        break
1678
      elif line == "?":
1679
        for entry in choices:
1680
          f.write(" %s - %s\n" % (entry[0], entry[2]))
1681
        f.write("\n")
1682
        continue
1683
  finally:
1684
    f.close()
1685
  return answer
1686

    
1687

    
1688
class JobSubmittedException(Exception):
1689
  """Job was submitted, client should exit.
1690

1691
  This exception has one argument, the ID of the job that was
1692
  submitted. The handler should print this ID.
1693

1694
  This is not an error, just a structured way to exit from clients.
1695

1696
  """
1697

    
1698

    
1699
def SendJob(ops, cl=None):
1700
  """Function to submit an opcode without waiting for the results.
1701

1702
  @type ops: list
1703
  @param ops: list of opcodes
1704
  @type cl: luxi.Client
1705
  @param cl: the luxi client to use for communicating with the master;
1706
             if None, a new client will be created
1707

1708
  """
1709
  if cl is None:
1710
    cl = GetClient()
1711

    
1712
  job_id = cl.SubmitJob(ops)
1713

    
1714
  return job_id
1715

    
1716

    
1717
def GenericPollJob(job_id, cbs, report_cbs):
1718
  """Generic job-polling function.
1719

1720
  @type job_id: number
1721
  @param job_id: Job ID
1722
  @type cbs: Instance of L{JobPollCbBase}
1723
  @param cbs: Data callbacks
1724
  @type report_cbs: Instance of L{JobPollReportCbBase}
1725
  @param report_cbs: Reporting callbacks
1726

1727
  """
1728
  prev_job_info = None
1729
  prev_logmsg_serial = None
1730

    
1731
  status = None
1732

    
1733
  while True:
1734
    result = cbs.WaitForJobChangeOnce(job_id, ["status"], prev_job_info,
1735
                                      prev_logmsg_serial)
1736
    if not result:
1737
      # job not found, go away!
1738
      raise errors.JobLost("Job with id %s lost" % job_id)
1739

    
1740
    if result == constants.JOB_NOTCHANGED:
1741
      report_cbs.ReportNotChanged(job_id, status)
1742

    
1743
      # Wait again
1744
      continue
1745

    
1746
    # Split result, a tuple of (field values, log entries)
1747
    (job_info, log_entries) = result
1748
    (status, ) = job_info
1749

    
1750
    if log_entries:
1751
      for log_entry in log_entries:
1752
        (serial, timestamp, log_type, message) = log_entry
1753
        report_cbs.ReportLogMessage(job_id, serial, timestamp,
1754
                                    log_type, message)
1755
        prev_logmsg_serial = max(prev_logmsg_serial, serial)
1756

    
1757
    # TODO: Handle canceled and archived jobs
1758
    elif status in (constants.JOB_STATUS_SUCCESS,
1759
                    constants.JOB_STATUS_ERROR,
1760
                    constants.JOB_STATUS_CANCELING,
1761
                    constants.JOB_STATUS_CANCELED):
1762
      break
1763

    
1764
    prev_job_info = job_info
1765

    
1766
  jobs = cbs.QueryJobs([job_id], ["status", "opstatus", "opresult"])
1767
  if not jobs:
1768
    raise errors.JobLost("Job with id %s lost" % job_id)
1769

    
1770
  status, opstatus, result = jobs[0]
1771

    
1772
  if status == constants.JOB_STATUS_SUCCESS:
1773
    return result
1774

    
1775
  if status in (constants.JOB_STATUS_CANCELING, constants.JOB_STATUS_CANCELED):
1776
    raise errors.OpExecError("Job was canceled")
1777

    
1778
  has_ok = False
1779
  for idx, (status, msg) in enumerate(zip(opstatus, result)):
1780
    if status == constants.OP_STATUS_SUCCESS:
1781
      has_ok = True
1782
    elif status == constants.OP_STATUS_ERROR:
1783
      errors.MaybeRaise(msg)
1784

    
1785
      if has_ok:
1786
        raise errors.OpExecError("partial failure (opcode %d): %s" %
1787
                                 (idx, msg))
1788

    
1789
      raise errors.OpExecError(str(msg))
1790

    
1791
  # default failure mode
1792
  raise errors.OpExecError(result)
1793

    
1794

    
1795
class JobPollCbBase:
1796
  """Base class for L{GenericPollJob} callbacks.
1797

1798
  """
1799
  def __init__(self):
1800
    """Initializes this class.
1801

1802
    """
1803

    
1804
  def WaitForJobChangeOnce(self, job_id, fields,
1805
                           prev_job_info, prev_log_serial):
1806
    """Waits for changes on a job.
1807

1808
    """
1809
    raise NotImplementedError()
1810

    
1811
  def QueryJobs(self, job_ids, fields):
1812
    """Returns the selected fields for the selected job IDs.
1813

1814
    @type job_ids: list of numbers
1815
    @param job_ids: Job IDs
1816
    @type fields: list of strings
1817
    @param fields: Fields
1818

1819
    """
1820
    raise NotImplementedError()
1821

    
1822

    
1823
class JobPollReportCbBase:
1824
  """Base class for L{GenericPollJob} reporting callbacks.
1825

1826
  """
1827
  def __init__(self):
1828
    """Initializes this class.
1829

1830
    """
1831

    
1832
  def ReportLogMessage(self, job_id, serial, timestamp, log_type, log_msg):
1833
    """Handles a log message.
1834

1835
    """
1836
    raise NotImplementedError()
1837

    
1838
  def ReportNotChanged(self, job_id, status):
1839
    """Called for if a job hasn't changed in a while.
1840

1841
    @type job_id: number
1842
    @param job_id: Job ID
1843
    @type status: string or None
1844
    @param status: Job status if available
1845

1846
    """
1847
    raise NotImplementedError()
1848

    
1849

    
1850
class _LuxiJobPollCb(JobPollCbBase):
1851
  def __init__(self, cl):
1852
    """Initializes this class.
1853

1854
    """
1855
    JobPollCbBase.__init__(self)
1856
    self.cl = cl
1857

    
1858
  def WaitForJobChangeOnce(self, job_id, fields,
1859
                           prev_job_info, prev_log_serial):
1860
    """Waits for changes on a job.
1861

1862
    """
1863
    return self.cl.WaitForJobChangeOnce(job_id, fields,
1864
                                        prev_job_info, prev_log_serial)
1865

    
1866
  def QueryJobs(self, job_ids, fields):
1867
    """Returns the selected fields for the selected job IDs.
1868

1869
    """
1870
    return self.cl.QueryJobs(job_ids, fields)
1871

    
1872

    
1873
class FeedbackFnJobPollReportCb(JobPollReportCbBase):
1874
  def __init__(self, feedback_fn):
1875
    """Initializes this class.
1876

1877
    """
1878
    JobPollReportCbBase.__init__(self)
1879

    
1880
    self.feedback_fn = feedback_fn
1881

    
1882
    assert callable(feedback_fn)
1883

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

1887
    """
1888
    self.feedback_fn((timestamp, log_type, log_msg))
1889

    
1890
  def ReportNotChanged(self, job_id, status):
1891
    """Called if a job hasn't changed in a while.
1892

1893
    """
1894
    # Ignore
1895

    
1896

    
1897
class StdioJobPollReportCb(JobPollReportCbBase):
1898
  def __init__(self):
1899
    """Initializes this class.
1900

1901
    """
1902
    JobPollReportCbBase.__init__(self)
1903

    
1904
    self.notified_queued = False
1905
    self.notified_waitlock = False
1906

    
1907
  def ReportLogMessage(self, job_id, serial, timestamp, log_type, log_msg):
1908
    """Handles a log message.
1909

1910
    """
1911
    ToStdout("%s %s", time.ctime(utils.MergeTime(timestamp)),
1912
             FormatLogMessage(log_type, log_msg))
1913

    
1914
  def ReportNotChanged(self, job_id, status):
1915
    """Called if a job hasn't changed in a while.
1916

1917
    """
1918
    if status is None:
1919
      return
1920

    
1921
    if status == constants.JOB_STATUS_QUEUED and not self.notified_queued:
1922
      ToStderr("Job %s is waiting in queue", job_id)
1923
      self.notified_queued = True
1924

    
1925
    elif status == constants.JOB_STATUS_WAITING and not self.notified_waitlock:
1926
      ToStderr("Job %s is trying to acquire all necessary locks", job_id)
1927
      self.notified_waitlock = True
1928

    
1929

    
1930
def FormatLogMessage(log_type, log_msg):
1931
  """Formats a job message according to its type.
1932

1933
  """
1934
  if log_type != constants.ELOG_MESSAGE:
1935
    log_msg = str(log_msg)
1936

    
1937
  return utils.SafeEncode(log_msg)
1938

    
1939

    
1940
def PollJob(job_id, cl=None, feedback_fn=None, reporter=None):
1941
  """Function to poll for the result of a job.
1942

1943
  @type job_id: job identified
1944
  @param job_id: the job to poll for results
1945
  @type cl: luxi.Client
1946
  @param cl: the luxi client to use for communicating with the master;
1947
             if None, a new client will be created
1948

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

    
1953
  if reporter is None:
1954
    if feedback_fn:
1955
      reporter = FeedbackFnJobPollReportCb(feedback_fn)
1956
    else:
1957
      reporter = StdioJobPollReportCb()
1958
  elif feedback_fn:
1959
    raise errors.ProgrammerError("Can't specify reporter and feedback function")
1960

    
1961
  return GenericPollJob(job_id, _LuxiJobPollCb(cl), reporter)
1962

    
1963

    
1964
def SubmitOpCode(op, cl=None, feedback_fn=None, opts=None, reporter=None):
1965
  """Legacy function to submit an opcode.
1966

1967
  This is just a simple wrapper over the construction of the processor
1968
  instance. It should be extended to better handle feedback and
1969
  interaction functions.
1970

1971
  """
1972
  if cl is None:
1973
    cl = GetClient()
1974

    
1975
  SetGenericOpcodeOpts([op], opts)
1976

    
1977
  job_id = SendJob([op], cl=cl)
1978

    
1979
  op_results = PollJob(job_id, cl=cl, feedback_fn=feedback_fn,
1980
                       reporter=reporter)
1981

    
1982
  return op_results[0]
1983

    
1984

    
1985
def SubmitOrSend(op, opts, cl=None, feedback_fn=None):
1986
  """Wrapper around SubmitOpCode or SendJob.
1987

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

1993
  It will also process the opcodes if we're sending the via SendJob
1994
  (otherwise SubmitOpCode does it).
1995

1996
  """
1997
  if opts and opts.submit_only:
1998
    job = [op]
1999
    SetGenericOpcodeOpts(job, opts)
2000
    job_id = SendJob(job, cl=cl)
2001
    raise JobSubmittedException(job_id)
2002
  else:
2003
    return SubmitOpCode(op, cl=cl, feedback_fn=feedback_fn, opts=opts)
2004

    
2005

    
2006
def SetGenericOpcodeOpts(opcode_list, options):
2007
  """Processor for generic options.
2008

2009
  This function updates the given opcodes based on generic command
2010
  line options (like debug, dry-run, etc.).
2011

2012
  @param opcode_list: list of opcodes
2013
  @param options: command line options or None
2014
  @return: None (in-place modification)
2015

2016
  """
2017
  if not options:
2018
    return
2019
  for op in opcode_list:
2020
    op.debug_level = options.debug
2021
    if hasattr(options, "dry_run"):
2022
      op.dry_run = options.dry_run
2023
    if getattr(options, "priority", None) is not None:
2024
      op.priority = _PRIONAME_TO_VALUE[options.priority]
2025

    
2026

    
2027
def GetClient():
2028
  # TODO: Cache object?
2029
  try:
2030
    client = luxi.Client()
2031
  except luxi.NoMasterError:
2032
    ss = ssconf.SimpleStore()
2033

    
2034
    # Try to read ssconf file
2035
    try:
2036
      ss.GetMasterNode()
2037
    except errors.ConfigurationError:
2038
      raise errors.OpPrereqError("Cluster not initialized or this machine is"
2039
                                 " not part of a cluster")
2040

    
2041
    master, myself = ssconf.GetMasterAndMyself(ss=ss)
2042
    if master != myself:
2043
      raise errors.OpPrereqError("This is not the master node, please connect"
2044
                                 " to node '%s' and rerun the command" %
2045
                                 master)
2046
    raise
2047
  return client
2048

    
2049

    
2050
def FormatError(err):
2051
  """Return a formatted error message for a given error.
2052

2053
  This function takes an exception instance and returns a tuple
2054
  consisting of two values: first, the recommended exit code, and
2055
  second, a string describing the error message (not
2056
  newline-terminated).
2057

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

    
2137

    
2138
def GenericMain(commands, override=None, aliases=None,
2139
                env_override=frozenset()):
2140
  """Generic main function for all the gnt-* commands.
2141

2142
  @param commands: a dictionary with a special structure, see the design doc
2143
                   for command line handling.
2144
  @param override: if not None, we expect a dictionary with keys that will
2145
                   override command line options; this can be used to pass
2146
                   options from the scripts to generic functions
2147
  @param aliases: dictionary with command aliases {'alias': 'target, ...}
2148
  @param env_override: list of environment names which are allowed to submit
2149
                       default args for commands
2150

2151
  """
2152
  # save the program name and the entire command line for later logging
2153
  if sys.argv:
2154
    binary = os.path.basename(sys.argv[0])
2155
    if not binary:
2156
      binary = sys.argv[0]
2157

    
2158
    if len(sys.argv) >= 2:
2159
      logname = utils.ShellQuoteArgs([binary, sys.argv[1]])
2160
    else:
2161
      logname = binary
2162

    
2163
    cmdline = utils.ShellQuoteArgs([binary] + sys.argv[1:])
2164
  else:
2165
    binary = "<unknown program>"
2166
    cmdline = "<unknown>"
2167

    
2168
  if aliases is None:
2169
    aliases = {}
2170

    
2171
  try:
2172
    func, options, args = _ParseArgs(sys.argv, commands, aliases, env_override)
2173
  except errors.ParameterError, err:
2174
    result, err_msg = FormatError(err)
2175
    ToStderr(err_msg)
2176
    return 1
2177

    
2178
  if func is None: # parse error
2179
    return 1
2180

    
2181
  if override is not None:
2182
    for key, val in override.iteritems():
2183
      setattr(options, key, val)
2184

    
2185
  utils.SetupLogging(constants.LOG_COMMANDS, logname, debug=options.debug,
2186
                     stderr_logging=True)
2187

    
2188
  logging.info("Command line: %s", cmdline)
2189

    
2190
  try:
2191
    result = func(options, args)
2192
  except (errors.GenericError, luxi.ProtocolError,
2193
          JobSubmittedException), err:
2194
    result, err_msg = FormatError(err)
2195
    logging.exception("Error during command processing")
2196
    ToStderr(err_msg)
2197
  except KeyboardInterrupt:
2198
    result = constants.EXIT_FAILURE
2199
    ToStderr("Aborted. Note that if the operation created any jobs, they"
2200
             " might have been submitted and"
2201
             " will continue to run in the background.")
2202
  except IOError, err:
2203
    if err.errno == errno.EPIPE:
2204
      # our terminal went away, we'll exit
2205
      sys.exit(constants.EXIT_FAILURE)
2206
    else:
2207
      raise
2208

    
2209
  return result
2210

    
2211

    
2212
def ParseNicOption(optvalue):
2213
  """Parses the value of the --net option(s).
2214

2215
  """
2216
  try:
2217
    nic_max = max(int(nidx[0]) + 1 for nidx in optvalue)
2218
  except (TypeError, ValueError), err:
2219
    raise errors.OpPrereqError("Invalid NIC index passed: %s" % str(err))
2220

    
2221
  nics = [{}] * nic_max
2222
  for nidx, ndict in optvalue:
2223
    nidx = int(nidx)
2224

    
2225
    if not isinstance(ndict, dict):
2226
      raise errors.OpPrereqError("Invalid nic/%d value: expected dict,"
2227
                                 " got %s" % (nidx, ndict))
2228

    
2229
    utils.ForceDictType(ndict, constants.INIC_PARAMS_TYPES)
2230

    
2231
    nics[nidx] = ndict
2232

    
2233
  return nics
2234

    
2235

    
2236
def GenericInstanceCreate(mode, opts, args):
2237
  """Add an instance to the cluster via either creation or import.
2238

2239
  @param mode: constants.INSTANCE_CREATE or constants.INSTANCE_IMPORT
2240
  @param opts: the command line options selected by the user
2241
  @type args: list
2242
  @param args: should contain only one element, the new instance name
2243
  @rtype: int
2244
  @return: the desired exit code
2245

2246
  """
2247
  instance = args[0]
2248

    
2249
  (pnode, snode) = SplitNodeOption(opts.node)
2250

    
2251
  hypervisor = None
2252
  hvparams = {}
2253
  if opts.hypervisor:
2254
    hypervisor, hvparams = opts.hypervisor
2255

    
2256
  if opts.nics:
2257
    nics = ParseNicOption(opts.nics)
2258
  elif opts.no_nics:
2259
    # no nics
2260
    nics = []
2261
  elif mode == constants.INSTANCE_CREATE:
2262
    # default of one nic, all auto
2263
    nics = [{}]
2264
  else:
2265
    # mode == import
2266
    nics = []
2267

    
2268
  if opts.disk_template == constants.DT_DISKLESS:
2269
    if opts.disks or opts.sd_size is not None:
2270
      raise errors.OpPrereqError("Diskless instance but disk"
2271
                                 " information passed")
2272
    disks = []
2273
  else:
2274
    if (not opts.disks and not opts.sd_size
2275
        and mode == constants.INSTANCE_CREATE):
2276
      raise errors.OpPrereqError("No disk information specified")
2277
    if opts.disks and opts.sd_size is not None:
2278
      raise errors.OpPrereqError("Please use either the '--disk' or"
2279
                                 " '-s' option")
2280
    if opts.sd_size is not None:
2281
      opts.disks = [(0, {constants.IDISK_SIZE: opts.sd_size})]
2282

    
2283
    if opts.disks:
2284
      try:
2285
        disk_max = max(int(didx[0]) + 1 for didx in opts.disks)
2286
      except ValueError, err:
2287
        raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err))
2288
      disks = [{}] * disk_max
2289
    else:
2290
      disks = []
2291
    for didx, ddict in opts.disks:
2292
      didx = int(didx)
2293
      if not isinstance(ddict, dict):
2294
        msg = "Invalid disk/%d value: expected dict, got %s" % (didx, ddict)
2295
        raise errors.OpPrereqError(msg)
2296
      elif constants.IDISK_SIZE in ddict:
2297
        if constants.IDISK_ADOPT in ddict:
2298
          raise errors.OpPrereqError("Only one of 'size' and 'adopt' allowed"
2299
                                     " (disk %d)" % didx)
2300
        try:
2301
          ddict[constants.IDISK_SIZE] = \
2302
            utils.ParseUnit(ddict[constants.IDISK_SIZE])
2303
        except ValueError, err:
2304
          raise errors.OpPrereqError("Invalid disk size for disk %d: %s" %
2305
                                     (didx, err))
2306
      elif constants.IDISK_ADOPT in ddict:
2307
        if mode == constants.INSTANCE_IMPORT:
2308
          raise errors.OpPrereqError("Disk adoption not allowed for instance"
2309
                                     " import")
2310
        ddict[constants.IDISK_SIZE] = 0
2311
      else:
2312
        raise errors.OpPrereqError("Missing size or adoption source for"
2313
                                   " disk %d" % didx)
2314
      disks[didx] = ddict
2315

    
2316
  if opts.tags is not None:
2317
    tags = opts.tags.split(",")
2318
  else:
2319
    tags = []
2320

    
2321
  utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_COMPAT)
2322
  utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
2323

    
2324
  if mode == constants.INSTANCE_CREATE:
2325
    start = opts.start
2326
    os_type = opts.os
2327
    force_variant = opts.force_variant
2328
    src_node = None
2329
    src_path = None
2330
    no_install = opts.no_install
2331
    identify_defaults = False
2332
  elif mode == constants.INSTANCE_IMPORT:
2333
    start = False
2334
    os_type = None
2335
    force_variant = False
2336
    src_node = opts.src_node
2337
    src_path = opts.src_dir
2338
    no_install = None
2339
    identify_defaults = opts.identify_defaults
2340
  else:
2341
    raise errors.ProgrammerError("Invalid creation mode %s" % mode)
2342

    
2343
  op = opcodes.OpInstanceCreate(instance_name=instance,
2344
                                disks=disks,
2345
                                disk_template=opts.disk_template,
2346
                                nics=nics,
2347
                                pnode=pnode, snode=snode,
2348
                                ip_check=opts.ip_check,
2349
                                name_check=opts.name_check,
2350
                                wait_for_sync=opts.wait_for_sync,
2351
                                file_storage_dir=opts.file_storage_dir,
2352
                                file_driver=opts.file_driver,
2353
                                iallocator=opts.iallocator,
2354
                                hypervisor=hypervisor,
2355
                                hvparams=hvparams,
2356
                                beparams=opts.beparams,
2357
                                osparams=opts.osparams,
2358
                                mode=mode,
2359
                                start=start,
2360
                                os_type=os_type,
2361
                                force_variant=force_variant,
2362
                                src_node=src_node,
2363
                                src_path=src_path,
2364
                                tags=tags,
2365
                                no_install=no_install,
2366
                                identify_defaults=identify_defaults,
2367
                                ignore_ipolicy=opts.ignore_ipolicy)
2368

    
2369
  SubmitOrSend(op, opts)
2370
  return 0
2371

    
2372

    
2373
class _RunWhileClusterStoppedHelper:
2374
  """Helper class for L{RunWhileClusterStopped} to simplify state management
2375

2376
  """
2377
  def __init__(self, feedback_fn, cluster_name, master_node, online_nodes):
2378
    """Initializes this class.
2379

2380
    @type feedback_fn: callable
2381
    @param feedback_fn: Feedback function
2382
    @type cluster_name: string
2383
    @param cluster_name: Cluster name
2384
    @type master_node: string
2385
    @param master_node Master node name
2386
    @type online_nodes: list
2387
    @param online_nodes: List of names of online nodes
2388

2389
    """
2390
    self.feedback_fn = feedback_fn
2391
    self.cluster_name = cluster_name
2392
    self.master_node = master_node
2393
    self.online_nodes = online_nodes
2394

    
2395
    self.ssh = ssh.SshRunner(self.cluster_name)
2396

    
2397
    self.nonmaster_nodes = [name for name in online_nodes
2398
                            if name != master_node]
2399

    
2400
    assert self.master_node not in self.nonmaster_nodes
2401

    
2402
  def _RunCmd(self, node_name, cmd):
2403
    """Runs a command on the local or a remote machine.
2404

2405
    @type node_name: string
2406
    @param node_name: Machine name
2407
    @type cmd: list
2408
    @param cmd: Command
2409

2410
    """
2411
    if node_name is None or node_name == self.master_node:
2412
      # No need to use SSH
2413
      result = utils.RunCmd(cmd)
2414
    else:
2415
      result = self.ssh.Run(node_name, "root", utils.ShellQuoteArgs(cmd))
2416

    
2417
    if result.failed:
2418
      errmsg = ["Failed to run command %s" % result.cmd]
2419
      if node_name:
2420
        errmsg.append("on node %s" % node_name)
2421
      errmsg.append(": exitcode %s and error %s" %
2422
                    (result.exit_code, result.output))
2423
      raise errors.OpExecError(" ".join(errmsg))
2424

    
2425
  def Call(self, fn, *args):
2426
    """Call function while all daemons are stopped.
2427

2428
    @type fn: callable
2429
    @param fn: Function to be called
2430

2431
    """
2432
    # Pause watcher by acquiring an exclusive lock on watcher state file
2433
    self.feedback_fn("Blocking watcher")
2434
    watcher_block = utils.FileLock.Open(constants.WATCHER_LOCK_FILE)
2435
    try:
2436
      # TODO: Currently, this just blocks. There's no timeout.
2437
      # TODO: Should it be a shared lock?
2438
      watcher_block.Exclusive(blocking=True)
2439

    
2440
      # Stop master daemons, so that no new jobs can come in and all running
2441
      # ones are finished
2442
      self.feedback_fn("Stopping master daemons")
2443
      self._RunCmd(None, [constants.DAEMON_UTIL, "stop-master"])
2444
      try:
2445
        # Stop daemons on all nodes
2446
        for node_name in self.online_nodes:
2447
          self.feedback_fn("Stopping daemons on %s" % node_name)
2448
          self._RunCmd(node_name, [constants.DAEMON_UTIL, "stop-all"])
2449

    
2450
        # All daemons are shut down now
2451
        try:
2452
          return fn(self, *args)
2453
        except Exception, err:
2454
          _, errmsg = FormatError(err)
2455
          logging.exception("Caught exception")
2456
          self.feedback_fn(errmsg)
2457
          raise
2458
      finally:
2459
        # Start cluster again, master node last
2460
        for node_name in self.nonmaster_nodes + [self.master_node]:
2461
          self.feedback_fn("Starting daemons on %s" % node_name)
2462
          self._RunCmd(node_name, [constants.DAEMON_UTIL, "start-all"])
2463
    finally:
2464
      # Resume watcher
2465
      watcher_block.Close()
2466

    
2467

    
2468
def RunWhileClusterStopped(feedback_fn, fn, *args):
2469
  """Calls a function while all cluster daemons are stopped.
2470

2471
  @type feedback_fn: callable
2472
  @param feedback_fn: Feedback function
2473
  @type fn: callable
2474
  @param fn: Function to be called when daemons are stopped
2475

2476
  """
2477
  feedback_fn("Gathering cluster information")
2478

    
2479
  # This ensures we're running on the master daemon
2480
  cl = GetClient()
2481

    
2482
  (cluster_name, master_node) = \
2483
    cl.QueryConfigValues(["cluster_name", "master_node"])
2484

    
2485
  online_nodes = GetOnlineNodes([], cl=cl)
2486

    
2487
  # Don't keep a reference to the client. The master daemon will go away.
2488
  del cl
2489

    
2490
  assert master_node in online_nodes
2491

    
2492
  return _RunWhileClusterStoppedHelper(feedback_fn, cluster_name, master_node,
2493
                                       online_nodes).Call(fn, *args)
2494

    
2495

    
2496
def GenerateTable(headers, fields, separator, data,
2497
                  numfields=None, unitfields=None,
2498
                  units=None):
2499
  """Prints a table with headers and different fields.
2500

2501
  @type headers: dict
2502
  @param headers: dictionary mapping field names to headers for
2503
      the table
2504
  @type fields: list
2505
  @param fields: the field names corresponding to each row in
2506
      the data field
2507
  @param separator: the separator to be used; if this is None,
2508
      the default 'smart' algorithm is used which computes optimal
2509
      field width, otherwise just the separator is used between
2510
      each field
2511
  @type data: list
2512
  @param data: a list of lists, each sublist being one row to be output
2513
  @type numfields: list
2514
  @param numfields: a list with the fields that hold numeric
2515
      values and thus should be right-aligned
2516
  @type unitfields: list
2517
  @param unitfields: a list with the fields that hold numeric
2518
      values that should be formatted with the units field
2519
  @type units: string or None
2520
  @param units: the units we should use for formatting, or None for
2521
      automatic choice (human-readable for non-separator usage, otherwise
2522
      megabytes); this is a one-letter string
2523

2524
  """
2525
  if units is None:
2526
    if separator:
2527
      units = "m"
2528
    else:
2529
      units = "h"
2530

    
2531
  if numfields is None:
2532
    numfields = []
2533
  if unitfields is None:
2534
    unitfields = []
2535

    
2536
  numfields = utils.FieldSet(*numfields)   # pylint: disable=W0142
2537
  unitfields = utils.FieldSet(*unitfields) # pylint: disable=W0142
2538

    
2539
  format_fields = []
2540
  for field in fields:
2541
    if headers and field not in headers:
2542
      # TODO: handle better unknown fields (either revert to old
2543
      # style of raising exception, or deal more intelligently with
2544
      # variable fields)
2545
      headers[field] = field
2546
    if separator is not None:
2547
      format_fields.append("%s")
2548
    elif numfields.Matches(field):
2549
      format_fields.append("%*s")
2550
    else:
2551
      format_fields.append("%-*s")
2552

    
2553
  if separator is None:
2554
    mlens = [0 for name in fields]
2555
    format_str = " ".join(format_fields)
2556
  else:
2557
    format_str = separator.replace("%", "%%").join(format_fields)
2558

    
2559
  for row in data:
2560
    if row is None:
2561
      continue
2562
    for idx, val in enumerate(row):
2563
      if unitfields.Matches(fields[idx]):
2564
        try:
2565
          val = int(val)
2566
        except (TypeError, ValueError):
2567
          pass
2568
        else:
2569
          val = row[idx] = utils.FormatUnit(val, units)
2570
      val = row[idx] = str(val)
2571
      if separator is None:
2572
        mlens[idx] = max(mlens[idx], len(val))
2573

    
2574
  result = []
2575
  if headers:
2576
    args = []
2577
    for idx, name in enumerate(fields):
2578
      hdr = headers[name]
2579
      if separator is None:
2580
        mlens[idx] = max(mlens[idx], len(hdr))
2581
        args.append(mlens[idx])
2582
      args.append(hdr)
2583
    result.append(format_str % tuple(args))
2584

    
2585
  if separator is None:
2586
    assert len(mlens) == len(fields)
2587

    
2588
    if fields and not numfields.Matches(fields[-1]):
2589
      mlens[-1] = 0
2590

    
2591
  for line in data:
2592
    args = []
2593
    if line is None:
2594
      line = ["-" for _ in fields]
2595
    for idx in range(len(fields)):
2596
      if separator is None:
2597
        args.append(mlens[idx])
2598
      args.append(line[idx])
2599
    result.append(format_str % tuple(args))
2600

    
2601
  return result
2602

    
2603

    
2604
def _FormatBool(value):
2605
  """Formats a boolean value as a string.
2606

2607
  """
2608
  if value:
2609
    return "Y"
2610
  return "N"
2611

    
2612

    
2613
#: Default formatting for query results; (callback, align right)
2614
_DEFAULT_FORMAT_QUERY = {
2615
  constants.QFT_TEXT: (str, False),
2616
  constants.QFT_BOOL: (_FormatBool, False),
2617
  constants.QFT_NUMBER: (str, True),
2618
  constants.QFT_TIMESTAMP: (utils.FormatTime, False),
2619
  constants.QFT_OTHER: (str, False),
2620
  constants.QFT_UNKNOWN: (str, False),
2621
  }
2622

    
2623

    
2624
def _GetColumnFormatter(fdef, override, unit):
2625
  """Returns formatting function for a field.
2626

2627
  @type fdef: L{objects.QueryFieldDefinition}
2628
  @type override: dict
2629
  @param override: Dictionary for overriding field formatting functions,
2630
    indexed by field name, contents like L{_DEFAULT_FORMAT_QUERY}
2631
  @type unit: string
2632
  @param unit: Unit used for formatting fields of type L{constants.QFT_UNIT}
2633
  @rtype: tuple; (callable, bool)
2634
  @return: Returns the function to format a value (takes one parameter) and a
2635
    boolean for aligning the value on the right-hand side
2636

2637
  """
2638
  fmt = override.get(fdef.name, None)
2639
  if fmt is not None:
2640
    return fmt
2641

    
2642
  assert constants.QFT_UNIT not in _DEFAULT_FORMAT_QUERY
2643

    
2644
  if fdef.kind == constants.QFT_UNIT:
2645
    # Can't keep this information in the static dictionary
2646
    return (lambda value: utils.FormatUnit(value, unit), True)
2647

    
2648
  fmt = _DEFAULT_FORMAT_QUERY.get(fdef.kind, None)
2649
  if fmt is not None:
2650
    return fmt
2651

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

    
2654

    
2655
class _QueryColumnFormatter:
2656
  """Callable class for formatting fields of a query.
2657

2658
  """
2659
  def __init__(self, fn, status_fn, verbose):
2660
    """Initializes this class.
2661

2662
    @type fn: callable
2663
    @param fn: Formatting function
2664
    @type status_fn: callable
2665
    @param status_fn: Function to report fields' status
2666
    @type verbose: boolean
2667
    @param verbose: whether to use verbose field descriptions or not
2668

2669
    """
2670
    self._fn = fn
2671
    self._status_fn = status_fn
2672
    self._verbose = verbose
2673

    
2674
  def __call__(self, data):
2675
    """Returns a field's string representation.
2676

2677
    """
2678
    (status, value) = data
2679

    
2680
    # Report status
2681
    self._status_fn(status)
2682

    
2683
    if status == constants.RS_NORMAL:
2684
      return self._fn(value)
2685

    
2686
    assert value is None, \
2687
           "Found value %r for abnormal status %s" % (value, status)
2688

    
2689
    return FormatResultError(status, self._verbose)
2690

    
2691

    
2692
def FormatResultError(status, verbose):
2693
  """Formats result status other than L{constants.RS_NORMAL}.
2694

2695
  @param status: The result status
2696
  @type verbose: boolean
2697
  @param verbose: Whether to return the verbose text
2698
  @return: Text of result status
2699

2700
  """
2701
  assert status != constants.RS_NORMAL, \
2702
         "FormatResultError called with status equal to constants.RS_NORMAL"
2703
  try:
2704
    (verbose_text, normal_text) = constants.RSS_DESCRIPTION[status]
2705
  except KeyError:
2706
    raise NotImplementedError("Unknown status %s" % status)
2707
  else:
2708
    if verbose:
2709
      return verbose_text
2710
    return normal_text
2711

    
2712

    
2713
def FormatQueryResult(result, unit=None, format_override=None, separator=None,
2714
                      header=False, verbose=False):
2715
  """Formats data in L{objects.QueryResponse}.
2716

2717
  @type result: L{objects.QueryResponse}
2718
  @param result: result of query operation
2719
  @type unit: string
2720
  @param unit: Unit used for formatting fields of type L{constants.QFT_UNIT},
2721
    see L{utils.text.FormatUnit}
2722
  @type format_override: dict
2723
  @param format_override: Dictionary for overriding field formatting functions,
2724
    indexed by field name, contents like L{_DEFAULT_FORMAT_QUERY}
2725
  @type separator: string or None
2726
  @param separator: String used to separate fields
2727
  @type header: bool
2728
  @param header: Whether to output header row
2729
  @type verbose: boolean
2730
  @param verbose: whether to use verbose field descriptions or not
2731

2732
  """
2733
  if unit is None:
2734
    if separator:
2735
      unit = "m"
2736
    else:
2737
      unit = "h"
2738

    
2739
  if format_override is None:
2740
    format_override = {}
2741

    
2742
  stats = dict.fromkeys(constants.RS_ALL, 0)
2743

    
2744
  def _RecordStatus(status):
2745
    if status in stats:
2746
      stats[status] += 1
2747

    
2748
  columns = []
2749
  for fdef in result.fields:
2750
    assert fdef.title and fdef.name
2751
    (fn, align_right) = _GetColumnFormatter(fdef, format_override, unit)
2752
    columns.append(TableColumn(fdef.title,
2753
                               _QueryColumnFormatter(fn, _RecordStatus,
2754
                                                     verbose),
2755
                               align_right))
2756

    
2757
  table = FormatTable(result.data, columns, header, separator)
2758

    
2759
  # Collect statistics
2760
  assert len(stats) == len(constants.RS_ALL)
2761
  assert compat.all(count >= 0 for count in stats.values())
2762

    
2763
  # Determine overall status. If there was no data, unknown fields must be
2764
  # detected via the field definitions.
2765
  if (stats[constants.RS_UNKNOWN] or
2766
      (not result.data and _GetUnknownFields(result.fields))):
2767
    status = QR_UNKNOWN
2768
  elif compat.any(count > 0 for key, count in stats.items()
2769
                  if key != constants.RS_NORMAL):
2770
    status = QR_INCOMPLETE
2771
  else:
2772
    status = QR_NORMAL
2773

    
2774
  return (status, table)
2775

    
2776

    
2777
def _GetUnknownFields(fdefs):
2778
  """Returns list of unknown fields included in C{fdefs}.
2779

2780
  @type fdefs: list of L{objects.QueryFieldDefinition}
2781

2782
  """
2783
  return [fdef for fdef in fdefs
2784
          if fdef.kind == constants.QFT_UNKNOWN]
2785

    
2786

    
2787
def _WarnUnknownFields(fdefs):
2788
  """Prints a warning to stderr if a query included unknown fields.
2789

2790
  @type fdefs: list of L{objects.QueryFieldDefinition}
2791

2792
  """
2793
  unknown = _GetUnknownFields(fdefs)
2794
  if unknown:
2795
    ToStderr("Warning: Queried for unknown fields %s",
2796
             utils.CommaJoin(fdef.name for fdef in unknown))
2797
    return True
2798

    
2799
  return False
2800

    
2801

    
2802
def GenericList(resource, fields, names, unit, separator, header, cl=None,
2803
                format_override=None, verbose=False, force_filter=False):
2804
  """Generic implementation for listing all items of a resource.
2805

2806
  @param resource: One of L{constants.QR_VIA_LUXI}
2807
  @type fields: list of strings
2808
  @param fields: List of fields to query for
2809
  @type names: list of strings
2810
  @param names: Names of items to query for
2811
  @type unit: string or None
2812
  @param unit: Unit used for formatting fields of type L{constants.QFT_UNIT} or
2813
    None for automatic choice (human-readable for non-separator usage,
2814
    otherwise megabytes); this is a one-letter string
2815
  @type separator: string or None
2816
  @param separator: String used to separate fields
2817
  @type header: bool
2818
  @param header: Whether to show header row
2819
  @type force_filter: bool
2820
  @param force_filter: Whether to always treat names as filter
2821
  @type format_override: dict
2822
  @param format_override: Dictionary for overriding field formatting functions,
2823
    indexed by field name, contents like L{_DEFAULT_FORMAT_QUERY}
2824
  @type verbose: boolean
2825
  @param verbose: whether to use verbose field descriptions or not
2826

2827
  """
2828
  if not names:
2829
    names = None
2830

    
2831
  qfilter = qlang.MakeFilter(names, force_filter)
2832

    
2833
  if cl is None:
2834
    cl = GetClient()
2835

    
2836
  response = cl.Query(resource, fields, qfilter)
2837

    
2838
  found_unknown = _WarnUnknownFields(response.fields)
2839

    
2840
  (status, data) = FormatQueryResult(response, unit=unit, separator=separator,
2841
                                     header=header,
2842
                                     format_override=format_override,
2843
                                     verbose=verbose)
2844

    
2845
  for line in data:
2846
    ToStdout(line)
2847

    
2848
  assert ((found_unknown and status == QR_UNKNOWN) or
2849
          (not found_unknown and status != QR_UNKNOWN))
2850

    
2851
  if status == QR_UNKNOWN:
2852
    return constants.EXIT_UNKNOWN_FIELD
2853

    
2854
  # TODO: Should the list command fail if not all data could be collected?
2855
  return constants.EXIT_SUCCESS
2856

    
2857

    
2858
def GenericListFields(resource, fields, separator, header, cl=None):
2859
  """Generic implementation for listing fields for a resource.
2860

2861
  @param resource: One of L{constants.QR_VIA_LUXI}
2862
  @type fields: list of strings
2863
  @param fields: List of fields to query for
2864
  @type separator: string or None
2865
  @param separator: String used to separate fields
2866
  @type header: bool
2867
  @param header: Whether to show header row
2868

2869
  """
2870
  if cl is None:
2871
    cl = GetClient()
2872

    
2873
  if not fields:
2874
    fields = None
2875

    
2876
  response = cl.QueryFields(resource, fields)
2877

    
2878
  found_unknown = _WarnUnknownFields(response.fields)
2879

    
2880
  columns = [
2881
    TableColumn("Name", str, False),
2882
    TableColumn("Title", str, False),
2883
    TableColumn("Description", str, False),
2884
    ]
2885

    
2886
  rows = [[fdef.name, fdef.title, fdef.doc] for fdef in response.fields]
2887

    
2888
  for line in FormatTable(rows, columns, header, separator):
2889
    ToStdout(line)
2890

    
2891
  if found_unknown:
2892
    return constants.EXIT_UNKNOWN_FIELD
2893

    
2894
  return constants.EXIT_SUCCESS
2895

    
2896

    
2897
class TableColumn:
2898
  """Describes a column for L{FormatTable}.
2899

2900
  """
2901
  def __init__(self, title, fn, align_right):
2902
    """Initializes this class.
2903

2904
    @type title: string
2905
    @param title: Column title
2906
    @type fn: callable
2907
    @param fn: Formatting function
2908
    @type align_right: bool
2909
    @param align_right: Whether to align values on the right-hand side
2910

2911
    """
2912
    self.title = title
2913
    self.format = fn
2914
    self.align_right = align_right
2915

    
2916

    
2917
def _GetColFormatString(width, align_right):
2918
  """Returns the format string for a field.
2919

2920
  """
2921
  if align_right:
2922
    sign = ""
2923
  else:
2924
    sign = "-"
2925

    
2926
  return "%%%s%ss" % (sign, width)
2927

    
2928

    
2929
def FormatTable(rows, columns, header, separator):
2930
  """Formats data as a table.
2931

2932
  @type rows: list of lists
2933
  @param rows: Row data, one list per row
2934
  @type columns: list of L{TableColumn}
2935
  @param columns: Column descriptions
2936
  @type header: bool
2937
  @param header: Whether to show header row
2938
  @type separator: string or None
2939
  @param separator: String used to separate columns
2940

2941
  """
2942
  if header:
2943
    data = [[col.title for col in columns]]
2944
    colwidth = [len(col.title) for col in columns]
2945
  else:
2946
    data = []
2947
    colwidth = [0 for _ in columns]
2948

    
2949
  # Format row data
2950
  for row in rows:
2951
    assert len(row) == len(columns)
2952

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

    
2955
    if separator is None:
2956
      # Update column widths
2957
      for idx, (oldwidth, value) in enumerate(zip(colwidth, formatted)):
2958
        # Modifying a list's items while iterating is fine
2959
        colwidth[idx] = max(oldwidth, len(value))
2960

    
2961
    data.append(formatted)
2962

    
2963
  if separator is not None:
2964
    # Return early if a separator is used
2965
    return [separator.join(row) for row in data]
2966

    
2967
  if columns and not columns[-1].align_right:
2968
    # Avoid unnecessary spaces at end of line
2969
    colwidth[-1] = 0
2970

    
2971
  # Build format string
2972
  fmt = " ".join([_GetColFormatString(width, col.align_right)
2973
                  for col, width in zip(columns, colwidth)])
2974

    
2975
  return [fmt % tuple(row) for row in data]
2976

    
2977

    
2978
def FormatTimestamp(ts):
2979
  """Formats a given timestamp.
2980

2981
  @type ts: timestamp
2982
  @param ts: a timeval-type timestamp, a tuple of seconds and microseconds
2983

2984
  @rtype: string
2985
  @return: a string with the formatted timestamp
2986

2987
  """
2988
  if not isinstance(ts, (tuple, list)) or len(ts) != 2:
2989
    return "?"
2990
  sec, usec = ts
2991
  return time.strftime("%F %T", time.localtime(sec)) + ".%06d" % usec
2992

    
2993

    
2994
def ParseTimespec(value):
2995
  """Parse a time specification.
2996

2997
  The following suffixed will be recognized:
2998

2999
    - s: seconds
3000
    - m: minutes
3001
    - h: hours
3002
    - d: day
3003
    - w: weeks
3004

3005
  Without any suffix, the value will be taken to be in seconds.
3006

3007
  """
3008
  value = str(value)
3009
  if not value:
3010
    raise errors.OpPrereqError("Empty time specification passed")
3011
  suffix_map = {
3012
    "s": 1,
3013
    "m": 60,
3014
    "h": 3600,
3015
    "d": 86400,
3016
    "w": 604800,
3017
    }
3018
  if value[-1] not in suffix_map:
3019
    try:
3020
      value = int(value)
3021
    except (TypeError, ValueError):
3022
      raise errors.OpPrereqError("Invalid time specification '%s'" % value)
3023
  else:
3024
    multiplier = suffix_map[value[-1]]
3025
    value = value[:-1]
3026
    if not value: # no data left after stripping the suffix
3027
      raise errors.OpPrereqError("Invalid time specification (only"
3028
                                 " suffix passed)")
3029
    try:
3030
      value = int(value) * multiplier
3031
    except (TypeError, ValueError):
3032
      raise errors.OpPrereqError("Invalid time specification '%s'" % value)
3033
  return value
3034

    
3035

    
3036
def GetOnlineNodes(nodes, cl=None, nowarn=False, secondary_ips=False,
3037
                   filter_master=False, nodegroup=None):
3038
  """Returns the names of online nodes.
3039

3040
  This function will also log a warning on stderr with the names of
3041
  the online nodes.
3042

3043
  @param nodes: if not empty, use only this subset of nodes (minus the
3044
      offline ones)
3045
  @param cl: if not None, luxi client to use
3046
  @type nowarn: boolean
3047
  @param nowarn: by default, this function will output a note with the
3048
      offline nodes that are skipped; if this parameter is True the
3049
      note is not displayed
3050
  @type secondary_ips: boolean
3051
  @param secondary_ips: if True, return the secondary IPs instead of the
3052
      names, useful for doing network traffic over the replication interface
3053
      (if any)
3054
  @type filter_master: boolean
3055
  @param filter_master: if True, do not return the master node in the list
3056
      (useful in coordination with secondary_ips where we cannot check our
3057
      node name against the list)
3058
  @type nodegroup: string
3059
  @param nodegroup: If set, only return nodes in this node group
3060

3061
  """
3062
  if cl is None:
3063
    cl = GetClient()
3064

    
3065
  qfilter = []
3066

    
3067
  if nodes:
3068
    qfilter.append(qlang.MakeSimpleFilter("name", nodes))
3069

    
3070
  if nodegroup is not None:
3071
    qfilter.append([qlang.OP_OR, [qlang.OP_EQUAL, "group", nodegroup],
3072
                                 [qlang.OP_EQUAL, "group.uuid", nodegroup]])
3073

    
3074
  if filter_master:
3075
    qfilter.append([qlang.OP_NOT, [qlang.OP_TRUE, "master"]])
3076

    
3077
  if qfilter:
3078
    if len(qfilter) > 1:
3079
      final_filter = [qlang.OP_AND] + qfilter
3080
    else:
3081
      assert len(qfilter) == 1
3082
      final_filter = qfilter[0]
3083
  else:
3084
    final_filter = None
3085

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

    
3088
  def _IsOffline(row):
3089
    (_, (_, offline), _) = row
3090
    return offline
3091

    
3092
  def _GetName(row):
3093
    ((_, name), _, _) = row
3094
    return name
3095

    
3096
  def _GetSip(row):
3097
    (_, _, (_, sip)) = row
3098
    return sip
3099

    
3100
  (offline, online) = compat.partition(result.data, _IsOffline)
3101

    
3102
  if offline and not nowarn:
3103
    ToStderr("Note: skipping offline node(s): %s" %
3104
             utils.CommaJoin(map(_GetName, offline)))
3105

    
3106
  if secondary_ips:
3107
    fn = _GetSip
3108
  else:
3109
    fn = _GetName
3110

    
3111
  return map(fn, online)
3112

    
3113

    
3114
def _ToStream(stream, txt, *args):
3115
  """Write a message to a stream, bypassing the logging system
3116

3117
  @type stream: file object
3118
  @param stream: the file to which we should write
3119
  @type txt: str
3120
  @param txt: the message
3121

3122
  """
3123
  try:
3124
    if args:
3125
      args = tuple(args)
3126
      stream.write(txt % args)
3127
    else:
3128
      stream.write(txt)
3129
    stream.write("\n")
3130
    stream.flush()
3131
  except IOError, err:
3132
    if err.errno == errno.EPIPE:
3133
      # our terminal went away, we'll exit
3134
      sys.exit(constants.EXIT_FAILURE)
3135
    else:
3136
      raise
3137

    
3138

    
3139
def ToStdout(txt, *args):
3140
  """Write a message to stdout only, bypassing the logging system
3141

3142
  This is just a wrapper over _ToStream.
3143

3144
  @type txt: str
3145
  @param txt: the message
3146

3147
  """
3148
  _ToStream(sys.stdout, txt, *args)
3149

    
3150

    
3151
def ToStderr(txt, *args):
3152
  """Write a message to stderr only, bypassing the logging system
3153

3154
  This is just a wrapper over _ToStream.
3155

3156
  @type txt: str
3157
  @param txt: the message
3158

3159
  """
3160
  _ToStream(sys.stderr, txt, *args)
3161

    
3162

    
3163
class JobExecutor(object):
3164
  """Class which manages the submission and execution of multiple jobs.
3165

3166
  Note that instances of this class should not be reused between
3167
  GetResults() calls.
3168

3169
  """
3170
  def __init__(self, cl=None, verbose=True, opts=None, feedback_fn=None):
3171
    self.queue = []
3172
    if cl is None:
3173
      cl = GetClient()
3174
    self.cl = cl
3175
    self.verbose = verbose
3176
    self.jobs = []
3177
    self.opts = opts
3178
    self.feedback_fn = feedback_fn
3179
    self._counter = itertools.count()
3180

    
3181
  @staticmethod
3182
  def _IfName(name, fmt):
3183
    """Helper function for formatting name.
3184

3185
    """
3186
    if name:
3187
      return fmt % name
3188

    
3189
    return ""
3190

    
3191
  def QueueJob(self, name, *ops):
3192
    """Record a job for later submit.
3193

3194
    @type name: string
3195
    @param name: a description of the job, will be used in WaitJobSet
3196

3197
    """
3198
    SetGenericOpcodeOpts(ops, self.opts)
3199
    self.queue.append((self._counter.next(), name, ops))
3200

    
3201
  def AddJobId(self, name, status, job_id):
3202
    """Adds a job ID to the internal queue.
3203

3204
    """
3205
    self.jobs.append((self._counter.next(), status, job_id, name))
3206

    
3207
  def SubmitPending(self, each=False):
3208
    """Submit all pending jobs.
3209

3210
    """
3211
    if each:
3212
      results = []
3213
      for (_, _, ops) in self.queue:
3214
        # SubmitJob will remove the success status, but raise an exception if
3215
        # the submission fails, so we'll notice that anyway.
3216
        results.append([True, self.cl.SubmitJob(ops)[0]])
3217
    else:
3218
      results = self.cl.SubmitManyJobs([ops for (_, _, ops) in self.queue])
3219
    for ((status, data), (idx, name, _)) in zip(results, self.queue):
3220
      self.jobs.append((idx, status, data, name))
3221

    
3222
  def _ChooseJob(self):
3223
    """Choose a non-waiting/queued job to poll next.
3224

3225
    """
3226
    assert self.jobs, "_ChooseJob called with empty job list"
3227

    
3228
    result = self.cl.QueryJobs([i[2] for i in self.jobs[:_CHOOSE_BATCH]],
3229
                               ["status"])
3230
    assert result
3231

    
3232
    for job_data, status in zip(self.jobs, result):
3233
      if (isinstance(status, list) and status and
3234
          status[0] in (constants.JOB_STATUS_QUEUED,
3235
                        constants.JOB_STATUS_WAITING,
3236
                        constants.JOB_STATUS_CANCELING)):
3237
        # job is still present and waiting
3238
        continue
3239
      # good candidate found (either running job or lost job)
3240
      self.jobs.remove(job_data)
3241
      return job_data
3242

    
3243
    # no job found
3244
    return self.jobs.pop(0)
3245

    
3246
  def GetResults(self):
3247
    """Wait for and return the results of all jobs.
3248

3249
    @rtype: list
3250
    @return: list of tuples (success, job results), in the same order
3251
        as the submitted jobs; if a job has failed, instead of the result
3252
        there will be the error message
3253

3254
    """
3255
    if not self.jobs:
3256
      self.SubmitPending()
3257
    results = []
3258
    if self.verbose:
3259
      ok_jobs = [row[2] for row in self.jobs if row[1]]
3260
      if ok_jobs:
3261
        ToStdout("Submitted jobs %s", utils.CommaJoin(ok_jobs))
3262

    
3263
    # first, remove any non-submitted jobs
3264
    self.jobs, failures = compat.partition(self.jobs, lambda x: x[1])
3265
    for idx, _, jid, name in failures:
3266
      ToStderr("Failed to submit job%s: %s", self._IfName(name, " for %s"), jid)
3267
      results.append((idx, False, jid))
3268

    
3269
    while self.jobs:
3270
      (idx, _, jid, name) = self._ChooseJob()
3271
      ToStdout("Waiting for job %s%s ...", jid, self._IfName(name, " for %s"))
3272
      try:
3273
        job_result = PollJob(jid, cl=self.cl, feedback_fn=self.feedback_fn)
3274
        success = True
3275
      except errors.JobLost, err:
3276
        _, job_result = FormatError(err)
3277
        ToStderr("Job %s%s has been archived, cannot check its result",
3278
                 jid, self._IfName(name, " for %s"))
3279
        success = False
3280
      except (errors.GenericError, luxi.ProtocolError), err:
3281
        _, job_result = FormatError(err)
3282
        success = False
3283
        # the error message will always be shown, verbose or not
3284
        ToStderr("Job %s%s has failed: %s",
3285
                 jid, self._IfName(name, " for %s"), job_result)
3286

    
3287
      results.append((idx, success, job_result))
3288

    
3289
    # sort based on the index, then drop it
3290
    results.sort()
3291
    results = [i[1:] for i in results]
3292

    
3293
    return results
3294

    
3295
  def WaitOrShow(self, wait):
3296
    """Wait for job results or only print the job IDs.
3297

3298
    @type wait: boolean
3299
    @param wait: whether to wait or not
3300

3301
    """
3302
    if wait:
3303
      return self.GetResults()
3304
    else:
3305
      if not self.jobs:
3306
        self.SubmitPending()
3307
      for _, status, result, name in self.jobs:
3308
        if status:
3309
          ToStdout("%s: %s", result, name)
3310
        else:
3311
          ToStderr("Failure for %s: %s", name, result)
3312
      return [row[1:3] for row in self.jobs]
3313

    
3314

    
3315
def FormatParameterDict(buf, param_dict, actual, level=1):
3316
  """Formats a parameter dictionary.
3317

3318
  @type buf: L{StringIO}
3319
  @param buf: the buffer into which to write
3320
  @type param_dict: dict
3321
  @param param_dict: the own parameters
3322
  @type actual: dict
3323
  @param actual: the current parameter set (including defaults)
3324
  @param level: Level of indent
3325

3326
  """
3327
  indent = "  " * level
3328
  for key in sorted(actual):
3329
    val = param_dict.get(key, "default (%s)" % actual[key])
3330
    buf.write("%s- %s: %s\n" % (indent, key, val))
3331

    
3332

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

3336
  This function is used to request confirmation for doing an operation
3337
  on a given list of list_type.
3338

3339
  @type names: list
3340
  @param names: the list of names that we display when
3341
      we ask for confirmation
3342
  @type list_type: str
3343
  @param list_type: Human readable name for elements in the list (e.g. nodes)
3344
  @type text: str
3345
  @param text: the operation that the user should confirm
3346
  @rtype: boolean
3347
  @return: True or False depending on user's confirmation.
3348

3349
  """
3350
  count = len(names)
3351
  msg = ("The %s will operate on %d %s.\n%s"
3352
         "Do you want to continue?" % (text, count, list_type, extra))
3353
  affected = (("\nAffected %s:\n" % list_type) +
3354
              "\n".join(["  %s" % name for name in names]))
3355

    
3356
  choices = [("y", True, "Yes, execute the %s" % text),
3357
             ("n", False, "No, abort the %s" % text)]
3358

    
3359
  if count > 20:
3360
    choices.insert(1, ("v", "v", "View the list of affected %s" % list_type))
3361
    question = msg
3362
  else:
3363
    question = msg + affected
3364

    
3365
  choice = AskUser(question, choices)
3366
  if choice == "v":
3367
    choices.pop(1)
3368
    choice = AskUser(msg + affected, choices)
3369
  return choice