Statistics
| Branch: | Tag: | Revision:

root / lib / cli.py @ ef40c537

History | View | Annotate | Download (115.3 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
from ganeti import objects
47

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

    
51

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

    
266
NO_PREFIX = "no_"
267
UN_PREFIX = "-"
268

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

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

    
281
# Query result status for clients
282
(QR_NORMAL,
283
 QR_UNKNOWN,
284
 QR_INCOMPLETE) = range(3)
285

    
286
#: Maximum batch size for ChooseJob
287
_CHOOSE_BATCH = 25
288

    
289

    
290
# constants used to create InstancePolicy dictionary
291
TISPECS_GROUP_TYPES = {
292
  constants.ISPECS_MIN: constants.VTYPE_INT,
293
  constants.ISPECS_MAX: constants.VTYPE_INT,
294
  }
295

    
296
TISPECS_CLUSTER_TYPES = {
297
  constants.ISPECS_MIN: constants.VTYPE_INT,
298
  constants.ISPECS_MAX: constants.VTYPE_INT,
299
  constants.ISPECS_STD: constants.VTYPE_INT,
300
  }
301

    
302

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

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

    
312

    
313
class ArgSuggest(_Argument):
314
  """Suggesting argument.
315

316
  Value can be any of the ones passed to the constructor.
317

318
  """
319
  # pylint: disable=W0622
320
  def __init__(self, min=0, max=None, choices=None):
321
    _Argument.__init__(self, min=min, max=max)
322
    self.choices = choices
323

    
324
  def __repr__(self):
325
    return ("<%s min=%s max=%s choices=%r>" %
326
            (self.__class__.__name__, self.min, self.max, self.choices))
327

    
328

    
329
class ArgChoice(ArgSuggest):
330
  """Choice argument.
331

332
  Value can be any of the ones passed to the constructor. Like L{ArgSuggest},
333
  but value must be one of the choices.
334

335
  """
336

    
337

    
338
class ArgUnknown(_Argument):
339
  """Unknown argument to program (e.g. determined at runtime).
340

341
  """
342

    
343

    
344
class ArgInstance(_Argument):
345
  """Instances argument.
346

347
  """
348

    
349

    
350
class ArgNode(_Argument):
351
  """Node argument.
352

353
  """
354

    
355

    
356
class ArgGroup(_Argument):
357
  """Node group argument.
358

359
  """
360

    
361

    
362
class ArgJobId(_Argument):
363
  """Job ID argument.
364

365
  """
366

    
367

    
368
class ArgFile(_Argument):
369
  """File path argument.
370

371
  """
372

    
373

    
374
class ArgCommand(_Argument):
375
  """Command argument.
376

377
  """
378

    
379

    
380
class ArgHost(_Argument):
381
  """Host argument.
382

383
  """
384

    
385

    
386
class ArgOs(_Argument):
387
  """OS argument.
388

389
  """
390

    
391

    
392
ARGS_NONE = []
393
ARGS_MANY_INSTANCES = [ArgInstance()]
394
ARGS_MANY_NODES = [ArgNode()]
395
ARGS_MANY_GROUPS = [ArgGroup()]
396
ARGS_ONE_INSTANCE = [ArgInstance(min=1, max=1)]
397
ARGS_ONE_NODE = [ArgNode(min=1, max=1)]
398
# TODO
399
ARGS_ONE_GROUP = [ArgGroup(min=1, max=1)]
400
ARGS_ONE_OS = [ArgOs(min=1, max=1)]
401

    
402

    
403
def _ExtractTagsObject(opts, args):
404
  """Extract the tag type object.
405

406
  Note that this function will modify its args parameter.
407

408
  """
409
  if not hasattr(opts, "tag_type"):
410
    raise errors.ProgrammerError("tag_type not passed to _ExtractTagsObject")
411
  kind = opts.tag_type
412
  if kind == constants.TAG_CLUSTER:
413
    retval = kind, kind
414
  elif kind in (constants.TAG_NODEGROUP,
415
                constants.TAG_NODE,
416
                constants.TAG_INSTANCE):
417
    if not args:
418
      raise errors.OpPrereqError("no arguments passed to the command")
419
    name = args.pop(0)
420
    retval = kind, name
421
  else:
422
    raise errors.ProgrammerError("Unhandled tag type '%s'" % kind)
423
  return retval
424

    
425

    
426
def _ExtendTags(opts, args):
427
  """Extend the args if a source file has been given.
428

429
  This function will extend the tags with the contents of the file
430
  passed in the 'tags_source' attribute of the opts parameter. A file
431
  named '-' will be replaced by stdin.
432

433
  """
434
  fname = opts.tags_source
435
  if fname is None:
436
    return
437
  if fname == "-":
438
    new_fh = sys.stdin
439
  else:
440
    new_fh = open(fname, "r")
441
  new_data = []
442
  try:
443
    # we don't use the nice 'new_data = [line.strip() for line in fh]'
444
    # because of python bug 1633941
445
    while True:
446
      line = new_fh.readline()
447
      if not line:
448
        break
449
      new_data.append(line.strip())
450
  finally:
451
    new_fh.close()
452
  args.extend(new_data)
453

    
454

    
455
def ListTags(opts, args):
456
  """List the tags on a given object.
457

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

463
  """
464
  kind, name = _ExtractTagsObject(opts, args)
465
  cl = GetClient()
466
  result = cl.QueryTags(kind, name)
467
  result = list(result)
468
  result.sort()
469
  for tag in result:
470
    ToStdout(tag)
471

    
472

    
473
def AddTags(opts, args):
474
  """Add tags on a given object.
475

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

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

    
489

    
490
def RemoveTags(opts, args):
491
  """Remove tags from a given object.
492

493
  This is a generic implementation that knows how to deal with all
494
  three cases of tag objects (cluster, node, instance). The opts
495
  argument is expected to contain a tag_type field denoting what
496
  object type we work on.
497

498
  """
499
  kind, name = _ExtractTagsObject(opts, args)
500
  _ExtendTags(opts, args)
501
  if not args:
502
    raise errors.OpPrereqError("No tags to be removed")
503
  op = opcodes.OpTagsDel(kind=kind, name=name, tags=args)
504
  SubmitOrSend(op, opts)
505

    
506

    
507
def check_unit(option, opt, value): # pylint: disable=W0613
508
  """OptParsers custom converter for units.
509

510
  """
511
  try:
512
    return utils.ParseUnit(value)
513
  except errors.UnitParseError, err:
514
    raise OptionValueError("option %s: %s" % (opt, err))
515

    
516

    
517
def _SplitKeyVal(opt, data):
518
  """Convert a KeyVal string into a dict.
519

520
  This function will convert a key=val[,...] string into a dict. Empty
521
  values will be converted specially: keys which have the prefix 'no_'
522
  will have the value=False and the prefix stripped, the others will
523
  have value=True.
524

525
  @type opt: string
526
  @param opt: a string holding the option name for which we process the
527
      data, used in building error messages
528
  @type data: string
529
  @param data: a string of the format key=val,key=val,...
530
  @rtype: dict
531
  @return: {key=val, key=val}
532
  @raises errors.ParameterError: if there are duplicate keys
533

534
  """
535
  kv_dict = {}
536
  if data:
537
    for elem in utils.UnescapeAndSplit(data, sep=","):
538
      if "=" in elem:
539
        key, val = elem.split("=", 1)
540
      else:
541
        if elem.startswith(NO_PREFIX):
542
          key, val = elem[len(NO_PREFIX):], False
543
        elif elem.startswith(UN_PREFIX):
544
          key, val = elem[len(UN_PREFIX):], None
545
        else:
546
          key, val = elem, True
547
      if key in kv_dict:
548
        raise errors.ParameterError("Duplicate key '%s' in option %s" %
549
                                    (key, opt))
550
      kv_dict[key] = val
551
  return kv_dict
552

    
553

    
554
def check_ident_key_val(option, opt, value):  # pylint: disable=W0613
555
  """Custom parser for ident:key=val,key=val options.
556

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

560
  """
561
  if ":" not in value:
562
    ident, rest = value, ""
563
  else:
564
    ident, rest = value.split(":", 1)
565

    
566
  if ident.startswith(NO_PREFIX):
567
    if rest:
568
      msg = "Cannot pass options when removing parameter groups: %s" % value
569
      raise errors.ParameterError(msg)
570
    retval = (ident[len(NO_PREFIX):], False)
571
  elif (ident.startswith(UN_PREFIX) and
572
        (len(ident) <= len(UN_PREFIX) or
573
         not ident[len(UN_PREFIX)][0].isdigit())):
574
    if rest:
575
      msg = "Cannot pass options when removing parameter groups: %s" % value
576
      raise errors.ParameterError(msg)
577
    retval = (ident[len(UN_PREFIX):], None)
578
  else:
579
    kv_dict = _SplitKeyVal(opt, rest)
580
    retval = (ident, kv_dict)
581
  return retval
582

    
583

    
584
def check_key_val(option, opt, value):  # pylint: disable=W0613
585
  """Custom parser class for key=val,key=val options.
586

587
  This will store the parsed values as a dict {key: val}.
588

589
  """
590
  return _SplitKeyVal(opt, value)
591

    
592

    
593
def check_bool(option, opt, value): # pylint: disable=W0613
594
  """Custom parser for yes/no options.
595

596
  This will store the parsed value as either True or False.
597

598
  """
599
  value = value.lower()
600
  if value == constants.VALUE_FALSE or value == "no":
601
    return False
602
  elif value == constants.VALUE_TRUE or value == "yes":
603
    return True
604
  else:
605
    raise errors.ParameterError("Invalid boolean value '%s'" % value)
606

    
607

    
608
def check_list(option, opt, value): # pylint: disable=W0613
609
  """Custom parser for comma-separated lists.
610

611
  """
612
  # we have to make this explicit check since "".split(",") is [""],
613
  # not an empty list :(
614
  if not value:
615
    return []
616
  else:
617
    return utils.UnescapeAndSplit(value)
618

    
619

    
620
def check_maybefloat(option, opt, value): # pylint: disable=W0613
621
  """Custom parser for float numbers which might be also defaults.
622

623
  """
624
  value = value.lower()
625

    
626
  if value == constants.VALUE_DEFAULT:
627
    return value
628
  else:
629
    return float(value)
630

    
631

    
632
# completion_suggestion is normally a list. Using numeric values not evaluating
633
# to False for dynamic completion.
634
(OPT_COMPL_MANY_NODES,
635
 OPT_COMPL_ONE_NODE,
636
 OPT_COMPL_ONE_INSTANCE,
637
 OPT_COMPL_ONE_OS,
638
 OPT_COMPL_ONE_IALLOCATOR,
639
 OPT_COMPL_INST_ADD_NODES,
640
 OPT_COMPL_ONE_NODEGROUP) = range(100, 107)
641

    
642
OPT_COMPL_ALL = frozenset([
643
  OPT_COMPL_MANY_NODES,
644
  OPT_COMPL_ONE_NODE,
645
  OPT_COMPL_ONE_INSTANCE,
646
  OPT_COMPL_ONE_OS,
647
  OPT_COMPL_ONE_IALLOCATOR,
648
  OPT_COMPL_INST_ADD_NODES,
649
  OPT_COMPL_ONE_NODEGROUP,
650
  ])
651

    
652

    
653
class CliOption(Option):
654
  """Custom option class for optparse.
655

656
  """
657
  ATTRS = Option.ATTRS + [
658
    "completion_suggest",
659
    ]
660
  TYPES = Option.TYPES + (
661
    "identkeyval",
662
    "keyval",
663
    "unit",
664
    "bool",
665
    "list",
666
    "maybefloat",
667
    )
668
  TYPE_CHECKER = Option.TYPE_CHECKER.copy()
669
  TYPE_CHECKER["identkeyval"] = check_ident_key_val
670
  TYPE_CHECKER["keyval"] = check_key_val
671
  TYPE_CHECKER["unit"] = check_unit
672
  TYPE_CHECKER["bool"] = check_bool
673
  TYPE_CHECKER["list"] = check_list
674
  TYPE_CHECKER["maybefloat"] = check_maybefloat
675

    
676

    
677
# optparse.py sets make_option, so we do it for our own option class, too
678
cli_option = CliOption
679

    
680

    
681
_YORNO = "yes|no"
682

    
683
DEBUG_OPT = cli_option("-d", "--debug", default=0, action="count",
684
                       help="Increase debugging level")
685

    
686
NOHDR_OPT = cli_option("--no-headers", default=False,
687
                       action="store_true", dest="no_headers",
688
                       help="Don't display column headers")
689

    
690
SEP_OPT = cli_option("--separator", default=None,
691
                     action="store", dest="separator",
692
                     help=("Separator between output fields"
693
                           " (defaults to one space)"))
694

    
695
USEUNITS_OPT = cli_option("--units", default=None,
696
                          dest="units", choices=("h", "m", "g", "t"),
697
                          help="Specify units for output (one of h/m/g/t)")
698

    
699
FIELDS_OPT = cli_option("-o", "--output", dest="output", action="store",
700
                        type="string", metavar="FIELDS",
701
                        help="Comma separated list of output fields")
702

    
703
FORCE_OPT = cli_option("-f", "--force", dest="force", action="store_true",
704
                       default=False, help="Force the operation")
705

    
706
CONFIRM_OPT = cli_option("--yes", dest="confirm", action="store_true",
707
                         default=False, help="Do not require confirmation")
708

    
709
IGNORE_OFFLINE_OPT = cli_option("--ignore-offline", dest="ignore_offline",
710
                                  action="store_true", default=False,
711
                                  help=("Ignore offline nodes and do as much"
712
                                        " as possible"))
713

    
714
TAG_ADD_OPT = cli_option("--tags", dest="tags",
715
                         default=None, help="Comma-separated list of instance"
716
                                            " tags")
717

    
718
TAG_SRC_OPT = cli_option("--from", dest="tags_source",
719
                         default=None, help="File with tag names")
720

    
721
SUBMIT_OPT = cli_option("--submit", dest="submit_only",
722
                        default=False, action="store_true",
723
                        help=("Submit the job and return the job ID, but"
724
                              " don't wait for the job to finish"))
725

    
726
SYNC_OPT = cli_option("--sync", dest="do_locking",
727
                      default=False, action="store_true",
728
                      help=("Grab locks while doing the queries"
729
                            " in order to ensure more consistent results"))
730

    
731
DRY_RUN_OPT = cli_option("--dry-run", default=False,
732
                         action="store_true",
733
                         help=("Do not execute the operation, just run the"
734
                               " check steps and verify it it could be"
735
                               " executed"))
736

    
737
VERBOSE_OPT = cli_option("-v", "--verbose", default=False,
738
                         action="store_true",
739
                         help="Increase the verbosity of the operation")
740

    
741
DEBUG_SIMERR_OPT = cli_option("--debug-simulate-errors", default=False,
742
                              action="store_true", dest="simulate_errors",
743
                              help="Debugging option that makes the operation"
744
                              " treat most runtime checks as failed")
745

    
746
NWSYNC_OPT = cli_option("--no-wait-for-sync", dest="wait_for_sync",
747
                        default=True, action="store_false",
748
                        help="Don't wait for sync (DANGEROUS!)")
749

    
750
ONLINE_INST_OPT = cli_option("--online", dest="online_inst",
751
                             action="store_true", default=False,
752
                             help="Enable offline instance")
753

    
754
OFFLINE_INST_OPT = cli_option("--offline", dest="offline_inst",
755
                              action="store_true", default=False,
756
                              help="Disable down instance")
757

    
758
DISK_TEMPLATE_OPT = cli_option("-t", "--disk-template", dest="disk_template",
759
                               help=("Custom disk setup (%s)" %
760
                                     utils.CommaJoin(constants.DISK_TEMPLATES)),
761
                               default=None, metavar="TEMPL",
762
                               choices=list(constants.DISK_TEMPLATES))
763

    
764
NONICS_OPT = cli_option("--no-nics", default=False, action="store_true",
765
                        help="Do not create any network cards for"
766
                        " the instance")
767

    
768
FILESTORE_DIR_OPT = cli_option("--file-storage-dir", dest="file_storage_dir",
769
                               help="Relative path under default cluster-wide"
770
                               " file storage dir to store file-based disks",
771
                               default=None, metavar="<DIR>")
772

    
773
FILESTORE_DRIVER_OPT = cli_option("--file-driver", dest="file_driver",
774
                                  help="Driver to use for image files",
775
                                  default="loop", metavar="<DRIVER>",
776
                                  choices=list(constants.FILE_DRIVER))
777

    
778
IALLOCATOR_OPT = cli_option("-I", "--iallocator", metavar="<NAME>",
779
                            help="Select nodes for the instance automatically"
780
                            " using the <NAME> iallocator plugin",
781
                            default=None, type="string",
782
                            completion_suggest=OPT_COMPL_ONE_IALLOCATOR)
783

    
784
DEFAULT_IALLOCATOR_OPT = cli_option("-I", "--default-iallocator",
785
                            metavar="<NAME>",
786
                            help="Set the default instance allocator plugin",
787
                            default=None, type="string",
788
                            completion_suggest=OPT_COMPL_ONE_IALLOCATOR)
789

    
790
OS_OPT = cli_option("-o", "--os-type", dest="os", help="What OS to run",
791
                    metavar="<os>",
792
                    completion_suggest=OPT_COMPL_ONE_OS)
793

    
794
OSPARAMS_OPT = cli_option("-O", "--os-parameters", dest="osparams",
795
                         type="keyval", default={},
796
                         help="OS parameters")
797

    
798
FORCE_VARIANT_OPT = cli_option("--force-variant", dest="force_variant",
799
                               action="store_true", default=False,
800
                               help="Force an unknown variant")
801

    
802
NO_INSTALL_OPT = cli_option("--no-install", dest="no_install",
803
                            action="store_true", default=False,
804
                            help="Do not install the OS (will"
805
                            " enable no-start)")
806

    
807
NORUNTIME_CHGS_OPT = cli_option("--no-runtime-changes",
808
                                dest="allow_runtime_chgs",
809
                                default=True, action="store_false",
810
                                help="Don't allow runtime changes")
811

    
812
BACKEND_OPT = cli_option("-B", "--backend-parameters", dest="beparams",
813
                         type="keyval", default={},
814
                         help="Backend parameters")
815

    
816
HVOPTS_OPT = cli_option("-H", "--hypervisor-parameters", type="keyval",
817
                        default={}, dest="hvparams",
818
                        help="Hypervisor parameters")
819

    
820
DISK_PARAMS_OPT = cli_option("-D", "--disk-parameters", dest="diskparams",
821
                             help="Disk template parameters, in the format"
822
                             " template:option=value,option=value,...",
823
                             type="identkeyval", action="append", default=[])
824

    
825
SPECS_MEM_SIZE_OPT = cli_option("--specs-mem-size", dest="ispecs_mem_size",
826
                                 type="keyval", default={},
827
                                 help="Memory size specs: list of key=value,"
828
                                " where key is one of min, max, std"
829
                                 " (in MB or using a unit)")
830

    
831
SPECS_CPU_COUNT_OPT = cli_option("--specs-cpu-count", dest="ispecs_cpu_count",
832
                                 type="keyval", default={},
833
                                 help="CPU count specs: list of key=value,"
834
                                 " where key is one of min, max, std")
835

    
836
SPECS_DISK_COUNT_OPT = cli_option("--specs-disk-count",
837
                                  dest="ispecs_disk_count",
838
                                  type="keyval", default={},
839
                                  help="Disk count specs: list of key=value,"
840
                                  " where key is one of min, max, std")
841

    
842
SPECS_DISK_SIZE_OPT = cli_option("--specs-disk-size", dest="ispecs_disk_size",
843
                                 type="keyval", default={},
844
                                 help="Disk size specs: list of key=value,"
845
                                " where key is one of min, max, std"
846
                                 " (in MB or using a unit)")
847

    
848
SPECS_NIC_COUNT_OPT = cli_option("--specs-nic-count", dest="ispecs_nic_count",
849
                                 type="keyval", default={},
850
                                 help="NIC count specs: list of key=value,"
851
                                 " where key is one of min, max, std")
852

    
853
IPOLICY_DISK_TEMPLATES = cli_option("--ipolicy-disk-templates",
854
                                 dest="ipolicy_disk_templates",
855
                                 type="list", default=None,
856
                                 help="Comma-separated list of"
857
                                 " enabled disk templates")
858

    
859
IPOLICY_VCPU_RATIO = cli_option("--ipolicy-vcpu-ratio",
860
                                 dest="ipolicy_vcpu_ratio",
861
                                 type="maybefloat", default=None,
862
                                 help="The maximum allowed vcpu-to-cpu ratio")
863

    
864
IPOLICY_SPINDLE_RATIO = cli_option("--ipolicy-spindle-ratio",
865
                                   dest="ipolicy_spindle_ratio",
866
                                   type="maybefloat", default=None,
867
                                   help=("The maximum allowed instances to"
868
                                         " spindle ratio"))
869

    
870
HYPERVISOR_OPT = cli_option("-H", "--hypervisor-parameters", dest="hypervisor",
871
                            help="Hypervisor and hypervisor options, in the"
872
                            " format hypervisor:option=value,option=value,...",
873
                            default=None, type="identkeyval")
874

    
875
HVLIST_OPT = cli_option("-H", "--hypervisor-parameters", dest="hvparams",
876
                        help="Hypervisor and hypervisor options, in the"
877
                        " format hypervisor:option=value,option=value,...",
878
                        default=[], action="append", type="identkeyval")
879

    
880
NOIPCHECK_OPT = cli_option("--no-ip-check", dest="ip_check", default=True,
881
                           action="store_false",
882
                           help="Don't check that the instance's IP"
883
                           " is alive")
884

    
885
NONAMECHECK_OPT = cli_option("--no-name-check", dest="name_check",
886
                             default=True, action="store_false",
887
                             help="Don't check that the instance's name"
888
                             " is resolvable")
889

    
890
NET_OPT = cli_option("--net",
891
                     help="NIC parameters", default=[],
892
                     dest="nics", action="append", type="identkeyval")
893

    
894
DISK_OPT = cli_option("--disk", help="Disk parameters", default=[],
895
                      dest="disks", action="append", type="identkeyval")
896

    
897
DISKIDX_OPT = cli_option("--disks", dest="disks", default=None,
898
                         help="Comma-separated list of disks"
899
                         " indices to act on (e.g. 0,2) (optional,"
900
                         " defaults to all disks)")
901

    
902
OS_SIZE_OPT = cli_option("-s", "--os-size", dest="sd_size",
903
                         help="Enforces a single-disk configuration using the"
904
                         " given disk size, in MiB unless a suffix is used",
905
                         default=None, type="unit", metavar="<size>")
906

    
907
IGNORE_CONSIST_OPT = cli_option("--ignore-consistency",
908
                                dest="ignore_consistency",
909
                                action="store_true", default=False,
910
                                help="Ignore the consistency of the disks on"
911
                                " the secondary")
912

    
913
ALLOW_FAILOVER_OPT = cli_option("--allow-failover",
914
                                dest="allow_failover",
915
                                action="store_true", default=False,
916
                                help="If migration is not possible fallback to"
917
                                     " failover")
918

    
919
NONLIVE_OPT = cli_option("--non-live", dest="live",
920
                         default=True, action="store_false",
921
                         help="Do a non-live migration (this usually means"
922
                         " freeze the instance, save the state, transfer and"
923
                         " only then resume running on the secondary node)")
924

    
925
MIGRATION_MODE_OPT = cli_option("--migration-mode", dest="migration_mode",
926
                                default=None,
927
                                choices=list(constants.HT_MIGRATION_MODES),
928
                                help="Override default migration mode (choose"
929
                                " either live or non-live")
930

    
931
NODE_PLACEMENT_OPT = cli_option("-n", "--node", dest="node",
932
                                help="Target node and optional secondary node",
933
                                metavar="<pnode>[:<snode>]",
934
                                completion_suggest=OPT_COMPL_INST_ADD_NODES)
935

    
936
NODE_LIST_OPT = cli_option("-n", "--node", dest="nodes", default=[],
937
                           action="append", metavar="<node>",
938
                           help="Use only this node (can be used multiple"
939
                           " times, if not given defaults to all nodes)",
940
                           completion_suggest=OPT_COMPL_ONE_NODE)
941

    
942
NODEGROUP_OPT_NAME = "--node-group"
943
NODEGROUP_OPT = cli_option("-g", NODEGROUP_OPT_NAME,
944
                           dest="nodegroup",
945
                           help="Node group (name or uuid)",
946
                           metavar="<nodegroup>",
947
                           default=None, type="string",
948
                           completion_suggest=OPT_COMPL_ONE_NODEGROUP)
949

    
950
SINGLE_NODE_OPT = cli_option("-n", "--node", dest="node", help="Target node",
951
                             metavar="<node>",
952
                             completion_suggest=OPT_COMPL_ONE_NODE)
953

    
954
NOSTART_OPT = cli_option("--no-start", dest="start", default=True,
955
                         action="store_false",
956
                         help="Don't start the instance after creation")
957

    
958
SHOWCMD_OPT = cli_option("--show-cmd", dest="show_command",
959
                         action="store_true", default=False,
960
                         help="Show command instead of executing it")
961

    
962
CLEANUP_OPT = cli_option("--cleanup", dest="cleanup",
963
                         default=False, action="store_true",
964
                         help="Instead of performing the migration, try to"
965
                         " recover from a failed cleanup. This is safe"
966
                         " to run even if the instance is healthy, but it"
967
                         " will create extra replication traffic and "
968
                         " disrupt briefly the replication (like during the"
969
                         " migration")
970

    
971
STATIC_OPT = cli_option("-s", "--static", dest="static",
972
                        action="store_true", default=False,
973
                        help="Only show configuration data, not runtime data")
974

    
975
ALL_OPT = cli_option("--all", dest="show_all",
976
                     default=False, action="store_true",
977
                     help="Show info on all instances on the cluster."
978
                     " This can take a long time to run, use wisely")
979

    
980
SELECT_OS_OPT = cli_option("--select-os", dest="select_os",
981
                           action="store_true", default=False,
982
                           help="Interactive OS reinstall, lists available"
983
                           " OS templates for selection")
984

    
985
IGNORE_FAILURES_OPT = cli_option("--ignore-failures", dest="ignore_failures",
986
                                 action="store_true", default=False,
987
                                 help="Remove the instance from the cluster"
988
                                 " configuration even if there are failures"
989
                                 " during the removal process")
990

    
991
IGNORE_REMOVE_FAILURES_OPT = cli_option("--ignore-remove-failures",
992
                                        dest="ignore_remove_failures",
993
                                        action="store_true", default=False,
994
                                        help="Remove the instance from the"
995
                                        " cluster configuration even if there"
996
                                        " are failures during the removal"
997
                                        " process")
998

    
999
REMOVE_INSTANCE_OPT = cli_option("--remove-instance", dest="remove_instance",
1000
                                 action="store_true", default=False,
1001
                                 help="Remove the instance from the cluster")
1002

    
1003
DST_NODE_OPT = cli_option("-n", "--target-node", dest="dst_node",
1004
                               help="Specifies the new node for the instance",
1005
                               metavar="NODE", default=None,
1006
                               completion_suggest=OPT_COMPL_ONE_NODE)
1007

    
1008
NEW_SECONDARY_OPT = cli_option("-n", "--new-secondary", dest="dst_node",
1009
                               help="Specifies the new secondary node",
1010
                               metavar="NODE", default=None,
1011
                               completion_suggest=OPT_COMPL_ONE_NODE)
1012

    
1013
ON_PRIMARY_OPT = cli_option("-p", "--on-primary", dest="on_primary",
1014
                            default=False, action="store_true",
1015
                            help="Replace the disk(s) on the primary"
1016
                                 " node (applies only to internally mirrored"
1017
                                 " disk templates, e.g. %s)" %
1018
                                 utils.CommaJoin(constants.DTS_INT_MIRROR))
1019

    
1020
ON_SECONDARY_OPT = cli_option("-s", "--on-secondary", dest="on_secondary",
1021
                              default=False, action="store_true",
1022
                              help="Replace the disk(s) on the secondary"
1023
                                   " node (applies only to internally mirrored"
1024
                                   " disk templates, e.g. %s)" %
1025
                                   utils.CommaJoin(constants.DTS_INT_MIRROR))
1026

    
1027
AUTO_PROMOTE_OPT = cli_option("--auto-promote", dest="auto_promote",
1028
                              default=False, action="store_true",
1029
                              help="Lock all nodes and auto-promote as needed"
1030
                              " to MC status")
1031

    
1032
AUTO_REPLACE_OPT = cli_option("-a", "--auto", dest="auto",
1033
                              default=False, action="store_true",
1034
                              help="Automatically replace faulty disks"
1035
                                   " (applies only to internally mirrored"
1036
                                   " disk templates, e.g. %s)" %
1037
                                   utils.CommaJoin(constants.DTS_INT_MIRROR))
1038

    
1039
IGNORE_SIZE_OPT = cli_option("--ignore-size", dest="ignore_size",
1040
                             default=False, action="store_true",
1041
                             help="Ignore current recorded size"
1042
                             " (useful for forcing activation when"
1043
                             " the recorded size is wrong)")
1044

    
1045
SRC_NODE_OPT = cli_option("--src-node", dest="src_node", help="Source node",
1046
                          metavar="<node>",
1047
                          completion_suggest=OPT_COMPL_ONE_NODE)
1048

    
1049
SRC_DIR_OPT = cli_option("--src-dir", dest="src_dir", help="Source directory",
1050
                         metavar="<dir>")
1051

    
1052
SECONDARY_IP_OPT = cli_option("-s", "--secondary-ip", dest="secondary_ip",
1053
                              help="Specify the secondary ip for the node",
1054
                              metavar="ADDRESS", default=None)
1055

    
1056
READD_OPT = cli_option("--readd", dest="readd",
1057
                       default=False, action="store_true",
1058
                       help="Readd old node after replacing it")
1059

    
1060
NOSSH_KEYCHECK_OPT = cli_option("--no-ssh-key-check", dest="ssh_key_check",
1061
                                default=True, action="store_false",
1062
                                help="Disable SSH key fingerprint checking")
1063

    
1064
NODE_FORCE_JOIN_OPT = cli_option("--force-join", dest="force_join",
1065
                                 default=False, action="store_true",
1066
                                 help="Force the joining of a node")
1067

    
1068
MC_OPT = cli_option("-C", "--master-candidate", dest="master_candidate",
1069
                    type="bool", default=None, metavar=_YORNO,
1070
                    help="Set the master_candidate flag on the node")
1071

    
1072
OFFLINE_OPT = cli_option("-O", "--offline", dest="offline", metavar=_YORNO,
1073
                         type="bool", default=None,
1074
                         help=("Set the offline flag on the node"
1075
                               " (cluster does not communicate with offline"
1076
                               " nodes)"))
1077

    
1078
DRAINED_OPT = cli_option("-D", "--drained", dest="drained", metavar=_YORNO,
1079
                         type="bool", default=None,
1080
                         help=("Set the drained flag on the node"
1081
                               " (excluded from allocation operations)"))
1082

    
1083
CAPAB_MASTER_OPT = cli_option("--master-capable", dest="master_capable",
1084
                    type="bool", default=None, metavar=_YORNO,
1085
                    help="Set the master_capable flag on the node")
1086

    
1087
CAPAB_VM_OPT = cli_option("--vm-capable", dest="vm_capable",
1088
                    type="bool", default=None, metavar=_YORNO,
1089
                    help="Set the vm_capable flag on the node")
1090

    
1091
ALLOCATABLE_OPT = cli_option("--allocatable", dest="allocatable",
1092
                             type="bool", default=None, metavar=_YORNO,
1093
                             help="Set the allocatable flag on a volume")
1094

    
1095
NOLVM_STORAGE_OPT = cli_option("--no-lvm-storage", dest="lvm_storage",
1096
                               help="Disable support for lvm based instances"
1097
                               " (cluster-wide)",
1098
                               action="store_false", default=True)
1099

    
1100
ENABLED_HV_OPT = cli_option("--enabled-hypervisors",
1101
                            dest="enabled_hypervisors",
1102
                            help="Comma-separated list of hypervisors",
1103
                            type="string", default=None)
1104

    
1105
NIC_PARAMS_OPT = cli_option("-N", "--nic-parameters", dest="nicparams",
1106
                            type="keyval", default={},
1107
                            help="NIC parameters")
1108

    
1109
CP_SIZE_OPT = cli_option("-C", "--candidate-pool-size", default=None,
1110
                         dest="candidate_pool_size", type="int",
1111
                         help="Set the candidate pool size")
1112

    
1113
VG_NAME_OPT = cli_option("--vg-name", dest="vg_name",
1114
                         help=("Enables LVM and specifies the volume group"
1115
                               " name (cluster-wide) for disk allocation"
1116
                               " [%s]" % constants.DEFAULT_VG),
1117
                         metavar="VG", default=None)
1118

    
1119
YES_DOIT_OPT = cli_option("--yes-do-it", "--ya-rly", dest="yes_do_it",
1120
                          help="Destroy cluster", action="store_true")
1121

    
1122
NOVOTING_OPT = cli_option("--no-voting", dest="no_voting",
1123
                          help="Skip node agreement check (dangerous)",
1124
                          action="store_true", default=False)
1125

    
1126
MAC_PREFIX_OPT = cli_option("-m", "--mac-prefix", dest="mac_prefix",
1127
                            help="Specify the mac prefix for the instance IP"
1128
                            " addresses, in the format XX:XX:XX",
1129
                            metavar="PREFIX",
1130
                            default=None)
1131

    
1132
MASTER_NETDEV_OPT = cli_option("--master-netdev", dest="master_netdev",
1133
                               help="Specify the node interface (cluster-wide)"
1134
                               " on which the master IP address will be added"
1135
                               " (cluster init default: %s)" %
1136
                               constants.DEFAULT_BRIDGE,
1137
                               metavar="NETDEV",
1138
                               default=None)
1139

    
1140
MASTER_NETMASK_OPT = cli_option("--master-netmask", dest="master_netmask",
1141
                                help="Specify the netmask of the master IP",
1142
                                metavar="NETMASK",
1143
                                default=None)
1144

    
1145
USE_EXTERNAL_MIP_SCRIPT = cli_option("--use-external-mip-script",
1146
                                dest="use_external_mip_script",
1147
                                help="Specify whether to run a user-provided"
1148
                                " script for the master IP address turnup and"
1149
                                " turndown operations",
1150
                                type="bool", metavar=_YORNO, default=None)
1151

    
1152
GLOBAL_FILEDIR_OPT = cli_option("--file-storage-dir", dest="file_storage_dir",
1153
                                help="Specify the default directory (cluster-"
1154
                                "wide) for storing the file-based disks [%s]" %
1155
                                constants.DEFAULT_FILE_STORAGE_DIR,
1156
                                metavar="DIR",
1157
                                default=constants.DEFAULT_FILE_STORAGE_DIR)
1158

    
1159
GLOBAL_SHARED_FILEDIR_OPT = cli_option("--shared-file-storage-dir",
1160
                            dest="shared_file_storage_dir",
1161
                            help="Specify the default directory (cluster-"
1162
                            "wide) for storing the shared file-based"
1163
                            " disks [%s]" %
1164
                            constants.DEFAULT_SHARED_FILE_STORAGE_DIR,
1165
                            metavar="SHAREDDIR",
1166
                            default=constants.DEFAULT_SHARED_FILE_STORAGE_DIR)
1167

    
1168
NOMODIFY_ETCHOSTS_OPT = cli_option("--no-etc-hosts", dest="modify_etc_hosts",
1169
                                   help="Don't modify /etc/hosts",
1170
                                   action="store_false", default=True)
1171

    
1172
NOMODIFY_SSH_SETUP_OPT = cli_option("--no-ssh-init", dest="modify_ssh_setup",
1173
                                    help="Don't initialize SSH keys",
1174
                                    action="store_false", default=True)
1175

    
1176
ERROR_CODES_OPT = cli_option("--error-codes", dest="error_codes",
1177
                             help="Enable parseable error messages",
1178
                             action="store_true", default=False)
1179

    
1180
NONPLUS1_OPT = cli_option("--no-nplus1-mem", dest="skip_nplusone_mem",
1181
                          help="Skip N+1 memory redundancy tests",
1182
                          action="store_true", default=False)
1183

    
1184
REBOOT_TYPE_OPT = cli_option("-t", "--type", dest="reboot_type",
1185
                             help="Type of reboot: soft/hard/full",
1186
                             default=constants.INSTANCE_REBOOT_HARD,
1187
                             metavar="<REBOOT>",
1188
                             choices=list(constants.REBOOT_TYPES))
1189

    
1190
IGNORE_SECONDARIES_OPT = cli_option("--ignore-secondaries",
1191
                                    dest="ignore_secondaries",
1192
                                    default=False, action="store_true",
1193
                                    help="Ignore errors from secondaries")
1194

    
1195
NOSHUTDOWN_OPT = cli_option("--noshutdown", dest="shutdown",
1196
                            action="store_false", default=True,
1197
                            help="Don't shutdown the instance (unsafe)")
1198

    
1199
TIMEOUT_OPT = cli_option("--timeout", dest="timeout", type="int",
1200
                         default=constants.DEFAULT_SHUTDOWN_TIMEOUT,
1201
                         help="Maximum time to wait")
1202

    
1203
SHUTDOWN_TIMEOUT_OPT = cli_option("--shutdown-timeout",
1204
                         dest="shutdown_timeout", type="int",
1205
                         default=constants.DEFAULT_SHUTDOWN_TIMEOUT,
1206
                         help="Maximum time to wait for instance shutdown")
1207

    
1208
INTERVAL_OPT = cli_option("--interval", dest="interval", type="int",
1209
                          default=None,
1210
                          help=("Number of seconds between repetions of the"
1211
                                " command"))
1212

    
1213
EARLY_RELEASE_OPT = cli_option("--early-release",
1214
                               dest="early_release", default=False,
1215
                               action="store_true",
1216
                               help="Release the locks on the secondary"
1217
                               " node(s) early")
1218

    
1219
NEW_CLUSTER_CERT_OPT = cli_option("--new-cluster-certificate",
1220
                                  dest="new_cluster_cert",
1221
                                  default=False, action="store_true",
1222
                                  help="Generate a new cluster certificate")
1223

    
1224
RAPI_CERT_OPT = cli_option("--rapi-certificate", dest="rapi_cert",
1225
                           default=None,
1226
                           help="File containing new RAPI certificate")
1227

    
1228
NEW_RAPI_CERT_OPT = cli_option("--new-rapi-certificate", dest="new_rapi_cert",
1229
                               default=None, action="store_true",
1230
                               help=("Generate a new self-signed RAPI"
1231
                                     " certificate"))
1232

    
1233
SPICE_CERT_OPT = cli_option("--spice-certificate", dest="spice_cert",
1234
                           default=None,
1235
                           help="File containing new SPICE certificate")
1236

    
1237
SPICE_CACERT_OPT = cli_option("--spice-ca-certificate", dest="spice_cacert",
1238
                           default=None,
1239
                           help="File containing the certificate of the CA"
1240
                                " which signed the SPICE certificate")
1241

    
1242
NEW_SPICE_CERT_OPT = cli_option("--new-spice-certificate",
1243
                               dest="new_spice_cert", default=None,
1244
                               action="store_true",
1245
                               help=("Generate a new self-signed SPICE"
1246
                                     " certificate"))
1247

    
1248
NEW_CONFD_HMAC_KEY_OPT = cli_option("--new-confd-hmac-key",
1249
                                    dest="new_confd_hmac_key",
1250
                                    default=False, action="store_true",
1251
                                    help=("Create a new HMAC key for %s" %
1252
                                          constants.CONFD))
1253

    
1254
CLUSTER_DOMAIN_SECRET_OPT = cli_option("--cluster-domain-secret",
1255
                                       dest="cluster_domain_secret",
1256
                                       default=None,
1257
                                       help=("Load new new cluster domain"
1258
                                             " secret from file"))
1259

    
1260
NEW_CLUSTER_DOMAIN_SECRET_OPT = cli_option("--new-cluster-domain-secret",
1261
                                           dest="new_cluster_domain_secret",
1262
                                           default=False, action="store_true",
1263
                                           help=("Create a new cluster domain"
1264
                                                 " secret"))
1265

    
1266
USE_REPL_NET_OPT = cli_option("--use-replication-network",
1267
                              dest="use_replication_network",
1268
                              help="Whether to use the replication network"
1269
                              " for talking to the nodes",
1270
                              action="store_true", default=False)
1271

    
1272
MAINTAIN_NODE_HEALTH_OPT = \
1273
    cli_option("--maintain-node-health", dest="maintain_node_health",
1274
               metavar=_YORNO, default=None, type="bool",
1275
               help="Configure the cluster to automatically maintain node"
1276
               " health, by shutting down unknown instances, shutting down"
1277
               " unknown DRBD devices, etc.")
1278

    
1279
IDENTIFY_DEFAULTS_OPT = \
1280
    cli_option("--identify-defaults", dest="identify_defaults",
1281
               default=False, action="store_true",
1282
               help="Identify which saved instance parameters are equal to"
1283
               " the current cluster defaults and set them as such, instead"
1284
               " of marking them as overridden")
1285

    
1286
UIDPOOL_OPT = cli_option("--uid-pool", default=None,
1287
                         action="store", dest="uid_pool",
1288
                         help=("A list of user-ids or user-id"
1289
                               " ranges separated by commas"))
1290

    
1291
ADD_UIDS_OPT = cli_option("--add-uids", default=None,
1292
                          action="store", dest="add_uids",
1293
                          help=("A list of user-ids or user-id"
1294
                                " ranges separated by commas, to be"
1295
                                " added to the user-id pool"))
1296

    
1297
REMOVE_UIDS_OPT = cli_option("--remove-uids", default=None,
1298
                             action="store", dest="remove_uids",
1299
                             help=("A list of user-ids or user-id"
1300
                                   " ranges separated by commas, to be"
1301
                                   " removed from the user-id pool"))
1302

    
1303
RESERVED_LVS_OPT = cli_option("--reserved-lvs", default=None,
1304
                             action="store", dest="reserved_lvs",
1305
                             help=("A comma-separated list of reserved"
1306
                                   " logical volumes names, that will be"
1307
                                   " ignored by cluster verify"))
1308

    
1309
ROMAN_OPT = cli_option("--roman",
1310
                       dest="roman_integers", default=False,
1311
                       action="store_true",
1312
                       help="Use roman numbers for positive integers")
1313

    
1314
DRBD_HELPER_OPT = cli_option("--drbd-usermode-helper", dest="drbd_helper",
1315
                             action="store", default=None,
1316
                             help="Specifies usermode helper for DRBD")
1317

    
1318
NODRBD_STORAGE_OPT = cli_option("--no-drbd-storage", dest="drbd_storage",
1319
                                action="store_false", default=True,
1320
                                help="Disable support for DRBD")
1321

    
1322
PRIMARY_IP_VERSION_OPT = \
1323
    cli_option("--primary-ip-version", default=constants.IP4_VERSION,
1324
               action="store", dest="primary_ip_version",
1325
               metavar="%d|%d" % (constants.IP4_VERSION,
1326
                                  constants.IP6_VERSION),
1327
               help="Cluster-wide IP version for primary IP")
1328

    
1329
PRIORITY_OPT = cli_option("--priority", default=None, dest="priority",
1330
                          metavar="|".join(name for name, _ in _PRIORITY_NAMES),
1331
                          choices=_PRIONAME_TO_VALUE.keys(),
1332
                          help="Priority for opcode processing")
1333

    
1334
HID_OS_OPT = cli_option("--hidden", dest="hidden",
1335
                        type="bool", default=None, metavar=_YORNO,
1336
                        help="Sets the hidden flag on the OS")
1337

    
1338
BLK_OS_OPT = cli_option("--blacklisted", dest="blacklisted",
1339
                        type="bool", default=None, metavar=_YORNO,
1340
                        help="Sets the blacklisted flag on the OS")
1341

    
1342
PREALLOC_WIPE_DISKS_OPT = cli_option("--prealloc-wipe-disks", default=None,
1343
                                     type="bool", metavar=_YORNO,
1344
                                     dest="prealloc_wipe_disks",
1345
                                     help=("Wipe disks prior to instance"
1346
                                           " creation"))
1347

    
1348
NODE_PARAMS_OPT = cli_option("--node-parameters", dest="ndparams",
1349
                             type="keyval", default=None,
1350
                             help="Node parameters")
1351

    
1352
ALLOC_POLICY_OPT = cli_option("--alloc-policy", dest="alloc_policy",
1353
                              action="store", metavar="POLICY", default=None,
1354
                              help="Allocation policy for the node group")
1355

    
1356
NODE_POWERED_OPT = cli_option("--node-powered", default=None,
1357
                              type="bool", metavar=_YORNO,
1358
                              dest="node_powered",
1359
                              help="Specify if the SoR for node is powered")
1360

    
1361
OOB_TIMEOUT_OPT = cli_option("--oob-timeout", dest="oob_timeout", type="int",
1362
                         default=constants.OOB_TIMEOUT,
1363
                         help="Maximum time to wait for out-of-band helper")
1364

    
1365
POWER_DELAY_OPT = cli_option("--power-delay", dest="power_delay", type="float",
1366
                             default=constants.OOB_POWER_DELAY,
1367
                             help="Time in seconds to wait between power-ons")
1368

    
1369
FORCE_FILTER_OPT = cli_option("-F", "--filter", dest="force_filter",
1370
                              action="store_true", default=False,
1371
                              help=("Whether command argument should be treated"
1372
                                    " as filter"))
1373

    
1374
NO_REMEMBER_OPT = cli_option("--no-remember",
1375
                             dest="no_remember",
1376
                             action="store_true", default=False,
1377
                             help="Perform but do not record the change"
1378
                             " in the configuration")
1379

    
1380
PRIMARY_ONLY_OPT = cli_option("-p", "--primary-only",
1381
                              default=False, action="store_true",
1382
                              help="Evacuate primary instances only")
1383

    
1384
SECONDARY_ONLY_OPT = cli_option("-s", "--secondary-only",
1385
                                default=False, action="store_true",
1386
                                help="Evacuate secondary instances only"
1387
                                     " (applies only to internally mirrored"
1388
                                     " disk templates, e.g. %s)" %
1389
                                     utils.CommaJoin(constants.DTS_INT_MIRROR))
1390

    
1391
STARTUP_PAUSED_OPT = cli_option("--paused", dest="startup_paused",
1392
                                action="store_true", default=False,
1393
                                help="Pause instance at startup")
1394

    
1395
TO_GROUP_OPT = cli_option("--to", dest="to", metavar="<group>",
1396
                          help="Destination node group (name or uuid)",
1397
                          default=None, action="append",
1398
                          completion_suggest=OPT_COMPL_ONE_NODEGROUP)
1399

    
1400
IGNORE_ERRORS_OPT = cli_option("-I", "--ignore-errors", default=[],
1401
                               action="append", dest="ignore_errors",
1402
                               choices=list(constants.CV_ALL_ECODES_STRINGS),
1403
                               help="Error code to be ignored")
1404

    
1405
DISK_STATE_OPT = cli_option("--disk-state", default=[], dest="disk_state",
1406
                            action="append",
1407
                            help=("Specify disk state information in the format"
1408
                                  " storage_type/identifier:option=value,..."),
1409
                            type="identkeyval")
1410

    
1411
HV_STATE_OPT = cli_option("--hypervisor-state", default=[], dest="hv_state",
1412
                          action="append",
1413
                          help=("Specify hypervisor state information in the"
1414
                                " format hypervisor:option=value,..."),
1415
                          type="identkeyval")
1416

    
1417
IGNORE_IPOLICY_OPT = cli_option("--ignore-ipolicy", dest="ignore_ipolicy",
1418
                                action="store_true", default=False,
1419
                                help="Ignore instance policy violations")
1420

    
1421
RUNTIME_MEM_OPT = cli_option("-m", "--runtime-memory", dest="runtime_mem",
1422
                             help="Sets the instance's runtime memory,"
1423
                             " ballooning it up or down to the new value",
1424
                             default=None, type="unit", metavar="<size>")
1425

    
1426
ABSOLUTE_OPT = cli_option("--absolute", dest="absolute",
1427
                          action="store_true", default=False,
1428
                          help="Marks the grow as absolute instead of the"
1429
                          " (default) relative mode")
1430

    
1431
#: Options provided by all commands
1432
COMMON_OPTS = [DEBUG_OPT]
1433

    
1434
# common options for creating instances. add and import then add their own
1435
# specific ones.
1436
COMMON_CREATE_OPTS = [
1437
  BACKEND_OPT,
1438
  DISK_OPT,
1439
  DISK_TEMPLATE_OPT,
1440
  FILESTORE_DIR_OPT,
1441
  FILESTORE_DRIVER_OPT,
1442
  HYPERVISOR_OPT,
1443
  IALLOCATOR_OPT,
1444
  NET_OPT,
1445
  NODE_PLACEMENT_OPT,
1446
  NOIPCHECK_OPT,
1447
  NONAMECHECK_OPT,
1448
  NONICS_OPT,
1449
  NWSYNC_OPT,
1450
  OSPARAMS_OPT,
1451
  OS_SIZE_OPT,
1452
  SUBMIT_OPT,
1453
  TAG_ADD_OPT,
1454
  DRY_RUN_OPT,
1455
  PRIORITY_OPT,
1456
  ]
1457

    
1458
# common instance policy options
1459
INSTANCE_POLICY_OPTS = [
1460
  SPECS_CPU_COUNT_OPT,
1461
  SPECS_DISK_COUNT_OPT,
1462
  SPECS_DISK_SIZE_OPT,
1463
  SPECS_MEM_SIZE_OPT,
1464
  SPECS_NIC_COUNT_OPT,
1465
  IPOLICY_DISK_TEMPLATES,
1466
  IPOLICY_VCPU_RATIO,
1467
  IPOLICY_SPINDLE_RATIO,
1468
  ]
1469

    
1470

    
1471
def _ParseArgs(argv, commands, aliases, env_override):
1472
  """Parser for the command line arguments.
1473

1474
  This function parses the arguments and returns the function which
1475
  must be executed together with its (modified) arguments.
1476

1477
  @param argv: the command line
1478
  @param commands: dictionary with special contents, see the design
1479
      doc for cmdline handling
1480
  @param aliases: dictionary with command aliases {'alias': 'target, ...}
1481
  @param env_override: list of env variables allowed for default args
1482

1483
  """
1484
  assert not (env_override - set(commands))
1485

    
1486
  if len(argv) == 0:
1487
    binary = "<command>"
1488
  else:
1489
    binary = argv[0].split("/")[-1]
1490

    
1491
  if len(argv) > 1 and argv[1] == "--version":
1492
    ToStdout("%s (ganeti %s) %s", binary, constants.VCS_VERSION,
1493
             constants.RELEASE_VERSION)
1494
    # Quit right away. That way we don't have to care about this special
1495
    # argument. optparse.py does it the same.
1496
    sys.exit(0)
1497

    
1498
  if len(argv) < 2 or not (argv[1] in commands or
1499
                           argv[1] in aliases):
1500
    # let's do a nice thing
1501
    sortedcmds = commands.keys()
1502
    sortedcmds.sort()
1503

    
1504
    ToStdout("Usage: %s {command} [options...] [argument...]", binary)
1505
    ToStdout("%s <command> --help to see details, or man %s", binary, binary)
1506
    ToStdout("")
1507

    
1508
    # compute the max line length for cmd + usage
1509
    mlen = max([len(" %s" % cmd) for cmd in commands])
1510
    mlen = min(60, mlen) # should not get here...
1511

    
1512
    # and format a nice command list
1513
    ToStdout("Commands:")
1514
    for cmd in sortedcmds:
1515
      cmdstr = " %s" % (cmd,)
1516
      help_text = commands[cmd][4]
1517
      help_lines = textwrap.wrap(help_text, 79 - 3 - mlen)
1518
      ToStdout("%-*s - %s", mlen, cmdstr, help_lines.pop(0))
1519
      for line in help_lines:
1520
        ToStdout("%-*s   %s", mlen, "", line)
1521

    
1522
    ToStdout("")
1523

    
1524
    return None, None, None
1525

    
1526
  # get command, unalias it, and look it up in commands
1527
  cmd = argv.pop(1)
1528
  if cmd in aliases:
1529
    if cmd in commands:
1530
      raise errors.ProgrammerError("Alias '%s' overrides an existing"
1531
                                   " command" % cmd)
1532

    
1533
    if aliases[cmd] not in commands:
1534
      raise errors.ProgrammerError("Alias '%s' maps to non-existing"
1535
                                   " command '%s'" % (cmd, aliases[cmd]))
1536

    
1537
    cmd = aliases[cmd]
1538

    
1539
  if cmd in env_override:
1540
    args_env_name = ("%s_%s" % (binary.replace("-", "_"), cmd)).upper()
1541
    env_args = os.environ.get(args_env_name)
1542
    if env_args:
1543
      argv = utils.InsertAtPos(argv, 1, shlex.split(env_args))
1544

    
1545
  func, args_def, parser_opts, usage, description = commands[cmd]
1546
  parser = OptionParser(option_list=parser_opts + COMMON_OPTS,
1547
                        description=description,
1548
                        formatter=TitledHelpFormatter(),
1549
                        usage="%%prog %s %s" % (cmd, usage))
1550
  parser.disable_interspersed_args()
1551
  options, args = parser.parse_args(args=argv[1:])
1552

    
1553
  if not _CheckArguments(cmd, args_def, args):
1554
    return None, None, None
1555

    
1556
  return func, options, args
1557

    
1558

    
1559
def _CheckArguments(cmd, args_def, args):
1560
  """Verifies the arguments using the argument definition.
1561

1562
  Algorithm:
1563

1564
    1. Abort with error if values specified by user but none expected.
1565

1566
    1. For each argument in definition
1567

1568
      1. Keep running count of minimum number of values (min_count)
1569
      1. Keep running count of maximum number of values (max_count)
1570
      1. If it has an unlimited number of values
1571

1572
        1. Abort with error if it's not the last argument in the definition
1573

1574
    1. If last argument has limited number of values
1575

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

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

1580
  """
1581
  if args and not args_def:
1582
    ToStderr("Error: Command %s expects no arguments", cmd)
1583
    return False
1584

    
1585
  min_count = None
1586
  max_count = None
1587
  check_max = None
1588

    
1589
  last_idx = len(args_def) - 1
1590

    
1591
  for idx, arg in enumerate(args_def):
1592
    if min_count is None:
1593
      min_count = arg.min
1594
    elif arg.min is not None:
1595
      min_count += arg.min
1596

    
1597
    if max_count is None:
1598
      max_count = arg.max
1599
    elif arg.max is not None:
1600
      max_count += arg.max
1601

    
1602
    if idx == last_idx:
1603
      check_max = (arg.max is not None)
1604

    
1605
    elif arg.max is None:
1606
      raise errors.ProgrammerError("Only the last argument can have max=None")
1607

    
1608
  if check_max:
1609
    # Command with exact number of arguments
1610
    if (min_count is not None and max_count is not None and
1611
        min_count == max_count and len(args) != min_count):
1612
      ToStderr("Error: Command %s expects %d argument(s)", cmd, min_count)
1613
      return False
1614

    
1615
    # Command with limited number of arguments
1616
    if max_count is not None and len(args) > max_count:
1617
      ToStderr("Error: Command %s expects only %d argument(s)",
1618
               cmd, max_count)
1619
      return False
1620

    
1621
  # Command with some required arguments
1622
  if min_count is not None and len(args) < min_count:
1623
    ToStderr("Error: Command %s expects at least %d argument(s)",
1624
             cmd, min_count)
1625
    return False
1626

    
1627
  return True
1628

    
1629

    
1630
def SplitNodeOption(value):
1631
  """Splits the value of a --node option.
1632

1633
  """
1634
  if value and ":" in value:
1635
    return value.split(":", 1)
1636
  else:
1637
    return (value, None)
1638

    
1639

    
1640
def CalculateOSNames(os_name, os_variants):
1641
  """Calculates all the names an OS can be called, according to its variants.
1642

1643
  @type os_name: string
1644
  @param os_name: base name of the os
1645
  @type os_variants: list or None
1646
  @param os_variants: list of supported variants
1647
  @rtype: list
1648
  @return: list of valid names
1649

1650
  """
1651
  if os_variants:
1652
    return ["%s+%s" % (os_name, v) for v in os_variants]
1653
  else:
1654
    return [os_name]
1655

    
1656

    
1657
def ParseFields(selected, default):
1658
  """Parses the values of "--field"-like options.
1659

1660
  @type selected: string or None
1661
  @param selected: User-selected options
1662
  @type default: list
1663
  @param default: Default fields
1664

1665
  """
1666
  if selected is None:
1667
    return default
1668

    
1669
  if selected.startswith("+"):
1670
    return default + selected[1:].split(",")
1671

    
1672
  return selected.split(",")
1673

    
1674

    
1675
UsesRPC = rpc.RunWithRPC
1676

    
1677

    
1678
def AskUser(text, choices=None):
1679
  """Ask the user a question.
1680

1681
  @param text: the question to ask
1682

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

1688
  @return: one of the return values from the choices list; if input is
1689
      not possible (i.e. not running with a tty, we return the last
1690
      entry from the list
1691

1692
  """
1693
  if choices is None:
1694
    choices = [("y", True, "Perform the operation"),
1695
               ("n", False, "Do not perform the operation")]
1696
  if not choices or not isinstance(choices, list):
1697
    raise errors.ProgrammerError("Invalid choices argument to AskUser")
1698
  for entry in choices:
1699
    if not isinstance(entry, tuple) or len(entry) < 3 or entry[0] == "?":
1700
      raise errors.ProgrammerError("Invalid choices element to AskUser")
1701

    
1702
  answer = choices[-1][1]
1703
  new_text = []
1704
  for line in text.splitlines():
1705
    new_text.append(textwrap.fill(line, 70, replace_whitespace=False))
1706
  text = "\n".join(new_text)
1707
  try:
1708
    f = file("/dev/tty", "a+")
1709
  except IOError:
1710
    return answer
1711
  try:
1712
    chars = [entry[0] for entry in choices]
1713
    chars[-1] = "[%s]" % chars[-1]
1714
    chars.append("?")
1715
    maps = dict([(entry[0], entry[1]) for entry in choices])
1716
    while True:
1717
      f.write(text)
1718
      f.write("\n")
1719
      f.write("/".join(chars))
1720
      f.write(": ")
1721
      line = f.readline(2).strip().lower()
1722
      if line in maps:
1723
        answer = maps[line]
1724
        break
1725
      elif line == "?":
1726
        for entry in choices:
1727
          f.write(" %s - %s\n" % (entry[0], entry[2]))
1728
        f.write("\n")
1729
        continue
1730
  finally:
1731
    f.close()
1732
  return answer
1733

    
1734

    
1735
class JobSubmittedException(Exception):
1736
  """Job was submitted, client should exit.
1737

1738
  This exception has one argument, the ID of the job that was
1739
  submitted. The handler should print this ID.
1740

1741
  This is not an error, just a structured way to exit from clients.
1742

1743
  """
1744

    
1745

    
1746
def SendJob(ops, cl=None):
1747
  """Function to submit an opcode without waiting for the results.
1748

1749
  @type ops: list
1750
  @param ops: list of opcodes
1751
  @type cl: luxi.Client
1752
  @param cl: the luxi client to use for communicating with the master;
1753
             if None, a new client will be created
1754

1755
  """
1756
  if cl is None:
1757
    cl = GetClient()
1758

    
1759
  job_id = cl.SubmitJob(ops)
1760

    
1761
  return job_id
1762

    
1763

    
1764
def GenericPollJob(job_id, cbs, report_cbs):
1765
  """Generic job-polling function.
1766

1767
  @type job_id: number
1768
  @param job_id: Job ID
1769
  @type cbs: Instance of L{JobPollCbBase}
1770
  @param cbs: Data callbacks
1771
  @type report_cbs: Instance of L{JobPollReportCbBase}
1772
  @param report_cbs: Reporting callbacks
1773

1774
  """
1775
  prev_job_info = None
1776
  prev_logmsg_serial = None
1777

    
1778
  status = None
1779

    
1780
  while True:
1781
    result = cbs.WaitForJobChangeOnce(job_id, ["status"], prev_job_info,
1782
                                      prev_logmsg_serial)
1783
    if not result:
1784
      # job not found, go away!
1785
      raise errors.JobLost("Job with id %s lost" % job_id)
1786

    
1787
    if result == constants.JOB_NOTCHANGED:
1788
      report_cbs.ReportNotChanged(job_id, status)
1789

    
1790
      # Wait again
1791
      continue
1792

    
1793
    # Split result, a tuple of (field values, log entries)
1794
    (job_info, log_entries) = result
1795
    (status, ) = job_info
1796

    
1797
    if log_entries:
1798
      for log_entry in log_entries:
1799
        (serial, timestamp, log_type, message) = log_entry
1800
        report_cbs.ReportLogMessage(job_id, serial, timestamp,
1801
                                    log_type, message)
1802
        prev_logmsg_serial = max(prev_logmsg_serial, serial)
1803

    
1804
    # TODO: Handle canceled and archived jobs
1805
    elif status in (constants.JOB_STATUS_SUCCESS,
1806
                    constants.JOB_STATUS_ERROR,
1807
                    constants.JOB_STATUS_CANCELING,
1808
                    constants.JOB_STATUS_CANCELED):
1809
      break
1810

    
1811
    prev_job_info = job_info
1812

    
1813
  jobs = cbs.QueryJobs([job_id], ["status", "opstatus", "opresult"])
1814
  if not jobs:
1815
    raise errors.JobLost("Job with id %s lost" % job_id)
1816

    
1817
  status, opstatus, result = jobs[0]
1818

    
1819
  if status == constants.JOB_STATUS_SUCCESS:
1820
    return result
1821

    
1822
  if status in (constants.JOB_STATUS_CANCELING, constants.JOB_STATUS_CANCELED):
1823
    raise errors.OpExecError("Job was canceled")
1824

    
1825
  has_ok = False
1826
  for idx, (status, msg) in enumerate(zip(opstatus, result)):
1827
    if status == constants.OP_STATUS_SUCCESS:
1828
      has_ok = True
1829
    elif status == constants.OP_STATUS_ERROR:
1830
      errors.MaybeRaise(msg)
1831

    
1832
      if has_ok:
1833
        raise errors.OpExecError("partial failure (opcode %d): %s" %
1834
                                 (idx, msg))
1835

    
1836
      raise errors.OpExecError(str(msg))
1837

    
1838
  # default failure mode
1839
  raise errors.OpExecError(result)
1840

    
1841

    
1842
class JobPollCbBase:
1843
  """Base class for L{GenericPollJob} callbacks.
1844

1845
  """
1846
  def __init__(self):
1847
    """Initializes this class.
1848

1849
    """
1850

    
1851
  def WaitForJobChangeOnce(self, job_id, fields,
1852
                           prev_job_info, prev_log_serial):
1853
    """Waits for changes on a job.
1854

1855
    """
1856
    raise NotImplementedError()
1857

    
1858
  def QueryJobs(self, job_ids, fields):
1859
    """Returns the selected fields for the selected job IDs.
1860

1861
    @type job_ids: list of numbers
1862
    @param job_ids: Job IDs
1863
    @type fields: list of strings
1864
    @param fields: Fields
1865

1866
    """
1867
    raise NotImplementedError()
1868

    
1869

    
1870
class JobPollReportCbBase:
1871
  """Base class for L{GenericPollJob} reporting callbacks.
1872

1873
  """
1874
  def __init__(self):
1875
    """Initializes this class.
1876

1877
    """
1878

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

1882
    """
1883
    raise NotImplementedError()
1884

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

1888
    @type job_id: number
1889
    @param job_id: Job ID
1890
    @type status: string or None
1891
    @param status: Job status if available
1892

1893
    """
1894
    raise NotImplementedError()
1895

    
1896

    
1897
class _LuxiJobPollCb(JobPollCbBase):
1898
  def __init__(self, cl):
1899
    """Initializes this class.
1900

1901
    """
1902
    JobPollCbBase.__init__(self)
1903
    self.cl = cl
1904

    
1905
  def WaitForJobChangeOnce(self, job_id, fields,
1906
                           prev_job_info, prev_log_serial):
1907
    """Waits for changes on a job.
1908

1909
    """
1910
    return self.cl.WaitForJobChangeOnce(job_id, fields,
1911
                                        prev_job_info, prev_log_serial)
1912

    
1913
  def QueryJobs(self, job_ids, fields):
1914
    """Returns the selected fields for the selected job IDs.
1915

1916
    """
1917
    return self.cl.QueryJobs(job_ids, fields)
1918

    
1919

    
1920
class FeedbackFnJobPollReportCb(JobPollReportCbBase):
1921
  def __init__(self, feedback_fn):
1922
    """Initializes this class.
1923

1924
    """
1925
    JobPollReportCbBase.__init__(self)
1926

    
1927
    self.feedback_fn = feedback_fn
1928

    
1929
    assert callable(feedback_fn)
1930

    
1931
  def ReportLogMessage(self, job_id, serial, timestamp, log_type, log_msg):
1932
    """Handles a log message.
1933

1934
    """
1935
    self.feedback_fn((timestamp, log_type, log_msg))
1936

    
1937
  def ReportNotChanged(self, job_id, status):
1938
    """Called if a job hasn't changed in a while.
1939

1940
    """
1941
    # Ignore
1942

    
1943

    
1944
class StdioJobPollReportCb(JobPollReportCbBase):
1945
  def __init__(self):
1946
    """Initializes this class.
1947

1948
    """
1949
    JobPollReportCbBase.__init__(self)
1950

    
1951
    self.notified_queued = False
1952
    self.notified_waitlock = False
1953

    
1954
  def ReportLogMessage(self, job_id, serial, timestamp, log_type, log_msg):
1955
    """Handles a log message.
1956

1957
    """
1958
    ToStdout("%s %s", time.ctime(utils.MergeTime(timestamp)),
1959
             FormatLogMessage(log_type, log_msg))
1960

    
1961
  def ReportNotChanged(self, job_id, status):
1962
    """Called if a job hasn't changed in a while.
1963

1964
    """
1965
    if status is None:
1966
      return
1967

    
1968
    if status == constants.JOB_STATUS_QUEUED and not self.notified_queued:
1969
      ToStderr("Job %s is waiting in queue", job_id)
1970
      self.notified_queued = True
1971

    
1972
    elif status == constants.JOB_STATUS_WAITING and not self.notified_waitlock:
1973
      ToStderr("Job %s is trying to acquire all necessary locks", job_id)
1974
      self.notified_waitlock = True
1975

    
1976

    
1977
def FormatLogMessage(log_type, log_msg):
1978
  """Formats a job message according to its type.
1979

1980
  """
1981
  if log_type != constants.ELOG_MESSAGE:
1982
    log_msg = str(log_msg)
1983

    
1984
  return utils.SafeEncode(log_msg)
1985

    
1986

    
1987
def PollJob(job_id, cl=None, feedback_fn=None, reporter=None):
1988
  """Function to poll for the result of a job.
1989

1990
  @type job_id: job identified
1991
  @param job_id: the job to poll for results
1992
  @type cl: luxi.Client
1993
  @param cl: the luxi client to use for communicating with the master;
1994
             if None, a new client will be created
1995

1996
  """
1997
  if cl is None:
1998
    cl = GetClient()
1999

    
2000
  if reporter is None:
2001
    if feedback_fn:
2002
      reporter = FeedbackFnJobPollReportCb(feedback_fn)
2003
    else:
2004
      reporter = StdioJobPollReportCb()
2005
  elif feedback_fn:
2006
    raise errors.ProgrammerError("Can't specify reporter and feedback function")
2007

    
2008
  return GenericPollJob(job_id, _LuxiJobPollCb(cl), reporter)
2009

    
2010

    
2011
def SubmitOpCode(op, cl=None, feedback_fn=None, opts=None, reporter=None):
2012
  """Legacy function to submit an opcode.
2013

2014
  This is just a simple wrapper over the construction of the processor
2015
  instance. It should be extended to better handle feedback and
2016
  interaction functions.
2017

2018
  """
2019
  if cl is None:
2020
    cl = GetClient()
2021

    
2022
  SetGenericOpcodeOpts([op], opts)
2023

    
2024
  job_id = SendJob([op], cl=cl)
2025

    
2026
  op_results = PollJob(job_id, cl=cl, feedback_fn=feedback_fn,
2027
                       reporter=reporter)
2028

    
2029
  return op_results[0]
2030

    
2031

    
2032
def SubmitOrSend(op, opts, cl=None, feedback_fn=None):
2033
  """Wrapper around SubmitOpCode or SendJob.
2034

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

2040
  It will also process the opcodes if we're sending the via SendJob
2041
  (otherwise SubmitOpCode does it).
2042

2043
  """
2044
  if opts and opts.submit_only:
2045
    job = [op]
2046
    SetGenericOpcodeOpts(job, opts)
2047
    job_id = SendJob(job, cl=cl)
2048
    raise JobSubmittedException(job_id)
2049
  else:
2050
    return SubmitOpCode(op, cl=cl, feedback_fn=feedback_fn, opts=opts)
2051

    
2052

    
2053
def SetGenericOpcodeOpts(opcode_list, options):
2054
  """Processor for generic options.
2055

2056
  This function updates the given opcodes based on generic command
2057
  line options (like debug, dry-run, etc.).
2058

2059
  @param opcode_list: list of opcodes
2060
  @param options: command line options or None
2061
  @return: None (in-place modification)
2062

2063
  """
2064
  if not options:
2065
    return
2066
  for op in opcode_list:
2067
    op.debug_level = options.debug
2068
    if hasattr(options, "dry_run"):
2069
      op.dry_run = options.dry_run
2070
    if getattr(options, "priority", None) is not None:
2071
      op.priority = _PRIONAME_TO_VALUE[options.priority]
2072

    
2073

    
2074
def GetClient():
2075
  # TODO: Cache object?
2076
  try:
2077
    client = luxi.Client()
2078
  except luxi.NoMasterError:
2079
    ss = ssconf.SimpleStore()
2080

    
2081
    # Try to read ssconf file
2082
    try:
2083
      ss.GetMasterNode()
2084
    except errors.ConfigurationError:
2085
      raise errors.OpPrereqError("Cluster not initialized or this machine is"
2086
                                 " not part of a cluster")
2087

    
2088
    master, myself = ssconf.GetMasterAndMyself(ss=ss)
2089
    if master != myself:
2090
      raise errors.OpPrereqError("This is not the master node, please connect"
2091
                                 " to node '%s' and rerun the command" %
2092
                                 master)
2093
    raise
2094
  return client
2095

    
2096

    
2097
def FormatError(err):
2098
  """Return a formatted error message for a given error.
2099

2100
  This function takes an exception instance and returns a tuple
2101
  consisting of two values: first, the recommended exit code, and
2102
  second, a string describing the error message (not
2103
  newline-terminated).
2104

2105
  """
2106
  retcode = 1
2107
  obuf = StringIO()
2108
  msg = str(err)
2109
  if isinstance(err, errors.ConfigurationError):
2110
    txt = "Corrupt configuration file: %s" % msg
2111
    logging.error(txt)
2112
    obuf.write(txt + "\n")
2113
    obuf.write("Aborting.")
2114
    retcode = 2
2115
  elif isinstance(err, errors.HooksAbort):
2116
    obuf.write("Failure: hooks execution failed:\n")
2117
    for node, script, out in err.args[0]:
2118
      if out:
2119
        obuf.write("  node: %s, script: %s, output: %s\n" %
2120
                   (node, script, out))
2121
      else:
2122
        obuf.write("  node: %s, script: %s (no output)\n" %
2123
                   (node, script))
2124
  elif isinstance(err, errors.HooksFailure):
2125
    obuf.write("Failure: hooks general failure: %s" % msg)
2126
  elif isinstance(err, errors.ResolverError):
2127
    this_host = netutils.Hostname.GetSysName()
2128
    if err.args[0] == this_host:
2129
      msg = "Failure: can't resolve my own hostname ('%s')"
2130
    else:
2131
      msg = "Failure: can't resolve hostname '%s'"
2132
    obuf.write(msg % err.args[0])
2133
  elif isinstance(err, errors.OpPrereqError):
2134
    if len(err.args) == 2:
2135
      obuf.write("Failure: prerequisites not met for this"
2136
               " operation:\nerror type: %s, error details:\n%s" %
2137
                 (err.args[1], err.args[0]))
2138
    else:
2139
      obuf.write("Failure: prerequisites not met for this"
2140
                 " operation:\n%s" % msg)
2141
  elif isinstance(err, errors.OpExecError):
2142
    obuf.write("Failure: command execution error:\n%s" % msg)
2143
  elif isinstance(err, errors.TagError):
2144
    obuf.write("Failure: invalid tag(s) given:\n%s" % msg)
2145
  elif isinstance(err, errors.JobQueueDrainError):
2146
    obuf.write("Failure: the job queue is marked for drain and doesn't"
2147
               " accept new requests\n")
2148
  elif isinstance(err, errors.JobQueueFull):
2149
    obuf.write("Failure: the job queue is full and doesn't accept new"
2150
               " job submissions until old jobs are archived\n")
2151
  elif isinstance(err, errors.TypeEnforcementError):
2152
    obuf.write("Parameter Error: %s" % msg)
2153
  elif isinstance(err, errors.ParameterError):
2154
    obuf.write("Failure: unknown/wrong parameter name '%s'" % msg)
2155
  elif isinstance(err, luxi.NoMasterError):
2156
    obuf.write("Cannot communicate with the master daemon.\nIs it running"
2157
               " and listening for connections?")
2158
  elif isinstance(err, luxi.TimeoutError):
2159
    obuf.write("Timeout while talking to the master daemon. Jobs might have"
2160
               " been submitted and will continue to run even if the call"
2161
               " timed out. Useful commands in this situation are \"gnt-job"
2162
               " list\", \"gnt-job cancel\" and \"gnt-job watch\". Error:\n")
2163
    obuf.write(msg)
2164
  elif isinstance(err, luxi.PermissionError):
2165
    obuf.write("It seems you don't have permissions to connect to the"
2166
               " master daemon.\nPlease retry as a different user.")
2167
  elif isinstance(err, luxi.ProtocolError):
2168
    obuf.write("Unhandled protocol error while talking to the master daemon:\n"
2169
               "%s" % msg)
2170
  elif isinstance(err, errors.JobLost):
2171
    obuf.write("Error checking job status: %s" % msg)
2172
  elif isinstance(err, errors.QueryFilterParseError):
2173
    obuf.write("Error while parsing query filter: %s\n" % err.args[0])
2174
    obuf.write("\n".join(err.GetDetails()))
2175
  elif isinstance(err, errors.GenericError):
2176
    obuf.write("Unhandled Ganeti error: %s" % msg)
2177
  elif isinstance(err, JobSubmittedException):
2178
    obuf.write("JobID: %s\n" % err.args[0])
2179
    retcode = 0
2180
  else:
2181
    obuf.write("Unhandled exception: %s" % msg)
2182
  return retcode, obuf.getvalue().rstrip("\n")
2183

    
2184

    
2185
def GenericMain(commands, override=None, aliases=None,
2186
                env_override=frozenset()):
2187
  """Generic main function for all the gnt-* commands.
2188

2189
  @param commands: a dictionary with a special structure, see the design doc
2190
                   for command line handling.
2191
  @param override: if not None, we expect a dictionary with keys that will
2192
                   override command line options; this can be used to pass
2193
                   options from the scripts to generic functions
2194
  @param aliases: dictionary with command aliases {'alias': 'target, ...}
2195
  @param env_override: list of environment names which are allowed to submit
2196
                       default args for commands
2197

2198
  """
2199
  # save the program name and the entire command line for later logging
2200
  if sys.argv:
2201
    binary = os.path.basename(sys.argv[0])
2202
    if not binary:
2203
      binary = sys.argv[0]
2204

    
2205
    if len(sys.argv) >= 2:
2206
      logname = utils.ShellQuoteArgs([binary, sys.argv[1]])
2207
    else:
2208
      logname = binary
2209

    
2210
    cmdline = utils.ShellQuoteArgs([binary] + sys.argv[1:])
2211
  else:
2212
    binary = "<unknown program>"
2213
    cmdline = "<unknown>"
2214

    
2215
  if aliases is None:
2216
    aliases = {}
2217

    
2218
  try:
2219
    func, options, args = _ParseArgs(sys.argv, commands, aliases, env_override)
2220
  except errors.ParameterError, err:
2221
    result, err_msg = FormatError(err)
2222
    ToStderr(err_msg)
2223
    return 1
2224

    
2225
  if func is None: # parse error
2226
    return 1
2227

    
2228
  if override is not None:
2229
    for key, val in override.iteritems():
2230
      setattr(options, key, val)
2231

    
2232
  utils.SetupLogging(constants.LOG_COMMANDS, logname, debug=options.debug,
2233
                     stderr_logging=True)
2234

    
2235
  logging.info("Command line: %s", cmdline)
2236

    
2237
  try:
2238
    result = func(options, args)
2239
  except (errors.GenericError, luxi.ProtocolError,
2240
          JobSubmittedException), err:
2241
    result, err_msg = FormatError(err)
2242
    logging.exception("Error during command processing")
2243
    ToStderr(err_msg)
2244
  except KeyboardInterrupt:
2245
    result = constants.EXIT_FAILURE
2246
    ToStderr("Aborted. Note that if the operation created any jobs, they"
2247
             " might have been submitted and"
2248
             " will continue to run in the background.")
2249
  except IOError, err:
2250
    if err.errno == errno.EPIPE:
2251
      # our terminal went away, we'll exit
2252
      sys.exit(constants.EXIT_FAILURE)
2253
    else:
2254
      raise
2255

    
2256
  return result
2257

    
2258

    
2259
def ParseNicOption(optvalue):
2260
  """Parses the value of the --net option(s).
2261

2262
  """
2263
  try:
2264
    nic_max = max(int(nidx[0]) + 1 for nidx in optvalue)
2265
  except (TypeError, ValueError), err:
2266
    raise errors.OpPrereqError("Invalid NIC index passed: %s" % str(err))
2267

    
2268
  nics = [{}] * nic_max
2269
  for nidx, ndict in optvalue:
2270
    nidx = int(nidx)
2271

    
2272
    if not isinstance(ndict, dict):
2273
      raise errors.OpPrereqError("Invalid nic/%d value: expected dict,"
2274
                                 " got %s" % (nidx, ndict))
2275

    
2276
    utils.ForceDictType(ndict, constants.INIC_PARAMS_TYPES)
2277

    
2278
    nics[nidx] = ndict
2279

    
2280
  return nics
2281

    
2282

    
2283
def GenericInstanceCreate(mode, opts, args):
2284
  """Add an instance to the cluster via either creation or import.
2285

2286
  @param mode: constants.INSTANCE_CREATE or constants.INSTANCE_IMPORT
2287
  @param opts: the command line options selected by the user
2288
  @type args: list
2289
  @param args: should contain only one element, the new instance name
2290
  @rtype: int
2291
  @return: the desired exit code
2292

2293
  """
2294
  instance = args[0]
2295

    
2296
  (pnode, snode) = SplitNodeOption(opts.node)
2297

    
2298
  hypervisor = None
2299
  hvparams = {}
2300
  if opts.hypervisor:
2301
    hypervisor, hvparams = opts.hypervisor
2302

    
2303
  if opts.nics:
2304
    nics = ParseNicOption(opts.nics)
2305
  elif opts.no_nics:
2306
    # no nics
2307
    nics = []
2308
  elif mode == constants.INSTANCE_CREATE:
2309
    # default of one nic, all auto
2310
    nics = [{}]
2311
  else:
2312
    # mode == import
2313
    nics = []
2314

    
2315
  if opts.disk_template == constants.DT_DISKLESS:
2316
    if opts.disks or opts.sd_size is not None:
2317
      raise errors.OpPrereqError("Diskless instance but disk"
2318
                                 " information passed")
2319
    disks = []
2320
  else:
2321
    if (not opts.disks and not opts.sd_size
2322
        and mode == constants.INSTANCE_CREATE):
2323
      raise errors.OpPrereqError("No disk information specified")
2324
    if opts.disks and opts.sd_size is not None:
2325
      raise errors.OpPrereqError("Please use either the '--disk' or"
2326
                                 " '-s' option")
2327
    if opts.sd_size is not None:
2328
      opts.disks = [(0, {constants.IDISK_SIZE: opts.sd_size})]
2329

    
2330
    if opts.disks:
2331
      try:
2332
        disk_max = max(int(didx[0]) + 1 for didx in opts.disks)
2333
      except ValueError, err:
2334
        raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err))
2335
      disks = [{}] * disk_max
2336
    else:
2337
      disks = []
2338
    for didx, ddict in opts.disks:
2339
      didx = int(didx)
2340
      if not isinstance(ddict, dict):
2341
        msg = "Invalid disk/%d value: expected dict, got %s" % (didx, ddict)
2342
        raise errors.OpPrereqError(msg)
2343
      elif constants.IDISK_SIZE in ddict:
2344
        if constants.IDISK_ADOPT in ddict:
2345
          raise errors.OpPrereqError("Only one of 'size' and 'adopt' allowed"
2346
                                     " (disk %d)" % didx)
2347
        try:
2348
          ddict[constants.IDISK_SIZE] = \
2349
            utils.ParseUnit(ddict[constants.IDISK_SIZE])
2350
        except ValueError, err:
2351
          raise errors.OpPrereqError("Invalid disk size for disk %d: %s" %
2352
                                     (didx, err))
2353
      elif constants.IDISK_ADOPT in ddict:
2354
        if mode == constants.INSTANCE_IMPORT:
2355
          raise errors.OpPrereqError("Disk adoption not allowed for instance"
2356
                                     " import")
2357
        ddict[constants.IDISK_SIZE] = 0
2358
      else:
2359
        raise errors.OpPrereqError("Missing size or adoption source for"
2360
                                   " disk %d" % didx)
2361
      disks[didx] = ddict
2362

    
2363
  if opts.tags is not None:
2364
    tags = opts.tags.split(",")
2365
  else:
2366
    tags = []
2367

    
2368
  utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_COMPAT)
2369
  utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
2370

    
2371
  if mode == constants.INSTANCE_CREATE:
2372
    start = opts.start
2373
    os_type = opts.os
2374
    force_variant = opts.force_variant
2375
    src_node = None
2376
    src_path = None
2377
    no_install = opts.no_install
2378
    identify_defaults = False
2379
  elif mode == constants.INSTANCE_IMPORT:
2380
    start = False
2381
    os_type = None
2382
    force_variant = False
2383
    src_node = opts.src_node
2384
    src_path = opts.src_dir
2385
    no_install = None
2386
    identify_defaults = opts.identify_defaults
2387
  else:
2388
    raise errors.ProgrammerError("Invalid creation mode %s" % mode)
2389

    
2390
  op = opcodes.OpInstanceCreate(instance_name=instance,
2391
                                disks=disks,
2392
                                disk_template=opts.disk_template,
2393
                                nics=nics,
2394
                                pnode=pnode, snode=snode,
2395
                                ip_check=opts.ip_check,
2396
                                name_check=opts.name_check,
2397
                                wait_for_sync=opts.wait_for_sync,
2398
                                file_storage_dir=opts.file_storage_dir,
2399
                                file_driver=opts.file_driver,
2400
                                iallocator=opts.iallocator,
2401
                                hypervisor=hypervisor,
2402
                                hvparams=hvparams,
2403
                                beparams=opts.beparams,
2404
                                osparams=opts.osparams,
2405
                                mode=mode,
2406
                                start=start,
2407
                                os_type=os_type,
2408
                                force_variant=force_variant,
2409
                                src_node=src_node,
2410
                                src_path=src_path,
2411
                                tags=tags,
2412
                                no_install=no_install,
2413
                                identify_defaults=identify_defaults,
2414
                                ignore_ipolicy=opts.ignore_ipolicy)
2415

    
2416
  SubmitOrSend(op, opts)
2417
  return 0
2418

    
2419

    
2420
class _RunWhileClusterStoppedHelper:
2421
  """Helper class for L{RunWhileClusterStopped} to simplify state management
2422

2423
  """
2424
  def __init__(self, feedback_fn, cluster_name, master_node, online_nodes):
2425
    """Initializes this class.
2426

2427
    @type feedback_fn: callable
2428
    @param feedback_fn: Feedback function
2429
    @type cluster_name: string
2430
    @param cluster_name: Cluster name
2431
    @type master_node: string
2432
    @param master_node Master node name
2433
    @type online_nodes: list
2434
    @param online_nodes: List of names of online nodes
2435

2436
    """
2437
    self.feedback_fn = feedback_fn
2438
    self.cluster_name = cluster_name
2439
    self.master_node = master_node
2440
    self.online_nodes = online_nodes
2441

    
2442
    self.ssh = ssh.SshRunner(self.cluster_name)
2443

    
2444
    self.nonmaster_nodes = [name for name in online_nodes
2445
                            if name != master_node]
2446

    
2447
    assert self.master_node not in self.nonmaster_nodes
2448

    
2449
  def _RunCmd(self, node_name, cmd):
2450
    """Runs a command on the local or a remote machine.
2451

2452
    @type node_name: string
2453
    @param node_name: Machine name
2454
    @type cmd: list
2455
    @param cmd: Command
2456

2457
    """
2458
    if node_name is None or node_name == self.master_node:
2459
      # No need to use SSH
2460
      result = utils.RunCmd(cmd)
2461
    else:
2462
      result = self.ssh.Run(node_name, "root", utils.ShellQuoteArgs(cmd))
2463

    
2464
    if result.failed:
2465
      errmsg = ["Failed to run command %s" % result.cmd]
2466
      if node_name:
2467
        errmsg.append("on node %s" % node_name)
2468
      errmsg.append(": exitcode %s and error %s" %
2469
                    (result.exit_code, result.output))
2470
      raise errors.OpExecError(" ".join(errmsg))
2471

    
2472
  def Call(self, fn, *args):
2473
    """Call function while all daemons are stopped.
2474

2475
    @type fn: callable
2476
    @param fn: Function to be called
2477

2478
    """
2479
    # Pause watcher by acquiring an exclusive lock on watcher state file
2480
    self.feedback_fn("Blocking watcher")
2481
    watcher_block = utils.FileLock.Open(constants.WATCHER_LOCK_FILE)
2482
    try:
2483
      # TODO: Currently, this just blocks. There's no timeout.
2484
      # TODO: Should it be a shared lock?
2485
      watcher_block.Exclusive(blocking=True)
2486

    
2487
      # Stop master daemons, so that no new jobs can come in and all running
2488
      # ones are finished
2489
      self.feedback_fn("Stopping master daemons")
2490
      self._RunCmd(None, [constants.DAEMON_UTIL, "stop-master"])
2491
      try:
2492
        # Stop daemons on all nodes
2493
        for node_name in self.online_nodes:
2494
          self.feedback_fn("Stopping daemons on %s" % node_name)
2495
          self._RunCmd(node_name, [constants.DAEMON_UTIL, "stop-all"])
2496

    
2497
        # All daemons are shut down now
2498
        try:
2499
          return fn(self, *args)
2500
        except Exception, err:
2501
          _, errmsg = FormatError(err)
2502
          logging.exception("Caught exception")
2503
          self.feedback_fn(errmsg)
2504
          raise
2505
      finally:
2506
        # Start cluster again, master node last
2507
        for node_name in self.nonmaster_nodes + [self.master_node]:
2508
          self.feedback_fn("Starting daemons on %s" % node_name)
2509
          self._RunCmd(node_name, [constants.DAEMON_UTIL, "start-all"])
2510
    finally:
2511
      # Resume watcher
2512
      watcher_block.Close()
2513

    
2514

    
2515
def RunWhileClusterStopped(feedback_fn, fn, *args):
2516
  """Calls a function while all cluster daemons are stopped.
2517

2518
  @type feedback_fn: callable
2519
  @param feedback_fn: Feedback function
2520
  @type fn: callable
2521
  @param fn: Function to be called when daemons are stopped
2522

2523
  """
2524
  feedback_fn("Gathering cluster information")
2525

    
2526
  # This ensures we're running on the master daemon
2527
  cl = GetClient()
2528

    
2529
  (cluster_name, master_node) = \
2530
    cl.QueryConfigValues(["cluster_name", "master_node"])
2531

    
2532
  online_nodes = GetOnlineNodes([], cl=cl)
2533

    
2534
  # Don't keep a reference to the client. The master daemon will go away.
2535
  del cl
2536

    
2537
  assert master_node in online_nodes
2538

    
2539
  return _RunWhileClusterStoppedHelper(feedback_fn, cluster_name, master_node,
2540
                                       online_nodes).Call(fn, *args)
2541

    
2542

    
2543
def GenerateTable(headers, fields, separator, data,
2544
                  numfields=None, unitfields=None,
2545
                  units=None):
2546
  """Prints a table with headers and different fields.
2547

2548
  @type headers: dict
2549
  @param headers: dictionary mapping field names to headers for
2550
      the table
2551
  @type fields: list
2552
  @param fields: the field names corresponding to each row in
2553
      the data field
2554
  @param separator: the separator to be used; if this is None,
2555
      the default 'smart' algorithm is used which computes optimal
2556
      field width, otherwise just the separator is used between
2557
      each field
2558
  @type data: list
2559
  @param data: a list of lists, each sublist being one row to be output
2560
  @type numfields: list
2561
  @param numfields: a list with the fields that hold numeric
2562
      values and thus should be right-aligned
2563
  @type unitfields: list
2564
  @param unitfields: a list with the fields that hold numeric
2565
      values that should be formatted with the units field
2566
  @type units: string or None
2567
  @param units: the units we should use for formatting, or None for
2568
      automatic choice (human-readable for non-separator usage, otherwise
2569
      megabytes); this is a one-letter string
2570

2571
  """
2572
  if units is None:
2573
    if separator:
2574
      units = "m"
2575
    else:
2576
      units = "h"
2577

    
2578
  if numfields is None:
2579
    numfields = []
2580
  if unitfields is None:
2581
    unitfields = []
2582

    
2583
  numfields = utils.FieldSet(*numfields)   # pylint: disable=W0142
2584
  unitfields = utils.FieldSet(*unitfields) # pylint: disable=W0142
2585

    
2586
  format_fields = []
2587
  for field in fields:
2588
    if headers and field not in headers:
2589
      # TODO: handle better unknown fields (either revert to old
2590
      # style of raising exception, or deal more intelligently with
2591
      # variable fields)
2592
      headers[field] = field
2593
    if separator is not None:
2594
      format_fields.append("%s")
2595
    elif numfields.Matches(field):
2596
      format_fields.append("%*s")
2597
    else:
2598
      format_fields.append("%-*s")
2599

    
2600
  if separator is None:
2601
    mlens = [0 for name in fields]
2602
    format_str = " ".join(format_fields)
2603
  else:
2604
    format_str = separator.replace("%", "%%").join(format_fields)
2605

    
2606
  for row in data:
2607
    if row is None:
2608
      continue
2609
    for idx, val in enumerate(row):
2610
      if unitfields.Matches(fields[idx]):
2611
        try:
2612
          val = int(val)
2613
        except (TypeError, ValueError):
2614
          pass
2615
        else:
2616
          val = row[idx] = utils.FormatUnit(val, units)
2617
      val = row[idx] = str(val)
2618
      if separator is None:
2619
        mlens[idx] = max(mlens[idx], len(val))
2620

    
2621
  result = []
2622
  if headers:
2623
    args = []
2624
    for idx, name in enumerate(fields):
2625
      hdr = headers[name]
2626
      if separator is None:
2627
        mlens[idx] = max(mlens[idx], len(hdr))
2628
        args.append(mlens[idx])
2629
      args.append(hdr)
2630
    result.append(format_str % tuple(args))
2631

    
2632
  if separator is None:
2633
    assert len(mlens) == len(fields)
2634

    
2635
    if fields and not numfields.Matches(fields[-1]):
2636
      mlens[-1] = 0
2637

    
2638
  for line in data:
2639
    args = []
2640
    if line is None:
2641
      line = ["-" for _ in fields]
2642
    for idx in range(len(fields)):
2643
      if separator is None:
2644
        args.append(mlens[idx])
2645
      args.append(line[idx])
2646
    result.append(format_str % tuple(args))
2647

    
2648
  return result
2649

    
2650

    
2651
def _FormatBool(value):
2652
  """Formats a boolean value as a string.
2653

2654
  """
2655
  if value:
2656
    return "Y"
2657
  return "N"
2658

    
2659

    
2660
#: Default formatting for query results; (callback, align right)
2661
_DEFAULT_FORMAT_QUERY = {
2662
  constants.QFT_TEXT: (str, False),
2663
  constants.QFT_BOOL: (_FormatBool, False),
2664
  constants.QFT_NUMBER: (str, True),
2665
  constants.QFT_TIMESTAMP: (utils.FormatTime, False),
2666
  constants.QFT_OTHER: (str, False),
2667
  constants.QFT_UNKNOWN: (str, False),
2668
  }
2669

    
2670

    
2671
def _GetColumnFormatter(fdef, override, unit):
2672
  """Returns formatting function for a field.
2673

2674
  @type fdef: L{objects.QueryFieldDefinition}
2675
  @type override: dict
2676
  @param override: Dictionary for overriding field formatting functions,
2677
    indexed by field name, contents like L{_DEFAULT_FORMAT_QUERY}
2678
  @type unit: string
2679
  @param unit: Unit used for formatting fields of type L{constants.QFT_UNIT}
2680
  @rtype: tuple; (callable, bool)
2681
  @return: Returns the function to format a value (takes one parameter) and a
2682
    boolean for aligning the value on the right-hand side
2683

2684
  """
2685
  fmt = override.get(fdef.name, None)
2686
  if fmt is not None:
2687
    return fmt
2688

    
2689
  assert constants.QFT_UNIT not in _DEFAULT_FORMAT_QUERY
2690

    
2691
  if fdef.kind == constants.QFT_UNIT:
2692
    # Can't keep this information in the static dictionary
2693
    return (lambda value: utils.FormatUnit(value, unit), True)
2694

    
2695
  fmt = _DEFAULT_FORMAT_QUERY.get(fdef.kind, None)
2696
  if fmt is not None:
2697
    return fmt
2698

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

    
2701

    
2702
class _QueryColumnFormatter:
2703
  """Callable class for formatting fields of a query.
2704

2705
  """
2706
  def __init__(self, fn, status_fn, verbose):
2707
    """Initializes this class.
2708

2709
    @type fn: callable
2710
    @param fn: Formatting function
2711
    @type status_fn: callable
2712
    @param status_fn: Function to report fields' status
2713
    @type verbose: boolean
2714
    @param verbose: whether to use verbose field descriptions or not
2715

2716
    """
2717
    self._fn = fn
2718
    self._status_fn = status_fn
2719
    self._verbose = verbose
2720

    
2721
  def __call__(self, data):
2722
    """Returns a field's string representation.
2723

2724
    """
2725
    (status, value) = data
2726

    
2727
    # Report status
2728
    self._status_fn(status)
2729

    
2730
    if status == constants.RS_NORMAL:
2731
      return self._fn(value)
2732

    
2733
    assert value is None, \
2734
           "Found value %r for abnormal status %s" % (value, status)
2735

    
2736
    return FormatResultError(status, self._verbose)
2737

    
2738

    
2739
def FormatResultError(status, verbose):
2740
  """Formats result status other than L{constants.RS_NORMAL}.
2741

2742
  @param status: The result status
2743
  @type verbose: boolean
2744
  @param verbose: Whether to return the verbose text
2745
  @return: Text of result status
2746

2747
  """
2748
  assert status != constants.RS_NORMAL, \
2749
         "FormatResultError called with status equal to constants.RS_NORMAL"
2750
  try:
2751
    (verbose_text, normal_text) = constants.RSS_DESCRIPTION[status]
2752
  except KeyError:
2753
    raise NotImplementedError("Unknown status %s" % status)
2754
  else:
2755
    if verbose:
2756
      return verbose_text
2757
    return normal_text
2758

    
2759

    
2760
def FormatQueryResult(result, unit=None, format_override=None, separator=None,
2761
                      header=False, verbose=False):
2762
  """Formats data in L{objects.QueryResponse}.
2763

2764
  @type result: L{objects.QueryResponse}
2765
  @param result: result of query operation
2766
  @type unit: string
2767
  @param unit: Unit used for formatting fields of type L{constants.QFT_UNIT},
2768
    see L{utils.text.FormatUnit}
2769
  @type format_override: dict
2770
  @param format_override: Dictionary for overriding field formatting functions,
2771
    indexed by field name, contents like L{_DEFAULT_FORMAT_QUERY}
2772
  @type separator: string or None
2773
  @param separator: String used to separate fields
2774
  @type header: bool
2775
  @param header: Whether to output header row
2776
  @type verbose: boolean
2777
  @param verbose: whether to use verbose field descriptions or not
2778

2779
  """
2780
  if unit is None:
2781
    if separator:
2782
      unit = "m"
2783
    else:
2784
      unit = "h"
2785

    
2786
  if format_override is None:
2787
    format_override = {}
2788

    
2789
  stats = dict.fromkeys(constants.RS_ALL, 0)
2790

    
2791
  def _RecordStatus(status):
2792
    if status in stats:
2793
      stats[status] += 1
2794

    
2795
  columns = []
2796
  for fdef in result.fields:
2797
    assert fdef.title and fdef.name
2798
    (fn, align_right) = _GetColumnFormatter(fdef, format_override, unit)
2799
    columns.append(TableColumn(fdef.title,
2800
                               _QueryColumnFormatter(fn, _RecordStatus,
2801
                                                     verbose),
2802
                               align_right))
2803

    
2804
  table = FormatTable(result.data, columns, header, separator)
2805

    
2806
  # Collect statistics
2807
  assert len(stats) == len(constants.RS_ALL)
2808
  assert compat.all(count >= 0 for count in stats.values())
2809

    
2810
  # Determine overall status. If there was no data, unknown fields must be
2811
  # detected via the field definitions.
2812
  if (stats[constants.RS_UNKNOWN] or
2813
      (not result.data and _GetUnknownFields(result.fields))):
2814
    status = QR_UNKNOWN
2815
  elif compat.any(count > 0 for key, count in stats.items()
2816
                  if key != constants.RS_NORMAL):
2817
    status = QR_INCOMPLETE
2818
  else:
2819
    status = QR_NORMAL
2820

    
2821
  return (status, table)
2822

    
2823

    
2824
def _GetUnknownFields(fdefs):
2825
  """Returns list of unknown fields included in C{fdefs}.
2826

2827
  @type fdefs: list of L{objects.QueryFieldDefinition}
2828

2829
  """
2830
  return [fdef for fdef in fdefs
2831
          if fdef.kind == constants.QFT_UNKNOWN]
2832

    
2833

    
2834
def _WarnUnknownFields(fdefs):
2835
  """Prints a warning to stderr if a query included unknown fields.
2836

2837
  @type fdefs: list of L{objects.QueryFieldDefinition}
2838

2839
  """
2840
  unknown = _GetUnknownFields(fdefs)
2841
  if unknown:
2842
    ToStderr("Warning: Queried for unknown fields %s",
2843
             utils.CommaJoin(fdef.name for fdef in unknown))
2844
    return True
2845

    
2846
  return False
2847

    
2848

    
2849
def GenericList(resource, fields, names, unit, separator, header, cl=None,
2850
                format_override=None, verbose=False, force_filter=False,
2851
                namefield=None, qfilter=None):
2852
  """Generic implementation for listing all items of a resource.
2853

2854
  @param resource: One of L{constants.QR_VIA_LUXI}
2855
  @type fields: list of strings
2856
  @param fields: List of fields to query for
2857
  @type names: list of strings
2858
  @param names: Names of items to query for
2859
  @type unit: string or None
2860
  @param unit: Unit used for formatting fields of type L{constants.QFT_UNIT} or
2861
    None for automatic choice (human-readable for non-separator usage,
2862
    otherwise megabytes); this is a one-letter string
2863
  @type separator: string or None
2864
  @param separator: String used to separate fields
2865
  @type header: bool
2866
  @param header: Whether to show header row
2867
  @type force_filter: bool
2868
  @param force_filter: Whether to always treat names as filter
2869
  @type format_override: dict
2870
  @param format_override: Dictionary for overriding field formatting functions,
2871
    indexed by field name, contents like L{_DEFAULT_FORMAT_QUERY}
2872
  @type verbose: boolean
2873
  @param verbose: whether to use verbose field descriptions or not
2874
  @type namefield: string
2875
  @param namefield: Name of field to use for simple filters (see
2876
    L{qlang.MakeFilter} for details)
2877
  @type qfilter: list or None
2878
  @param qfilter: Query filter (in addition to names)
2879

2880
  """
2881
  if not names:
2882
    names = None
2883

    
2884
  namefilter = qlang.MakeFilter(names, force_filter, namefield=namefield)
2885

    
2886
  if qfilter is None:
2887
    qfilter = namefilter
2888
  elif namefilter is not None:
2889
    qfilter = [qlang.OP_AND, namefilter, qfilter]
2890

    
2891
  if cl is None:
2892
    cl = GetClient()
2893

    
2894
  response = cl.Query(resource, fields, qfilter)
2895

    
2896
  found_unknown = _WarnUnknownFields(response.fields)
2897

    
2898
  (status, data) = FormatQueryResult(response, unit=unit, separator=separator,
2899
                                     header=header,
2900
                                     format_override=format_override,
2901
                                     verbose=verbose)
2902

    
2903
  for line in data:
2904
    ToStdout(line)
2905

    
2906
  assert ((found_unknown and status == QR_UNKNOWN) or
2907
          (not found_unknown and status != QR_UNKNOWN))
2908

    
2909
  if status == QR_UNKNOWN:
2910
    return constants.EXIT_UNKNOWN_FIELD
2911

    
2912
  # TODO: Should the list command fail if not all data could be collected?
2913
  return constants.EXIT_SUCCESS
2914

    
2915

    
2916
def GenericListFields(resource, fields, separator, header, cl=None):
2917
  """Generic implementation for listing fields for a resource.
2918

2919
  @param resource: One of L{constants.QR_VIA_LUXI}
2920
  @type fields: list of strings
2921
  @param fields: List of fields to query for
2922
  @type separator: string or None
2923
  @param separator: String used to separate fields
2924
  @type header: bool
2925
  @param header: Whether to show header row
2926

2927
  """
2928
  if cl is None:
2929
    cl = GetClient()
2930

    
2931
  if not fields:
2932
    fields = None
2933

    
2934
  response = cl.QueryFields(resource, fields)
2935

    
2936
  found_unknown = _WarnUnknownFields(response.fields)
2937

    
2938
  columns = [
2939
    TableColumn("Name", str, False),
2940
    TableColumn("Title", str, False),
2941
    TableColumn("Description", str, False),
2942
    ]
2943

    
2944
  rows = [[fdef.name, fdef.title, fdef.doc] for fdef in response.fields]
2945

    
2946
  for line in FormatTable(rows, columns, header, separator):
2947
    ToStdout(line)
2948

    
2949
  if found_unknown:
2950
    return constants.EXIT_UNKNOWN_FIELD
2951

    
2952
  return constants.EXIT_SUCCESS
2953

    
2954

    
2955
class TableColumn:
2956
  """Describes a column for L{FormatTable}.
2957

2958
  """
2959
  def __init__(self, title, fn, align_right):
2960
    """Initializes this class.
2961

2962
    @type title: string
2963
    @param title: Column title
2964
    @type fn: callable
2965
    @param fn: Formatting function
2966
    @type align_right: bool
2967
    @param align_right: Whether to align values on the right-hand side
2968

2969
    """
2970
    self.title = title
2971
    self.format = fn
2972
    self.align_right = align_right
2973

    
2974

    
2975
def _GetColFormatString(width, align_right):
2976
  """Returns the format string for a field.
2977

2978
  """
2979
  if align_right:
2980
    sign = ""
2981
  else:
2982
    sign = "-"
2983

    
2984
  return "%%%s%ss" % (sign, width)
2985

    
2986

    
2987
def FormatTable(rows, columns, header, separator):
2988
  """Formats data as a table.
2989

2990
  @type rows: list of lists
2991
  @param rows: Row data, one list per row
2992
  @type columns: list of L{TableColumn}
2993
  @param columns: Column descriptions
2994
  @type header: bool
2995
  @param header: Whether to show header row
2996
  @type separator: string or None
2997
  @param separator: String used to separate columns
2998

2999
  """
3000
  if header:
3001
    data = [[col.title for col in columns]]
3002
    colwidth = [len(col.title) for col in columns]
3003
  else:
3004
    data = []
3005
    colwidth = [0 for _ in columns]
3006

    
3007
  # Format row data
3008
  for row in rows:
3009
    assert len(row) == len(columns)
3010

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

    
3013
    if separator is None:
3014
      # Update column widths
3015
      for idx, (oldwidth, value) in enumerate(zip(colwidth, formatted)):
3016
        # Modifying a list's items while iterating is fine
3017
        colwidth[idx] = max(oldwidth, len(value))
3018

    
3019
    data.append(formatted)
3020

    
3021
  if separator is not None:
3022
    # Return early if a separator is used
3023
    return [separator.join(row) for row in data]
3024

    
3025
  if columns and not columns[-1].align_right:
3026
    # Avoid unnecessary spaces at end of line
3027
    colwidth[-1] = 0
3028

    
3029
  # Build format string
3030
  fmt = " ".join([_GetColFormatString(width, col.align_right)
3031
                  for col, width in zip(columns, colwidth)])
3032

    
3033
  return [fmt % tuple(row) for row in data]
3034

    
3035

    
3036
def FormatTimestamp(ts):
3037
  """Formats a given timestamp.
3038

3039
  @type ts: timestamp
3040
  @param ts: a timeval-type timestamp, a tuple of seconds and microseconds
3041

3042
  @rtype: string
3043
  @return: a string with the formatted timestamp
3044

3045
  """
3046
  if not isinstance(ts, (tuple, list)) or len(ts) != 2:
3047
    return "?"
3048

    
3049
  (sec, usecs) = ts
3050
  return utils.FormatTime(sec, usecs=usecs)
3051

    
3052

    
3053
def ParseTimespec(value):
3054
  """Parse a time specification.
3055

3056
  The following suffixed will be recognized:
3057

3058
    - s: seconds
3059
    - m: minutes
3060
    - h: hours
3061
    - d: day
3062
    - w: weeks
3063

3064
  Without any suffix, the value will be taken to be in seconds.
3065

3066
  """
3067
  value = str(value)
3068
  if not value:
3069
    raise errors.OpPrereqError("Empty time specification passed")
3070
  suffix_map = {
3071
    "s": 1,
3072
    "m": 60,
3073
    "h": 3600,
3074
    "d": 86400,
3075
    "w": 604800,
3076
    }
3077
  if value[-1] not in suffix_map:
3078
    try:
3079
      value = int(value)
3080
    except (TypeError, ValueError):
3081
      raise errors.OpPrereqError("Invalid time specification '%s'" % value)
3082
  else:
3083
    multiplier = suffix_map[value[-1]]
3084
    value = value[:-1]
3085
    if not value: # no data left after stripping the suffix
3086
      raise errors.OpPrereqError("Invalid time specification (only"
3087
                                 " suffix passed)")
3088
    try:
3089
      value = int(value) * multiplier
3090
    except (TypeError, ValueError):
3091
      raise errors.OpPrereqError("Invalid time specification '%s'" % value)
3092
  return value
3093

    
3094

    
3095
def GetOnlineNodes(nodes, cl=None, nowarn=False, secondary_ips=False,
3096
                   filter_master=False, nodegroup=None):
3097
  """Returns the names of online nodes.
3098

3099
  This function will also log a warning on stderr with the names of
3100
  the online nodes.
3101

3102
  @param nodes: if not empty, use only this subset of nodes (minus the
3103
      offline ones)
3104
  @param cl: if not None, luxi client to use
3105
  @type nowarn: boolean
3106
  @param nowarn: by default, this function will output a note with the
3107
      offline nodes that are skipped; if this parameter is True the
3108
      note is not displayed
3109
  @type secondary_ips: boolean
3110
  @param secondary_ips: if True, return the secondary IPs instead of the
3111
      names, useful for doing network traffic over the replication interface
3112
      (if any)
3113
  @type filter_master: boolean
3114
  @param filter_master: if True, do not return the master node in the list
3115
      (useful in coordination with secondary_ips where we cannot check our
3116
      node name against the list)
3117
  @type nodegroup: string
3118
  @param nodegroup: If set, only return nodes in this node group
3119

3120
  """
3121
  if cl is None:
3122
    cl = GetClient()
3123

    
3124
  qfilter = []
3125

    
3126
  if nodes:
3127
    qfilter.append(qlang.MakeSimpleFilter("name", nodes))
3128

    
3129
  if nodegroup is not None:
3130
    qfilter.append([qlang.OP_OR, [qlang.OP_EQUAL, "group", nodegroup],
3131
                                 [qlang.OP_EQUAL, "group.uuid", nodegroup]])
3132

    
3133
  if filter_master:
3134
    qfilter.append([qlang.OP_NOT, [qlang.OP_TRUE, "master"]])
3135

    
3136
  if qfilter:
3137
    if len(qfilter) > 1:
3138
      final_filter = [qlang.OP_AND] + qfilter
3139
    else:
3140
      assert len(qfilter) == 1
3141
      final_filter = qfilter[0]
3142
  else:
3143
    final_filter = None
3144

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

    
3147
  def _IsOffline(row):
3148
    (_, (_, offline), _) = row
3149
    return offline
3150

    
3151
  def _GetName(row):
3152
    ((_, name), _, _) = row
3153
    return name
3154

    
3155
  def _GetSip(row):
3156
    (_, _, (_, sip)) = row
3157
    return sip
3158

    
3159
  (offline, online) = compat.partition(result.data, _IsOffline)
3160

    
3161
  if offline and not nowarn:
3162
    ToStderr("Note: skipping offline node(s): %s" %
3163
             utils.CommaJoin(map(_GetName, offline)))
3164

    
3165
  if secondary_ips:
3166
    fn = _GetSip
3167
  else:
3168
    fn = _GetName
3169

    
3170
  return map(fn, online)
3171

    
3172

    
3173
def _ToStream(stream, txt, *args):
3174
  """Write a message to a stream, bypassing the logging system
3175

3176
  @type stream: file object
3177
  @param stream: the file to which we should write
3178
  @type txt: str
3179
  @param txt: the message
3180

3181
  """
3182
  try:
3183
    if args:
3184
      args = tuple(args)
3185
      stream.write(txt % args)
3186
    else:
3187
      stream.write(txt)
3188
    stream.write("\n")
3189
    stream.flush()
3190
  except IOError, err:
3191
    if err.errno == errno.EPIPE:
3192
      # our terminal went away, we'll exit
3193
      sys.exit(constants.EXIT_FAILURE)
3194
    else:
3195
      raise
3196

    
3197

    
3198
def ToStdout(txt, *args):
3199
  """Write a message to stdout only, bypassing the logging system
3200

3201
  This is just a wrapper over _ToStream.
3202

3203
  @type txt: str
3204
  @param txt: the message
3205

3206
  """
3207
  _ToStream(sys.stdout, txt, *args)
3208

    
3209

    
3210
def ToStderr(txt, *args):
3211
  """Write a message to stderr only, bypassing the logging system
3212

3213
  This is just a wrapper over _ToStream.
3214

3215
  @type txt: str
3216
  @param txt: the message
3217

3218
  """
3219
  _ToStream(sys.stderr, txt, *args)
3220

    
3221

    
3222
class JobExecutor(object):
3223
  """Class which manages the submission and execution of multiple jobs.
3224

3225
  Note that instances of this class should not be reused between
3226
  GetResults() calls.
3227

3228
  """
3229
  def __init__(self, cl=None, verbose=True, opts=None, feedback_fn=None):
3230
    self.queue = []
3231
    if cl is None:
3232
      cl = GetClient()
3233
    self.cl = cl
3234
    self.verbose = verbose
3235
    self.jobs = []
3236
    self.opts = opts
3237
    self.feedback_fn = feedback_fn
3238
    self._counter = itertools.count()
3239

    
3240
  @staticmethod
3241
  def _IfName(name, fmt):
3242
    """Helper function for formatting name.
3243

3244
    """
3245
    if name:
3246
      return fmt % name
3247

    
3248
    return ""
3249

    
3250
  def QueueJob(self, name, *ops):
3251
    """Record a job for later submit.
3252

3253
    @type name: string
3254
    @param name: a description of the job, will be used in WaitJobSet
3255

3256
    """
3257
    SetGenericOpcodeOpts(ops, self.opts)
3258
    self.queue.append((self._counter.next(), name, ops))
3259

    
3260
  def AddJobId(self, name, status, job_id):
3261
    """Adds a job ID to the internal queue.
3262

3263
    """
3264
    self.jobs.append((self._counter.next(), status, job_id, name))
3265

    
3266
  def SubmitPending(self, each=False):
3267
    """Submit all pending jobs.
3268

3269
    """
3270
    if each:
3271
      results = []
3272
      for (_, _, ops) in self.queue:
3273
        # SubmitJob will remove the success status, but raise an exception if
3274
        # the submission fails, so we'll notice that anyway.
3275
        results.append([True, self.cl.SubmitJob(ops)[0]])
3276
    else:
3277
      results = self.cl.SubmitManyJobs([ops for (_, _, ops) in self.queue])
3278
    for ((status, data), (idx, name, _)) in zip(results, self.queue):
3279
      self.jobs.append((idx, status, data, name))
3280

    
3281
  def _ChooseJob(self):
3282
    """Choose a non-waiting/queued job to poll next.
3283

3284
    """
3285
    assert self.jobs, "_ChooseJob called with empty job list"
3286

    
3287
    result = self.cl.QueryJobs([i[2] for i in self.jobs[:_CHOOSE_BATCH]],
3288
                               ["status"])
3289
    assert result
3290

    
3291
    for job_data, status in zip(self.jobs, result):
3292
      if (isinstance(status, list) and status and
3293
          status[0] in (constants.JOB_STATUS_QUEUED,
3294
                        constants.JOB_STATUS_WAITING,
3295
                        constants.JOB_STATUS_CANCELING)):
3296
        # job is still present and waiting
3297
        continue
3298
      # good candidate found (either running job or lost job)
3299
      self.jobs.remove(job_data)
3300
      return job_data
3301

    
3302
    # no job found
3303
    return self.jobs.pop(0)
3304

    
3305
  def GetResults(self):
3306
    """Wait for and return the results of all jobs.
3307

3308
    @rtype: list
3309
    @return: list of tuples (success, job results), in the same order
3310
        as the submitted jobs; if a job has failed, instead of the result
3311
        there will be the error message
3312

3313
    """
3314
    if not self.jobs:
3315
      self.SubmitPending()
3316
    results = []
3317
    if self.verbose:
3318
      ok_jobs = [row[2] for row in self.jobs if row[1]]
3319
      if ok_jobs:
3320
        ToStdout("Submitted jobs %s", utils.CommaJoin(ok_jobs))
3321

    
3322
    # first, remove any non-submitted jobs
3323
    self.jobs, failures = compat.partition(self.jobs, lambda x: x[1])
3324
    for idx, _, jid, name in failures:
3325
      ToStderr("Failed to submit job%s: %s", self._IfName(name, " for %s"), jid)
3326
      results.append((idx, False, jid))
3327

    
3328
    while self.jobs:
3329
      (idx, _, jid, name) = self._ChooseJob()
3330
      ToStdout("Waiting for job %s%s ...", jid, self._IfName(name, " for %s"))
3331
      try:
3332
        job_result = PollJob(jid, cl=self.cl, feedback_fn=self.feedback_fn)
3333
        success = True
3334
      except errors.JobLost, err:
3335
        _, job_result = FormatError(err)
3336
        ToStderr("Job %s%s has been archived, cannot check its result",
3337
                 jid, self._IfName(name, " for %s"))
3338
        success = False
3339
      except (errors.GenericError, luxi.ProtocolError), err:
3340
        _, job_result = FormatError(err)
3341
        success = False
3342
        # the error message will always be shown, verbose or not
3343
        ToStderr("Job %s%s has failed: %s",
3344
                 jid, self._IfName(name, " for %s"), job_result)
3345

    
3346
      results.append((idx, success, job_result))
3347

    
3348
    # sort based on the index, then drop it
3349
    results.sort()
3350
    results = [i[1:] for i in results]
3351

    
3352
    return results
3353

    
3354
  def WaitOrShow(self, wait):
3355
    """Wait for job results or only print the job IDs.
3356

3357
    @type wait: boolean
3358
    @param wait: whether to wait or not
3359

3360
    """
3361
    if wait:
3362
      return self.GetResults()
3363
    else:
3364
      if not self.jobs:
3365
        self.SubmitPending()
3366
      for _, status, result, name in self.jobs:
3367
        if status:
3368
          ToStdout("%s: %s", result, name)
3369
        else:
3370
          ToStderr("Failure for %s: %s", name, result)
3371
      return [row[1:3] for row in self.jobs]
3372

    
3373

    
3374
def FormatParameterDict(buf, param_dict, actual, level=1):
3375
  """Formats a parameter dictionary.
3376

3377
  @type buf: L{StringIO}
3378
  @param buf: the buffer into which to write
3379
  @type param_dict: dict
3380
  @param param_dict: the own parameters
3381
  @type actual: dict
3382
  @param actual: the current parameter set (including defaults)
3383
  @param level: Level of indent
3384

3385
  """
3386
  indent = "  " * level
3387

    
3388
  for key in sorted(actual):
3389
    data = actual[key]
3390
    buf.write("%s- %s:" % (indent, key))
3391

    
3392
    if isinstance(data, dict) and data:
3393
      buf.write("\n")
3394
      FormatParameterDict(buf, param_dict.get(key, {}), data,
3395
                          level=level + 1)
3396
    else:
3397
      val = param_dict.get(key, "default (%s)" % data)
3398
      buf.write(" %s\n" % val)
3399

    
3400

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

3404
  This function is used to request confirmation for doing an operation
3405
  on a given list of list_type.
3406

3407
  @type names: list
3408
  @param names: the list of names that we display when
3409
      we ask for confirmation
3410
  @type list_type: str
3411
  @param list_type: Human readable name for elements in the list (e.g. nodes)
3412
  @type text: str
3413
  @param text: the operation that the user should confirm
3414
  @rtype: boolean
3415
  @return: True or False depending on user's confirmation.
3416

3417
  """
3418
  count = len(names)
3419
  msg = ("The %s will operate on %d %s.\n%s"
3420
         "Do you want to continue?" % (text, count, list_type, extra))
3421
  affected = (("\nAffected %s:\n" % list_type) +
3422
              "\n".join(["  %s" % name for name in names]))
3423

    
3424
  choices = [("y", True, "Yes, execute the %s" % text),
3425
             ("n", False, "No, abort the %s" % text)]
3426

    
3427
  if count > 20:
3428
    choices.insert(1, ("v", "v", "View the list of affected %s" % list_type))
3429
    question = msg
3430
  else:
3431
    question = msg + affected
3432

    
3433
  choice = AskUser(question, choices)
3434
  if choice == "v":
3435
    choices.pop(1)
3436
    choice = AskUser(msg + affected, choices)
3437
  return choice
3438

    
3439

    
3440
def _MaybeParseUnit(elements):
3441
  """Parses and returns an array of potential values with units.
3442

3443
  """
3444
  parsed = {}
3445
  for k, v in elements.items():
3446
    if v == constants.VALUE_DEFAULT:
3447
      parsed[k] = v
3448
    else:
3449
      parsed[k] = utils.ParseUnit(v)
3450
  return parsed
3451

    
3452

    
3453
def CreateIPolicyFromOpts(ispecs_mem_size=None,
3454
                          ispecs_cpu_count=None,
3455
                          ispecs_disk_count=None,
3456
                          ispecs_disk_size=None,
3457
                          ispecs_nic_count=None,
3458
                          ipolicy_disk_templates=None,
3459
                          ipolicy_vcpu_ratio=None,
3460
                          ipolicy_spindle_ratio=None,
3461
                          group_ipolicy=False,
3462
                          allowed_values=None,
3463
                          fill_all=False):
3464
  """Creation of instance policy based on command line options.
3465

3466
  @param fill_all: whether for cluster policies we should ensure that
3467
    all values are filled
3468

3469

3470
  """
3471
  try:
3472
    if ispecs_mem_size:
3473
      ispecs_mem_size = _MaybeParseUnit(ispecs_mem_size)
3474
    if ispecs_disk_size:
3475
      ispecs_disk_size = _MaybeParseUnit(ispecs_disk_size)
3476
  except (TypeError, ValueError, errors.UnitParseError), err:
3477
    raise errors.OpPrereqError("Invalid disk (%s) or memory (%s) size"
3478
                               " in policy: %s" %
3479
                               (ispecs_disk_size, ispecs_mem_size, err),
3480
                               errors.ECODE_INVAL)
3481

    
3482
  # prepare ipolicy dict
3483
  ipolicy_transposed = {
3484
    constants.ISPEC_MEM_SIZE: ispecs_mem_size,
3485
    constants.ISPEC_CPU_COUNT: ispecs_cpu_count,
3486
    constants.ISPEC_DISK_COUNT: ispecs_disk_count,
3487
    constants.ISPEC_DISK_SIZE: ispecs_disk_size,
3488
    constants.ISPEC_NIC_COUNT: ispecs_nic_count,
3489
    }
3490

    
3491
  # first, check that the values given are correct
3492
  if group_ipolicy:
3493
    forced_type = TISPECS_GROUP_TYPES
3494
  else:
3495
    forced_type = TISPECS_CLUSTER_TYPES
3496

    
3497
  for specs in ipolicy_transposed.values():
3498
    utils.ForceDictType(specs, forced_type, allowed_values=allowed_values)
3499

    
3500
  # then transpose
3501
  ipolicy_out = objects.MakeEmptyIPolicy()
3502
  for name, specs in ipolicy_transposed.iteritems():
3503
    assert name in constants.ISPECS_PARAMETERS
3504
    for key, val in specs.items(): # {min: .. ,max: .., std: ..}
3505
      ipolicy_out[key][name] = val
3506

    
3507
  # no filldict for non-dicts
3508
  if not group_ipolicy and fill_all:
3509
    if ipolicy_disk_templates is None:
3510
      ipolicy_disk_templates = constants.DISK_TEMPLATES
3511
    if ipolicy_vcpu_ratio is None:
3512
      ipolicy_vcpu_ratio = \
3513
        constants.IPOLICY_DEFAULTS[constants.IPOLICY_VCPU_RATIO]
3514
    if ipolicy_spindle_ratio is None:
3515
      ipolicy_spindle_ratio = \
3516
        constants.IPOLICY_DEFAULTS[constants.IPOLICY_SPINDLE_RATIO]
3517
  if ipolicy_disk_templates is not None:
3518
    ipolicy_out[constants.IPOLICY_DTS] = list(ipolicy_disk_templates)
3519
  if ipolicy_vcpu_ratio is not None:
3520
    ipolicy_out[constants.IPOLICY_VCPU_RATIO] = ipolicy_vcpu_ratio
3521
  if ipolicy_spindle_ratio is not None:
3522
    ipolicy_out[constants.IPOLICY_SPINDLE_RATIO] = ipolicy_spindle_ratio
3523

    
3524
  assert not (frozenset(ipolicy_out.keys()) - constants.IPOLICY_ALL_KEYS)
3525

    
3526
  return ipolicy_out