Statistics
| Branch: | Tag: | Revision:

root / lib / cli.py @ 63c73073

History | View | Annotate | Download (121 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
from ganeti import pathutils
48

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

    
52

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

    
282
NO_PREFIX = "no_"
283
UN_PREFIX = "-"
284

    
285
#: Priorities (sorted)
286
_PRIORITY_NAMES = [
287
  ("low", constants.OP_PRIO_LOW),
288
  ("normal", constants.OP_PRIO_NORMAL),
289
  ("high", constants.OP_PRIO_HIGH),
290
  ]
291

    
292
#: Priority dictionary for easier lookup
293
# TODO: Replace this and _PRIORITY_NAMES with a single sorted dictionary once
294
# we migrate to Python 2.6
295
_PRIONAME_TO_VALUE = dict(_PRIORITY_NAMES)
296

    
297
# Query result status for clients
298
(QR_NORMAL,
299
 QR_UNKNOWN,
300
 QR_INCOMPLETE) = range(3)
301

    
302
#: Maximum batch size for ChooseJob
303
_CHOOSE_BATCH = 25
304

    
305

    
306
# constants used to create InstancePolicy dictionary
307
TISPECS_GROUP_TYPES = {
308
  constants.ISPECS_MIN: constants.VTYPE_INT,
309
  constants.ISPECS_MAX: constants.VTYPE_INT,
310
  }
311

    
312
TISPECS_CLUSTER_TYPES = {
313
  constants.ISPECS_MIN: constants.VTYPE_INT,
314
  constants.ISPECS_MAX: constants.VTYPE_INT,
315
  constants.ISPECS_STD: constants.VTYPE_INT,
316
  }
317

    
318

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

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

    
328

    
329
class ArgSuggest(_Argument):
330
  """Suggesting argument.
331

332
  Value can be any of the ones passed to the constructor.
333

334
  """
335
  # pylint: disable=W0622
336
  def __init__(self, min=0, max=None, choices=None):
337
    _Argument.__init__(self, min=min, max=max)
338
    self.choices = choices
339

    
340
  def __repr__(self):
341
    return ("<%s min=%s max=%s choices=%r>" %
342
            (self.__class__.__name__, self.min, self.max, self.choices))
343

    
344

    
345
class ArgChoice(ArgSuggest):
346
  """Choice argument.
347

348
  Value can be any of the ones passed to the constructor. Like L{ArgSuggest},
349
  but value must be one of the choices.
350

351
  """
352

    
353

    
354
class ArgUnknown(_Argument):
355
  """Unknown argument to program (e.g. determined at runtime).
356

357
  """
358

    
359

    
360
class ArgInstance(_Argument):
361
  """Instances argument.
362

363
  """
364

    
365

    
366
class ArgNode(_Argument):
367
  """Node argument.
368

369
  """
370

    
371

    
372
class ArgNetwork(_Argument):
373
  """Network argument.
374

375
  """
376

    
377

    
378
class ArgGroup(_Argument):
379
  """Node group argument.
380

381
  """
382

    
383

    
384
class ArgJobId(_Argument):
385
  """Job ID argument.
386

387
  """
388

    
389

    
390
class ArgFile(_Argument):
391
  """File path argument.
392

393
  """
394

    
395

    
396
class ArgCommand(_Argument):
397
  """Command argument.
398

399
  """
400

    
401

    
402
class ArgHost(_Argument):
403
  """Host argument.
404

405
  """
406

    
407

    
408
class ArgOs(_Argument):
409
  """OS argument.
410

411
  """
412

    
413

    
414
ARGS_NONE = []
415
ARGS_MANY_INSTANCES = [ArgInstance()]
416
ARGS_MANY_NETWORKS = [ArgNetwork()]
417
ARGS_MANY_NODES = [ArgNode()]
418
ARGS_MANY_GROUPS = [ArgGroup()]
419
ARGS_ONE_INSTANCE = [ArgInstance(min=1, max=1)]
420
ARGS_ONE_NETWORK = [ArgNetwork(min=1, max=1)]
421
ARGS_ONE_NODE = [ArgNode(min=1, max=1)]
422
# TODO
423
ARGS_ONE_GROUP = [ArgGroup(min=1, max=1)]
424
ARGS_ONE_OS = [ArgOs(min=1, max=1)]
425

    
426

    
427
def _ExtractTagsObject(opts, args):
428
  """Extract the tag type object.
429

430
  Note that this function will modify its args parameter.
431

432
  """
433
  if not hasattr(opts, "tag_type"):
434
    raise errors.ProgrammerError("tag_type not passed to _ExtractTagsObject")
435
  kind = opts.tag_type
436
  if kind == constants.TAG_CLUSTER:
437
    retval = kind, None
438
  elif kind in (constants.TAG_NODEGROUP,
439
                constants.TAG_NODE,
440
                constants.TAG_NETWORK,
441
                constants.TAG_INSTANCE):
442
    if not args:
443
      raise errors.OpPrereqError("no arguments passed to the command",
444
                                 errors.ECODE_INVAL)
445
    name = args.pop(0)
446
    retval = kind, name
447
  else:
448
    raise errors.ProgrammerError("Unhandled tag type '%s'" % kind)
449
  return retval
450

    
451

    
452
def _ExtendTags(opts, args):
453
  """Extend the args if a source file has been given.
454

455
  This function will extend the tags with the contents of the file
456
  passed in the 'tags_source' attribute of the opts parameter. A file
457
  named '-' will be replaced by stdin.
458

459
  """
460
  fname = opts.tags_source
461
  if fname is None:
462
    return
463
  if fname == "-":
464
    new_fh = sys.stdin
465
  else:
466
    new_fh = open(fname, "r")
467
  new_data = []
468
  try:
469
    # we don't use the nice 'new_data = [line.strip() for line in fh]'
470
    # because of python bug 1633941
471
    while True:
472
      line = new_fh.readline()
473
      if not line:
474
        break
475
      new_data.append(line.strip())
476
  finally:
477
    new_fh.close()
478
  args.extend(new_data)
479

    
480

    
481
def ListTags(opts, args):
482
  """List the tags on a given object.
483

484
  This is a generic implementation that knows how to deal with all
485
  three cases of tag objects (cluster, node, instance). The opts
486
  argument is expected to contain a tag_type field denoting what
487
  object type we work on.
488

489
  """
490
  kind, name = _ExtractTagsObject(opts, args)
491
  cl = GetClient(query=True)
492
  result = cl.QueryTags(kind, name)
493
  result = list(result)
494
  result.sort()
495
  for tag in result:
496
    ToStdout(tag)
497

    
498

    
499
def AddTags(opts, args):
500
  """Add tags on a given object.
501

502
  This is a generic implementation that knows how to deal with all
503
  three cases of tag objects (cluster, node, instance). The opts
504
  argument is expected to contain a tag_type field denoting what
505
  object type we work on.
506

507
  """
508
  kind, name = _ExtractTagsObject(opts, args)
509
  _ExtendTags(opts, args)
510
  if not args:
511
    raise errors.OpPrereqError("No tags to be added", errors.ECODE_INVAL)
512
  op = opcodes.OpTagsSet(kind=kind, name=name, tags=args)
513
  SubmitOrSend(op, opts)
514

    
515

    
516
def RemoveTags(opts, args):
517
  """Remove tags from a given object.
518

519
  This is a generic implementation that knows how to deal with all
520
  three cases of tag objects (cluster, node, instance). The opts
521
  argument is expected to contain a tag_type field denoting what
522
  object type we work on.
523

524
  """
525
  kind, name = _ExtractTagsObject(opts, args)
526
  _ExtendTags(opts, args)
527
  if not args:
528
    raise errors.OpPrereqError("No tags to be removed", errors.ECODE_INVAL)
529
  op = opcodes.OpTagsDel(kind=kind, name=name, tags=args)
530
  SubmitOrSend(op, opts)
531

    
532

    
533
def check_unit(option, opt, value): # pylint: disable=W0613
534
  """OptParsers custom converter for units.
535

536
  """
537
  try:
538
    return utils.ParseUnit(value)
539
  except errors.UnitParseError, err:
540
    raise OptionValueError("option %s: %s" % (opt, err))
541

    
542

    
543
def _SplitKeyVal(opt, data):
544
  """Convert a KeyVal string into a dict.
545

546
  This function will convert a key=val[,...] string into a dict. Empty
547
  values will be converted specially: keys which have the prefix 'no_'
548
  will have the value=False and the prefix stripped, the others will
549
  have value=True.
550

551
  @type opt: string
552
  @param opt: a string holding the option name for which we process the
553
      data, used in building error messages
554
  @type data: string
555
  @param data: a string of the format key=val,key=val,...
556
  @rtype: dict
557
  @return: {key=val, key=val}
558
  @raises errors.ParameterError: if there are duplicate keys
559

560
  """
561
  kv_dict = {}
562
  if data:
563
    for elem in utils.UnescapeAndSplit(data, sep=","):
564
      if "=" in elem:
565
        key, val = elem.split("=", 1)
566
      else:
567
        if elem.startswith(NO_PREFIX):
568
          key, val = elem[len(NO_PREFIX):], False
569
        elif elem.startswith(UN_PREFIX):
570
          key, val = elem[len(UN_PREFIX):], None
571
        else:
572
          key, val = elem, True
573
      if key in kv_dict:
574
        raise errors.ParameterError("Duplicate key '%s' in option %s" %
575
                                    (key, opt))
576
      kv_dict[key] = val
577
  return kv_dict
578

    
579

    
580
def check_ident_key_val(option, opt, value):  # pylint: disable=W0613
581
  """Custom parser for ident:key=val,key=val options.
582

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

586
  """
587
  if ":" not in value:
588
    ident, rest = value, ""
589
  else:
590
    ident, rest = value.split(":", 1)
591

    
592
  if ident.startswith(NO_PREFIX):
593
    if rest:
594
      msg = "Cannot pass options when removing parameter groups: %s" % value
595
      raise errors.ParameterError(msg)
596
    retval = (ident[len(NO_PREFIX):], False)
597
  elif (ident.startswith(UN_PREFIX) and
598
        (len(ident) <= len(UN_PREFIX) or
599
         not ident[len(UN_PREFIX)][0].isdigit())):
600
    if rest:
601
      msg = "Cannot pass options when removing parameter groups: %s" % value
602
      raise errors.ParameterError(msg)
603
    retval = (ident[len(UN_PREFIX):], None)
604
  else:
605
    kv_dict = _SplitKeyVal(opt, rest)
606
    retval = (ident, kv_dict)
607
  return retval
608

    
609

    
610
def check_key_val(option, opt, value):  # pylint: disable=W0613
611
  """Custom parser class for key=val,key=val options.
612

613
  This will store the parsed values as a dict {key: val}.
614

615
  """
616
  return _SplitKeyVal(opt, value)
617

    
618

    
619
def check_bool(option, opt, value): # pylint: disable=W0613
620
  """Custom parser for yes/no options.
621

622
  This will store the parsed value as either True or False.
623

624
  """
625
  value = value.lower()
626
  if value == constants.VALUE_FALSE or value == "no":
627
    return False
628
  elif value == constants.VALUE_TRUE or value == "yes":
629
    return True
630
  else:
631
    raise errors.ParameterError("Invalid boolean value '%s'" % value)
632

    
633

    
634
def check_list(option, opt, value): # pylint: disable=W0613
635
  """Custom parser for comma-separated lists.
636

637
  """
638
  # we have to make this explicit check since "".split(",") is [""],
639
  # not an empty list :(
640
  if not value:
641
    return []
642
  else:
643
    return utils.UnescapeAndSplit(value)
644

    
645

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

649
  """
650
  value = value.lower()
651

    
652
  if value == constants.VALUE_DEFAULT:
653
    return value
654
  else:
655
    return float(value)
656

    
657

    
658
# completion_suggestion is normally a list. Using numeric values not evaluating
659
# to False for dynamic completion.
660
(OPT_COMPL_MANY_NODES,
661
 OPT_COMPL_ONE_NODE,
662
 OPT_COMPL_ONE_INSTANCE,
663
 OPT_COMPL_ONE_OS,
664
 OPT_COMPL_ONE_IALLOCATOR,
665
 OPT_COMPL_ONE_NETWORK,
666
 OPT_COMPL_INST_ADD_NODES,
667
 OPT_COMPL_ONE_NODEGROUP) = range(100, 108)
668

    
669
OPT_COMPL_ALL = compat.UniqueFrozenset([
670
  OPT_COMPL_MANY_NODES,
671
  OPT_COMPL_ONE_NODE,
672
  OPT_COMPL_ONE_INSTANCE,
673
  OPT_COMPL_ONE_OS,
674
  OPT_COMPL_ONE_IALLOCATOR,
675
  OPT_COMPL_ONE_NETWORK,
676
  OPT_COMPL_INST_ADD_NODES,
677
  OPT_COMPL_ONE_NODEGROUP,
678
  ])
679

    
680

    
681
class CliOption(Option):
682
  """Custom option class for optparse.
683

684
  """
685
  ATTRS = Option.ATTRS + [
686
    "completion_suggest",
687
    ]
688
  TYPES = Option.TYPES + (
689
    "identkeyval",
690
    "keyval",
691
    "unit",
692
    "bool",
693
    "list",
694
    "maybefloat",
695
    )
696
  TYPE_CHECKER = Option.TYPE_CHECKER.copy()
697
  TYPE_CHECKER["identkeyval"] = check_ident_key_val
698
  TYPE_CHECKER["keyval"] = check_key_val
699
  TYPE_CHECKER["unit"] = check_unit
700
  TYPE_CHECKER["bool"] = check_bool
701
  TYPE_CHECKER["list"] = check_list
702
  TYPE_CHECKER["maybefloat"] = check_maybefloat
703

    
704

    
705
# optparse.py sets make_option, so we do it for our own option class, too
706
cli_option = CliOption
707

    
708

    
709
_YORNO = "yes|no"
710

    
711
DEBUG_OPT = cli_option("-d", "--debug", default=0, action="count",
712
                       help="Increase debugging level")
713

    
714
NOHDR_OPT = cli_option("--no-headers", default=False,
715
                       action="store_true", dest="no_headers",
716
                       help="Don't display column headers")
717

    
718
SEP_OPT = cli_option("--separator", default=None,
719
                     action="store", dest="separator",
720
                     help=("Separator between output fields"
721
                           " (defaults to one space)"))
722

    
723
USEUNITS_OPT = cli_option("--units", default=None,
724
                          dest="units", choices=("h", "m", "g", "t"),
725
                          help="Specify units for output (one of h/m/g/t)")
726

    
727
FIELDS_OPT = cli_option("-o", "--output", dest="output", action="store",
728
                        type="string", metavar="FIELDS",
729
                        help="Comma separated list of output fields")
730

    
731
FORCE_OPT = cli_option("-f", "--force", dest="force", action="store_true",
732
                       default=False, help="Force the operation")
733

    
734
CONFIRM_OPT = cli_option("--yes", dest="confirm", action="store_true",
735
                         default=False, help="Do not require confirmation")
736

    
737
IGNORE_OFFLINE_OPT = cli_option("--ignore-offline", dest="ignore_offline",
738
                                  action="store_true", default=False,
739
                                  help=("Ignore offline nodes and do as much"
740
                                        " as possible"))
741

    
742
TAG_ADD_OPT = cli_option("--tags", dest="tags",
743
                         default=None, help="Comma-separated list of instance"
744
                                            " tags")
745

    
746
TAG_SRC_OPT = cli_option("--from", dest="tags_source",
747
                         default=None, help="File with tag names")
748

    
749
SUBMIT_OPT = cli_option("--submit", dest="submit_only",
750
                        default=False, action="store_true",
751
                        help=("Submit the job and return the job ID, but"
752
                              " don't wait for the job to finish"))
753

    
754
SYNC_OPT = cli_option("--sync", dest="do_locking",
755
                      default=False, action="store_true",
756
                      help=("Grab locks while doing the queries"
757
                            " in order to ensure more consistent results"))
758

    
759
DRY_RUN_OPT = cli_option("--dry-run", default=False,
760
                         action="store_true",
761
                         help=("Do not execute the operation, just run the"
762
                               " check steps and verify if it could be"
763
                               " executed"))
764

    
765
VERBOSE_OPT = cli_option("-v", "--verbose", default=False,
766
                         action="store_true",
767
                         help="Increase the verbosity of the operation")
768

    
769
DEBUG_SIMERR_OPT = cli_option("--debug-simulate-errors", default=False,
770
                              action="store_true", dest="simulate_errors",
771
                              help="Debugging option that makes the operation"
772
                              " treat most runtime checks as failed")
773

    
774
NWSYNC_OPT = cli_option("--no-wait-for-sync", dest="wait_for_sync",
775
                        default=True, action="store_false",
776
                        help="Don't wait for sync (DANGEROUS!)")
777

    
778
WFSYNC_OPT = cli_option("--wait-for-sync", dest="wait_for_sync",
779
                        default=False, action="store_true",
780
                        help="Wait for disks to sync")
781

    
782
ONLINE_INST_OPT = cli_option("--online", dest="online_inst",
783
                             action="store_true", default=False,
784
                             help="Enable offline instance")
785

    
786
OFFLINE_INST_OPT = cli_option("--offline", dest="offline_inst",
787
                              action="store_true", default=False,
788
                              help="Disable down instance")
789

    
790
DISK_TEMPLATE_OPT = cli_option("-t", "--disk-template", dest="disk_template",
791
                               help=("Custom disk setup (%s)" %
792
                                     utils.CommaJoin(constants.DISK_TEMPLATES)),
793
                               default=None, metavar="TEMPL",
794
                               choices=list(constants.DISK_TEMPLATES))
795

    
796
NONICS_OPT = cli_option("--no-nics", default=False, action="store_true",
797
                        help="Do not create any network cards for"
798
                        " the instance")
799

    
800
FILESTORE_DIR_OPT = cli_option("--file-storage-dir", dest="file_storage_dir",
801
                               help="Relative path under default cluster-wide"
802
                               " file storage dir to store file-based disks",
803
                               default=None, metavar="<DIR>")
804

    
805
FILESTORE_DRIVER_OPT = cli_option("--file-driver", dest="file_driver",
806
                                  help="Driver to use for image files",
807
                                  default="loop", metavar="<DRIVER>",
808
                                  choices=list(constants.FILE_DRIVER))
809

    
810
IALLOCATOR_OPT = cli_option("-I", "--iallocator", metavar="<NAME>",
811
                            help="Select nodes for the instance automatically"
812
                            " using the <NAME> iallocator plugin",
813
                            default=None, type="string",
814
                            completion_suggest=OPT_COMPL_ONE_IALLOCATOR)
815

    
816
DEFAULT_IALLOCATOR_OPT = cli_option("-I", "--default-iallocator",
817
                                    metavar="<NAME>",
818
                                    help="Set the default instance"
819
                                    " allocator plugin",
820
                                    default=None, type="string",
821
                                    completion_suggest=OPT_COMPL_ONE_IALLOCATOR)
822

    
823
OS_OPT = cli_option("-o", "--os-type", dest="os", help="What OS to run",
824
                    metavar="<os>",
825
                    completion_suggest=OPT_COMPL_ONE_OS)
826

    
827
OSPARAMS_OPT = cli_option("-O", "--os-parameters", dest="osparams",
828
                          type="keyval", default={},
829
                          help="OS parameters")
830

    
831
FORCE_VARIANT_OPT = cli_option("--force-variant", dest="force_variant",
832
                               action="store_true", default=False,
833
                               help="Force an unknown variant")
834

    
835
NO_INSTALL_OPT = cli_option("--no-install", dest="no_install",
836
                            action="store_true", default=False,
837
                            help="Do not install the OS (will"
838
                            " enable no-start)")
839

    
840
NORUNTIME_CHGS_OPT = cli_option("--no-runtime-changes",
841
                                dest="allow_runtime_chgs",
842
                                default=True, action="store_false",
843
                                help="Don't allow runtime changes")
844

    
845
BACKEND_OPT = cli_option("-B", "--backend-parameters", dest="beparams",
846
                         type="keyval", default={},
847
                         help="Backend parameters")
848

    
849
HVOPTS_OPT = cli_option("-H", "--hypervisor-parameters", type="keyval",
850
                        default={}, dest="hvparams",
851
                        help="Hypervisor parameters")
852

    
853
DISK_PARAMS_OPT = cli_option("-D", "--disk-parameters", dest="diskparams",
854
                             help="Disk template parameters, in the format"
855
                             " template:option=value,option=value,...",
856
                             type="identkeyval", action="append", default=[])
857

    
858
SPECS_MEM_SIZE_OPT = cli_option("--specs-mem-size", dest="ispecs_mem_size",
859
                                 type="keyval", default={},
860
                                 help="Memory size specs: list of key=value,"
861
                                " where key is one of min, max, std"
862
                                 " (in MB or using a unit)")
863

    
864
SPECS_CPU_COUNT_OPT = cli_option("--specs-cpu-count", dest="ispecs_cpu_count",
865
                                 type="keyval", default={},
866
                                 help="CPU count specs: list of key=value,"
867
                                 " where key is one of min, max, std")
868

    
869
SPECS_DISK_COUNT_OPT = cli_option("--specs-disk-count",
870
                                  dest="ispecs_disk_count",
871
                                  type="keyval", default={},
872
                                  help="Disk count specs: list of key=value,"
873
                                  " where key is one of min, max, std")
874

    
875
SPECS_DISK_SIZE_OPT = cli_option("--specs-disk-size", dest="ispecs_disk_size",
876
                                 type="keyval", default={},
877
                                 help="Disk size specs: list of key=value,"
878
                                 " where key is one of min, max, std"
879
                                 " (in MB or using a unit)")
880

    
881
SPECS_NIC_COUNT_OPT = cli_option("--specs-nic-count", dest="ispecs_nic_count",
882
                                 type="keyval", default={},
883
                                 help="NIC count specs: list of key=value,"
884
                                 " where key is one of min, max, std")
885

    
886
IPOLICY_DISK_TEMPLATES = cli_option("--ipolicy-disk-templates",
887
                                    dest="ipolicy_disk_templates",
888
                                    type="list", default=None,
889
                                    help="Comma-separated list of"
890
                                    " enabled disk templates")
891

    
892
IPOLICY_VCPU_RATIO = cli_option("--ipolicy-vcpu-ratio",
893
                                 dest="ipolicy_vcpu_ratio",
894
                                 type="maybefloat", default=None,
895
                                 help="The maximum allowed vcpu-to-cpu ratio")
896

    
897
IPOLICY_SPINDLE_RATIO = cli_option("--ipolicy-spindle-ratio",
898
                                   dest="ipolicy_spindle_ratio",
899
                                   type="maybefloat", default=None,
900
                                   help=("The maximum allowed instances to"
901
                                         " spindle ratio"))
902

    
903
HYPERVISOR_OPT = cli_option("-H", "--hypervisor-parameters", dest="hypervisor",
904
                            help="Hypervisor and hypervisor options, in the"
905
                            " format hypervisor:option=value,option=value,...",
906
                            default=None, type="identkeyval")
907

    
908
HVLIST_OPT = cli_option("-H", "--hypervisor-parameters", dest="hvparams",
909
                        help="Hypervisor and hypervisor options, in the"
910
                        " format hypervisor:option=value,option=value,...",
911
                        default=[], action="append", type="identkeyval")
912

    
913
NOIPCHECK_OPT = cli_option("--no-ip-check", dest="ip_check", default=True,
914
                           action="store_false",
915
                           help="Don't check that the instance's IP"
916
                           " is alive")
917

    
918
NONAMECHECK_OPT = cli_option("--no-name-check", dest="name_check",
919
                             default=True, action="store_false",
920
                             help="Don't check that the instance's name"
921
                             " is resolvable")
922

    
923
NET_OPT = cli_option("--net",
924
                     help="NIC parameters", default=[],
925
                     dest="nics", action="append", type="identkeyval")
926

    
927
DISK_OPT = cli_option("--disk", help="Disk parameters", default=[],
928
                      dest="disks", action="append", type="identkeyval")
929

    
930
DISKIDX_OPT = cli_option("--disks", dest="disks", default=None,
931
                         help="Comma-separated list of disks"
932
                         " indices to act on (e.g. 0,2) (optional,"
933
                         " defaults to all disks)")
934

    
935
OS_SIZE_OPT = cli_option("-s", "--os-size", dest="sd_size",
936
                         help="Enforces a single-disk configuration using the"
937
                         " given disk size, in MiB unless a suffix is used",
938
                         default=None, type="unit", metavar="<size>")
939

    
940
IGNORE_CONSIST_OPT = cli_option("--ignore-consistency",
941
                                dest="ignore_consistency",
942
                                action="store_true", default=False,
943
                                help="Ignore the consistency of the disks on"
944
                                " the secondary")
945

    
946
ALLOW_FAILOVER_OPT = cli_option("--allow-failover",
947
                                dest="allow_failover",
948
                                action="store_true", default=False,
949
                                help="If migration is not possible fallback to"
950
                                     " failover")
951

    
952
NONLIVE_OPT = cli_option("--non-live", dest="live",
953
                         default=True, action="store_false",
954
                         help="Do a non-live migration (this usually means"
955
                         " freeze the instance, save the state, transfer and"
956
                         " only then resume running on the secondary node)")
957

    
958
MIGRATION_MODE_OPT = cli_option("--migration-mode", dest="migration_mode",
959
                                default=None,
960
                                choices=list(constants.HT_MIGRATION_MODES),
961
                                help="Override default migration mode (choose"
962
                                " either live or non-live")
963

    
964
NODE_PLACEMENT_OPT = cli_option("-n", "--node", dest="node",
965
                                help="Target node and optional secondary node",
966
                                metavar="<pnode>[:<snode>]",
967
                                completion_suggest=OPT_COMPL_INST_ADD_NODES)
968

    
969
NODE_LIST_OPT = cli_option("-n", "--node", dest="nodes", default=[],
970
                           action="append", metavar="<node>",
971
                           help="Use only this node (can be used multiple"
972
                           " times, if not given defaults to all nodes)",
973
                           completion_suggest=OPT_COMPL_ONE_NODE)
974

    
975
NODEGROUP_OPT_NAME = "--node-group"
976
NODEGROUP_OPT = cli_option("-g", NODEGROUP_OPT_NAME,
977
                           dest="nodegroup",
978
                           help="Node group (name or uuid)",
979
                           metavar="<nodegroup>",
980
                           default=None, type="string",
981
                           completion_suggest=OPT_COMPL_ONE_NODEGROUP)
982

    
983
SINGLE_NODE_OPT = cli_option("-n", "--node", dest="node", help="Target node",
984
                             metavar="<node>",
985
                             completion_suggest=OPT_COMPL_ONE_NODE)
986

    
987
NOSTART_OPT = cli_option("--no-start", dest="start", default=True,
988
                         action="store_false",
989
                         help="Don't start the instance after creation")
990

    
991
SHOWCMD_OPT = cli_option("--show-cmd", dest="show_command",
992
                         action="store_true", default=False,
993
                         help="Show command instead of executing it")
994

    
995
CLEANUP_OPT = cli_option("--cleanup", dest="cleanup",
996
                         default=False, action="store_true",
997
                         help="Instead of performing the migration, try to"
998
                         " recover from a failed cleanup. This is safe"
999
                         " to run even if the instance is healthy, but it"
1000
                         " will create extra replication traffic and "
1001
                         " disrupt briefly the replication (like during the"
1002
                         " migration")
1003

    
1004
STATIC_OPT = cli_option("-s", "--static", dest="static",
1005
                        action="store_true", default=False,
1006
                        help="Only show configuration data, not runtime data")
1007

    
1008
ALL_OPT = cli_option("--all", dest="show_all",
1009
                     default=False, action="store_true",
1010
                     help="Show info on all instances on the cluster."
1011
                     " This can take a long time to run, use wisely")
1012

    
1013
SELECT_OS_OPT = cli_option("--select-os", dest="select_os",
1014
                           action="store_true", default=False,
1015
                           help="Interactive OS reinstall, lists available"
1016
                           " OS templates for selection")
1017

    
1018
IGNORE_FAILURES_OPT = cli_option("--ignore-failures", dest="ignore_failures",
1019
                                 action="store_true", default=False,
1020
                                 help="Remove the instance from the cluster"
1021
                                 " configuration even if there are failures"
1022
                                 " during the removal process")
1023

    
1024
IGNORE_REMOVE_FAILURES_OPT = cli_option("--ignore-remove-failures",
1025
                                        dest="ignore_remove_failures",
1026
                                        action="store_true", default=False,
1027
                                        help="Remove the instance from the"
1028
                                        " cluster configuration even if there"
1029
                                        " are failures during the removal"
1030
                                        " process")
1031

    
1032
REMOVE_INSTANCE_OPT = cli_option("--remove-instance", dest="remove_instance",
1033
                                 action="store_true", default=False,
1034
                                 help="Remove the instance from the cluster")
1035

    
1036
DST_NODE_OPT = cli_option("-n", "--target-node", dest="dst_node",
1037
                               help="Specifies the new node for the instance",
1038
                               metavar="NODE", default=None,
1039
                               completion_suggest=OPT_COMPL_ONE_NODE)
1040

    
1041
NEW_SECONDARY_OPT = cli_option("-n", "--new-secondary", dest="dst_node",
1042
                               help="Specifies the new secondary node",
1043
                               metavar="NODE", default=None,
1044
                               completion_suggest=OPT_COMPL_ONE_NODE)
1045

    
1046
ON_PRIMARY_OPT = cli_option("-p", "--on-primary", dest="on_primary",
1047
                            default=False, action="store_true",
1048
                            help="Replace the disk(s) on the primary"
1049
                                 " node (applies only to internally mirrored"
1050
                                 " disk templates, e.g. %s)" %
1051
                                 utils.CommaJoin(constants.DTS_INT_MIRROR))
1052

    
1053
ON_SECONDARY_OPT = cli_option("-s", "--on-secondary", dest="on_secondary",
1054
                              default=False, action="store_true",
1055
                              help="Replace the disk(s) on the secondary"
1056
                                   " node (applies only to internally mirrored"
1057
                                   " disk templates, e.g. %s)" %
1058
                                   utils.CommaJoin(constants.DTS_INT_MIRROR))
1059

    
1060
AUTO_PROMOTE_OPT = cli_option("--auto-promote", dest="auto_promote",
1061
                              default=False, action="store_true",
1062
                              help="Lock all nodes and auto-promote as needed"
1063
                              " to MC status")
1064

    
1065
AUTO_REPLACE_OPT = cli_option("-a", "--auto", dest="auto",
1066
                              default=False, action="store_true",
1067
                              help="Automatically replace faulty disks"
1068
                                   " (applies only to internally mirrored"
1069
                                   " disk templates, e.g. %s)" %
1070
                                   utils.CommaJoin(constants.DTS_INT_MIRROR))
1071

    
1072
IGNORE_SIZE_OPT = cli_option("--ignore-size", dest="ignore_size",
1073
                             default=False, action="store_true",
1074
                             help="Ignore current recorded size"
1075
                             " (useful for forcing activation when"
1076
                             " the recorded size is wrong)")
1077

    
1078
SRC_NODE_OPT = cli_option("--src-node", dest="src_node", help="Source node",
1079
                          metavar="<node>",
1080
                          completion_suggest=OPT_COMPL_ONE_NODE)
1081

    
1082
SRC_DIR_OPT = cli_option("--src-dir", dest="src_dir", help="Source directory",
1083
                         metavar="<dir>")
1084

    
1085
SECONDARY_IP_OPT = cli_option("-s", "--secondary-ip", dest="secondary_ip",
1086
                              help="Specify the secondary ip for the node",
1087
                              metavar="ADDRESS", default=None)
1088

    
1089
READD_OPT = cli_option("--readd", dest="readd",
1090
                       default=False, action="store_true",
1091
                       help="Readd old node after replacing it")
1092

    
1093
NOSSH_KEYCHECK_OPT = cli_option("--no-ssh-key-check", dest="ssh_key_check",
1094
                                default=True, action="store_false",
1095
                                help="Disable SSH key fingerprint checking")
1096

    
1097
NODE_FORCE_JOIN_OPT = cli_option("--force-join", dest="force_join",
1098
                                 default=False, action="store_true",
1099
                                 help="Force the joining of a node")
1100

    
1101
MC_OPT = cli_option("-C", "--master-candidate", dest="master_candidate",
1102
                    type="bool", default=None, metavar=_YORNO,
1103
                    help="Set the master_candidate flag on the node")
1104

    
1105
OFFLINE_OPT = cli_option("-O", "--offline", dest="offline", metavar=_YORNO,
1106
                         type="bool", default=None,
1107
                         help=("Set the offline flag on the node"
1108
                               " (cluster does not communicate with offline"
1109
                               " nodes)"))
1110

    
1111
DRAINED_OPT = cli_option("-D", "--drained", dest="drained", metavar=_YORNO,
1112
                         type="bool", default=None,
1113
                         help=("Set the drained flag on the node"
1114
                               " (excluded from allocation operations)"))
1115

    
1116
CAPAB_MASTER_OPT = cli_option("--master-capable", dest="master_capable",
1117
                              type="bool", default=None, metavar=_YORNO,
1118
                              help="Set the master_capable flag on the node")
1119

    
1120
CAPAB_VM_OPT = cli_option("--vm-capable", dest="vm_capable",
1121
                          type="bool", default=None, metavar=_YORNO,
1122
                          help="Set the vm_capable flag on the node")
1123

    
1124
ALLOCATABLE_OPT = cli_option("--allocatable", dest="allocatable",
1125
                             type="bool", default=None, metavar=_YORNO,
1126
                             help="Set the allocatable flag on a volume")
1127

    
1128
NOLVM_STORAGE_OPT = cli_option("--no-lvm-storage", dest="lvm_storage",
1129
                               help="Disable support for lvm based instances"
1130
                               " (cluster-wide)",
1131
                               action="store_false", default=True)
1132

    
1133
ENABLED_HV_OPT = cli_option("--enabled-hypervisors",
1134
                            dest="enabled_hypervisors",
1135
                            help="Comma-separated list of hypervisors",
1136
                            type="string", default=None)
1137

    
1138
NIC_PARAMS_OPT = cli_option("-N", "--nic-parameters", dest="nicparams",
1139
                            type="keyval", default={},
1140
                            help="NIC parameters")
1141

    
1142
CP_SIZE_OPT = cli_option("-C", "--candidate-pool-size", default=None,
1143
                         dest="candidate_pool_size", type="int",
1144
                         help="Set the candidate pool size")
1145

    
1146
VG_NAME_OPT = cli_option("--vg-name", dest="vg_name",
1147
                         help=("Enables LVM and specifies the volume group"
1148
                               " name (cluster-wide) for disk allocation"
1149
                               " [%s]" % constants.DEFAULT_VG),
1150
                         metavar="VG", default=None)
1151

    
1152
YES_DOIT_OPT = cli_option("--yes-do-it", "--ya-rly", dest="yes_do_it",
1153
                          help="Destroy cluster", action="store_true")
1154

    
1155
NOVOTING_OPT = cli_option("--no-voting", dest="no_voting",
1156
                          help="Skip node agreement check (dangerous)",
1157
                          action="store_true", default=False)
1158

    
1159
MAC_PREFIX_OPT = cli_option("-m", "--mac-prefix", dest="mac_prefix",
1160
                            help="Specify the mac prefix for the instance IP"
1161
                            " addresses, in the format XX:XX:XX",
1162
                            metavar="PREFIX",
1163
                            default=None)
1164

    
1165
MASTER_NETDEV_OPT = cli_option("--master-netdev", dest="master_netdev",
1166
                               help="Specify the node interface (cluster-wide)"
1167
                               " on which the master IP address will be added"
1168
                               " (cluster init default: %s)" %
1169
                               constants.DEFAULT_BRIDGE,
1170
                               metavar="NETDEV",
1171
                               default=None)
1172

    
1173
MASTER_NETMASK_OPT = cli_option("--master-netmask", dest="master_netmask",
1174
                                help="Specify the netmask of the master IP",
1175
                                metavar="NETMASK",
1176
                                default=None)
1177

    
1178
USE_EXTERNAL_MIP_SCRIPT = cli_option("--use-external-mip-script",
1179
                                     dest="use_external_mip_script",
1180
                                     help="Specify whether to run a"
1181
                                     " user-provided script for the master"
1182
                                     " IP address turnup and"
1183
                                     " turndown operations",
1184
                                     type="bool", metavar=_YORNO, default=None)
1185

    
1186
GLOBAL_FILEDIR_OPT = cli_option("--file-storage-dir", dest="file_storage_dir",
1187
                                help="Specify the default directory (cluster-"
1188
                                "wide) for storing the file-based disks [%s]" %
1189
                                pathutils.DEFAULT_FILE_STORAGE_DIR,
1190
                                metavar="DIR",
1191
                                default=pathutils.DEFAULT_FILE_STORAGE_DIR)
1192

    
1193
GLOBAL_SHARED_FILEDIR_OPT = cli_option(
1194
  "--shared-file-storage-dir",
1195
  dest="shared_file_storage_dir",
1196
  help="Specify the default directory (cluster-wide) for storing the"
1197
  " shared file-based disks [%s]" %
1198
  pathutils.DEFAULT_SHARED_FILE_STORAGE_DIR,
1199
  metavar="SHAREDDIR", default=pathutils.DEFAULT_SHARED_FILE_STORAGE_DIR)
1200

    
1201
NOMODIFY_ETCHOSTS_OPT = cli_option("--no-etc-hosts", dest="modify_etc_hosts",
1202
                                   help="Don't modify %s" % pathutils.ETC_HOSTS,
1203
                                   action="store_false", default=True)
1204

    
1205
NOMODIFY_SSH_SETUP_OPT = cli_option("--no-ssh-init", dest="modify_ssh_setup",
1206
                                    help="Don't initialize SSH keys",
1207
                                    action="store_false", default=True)
1208

    
1209
ERROR_CODES_OPT = cli_option("--error-codes", dest="error_codes",
1210
                             help="Enable parseable error messages",
1211
                             action="store_true", default=False)
1212

    
1213
NONPLUS1_OPT = cli_option("--no-nplus1-mem", dest="skip_nplusone_mem",
1214
                          help="Skip N+1 memory redundancy tests",
1215
                          action="store_true", default=False)
1216

    
1217
REBOOT_TYPE_OPT = cli_option("-t", "--type", dest="reboot_type",
1218
                             help="Type of reboot: soft/hard/full",
1219
                             default=constants.INSTANCE_REBOOT_HARD,
1220
                             metavar="<REBOOT>",
1221
                             choices=list(constants.REBOOT_TYPES))
1222

    
1223
IGNORE_SECONDARIES_OPT = cli_option("--ignore-secondaries",
1224
                                    dest="ignore_secondaries",
1225
                                    default=False, action="store_true",
1226
                                    help="Ignore errors from secondaries")
1227

    
1228
NOSHUTDOWN_OPT = cli_option("--noshutdown", dest="shutdown",
1229
                            action="store_false", default=True,
1230
                            help="Don't shutdown the instance (unsafe)")
1231

    
1232
TIMEOUT_OPT = cli_option("--timeout", dest="timeout", type="int",
1233
                         default=constants.DEFAULT_SHUTDOWN_TIMEOUT,
1234
                         help="Maximum time to wait")
1235

    
1236
SHUTDOWN_TIMEOUT_OPT = cli_option("--shutdown-timeout",
1237
                                  dest="shutdown_timeout", type="int",
1238
                                  default=constants.DEFAULT_SHUTDOWN_TIMEOUT,
1239
                                  help="Maximum time to wait for instance"
1240
                                  " shutdown")
1241

    
1242
INTERVAL_OPT = cli_option("--interval", dest="interval", type="int",
1243
                          default=None,
1244
                          help=("Number of seconds between repetions of the"
1245
                                " command"))
1246

    
1247
EARLY_RELEASE_OPT = cli_option("--early-release",
1248
                               dest="early_release", default=False,
1249
                               action="store_true",
1250
                               help="Release the locks on the secondary"
1251
                               " node(s) early")
1252

    
1253
NEW_CLUSTER_CERT_OPT = cli_option("--new-cluster-certificate",
1254
                                  dest="new_cluster_cert",
1255
                                  default=False, action="store_true",
1256
                                  help="Generate a new cluster certificate")
1257

    
1258
RAPI_CERT_OPT = cli_option("--rapi-certificate", dest="rapi_cert",
1259
                           default=None,
1260
                           help="File containing new RAPI certificate")
1261

    
1262
NEW_RAPI_CERT_OPT = cli_option("--new-rapi-certificate", dest="new_rapi_cert",
1263
                               default=None, action="store_true",
1264
                               help=("Generate a new self-signed RAPI"
1265
                                     " certificate"))
1266

    
1267
SPICE_CERT_OPT = cli_option("--spice-certificate", dest="spice_cert",
1268
                            default=None,
1269
                            help="File containing new SPICE certificate")
1270

    
1271
SPICE_CACERT_OPT = cli_option("--spice-ca-certificate", dest="spice_cacert",
1272
                              default=None,
1273
                              help="File containing the certificate of the CA"
1274
                              " which signed the SPICE certificate")
1275

    
1276
NEW_SPICE_CERT_OPT = cli_option("--new-spice-certificate",
1277
                                dest="new_spice_cert", default=None,
1278
                                action="store_true",
1279
                                help=("Generate a new self-signed SPICE"
1280
                                      " certificate"))
1281

    
1282
NEW_CONFD_HMAC_KEY_OPT = cli_option("--new-confd-hmac-key",
1283
                                    dest="new_confd_hmac_key",
1284
                                    default=False, action="store_true",
1285
                                    help=("Create a new HMAC key for %s" %
1286
                                          constants.CONFD))
1287

    
1288
CLUSTER_DOMAIN_SECRET_OPT = cli_option("--cluster-domain-secret",
1289
                                       dest="cluster_domain_secret",
1290
                                       default=None,
1291
                                       help=("Load new new cluster domain"
1292
                                             " secret from file"))
1293

    
1294
NEW_CLUSTER_DOMAIN_SECRET_OPT = cli_option("--new-cluster-domain-secret",
1295
                                           dest="new_cluster_domain_secret",
1296
                                           default=False, action="store_true",
1297
                                           help=("Create a new cluster domain"
1298
                                                 " secret"))
1299

    
1300
USE_REPL_NET_OPT = cli_option("--use-replication-network",
1301
                              dest="use_replication_network",
1302
                              help="Whether to use the replication network"
1303
                              " for talking to the nodes",
1304
                              action="store_true", default=False)
1305

    
1306
MAINTAIN_NODE_HEALTH_OPT = \
1307
    cli_option("--maintain-node-health", dest="maintain_node_health",
1308
               metavar=_YORNO, default=None, type="bool",
1309
               help="Configure the cluster to automatically maintain node"
1310
               " health, by shutting down unknown instances, shutting down"
1311
               " unknown DRBD devices, etc.")
1312

    
1313
IDENTIFY_DEFAULTS_OPT = \
1314
    cli_option("--identify-defaults", dest="identify_defaults",
1315
               default=False, action="store_true",
1316
               help="Identify which saved instance parameters are equal to"
1317
               " the current cluster defaults and set them as such, instead"
1318
               " of marking them as overridden")
1319

    
1320
UIDPOOL_OPT = cli_option("--uid-pool", default=None,
1321
                         action="store", dest="uid_pool",
1322
                         help=("A list of user-ids or user-id"
1323
                               " ranges separated by commas"))
1324

    
1325
ADD_UIDS_OPT = cli_option("--add-uids", default=None,
1326
                          action="store", dest="add_uids",
1327
                          help=("A list of user-ids or user-id"
1328
                                " ranges separated by commas, to be"
1329
                                " added to the user-id pool"))
1330

    
1331
REMOVE_UIDS_OPT = cli_option("--remove-uids", default=None,
1332
                             action="store", dest="remove_uids",
1333
                             help=("A list of user-ids or user-id"
1334
                                   " ranges separated by commas, to be"
1335
                                   " removed from the user-id pool"))
1336

    
1337
RESERVED_LVS_OPT = cli_option("--reserved-lvs", default=None,
1338
                              action="store", dest="reserved_lvs",
1339
                              help=("A comma-separated list of reserved"
1340
                                    " logical volumes names, that will be"
1341
                                    " ignored by cluster verify"))
1342

    
1343
ROMAN_OPT = cli_option("--roman",
1344
                       dest="roman_integers", default=False,
1345
                       action="store_true",
1346
                       help="Use roman numbers for positive integers")
1347

    
1348
DRBD_HELPER_OPT = cli_option("--drbd-usermode-helper", dest="drbd_helper",
1349
                             action="store", default=None,
1350
                             help="Specifies usermode helper for DRBD")
1351

    
1352
NODRBD_STORAGE_OPT = cli_option("--no-drbd-storage", dest="drbd_storage",
1353
                                action="store_false", default=True,
1354
                                help="Disable support for DRBD")
1355

    
1356
PRIMARY_IP_VERSION_OPT = \
1357
    cli_option("--primary-ip-version", default=constants.IP4_VERSION,
1358
               action="store", dest="primary_ip_version",
1359
               metavar="%d|%d" % (constants.IP4_VERSION,
1360
                                  constants.IP6_VERSION),
1361
               help="Cluster-wide IP version for primary IP")
1362

    
1363
SHOW_MACHINE_OPT = cli_option("-M", "--show-machine-names", default=False,
1364
                              action="store_true",
1365
                              help="Show machine name for every line in output")
1366

    
1367
FAILURE_ONLY_OPT = cli_option("--failure-only", default=False,
1368
                              action="store_true",
1369
                              help=("Hide successful results and show failures"
1370
                                    " only (determined by the exit code)"))
1371

    
1372

    
1373
def _PriorityOptionCb(option, _, value, parser):
1374
  """Callback for processing C{--priority} option.
1375

1376
  """
1377
  value = _PRIONAME_TO_VALUE[value]
1378

    
1379
  setattr(parser.values, option.dest, value)
1380

    
1381

    
1382
PRIORITY_OPT = cli_option("--priority", default=None, dest="priority",
1383
                          metavar="|".join(name for name, _ in _PRIORITY_NAMES),
1384
                          choices=_PRIONAME_TO_VALUE.keys(),
1385
                          action="callback", type="choice",
1386
                          callback=_PriorityOptionCb,
1387
                          help="Priority for opcode processing")
1388

    
1389
HID_OS_OPT = cli_option("--hidden", dest="hidden",
1390
                        type="bool", default=None, metavar=_YORNO,
1391
                        help="Sets the hidden flag on the OS")
1392

    
1393
BLK_OS_OPT = cli_option("--blacklisted", dest="blacklisted",
1394
                        type="bool", default=None, metavar=_YORNO,
1395
                        help="Sets the blacklisted flag on the OS")
1396

    
1397
PREALLOC_WIPE_DISKS_OPT = cli_option("--prealloc-wipe-disks", default=None,
1398
                                     type="bool", metavar=_YORNO,
1399
                                     dest="prealloc_wipe_disks",
1400
                                     help=("Wipe disks prior to instance"
1401
                                           " creation"))
1402

    
1403
NODE_PARAMS_OPT = cli_option("--node-parameters", dest="ndparams",
1404
                             type="keyval", default=None,
1405
                             help="Node parameters")
1406

    
1407
ALLOC_POLICY_OPT = cli_option("--alloc-policy", dest="alloc_policy",
1408
                              action="store", metavar="POLICY", default=None,
1409
                              help="Allocation policy for the node group")
1410

    
1411
NODE_POWERED_OPT = cli_option("--node-powered", default=None,
1412
                              type="bool", metavar=_YORNO,
1413
                              dest="node_powered",
1414
                              help="Specify if the SoR for node is powered")
1415

    
1416
OOB_TIMEOUT_OPT = cli_option("--oob-timeout", dest="oob_timeout", type="int",
1417
                             default=constants.OOB_TIMEOUT,
1418
                             help="Maximum time to wait for out-of-band helper")
1419

    
1420
POWER_DELAY_OPT = cli_option("--power-delay", dest="power_delay", type="float",
1421
                             default=constants.OOB_POWER_DELAY,
1422
                             help="Time in seconds to wait between power-ons")
1423

    
1424
FORCE_FILTER_OPT = cli_option("-F", "--filter", dest="force_filter",
1425
                              action="store_true", default=False,
1426
                              help=("Whether command argument should be treated"
1427
                                    " as filter"))
1428

    
1429
NO_REMEMBER_OPT = cli_option("--no-remember",
1430
                             dest="no_remember",
1431
                             action="store_true", default=False,
1432
                             help="Perform but do not record the change"
1433
                             " in the configuration")
1434

    
1435
PRIMARY_ONLY_OPT = cli_option("-p", "--primary-only",
1436
                              default=False, action="store_true",
1437
                              help="Evacuate primary instances only")
1438

    
1439
SECONDARY_ONLY_OPT = cli_option("-s", "--secondary-only",
1440
                                default=False, action="store_true",
1441
                                help="Evacuate secondary instances only"
1442
                                     " (applies only to internally mirrored"
1443
                                     " disk templates, e.g. %s)" %
1444
                                     utils.CommaJoin(constants.DTS_INT_MIRROR))
1445

    
1446
STARTUP_PAUSED_OPT = cli_option("--paused", dest="startup_paused",
1447
                                action="store_true", default=False,
1448
                                help="Pause instance at startup")
1449

    
1450
TO_GROUP_OPT = cli_option("--to", dest="to", metavar="<group>",
1451
                          help="Destination node group (name or uuid)",
1452
                          default=None, action="append",
1453
                          completion_suggest=OPT_COMPL_ONE_NODEGROUP)
1454

    
1455
IGNORE_ERRORS_OPT = cli_option("-I", "--ignore-errors", default=[],
1456
                               action="append", dest="ignore_errors",
1457
                               choices=list(constants.CV_ALL_ECODES_STRINGS),
1458
                               help="Error code to be ignored")
1459

    
1460
DISK_STATE_OPT = cli_option("--disk-state", default=[], dest="disk_state",
1461
                            action="append",
1462
                            help=("Specify disk state information in the"
1463
                                  " format"
1464
                                  " storage_type/identifier:option=value,...;"
1465
                                  " note this is unused for now"),
1466
                            type="identkeyval")
1467

    
1468
HV_STATE_OPT = cli_option("--hypervisor-state", default=[], dest="hv_state",
1469
                          action="append",
1470
                          help=("Specify hypervisor state information in the"
1471
                                " format hypervisor:option=value,...;"
1472
                                " note this is unused for now"),
1473
                          type="identkeyval")
1474

    
1475
IGNORE_IPOLICY_OPT = cli_option("--ignore-ipolicy", dest="ignore_ipolicy",
1476
                                action="store_true", default=False,
1477
                                help="Ignore instance policy violations")
1478

    
1479
RUNTIME_MEM_OPT = cli_option("-m", "--runtime-memory", dest="runtime_mem",
1480
                             help="Sets the instance's runtime memory,"
1481
                             " ballooning it up or down to the new value",
1482
                             default=None, type="unit", metavar="<size>")
1483

    
1484
ABSOLUTE_OPT = cli_option("--absolute", dest="absolute",
1485
                          action="store_true", default=False,
1486
                          help="Marks the grow as absolute instead of the"
1487
                          " (default) relative mode")
1488

    
1489
NETWORK_OPT = cli_option("--network",
1490
                         action="store", default=None, dest="network",
1491
                         help="IP network in CIDR notation")
1492

    
1493
GATEWAY_OPT = cli_option("--gateway",
1494
                         action="store", default=None, dest="gateway",
1495
                         help="IP address of the router (gateway)")
1496

    
1497
ADD_RESERVED_IPS_OPT = cli_option("--add-reserved-ips",
1498
                                  action="store", default=None,
1499
                                  dest="add_reserved_ips",
1500
                                  help="Comma-separated list of"
1501
                                  " reserved IPs to add")
1502

    
1503
REMOVE_RESERVED_IPS_OPT = cli_option("--remove-reserved-ips",
1504
                                     action="store", default=None,
1505
                                     dest="remove_reserved_ips",
1506
                                     help="Comma-delimited list of"
1507
                                     " reserved IPs to remove")
1508

    
1509
NETWORK_TYPE_OPT = cli_option("--network-type",
1510
                              action="store", default=None, dest="network_type",
1511
                              help="Network type: private, public, None")
1512

    
1513
NETWORK6_OPT = cli_option("--network6",
1514
                          action="store", default=None, dest="network6",
1515
                          help="IP network in CIDR notation")
1516

    
1517
GATEWAY6_OPT = cli_option("--gateway6",
1518
                          action="store", default=None, dest="gateway6",
1519
                          help="IP6 address of the router (gateway)")
1520

    
1521
NOCONFLICTSCHECK_OPT = cli_option("--no-conflicts-check",
1522
                                  dest="conflicts_check",
1523
                                  default=True,
1524
                                  action="store_false",
1525
                                  help="Don't check for conflicting IPs")
1526

    
1527
#: Options provided by all commands
1528
COMMON_OPTS = [DEBUG_OPT]
1529

    
1530
# common options for creating instances. add and import then add their own
1531
# specific ones.
1532
COMMON_CREATE_OPTS = [
1533
  BACKEND_OPT,
1534
  DISK_OPT,
1535
  DISK_TEMPLATE_OPT,
1536
  FILESTORE_DIR_OPT,
1537
  FILESTORE_DRIVER_OPT,
1538
  HYPERVISOR_OPT,
1539
  IALLOCATOR_OPT,
1540
  NET_OPT,
1541
  NODE_PLACEMENT_OPT,
1542
  NOIPCHECK_OPT,
1543
  NOCONFLICTSCHECK_OPT,
1544
  NONAMECHECK_OPT,
1545
  NONICS_OPT,
1546
  NWSYNC_OPT,
1547
  OSPARAMS_OPT,
1548
  OS_SIZE_OPT,
1549
  SUBMIT_OPT,
1550
  TAG_ADD_OPT,
1551
  DRY_RUN_OPT,
1552
  PRIORITY_OPT,
1553
  ]
1554

    
1555
# common instance policy options
1556
INSTANCE_POLICY_OPTS = [
1557
  SPECS_CPU_COUNT_OPT,
1558
  SPECS_DISK_COUNT_OPT,
1559
  SPECS_DISK_SIZE_OPT,
1560
  SPECS_MEM_SIZE_OPT,
1561
  SPECS_NIC_COUNT_OPT,
1562
  IPOLICY_DISK_TEMPLATES,
1563
  IPOLICY_VCPU_RATIO,
1564
  IPOLICY_SPINDLE_RATIO,
1565
  ]
1566

    
1567

    
1568
class _ShowUsage(Exception):
1569
  """Exception class for L{_ParseArgs}.
1570

1571
  """
1572
  def __init__(self, exit_error):
1573
    """Initializes instances of this class.
1574

1575
    @type exit_error: bool
1576
    @param exit_error: Whether to report failure on exit
1577

1578
    """
1579
    Exception.__init__(self)
1580
    self.exit_error = exit_error
1581

    
1582

    
1583
class _ShowVersion(Exception):
1584
  """Exception class for L{_ParseArgs}.
1585

1586
  """
1587

    
1588

    
1589
def _ParseArgs(binary, argv, commands, aliases, env_override):
1590
  """Parser for the command line arguments.
1591

1592
  This function parses the arguments and returns the function which
1593
  must be executed together with its (modified) arguments.
1594

1595
  @param binary: Script name
1596
  @param argv: Command line arguments
1597
  @param commands: Dictionary containing command definitions
1598
  @param aliases: dictionary with command aliases {"alias": "target", ...}
1599
  @param env_override: list of env variables allowed for default args
1600
  @raise _ShowUsage: If usage description should be shown
1601
  @raise _ShowVersion: If version should be shown
1602

1603
  """
1604
  assert not (env_override - set(commands))
1605
  assert not (set(aliases.keys()) & set(commands.keys()))
1606

    
1607
  if len(argv) > 1:
1608
    cmd = argv[1]
1609
  else:
1610
    # No option or command given
1611
    raise _ShowUsage(exit_error=True)
1612

    
1613
  if cmd == "--version":
1614
    raise _ShowVersion()
1615
  elif cmd == "--help":
1616
    raise _ShowUsage(exit_error=False)
1617
  elif not (cmd in commands or cmd in aliases):
1618
    raise _ShowUsage(exit_error=True)
1619

    
1620
  # get command, unalias it, and look it up in commands
1621
  if cmd in aliases:
1622
    if aliases[cmd] not in commands:
1623
      raise errors.ProgrammerError("Alias '%s' maps to non-existing"
1624
                                   " command '%s'" % (cmd, aliases[cmd]))
1625

    
1626
    cmd = aliases[cmd]
1627

    
1628
  if cmd in env_override:
1629
    args_env_name = ("%s_%s" % (binary.replace("-", "_"), cmd)).upper()
1630
    env_args = os.environ.get(args_env_name)
1631
    if env_args:
1632
      argv = utils.InsertAtPos(argv, 2, shlex.split(env_args))
1633

    
1634
  func, args_def, parser_opts, usage, description = commands[cmd]
1635
  parser = OptionParser(option_list=parser_opts + COMMON_OPTS,
1636
                        description=description,
1637
                        formatter=TitledHelpFormatter(),
1638
                        usage="%%prog %s %s" % (cmd, usage))
1639
  parser.disable_interspersed_args()
1640
  options, args = parser.parse_args(args=argv[2:])
1641

    
1642
  if not _CheckArguments(cmd, args_def, args):
1643
    return None, None, None
1644

    
1645
  return func, options, args
1646

    
1647

    
1648
def _FormatUsage(binary, commands):
1649
  """Generates a nice description of all commands.
1650

1651
  @param binary: Script name
1652
  @param commands: Dictionary containing command definitions
1653

1654
  """
1655
  # compute the max line length for cmd + usage
1656
  mlen = min(60, max(map(len, commands)))
1657

    
1658
  yield "Usage: %s {command} [options...] [argument...]" % binary
1659
  yield "%s <command> --help to see details, or man %s" % (binary, binary)
1660
  yield ""
1661
  yield "Commands:"
1662

    
1663
  # and format a nice command list
1664
  for (cmd, (_, _, _, _, help_text)) in sorted(commands.items()):
1665
    help_lines = textwrap.wrap(help_text, 79 - 3 - mlen)
1666
    yield " %-*s - %s" % (mlen, cmd, help_lines.pop(0))
1667
    for line in help_lines:
1668
      yield " %-*s   %s" % (mlen, "", line)
1669

    
1670
  yield ""
1671

    
1672

    
1673
def _CheckArguments(cmd, args_def, args):
1674
  """Verifies the arguments using the argument definition.
1675

1676
  Algorithm:
1677

1678
    1. Abort with error if values specified by user but none expected.
1679

1680
    1. For each argument in definition
1681

1682
      1. Keep running count of minimum number of values (min_count)
1683
      1. Keep running count of maximum number of values (max_count)
1684
      1. If it has an unlimited number of values
1685

1686
        1. Abort with error if it's not the last argument in the definition
1687

1688
    1. If last argument has limited number of values
1689

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

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

1694
  """
1695
  if args and not args_def:
1696
    ToStderr("Error: Command %s expects no arguments", cmd)
1697
    return False
1698

    
1699
  min_count = None
1700
  max_count = None
1701
  check_max = None
1702

    
1703
  last_idx = len(args_def) - 1
1704

    
1705
  for idx, arg in enumerate(args_def):
1706
    if min_count is None:
1707
      min_count = arg.min
1708
    elif arg.min is not None:
1709
      min_count += arg.min
1710

    
1711
    if max_count is None:
1712
      max_count = arg.max
1713
    elif arg.max is not None:
1714
      max_count += arg.max
1715

    
1716
    if idx == last_idx:
1717
      check_max = (arg.max is not None)
1718

    
1719
    elif arg.max is None:
1720
      raise errors.ProgrammerError("Only the last argument can have max=None")
1721

    
1722
  if check_max:
1723
    # Command with exact number of arguments
1724
    if (min_count is not None and max_count is not None and
1725
        min_count == max_count and len(args) != min_count):
1726
      ToStderr("Error: Command %s expects %d argument(s)", cmd, min_count)
1727
      return False
1728

    
1729
    # Command with limited number of arguments
1730
    if max_count is not None and len(args) > max_count:
1731
      ToStderr("Error: Command %s expects only %d argument(s)",
1732
               cmd, max_count)
1733
      return False
1734

    
1735
  # Command with some required arguments
1736
  if min_count is not None and len(args) < min_count:
1737
    ToStderr("Error: Command %s expects at least %d argument(s)",
1738
             cmd, min_count)
1739
    return False
1740

    
1741
  return True
1742

    
1743

    
1744
def SplitNodeOption(value):
1745
  """Splits the value of a --node option.
1746

1747
  """
1748
  if value and ":" in value:
1749
    return value.split(":", 1)
1750
  else:
1751
    return (value, None)
1752

    
1753

    
1754
def CalculateOSNames(os_name, os_variants):
1755
  """Calculates all the names an OS can be called, according to its variants.
1756

1757
  @type os_name: string
1758
  @param os_name: base name of the os
1759
  @type os_variants: list or None
1760
  @param os_variants: list of supported variants
1761
  @rtype: list
1762
  @return: list of valid names
1763

1764
  """
1765
  if os_variants:
1766
    return ["%s+%s" % (os_name, v) for v in os_variants]
1767
  else:
1768
    return [os_name]
1769

    
1770

    
1771
def ParseFields(selected, default):
1772
  """Parses the values of "--field"-like options.
1773

1774
  @type selected: string or None
1775
  @param selected: User-selected options
1776
  @type default: list
1777
  @param default: Default fields
1778

1779
  """
1780
  if selected is None:
1781
    return default
1782

    
1783
  if selected.startswith("+"):
1784
    return default + selected[1:].split(",")
1785

    
1786
  return selected.split(",")
1787

    
1788

    
1789
UsesRPC = rpc.RunWithRPC
1790

    
1791

    
1792
def AskUser(text, choices=None):
1793
  """Ask the user a question.
1794

1795
  @param text: the question to ask
1796

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

1802
  @return: one of the return values from the choices list; if input is
1803
      not possible (i.e. not running with a tty, we return the last
1804
      entry from the list
1805

1806
  """
1807
  if choices is None:
1808
    choices = [("y", True, "Perform the operation"),
1809
               ("n", False, "Do not perform the operation")]
1810
  if not choices or not isinstance(choices, list):
1811
    raise errors.ProgrammerError("Invalid choices argument to AskUser")
1812
  for entry in choices:
1813
    if not isinstance(entry, tuple) or len(entry) < 3 or entry[0] == "?":
1814
      raise errors.ProgrammerError("Invalid choices element to AskUser")
1815

    
1816
  answer = choices[-1][1]
1817
  new_text = []
1818
  for line in text.splitlines():
1819
    new_text.append(textwrap.fill(line, 70, replace_whitespace=False))
1820
  text = "\n".join(new_text)
1821
  try:
1822
    f = file("/dev/tty", "a+")
1823
  except IOError:
1824
    return answer
1825
  try:
1826
    chars = [entry[0] for entry in choices]
1827
    chars[-1] = "[%s]" % chars[-1]
1828
    chars.append("?")
1829
    maps = dict([(entry[0], entry[1]) for entry in choices])
1830
    while True:
1831
      f.write(text)
1832
      f.write("\n")
1833
      f.write("/".join(chars))
1834
      f.write(": ")
1835
      line = f.readline(2).strip().lower()
1836
      if line in maps:
1837
        answer = maps[line]
1838
        break
1839
      elif line == "?":
1840
        for entry in choices:
1841
          f.write(" %s - %s\n" % (entry[0], entry[2]))
1842
        f.write("\n")
1843
        continue
1844
  finally:
1845
    f.close()
1846
  return answer
1847

    
1848

    
1849
class JobSubmittedException(Exception):
1850
  """Job was submitted, client should exit.
1851

1852
  This exception has one argument, the ID of the job that was
1853
  submitted. The handler should print this ID.
1854

1855
  This is not an error, just a structured way to exit from clients.
1856

1857
  """
1858

    
1859

    
1860
def SendJob(ops, cl=None):
1861
  """Function to submit an opcode without waiting for the results.
1862

1863
  @type ops: list
1864
  @param ops: list of opcodes
1865
  @type cl: luxi.Client
1866
  @param cl: the luxi client to use for communicating with the master;
1867
             if None, a new client will be created
1868

1869
  """
1870
  if cl is None:
1871
    cl = GetClient()
1872

    
1873
  job_id = cl.SubmitJob(ops)
1874

    
1875
  return job_id
1876

    
1877

    
1878
def GenericPollJob(job_id, cbs, report_cbs):
1879
  """Generic job-polling function.
1880

1881
  @type job_id: number
1882
  @param job_id: Job ID
1883
  @type cbs: Instance of L{JobPollCbBase}
1884
  @param cbs: Data callbacks
1885
  @type report_cbs: Instance of L{JobPollReportCbBase}
1886
  @param report_cbs: Reporting callbacks
1887

1888
  """
1889
  prev_job_info = None
1890
  prev_logmsg_serial = None
1891

    
1892
  status = None
1893

    
1894
  while True:
1895
    result = cbs.WaitForJobChangeOnce(job_id, ["status"], prev_job_info,
1896
                                      prev_logmsg_serial)
1897
    if not result:
1898
      # job not found, go away!
1899
      raise errors.JobLost("Job with id %s lost" % job_id)
1900

    
1901
    if result == constants.JOB_NOTCHANGED:
1902
      report_cbs.ReportNotChanged(job_id, status)
1903

    
1904
      # Wait again
1905
      continue
1906

    
1907
    # Split result, a tuple of (field values, log entries)
1908
    (job_info, log_entries) = result
1909
    (status, ) = job_info
1910

    
1911
    if log_entries:
1912
      for log_entry in log_entries:
1913
        (serial, timestamp, log_type, message) = log_entry
1914
        report_cbs.ReportLogMessage(job_id, serial, timestamp,
1915
                                    log_type, message)
1916
        prev_logmsg_serial = max(prev_logmsg_serial, serial)
1917

    
1918
    # TODO: Handle canceled and archived jobs
1919
    elif status in (constants.JOB_STATUS_SUCCESS,
1920
                    constants.JOB_STATUS_ERROR,
1921
                    constants.JOB_STATUS_CANCELING,
1922
                    constants.JOB_STATUS_CANCELED):
1923
      break
1924

    
1925
    prev_job_info = job_info
1926

    
1927
  jobs = cbs.QueryJobs([job_id], ["status", "opstatus", "opresult"])
1928
  if not jobs:
1929
    raise errors.JobLost("Job with id %s lost" % job_id)
1930

    
1931
  status, opstatus, result = jobs[0]
1932

    
1933
  if status == constants.JOB_STATUS_SUCCESS:
1934
    return result
1935

    
1936
  if status in (constants.JOB_STATUS_CANCELING, constants.JOB_STATUS_CANCELED):
1937
    raise errors.OpExecError("Job was canceled")
1938

    
1939
  has_ok = False
1940
  for idx, (status, msg) in enumerate(zip(opstatus, result)):
1941
    if status == constants.OP_STATUS_SUCCESS:
1942
      has_ok = True
1943
    elif status == constants.OP_STATUS_ERROR:
1944
      errors.MaybeRaise(msg)
1945

    
1946
      if has_ok:
1947
        raise errors.OpExecError("partial failure (opcode %d): %s" %
1948
                                 (idx, msg))
1949

    
1950
      raise errors.OpExecError(str(msg))
1951

    
1952
  # default failure mode
1953
  raise errors.OpExecError(result)
1954

    
1955

    
1956
class JobPollCbBase:
1957
  """Base class for L{GenericPollJob} callbacks.
1958

1959
  """
1960
  def __init__(self):
1961
    """Initializes this class.
1962

1963
    """
1964

    
1965
  def WaitForJobChangeOnce(self, job_id, fields,
1966
                           prev_job_info, prev_log_serial):
1967
    """Waits for changes on a job.
1968

1969
    """
1970
    raise NotImplementedError()
1971

    
1972
  def QueryJobs(self, job_ids, fields):
1973
    """Returns the selected fields for the selected job IDs.
1974

1975
    @type job_ids: list of numbers
1976
    @param job_ids: Job IDs
1977
    @type fields: list of strings
1978
    @param fields: Fields
1979

1980
    """
1981
    raise NotImplementedError()
1982

    
1983

    
1984
class JobPollReportCbBase:
1985
  """Base class for L{GenericPollJob} reporting callbacks.
1986

1987
  """
1988
  def __init__(self):
1989
    """Initializes this class.
1990

1991
    """
1992

    
1993
  def ReportLogMessage(self, job_id, serial, timestamp, log_type, log_msg):
1994
    """Handles a log message.
1995

1996
    """
1997
    raise NotImplementedError()
1998

    
1999
  def ReportNotChanged(self, job_id, status):
2000
    """Called for if a job hasn't changed in a while.
2001

2002
    @type job_id: number
2003
    @param job_id: Job ID
2004
    @type status: string or None
2005
    @param status: Job status if available
2006

2007
    """
2008
    raise NotImplementedError()
2009

    
2010

    
2011
class _LuxiJobPollCb(JobPollCbBase):
2012
  def __init__(self, cl):
2013
    """Initializes this class.
2014

2015
    """
2016
    JobPollCbBase.__init__(self)
2017
    self.cl = cl
2018

    
2019
  def WaitForJobChangeOnce(self, job_id, fields,
2020
                           prev_job_info, prev_log_serial):
2021
    """Waits for changes on a job.
2022

2023
    """
2024
    return self.cl.WaitForJobChangeOnce(job_id, fields,
2025
                                        prev_job_info, prev_log_serial)
2026

    
2027
  def QueryJobs(self, job_ids, fields):
2028
    """Returns the selected fields for the selected job IDs.
2029

2030
    """
2031
    return self.cl.QueryJobs(job_ids, fields)
2032

    
2033

    
2034
class FeedbackFnJobPollReportCb(JobPollReportCbBase):
2035
  def __init__(self, feedback_fn):
2036
    """Initializes this class.
2037

2038
    """
2039
    JobPollReportCbBase.__init__(self)
2040

    
2041
    self.feedback_fn = feedback_fn
2042

    
2043
    assert callable(feedback_fn)
2044

    
2045
  def ReportLogMessage(self, job_id, serial, timestamp, log_type, log_msg):
2046
    """Handles a log message.
2047

2048
    """
2049
    self.feedback_fn((timestamp, log_type, log_msg))
2050

    
2051
  def ReportNotChanged(self, job_id, status):
2052
    """Called if a job hasn't changed in a while.
2053

2054
    """
2055
    # Ignore
2056

    
2057

    
2058
class StdioJobPollReportCb(JobPollReportCbBase):
2059
  def __init__(self):
2060
    """Initializes this class.
2061

2062
    """
2063
    JobPollReportCbBase.__init__(self)
2064

    
2065
    self.notified_queued = False
2066
    self.notified_waitlock = False
2067

    
2068
  def ReportLogMessage(self, job_id, serial, timestamp, log_type, log_msg):
2069
    """Handles a log message.
2070

2071
    """
2072
    ToStdout("%s %s", time.ctime(utils.MergeTime(timestamp)),
2073
             FormatLogMessage(log_type, log_msg))
2074

    
2075
  def ReportNotChanged(self, job_id, status):
2076
    """Called if a job hasn't changed in a while.
2077

2078
    """
2079
    if status is None:
2080
      return
2081

    
2082
    if status == constants.JOB_STATUS_QUEUED and not self.notified_queued:
2083
      ToStderr("Job %s is waiting in queue", job_id)
2084
      self.notified_queued = True
2085

    
2086
    elif status == constants.JOB_STATUS_WAITING and not self.notified_waitlock:
2087
      ToStderr("Job %s is trying to acquire all necessary locks", job_id)
2088
      self.notified_waitlock = True
2089

    
2090

    
2091
def FormatLogMessage(log_type, log_msg):
2092
  """Formats a job message according to its type.
2093

2094
  """
2095
  if log_type != constants.ELOG_MESSAGE:
2096
    log_msg = str(log_msg)
2097

    
2098
  return utils.SafeEncode(log_msg)
2099

    
2100

    
2101
def PollJob(job_id, cl=None, feedback_fn=None, reporter=None):
2102
  """Function to poll for the result of a job.
2103

2104
  @type job_id: job identified
2105
  @param job_id: the job to poll for results
2106
  @type cl: luxi.Client
2107
  @param cl: the luxi client to use for communicating with the master;
2108
             if None, a new client will be created
2109

2110
  """
2111
  if cl is None:
2112
    cl = GetClient()
2113

    
2114
  if reporter is None:
2115
    if feedback_fn:
2116
      reporter = FeedbackFnJobPollReportCb(feedback_fn)
2117
    else:
2118
      reporter = StdioJobPollReportCb()
2119
  elif feedback_fn:
2120
    raise errors.ProgrammerError("Can't specify reporter and feedback function")
2121

    
2122
  return GenericPollJob(job_id, _LuxiJobPollCb(cl), reporter)
2123

    
2124

    
2125
def SubmitOpCode(op, cl=None, feedback_fn=None, opts=None, reporter=None):
2126
  """Legacy function to submit an opcode.
2127

2128
  This is just a simple wrapper over the construction of the processor
2129
  instance. It should be extended to better handle feedback and
2130
  interaction functions.
2131

2132
  """
2133
  if cl is None:
2134
    cl = GetClient()
2135

    
2136
  SetGenericOpcodeOpts([op], opts)
2137

    
2138
  job_id = SendJob([op], cl=cl)
2139

    
2140
  op_results = PollJob(job_id, cl=cl, feedback_fn=feedback_fn,
2141
                       reporter=reporter)
2142

    
2143
  return op_results[0]
2144

    
2145

    
2146
def SubmitOrSend(op, opts, cl=None, feedback_fn=None):
2147
  """Wrapper around SubmitOpCode or SendJob.
2148

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

2154
  It will also process the opcodes if we're sending the via SendJob
2155
  (otherwise SubmitOpCode does it).
2156

2157
  """
2158
  if opts and opts.submit_only:
2159
    job = [op]
2160
    SetGenericOpcodeOpts(job, opts)
2161
    job_id = SendJob(job, cl=cl)
2162
    raise JobSubmittedException(job_id)
2163
  else:
2164
    return SubmitOpCode(op, cl=cl, feedback_fn=feedback_fn, opts=opts)
2165

    
2166

    
2167
def SetGenericOpcodeOpts(opcode_list, options):
2168
  """Processor for generic options.
2169

2170
  This function updates the given opcodes based on generic command
2171
  line options (like debug, dry-run, etc.).
2172

2173
  @param opcode_list: list of opcodes
2174
  @param options: command line options or None
2175
  @return: None (in-place modification)
2176

2177
  """
2178
  if not options:
2179
    return
2180
  for op in opcode_list:
2181
    op.debug_level = options.debug
2182
    if hasattr(options, "dry_run"):
2183
      op.dry_run = options.dry_run
2184
    if getattr(options, "priority", None) is not None:
2185
      op.priority = options.priority
2186

    
2187

    
2188
def GetClient(query=False):
2189
  """Connects to the a luxi socket and returns a client.
2190

2191
  @type query: boolean
2192
  @param query: this signifies that the client will only be
2193
      used for queries; if the build-time parameter
2194
      enable-split-queries is enabled, then the client will be
2195
      connected to the query socket instead of the masterd socket
2196

2197
  """
2198
  if query and constants.ENABLE_SPLIT_QUERY:
2199
    address = pathutils.QUERY_SOCKET
2200
  else:
2201
    address = None
2202
  # TODO: Cache object?
2203
  try:
2204
    client = luxi.Client(address=address)
2205
  except luxi.NoMasterError:
2206
    ss = ssconf.SimpleStore()
2207

    
2208
    # Try to read ssconf file
2209
    try:
2210
      ss.GetMasterNode()
2211
    except errors.ConfigurationError:
2212
      raise errors.OpPrereqError("Cluster not initialized or this machine is"
2213
                                 " not part of a cluster",
2214
                                 errors.ECODE_INVAL)
2215

    
2216
    master, myself = ssconf.GetMasterAndMyself(ss=ss)
2217
    if master != myself:
2218
      raise errors.OpPrereqError("This is not the master node, please connect"
2219
                                 " to node '%s' and rerun the command" %
2220
                                 master, errors.ECODE_INVAL)
2221
    raise
2222
  return client
2223

    
2224

    
2225
def FormatError(err):
2226
  """Return a formatted error message for a given error.
2227

2228
  This function takes an exception instance and returns a tuple
2229
  consisting of two values: first, the recommended exit code, and
2230
  second, a string describing the error message (not
2231
  newline-terminated).
2232

2233
  """
2234
  retcode = 1
2235
  obuf = StringIO()
2236
  msg = str(err)
2237
  if isinstance(err, errors.ConfigurationError):
2238
    txt = "Corrupt configuration file: %s" % msg
2239
    logging.error(txt)
2240
    obuf.write(txt + "\n")
2241
    obuf.write("Aborting.")
2242
    retcode = 2
2243
  elif isinstance(err, errors.HooksAbort):
2244
    obuf.write("Failure: hooks execution failed:\n")
2245
    for node, script, out in err.args[0]:
2246
      if out:
2247
        obuf.write("  node: %s, script: %s, output: %s\n" %
2248
                   (node, script, out))
2249
      else:
2250
        obuf.write("  node: %s, script: %s (no output)\n" %
2251
                   (node, script))
2252
  elif isinstance(err, errors.HooksFailure):
2253
    obuf.write("Failure: hooks general failure: %s" % msg)
2254
  elif isinstance(err, errors.ResolverError):
2255
    this_host = netutils.Hostname.GetSysName()
2256
    if err.args[0] == this_host:
2257
      msg = "Failure: can't resolve my own hostname ('%s')"
2258
    else:
2259
      msg = "Failure: can't resolve hostname '%s'"
2260
    obuf.write(msg % err.args[0])
2261
  elif isinstance(err, errors.OpPrereqError):
2262
    if len(err.args) == 2:
2263
      obuf.write("Failure: prerequisites not met for this"
2264
                 " operation:\nerror type: %s, error details:\n%s" %
2265
                 (err.args[1], err.args[0]))
2266
    else:
2267
      obuf.write("Failure: prerequisites not met for this"
2268
                 " operation:\n%s" % msg)
2269
  elif isinstance(err, errors.OpExecError):
2270
    obuf.write("Failure: command execution error:\n%s" % msg)
2271
  elif isinstance(err, errors.TagError):
2272
    obuf.write("Failure: invalid tag(s) given:\n%s" % msg)
2273
  elif isinstance(err, errors.JobQueueDrainError):
2274
    obuf.write("Failure: the job queue is marked for drain and doesn't"
2275
               " accept new requests\n")
2276
  elif isinstance(err, errors.JobQueueFull):
2277
    obuf.write("Failure: the job queue is full and doesn't accept new"
2278
               " job submissions until old jobs are archived\n")
2279
  elif isinstance(err, errors.TypeEnforcementError):
2280
    obuf.write("Parameter Error: %s" % msg)
2281
  elif isinstance(err, errors.ParameterError):
2282
    obuf.write("Failure: unknown/wrong parameter name '%s'" % msg)
2283
  elif isinstance(err, luxi.NoMasterError):
2284
    obuf.write("Cannot communicate with the master daemon.\nIs it running"
2285
               " and listening for connections?")
2286
  elif isinstance(err, luxi.TimeoutError):
2287
    obuf.write("Timeout while talking to the master daemon. Jobs might have"
2288
               " been submitted and will continue to run even if the call"
2289
               " timed out. Useful commands in this situation are \"gnt-job"
2290
               " list\", \"gnt-job cancel\" and \"gnt-job watch\". Error:\n")
2291
    obuf.write(msg)
2292
  elif isinstance(err, luxi.PermissionError):
2293
    obuf.write("It seems you don't have permissions to connect to the"
2294
               " master daemon.\nPlease retry as a different user.")
2295
  elif isinstance(err, luxi.ProtocolError):
2296
    obuf.write("Unhandled protocol error while talking to the master daemon:\n"
2297
               "%s" % msg)
2298
  elif isinstance(err, errors.JobLost):
2299
    obuf.write("Error checking job status: %s" % msg)
2300
  elif isinstance(err, errors.QueryFilterParseError):
2301
    obuf.write("Error while parsing query filter: %s\n" % err.args[0])
2302
    obuf.write("\n".join(err.GetDetails()))
2303
  elif isinstance(err, errors.GenericError):
2304
    obuf.write("Unhandled Ganeti error: %s" % msg)
2305
  elif isinstance(err, JobSubmittedException):
2306
    obuf.write("JobID: %s\n" % err.args[0])
2307
    retcode = 0
2308
  else:
2309
    obuf.write("Unhandled exception: %s" % msg)
2310
  return retcode, obuf.getvalue().rstrip("\n")
2311

    
2312

    
2313
def GenericMain(commands, override=None, aliases=None,
2314
                env_override=frozenset()):
2315
  """Generic main function for all the gnt-* commands.
2316

2317
  @param commands: a dictionary with a special structure, see the design doc
2318
                   for command line handling.
2319
  @param override: if not None, we expect a dictionary with keys that will
2320
                   override command line options; this can be used to pass
2321
                   options from the scripts to generic functions
2322
  @param aliases: dictionary with command aliases {'alias': 'target, ...}
2323
  @param env_override: list of environment names which are allowed to submit
2324
                       default args for commands
2325

2326
  """
2327
  # save the program name and the entire command line for later logging
2328
  if sys.argv:
2329
    binary = os.path.basename(sys.argv[0])
2330
    if not binary:
2331
      binary = sys.argv[0]
2332

    
2333
    if len(sys.argv) >= 2:
2334
      logname = utils.ShellQuoteArgs([binary, sys.argv[1]])
2335
    else:
2336
      logname = binary
2337

    
2338
    cmdline = utils.ShellQuoteArgs([binary] + sys.argv[1:])
2339
  else:
2340
    binary = "<unknown program>"
2341
    cmdline = "<unknown>"
2342

    
2343
  if aliases is None:
2344
    aliases = {}
2345

    
2346
  try:
2347
    (func, options, args) = _ParseArgs(binary, sys.argv, commands, aliases,
2348
                                       env_override)
2349
  except _ShowVersion:
2350
    ToStdout("%s (ganeti %s) %s", binary, constants.VCS_VERSION,
2351
             constants.RELEASE_VERSION)
2352
    return constants.EXIT_SUCCESS
2353
  except _ShowUsage, err:
2354
    for line in _FormatUsage(binary, commands):
2355
      ToStdout(line)
2356

    
2357
    if err.exit_error:
2358
      return constants.EXIT_FAILURE
2359
    else:
2360
      return constants.EXIT_SUCCESS
2361
  except errors.ParameterError, err:
2362
    result, err_msg = FormatError(err)
2363
    ToStderr(err_msg)
2364
    return 1
2365

    
2366
  if func is None: # parse error
2367
    return 1
2368

    
2369
  if override is not None:
2370
    for key, val in override.iteritems():
2371
      setattr(options, key, val)
2372

    
2373
  utils.SetupLogging(pathutils.LOG_COMMANDS, logname, debug=options.debug,
2374
                     stderr_logging=True)
2375

    
2376
  logging.info("Command line: %s", cmdline)
2377

    
2378
  try:
2379
    result = func(options, args)
2380
  except (errors.GenericError, luxi.ProtocolError,
2381
          JobSubmittedException), err:
2382
    result, err_msg = FormatError(err)
2383
    logging.exception("Error during command processing")
2384
    ToStderr(err_msg)
2385
  except KeyboardInterrupt:
2386
    result = constants.EXIT_FAILURE
2387
    ToStderr("Aborted. Note that if the operation created any jobs, they"
2388
             " might have been submitted and"
2389
             " will continue to run in the background.")
2390
  except IOError, err:
2391
    if err.errno == errno.EPIPE:
2392
      # our terminal went away, we'll exit
2393
      sys.exit(constants.EXIT_FAILURE)
2394
    else:
2395
      raise
2396

    
2397
  return result
2398

    
2399

    
2400
def ParseNicOption(optvalue):
2401
  """Parses the value of the --net option(s).
2402

2403
  """
2404
  try:
2405
    nic_max = max(int(nidx[0]) + 1 for nidx in optvalue)
2406
  except (TypeError, ValueError), err:
2407
    raise errors.OpPrereqError("Invalid NIC index passed: %s" % str(err),
2408
                               errors.ECODE_INVAL)
2409

    
2410
  nics = [{}] * nic_max
2411
  for nidx, ndict in optvalue:
2412
    nidx = int(nidx)
2413

    
2414
    if not isinstance(ndict, dict):
2415
      raise errors.OpPrereqError("Invalid nic/%d value: expected dict,"
2416
                                 " got %s" % (nidx, ndict), errors.ECODE_INVAL)
2417

    
2418
    utils.ForceDictType(ndict, constants.INIC_PARAMS_TYPES)
2419

    
2420
    nics[nidx] = ndict
2421

    
2422
  return nics
2423

    
2424

    
2425
def GenericInstanceCreate(mode, opts, args):
2426
  """Add an instance to the cluster via either creation or import.
2427

2428
  @param mode: constants.INSTANCE_CREATE or constants.INSTANCE_IMPORT
2429
  @param opts: the command line options selected by the user
2430
  @type args: list
2431
  @param args: should contain only one element, the new instance name
2432
  @rtype: int
2433
  @return: the desired exit code
2434

2435
  """
2436
  instance = args[0]
2437

    
2438
  (pnode, snode) = SplitNodeOption(opts.node)
2439

    
2440
  hypervisor = None
2441
  hvparams = {}
2442
  if opts.hypervisor:
2443
    hypervisor, hvparams = opts.hypervisor
2444

    
2445
  if opts.nics:
2446
    nics = ParseNicOption(opts.nics)
2447
  elif opts.no_nics:
2448
    # no nics
2449
    nics = []
2450
  elif mode == constants.INSTANCE_CREATE:
2451
    # default of one nic, all auto
2452
    nics = [{}]
2453
  else:
2454
    # mode == import
2455
    nics = []
2456

    
2457
  if opts.disk_template == constants.DT_DISKLESS:
2458
    if opts.disks or opts.sd_size is not None:
2459
      raise errors.OpPrereqError("Diskless instance but disk"
2460
                                 " information passed", errors.ECODE_INVAL)
2461
    disks = []
2462
  else:
2463
    if (not opts.disks and not opts.sd_size
2464
        and mode == constants.INSTANCE_CREATE):
2465
      raise errors.OpPrereqError("No disk information specified",
2466
                                 errors.ECODE_INVAL)
2467
    if opts.disks and opts.sd_size is not None:
2468
      raise errors.OpPrereqError("Please use either the '--disk' or"
2469
                                 " '-s' option", errors.ECODE_INVAL)
2470
    if opts.sd_size is not None:
2471
      opts.disks = [(0, {constants.IDISK_SIZE: opts.sd_size})]
2472

    
2473
    if opts.disks:
2474
      try:
2475
        disk_max = max(int(didx[0]) + 1 for didx in opts.disks)
2476
      except ValueError, err:
2477
        raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err),
2478
                                   errors.ECODE_INVAL)
2479
      disks = [{}] * disk_max
2480
    else:
2481
      disks = []
2482
    for didx, ddict in opts.disks:
2483
      didx = int(didx)
2484
      if not isinstance(ddict, dict):
2485
        msg = "Invalid disk/%d value: expected dict, got %s" % (didx, ddict)
2486
        raise errors.OpPrereqError(msg, errors.ECODE_INVAL)
2487
      elif constants.IDISK_SIZE in ddict:
2488
        if constants.IDISK_ADOPT in ddict:
2489
          raise errors.OpPrereqError("Only one of 'size' and 'adopt' allowed"
2490
                                     " (disk %d)" % didx, errors.ECODE_INVAL)
2491
        try:
2492
          ddict[constants.IDISK_SIZE] = \
2493
            utils.ParseUnit(ddict[constants.IDISK_SIZE])
2494
        except ValueError, err:
2495
          raise errors.OpPrereqError("Invalid disk size for disk %d: %s" %
2496
                                     (didx, err), errors.ECODE_INVAL)
2497
      elif constants.IDISK_ADOPT in ddict:
2498
        if mode == constants.INSTANCE_IMPORT:
2499
          raise errors.OpPrereqError("Disk adoption not allowed for instance"
2500
                                     " import", errors.ECODE_INVAL)
2501
        ddict[constants.IDISK_SIZE] = 0
2502
      else:
2503
        raise errors.OpPrereqError("Missing size or adoption source for"
2504
                                   " disk %d" % didx, errors.ECODE_INVAL)
2505
      disks[didx] = ddict
2506

    
2507
  if opts.tags is not None:
2508
    tags = opts.tags.split(",")
2509
  else:
2510
    tags = []
2511

    
2512
  utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_COMPAT)
2513
  utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
2514

    
2515
  if mode == constants.INSTANCE_CREATE:
2516
    start = opts.start
2517
    os_type = opts.os
2518
    force_variant = opts.force_variant
2519
    src_node = None
2520
    src_path = None
2521
    no_install = opts.no_install
2522
    identify_defaults = False
2523
  elif mode == constants.INSTANCE_IMPORT:
2524
    start = False
2525
    os_type = None
2526
    force_variant = False
2527
    src_node = opts.src_node
2528
    src_path = opts.src_dir
2529
    no_install = None
2530
    identify_defaults = opts.identify_defaults
2531
  else:
2532
    raise errors.ProgrammerError("Invalid creation mode %s" % mode)
2533

    
2534
  op = opcodes.OpInstanceCreate(instance_name=instance,
2535
                                disks=disks,
2536
                                disk_template=opts.disk_template,
2537
                                nics=nics,
2538
                                conflicts_check=opts.conflicts_check,
2539
                                pnode=pnode, snode=snode,
2540
                                ip_check=opts.ip_check,
2541
                                name_check=opts.name_check,
2542
                                wait_for_sync=opts.wait_for_sync,
2543
                                file_storage_dir=opts.file_storage_dir,
2544
                                file_driver=opts.file_driver,
2545
                                iallocator=opts.iallocator,
2546
                                hypervisor=hypervisor,
2547
                                hvparams=hvparams,
2548
                                beparams=opts.beparams,
2549
                                osparams=opts.osparams,
2550
                                mode=mode,
2551
                                start=start,
2552
                                os_type=os_type,
2553
                                force_variant=force_variant,
2554
                                src_node=src_node,
2555
                                src_path=src_path,
2556
                                tags=tags,
2557
                                no_install=no_install,
2558
                                identify_defaults=identify_defaults,
2559
                                ignore_ipolicy=opts.ignore_ipolicy)
2560

    
2561
  SubmitOrSend(op, opts)
2562
  return 0
2563

    
2564

    
2565
class _RunWhileClusterStoppedHelper:
2566
  """Helper class for L{RunWhileClusterStopped} to simplify state management
2567

2568
  """
2569
  def __init__(self, feedback_fn, cluster_name, master_node, online_nodes):
2570
    """Initializes this class.
2571

2572
    @type feedback_fn: callable
2573
    @param feedback_fn: Feedback function
2574
    @type cluster_name: string
2575
    @param cluster_name: Cluster name
2576
    @type master_node: string
2577
    @param master_node Master node name
2578
    @type online_nodes: list
2579
    @param online_nodes: List of names of online nodes
2580

2581
    """
2582
    self.feedback_fn = feedback_fn
2583
    self.cluster_name = cluster_name
2584
    self.master_node = master_node
2585
    self.online_nodes = online_nodes
2586

    
2587
    self.ssh = ssh.SshRunner(self.cluster_name)
2588

    
2589
    self.nonmaster_nodes = [name for name in online_nodes
2590
                            if name != master_node]
2591

    
2592
    assert self.master_node not in self.nonmaster_nodes
2593

    
2594
  def _RunCmd(self, node_name, cmd):
2595
    """Runs a command on the local or a remote machine.
2596

2597
    @type node_name: string
2598
    @param node_name: Machine name
2599
    @type cmd: list
2600
    @param cmd: Command
2601

2602
    """
2603
    if node_name is None or node_name == self.master_node:
2604
      # No need to use SSH
2605
      result = utils.RunCmd(cmd)
2606
    else:
2607
      result = self.ssh.Run(node_name, constants.SSH_LOGIN_USER,
2608
                            utils.ShellQuoteArgs(cmd))
2609

    
2610
    if result.failed:
2611
      errmsg = ["Failed to run command %s" % result.cmd]
2612
      if node_name:
2613
        errmsg.append("on node %s" % node_name)
2614
      errmsg.append(": exitcode %s and error %s" %
2615
                    (result.exit_code, result.output))
2616
      raise errors.OpExecError(" ".join(errmsg))
2617

    
2618
  def Call(self, fn, *args):
2619
    """Call function while all daemons are stopped.
2620

2621
    @type fn: callable
2622
    @param fn: Function to be called
2623

2624
    """
2625
    # Pause watcher by acquiring an exclusive lock on watcher state file
2626
    self.feedback_fn("Blocking watcher")
2627
    watcher_block = utils.FileLock.Open(pathutils.WATCHER_LOCK_FILE)
2628
    try:
2629
      # TODO: Currently, this just blocks. There's no timeout.
2630
      # TODO: Should it be a shared lock?
2631
      watcher_block.Exclusive(blocking=True)
2632

    
2633
      # Stop master daemons, so that no new jobs can come in and all running
2634
      # ones are finished
2635
      self.feedback_fn("Stopping master daemons")
2636
      self._RunCmd(None, [pathutils.DAEMON_UTIL, "stop-master"])
2637
      try:
2638
        # Stop daemons on all nodes
2639
        for node_name in self.online_nodes:
2640
          self.feedback_fn("Stopping daemons on %s" % node_name)
2641
          self._RunCmd(node_name, [pathutils.DAEMON_UTIL, "stop-all"])
2642

    
2643
        # All daemons are shut down now
2644
        try:
2645
          return fn(self, *args)
2646
        except Exception, err:
2647
          _, errmsg = FormatError(err)
2648
          logging.exception("Caught exception")
2649
          self.feedback_fn(errmsg)
2650
          raise
2651
      finally:
2652
        # Start cluster again, master node last
2653
        for node_name in self.nonmaster_nodes + [self.master_node]:
2654
          self.feedback_fn("Starting daemons on %s" % node_name)
2655
          self._RunCmd(node_name, [pathutils.DAEMON_UTIL, "start-all"])
2656
    finally:
2657
      # Resume watcher
2658
      watcher_block.Close()
2659

    
2660

    
2661
def RunWhileClusterStopped(feedback_fn, fn, *args):
2662
  """Calls a function while all cluster daemons are stopped.
2663

2664
  @type feedback_fn: callable
2665
  @param feedback_fn: Feedback function
2666
  @type fn: callable
2667
  @param fn: Function to be called when daemons are stopped
2668

2669
  """
2670
  feedback_fn("Gathering cluster information")
2671

    
2672
  # This ensures we're running on the master daemon
2673
  cl = GetClient()
2674

    
2675
  (cluster_name, master_node) = \
2676
    cl.QueryConfigValues(["cluster_name", "master_node"])
2677

    
2678
  online_nodes = GetOnlineNodes([], cl=cl)
2679

    
2680
  # Don't keep a reference to the client. The master daemon will go away.
2681
  del cl
2682

    
2683
  assert master_node in online_nodes
2684

    
2685
  return _RunWhileClusterStoppedHelper(feedback_fn, cluster_name, master_node,
2686
                                       online_nodes).Call(fn, *args)
2687

    
2688

    
2689
def GenerateTable(headers, fields, separator, data,
2690
                  numfields=None, unitfields=None,
2691
                  units=None):
2692
  """Prints a table with headers and different fields.
2693

2694
  @type headers: dict
2695
  @param headers: dictionary mapping field names to headers for
2696
      the table
2697
  @type fields: list
2698
  @param fields: the field names corresponding to each row in
2699
      the data field
2700
  @param separator: the separator to be used; if this is None,
2701
      the default 'smart' algorithm is used which computes optimal
2702
      field width, otherwise just the separator is used between
2703
      each field
2704
  @type data: list
2705
  @param data: a list of lists, each sublist being one row to be output
2706
  @type numfields: list
2707
  @param numfields: a list with the fields that hold numeric
2708
      values and thus should be right-aligned
2709
  @type unitfields: list
2710
  @param unitfields: a list with the fields that hold numeric
2711
      values that should be formatted with the units field
2712
  @type units: string or None
2713
  @param units: the units we should use for formatting, or None for
2714
      automatic choice (human-readable for non-separator usage, otherwise
2715
      megabytes); this is a one-letter string
2716

2717
  """
2718
  if units is None:
2719
    if separator:
2720
      units = "m"
2721
    else:
2722
      units = "h"
2723

    
2724
  if numfields is None:
2725
    numfields = []
2726
  if unitfields is None:
2727
    unitfields = []
2728

    
2729
  numfields = utils.FieldSet(*numfields)   # pylint: disable=W0142
2730
  unitfields = utils.FieldSet(*unitfields) # pylint: disable=W0142
2731

    
2732
  format_fields = []
2733
  for field in fields:
2734
    if headers and field not in headers:
2735
      # TODO: handle better unknown fields (either revert to old
2736
      # style of raising exception, or deal more intelligently with
2737
      # variable fields)
2738
      headers[field] = field
2739
    if separator is not None:
2740
      format_fields.append("%s")
2741
    elif numfields.Matches(field):
2742
      format_fields.append("%*s")
2743
    else:
2744
      format_fields.append("%-*s")
2745

    
2746
  if separator is None:
2747
    mlens = [0 for name in fields]
2748
    format_str = " ".join(format_fields)
2749
  else:
2750
    format_str = separator.replace("%", "%%").join(format_fields)
2751

    
2752
  for row in data:
2753
    if row is None:
2754
      continue
2755
    for idx, val in enumerate(row):
2756
      if unitfields.Matches(fields[idx]):
2757
        try:
2758
          val = int(val)
2759
        except (TypeError, ValueError):
2760
          pass
2761
        else:
2762
          val = row[idx] = utils.FormatUnit(val, units)
2763
      val = row[idx] = str(val)
2764
      if separator is None:
2765
        mlens[idx] = max(mlens[idx], len(val))
2766

    
2767
  result = []
2768
  if headers:
2769
    args = []
2770
    for idx, name in enumerate(fields):
2771
      hdr = headers[name]
2772
      if separator is None:
2773
        mlens[idx] = max(mlens[idx], len(hdr))
2774
        args.append(mlens[idx])
2775
      args.append(hdr)
2776
    result.append(format_str % tuple(args))
2777

    
2778
  if separator is None:
2779
    assert len(mlens) == len(fields)
2780

    
2781
    if fields and not numfields.Matches(fields[-1]):
2782
      mlens[-1] = 0
2783

    
2784
  for line in data:
2785
    args = []
2786
    if line is None:
2787
      line = ["-" for _ in fields]
2788
    for idx in range(len(fields)):
2789
      if separator is None:
2790
        args.append(mlens[idx])
2791
      args.append(line[idx])
2792
    result.append(format_str % tuple(args))
2793

    
2794
  return result
2795

    
2796

    
2797
def _FormatBool(value):
2798
  """Formats a boolean value as a string.
2799

2800
  """
2801
  if value:
2802
    return "Y"
2803
  return "N"
2804

    
2805

    
2806
#: Default formatting for query results; (callback, align right)
2807
_DEFAULT_FORMAT_QUERY = {
2808
  constants.QFT_TEXT: (str, False),
2809
  constants.QFT_BOOL: (_FormatBool, False),
2810
  constants.QFT_NUMBER: (str, True),
2811
  constants.QFT_TIMESTAMP: (utils.FormatTime, False),
2812
  constants.QFT_OTHER: (str, False),
2813
  constants.QFT_UNKNOWN: (str, False),
2814
  }
2815

    
2816

    
2817
def _GetColumnFormatter(fdef, override, unit):
2818
  """Returns formatting function for a field.
2819

2820
  @type fdef: L{objects.QueryFieldDefinition}
2821
  @type override: dict
2822
  @param override: Dictionary for overriding field formatting functions,
2823
    indexed by field name, contents like L{_DEFAULT_FORMAT_QUERY}
2824
  @type unit: string
2825
  @param unit: Unit used for formatting fields of type L{constants.QFT_UNIT}
2826
  @rtype: tuple; (callable, bool)
2827
  @return: Returns the function to format a value (takes one parameter) and a
2828
    boolean for aligning the value on the right-hand side
2829

2830
  """
2831
  fmt = override.get(fdef.name, None)
2832
  if fmt is not None:
2833
    return fmt
2834

    
2835
  assert constants.QFT_UNIT not in _DEFAULT_FORMAT_QUERY
2836

    
2837
  if fdef.kind == constants.QFT_UNIT:
2838
    # Can't keep this information in the static dictionary
2839
    return (lambda value: utils.FormatUnit(value, unit), True)
2840

    
2841
  fmt = _DEFAULT_FORMAT_QUERY.get(fdef.kind, None)
2842
  if fmt is not None:
2843
    return fmt
2844

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

    
2847

    
2848
class _QueryColumnFormatter:
2849
  """Callable class for formatting fields of a query.
2850

2851
  """
2852
  def __init__(self, fn, status_fn, verbose):
2853
    """Initializes this class.
2854

2855
    @type fn: callable
2856
    @param fn: Formatting function
2857
    @type status_fn: callable
2858
    @param status_fn: Function to report fields' status
2859
    @type verbose: boolean
2860
    @param verbose: whether to use verbose field descriptions or not
2861

2862
    """
2863
    self._fn = fn
2864
    self._status_fn = status_fn
2865
    self._verbose = verbose
2866

    
2867
  def __call__(self, data):
2868
    """Returns a field's string representation.
2869

2870
    """
2871
    (status, value) = data
2872

    
2873
    # Report status
2874
    self._status_fn(status)
2875

    
2876
    if status == constants.RS_NORMAL:
2877
      return self._fn(value)
2878

    
2879
    assert value is None, \
2880
           "Found value %r for abnormal status %s" % (value, status)
2881

    
2882
    return FormatResultError(status, self._verbose)
2883

    
2884

    
2885
def FormatResultError(status, verbose):
2886
  """Formats result status other than L{constants.RS_NORMAL}.
2887

2888
  @param status: The result status
2889
  @type verbose: boolean
2890
  @param verbose: Whether to return the verbose text
2891
  @return: Text of result status
2892

2893
  """
2894
  assert status != constants.RS_NORMAL, \
2895
         "FormatResultError called with status equal to constants.RS_NORMAL"
2896
  try:
2897
    (verbose_text, normal_text) = constants.RSS_DESCRIPTION[status]
2898
  except KeyError:
2899
    raise NotImplementedError("Unknown status %s" % status)
2900
  else:
2901
    if verbose:
2902
      return verbose_text
2903
    return normal_text
2904

    
2905

    
2906
def FormatQueryResult(result, unit=None, format_override=None, separator=None,
2907
                      header=False, verbose=False):
2908
  """Formats data in L{objects.QueryResponse}.
2909

2910
  @type result: L{objects.QueryResponse}
2911
  @param result: result of query operation
2912
  @type unit: string
2913
  @param unit: Unit used for formatting fields of type L{constants.QFT_UNIT},
2914
    see L{utils.text.FormatUnit}
2915
  @type format_override: dict
2916
  @param format_override: Dictionary for overriding field formatting functions,
2917
    indexed by field name, contents like L{_DEFAULT_FORMAT_QUERY}
2918
  @type separator: string or None
2919
  @param separator: String used to separate fields
2920
  @type header: bool
2921
  @param header: Whether to output header row
2922
  @type verbose: boolean
2923
  @param verbose: whether to use verbose field descriptions or not
2924

2925
  """
2926
  if unit is None:
2927
    if separator:
2928
      unit = "m"
2929
    else:
2930
      unit = "h"
2931

    
2932
  if format_override is None:
2933
    format_override = {}
2934

    
2935
  stats = dict.fromkeys(constants.RS_ALL, 0)
2936

    
2937
  def _RecordStatus(status):
2938
    if status in stats:
2939
      stats[status] += 1
2940

    
2941
  columns = []
2942
  for fdef in result.fields:
2943
    assert fdef.title and fdef.name
2944
    (fn, align_right) = _GetColumnFormatter(fdef, format_override, unit)
2945
    columns.append(TableColumn(fdef.title,
2946
                               _QueryColumnFormatter(fn, _RecordStatus,
2947
                                                     verbose),
2948
                               align_right))
2949

    
2950
  table = FormatTable(result.data, columns, header, separator)
2951

    
2952
  # Collect statistics
2953
  assert len(stats) == len(constants.RS_ALL)
2954
  assert compat.all(count >= 0 for count in stats.values())
2955

    
2956
  # Determine overall status. If there was no data, unknown fields must be
2957
  # detected via the field definitions.
2958
  if (stats[constants.RS_UNKNOWN] or
2959
      (not result.data and _GetUnknownFields(result.fields))):
2960
    status = QR_UNKNOWN
2961
  elif compat.any(count > 0 for key, count in stats.items()
2962
                  if key != constants.RS_NORMAL):
2963
    status = QR_INCOMPLETE
2964
  else:
2965
    status = QR_NORMAL
2966

    
2967
  return (status, table)
2968

    
2969

    
2970
def _GetUnknownFields(fdefs):
2971
  """Returns list of unknown fields included in C{fdefs}.
2972

2973
  @type fdefs: list of L{objects.QueryFieldDefinition}
2974

2975
  """
2976
  return [fdef for fdef in fdefs
2977
          if fdef.kind == constants.QFT_UNKNOWN]
2978

    
2979

    
2980
def _WarnUnknownFields(fdefs):
2981
  """Prints a warning to stderr if a query included unknown fields.
2982

2983
  @type fdefs: list of L{objects.QueryFieldDefinition}
2984

2985
  """
2986
  unknown = _GetUnknownFields(fdefs)
2987
  if unknown:
2988
    ToStderr("Warning: Queried for unknown fields %s",
2989
             utils.CommaJoin(fdef.name for fdef in unknown))
2990
    return True
2991

    
2992
  return False
2993

    
2994

    
2995
def GenericList(resource, fields, names, unit, separator, header, cl=None,
2996
                format_override=None, verbose=False, force_filter=False,
2997
                namefield=None, qfilter=None, isnumeric=False):
2998
  """Generic implementation for listing all items of a resource.
2999

3000
  @param resource: One of L{constants.QR_VIA_LUXI}
3001
  @type fields: list of strings
3002
  @param fields: List of fields to query for
3003
  @type names: list of strings
3004
  @param names: Names of items to query for
3005
  @type unit: string or None
3006
  @param unit: Unit used for formatting fields of type L{constants.QFT_UNIT} or
3007
    None for automatic choice (human-readable for non-separator usage,
3008
    otherwise megabytes); this is a one-letter string
3009
  @type separator: string or None
3010
  @param separator: String used to separate fields
3011
  @type header: bool
3012
  @param header: Whether to show header row
3013
  @type force_filter: bool
3014
  @param force_filter: Whether to always treat names as filter
3015
  @type format_override: dict
3016
  @param format_override: Dictionary for overriding field formatting functions,
3017
    indexed by field name, contents like L{_DEFAULT_FORMAT_QUERY}
3018
  @type verbose: boolean
3019
  @param verbose: whether to use verbose field descriptions or not
3020
  @type namefield: string
3021
  @param namefield: Name of field to use for simple filters (see
3022
    L{qlang.MakeFilter} for details)
3023
  @type qfilter: list or None
3024
  @param qfilter: Query filter (in addition to names)
3025
  @param isnumeric: bool
3026
  @param isnumeric: Whether the namefield's type is numeric, and therefore
3027
    any simple filters built by namefield should use integer values to
3028
    reflect that
3029

3030
  """
3031
  if not names:
3032
    names = None
3033

    
3034
  namefilter = qlang.MakeFilter(names, force_filter, namefield=namefield,
3035
                                isnumeric=isnumeric)
3036

    
3037
  if qfilter is None:
3038
    qfilter = namefilter
3039
  elif namefilter is not None:
3040
    qfilter = [qlang.OP_AND, namefilter, qfilter]
3041

    
3042
  if cl is None:
3043
    cl = GetClient()
3044

    
3045
  response = cl.Query(resource, fields, qfilter)
3046

    
3047
  found_unknown = _WarnUnknownFields(response.fields)
3048

    
3049
  (status, data) = FormatQueryResult(response, unit=unit, separator=separator,
3050
                                     header=header,
3051
                                     format_override=format_override,
3052
                                     verbose=verbose)
3053

    
3054
  for line in data:
3055
    ToStdout(line)
3056

    
3057
  assert ((found_unknown and status == QR_UNKNOWN) or
3058
          (not found_unknown and status != QR_UNKNOWN))
3059

    
3060
  if status == QR_UNKNOWN:
3061
    return constants.EXIT_UNKNOWN_FIELD
3062

    
3063
  # TODO: Should the list command fail if not all data could be collected?
3064
  return constants.EXIT_SUCCESS
3065

    
3066

    
3067
def GenericListFields(resource, fields, separator, header, cl=None):
3068
  """Generic implementation for listing fields for a resource.
3069

3070
  @param resource: One of L{constants.QR_VIA_LUXI}
3071
  @type fields: list of strings
3072
  @param fields: List of fields to query for
3073
  @type separator: string or None
3074
  @param separator: String used to separate fields
3075
  @type header: bool
3076
  @param header: Whether to show header row
3077

3078
  """
3079
  if cl is None:
3080
    cl = GetClient()
3081

    
3082
  if not fields:
3083
    fields = None
3084

    
3085
  response = cl.QueryFields(resource, fields)
3086

    
3087
  found_unknown = _WarnUnknownFields(response.fields)
3088

    
3089
  columns = [
3090
    TableColumn("Name", str, False),
3091
    TableColumn("Title", str, False),
3092
    TableColumn("Description", str, False),
3093
    ]
3094

    
3095
  rows = [[fdef.name, fdef.title, fdef.doc] for fdef in response.fields]
3096

    
3097
  for line in FormatTable(rows, columns, header, separator):
3098
    ToStdout(line)
3099

    
3100
  if found_unknown:
3101
    return constants.EXIT_UNKNOWN_FIELD
3102

    
3103
  return constants.EXIT_SUCCESS
3104

    
3105

    
3106
class TableColumn:
3107
  """Describes a column for L{FormatTable}.
3108

3109
  """
3110
  def __init__(self, title, fn, align_right):
3111
    """Initializes this class.
3112

3113
    @type title: string
3114
    @param title: Column title
3115
    @type fn: callable
3116
    @param fn: Formatting function
3117
    @type align_right: bool
3118
    @param align_right: Whether to align values on the right-hand side
3119

3120
    """
3121
    self.title = title
3122
    self.format = fn
3123
    self.align_right = align_right
3124

    
3125

    
3126
def _GetColFormatString(width, align_right):
3127
  """Returns the format string for a field.
3128

3129
  """
3130
  if align_right:
3131
    sign = ""
3132
  else:
3133
    sign = "-"
3134

    
3135
  return "%%%s%ss" % (sign, width)
3136

    
3137

    
3138
def FormatTable(rows, columns, header, separator):
3139
  """Formats data as a table.
3140

3141
  @type rows: list of lists
3142
  @param rows: Row data, one list per row
3143
  @type columns: list of L{TableColumn}
3144
  @param columns: Column descriptions
3145
  @type header: bool
3146
  @param header: Whether to show header row
3147
  @type separator: string or None
3148
  @param separator: String used to separate columns
3149

3150
  """
3151
  if header:
3152
    data = [[col.title for col in columns]]
3153
    colwidth = [len(col.title) for col in columns]
3154
  else:
3155
    data = []
3156
    colwidth = [0 for _ in columns]
3157

    
3158
  # Format row data
3159
  for row in rows:
3160
    assert len(row) == len(columns)
3161

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

    
3164
    if separator is None:
3165
      # Update column widths
3166
      for idx, (oldwidth, value) in enumerate(zip(colwidth, formatted)):
3167
        # Modifying a list's items while iterating is fine
3168
        colwidth[idx] = max(oldwidth, len(value))
3169

    
3170
    data.append(formatted)
3171

    
3172
  if separator is not None:
3173
    # Return early if a separator is used
3174
    return [separator.join(row) for row in data]
3175

    
3176
  if columns and not columns[-1].align_right:
3177
    # Avoid unnecessary spaces at end of line
3178
    colwidth[-1] = 0
3179

    
3180
  # Build format string
3181
  fmt = " ".join([_GetColFormatString(width, col.align_right)
3182
                  for col, width in zip(columns, colwidth)])
3183

    
3184
  return [fmt % tuple(row) for row in data]
3185

    
3186

    
3187
def FormatTimestamp(ts):
3188
  """Formats a given timestamp.
3189

3190
  @type ts: timestamp
3191
  @param ts: a timeval-type timestamp, a tuple of seconds and microseconds
3192

3193
  @rtype: string
3194
  @return: a string with the formatted timestamp
3195

3196
  """
3197
  if not isinstance(ts, (tuple, list)) or len(ts) != 2:
3198
    return "?"
3199

    
3200
  (sec, usecs) = ts
3201
  return utils.FormatTime(sec, usecs=usecs)
3202

    
3203

    
3204
def ParseTimespec(value):
3205
  """Parse a time specification.
3206

3207
  The following suffixed will be recognized:
3208

3209
    - s: seconds
3210
    - m: minutes
3211
    - h: hours
3212
    - d: day
3213
    - w: weeks
3214

3215
  Without any suffix, the value will be taken to be in seconds.
3216

3217
  """
3218
  value = str(value)
3219
  if not value:
3220
    raise errors.OpPrereqError("Empty time specification passed",
3221
                               errors.ECODE_INVAL)
3222
  suffix_map = {
3223
    "s": 1,
3224
    "m": 60,
3225
    "h": 3600,
3226
    "d": 86400,
3227
    "w": 604800,
3228
    }
3229
  if value[-1] not in suffix_map:
3230
    try:
3231
      value = int(value)
3232
    except (TypeError, ValueError):
3233
      raise errors.OpPrereqError("Invalid time specification '%s'" % value,
3234
                                 errors.ECODE_INVAL)
3235
  else:
3236
    multiplier = suffix_map[value[-1]]
3237
    value = value[:-1]
3238
    if not value: # no data left after stripping the suffix
3239
      raise errors.OpPrereqError("Invalid time specification (only"
3240
                                 " suffix passed)", errors.ECODE_INVAL)
3241
    try:
3242
      value = int(value) * multiplier
3243
    except (TypeError, ValueError):
3244
      raise errors.OpPrereqError("Invalid time specification '%s'" % value,
3245
                                 errors.ECODE_INVAL)
3246
  return value
3247

    
3248

    
3249
def GetOnlineNodes(nodes, cl=None, nowarn=False, secondary_ips=False,
3250
                   filter_master=False, nodegroup=None):
3251
  """Returns the names of online nodes.
3252

3253
  This function will also log a warning on stderr with the names of
3254
  the online nodes.
3255

3256
  @param nodes: if not empty, use only this subset of nodes (minus the
3257
      offline ones)
3258
  @param cl: if not None, luxi client to use
3259
  @type nowarn: boolean
3260
  @param nowarn: by default, this function will output a note with the
3261
      offline nodes that are skipped; if this parameter is True the
3262
      note is not displayed
3263
  @type secondary_ips: boolean
3264
  @param secondary_ips: if True, return the secondary IPs instead of the
3265
      names, useful for doing network traffic over the replication interface
3266
      (if any)
3267
  @type filter_master: boolean
3268
  @param filter_master: if True, do not return the master node in the list
3269
      (useful in coordination with secondary_ips where we cannot check our
3270
      node name against the list)
3271
  @type nodegroup: string
3272
  @param nodegroup: If set, only return nodes in this node group
3273

3274
  """
3275
  if cl is None:
3276
    cl = GetClient()
3277

    
3278
  qfilter = []
3279

    
3280
  if nodes:
3281
    qfilter.append(qlang.MakeSimpleFilter("name", nodes))
3282

    
3283
  if nodegroup is not None:
3284
    qfilter.append([qlang.OP_OR, [qlang.OP_EQUAL, "group", nodegroup],
3285
                                 [qlang.OP_EQUAL, "group.uuid", nodegroup]])
3286

    
3287
  if filter_master:
3288
    qfilter.append([qlang.OP_NOT, [qlang.OP_TRUE, "master"]])
3289

    
3290
  if qfilter:
3291
    if len(qfilter) > 1:
3292
      final_filter = [qlang.OP_AND] + qfilter
3293
    else:
3294
      assert len(qfilter) == 1
3295
      final_filter = qfilter[0]
3296
  else:
3297
    final_filter = None
3298

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

    
3301
  def _IsOffline(row):
3302
    (_, (_, offline), _) = row
3303
    return offline
3304

    
3305
  def _GetName(row):
3306
    ((_, name), _, _) = row
3307
    return name
3308

    
3309
  def _GetSip(row):
3310
    (_, _, (_, sip)) = row
3311
    return sip
3312

    
3313
  (offline, online) = compat.partition(result.data, _IsOffline)
3314

    
3315
  if offline and not nowarn:
3316
    ToStderr("Note: skipping offline node(s): %s" %
3317
             utils.CommaJoin(map(_GetName, offline)))
3318

    
3319
  if secondary_ips:
3320
    fn = _GetSip
3321
  else:
3322
    fn = _GetName
3323

    
3324
  return map(fn, online)
3325

    
3326

    
3327
def _ToStream(stream, txt, *args):
3328
  """Write a message to a stream, bypassing the logging system
3329

3330
  @type stream: file object
3331
  @param stream: the file to which we should write
3332
  @type txt: str
3333
  @param txt: the message
3334

3335
  """
3336
  try:
3337
    if args:
3338
      args = tuple(args)
3339
      stream.write(txt % args)
3340
    else:
3341
      stream.write(txt)
3342
    stream.write("\n")
3343
    stream.flush()
3344
  except IOError, err:
3345
    if err.errno == errno.EPIPE:
3346
      # our terminal went away, we'll exit
3347
      sys.exit(constants.EXIT_FAILURE)
3348
    else:
3349
      raise
3350

    
3351

    
3352
def ToStdout(txt, *args):
3353
  """Write a message to stdout only, bypassing the logging system
3354

3355
  This is just a wrapper over _ToStream.
3356

3357
  @type txt: str
3358
  @param txt: the message
3359

3360
  """
3361
  _ToStream(sys.stdout, txt, *args)
3362

    
3363

    
3364
def ToStderr(txt, *args):
3365
  """Write a message to stderr only, bypassing the logging system
3366

3367
  This is just a wrapper over _ToStream.
3368

3369
  @type txt: str
3370
  @param txt: the message
3371

3372
  """
3373
  _ToStream(sys.stderr, txt, *args)
3374

    
3375

    
3376
class JobExecutor(object):
3377
  """Class which manages the submission and execution of multiple jobs.
3378

3379
  Note that instances of this class should not be reused between
3380
  GetResults() calls.
3381

3382
  """
3383
  def __init__(self, cl=None, verbose=True, opts=None, feedback_fn=None):
3384
    self.queue = []
3385
    if cl is None:
3386
      cl = GetClient()
3387
    self.cl = cl
3388
    self.verbose = verbose
3389
    self.jobs = []
3390
    self.opts = opts
3391
    self.feedback_fn = feedback_fn
3392
    self._counter = itertools.count()
3393

    
3394
  @staticmethod
3395
  def _IfName(name, fmt):
3396
    """Helper function for formatting name.
3397

3398
    """
3399
    if name:
3400
      return fmt % name
3401

    
3402
    return ""
3403

    
3404
  def QueueJob(self, name, *ops):
3405
    """Record a job for later submit.
3406

3407
    @type name: string
3408
    @param name: a description of the job, will be used in WaitJobSet
3409

3410
    """
3411
    SetGenericOpcodeOpts(ops, self.opts)
3412
    self.queue.append((self._counter.next(), name, ops))
3413

    
3414
  def AddJobId(self, name, status, job_id):
3415
    """Adds a job ID to the internal queue.
3416

3417
    """
3418
    self.jobs.append((self._counter.next(), status, job_id, name))
3419

    
3420
  def SubmitPending(self, each=False):
3421
    """Submit all pending jobs.
3422

3423
    """
3424
    if each:
3425
      results = []
3426
      for (_, _, ops) in self.queue:
3427
        # SubmitJob will remove the success status, but raise an exception if
3428
        # the submission fails, so we'll notice that anyway.
3429
        results.append([True, self.cl.SubmitJob(ops)[0]])
3430
    else:
3431
      results = self.cl.SubmitManyJobs([ops for (_, _, ops) in self.queue])
3432
    for ((status, data), (idx, name, _)) in zip(results, self.queue):
3433
      self.jobs.append((idx, status, data, name))
3434

    
3435
  def _ChooseJob(self):
3436
    """Choose a non-waiting/queued job to poll next.
3437

3438
    """
3439
    assert self.jobs, "_ChooseJob called with empty job list"
3440

    
3441
    result = self.cl.QueryJobs([i[2] for i in self.jobs[:_CHOOSE_BATCH]],
3442
                               ["status"])
3443
    assert result
3444

    
3445
    for job_data, status in zip(self.jobs, result):
3446
      if (isinstance(status, list) and status and
3447
          status[0] in (constants.JOB_STATUS_QUEUED,
3448
                        constants.JOB_STATUS_WAITING,
3449
                        constants.JOB_STATUS_CANCELING)):
3450
        # job is still present and waiting
3451
        continue
3452
      # good candidate found (either running job or lost job)
3453
      self.jobs.remove(job_data)
3454
      return job_data
3455

    
3456
    # no job found
3457
    return self.jobs.pop(0)
3458

    
3459
  def GetResults(self):
3460
    """Wait for and return the results of all jobs.
3461

3462
    @rtype: list
3463
    @return: list of tuples (success, job results), in the same order
3464
        as the submitted jobs; if a job has failed, instead of the result
3465
        there will be the error message
3466

3467
    """
3468
    if not self.jobs:
3469
      self.SubmitPending()
3470
    results = []
3471
    if self.verbose:
3472
      ok_jobs = [row[2] for row in self.jobs if row[1]]
3473
      if ok_jobs:
3474
        ToStdout("Submitted jobs %s", utils.CommaJoin(ok_jobs))
3475

    
3476
    # first, remove any non-submitted jobs
3477
    self.jobs, failures = compat.partition(self.jobs, lambda x: x[1])
3478
    for idx, _, jid, name in failures:
3479
      ToStderr("Failed to submit job%s: %s", self._IfName(name, " for %s"), jid)
3480
      results.append((idx, False, jid))
3481

    
3482
    while self.jobs:
3483
      (idx, _, jid, name) = self._ChooseJob()
3484
      ToStdout("Waiting for job %s%s ...", jid, self._IfName(name, " for %s"))
3485
      try:
3486
        job_result = PollJob(jid, cl=self.cl, feedback_fn=self.feedback_fn)
3487
        success = True
3488
      except errors.JobLost, err:
3489
        _, job_result = FormatError(err)
3490
        ToStderr("Job %s%s has been archived, cannot check its result",
3491
                 jid, self._IfName(name, " for %s"))
3492
        success = False
3493
      except (errors.GenericError, luxi.ProtocolError), err:
3494
        _, job_result = FormatError(err)
3495
        success = False
3496
        # the error message will always be shown, verbose or not
3497
        ToStderr("Job %s%s has failed: %s",
3498
                 jid, self._IfName(name, " for %s"), job_result)
3499

    
3500
      results.append((idx, success, job_result))
3501

    
3502
    # sort based on the index, then drop it
3503
    results.sort()
3504
    results = [i[1:] for i in results]
3505

    
3506
    return results
3507

    
3508
  def WaitOrShow(self, wait):
3509
    """Wait for job results or only print the job IDs.
3510

3511
    @type wait: boolean
3512
    @param wait: whether to wait or not
3513

3514
    """
3515
    if wait:
3516
      return self.GetResults()
3517
    else:
3518
      if not self.jobs:
3519
        self.SubmitPending()
3520
      for _, status, result, name in self.jobs:
3521
        if status:
3522
          ToStdout("%s: %s", result, name)
3523
        else:
3524
          ToStderr("Failure for %s: %s", name, result)
3525
      return [row[1:3] for row in self.jobs]
3526

    
3527

    
3528
def FormatParameterDict(buf, param_dict, actual, level=1):
3529
  """Formats a parameter dictionary.
3530

3531
  @type buf: L{StringIO}
3532
  @param buf: the buffer into which to write
3533
  @type param_dict: dict
3534
  @param param_dict: the own parameters
3535
  @type actual: dict
3536
  @param actual: the current parameter set (including defaults)
3537
  @param level: Level of indent
3538

3539
  """
3540
  indent = "  " * level
3541

    
3542
  for key in sorted(actual):
3543
    data = actual[key]
3544
    buf.write("%s- %s:" % (indent, key))
3545

    
3546
    if isinstance(data, dict) and data:
3547
      buf.write("\n")
3548
      FormatParameterDict(buf, param_dict.get(key, {}), data,
3549
                          level=level + 1)
3550
    else:
3551
      val = param_dict.get(key, "default (%s)" % data)
3552
      buf.write(" %s\n" % val)
3553

    
3554

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

3558
  This function is used to request confirmation for doing an operation
3559
  on a given list of list_type.
3560

3561
  @type names: list
3562
  @param names: the list of names that we display when
3563
      we ask for confirmation
3564
  @type list_type: str
3565
  @param list_type: Human readable name for elements in the list (e.g. nodes)
3566
  @type text: str
3567
  @param text: the operation that the user should confirm
3568
  @rtype: boolean
3569
  @return: True or False depending on user's confirmation.
3570

3571
  """
3572
  count = len(names)
3573
  msg = ("The %s will operate on %d %s.\n%s"
3574
         "Do you want to continue?" % (text, count, list_type, extra))
3575
  affected = (("\nAffected %s:\n" % list_type) +
3576
              "\n".join(["  %s" % name for name in names]))
3577

    
3578
  choices = [("y", True, "Yes, execute the %s" % text),
3579
             ("n", False, "No, abort the %s" % text)]
3580

    
3581
  if count > 20:
3582
    choices.insert(1, ("v", "v", "View the list of affected %s" % list_type))
3583
    question = msg
3584
  else:
3585
    question = msg + affected
3586

    
3587
  choice = AskUser(question, choices)
3588
  if choice == "v":
3589
    choices.pop(1)
3590
    choice = AskUser(msg + affected, choices)
3591
  return choice
3592

    
3593

    
3594
def _MaybeParseUnit(elements):
3595
  """Parses and returns an array of potential values with units.
3596

3597
  """
3598
  parsed = {}
3599
  for k, v in elements.items():
3600
    if v == constants.VALUE_DEFAULT:
3601
      parsed[k] = v
3602
    else:
3603
      parsed[k] = utils.ParseUnit(v)
3604
  return parsed
3605

    
3606

    
3607
def CreateIPolicyFromOpts(ispecs_mem_size=None,
3608
                          ispecs_cpu_count=None,
3609
                          ispecs_disk_count=None,
3610
                          ispecs_disk_size=None,
3611
                          ispecs_nic_count=None,
3612
                          ipolicy_disk_templates=None,
3613
                          ipolicy_vcpu_ratio=None,
3614
                          ipolicy_spindle_ratio=None,
3615
                          group_ipolicy=False,
3616
                          allowed_values=None,
3617
                          fill_all=False):
3618
  """Creation of instance policy based on command line options.
3619

3620
  @param fill_all: whether for cluster policies we should ensure that
3621
    all values are filled
3622

3623

3624
  """
3625
  try:
3626
    if ispecs_mem_size:
3627
      ispecs_mem_size = _MaybeParseUnit(ispecs_mem_size)
3628
    if ispecs_disk_size:
3629
      ispecs_disk_size = _MaybeParseUnit(ispecs_disk_size)
3630
  except (TypeError, ValueError, errors.UnitParseError), err:
3631
    raise errors.OpPrereqError("Invalid disk (%s) or memory (%s) size"
3632
                               " in policy: %s" %
3633
                               (ispecs_disk_size, ispecs_mem_size, err),
3634
                               errors.ECODE_INVAL)
3635

    
3636
  # prepare ipolicy dict
3637
  ipolicy_transposed = {
3638
    constants.ISPEC_MEM_SIZE: ispecs_mem_size,
3639
    constants.ISPEC_CPU_COUNT: ispecs_cpu_count,
3640
    constants.ISPEC_DISK_COUNT: ispecs_disk_count,
3641
    constants.ISPEC_DISK_SIZE: ispecs_disk_size,
3642
    constants.ISPEC_NIC_COUNT: ispecs_nic_count,
3643
    }
3644

    
3645
  # first, check that the values given are correct
3646
  if group_ipolicy:
3647
    forced_type = TISPECS_GROUP_TYPES
3648
  else:
3649
    forced_type = TISPECS_CLUSTER_TYPES
3650

    
3651
  for specs in ipolicy_transposed.values():
3652
    utils.ForceDictType(specs, forced_type, allowed_values=allowed_values)
3653

    
3654
  # then transpose
3655
  ipolicy_out = objects.MakeEmptyIPolicy()
3656
  for name, specs in ipolicy_transposed.iteritems():
3657
    assert name in constants.ISPECS_PARAMETERS
3658
    for key, val in specs.items(): # {min: .. ,max: .., std: ..}
3659
      ipolicy_out[key][name] = val
3660

    
3661
  # no filldict for non-dicts
3662
  if not group_ipolicy and fill_all:
3663
    if ipolicy_disk_templates is None:
3664
      ipolicy_disk_templates = constants.DISK_TEMPLATES
3665
    if ipolicy_vcpu_ratio is None:
3666
      ipolicy_vcpu_ratio = \
3667
        constants.IPOLICY_DEFAULTS[constants.IPOLICY_VCPU_RATIO]
3668
    if ipolicy_spindle_ratio is None:
3669
      ipolicy_spindle_ratio = \
3670
        constants.IPOLICY_DEFAULTS[constants.IPOLICY_SPINDLE_RATIO]
3671
  if ipolicy_disk_templates is not None:
3672
    ipolicy_out[constants.IPOLICY_DTS] = list(ipolicy_disk_templates)
3673
  if ipolicy_vcpu_ratio is not None:
3674
    ipolicy_out[constants.IPOLICY_VCPU_RATIO] = ipolicy_vcpu_ratio
3675
  if ipolicy_spindle_ratio is not None:
3676
    ipolicy_out[constants.IPOLICY_SPINDLE_RATIO] = ipolicy_spindle_ratio
3677

    
3678
  assert not (frozenset(ipolicy_out.keys()) - constants.IPOLICY_ALL_KEYS)
3679

    
3680
  return ipolicy_out