Statistics
| Branch: | Tag: | Revision:

root / lib / cli.py @ 2cfbc784

History | View | Annotate | Download (116.3 kB)

1
#
2
#
3

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

    
21

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

    
24

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

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

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

    
51

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

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

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

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

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

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

    
290

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

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

    
303

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

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

    
313

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

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

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

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

    
329

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

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

336
  """
337

    
338

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

342
  """
343

    
344

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

348
  """
349

    
350

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

354
  """
355

    
356

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

360
  """
361

    
362

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

366
  """
367

    
368

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

372
  """
373

    
374

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

378
  """
379

    
380

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

384
  """
385

    
386

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

390
  """
391

    
392

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

    
403

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

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

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

    
427

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

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

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

    
456

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

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

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

    
474

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

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

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

    
491

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

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

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

    
508

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

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

    
518

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

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

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

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

    
555

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

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

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

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

    
585

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

589
  This will store the parsed values as a dict {key: val}.
590

591
  """
592
  return _SplitKeyVal(opt, value)
593

    
594

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

598
  This will store the parsed value as either True or False.
599

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

    
609

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

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

    
621

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

625
  """
626
  value = value.lower()
627

    
628
  if value == constants.VALUE_DEFAULT:
629
    return value
630
  else:
631
    return float(value)
632

    
633

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

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

    
654

    
655
class CliOption(Option):
656
  """Custom option class for optparse.
657

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

    
678

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

    
682

    
683
_YORNO = "yes|no"
684

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
752
WFSYNC_OPT = cli_option("--wait-for-sync", dest="wait_for_sync",
753
                        default=False, action="store_true",
754
                        help="Wait for disks to sync")
755

    
756
ONLINE_INST_OPT = cli_option("--online", dest="online_inst",
757
                             action="store_true", default=False,
758
                             help="Enable offline instance")
759

    
760
OFFLINE_INST_OPT = cli_option("--offline", dest="offline_inst",
761
                              action="store_true", default=False,
762
                              help="Disable down instance")
763

    
764
DISK_TEMPLATE_OPT = cli_option("-t", "--disk-template", dest="disk_template",
765
                               help=("Custom disk setup (%s)" %
766
                                     utils.CommaJoin(constants.DISK_TEMPLATES)),
767
                               default=None, metavar="TEMPL",
768
                               choices=list(constants.DISK_TEMPLATES))
769

    
770
NONICS_OPT = cli_option("--no-nics", default=False, action="store_true",
771
                        help="Do not create any network cards for"
772
                        " the instance")
773

    
774
FILESTORE_DIR_OPT = cli_option("--file-storage-dir", dest="file_storage_dir",
775
                               help="Relative path under default cluster-wide"
776
                               " file storage dir to store file-based disks",
777
                               default=None, metavar="<DIR>")
778

    
779
FILESTORE_DRIVER_OPT = cli_option("--file-driver", dest="file_driver",
780
                                  help="Driver to use for image files",
781
                                  default="loop", metavar="<DRIVER>",
782
                                  choices=list(constants.FILE_DRIVER))
783

    
784
IALLOCATOR_OPT = cli_option("-I", "--iallocator", metavar="<NAME>",
785
                            help="Select nodes for the instance automatically"
786
                            " using the <NAME> iallocator plugin",
787
                            default=None, type="string",
788
                            completion_suggest=OPT_COMPL_ONE_IALLOCATOR)
789

    
790
DEFAULT_IALLOCATOR_OPT = cli_option("-I", "--default-iallocator",
791
                            metavar="<NAME>",
792
                            help="Set the default instance allocator plugin",
793
                            default=None, type="string",
794
                            completion_suggest=OPT_COMPL_ONE_IALLOCATOR)
795

    
796
OS_OPT = cli_option("-o", "--os-type", dest="os", help="What OS to run",
797
                    metavar="<os>",
798
                    completion_suggest=OPT_COMPL_ONE_OS)
799

    
800
OSPARAMS_OPT = cli_option("-O", "--os-parameters", dest="osparams",
801
                         type="keyval", default={},
802
                         help="OS parameters")
803

    
804
FORCE_VARIANT_OPT = cli_option("--force-variant", dest="force_variant",
805
                               action="store_true", default=False,
806
                               help="Force an unknown variant")
807

    
808
NO_INSTALL_OPT = cli_option("--no-install", dest="no_install",
809
                            action="store_true", default=False,
810
                            help="Do not install the OS (will"
811
                            " enable no-start)")
812

    
813
NORUNTIME_CHGS_OPT = cli_option("--no-runtime-changes",
814
                                dest="allow_runtime_chgs",
815
                                default=True, action="store_false",
816
                                help="Don't allow runtime changes")
817

    
818
BACKEND_OPT = cli_option("-B", "--backend-parameters", dest="beparams",
819
                         type="keyval", default={},
820
                         help="Backend parameters")
821

    
822
HVOPTS_OPT = cli_option("-H", "--hypervisor-parameters", type="keyval",
823
                        default={}, dest="hvparams",
824
                        help="Hypervisor parameters")
825

    
826
DISK_PARAMS_OPT = cli_option("-D", "--disk-parameters", dest="diskparams",
827
                             help="Disk template parameters, in the format"
828
                             " template:option=value,option=value,...",
829
                             type="identkeyval", action="append", default=[])
830

    
831
SPECS_MEM_SIZE_OPT = cli_option("--specs-mem-size", dest="ispecs_mem_size",
832
                                 type="keyval", default={},
833
                                 help="Memory size specs: list of key=value,"
834
                                " where key is one of min, max, std"
835
                                 " (in MB or using a unit)")
836

    
837
SPECS_CPU_COUNT_OPT = cli_option("--specs-cpu-count", dest="ispecs_cpu_count",
838
                                 type="keyval", default={},
839
                                 help="CPU count specs: list of key=value,"
840
                                 " where key is one of min, max, std")
841

    
842
SPECS_DISK_COUNT_OPT = cli_option("--specs-disk-count",
843
                                  dest="ispecs_disk_count",
844
                                  type="keyval", default={},
845
                                  help="Disk count specs: list of key=value,"
846
                                  " where key is one of min, max, std")
847

    
848
SPECS_DISK_SIZE_OPT = cli_option("--specs-disk-size", dest="ispecs_disk_size",
849
                                 type="keyval", default={},
850
                                 help="Disk size specs: list of key=value,"
851
                                " where key is one of min, max, std"
852
                                 " (in MB or using a unit)")
853

    
854
SPECS_NIC_COUNT_OPT = cli_option("--specs-nic-count", dest="ispecs_nic_count",
855
                                 type="keyval", default={},
856
                                 help="NIC count specs: list of key=value,"
857
                                 " where key is one of min, max, std")
858

    
859
IPOLICY_DISK_TEMPLATES = cli_option("--ipolicy-disk-templates",
860
                                 dest="ipolicy_disk_templates",
861
                                 type="list", default=None,
862
                                 help="Comma-separated list of"
863
                                 " enabled disk templates")
864

    
865
IPOLICY_VCPU_RATIO = cli_option("--ipolicy-vcpu-ratio",
866
                                 dest="ipolicy_vcpu_ratio",
867
                                 type="maybefloat", default=None,
868
                                 help="The maximum allowed vcpu-to-cpu ratio")
869

    
870
IPOLICY_SPINDLE_RATIO = cli_option("--ipolicy-spindle-ratio",
871
                                   dest="ipolicy_spindle_ratio",
872
                                   type="maybefloat", default=None,
873
                                   help=("The maximum allowed instances to"
874
                                         " spindle ratio"))
875

    
876
HYPERVISOR_OPT = cli_option("-H", "--hypervisor-parameters", dest="hypervisor",
877
                            help="Hypervisor and hypervisor options, in the"
878
                            " format hypervisor:option=value,option=value,...",
879
                            default=None, type="identkeyval")
880

    
881
HVLIST_OPT = cli_option("-H", "--hypervisor-parameters", dest="hvparams",
882
                        help="Hypervisor and hypervisor options, in the"
883
                        " format hypervisor:option=value,option=value,...",
884
                        default=[], action="append", type="identkeyval")
885

    
886
NOIPCHECK_OPT = cli_option("--no-ip-check", dest="ip_check", default=True,
887
                           action="store_false",
888
                           help="Don't check that the instance's IP"
889
                           " is alive")
890

    
891
NONAMECHECK_OPT = cli_option("--no-name-check", dest="name_check",
892
                             default=True, action="store_false",
893
                             help="Don't check that the instance's name"
894
                             " is resolvable")
895

    
896
NET_OPT = cli_option("--net",
897
                     help="NIC parameters", default=[],
898
                     dest="nics", action="append", type="identkeyval")
899

    
900
DISK_OPT = cli_option("--disk", help="Disk parameters", default=[],
901
                      dest="disks", action="append", type="identkeyval")
902

    
903
DISKIDX_OPT = cli_option("--disks", dest="disks", default=None,
904
                         help="Comma-separated list of disks"
905
                         " indices to act on (e.g. 0,2) (optional,"
906
                         " defaults to all disks)")
907

    
908
OS_SIZE_OPT = cli_option("-s", "--os-size", dest="sd_size",
909
                         help="Enforces a single-disk configuration using the"
910
                         " given disk size, in MiB unless a suffix is used",
911
                         default=None, type="unit", metavar="<size>")
912

    
913
IGNORE_CONSIST_OPT = cli_option("--ignore-consistency",
914
                                dest="ignore_consistency",
915
                                action="store_true", default=False,
916
                                help="Ignore the consistency of the disks on"
917
                                " the secondary")
918

    
919
ALLOW_FAILOVER_OPT = cli_option("--allow-failover",
920
                                dest="allow_failover",
921
                                action="store_true", default=False,
922
                                help="If migration is not possible fallback to"
923
                                     " failover")
924

    
925
NONLIVE_OPT = cli_option("--non-live", dest="live",
926
                         default=True, action="store_false",
927
                         help="Do a non-live migration (this usually means"
928
                         " freeze the instance, save the state, transfer and"
929
                         " only then resume running on the secondary node)")
930

    
931
MIGRATION_MODE_OPT = cli_option("--migration-mode", dest="migration_mode",
932
                                default=None,
933
                                choices=list(constants.HT_MIGRATION_MODES),
934
                                help="Override default migration mode (choose"
935
                                " either live or non-live")
936

    
937
NODE_PLACEMENT_OPT = cli_option("-n", "--node", dest="node",
938
                                help="Target node and optional secondary node",
939
                                metavar="<pnode>[:<snode>]",
940
                                completion_suggest=OPT_COMPL_INST_ADD_NODES)
941

    
942
NODE_LIST_OPT = cli_option("-n", "--node", dest="nodes", default=[],
943
                           action="append", metavar="<node>",
944
                           help="Use only this node (can be used multiple"
945
                           " times, if not given defaults to all nodes)",
946
                           completion_suggest=OPT_COMPL_ONE_NODE)
947

    
948
NODEGROUP_OPT_NAME = "--node-group"
949
NODEGROUP_OPT = cli_option("-g", NODEGROUP_OPT_NAME,
950
                           dest="nodegroup",
951
                           help="Node group (name or uuid)",
952
                           metavar="<nodegroup>",
953
                           default=None, type="string",
954
                           completion_suggest=OPT_COMPL_ONE_NODEGROUP)
955

    
956
SINGLE_NODE_OPT = cli_option("-n", "--node", dest="node", help="Target node",
957
                             metavar="<node>",
958
                             completion_suggest=OPT_COMPL_ONE_NODE)
959

    
960
NOSTART_OPT = cli_option("--no-start", dest="start", default=True,
961
                         action="store_false",
962
                         help="Don't start the instance after creation")
963

    
964
SHOWCMD_OPT = cli_option("--show-cmd", dest="show_command",
965
                         action="store_true", default=False,
966
                         help="Show command instead of executing it")
967

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

    
977
STATIC_OPT = cli_option("-s", "--static", dest="static",
978
                        action="store_true", default=False,
979
                        help="Only show configuration data, not runtime data")
980

    
981
ALL_OPT = cli_option("--all", dest="show_all",
982
                     default=False, action="store_true",
983
                     help="Show info on all instances on the cluster."
984
                     " This can take a long time to run, use wisely")
985

    
986
SELECT_OS_OPT = cli_option("--select-os", dest="select_os",
987
                           action="store_true", default=False,
988
                           help="Interactive OS reinstall, lists available"
989
                           " OS templates for selection")
990

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

    
997
IGNORE_REMOVE_FAILURES_OPT = cli_option("--ignore-remove-failures",
998
                                        dest="ignore_remove_failures",
999
                                        action="store_true", default=False,
1000
                                        help="Remove the instance from the"
1001
                                        " cluster configuration even if there"
1002
                                        " are failures during the removal"
1003
                                        " process")
1004

    
1005
REMOVE_INSTANCE_OPT = cli_option("--remove-instance", dest="remove_instance",
1006
                                 action="store_true", default=False,
1007
                                 help="Remove the instance from the cluster")
1008

    
1009
DST_NODE_OPT = cli_option("-n", "--target-node", dest="dst_node",
1010
                               help="Specifies the new node for the instance",
1011
                               metavar="NODE", default=None,
1012
                               completion_suggest=OPT_COMPL_ONE_NODE)
1013

    
1014
NEW_SECONDARY_OPT = cli_option("-n", "--new-secondary", dest="dst_node",
1015
                               help="Specifies the new secondary node",
1016
                               metavar="NODE", default=None,
1017
                               completion_suggest=OPT_COMPL_ONE_NODE)
1018

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

    
1026
ON_SECONDARY_OPT = cli_option("-s", "--on-secondary", dest="on_secondary",
1027
                              default=False, action="store_true",
1028
                              help="Replace the disk(s) on the secondary"
1029
                                   " node (applies only to internally mirrored"
1030
                                   " disk templates, e.g. %s)" %
1031
                                   utils.CommaJoin(constants.DTS_INT_MIRROR))
1032

    
1033
AUTO_PROMOTE_OPT = cli_option("--auto-promote", dest="auto_promote",
1034
                              default=False, action="store_true",
1035
                              help="Lock all nodes and auto-promote as needed"
1036
                              " to MC status")
1037

    
1038
AUTO_REPLACE_OPT = cli_option("-a", "--auto", dest="auto",
1039
                              default=False, action="store_true",
1040
                              help="Automatically replace faulty disks"
1041
                                   " (applies only to internally mirrored"
1042
                                   " disk templates, e.g. %s)" %
1043
                                   utils.CommaJoin(constants.DTS_INT_MIRROR))
1044

    
1045
IGNORE_SIZE_OPT = cli_option("--ignore-size", dest="ignore_size",
1046
                             default=False, action="store_true",
1047
                             help="Ignore current recorded size"
1048
                             " (useful for forcing activation when"
1049
                             " the recorded size is wrong)")
1050

    
1051
SRC_NODE_OPT = cli_option("--src-node", dest="src_node", help="Source node",
1052
                          metavar="<node>",
1053
                          completion_suggest=OPT_COMPL_ONE_NODE)
1054

    
1055
SRC_DIR_OPT = cli_option("--src-dir", dest="src_dir", help="Source directory",
1056
                         metavar="<dir>")
1057

    
1058
SECONDARY_IP_OPT = cli_option("-s", "--secondary-ip", dest="secondary_ip",
1059
                              help="Specify the secondary ip for the node",
1060
                              metavar="ADDRESS", default=None)
1061

    
1062
READD_OPT = cli_option("--readd", dest="readd",
1063
                       default=False, action="store_true",
1064
                       help="Readd old node after replacing it")
1065

    
1066
NOSSH_KEYCHECK_OPT = cli_option("--no-ssh-key-check", dest="ssh_key_check",
1067
                                default=True, action="store_false",
1068
                                help="Disable SSH key fingerprint checking")
1069

    
1070
NODE_FORCE_JOIN_OPT = cli_option("--force-join", dest="force_join",
1071
                                 default=False, action="store_true",
1072
                                 help="Force the joining of a node")
1073

    
1074
MC_OPT = cli_option("-C", "--master-candidate", dest="master_candidate",
1075
                    type="bool", default=None, metavar=_YORNO,
1076
                    help="Set the master_candidate flag on the node")
1077

    
1078
OFFLINE_OPT = cli_option("-O", "--offline", dest="offline", metavar=_YORNO,
1079
                         type="bool", default=None,
1080
                         help=("Set the offline flag on the node"
1081
                               " (cluster does not communicate with offline"
1082
                               " nodes)"))
1083

    
1084
DRAINED_OPT = cli_option("-D", "--drained", dest="drained", metavar=_YORNO,
1085
                         type="bool", default=None,
1086
                         help=("Set the drained flag on the node"
1087
                               " (excluded from allocation operations)"))
1088

    
1089
CAPAB_MASTER_OPT = cli_option("--master-capable", dest="master_capable",
1090
                    type="bool", default=None, metavar=_YORNO,
1091
                    help="Set the master_capable flag on the node")
1092

    
1093
CAPAB_VM_OPT = cli_option("--vm-capable", dest="vm_capable",
1094
                    type="bool", default=None, metavar=_YORNO,
1095
                    help="Set the vm_capable flag on the node")
1096

    
1097
ALLOCATABLE_OPT = cli_option("--allocatable", dest="allocatable",
1098
                             type="bool", default=None, metavar=_YORNO,
1099
                             help="Set the allocatable flag on a volume")
1100

    
1101
NOLVM_STORAGE_OPT = cli_option("--no-lvm-storage", dest="lvm_storage",
1102
                               help="Disable support for lvm based instances"
1103
                               " (cluster-wide)",
1104
                               action="store_false", default=True)
1105

    
1106
ENABLED_HV_OPT = cli_option("--enabled-hypervisors",
1107
                            dest="enabled_hypervisors",
1108
                            help="Comma-separated list of hypervisors",
1109
                            type="string", default=None)
1110

    
1111
NIC_PARAMS_OPT = cli_option("-N", "--nic-parameters", dest="nicparams",
1112
                            type="keyval", default={},
1113
                            help="NIC parameters")
1114

    
1115
CP_SIZE_OPT = cli_option("-C", "--candidate-pool-size", default=None,
1116
                         dest="candidate_pool_size", type="int",
1117
                         help="Set the candidate pool size")
1118

    
1119
VG_NAME_OPT = cli_option("--vg-name", dest="vg_name",
1120
                         help=("Enables LVM and specifies the volume group"
1121
                               " name (cluster-wide) for disk allocation"
1122
                               " [%s]" % constants.DEFAULT_VG),
1123
                         metavar="VG", default=None)
1124

    
1125
YES_DOIT_OPT = cli_option("--yes-do-it", "--ya-rly", dest="yes_do_it",
1126
                          help="Destroy cluster", action="store_true")
1127

    
1128
NOVOTING_OPT = cli_option("--no-voting", dest="no_voting",
1129
                          help="Skip node agreement check (dangerous)",
1130
                          action="store_true", default=False)
1131

    
1132
MAC_PREFIX_OPT = cli_option("-m", "--mac-prefix", dest="mac_prefix",
1133
                            help="Specify the mac prefix for the instance IP"
1134
                            " addresses, in the format XX:XX:XX",
1135
                            metavar="PREFIX",
1136
                            default=None)
1137

    
1138
MASTER_NETDEV_OPT = cli_option("--master-netdev", dest="master_netdev",
1139
                               help="Specify the node interface (cluster-wide)"
1140
                               " on which the master IP address will be added"
1141
                               " (cluster init default: %s)" %
1142
                               constants.DEFAULT_BRIDGE,
1143
                               metavar="NETDEV",
1144
                               default=None)
1145

    
1146
MASTER_NETMASK_OPT = cli_option("--master-netmask", dest="master_netmask",
1147
                                help="Specify the netmask of the master IP",
1148
                                metavar="NETMASK",
1149
                                default=None)
1150

    
1151
USE_EXTERNAL_MIP_SCRIPT = cli_option("--use-external-mip-script",
1152
                                dest="use_external_mip_script",
1153
                                help="Specify whether to run a user-provided"
1154
                                " script for the master IP address turnup and"
1155
                                " turndown operations",
1156
                                type="bool", metavar=_YORNO, default=None)
1157

    
1158
GLOBAL_FILEDIR_OPT = cli_option("--file-storage-dir", dest="file_storage_dir",
1159
                                help="Specify the default directory (cluster-"
1160
                                "wide) for storing the file-based disks [%s]" %
1161
                                constants.DEFAULT_FILE_STORAGE_DIR,
1162
                                metavar="DIR",
1163
                                default=constants.DEFAULT_FILE_STORAGE_DIR)
1164

    
1165
GLOBAL_SHARED_FILEDIR_OPT = cli_option("--shared-file-storage-dir",
1166
                            dest="shared_file_storage_dir",
1167
                            help="Specify the default directory (cluster-"
1168
                            "wide) for storing the shared file-based"
1169
                            " disks [%s]" %
1170
                            constants.DEFAULT_SHARED_FILE_STORAGE_DIR,
1171
                            metavar="SHAREDDIR",
1172
                            default=constants.DEFAULT_SHARED_FILE_STORAGE_DIR)
1173

    
1174
NOMODIFY_ETCHOSTS_OPT = cli_option("--no-etc-hosts", dest="modify_etc_hosts",
1175
                                   help="Don't modify /etc/hosts",
1176
                                   action="store_false", default=True)
1177

    
1178
NOMODIFY_SSH_SETUP_OPT = cli_option("--no-ssh-init", dest="modify_ssh_setup",
1179
                                    help="Don't initialize SSH keys",
1180
                                    action="store_false", default=True)
1181

    
1182
ERROR_CODES_OPT = cli_option("--error-codes", dest="error_codes",
1183
                             help="Enable parseable error messages",
1184
                             action="store_true", default=False)
1185

    
1186
NONPLUS1_OPT = cli_option("--no-nplus1-mem", dest="skip_nplusone_mem",
1187
                          help="Skip N+1 memory redundancy tests",
1188
                          action="store_true", default=False)
1189

    
1190
REBOOT_TYPE_OPT = cli_option("-t", "--type", dest="reboot_type",
1191
                             help="Type of reboot: soft/hard/full",
1192
                             default=constants.INSTANCE_REBOOT_HARD,
1193
                             metavar="<REBOOT>",
1194
                             choices=list(constants.REBOOT_TYPES))
1195

    
1196
IGNORE_SECONDARIES_OPT = cli_option("--ignore-secondaries",
1197
                                    dest="ignore_secondaries",
1198
                                    default=False, action="store_true",
1199
                                    help="Ignore errors from secondaries")
1200

    
1201
NOSHUTDOWN_OPT = cli_option("--noshutdown", dest="shutdown",
1202
                            action="store_false", default=True,
1203
                            help="Don't shutdown the instance (unsafe)")
1204

    
1205
TIMEOUT_OPT = cli_option("--timeout", dest="timeout", type="int",
1206
                         default=constants.DEFAULT_SHUTDOWN_TIMEOUT,
1207
                         help="Maximum time to wait")
1208

    
1209
SHUTDOWN_TIMEOUT_OPT = cli_option("--shutdown-timeout",
1210
                         dest="shutdown_timeout", type="int",
1211
                         default=constants.DEFAULT_SHUTDOWN_TIMEOUT,
1212
                         help="Maximum time to wait for instance shutdown")
1213

    
1214
INTERVAL_OPT = cli_option("--interval", dest="interval", type="int",
1215
                          default=None,
1216
                          help=("Number of seconds between repetions of the"
1217
                                " command"))
1218

    
1219
EARLY_RELEASE_OPT = cli_option("--early-release",
1220
                               dest="early_release", default=False,
1221
                               action="store_true",
1222
                               help="Release the locks on the secondary"
1223
                               " node(s) early")
1224

    
1225
NEW_CLUSTER_CERT_OPT = cli_option("--new-cluster-certificate",
1226
                                  dest="new_cluster_cert",
1227
                                  default=False, action="store_true",
1228
                                  help="Generate a new cluster certificate")
1229

    
1230
RAPI_CERT_OPT = cli_option("--rapi-certificate", dest="rapi_cert",
1231
                           default=None,
1232
                           help="File containing new RAPI certificate")
1233

    
1234
NEW_RAPI_CERT_OPT = cli_option("--new-rapi-certificate", dest="new_rapi_cert",
1235
                               default=None, action="store_true",
1236
                               help=("Generate a new self-signed RAPI"
1237
                                     " certificate"))
1238

    
1239
SPICE_CERT_OPT = cli_option("--spice-certificate", dest="spice_cert",
1240
                           default=None,
1241
                           help="File containing new SPICE certificate")
1242

    
1243
SPICE_CACERT_OPT = cli_option("--spice-ca-certificate", dest="spice_cacert",
1244
                           default=None,
1245
                           help="File containing the certificate of the CA"
1246
                                " which signed the SPICE certificate")
1247

    
1248
NEW_SPICE_CERT_OPT = cli_option("--new-spice-certificate",
1249
                               dest="new_spice_cert", default=None,
1250
                               action="store_true",
1251
                               help=("Generate a new self-signed SPICE"
1252
                                     " certificate"))
1253

    
1254
NEW_CONFD_HMAC_KEY_OPT = cli_option("--new-confd-hmac-key",
1255
                                    dest="new_confd_hmac_key",
1256
                                    default=False, action="store_true",
1257
                                    help=("Create a new HMAC key for %s" %
1258
                                          constants.CONFD))
1259

    
1260
CLUSTER_DOMAIN_SECRET_OPT = cli_option("--cluster-domain-secret",
1261
                                       dest="cluster_domain_secret",
1262
                                       default=None,
1263
                                       help=("Load new new cluster domain"
1264
                                             " secret from file"))
1265

    
1266
NEW_CLUSTER_DOMAIN_SECRET_OPT = cli_option("--new-cluster-domain-secret",
1267
                                           dest="new_cluster_domain_secret",
1268
                                           default=False, action="store_true",
1269
                                           help=("Create a new cluster domain"
1270
                                                 " secret"))
1271

    
1272
USE_REPL_NET_OPT = cli_option("--use-replication-network",
1273
                              dest="use_replication_network",
1274
                              help="Whether to use the replication network"
1275
                              " for talking to the nodes",
1276
                              action="store_true", default=False)
1277

    
1278
MAINTAIN_NODE_HEALTH_OPT = \
1279
    cli_option("--maintain-node-health", dest="maintain_node_health",
1280
               metavar=_YORNO, default=None, type="bool",
1281
               help="Configure the cluster to automatically maintain node"
1282
               " health, by shutting down unknown instances, shutting down"
1283
               " unknown DRBD devices, etc.")
1284

    
1285
IDENTIFY_DEFAULTS_OPT = \
1286
    cli_option("--identify-defaults", dest="identify_defaults",
1287
               default=False, action="store_true",
1288
               help="Identify which saved instance parameters are equal to"
1289
               " the current cluster defaults and set them as such, instead"
1290
               " of marking them as overridden")
1291

    
1292
UIDPOOL_OPT = cli_option("--uid-pool", default=None,
1293
                         action="store", dest="uid_pool",
1294
                         help=("A list of user-ids or user-id"
1295
                               " ranges separated by commas"))
1296

    
1297
ADD_UIDS_OPT = cli_option("--add-uids", default=None,
1298
                          action="store", dest="add_uids",
1299
                          help=("A list of user-ids or user-id"
1300
                                " ranges separated by commas, to be"
1301
                                " added to the user-id pool"))
1302

    
1303
REMOVE_UIDS_OPT = cli_option("--remove-uids", default=None,
1304
                             action="store", dest="remove_uids",
1305
                             help=("A list of user-ids or user-id"
1306
                                   " ranges separated by commas, to be"
1307
                                   " removed from the user-id pool"))
1308

    
1309
RESERVED_LVS_OPT = cli_option("--reserved-lvs", default=None,
1310
                             action="store", dest="reserved_lvs",
1311
                             help=("A comma-separated list of reserved"
1312
                                   " logical volumes names, that will be"
1313
                                   " ignored by cluster verify"))
1314

    
1315
ROMAN_OPT = cli_option("--roman",
1316
                       dest="roman_integers", default=False,
1317
                       action="store_true",
1318
                       help="Use roman numbers for positive integers")
1319

    
1320
DRBD_HELPER_OPT = cli_option("--drbd-usermode-helper", dest="drbd_helper",
1321
                             action="store", default=None,
1322
                             help="Specifies usermode helper for DRBD")
1323

    
1324
NODRBD_STORAGE_OPT = cli_option("--no-drbd-storage", dest="drbd_storage",
1325
                                action="store_false", default=True,
1326
                                help="Disable support for DRBD")
1327

    
1328
PRIMARY_IP_VERSION_OPT = \
1329
    cli_option("--primary-ip-version", default=constants.IP4_VERSION,
1330
               action="store", dest="primary_ip_version",
1331
               metavar="%d|%d" % (constants.IP4_VERSION,
1332
                                  constants.IP6_VERSION),
1333
               help="Cluster-wide IP version for primary IP")
1334

    
1335
PRIORITY_OPT = cli_option("--priority", default=None, dest="priority",
1336
                          metavar="|".join(name for name, _ in _PRIORITY_NAMES),
1337
                          choices=_PRIONAME_TO_VALUE.keys(),
1338
                          help="Priority for opcode processing")
1339

    
1340
HID_OS_OPT = cli_option("--hidden", dest="hidden",
1341
                        type="bool", default=None, metavar=_YORNO,
1342
                        help="Sets the hidden flag on the OS")
1343

    
1344
BLK_OS_OPT = cli_option("--blacklisted", dest="blacklisted",
1345
                        type="bool", default=None, metavar=_YORNO,
1346
                        help="Sets the blacklisted flag on the OS")
1347

    
1348
PREALLOC_WIPE_DISKS_OPT = cli_option("--prealloc-wipe-disks", default=None,
1349
                                     type="bool", metavar=_YORNO,
1350
                                     dest="prealloc_wipe_disks",
1351
                                     help=("Wipe disks prior to instance"
1352
                                           " creation"))
1353

    
1354
NODE_PARAMS_OPT = cli_option("--node-parameters", dest="ndparams",
1355
                             type="keyval", default=None,
1356
                             help="Node parameters")
1357

    
1358
ALLOC_POLICY_OPT = cli_option("--alloc-policy", dest="alloc_policy",
1359
                              action="store", metavar="POLICY", default=None,
1360
                              help="Allocation policy for the node group")
1361

    
1362
NODE_POWERED_OPT = cli_option("--node-powered", default=None,
1363
                              type="bool", metavar=_YORNO,
1364
                              dest="node_powered",
1365
                              help="Specify if the SoR for node is powered")
1366

    
1367
OOB_TIMEOUT_OPT = cli_option("--oob-timeout", dest="oob_timeout", type="int",
1368
                         default=constants.OOB_TIMEOUT,
1369
                         help="Maximum time to wait for out-of-band helper")
1370

    
1371
POWER_DELAY_OPT = cli_option("--power-delay", dest="power_delay", type="float",
1372
                             default=constants.OOB_POWER_DELAY,
1373
                             help="Time in seconds to wait between power-ons")
1374

    
1375
FORCE_FILTER_OPT = cli_option("-F", "--filter", dest="force_filter",
1376
                              action="store_true", default=False,
1377
                              help=("Whether command argument should be treated"
1378
                                    " as filter"))
1379

    
1380
NO_REMEMBER_OPT = cli_option("--no-remember",
1381
                             dest="no_remember",
1382
                             action="store_true", default=False,
1383
                             help="Perform but do not record the change"
1384
                             " in the configuration")
1385

    
1386
PRIMARY_ONLY_OPT = cli_option("-p", "--primary-only",
1387
                              default=False, action="store_true",
1388
                              help="Evacuate primary instances only")
1389

    
1390
SECONDARY_ONLY_OPT = cli_option("-s", "--secondary-only",
1391
                                default=False, action="store_true",
1392
                                help="Evacuate secondary instances only"
1393
                                     " (applies only to internally mirrored"
1394
                                     " disk templates, e.g. %s)" %
1395
                                     utils.CommaJoin(constants.DTS_INT_MIRROR))
1396

    
1397
STARTUP_PAUSED_OPT = cli_option("--paused", dest="startup_paused",
1398
                                action="store_true", default=False,
1399
                                help="Pause instance at startup")
1400

    
1401
TO_GROUP_OPT = cli_option("--to", dest="to", metavar="<group>",
1402
                          help="Destination node group (name or uuid)",
1403
                          default=None, action="append",
1404
                          completion_suggest=OPT_COMPL_ONE_NODEGROUP)
1405

    
1406
IGNORE_ERRORS_OPT = cli_option("-I", "--ignore-errors", default=[],
1407
                               action="append", dest="ignore_errors",
1408
                               choices=list(constants.CV_ALL_ECODES_STRINGS),
1409
                               help="Error code to be ignored")
1410

    
1411
DISK_STATE_OPT = cli_option("--disk-state", default=[], dest="disk_state",
1412
                            action="append",
1413
                            help=("Specify disk state information in the"
1414
                                  " format"
1415
                                  " storage_type/identifier:option=value,...;"
1416
                                  " note this is unused for now"),
1417
                            type="identkeyval")
1418

    
1419
HV_STATE_OPT = cli_option("--hypervisor-state", default=[], dest="hv_state",
1420
                          action="append",
1421
                          help=("Specify hypervisor state information in the"
1422
                                " format hypervisor:option=value,...;"
1423
                                " note this is unused for now"),
1424
                          type="identkeyval")
1425

    
1426
IGNORE_IPOLICY_OPT = cli_option("--ignore-ipolicy", dest="ignore_ipolicy",
1427
                                action="store_true", default=False,
1428
                                help="Ignore instance policy violations")
1429

    
1430
RUNTIME_MEM_OPT = cli_option("-m", "--runtime-memory", dest="runtime_mem",
1431
                             help="Sets the instance's runtime memory,"
1432
                             " ballooning it up or down to the new value",
1433
                             default=None, type="unit", metavar="<size>")
1434

    
1435
ABSOLUTE_OPT = cli_option("--absolute", dest="absolute",
1436
                          action="store_true", default=False,
1437
                          help="Marks the grow as absolute instead of the"
1438
                          " (default) relative mode")
1439

    
1440
#: Options provided by all commands
1441
COMMON_OPTS = [DEBUG_OPT]
1442

    
1443
# common options for creating instances. add and import then add their own
1444
# specific ones.
1445
COMMON_CREATE_OPTS = [
1446
  BACKEND_OPT,
1447
  DISK_OPT,
1448
  DISK_TEMPLATE_OPT,
1449
  FILESTORE_DIR_OPT,
1450
  FILESTORE_DRIVER_OPT,
1451
  HYPERVISOR_OPT,
1452
  IALLOCATOR_OPT,
1453
  NET_OPT,
1454
  NODE_PLACEMENT_OPT,
1455
  NOIPCHECK_OPT,
1456
  NONAMECHECK_OPT,
1457
  NONICS_OPT,
1458
  NWSYNC_OPT,
1459
  OSPARAMS_OPT,
1460
  OS_SIZE_OPT,
1461
  SUBMIT_OPT,
1462
  TAG_ADD_OPT,
1463
  DRY_RUN_OPT,
1464
  PRIORITY_OPT,
1465
  ]
1466

    
1467
# common instance policy options
1468
INSTANCE_POLICY_OPTS = [
1469
  SPECS_CPU_COUNT_OPT,
1470
  SPECS_DISK_COUNT_OPT,
1471
  SPECS_DISK_SIZE_OPT,
1472
  SPECS_MEM_SIZE_OPT,
1473
  SPECS_NIC_COUNT_OPT,
1474
  IPOLICY_DISK_TEMPLATES,
1475
  IPOLICY_VCPU_RATIO,
1476
  IPOLICY_SPINDLE_RATIO,
1477
  ]
1478

    
1479

    
1480
def _ParseArgs(argv, commands, aliases, env_override):
1481
  """Parser for the command line arguments.
1482

1483
  This function parses the arguments and returns the function which
1484
  must be executed together with its (modified) arguments.
1485

1486
  @param argv: the command line
1487
  @param commands: dictionary with special contents, see the design
1488
      doc for cmdline handling
1489
  @param aliases: dictionary with command aliases {'alias': 'target, ...}
1490
  @param env_override: list of env variables allowed for default args
1491

1492
  """
1493
  assert not (env_override - set(commands))
1494

    
1495
  if len(argv) == 0:
1496
    binary = "<command>"
1497
  else:
1498
    binary = argv[0].split("/")[-1]
1499

    
1500
  if len(argv) > 1 and argv[1] == "--version":
1501
    ToStdout("%s (ganeti %s) %s", binary, constants.VCS_VERSION,
1502
             constants.RELEASE_VERSION)
1503
    # Quit right away. That way we don't have to care about this special
1504
    # argument. optparse.py does it the same.
1505
    sys.exit(0)
1506

    
1507
  if len(argv) < 2 or not (argv[1] in commands or
1508
                           argv[1] in aliases):
1509
    # let's do a nice thing
1510
    sortedcmds = commands.keys()
1511
    sortedcmds.sort()
1512

    
1513
    ToStdout("Usage: %s {command} [options...] [argument...]", binary)
1514
    ToStdout("%s <command> --help to see details, or man %s", binary, binary)
1515
    ToStdout("")
1516

    
1517
    # compute the max line length for cmd + usage
1518
    mlen = max([len(" %s" % cmd) for cmd in commands])
1519
    mlen = min(60, mlen) # should not get here...
1520

    
1521
    # and format a nice command list
1522
    ToStdout("Commands:")
1523
    for cmd in sortedcmds:
1524
      cmdstr = " %s" % (cmd,)
1525
      help_text = commands[cmd][4]
1526
      help_lines = textwrap.wrap(help_text, 79 - 3 - mlen)
1527
      ToStdout("%-*s - %s", mlen, cmdstr, help_lines.pop(0))
1528
      for line in help_lines:
1529
        ToStdout("%-*s   %s", mlen, "", line)
1530

    
1531
    ToStdout("")
1532

    
1533
    return None, None, None
1534

    
1535
  # get command, unalias it, and look it up in commands
1536
  cmd = argv.pop(1)
1537
  if cmd in aliases:
1538
    if cmd in commands:
1539
      raise errors.ProgrammerError("Alias '%s' overrides an existing"
1540
                                   " command" % cmd)
1541

    
1542
    if aliases[cmd] not in commands:
1543
      raise errors.ProgrammerError("Alias '%s' maps to non-existing"
1544
                                   " command '%s'" % (cmd, aliases[cmd]))
1545

    
1546
    cmd = aliases[cmd]
1547

    
1548
  if cmd in env_override:
1549
    args_env_name = ("%s_%s" % (binary.replace("-", "_"), cmd)).upper()
1550
    env_args = os.environ.get(args_env_name)
1551
    if env_args:
1552
      argv = utils.InsertAtPos(argv, 1, shlex.split(env_args))
1553

    
1554
  func, args_def, parser_opts, usage, description = commands[cmd]
1555
  parser = OptionParser(option_list=parser_opts + COMMON_OPTS,
1556
                        description=description,
1557
                        formatter=TitledHelpFormatter(),
1558
                        usage="%%prog %s %s" % (cmd, usage))
1559
  parser.disable_interspersed_args()
1560
  options, args = parser.parse_args(args=argv[1:])
1561

    
1562
  if not _CheckArguments(cmd, args_def, args):
1563
    return None, None, None
1564

    
1565
  return func, options, args
1566

    
1567

    
1568
def _CheckArguments(cmd, args_def, args):
1569
  """Verifies the arguments using the argument definition.
1570

1571
  Algorithm:
1572

1573
    1. Abort with error if values specified by user but none expected.
1574

1575
    1. For each argument in definition
1576

1577
      1. Keep running count of minimum number of values (min_count)
1578
      1. Keep running count of maximum number of values (max_count)
1579
      1. If it has an unlimited number of values
1580

1581
        1. Abort with error if it's not the last argument in the definition
1582

1583
    1. If last argument has limited number of values
1584

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

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

1589
  """
1590
  if args and not args_def:
1591
    ToStderr("Error: Command %s expects no arguments", cmd)
1592
    return False
1593

    
1594
  min_count = None
1595
  max_count = None
1596
  check_max = None
1597

    
1598
  last_idx = len(args_def) - 1
1599

    
1600
  for idx, arg in enumerate(args_def):
1601
    if min_count is None:
1602
      min_count = arg.min
1603
    elif arg.min is not None:
1604
      min_count += arg.min
1605

    
1606
    if max_count is None:
1607
      max_count = arg.max
1608
    elif arg.max is not None:
1609
      max_count += arg.max
1610

    
1611
    if idx == last_idx:
1612
      check_max = (arg.max is not None)
1613

    
1614
    elif arg.max is None:
1615
      raise errors.ProgrammerError("Only the last argument can have max=None")
1616

    
1617
  if check_max:
1618
    # Command with exact number of arguments
1619
    if (min_count is not None and max_count is not None and
1620
        min_count == max_count and len(args) != min_count):
1621
      ToStderr("Error: Command %s expects %d argument(s)", cmd, min_count)
1622
      return False
1623

    
1624
    # Command with limited number of arguments
1625
    if max_count is not None and len(args) > max_count:
1626
      ToStderr("Error: Command %s expects only %d argument(s)",
1627
               cmd, max_count)
1628
      return False
1629

    
1630
  # Command with some required arguments
1631
  if min_count is not None and len(args) < min_count:
1632
    ToStderr("Error: Command %s expects at least %d argument(s)",
1633
             cmd, min_count)
1634
    return False
1635

    
1636
  return True
1637

    
1638

    
1639
def SplitNodeOption(value):
1640
  """Splits the value of a --node option.
1641

1642
  """
1643
  if value and ":" in value:
1644
    return value.split(":", 1)
1645
  else:
1646
    return (value, None)
1647

    
1648

    
1649
def CalculateOSNames(os_name, os_variants):
1650
  """Calculates all the names an OS can be called, according to its variants.
1651

1652
  @type os_name: string
1653
  @param os_name: base name of the os
1654
  @type os_variants: list or None
1655
  @param os_variants: list of supported variants
1656
  @rtype: list
1657
  @return: list of valid names
1658

1659
  """
1660
  if os_variants:
1661
    return ["%s+%s" % (os_name, v) for v in os_variants]
1662
  else:
1663
    return [os_name]
1664

    
1665

    
1666
def ParseFields(selected, default):
1667
  """Parses the values of "--field"-like options.
1668

1669
  @type selected: string or None
1670
  @param selected: User-selected options
1671
  @type default: list
1672
  @param default: Default fields
1673

1674
  """
1675
  if selected is None:
1676
    return default
1677

    
1678
  if selected.startswith("+"):
1679
    return default + selected[1:].split(",")
1680

    
1681
  return selected.split(",")
1682

    
1683

    
1684
UsesRPC = rpc.RunWithRPC
1685

    
1686

    
1687
def AskUser(text, choices=None):
1688
  """Ask the user a question.
1689

1690
  @param text: the question to ask
1691

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

1697
  @return: one of the return values from the choices list; if input is
1698
      not possible (i.e. not running with a tty, we return the last
1699
      entry from the list
1700

1701
  """
1702
  if choices is None:
1703
    choices = [("y", True, "Perform the operation"),
1704
               ("n", False, "Do not perform the operation")]
1705
  if not choices or not isinstance(choices, list):
1706
    raise errors.ProgrammerError("Invalid choices argument to AskUser")
1707
  for entry in choices:
1708
    if not isinstance(entry, tuple) or len(entry) < 3 or entry[0] == "?":
1709
      raise errors.ProgrammerError("Invalid choices element to AskUser")
1710

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

    
1743

    
1744
class JobSubmittedException(Exception):
1745
  """Job was submitted, client should exit.
1746

1747
  This exception has one argument, the ID of the job that was
1748
  submitted. The handler should print this ID.
1749

1750
  This is not an error, just a structured way to exit from clients.
1751

1752
  """
1753

    
1754

    
1755
def SendJob(ops, cl=None):
1756
  """Function to submit an opcode without waiting for the results.
1757

1758
  @type ops: list
1759
  @param ops: list of opcodes
1760
  @type cl: luxi.Client
1761
  @param cl: the luxi client to use for communicating with the master;
1762
             if None, a new client will be created
1763

1764
  """
1765
  if cl is None:
1766
    cl = GetClient()
1767

    
1768
  job_id = cl.SubmitJob(ops)
1769

    
1770
  return job_id
1771

    
1772

    
1773
def GenericPollJob(job_id, cbs, report_cbs):
1774
  """Generic job-polling function.
1775

1776
  @type job_id: number
1777
  @param job_id: Job ID
1778
  @type cbs: Instance of L{JobPollCbBase}
1779
  @param cbs: Data callbacks
1780
  @type report_cbs: Instance of L{JobPollReportCbBase}
1781
  @param report_cbs: Reporting callbacks
1782

1783
  """
1784
  prev_job_info = None
1785
  prev_logmsg_serial = None
1786

    
1787
  status = None
1788

    
1789
  while True:
1790
    result = cbs.WaitForJobChangeOnce(job_id, ["status"], prev_job_info,
1791
                                      prev_logmsg_serial)
1792
    if not result:
1793
      # job not found, go away!
1794
      raise errors.JobLost("Job with id %s lost" % job_id)
1795

    
1796
    if result == constants.JOB_NOTCHANGED:
1797
      report_cbs.ReportNotChanged(job_id, status)
1798

    
1799
      # Wait again
1800
      continue
1801

    
1802
    # Split result, a tuple of (field values, log entries)
1803
    (job_info, log_entries) = result
1804
    (status, ) = job_info
1805

    
1806
    if log_entries:
1807
      for log_entry in log_entries:
1808
        (serial, timestamp, log_type, message) = log_entry
1809
        report_cbs.ReportLogMessage(job_id, serial, timestamp,
1810
                                    log_type, message)
1811
        prev_logmsg_serial = max(prev_logmsg_serial, serial)
1812

    
1813
    # TODO: Handle canceled and archived jobs
1814
    elif status in (constants.JOB_STATUS_SUCCESS,
1815
                    constants.JOB_STATUS_ERROR,
1816
                    constants.JOB_STATUS_CANCELING,
1817
                    constants.JOB_STATUS_CANCELED):
1818
      break
1819

    
1820
    prev_job_info = job_info
1821

    
1822
  jobs = cbs.QueryJobs([job_id], ["status", "opstatus", "opresult"])
1823
  if not jobs:
1824
    raise errors.JobLost("Job with id %s lost" % job_id)
1825

    
1826
  status, opstatus, result = jobs[0]
1827

    
1828
  if status == constants.JOB_STATUS_SUCCESS:
1829
    return result
1830

    
1831
  if status in (constants.JOB_STATUS_CANCELING, constants.JOB_STATUS_CANCELED):
1832
    raise errors.OpExecError("Job was canceled")
1833

    
1834
  has_ok = False
1835
  for idx, (status, msg) in enumerate(zip(opstatus, result)):
1836
    if status == constants.OP_STATUS_SUCCESS:
1837
      has_ok = True
1838
    elif status == constants.OP_STATUS_ERROR:
1839
      errors.MaybeRaise(msg)
1840

    
1841
      if has_ok:
1842
        raise errors.OpExecError("partial failure (opcode %d): %s" %
1843
                                 (idx, msg))
1844

    
1845
      raise errors.OpExecError(str(msg))
1846

    
1847
  # default failure mode
1848
  raise errors.OpExecError(result)
1849

    
1850

    
1851
class JobPollCbBase:
1852
  """Base class for L{GenericPollJob} callbacks.
1853

1854
  """
1855
  def __init__(self):
1856
    """Initializes this class.
1857

1858
    """
1859

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

1864
    """
1865
    raise NotImplementedError()
1866

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

1870
    @type job_ids: list of numbers
1871
    @param job_ids: Job IDs
1872
    @type fields: list of strings
1873
    @param fields: Fields
1874

1875
    """
1876
    raise NotImplementedError()
1877

    
1878

    
1879
class JobPollReportCbBase:
1880
  """Base class for L{GenericPollJob} reporting callbacks.
1881

1882
  """
1883
  def __init__(self):
1884
    """Initializes this class.
1885

1886
    """
1887

    
1888
  def ReportLogMessage(self, job_id, serial, timestamp, log_type, log_msg):
1889
    """Handles a log message.
1890

1891
    """
1892
    raise NotImplementedError()
1893

    
1894
  def ReportNotChanged(self, job_id, status):
1895
    """Called for if a job hasn't changed in a while.
1896

1897
    @type job_id: number
1898
    @param job_id: Job ID
1899
    @type status: string or None
1900
    @param status: Job status if available
1901

1902
    """
1903
    raise NotImplementedError()
1904

    
1905

    
1906
class _LuxiJobPollCb(JobPollCbBase):
1907
  def __init__(self, cl):
1908
    """Initializes this class.
1909

1910
    """
1911
    JobPollCbBase.__init__(self)
1912
    self.cl = cl
1913

    
1914
  def WaitForJobChangeOnce(self, job_id, fields,
1915
                           prev_job_info, prev_log_serial):
1916
    """Waits for changes on a job.
1917

1918
    """
1919
    return self.cl.WaitForJobChangeOnce(job_id, fields,
1920
                                        prev_job_info, prev_log_serial)
1921

    
1922
  def QueryJobs(self, job_ids, fields):
1923
    """Returns the selected fields for the selected job IDs.
1924

1925
    """
1926
    return self.cl.QueryJobs(job_ids, fields)
1927

    
1928

    
1929
class FeedbackFnJobPollReportCb(JobPollReportCbBase):
1930
  def __init__(self, feedback_fn):
1931
    """Initializes this class.
1932

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

    
1936
    self.feedback_fn = feedback_fn
1937

    
1938
    assert callable(feedback_fn)
1939

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

1943
    """
1944
    self.feedback_fn((timestamp, log_type, log_msg))
1945

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

1949
    """
1950
    # Ignore
1951

    
1952

    
1953
class StdioJobPollReportCb(JobPollReportCbBase):
1954
  def __init__(self):
1955
    """Initializes this class.
1956

1957
    """
1958
    JobPollReportCbBase.__init__(self)
1959

    
1960
    self.notified_queued = False
1961
    self.notified_waitlock = False
1962

    
1963
  def ReportLogMessage(self, job_id, serial, timestamp, log_type, log_msg):
1964
    """Handles a log message.
1965

1966
    """
1967
    ToStdout("%s %s", time.ctime(utils.MergeTime(timestamp)),
1968
             FormatLogMessage(log_type, log_msg))
1969

    
1970
  def ReportNotChanged(self, job_id, status):
1971
    """Called if a job hasn't changed in a while.
1972

1973
    """
1974
    if status is None:
1975
      return
1976

    
1977
    if status == constants.JOB_STATUS_QUEUED and not self.notified_queued:
1978
      ToStderr("Job %s is waiting in queue", job_id)
1979
      self.notified_queued = True
1980

    
1981
    elif status == constants.JOB_STATUS_WAITING and not self.notified_waitlock:
1982
      ToStderr("Job %s is trying to acquire all necessary locks", job_id)
1983
      self.notified_waitlock = True
1984

    
1985

    
1986
def FormatLogMessage(log_type, log_msg):
1987
  """Formats a job message according to its type.
1988

1989
  """
1990
  if log_type != constants.ELOG_MESSAGE:
1991
    log_msg = str(log_msg)
1992

    
1993
  return utils.SafeEncode(log_msg)
1994

    
1995

    
1996
def PollJob(job_id, cl=None, feedback_fn=None, reporter=None):
1997
  """Function to poll for the result of a job.
1998

1999
  @type job_id: job identified
2000
  @param job_id: the job to poll for results
2001
  @type cl: luxi.Client
2002
  @param cl: the luxi client to use for communicating with the master;
2003
             if None, a new client will be created
2004

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

    
2009
  if reporter is None:
2010
    if feedback_fn:
2011
      reporter = FeedbackFnJobPollReportCb(feedback_fn)
2012
    else:
2013
      reporter = StdioJobPollReportCb()
2014
  elif feedback_fn:
2015
    raise errors.ProgrammerError("Can't specify reporter and feedback function")
2016

    
2017
  return GenericPollJob(job_id, _LuxiJobPollCb(cl), reporter)
2018

    
2019

    
2020
def SubmitOpCode(op, cl=None, feedback_fn=None, opts=None, reporter=None):
2021
  """Legacy function to submit an opcode.
2022

2023
  This is just a simple wrapper over the construction of the processor
2024
  instance. It should be extended to better handle feedback and
2025
  interaction functions.
2026

2027
  """
2028
  if cl is None:
2029
    cl = GetClient()
2030

    
2031
  SetGenericOpcodeOpts([op], opts)
2032

    
2033
  job_id = SendJob([op], cl=cl)
2034

    
2035
  op_results = PollJob(job_id, cl=cl, feedback_fn=feedback_fn,
2036
                       reporter=reporter)
2037

    
2038
  return op_results[0]
2039

    
2040

    
2041
def SubmitOrSend(op, opts, cl=None, feedback_fn=None):
2042
  """Wrapper around SubmitOpCode or SendJob.
2043

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

2049
  It will also process the opcodes if we're sending the via SendJob
2050
  (otherwise SubmitOpCode does it).
2051

2052
  """
2053
  if opts and opts.submit_only:
2054
    job = [op]
2055
    SetGenericOpcodeOpts(job, opts)
2056
    job_id = SendJob(job, cl=cl)
2057
    raise JobSubmittedException(job_id)
2058
  else:
2059
    return SubmitOpCode(op, cl=cl, feedback_fn=feedback_fn, opts=opts)
2060

    
2061

    
2062
def SetGenericOpcodeOpts(opcode_list, options):
2063
  """Processor for generic options.
2064

2065
  This function updates the given opcodes based on generic command
2066
  line options (like debug, dry-run, etc.).
2067

2068
  @param opcode_list: list of opcodes
2069
  @param options: command line options or None
2070
  @return: None (in-place modification)
2071

2072
  """
2073
  if not options:
2074
    return
2075
  for op in opcode_list:
2076
    op.debug_level = options.debug
2077
    if hasattr(options, "dry_run"):
2078
      op.dry_run = options.dry_run
2079
    if getattr(options, "priority", None) is not None:
2080
      op.priority = _PRIONAME_TO_VALUE[options.priority]
2081

    
2082

    
2083
def GetClient():
2084
  # TODO: Cache object?
2085
  try:
2086
    client = luxi.Client()
2087
  except luxi.NoMasterError:
2088
    ss = ssconf.SimpleStore()
2089

    
2090
    # Try to read ssconf file
2091
    try:
2092
      ss.GetMasterNode()
2093
    except errors.ConfigurationError:
2094
      raise errors.OpPrereqError("Cluster not initialized or this machine is"
2095
                                 " not part of a cluster",
2096
                                 errors.ECODE_INVAL)
2097

    
2098
    master, myself = ssconf.GetMasterAndMyself(ss=ss)
2099
    if master != myself:
2100
      raise errors.OpPrereqError("This is not the master node, please connect"
2101
                                 " to node '%s' and rerun the command" %
2102
                                 master, errors.ECODE_INVAL)
2103
    raise
2104
  return client
2105

    
2106

    
2107
def FormatError(err):
2108
  """Return a formatted error message for a given error.
2109

2110
  This function takes an exception instance and returns a tuple
2111
  consisting of two values: first, the recommended exit code, and
2112
  second, a string describing the error message (not
2113
  newline-terminated).
2114

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

    
2194

    
2195
def GenericMain(commands, override=None, aliases=None,
2196
                env_override=frozenset()):
2197
  """Generic main function for all the gnt-* commands.
2198

2199
  @param commands: a dictionary with a special structure, see the design doc
2200
                   for command line handling.
2201
  @param override: if not None, we expect a dictionary with keys that will
2202
                   override command line options; this can be used to pass
2203
                   options from the scripts to generic functions
2204
  @param aliases: dictionary with command aliases {'alias': 'target, ...}
2205
  @param env_override: list of environment names which are allowed to submit
2206
                       default args for commands
2207

2208
  """
2209
  # save the program name and the entire command line for later logging
2210
  if sys.argv:
2211
    binary = os.path.basename(sys.argv[0])
2212
    if not binary:
2213
      binary = sys.argv[0]
2214

    
2215
    if len(sys.argv) >= 2:
2216
      logname = utils.ShellQuoteArgs([binary, sys.argv[1]])
2217
    else:
2218
      logname = binary
2219

    
2220
    cmdline = utils.ShellQuoteArgs([binary] + sys.argv[1:])
2221
  else:
2222
    binary = "<unknown program>"
2223
    cmdline = "<unknown>"
2224

    
2225
  if aliases is None:
2226
    aliases = {}
2227

    
2228
  try:
2229
    func, options, args = _ParseArgs(sys.argv, commands, aliases, env_override)
2230
  except errors.ParameterError, err:
2231
    result, err_msg = FormatError(err)
2232
    ToStderr(err_msg)
2233
    return 1
2234

    
2235
  if func is None: # parse error
2236
    return 1
2237

    
2238
  if override is not None:
2239
    for key, val in override.iteritems():
2240
      setattr(options, key, val)
2241

    
2242
  utils.SetupLogging(constants.LOG_COMMANDS, logname, debug=options.debug,
2243
                     stderr_logging=True)
2244

    
2245
  logging.info("Command line: %s", cmdline)
2246

    
2247
  try:
2248
    result = func(options, args)
2249
  except (errors.GenericError, luxi.ProtocolError,
2250
          JobSubmittedException), err:
2251
    result, err_msg = FormatError(err)
2252
    logging.exception("Error during command processing")
2253
    ToStderr(err_msg)
2254
  except KeyboardInterrupt:
2255
    result = constants.EXIT_FAILURE
2256
    ToStderr("Aborted. Note that if the operation created any jobs, they"
2257
             " might have been submitted and"
2258
             " will continue to run in the background.")
2259
  except IOError, err:
2260
    if err.errno == errno.EPIPE:
2261
      # our terminal went away, we'll exit
2262
      sys.exit(constants.EXIT_FAILURE)
2263
    else:
2264
      raise
2265

    
2266
  return result
2267

    
2268

    
2269
def ParseNicOption(optvalue):
2270
  """Parses the value of the --net option(s).
2271

2272
  """
2273
  try:
2274
    nic_max = max(int(nidx[0]) + 1 for nidx in optvalue)
2275
  except (TypeError, ValueError), err:
2276
    raise errors.OpPrereqError("Invalid NIC index passed: %s" % str(err),
2277
                               errors.ECODE_INVAL)
2278

    
2279
  nics = [{}] * nic_max
2280
  for nidx, ndict in optvalue:
2281
    nidx = int(nidx)
2282

    
2283
    if not isinstance(ndict, dict):
2284
      raise errors.OpPrereqError("Invalid nic/%d value: expected dict,"
2285
                                 " got %s" % (nidx, ndict), errors.ECODE_INVAL)
2286

    
2287
    utils.ForceDictType(ndict, constants.INIC_PARAMS_TYPES)
2288

    
2289
    nics[nidx] = ndict
2290

    
2291
  return nics
2292

    
2293

    
2294
def GenericInstanceCreate(mode, opts, args):
2295
  """Add an instance to the cluster via either creation or import.
2296

2297
  @param mode: constants.INSTANCE_CREATE or constants.INSTANCE_IMPORT
2298
  @param opts: the command line options selected by the user
2299
  @type args: list
2300
  @param args: should contain only one element, the new instance name
2301
  @rtype: int
2302
  @return: the desired exit code
2303

2304
  """
2305
  instance = args[0]
2306

    
2307
  (pnode, snode) = SplitNodeOption(opts.node)
2308

    
2309
  hypervisor = None
2310
  hvparams = {}
2311
  if opts.hypervisor:
2312
    hypervisor, hvparams = opts.hypervisor
2313

    
2314
  if opts.nics:
2315
    nics = ParseNicOption(opts.nics)
2316
  elif opts.no_nics:
2317
    # no nics
2318
    nics = []
2319
  elif mode == constants.INSTANCE_CREATE:
2320
    # default of one nic, all auto
2321
    nics = [{}]
2322
  else:
2323
    # mode == import
2324
    nics = []
2325

    
2326
  if opts.disk_template == constants.DT_DISKLESS:
2327
    if opts.disks or opts.sd_size is not None:
2328
      raise errors.OpPrereqError("Diskless instance but disk"
2329
                                 " information passed", errors.ECODE_INVAL)
2330
    disks = []
2331
  else:
2332
    if (not opts.disks and not opts.sd_size
2333
        and mode == constants.INSTANCE_CREATE):
2334
      raise errors.OpPrereqError("No disk information specified",
2335
                                 errors.ECODE_INVAL)
2336
    if opts.disks and opts.sd_size is not None:
2337
      raise errors.OpPrereqError("Please use either the '--disk' or"
2338
                                 " '-s' option", errors.ECODE_INVAL)
2339
    if opts.sd_size is not None:
2340
      opts.disks = [(0, {constants.IDISK_SIZE: opts.sd_size})]
2341

    
2342
    if opts.disks:
2343
      try:
2344
        disk_max = max(int(didx[0]) + 1 for didx in opts.disks)
2345
      except ValueError, err:
2346
        raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err),
2347
                                   errors.ECODE_INVAL)
2348
      disks = [{}] * disk_max
2349
    else:
2350
      disks = []
2351
    for didx, ddict in opts.disks:
2352
      didx = int(didx)
2353
      if not isinstance(ddict, dict):
2354
        msg = "Invalid disk/%d value: expected dict, got %s" % (didx, ddict)
2355
        raise errors.OpPrereqError(msg, errors.ECODE_INVAL)
2356
      elif constants.IDISK_SIZE in ddict:
2357
        if constants.IDISK_ADOPT in ddict:
2358
          raise errors.OpPrereqError("Only one of 'size' and 'adopt' allowed"
2359
                                     " (disk %d)" % didx, errors.ECODE_INVAL)
2360
        try:
2361
          ddict[constants.IDISK_SIZE] = \
2362
            utils.ParseUnit(ddict[constants.IDISK_SIZE])
2363
        except ValueError, err:
2364
          raise errors.OpPrereqError("Invalid disk size for disk %d: %s" %
2365
                                     (didx, err), errors.ECODE_INVAL)
2366
      elif constants.IDISK_ADOPT in ddict:
2367
        if mode == constants.INSTANCE_IMPORT:
2368
          raise errors.OpPrereqError("Disk adoption not allowed for instance"
2369
                                     " import", errors.ECODE_INVAL)
2370
        ddict[constants.IDISK_SIZE] = 0
2371
      else:
2372
        raise errors.OpPrereqError("Missing size or adoption source for"
2373
                                   " disk %d" % didx, errors.ECODE_INVAL)
2374
      disks[didx] = ddict
2375

    
2376
  if opts.tags is not None:
2377
    tags = opts.tags.split(",")
2378
  else:
2379
    tags = []
2380

    
2381
  utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_COMPAT)
2382
  utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
2383

    
2384
  if mode == constants.INSTANCE_CREATE:
2385
    start = opts.start
2386
    os_type = opts.os
2387
    force_variant = opts.force_variant
2388
    src_node = None
2389
    src_path = None
2390
    no_install = opts.no_install
2391
    identify_defaults = False
2392
  elif mode == constants.INSTANCE_IMPORT:
2393
    start = False
2394
    os_type = None
2395
    force_variant = False
2396
    src_node = opts.src_node
2397
    src_path = opts.src_dir
2398
    no_install = None
2399
    identify_defaults = opts.identify_defaults
2400
  else:
2401
    raise errors.ProgrammerError("Invalid creation mode %s" % mode)
2402

    
2403
  op = opcodes.OpInstanceCreate(instance_name=instance,
2404
                                disks=disks,
2405
                                disk_template=opts.disk_template,
2406
                                nics=nics,
2407
                                pnode=pnode, snode=snode,
2408
                                ip_check=opts.ip_check,
2409
                                name_check=opts.name_check,
2410
                                wait_for_sync=opts.wait_for_sync,
2411
                                file_storage_dir=opts.file_storage_dir,
2412
                                file_driver=opts.file_driver,
2413
                                iallocator=opts.iallocator,
2414
                                hypervisor=hypervisor,
2415
                                hvparams=hvparams,
2416
                                beparams=opts.beparams,
2417
                                osparams=opts.osparams,
2418
                                mode=mode,
2419
                                start=start,
2420
                                os_type=os_type,
2421
                                force_variant=force_variant,
2422
                                src_node=src_node,
2423
                                src_path=src_path,
2424
                                tags=tags,
2425
                                no_install=no_install,
2426
                                identify_defaults=identify_defaults,
2427
                                ignore_ipolicy=opts.ignore_ipolicy)
2428

    
2429
  SubmitOrSend(op, opts)
2430
  return 0
2431

    
2432

    
2433
class _RunWhileClusterStoppedHelper:
2434
  """Helper class for L{RunWhileClusterStopped} to simplify state management
2435

2436
  """
2437
  def __init__(self, feedback_fn, cluster_name, master_node, online_nodes):
2438
    """Initializes this class.
2439

2440
    @type feedback_fn: callable
2441
    @param feedback_fn: Feedback function
2442
    @type cluster_name: string
2443
    @param cluster_name: Cluster name
2444
    @type master_node: string
2445
    @param master_node Master node name
2446
    @type online_nodes: list
2447
    @param online_nodes: List of names of online nodes
2448

2449
    """
2450
    self.feedback_fn = feedback_fn
2451
    self.cluster_name = cluster_name
2452
    self.master_node = master_node
2453
    self.online_nodes = online_nodes
2454

    
2455
    self.ssh = ssh.SshRunner(self.cluster_name)
2456

    
2457
    self.nonmaster_nodes = [name for name in online_nodes
2458
                            if name != master_node]
2459

    
2460
    assert self.master_node not in self.nonmaster_nodes
2461

    
2462
  def _RunCmd(self, node_name, cmd):
2463
    """Runs a command on the local or a remote machine.
2464

2465
    @type node_name: string
2466
    @param node_name: Machine name
2467
    @type cmd: list
2468
    @param cmd: Command
2469

2470
    """
2471
    if node_name is None or node_name == self.master_node:
2472
      # No need to use SSH
2473
      result = utils.RunCmd(cmd)
2474
    else:
2475
      result = self.ssh.Run(node_name, "root", utils.ShellQuoteArgs(cmd))
2476

    
2477
    if result.failed:
2478
      errmsg = ["Failed to run command %s" % result.cmd]
2479
      if node_name:
2480
        errmsg.append("on node %s" % node_name)
2481
      errmsg.append(": exitcode %s and error %s" %
2482
                    (result.exit_code, result.output))
2483
      raise errors.OpExecError(" ".join(errmsg))
2484

    
2485
  def Call(self, fn, *args):
2486
    """Call function while all daemons are stopped.
2487

2488
    @type fn: callable
2489
    @param fn: Function to be called
2490

2491
    """
2492
    # Pause watcher by acquiring an exclusive lock on watcher state file
2493
    self.feedback_fn("Blocking watcher")
2494
    watcher_block = utils.FileLock.Open(constants.WATCHER_LOCK_FILE)
2495
    try:
2496
      # TODO: Currently, this just blocks. There's no timeout.
2497
      # TODO: Should it be a shared lock?
2498
      watcher_block.Exclusive(blocking=True)
2499

    
2500
      # Stop master daemons, so that no new jobs can come in and all running
2501
      # ones are finished
2502
      self.feedback_fn("Stopping master daemons")
2503
      self._RunCmd(None, [constants.DAEMON_UTIL, "stop-master"])
2504
      try:
2505
        # Stop daemons on all nodes
2506
        for node_name in self.online_nodes:
2507
          self.feedback_fn("Stopping daemons on %s" % node_name)
2508
          self._RunCmd(node_name, [constants.DAEMON_UTIL, "stop-all"])
2509

    
2510
        # All daemons are shut down now
2511
        try:
2512
          return fn(self, *args)
2513
        except Exception, err:
2514
          _, errmsg = FormatError(err)
2515
          logging.exception("Caught exception")
2516
          self.feedback_fn(errmsg)
2517
          raise
2518
      finally:
2519
        # Start cluster again, master node last
2520
        for node_name in self.nonmaster_nodes + [self.master_node]:
2521
          self.feedback_fn("Starting daemons on %s" % node_name)
2522
          self._RunCmd(node_name, [constants.DAEMON_UTIL, "start-all"])
2523
    finally:
2524
      # Resume watcher
2525
      watcher_block.Close()
2526

    
2527

    
2528
def RunWhileClusterStopped(feedback_fn, fn, *args):
2529
  """Calls a function while all cluster daemons are stopped.
2530

2531
  @type feedback_fn: callable
2532
  @param feedback_fn: Feedback function
2533
  @type fn: callable
2534
  @param fn: Function to be called when daemons are stopped
2535

2536
  """
2537
  feedback_fn("Gathering cluster information")
2538

    
2539
  # This ensures we're running on the master daemon
2540
  cl = GetClient()
2541

    
2542
  (cluster_name, master_node) = \
2543
    cl.QueryConfigValues(["cluster_name", "master_node"])
2544

    
2545
  online_nodes = GetOnlineNodes([], cl=cl)
2546

    
2547
  # Don't keep a reference to the client. The master daemon will go away.
2548
  del cl
2549

    
2550
  assert master_node in online_nodes
2551

    
2552
  return _RunWhileClusterStoppedHelper(feedback_fn, cluster_name, master_node,
2553
                                       online_nodes).Call(fn, *args)
2554

    
2555

    
2556
def GenerateTable(headers, fields, separator, data,
2557
                  numfields=None, unitfields=None,
2558
                  units=None):
2559
  """Prints a table with headers and different fields.
2560

2561
  @type headers: dict
2562
  @param headers: dictionary mapping field names to headers for
2563
      the table
2564
  @type fields: list
2565
  @param fields: the field names corresponding to each row in
2566
      the data field
2567
  @param separator: the separator to be used; if this is None,
2568
      the default 'smart' algorithm is used which computes optimal
2569
      field width, otherwise just the separator is used between
2570
      each field
2571
  @type data: list
2572
  @param data: a list of lists, each sublist being one row to be output
2573
  @type numfields: list
2574
  @param numfields: a list with the fields that hold numeric
2575
      values and thus should be right-aligned
2576
  @type unitfields: list
2577
  @param unitfields: a list with the fields that hold numeric
2578
      values that should be formatted with the units field
2579
  @type units: string or None
2580
  @param units: the units we should use for formatting, or None for
2581
      automatic choice (human-readable for non-separator usage, otherwise
2582
      megabytes); this is a one-letter string
2583

2584
  """
2585
  if units is None:
2586
    if separator:
2587
      units = "m"
2588
    else:
2589
      units = "h"
2590

    
2591
  if numfields is None:
2592
    numfields = []
2593
  if unitfields is None:
2594
    unitfields = []
2595

    
2596
  numfields = utils.FieldSet(*numfields)   # pylint: disable=W0142
2597
  unitfields = utils.FieldSet(*unitfields) # pylint: disable=W0142
2598

    
2599
  format_fields = []
2600
  for field in fields:
2601
    if headers and field not in headers:
2602
      # TODO: handle better unknown fields (either revert to old
2603
      # style of raising exception, or deal more intelligently with
2604
      # variable fields)
2605
      headers[field] = field
2606
    if separator is not None:
2607
      format_fields.append("%s")
2608
    elif numfields.Matches(field):
2609
      format_fields.append("%*s")
2610
    else:
2611
      format_fields.append("%-*s")
2612

    
2613
  if separator is None:
2614
    mlens = [0 for name in fields]
2615
    format_str = " ".join(format_fields)
2616
  else:
2617
    format_str = separator.replace("%", "%%").join(format_fields)
2618

    
2619
  for row in data:
2620
    if row is None:
2621
      continue
2622
    for idx, val in enumerate(row):
2623
      if unitfields.Matches(fields[idx]):
2624
        try:
2625
          val = int(val)
2626
        except (TypeError, ValueError):
2627
          pass
2628
        else:
2629
          val = row[idx] = utils.FormatUnit(val, units)
2630
      val = row[idx] = str(val)
2631
      if separator is None:
2632
        mlens[idx] = max(mlens[idx], len(val))
2633

    
2634
  result = []
2635
  if headers:
2636
    args = []
2637
    for idx, name in enumerate(fields):
2638
      hdr = headers[name]
2639
      if separator is None:
2640
        mlens[idx] = max(mlens[idx], len(hdr))
2641
        args.append(mlens[idx])
2642
      args.append(hdr)
2643
    result.append(format_str % tuple(args))
2644

    
2645
  if separator is None:
2646
    assert len(mlens) == len(fields)
2647

    
2648
    if fields and not numfields.Matches(fields[-1]):
2649
      mlens[-1] = 0
2650

    
2651
  for line in data:
2652
    args = []
2653
    if line is None:
2654
      line = ["-" for _ in fields]
2655
    for idx in range(len(fields)):
2656
      if separator is None:
2657
        args.append(mlens[idx])
2658
      args.append(line[idx])
2659
    result.append(format_str % tuple(args))
2660

    
2661
  return result
2662

    
2663

    
2664
def _FormatBool(value):
2665
  """Formats a boolean value as a string.
2666

2667
  """
2668
  if value:
2669
    return "Y"
2670
  return "N"
2671

    
2672

    
2673
#: Default formatting for query results; (callback, align right)
2674
_DEFAULT_FORMAT_QUERY = {
2675
  constants.QFT_TEXT: (str, False),
2676
  constants.QFT_BOOL: (_FormatBool, False),
2677
  constants.QFT_NUMBER: (str, True),
2678
  constants.QFT_TIMESTAMP: (utils.FormatTime, False),
2679
  constants.QFT_OTHER: (str, False),
2680
  constants.QFT_UNKNOWN: (str, False),
2681
  }
2682

    
2683

    
2684
def _GetColumnFormatter(fdef, override, unit):
2685
  """Returns formatting function for a field.
2686

2687
  @type fdef: L{objects.QueryFieldDefinition}
2688
  @type override: dict
2689
  @param override: Dictionary for overriding field formatting functions,
2690
    indexed by field name, contents like L{_DEFAULT_FORMAT_QUERY}
2691
  @type unit: string
2692
  @param unit: Unit used for formatting fields of type L{constants.QFT_UNIT}
2693
  @rtype: tuple; (callable, bool)
2694
  @return: Returns the function to format a value (takes one parameter) and a
2695
    boolean for aligning the value on the right-hand side
2696

2697
  """
2698
  fmt = override.get(fdef.name, None)
2699
  if fmt is not None:
2700
    return fmt
2701

    
2702
  assert constants.QFT_UNIT not in _DEFAULT_FORMAT_QUERY
2703

    
2704
  if fdef.kind == constants.QFT_UNIT:
2705
    # Can't keep this information in the static dictionary
2706
    return (lambda value: utils.FormatUnit(value, unit), True)
2707

    
2708
  fmt = _DEFAULT_FORMAT_QUERY.get(fdef.kind, None)
2709
  if fmt is not None:
2710
    return fmt
2711

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

    
2714

    
2715
class _QueryColumnFormatter:
2716
  """Callable class for formatting fields of a query.
2717

2718
  """
2719
  def __init__(self, fn, status_fn, verbose):
2720
    """Initializes this class.
2721

2722
    @type fn: callable
2723
    @param fn: Formatting function
2724
    @type status_fn: callable
2725
    @param status_fn: Function to report fields' status
2726
    @type verbose: boolean
2727
    @param verbose: whether to use verbose field descriptions or not
2728

2729
    """
2730
    self._fn = fn
2731
    self._status_fn = status_fn
2732
    self._verbose = verbose
2733

    
2734
  def __call__(self, data):
2735
    """Returns a field's string representation.
2736

2737
    """
2738
    (status, value) = data
2739

    
2740
    # Report status
2741
    self._status_fn(status)
2742

    
2743
    if status == constants.RS_NORMAL:
2744
      return self._fn(value)
2745

    
2746
    assert value is None, \
2747
           "Found value %r for abnormal status %s" % (value, status)
2748

    
2749
    return FormatResultError(status, self._verbose)
2750

    
2751

    
2752
def FormatResultError(status, verbose):
2753
  """Formats result status other than L{constants.RS_NORMAL}.
2754

2755
  @param status: The result status
2756
  @type verbose: boolean
2757
  @param verbose: Whether to return the verbose text
2758
  @return: Text of result status
2759

2760
  """
2761
  assert status != constants.RS_NORMAL, \
2762
         "FormatResultError called with status equal to constants.RS_NORMAL"
2763
  try:
2764
    (verbose_text, normal_text) = constants.RSS_DESCRIPTION[status]
2765
  except KeyError:
2766
    raise NotImplementedError("Unknown status %s" % status)
2767
  else:
2768
    if verbose:
2769
      return verbose_text
2770
    return normal_text
2771

    
2772

    
2773
def FormatQueryResult(result, unit=None, format_override=None, separator=None,
2774
                      header=False, verbose=False):
2775
  """Formats data in L{objects.QueryResponse}.
2776

2777
  @type result: L{objects.QueryResponse}
2778
  @param result: result of query operation
2779
  @type unit: string
2780
  @param unit: Unit used for formatting fields of type L{constants.QFT_UNIT},
2781
    see L{utils.text.FormatUnit}
2782
  @type format_override: dict
2783
  @param format_override: Dictionary for overriding field formatting functions,
2784
    indexed by field name, contents like L{_DEFAULT_FORMAT_QUERY}
2785
  @type separator: string or None
2786
  @param separator: String used to separate fields
2787
  @type header: bool
2788
  @param header: Whether to output header row
2789
  @type verbose: boolean
2790
  @param verbose: whether to use verbose field descriptions or not
2791

2792
  """
2793
  if unit is None:
2794
    if separator:
2795
      unit = "m"
2796
    else:
2797
      unit = "h"
2798

    
2799
  if format_override is None:
2800
    format_override = {}
2801

    
2802
  stats = dict.fromkeys(constants.RS_ALL, 0)
2803

    
2804
  def _RecordStatus(status):
2805
    if status in stats:
2806
      stats[status] += 1
2807

    
2808
  columns = []
2809
  for fdef in result.fields:
2810
    assert fdef.title and fdef.name
2811
    (fn, align_right) = _GetColumnFormatter(fdef, format_override, unit)
2812
    columns.append(TableColumn(fdef.title,
2813
                               _QueryColumnFormatter(fn, _RecordStatus,
2814
                                                     verbose),
2815
                               align_right))
2816

    
2817
  table = FormatTable(result.data, columns, header, separator)
2818

    
2819
  # Collect statistics
2820
  assert len(stats) == len(constants.RS_ALL)
2821
  assert compat.all(count >= 0 for count in stats.values())
2822

    
2823
  # Determine overall status. If there was no data, unknown fields must be
2824
  # detected via the field definitions.
2825
  if (stats[constants.RS_UNKNOWN] or
2826
      (not result.data and _GetUnknownFields(result.fields))):
2827
    status = QR_UNKNOWN
2828
  elif compat.any(count > 0 for key, count in stats.items()
2829
                  if key != constants.RS_NORMAL):
2830
    status = QR_INCOMPLETE
2831
  else:
2832
    status = QR_NORMAL
2833

    
2834
  return (status, table)
2835

    
2836

    
2837
def _GetUnknownFields(fdefs):
2838
  """Returns list of unknown fields included in C{fdefs}.
2839

2840
  @type fdefs: list of L{objects.QueryFieldDefinition}
2841

2842
  """
2843
  return [fdef for fdef in fdefs
2844
          if fdef.kind == constants.QFT_UNKNOWN]
2845

    
2846

    
2847
def _WarnUnknownFields(fdefs):
2848
  """Prints a warning to stderr if a query included unknown fields.
2849

2850
  @type fdefs: list of L{objects.QueryFieldDefinition}
2851

2852
  """
2853
  unknown = _GetUnknownFields(fdefs)
2854
  if unknown:
2855
    ToStderr("Warning: Queried for unknown fields %s",
2856
             utils.CommaJoin(fdef.name for fdef in unknown))
2857
    return True
2858

    
2859
  return False
2860

    
2861

    
2862
def GenericList(resource, fields, names, unit, separator, header, cl=None,
2863
                format_override=None, verbose=False, force_filter=False,
2864
                namefield=None, qfilter=None):
2865
  """Generic implementation for listing all items of a resource.
2866

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

2893
  """
2894
  if not names:
2895
    names = None
2896

    
2897
  namefilter = qlang.MakeFilter(names, force_filter, namefield=namefield)
2898

    
2899
  if qfilter is None:
2900
    qfilter = namefilter
2901
  elif namefilter is not None:
2902
    qfilter = [qlang.OP_AND, namefilter, qfilter]
2903

    
2904
  if cl is None:
2905
    cl = GetClient()
2906

    
2907
  response = cl.Query(resource, fields, qfilter)
2908

    
2909
  found_unknown = _WarnUnknownFields(response.fields)
2910

    
2911
  (status, data) = FormatQueryResult(response, unit=unit, separator=separator,
2912
                                     header=header,
2913
                                     format_override=format_override,
2914
                                     verbose=verbose)
2915

    
2916
  for line in data:
2917
    ToStdout(line)
2918

    
2919
  assert ((found_unknown and status == QR_UNKNOWN) or
2920
          (not found_unknown and status != QR_UNKNOWN))
2921

    
2922
  if status == QR_UNKNOWN:
2923
    return constants.EXIT_UNKNOWN_FIELD
2924

    
2925
  # TODO: Should the list command fail if not all data could be collected?
2926
  return constants.EXIT_SUCCESS
2927

    
2928

    
2929
def GenericListFields(resource, fields, separator, header, cl=None):
2930
  """Generic implementation for listing fields for a resource.
2931

2932
  @param resource: One of L{constants.QR_VIA_LUXI}
2933
  @type fields: list of strings
2934
  @param fields: List of fields to query for
2935
  @type separator: string or None
2936
  @param separator: String used to separate fields
2937
  @type header: bool
2938
  @param header: Whether to show header row
2939

2940
  """
2941
  if cl is None:
2942
    cl = GetClient()
2943

    
2944
  if not fields:
2945
    fields = None
2946

    
2947
  response = cl.QueryFields(resource, fields)
2948

    
2949
  found_unknown = _WarnUnknownFields(response.fields)
2950

    
2951
  columns = [
2952
    TableColumn("Name", str, False),
2953
    TableColumn("Title", str, False),
2954
    TableColumn("Description", str, False),
2955
    ]
2956

    
2957
  rows = [[fdef.name, fdef.title, fdef.doc] for fdef in response.fields]
2958

    
2959
  for line in FormatTable(rows, columns, header, separator):
2960
    ToStdout(line)
2961

    
2962
  if found_unknown:
2963
    return constants.EXIT_UNKNOWN_FIELD
2964

    
2965
  return constants.EXIT_SUCCESS
2966

    
2967

    
2968
class TableColumn:
2969
  """Describes a column for L{FormatTable}.
2970

2971
  """
2972
  def __init__(self, title, fn, align_right):
2973
    """Initializes this class.
2974

2975
    @type title: string
2976
    @param title: Column title
2977
    @type fn: callable
2978
    @param fn: Formatting function
2979
    @type align_right: bool
2980
    @param align_right: Whether to align values on the right-hand side
2981

2982
    """
2983
    self.title = title
2984
    self.format = fn
2985
    self.align_right = align_right
2986

    
2987

    
2988
def _GetColFormatString(width, align_right):
2989
  """Returns the format string for a field.
2990

2991
  """
2992
  if align_right:
2993
    sign = ""
2994
  else:
2995
    sign = "-"
2996

    
2997
  return "%%%s%ss" % (sign, width)
2998

    
2999

    
3000
def FormatTable(rows, columns, header, separator):
3001
  """Formats data as a table.
3002

3003
  @type rows: list of lists
3004
  @param rows: Row data, one list per row
3005
  @type columns: list of L{TableColumn}
3006
  @param columns: Column descriptions
3007
  @type header: bool
3008
  @param header: Whether to show header row
3009
  @type separator: string or None
3010
  @param separator: String used to separate columns
3011

3012
  """
3013
  if header:
3014
    data = [[col.title for col in columns]]
3015
    colwidth = [len(col.title) for col in columns]
3016
  else:
3017
    data = []
3018
    colwidth = [0 for _ in columns]
3019

    
3020
  # Format row data
3021
  for row in rows:
3022
    assert len(row) == len(columns)
3023

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

    
3026
    if separator is None:
3027
      # Update column widths
3028
      for idx, (oldwidth, value) in enumerate(zip(colwidth, formatted)):
3029
        # Modifying a list's items while iterating is fine
3030
        colwidth[idx] = max(oldwidth, len(value))
3031

    
3032
    data.append(formatted)
3033

    
3034
  if separator is not None:
3035
    # Return early if a separator is used
3036
    return [separator.join(row) for row in data]
3037

    
3038
  if columns and not columns[-1].align_right:
3039
    # Avoid unnecessary spaces at end of line
3040
    colwidth[-1] = 0
3041

    
3042
  # Build format string
3043
  fmt = " ".join([_GetColFormatString(width, col.align_right)
3044
                  for col, width in zip(columns, colwidth)])
3045

    
3046
  return [fmt % tuple(row) for row in data]
3047

    
3048

    
3049
def FormatTimestamp(ts):
3050
  """Formats a given timestamp.
3051

3052
  @type ts: timestamp
3053
  @param ts: a timeval-type timestamp, a tuple of seconds and microseconds
3054

3055
  @rtype: string
3056
  @return: a string with the formatted timestamp
3057

3058
  """
3059
  if not isinstance(ts, (tuple, list)) or len(ts) != 2:
3060
    return "?"
3061

    
3062
  (sec, usecs) = ts
3063
  return utils.FormatTime(sec, usecs=usecs)
3064

    
3065

    
3066
def ParseTimespec(value):
3067
  """Parse a time specification.
3068

3069
  The following suffixed will be recognized:
3070

3071
    - s: seconds
3072
    - m: minutes
3073
    - h: hours
3074
    - d: day
3075
    - w: weeks
3076

3077
  Without any suffix, the value will be taken to be in seconds.
3078

3079
  """
3080
  value = str(value)
3081
  if not value:
3082
    raise errors.OpPrereqError("Empty time specification passed",
3083
                               errors.ECODE_INVAL)
3084
  suffix_map = {
3085
    "s": 1,
3086
    "m": 60,
3087
    "h": 3600,
3088
    "d": 86400,
3089
    "w": 604800,
3090
    }
3091
  if value[-1] not in suffix_map:
3092
    try:
3093
      value = int(value)
3094
    except (TypeError, ValueError):
3095
      raise errors.OpPrereqError("Invalid time specification '%s'" % value,
3096
                                 errors.ECODE_INVAL)
3097
  else:
3098
    multiplier = suffix_map[value[-1]]
3099
    value = value[:-1]
3100
    if not value: # no data left after stripping the suffix
3101
      raise errors.OpPrereqError("Invalid time specification (only"
3102
                                 " suffix passed)", errors.ECODE_INVAL)
3103
    try:
3104
      value = int(value) * multiplier
3105
    except (TypeError, ValueError):
3106
      raise errors.OpPrereqError("Invalid time specification '%s'" % value,
3107
                                 errors.ECODE_INVAL)
3108
  return value
3109

    
3110

    
3111
def GetOnlineNodes(nodes, cl=None, nowarn=False, secondary_ips=False,
3112
                   filter_master=False, nodegroup=None):
3113
  """Returns the names of online nodes.
3114

3115
  This function will also log a warning on stderr with the names of
3116
  the online nodes.
3117

3118
  @param nodes: if not empty, use only this subset of nodes (minus the
3119
      offline ones)
3120
  @param cl: if not None, luxi client to use
3121
  @type nowarn: boolean
3122
  @param nowarn: by default, this function will output a note with the
3123
      offline nodes that are skipped; if this parameter is True the
3124
      note is not displayed
3125
  @type secondary_ips: boolean
3126
  @param secondary_ips: if True, return the secondary IPs instead of the
3127
      names, useful for doing network traffic over the replication interface
3128
      (if any)
3129
  @type filter_master: boolean
3130
  @param filter_master: if True, do not return the master node in the list
3131
      (useful in coordination with secondary_ips where we cannot check our
3132
      node name against the list)
3133
  @type nodegroup: string
3134
  @param nodegroup: If set, only return nodes in this node group
3135

3136
  """
3137
  if cl is None:
3138
    cl = GetClient()
3139

    
3140
  qfilter = []
3141

    
3142
  if nodes:
3143
    qfilter.append(qlang.MakeSimpleFilter("name", nodes))
3144

    
3145
  if nodegroup is not None:
3146
    qfilter.append([qlang.OP_OR, [qlang.OP_EQUAL, "group", nodegroup],
3147
                                 [qlang.OP_EQUAL, "group.uuid", nodegroup]])
3148

    
3149
  if filter_master:
3150
    qfilter.append([qlang.OP_NOT, [qlang.OP_TRUE, "master"]])
3151

    
3152
  if qfilter:
3153
    if len(qfilter) > 1:
3154
      final_filter = [qlang.OP_AND] + qfilter
3155
    else:
3156
      assert len(qfilter) == 1
3157
      final_filter = qfilter[0]
3158
  else:
3159
    final_filter = None
3160

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

    
3163
  def _IsOffline(row):
3164
    (_, (_, offline), _) = row
3165
    return offline
3166

    
3167
  def _GetName(row):
3168
    ((_, name), _, _) = row
3169
    return name
3170

    
3171
  def _GetSip(row):
3172
    (_, _, (_, sip)) = row
3173
    return sip
3174

    
3175
  (offline, online) = compat.partition(result.data, _IsOffline)
3176

    
3177
  if offline and not nowarn:
3178
    ToStderr("Note: skipping offline node(s): %s" %
3179
             utils.CommaJoin(map(_GetName, offline)))
3180

    
3181
  if secondary_ips:
3182
    fn = _GetSip
3183
  else:
3184
    fn = _GetName
3185

    
3186
  return map(fn, online)
3187

    
3188

    
3189
def _ToStream(stream, txt, *args):
3190
  """Write a message to a stream, bypassing the logging system
3191

3192
  @type stream: file object
3193
  @param stream: the file to which we should write
3194
  @type txt: str
3195
  @param txt: the message
3196

3197
  """
3198
  try:
3199
    if args:
3200
      args = tuple(args)
3201
      stream.write(txt % args)
3202
    else:
3203
      stream.write(txt)
3204
    stream.write("\n")
3205
    stream.flush()
3206
  except IOError, err:
3207
    if err.errno == errno.EPIPE:
3208
      # our terminal went away, we'll exit
3209
      sys.exit(constants.EXIT_FAILURE)
3210
    else:
3211
      raise
3212

    
3213

    
3214
def ToStdout(txt, *args):
3215
  """Write a message to stdout only, bypassing the logging system
3216

3217
  This is just a wrapper over _ToStream.
3218

3219
  @type txt: str
3220
  @param txt: the message
3221

3222
  """
3223
  _ToStream(sys.stdout, txt, *args)
3224

    
3225

    
3226
def ToStderr(txt, *args):
3227
  """Write a message to stderr only, bypassing the logging system
3228

3229
  This is just a wrapper over _ToStream.
3230

3231
  @type txt: str
3232
  @param txt: the message
3233

3234
  """
3235
  _ToStream(sys.stderr, txt, *args)
3236

    
3237

    
3238
class JobExecutor(object):
3239
  """Class which manages the submission and execution of multiple jobs.
3240

3241
  Note that instances of this class should not be reused between
3242
  GetResults() calls.
3243

3244
  """
3245
  def __init__(self, cl=None, verbose=True, opts=None, feedback_fn=None):
3246
    self.queue = []
3247
    if cl is None:
3248
      cl = GetClient()
3249
    self.cl = cl
3250
    self.verbose = verbose
3251
    self.jobs = []
3252
    self.opts = opts
3253
    self.feedback_fn = feedback_fn
3254
    self._counter = itertools.count()
3255

    
3256
  @staticmethod
3257
  def _IfName(name, fmt):
3258
    """Helper function for formatting name.
3259

3260
    """
3261
    if name:
3262
      return fmt % name
3263

    
3264
    return ""
3265

    
3266
  def QueueJob(self, name, *ops):
3267
    """Record a job for later submit.
3268

3269
    @type name: string
3270
    @param name: a description of the job, will be used in WaitJobSet
3271

3272
    """
3273
    SetGenericOpcodeOpts(ops, self.opts)
3274
    self.queue.append((self._counter.next(), name, ops))
3275

    
3276
  def AddJobId(self, name, status, job_id):
3277
    """Adds a job ID to the internal queue.
3278

3279
    """
3280
    self.jobs.append((self._counter.next(), status, job_id, name))
3281

    
3282
  def SubmitPending(self, each=False):
3283
    """Submit all pending jobs.
3284

3285
    """
3286
    if each:
3287
      results = []
3288
      for (_, _, ops) in self.queue:
3289
        # SubmitJob will remove the success status, but raise an exception if
3290
        # the submission fails, so we'll notice that anyway.
3291
        results.append([True, self.cl.SubmitJob(ops)[0]])
3292
    else:
3293
      results = self.cl.SubmitManyJobs([ops for (_, _, ops) in self.queue])
3294
    for ((status, data), (idx, name, _)) in zip(results, self.queue):
3295
      self.jobs.append((idx, status, data, name))
3296

    
3297
  def _ChooseJob(self):
3298
    """Choose a non-waiting/queued job to poll next.
3299

3300
    """
3301
    assert self.jobs, "_ChooseJob called with empty job list"
3302

    
3303
    result = self.cl.QueryJobs([i[2] for i in self.jobs[:_CHOOSE_BATCH]],
3304
                               ["status"])
3305
    assert result
3306

    
3307
    for job_data, status in zip(self.jobs, result):
3308
      if (isinstance(status, list) and status and
3309
          status[0] in (constants.JOB_STATUS_QUEUED,
3310
                        constants.JOB_STATUS_WAITING,
3311
                        constants.JOB_STATUS_CANCELING)):
3312
        # job is still present and waiting
3313
        continue
3314
      # good candidate found (either running job or lost job)
3315
      self.jobs.remove(job_data)
3316
      return job_data
3317

    
3318
    # no job found
3319
    return self.jobs.pop(0)
3320

    
3321
  def GetResults(self):
3322
    """Wait for and return the results of all jobs.
3323

3324
    @rtype: list
3325
    @return: list of tuples (success, job results), in the same order
3326
        as the submitted jobs; if a job has failed, instead of the result
3327
        there will be the error message
3328

3329
    """
3330
    if not self.jobs:
3331
      self.SubmitPending()
3332
    results = []
3333
    if self.verbose:
3334
      ok_jobs = [row[2] for row in self.jobs if row[1]]
3335
      if ok_jobs:
3336
        ToStdout("Submitted jobs %s", utils.CommaJoin(ok_jobs))
3337

    
3338
    # first, remove any non-submitted jobs
3339
    self.jobs, failures = compat.partition(self.jobs, lambda x: x[1])
3340
    for idx, _, jid, name in failures:
3341
      ToStderr("Failed to submit job%s: %s", self._IfName(name, " for %s"), jid)
3342
      results.append((idx, False, jid))
3343

    
3344
    while self.jobs:
3345
      (idx, _, jid, name) = self._ChooseJob()
3346
      ToStdout("Waiting for job %s%s ...", jid, self._IfName(name, " for %s"))
3347
      try:
3348
        job_result = PollJob(jid, cl=self.cl, feedback_fn=self.feedback_fn)
3349
        success = True
3350
      except errors.JobLost, err:
3351
        _, job_result = FormatError(err)
3352
        ToStderr("Job %s%s has been archived, cannot check its result",
3353
                 jid, self._IfName(name, " for %s"))
3354
        success = False
3355
      except (errors.GenericError, luxi.ProtocolError), err:
3356
        _, job_result = FormatError(err)
3357
        success = False
3358
        # the error message will always be shown, verbose or not
3359
        ToStderr("Job %s%s has failed: %s",
3360
                 jid, self._IfName(name, " for %s"), job_result)
3361

    
3362
      results.append((idx, success, job_result))
3363

    
3364
    # sort based on the index, then drop it
3365
    results.sort()
3366
    results = [i[1:] for i in results]
3367

    
3368
    return results
3369

    
3370
  def WaitOrShow(self, wait):
3371
    """Wait for job results or only print the job IDs.
3372

3373
    @type wait: boolean
3374
    @param wait: whether to wait or not
3375

3376
    """
3377
    if wait:
3378
      return self.GetResults()
3379
    else:
3380
      if not self.jobs:
3381
        self.SubmitPending()
3382
      for _, status, result, name in self.jobs:
3383
        if status:
3384
          ToStdout("%s: %s", result, name)
3385
        else:
3386
          ToStderr("Failure for %s: %s", name, result)
3387
      return [row[1:3] for row in self.jobs]
3388

    
3389

    
3390
def FormatParameterDict(buf, param_dict, actual, level=1):
3391
  """Formats a parameter dictionary.
3392

3393
  @type buf: L{StringIO}
3394
  @param buf: the buffer into which to write
3395
  @type param_dict: dict
3396
  @param param_dict: the own parameters
3397
  @type actual: dict
3398
  @param actual: the current parameter set (including defaults)
3399
  @param level: Level of indent
3400

3401
  """
3402
  indent = "  " * level
3403

    
3404
  for key in sorted(actual):
3405
    data = actual[key]
3406
    buf.write("%s- %s:" % (indent, key))
3407

    
3408
    if isinstance(data, dict) and data:
3409
      buf.write("\n")
3410
      FormatParameterDict(buf, param_dict.get(key, {}), data,
3411
                          level=level + 1)
3412
    else:
3413
      val = param_dict.get(key, "default (%s)" % data)
3414
      buf.write(" %s\n" % val)
3415

    
3416

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

3420
  This function is used to request confirmation for doing an operation
3421
  on a given list of list_type.
3422

3423
  @type names: list
3424
  @param names: the list of names that we display when
3425
      we ask for confirmation
3426
  @type list_type: str
3427
  @param list_type: Human readable name for elements in the list (e.g. nodes)
3428
  @type text: str
3429
  @param text: the operation that the user should confirm
3430
  @rtype: boolean
3431
  @return: True or False depending on user's confirmation.
3432

3433
  """
3434
  count = len(names)
3435
  msg = ("The %s will operate on %d %s.\n%s"
3436
         "Do you want to continue?" % (text, count, list_type, extra))
3437
  affected = (("\nAffected %s:\n" % list_type) +
3438
              "\n".join(["  %s" % name for name in names]))
3439

    
3440
  choices = [("y", True, "Yes, execute the %s" % text),
3441
             ("n", False, "No, abort the %s" % text)]
3442

    
3443
  if count > 20:
3444
    choices.insert(1, ("v", "v", "View the list of affected %s" % list_type))
3445
    question = msg
3446
  else:
3447
    question = msg + affected
3448

    
3449
  choice = AskUser(question, choices)
3450
  if choice == "v":
3451
    choices.pop(1)
3452
    choice = AskUser(msg + affected, choices)
3453
  return choice
3454

    
3455

    
3456
def _MaybeParseUnit(elements):
3457
  """Parses and returns an array of potential values with units.
3458

3459
  """
3460
  parsed = {}
3461
  for k, v in elements.items():
3462
    if v == constants.VALUE_DEFAULT:
3463
      parsed[k] = v
3464
    else:
3465
      parsed[k] = utils.ParseUnit(v)
3466
  return parsed
3467

    
3468

    
3469
def CreateIPolicyFromOpts(ispecs_mem_size=None,
3470
                          ispecs_cpu_count=None,
3471
                          ispecs_disk_count=None,
3472
                          ispecs_disk_size=None,
3473
                          ispecs_nic_count=None,
3474
                          ipolicy_disk_templates=None,
3475
                          ipolicy_vcpu_ratio=None,
3476
                          ipolicy_spindle_ratio=None,
3477
                          group_ipolicy=False,
3478
                          allowed_values=None,
3479
                          fill_all=False):
3480
  """Creation of instance policy based on command line options.
3481

3482
  @param fill_all: whether for cluster policies we should ensure that
3483
    all values are filled
3484

3485

3486
  """
3487
  try:
3488
    if ispecs_mem_size:
3489
      ispecs_mem_size = _MaybeParseUnit(ispecs_mem_size)
3490
    if ispecs_disk_size:
3491
      ispecs_disk_size = _MaybeParseUnit(ispecs_disk_size)
3492
  except (TypeError, ValueError, errors.UnitParseError), err:
3493
    raise errors.OpPrereqError("Invalid disk (%s) or memory (%s) size"
3494
                               " in policy: %s" %
3495
                               (ispecs_disk_size, ispecs_mem_size, err),
3496
                               errors.ECODE_INVAL)
3497

    
3498
  # prepare ipolicy dict
3499
  ipolicy_transposed = {
3500
    constants.ISPEC_MEM_SIZE: ispecs_mem_size,
3501
    constants.ISPEC_CPU_COUNT: ispecs_cpu_count,
3502
    constants.ISPEC_DISK_COUNT: ispecs_disk_count,
3503
    constants.ISPEC_DISK_SIZE: ispecs_disk_size,
3504
    constants.ISPEC_NIC_COUNT: ispecs_nic_count,
3505
    }
3506

    
3507
  # first, check that the values given are correct
3508
  if group_ipolicy:
3509
    forced_type = TISPECS_GROUP_TYPES
3510
  else:
3511
    forced_type = TISPECS_CLUSTER_TYPES
3512

    
3513
  for specs in ipolicy_transposed.values():
3514
    utils.ForceDictType(specs, forced_type, allowed_values=allowed_values)
3515

    
3516
  # then transpose
3517
  ipolicy_out = objects.MakeEmptyIPolicy()
3518
  for name, specs in ipolicy_transposed.iteritems():
3519
    assert name in constants.ISPECS_PARAMETERS
3520
    for key, val in specs.items(): # {min: .. ,max: .., std: ..}
3521
      ipolicy_out[key][name] = val
3522

    
3523
  # no filldict for non-dicts
3524
  if not group_ipolicy and fill_all:
3525
    if ipolicy_disk_templates is None:
3526
      ipolicy_disk_templates = constants.DISK_TEMPLATES
3527
    if ipolicy_vcpu_ratio is None:
3528
      ipolicy_vcpu_ratio = \
3529
        constants.IPOLICY_DEFAULTS[constants.IPOLICY_VCPU_RATIO]
3530
    if ipolicy_spindle_ratio is None:
3531
      ipolicy_spindle_ratio = \
3532
        constants.IPOLICY_DEFAULTS[constants.IPOLICY_SPINDLE_RATIO]
3533
  if ipolicy_disk_templates is not None:
3534
    ipolicy_out[constants.IPOLICY_DTS] = list(ipolicy_disk_templates)
3535
  if ipolicy_vcpu_ratio is not None:
3536
    ipolicy_out[constants.IPOLICY_VCPU_RATIO] = ipolicy_vcpu_ratio
3537
  if ipolicy_spindle_ratio is not None:
3538
    ipolicy_out[constants.IPOLICY_SPINDLE_RATIO] = ipolicy_spindle_ratio
3539

    
3540
  assert not (frozenset(ipolicy_out.keys()) - constants.IPOLICY_ALL_KEYS)
3541

    
3542
  return ipolicy_out