Statistics
| Branch: | Tag: | Revision:

root / lib / cli.py @ 8c0b16f6

History | View | Annotate | Download (109.8 kB)

1
#
2
#
3

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

    
21

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

    
24

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

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

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

    
50

    
51
__all__ = [
52
  # Command line options
53
  "ADD_UIDS_OPT",
54
  "ALLOCATABLE_OPT",
55
  "ALLOC_POLICY_OPT",
56
  "ALL_OPT",
57
  "ALLOW_FAILOVER_OPT",
58
  "AUTO_PROMOTE_OPT",
59
  "AUTO_REPLACE_OPT",
60
  "BACKEND_OPT",
61
  "BLK_OS_OPT",
62
  "CAPAB_MASTER_OPT",
63
  "CAPAB_VM_OPT",
64
  "CLEANUP_OPT",
65
  "CLUSTER_DOMAIN_SECRET_OPT",
66
  "CONFIRM_OPT",
67
  "CP_SIZE_OPT",
68
  "DEBUG_OPT",
69
  "DEBUG_SIMERR_OPT",
70
  "DISKIDX_OPT",
71
  "DISK_OPT",
72
  "DISK_PARAMS_OPT",
73
  "DISK_TEMPLATE_OPT",
74
  "DRAINED_OPT",
75
  "DRY_RUN_OPT",
76
  "DRBD_HELPER_OPT",
77
  "DST_NODE_OPT",
78
  "EARLY_RELEASE_OPT",
79
  "ENABLED_HV_OPT",
80
  "ERROR_CODES_OPT",
81
  "FIELDS_OPT",
82
  "FILESTORE_DIR_OPT",
83
  "FILESTORE_DRIVER_OPT",
84
  "FORCE_FILTER_OPT",
85
  "FORCE_OPT",
86
  "FORCE_VARIANT_OPT",
87
  "GLOBAL_FILEDIR_OPT",
88
  "HID_OS_OPT",
89
  "GLOBAL_SHARED_FILEDIR_OPT",
90
  "HVLIST_OPT",
91
  "HVOPTS_OPT",
92
  "HYPERVISOR_OPT",
93
  "IALLOCATOR_OPT",
94
  "DEFAULT_IALLOCATOR_OPT",
95
  "IDENTIFY_DEFAULTS_OPT",
96
  "IGNORE_CONSIST_OPT",
97
  "IGNORE_ERRORS_OPT",
98
  "IGNORE_FAILURES_OPT",
99
  "IGNORE_OFFLINE_OPT",
100
  "IGNORE_REMOVE_FAILURES_OPT",
101
  "IGNORE_SECONDARIES_OPT",
102
  "IGNORE_SIZE_OPT",
103
  "INTERVAL_OPT",
104
  "MAC_PREFIX_OPT",
105
  "MAINTAIN_NODE_HEALTH_OPT",
106
  "MASTER_NETDEV_OPT",
107
  "MASTER_NETMASK_OPT",
108
  "MC_OPT",
109
  "MIGRATION_MODE_OPT",
110
  "NET_OPT",
111
  "NEW_CLUSTER_CERT_OPT",
112
  "NEW_CLUSTER_DOMAIN_SECRET_OPT",
113
  "NEW_CONFD_HMAC_KEY_OPT",
114
  "NEW_RAPI_CERT_OPT",
115
  "NEW_SECONDARY_OPT",
116
  "NEW_SPICE_CERT_OPT",
117
  "NIC_PARAMS_OPT",
118
  "NODE_FORCE_JOIN_OPT",
119
  "NODE_LIST_OPT",
120
  "NODE_PLACEMENT_OPT",
121
  "NODEGROUP_OPT",
122
  "NODE_PARAMS_OPT",
123
  "NODE_POWERED_OPT",
124
  "NODRBD_STORAGE_OPT",
125
  "NOHDR_OPT",
126
  "NOIPCHECK_OPT",
127
  "NO_INSTALL_OPT",
128
  "NONAMECHECK_OPT",
129
  "NOLVM_STORAGE_OPT",
130
  "NOMODIFY_ETCHOSTS_OPT",
131
  "NOMODIFY_SSH_SETUP_OPT",
132
  "NONICS_OPT",
133
  "NONLIVE_OPT",
134
  "NONPLUS1_OPT",
135
  "NORUNTIME_CHGS_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
  "IPOLICY_DISK_TEMPLATES",
177
  "IPOLICY_VCPU_RATIO",
178
  "SPICE_CACERT_OPT",
179
  "SPICE_CERT_OPT",
180
  "SRC_DIR_OPT",
181
  "SRC_NODE_OPT",
182
  "SUBMIT_OPT",
183
  "STARTUP_PAUSED_OPT",
184
  "STATIC_OPT",
185
  "SYNC_OPT",
186
  "TAG_ADD_OPT",
187
  "TAG_SRC_OPT",
188
  "TIMEOUT_OPT",
189
  "TO_GROUP_OPT",
190
  "UIDPOOL_OPT",
191
  "USEUNITS_OPT",
192
  "USE_EXTERNAL_MIP_SCRIPT",
193
  "USE_REPL_NET_OPT",
194
  "VERBOSE_OPT",
195
  "VG_NAME_OPT",
196
  "YES_DOIT_OPT",
197
  "DISK_STATE_OPT",
198
  "HV_STATE_OPT",
199
  "IGNORE_IPOLICY_OPT",
200
  "INSTANCE_POLICY_OPTS",
201
  # Generic functions for CLI programs
202
  "ConfirmOperation",
203
  "GenericMain",
204
  "GenericInstanceCreate",
205
  "GenericList",
206
  "GenericListFields",
207
  "GetClient",
208
  "GetOnlineNodes",
209
  "JobExecutor",
210
  "JobSubmittedException",
211
  "ParseTimespec",
212
  "RunWhileClusterStopped",
213
  "SubmitOpCode",
214
  "SubmitOrSend",
215
  "UsesRPC",
216
  # Formatting functions
217
  "ToStderr", "ToStdout",
218
  "FormatError",
219
  "FormatQueryResult",
220
  "FormatParameterDict",
221
  "GenerateTable",
222
  "AskUser",
223
  "FormatTimestamp",
224
  "FormatLogMessage",
225
  # Tags functions
226
  "ListTags",
227
  "AddTags",
228
  "RemoveTags",
229
  # command line options support infrastructure
230
  "ARGS_MANY_INSTANCES",
231
  "ARGS_MANY_NODES",
232
  "ARGS_MANY_GROUPS",
233
  "ARGS_NONE",
234
  "ARGS_ONE_INSTANCE",
235
  "ARGS_ONE_NODE",
236
  "ARGS_ONE_GROUP",
237
  "ARGS_ONE_OS",
238
  "ArgChoice",
239
  "ArgCommand",
240
  "ArgFile",
241
  "ArgGroup",
242
  "ArgHost",
243
  "ArgInstance",
244
  "ArgJobId",
245
  "ArgNode",
246
  "ArgOs",
247
  "ArgSuggest",
248
  "ArgUnknown",
249
  "OPT_COMPL_INST_ADD_NODES",
250
  "OPT_COMPL_MANY_NODES",
251
  "OPT_COMPL_ONE_IALLOCATOR",
252
  "OPT_COMPL_ONE_INSTANCE",
253
  "OPT_COMPL_ONE_NODE",
254
  "OPT_COMPL_ONE_NODEGROUP",
255
  "OPT_COMPL_ONE_OS",
256
  "cli_option",
257
  "SplitNodeOption",
258
  "CalculateOSNames",
259
  "ParseFields",
260
  "COMMON_CREATE_OPTS",
261
  ]
262

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

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

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

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

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

    
286

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

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

    
296

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

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

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

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

    
312

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

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

319
  """
320

    
321

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

325
  """
326

    
327

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

331
  """
332

    
333

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

337
  """
338

    
339

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

343
  """
344

    
345

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

349
  """
350

    
351

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

355
  """
356

    
357

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

361
  """
362

    
363

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

367
  """
368

    
369

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

373
  """
374

    
375

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

    
386

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

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

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

    
409

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

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

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

    
438

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

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

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

    
456

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

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

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

    
473

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

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

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

    
490

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

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

    
500

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

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

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

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

    
537

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

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

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

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

    
565

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

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

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

    
574

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

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

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

    
589

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

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

    
601

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

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

    
622

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

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

    
644

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

    
648

    
649
_YORNO = "yes|no"
650

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
775
NORUNTIME_CHGS_OPT = cli_option("--no-runtime-changes",
776
                                dest="allow_runtime_chgs",
777
                                default=True, action="store_false",
778
                                help="Don't allow runtime changes")
779

    
780
BACKEND_OPT = cli_option("-B", "--backend-parameters", dest="beparams",
781
                         type="keyval", default={},
782
                         help="Backend parameters")
783

    
784
HVOPTS_OPT = cli_option("-H", "--hypervisor-parameters", type="keyval",
785
                        default={}, dest="hvparams",
786
                        help="Hypervisor parameters")
787

    
788
DISK_PARAMS_OPT = cli_option("-D", "--disk-parameters", dest="diskparams",
789
                             help="Disk template parameters, in the format"
790
                             " template:option=value,option=value,...",
791
                             type="identkeyval", action="append", default=[])
792

    
793
SPECS_MEM_SIZE_OPT = cli_option("--specs-mem-size", dest="ispecs_mem_size",
794
                                 type="keyval", default={},
795
                                 help="Memory count specs: min, max, std"
796
                                 " (in MB)")
797

    
798
SPECS_CPU_COUNT_OPT = cli_option("--specs-cpu-count", dest="ispecs_cpu_count",
799
                                 type="keyval", default={},
800
                                 help="CPU count specs: min, max, std")
801

    
802
SPECS_DISK_COUNT_OPT = cli_option("--specs-disk-count",
803
                                  dest="ispecs_disk_count",
804
                                  type="keyval", default={},
805
                                  help="Disk count specs: min, max, std")
806

    
807
SPECS_DISK_SIZE_OPT = cli_option("--specs-disk-size", dest="ispecs_disk_size",
808
                                 type="keyval", default={},
809
                                 help="Disk size specs: min, max, std (in MB)")
810

    
811
SPECS_NIC_COUNT_OPT = cli_option("--specs-nic-count", dest="ispecs_nic_count",
812
                                 type="keyval", default={},
813
                                 help="NIC count specs: min, max, std")
814

    
815
IPOLICY_DISK_TEMPLATES = cli_option("--ipolicy-disk-templates",
816
                                 dest="ipolicy_disk_templates",
817
                                 type="list", default=None,
818
                                 help="Comma-separated list of"
819
                                 " enabled disk templates")
820

    
821
IPOLICY_VCPU_RATIO = cli_option("--ipolicy-vcpu-ratio",
822
                                 dest="ipolicy_vcpu_ratio",
823
                                 type="float", default=None,
824
                                 help="The maximum allowed vcpu-to-cpu ratio")
825

    
826
HYPERVISOR_OPT = cli_option("-H", "--hypervisor-parameters", dest="hypervisor",
827
                            help="Hypervisor and hypervisor options, in the"
828
                            " format hypervisor:option=value,option=value,...",
829
                            default=None, type="identkeyval")
830

    
831
HVLIST_OPT = cli_option("-H", "--hypervisor-parameters", dest="hvparams",
832
                        help="Hypervisor and hypervisor options, in the"
833
                        " format hypervisor:option=value,option=value,...",
834
                        default=[], action="append", type="identkeyval")
835

    
836
NOIPCHECK_OPT = cli_option("--no-ip-check", dest="ip_check", default=True,
837
                           action="store_false",
838
                           help="Don't check that the instance's IP"
839
                           " is alive")
840

    
841
NONAMECHECK_OPT = cli_option("--no-name-check", dest="name_check",
842
                             default=True, action="store_false",
843
                             help="Don't check that the instance's name"
844
                             " is resolvable")
845

    
846
NET_OPT = cli_option("--net",
847
                     help="NIC parameters", default=[],
848
                     dest="nics", action="append", type="identkeyval")
849

    
850
DISK_OPT = cli_option("--disk", help="Disk parameters", default=[],
851
                      dest="disks", action="append", type="identkeyval")
852

    
853
DISKIDX_OPT = cli_option("--disks", dest="disks", default=None,
854
                         help="Comma-separated list of disks"
855
                         " indices to act on (e.g. 0,2) (optional,"
856
                         " defaults to all disks)")
857

    
858
OS_SIZE_OPT = cli_option("-s", "--os-size", dest="sd_size",
859
                         help="Enforces a single-disk configuration using the"
860
                         " given disk size, in MiB unless a suffix is used",
861
                         default=None, type="unit", metavar="<size>")
862

    
863
IGNORE_CONSIST_OPT = cli_option("--ignore-consistency",
864
                                dest="ignore_consistency",
865
                                action="store_true", default=False,
866
                                help="Ignore the consistency of the disks on"
867
                                " the secondary")
868

    
869
ALLOW_FAILOVER_OPT = cli_option("--allow-failover",
870
                                dest="allow_failover",
871
                                action="store_true", default=False,
872
                                help="If migration is not possible fallback to"
873
                                     " failover")
874

    
875
NONLIVE_OPT = cli_option("--non-live", dest="live",
876
                         default=True, action="store_false",
877
                         help="Do a non-live migration (this usually means"
878
                         " freeze the instance, save the state, transfer and"
879
                         " only then resume running on the secondary node)")
880

    
881
MIGRATION_MODE_OPT = cli_option("--migration-mode", dest="migration_mode",
882
                                default=None,
883
                                choices=list(constants.HT_MIGRATION_MODES),
884
                                help="Override default migration mode (choose"
885
                                " either live or non-live")
886

    
887
NODE_PLACEMENT_OPT = cli_option("-n", "--node", dest="node",
888
                                help="Target node and optional secondary node",
889
                                metavar="<pnode>[:<snode>]",
890
                                completion_suggest=OPT_COMPL_INST_ADD_NODES)
891

    
892
NODE_LIST_OPT = cli_option("-n", "--node", dest="nodes", default=[],
893
                           action="append", metavar="<node>",
894
                           help="Use only this node (can be used multiple"
895
                           " times, if not given defaults to all nodes)",
896
                           completion_suggest=OPT_COMPL_ONE_NODE)
897

    
898
NODEGROUP_OPT_NAME = "--node-group"
899
NODEGROUP_OPT = cli_option("-g", NODEGROUP_OPT_NAME,
900
                           dest="nodegroup",
901
                           help="Node group (name or uuid)",
902
                           metavar="<nodegroup>",
903
                           default=None, type="string",
904
                           completion_suggest=OPT_COMPL_ONE_NODEGROUP)
905

    
906
SINGLE_NODE_OPT = cli_option("-n", "--node", dest="node", help="Target node",
907
                             metavar="<node>",
908
                             completion_suggest=OPT_COMPL_ONE_NODE)
909

    
910
NOSTART_OPT = cli_option("--no-start", dest="start", default=True,
911
                         action="store_false",
912
                         help="Don't start the instance after creation")
913

    
914
SHOWCMD_OPT = cli_option("--show-cmd", dest="show_command",
915
                         action="store_true", default=False,
916
                         help="Show command instead of executing it")
917

    
918
CLEANUP_OPT = cli_option("--cleanup", dest="cleanup",
919
                         default=False, action="store_true",
920
                         help="Instead of performing the migration, try to"
921
                         " recover from a failed cleanup. This is safe"
922
                         " to run even if the instance is healthy, but it"
923
                         " will create extra replication traffic and "
924
                         " disrupt briefly the replication (like during the"
925
                         " migration")
926

    
927
STATIC_OPT = cli_option("-s", "--static", dest="static",
928
                        action="store_true", default=False,
929
                        help="Only show configuration data, not runtime data")
930

    
931
ALL_OPT = cli_option("--all", dest="show_all",
932
                     default=False, action="store_true",
933
                     help="Show info on all instances on the cluster."
934
                     " This can take a long time to run, use wisely")
935

    
936
SELECT_OS_OPT = cli_option("--select-os", dest="select_os",
937
                           action="store_true", default=False,
938
                           help="Interactive OS reinstall, lists available"
939
                           " OS templates for selection")
940

    
941
IGNORE_FAILURES_OPT = cli_option("--ignore-failures", dest="ignore_failures",
942
                                 action="store_true", default=False,
943
                                 help="Remove the instance from the cluster"
944
                                 " configuration even if there are failures"
945
                                 " during the removal process")
946

    
947
IGNORE_REMOVE_FAILURES_OPT = cli_option("--ignore-remove-failures",
948
                                        dest="ignore_remove_failures",
949
                                        action="store_true", default=False,
950
                                        help="Remove the instance from the"
951
                                        " cluster configuration even if there"
952
                                        " are failures during the removal"
953
                                        " process")
954

    
955
REMOVE_INSTANCE_OPT = cli_option("--remove-instance", dest="remove_instance",
956
                                 action="store_true", default=False,
957
                                 help="Remove the instance from the cluster")
958

    
959
DST_NODE_OPT = cli_option("-n", "--target-node", dest="dst_node",
960
                               help="Specifies the new node for the instance",
961
                               metavar="NODE", default=None,
962
                               completion_suggest=OPT_COMPL_ONE_NODE)
963

    
964
NEW_SECONDARY_OPT = cli_option("-n", "--new-secondary", dest="dst_node",
965
                               help="Specifies the new secondary node",
966
                               metavar="NODE", default=None,
967
                               completion_suggest=OPT_COMPL_ONE_NODE)
968

    
969
ON_PRIMARY_OPT = cli_option("-p", "--on-primary", dest="on_primary",
970
                            default=False, action="store_true",
971
                            help="Replace the disk(s) on the primary"
972
                                 " node (applies only to internally mirrored"
973
                                 " disk templates, e.g. %s)" %
974
                                 utils.CommaJoin(constants.DTS_INT_MIRROR))
975

    
976
ON_SECONDARY_OPT = cli_option("-s", "--on-secondary", dest="on_secondary",
977
                              default=False, action="store_true",
978
                              help="Replace the disk(s) on the secondary"
979
                                   " node (applies only to internally mirrored"
980
                                   " disk templates, e.g. %s)" %
981
                                   utils.CommaJoin(constants.DTS_INT_MIRROR))
982

    
983
AUTO_PROMOTE_OPT = cli_option("--auto-promote", dest="auto_promote",
984
                              default=False, action="store_true",
985
                              help="Lock all nodes and auto-promote as needed"
986
                              " to MC status")
987

    
988
AUTO_REPLACE_OPT = cli_option("-a", "--auto", dest="auto",
989
                              default=False, action="store_true",
990
                              help="Automatically replace faulty disks"
991
                                   " (applies only to internally mirrored"
992
                                   " disk templates, e.g. %s)" %
993
                                   utils.CommaJoin(constants.DTS_INT_MIRROR))
994

    
995
IGNORE_SIZE_OPT = cli_option("--ignore-size", dest="ignore_size",
996
                             default=False, action="store_true",
997
                             help="Ignore current recorded size"
998
                             " (useful for forcing activation when"
999
                             " the recorded size is wrong)")
1000

    
1001
SRC_NODE_OPT = cli_option("--src-node", dest="src_node", help="Source node",
1002
                          metavar="<node>",
1003
                          completion_suggest=OPT_COMPL_ONE_NODE)
1004

    
1005
SRC_DIR_OPT = cli_option("--src-dir", dest="src_dir", help="Source directory",
1006
                         metavar="<dir>")
1007

    
1008
SECONDARY_IP_OPT = cli_option("-s", "--secondary-ip", dest="secondary_ip",
1009
                              help="Specify the secondary ip for the node",
1010
                              metavar="ADDRESS", default=None)
1011

    
1012
READD_OPT = cli_option("--readd", dest="readd",
1013
                       default=False, action="store_true",
1014
                       help="Readd old node after replacing it")
1015

    
1016
NOSSH_KEYCHECK_OPT = cli_option("--no-ssh-key-check", dest="ssh_key_check",
1017
                                default=True, action="store_false",
1018
                                help="Disable SSH key fingerprint checking")
1019

    
1020
NODE_FORCE_JOIN_OPT = cli_option("--force-join", dest="force_join",
1021
                                 default=False, action="store_true",
1022
                                 help="Force the joining of a node")
1023

    
1024
MC_OPT = cli_option("-C", "--master-candidate", dest="master_candidate",
1025
                    type="bool", default=None, metavar=_YORNO,
1026
                    help="Set the master_candidate flag on the node")
1027

    
1028
OFFLINE_OPT = cli_option("-O", "--offline", dest="offline", metavar=_YORNO,
1029
                         type="bool", default=None,
1030
                         help=("Set the offline flag on the node"
1031
                               " (cluster does not communicate with offline"
1032
                               " nodes)"))
1033

    
1034
DRAINED_OPT = cli_option("-D", "--drained", dest="drained", metavar=_YORNO,
1035
                         type="bool", default=None,
1036
                         help=("Set the drained flag on the node"
1037
                               " (excluded from allocation operations)"))
1038

    
1039
CAPAB_MASTER_OPT = cli_option("--master-capable", dest="master_capable",
1040
                    type="bool", default=None, metavar=_YORNO,
1041
                    help="Set the master_capable flag on the node")
1042

    
1043
CAPAB_VM_OPT = cli_option("--vm-capable", dest="vm_capable",
1044
                    type="bool", default=None, metavar=_YORNO,
1045
                    help="Set the vm_capable flag on the node")
1046

    
1047
ALLOCATABLE_OPT = cli_option("--allocatable", dest="allocatable",
1048
                             type="bool", default=None, metavar=_YORNO,
1049
                             help="Set the allocatable flag on a volume")
1050

    
1051
NOLVM_STORAGE_OPT = cli_option("--no-lvm-storage", dest="lvm_storage",
1052
                               help="Disable support for lvm based instances"
1053
                               " (cluster-wide)",
1054
                               action="store_false", default=True)
1055

    
1056
ENABLED_HV_OPT = cli_option("--enabled-hypervisors",
1057
                            dest="enabled_hypervisors",
1058
                            help="Comma-separated list of hypervisors",
1059
                            type="string", default=None)
1060

    
1061
NIC_PARAMS_OPT = cli_option("-N", "--nic-parameters", dest="nicparams",
1062
                            type="keyval", default={},
1063
                            help="NIC parameters")
1064

    
1065
CP_SIZE_OPT = cli_option("-C", "--candidate-pool-size", default=None,
1066
                         dest="candidate_pool_size", type="int",
1067
                         help="Set the candidate pool size")
1068

    
1069
VG_NAME_OPT = cli_option("--vg-name", dest="vg_name",
1070
                         help=("Enables LVM and specifies the volume group"
1071
                               " name (cluster-wide) for disk allocation"
1072
                               " [%s]" % constants.DEFAULT_VG),
1073
                         metavar="VG", default=None)
1074

    
1075
YES_DOIT_OPT = cli_option("--yes-do-it", "--ya-rly", dest="yes_do_it",
1076
                          help="Destroy cluster", action="store_true")
1077

    
1078
NOVOTING_OPT = cli_option("--no-voting", dest="no_voting",
1079
                          help="Skip node agreement check (dangerous)",
1080
                          action="store_true", default=False)
1081

    
1082
MAC_PREFIX_OPT = cli_option("-m", "--mac-prefix", dest="mac_prefix",
1083
                            help="Specify the mac prefix for the instance IP"
1084
                            " addresses, in the format XX:XX:XX",
1085
                            metavar="PREFIX",
1086
                            default=None)
1087

    
1088
MASTER_NETDEV_OPT = cli_option("--master-netdev", dest="master_netdev",
1089
                               help="Specify the node interface (cluster-wide)"
1090
                               " on which the master IP address will be added"
1091
                               " (cluster init default: %s)" %
1092
                               constants.DEFAULT_BRIDGE,
1093
                               metavar="NETDEV",
1094
                               default=None)
1095

    
1096
MASTER_NETMASK_OPT = cli_option("--master-netmask", dest="master_netmask",
1097
                                help="Specify the netmask of the master IP",
1098
                                metavar="NETMASK",
1099
                                default=None)
1100

    
1101
USE_EXTERNAL_MIP_SCRIPT = cli_option("--use-external-mip-script",
1102
                                dest="use_external_mip_script",
1103
                                help="Specify whether to run a user-provided"
1104
                                " script for the master IP address turnup and"
1105
                                " turndown operations",
1106
                                type="bool", metavar=_YORNO, default=None)
1107

    
1108
GLOBAL_FILEDIR_OPT = cli_option("--file-storage-dir", dest="file_storage_dir",
1109
                                help="Specify the default directory (cluster-"
1110
                                "wide) for storing the file-based disks [%s]" %
1111
                                constants.DEFAULT_FILE_STORAGE_DIR,
1112
                                metavar="DIR",
1113
                                default=constants.DEFAULT_FILE_STORAGE_DIR)
1114

    
1115
GLOBAL_SHARED_FILEDIR_OPT = cli_option("--shared-file-storage-dir",
1116
                            dest="shared_file_storage_dir",
1117
                            help="Specify the default directory (cluster-"
1118
                            "wide) for storing the shared file-based"
1119
                            " disks [%s]" %
1120
                            constants.DEFAULT_SHARED_FILE_STORAGE_DIR,
1121
                            metavar="SHAREDDIR",
1122
                            default=constants.DEFAULT_SHARED_FILE_STORAGE_DIR)
1123

    
1124
NOMODIFY_ETCHOSTS_OPT = cli_option("--no-etc-hosts", dest="modify_etc_hosts",
1125
                                   help="Don't modify /etc/hosts",
1126
                                   action="store_false", default=True)
1127

    
1128
NOMODIFY_SSH_SETUP_OPT = cli_option("--no-ssh-init", dest="modify_ssh_setup",
1129
                                    help="Don't initialize SSH keys",
1130
                                    action="store_false", default=True)
1131

    
1132
ERROR_CODES_OPT = cli_option("--error-codes", dest="error_codes",
1133
                             help="Enable parseable error messages",
1134
                             action="store_true", default=False)
1135

    
1136
NONPLUS1_OPT = cli_option("--no-nplus1-mem", dest="skip_nplusone_mem",
1137
                          help="Skip N+1 memory redundancy tests",
1138
                          action="store_true", default=False)
1139

    
1140
REBOOT_TYPE_OPT = cli_option("-t", "--type", dest="reboot_type",
1141
                             help="Type of reboot: soft/hard/full",
1142
                             default=constants.INSTANCE_REBOOT_HARD,
1143
                             metavar="<REBOOT>",
1144
                             choices=list(constants.REBOOT_TYPES))
1145

    
1146
IGNORE_SECONDARIES_OPT = cli_option("--ignore-secondaries",
1147
                                    dest="ignore_secondaries",
1148
                                    default=False, action="store_true",
1149
                                    help="Ignore errors from secondaries")
1150

    
1151
NOSHUTDOWN_OPT = cli_option("--noshutdown", dest="shutdown",
1152
                            action="store_false", default=True,
1153
                            help="Don't shutdown the instance (unsafe)")
1154

    
1155
TIMEOUT_OPT = cli_option("--timeout", dest="timeout", type="int",
1156
                         default=constants.DEFAULT_SHUTDOWN_TIMEOUT,
1157
                         help="Maximum time to wait")
1158

    
1159
SHUTDOWN_TIMEOUT_OPT = cli_option("--shutdown-timeout",
1160
                         dest="shutdown_timeout", type="int",
1161
                         default=constants.DEFAULT_SHUTDOWN_TIMEOUT,
1162
                         help="Maximum time to wait for instance shutdown")
1163

    
1164
INTERVAL_OPT = cli_option("--interval", dest="interval", type="int",
1165
                          default=None,
1166
                          help=("Number of seconds between repetions of the"
1167
                                " command"))
1168

    
1169
EARLY_RELEASE_OPT = cli_option("--early-release",
1170
                               dest="early_release", default=False,
1171
                               action="store_true",
1172
                               help="Release the locks on the secondary"
1173
                               " node(s) early")
1174

    
1175
NEW_CLUSTER_CERT_OPT = cli_option("--new-cluster-certificate",
1176
                                  dest="new_cluster_cert",
1177
                                  default=False, action="store_true",
1178
                                  help="Generate a new cluster certificate")
1179

    
1180
RAPI_CERT_OPT = cli_option("--rapi-certificate", dest="rapi_cert",
1181
                           default=None,
1182
                           help="File containing new RAPI certificate")
1183

    
1184
NEW_RAPI_CERT_OPT = cli_option("--new-rapi-certificate", dest="new_rapi_cert",
1185
                               default=None, action="store_true",
1186
                               help=("Generate a new self-signed RAPI"
1187
                                     " certificate"))
1188

    
1189
SPICE_CERT_OPT = cli_option("--spice-certificate", dest="spice_cert",
1190
                           default=None,
1191
                           help="File containing new SPICE certificate")
1192

    
1193
SPICE_CACERT_OPT = cli_option("--spice-ca-certificate", dest="spice_cacert",
1194
                           default=None,
1195
                           help="File containing the certificate of the CA"
1196
                                " which signed the SPICE certificate")
1197

    
1198
NEW_SPICE_CERT_OPT = cli_option("--new-spice-certificate",
1199
                               dest="new_spice_cert", default=None,
1200
                               action="store_true",
1201
                               help=("Generate a new self-signed SPICE"
1202
                                     " certificate"))
1203

    
1204
NEW_CONFD_HMAC_KEY_OPT = cli_option("--new-confd-hmac-key",
1205
                                    dest="new_confd_hmac_key",
1206
                                    default=False, action="store_true",
1207
                                    help=("Create a new HMAC key for %s" %
1208
                                          constants.CONFD))
1209

    
1210
CLUSTER_DOMAIN_SECRET_OPT = cli_option("--cluster-domain-secret",
1211
                                       dest="cluster_domain_secret",
1212
                                       default=None,
1213
                                       help=("Load new new cluster domain"
1214
                                             " secret from file"))
1215

    
1216
NEW_CLUSTER_DOMAIN_SECRET_OPT = cli_option("--new-cluster-domain-secret",
1217
                                           dest="new_cluster_domain_secret",
1218
                                           default=False, action="store_true",
1219
                                           help=("Create a new cluster domain"
1220
                                                 " secret"))
1221

    
1222
USE_REPL_NET_OPT = cli_option("--use-replication-network",
1223
                              dest="use_replication_network",
1224
                              help="Whether to use the replication network"
1225
                              " for talking to the nodes",
1226
                              action="store_true", default=False)
1227

    
1228
MAINTAIN_NODE_HEALTH_OPT = \
1229
    cli_option("--maintain-node-health", dest="maintain_node_health",
1230
               metavar=_YORNO, default=None, type="bool",
1231
               help="Configure the cluster to automatically maintain node"
1232
               " health, by shutting down unknown instances, shutting down"
1233
               " unknown DRBD devices, etc.")
1234

    
1235
IDENTIFY_DEFAULTS_OPT = \
1236
    cli_option("--identify-defaults", dest="identify_defaults",
1237
               default=False, action="store_true",
1238
               help="Identify which saved instance parameters are equal to"
1239
               " the current cluster defaults and set them as such, instead"
1240
               " of marking them as overridden")
1241

    
1242
UIDPOOL_OPT = cli_option("--uid-pool", default=None,
1243
                         action="store", dest="uid_pool",
1244
                         help=("A list of user-ids or user-id"
1245
                               " ranges separated by commas"))
1246

    
1247
ADD_UIDS_OPT = cli_option("--add-uids", default=None,
1248
                          action="store", dest="add_uids",
1249
                          help=("A list of user-ids or user-id"
1250
                                " ranges separated by commas, to be"
1251
                                " added to the user-id pool"))
1252

    
1253
REMOVE_UIDS_OPT = cli_option("--remove-uids", default=None,
1254
                             action="store", dest="remove_uids",
1255
                             help=("A list of user-ids or user-id"
1256
                                   " ranges separated by commas, to be"
1257
                                   " removed from the user-id pool"))
1258

    
1259
RESERVED_LVS_OPT = cli_option("--reserved-lvs", default=None,
1260
                             action="store", dest="reserved_lvs",
1261
                             help=("A comma-separated list of reserved"
1262
                                   " logical volumes names, that will be"
1263
                                   " ignored by cluster verify"))
1264

    
1265
ROMAN_OPT = cli_option("--roman",
1266
                       dest="roman_integers", default=False,
1267
                       action="store_true",
1268
                       help="Use roman numbers for positive integers")
1269

    
1270
DRBD_HELPER_OPT = cli_option("--drbd-usermode-helper", dest="drbd_helper",
1271
                             action="store", default=None,
1272
                             help="Specifies usermode helper for DRBD")
1273

    
1274
NODRBD_STORAGE_OPT = cli_option("--no-drbd-storage", dest="drbd_storage",
1275
                                action="store_false", default=True,
1276
                                help="Disable support for DRBD")
1277

    
1278
PRIMARY_IP_VERSION_OPT = \
1279
    cli_option("--primary-ip-version", default=constants.IP4_VERSION,
1280
               action="store", dest="primary_ip_version",
1281
               metavar="%d|%d" % (constants.IP4_VERSION,
1282
                                  constants.IP6_VERSION),
1283
               help="Cluster-wide IP version for primary IP")
1284

    
1285
PRIORITY_OPT = cli_option("--priority", default=None, dest="priority",
1286
                          metavar="|".join(name for name, _ in _PRIORITY_NAMES),
1287
                          choices=_PRIONAME_TO_VALUE.keys(),
1288
                          help="Priority for opcode processing")
1289

    
1290
HID_OS_OPT = cli_option("--hidden", dest="hidden",
1291
                        type="bool", default=None, metavar=_YORNO,
1292
                        help="Sets the hidden flag on the OS")
1293

    
1294
BLK_OS_OPT = cli_option("--blacklisted", dest="blacklisted",
1295
                        type="bool", default=None, metavar=_YORNO,
1296
                        help="Sets the blacklisted flag on the OS")
1297

    
1298
PREALLOC_WIPE_DISKS_OPT = cli_option("--prealloc-wipe-disks", default=None,
1299
                                     type="bool", metavar=_YORNO,
1300
                                     dest="prealloc_wipe_disks",
1301
                                     help=("Wipe disks prior to instance"
1302
                                           " creation"))
1303

    
1304
NODE_PARAMS_OPT = cli_option("--node-parameters", dest="ndparams",
1305
                             type="keyval", default=None,
1306
                             help="Node parameters")
1307

    
1308
ALLOC_POLICY_OPT = cli_option("--alloc-policy", dest="alloc_policy",
1309
                              action="store", metavar="POLICY", default=None,
1310
                              help="Allocation policy for the node group")
1311

    
1312
NODE_POWERED_OPT = cli_option("--node-powered", default=None,
1313
                              type="bool", metavar=_YORNO,
1314
                              dest="node_powered",
1315
                              help="Specify if the SoR for node is powered")
1316

    
1317
OOB_TIMEOUT_OPT = cli_option("--oob-timeout", dest="oob_timeout", type="int",
1318
                         default=constants.OOB_TIMEOUT,
1319
                         help="Maximum time to wait for out-of-band helper")
1320

    
1321
POWER_DELAY_OPT = cli_option("--power-delay", dest="power_delay", type="float",
1322
                             default=constants.OOB_POWER_DELAY,
1323
                             help="Time in seconds to wait between power-ons")
1324

    
1325
FORCE_FILTER_OPT = cli_option("-F", "--filter", dest="force_filter",
1326
                              action="store_true", default=False,
1327
                              help=("Whether command argument should be treated"
1328
                                    " as filter"))
1329

    
1330
NO_REMEMBER_OPT = cli_option("--no-remember",
1331
                             dest="no_remember",
1332
                             action="store_true", default=False,
1333
                             help="Perform but do not record the change"
1334
                             " in the configuration")
1335

    
1336
PRIMARY_ONLY_OPT = cli_option("-p", "--primary-only",
1337
                              default=False, action="store_true",
1338
                              help="Evacuate primary instances only")
1339

    
1340
SECONDARY_ONLY_OPT = cli_option("-s", "--secondary-only",
1341
                                default=False, action="store_true",
1342
                                help="Evacuate secondary instances only"
1343
                                     " (applies only to internally mirrored"
1344
                                     " disk templates, e.g. %s)" %
1345
                                     utils.CommaJoin(constants.DTS_INT_MIRROR))
1346

    
1347
STARTUP_PAUSED_OPT = cli_option("--paused", dest="startup_paused",
1348
                                action="store_true", default=False,
1349
                                help="Pause instance at startup")
1350

    
1351
TO_GROUP_OPT = cli_option("--to", dest="to", metavar="<group>",
1352
                          help="Destination node group (name or uuid)",
1353
                          default=None, action="append",
1354
                          completion_suggest=OPT_COMPL_ONE_NODEGROUP)
1355

    
1356
IGNORE_ERRORS_OPT = cli_option("-I", "--ignore-errors", default=[],
1357
                               action="append", dest="ignore_errors",
1358
                               choices=list(constants.CV_ALL_ECODES_STRINGS),
1359
                               help="Error code to be ignored")
1360

    
1361
DISK_STATE_OPT = cli_option("--disk-state", default=[], dest="disk_state",
1362
                            action="append",
1363
                            help=("Specify disk state information in the format"
1364
                                  " storage_type/identifier:option=value,..."),
1365
                            type="identkeyval")
1366

    
1367
HV_STATE_OPT = cli_option("--hypervisor-state", default=[], dest="hv_state",
1368
                          action="append",
1369
                          help=("Specify hypervisor state information in the"
1370
                                " format hypervisor:option=value,..."),
1371
                          type="identkeyval")
1372

    
1373
IGNORE_IPOLICY_OPT = cli_option("--ignore-ipolicy", dest="ignore_ipolicy",
1374
                                action="store_true", default=False,
1375
                                help="Ignore instance policy violations")
1376

    
1377
RUNTIME_MEM_OPT = cli_option("-m", "--runtime-memory", dest="runtime_mem",
1378
                             help="Sets the instance's runtime memory,"
1379
                             " ballooning it up or down to the new value",
1380
                             default=None, type="unit", metavar="<size>")
1381

    
1382
#: Options provided by all commands
1383
COMMON_OPTS = [DEBUG_OPT]
1384

    
1385
# common options for creating instances. add and import then add their own
1386
# specific ones.
1387
COMMON_CREATE_OPTS = [
1388
  BACKEND_OPT,
1389
  DISK_OPT,
1390
  DISK_TEMPLATE_OPT,
1391
  FILESTORE_DIR_OPT,
1392
  FILESTORE_DRIVER_OPT,
1393
  HYPERVISOR_OPT,
1394
  IALLOCATOR_OPT,
1395
  NET_OPT,
1396
  NODE_PLACEMENT_OPT,
1397
  NOIPCHECK_OPT,
1398
  NONAMECHECK_OPT,
1399
  NONICS_OPT,
1400
  NWSYNC_OPT,
1401
  OSPARAMS_OPT,
1402
  OS_SIZE_OPT,
1403
  SUBMIT_OPT,
1404
  TAG_ADD_OPT,
1405
  DRY_RUN_OPT,
1406
  PRIORITY_OPT,
1407
  ]
1408

    
1409
# common instance policy options
1410
INSTANCE_POLICY_OPTS = [
1411
  SPECS_CPU_COUNT_OPT,
1412
  SPECS_DISK_COUNT_OPT,
1413
  SPECS_DISK_SIZE_OPT,
1414
  SPECS_MEM_SIZE_OPT,
1415
  SPECS_NIC_COUNT_OPT,
1416
  IPOLICY_DISK_TEMPLATES,
1417
  IPOLICY_VCPU_RATIO,
1418
  ]
1419

    
1420

    
1421
def _ParseArgs(argv, commands, aliases, env_override):
1422
  """Parser for the command line arguments.
1423

1424
  This function parses the arguments and returns the function which
1425
  must be executed together with its (modified) arguments.
1426

1427
  @param argv: the command line
1428
  @param commands: dictionary with special contents, see the design
1429
      doc for cmdline handling
1430
  @param aliases: dictionary with command aliases {'alias': 'target, ...}
1431
  @param env_override: list of env variables allowed for default args
1432

1433
  """
1434
  assert not (env_override - set(commands))
1435

    
1436
  if len(argv) == 0:
1437
    binary = "<command>"
1438
  else:
1439
    binary = argv[0].split("/")[-1]
1440

    
1441
  if len(argv) > 1 and argv[1] == "--version":
1442
    ToStdout("%s (ganeti %s) %s", binary, constants.VCS_VERSION,
1443
             constants.RELEASE_VERSION)
1444
    # Quit right away. That way we don't have to care about this special
1445
    # argument. optparse.py does it the same.
1446
    sys.exit(0)
1447

    
1448
  if len(argv) < 2 or not (argv[1] in commands or
1449
                           argv[1] in aliases):
1450
    # let's do a nice thing
1451
    sortedcmds = commands.keys()
1452
    sortedcmds.sort()
1453

    
1454
    ToStdout("Usage: %s {command} [options...] [argument...]", binary)
1455
    ToStdout("%s <command> --help to see details, or man %s", binary, binary)
1456
    ToStdout("")
1457

    
1458
    # compute the max line length for cmd + usage
1459
    mlen = max([len(" %s" % cmd) for cmd in commands])
1460
    mlen = min(60, mlen) # should not get here...
1461

    
1462
    # and format a nice command list
1463
    ToStdout("Commands:")
1464
    for cmd in sortedcmds:
1465
      cmdstr = " %s" % (cmd,)
1466
      help_text = commands[cmd][4]
1467
      help_lines = textwrap.wrap(help_text, 79 - 3 - mlen)
1468
      ToStdout("%-*s - %s", mlen, cmdstr, help_lines.pop(0))
1469
      for line in help_lines:
1470
        ToStdout("%-*s   %s", mlen, "", line)
1471

    
1472
    ToStdout("")
1473

    
1474
    return None, None, None
1475

    
1476
  # get command, unalias it, and look it up in commands
1477
  cmd = argv.pop(1)
1478
  if cmd in aliases:
1479
    if cmd in commands:
1480
      raise errors.ProgrammerError("Alias '%s' overrides an existing"
1481
                                   " command" % cmd)
1482

    
1483
    if aliases[cmd] not in commands:
1484
      raise errors.ProgrammerError("Alias '%s' maps to non-existing"
1485
                                   " command '%s'" % (cmd, aliases[cmd]))
1486

    
1487
    cmd = aliases[cmd]
1488

    
1489
  if cmd in env_override:
1490
    args_env_name = ("%s_%s" % (binary.replace("-", "_"), cmd)).upper()
1491
    env_args = os.environ.get(args_env_name)
1492
    if env_args:
1493
      argv = utils.InsertAtPos(argv, 1, shlex.split(env_args))
1494

    
1495
  func, args_def, parser_opts, usage, description = commands[cmd]
1496
  parser = OptionParser(option_list=parser_opts + COMMON_OPTS,
1497
                        description=description,
1498
                        formatter=TitledHelpFormatter(),
1499
                        usage="%%prog %s %s" % (cmd, usage))
1500
  parser.disable_interspersed_args()
1501
  options, args = parser.parse_args(args=argv[1:])
1502

    
1503
  if not _CheckArguments(cmd, args_def, args):
1504
    return None, None, None
1505

    
1506
  return func, options, args
1507

    
1508

    
1509
def _CheckArguments(cmd, args_def, args):
1510
  """Verifies the arguments using the argument definition.
1511

1512
  Algorithm:
1513

1514
    1. Abort with error if values specified by user but none expected.
1515

1516
    1. For each argument in definition
1517

1518
      1. Keep running count of minimum number of values (min_count)
1519
      1. Keep running count of maximum number of values (max_count)
1520
      1. If it has an unlimited number of values
1521

1522
        1. Abort with error if it's not the last argument in the definition
1523

1524
    1. If last argument has limited number of values
1525

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

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

1530
  """
1531
  if args and not args_def:
1532
    ToStderr("Error: Command %s expects no arguments", cmd)
1533
    return False
1534

    
1535
  min_count = None
1536
  max_count = None
1537
  check_max = None
1538

    
1539
  last_idx = len(args_def) - 1
1540

    
1541
  for idx, arg in enumerate(args_def):
1542
    if min_count is None:
1543
      min_count = arg.min
1544
    elif arg.min is not None:
1545
      min_count += arg.min
1546

    
1547
    if max_count is None:
1548
      max_count = arg.max
1549
    elif arg.max is not None:
1550
      max_count += arg.max
1551

    
1552
    if idx == last_idx:
1553
      check_max = (arg.max is not None)
1554

    
1555
    elif arg.max is None:
1556
      raise errors.ProgrammerError("Only the last argument can have max=None")
1557

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

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

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

    
1577
  return True
1578

    
1579

    
1580
def SplitNodeOption(value):
1581
  """Splits the value of a --node option.
1582

1583
  """
1584
  if value and ":" in value:
1585
    return value.split(":", 1)
1586
  else:
1587
    return (value, None)
1588

    
1589

    
1590
def CalculateOSNames(os_name, os_variants):
1591
  """Calculates all the names an OS can be called, according to its variants.
1592

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

1600
  """
1601
  if os_variants:
1602
    return ["%s+%s" % (os_name, v) for v in os_variants]
1603
  else:
1604
    return [os_name]
1605

    
1606

    
1607
def ParseFields(selected, default):
1608
  """Parses the values of "--field"-like options.
1609

1610
  @type selected: string or None
1611
  @param selected: User-selected options
1612
  @type default: list
1613
  @param default: Default fields
1614

1615
  """
1616
  if selected is None:
1617
    return default
1618

    
1619
  if selected.startswith("+"):
1620
    return default + selected[1:].split(",")
1621

    
1622
  return selected.split(",")
1623

    
1624

    
1625
UsesRPC = rpc.RunWithRPC
1626

    
1627

    
1628
def AskUser(text, choices=None):
1629
  """Ask the user a question.
1630

1631
  @param text: the question to ask
1632

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

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

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

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

    
1684

    
1685
class JobSubmittedException(Exception):
1686
  """Job was submitted, client should exit.
1687

1688
  This exception has one argument, the ID of the job that was
1689
  submitted. The handler should print this ID.
1690

1691
  This is not an error, just a structured way to exit from clients.
1692

1693
  """
1694

    
1695

    
1696
def SendJob(ops, cl=None):
1697
  """Function to submit an opcode without waiting for the results.
1698

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

1705
  """
1706
  if cl is None:
1707
    cl = GetClient()
1708

    
1709
  job_id = cl.SubmitJob(ops)
1710

    
1711
  return job_id
1712

    
1713

    
1714
def GenericPollJob(job_id, cbs, report_cbs):
1715
  """Generic job-polling function.
1716

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

1724
  """
1725
  prev_job_info = None
1726
  prev_logmsg_serial = None
1727

    
1728
  status = None
1729

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

    
1737
    if result == constants.JOB_NOTCHANGED:
1738
      report_cbs.ReportNotChanged(job_id, status)
1739

    
1740
      # Wait again
1741
      continue
1742

    
1743
    # Split result, a tuple of (field values, log entries)
1744
    (job_info, log_entries) = result
1745
    (status, ) = job_info
1746

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

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

    
1761
    prev_job_info = job_info
1762

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

    
1767
  status, opstatus, result = jobs[0]
1768

    
1769
  if status == constants.JOB_STATUS_SUCCESS:
1770
    return result
1771

    
1772
  if status in (constants.JOB_STATUS_CANCELING, constants.JOB_STATUS_CANCELED):
1773
    raise errors.OpExecError("Job was canceled")
1774

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

    
1782
      if has_ok:
1783
        raise errors.OpExecError("partial failure (opcode %d): %s" %
1784
                                 (idx, msg))
1785

    
1786
      raise errors.OpExecError(str(msg))
1787

    
1788
  # default failure mode
1789
  raise errors.OpExecError(result)
1790

    
1791

    
1792
class JobPollCbBase:
1793
  """Base class for L{GenericPollJob} callbacks.
1794

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

1799
    """
1800

    
1801
  def WaitForJobChangeOnce(self, job_id, fields,
1802
                           prev_job_info, prev_log_serial):
1803
    """Waits for changes on a job.
1804

1805
    """
1806
    raise NotImplementedError()
1807

    
1808
  def QueryJobs(self, job_ids, fields):
1809
    """Returns the selected fields for the selected job IDs.
1810

1811
    @type job_ids: list of numbers
1812
    @param job_ids: Job IDs
1813
    @type fields: list of strings
1814
    @param fields: Fields
1815

1816
    """
1817
    raise NotImplementedError()
1818

    
1819

    
1820
class JobPollReportCbBase:
1821
  """Base class for L{GenericPollJob} reporting callbacks.
1822

1823
  """
1824
  def __init__(self):
1825
    """Initializes this class.
1826

1827
    """
1828

    
1829
  def ReportLogMessage(self, job_id, serial, timestamp, log_type, log_msg):
1830
    """Handles a log message.
1831

1832
    """
1833
    raise NotImplementedError()
1834

    
1835
  def ReportNotChanged(self, job_id, status):
1836
    """Called for if a job hasn't changed in a while.
1837

1838
    @type job_id: number
1839
    @param job_id: Job ID
1840
    @type status: string or None
1841
    @param status: Job status if available
1842

1843
    """
1844
    raise NotImplementedError()
1845

    
1846

    
1847
class _LuxiJobPollCb(JobPollCbBase):
1848
  def __init__(self, cl):
1849
    """Initializes this class.
1850

1851
    """
1852
    JobPollCbBase.__init__(self)
1853
    self.cl = cl
1854

    
1855
  def WaitForJobChangeOnce(self, job_id, fields,
1856
                           prev_job_info, prev_log_serial):
1857
    """Waits for changes on a job.
1858

1859
    """
1860
    return self.cl.WaitForJobChangeOnce(job_id, fields,
1861
                                        prev_job_info, prev_log_serial)
1862

    
1863
  def QueryJobs(self, job_ids, fields):
1864
    """Returns the selected fields for the selected job IDs.
1865

1866
    """
1867
    return self.cl.QueryJobs(job_ids, fields)
1868

    
1869

    
1870
class FeedbackFnJobPollReportCb(JobPollReportCbBase):
1871
  def __init__(self, feedback_fn):
1872
    """Initializes this class.
1873

1874
    """
1875
    JobPollReportCbBase.__init__(self)
1876

    
1877
    self.feedback_fn = feedback_fn
1878

    
1879
    assert callable(feedback_fn)
1880

    
1881
  def ReportLogMessage(self, job_id, serial, timestamp, log_type, log_msg):
1882
    """Handles a log message.
1883

1884
    """
1885
    self.feedback_fn((timestamp, log_type, log_msg))
1886

    
1887
  def ReportNotChanged(self, job_id, status):
1888
    """Called if a job hasn't changed in a while.
1889

1890
    """
1891
    # Ignore
1892

    
1893

    
1894
class StdioJobPollReportCb(JobPollReportCbBase):
1895
  def __init__(self):
1896
    """Initializes this class.
1897

1898
    """
1899
    JobPollReportCbBase.__init__(self)
1900

    
1901
    self.notified_queued = False
1902
    self.notified_waitlock = False
1903

    
1904
  def ReportLogMessage(self, job_id, serial, timestamp, log_type, log_msg):
1905
    """Handles a log message.
1906

1907
    """
1908
    ToStdout("%s %s", time.ctime(utils.MergeTime(timestamp)),
1909
             FormatLogMessage(log_type, log_msg))
1910

    
1911
  def ReportNotChanged(self, job_id, status):
1912
    """Called if a job hasn't changed in a while.
1913

1914
    """
1915
    if status is None:
1916
      return
1917

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

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

    
1926

    
1927
def FormatLogMessage(log_type, log_msg):
1928
  """Formats a job message according to its type.
1929

1930
  """
1931
  if log_type != constants.ELOG_MESSAGE:
1932
    log_msg = str(log_msg)
1933

    
1934
  return utils.SafeEncode(log_msg)
1935

    
1936

    
1937
def PollJob(job_id, cl=None, feedback_fn=None, reporter=None):
1938
  """Function to poll for the result of a job.
1939

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

1946
  """
1947
  if cl is None:
1948
    cl = GetClient()
1949

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

    
1958
  return GenericPollJob(job_id, _LuxiJobPollCb(cl), reporter)
1959

    
1960

    
1961
def SubmitOpCode(op, cl=None, feedback_fn=None, opts=None, reporter=None):
1962
  """Legacy function to submit an opcode.
1963

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

1968
  """
1969
  if cl is None:
1970
    cl = GetClient()
1971

    
1972
  SetGenericOpcodeOpts([op], opts)
1973

    
1974
  job_id = SendJob([op], cl=cl)
1975

    
1976
  op_results = PollJob(job_id, cl=cl, feedback_fn=feedback_fn,
1977
                       reporter=reporter)
1978

    
1979
  return op_results[0]
1980

    
1981

    
1982
def SubmitOrSend(op, opts, cl=None, feedback_fn=None):
1983
  """Wrapper around SubmitOpCode or SendJob.
1984

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

1990
  It will also process the opcodes if we're sending the via SendJob
1991
  (otherwise SubmitOpCode does it).
1992

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

    
2002

    
2003
def SetGenericOpcodeOpts(opcode_list, options):
2004
  """Processor for generic options.
2005

2006
  This function updates the given opcodes based on generic command
2007
  line options (like debug, dry-run, etc.).
2008

2009
  @param opcode_list: list of opcodes
2010
  @param options: command line options or None
2011
  @return: None (in-place modification)
2012

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

    
2023

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

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

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

    
2046

    
2047
def FormatError(err):
2048
  """Return a formatted error message for a given error.
2049

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

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

    
2134

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

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

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

    
2155
    if len(sys.argv) >= 2:
2156
      logname = utils.ShellQuoteArgs([binary, sys.argv[1]])
2157
    else:
2158
      logname = binary
2159

    
2160
    cmdline = utils.ShellQuoteArgs([binary] + sys.argv[1:])
2161
  else:
2162
    binary = "<unknown program>"
2163
    cmdline = "<unknown>"
2164

    
2165
  if aliases is None:
2166
    aliases = {}
2167

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

    
2175
  if func is None: # parse error
2176
    return 1
2177

    
2178
  if override is not None:
2179
    for key, val in override.iteritems():
2180
      setattr(options, key, val)
2181

    
2182
  utils.SetupLogging(constants.LOG_COMMANDS, logname, debug=options.debug,
2183
                     stderr_logging=True)
2184

    
2185
  logging.info("Command line: %s", cmdline)
2186

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

    
2206
  return result
2207

    
2208

    
2209
def ParseNicOption(optvalue):
2210
  """Parses the value of the --net option(s).
2211

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

    
2218
  nics = [{}] * nic_max
2219
  for nidx, ndict in optvalue:
2220
    nidx = int(nidx)
2221

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

    
2226
    utils.ForceDictType(ndict, constants.INIC_PARAMS_TYPES)
2227

    
2228
    nics[nidx] = ndict
2229

    
2230
  return nics
2231

    
2232

    
2233
def GenericInstanceCreate(mode, opts, args):
2234
  """Add an instance to the cluster via either creation or import.
2235

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

2243
  """
2244
  instance = args[0]
2245

    
2246
  (pnode, snode) = SplitNodeOption(opts.node)
2247

    
2248
  hypervisor = None
2249
  hvparams = {}
2250
  if opts.hypervisor:
2251
    hypervisor, hvparams = opts.hypervisor
2252

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

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

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

    
2313
  if opts.tags is not None:
2314
    tags = opts.tags.split(",")
2315
  else:
2316
    tags = []
2317

    
2318
  utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_COMPAT)
2319
  utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
2320

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

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

    
2366
  SubmitOrSend(op, opts)
2367
  return 0
2368

    
2369

    
2370
class _RunWhileClusterStoppedHelper:
2371
  """Helper class for L{RunWhileClusterStopped} to simplify state management
2372

2373
  """
2374
  def __init__(self, feedback_fn, cluster_name, master_node, online_nodes):
2375
    """Initializes this class.
2376

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

2386
    """
2387
    self.feedback_fn = feedback_fn
2388
    self.cluster_name = cluster_name
2389
    self.master_node = master_node
2390
    self.online_nodes = online_nodes
2391

    
2392
    self.ssh = ssh.SshRunner(self.cluster_name)
2393

    
2394
    self.nonmaster_nodes = [name for name in online_nodes
2395
                            if name != master_node]
2396

    
2397
    assert self.master_node not in self.nonmaster_nodes
2398

    
2399
  def _RunCmd(self, node_name, cmd):
2400
    """Runs a command on the local or a remote machine.
2401

2402
    @type node_name: string
2403
    @param node_name: Machine name
2404
    @type cmd: list
2405
    @param cmd: Command
2406

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

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

    
2422
  def Call(self, fn, *args):
2423
    """Call function while all daemons are stopped.
2424

2425
    @type fn: callable
2426
    @param fn: Function to be called
2427

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

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

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

    
2464

    
2465
def RunWhileClusterStopped(feedback_fn, fn, *args):
2466
  """Calls a function while all cluster daemons are stopped.
2467

2468
  @type feedback_fn: callable
2469
  @param feedback_fn: Feedback function
2470
  @type fn: callable
2471
  @param fn: Function to be called when daemons are stopped
2472

2473
  """
2474
  feedback_fn("Gathering cluster information")
2475

    
2476
  # This ensures we're running on the master daemon
2477
  cl = GetClient()
2478

    
2479
  (cluster_name, master_node) = \
2480
    cl.QueryConfigValues(["cluster_name", "master_node"])
2481

    
2482
  online_nodes = GetOnlineNodes([], cl=cl)
2483

    
2484
  # Don't keep a reference to the client. The master daemon will go away.
2485
  del cl
2486

    
2487
  assert master_node in online_nodes
2488

    
2489
  return _RunWhileClusterStoppedHelper(feedback_fn, cluster_name, master_node,
2490
                                       online_nodes).Call(fn, *args)
2491

    
2492

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

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

2521
  """
2522
  if units is None:
2523
    if separator:
2524
      units = "m"
2525
    else:
2526
      units = "h"
2527

    
2528
  if numfields is None:
2529
    numfields = []
2530
  if unitfields is None:
2531
    unitfields = []
2532

    
2533
  numfields = utils.FieldSet(*numfields)   # pylint: disable=W0142
2534
  unitfields = utils.FieldSet(*unitfields) # pylint: disable=W0142
2535

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

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

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

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

    
2582
  if separator is None:
2583
    assert len(mlens) == len(fields)
2584

    
2585
    if fields and not numfields.Matches(fields[-1]):
2586
      mlens[-1] = 0
2587

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

    
2598
  return result
2599

    
2600

    
2601
def _FormatBool(value):
2602
  """Formats a boolean value as a string.
2603

2604
  """
2605
  if value:
2606
    return "Y"
2607
  return "N"
2608

    
2609

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

    
2620

    
2621
def _GetColumnFormatter(fdef, override, unit):
2622
  """Returns formatting function for a field.
2623

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

2634
  """
2635
  fmt = override.get(fdef.name, None)
2636
  if fmt is not None:
2637
    return fmt
2638

    
2639
  assert constants.QFT_UNIT not in _DEFAULT_FORMAT_QUERY
2640

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

    
2645
  fmt = _DEFAULT_FORMAT_QUERY.get(fdef.kind, None)
2646
  if fmt is not None:
2647
    return fmt
2648

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

    
2651

    
2652
class _QueryColumnFormatter:
2653
  """Callable class for formatting fields of a query.
2654

2655
  """
2656
  def __init__(self, fn, status_fn, verbose):
2657
    """Initializes this class.
2658

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

2666
    """
2667
    self._fn = fn
2668
    self._status_fn = status_fn
2669
    self._verbose = verbose
2670

    
2671
  def __call__(self, data):
2672
    """Returns a field's string representation.
2673

2674
    """
2675
    (status, value) = data
2676

    
2677
    # Report status
2678
    self._status_fn(status)
2679

    
2680
    if status == constants.RS_NORMAL:
2681
      return self._fn(value)
2682

    
2683
    assert value is None, \
2684
           "Found value %r for abnormal status %s" % (value, status)
2685

    
2686
    return FormatResultError(status, self._verbose)
2687

    
2688

    
2689
def FormatResultError(status, verbose):
2690
  """Formats result status other than L{constants.RS_NORMAL}.
2691

2692
  @param status: The result status
2693
  @type verbose: boolean
2694
  @param verbose: Whether to return the verbose text
2695
  @return: Text of result status
2696

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

    
2709

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

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

2729
  """
2730
  if unit is None:
2731
    if separator:
2732
      unit = "m"
2733
    else:
2734
      unit = "h"
2735

    
2736
  if format_override is None:
2737
    format_override = {}
2738

    
2739
  stats = dict.fromkeys(constants.RS_ALL, 0)
2740

    
2741
  def _RecordStatus(status):
2742
    if status in stats:
2743
      stats[status] += 1
2744

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

    
2754
  table = FormatTable(result.data, columns, header, separator)
2755

    
2756
  # Collect statistics
2757
  assert len(stats) == len(constants.RS_ALL)
2758
  assert compat.all(count >= 0 for count in stats.values())
2759

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

    
2771
  return (status, table)
2772

    
2773

    
2774
def _GetUnknownFields(fdefs):
2775
  """Returns list of unknown fields included in C{fdefs}.
2776

2777
  @type fdefs: list of L{objects.QueryFieldDefinition}
2778

2779
  """
2780
  return [fdef for fdef in fdefs
2781
          if fdef.kind == constants.QFT_UNKNOWN]
2782

    
2783

    
2784
def _WarnUnknownFields(fdefs):
2785
  """Prints a warning to stderr if a query included unknown fields.
2786

2787
  @type fdefs: list of L{objects.QueryFieldDefinition}
2788

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

    
2796
  return False
2797

    
2798

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

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

2824
  """
2825
  if not names:
2826
    names = None
2827

    
2828
  qfilter = qlang.MakeFilter(names, force_filter)
2829

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

    
2833
  response = cl.Query(resource, fields, qfilter)
2834

    
2835
  found_unknown = _WarnUnknownFields(response.fields)
2836

    
2837
  (status, data) = FormatQueryResult(response, unit=unit, separator=separator,
2838
                                     header=header,
2839
                                     format_override=format_override,
2840
                                     verbose=verbose)
2841

    
2842
  for line in data:
2843
    ToStdout(line)
2844

    
2845
  assert ((found_unknown and status == QR_UNKNOWN) or
2846
          (not found_unknown and status != QR_UNKNOWN))
2847

    
2848
  if status == QR_UNKNOWN:
2849
    return constants.EXIT_UNKNOWN_FIELD
2850

    
2851
  # TODO: Should the list command fail if not all data could be collected?
2852
  return constants.EXIT_SUCCESS
2853

    
2854

    
2855
def GenericListFields(resource, fields, separator, header, cl=None):
2856
  """Generic implementation for listing fields for a resource.
2857

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

2866
  """
2867
  if cl is None:
2868
    cl = GetClient()
2869

    
2870
  if not fields:
2871
    fields = None
2872

    
2873
  response = cl.QueryFields(resource, fields)
2874

    
2875
  found_unknown = _WarnUnknownFields(response.fields)
2876

    
2877
  columns = [
2878
    TableColumn("Name", str, False),
2879
    TableColumn("Title", str, False),
2880
    TableColumn("Description", str, False),
2881
    ]
2882

    
2883
  rows = [[fdef.name, fdef.title, fdef.doc] for fdef in response.fields]
2884

    
2885
  for line in FormatTable(rows, columns, header, separator):
2886
    ToStdout(line)
2887

    
2888
  if found_unknown:
2889
    return constants.EXIT_UNKNOWN_FIELD
2890

    
2891
  return constants.EXIT_SUCCESS
2892

    
2893

    
2894
class TableColumn:
2895
  """Describes a column for L{FormatTable}.
2896

2897
  """
2898
  def __init__(self, title, fn, align_right):
2899
    """Initializes this class.
2900

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

2908
    """
2909
    self.title = title
2910
    self.format = fn
2911
    self.align_right = align_right
2912

    
2913

    
2914
def _GetColFormatString(width, align_right):
2915
  """Returns the format string for a field.
2916

2917
  """
2918
  if align_right:
2919
    sign = ""
2920
  else:
2921
    sign = "-"
2922

    
2923
  return "%%%s%ss" % (sign, width)
2924

    
2925

    
2926
def FormatTable(rows, columns, header, separator):
2927
  """Formats data as a table.
2928

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

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

    
2946
  # Format row data
2947
  for row in rows:
2948
    assert len(row) == len(columns)
2949

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

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

    
2958
    data.append(formatted)
2959

    
2960
  if separator is not None:
2961
    # Return early if a separator is used
2962
    return [separator.join(row) for row in data]
2963

    
2964
  if columns and not columns[-1].align_right:
2965
    # Avoid unnecessary spaces at end of line
2966
    colwidth[-1] = 0
2967

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

    
2972
  return [fmt % tuple(row) for row in data]
2973

    
2974

    
2975
def FormatTimestamp(ts):
2976
  """Formats a given timestamp.
2977

2978
  @type ts: timestamp
2979
  @param ts: a timeval-type timestamp, a tuple of seconds and microseconds
2980

2981
  @rtype: string
2982
  @return: a string with the formatted timestamp
2983

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

    
2990

    
2991
def ParseTimespec(value):
2992
  """Parse a time specification.
2993

2994
  The following suffixed will be recognized:
2995

2996
    - s: seconds
2997
    - m: minutes
2998
    - h: hours
2999
    - d: day
3000
    - w: weeks
3001

3002
  Without any suffix, the value will be taken to be in seconds.
3003

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

    
3032

    
3033
def GetOnlineNodes(nodes, cl=None, nowarn=False, secondary_ips=False,
3034
                   filter_master=False, nodegroup=None):
3035
  """Returns the names of online nodes.
3036

3037
  This function will also log a warning on stderr with the names of
3038
  the online nodes.
3039

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

3058
  """
3059
  if cl is None:
3060
    cl = GetClient()
3061

    
3062
  qfilter = []
3063

    
3064
  if nodes:
3065
    qfilter.append(qlang.MakeSimpleFilter("name", nodes))
3066

    
3067
  if nodegroup is not None:
3068
    qfilter.append([qlang.OP_OR, [qlang.OP_EQUAL, "group", nodegroup],
3069
                                 [qlang.OP_EQUAL, "group.uuid", nodegroup]])
3070

    
3071
  if filter_master:
3072
    qfilter.append([qlang.OP_NOT, [qlang.OP_TRUE, "master"]])
3073

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

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

    
3085
  def _IsOffline(row):
3086
    (_, (_, offline), _) = row
3087
    return offline
3088

    
3089
  def _GetName(row):
3090
    ((_, name), _, _) = row
3091
    return name
3092

    
3093
  def _GetSip(row):
3094
    (_, _, (_, sip)) = row
3095
    return sip
3096

    
3097
  (offline, online) = compat.partition(result.data, _IsOffline)
3098

    
3099
  if offline and not nowarn:
3100
    ToStderr("Note: skipping offline node(s): %s" %
3101
             utils.CommaJoin(map(_GetName, offline)))
3102

    
3103
  if secondary_ips:
3104
    fn = _GetSip
3105
  else:
3106
    fn = _GetName
3107

    
3108
  return map(fn, online)
3109

    
3110

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

3114
  @type stream: file object
3115
  @param stream: the file to which we should write
3116
  @type txt: str
3117
  @param txt: the message
3118

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

    
3135

    
3136
def ToStdout(txt, *args):
3137
  """Write a message to stdout only, bypassing the logging system
3138

3139
  This is just a wrapper over _ToStream.
3140

3141
  @type txt: str
3142
  @param txt: the message
3143

3144
  """
3145
  _ToStream(sys.stdout, txt, *args)
3146

    
3147

    
3148
def ToStderr(txt, *args):
3149
  """Write a message to stderr only, bypassing the logging system
3150

3151
  This is just a wrapper over _ToStream.
3152

3153
  @type txt: str
3154
  @param txt: the message
3155

3156
  """
3157
  _ToStream(sys.stderr, txt, *args)
3158

    
3159

    
3160
class JobExecutor(object):
3161
  """Class which manages the submission and execution of multiple jobs.
3162

3163
  Note that instances of this class should not be reused between
3164
  GetResults() calls.
3165

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

    
3178
  @staticmethod
3179
  def _IfName(name, fmt):
3180
    """Helper function for formatting name.
3181

3182
    """
3183
    if name:
3184
      return fmt % name
3185

    
3186
    return ""
3187

    
3188
  def QueueJob(self, name, *ops):
3189
    """Record a job for later submit.
3190

3191
    @type name: string
3192
    @param name: a description of the job, will be used in WaitJobSet
3193

3194
    """
3195
    SetGenericOpcodeOpts(ops, self.opts)
3196
    self.queue.append((self._counter.next(), name, ops))
3197

    
3198
  def AddJobId(self, name, status, job_id):
3199
    """Adds a job ID to the internal queue.
3200

3201
    """
3202
    self.jobs.append((self._counter.next(), status, job_id, name))
3203

    
3204
  def SubmitPending(self, each=False):
3205
    """Submit all pending jobs.
3206

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

    
3219
  def _ChooseJob(self):
3220
    """Choose a non-waiting/queued job to poll next.
3221

3222
    """
3223
    assert self.jobs, "_ChooseJob called with empty job list"
3224

    
3225
    result = self.cl.QueryJobs([i[2] for i in self.jobs[:_CHOOSE_BATCH]],
3226
                               ["status"])
3227
    assert result
3228

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

    
3240
    # no job found
3241
    return self.jobs.pop(0)
3242

    
3243
  def GetResults(self):
3244
    """Wait for and return the results of all jobs.
3245

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

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

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

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

    
3284
      results.append((idx, success, job_result))
3285

    
3286
    # sort based on the index, then drop it
3287
    results.sort()
3288
    results = [i[1:] for i in results]
3289

    
3290
    return results
3291

    
3292
  def WaitOrShow(self, wait):
3293
    """Wait for job results or only print the job IDs.
3294

3295
    @type wait: boolean
3296
    @param wait: whether to wait or not
3297

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

    
3311

    
3312
def FormatParameterDict(buf, param_dict, actual, level=1):
3313
  """Formats a parameter dictionary.
3314

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

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

    
3329

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

3333
  This function is used to request confirmation for doing an operation
3334
  on a given list of list_type.
3335

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

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

    
3353
  choices = [("y", True, "Yes, execute the %s" % text),
3354
             ("n", False, "No, abort the %s" % text)]
3355

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

    
3362
  choice = AskUser(question, choices)
3363
  if choice == "v":
3364
    choices.pop(1)
3365
    choice = AskUser(msg + affected, choices)
3366
  return choice