Statistics
| Branch: | Tag: | Revision:

root / lib / cli.py @ 9c784fb3

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

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

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

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

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

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

    
304

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

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

    
317

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

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

    
327

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

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

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

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

    
343

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

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

350
  """
351

    
352

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

356
  """
357

    
358

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

362
  """
363

    
364

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

368
  """
369

    
370

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

374
  """
375

    
376
class ArgGroup(_Argument):
377
  """Node group argument.
378

379
  """
380

    
381

    
382
class ArgJobId(_Argument):
383
  """Job ID argument.
384

385
  """
386

    
387

    
388
class ArgFile(_Argument):
389
  """File path argument.
390

391
  """
392

    
393

    
394
class ArgCommand(_Argument):
395
  """Command argument.
396

397
  """
398

    
399

    
400
class ArgHost(_Argument):
401
  """Host argument.
402

403
  """
404

    
405

    
406
class ArgOs(_Argument):
407
  """OS argument.
408

409
  """
410

    
411

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

    
424

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

428
  Note that this function will modify its args parameter.
429

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

    
448

    
449
def _ExtendTags(opts, args):
450
  """Extend the args if a source file has been given.
451

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

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

    
477

    
478
def ListTags(opts, args):
479
  """List the tags on a given object.
480

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

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

    
495

    
496
def AddTags(opts, args):
497
  """Add tags on a given object.
498

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

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

    
512

    
513
def RemoveTags(opts, args):
514
  """Remove tags from a given object.
515

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

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

    
529

    
530
def check_unit(option, opt, value): # pylint: disable=W0613
531
  """OptParsers custom converter for units.
532

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

    
539

    
540
def _SplitKeyVal(opt, data):
541
  """Convert a KeyVal string into a dict.
542

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

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

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

    
576

    
577
def check_ident_key_val(option, opt, value):  # pylint: disable=W0613
578
  """Custom parser for ident:key=val,key=val options.
579

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

583
  """
584
  if ":" not in value:
585
    ident, rest = value, ""
586
  else:
587
    ident, rest = value.split(":", 1)
588

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

    
606

    
607
def check_key_val(option, opt, value):  # pylint: disable=W0613
608
  """Custom parser class for key=val,key=val options.
609

610
  This will store the parsed values as a dict {key: val}.
611

612
  """
613
  return _SplitKeyVal(opt, value)
614

    
615

    
616
def check_bool(option, opt, value): # pylint: disable=W0613
617
  """Custom parser for yes/no options.
618

619
  This will store the parsed value as either True or False.
620

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

    
630

    
631
def check_list(option, opt, value): # pylint: disable=W0613
632
  """Custom parser for comma-separated lists.
633

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

    
642

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

646
  """
647
  value = value.lower()
648

    
649
  if value == constants.VALUE_DEFAULT:
650
    return value
651
  else:
652
    return float(value)
653

    
654

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

    
666
OPT_COMPL_ALL = frozenset([
667
  OPT_COMPL_MANY_NODES,
668
  OPT_COMPL_ONE_NODE,
669
  OPT_COMPL_ONE_INSTANCE,
670
  OPT_COMPL_ONE_OS,
671
  OPT_COMPL_ONE_IALLOCATOR,
672
  OPT_COMPL_ONE_NETWORK,
673
  OPT_COMPL_INST_ADD_NODES,
674
  OPT_COMPL_ONE_NODEGROUP,
675
  ])
676

    
677

    
678
class CliOption(Option):
679
  """Custom option class for optparse.
680

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

    
701

    
702
# optparse.py sets make_option, so we do it for our own option class, too
703
cli_option = CliOption
704

    
705

    
706
_YORNO = "yes|no"
707

    
708
DEBUG_OPT = cli_option("-d", "--debug", default=0, action="count",
709
                       help="Increase debugging level")
710

    
711
NOHDR_OPT = cli_option("--no-headers", default=False,
712
                       action="store_true", dest="no_headers",
713
                       help="Don't display column headers")
714

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

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

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

    
728
FORCE_OPT = cli_option("-f", "--force", dest="force", action="store_true",
729
                       default=False, help="Force the operation")
730

    
731
CONFIRM_OPT = cli_option("--yes", dest="confirm", action="store_true",
732
                         default=False, help="Do not require confirmation")
733

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

    
739
TAG_ADD_OPT = cli_option("--tags", dest="tags",
740
                         default=None, help="Comma-separated list of instance"
741
                                            " tags")
742

    
743
TAG_SRC_OPT = cli_option("--from", dest="tags_source",
744
                         default=None, help="File with tag names")
745

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

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

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

    
762
VERBOSE_OPT = cli_option("-v", "--verbose", default=False,
763
                         action="store_true",
764
                         help="Increase the verbosity of the operation")
765

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

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

    
775
WFSYNC_OPT = cli_option("--wait-for-sync", dest="wait_for_sync",
776
                        default=False, action="store_true",
777
                        help="Wait for disks to sync")
778

    
779
ONLINE_INST_OPT = cli_option("--online", dest="online_inst",
780
                             action="store_true", default=False,
781
                             help="Enable offline instance")
782

    
783
OFFLINE_INST_OPT = cli_option("--offline", dest="offline_inst",
784
                              action="store_true", default=False,
785
                              help="Disable down instance")
786

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

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

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

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

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

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

    
820
OS_OPT = cli_option("-o", "--os-type", dest="os", help="What OS to run",
821
                    metavar="<os>",
822
                    completion_suggest=OPT_COMPL_ONE_OS)
823

    
824
OSPARAMS_OPT = cli_option("-O", "--os-parameters", dest="osparams",
825
                          type="keyval", default={},
826
                          help="OS parameters")
827

    
828
FORCE_VARIANT_OPT = cli_option("--force-variant", dest="force_variant",
829
                               action="store_true", default=False,
830
                               help="Force an unknown variant")
831

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

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

    
842
BACKEND_OPT = cli_option("-B", "--backend-parameters", dest="beparams",
843
                         type="keyval", default={},
844
                         help="Backend parameters")
845

    
846
HVOPTS_OPT = cli_option("-H", "--hypervisor-parameters", type="keyval",
847
                        default={}, dest="hvparams",
848
                        help="Hypervisor parameters")
849

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

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

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

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

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

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

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

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

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

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

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

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

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

    
920
NET_OPT = cli_option("--net",
921
                     help="NIC parameters", default=[],
922
                     dest="nics", action="append", type="identkeyval")
923

    
924
DISK_OPT = cli_option("--disk", help="Disk parameters", default=[],
925
                      dest="disks", action="append", type="identkeyval")
926

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

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

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

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

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

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

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

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

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

    
980
SINGLE_NODE_OPT = cli_option("-n", "--node", dest="node", help="Target node",
981
                             metavar="<node>",
982
                             completion_suggest=OPT_COMPL_ONE_NODE)
983

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

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

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

    
1001
STATIC_OPT = cli_option("-s", "--static", dest="static",
1002
                        action="store_true", default=False,
1003
                        help="Only show configuration data, not runtime data")
1004

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

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

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

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

    
1029
REMOVE_INSTANCE_OPT = cli_option("--remove-instance", dest="remove_instance",
1030
                                 action="store_true", default=False,
1031
                                 help="Remove the instance from the cluster")
1032

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

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

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

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

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

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

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

    
1075
SRC_NODE_OPT = cli_option("--src-node", dest="src_node", help="Source node",
1076
                          metavar="<node>",
1077
                          completion_suggest=OPT_COMPL_ONE_NODE)
1078

    
1079
SRC_DIR_OPT = cli_option("--src-dir", dest="src_dir", help="Source directory",
1080
                         metavar="<dir>")
1081

    
1082
SECONDARY_IP_OPT = cli_option("-s", "--secondary-ip", dest="secondary_ip",
1083
                              help="Specify the secondary ip for the node",
1084
                              metavar="ADDRESS", default=None)
1085

    
1086
READD_OPT = cli_option("--readd", dest="readd",
1087
                       default=False, action="store_true",
1088
                       help="Readd old node after replacing it")
1089

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

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

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

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

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

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

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

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

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

    
1130
ENABLED_HV_OPT = cli_option("--enabled-hypervisors",
1131
                            dest="enabled_hypervisors",
1132
                            help="Comma-separated list of hypervisors",
1133
                            type="string", default=None)
1134

    
1135
NIC_PARAMS_OPT = cli_option("-N", "--nic-parameters", dest="nicparams",
1136
                            type="keyval", default={},
1137
                            help="NIC parameters")
1138

    
1139
CP_SIZE_OPT = cli_option("-C", "--candidate-pool-size", default=None,
1140
                         dest="candidate_pool_size", type="int",
1141
                         help="Set the candidate pool size")
1142

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

    
1149
YES_DOIT_OPT = cli_option("--yes-do-it", "--ya-rly", dest="yes_do_it",
1150
                          help="Destroy cluster", action="store_true")
1151

    
1152
NOVOTING_OPT = cli_option("--no-voting", dest="no_voting",
1153
                          help="Skip node agreement check (dangerous)",
1154
                          action="store_true", default=False)
1155

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

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

    
1170
MASTER_NETMASK_OPT = cli_option("--master-netmask", dest="master_netmask",
1171
                                help="Specify the netmask of the master IP",
1172
                                metavar="NETMASK",
1173
                                default=None)
1174

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

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

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

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

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

    
1206
ERROR_CODES_OPT = cli_option("--error-codes", dest="error_codes",
1207
                             help="Enable parseable error messages",
1208
                             action="store_true", default=False)
1209

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

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

    
1220
IGNORE_SECONDARIES_OPT = cli_option("--ignore-secondaries",
1221
                                    dest="ignore_secondaries",
1222
                                    default=False, action="store_true",
1223
                                    help="Ignore errors from secondaries")
1224

    
1225
NOSHUTDOWN_OPT = cli_option("--noshutdown", dest="shutdown",
1226
                            action="store_false", default=True,
1227
                            help="Don't shutdown the instance (unsafe)")
1228

    
1229
TIMEOUT_OPT = cli_option("--timeout", dest="timeout", type="int",
1230
                         default=constants.DEFAULT_SHUTDOWN_TIMEOUT,
1231
                         help="Maximum time to wait")
1232

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

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

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

    
1250
NEW_CLUSTER_CERT_OPT = cli_option("--new-cluster-certificate",
1251
                                  dest="new_cluster_cert",
1252
                                  default=False, action="store_true",
1253
                                  help="Generate a new cluster certificate")
1254

    
1255
RAPI_CERT_OPT = cli_option("--rapi-certificate", dest="rapi_cert",
1256
                           default=None,
1257
                           help="File containing new RAPI certificate")
1258

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

    
1264
SPICE_CERT_OPT = cli_option("--spice-certificate", dest="spice_cert",
1265
                            default=None,
1266
                            help="File containing new SPICE certificate")
1267

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

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

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

    
1285
CLUSTER_DOMAIN_SECRET_OPT = cli_option("--cluster-domain-secret",
1286
                                       dest="cluster_domain_secret",
1287
                                       default=None,
1288
                                       help=("Load new new cluster domain"
1289
                                             " secret from file"))
1290

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

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

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

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

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

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

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

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

    
1340
ROMAN_OPT = cli_option("--roman",
1341
                       dest="roman_integers", default=False,
1342
                       action="store_true",
1343
                       help="Use roman numbers for positive integers")
1344

    
1345
DRBD_HELPER_OPT = cli_option("--drbd-usermode-helper", dest="drbd_helper",
1346
                             action="store", default=None,
1347
                             help="Specifies usermode helper for DRBD")
1348

    
1349
NODRBD_STORAGE_OPT = cli_option("--no-drbd-storage", dest="drbd_storage",
1350
                                action="store_false", default=True,
1351
                                help="Disable support for DRBD")
1352

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

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

    
1364

    
1365
def _PriorityOptionCb(option, _, value, parser):
1366
  """Callback for processing C{--priority} option.
1367

1368
  """
1369
  value = _PRIONAME_TO_VALUE[value]
1370

    
1371
  setattr(parser.values, option.dest, value)
1372

    
1373

    
1374
PRIORITY_OPT = cli_option("--priority", default=None, dest="priority",
1375
                          metavar="|".join(name for name, _ in _PRIORITY_NAMES),
1376
                          choices=_PRIONAME_TO_VALUE.keys(),
1377
                          action="callback", type="choice",
1378
                          callback=_PriorityOptionCb,
1379
                          help="Priority for opcode processing")
1380

    
1381
HID_OS_OPT = cli_option("--hidden", dest="hidden",
1382
                        type="bool", default=None, metavar=_YORNO,
1383
                        help="Sets the hidden flag on the OS")
1384

    
1385
BLK_OS_OPT = cli_option("--blacklisted", dest="blacklisted",
1386
                        type="bool", default=None, metavar=_YORNO,
1387
                        help="Sets the blacklisted flag on the OS")
1388

    
1389
PREALLOC_WIPE_DISKS_OPT = cli_option("--prealloc-wipe-disks", default=None,
1390
                                     type="bool", metavar=_YORNO,
1391
                                     dest="prealloc_wipe_disks",
1392
                                     help=("Wipe disks prior to instance"
1393
                                           " creation"))
1394

    
1395
NODE_PARAMS_OPT = cli_option("--node-parameters", dest="ndparams",
1396
                             type="keyval", default=None,
1397
                             help="Node parameters")
1398

    
1399
ALLOC_POLICY_OPT = cli_option("--alloc-policy", dest="alloc_policy",
1400
                              action="store", metavar="POLICY", default=None,
1401
                              help="Allocation policy for the node group")
1402

    
1403
NODE_POWERED_OPT = cli_option("--node-powered", default=None,
1404
                              type="bool", metavar=_YORNO,
1405
                              dest="node_powered",
1406
                              help="Specify if the SoR for node is powered")
1407

    
1408
OOB_TIMEOUT_OPT = cli_option("--oob-timeout", dest="oob_timeout", type="int",
1409
                             default=constants.OOB_TIMEOUT,
1410
                             help="Maximum time to wait for out-of-band helper")
1411

    
1412
POWER_DELAY_OPT = cli_option("--power-delay", dest="power_delay", type="float",
1413
                             default=constants.OOB_POWER_DELAY,
1414
                             help="Time in seconds to wait between power-ons")
1415

    
1416
FORCE_FILTER_OPT = cli_option("-F", "--filter", dest="force_filter",
1417
                              action="store_true", default=False,
1418
                              help=("Whether command argument should be treated"
1419
                                    " as filter"))
1420

    
1421
NO_REMEMBER_OPT = cli_option("--no-remember",
1422
                             dest="no_remember",
1423
                             action="store_true", default=False,
1424
                             help="Perform but do not record the change"
1425
                             " in the configuration")
1426

    
1427
PRIMARY_ONLY_OPT = cli_option("-p", "--primary-only",
1428
                              default=False, action="store_true",
1429
                              help="Evacuate primary instances only")
1430

    
1431
SECONDARY_ONLY_OPT = cli_option("-s", "--secondary-only",
1432
                                default=False, action="store_true",
1433
                                help="Evacuate secondary instances only"
1434
                                     " (applies only to internally mirrored"
1435
                                     " disk templates, e.g. %s)" %
1436
                                     utils.CommaJoin(constants.DTS_INT_MIRROR))
1437

    
1438
STARTUP_PAUSED_OPT = cli_option("--paused", dest="startup_paused",
1439
                                action="store_true", default=False,
1440
                                help="Pause instance at startup")
1441

    
1442
TO_GROUP_OPT = cli_option("--to", dest="to", metavar="<group>",
1443
                          help="Destination node group (name or uuid)",
1444
                          default=None, action="append",
1445
                          completion_suggest=OPT_COMPL_ONE_NODEGROUP)
1446

    
1447
IGNORE_ERRORS_OPT = cli_option("-I", "--ignore-errors", default=[],
1448
                               action="append", dest="ignore_errors",
1449
                               choices=list(constants.CV_ALL_ECODES_STRINGS),
1450
                               help="Error code to be ignored")
1451

    
1452
DISK_STATE_OPT = cli_option("--disk-state", default=[], dest="disk_state",
1453
                            action="append",
1454
                            help=("Specify disk state information in the"
1455
                                  " format"
1456
                                  " storage_type/identifier:option=value,...;"
1457
                                  " note this is unused for now"),
1458
                            type="identkeyval")
1459

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

    
1467
IGNORE_IPOLICY_OPT = cli_option("--ignore-ipolicy", dest="ignore_ipolicy",
1468
                                action="store_true", default=False,
1469
                                help="Ignore instance policy violations")
1470

    
1471
RUNTIME_MEM_OPT = cli_option("-m", "--runtime-memory", dest="runtime_mem",
1472
                             help="Sets the instance's runtime memory,"
1473
                             " ballooning it up or down to the new value",
1474
                             default=None, type="unit", metavar="<size>")
1475

    
1476
ABSOLUTE_OPT = cli_option("--absolute", dest="absolute",
1477
                          action="store_true", default=False,
1478
                          help="Marks the grow as absolute instead of the"
1479
                          " (default) relative mode")
1480

    
1481
NETWORK_OPT = cli_option("--network",
1482
                         action="store", default=None, dest="network",
1483
                         help="IP network in CIDR notation")
1484

    
1485
GATEWAY_OPT = cli_option("--gateway",
1486
                         action="store", default=None, dest="gateway",
1487
                         help="IP address of the router (gateway)")
1488

    
1489
ADD_RESERVED_IPS_OPT = cli_option("--add-reserved-ips",
1490
                                  action="store", default=None,
1491
                                  dest="add_reserved_ips",
1492
                                  help="Comma-separated list of"
1493
                                  " reserved IPs to add")
1494

    
1495
REMOVE_RESERVED_IPS_OPT = cli_option("--remove-reserved-ips",
1496
                                     action="store", default=None,
1497
                                     dest="remove_reserved_ips",
1498
                                     help="Comma-delimited list of"
1499
                                     " reserved IPs to remove")
1500

    
1501
NETWORK_TYPE_OPT = cli_option("--network-type",
1502
                              action="store", default=None, dest="network_type",
1503
                              help="Network type: private, public, None")
1504

    
1505
NETWORK6_OPT = cli_option("--network6",
1506
                          action="store", default=None, dest="network6",
1507
                          help="IP network in CIDR notation")
1508

    
1509
GATEWAY6_OPT = cli_option("--gateway6",
1510
                          action="store", default=None, dest="gateway6",
1511
                          help="IP6 address of the router (gateway)")
1512

    
1513
NOCONFLICTSCHECK_OPT = cli_option("--no-conflicts-check",
1514
                                  dest="conflicts_check",
1515
                                  default=True,
1516
                                  action="store_false",
1517
                                  help="Don't check for conflicting IPs")
1518

    
1519
#: Options provided by all commands
1520
COMMON_OPTS = [DEBUG_OPT]
1521

    
1522
# common options for creating instances. add and import then add their own
1523
# specific ones.
1524
COMMON_CREATE_OPTS = [
1525
  BACKEND_OPT,
1526
  DISK_OPT,
1527
  DISK_TEMPLATE_OPT,
1528
  FILESTORE_DIR_OPT,
1529
  FILESTORE_DRIVER_OPT,
1530
  HYPERVISOR_OPT,
1531
  IALLOCATOR_OPT,
1532
  NET_OPT,
1533
  NODE_PLACEMENT_OPT,
1534
  NOIPCHECK_OPT,
1535
  NOCONFLICTSCHECK_OPT,
1536
  NONAMECHECK_OPT,
1537
  NONICS_OPT,
1538
  NWSYNC_OPT,
1539
  OSPARAMS_OPT,
1540
  OS_SIZE_OPT,
1541
  SUBMIT_OPT,
1542
  TAG_ADD_OPT,
1543
  DRY_RUN_OPT,
1544
  PRIORITY_OPT,
1545
  ]
1546

    
1547
# common instance policy options
1548
INSTANCE_POLICY_OPTS = [
1549
  SPECS_CPU_COUNT_OPT,
1550
  SPECS_DISK_COUNT_OPT,
1551
  SPECS_DISK_SIZE_OPT,
1552
  SPECS_MEM_SIZE_OPT,
1553
  SPECS_NIC_COUNT_OPT,
1554
  IPOLICY_DISK_TEMPLATES,
1555
  IPOLICY_VCPU_RATIO,
1556
  IPOLICY_SPINDLE_RATIO,
1557
  ]
1558

    
1559

    
1560
class _ShowUsage(Exception):
1561
  """Exception class for L{_ParseArgs}.
1562

1563
  """
1564
  def __init__(self, exit_error):
1565
    """Initializes instances of this class.
1566

1567
    @type exit_error: bool
1568
    @param exit_error: Whether to report failure on exit
1569

1570
    """
1571
    Exception.__init__(self)
1572
    self.exit_error = exit_error
1573

    
1574

    
1575
class _ShowVersion(Exception):
1576
  """Exception class for L{_ParseArgs}.
1577

1578
  """
1579

    
1580

    
1581
def _ParseArgs(binary, argv, commands, aliases, env_override):
1582
  """Parser for the command line arguments.
1583

1584
  This function parses the arguments and returns the function which
1585
  must be executed together with its (modified) arguments.
1586

1587
  @param binary: Script name
1588
  @param argv: Command line arguments
1589
  @param commands: Dictionary containing command definitions
1590
  @param aliases: dictionary with command aliases {"alias": "target", ...}
1591
  @param env_override: list of env variables allowed for default args
1592
  @raise _ShowUsage: If usage description should be shown
1593
  @raise _ShowVersion: If version should be shown
1594

1595
  """
1596
  assert not (env_override - set(commands))
1597
  assert not (set(aliases.keys()) & set(commands.keys()))
1598

    
1599
  if len(argv) > 1:
1600
    cmd = argv[1]
1601
  else:
1602
    # No option or command given
1603
    raise _ShowUsage(exit_error=True)
1604

    
1605
  if cmd == "--version":
1606
    raise _ShowVersion()
1607
  elif cmd == "--help":
1608
    raise _ShowUsage(exit_error=False)
1609
  elif not (cmd in commands or cmd in aliases):
1610
    raise _ShowUsage(exit_error=True)
1611

    
1612
  # get command, unalias it, and look it up in commands
1613
  if cmd in aliases:
1614
    if aliases[cmd] not in commands:
1615
      raise errors.ProgrammerError("Alias '%s' maps to non-existing"
1616
                                   " command '%s'" % (cmd, aliases[cmd]))
1617

    
1618
    cmd = aliases[cmd]
1619

    
1620
  if cmd in env_override:
1621
    args_env_name = ("%s_%s" % (binary.replace("-", "_"), cmd)).upper()
1622
    env_args = os.environ.get(args_env_name)
1623
    if env_args:
1624
      argv = utils.InsertAtPos(argv, 2, shlex.split(env_args))
1625

    
1626
  func, args_def, parser_opts, usage, description = commands[cmd]
1627
  parser = OptionParser(option_list=parser_opts + COMMON_OPTS,
1628
                        description=description,
1629
                        formatter=TitledHelpFormatter(),
1630
                        usage="%%prog %s %s" % (cmd, usage))
1631
  parser.disable_interspersed_args()
1632
  options, args = parser.parse_args(args=argv[2:])
1633

    
1634
  if not _CheckArguments(cmd, args_def, args):
1635
    return None, None, None
1636

    
1637
  return func, options, args
1638

    
1639

    
1640
def _FormatUsage(binary, commands):
1641
  """Generates a nice description of all commands.
1642

1643
  @param binary: Script name
1644
  @param commands: Dictionary containing command definitions
1645

1646
  """
1647
  # compute the max line length for cmd + usage
1648
  mlen = min(60, max(map(len, commands)))
1649

    
1650
  yield "Usage: %s {command} [options...] [argument...]" % binary
1651
  yield "%s <command> --help to see details, or man %s" % (binary, binary)
1652
  yield ""
1653
  yield "Commands:"
1654

    
1655
  # and format a nice command list
1656
  for (cmd, (_, _, _, _, help_text)) in sorted(commands.items()):
1657
    help_lines = textwrap.wrap(help_text, 79 - 3 - mlen)
1658
    yield " %-*s - %s" % (mlen, cmd, help_lines.pop(0))
1659
    for line in help_lines:
1660
      yield " %-*s   %s" % (mlen, "", line)
1661

    
1662
  yield ""
1663

    
1664

    
1665
def _CheckArguments(cmd, args_def, args):
1666
  """Verifies the arguments using the argument definition.
1667

1668
  Algorithm:
1669

1670
    1. Abort with error if values specified by user but none expected.
1671

1672
    1. For each argument in definition
1673

1674
      1. Keep running count of minimum number of values (min_count)
1675
      1. Keep running count of maximum number of values (max_count)
1676
      1. If it has an unlimited number of values
1677

1678
        1. Abort with error if it's not the last argument in the definition
1679

1680
    1. If last argument has limited number of values
1681

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

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

1686
  """
1687
  if args and not args_def:
1688
    ToStderr("Error: Command %s expects no arguments", cmd)
1689
    return False
1690

    
1691
  min_count = None
1692
  max_count = None
1693
  check_max = None
1694

    
1695
  last_idx = len(args_def) - 1
1696

    
1697
  for idx, arg in enumerate(args_def):
1698
    if min_count is None:
1699
      min_count = arg.min
1700
    elif arg.min is not None:
1701
      min_count += arg.min
1702

    
1703
    if max_count is None:
1704
      max_count = arg.max
1705
    elif arg.max is not None:
1706
      max_count += arg.max
1707

    
1708
    if idx == last_idx:
1709
      check_max = (arg.max is not None)
1710

    
1711
    elif arg.max is None:
1712
      raise errors.ProgrammerError("Only the last argument can have max=None")
1713

    
1714
  if check_max:
1715
    # Command with exact number of arguments
1716
    if (min_count is not None and max_count is not None and
1717
        min_count == max_count and len(args) != min_count):
1718
      ToStderr("Error: Command %s expects %d argument(s)", cmd, min_count)
1719
      return False
1720

    
1721
    # Command with limited number of arguments
1722
    if max_count is not None and len(args) > max_count:
1723
      ToStderr("Error: Command %s expects only %d argument(s)",
1724
               cmd, max_count)
1725
      return False
1726

    
1727
  # Command with some required arguments
1728
  if min_count is not None and len(args) < min_count:
1729
    ToStderr("Error: Command %s expects at least %d argument(s)",
1730
             cmd, min_count)
1731
    return False
1732

    
1733
  return True
1734

    
1735

    
1736
def SplitNodeOption(value):
1737
  """Splits the value of a --node option.
1738

1739
  """
1740
  if value and ":" in value:
1741
    return value.split(":", 1)
1742
  else:
1743
    return (value, None)
1744

    
1745

    
1746
def CalculateOSNames(os_name, os_variants):
1747
  """Calculates all the names an OS can be called, according to its variants.
1748

1749
  @type os_name: string
1750
  @param os_name: base name of the os
1751
  @type os_variants: list or None
1752
  @param os_variants: list of supported variants
1753
  @rtype: list
1754
  @return: list of valid names
1755

1756
  """
1757
  if os_variants:
1758
    return ["%s+%s" % (os_name, v) for v in os_variants]
1759
  else:
1760
    return [os_name]
1761

    
1762

    
1763
def ParseFields(selected, default):
1764
  """Parses the values of "--field"-like options.
1765

1766
  @type selected: string or None
1767
  @param selected: User-selected options
1768
  @type default: list
1769
  @param default: Default fields
1770

1771
  """
1772
  if selected is None:
1773
    return default
1774

    
1775
  if selected.startswith("+"):
1776
    return default + selected[1:].split(",")
1777

    
1778
  return selected.split(",")
1779

    
1780

    
1781
UsesRPC = rpc.RunWithRPC
1782

    
1783

    
1784
def AskUser(text, choices=None):
1785
  """Ask the user a question.
1786

1787
  @param text: the question to ask
1788

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

1794
  @return: one of the return values from the choices list; if input is
1795
      not possible (i.e. not running with a tty, we return the last
1796
      entry from the list
1797

1798
  """
1799
  if choices is None:
1800
    choices = [("y", True, "Perform the operation"),
1801
               ("n", False, "Do not perform the operation")]
1802
  if not choices or not isinstance(choices, list):
1803
    raise errors.ProgrammerError("Invalid choices argument to AskUser")
1804
  for entry in choices:
1805
    if not isinstance(entry, tuple) or len(entry) < 3 or entry[0] == "?":
1806
      raise errors.ProgrammerError("Invalid choices element to AskUser")
1807

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

    
1840

    
1841
class JobSubmittedException(Exception):
1842
  """Job was submitted, client should exit.
1843

1844
  This exception has one argument, the ID of the job that was
1845
  submitted. The handler should print this ID.
1846

1847
  This is not an error, just a structured way to exit from clients.
1848

1849
  """
1850

    
1851

    
1852
def SendJob(ops, cl=None):
1853
  """Function to submit an opcode without waiting for the results.
1854

1855
  @type ops: list
1856
  @param ops: list of opcodes
1857
  @type cl: luxi.Client
1858
  @param cl: the luxi client to use for communicating with the master;
1859
             if None, a new client will be created
1860

1861
  """
1862
  if cl is None:
1863
    cl = GetClient()
1864

    
1865
  job_id = cl.SubmitJob(ops)
1866

    
1867
  return job_id
1868

    
1869

    
1870
def GenericPollJob(job_id, cbs, report_cbs):
1871
  """Generic job-polling function.
1872

1873
  @type job_id: number
1874
  @param job_id: Job ID
1875
  @type cbs: Instance of L{JobPollCbBase}
1876
  @param cbs: Data callbacks
1877
  @type report_cbs: Instance of L{JobPollReportCbBase}
1878
  @param report_cbs: Reporting callbacks
1879

1880
  """
1881
  prev_job_info = None
1882
  prev_logmsg_serial = None
1883

    
1884
  status = None
1885

    
1886
  while True:
1887
    result = cbs.WaitForJobChangeOnce(job_id, ["status"], prev_job_info,
1888
                                      prev_logmsg_serial)
1889
    if not result:
1890
      # job not found, go away!
1891
      raise errors.JobLost("Job with id %s lost" % job_id)
1892

    
1893
    if result == constants.JOB_NOTCHANGED:
1894
      report_cbs.ReportNotChanged(job_id, status)
1895

    
1896
      # Wait again
1897
      continue
1898

    
1899
    # Split result, a tuple of (field values, log entries)
1900
    (job_info, log_entries) = result
1901
    (status, ) = job_info
1902

    
1903
    if log_entries:
1904
      for log_entry in log_entries:
1905
        (serial, timestamp, log_type, message) = log_entry
1906
        report_cbs.ReportLogMessage(job_id, serial, timestamp,
1907
                                    log_type, message)
1908
        prev_logmsg_serial = max(prev_logmsg_serial, serial)
1909

    
1910
    # TODO: Handle canceled and archived jobs
1911
    elif status in (constants.JOB_STATUS_SUCCESS,
1912
                    constants.JOB_STATUS_ERROR,
1913
                    constants.JOB_STATUS_CANCELING,
1914
                    constants.JOB_STATUS_CANCELED):
1915
      break
1916

    
1917
    prev_job_info = job_info
1918

    
1919
  jobs = cbs.QueryJobs([job_id], ["status", "opstatus", "opresult"])
1920
  if not jobs:
1921
    raise errors.JobLost("Job with id %s lost" % job_id)
1922

    
1923
  status, opstatus, result = jobs[0]
1924

    
1925
  if status == constants.JOB_STATUS_SUCCESS:
1926
    return result
1927

    
1928
  if status in (constants.JOB_STATUS_CANCELING, constants.JOB_STATUS_CANCELED):
1929
    raise errors.OpExecError("Job was canceled")
1930

    
1931
  has_ok = False
1932
  for idx, (status, msg) in enumerate(zip(opstatus, result)):
1933
    if status == constants.OP_STATUS_SUCCESS:
1934
      has_ok = True
1935
    elif status == constants.OP_STATUS_ERROR:
1936
      errors.MaybeRaise(msg)
1937

    
1938
      if has_ok:
1939
        raise errors.OpExecError("partial failure (opcode %d): %s" %
1940
                                 (idx, msg))
1941

    
1942
      raise errors.OpExecError(str(msg))
1943

    
1944
  # default failure mode
1945
  raise errors.OpExecError(result)
1946

    
1947

    
1948
class JobPollCbBase:
1949
  """Base class for L{GenericPollJob} callbacks.
1950

1951
  """
1952
  def __init__(self):
1953
    """Initializes this class.
1954

1955
    """
1956

    
1957
  def WaitForJobChangeOnce(self, job_id, fields,
1958
                           prev_job_info, prev_log_serial):
1959
    """Waits for changes on a job.
1960

1961
    """
1962
    raise NotImplementedError()
1963

    
1964
  def QueryJobs(self, job_ids, fields):
1965
    """Returns the selected fields for the selected job IDs.
1966

1967
    @type job_ids: list of numbers
1968
    @param job_ids: Job IDs
1969
    @type fields: list of strings
1970
    @param fields: Fields
1971

1972
    """
1973
    raise NotImplementedError()
1974

    
1975

    
1976
class JobPollReportCbBase:
1977
  """Base class for L{GenericPollJob} reporting callbacks.
1978

1979
  """
1980
  def __init__(self):
1981
    """Initializes this class.
1982

1983
    """
1984

    
1985
  def ReportLogMessage(self, job_id, serial, timestamp, log_type, log_msg):
1986
    """Handles a log message.
1987

1988
    """
1989
    raise NotImplementedError()
1990

    
1991
  def ReportNotChanged(self, job_id, status):
1992
    """Called for if a job hasn't changed in a while.
1993

1994
    @type job_id: number
1995
    @param job_id: Job ID
1996
    @type status: string or None
1997
    @param status: Job status if available
1998

1999
    """
2000
    raise NotImplementedError()
2001

    
2002

    
2003
class _LuxiJobPollCb(JobPollCbBase):
2004
  def __init__(self, cl):
2005
    """Initializes this class.
2006

2007
    """
2008
    JobPollCbBase.__init__(self)
2009
    self.cl = cl
2010

    
2011
  def WaitForJobChangeOnce(self, job_id, fields,
2012
                           prev_job_info, prev_log_serial):
2013
    """Waits for changes on a job.
2014

2015
    """
2016
    return self.cl.WaitForJobChangeOnce(job_id, fields,
2017
                                        prev_job_info, prev_log_serial)
2018

    
2019
  def QueryJobs(self, job_ids, fields):
2020
    """Returns the selected fields for the selected job IDs.
2021

2022
    """
2023
    return self.cl.QueryJobs(job_ids, fields)
2024

    
2025

    
2026
class FeedbackFnJobPollReportCb(JobPollReportCbBase):
2027
  def __init__(self, feedback_fn):
2028
    """Initializes this class.
2029

2030
    """
2031
    JobPollReportCbBase.__init__(self)
2032

    
2033
    self.feedback_fn = feedback_fn
2034

    
2035
    assert callable(feedback_fn)
2036

    
2037
  def ReportLogMessage(self, job_id, serial, timestamp, log_type, log_msg):
2038
    """Handles a log message.
2039

2040
    """
2041
    self.feedback_fn((timestamp, log_type, log_msg))
2042

    
2043
  def ReportNotChanged(self, job_id, status):
2044
    """Called if a job hasn't changed in a while.
2045

2046
    """
2047
    # Ignore
2048

    
2049

    
2050
class StdioJobPollReportCb(JobPollReportCbBase):
2051
  def __init__(self):
2052
    """Initializes this class.
2053

2054
    """
2055
    JobPollReportCbBase.__init__(self)
2056

    
2057
    self.notified_queued = False
2058
    self.notified_waitlock = False
2059

    
2060
  def ReportLogMessage(self, job_id, serial, timestamp, log_type, log_msg):
2061
    """Handles a log message.
2062

2063
    """
2064
    ToStdout("%s %s", time.ctime(utils.MergeTime(timestamp)),
2065
             FormatLogMessage(log_type, log_msg))
2066

    
2067
  def ReportNotChanged(self, job_id, status):
2068
    """Called if a job hasn't changed in a while.
2069

2070
    """
2071
    if status is None:
2072
      return
2073

    
2074
    if status == constants.JOB_STATUS_QUEUED and not self.notified_queued:
2075
      ToStderr("Job %s is waiting in queue", job_id)
2076
      self.notified_queued = True
2077

    
2078
    elif status == constants.JOB_STATUS_WAITING and not self.notified_waitlock:
2079
      ToStderr("Job %s is trying to acquire all necessary locks", job_id)
2080
      self.notified_waitlock = True
2081

    
2082

    
2083
def FormatLogMessage(log_type, log_msg):
2084
  """Formats a job message according to its type.
2085

2086
  """
2087
  if log_type != constants.ELOG_MESSAGE:
2088
    log_msg = str(log_msg)
2089

    
2090
  return utils.SafeEncode(log_msg)
2091

    
2092

    
2093
def PollJob(job_id, cl=None, feedback_fn=None, reporter=None):
2094
  """Function to poll for the result of a job.
2095

2096
  @type job_id: job identified
2097
  @param job_id: the job to poll for results
2098
  @type cl: luxi.Client
2099
  @param cl: the luxi client to use for communicating with the master;
2100
             if None, a new client will be created
2101

2102
  """
2103
  if cl is None:
2104
    cl = GetClient()
2105

    
2106
  if reporter is None:
2107
    if feedback_fn:
2108
      reporter = FeedbackFnJobPollReportCb(feedback_fn)
2109
    else:
2110
      reporter = StdioJobPollReportCb()
2111
  elif feedback_fn:
2112
    raise errors.ProgrammerError("Can't specify reporter and feedback function")
2113

    
2114
  return GenericPollJob(job_id, _LuxiJobPollCb(cl), reporter)
2115

    
2116

    
2117
def SubmitOpCode(op, cl=None, feedback_fn=None, opts=None, reporter=None):
2118
  """Legacy function to submit an opcode.
2119

2120
  This is just a simple wrapper over the construction of the processor
2121
  instance. It should be extended to better handle feedback and
2122
  interaction functions.
2123

2124
  """
2125
  if cl is None:
2126
    cl = GetClient()
2127

    
2128
  SetGenericOpcodeOpts([op], opts)
2129

    
2130
  job_id = SendJob([op], cl=cl)
2131

    
2132
  op_results = PollJob(job_id, cl=cl, feedback_fn=feedback_fn,
2133
                       reporter=reporter)
2134

    
2135
  return op_results[0]
2136

    
2137

    
2138
def SubmitOrSend(op, opts, cl=None, feedback_fn=None):
2139
  """Wrapper around SubmitOpCode or SendJob.
2140

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

2146
  It will also process the opcodes if we're sending the via SendJob
2147
  (otherwise SubmitOpCode does it).
2148

2149
  """
2150
  if opts and opts.submit_only:
2151
    job = [op]
2152
    SetGenericOpcodeOpts(job, opts)
2153
    job_id = SendJob(job, cl=cl)
2154
    raise JobSubmittedException(job_id)
2155
  else:
2156
    return SubmitOpCode(op, cl=cl, feedback_fn=feedback_fn, opts=opts)
2157

    
2158

    
2159
def SetGenericOpcodeOpts(opcode_list, options):
2160
  """Processor for generic options.
2161

2162
  This function updates the given opcodes based on generic command
2163
  line options (like debug, dry-run, etc.).
2164

2165
  @param opcode_list: list of opcodes
2166
  @param options: command line options or None
2167
  @return: None (in-place modification)
2168

2169
  """
2170
  if not options:
2171
    return
2172
  for op in opcode_list:
2173
    op.debug_level = options.debug
2174
    if hasattr(options, "dry_run"):
2175
      op.dry_run = options.dry_run
2176
    if getattr(options, "priority", None) is not None:
2177
      op.priority = options.priority
2178

    
2179

    
2180
def GetClient(query=False):
2181
  """Connects to the a luxi socket and returns a client.
2182

2183
  @type query: boolean
2184
  @param query: this signifies that the client will only be
2185
      used for queries; if the build-time parameter
2186
      enable-split-queries is enabled, then the client will be
2187
      connected to the query socket instead of the masterd socket
2188

2189
  """
2190
  if query and constants.ENABLE_SPLIT_QUERY:
2191
    address = pathutils.QUERY_SOCKET
2192
  else:
2193
    address = None
2194
  # TODO: Cache object?
2195
  try:
2196
    client = luxi.Client(address=address)
2197
  except luxi.NoMasterError:
2198
    ss = ssconf.SimpleStore()
2199

    
2200
    # Try to read ssconf file
2201
    try:
2202
      ss.GetMasterNode()
2203
    except errors.ConfigurationError:
2204
      raise errors.OpPrereqError("Cluster not initialized or this machine is"
2205
                                 " not part of a cluster",
2206
                                 errors.ECODE_INVAL)
2207

    
2208
    master, myself = ssconf.GetMasterAndMyself(ss=ss)
2209
    if master != myself:
2210
      raise errors.OpPrereqError("This is not the master node, please connect"
2211
                                 " to node '%s' and rerun the command" %
2212
                                 master, errors.ECODE_INVAL)
2213
    raise
2214
  return client
2215

    
2216

    
2217
def FormatError(err):
2218
  """Return a formatted error message for a given error.
2219

2220
  This function takes an exception instance and returns a tuple
2221
  consisting of two values: first, the recommended exit code, and
2222
  second, a string describing the error message (not
2223
  newline-terminated).
2224

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

    
2304

    
2305
def GenericMain(commands, override=None, aliases=None,
2306
                env_override=frozenset()):
2307
  """Generic main function for all the gnt-* commands.
2308

2309
  @param commands: a dictionary with a special structure, see the design doc
2310
                   for command line handling.
2311
  @param override: if not None, we expect a dictionary with keys that will
2312
                   override command line options; this can be used to pass
2313
                   options from the scripts to generic functions
2314
  @param aliases: dictionary with command aliases {'alias': 'target, ...}
2315
  @param env_override: list of environment names which are allowed to submit
2316
                       default args for commands
2317

2318
  """
2319
  # save the program name and the entire command line for later logging
2320
  if sys.argv:
2321
    binary = os.path.basename(sys.argv[0])
2322
    if not binary:
2323
      binary = sys.argv[0]
2324

    
2325
    if len(sys.argv) >= 2:
2326
      logname = utils.ShellQuoteArgs([binary, sys.argv[1]])
2327
    else:
2328
      logname = binary
2329

    
2330
    cmdline = utils.ShellQuoteArgs([binary] + sys.argv[1:])
2331
  else:
2332
    binary = "<unknown program>"
2333
    cmdline = "<unknown>"
2334

    
2335
  if aliases is None:
2336
    aliases = {}
2337

    
2338
  try:
2339
    (func, options, args) = _ParseArgs(binary, sys.argv, commands, aliases,
2340
                                       env_override)
2341
  except _ShowVersion:
2342
    ToStdout("%s (ganeti %s) %s", binary, constants.VCS_VERSION,
2343
             constants.RELEASE_VERSION)
2344
    return constants.EXIT_SUCCESS
2345
  except _ShowUsage, err:
2346
    for line in _FormatUsage(binary, commands):
2347
      ToStdout(line)
2348

    
2349
    if err.exit_error:
2350
      return constants.EXIT_FAILURE
2351
    else:
2352
      return constants.EXIT_SUCCESS
2353
  except errors.ParameterError, err:
2354
    result, err_msg = FormatError(err)
2355
    ToStderr(err_msg)
2356
    return 1
2357

    
2358
  if func is None: # parse error
2359
    return 1
2360

    
2361
  if override is not None:
2362
    for key, val in override.iteritems():
2363
      setattr(options, key, val)
2364

    
2365
  utils.SetupLogging(pathutils.LOG_COMMANDS, logname, debug=options.debug,
2366
                     stderr_logging=True)
2367

    
2368
  logging.info("Command line: %s", cmdline)
2369

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

    
2389
  return result
2390

    
2391

    
2392
def ParseNicOption(optvalue):
2393
  """Parses the value of the --net option(s).
2394

2395
  """
2396
  try:
2397
    nic_max = max(int(nidx[0]) + 1 for nidx in optvalue)
2398
  except (TypeError, ValueError), err:
2399
    raise errors.OpPrereqError("Invalid NIC index passed: %s" % str(err),
2400
                               errors.ECODE_INVAL)
2401

    
2402
  nics = [{}] * nic_max
2403
  for nidx, ndict in optvalue:
2404
    nidx = int(nidx)
2405

    
2406
    if not isinstance(ndict, dict):
2407
      raise errors.OpPrereqError("Invalid nic/%d value: expected dict,"
2408
                                 " got %s" % (nidx, ndict), errors.ECODE_INVAL)
2409

    
2410
    utils.ForceDictType(ndict, constants.INIC_PARAMS_TYPES)
2411

    
2412
    nics[nidx] = ndict
2413

    
2414
  return nics
2415

    
2416

    
2417
def GenericInstanceCreate(mode, opts, args):
2418
  """Add an instance to the cluster via either creation or import.
2419

2420
  @param mode: constants.INSTANCE_CREATE or constants.INSTANCE_IMPORT
2421
  @param opts: the command line options selected by the user
2422
  @type args: list
2423
  @param args: should contain only one element, the new instance name
2424
  @rtype: int
2425
  @return: the desired exit code
2426

2427
  """
2428
  instance = args[0]
2429

    
2430
  (pnode, snode) = SplitNodeOption(opts.node)
2431

    
2432
  hypervisor = None
2433
  hvparams = {}
2434
  if opts.hypervisor:
2435
    hypervisor, hvparams = opts.hypervisor
2436

    
2437
  if opts.nics:
2438
    nics = ParseNicOption(opts.nics)
2439
  elif opts.no_nics:
2440
    # no nics
2441
    nics = []
2442
  elif mode == constants.INSTANCE_CREATE:
2443
    # default of one nic, all auto
2444
    nics = [{}]
2445
  else:
2446
    # mode == import
2447
    nics = []
2448

    
2449
  if opts.disk_template == constants.DT_DISKLESS:
2450
    if opts.disks or opts.sd_size is not None:
2451
      raise errors.OpPrereqError("Diskless instance but disk"
2452
                                 " information passed", errors.ECODE_INVAL)
2453
    disks = []
2454
  else:
2455
    if (not opts.disks and not opts.sd_size
2456
        and mode == constants.INSTANCE_CREATE):
2457
      raise errors.OpPrereqError("No disk information specified",
2458
                                 errors.ECODE_INVAL)
2459
    if opts.disks and opts.sd_size is not None:
2460
      raise errors.OpPrereqError("Please use either the '--disk' or"
2461
                                 " '-s' option", errors.ECODE_INVAL)
2462
    if opts.sd_size is not None:
2463
      opts.disks = [(0, {constants.IDISK_SIZE: opts.sd_size})]
2464

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

    
2499
  if opts.tags is not None:
2500
    tags = opts.tags.split(",")
2501
  else:
2502
    tags = []
2503

    
2504
  utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_COMPAT)
2505
  utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
2506

    
2507
  if mode == constants.INSTANCE_CREATE:
2508
    start = opts.start
2509
    os_type = opts.os
2510
    force_variant = opts.force_variant
2511
    src_node = None
2512
    src_path = None
2513
    no_install = opts.no_install
2514
    identify_defaults = False
2515
  elif mode == constants.INSTANCE_IMPORT:
2516
    start = False
2517
    os_type = None
2518
    force_variant = False
2519
    src_node = opts.src_node
2520
    src_path = opts.src_dir
2521
    no_install = None
2522
    identify_defaults = opts.identify_defaults
2523
  else:
2524
    raise errors.ProgrammerError("Invalid creation mode %s" % mode)
2525

    
2526
  op = opcodes.OpInstanceCreate(instance_name=instance,
2527
                                disks=disks,
2528
                                disk_template=opts.disk_template,
2529
                                nics=nics,
2530
                                conflicts_check=opts.conflicts_check,
2531
                                pnode=pnode, snode=snode,
2532
                                ip_check=opts.ip_check,
2533
                                name_check=opts.name_check,
2534
                                wait_for_sync=opts.wait_for_sync,
2535
                                file_storage_dir=opts.file_storage_dir,
2536
                                file_driver=opts.file_driver,
2537
                                iallocator=opts.iallocator,
2538
                                hypervisor=hypervisor,
2539
                                hvparams=hvparams,
2540
                                beparams=opts.beparams,
2541
                                osparams=opts.osparams,
2542
                                mode=mode,
2543
                                start=start,
2544
                                os_type=os_type,
2545
                                force_variant=force_variant,
2546
                                src_node=src_node,
2547
                                src_path=src_path,
2548
                                tags=tags,
2549
                                no_install=no_install,
2550
                                identify_defaults=identify_defaults,
2551
                                ignore_ipolicy=opts.ignore_ipolicy)
2552

    
2553
  SubmitOrSend(op, opts)
2554
  return 0
2555

    
2556

    
2557
class _RunWhileClusterStoppedHelper:
2558
  """Helper class for L{RunWhileClusterStopped} to simplify state management
2559

2560
  """
2561
  def __init__(self, feedback_fn, cluster_name, master_node, online_nodes):
2562
    """Initializes this class.
2563

2564
    @type feedback_fn: callable
2565
    @param feedback_fn: Feedback function
2566
    @type cluster_name: string
2567
    @param cluster_name: Cluster name
2568
    @type master_node: string
2569
    @param master_node Master node name
2570
    @type online_nodes: list
2571
    @param online_nodes: List of names of online nodes
2572

2573
    """
2574
    self.feedback_fn = feedback_fn
2575
    self.cluster_name = cluster_name
2576
    self.master_node = master_node
2577
    self.online_nodes = online_nodes
2578

    
2579
    self.ssh = ssh.SshRunner(self.cluster_name)
2580

    
2581
    self.nonmaster_nodes = [name for name in online_nodes
2582
                            if name != master_node]
2583

    
2584
    assert self.master_node not in self.nonmaster_nodes
2585

    
2586
  def _RunCmd(self, node_name, cmd):
2587
    """Runs a command on the local or a remote machine.
2588

2589
    @type node_name: string
2590
    @param node_name: Machine name
2591
    @type cmd: list
2592
    @param cmd: Command
2593

2594
    """
2595
    if node_name is None or node_name == self.master_node:
2596
      # No need to use SSH
2597
      result = utils.RunCmd(cmd)
2598
    else:
2599
      result = self.ssh.Run(node_name, constants.SSH_LOGIN_USER,
2600
                            utils.ShellQuoteArgs(cmd))
2601

    
2602
    if result.failed:
2603
      errmsg = ["Failed to run command %s" % result.cmd]
2604
      if node_name:
2605
        errmsg.append("on node %s" % node_name)
2606
      errmsg.append(": exitcode %s and error %s" %
2607
                    (result.exit_code, result.output))
2608
      raise errors.OpExecError(" ".join(errmsg))
2609

    
2610
  def Call(self, fn, *args):
2611
    """Call function while all daemons are stopped.
2612

2613
    @type fn: callable
2614
    @param fn: Function to be called
2615

2616
    """
2617
    # Pause watcher by acquiring an exclusive lock on watcher state file
2618
    self.feedback_fn("Blocking watcher")
2619
    watcher_block = utils.FileLock.Open(pathutils.WATCHER_LOCK_FILE)
2620
    try:
2621
      # TODO: Currently, this just blocks. There's no timeout.
2622
      # TODO: Should it be a shared lock?
2623
      watcher_block.Exclusive(blocking=True)
2624

    
2625
      # Stop master daemons, so that no new jobs can come in and all running
2626
      # ones are finished
2627
      self.feedback_fn("Stopping master daemons")
2628
      self._RunCmd(None, [pathutils.DAEMON_UTIL, "stop-master"])
2629
      try:
2630
        # Stop daemons on all nodes
2631
        for node_name in self.online_nodes:
2632
          self.feedback_fn("Stopping daemons on %s" % node_name)
2633
          self._RunCmd(node_name, [pathutils.DAEMON_UTIL, "stop-all"])
2634

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

    
2652

    
2653
def RunWhileClusterStopped(feedback_fn, fn, *args):
2654
  """Calls a function while all cluster daemons are stopped.
2655

2656
  @type feedback_fn: callable
2657
  @param feedback_fn: Feedback function
2658
  @type fn: callable
2659
  @param fn: Function to be called when daemons are stopped
2660

2661
  """
2662
  feedback_fn("Gathering cluster information")
2663

    
2664
  # This ensures we're running on the master daemon
2665
  cl = GetClient()
2666

    
2667
  (cluster_name, master_node) = \
2668
    cl.QueryConfigValues(["cluster_name", "master_node"])
2669

    
2670
  online_nodes = GetOnlineNodes([], cl=cl)
2671

    
2672
  # Don't keep a reference to the client. The master daemon will go away.
2673
  del cl
2674

    
2675
  assert master_node in online_nodes
2676

    
2677
  return _RunWhileClusterStoppedHelper(feedback_fn, cluster_name, master_node,
2678
                                       online_nodes).Call(fn, *args)
2679

    
2680

    
2681
def GenerateTable(headers, fields, separator, data,
2682
                  numfields=None, unitfields=None,
2683
                  units=None):
2684
  """Prints a table with headers and different fields.
2685

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

2709
  """
2710
  if units is None:
2711
    if separator:
2712
      units = "m"
2713
    else:
2714
      units = "h"
2715

    
2716
  if numfields is None:
2717
    numfields = []
2718
  if unitfields is None:
2719
    unitfields = []
2720

    
2721
  numfields = utils.FieldSet(*numfields)   # pylint: disable=W0142
2722
  unitfields = utils.FieldSet(*unitfields) # pylint: disable=W0142
2723

    
2724
  format_fields = []
2725
  for field in fields:
2726
    if headers and field not in headers:
2727
      # TODO: handle better unknown fields (either revert to old
2728
      # style of raising exception, or deal more intelligently with
2729
      # variable fields)
2730
      headers[field] = field
2731
    if separator is not None:
2732
      format_fields.append("%s")
2733
    elif numfields.Matches(field):
2734
      format_fields.append("%*s")
2735
    else:
2736
      format_fields.append("%-*s")
2737

    
2738
  if separator is None:
2739
    mlens = [0 for name in fields]
2740
    format_str = " ".join(format_fields)
2741
  else:
2742
    format_str = separator.replace("%", "%%").join(format_fields)
2743

    
2744
  for row in data:
2745
    if row is None:
2746
      continue
2747
    for idx, val in enumerate(row):
2748
      if unitfields.Matches(fields[idx]):
2749
        try:
2750
          val = int(val)
2751
        except (TypeError, ValueError):
2752
          pass
2753
        else:
2754
          val = row[idx] = utils.FormatUnit(val, units)
2755
      val = row[idx] = str(val)
2756
      if separator is None:
2757
        mlens[idx] = max(mlens[idx], len(val))
2758

    
2759
  result = []
2760
  if headers:
2761
    args = []
2762
    for idx, name in enumerate(fields):
2763
      hdr = headers[name]
2764
      if separator is None:
2765
        mlens[idx] = max(mlens[idx], len(hdr))
2766
        args.append(mlens[idx])
2767
      args.append(hdr)
2768
    result.append(format_str % tuple(args))
2769

    
2770
  if separator is None:
2771
    assert len(mlens) == len(fields)
2772

    
2773
    if fields and not numfields.Matches(fields[-1]):
2774
      mlens[-1] = 0
2775

    
2776
  for line in data:
2777
    args = []
2778
    if line is None:
2779
      line = ["-" for _ in fields]
2780
    for idx in range(len(fields)):
2781
      if separator is None:
2782
        args.append(mlens[idx])
2783
      args.append(line[idx])
2784
    result.append(format_str % tuple(args))
2785

    
2786
  return result
2787

    
2788

    
2789
def _FormatBool(value):
2790
  """Formats a boolean value as a string.
2791

2792
  """
2793
  if value:
2794
    return "Y"
2795
  return "N"
2796

    
2797

    
2798
#: Default formatting for query results; (callback, align right)
2799
_DEFAULT_FORMAT_QUERY = {
2800
  constants.QFT_TEXT: (str, False),
2801
  constants.QFT_BOOL: (_FormatBool, False),
2802
  constants.QFT_NUMBER: (str, True),
2803
  constants.QFT_TIMESTAMP: (utils.FormatTime, False),
2804
  constants.QFT_OTHER: (str, False),
2805
  constants.QFT_UNKNOWN: (str, False),
2806
  }
2807

    
2808

    
2809
def _GetColumnFormatter(fdef, override, unit):
2810
  """Returns formatting function for a field.
2811

2812
  @type fdef: L{objects.QueryFieldDefinition}
2813
  @type override: dict
2814
  @param override: Dictionary for overriding field formatting functions,
2815
    indexed by field name, contents like L{_DEFAULT_FORMAT_QUERY}
2816
  @type unit: string
2817
  @param unit: Unit used for formatting fields of type L{constants.QFT_UNIT}
2818
  @rtype: tuple; (callable, bool)
2819
  @return: Returns the function to format a value (takes one parameter) and a
2820
    boolean for aligning the value on the right-hand side
2821

2822
  """
2823
  fmt = override.get(fdef.name, None)
2824
  if fmt is not None:
2825
    return fmt
2826

    
2827
  assert constants.QFT_UNIT not in _DEFAULT_FORMAT_QUERY
2828

    
2829
  if fdef.kind == constants.QFT_UNIT:
2830
    # Can't keep this information in the static dictionary
2831
    return (lambda value: utils.FormatUnit(value, unit), True)
2832

    
2833
  fmt = _DEFAULT_FORMAT_QUERY.get(fdef.kind, None)
2834
  if fmt is not None:
2835
    return fmt
2836

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

    
2839

    
2840
class _QueryColumnFormatter:
2841
  """Callable class for formatting fields of a query.
2842

2843
  """
2844
  def __init__(self, fn, status_fn, verbose):
2845
    """Initializes this class.
2846

2847
    @type fn: callable
2848
    @param fn: Formatting function
2849
    @type status_fn: callable
2850
    @param status_fn: Function to report fields' status
2851
    @type verbose: boolean
2852
    @param verbose: whether to use verbose field descriptions or not
2853

2854
    """
2855
    self._fn = fn
2856
    self._status_fn = status_fn
2857
    self._verbose = verbose
2858

    
2859
  def __call__(self, data):
2860
    """Returns a field's string representation.
2861

2862
    """
2863
    (status, value) = data
2864

    
2865
    # Report status
2866
    self._status_fn(status)
2867

    
2868
    if status == constants.RS_NORMAL:
2869
      return self._fn(value)
2870

    
2871
    assert value is None, \
2872
           "Found value %r for abnormal status %s" % (value, status)
2873

    
2874
    return FormatResultError(status, self._verbose)
2875

    
2876

    
2877
def FormatResultError(status, verbose):
2878
  """Formats result status other than L{constants.RS_NORMAL}.
2879

2880
  @param status: The result status
2881
  @type verbose: boolean
2882
  @param verbose: Whether to return the verbose text
2883
  @return: Text of result status
2884

2885
  """
2886
  assert status != constants.RS_NORMAL, \
2887
         "FormatResultError called with status equal to constants.RS_NORMAL"
2888
  try:
2889
    (verbose_text, normal_text) = constants.RSS_DESCRIPTION[status]
2890
  except KeyError:
2891
    raise NotImplementedError("Unknown status %s" % status)
2892
  else:
2893
    if verbose:
2894
      return verbose_text
2895
    return normal_text
2896

    
2897

    
2898
def FormatQueryResult(result, unit=None, format_override=None, separator=None,
2899
                      header=False, verbose=False):
2900
  """Formats data in L{objects.QueryResponse}.
2901

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

2917
  """
2918
  if unit is None:
2919
    if separator:
2920
      unit = "m"
2921
    else:
2922
      unit = "h"
2923

    
2924
  if format_override is None:
2925
    format_override = {}
2926

    
2927
  stats = dict.fromkeys(constants.RS_ALL, 0)
2928

    
2929
  def _RecordStatus(status):
2930
    if status in stats:
2931
      stats[status] += 1
2932

    
2933
  columns = []
2934
  for fdef in result.fields:
2935
    assert fdef.title and fdef.name
2936
    (fn, align_right) = _GetColumnFormatter(fdef, format_override, unit)
2937
    columns.append(TableColumn(fdef.title,
2938
                               _QueryColumnFormatter(fn, _RecordStatus,
2939
                                                     verbose),
2940
                               align_right))
2941

    
2942
  table = FormatTable(result.data, columns, header, separator)
2943

    
2944
  # Collect statistics
2945
  assert len(stats) == len(constants.RS_ALL)
2946
  assert compat.all(count >= 0 for count in stats.values())
2947

    
2948
  # Determine overall status. If there was no data, unknown fields must be
2949
  # detected via the field definitions.
2950
  if (stats[constants.RS_UNKNOWN] or
2951
      (not result.data and _GetUnknownFields(result.fields))):
2952
    status = QR_UNKNOWN
2953
  elif compat.any(count > 0 for key, count in stats.items()
2954
                  if key != constants.RS_NORMAL):
2955
    status = QR_INCOMPLETE
2956
  else:
2957
    status = QR_NORMAL
2958

    
2959
  return (status, table)
2960

    
2961

    
2962
def _GetUnknownFields(fdefs):
2963
  """Returns list of unknown fields included in C{fdefs}.
2964

2965
  @type fdefs: list of L{objects.QueryFieldDefinition}
2966

2967
  """
2968
  return [fdef for fdef in fdefs
2969
          if fdef.kind == constants.QFT_UNKNOWN]
2970

    
2971

    
2972
def _WarnUnknownFields(fdefs):
2973
  """Prints a warning to stderr if a query included unknown fields.
2974

2975
  @type fdefs: list of L{objects.QueryFieldDefinition}
2976

2977
  """
2978
  unknown = _GetUnknownFields(fdefs)
2979
  if unknown:
2980
    ToStderr("Warning: Queried for unknown fields %s",
2981
             utils.CommaJoin(fdef.name for fdef in unknown))
2982
    return True
2983

    
2984
  return False
2985

    
2986

    
2987
def GenericList(resource, fields, names, unit, separator, header, cl=None,
2988
                format_override=None, verbose=False, force_filter=False,
2989
                namefield=None, qfilter=None, isnumeric=False):
2990
  """Generic implementation for listing all items of a resource.
2991

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

3022
  """
3023
  if not names:
3024
    names = None
3025

    
3026
  namefilter = qlang.MakeFilter(names, force_filter, namefield=namefield,
3027
                                isnumeric=isnumeric)
3028

    
3029
  if qfilter is None:
3030
    qfilter = namefilter
3031
  elif namefilter is not None:
3032
    qfilter = [qlang.OP_AND, namefilter, qfilter]
3033

    
3034
  if cl is None:
3035
    cl = GetClient()
3036

    
3037
  response = cl.Query(resource, fields, qfilter)
3038

    
3039
  found_unknown = _WarnUnknownFields(response.fields)
3040

    
3041
  (status, data) = FormatQueryResult(response, unit=unit, separator=separator,
3042
                                     header=header,
3043
                                     format_override=format_override,
3044
                                     verbose=verbose)
3045

    
3046
  for line in data:
3047
    ToStdout(line)
3048

    
3049
  assert ((found_unknown and status == QR_UNKNOWN) or
3050
          (not found_unknown and status != QR_UNKNOWN))
3051

    
3052
  if status == QR_UNKNOWN:
3053
    return constants.EXIT_UNKNOWN_FIELD
3054

    
3055
  # TODO: Should the list command fail if not all data could be collected?
3056
  return constants.EXIT_SUCCESS
3057

    
3058

    
3059
def GenericListFields(resource, fields, separator, header, cl=None):
3060
  """Generic implementation for listing fields for a resource.
3061

3062
  @param resource: One of L{constants.QR_VIA_LUXI}
3063
  @type fields: list of strings
3064
  @param fields: List of fields to query for
3065
  @type separator: string or None
3066
  @param separator: String used to separate fields
3067
  @type header: bool
3068
  @param header: Whether to show header row
3069

3070
  """
3071
  if cl is None:
3072
    cl = GetClient()
3073

    
3074
  if not fields:
3075
    fields = None
3076

    
3077
  response = cl.QueryFields(resource, fields)
3078

    
3079
  found_unknown = _WarnUnknownFields(response.fields)
3080

    
3081
  columns = [
3082
    TableColumn("Name", str, False),
3083
    TableColumn("Title", str, False),
3084
    TableColumn("Description", str, False),
3085
    ]
3086

    
3087
  rows = [[fdef.name, fdef.title, fdef.doc] for fdef in response.fields]
3088

    
3089
  for line in FormatTable(rows, columns, header, separator):
3090
    ToStdout(line)
3091

    
3092
  if found_unknown:
3093
    return constants.EXIT_UNKNOWN_FIELD
3094

    
3095
  return constants.EXIT_SUCCESS
3096

    
3097

    
3098
class TableColumn:
3099
  """Describes a column for L{FormatTable}.
3100

3101
  """
3102
  def __init__(self, title, fn, align_right):
3103
    """Initializes this class.
3104

3105
    @type title: string
3106
    @param title: Column title
3107
    @type fn: callable
3108
    @param fn: Formatting function
3109
    @type align_right: bool
3110
    @param align_right: Whether to align values on the right-hand side
3111

3112
    """
3113
    self.title = title
3114
    self.format = fn
3115
    self.align_right = align_right
3116

    
3117

    
3118
def _GetColFormatString(width, align_right):
3119
  """Returns the format string for a field.
3120

3121
  """
3122
  if align_right:
3123
    sign = ""
3124
  else:
3125
    sign = "-"
3126

    
3127
  return "%%%s%ss" % (sign, width)
3128

    
3129

    
3130
def FormatTable(rows, columns, header, separator):
3131
  """Formats data as a table.
3132

3133
  @type rows: list of lists
3134
  @param rows: Row data, one list per row
3135
  @type columns: list of L{TableColumn}
3136
  @param columns: Column descriptions
3137
  @type header: bool
3138
  @param header: Whether to show header row
3139
  @type separator: string or None
3140
  @param separator: String used to separate columns
3141

3142
  """
3143
  if header:
3144
    data = [[col.title for col in columns]]
3145
    colwidth = [len(col.title) for col in columns]
3146
  else:
3147
    data = []
3148
    colwidth = [0 for _ in columns]
3149

    
3150
  # Format row data
3151
  for row in rows:
3152
    assert len(row) == len(columns)
3153

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

    
3156
    if separator is None:
3157
      # Update column widths
3158
      for idx, (oldwidth, value) in enumerate(zip(colwidth, formatted)):
3159
        # Modifying a list's items while iterating is fine
3160
        colwidth[idx] = max(oldwidth, len(value))
3161

    
3162
    data.append(formatted)
3163

    
3164
  if separator is not None:
3165
    # Return early if a separator is used
3166
    return [separator.join(row) for row in data]
3167

    
3168
  if columns and not columns[-1].align_right:
3169
    # Avoid unnecessary spaces at end of line
3170
    colwidth[-1] = 0
3171

    
3172
  # Build format string
3173
  fmt = " ".join([_GetColFormatString(width, col.align_right)
3174
                  for col, width in zip(columns, colwidth)])
3175

    
3176
  return [fmt % tuple(row) for row in data]
3177

    
3178

    
3179
def FormatTimestamp(ts):
3180
  """Formats a given timestamp.
3181

3182
  @type ts: timestamp
3183
  @param ts: a timeval-type timestamp, a tuple of seconds and microseconds
3184

3185
  @rtype: string
3186
  @return: a string with the formatted timestamp
3187

3188
  """
3189
  if not isinstance(ts, (tuple, list)) or len(ts) != 2:
3190
    return "?"
3191

    
3192
  (sec, usecs) = ts
3193
  return utils.FormatTime(sec, usecs=usecs)
3194

    
3195

    
3196
def ParseTimespec(value):
3197
  """Parse a time specification.
3198

3199
  The following suffixed will be recognized:
3200

3201
    - s: seconds
3202
    - m: minutes
3203
    - h: hours
3204
    - d: day
3205
    - w: weeks
3206

3207
  Without any suffix, the value will be taken to be in seconds.
3208

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

    
3240

    
3241
def GetOnlineNodes(nodes, cl=None, nowarn=False, secondary_ips=False,
3242
                   filter_master=False, nodegroup=None):
3243
  """Returns the names of online nodes.
3244

3245
  This function will also log a warning on stderr with the names of
3246
  the online nodes.
3247

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

3266
  """
3267
  if cl is None:
3268
    cl = GetClient()
3269

    
3270
  qfilter = []
3271

    
3272
  if nodes:
3273
    qfilter.append(qlang.MakeSimpleFilter("name", nodes))
3274

    
3275
  if nodegroup is not None:
3276
    qfilter.append([qlang.OP_OR, [qlang.OP_EQUAL, "group", nodegroup],
3277
                                 [qlang.OP_EQUAL, "group.uuid", nodegroup]])
3278

    
3279
  if filter_master:
3280
    qfilter.append([qlang.OP_NOT, [qlang.OP_TRUE, "master"]])
3281

    
3282
  if qfilter:
3283
    if len(qfilter) > 1:
3284
      final_filter = [qlang.OP_AND] + qfilter
3285
    else:
3286
      assert len(qfilter) == 1
3287
      final_filter = qfilter[0]
3288
  else:
3289
    final_filter = None
3290

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

    
3293
  def _IsOffline(row):
3294
    (_, (_, offline), _) = row
3295
    return offline
3296

    
3297
  def _GetName(row):
3298
    ((_, name), _, _) = row
3299
    return name
3300

    
3301
  def _GetSip(row):
3302
    (_, _, (_, sip)) = row
3303
    return sip
3304

    
3305
  (offline, online) = compat.partition(result.data, _IsOffline)
3306

    
3307
  if offline and not nowarn:
3308
    ToStderr("Note: skipping offline node(s): %s" %
3309
             utils.CommaJoin(map(_GetName, offline)))
3310

    
3311
  if secondary_ips:
3312
    fn = _GetSip
3313
  else:
3314
    fn = _GetName
3315

    
3316
  return map(fn, online)
3317

    
3318

    
3319
def _ToStream(stream, txt, *args):
3320
  """Write a message to a stream, bypassing the logging system
3321

3322
  @type stream: file object
3323
  @param stream: the file to which we should write
3324
  @type txt: str
3325
  @param txt: the message
3326

3327
  """
3328
  try:
3329
    if args:
3330
      args = tuple(args)
3331
      stream.write(txt % args)
3332
    else:
3333
      stream.write(txt)
3334
    stream.write("\n")
3335
    stream.flush()
3336
  except IOError, err:
3337
    if err.errno == errno.EPIPE:
3338
      # our terminal went away, we'll exit
3339
      sys.exit(constants.EXIT_FAILURE)
3340
    else:
3341
      raise
3342

    
3343

    
3344
def ToStdout(txt, *args):
3345
  """Write a message to stdout only, bypassing the logging system
3346

3347
  This is just a wrapper over _ToStream.
3348

3349
  @type txt: str
3350
  @param txt: the message
3351

3352
  """
3353
  _ToStream(sys.stdout, txt, *args)
3354

    
3355

    
3356
def ToStderr(txt, *args):
3357
  """Write a message to stderr only, bypassing the logging system
3358

3359
  This is just a wrapper over _ToStream.
3360

3361
  @type txt: str
3362
  @param txt: the message
3363

3364
  """
3365
  _ToStream(sys.stderr, txt, *args)
3366

    
3367

    
3368
class JobExecutor(object):
3369
  """Class which manages the submission and execution of multiple jobs.
3370

3371
  Note that instances of this class should not be reused between
3372
  GetResults() calls.
3373

3374
  """
3375
  def __init__(self, cl=None, verbose=True, opts=None, feedback_fn=None):
3376
    self.queue = []
3377
    if cl is None:
3378
      cl = GetClient()
3379
    self.cl = cl
3380
    self.verbose = verbose
3381
    self.jobs = []
3382
    self.opts = opts
3383
    self.feedback_fn = feedback_fn
3384
    self._counter = itertools.count()
3385

    
3386
  @staticmethod
3387
  def _IfName(name, fmt):
3388
    """Helper function for formatting name.
3389

3390
    """
3391
    if name:
3392
      return fmt % name
3393

    
3394
    return ""
3395

    
3396
  def QueueJob(self, name, *ops):
3397
    """Record a job for later submit.
3398

3399
    @type name: string
3400
    @param name: a description of the job, will be used in WaitJobSet
3401

3402
    """
3403
    SetGenericOpcodeOpts(ops, self.opts)
3404
    self.queue.append((self._counter.next(), name, ops))
3405

    
3406
  def AddJobId(self, name, status, job_id):
3407
    """Adds a job ID to the internal queue.
3408

3409
    """
3410
    self.jobs.append((self._counter.next(), status, job_id, name))
3411

    
3412
  def SubmitPending(self, each=False):
3413
    """Submit all pending jobs.
3414

3415
    """
3416
    if each:
3417
      results = []
3418
      for (_, _, ops) in self.queue:
3419
        # SubmitJob will remove the success status, but raise an exception if
3420
        # the submission fails, so we'll notice that anyway.
3421
        results.append([True, self.cl.SubmitJob(ops)[0]])
3422
    else:
3423
      results = self.cl.SubmitManyJobs([ops for (_, _, ops) in self.queue])
3424
    for ((status, data), (idx, name, _)) in zip(results, self.queue):
3425
      self.jobs.append((idx, status, data, name))
3426

    
3427
  def _ChooseJob(self):
3428
    """Choose a non-waiting/queued job to poll next.
3429

3430
    """
3431
    assert self.jobs, "_ChooseJob called with empty job list"
3432

    
3433
    result = self.cl.QueryJobs([i[2] for i in self.jobs[:_CHOOSE_BATCH]],
3434
                               ["status"])
3435
    assert result
3436

    
3437
    for job_data, status in zip(self.jobs, result):
3438
      if (isinstance(status, list) and status and
3439
          status[0] in (constants.JOB_STATUS_QUEUED,
3440
                        constants.JOB_STATUS_WAITING,
3441
                        constants.JOB_STATUS_CANCELING)):
3442
        # job is still present and waiting
3443
        continue
3444
      # good candidate found (either running job or lost job)
3445
      self.jobs.remove(job_data)
3446
      return job_data
3447

    
3448
    # no job found
3449
    return self.jobs.pop(0)
3450

    
3451
  def GetResults(self):
3452
    """Wait for and return the results of all jobs.
3453

3454
    @rtype: list
3455
    @return: list of tuples (success, job results), in the same order
3456
        as the submitted jobs; if a job has failed, instead of the result
3457
        there will be the error message
3458

3459
    """
3460
    if not self.jobs:
3461
      self.SubmitPending()
3462
    results = []
3463
    if self.verbose:
3464
      ok_jobs = [row[2] for row in self.jobs if row[1]]
3465
      if ok_jobs:
3466
        ToStdout("Submitted jobs %s", utils.CommaJoin(ok_jobs))
3467

    
3468
    # first, remove any non-submitted jobs
3469
    self.jobs, failures = compat.partition(self.jobs, lambda x: x[1])
3470
    for idx, _, jid, name in failures:
3471
      ToStderr("Failed to submit job%s: %s", self._IfName(name, " for %s"), jid)
3472
      results.append((idx, False, jid))
3473

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

    
3492
      results.append((idx, success, job_result))
3493

    
3494
    # sort based on the index, then drop it
3495
    results.sort()
3496
    results = [i[1:] for i in results]
3497

    
3498
    return results
3499

    
3500
  def WaitOrShow(self, wait):
3501
    """Wait for job results or only print the job IDs.
3502

3503
    @type wait: boolean
3504
    @param wait: whether to wait or not
3505

3506
    """
3507
    if wait:
3508
      return self.GetResults()
3509
    else:
3510
      if not self.jobs:
3511
        self.SubmitPending()
3512
      for _, status, result, name in self.jobs:
3513
        if status:
3514
          ToStdout("%s: %s", result, name)
3515
        else:
3516
          ToStderr("Failure for %s: %s", name, result)
3517
      return [row[1:3] for row in self.jobs]
3518

    
3519

    
3520
def FormatParameterDict(buf, param_dict, actual, level=1):
3521
  """Formats a parameter dictionary.
3522

3523
  @type buf: L{StringIO}
3524
  @param buf: the buffer into which to write
3525
  @type param_dict: dict
3526
  @param param_dict: the own parameters
3527
  @type actual: dict
3528
  @param actual: the current parameter set (including defaults)
3529
  @param level: Level of indent
3530

3531
  """
3532
  indent = "  " * level
3533

    
3534
  for key in sorted(actual):
3535
    data = actual[key]
3536
    buf.write("%s- %s:" % (indent, key))
3537

    
3538
    if isinstance(data, dict) and data:
3539
      buf.write("\n")
3540
      FormatParameterDict(buf, param_dict.get(key, {}), data,
3541
                          level=level + 1)
3542
    else:
3543
      val = param_dict.get(key, "default (%s)" % data)
3544
      buf.write(" %s\n" % val)
3545

    
3546

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

3550
  This function is used to request confirmation for doing an operation
3551
  on a given list of list_type.
3552

3553
  @type names: list
3554
  @param names: the list of names that we display when
3555
      we ask for confirmation
3556
  @type list_type: str
3557
  @param list_type: Human readable name for elements in the list (e.g. nodes)
3558
  @type text: str
3559
  @param text: the operation that the user should confirm
3560
  @rtype: boolean
3561
  @return: True or False depending on user's confirmation.
3562

3563
  """
3564
  count = len(names)
3565
  msg = ("The %s will operate on %d %s.\n%s"
3566
         "Do you want to continue?" % (text, count, list_type, extra))
3567
  affected = (("\nAffected %s:\n" % list_type) +
3568
              "\n".join(["  %s" % name for name in names]))
3569

    
3570
  choices = [("y", True, "Yes, execute the %s" % text),
3571
             ("n", False, "No, abort the %s" % text)]
3572

    
3573
  if count > 20:
3574
    choices.insert(1, ("v", "v", "View the list of affected %s" % list_type))
3575
    question = msg
3576
  else:
3577
    question = msg + affected
3578

    
3579
  choice = AskUser(question, choices)
3580
  if choice == "v":
3581
    choices.pop(1)
3582
    choice = AskUser(msg + affected, choices)
3583
  return choice
3584

    
3585

    
3586
def _MaybeParseUnit(elements):
3587
  """Parses and returns an array of potential values with units.
3588

3589
  """
3590
  parsed = {}
3591
  for k, v in elements.items():
3592
    if v == constants.VALUE_DEFAULT:
3593
      parsed[k] = v
3594
    else:
3595
      parsed[k] = utils.ParseUnit(v)
3596
  return parsed
3597

    
3598

    
3599
def CreateIPolicyFromOpts(ispecs_mem_size=None,
3600
                          ispecs_cpu_count=None,
3601
                          ispecs_disk_count=None,
3602
                          ispecs_disk_size=None,
3603
                          ispecs_nic_count=None,
3604
                          ipolicy_disk_templates=None,
3605
                          ipolicy_vcpu_ratio=None,
3606
                          ipolicy_spindle_ratio=None,
3607
                          group_ipolicy=False,
3608
                          allowed_values=None,
3609
                          fill_all=False):
3610
  """Creation of instance policy based on command line options.
3611

3612
  @param fill_all: whether for cluster policies we should ensure that
3613
    all values are filled
3614

3615

3616
  """
3617
  try:
3618
    if ispecs_mem_size:
3619
      ispecs_mem_size = _MaybeParseUnit(ispecs_mem_size)
3620
    if ispecs_disk_size:
3621
      ispecs_disk_size = _MaybeParseUnit(ispecs_disk_size)
3622
  except (TypeError, ValueError, errors.UnitParseError), err:
3623
    raise errors.OpPrereqError("Invalid disk (%s) or memory (%s) size"
3624
                               " in policy: %s" %
3625
                               (ispecs_disk_size, ispecs_mem_size, err),
3626
                               errors.ECODE_INVAL)
3627

    
3628
  # prepare ipolicy dict
3629
  ipolicy_transposed = {
3630
    constants.ISPEC_MEM_SIZE: ispecs_mem_size,
3631
    constants.ISPEC_CPU_COUNT: ispecs_cpu_count,
3632
    constants.ISPEC_DISK_COUNT: ispecs_disk_count,
3633
    constants.ISPEC_DISK_SIZE: ispecs_disk_size,
3634
    constants.ISPEC_NIC_COUNT: ispecs_nic_count,
3635
    }
3636

    
3637
  # first, check that the values given are correct
3638
  if group_ipolicy:
3639
    forced_type = TISPECS_GROUP_TYPES
3640
  else:
3641
    forced_type = TISPECS_CLUSTER_TYPES
3642

    
3643
  for specs in ipolicy_transposed.values():
3644
    utils.ForceDictType(specs, forced_type, allowed_values=allowed_values)
3645

    
3646
  # then transpose
3647
  ipolicy_out = objects.MakeEmptyIPolicy()
3648
  for name, specs in ipolicy_transposed.iteritems():
3649
    assert name in constants.ISPECS_PARAMETERS
3650
    for key, val in specs.items(): # {min: .. ,max: .., std: ..}
3651
      ipolicy_out[key][name] = val
3652

    
3653
  # no filldict for non-dicts
3654
  if not group_ipolicy and fill_all:
3655
    if ipolicy_disk_templates is None:
3656
      ipolicy_disk_templates = constants.DISK_TEMPLATES
3657
    if ipolicy_vcpu_ratio is None:
3658
      ipolicy_vcpu_ratio = \
3659
        constants.IPOLICY_DEFAULTS[constants.IPOLICY_VCPU_RATIO]
3660
    if ipolicy_spindle_ratio is None:
3661
      ipolicy_spindle_ratio = \
3662
        constants.IPOLICY_DEFAULTS[constants.IPOLICY_SPINDLE_RATIO]
3663
  if ipolicy_disk_templates is not None:
3664
    ipolicy_out[constants.IPOLICY_DTS] = list(ipolicy_disk_templates)
3665
  if ipolicy_vcpu_ratio is not None:
3666
    ipolicy_out[constants.IPOLICY_VCPU_RATIO] = ipolicy_vcpu_ratio
3667
  if ipolicy_spindle_ratio is not None:
3668
    ipolicy_out[constants.IPOLICY_SPINDLE_RATIO] = ipolicy_spindle_ratio
3669

    
3670
  assert not (frozenset(ipolicy_out.keys()) - constants.IPOLICY_ALL_KEYS)
3671

    
3672
  return ipolicy_out