Statistics
| Branch: | Tag: | Revision:

root / lib / cli.py @ ad5cc6bd

History | View | Annotate | Download (114.8 kB)

1
#
2
#
3

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

    
21

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

    
24

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

    
35
from ganeti import utils
36
from ganeti import errors
37
from ganeti import constants
38
from ganeti import opcodes
39
from ganeti import luxi
40
from ganeti import ssconf
41
from ganeti import rpc
42
from ganeti import ssh
43
from ganeti import compat
44
from ganeti import netutils
45
from ganeti import qlang
46
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
# completion_suggestion is normally a list. Using numeric values not evaluating
621
# to False for dynamic completion.
622
(OPT_COMPL_MANY_NODES,
623
 OPT_COMPL_ONE_NODE,
624
 OPT_COMPL_ONE_INSTANCE,
625
 OPT_COMPL_ONE_OS,
626
 OPT_COMPL_ONE_IALLOCATOR,
627
 OPT_COMPL_INST_ADD_NODES,
628
 OPT_COMPL_ONE_NODEGROUP) = range(100, 107)
629

    
630
OPT_COMPL_ALL = frozenset([
631
  OPT_COMPL_MANY_NODES,
632
  OPT_COMPL_ONE_NODE,
633
  OPT_COMPL_ONE_INSTANCE,
634
  OPT_COMPL_ONE_OS,
635
  OPT_COMPL_ONE_IALLOCATOR,
636
  OPT_COMPL_INST_ADD_NODES,
637
  OPT_COMPL_ONE_NODEGROUP,
638
  ])
639

    
640

    
641
class CliOption(Option):
642
  """Custom option class for optparse.
643

644
  """
645
  ATTRS = Option.ATTRS + [
646
    "completion_suggest",
647
    ]
648
  TYPES = Option.TYPES + (
649
    "identkeyval",
650
    "keyval",
651
    "unit",
652
    "bool",
653
    "list",
654
    )
655
  TYPE_CHECKER = Option.TYPE_CHECKER.copy()
656
  TYPE_CHECKER["identkeyval"] = check_ident_key_val
657
  TYPE_CHECKER["keyval"] = check_key_val
658
  TYPE_CHECKER["unit"] = check_unit
659
  TYPE_CHECKER["bool"] = check_bool
660
  TYPE_CHECKER["list"] = check_list
661

    
662

    
663
# optparse.py sets make_option, so we do it for our own option class, too
664
cli_option = CliOption
665

    
666

    
667
_YORNO = "yes|no"
668

    
669
DEBUG_OPT = cli_option("-d", "--debug", default=0, action="count",
670
                       help="Increase debugging level")
671

    
672
NOHDR_OPT = cli_option("--no-headers", default=False,
673
                       action="store_true", dest="no_headers",
674
                       help="Don't display column headers")
675

    
676
SEP_OPT = cli_option("--separator", default=None,
677
                     action="store", dest="separator",
678
                     help=("Separator between output fields"
679
                           " (defaults to one space)"))
680

    
681
USEUNITS_OPT = cli_option("--units", default=None,
682
                          dest="units", choices=("h", "m", "g", "t"),
683
                          help="Specify units for output (one of h/m/g/t)")
684

    
685
FIELDS_OPT = cli_option("-o", "--output", dest="output", action="store",
686
                        type="string", metavar="FIELDS",
687
                        help="Comma separated list of output fields")
688

    
689
FORCE_OPT = cli_option("-f", "--force", dest="force", action="store_true",
690
                       default=False, help="Force the operation")
691

    
692
CONFIRM_OPT = cli_option("--yes", dest="confirm", action="store_true",
693
                         default=False, help="Do not require confirmation")
694

    
695
IGNORE_OFFLINE_OPT = cli_option("--ignore-offline", dest="ignore_offline",
696
                                  action="store_true", default=False,
697
                                  help=("Ignore offline nodes and do as much"
698
                                        " as possible"))
699

    
700
TAG_ADD_OPT = cli_option("--tags", dest="tags",
701
                         default=None, help="Comma-separated list of instance"
702
                                            " tags")
703

    
704
TAG_SRC_OPT = cli_option("--from", dest="tags_source",
705
                         default=None, help="File with tag names")
706

    
707
SUBMIT_OPT = cli_option("--submit", dest="submit_only",
708
                        default=False, action="store_true",
709
                        help=("Submit the job and return the job ID, but"
710
                              " don't wait for the job to finish"))
711

    
712
SYNC_OPT = cli_option("--sync", dest="do_locking",
713
                      default=False, action="store_true",
714
                      help=("Grab locks while doing the queries"
715
                            " in order to ensure more consistent results"))
716

    
717
DRY_RUN_OPT = cli_option("--dry-run", default=False,
718
                         action="store_true",
719
                         help=("Do not execute the operation, just run the"
720
                               " check steps and verify it it could be"
721
                               " executed"))
722

    
723
VERBOSE_OPT = cli_option("-v", "--verbose", default=False,
724
                         action="store_true",
725
                         help="Increase the verbosity of the operation")
726

    
727
DEBUG_SIMERR_OPT = cli_option("--debug-simulate-errors", default=False,
728
                              action="store_true", dest="simulate_errors",
729
                              help="Debugging option that makes the operation"
730
                              " treat most runtime checks as failed")
731

    
732
NWSYNC_OPT = cli_option("--no-wait-for-sync", dest="wait_for_sync",
733
                        default=True, action="store_false",
734
                        help="Don't wait for sync (DANGEROUS!)")
735

    
736
ONLINE_INST_OPT = cli_option("--online", dest="online_inst",
737
                             action="store_true", default=False,
738
                             help="Enable offline instance")
739

    
740
OFFLINE_INST_OPT = cli_option("--offline", dest="offline_inst",
741
                              action="store_true", default=False,
742
                              help="Disable down instance")
743

    
744
DISK_TEMPLATE_OPT = cli_option("-t", "--disk-template", dest="disk_template",
745
                               help=("Custom disk setup (%s)" %
746
                                     utils.CommaJoin(constants.DISK_TEMPLATES)),
747
                               default=None, metavar="TEMPL",
748
                               choices=list(constants.DISK_TEMPLATES))
749

    
750
NONICS_OPT = cli_option("--no-nics", default=False, action="store_true",
751
                        help="Do not create any network cards for"
752
                        " the instance")
753

    
754
FILESTORE_DIR_OPT = cli_option("--file-storage-dir", dest="file_storage_dir",
755
                               help="Relative path under default cluster-wide"
756
                               " file storage dir to store file-based disks",
757
                               default=None, metavar="<DIR>")
758

    
759
FILESTORE_DRIVER_OPT = cli_option("--file-driver", dest="file_driver",
760
                                  help="Driver to use for image files",
761
                                  default="loop", metavar="<DRIVER>",
762
                                  choices=list(constants.FILE_DRIVER))
763

    
764
IALLOCATOR_OPT = cli_option("-I", "--iallocator", metavar="<NAME>",
765
                            help="Select nodes for the instance automatically"
766
                            " using the <NAME> iallocator plugin",
767
                            default=None, type="string",
768
                            completion_suggest=OPT_COMPL_ONE_IALLOCATOR)
769

    
770
DEFAULT_IALLOCATOR_OPT = cli_option("-I", "--default-iallocator",
771
                            metavar="<NAME>",
772
                            help="Set the default instance allocator plugin",
773
                            default=None, type="string",
774
                            completion_suggest=OPT_COMPL_ONE_IALLOCATOR)
775

    
776
OS_OPT = cli_option("-o", "--os-type", dest="os", help="What OS to run",
777
                    metavar="<os>",
778
                    completion_suggest=OPT_COMPL_ONE_OS)
779

    
780
OSPARAMS_OPT = cli_option("-O", "--os-parameters", dest="osparams",
781
                         type="keyval", default={},
782
                         help="OS parameters")
783

    
784
FORCE_VARIANT_OPT = cli_option("--force-variant", dest="force_variant",
785
                               action="store_true", default=False,
786
                               help="Force an unknown variant")
787

    
788
NO_INSTALL_OPT = cli_option("--no-install", dest="no_install",
789
                            action="store_true", default=False,
790
                            help="Do not install the OS (will"
791
                            " enable no-start)")
792

    
793
NORUNTIME_CHGS_OPT = cli_option("--no-runtime-changes",
794
                                dest="allow_runtime_chgs",
795
                                default=True, action="store_false",
796
                                help="Don't allow runtime changes")
797

    
798
BACKEND_OPT = cli_option("-B", "--backend-parameters", dest="beparams",
799
                         type="keyval", default={},
800
                         help="Backend parameters")
801

    
802
HVOPTS_OPT = cli_option("-H", "--hypervisor-parameters", type="keyval",
803
                        default={}, dest="hvparams",
804
                        help="Hypervisor parameters")
805

    
806
DISK_PARAMS_OPT = cli_option("-D", "--disk-parameters", dest="diskparams",
807
                             help="Disk template parameters, in the format"
808
                             " template:option=value,option=value,...",
809
                             type="identkeyval", action="append", default=[])
810

    
811
SPECS_MEM_SIZE_OPT = cli_option("--specs-mem-size", dest="ispecs_mem_size",
812
                                 type="keyval", default={},
813
                                 help="Memory size specs: list of key=value,"
814
                                " where key is one of min, max, std"
815
                                 " (in MB or using a unit)")
816

    
817
SPECS_CPU_COUNT_OPT = cli_option("--specs-cpu-count", dest="ispecs_cpu_count",
818
                                 type="keyval", default={},
819
                                 help="CPU count specs: list of key=value,"
820
                                 " where key is one of min, max, std")
821

    
822
SPECS_DISK_COUNT_OPT = cli_option("--specs-disk-count",
823
                                  dest="ispecs_disk_count",
824
                                  type="keyval", default={},
825
                                  help="Disk count specs: list of key=value,"
826
                                  " where key is one of min, max, std")
827

    
828
SPECS_DISK_SIZE_OPT = cli_option("--specs-disk-size", dest="ispecs_disk_size",
829
                                 type="keyval", default={},
830
                                 help="Disk size specs: list of key=value,"
831
                                " where key is one of min, max, std"
832
                                 " (in MB or using a unit)")
833

    
834
SPECS_NIC_COUNT_OPT = cli_option("--specs-nic-count", dest="ispecs_nic_count",
835
                                 type="keyval", default={},
836
                                 help="NIC count specs: list of key=value,"
837
                                 " where key is one of min, max, std")
838

    
839
IPOLICY_DISK_TEMPLATES = cli_option("--ipolicy-disk-templates",
840
                                 dest="ipolicy_disk_templates",
841
                                 type="list", default=None,
842
                                 help="Comma-separated list of"
843
                                 " enabled disk templates")
844

    
845
IPOLICY_VCPU_RATIO = cli_option("--ipolicy-vcpu-ratio",
846
                                 dest="ipolicy_vcpu_ratio",
847
                                 type="float", default=None,
848
                                 help="The maximum allowed vcpu-to-cpu ratio")
849

    
850
IPOLICY_SPINDLE_RATIO = cli_option("--ipolicy-spindle-ratio",
851
                                   dest="ipolicy_spindle_ratio",
852
                                   type="float", default=None,
853
                                   help=("The maximum allowed instances to"
854
                                         " spindle ratio"))
855

    
856
HYPERVISOR_OPT = cli_option("-H", "--hypervisor-parameters", dest="hypervisor",
857
                            help="Hypervisor and hypervisor options, in the"
858
                            " format hypervisor:option=value,option=value,...",
859
                            default=None, type="identkeyval")
860

    
861
HVLIST_OPT = cli_option("-H", "--hypervisor-parameters", dest="hvparams",
862
                        help="Hypervisor and hypervisor options, in the"
863
                        " format hypervisor:option=value,option=value,...",
864
                        default=[], action="append", type="identkeyval")
865

    
866
NOIPCHECK_OPT = cli_option("--no-ip-check", dest="ip_check", default=True,
867
                           action="store_false",
868
                           help="Don't check that the instance's IP"
869
                           " is alive")
870

    
871
NONAMECHECK_OPT = cli_option("--no-name-check", dest="name_check",
872
                             default=True, action="store_false",
873
                             help="Don't check that the instance's name"
874
                             " is resolvable")
875

    
876
NET_OPT = cli_option("--net",
877
                     help="NIC parameters", default=[],
878
                     dest="nics", action="append", type="identkeyval")
879

    
880
DISK_OPT = cli_option("--disk", help="Disk parameters", default=[],
881
                      dest="disks", action="append", type="identkeyval")
882

    
883
DISKIDX_OPT = cli_option("--disks", dest="disks", default=None,
884
                         help="Comma-separated list of disks"
885
                         " indices to act on (e.g. 0,2) (optional,"
886
                         " defaults to all disks)")
887

    
888
OS_SIZE_OPT = cli_option("-s", "--os-size", dest="sd_size",
889
                         help="Enforces a single-disk configuration using the"
890
                         " given disk size, in MiB unless a suffix is used",
891
                         default=None, type="unit", metavar="<size>")
892

    
893
IGNORE_CONSIST_OPT = cli_option("--ignore-consistency",
894
                                dest="ignore_consistency",
895
                                action="store_true", default=False,
896
                                help="Ignore the consistency of the disks on"
897
                                " the secondary")
898

    
899
ALLOW_FAILOVER_OPT = cli_option("--allow-failover",
900
                                dest="allow_failover",
901
                                action="store_true", default=False,
902
                                help="If migration is not possible fallback to"
903
                                     " failover")
904

    
905
NONLIVE_OPT = cli_option("--non-live", dest="live",
906
                         default=True, action="store_false",
907
                         help="Do a non-live migration (this usually means"
908
                         " freeze the instance, save the state, transfer and"
909
                         " only then resume running on the secondary node)")
910

    
911
MIGRATION_MODE_OPT = cli_option("--migration-mode", dest="migration_mode",
912
                                default=None,
913
                                choices=list(constants.HT_MIGRATION_MODES),
914
                                help="Override default migration mode (choose"
915
                                " either live or non-live")
916

    
917
NODE_PLACEMENT_OPT = cli_option("-n", "--node", dest="node",
918
                                help="Target node and optional secondary node",
919
                                metavar="<pnode>[:<snode>]",
920
                                completion_suggest=OPT_COMPL_INST_ADD_NODES)
921

    
922
NODE_LIST_OPT = cli_option("-n", "--node", dest="nodes", default=[],
923
                           action="append", metavar="<node>",
924
                           help="Use only this node (can be used multiple"
925
                           " times, if not given defaults to all nodes)",
926
                           completion_suggest=OPT_COMPL_ONE_NODE)
927

    
928
NODEGROUP_OPT_NAME = "--node-group"
929
NODEGROUP_OPT = cli_option("-g", NODEGROUP_OPT_NAME,
930
                           dest="nodegroup",
931
                           help="Node group (name or uuid)",
932
                           metavar="<nodegroup>",
933
                           default=None, type="string",
934
                           completion_suggest=OPT_COMPL_ONE_NODEGROUP)
935

    
936
SINGLE_NODE_OPT = cli_option("-n", "--node", dest="node", help="Target node",
937
                             metavar="<node>",
938
                             completion_suggest=OPT_COMPL_ONE_NODE)
939

    
940
NOSTART_OPT = cli_option("--no-start", dest="start", default=True,
941
                         action="store_false",
942
                         help="Don't start the instance after creation")
943

    
944
SHOWCMD_OPT = cli_option("--show-cmd", dest="show_command",
945
                         action="store_true", default=False,
946
                         help="Show command instead of executing it")
947

    
948
CLEANUP_OPT = cli_option("--cleanup", dest="cleanup",
949
                         default=False, action="store_true",
950
                         help="Instead of performing the migration, try to"
951
                         " recover from a failed cleanup. This is safe"
952
                         " to run even if the instance is healthy, but it"
953
                         " will create extra replication traffic and "
954
                         " disrupt briefly the replication (like during the"
955
                         " migration")
956

    
957
STATIC_OPT = cli_option("-s", "--static", dest="static",
958
                        action="store_true", default=False,
959
                        help="Only show configuration data, not runtime data")
960

    
961
ALL_OPT = cli_option("--all", dest="show_all",
962
                     default=False, action="store_true",
963
                     help="Show info on all instances on the cluster."
964
                     " This can take a long time to run, use wisely")
965

    
966
SELECT_OS_OPT = cli_option("--select-os", dest="select_os",
967
                           action="store_true", default=False,
968
                           help="Interactive OS reinstall, lists available"
969
                           " OS templates for selection")
970

    
971
IGNORE_FAILURES_OPT = cli_option("--ignore-failures", dest="ignore_failures",
972
                                 action="store_true", default=False,
973
                                 help="Remove the instance from the cluster"
974
                                 " configuration even if there are failures"
975
                                 " during the removal process")
976

    
977
IGNORE_REMOVE_FAILURES_OPT = cli_option("--ignore-remove-failures",
978
                                        dest="ignore_remove_failures",
979
                                        action="store_true", default=False,
980
                                        help="Remove the instance from the"
981
                                        " cluster configuration even if there"
982
                                        " are failures during the removal"
983
                                        " process")
984

    
985
REMOVE_INSTANCE_OPT = cli_option("--remove-instance", dest="remove_instance",
986
                                 action="store_true", default=False,
987
                                 help="Remove the instance from the cluster")
988

    
989
DST_NODE_OPT = cli_option("-n", "--target-node", dest="dst_node",
990
                               help="Specifies the new node for the instance",
991
                               metavar="NODE", default=None,
992
                               completion_suggest=OPT_COMPL_ONE_NODE)
993

    
994
NEW_SECONDARY_OPT = cli_option("-n", "--new-secondary", dest="dst_node",
995
                               help="Specifies the new secondary node",
996
                               metavar="NODE", default=None,
997
                               completion_suggest=OPT_COMPL_ONE_NODE)
998

    
999
ON_PRIMARY_OPT = cli_option("-p", "--on-primary", dest="on_primary",
1000
                            default=False, action="store_true",
1001
                            help="Replace the disk(s) on the primary"
1002
                                 " node (applies only to internally mirrored"
1003
                                 " disk templates, e.g. %s)" %
1004
                                 utils.CommaJoin(constants.DTS_INT_MIRROR))
1005

    
1006
ON_SECONDARY_OPT = cli_option("-s", "--on-secondary", dest="on_secondary",
1007
                              default=False, action="store_true",
1008
                              help="Replace the disk(s) on the secondary"
1009
                                   " node (applies only to internally mirrored"
1010
                                   " disk templates, e.g. %s)" %
1011
                                   utils.CommaJoin(constants.DTS_INT_MIRROR))
1012

    
1013
AUTO_PROMOTE_OPT = cli_option("--auto-promote", dest="auto_promote",
1014
                              default=False, action="store_true",
1015
                              help="Lock all nodes and auto-promote as needed"
1016
                              " to MC status")
1017

    
1018
AUTO_REPLACE_OPT = cli_option("-a", "--auto", dest="auto",
1019
                              default=False, action="store_true",
1020
                              help="Automatically replace faulty disks"
1021
                                   " (applies only to internally mirrored"
1022
                                   " disk templates, e.g. %s)" %
1023
                                   utils.CommaJoin(constants.DTS_INT_MIRROR))
1024

    
1025
IGNORE_SIZE_OPT = cli_option("--ignore-size", dest="ignore_size",
1026
                             default=False, action="store_true",
1027
                             help="Ignore current recorded size"
1028
                             " (useful for forcing activation when"
1029
                             " the recorded size is wrong)")
1030

    
1031
SRC_NODE_OPT = cli_option("--src-node", dest="src_node", help="Source node",
1032
                          metavar="<node>",
1033
                          completion_suggest=OPT_COMPL_ONE_NODE)
1034

    
1035
SRC_DIR_OPT = cli_option("--src-dir", dest="src_dir", help="Source directory",
1036
                         metavar="<dir>")
1037

    
1038
SECONDARY_IP_OPT = cli_option("-s", "--secondary-ip", dest="secondary_ip",
1039
                              help="Specify the secondary ip for the node",
1040
                              metavar="ADDRESS", default=None)
1041

    
1042
READD_OPT = cli_option("--readd", dest="readd",
1043
                       default=False, action="store_true",
1044
                       help="Readd old node after replacing it")
1045

    
1046
NOSSH_KEYCHECK_OPT = cli_option("--no-ssh-key-check", dest="ssh_key_check",
1047
                                default=True, action="store_false",
1048
                                help="Disable SSH key fingerprint checking")
1049

    
1050
NODE_FORCE_JOIN_OPT = cli_option("--force-join", dest="force_join",
1051
                                 default=False, action="store_true",
1052
                                 help="Force the joining of a node")
1053

    
1054
MC_OPT = cli_option("-C", "--master-candidate", dest="master_candidate",
1055
                    type="bool", default=None, metavar=_YORNO,
1056
                    help="Set the master_candidate flag on the node")
1057

    
1058
OFFLINE_OPT = cli_option("-O", "--offline", dest="offline", metavar=_YORNO,
1059
                         type="bool", default=None,
1060
                         help=("Set the offline flag on the node"
1061
                               " (cluster does not communicate with offline"
1062
                               " nodes)"))
1063

    
1064
DRAINED_OPT = cli_option("-D", "--drained", dest="drained", metavar=_YORNO,
1065
                         type="bool", default=None,
1066
                         help=("Set the drained flag on the node"
1067
                               " (excluded from allocation operations)"))
1068

    
1069
CAPAB_MASTER_OPT = cli_option("--master-capable", dest="master_capable",
1070
                    type="bool", default=None, metavar=_YORNO,
1071
                    help="Set the master_capable flag on the node")
1072

    
1073
CAPAB_VM_OPT = cli_option("--vm-capable", dest="vm_capable",
1074
                    type="bool", default=None, metavar=_YORNO,
1075
                    help="Set the vm_capable flag on the node")
1076

    
1077
ALLOCATABLE_OPT = cli_option("--allocatable", dest="allocatable",
1078
                             type="bool", default=None, metavar=_YORNO,
1079
                             help="Set the allocatable flag on a volume")
1080

    
1081
NOLVM_STORAGE_OPT = cli_option("--no-lvm-storage", dest="lvm_storage",
1082
                               help="Disable support for lvm based instances"
1083
                               " (cluster-wide)",
1084
                               action="store_false", default=True)
1085

    
1086
ENABLED_HV_OPT = cli_option("--enabled-hypervisors",
1087
                            dest="enabled_hypervisors",
1088
                            help="Comma-separated list of hypervisors",
1089
                            type="string", default=None)
1090

    
1091
NIC_PARAMS_OPT = cli_option("-N", "--nic-parameters", dest="nicparams",
1092
                            type="keyval", default={},
1093
                            help="NIC parameters")
1094

    
1095
CP_SIZE_OPT = cli_option("-C", "--candidate-pool-size", default=None,
1096
                         dest="candidate_pool_size", type="int",
1097
                         help="Set the candidate pool size")
1098

    
1099
VG_NAME_OPT = cli_option("--vg-name", dest="vg_name",
1100
                         help=("Enables LVM and specifies the volume group"
1101
                               " name (cluster-wide) for disk allocation"
1102
                               " [%s]" % constants.DEFAULT_VG),
1103
                         metavar="VG", default=None)
1104

    
1105
YES_DOIT_OPT = cli_option("--yes-do-it", "--ya-rly", dest="yes_do_it",
1106
                          help="Destroy cluster", action="store_true")
1107

    
1108
NOVOTING_OPT = cli_option("--no-voting", dest="no_voting",
1109
                          help="Skip node agreement check (dangerous)",
1110
                          action="store_true", default=False)
1111

    
1112
MAC_PREFIX_OPT = cli_option("-m", "--mac-prefix", dest="mac_prefix",
1113
                            help="Specify the mac prefix for the instance IP"
1114
                            " addresses, in the format XX:XX:XX",
1115
                            metavar="PREFIX",
1116
                            default=None)
1117

    
1118
MASTER_NETDEV_OPT = cli_option("--master-netdev", dest="master_netdev",
1119
                               help="Specify the node interface (cluster-wide)"
1120
                               " on which the master IP address will be added"
1121
                               " (cluster init default: %s)" %
1122
                               constants.DEFAULT_BRIDGE,
1123
                               metavar="NETDEV",
1124
                               default=None)
1125

    
1126
MASTER_NETMASK_OPT = cli_option("--master-netmask", dest="master_netmask",
1127
                                help="Specify the netmask of the master IP",
1128
                                metavar="NETMASK",
1129
                                default=None)
1130

    
1131
USE_EXTERNAL_MIP_SCRIPT = cli_option("--use-external-mip-script",
1132
                                dest="use_external_mip_script",
1133
                                help="Specify whether to run a user-provided"
1134
                                " script for the master IP address turnup and"
1135
                                " turndown operations",
1136
                                type="bool", metavar=_YORNO, default=None)
1137

    
1138
GLOBAL_FILEDIR_OPT = cli_option("--file-storage-dir", dest="file_storage_dir",
1139
                                help="Specify the default directory (cluster-"
1140
                                "wide) for storing the file-based disks [%s]" %
1141
                                constants.DEFAULT_FILE_STORAGE_DIR,
1142
                                metavar="DIR",
1143
                                default=constants.DEFAULT_FILE_STORAGE_DIR)
1144

    
1145
GLOBAL_SHARED_FILEDIR_OPT = cli_option("--shared-file-storage-dir",
1146
                            dest="shared_file_storage_dir",
1147
                            help="Specify the default directory (cluster-"
1148
                            "wide) for storing the shared file-based"
1149
                            " disks [%s]" %
1150
                            constants.DEFAULT_SHARED_FILE_STORAGE_DIR,
1151
                            metavar="SHAREDDIR",
1152
                            default=constants.DEFAULT_SHARED_FILE_STORAGE_DIR)
1153

    
1154
NOMODIFY_ETCHOSTS_OPT = cli_option("--no-etc-hosts", dest="modify_etc_hosts",
1155
                                   help="Don't modify /etc/hosts",
1156
                                   action="store_false", default=True)
1157

    
1158
NOMODIFY_SSH_SETUP_OPT = cli_option("--no-ssh-init", dest="modify_ssh_setup",
1159
                                    help="Don't initialize SSH keys",
1160
                                    action="store_false", default=True)
1161

    
1162
ERROR_CODES_OPT = cli_option("--error-codes", dest="error_codes",
1163
                             help="Enable parseable error messages",
1164
                             action="store_true", default=False)
1165

    
1166
NONPLUS1_OPT = cli_option("--no-nplus1-mem", dest="skip_nplusone_mem",
1167
                          help="Skip N+1 memory redundancy tests",
1168
                          action="store_true", default=False)
1169

    
1170
REBOOT_TYPE_OPT = cli_option("-t", "--type", dest="reboot_type",
1171
                             help="Type of reboot: soft/hard/full",
1172
                             default=constants.INSTANCE_REBOOT_HARD,
1173
                             metavar="<REBOOT>",
1174
                             choices=list(constants.REBOOT_TYPES))
1175

    
1176
IGNORE_SECONDARIES_OPT = cli_option("--ignore-secondaries",
1177
                                    dest="ignore_secondaries",
1178
                                    default=False, action="store_true",
1179
                                    help="Ignore errors from secondaries")
1180

    
1181
NOSHUTDOWN_OPT = cli_option("--noshutdown", dest="shutdown",
1182
                            action="store_false", default=True,
1183
                            help="Don't shutdown the instance (unsafe)")
1184

    
1185
TIMEOUT_OPT = cli_option("--timeout", dest="timeout", type="int",
1186
                         default=constants.DEFAULT_SHUTDOWN_TIMEOUT,
1187
                         help="Maximum time to wait")
1188

    
1189
SHUTDOWN_TIMEOUT_OPT = cli_option("--shutdown-timeout",
1190
                         dest="shutdown_timeout", type="int",
1191
                         default=constants.DEFAULT_SHUTDOWN_TIMEOUT,
1192
                         help="Maximum time to wait for instance shutdown")
1193

    
1194
INTERVAL_OPT = cli_option("--interval", dest="interval", type="int",
1195
                          default=None,
1196
                          help=("Number of seconds between repetions of the"
1197
                                " command"))
1198

    
1199
EARLY_RELEASE_OPT = cli_option("--early-release",
1200
                               dest="early_release", default=False,
1201
                               action="store_true",
1202
                               help="Release the locks on the secondary"
1203
                               " node(s) early")
1204

    
1205
NEW_CLUSTER_CERT_OPT = cli_option("--new-cluster-certificate",
1206
                                  dest="new_cluster_cert",
1207
                                  default=False, action="store_true",
1208
                                  help="Generate a new cluster certificate")
1209

    
1210
RAPI_CERT_OPT = cli_option("--rapi-certificate", dest="rapi_cert",
1211
                           default=None,
1212
                           help="File containing new RAPI certificate")
1213

    
1214
NEW_RAPI_CERT_OPT = cli_option("--new-rapi-certificate", dest="new_rapi_cert",
1215
                               default=None, action="store_true",
1216
                               help=("Generate a new self-signed RAPI"
1217
                                     " certificate"))
1218

    
1219
SPICE_CERT_OPT = cli_option("--spice-certificate", dest="spice_cert",
1220
                           default=None,
1221
                           help="File containing new SPICE certificate")
1222

    
1223
SPICE_CACERT_OPT = cli_option("--spice-ca-certificate", dest="spice_cacert",
1224
                           default=None,
1225
                           help="File containing the certificate of the CA"
1226
                                " which signed the SPICE certificate")
1227

    
1228
NEW_SPICE_CERT_OPT = cli_option("--new-spice-certificate",
1229
                               dest="new_spice_cert", default=None,
1230
                               action="store_true",
1231
                               help=("Generate a new self-signed SPICE"
1232
                                     " certificate"))
1233

    
1234
NEW_CONFD_HMAC_KEY_OPT = cli_option("--new-confd-hmac-key",
1235
                                    dest="new_confd_hmac_key",
1236
                                    default=False, action="store_true",
1237
                                    help=("Create a new HMAC key for %s" %
1238
                                          constants.CONFD))
1239

    
1240
CLUSTER_DOMAIN_SECRET_OPT = cli_option("--cluster-domain-secret",
1241
                                       dest="cluster_domain_secret",
1242
                                       default=None,
1243
                                       help=("Load new new cluster domain"
1244
                                             " secret from file"))
1245

    
1246
NEW_CLUSTER_DOMAIN_SECRET_OPT = cli_option("--new-cluster-domain-secret",
1247
                                           dest="new_cluster_domain_secret",
1248
                                           default=False, action="store_true",
1249
                                           help=("Create a new cluster domain"
1250
                                                 " secret"))
1251

    
1252
USE_REPL_NET_OPT = cli_option("--use-replication-network",
1253
                              dest="use_replication_network",
1254
                              help="Whether to use the replication network"
1255
                              " for talking to the nodes",
1256
                              action="store_true", default=False)
1257

    
1258
MAINTAIN_NODE_HEALTH_OPT = \
1259
    cli_option("--maintain-node-health", dest="maintain_node_health",
1260
               metavar=_YORNO, default=None, type="bool",
1261
               help="Configure the cluster to automatically maintain node"
1262
               " health, by shutting down unknown instances, shutting down"
1263
               " unknown DRBD devices, etc.")
1264

    
1265
IDENTIFY_DEFAULTS_OPT = \
1266
    cli_option("--identify-defaults", dest="identify_defaults",
1267
               default=False, action="store_true",
1268
               help="Identify which saved instance parameters are equal to"
1269
               " the current cluster defaults and set them as such, instead"
1270
               " of marking them as overridden")
1271

    
1272
UIDPOOL_OPT = cli_option("--uid-pool", default=None,
1273
                         action="store", dest="uid_pool",
1274
                         help=("A list of user-ids or user-id"
1275
                               " ranges separated by commas"))
1276

    
1277
ADD_UIDS_OPT = cli_option("--add-uids", default=None,
1278
                          action="store", dest="add_uids",
1279
                          help=("A list of user-ids or user-id"
1280
                                " ranges separated by commas, to be"
1281
                                " added to the user-id pool"))
1282

    
1283
REMOVE_UIDS_OPT = cli_option("--remove-uids", default=None,
1284
                             action="store", dest="remove_uids",
1285
                             help=("A list of user-ids or user-id"
1286
                                   " ranges separated by commas, to be"
1287
                                   " removed from the user-id pool"))
1288

    
1289
RESERVED_LVS_OPT = cli_option("--reserved-lvs", default=None,
1290
                             action="store", dest="reserved_lvs",
1291
                             help=("A comma-separated list of reserved"
1292
                                   " logical volumes names, that will be"
1293
                                   " ignored by cluster verify"))
1294

    
1295
ROMAN_OPT = cli_option("--roman",
1296
                       dest="roman_integers", default=False,
1297
                       action="store_true",
1298
                       help="Use roman numbers for positive integers")
1299

    
1300
DRBD_HELPER_OPT = cli_option("--drbd-usermode-helper", dest="drbd_helper",
1301
                             action="store", default=None,
1302
                             help="Specifies usermode helper for DRBD")
1303

    
1304
NODRBD_STORAGE_OPT = cli_option("--no-drbd-storage", dest="drbd_storage",
1305
                                action="store_false", default=True,
1306
                                help="Disable support for DRBD")
1307

    
1308
PRIMARY_IP_VERSION_OPT = \
1309
    cli_option("--primary-ip-version", default=constants.IP4_VERSION,
1310
               action="store", dest="primary_ip_version",
1311
               metavar="%d|%d" % (constants.IP4_VERSION,
1312
                                  constants.IP6_VERSION),
1313
               help="Cluster-wide IP version for primary IP")
1314

    
1315
PRIORITY_OPT = cli_option("--priority", default=None, dest="priority",
1316
                          metavar="|".join(name for name, _ in _PRIORITY_NAMES),
1317
                          choices=_PRIONAME_TO_VALUE.keys(),
1318
                          help="Priority for opcode processing")
1319

    
1320
HID_OS_OPT = cli_option("--hidden", dest="hidden",
1321
                        type="bool", default=None, metavar=_YORNO,
1322
                        help="Sets the hidden flag on the OS")
1323

    
1324
BLK_OS_OPT = cli_option("--blacklisted", dest="blacklisted",
1325
                        type="bool", default=None, metavar=_YORNO,
1326
                        help="Sets the blacklisted flag on the OS")
1327

    
1328
PREALLOC_WIPE_DISKS_OPT = cli_option("--prealloc-wipe-disks", default=None,
1329
                                     type="bool", metavar=_YORNO,
1330
                                     dest="prealloc_wipe_disks",
1331
                                     help=("Wipe disks prior to instance"
1332
                                           " creation"))
1333

    
1334
NODE_PARAMS_OPT = cli_option("--node-parameters", dest="ndparams",
1335
                             type="keyval", default=None,
1336
                             help="Node parameters")
1337

    
1338
ALLOC_POLICY_OPT = cli_option("--alloc-policy", dest="alloc_policy",
1339
                              action="store", metavar="POLICY", default=None,
1340
                              help="Allocation policy for the node group")
1341

    
1342
NODE_POWERED_OPT = cli_option("--node-powered", default=None,
1343
                              type="bool", metavar=_YORNO,
1344
                              dest="node_powered",
1345
                              help="Specify if the SoR for node is powered")
1346

    
1347
OOB_TIMEOUT_OPT = cli_option("--oob-timeout", dest="oob_timeout", type="int",
1348
                         default=constants.OOB_TIMEOUT,
1349
                         help="Maximum time to wait for out-of-band helper")
1350

    
1351
POWER_DELAY_OPT = cli_option("--power-delay", dest="power_delay", type="float",
1352
                             default=constants.OOB_POWER_DELAY,
1353
                             help="Time in seconds to wait between power-ons")
1354

    
1355
FORCE_FILTER_OPT = cli_option("-F", "--filter", dest="force_filter",
1356
                              action="store_true", default=False,
1357
                              help=("Whether command argument should be treated"
1358
                                    " as filter"))
1359

    
1360
NO_REMEMBER_OPT = cli_option("--no-remember",
1361
                             dest="no_remember",
1362
                             action="store_true", default=False,
1363
                             help="Perform but do not record the change"
1364
                             " in the configuration")
1365

    
1366
PRIMARY_ONLY_OPT = cli_option("-p", "--primary-only",
1367
                              default=False, action="store_true",
1368
                              help="Evacuate primary instances only")
1369

    
1370
SECONDARY_ONLY_OPT = cli_option("-s", "--secondary-only",
1371
                                default=False, action="store_true",
1372
                                help="Evacuate secondary instances only"
1373
                                     " (applies only to internally mirrored"
1374
                                     " disk templates, e.g. %s)" %
1375
                                     utils.CommaJoin(constants.DTS_INT_MIRROR))
1376

    
1377
STARTUP_PAUSED_OPT = cli_option("--paused", dest="startup_paused",
1378
                                action="store_true", default=False,
1379
                                help="Pause instance at startup")
1380

    
1381
TO_GROUP_OPT = cli_option("--to", dest="to", metavar="<group>",
1382
                          help="Destination node group (name or uuid)",
1383
                          default=None, action="append",
1384
                          completion_suggest=OPT_COMPL_ONE_NODEGROUP)
1385

    
1386
IGNORE_ERRORS_OPT = cli_option("-I", "--ignore-errors", default=[],
1387
                               action="append", dest="ignore_errors",
1388
                               choices=list(constants.CV_ALL_ECODES_STRINGS),
1389
                               help="Error code to be ignored")
1390

    
1391
DISK_STATE_OPT = cli_option("--disk-state", default=[], dest="disk_state",
1392
                            action="append",
1393
                            help=("Specify disk state information in the format"
1394
                                  " storage_type/identifier:option=value,..."),
1395
                            type="identkeyval")
1396

    
1397
HV_STATE_OPT = cli_option("--hypervisor-state", default=[], dest="hv_state",
1398
                          action="append",
1399
                          help=("Specify hypervisor state information in the"
1400
                                " format hypervisor:option=value,..."),
1401
                          type="identkeyval")
1402

    
1403
IGNORE_IPOLICY_OPT = cli_option("--ignore-ipolicy", dest="ignore_ipolicy",
1404
                                action="store_true", default=False,
1405
                                help="Ignore instance policy violations")
1406

    
1407
RUNTIME_MEM_OPT = cli_option("-m", "--runtime-memory", dest="runtime_mem",
1408
                             help="Sets the instance's runtime memory,"
1409
                             " ballooning it up or down to the new value",
1410
                             default=None, type="unit", metavar="<size>")
1411

    
1412
ABSOLUTE_OPT = cli_option("--absolute", dest="absolute",
1413
                          action="store_true", default=False,
1414
                          help="Marks the grow as absolute instead of the"
1415
                          " (default) relative mode")
1416

    
1417
#: Options provided by all commands
1418
COMMON_OPTS = [DEBUG_OPT]
1419

    
1420
# common options for creating instances. add and import then add their own
1421
# specific ones.
1422
COMMON_CREATE_OPTS = [
1423
  BACKEND_OPT,
1424
  DISK_OPT,
1425
  DISK_TEMPLATE_OPT,
1426
  FILESTORE_DIR_OPT,
1427
  FILESTORE_DRIVER_OPT,
1428
  HYPERVISOR_OPT,
1429
  IALLOCATOR_OPT,
1430
  NET_OPT,
1431
  NODE_PLACEMENT_OPT,
1432
  NOIPCHECK_OPT,
1433
  NONAMECHECK_OPT,
1434
  NONICS_OPT,
1435
  NWSYNC_OPT,
1436
  OSPARAMS_OPT,
1437
  OS_SIZE_OPT,
1438
  SUBMIT_OPT,
1439
  TAG_ADD_OPT,
1440
  DRY_RUN_OPT,
1441
  PRIORITY_OPT,
1442
  ]
1443

    
1444
# common instance policy options
1445
INSTANCE_POLICY_OPTS = [
1446
  SPECS_CPU_COUNT_OPT,
1447
  SPECS_DISK_COUNT_OPT,
1448
  SPECS_DISK_SIZE_OPT,
1449
  SPECS_MEM_SIZE_OPT,
1450
  SPECS_NIC_COUNT_OPT,
1451
  IPOLICY_DISK_TEMPLATES,
1452
  IPOLICY_VCPU_RATIO,
1453
  IPOLICY_SPINDLE_RATIO,
1454
  ]
1455

    
1456

    
1457
def _ParseArgs(argv, commands, aliases, env_override):
1458
  """Parser for the command line arguments.
1459

1460
  This function parses the arguments and returns the function which
1461
  must be executed together with its (modified) arguments.
1462

1463
  @param argv: the command line
1464
  @param commands: dictionary with special contents, see the design
1465
      doc for cmdline handling
1466
  @param aliases: dictionary with command aliases {'alias': 'target, ...}
1467
  @param env_override: list of env variables allowed for default args
1468

1469
  """
1470
  assert not (env_override - set(commands))
1471

    
1472
  if len(argv) == 0:
1473
    binary = "<command>"
1474
  else:
1475
    binary = argv[0].split("/")[-1]
1476

    
1477
  if len(argv) > 1 and argv[1] == "--version":
1478
    ToStdout("%s (ganeti %s) %s", binary, constants.VCS_VERSION,
1479
             constants.RELEASE_VERSION)
1480
    # Quit right away. That way we don't have to care about this special
1481
    # argument. optparse.py does it the same.
1482
    sys.exit(0)
1483

    
1484
  if len(argv) < 2 or not (argv[1] in commands or
1485
                           argv[1] in aliases):
1486
    # let's do a nice thing
1487
    sortedcmds = commands.keys()
1488
    sortedcmds.sort()
1489

    
1490
    ToStdout("Usage: %s {command} [options...] [argument...]", binary)
1491
    ToStdout("%s <command> --help to see details, or man %s", binary, binary)
1492
    ToStdout("")
1493

    
1494
    # compute the max line length for cmd + usage
1495
    mlen = max([len(" %s" % cmd) for cmd in commands])
1496
    mlen = min(60, mlen) # should not get here...
1497

    
1498
    # and format a nice command list
1499
    ToStdout("Commands:")
1500
    for cmd in sortedcmds:
1501
      cmdstr = " %s" % (cmd,)
1502
      help_text = commands[cmd][4]
1503
      help_lines = textwrap.wrap(help_text, 79 - 3 - mlen)
1504
      ToStdout("%-*s - %s", mlen, cmdstr, help_lines.pop(0))
1505
      for line in help_lines:
1506
        ToStdout("%-*s   %s", mlen, "", line)
1507

    
1508
    ToStdout("")
1509

    
1510
    return None, None, None
1511

    
1512
  # get command, unalias it, and look it up in commands
1513
  cmd = argv.pop(1)
1514
  if cmd in aliases:
1515
    if cmd in commands:
1516
      raise errors.ProgrammerError("Alias '%s' overrides an existing"
1517
                                   " command" % cmd)
1518

    
1519
    if aliases[cmd] not in commands:
1520
      raise errors.ProgrammerError("Alias '%s' maps to non-existing"
1521
                                   " command '%s'" % (cmd, aliases[cmd]))
1522

    
1523
    cmd = aliases[cmd]
1524

    
1525
  if cmd in env_override:
1526
    args_env_name = ("%s_%s" % (binary.replace("-", "_"), cmd)).upper()
1527
    env_args = os.environ.get(args_env_name)
1528
    if env_args:
1529
      argv = utils.InsertAtPos(argv, 1, shlex.split(env_args))
1530

    
1531
  func, args_def, parser_opts, usage, description = commands[cmd]
1532
  parser = OptionParser(option_list=parser_opts + COMMON_OPTS,
1533
                        description=description,
1534
                        formatter=TitledHelpFormatter(),
1535
                        usage="%%prog %s %s" % (cmd, usage))
1536
  parser.disable_interspersed_args()
1537
  options, args = parser.parse_args(args=argv[1:])
1538

    
1539
  if not _CheckArguments(cmd, args_def, args):
1540
    return None, None, None
1541

    
1542
  return func, options, args
1543

    
1544

    
1545
def _CheckArguments(cmd, args_def, args):
1546
  """Verifies the arguments using the argument definition.
1547

1548
  Algorithm:
1549

1550
    1. Abort with error if values specified by user but none expected.
1551

1552
    1. For each argument in definition
1553

1554
      1. Keep running count of minimum number of values (min_count)
1555
      1. Keep running count of maximum number of values (max_count)
1556
      1. If it has an unlimited number of values
1557

1558
        1. Abort with error if it's not the last argument in the definition
1559

1560
    1. If last argument has limited number of values
1561

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

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

1566
  """
1567
  if args and not args_def:
1568
    ToStderr("Error: Command %s expects no arguments", cmd)
1569
    return False
1570

    
1571
  min_count = None
1572
  max_count = None
1573
  check_max = None
1574

    
1575
  last_idx = len(args_def) - 1
1576

    
1577
  for idx, arg in enumerate(args_def):
1578
    if min_count is None:
1579
      min_count = arg.min
1580
    elif arg.min is not None:
1581
      min_count += arg.min
1582

    
1583
    if max_count is None:
1584
      max_count = arg.max
1585
    elif arg.max is not None:
1586
      max_count += arg.max
1587

    
1588
    if idx == last_idx:
1589
      check_max = (arg.max is not None)
1590

    
1591
    elif arg.max is None:
1592
      raise errors.ProgrammerError("Only the last argument can have max=None")
1593

    
1594
  if check_max:
1595
    # Command with exact number of arguments
1596
    if (min_count is not None and max_count is not None and
1597
        min_count == max_count and len(args) != min_count):
1598
      ToStderr("Error: Command %s expects %d argument(s)", cmd, min_count)
1599
      return False
1600

    
1601
    # Command with limited number of arguments
1602
    if max_count is not None and len(args) > max_count:
1603
      ToStderr("Error: Command %s expects only %d argument(s)",
1604
               cmd, max_count)
1605
      return False
1606

    
1607
  # Command with some required arguments
1608
  if min_count is not None and len(args) < min_count:
1609
    ToStderr("Error: Command %s expects at least %d argument(s)",
1610
             cmd, min_count)
1611
    return False
1612

    
1613
  return True
1614

    
1615

    
1616
def SplitNodeOption(value):
1617
  """Splits the value of a --node option.
1618

1619
  """
1620
  if value and ":" in value:
1621
    return value.split(":", 1)
1622
  else:
1623
    return (value, None)
1624

    
1625

    
1626
def CalculateOSNames(os_name, os_variants):
1627
  """Calculates all the names an OS can be called, according to its variants.
1628

1629
  @type os_name: string
1630
  @param os_name: base name of the os
1631
  @type os_variants: list or None
1632
  @param os_variants: list of supported variants
1633
  @rtype: list
1634
  @return: list of valid names
1635

1636
  """
1637
  if os_variants:
1638
    return ["%s+%s" % (os_name, v) for v in os_variants]
1639
  else:
1640
    return [os_name]
1641

    
1642

    
1643
def ParseFields(selected, default):
1644
  """Parses the values of "--field"-like options.
1645

1646
  @type selected: string or None
1647
  @param selected: User-selected options
1648
  @type default: list
1649
  @param default: Default fields
1650

1651
  """
1652
  if selected is None:
1653
    return default
1654

    
1655
  if selected.startswith("+"):
1656
    return default + selected[1:].split(",")
1657

    
1658
  return selected.split(",")
1659

    
1660

    
1661
UsesRPC = rpc.RunWithRPC
1662

    
1663

    
1664
def AskUser(text, choices=None):
1665
  """Ask the user a question.
1666

1667
  @param text: the question to ask
1668

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

1674
  @return: one of the return values from the choices list; if input is
1675
      not possible (i.e. not running with a tty, we return the last
1676
      entry from the list
1677

1678
  """
1679
  if choices is None:
1680
    choices = [("y", True, "Perform the operation"),
1681
               ("n", False, "Do not perform the operation")]
1682
  if not choices or not isinstance(choices, list):
1683
    raise errors.ProgrammerError("Invalid choices argument to AskUser")
1684
  for entry in choices:
1685
    if not isinstance(entry, tuple) or len(entry) < 3 or entry[0] == "?":
1686
      raise errors.ProgrammerError("Invalid choices element to AskUser")
1687

    
1688
  answer = choices[-1][1]
1689
  new_text = []
1690
  for line in text.splitlines():
1691
    new_text.append(textwrap.fill(line, 70, replace_whitespace=False))
1692
  text = "\n".join(new_text)
1693
  try:
1694
    f = file("/dev/tty", "a+")
1695
  except IOError:
1696
    return answer
1697
  try:
1698
    chars = [entry[0] for entry in choices]
1699
    chars[-1] = "[%s]" % chars[-1]
1700
    chars.append("?")
1701
    maps = dict([(entry[0], entry[1]) for entry in choices])
1702
    while True:
1703
      f.write(text)
1704
      f.write("\n")
1705
      f.write("/".join(chars))
1706
      f.write(": ")
1707
      line = f.readline(2).strip().lower()
1708
      if line in maps:
1709
        answer = maps[line]
1710
        break
1711
      elif line == "?":
1712
        for entry in choices:
1713
          f.write(" %s - %s\n" % (entry[0], entry[2]))
1714
        f.write("\n")
1715
        continue
1716
  finally:
1717
    f.close()
1718
  return answer
1719

    
1720

    
1721
class JobSubmittedException(Exception):
1722
  """Job was submitted, client should exit.
1723

1724
  This exception has one argument, the ID of the job that was
1725
  submitted. The handler should print this ID.
1726

1727
  This is not an error, just a structured way to exit from clients.
1728

1729
  """
1730

    
1731

    
1732
def SendJob(ops, cl=None):
1733
  """Function to submit an opcode without waiting for the results.
1734

1735
  @type ops: list
1736
  @param ops: list of opcodes
1737
  @type cl: luxi.Client
1738
  @param cl: the luxi client to use for communicating with the master;
1739
             if None, a new client will be created
1740

1741
  """
1742
  if cl is None:
1743
    cl = GetClient()
1744

    
1745
  job_id = cl.SubmitJob(ops)
1746

    
1747
  return job_id
1748

    
1749

    
1750
def GenericPollJob(job_id, cbs, report_cbs):
1751
  """Generic job-polling function.
1752

1753
  @type job_id: number
1754
  @param job_id: Job ID
1755
  @type cbs: Instance of L{JobPollCbBase}
1756
  @param cbs: Data callbacks
1757
  @type report_cbs: Instance of L{JobPollReportCbBase}
1758
  @param report_cbs: Reporting callbacks
1759

1760
  """
1761
  prev_job_info = None
1762
  prev_logmsg_serial = None
1763

    
1764
  status = None
1765

    
1766
  while True:
1767
    result = cbs.WaitForJobChangeOnce(job_id, ["status"], prev_job_info,
1768
                                      prev_logmsg_serial)
1769
    if not result:
1770
      # job not found, go away!
1771
      raise errors.JobLost("Job with id %s lost" % job_id)
1772

    
1773
    if result == constants.JOB_NOTCHANGED:
1774
      report_cbs.ReportNotChanged(job_id, status)
1775

    
1776
      # Wait again
1777
      continue
1778

    
1779
    # Split result, a tuple of (field values, log entries)
1780
    (job_info, log_entries) = result
1781
    (status, ) = job_info
1782

    
1783
    if log_entries:
1784
      for log_entry in log_entries:
1785
        (serial, timestamp, log_type, message) = log_entry
1786
        report_cbs.ReportLogMessage(job_id, serial, timestamp,
1787
                                    log_type, message)
1788
        prev_logmsg_serial = max(prev_logmsg_serial, serial)
1789

    
1790
    # TODO: Handle canceled and archived jobs
1791
    elif status in (constants.JOB_STATUS_SUCCESS,
1792
                    constants.JOB_STATUS_ERROR,
1793
                    constants.JOB_STATUS_CANCELING,
1794
                    constants.JOB_STATUS_CANCELED):
1795
      break
1796

    
1797
    prev_job_info = job_info
1798

    
1799
  jobs = cbs.QueryJobs([job_id], ["status", "opstatus", "opresult"])
1800
  if not jobs:
1801
    raise errors.JobLost("Job with id %s lost" % job_id)
1802

    
1803
  status, opstatus, result = jobs[0]
1804

    
1805
  if status == constants.JOB_STATUS_SUCCESS:
1806
    return result
1807

    
1808
  if status in (constants.JOB_STATUS_CANCELING, constants.JOB_STATUS_CANCELED):
1809
    raise errors.OpExecError("Job was canceled")
1810

    
1811
  has_ok = False
1812
  for idx, (status, msg) in enumerate(zip(opstatus, result)):
1813
    if status == constants.OP_STATUS_SUCCESS:
1814
      has_ok = True
1815
    elif status == constants.OP_STATUS_ERROR:
1816
      errors.MaybeRaise(msg)
1817

    
1818
      if has_ok:
1819
        raise errors.OpExecError("partial failure (opcode %d): %s" %
1820
                                 (idx, msg))
1821

    
1822
      raise errors.OpExecError(str(msg))
1823

    
1824
  # default failure mode
1825
  raise errors.OpExecError(result)
1826

    
1827

    
1828
class JobPollCbBase:
1829
  """Base class for L{GenericPollJob} callbacks.
1830

1831
  """
1832
  def __init__(self):
1833
    """Initializes this class.
1834

1835
    """
1836

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

1841
    """
1842
    raise NotImplementedError()
1843

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

1847
    @type job_ids: list of numbers
1848
    @param job_ids: Job IDs
1849
    @type fields: list of strings
1850
    @param fields: Fields
1851

1852
    """
1853
    raise NotImplementedError()
1854

    
1855

    
1856
class JobPollReportCbBase:
1857
  """Base class for L{GenericPollJob} reporting callbacks.
1858

1859
  """
1860
  def __init__(self):
1861
    """Initializes this class.
1862

1863
    """
1864

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

1868
    """
1869
    raise NotImplementedError()
1870

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

1874
    @type job_id: number
1875
    @param job_id: Job ID
1876
    @type status: string or None
1877
    @param status: Job status if available
1878

1879
    """
1880
    raise NotImplementedError()
1881

    
1882

    
1883
class _LuxiJobPollCb(JobPollCbBase):
1884
  def __init__(self, cl):
1885
    """Initializes this class.
1886

1887
    """
1888
    JobPollCbBase.__init__(self)
1889
    self.cl = cl
1890

    
1891
  def WaitForJobChangeOnce(self, job_id, fields,
1892
                           prev_job_info, prev_log_serial):
1893
    """Waits for changes on a job.
1894

1895
    """
1896
    return self.cl.WaitForJobChangeOnce(job_id, fields,
1897
                                        prev_job_info, prev_log_serial)
1898

    
1899
  def QueryJobs(self, job_ids, fields):
1900
    """Returns the selected fields for the selected job IDs.
1901

1902
    """
1903
    return self.cl.QueryJobs(job_ids, fields)
1904

    
1905

    
1906
class FeedbackFnJobPollReportCb(JobPollReportCbBase):
1907
  def __init__(self, feedback_fn):
1908
    """Initializes this class.
1909

1910
    """
1911
    JobPollReportCbBase.__init__(self)
1912

    
1913
    self.feedback_fn = feedback_fn
1914

    
1915
    assert callable(feedback_fn)
1916

    
1917
  def ReportLogMessage(self, job_id, serial, timestamp, log_type, log_msg):
1918
    """Handles a log message.
1919

1920
    """
1921
    self.feedback_fn((timestamp, log_type, log_msg))
1922

    
1923
  def ReportNotChanged(self, job_id, status):
1924
    """Called if a job hasn't changed in a while.
1925

1926
    """
1927
    # Ignore
1928

    
1929

    
1930
class StdioJobPollReportCb(JobPollReportCbBase):
1931
  def __init__(self):
1932
    """Initializes this class.
1933

1934
    """
1935
    JobPollReportCbBase.__init__(self)
1936

    
1937
    self.notified_queued = False
1938
    self.notified_waitlock = False
1939

    
1940
  def ReportLogMessage(self, job_id, serial, timestamp, log_type, log_msg):
1941
    """Handles a log message.
1942

1943
    """
1944
    ToStdout("%s %s", time.ctime(utils.MergeTime(timestamp)),
1945
             FormatLogMessage(log_type, log_msg))
1946

    
1947
  def ReportNotChanged(self, job_id, status):
1948
    """Called if a job hasn't changed in a while.
1949

1950
    """
1951
    if status is None:
1952
      return
1953

    
1954
    if status == constants.JOB_STATUS_QUEUED and not self.notified_queued:
1955
      ToStderr("Job %s is waiting in queue", job_id)
1956
      self.notified_queued = True
1957

    
1958
    elif status == constants.JOB_STATUS_WAITING and not self.notified_waitlock:
1959
      ToStderr("Job %s is trying to acquire all necessary locks", job_id)
1960
      self.notified_waitlock = True
1961

    
1962

    
1963
def FormatLogMessage(log_type, log_msg):
1964
  """Formats a job message according to its type.
1965

1966
  """
1967
  if log_type != constants.ELOG_MESSAGE:
1968
    log_msg = str(log_msg)
1969

    
1970
  return utils.SafeEncode(log_msg)
1971

    
1972

    
1973
def PollJob(job_id, cl=None, feedback_fn=None, reporter=None):
1974
  """Function to poll for the result of a job.
1975

1976
  @type job_id: job identified
1977
  @param job_id: the job to poll for results
1978
  @type cl: luxi.Client
1979
  @param cl: the luxi client to use for communicating with the master;
1980
             if None, a new client will be created
1981

1982
  """
1983
  if cl is None:
1984
    cl = GetClient()
1985

    
1986
  if reporter is None:
1987
    if feedback_fn:
1988
      reporter = FeedbackFnJobPollReportCb(feedback_fn)
1989
    else:
1990
      reporter = StdioJobPollReportCb()
1991
  elif feedback_fn:
1992
    raise errors.ProgrammerError("Can't specify reporter and feedback function")
1993

    
1994
  return GenericPollJob(job_id, _LuxiJobPollCb(cl), reporter)
1995

    
1996

    
1997
def SubmitOpCode(op, cl=None, feedback_fn=None, opts=None, reporter=None):
1998
  """Legacy function to submit an opcode.
1999

2000
  This is just a simple wrapper over the construction of the processor
2001
  instance. It should be extended to better handle feedback and
2002
  interaction functions.
2003

2004
  """
2005
  if cl is None:
2006
    cl = GetClient()
2007

    
2008
  SetGenericOpcodeOpts([op], opts)
2009

    
2010
  job_id = SendJob([op], cl=cl)
2011

    
2012
  op_results = PollJob(job_id, cl=cl, feedback_fn=feedback_fn,
2013
                       reporter=reporter)
2014

    
2015
  return op_results[0]
2016

    
2017

    
2018
def SubmitOrSend(op, opts, cl=None, feedback_fn=None):
2019
  """Wrapper around SubmitOpCode or SendJob.
2020

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

2026
  It will also process the opcodes if we're sending the via SendJob
2027
  (otherwise SubmitOpCode does it).
2028

2029
  """
2030
  if opts and opts.submit_only:
2031
    job = [op]
2032
    SetGenericOpcodeOpts(job, opts)
2033
    job_id = SendJob(job, cl=cl)
2034
    raise JobSubmittedException(job_id)
2035
  else:
2036
    return SubmitOpCode(op, cl=cl, feedback_fn=feedback_fn, opts=opts)
2037

    
2038

    
2039
def SetGenericOpcodeOpts(opcode_list, options):
2040
  """Processor for generic options.
2041

2042
  This function updates the given opcodes based on generic command
2043
  line options (like debug, dry-run, etc.).
2044

2045
  @param opcode_list: list of opcodes
2046
  @param options: command line options or None
2047
  @return: None (in-place modification)
2048

2049
  """
2050
  if not options:
2051
    return
2052
  for op in opcode_list:
2053
    op.debug_level = options.debug
2054
    if hasattr(options, "dry_run"):
2055
      op.dry_run = options.dry_run
2056
    if getattr(options, "priority", None) is not None:
2057
      op.priority = _PRIONAME_TO_VALUE[options.priority]
2058

    
2059

    
2060
def GetClient():
2061
  # TODO: Cache object?
2062
  try:
2063
    client = luxi.Client()
2064
  except luxi.NoMasterError:
2065
    ss = ssconf.SimpleStore()
2066

    
2067
    # Try to read ssconf file
2068
    try:
2069
      ss.GetMasterNode()
2070
    except errors.ConfigurationError:
2071
      raise errors.OpPrereqError("Cluster not initialized or this machine is"
2072
                                 " not part of a cluster")
2073

    
2074
    master, myself = ssconf.GetMasterAndMyself(ss=ss)
2075
    if master != myself:
2076
      raise errors.OpPrereqError("This is not the master node, please connect"
2077
                                 " to node '%s' and rerun the command" %
2078
                                 master)
2079
    raise
2080
  return client
2081

    
2082

    
2083
def FormatError(err):
2084
  """Return a formatted error message for a given error.
2085

2086
  This function takes an exception instance and returns a tuple
2087
  consisting of two values: first, the recommended exit code, and
2088
  second, a string describing the error message (not
2089
  newline-terminated).
2090

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

    
2170

    
2171
def GenericMain(commands, override=None, aliases=None,
2172
                env_override=frozenset()):
2173
  """Generic main function for all the gnt-* commands.
2174

2175
  @param commands: a dictionary with a special structure, see the design doc
2176
                   for command line handling.
2177
  @param override: if not None, we expect a dictionary with keys that will
2178
                   override command line options; this can be used to pass
2179
                   options from the scripts to generic functions
2180
  @param aliases: dictionary with command aliases {'alias': 'target, ...}
2181
  @param env_override: list of environment names which are allowed to submit
2182
                       default args for commands
2183

2184
  """
2185
  # save the program name and the entire command line for later logging
2186
  if sys.argv:
2187
    binary = os.path.basename(sys.argv[0])
2188
    if not binary:
2189
      binary = sys.argv[0]
2190

    
2191
    if len(sys.argv) >= 2:
2192
      logname = utils.ShellQuoteArgs([binary, sys.argv[1]])
2193
    else:
2194
      logname = binary
2195

    
2196
    cmdline = utils.ShellQuoteArgs([binary] + sys.argv[1:])
2197
  else:
2198
    binary = "<unknown program>"
2199
    cmdline = "<unknown>"
2200

    
2201
  if aliases is None:
2202
    aliases = {}
2203

    
2204
  try:
2205
    func, options, args = _ParseArgs(sys.argv, commands, aliases, env_override)
2206
  except errors.ParameterError, err:
2207
    result, err_msg = FormatError(err)
2208
    ToStderr(err_msg)
2209
    return 1
2210

    
2211
  if func is None: # parse error
2212
    return 1
2213

    
2214
  if override is not None:
2215
    for key, val in override.iteritems():
2216
      setattr(options, key, val)
2217

    
2218
  utils.SetupLogging(constants.LOG_COMMANDS, logname, debug=options.debug,
2219
                     stderr_logging=True)
2220

    
2221
  logging.info("Command line: %s", cmdline)
2222

    
2223
  try:
2224
    result = func(options, args)
2225
  except (errors.GenericError, luxi.ProtocolError,
2226
          JobSubmittedException), err:
2227
    result, err_msg = FormatError(err)
2228
    logging.exception("Error during command processing")
2229
    ToStderr(err_msg)
2230
  except KeyboardInterrupt:
2231
    result = constants.EXIT_FAILURE
2232
    ToStderr("Aborted. Note that if the operation created any jobs, they"
2233
             " might have been submitted and"
2234
             " will continue to run in the background.")
2235
  except IOError, err:
2236
    if err.errno == errno.EPIPE:
2237
      # our terminal went away, we'll exit
2238
      sys.exit(constants.EXIT_FAILURE)
2239
    else:
2240
      raise
2241

    
2242
  return result
2243

    
2244

    
2245
def ParseNicOption(optvalue):
2246
  """Parses the value of the --net option(s).
2247

2248
  """
2249
  try:
2250
    nic_max = max(int(nidx[0]) + 1 for nidx in optvalue)
2251
  except (TypeError, ValueError), err:
2252
    raise errors.OpPrereqError("Invalid NIC index passed: %s" % str(err))
2253

    
2254
  nics = [{}] * nic_max
2255
  for nidx, ndict in optvalue:
2256
    nidx = int(nidx)
2257

    
2258
    if not isinstance(ndict, dict):
2259
      raise errors.OpPrereqError("Invalid nic/%d value: expected dict,"
2260
                                 " got %s" % (nidx, ndict))
2261

    
2262
    utils.ForceDictType(ndict, constants.INIC_PARAMS_TYPES)
2263

    
2264
    nics[nidx] = ndict
2265

    
2266
  return nics
2267

    
2268

    
2269
def GenericInstanceCreate(mode, opts, args):
2270
  """Add an instance to the cluster via either creation or import.
2271

2272
  @param mode: constants.INSTANCE_CREATE or constants.INSTANCE_IMPORT
2273
  @param opts: the command line options selected by the user
2274
  @type args: list
2275
  @param args: should contain only one element, the new instance name
2276
  @rtype: int
2277
  @return: the desired exit code
2278

2279
  """
2280
  instance = args[0]
2281

    
2282
  (pnode, snode) = SplitNodeOption(opts.node)
2283

    
2284
  hypervisor = None
2285
  hvparams = {}
2286
  if opts.hypervisor:
2287
    hypervisor, hvparams = opts.hypervisor
2288

    
2289
  if opts.nics:
2290
    nics = ParseNicOption(opts.nics)
2291
  elif opts.no_nics:
2292
    # no nics
2293
    nics = []
2294
  elif mode == constants.INSTANCE_CREATE:
2295
    # default of one nic, all auto
2296
    nics = [{}]
2297
  else:
2298
    # mode == import
2299
    nics = []
2300

    
2301
  if opts.disk_template == constants.DT_DISKLESS:
2302
    if opts.disks or opts.sd_size is not None:
2303
      raise errors.OpPrereqError("Diskless instance but disk"
2304
                                 " information passed")
2305
    disks = []
2306
  else:
2307
    if (not opts.disks and not opts.sd_size
2308
        and mode == constants.INSTANCE_CREATE):
2309
      raise errors.OpPrereqError("No disk information specified")
2310
    if opts.disks and opts.sd_size is not None:
2311
      raise errors.OpPrereqError("Please use either the '--disk' or"
2312
                                 " '-s' option")
2313
    if opts.sd_size is not None:
2314
      opts.disks = [(0, {constants.IDISK_SIZE: opts.sd_size})]
2315

    
2316
    if opts.disks:
2317
      try:
2318
        disk_max = max(int(didx[0]) + 1 for didx in opts.disks)
2319
      except ValueError, err:
2320
        raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err))
2321
      disks = [{}] * disk_max
2322
    else:
2323
      disks = []
2324
    for didx, ddict in opts.disks:
2325
      didx = int(didx)
2326
      if not isinstance(ddict, dict):
2327
        msg = "Invalid disk/%d value: expected dict, got %s" % (didx, ddict)
2328
        raise errors.OpPrereqError(msg)
2329
      elif constants.IDISK_SIZE in ddict:
2330
        if constants.IDISK_ADOPT in ddict:
2331
          raise errors.OpPrereqError("Only one of 'size' and 'adopt' allowed"
2332
                                     " (disk %d)" % didx)
2333
        try:
2334
          ddict[constants.IDISK_SIZE] = \
2335
            utils.ParseUnit(ddict[constants.IDISK_SIZE])
2336
        except ValueError, err:
2337
          raise errors.OpPrereqError("Invalid disk size for disk %d: %s" %
2338
                                     (didx, err))
2339
      elif constants.IDISK_ADOPT in ddict:
2340
        if mode == constants.INSTANCE_IMPORT:
2341
          raise errors.OpPrereqError("Disk adoption not allowed for instance"
2342
                                     " import")
2343
        ddict[constants.IDISK_SIZE] = 0
2344
      else:
2345
        raise errors.OpPrereqError("Missing size or adoption source for"
2346
                                   " disk %d" % didx)
2347
      disks[didx] = ddict
2348

    
2349
  if opts.tags is not None:
2350
    tags = opts.tags.split(",")
2351
  else:
2352
    tags = []
2353

    
2354
  utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_COMPAT)
2355
  utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
2356

    
2357
  if mode == constants.INSTANCE_CREATE:
2358
    start = opts.start
2359
    os_type = opts.os
2360
    force_variant = opts.force_variant
2361
    src_node = None
2362
    src_path = None
2363
    no_install = opts.no_install
2364
    identify_defaults = False
2365
  elif mode == constants.INSTANCE_IMPORT:
2366
    start = False
2367
    os_type = None
2368
    force_variant = False
2369
    src_node = opts.src_node
2370
    src_path = opts.src_dir
2371
    no_install = None
2372
    identify_defaults = opts.identify_defaults
2373
  else:
2374
    raise errors.ProgrammerError("Invalid creation mode %s" % mode)
2375

    
2376
  op = opcodes.OpInstanceCreate(instance_name=instance,
2377
                                disks=disks,
2378
                                disk_template=opts.disk_template,
2379
                                nics=nics,
2380
                                pnode=pnode, snode=snode,
2381
                                ip_check=opts.ip_check,
2382
                                name_check=opts.name_check,
2383
                                wait_for_sync=opts.wait_for_sync,
2384
                                file_storage_dir=opts.file_storage_dir,
2385
                                file_driver=opts.file_driver,
2386
                                iallocator=opts.iallocator,
2387
                                hypervisor=hypervisor,
2388
                                hvparams=hvparams,
2389
                                beparams=opts.beparams,
2390
                                osparams=opts.osparams,
2391
                                mode=mode,
2392
                                start=start,
2393
                                os_type=os_type,
2394
                                force_variant=force_variant,
2395
                                src_node=src_node,
2396
                                src_path=src_path,
2397
                                tags=tags,
2398
                                no_install=no_install,
2399
                                identify_defaults=identify_defaults,
2400
                                ignore_ipolicy=opts.ignore_ipolicy)
2401

    
2402
  SubmitOrSend(op, opts)
2403
  return 0
2404

    
2405

    
2406
class _RunWhileClusterStoppedHelper:
2407
  """Helper class for L{RunWhileClusterStopped} to simplify state management
2408

2409
  """
2410
  def __init__(self, feedback_fn, cluster_name, master_node, online_nodes):
2411
    """Initializes this class.
2412

2413
    @type feedback_fn: callable
2414
    @param feedback_fn: Feedback function
2415
    @type cluster_name: string
2416
    @param cluster_name: Cluster name
2417
    @type master_node: string
2418
    @param master_node Master node name
2419
    @type online_nodes: list
2420
    @param online_nodes: List of names of online nodes
2421

2422
    """
2423
    self.feedback_fn = feedback_fn
2424
    self.cluster_name = cluster_name
2425
    self.master_node = master_node
2426
    self.online_nodes = online_nodes
2427

    
2428
    self.ssh = ssh.SshRunner(self.cluster_name)
2429

    
2430
    self.nonmaster_nodes = [name for name in online_nodes
2431
                            if name != master_node]
2432

    
2433
    assert self.master_node not in self.nonmaster_nodes
2434

    
2435
  def _RunCmd(self, node_name, cmd):
2436
    """Runs a command on the local or a remote machine.
2437

2438
    @type node_name: string
2439
    @param node_name: Machine name
2440
    @type cmd: list
2441
    @param cmd: Command
2442

2443
    """
2444
    if node_name is None or node_name == self.master_node:
2445
      # No need to use SSH
2446
      result = utils.RunCmd(cmd)
2447
    else:
2448
      result = self.ssh.Run(node_name, "root", utils.ShellQuoteArgs(cmd))
2449

    
2450
    if result.failed:
2451
      errmsg = ["Failed to run command %s" % result.cmd]
2452
      if node_name:
2453
        errmsg.append("on node %s" % node_name)
2454
      errmsg.append(": exitcode %s and error %s" %
2455
                    (result.exit_code, result.output))
2456
      raise errors.OpExecError(" ".join(errmsg))
2457

    
2458
  def Call(self, fn, *args):
2459
    """Call function while all daemons are stopped.
2460

2461
    @type fn: callable
2462
    @param fn: Function to be called
2463

2464
    """
2465
    # Pause watcher by acquiring an exclusive lock on watcher state file
2466
    self.feedback_fn("Blocking watcher")
2467
    watcher_block = utils.FileLock.Open(constants.WATCHER_LOCK_FILE)
2468
    try:
2469
      # TODO: Currently, this just blocks. There's no timeout.
2470
      # TODO: Should it be a shared lock?
2471
      watcher_block.Exclusive(blocking=True)
2472

    
2473
      # Stop master daemons, so that no new jobs can come in and all running
2474
      # ones are finished
2475
      self.feedback_fn("Stopping master daemons")
2476
      self._RunCmd(None, [constants.DAEMON_UTIL, "stop-master"])
2477
      try:
2478
        # Stop daemons on all nodes
2479
        for node_name in self.online_nodes:
2480
          self.feedback_fn("Stopping daemons on %s" % node_name)
2481
          self._RunCmd(node_name, [constants.DAEMON_UTIL, "stop-all"])
2482

    
2483
        # All daemons are shut down now
2484
        try:
2485
          return fn(self, *args)
2486
        except Exception, err:
2487
          _, errmsg = FormatError(err)
2488
          logging.exception("Caught exception")
2489
          self.feedback_fn(errmsg)
2490
          raise
2491
      finally:
2492
        # Start cluster again, master node last
2493
        for node_name in self.nonmaster_nodes + [self.master_node]:
2494
          self.feedback_fn("Starting daemons on %s" % node_name)
2495
          self._RunCmd(node_name, [constants.DAEMON_UTIL, "start-all"])
2496
    finally:
2497
      # Resume watcher
2498
      watcher_block.Close()
2499

    
2500

    
2501
def RunWhileClusterStopped(feedback_fn, fn, *args):
2502
  """Calls a function while all cluster daemons are stopped.
2503

2504
  @type feedback_fn: callable
2505
  @param feedback_fn: Feedback function
2506
  @type fn: callable
2507
  @param fn: Function to be called when daemons are stopped
2508

2509
  """
2510
  feedback_fn("Gathering cluster information")
2511

    
2512
  # This ensures we're running on the master daemon
2513
  cl = GetClient()
2514

    
2515
  (cluster_name, master_node) = \
2516
    cl.QueryConfigValues(["cluster_name", "master_node"])
2517

    
2518
  online_nodes = GetOnlineNodes([], cl=cl)
2519

    
2520
  # Don't keep a reference to the client. The master daemon will go away.
2521
  del cl
2522

    
2523
  assert master_node in online_nodes
2524

    
2525
  return _RunWhileClusterStoppedHelper(feedback_fn, cluster_name, master_node,
2526
                                       online_nodes).Call(fn, *args)
2527

    
2528

    
2529
def GenerateTable(headers, fields, separator, data,
2530
                  numfields=None, unitfields=None,
2531
                  units=None):
2532
  """Prints a table with headers and different fields.
2533

2534
  @type headers: dict
2535
  @param headers: dictionary mapping field names to headers for
2536
      the table
2537
  @type fields: list
2538
  @param fields: the field names corresponding to each row in
2539
      the data field
2540
  @param separator: the separator to be used; if this is None,
2541
      the default 'smart' algorithm is used which computes optimal
2542
      field width, otherwise just the separator is used between
2543
      each field
2544
  @type data: list
2545
  @param data: a list of lists, each sublist being one row to be output
2546
  @type numfields: list
2547
  @param numfields: a list with the fields that hold numeric
2548
      values and thus should be right-aligned
2549
  @type unitfields: list
2550
  @param unitfields: a list with the fields that hold numeric
2551
      values that should be formatted with the units field
2552
  @type units: string or None
2553
  @param units: the units we should use for formatting, or None for
2554
      automatic choice (human-readable for non-separator usage, otherwise
2555
      megabytes); this is a one-letter string
2556

2557
  """
2558
  if units is None:
2559
    if separator:
2560
      units = "m"
2561
    else:
2562
      units = "h"
2563

    
2564
  if numfields is None:
2565
    numfields = []
2566
  if unitfields is None:
2567
    unitfields = []
2568

    
2569
  numfields = utils.FieldSet(*numfields)   # pylint: disable=W0142
2570
  unitfields = utils.FieldSet(*unitfields) # pylint: disable=W0142
2571

    
2572
  format_fields = []
2573
  for field in fields:
2574
    if headers and field not in headers:
2575
      # TODO: handle better unknown fields (either revert to old
2576
      # style of raising exception, or deal more intelligently with
2577
      # variable fields)
2578
      headers[field] = field
2579
    if separator is not None:
2580
      format_fields.append("%s")
2581
    elif numfields.Matches(field):
2582
      format_fields.append("%*s")
2583
    else:
2584
      format_fields.append("%-*s")
2585

    
2586
  if separator is None:
2587
    mlens = [0 for name in fields]
2588
    format_str = " ".join(format_fields)
2589
  else:
2590
    format_str = separator.replace("%", "%%").join(format_fields)
2591

    
2592
  for row in data:
2593
    if row is None:
2594
      continue
2595
    for idx, val in enumerate(row):
2596
      if unitfields.Matches(fields[idx]):
2597
        try:
2598
          val = int(val)
2599
        except (TypeError, ValueError):
2600
          pass
2601
        else:
2602
          val = row[idx] = utils.FormatUnit(val, units)
2603
      val = row[idx] = str(val)
2604
      if separator is None:
2605
        mlens[idx] = max(mlens[idx], len(val))
2606

    
2607
  result = []
2608
  if headers:
2609
    args = []
2610
    for idx, name in enumerate(fields):
2611
      hdr = headers[name]
2612
      if separator is None:
2613
        mlens[idx] = max(mlens[idx], len(hdr))
2614
        args.append(mlens[idx])
2615
      args.append(hdr)
2616
    result.append(format_str % tuple(args))
2617

    
2618
  if separator is None:
2619
    assert len(mlens) == len(fields)
2620

    
2621
    if fields and not numfields.Matches(fields[-1]):
2622
      mlens[-1] = 0
2623

    
2624
  for line in data:
2625
    args = []
2626
    if line is None:
2627
      line = ["-" for _ in fields]
2628
    for idx in range(len(fields)):
2629
      if separator is None:
2630
        args.append(mlens[idx])
2631
      args.append(line[idx])
2632
    result.append(format_str % tuple(args))
2633

    
2634
  return result
2635

    
2636

    
2637
def _FormatBool(value):
2638
  """Formats a boolean value as a string.
2639

2640
  """
2641
  if value:
2642
    return "Y"
2643
  return "N"
2644

    
2645

    
2646
#: Default formatting for query results; (callback, align right)
2647
_DEFAULT_FORMAT_QUERY = {
2648
  constants.QFT_TEXT: (str, False),
2649
  constants.QFT_BOOL: (_FormatBool, False),
2650
  constants.QFT_NUMBER: (str, True),
2651
  constants.QFT_TIMESTAMP: (utils.FormatTime, False),
2652
  constants.QFT_OTHER: (str, False),
2653
  constants.QFT_UNKNOWN: (str, False),
2654
  }
2655

    
2656

    
2657
def _GetColumnFormatter(fdef, override, unit):
2658
  """Returns formatting function for a field.
2659

2660
  @type fdef: L{objects.QueryFieldDefinition}
2661
  @type override: dict
2662
  @param override: Dictionary for overriding field formatting functions,
2663
    indexed by field name, contents like L{_DEFAULT_FORMAT_QUERY}
2664
  @type unit: string
2665
  @param unit: Unit used for formatting fields of type L{constants.QFT_UNIT}
2666
  @rtype: tuple; (callable, bool)
2667
  @return: Returns the function to format a value (takes one parameter) and a
2668
    boolean for aligning the value on the right-hand side
2669

2670
  """
2671
  fmt = override.get(fdef.name, None)
2672
  if fmt is not None:
2673
    return fmt
2674

    
2675
  assert constants.QFT_UNIT not in _DEFAULT_FORMAT_QUERY
2676

    
2677
  if fdef.kind == constants.QFT_UNIT:
2678
    # Can't keep this information in the static dictionary
2679
    return (lambda value: utils.FormatUnit(value, unit), True)
2680

    
2681
  fmt = _DEFAULT_FORMAT_QUERY.get(fdef.kind, None)
2682
  if fmt is not None:
2683
    return fmt
2684

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

    
2687

    
2688
class _QueryColumnFormatter:
2689
  """Callable class for formatting fields of a query.
2690

2691
  """
2692
  def __init__(self, fn, status_fn, verbose):
2693
    """Initializes this class.
2694

2695
    @type fn: callable
2696
    @param fn: Formatting function
2697
    @type status_fn: callable
2698
    @param status_fn: Function to report fields' status
2699
    @type verbose: boolean
2700
    @param verbose: whether to use verbose field descriptions or not
2701

2702
    """
2703
    self._fn = fn
2704
    self._status_fn = status_fn
2705
    self._verbose = verbose
2706

    
2707
  def __call__(self, data):
2708
    """Returns a field's string representation.
2709

2710
    """
2711
    (status, value) = data
2712

    
2713
    # Report status
2714
    self._status_fn(status)
2715

    
2716
    if status == constants.RS_NORMAL:
2717
      return self._fn(value)
2718

    
2719
    assert value is None, \
2720
           "Found value %r for abnormal status %s" % (value, status)
2721

    
2722
    return FormatResultError(status, self._verbose)
2723

    
2724

    
2725
def FormatResultError(status, verbose):
2726
  """Formats result status other than L{constants.RS_NORMAL}.
2727

2728
  @param status: The result status
2729
  @type verbose: boolean
2730
  @param verbose: Whether to return the verbose text
2731
  @return: Text of result status
2732

2733
  """
2734
  assert status != constants.RS_NORMAL, \
2735
         "FormatResultError called with status equal to constants.RS_NORMAL"
2736
  try:
2737
    (verbose_text, normal_text) = constants.RSS_DESCRIPTION[status]
2738
  except KeyError:
2739
    raise NotImplementedError("Unknown status %s" % status)
2740
  else:
2741
    if verbose:
2742
      return verbose_text
2743
    return normal_text
2744

    
2745

    
2746
def FormatQueryResult(result, unit=None, format_override=None, separator=None,
2747
                      header=False, verbose=False):
2748
  """Formats data in L{objects.QueryResponse}.
2749

2750
  @type result: L{objects.QueryResponse}
2751
  @param result: result of query operation
2752
  @type unit: string
2753
  @param unit: Unit used for formatting fields of type L{constants.QFT_UNIT},
2754
    see L{utils.text.FormatUnit}
2755
  @type format_override: dict
2756
  @param format_override: Dictionary for overriding field formatting functions,
2757
    indexed by field name, contents like L{_DEFAULT_FORMAT_QUERY}
2758
  @type separator: string or None
2759
  @param separator: String used to separate fields
2760
  @type header: bool
2761
  @param header: Whether to output header row
2762
  @type verbose: boolean
2763
  @param verbose: whether to use verbose field descriptions or not
2764

2765
  """
2766
  if unit is None:
2767
    if separator:
2768
      unit = "m"
2769
    else:
2770
      unit = "h"
2771

    
2772
  if format_override is None:
2773
    format_override = {}
2774

    
2775
  stats = dict.fromkeys(constants.RS_ALL, 0)
2776

    
2777
  def _RecordStatus(status):
2778
    if status in stats:
2779
      stats[status] += 1
2780

    
2781
  columns = []
2782
  for fdef in result.fields:
2783
    assert fdef.title and fdef.name
2784
    (fn, align_right) = _GetColumnFormatter(fdef, format_override, unit)
2785
    columns.append(TableColumn(fdef.title,
2786
                               _QueryColumnFormatter(fn, _RecordStatus,
2787
                                                     verbose),
2788
                               align_right))
2789

    
2790
  table = FormatTable(result.data, columns, header, separator)
2791

    
2792
  # Collect statistics
2793
  assert len(stats) == len(constants.RS_ALL)
2794
  assert compat.all(count >= 0 for count in stats.values())
2795

    
2796
  # Determine overall status. If there was no data, unknown fields must be
2797
  # detected via the field definitions.
2798
  if (stats[constants.RS_UNKNOWN] or
2799
      (not result.data and _GetUnknownFields(result.fields))):
2800
    status = QR_UNKNOWN
2801
  elif compat.any(count > 0 for key, count in stats.items()
2802
                  if key != constants.RS_NORMAL):
2803
    status = QR_INCOMPLETE
2804
  else:
2805
    status = QR_NORMAL
2806

    
2807
  return (status, table)
2808

    
2809

    
2810
def _GetUnknownFields(fdefs):
2811
  """Returns list of unknown fields included in C{fdefs}.
2812

2813
  @type fdefs: list of L{objects.QueryFieldDefinition}
2814

2815
  """
2816
  return [fdef for fdef in fdefs
2817
          if fdef.kind == constants.QFT_UNKNOWN]
2818

    
2819

    
2820
def _WarnUnknownFields(fdefs):
2821
  """Prints a warning to stderr if a query included unknown fields.
2822

2823
  @type fdefs: list of L{objects.QueryFieldDefinition}
2824

2825
  """
2826
  unknown = _GetUnknownFields(fdefs)
2827
  if unknown:
2828
    ToStderr("Warning: Queried for unknown fields %s",
2829
             utils.CommaJoin(fdef.name for fdef in unknown))
2830
    return True
2831

    
2832
  return False
2833

    
2834

    
2835
def GenericList(resource, fields, names, unit, separator, header, cl=None,
2836
                format_override=None, verbose=False, force_filter=False,
2837
                namefield=None, qfilter=None):
2838
  """Generic implementation for listing all items of a resource.
2839

2840
  @param resource: One of L{constants.QR_VIA_LUXI}
2841
  @type fields: list of strings
2842
  @param fields: List of fields to query for
2843
  @type names: list of strings
2844
  @param names: Names of items to query for
2845
  @type unit: string or None
2846
  @param unit: Unit used for formatting fields of type L{constants.QFT_UNIT} or
2847
    None for automatic choice (human-readable for non-separator usage,
2848
    otherwise megabytes); this is a one-letter string
2849
  @type separator: string or None
2850
  @param separator: String used to separate fields
2851
  @type header: bool
2852
  @param header: Whether to show header row
2853
  @type force_filter: bool
2854
  @param force_filter: Whether to always treat names as filter
2855
  @type format_override: dict
2856
  @param format_override: Dictionary for overriding field formatting functions,
2857
    indexed by field name, contents like L{_DEFAULT_FORMAT_QUERY}
2858
  @type verbose: boolean
2859
  @param verbose: whether to use verbose field descriptions or not
2860
  @type namefield: string
2861
  @param namefield: Name of field to use for simple filters (see
2862
    L{qlang.MakeFilter} for details)
2863
  @type qfilter: list or None
2864
  @param qfilter: Query filter (in addition to names)
2865

2866
  """
2867
  if not names:
2868
    names = None
2869

    
2870
  namefilter = qlang.MakeFilter(names, force_filter, namefield=namefield)
2871

    
2872
  if qfilter is None:
2873
    qfilter = namefilter
2874
  elif namefilter is not None:
2875
    qfilter = [qlang.OP_AND, namefilter, qfilter]
2876

    
2877
  if cl is None:
2878
    cl = GetClient()
2879

    
2880
  response = cl.Query(resource, fields, qfilter)
2881

    
2882
  found_unknown = _WarnUnknownFields(response.fields)
2883

    
2884
  (status, data) = FormatQueryResult(response, unit=unit, separator=separator,
2885
                                     header=header,
2886
                                     format_override=format_override,
2887
                                     verbose=verbose)
2888

    
2889
  for line in data:
2890
    ToStdout(line)
2891

    
2892
  assert ((found_unknown and status == QR_UNKNOWN) or
2893
          (not found_unknown and status != QR_UNKNOWN))
2894

    
2895
  if status == QR_UNKNOWN:
2896
    return constants.EXIT_UNKNOWN_FIELD
2897

    
2898
  # TODO: Should the list command fail if not all data could be collected?
2899
  return constants.EXIT_SUCCESS
2900

    
2901

    
2902
def GenericListFields(resource, fields, separator, header, cl=None):
2903
  """Generic implementation for listing fields for a resource.
2904

2905
  @param resource: One of L{constants.QR_VIA_LUXI}
2906
  @type fields: list of strings
2907
  @param fields: List of fields to query for
2908
  @type separator: string or None
2909
  @param separator: String used to separate fields
2910
  @type header: bool
2911
  @param header: Whether to show header row
2912

2913
  """
2914
  if cl is None:
2915
    cl = GetClient()
2916

    
2917
  if not fields:
2918
    fields = None
2919

    
2920
  response = cl.QueryFields(resource, fields)
2921

    
2922
  found_unknown = _WarnUnknownFields(response.fields)
2923

    
2924
  columns = [
2925
    TableColumn("Name", str, False),
2926
    TableColumn("Title", str, False),
2927
    TableColumn("Description", str, False),
2928
    ]
2929

    
2930
  rows = [[fdef.name, fdef.title, fdef.doc] for fdef in response.fields]
2931

    
2932
  for line in FormatTable(rows, columns, header, separator):
2933
    ToStdout(line)
2934

    
2935
  if found_unknown:
2936
    return constants.EXIT_UNKNOWN_FIELD
2937

    
2938
  return constants.EXIT_SUCCESS
2939

    
2940

    
2941
class TableColumn:
2942
  """Describes a column for L{FormatTable}.
2943

2944
  """
2945
  def __init__(self, title, fn, align_right):
2946
    """Initializes this class.
2947

2948
    @type title: string
2949
    @param title: Column title
2950
    @type fn: callable
2951
    @param fn: Formatting function
2952
    @type align_right: bool
2953
    @param align_right: Whether to align values on the right-hand side
2954

2955
    """
2956
    self.title = title
2957
    self.format = fn
2958
    self.align_right = align_right
2959

    
2960

    
2961
def _GetColFormatString(width, align_right):
2962
  """Returns the format string for a field.
2963

2964
  """
2965
  if align_right:
2966
    sign = ""
2967
  else:
2968
    sign = "-"
2969

    
2970
  return "%%%s%ss" % (sign, width)
2971

    
2972

    
2973
def FormatTable(rows, columns, header, separator):
2974
  """Formats data as a table.
2975

2976
  @type rows: list of lists
2977
  @param rows: Row data, one list per row
2978
  @type columns: list of L{TableColumn}
2979
  @param columns: Column descriptions
2980
  @type header: bool
2981
  @param header: Whether to show header row
2982
  @type separator: string or None
2983
  @param separator: String used to separate columns
2984

2985
  """
2986
  if header:
2987
    data = [[col.title for col in columns]]
2988
    colwidth = [len(col.title) for col in columns]
2989
  else:
2990
    data = []
2991
    colwidth = [0 for _ in columns]
2992

    
2993
  # Format row data
2994
  for row in rows:
2995
    assert len(row) == len(columns)
2996

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

    
2999
    if separator is None:
3000
      # Update column widths
3001
      for idx, (oldwidth, value) in enumerate(zip(colwidth, formatted)):
3002
        # Modifying a list's items while iterating is fine
3003
        colwidth[idx] = max(oldwidth, len(value))
3004

    
3005
    data.append(formatted)
3006

    
3007
  if separator is not None:
3008
    # Return early if a separator is used
3009
    return [separator.join(row) for row in data]
3010

    
3011
  if columns and not columns[-1].align_right:
3012
    # Avoid unnecessary spaces at end of line
3013
    colwidth[-1] = 0
3014

    
3015
  # Build format string
3016
  fmt = " ".join([_GetColFormatString(width, col.align_right)
3017
                  for col, width in zip(columns, colwidth)])
3018

    
3019
  return [fmt % tuple(row) for row in data]
3020

    
3021

    
3022
def FormatTimestamp(ts):
3023
  """Formats a given timestamp.
3024

3025
  @type ts: timestamp
3026
  @param ts: a timeval-type timestamp, a tuple of seconds and microseconds
3027

3028
  @rtype: string
3029
  @return: a string with the formatted timestamp
3030

3031
  """
3032
  if not isinstance(ts, (tuple, list)) or len(ts) != 2:
3033
    return "?"
3034

    
3035
  (sec, usecs) = ts
3036
  return utils.FormatTime(sec, usecs=usecs)
3037

    
3038

    
3039
def ParseTimespec(value):
3040
  """Parse a time specification.
3041

3042
  The following suffixed will be recognized:
3043

3044
    - s: seconds
3045
    - m: minutes
3046
    - h: hours
3047
    - d: day
3048
    - w: weeks
3049

3050
  Without any suffix, the value will be taken to be in seconds.
3051

3052
  """
3053
  value = str(value)
3054
  if not value:
3055
    raise errors.OpPrereqError("Empty time specification passed")
3056
  suffix_map = {
3057
    "s": 1,
3058
    "m": 60,
3059
    "h": 3600,
3060
    "d": 86400,
3061
    "w": 604800,
3062
    }
3063
  if value[-1] not in suffix_map:
3064
    try:
3065
      value = int(value)
3066
    except (TypeError, ValueError):
3067
      raise errors.OpPrereqError("Invalid time specification '%s'" % value)
3068
  else:
3069
    multiplier = suffix_map[value[-1]]
3070
    value = value[:-1]
3071
    if not value: # no data left after stripping the suffix
3072
      raise errors.OpPrereqError("Invalid time specification (only"
3073
                                 " suffix passed)")
3074
    try:
3075
      value = int(value) * multiplier
3076
    except (TypeError, ValueError):
3077
      raise errors.OpPrereqError("Invalid time specification '%s'" % value)
3078
  return value
3079

    
3080

    
3081
def GetOnlineNodes(nodes, cl=None, nowarn=False, secondary_ips=False,
3082
                   filter_master=False, nodegroup=None):
3083
  """Returns the names of online nodes.
3084

3085
  This function will also log a warning on stderr with the names of
3086
  the online nodes.
3087

3088
  @param nodes: if not empty, use only this subset of nodes (minus the
3089
      offline ones)
3090
  @param cl: if not None, luxi client to use
3091
  @type nowarn: boolean
3092
  @param nowarn: by default, this function will output a note with the
3093
      offline nodes that are skipped; if this parameter is True the
3094
      note is not displayed
3095
  @type secondary_ips: boolean
3096
  @param secondary_ips: if True, return the secondary IPs instead of the
3097
      names, useful for doing network traffic over the replication interface
3098
      (if any)
3099
  @type filter_master: boolean
3100
  @param filter_master: if True, do not return the master node in the list
3101
      (useful in coordination with secondary_ips where we cannot check our
3102
      node name against the list)
3103
  @type nodegroup: string
3104
  @param nodegroup: If set, only return nodes in this node group
3105

3106
  """
3107
  if cl is None:
3108
    cl = GetClient()
3109

    
3110
  qfilter = []
3111

    
3112
  if nodes:
3113
    qfilter.append(qlang.MakeSimpleFilter("name", nodes))
3114

    
3115
  if nodegroup is not None:
3116
    qfilter.append([qlang.OP_OR, [qlang.OP_EQUAL, "group", nodegroup],
3117
                                 [qlang.OP_EQUAL, "group.uuid", nodegroup]])
3118

    
3119
  if filter_master:
3120
    qfilter.append([qlang.OP_NOT, [qlang.OP_TRUE, "master"]])
3121

    
3122
  if qfilter:
3123
    if len(qfilter) > 1:
3124
      final_filter = [qlang.OP_AND] + qfilter
3125
    else:
3126
      assert len(qfilter) == 1
3127
      final_filter = qfilter[0]
3128
  else:
3129
    final_filter = None
3130

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

    
3133
  def _IsOffline(row):
3134
    (_, (_, offline), _) = row
3135
    return offline
3136

    
3137
  def _GetName(row):
3138
    ((_, name), _, _) = row
3139
    return name
3140

    
3141
  def _GetSip(row):
3142
    (_, _, (_, sip)) = row
3143
    return sip
3144

    
3145
  (offline, online) = compat.partition(result.data, _IsOffline)
3146

    
3147
  if offline and not nowarn:
3148
    ToStderr("Note: skipping offline node(s): %s" %
3149
             utils.CommaJoin(map(_GetName, offline)))
3150

    
3151
  if secondary_ips:
3152
    fn = _GetSip
3153
  else:
3154
    fn = _GetName
3155

    
3156
  return map(fn, online)
3157

    
3158

    
3159
def _ToStream(stream, txt, *args):
3160
  """Write a message to a stream, bypassing the logging system
3161

3162
  @type stream: file object
3163
  @param stream: the file to which we should write
3164
  @type txt: str
3165
  @param txt: the message
3166

3167
  """
3168
  try:
3169
    if args:
3170
      args = tuple(args)
3171
      stream.write(txt % args)
3172
    else:
3173
      stream.write(txt)
3174
    stream.write("\n")
3175
    stream.flush()
3176
  except IOError, err:
3177
    if err.errno == errno.EPIPE:
3178
      # our terminal went away, we'll exit
3179
      sys.exit(constants.EXIT_FAILURE)
3180
    else:
3181
      raise
3182

    
3183

    
3184
def ToStdout(txt, *args):
3185
  """Write a message to stdout only, bypassing the logging system
3186

3187
  This is just a wrapper over _ToStream.
3188

3189
  @type txt: str
3190
  @param txt: the message
3191

3192
  """
3193
  _ToStream(sys.stdout, txt, *args)
3194

    
3195

    
3196
def ToStderr(txt, *args):
3197
  """Write a message to stderr only, bypassing the logging system
3198

3199
  This is just a wrapper over _ToStream.
3200

3201
  @type txt: str
3202
  @param txt: the message
3203

3204
  """
3205
  _ToStream(sys.stderr, txt, *args)
3206

    
3207

    
3208
class JobExecutor(object):
3209
  """Class which manages the submission and execution of multiple jobs.
3210

3211
  Note that instances of this class should not be reused between
3212
  GetResults() calls.
3213

3214
  """
3215
  def __init__(self, cl=None, verbose=True, opts=None, feedback_fn=None):
3216
    self.queue = []
3217
    if cl is None:
3218
      cl = GetClient()
3219
    self.cl = cl
3220
    self.verbose = verbose
3221
    self.jobs = []
3222
    self.opts = opts
3223
    self.feedback_fn = feedback_fn
3224
    self._counter = itertools.count()
3225

    
3226
  @staticmethod
3227
  def _IfName(name, fmt):
3228
    """Helper function for formatting name.
3229

3230
    """
3231
    if name:
3232
      return fmt % name
3233

    
3234
    return ""
3235

    
3236
  def QueueJob(self, name, *ops):
3237
    """Record a job for later submit.
3238

3239
    @type name: string
3240
    @param name: a description of the job, will be used in WaitJobSet
3241

3242
    """
3243
    SetGenericOpcodeOpts(ops, self.opts)
3244
    self.queue.append((self._counter.next(), name, ops))
3245

    
3246
  def AddJobId(self, name, status, job_id):
3247
    """Adds a job ID to the internal queue.
3248

3249
    """
3250
    self.jobs.append((self._counter.next(), status, job_id, name))
3251

    
3252
  def SubmitPending(self, each=False):
3253
    """Submit all pending jobs.
3254

3255
    """
3256
    if each:
3257
      results = []
3258
      for (_, _, ops) in self.queue:
3259
        # SubmitJob will remove the success status, but raise an exception if
3260
        # the submission fails, so we'll notice that anyway.
3261
        results.append([True, self.cl.SubmitJob(ops)[0]])
3262
    else:
3263
      results = self.cl.SubmitManyJobs([ops for (_, _, ops) in self.queue])
3264
    for ((status, data), (idx, name, _)) in zip(results, self.queue):
3265
      self.jobs.append((idx, status, data, name))
3266

    
3267
  def _ChooseJob(self):
3268
    """Choose a non-waiting/queued job to poll next.
3269

3270
    """
3271
    assert self.jobs, "_ChooseJob called with empty job list"
3272

    
3273
    result = self.cl.QueryJobs([i[2] for i in self.jobs[:_CHOOSE_BATCH]],
3274
                               ["status"])
3275
    assert result
3276

    
3277
    for job_data, status in zip(self.jobs, result):
3278
      if (isinstance(status, list) and status and
3279
          status[0] in (constants.JOB_STATUS_QUEUED,
3280
                        constants.JOB_STATUS_WAITING,
3281
                        constants.JOB_STATUS_CANCELING)):
3282
        # job is still present and waiting
3283
        continue
3284
      # good candidate found (either running job or lost job)
3285
      self.jobs.remove(job_data)
3286
      return job_data
3287

    
3288
    # no job found
3289
    return self.jobs.pop(0)
3290

    
3291
  def GetResults(self):
3292
    """Wait for and return the results of all jobs.
3293

3294
    @rtype: list
3295
    @return: list of tuples (success, job results), in the same order
3296
        as the submitted jobs; if a job has failed, instead of the result
3297
        there will be the error message
3298

3299
    """
3300
    if not self.jobs:
3301
      self.SubmitPending()
3302
    results = []
3303
    if self.verbose:
3304
      ok_jobs = [row[2] for row in self.jobs if row[1]]
3305
      if ok_jobs:
3306
        ToStdout("Submitted jobs %s", utils.CommaJoin(ok_jobs))
3307

    
3308
    # first, remove any non-submitted jobs
3309
    self.jobs, failures = compat.partition(self.jobs, lambda x: x[1])
3310
    for idx, _, jid, name in failures:
3311
      ToStderr("Failed to submit job%s: %s", self._IfName(name, " for %s"), jid)
3312
      results.append((idx, False, jid))
3313

    
3314
    while self.jobs:
3315
      (idx, _, jid, name) = self._ChooseJob()
3316
      ToStdout("Waiting for job %s%s ...", jid, self._IfName(name, " for %s"))
3317
      try:
3318
        job_result = PollJob(jid, cl=self.cl, feedback_fn=self.feedback_fn)
3319
        success = True
3320
      except errors.JobLost, err:
3321
        _, job_result = FormatError(err)
3322
        ToStderr("Job %s%s has been archived, cannot check its result",
3323
                 jid, self._IfName(name, " for %s"))
3324
        success = False
3325
      except (errors.GenericError, luxi.ProtocolError), err:
3326
        _, job_result = FormatError(err)
3327
        success = False
3328
        # the error message will always be shown, verbose or not
3329
        ToStderr("Job %s%s has failed: %s",
3330
                 jid, self._IfName(name, " for %s"), job_result)
3331

    
3332
      results.append((idx, success, job_result))
3333

    
3334
    # sort based on the index, then drop it
3335
    results.sort()
3336
    results = [i[1:] for i in results]
3337

    
3338
    return results
3339

    
3340
  def WaitOrShow(self, wait):
3341
    """Wait for job results or only print the job IDs.
3342

3343
    @type wait: boolean
3344
    @param wait: whether to wait or not
3345

3346
    """
3347
    if wait:
3348
      return self.GetResults()
3349
    else:
3350
      if not self.jobs:
3351
        self.SubmitPending()
3352
      for _, status, result, name in self.jobs:
3353
        if status:
3354
          ToStdout("%s: %s", result, name)
3355
        else:
3356
          ToStderr("Failure for %s: %s", name, result)
3357
      return [row[1:3] for row in self.jobs]
3358

    
3359

    
3360
def FormatParameterDict(buf, param_dict, actual, level=1):
3361
  """Formats a parameter dictionary.
3362

3363
  @type buf: L{StringIO}
3364
  @param buf: the buffer into which to write
3365
  @type param_dict: dict
3366
  @param param_dict: the own parameters
3367
  @type actual: dict
3368
  @param actual: the current parameter set (including defaults)
3369
  @param level: Level of indent
3370

3371
  """
3372
  indent = "  " * level
3373

    
3374
  for key in sorted(actual):
3375
    data = actual[key]
3376
    buf.write("%s- %s:" % (indent, key))
3377

    
3378
    if isinstance(data, dict) and data:
3379
      buf.write("\n")
3380
      FormatParameterDict(buf, param_dict.get(key, {}), data,
3381
                          level=level + 1)
3382
    else:
3383
      val = param_dict.get(key, "default (%s)" % data)
3384
      buf.write(" %s\n" % val)
3385

    
3386

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

3390
  This function is used to request confirmation for doing an operation
3391
  on a given list of list_type.
3392

3393
  @type names: list
3394
  @param names: the list of names that we display when
3395
      we ask for confirmation
3396
  @type list_type: str
3397
  @param list_type: Human readable name for elements in the list (e.g. nodes)
3398
  @type text: str
3399
  @param text: the operation that the user should confirm
3400
  @rtype: boolean
3401
  @return: True or False depending on user's confirmation.
3402

3403
  """
3404
  count = len(names)
3405
  msg = ("The %s will operate on %d %s.\n%s"
3406
         "Do you want to continue?" % (text, count, list_type, extra))
3407
  affected = (("\nAffected %s:\n" % list_type) +
3408
              "\n".join(["  %s" % name for name in names]))
3409

    
3410
  choices = [("y", True, "Yes, execute the %s" % text),
3411
             ("n", False, "No, abort the %s" % text)]
3412

    
3413
  if count > 20:
3414
    choices.insert(1, ("v", "v", "View the list of affected %s" % list_type))
3415
    question = msg
3416
  else:
3417
    question = msg + affected
3418

    
3419
  choice = AskUser(question, choices)
3420
  if choice == "v":
3421
    choices.pop(1)
3422
    choice = AskUser(msg + affected, choices)
3423
  return choice
3424

    
3425

    
3426
def CreateIPolicyFromOpts(ispecs_mem_size=None,
3427
                          ispecs_cpu_count=None,
3428
                          ispecs_disk_count=None,
3429
                          ispecs_disk_size=None,
3430
                          ispecs_nic_count=None,
3431
                          ipolicy_disk_templates=None,
3432
                          ipolicy_vcpu_ratio=None,
3433
                          ipolicy_spindle_ratio=None,
3434
                          group_ipolicy=False,
3435
                          allowed_values=None,
3436
                          fill_all=False):
3437
  """Creation of instance policy based on command line options.
3438

3439
  @param fill_all: whether for cluster policies we should ensure that
3440
    all values are filled
3441

3442

3443
  """
3444
  try:
3445
    if ispecs_mem_size:
3446
      for k in ispecs_mem_size:
3447
        ispecs_mem_size[k] = utils.ParseUnit(ispecs_mem_size[k])
3448
    if ispecs_disk_size:
3449
      for k in ispecs_disk_size:
3450
        ispecs_disk_size[k] = utils.ParseUnit(ispecs_disk_size[k])
3451
  except (TypeError, ValueError, errors.UnitParseError), err:
3452
    raise errors.OpPrereqError("Invalid disk (%s) or memory (%s) size"
3453
                               " in policy: %s" %
3454
                               (ispecs_disk_size, ispecs_mem_size, err),
3455
                               errors.ECODE_INVAL)
3456

    
3457
  # prepare ipolicy dict
3458
  ipolicy_transposed = {
3459
    constants.ISPEC_MEM_SIZE: ispecs_mem_size,
3460
    constants.ISPEC_CPU_COUNT: ispecs_cpu_count,
3461
    constants.ISPEC_DISK_COUNT: ispecs_disk_count,
3462
    constants.ISPEC_DISK_SIZE: ispecs_disk_size,
3463
    constants.ISPEC_NIC_COUNT: ispecs_nic_count,
3464
    }
3465

    
3466
  # first, check that the values given are correct
3467
  if group_ipolicy:
3468
    forced_type = TISPECS_GROUP_TYPES
3469
  else:
3470
    forced_type = TISPECS_CLUSTER_TYPES
3471

    
3472
  for specs in ipolicy_transposed.values():
3473
    utils.ForceDictType(specs, forced_type, allowed_values=allowed_values)
3474

    
3475
  # then transpose
3476
  ipolicy_out = objects.MakeEmptyIPolicy()
3477
  for name, specs in ipolicy_transposed.iteritems():
3478
    assert name in constants.ISPECS_PARAMETERS
3479
    for key, val in specs.items(): # {min: .. ,max: .., std: ..}
3480
      ipolicy_out[key][name] = val
3481

    
3482
  # no filldict for non-dicts
3483
  if not group_ipolicy and fill_all:
3484
    if ipolicy_disk_templates is None:
3485
      ipolicy_disk_templates = constants.DISK_TEMPLATES
3486
    if ipolicy_vcpu_ratio is None:
3487
      ipolicy_vcpu_ratio = \
3488
        constants.IPOLICY_DEFAULTS[constants.IPOLICY_VCPU_RATIO]
3489
    if ipolicy_spindle_ratio is None:
3490
      ipolicy_spindle_ratio = \
3491
        constants.IPOLICY_DEFAULTS[constants.IPOLICY_SPINDLE_RATIO]
3492
  if ipolicy_disk_templates is not None:
3493
    ipolicy_out[constants.IPOLICY_DTS] = list(ipolicy_disk_templates)
3494
  if ipolicy_vcpu_ratio is not None:
3495
    ipolicy_out[constants.IPOLICY_VCPU_RATIO] = ipolicy_vcpu_ratio
3496
  if ipolicy_spindle_ratio is not None:
3497
    ipolicy_out[constants.IPOLICY_SPINDLE_RATIO] = ipolicy_spindle_ratio
3498

    
3499
  assert not (frozenset(ipolicy_out.keys()) - constants.IPOLICY_ALL_KEYS)
3500

    
3501
  return ipolicy_out