Statistics
| Branch: | Tag: | Revision:

root / lib / cli.py @ c270ee07

History | View | Annotate | Download (122.6 kB)

1
#
2
#
3

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

    
285
NO_PREFIX = "no_"
286
UN_PREFIX = "-"
287

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

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

    
300
# Query result status for clients
301
(QR_NORMAL,
302
 QR_UNKNOWN,
303
 QR_INCOMPLETE) = range(3)
304

    
305
#: Maximum batch size for ChooseJob
306
_CHOOSE_BATCH = 25
307

    
308

    
309
# constants used to create InstancePolicy dictionary
310
TISPECS_GROUP_TYPES = {
311
  constants.ISPECS_MIN: constants.VTYPE_INT,
312
  constants.ISPECS_MAX: constants.VTYPE_INT,
313
  }
314

    
315
TISPECS_CLUSTER_TYPES = {
316
  constants.ISPECS_MIN: constants.VTYPE_INT,
317
  constants.ISPECS_MAX: constants.VTYPE_INT,
318
  constants.ISPECS_STD: constants.VTYPE_INT,
319
  }
320

    
321
#: User-friendly names for query2 field types
322
_QFT_NAMES = {
323
  constants.QFT_UNKNOWN: "Unknown",
324
  constants.QFT_TEXT: "Text",
325
  constants.QFT_BOOL: "Boolean",
326
  constants.QFT_NUMBER: "Number",
327
  constants.QFT_UNIT: "Storage size",
328
  constants.QFT_TIMESTAMP: "Timestamp",
329
  constants.QFT_OTHER: "Custom",
330
  }
331

    
332

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

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

    
342

    
343
class ArgSuggest(_Argument):
344
  """Suggesting argument.
345

346
  Value can be any of the ones passed to the constructor.
347

348
  """
349
  # pylint: disable=W0622
350
  def __init__(self, min=0, max=None, choices=None):
351
    _Argument.__init__(self, min=min, max=max)
352
    self.choices = choices
353

    
354
  def __repr__(self):
355
    return ("<%s min=%s max=%s choices=%r>" %
356
            (self.__class__.__name__, self.min, self.max, self.choices))
357

    
358

    
359
class ArgChoice(ArgSuggest):
360
  """Choice argument.
361

362
  Value can be any of the ones passed to the constructor. Like L{ArgSuggest},
363
  but value must be one of the choices.
364

365
  """
366

    
367

    
368
class ArgUnknown(_Argument):
369
  """Unknown argument to program (e.g. determined at runtime).
370

371
  """
372

    
373

    
374
class ArgInstance(_Argument):
375
  """Instances argument.
376

377
  """
378

    
379

    
380
class ArgNode(_Argument):
381
  """Node argument.
382

383
  """
384

    
385

    
386
class ArgNetwork(_Argument):
387
  """Network argument.
388

389
  """
390

    
391

    
392
class ArgGroup(_Argument):
393
  """Node group argument.
394

395
  """
396

    
397

    
398
class ArgJobId(_Argument):
399
  """Job ID argument.
400

401
  """
402

    
403

    
404
class ArgFile(_Argument):
405
  """File path argument.
406

407
  """
408

    
409

    
410
class ArgCommand(_Argument):
411
  """Command argument.
412

413
  """
414

    
415

    
416
class ArgHost(_Argument):
417
  """Host argument.
418

419
  """
420

    
421

    
422
class ArgOs(_Argument):
423
  """OS argument.
424

425
  """
426

    
427

    
428
class ArgExtStorage(_Argument):
429
  """ExtStorage argument.
430

431
  """
432

    
433

    
434
ARGS_NONE = []
435
ARGS_MANY_INSTANCES = [ArgInstance()]
436
ARGS_MANY_NETWORKS = [ArgNetwork()]
437
ARGS_MANY_NODES = [ArgNode()]
438
ARGS_MANY_GROUPS = [ArgGroup()]
439
ARGS_ONE_INSTANCE = [ArgInstance(min=1, max=1)]
440
ARGS_ONE_NETWORK = [ArgNetwork(min=1, max=1)]
441
ARGS_ONE_NODE = [ArgNode(min=1, max=1)]
442
# TODO
443
ARGS_ONE_GROUP = [ArgGroup(min=1, max=1)]
444
ARGS_ONE_OS = [ArgOs(min=1, max=1)]
445

    
446

    
447
def _ExtractTagsObject(opts, args):
448
  """Extract the tag type object.
449

450
  Note that this function will modify its args parameter.
451

452
  """
453
  if not hasattr(opts, "tag_type"):
454
    raise errors.ProgrammerError("tag_type not passed to _ExtractTagsObject")
455
  kind = opts.tag_type
456
  if kind == constants.TAG_CLUSTER:
457
    retval = kind, None
458
  elif kind in (constants.TAG_NODEGROUP,
459
                constants.TAG_NODE,
460
                constants.TAG_NETWORK,
461
                constants.TAG_INSTANCE):
462
    if not args:
463
      raise errors.OpPrereqError("no arguments passed to the command",
464
                                 errors.ECODE_INVAL)
465
    name = args.pop(0)
466
    retval = kind, name
467
  else:
468
    raise errors.ProgrammerError("Unhandled tag type '%s'" % kind)
469
  return retval
470

    
471

    
472
def _ExtendTags(opts, args):
473
  """Extend the args if a source file has been given.
474

475
  This function will extend the tags with the contents of the file
476
  passed in the 'tags_source' attribute of the opts parameter. A file
477
  named '-' will be replaced by stdin.
478

479
  """
480
  fname = opts.tags_source
481
  if fname is None:
482
    return
483
  if fname == "-":
484
    new_fh = sys.stdin
485
  else:
486
    new_fh = open(fname, "r")
487
  new_data = []
488
  try:
489
    # we don't use the nice 'new_data = [line.strip() for line in fh]'
490
    # because of python bug 1633941
491
    while True:
492
      line = new_fh.readline()
493
      if not line:
494
        break
495
      new_data.append(line.strip())
496
  finally:
497
    new_fh.close()
498
  args.extend(new_data)
499

    
500

    
501
def ListTags(opts, args):
502
  """List the tags on a given object.
503

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

509
  """
510
  kind, name = _ExtractTagsObject(opts, args)
511
  cl = GetClient(query=True)
512
  result = cl.QueryTags(kind, name)
513
  result = list(result)
514
  result.sort()
515
  for tag in result:
516
    ToStdout(tag)
517

    
518

    
519
def AddTags(opts, args):
520
  """Add tags on a given object.
521

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

527
  """
528
  kind, name = _ExtractTagsObject(opts, args)
529
  _ExtendTags(opts, args)
530
  if not args:
531
    raise errors.OpPrereqError("No tags to be added", errors.ECODE_INVAL)
532
  op = opcodes.OpTagsSet(kind=kind, name=name, tags=args)
533
  SubmitOrSend(op, opts)
534

    
535

    
536
def RemoveTags(opts, args):
537
  """Remove tags from a given object.
538

539
  This is a generic implementation that knows how to deal with all
540
  three cases of tag objects (cluster, node, instance). The opts
541
  argument is expected to contain a tag_type field denoting what
542
  object type we work on.
543

544
  """
545
  kind, name = _ExtractTagsObject(opts, args)
546
  _ExtendTags(opts, args)
547
  if not args:
548
    raise errors.OpPrereqError("No tags to be removed", errors.ECODE_INVAL)
549
  op = opcodes.OpTagsDel(kind=kind, name=name, tags=args)
550
  SubmitOrSend(op, opts)
551

    
552

    
553
def check_unit(option, opt, value): # pylint: disable=W0613
554
  """OptParsers custom converter for units.
555

556
  """
557
  try:
558
    return utils.ParseUnit(value)
559
  except errors.UnitParseError, err:
560
    raise OptionValueError("option %s: %s" % (opt, err))
561

    
562

    
563
def _SplitKeyVal(opt, data):
564
  """Convert a KeyVal string into a dict.
565

566
  This function will convert a key=val[,...] string into a dict. Empty
567
  values will be converted specially: keys which have the prefix 'no_'
568
  will have the value=False and the prefix stripped, the others will
569
  have value=True.
570

571
  @type opt: string
572
  @param opt: a string holding the option name for which we process the
573
      data, used in building error messages
574
  @type data: string
575
  @param data: a string of the format key=val,key=val,...
576
  @rtype: dict
577
  @return: {key=val, key=val}
578
  @raises errors.ParameterError: if there are duplicate keys
579

580
  """
581
  kv_dict = {}
582
  if data:
583
    for elem in utils.UnescapeAndSplit(data, sep=","):
584
      if "=" in elem:
585
        key, val = elem.split("=", 1)
586
      else:
587
        if elem.startswith(NO_PREFIX):
588
          key, val = elem[len(NO_PREFIX):], False
589
        elif elem.startswith(UN_PREFIX):
590
          key, val = elem[len(UN_PREFIX):], None
591
        else:
592
          key, val = elem, True
593
      if key in kv_dict:
594
        raise errors.ParameterError("Duplicate key '%s' in option %s" %
595
                                    (key, opt))
596
      kv_dict[key] = val
597
  return kv_dict
598

    
599

    
600
def check_ident_key_val(option, opt, value):  # pylint: disable=W0613
601
  """Custom parser for ident:key=val,key=val options.
602

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

606
  """
607
  if ":" not in value:
608
    ident, rest = value, ""
609
  else:
610
    ident, rest = value.split(":", 1)
611

    
612
  if ident.startswith(NO_PREFIX):
613
    if rest:
614
      msg = "Cannot pass options when removing parameter groups: %s" % value
615
      raise errors.ParameterError(msg)
616
    retval = (ident[len(NO_PREFIX):], False)
617
  elif (ident.startswith(UN_PREFIX) and
618
        (len(ident) <= len(UN_PREFIX) or
619
         not ident[len(UN_PREFIX)][0].isdigit())):
620
    if rest:
621
      msg = "Cannot pass options when removing parameter groups: %s" % value
622
      raise errors.ParameterError(msg)
623
    retval = (ident[len(UN_PREFIX):], None)
624
  else:
625
    kv_dict = _SplitKeyVal(opt, rest)
626
    retval = (ident, kv_dict)
627
  return retval
628

    
629

    
630
def check_key_val(option, opt, value):  # pylint: disable=W0613
631
  """Custom parser class for key=val,key=val options.
632

633
  This will store the parsed values as a dict {key: val}.
634

635
  """
636
  return _SplitKeyVal(opt, value)
637

    
638

    
639
def check_bool(option, opt, value): # pylint: disable=W0613
640
  """Custom parser for yes/no options.
641

642
  This will store the parsed value as either True or False.
643

644
  """
645
  value = value.lower()
646
  if value == constants.VALUE_FALSE or value == "no":
647
    return False
648
  elif value == constants.VALUE_TRUE or value == "yes":
649
    return True
650
  else:
651
    raise errors.ParameterError("Invalid boolean value '%s'" % value)
652

    
653

    
654
def check_list(option, opt, value): # pylint: disable=W0613
655
  """Custom parser for comma-separated lists.
656

657
  """
658
  # we have to make this explicit check since "".split(",") is [""],
659
  # not an empty list :(
660
  if not value:
661
    return []
662
  else:
663
    return utils.UnescapeAndSplit(value)
664

    
665

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

669
  """
670
  value = value.lower()
671

    
672
  if value == constants.VALUE_DEFAULT:
673
    return value
674
  else:
675
    return float(value)
676

    
677

    
678
# completion_suggestion is normally a list. Using numeric values not evaluating
679
# to False for dynamic completion.
680
(OPT_COMPL_MANY_NODES,
681
 OPT_COMPL_ONE_NODE,
682
 OPT_COMPL_ONE_INSTANCE,
683
 OPT_COMPL_ONE_OS,
684
 OPT_COMPL_ONE_EXTSTORAGE,
685
 OPT_COMPL_ONE_IALLOCATOR,
686
 OPT_COMPL_ONE_NETWORK,
687
 OPT_COMPL_INST_ADD_NODES,
688
 OPT_COMPL_ONE_NODEGROUP) = range(100, 109)
689

    
690
OPT_COMPL_ALL = compat.UniqueFrozenset([
691
  OPT_COMPL_MANY_NODES,
692
  OPT_COMPL_ONE_NODE,
693
  OPT_COMPL_ONE_INSTANCE,
694
  OPT_COMPL_ONE_OS,
695
  OPT_COMPL_ONE_EXTSTORAGE,
696
  OPT_COMPL_ONE_IALLOCATOR,
697
  OPT_COMPL_ONE_NETWORK,
698
  OPT_COMPL_INST_ADD_NODES,
699
  OPT_COMPL_ONE_NODEGROUP,
700
  ])
701

    
702

    
703
class CliOption(Option):
704
  """Custom option class for optparse.
705

706
  """
707
  ATTRS = Option.ATTRS + [
708
    "completion_suggest",
709
    ]
710
  TYPES = Option.TYPES + (
711
    "identkeyval",
712
    "keyval",
713
    "unit",
714
    "bool",
715
    "list",
716
    "maybefloat",
717
    )
718
  TYPE_CHECKER = Option.TYPE_CHECKER.copy()
719
  TYPE_CHECKER["identkeyval"] = check_ident_key_val
720
  TYPE_CHECKER["keyval"] = check_key_val
721
  TYPE_CHECKER["unit"] = check_unit
722
  TYPE_CHECKER["bool"] = check_bool
723
  TYPE_CHECKER["list"] = check_list
724
  TYPE_CHECKER["maybefloat"] = check_maybefloat
725

    
726

    
727
# optparse.py sets make_option, so we do it for our own option class, too
728
cli_option = CliOption
729

    
730

    
731
_YORNO = "yes|no"
732

    
733
DEBUG_OPT = cli_option("-d", "--debug", default=0, action="count",
734
                       help="Increase debugging level")
735

    
736
NOHDR_OPT = cli_option("--no-headers", default=False,
737
                       action="store_true", dest="no_headers",
738
                       help="Don't display column headers")
739

    
740
SEP_OPT = cli_option("--separator", default=None,
741
                     action="store", dest="separator",
742
                     help=("Separator between output fields"
743
                           " (defaults to one space)"))
744

    
745
USEUNITS_OPT = cli_option("--units", default=None,
746
                          dest="units", choices=("h", "m", "g", "t"),
747
                          help="Specify units for output (one of h/m/g/t)")
748

    
749
FIELDS_OPT = cli_option("-o", "--output", dest="output", action="store",
750
                        type="string", metavar="FIELDS",
751
                        help="Comma separated list of output fields")
752

    
753
FORCE_OPT = cli_option("-f", "--force", dest="force", action="store_true",
754
                       default=False, help="Force the operation")
755

    
756
CONFIRM_OPT = cli_option("--yes", dest="confirm", action="store_true",
757
                         default=False, help="Do not require confirmation")
758

    
759
IGNORE_OFFLINE_OPT = cli_option("--ignore-offline", dest="ignore_offline",
760
                                  action="store_true", default=False,
761
                                  help=("Ignore offline nodes and do as much"
762
                                        " as possible"))
763

    
764
TAG_ADD_OPT = cli_option("--tags", dest="tags",
765
                         default=None, help="Comma-separated list of instance"
766
                                            " tags")
767

    
768
TAG_SRC_OPT = cli_option("--from", dest="tags_source",
769
                         default=None, help="File with tag names")
770

    
771
SUBMIT_OPT = cli_option("--submit", dest="submit_only",
772
                        default=False, action="store_true",
773
                        help=("Submit the job and return the job ID, but"
774
                              " don't wait for the job to finish"))
775

    
776
SYNC_OPT = cli_option("--sync", dest="do_locking",
777
                      default=False, action="store_true",
778
                      help=("Grab locks while doing the queries"
779
                            " in order to ensure more consistent results"))
780

    
781
DRY_RUN_OPT = cli_option("--dry-run", default=False,
782
                         action="store_true",
783
                         help=("Do not execute the operation, just run the"
784
                               " check steps and verify if it could be"
785
                               " executed"))
786

    
787
VERBOSE_OPT = cli_option("-v", "--verbose", default=False,
788
                         action="store_true",
789
                         help="Increase the verbosity of the operation")
790

    
791
DEBUG_SIMERR_OPT = cli_option("--debug-simulate-errors", default=False,
792
                              action="store_true", dest="simulate_errors",
793
                              help="Debugging option that makes the operation"
794
                              " treat most runtime checks as failed")
795

    
796
NWSYNC_OPT = cli_option("--no-wait-for-sync", dest="wait_for_sync",
797
                        default=True, action="store_false",
798
                        help="Don't wait for sync (DANGEROUS!)")
799

    
800
WFSYNC_OPT = cli_option("--wait-for-sync", dest="wait_for_sync",
801
                        default=False, action="store_true",
802
                        help="Wait for disks to sync")
803

    
804
ONLINE_INST_OPT = cli_option("--online", dest="online_inst",
805
                             action="store_true", default=False,
806
                             help="Enable offline instance")
807

    
808
OFFLINE_INST_OPT = cli_option("--offline", dest="offline_inst",
809
                              action="store_true", default=False,
810
                              help="Disable down instance")
811

    
812
DISK_TEMPLATE_OPT = cli_option("-t", "--disk-template", dest="disk_template",
813
                               help=("Custom disk setup (%s)" %
814
                                     utils.CommaJoin(constants.DISK_TEMPLATES)),
815
                               default=None, metavar="TEMPL",
816
                               choices=list(constants.DISK_TEMPLATES))
817

    
818
NONICS_OPT = cli_option("--no-nics", default=False, action="store_true",
819
                        help="Do not create any network cards for"
820
                        " the instance")
821

    
822
FILESTORE_DIR_OPT = cli_option("--file-storage-dir", dest="file_storage_dir",
823
                               help="Relative path under default cluster-wide"
824
                               " file storage dir to store file-based disks",
825
                               default=None, metavar="<DIR>")
826

    
827
FILESTORE_DRIVER_OPT = cli_option("--file-driver", dest="file_driver",
828
                                  help="Driver to use for image files",
829
                                  default="loop", metavar="<DRIVER>",
830
                                  choices=list(constants.FILE_DRIVER))
831

    
832
IALLOCATOR_OPT = cli_option("-I", "--iallocator", metavar="<NAME>",
833
                            help="Select nodes for the instance automatically"
834
                            " using the <NAME> iallocator plugin",
835
                            default=None, type="string",
836
                            completion_suggest=OPT_COMPL_ONE_IALLOCATOR)
837

    
838
DEFAULT_IALLOCATOR_OPT = cli_option("-I", "--default-iallocator",
839
                                    metavar="<NAME>",
840
                                    help="Set the default instance"
841
                                    " allocator plugin",
842
                                    default=None, type="string",
843
                                    completion_suggest=OPT_COMPL_ONE_IALLOCATOR)
844

    
845
OS_OPT = cli_option("-o", "--os-type", dest="os", help="What OS to run",
846
                    metavar="<os>",
847
                    completion_suggest=OPT_COMPL_ONE_OS)
848

    
849
OSPARAMS_OPT = cli_option("-O", "--os-parameters", dest="osparams",
850
                          type="keyval", default={},
851
                          help="OS parameters")
852

    
853
FORCE_VARIANT_OPT = cli_option("--force-variant", dest="force_variant",
854
                               action="store_true", default=False,
855
                               help="Force an unknown variant")
856

    
857
NO_INSTALL_OPT = cli_option("--no-install", dest="no_install",
858
                            action="store_true", default=False,
859
                            help="Do not install the OS (will"
860
                            " enable no-start)")
861

    
862
NORUNTIME_CHGS_OPT = cli_option("--no-runtime-changes",
863
                                dest="allow_runtime_chgs",
864
                                default=True, action="store_false",
865
                                help="Don't allow runtime changes")
866

    
867
BACKEND_OPT = cli_option("-B", "--backend-parameters", dest="beparams",
868
                         type="keyval", default={},
869
                         help="Backend parameters")
870

    
871
HVOPTS_OPT = cli_option("-H", "--hypervisor-parameters", type="keyval",
872
                        default={}, dest="hvparams",
873
                        help="Hypervisor parameters")
874

    
875
DISK_PARAMS_OPT = cli_option("-D", "--disk-parameters", dest="diskparams",
876
                             help="Disk template parameters, in the format"
877
                             " template:option=value,option=value,...",
878
                             type="identkeyval", action="append", default=[])
879

    
880
SPECS_MEM_SIZE_OPT = cli_option("--specs-mem-size", dest="ispecs_mem_size",
881
                                 type="keyval", default={},
882
                                 help="Memory size specs: list of key=value,"
883
                                " where key is one of min, max, std"
884
                                 " (in MB or using a unit)")
885

    
886
SPECS_CPU_COUNT_OPT = cli_option("--specs-cpu-count", dest="ispecs_cpu_count",
887
                                 type="keyval", default={},
888
                                 help="CPU count specs: list of key=value,"
889
                                 " where key is one of min, max, std")
890

    
891
SPECS_DISK_COUNT_OPT = cli_option("--specs-disk-count",
892
                                  dest="ispecs_disk_count",
893
                                  type="keyval", default={},
894
                                  help="Disk count specs: list of key=value,"
895
                                  " where key is one of min, max, std")
896

    
897
SPECS_DISK_SIZE_OPT = cli_option("--specs-disk-size", dest="ispecs_disk_size",
898
                                 type="keyval", default={},
899
                                 help="Disk size specs: list of key=value,"
900
                                 " where key is one of min, max, std"
901
                                 " (in MB or using a unit)")
902

    
903
SPECS_NIC_COUNT_OPT = cli_option("--specs-nic-count", dest="ispecs_nic_count",
904
                                 type="keyval", default={},
905
                                 help="NIC count specs: list of key=value,"
906
                                 " where key is one of min, max, std")
907

    
908
IPOLICY_DISK_TEMPLATES = cli_option("--ipolicy-disk-templates",
909
                                    dest="ipolicy_disk_templates",
910
                                    type="list", default=None,
911
                                    help="Comma-separated list of"
912
                                    " enabled disk templates")
913

    
914
IPOLICY_VCPU_RATIO = cli_option("--ipolicy-vcpu-ratio",
915
                                 dest="ipolicy_vcpu_ratio",
916
                                 type="maybefloat", default=None,
917
                                 help="The maximum allowed vcpu-to-cpu ratio")
918

    
919
IPOLICY_SPINDLE_RATIO = cli_option("--ipolicy-spindle-ratio",
920
                                   dest="ipolicy_spindle_ratio",
921
                                   type="maybefloat", default=None,
922
                                   help=("The maximum allowed instances to"
923
                                         " spindle ratio"))
924

    
925
HYPERVISOR_OPT = cli_option("-H", "--hypervisor-parameters", dest="hypervisor",
926
                            help="Hypervisor and hypervisor options, in the"
927
                            " format hypervisor:option=value,option=value,...",
928
                            default=None, type="identkeyval")
929

    
930
HVLIST_OPT = cli_option("-H", "--hypervisor-parameters", dest="hvparams",
931
                        help="Hypervisor and hypervisor options, in the"
932
                        " format hypervisor:option=value,option=value,...",
933
                        default=[], action="append", type="identkeyval")
934

    
935
NOIPCHECK_OPT = cli_option("--no-ip-check", dest="ip_check", default=True,
936
                           action="store_false",
937
                           help="Don't check that the instance's IP"
938
                           " is alive")
939

    
940
NONAMECHECK_OPT = cli_option("--no-name-check", dest="name_check",
941
                             default=True, action="store_false",
942
                             help="Don't check that the instance's name"
943
                             " is resolvable")
944

    
945
NET_OPT = cli_option("--net",
946
                     help="NIC parameters", default=[],
947
                     dest="nics", action="append", type="identkeyval")
948

    
949
DISK_OPT = cli_option("--disk", help="Disk parameters", default=[],
950
                      dest="disks", action="append", type="identkeyval")
951

    
952
DISKIDX_OPT = cli_option("--disks", dest="disks", default=None,
953
                         help="Comma-separated list of disks"
954
                         " indices to act on (e.g. 0,2) (optional,"
955
                         " defaults to all disks)")
956

    
957
OS_SIZE_OPT = cli_option("-s", "--os-size", dest="sd_size",
958
                         help="Enforces a single-disk configuration using the"
959
                         " given disk size, in MiB unless a suffix is used",
960
                         default=None, type="unit", metavar="<size>")
961

    
962
IGNORE_CONSIST_OPT = cli_option("--ignore-consistency",
963
                                dest="ignore_consistency",
964
                                action="store_true", default=False,
965
                                help="Ignore the consistency of the disks on"
966
                                " the secondary")
967

    
968
ALLOW_FAILOVER_OPT = cli_option("--allow-failover",
969
                                dest="allow_failover",
970
                                action="store_true", default=False,
971
                                help="If migration is not possible fallback to"
972
                                     " failover")
973

    
974
NONLIVE_OPT = cli_option("--non-live", dest="live",
975
                         default=True, action="store_false",
976
                         help="Do a non-live migration (this usually means"
977
                         " freeze the instance, save the state, transfer and"
978
                         " only then resume running on the secondary node)")
979

    
980
MIGRATION_MODE_OPT = cli_option("--migration-mode", dest="migration_mode",
981
                                default=None,
982
                                choices=list(constants.HT_MIGRATION_MODES),
983
                                help="Override default migration mode (choose"
984
                                " either live or non-live")
985

    
986
NODE_PLACEMENT_OPT = cli_option("-n", "--node", dest="node",
987
                                help="Target node and optional secondary node",
988
                                metavar="<pnode>[:<snode>]",
989
                                completion_suggest=OPT_COMPL_INST_ADD_NODES)
990

    
991
NODE_LIST_OPT = cli_option("-n", "--node", dest="nodes", default=[],
992
                           action="append", metavar="<node>",
993
                           help="Use only this node (can be used multiple"
994
                           " times, if not given defaults to all nodes)",
995
                           completion_suggest=OPT_COMPL_ONE_NODE)
996

    
997
NODEGROUP_OPT_NAME = "--node-group"
998
NODEGROUP_OPT = cli_option("-g", NODEGROUP_OPT_NAME,
999
                           dest="nodegroup",
1000
                           help="Node group (name or uuid)",
1001
                           metavar="<nodegroup>",
1002
                           default=None, type="string",
1003
                           completion_suggest=OPT_COMPL_ONE_NODEGROUP)
1004

    
1005
SINGLE_NODE_OPT = cli_option("-n", "--node", dest="node", help="Target node",
1006
                             metavar="<node>",
1007
                             completion_suggest=OPT_COMPL_ONE_NODE)
1008

    
1009
NOSTART_OPT = cli_option("--no-start", dest="start", default=True,
1010
                         action="store_false",
1011
                         help="Don't start the instance after creation")
1012

    
1013
SHOWCMD_OPT = cli_option("--show-cmd", dest="show_command",
1014
                         action="store_true", default=False,
1015
                         help="Show command instead of executing it")
1016

    
1017
CLEANUP_OPT = cli_option("--cleanup", dest="cleanup",
1018
                         default=False, action="store_true",
1019
                         help="Instead of performing the migration, try to"
1020
                         " recover from a failed cleanup. This is safe"
1021
                         " to run even if the instance is healthy, but it"
1022
                         " will create extra replication traffic and "
1023
                         " disrupt briefly the replication (like during the"
1024
                         " migration")
1025

    
1026
STATIC_OPT = cli_option("-s", "--static", dest="static",
1027
                        action="store_true", default=False,
1028
                        help="Only show configuration data, not runtime data")
1029

    
1030
ALL_OPT = cli_option("--all", dest="show_all",
1031
                     default=False, action="store_true",
1032
                     help="Show info on all instances on the cluster."
1033
                     " This can take a long time to run, use wisely")
1034

    
1035
SELECT_OS_OPT = cli_option("--select-os", dest="select_os",
1036
                           action="store_true", default=False,
1037
                           help="Interactive OS reinstall, lists available"
1038
                           " OS templates for selection")
1039

    
1040
IGNORE_FAILURES_OPT = cli_option("--ignore-failures", dest="ignore_failures",
1041
                                 action="store_true", default=False,
1042
                                 help="Remove the instance from the cluster"
1043
                                 " configuration even if there are failures"
1044
                                 " during the removal process")
1045

    
1046
IGNORE_REMOVE_FAILURES_OPT = cli_option("--ignore-remove-failures",
1047
                                        dest="ignore_remove_failures",
1048
                                        action="store_true", default=False,
1049
                                        help="Remove the instance from the"
1050
                                        " cluster configuration even if there"
1051
                                        " are failures during the removal"
1052
                                        " process")
1053

    
1054
REMOVE_INSTANCE_OPT = cli_option("--remove-instance", dest="remove_instance",
1055
                                 action="store_true", default=False,
1056
                                 help="Remove the instance from the cluster")
1057

    
1058
DST_NODE_OPT = cli_option("-n", "--target-node", dest="dst_node",
1059
                               help="Specifies the new node for the instance",
1060
                               metavar="NODE", default=None,
1061
                               completion_suggest=OPT_COMPL_ONE_NODE)
1062

    
1063
NEW_SECONDARY_OPT = cli_option("-n", "--new-secondary", dest="dst_node",
1064
                               help="Specifies the new secondary node",
1065
                               metavar="NODE", default=None,
1066
                               completion_suggest=OPT_COMPL_ONE_NODE)
1067

    
1068
ON_PRIMARY_OPT = cli_option("-p", "--on-primary", dest="on_primary",
1069
                            default=False, action="store_true",
1070
                            help="Replace the disk(s) on the primary"
1071
                                 " node (applies only to internally mirrored"
1072
                                 " disk templates, e.g. %s)" %
1073
                                 utils.CommaJoin(constants.DTS_INT_MIRROR))
1074

    
1075
ON_SECONDARY_OPT = cli_option("-s", "--on-secondary", dest="on_secondary",
1076
                              default=False, action="store_true",
1077
                              help="Replace the disk(s) on the secondary"
1078
                                   " node (applies only to internally mirrored"
1079
                                   " disk templates, e.g. %s)" %
1080
                                   utils.CommaJoin(constants.DTS_INT_MIRROR))
1081

    
1082
AUTO_PROMOTE_OPT = cli_option("--auto-promote", dest="auto_promote",
1083
                              default=False, action="store_true",
1084
                              help="Lock all nodes and auto-promote as needed"
1085
                              " to MC status")
1086

    
1087
AUTO_REPLACE_OPT = cli_option("-a", "--auto", dest="auto",
1088
                              default=False, action="store_true",
1089
                              help="Automatically replace faulty disks"
1090
                                   " (applies only to internally mirrored"
1091
                                   " disk templates, e.g. %s)" %
1092
                                   utils.CommaJoin(constants.DTS_INT_MIRROR))
1093

    
1094
IGNORE_SIZE_OPT = cli_option("--ignore-size", dest="ignore_size",
1095
                             default=False, action="store_true",
1096
                             help="Ignore current recorded size"
1097
                             " (useful for forcing activation when"
1098
                             " the recorded size is wrong)")
1099

    
1100
SRC_NODE_OPT = cli_option("--src-node", dest="src_node", help="Source node",
1101
                          metavar="<node>",
1102
                          completion_suggest=OPT_COMPL_ONE_NODE)
1103

    
1104
SRC_DIR_OPT = cli_option("--src-dir", dest="src_dir", help="Source directory",
1105
                         metavar="<dir>")
1106

    
1107
SECONDARY_IP_OPT = cli_option("-s", "--secondary-ip", dest="secondary_ip",
1108
                              help="Specify the secondary ip for the node",
1109
                              metavar="ADDRESS", default=None)
1110

    
1111
READD_OPT = cli_option("--readd", dest="readd",
1112
                       default=False, action="store_true",
1113
                       help="Readd old node after replacing it")
1114

    
1115
NOSSH_KEYCHECK_OPT = cli_option("--no-ssh-key-check", dest="ssh_key_check",
1116
                                default=True, action="store_false",
1117
                                help="Disable SSH key fingerprint checking")
1118

    
1119
NODE_FORCE_JOIN_OPT = cli_option("--force-join", dest="force_join",
1120
                                 default=False, action="store_true",
1121
                                 help="Force the joining of a node")
1122

    
1123
MC_OPT = cli_option("-C", "--master-candidate", dest="master_candidate",
1124
                    type="bool", default=None, metavar=_YORNO,
1125
                    help="Set the master_candidate flag on the node")
1126

    
1127
OFFLINE_OPT = cli_option("-O", "--offline", dest="offline", metavar=_YORNO,
1128
                         type="bool", default=None,
1129
                         help=("Set the offline flag on the node"
1130
                               " (cluster does not communicate with offline"
1131
                               " nodes)"))
1132

    
1133
DRAINED_OPT = cli_option("-D", "--drained", dest="drained", metavar=_YORNO,
1134
                         type="bool", default=None,
1135
                         help=("Set the drained flag on the node"
1136
                               " (excluded from allocation operations)"))
1137

    
1138
CAPAB_MASTER_OPT = cli_option("--master-capable", dest="master_capable",
1139
                              type="bool", default=None, metavar=_YORNO,
1140
                              help="Set the master_capable flag on the node")
1141

    
1142
CAPAB_VM_OPT = cli_option("--vm-capable", dest="vm_capable",
1143
                          type="bool", default=None, metavar=_YORNO,
1144
                          help="Set the vm_capable flag on the node")
1145

    
1146
ALLOCATABLE_OPT = cli_option("--allocatable", dest="allocatable",
1147
                             type="bool", default=None, metavar=_YORNO,
1148
                             help="Set the allocatable flag on a volume")
1149

    
1150
NOLVM_STORAGE_OPT = cli_option("--no-lvm-storage", dest="lvm_storage",
1151
                               help="Disable support for lvm based instances"
1152
                               " (cluster-wide)",
1153
                               action="store_false", default=True)
1154

    
1155
ENABLED_HV_OPT = cli_option("--enabled-hypervisors",
1156
                            dest="enabled_hypervisors",
1157
                            help="Comma-separated list of hypervisors",
1158
                            type="string", default=None)
1159

    
1160
ENABLED_STORAGE_TYPES_OPT = cli_option("--enabled-storage-types",
1161
                                       dest="enabled_storage_types",
1162
                                       help="Comma-separated list of "
1163
                                            "storage methods",
1164
                                       type="string", default=None)
1165

    
1166
NIC_PARAMS_OPT = cli_option("-N", "--nic-parameters", dest="nicparams",
1167
                            type="keyval", default={},
1168
                            help="NIC parameters")
1169

    
1170
CP_SIZE_OPT = cli_option("-C", "--candidate-pool-size", default=None,
1171
                         dest="candidate_pool_size", type="int",
1172
                         help="Set the candidate pool size")
1173

    
1174
VG_NAME_OPT = cli_option("--vg-name", dest="vg_name",
1175
                         help=("Enables LVM and specifies the volume group"
1176
                               " name (cluster-wide) for disk allocation"
1177
                               " [%s]" % constants.DEFAULT_VG),
1178
                         metavar="VG", default=None)
1179

    
1180
YES_DOIT_OPT = cli_option("--yes-do-it", "--ya-rly", dest="yes_do_it",
1181
                          help="Destroy cluster", action="store_true")
1182

    
1183
NOVOTING_OPT = cli_option("--no-voting", dest="no_voting",
1184
                          help="Skip node agreement check (dangerous)",
1185
                          action="store_true", default=False)
1186

    
1187
MAC_PREFIX_OPT = cli_option("-m", "--mac-prefix", dest="mac_prefix",
1188
                            help="Specify the mac prefix for the instance IP"
1189
                            " addresses, in the format XX:XX:XX",
1190
                            metavar="PREFIX",
1191
                            default=None)
1192

    
1193
MASTER_NETDEV_OPT = cli_option("--master-netdev", dest="master_netdev",
1194
                               help="Specify the node interface (cluster-wide)"
1195
                               " on which the master IP address will be added"
1196
                               " (cluster init default: %s)" %
1197
                               constants.DEFAULT_BRIDGE,
1198
                               metavar="NETDEV",
1199
                               default=None)
1200

    
1201
MASTER_NETMASK_OPT = cli_option("--master-netmask", dest="master_netmask",
1202
                                help="Specify the netmask of the master IP",
1203
                                metavar="NETMASK",
1204
                                default=None)
1205

    
1206
USE_EXTERNAL_MIP_SCRIPT = cli_option("--use-external-mip-script",
1207
                                     dest="use_external_mip_script",
1208
                                     help="Specify whether to run a"
1209
                                     " user-provided script for the master"
1210
                                     " IP address turnup and"
1211
                                     " turndown operations",
1212
                                     type="bool", metavar=_YORNO, default=None)
1213

    
1214
GLOBAL_FILEDIR_OPT = cli_option("--file-storage-dir", dest="file_storage_dir",
1215
                                help="Specify the default directory (cluster-"
1216
                                "wide) for storing the file-based disks [%s]" %
1217
                                pathutils.DEFAULT_FILE_STORAGE_DIR,
1218
                                metavar="DIR",
1219
                                default=pathutils.DEFAULT_FILE_STORAGE_DIR)
1220

    
1221
GLOBAL_SHARED_FILEDIR_OPT = cli_option(
1222
  "--shared-file-storage-dir",
1223
  dest="shared_file_storage_dir",
1224
  help="Specify the default directory (cluster-wide) for storing the"
1225
  " shared file-based disks [%s]" %
1226
  pathutils.DEFAULT_SHARED_FILE_STORAGE_DIR,
1227
  metavar="SHAREDDIR", default=pathutils.DEFAULT_SHARED_FILE_STORAGE_DIR)
1228

    
1229
NOMODIFY_ETCHOSTS_OPT = cli_option("--no-etc-hosts", dest="modify_etc_hosts",
1230
                                   help="Don't modify %s" % pathutils.ETC_HOSTS,
1231
                                   action="store_false", default=True)
1232

    
1233
NOMODIFY_SSH_SETUP_OPT = cli_option("--no-ssh-init", dest="modify_ssh_setup",
1234
                                    help="Don't initialize SSH keys",
1235
                                    action="store_false", default=True)
1236

    
1237
ERROR_CODES_OPT = cli_option("--error-codes", dest="error_codes",
1238
                             help="Enable parseable error messages",
1239
                             action="store_true", default=False)
1240

    
1241
NONPLUS1_OPT = cli_option("--no-nplus1-mem", dest="skip_nplusone_mem",
1242
                          help="Skip N+1 memory redundancy tests",
1243
                          action="store_true", default=False)
1244

    
1245
REBOOT_TYPE_OPT = cli_option("-t", "--type", dest="reboot_type",
1246
                             help="Type of reboot: soft/hard/full",
1247
                             default=constants.INSTANCE_REBOOT_HARD,
1248
                             metavar="<REBOOT>",
1249
                             choices=list(constants.REBOOT_TYPES))
1250

    
1251
IGNORE_SECONDARIES_OPT = cli_option("--ignore-secondaries",
1252
                                    dest="ignore_secondaries",
1253
                                    default=False, action="store_true",
1254
                                    help="Ignore errors from secondaries")
1255

    
1256
NOSHUTDOWN_OPT = cli_option("--noshutdown", dest="shutdown",
1257
                            action="store_false", default=True,
1258
                            help="Don't shutdown the instance (unsafe)")
1259

    
1260
TIMEOUT_OPT = cli_option("--timeout", dest="timeout", type="int",
1261
                         default=constants.DEFAULT_SHUTDOWN_TIMEOUT,
1262
                         help="Maximum time to wait")
1263

    
1264
SHUTDOWN_TIMEOUT_OPT = cli_option("--shutdown-timeout",
1265
                                  dest="shutdown_timeout", type="int",
1266
                                  default=constants.DEFAULT_SHUTDOWN_TIMEOUT,
1267
                                  help="Maximum time to wait for instance"
1268
                                  " shutdown")
1269

    
1270
INTERVAL_OPT = cli_option("--interval", dest="interval", type="int",
1271
                          default=None,
1272
                          help=("Number of seconds between repetions of the"
1273
                                " command"))
1274

    
1275
EARLY_RELEASE_OPT = cli_option("--early-release",
1276
                               dest="early_release", default=False,
1277
                               action="store_true",
1278
                               help="Release the locks on the secondary"
1279
                               " node(s) early")
1280

    
1281
NEW_CLUSTER_CERT_OPT = cli_option("--new-cluster-certificate",
1282
                                  dest="new_cluster_cert",
1283
                                  default=False, action="store_true",
1284
                                  help="Generate a new cluster certificate")
1285

    
1286
RAPI_CERT_OPT = cli_option("--rapi-certificate", dest="rapi_cert",
1287
                           default=None,
1288
                           help="File containing new RAPI certificate")
1289

    
1290
NEW_RAPI_CERT_OPT = cli_option("--new-rapi-certificate", dest="new_rapi_cert",
1291
                               default=None, action="store_true",
1292
                               help=("Generate a new self-signed RAPI"
1293
                                     " certificate"))
1294

    
1295
SPICE_CERT_OPT = cli_option("--spice-certificate", dest="spice_cert",
1296
                            default=None,
1297
                            help="File containing new SPICE certificate")
1298

    
1299
SPICE_CACERT_OPT = cli_option("--spice-ca-certificate", dest="spice_cacert",
1300
                              default=None,
1301
                              help="File containing the certificate of the CA"
1302
                              " which signed the SPICE certificate")
1303

    
1304
NEW_SPICE_CERT_OPT = cli_option("--new-spice-certificate",
1305
                                dest="new_spice_cert", default=None,
1306
                                action="store_true",
1307
                                help=("Generate a new self-signed SPICE"
1308
                                      " certificate"))
1309

    
1310
NEW_CONFD_HMAC_KEY_OPT = cli_option("--new-confd-hmac-key",
1311
                                    dest="new_confd_hmac_key",
1312
                                    default=False, action="store_true",
1313
                                    help=("Create a new HMAC key for %s" %
1314
                                          constants.CONFD))
1315

    
1316
CLUSTER_DOMAIN_SECRET_OPT = cli_option("--cluster-domain-secret",
1317
                                       dest="cluster_domain_secret",
1318
                                       default=None,
1319
                                       help=("Load new new cluster domain"
1320
                                             " secret from file"))
1321

    
1322
NEW_CLUSTER_DOMAIN_SECRET_OPT = cli_option("--new-cluster-domain-secret",
1323
                                           dest="new_cluster_domain_secret",
1324
                                           default=False, action="store_true",
1325
                                           help=("Create a new cluster domain"
1326
                                                 " secret"))
1327

    
1328
USE_REPL_NET_OPT = cli_option("--use-replication-network",
1329
                              dest="use_replication_network",
1330
                              help="Whether to use the replication network"
1331
                              " for talking to the nodes",
1332
                              action="store_true", default=False)
1333

    
1334
MAINTAIN_NODE_HEALTH_OPT = \
1335
    cli_option("--maintain-node-health", dest="maintain_node_health",
1336
               metavar=_YORNO, default=None, type="bool",
1337
               help="Configure the cluster to automatically maintain node"
1338
               " health, by shutting down unknown instances, shutting down"
1339
               " unknown DRBD devices, etc.")
1340

    
1341
IDENTIFY_DEFAULTS_OPT = \
1342
    cli_option("--identify-defaults", dest="identify_defaults",
1343
               default=False, action="store_true",
1344
               help="Identify which saved instance parameters are equal to"
1345
               " the current cluster defaults and set them as such, instead"
1346
               " of marking them as overridden")
1347

    
1348
UIDPOOL_OPT = cli_option("--uid-pool", default=None,
1349
                         action="store", dest="uid_pool",
1350
                         help=("A list of user-ids or user-id"
1351
                               " ranges separated by commas"))
1352

    
1353
ADD_UIDS_OPT = cli_option("--add-uids", default=None,
1354
                          action="store", dest="add_uids",
1355
                          help=("A list of user-ids or user-id"
1356
                                " ranges separated by commas, to be"
1357
                                " added to the user-id pool"))
1358

    
1359
REMOVE_UIDS_OPT = cli_option("--remove-uids", default=None,
1360
                             action="store", dest="remove_uids",
1361
                             help=("A list of user-ids or user-id"
1362
                                   " ranges separated by commas, to be"
1363
                                   " removed from the user-id pool"))
1364

    
1365
RESERVED_LVS_OPT = cli_option("--reserved-lvs", default=None,
1366
                              action="store", dest="reserved_lvs",
1367
                              help=("A comma-separated list of reserved"
1368
                                    " logical volumes names, that will be"
1369
                                    " ignored by cluster verify"))
1370

    
1371
ROMAN_OPT = cli_option("--roman",
1372
                       dest="roman_integers", default=False,
1373
                       action="store_true",
1374
                       help="Use roman numbers for positive integers")
1375

    
1376
DRBD_HELPER_OPT = cli_option("--drbd-usermode-helper", dest="drbd_helper",
1377
                             action="store", default=None,
1378
                             help="Specifies usermode helper for DRBD")
1379

    
1380
NODRBD_STORAGE_OPT = cli_option("--no-drbd-storage", dest="drbd_storage",
1381
                                action="store_false", default=True,
1382
                                help="Disable support for DRBD")
1383

    
1384
PRIMARY_IP_VERSION_OPT = \
1385
    cli_option("--primary-ip-version", default=constants.IP4_VERSION,
1386
               action="store", dest="primary_ip_version",
1387
               metavar="%d|%d" % (constants.IP4_VERSION,
1388
                                  constants.IP6_VERSION),
1389
               help="Cluster-wide IP version for primary IP")
1390

    
1391
SHOW_MACHINE_OPT = cli_option("-M", "--show-machine-names", default=False,
1392
                              action="store_true",
1393
                              help="Show machine name for every line in output")
1394

    
1395
FAILURE_ONLY_OPT = cli_option("--failure-only", default=False,
1396
                              action="store_true",
1397
                              help=("Hide successful results and show failures"
1398
                                    " only (determined by the exit code)"))
1399

    
1400
REASON_OPT = cli_option("--reason", default=None,
1401
                        help="The reason for executing a VM-state-changing"
1402
                             " operation")
1403

    
1404

    
1405
def _PriorityOptionCb(option, _, value, parser):
1406
  """Callback for processing C{--priority} option.
1407

1408
  """
1409
  value = _PRIONAME_TO_VALUE[value]
1410

    
1411
  setattr(parser.values, option.dest, value)
1412

    
1413

    
1414
PRIORITY_OPT = cli_option("--priority", default=None, dest="priority",
1415
                          metavar="|".join(name for name, _ in _PRIORITY_NAMES),
1416
                          choices=_PRIONAME_TO_VALUE.keys(),
1417
                          action="callback", type="choice",
1418
                          callback=_PriorityOptionCb,
1419
                          help="Priority for opcode processing")
1420

    
1421
HID_OS_OPT = cli_option("--hidden", dest="hidden",
1422
                        type="bool", default=None, metavar=_YORNO,
1423
                        help="Sets the hidden flag on the OS")
1424

    
1425
BLK_OS_OPT = cli_option("--blacklisted", dest="blacklisted",
1426
                        type="bool", default=None, metavar=_YORNO,
1427
                        help="Sets the blacklisted flag on the OS")
1428

    
1429
PREALLOC_WIPE_DISKS_OPT = cli_option("--prealloc-wipe-disks", default=None,
1430
                                     type="bool", metavar=_YORNO,
1431
                                     dest="prealloc_wipe_disks",
1432
                                     help=("Wipe disks prior to instance"
1433
                                           " creation"))
1434

    
1435
NODE_PARAMS_OPT = cli_option("--node-parameters", dest="ndparams",
1436
                             type="keyval", default=None,
1437
                             help="Node parameters")
1438

    
1439
ALLOC_POLICY_OPT = cli_option("--alloc-policy", dest="alloc_policy",
1440
                              action="store", metavar="POLICY", default=None,
1441
                              help="Allocation policy for the node group")
1442

    
1443
NODE_POWERED_OPT = cli_option("--node-powered", default=None,
1444
                              type="bool", metavar=_YORNO,
1445
                              dest="node_powered",
1446
                              help="Specify if the SoR for node is powered")
1447

    
1448
OOB_TIMEOUT_OPT = cli_option("--oob-timeout", dest="oob_timeout", type="int",
1449
                             default=constants.OOB_TIMEOUT,
1450
                             help="Maximum time to wait for out-of-band helper")
1451

    
1452
POWER_DELAY_OPT = cli_option("--power-delay", dest="power_delay", type="float",
1453
                             default=constants.OOB_POWER_DELAY,
1454
                             help="Time in seconds to wait between power-ons")
1455

    
1456
FORCE_FILTER_OPT = cli_option("-F", "--filter", dest="force_filter",
1457
                              action="store_true", default=False,
1458
                              help=("Whether command argument should be treated"
1459
                                    " as filter"))
1460

    
1461
NO_REMEMBER_OPT = cli_option("--no-remember",
1462
                             dest="no_remember",
1463
                             action="store_true", default=False,
1464
                             help="Perform but do not record the change"
1465
                             " in the configuration")
1466

    
1467
PRIMARY_ONLY_OPT = cli_option("-p", "--primary-only",
1468
                              default=False, action="store_true",
1469
                              help="Evacuate primary instances only")
1470

    
1471
SECONDARY_ONLY_OPT = cli_option("-s", "--secondary-only",
1472
                                default=False, action="store_true",
1473
                                help="Evacuate secondary instances only"
1474
                                     " (applies only to internally mirrored"
1475
                                     " disk templates, e.g. %s)" %
1476
                                     utils.CommaJoin(constants.DTS_INT_MIRROR))
1477

    
1478
STARTUP_PAUSED_OPT = cli_option("--paused", dest="startup_paused",
1479
                                action="store_true", default=False,
1480
                                help="Pause instance at startup")
1481

    
1482
TO_GROUP_OPT = cli_option("--to", dest="to", metavar="<group>",
1483
                          help="Destination node group (name or uuid)",
1484
                          default=None, action="append",
1485
                          completion_suggest=OPT_COMPL_ONE_NODEGROUP)
1486

    
1487
IGNORE_ERRORS_OPT = cli_option("-I", "--ignore-errors", default=[],
1488
                               action="append", dest="ignore_errors",
1489
                               choices=list(constants.CV_ALL_ECODES_STRINGS),
1490
                               help="Error code to be ignored")
1491

    
1492
DISK_STATE_OPT = cli_option("--disk-state", default=[], dest="disk_state",
1493
                            action="append",
1494
                            help=("Specify disk state information in the"
1495
                                  " format"
1496
                                  " storage_type/identifier:option=value,...;"
1497
                                  " note this is unused for now"),
1498
                            type="identkeyval")
1499

    
1500
HV_STATE_OPT = cli_option("--hypervisor-state", default=[], dest="hv_state",
1501
                          action="append",
1502
                          help=("Specify hypervisor state information in the"
1503
                                " format hypervisor:option=value,...;"
1504
                                " note this is unused for now"),
1505
                          type="identkeyval")
1506

    
1507
IGNORE_IPOLICY_OPT = cli_option("--ignore-ipolicy", dest="ignore_ipolicy",
1508
                                action="store_true", default=False,
1509
                                help="Ignore instance policy violations")
1510

    
1511
RUNTIME_MEM_OPT = cli_option("-m", "--runtime-memory", dest="runtime_mem",
1512
                             help="Sets the instance's runtime memory,"
1513
                             " ballooning it up or down to the new value",
1514
                             default=None, type="unit", metavar="<size>")
1515

    
1516
ABSOLUTE_OPT = cli_option("--absolute", dest="absolute",
1517
                          action="store_true", default=False,
1518
                          help="Marks the grow as absolute instead of the"
1519
                          " (default) relative mode")
1520

    
1521
NETWORK_OPT = cli_option("--network",
1522
                         action="store", default=None, dest="network",
1523
                         help="IP network in CIDR notation")
1524

    
1525
GATEWAY_OPT = cli_option("--gateway",
1526
                         action="store", default=None, dest="gateway",
1527
                         help="IP address of the router (gateway)")
1528

    
1529
ADD_RESERVED_IPS_OPT = cli_option("--add-reserved-ips",
1530
                                  action="store", default=None,
1531
                                  dest="add_reserved_ips",
1532
                                  help="Comma-separated list of"
1533
                                  " reserved IPs to add")
1534

    
1535
REMOVE_RESERVED_IPS_OPT = cli_option("--remove-reserved-ips",
1536
                                     action="store", default=None,
1537
                                     dest="remove_reserved_ips",
1538
                                     help="Comma-delimited list of"
1539
                                     " reserved IPs to remove")
1540

    
1541
NETWORK6_OPT = cli_option("--network6",
1542
                          action="store", default=None, dest="network6",
1543
                          help="IP network in CIDR notation")
1544

    
1545
GATEWAY6_OPT = cli_option("--gateway6",
1546
                          action="store", default=None, dest="gateway6",
1547
                          help="IP6 address of the router (gateway)")
1548

    
1549
NOCONFLICTSCHECK_OPT = cli_option("--no-conflicts-check",
1550
                                  dest="conflicts_check",
1551
                                  default=True,
1552
                                  action="store_false",
1553
                                  help="Don't check for conflicting IPs")
1554

    
1555
#: Options provided by all commands
1556
COMMON_OPTS = [DEBUG_OPT]
1557

    
1558
# common options for creating instances. add and import then add their own
1559
# specific ones.
1560
COMMON_CREATE_OPTS = [
1561
  BACKEND_OPT,
1562
  DISK_OPT,
1563
  DISK_TEMPLATE_OPT,
1564
  FILESTORE_DIR_OPT,
1565
  FILESTORE_DRIVER_OPT,
1566
  HYPERVISOR_OPT,
1567
  IALLOCATOR_OPT,
1568
  NET_OPT,
1569
  NODE_PLACEMENT_OPT,
1570
  NOIPCHECK_OPT,
1571
  NOCONFLICTSCHECK_OPT,
1572
  NONAMECHECK_OPT,
1573
  NONICS_OPT,
1574
  NWSYNC_OPT,
1575
  OSPARAMS_OPT,
1576
  OS_SIZE_OPT,
1577
  SUBMIT_OPT,
1578
  TAG_ADD_OPT,
1579
  DRY_RUN_OPT,
1580
  PRIORITY_OPT,
1581
  ]
1582

    
1583
# common instance policy options
1584
INSTANCE_POLICY_OPTS = [
1585
  SPECS_CPU_COUNT_OPT,
1586
  SPECS_DISK_COUNT_OPT,
1587
  SPECS_DISK_SIZE_OPT,
1588
  SPECS_MEM_SIZE_OPT,
1589
  SPECS_NIC_COUNT_OPT,
1590
  IPOLICY_DISK_TEMPLATES,
1591
  IPOLICY_VCPU_RATIO,
1592
  IPOLICY_SPINDLE_RATIO,
1593
  ]
1594

    
1595

    
1596
class _ShowUsage(Exception):
1597
  """Exception class for L{_ParseArgs}.
1598

1599
  """
1600
  def __init__(self, exit_error):
1601
    """Initializes instances of this class.
1602

1603
    @type exit_error: bool
1604
    @param exit_error: Whether to report failure on exit
1605

1606
    """
1607
    Exception.__init__(self)
1608
    self.exit_error = exit_error
1609

    
1610

    
1611
class _ShowVersion(Exception):
1612
  """Exception class for L{_ParseArgs}.
1613

1614
  """
1615

    
1616

    
1617
def _ParseArgs(binary, argv, commands, aliases, env_override):
1618
  """Parser for the command line arguments.
1619

1620
  This function parses the arguments and returns the function which
1621
  must be executed together with its (modified) arguments.
1622

1623
  @param binary: Script name
1624
  @param argv: Command line arguments
1625
  @param commands: Dictionary containing command definitions
1626
  @param aliases: dictionary with command aliases {"alias": "target", ...}
1627
  @param env_override: list of env variables allowed for default args
1628
  @raise _ShowUsage: If usage description should be shown
1629
  @raise _ShowVersion: If version should be shown
1630

1631
  """
1632
  assert not (env_override - set(commands))
1633
  assert not (set(aliases.keys()) & set(commands.keys()))
1634

    
1635
  if len(argv) > 1:
1636
    cmd = argv[1]
1637
  else:
1638
    # No option or command given
1639
    raise _ShowUsage(exit_error=True)
1640

    
1641
  if cmd == "--version":
1642
    raise _ShowVersion()
1643
  elif cmd == "--help":
1644
    raise _ShowUsage(exit_error=False)
1645
  elif not (cmd in commands or cmd in aliases):
1646
    raise _ShowUsage(exit_error=True)
1647

    
1648
  # get command, unalias it, and look it up in commands
1649
  if cmd in aliases:
1650
    if aliases[cmd] not in commands:
1651
      raise errors.ProgrammerError("Alias '%s' maps to non-existing"
1652
                                   " command '%s'" % (cmd, aliases[cmd]))
1653

    
1654
    cmd = aliases[cmd]
1655

    
1656
  if cmd in env_override:
1657
    args_env_name = ("%s_%s" % (binary.replace("-", "_"), cmd)).upper()
1658
    env_args = os.environ.get(args_env_name)
1659
    if env_args:
1660
      argv = utils.InsertAtPos(argv, 2, shlex.split(env_args))
1661

    
1662
  func, args_def, parser_opts, usage, description = commands[cmd]
1663
  parser = OptionParser(option_list=parser_opts + COMMON_OPTS,
1664
                        description=description,
1665
                        formatter=TitledHelpFormatter(),
1666
                        usage="%%prog %s %s" % (cmd, usage))
1667
  parser.disable_interspersed_args()
1668
  options, args = parser.parse_args(args=argv[2:])
1669

    
1670
  if not _CheckArguments(cmd, args_def, args):
1671
    return None, None, None
1672

    
1673
  return func, options, args
1674

    
1675

    
1676
def _FormatUsage(binary, commands):
1677
  """Generates a nice description of all commands.
1678

1679
  @param binary: Script name
1680
  @param commands: Dictionary containing command definitions
1681

1682
  """
1683
  # compute the max line length for cmd + usage
1684
  mlen = min(60, max(map(len, commands)))
1685

    
1686
  yield "Usage: %s {command} [options...] [argument...]" % binary
1687
  yield "%s <command> --help to see details, or man %s" % (binary, binary)
1688
  yield ""
1689
  yield "Commands:"
1690

    
1691
  # and format a nice command list
1692
  for (cmd, (_, _, _, _, help_text)) in sorted(commands.items()):
1693
    help_lines = textwrap.wrap(help_text, 79 - 3 - mlen)
1694
    yield " %-*s - %s" % (mlen, cmd, help_lines.pop(0))
1695
    for line in help_lines:
1696
      yield " %-*s   %s" % (mlen, "", line)
1697

    
1698
  yield ""
1699

    
1700

    
1701
def _CheckArguments(cmd, args_def, args):
1702
  """Verifies the arguments using the argument definition.
1703

1704
  Algorithm:
1705

1706
    1. Abort with error if values specified by user but none expected.
1707

1708
    1. For each argument in definition
1709

1710
      1. Keep running count of minimum number of values (min_count)
1711
      1. Keep running count of maximum number of values (max_count)
1712
      1. If it has an unlimited number of values
1713

1714
        1. Abort with error if it's not the last argument in the definition
1715

1716
    1. If last argument has limited number of values
1717

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

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

1722
  """
1723
  if args and not args_def:
1724
    ToStderr("Error: Command %s expects no arguments", cmd)
1725
    return False
1726

    
1727
  min_count = None
1728
  max_count = None
1729
  check_max = None
1730

    
1731
  last_idx = len(args_def) - 1
1732

    
1733
  for idx, arg in enumerate(args_def):
1734
    if min_count is None:
1735
      min_count = arg.min
1736
    elif arg.min is not None:
1737
      min_count += arg.min
1738

    
1739
    if max_count is None:
1740
      max_count = arg.max
1741
    elif arg.max is not None:
1742
      max_count += arg.max
1743

    
1744
    if idx == last_idx:
1745
      check_max = (arg.max is not None)
1746

    
1747
    elif arg.max is None:
1748
      raise errors.ProgrammerError("Only the last argument can have max=None")
1749

    
1750
  if check_max:
1751
    # Command with exact number of arguments
1752
    if (min_count is not None and max_count is not None and
1753
        min_count == max_count and len(args) != min_count):
1754
      ToStderr("Error: Command %s expects %d argument(s)", cmd, min_count)
1755
      return False
1756

    
1757
    # Command with limited number of arguments
1758
    if max_count is not None and len(args) > max_count:
1759
      ToStderr("Error: Command %s expects only %d argument(s)",
1760
               cmd, max_count)
1761
      return False
1762

    
1763
  # Command with some required arguments
1764
  if min_count is not None and len(args) < min_count:
1765
    ToStderr("Error: Command %s expects at least %d argument(s)",
1766
             cmd, min_count)
1767
    return False
1768

    
1769
  return True
1770

    
1771

    
1772
def SplitNodeOption(value):
1773
  """Splits the value of a --node option.
1774

1775
  """
1776
  if value and ":" in value:
1777
    return value.split(":", 1)
1778
  else:
1779
    return (value, None)
1780

    
1781

    
1782
def CalculateOSNames(os_name, os_variants):
1783
  """Calculates all the names an OS can be called, according to its variants.
1784

1785
  @type os_name: string
1786
  @param os_name: base name of the os
1787
  @type os_variants: list or None
1788
  @param os_variants: list of supported variants
1789
  @rtype: list
1790
  @return: list of valid names
1791

1792
  """
1793
  if os_variants:
1794
    return ["%s+%s" % (os_name, v) for v in os_variants]
1795
  else:
1796
    return [os_name]
1797

    
1798

    
1799
def ParseFields(selected, default):
1800
  """Parses the values of "--field"-like options.
1801

1802
  @type selected: string or None
1803
  @param selected: User-selected options
1804
  @type default: list
1805
  @param default: Default fields
1806

1807
  """
1808
  if selected is None:
1809
    return default
1810

    
1811
  if selected.startswith("+"):
1812
    return default + selected[1:].split(",")
1813

    
1814
  return selected.split(",")
1815

    
1816

    
1817
UsesRPC = rpc.RunWithRPC
1818

    
1819

    
1820
def AskUser(text, choices=None):
1821
  """Ask the user a question.
1822

1823
  @param text: the question to ask
1824

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

1830
  @return: one of the return values from the choices list; if input is
1831
      not possible (i.e. not running with a tty, we return the last
1832
      entry from the list
1833

1834
  """
1835
  if choices is None:
1836
    choices = [("y", True, "Perform the operation"),
1837
               ("n", False, "Do not perform the operation")]
1838
  if not choices or not isinstance(choices, list):
1839
    raise errors.ProgrammerError("Invalid choices argument to AskUser")
1840
  for entry in choices:
1841
    if not isinstance(entry, tuple) or len(entry) < 3 or entry[0] == "?":
1842
      raise errors.ProgrammerError("Invalid choices element to AskUser")
1843

    
1844
  answer = choices[-1][1]
1845
  new_text = []
1846
  for line in text.splitlines():
1847
    new_text.append(textwrap.fill(line, 70, replace_whitespace=False))
1848
  text = "\n".join(new_text)
1849
  try:
1850
    f = file("/dev/tty", "a+")
1851
  except IOError:
1852
    return answer
1853
  try:
1854
    chars = [entry[0] for entry in choices]
1855
    chars[-1] = "[%s]" % chars[-1]
1856
    chars.append("?")
1857
    maps = dict([(entry[0], entry[1]) for entry in choices])
1858
    while True:
1859
      f.write(text)
1860
      f.write("\n")
1861
      f.write("/".join(chars))
1862
      f.write(": ")
1863
      line = f.readline(2).strip().lower()
1864
      if line in maps:
1865
        answer = maps[line]
1866
        break
1867
      elif line == "?":
1868
        for entry in choices:
1869
          f.write(" %s - %s\n" % (entry[0], entry[2]))
1870
        f.write("\n")
1871
        continue
1872
  finally:
1873
    f.close()
1874
  return answer
1875

    
1876

    
1877
class JobSubmittedException(Exception):
1878
  """Job was submitted, client should exit.
1879

1880
  This exception has one argument, the ID of the job that was
1881
  submitted. The handler should print this ID.
1882

1883
  This is not an error, just a structured way to exit from clients.
1884

1885
  """
1886

    
1887

    
1888
def SendJob(ops, cl=None):
1889
  """Function to submit an opcode without waiting for the results.
1890

1891
  @type ops: list
1892
  @param ops: list of opcodes
1893
  @type cl: luxi.Client
1894
  @param cl: the luxi client to use for communicating with the master;
1895
             if None, a new client will be created
1896

1897
  """
1898
  if cl is None:
1899
    cl = GetClient()
1900

    
1901
  job_id = cl.SubmitJob(ops)
1902

    
1903
  return job_id
1904

    
1905

    
1906
def GenericPollJob(job_id, cbs, report_cbs):
1907
  """Generic job-polling function.
1908

1909
  @type job_id: number
1910
  @param job_id: Job ID
1911
  @type cbs: Instance of L{JobPollCbBase}
1912
  @param cbs: Data callbacks
1913
  @type report_cbs: Instance of L{JobPollReportCbBase}
1914
  @param report_cbs: Reporting callbacks
1915

1916
  """
1917
  prev_job_info = None
1918
  prev_logmsg_serial = None
1919

    
1920
  status = None
1921

    
1922
  while True:
1923
    result = cbs.WaitForJobChangeOnce(job_id, ["status"], prev_job_info,
1924
                                      prev_logmsg_serial)
1925
    if not result:
1926
      # job not found, go away!
1927
      raise errors.JobLost("Job with id %s lost" % job_id)
1928

    
1929
    if result == constants.JOB_NOTCHANGED:
1930
      report_cbs.ReportNotChanged(job_id, status)
1931

    
1932
      # Wait again
1933
      continue
1934

    
1935
    # Split result, a tuple of (field values, log entries)
1936
    (job_info, log_entries) = result
1937
    (status, ) = job_info
1938

    
1939
    if log_entries:
1940
      for log_entry in log_entries:
1941
        (serial, timestamp, log_type, message) = log_entry
1942
        report_cbs.ReportLogMessage(job_id, serial, timestamp,
1943
                                    log_type, message)
1944
        prev_logmsg_serial = max(prev_logmsg_serial, serial)
1945

    
1946
    # TODO: Handle canceled and archived jobs
1947
    elif status in (constants.JOB_STATUS_SUCCESS,
1948
                    constants.JOB_STATUS_ERROR,
1949
                    constants.JOB_STATUS_CANCELING,
1950
                    constants.JOB_STATUS_CANCELED):
1951
      break
1952

    
1953
    prev_job_info = job_info
1954

    
1955
  jobs = cbs.QueryJobs([job_id], ["status", "opstatus", "opresult"])
1956
  if not jobs:
1957
    raise errors.JobLost("Job with id %s lost" % job_id)
1958

    
1959
  status, opstatus, result = jobs[0]
1960

    
1961
  if status == constants.JOB_STATUS_SUCCESS:
1962
    return result
1963

    
1964
  if status in (constants.JOB_STATUS_CANCELING, constants.JOB_STATUS_CANCELED):
1965
    raise errors.OpExecError("Job was canceled")
1966

    
1967
  has_ok = False
1968
  for idx, (status, msg) in enumerate(zip(opstatus, result)):
1969
    if status == constants.OP_STATUS_SUCCESS:
1970
      has_ok = True
1971
    elif status == constants.OP_STATUS_ERROR:
1972
      errors.MaybeRaise(msg)
1973

    
1974
      if has_ok:
1975
        raise errors.OpExecError("partial failure (opcode %d): %s" %
1976
                                 (idx, msg))
1977

    
1978
      raise errors.OpExecError(str(msg))
1979

    
1980
  # default failure mode
1981
  raise errors.OpExecError(result)
1982

    
1983

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

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

1991
    """
1992

    
1993
  def WaitForJobChangeOnce(self, job_id, fields,
1994
                           prev_job_info, prev_log_serial):
1995
    """Waits for changes on a job.
1996

1997
    """
1998
    raise NotImplementedError()
1999

    
2000
  def QueryJobs(self, job_ids, fields):
2001
    """Returns the selected fields for the selected job IDs.
2002

2003
    @type job_ids: list of numbers
2004
    @param job_ids: Job IDs
2005
    @type fields: list of strings
2006
    @param fields: Fields
2007

2008
    """
2009
    raise NotImplementedError()
2010

    
2011

    
2012
class JobPollReportCbBase:
2013
  """Base class for L{GenericPollJob} reporting callbacks.
2014

2015
  """
2016
  def __init__(self):
2017
    """Initializes this class.
2018

2019
    """
2020

    
2021
  def ReportLogMessage(self, job_id, serial, timestamp, log_type, log_msg):
2022
    """Handles a log message.
2023

2024
    """
2025
    raise NotImplementedError()
2026

    
2027
  def ReportNotChanged(self, job_id, status):
2028
    """Called for if a job hasn't changed in a while.
2029

2030
    @type job_id: number
2031
    @param job_id: Job ID
2032
    @type status: string or None
2033
    @param status: Job status if available
2034

2035
    """
2036
    raise NotImplementedError()
2037

    
2038

    
2039
class _LuxiJobPollCb(JobPollCbBase):
2040
  def __init__(self, cl):
2041
    """Initializes this class.
2042

2043
    """
2044
    JobPollCbBase.__init__(self)
2045
    self.cl = cl
2046

    
2047
  def WaitForJobChangeOnce(self, job_id, fields,
2048
                           prev_job_info, prev_log_serial):
2049
    """Waits for changes on a job.
2050

2051
    """
2052
    return self.cl.WaitForJobChangeOnce(job_id, fields,
2053
                                        prev_job_info, prev_log_serial)
2054

    
2055
  def QueryJobs(self, job_ids, fields):
2056
    """Returns the selected fields for the selected job IDs.
2057

2058
    """
2059
    return self.cl.QueryJobs(job_ids, fields)
2060

    
2061

    
2062
class FeedbackFnJobPollReportCb(JobPollReportCbBase):
2063
  def __init__(self, feedback_fn):
2064
    """Initializes this class.
2065

2066
    """
2067
    JobPollReportCbBase.__init__(self)
2068

    
2069
    self.feedback_fn = feedback_fn
2070

    
2071
    assert callable(feedback_fn)
2072

    
2073
  def ReportLogMessage(self, job_id, serial, timestamp, log_type, log_msg):
2074
    """Handles a log message.
2075

2076
    """
2077
    self.feedback_fn((timestamp, log_type, log_msg))
2078

    
2079
  def ReportNotChanged(self, job_id, status):
2080
    """Called if a job hasn't changed in a while.
2081

2082
    """
2083
    # Ignore
2084

    
2085

    
2086
class StdioJobPollReportCb(JobPollReportCbBase):
2087
  def __init__(self):
2088
    """Initializes this class.
2089

2090
    """
2091
    JobPollReportCbBase.__init__(self)
2092

    
2093
    self.notified_queued = False
2094
    self.notified_waitlock = False
2095

    
2096
  def ReportLogMessage(self, job_id, serial, timestamp, log_type, log_msg):
2097
    """Handles a log message.
2098

2099
    """
2100
    ToStdout("%s %s", time.ctime(utils.MergeTime(timestamp)),
2101
             FormatLogMessage(log_type, log_msg))
2102

    
2103
  def ReportNotChanged(self, job_id, status):
2104
    """Called if a job hasn't changed in a while.
2105

2106
    """
2107
    if status is None:
2108
      return
2109

    
2110
    if status == constants.JOB_STATUS_QUEUED and not self.notified_queued:
2111
      ToStderr("Job %s is waiting in queue", job_id)
2112
      self.notified_queued = True
2113

    
2114
    elif status == constants.JOB_STATUS_WAITING and not self.notified_waitlock:
2115
      ToStderr("Job %s is trying to acquire all necessary locks", job_id)
2116
      self.notified_waitlock = True
2117

    
2118

    
2119
def FormatLogMessage(log_type, log_msg):
2120
  """Formats a job message according to its type.
2121

2122
  """
2123
  if log_type != constants.ELOG_MESSAGE:
2124
    log_msg = str(log_msg)
2125

    
2126
  return utils.SafeEncode(log_msg)
2127

    
2128

    
2129
def PollJob(job_id, cl=None, feedback_fn=None, reporter=None):
2130
  """Function to poll for the result of a job.
2131

2132
  @type job_id: job identified
2133
  @param job_id: the job to poll for results
2134
  @type cl: luxi.Client
2135
  @param cl: the luxi client to use for communicating with the master;
2136
             if None, a new client will be created
2137

2138
  """
2139
  if cl is None:
2140
    cl = GetClient()
2141

    
2142
  if reporter is None:
2143
    if feedback_fn:
2144
      reporter = FeedbackFnJobPollReportCb(feedback_fn)
2145
    else:
2146
      reporter = StdioJobPollReportCb()
2147
  elif feedback_fn:
2148
    raise errors.ProgrammerError("Can't specify reporter and feedback function")
2149

    
2150
  return GenericPollJob(job_id, _LuxiJobPollCb(cl), reporter)
2151

    
2152

    
2153
def SubmitOpCode(op, cl=None, feedback_fn=None, opts=None, reporter=None):
2154
  """Legacy function to submit an opcode.
2155

2156
  This is just a simple wrapper over the construction of the processor
2157
  instance. It should be extended to better handle feedback and
2158
  interaction functions.
2159

2160
  """
2161
  if cl is None:
2162
    cl = GetClient()
2163

    
2164
  SetGenericOpcodeOpts([op], opts)
2165

    
2166
  job_id = SendJob([op], cl=cl)
2167

    
2168
  op_results = PollJob(job_id, cl=cl, feedback_fn=feedback_fn,
2169
                       reporter=reporter)
2170

    
2171
  return op_results[0]
2172

    
2173

    
2174
def SubmitOrSend(op, opts, cl=None, feedback_fn=None):
2175
  """Wrapper around SubmitOpCode or SendJob.
2176

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

2182
  It will also process the opcodes if we're sending the via SendJob
2183
  (otherwise SubmitOpCode does it).
2184

2185
  """
2186
  if opts and opts.submit_only:
2187
    job = [op]
2188
    SetGenericOpcodeOpts(job, opts)
2189
    job_id = SendJob(job, cl=cl)
2190
    raise JobSubmittedException(job_id)
2191
  else:
2192
    return SubmitOpCode(op, cl=cl, feedback_fn=feedback_fn, opts=opts)
2193

    
2194

    
2195
def SetGenericOpcodeOpts(opcode_list, options):
2196
  """Processor for generic options.
2197

2198
  This function updates the given opcodes based on generic command
2199
  line options (like debug, dry-run, etc.).
2200

2201
  @param opcode_list: list of opcodes
2202
  @param options: command line options or None
2203
  @return: None (in-place modification)
2204

2205
  """
2206
  if not options:
2207
    return
2208
  for op in opcode_list:
2209
    op.debug_level = options.debug
2210
    if hasattr(options, "dry_run"):
2211
      op.dry_run = options.dry_run
2212
    if getattr(options, "priority", None) is not None:
2213
      op.priority = options.priority
2214

    
2215

    
2216
def GetClient(query=False):
2217
  """Connects to the a luxi socket and returns a client.
2218

2219
  @type query: boolean
2220
  @param query: this signifies that the client will only be
2221
      used for queries; if the build-time parameter
2222
      enable-split-queries is enabled, then the client will be
2223
      connected to the query socket instead of the masterd socket
2224

2225
  """
2226
  override_socket = os.getenv(constants.LUXI_OVERRIDE, "")
2227
  if override_socket:
2228
    if override_socket == constants.LUXI_OVERRIDE_MASTER:
2229
      address = pathutils.MASTER_SOCKET
2230
    elif override_socket == constants.LUXI_OVERRIDE_QUERY:
2231
      address = pathutils.QUERY_SOCKET
2232
    else:
2233
      address = override_socket
2234
  elif query and constants.ENABLE_SPLIT_QUERY:
2235
    address = pathutils.QUERY_SOCKET
2236
  else:
2237
    address = None
2238
  # TODO: Cache object?
2239
  try:
2240
    client = luxi.Client(address=address)
2241
  except luxi.NoMasterError:
2242
    ss = ssconf.SimpleStore()
2243

    
2244
    # Try to read ssconf file
2245
    try:
2246
      ss.GetMasterNode()
2247
    except errors.ConfigurationError:
2248
      raise errors.OpPrereqError("Cluster not initialized or this machine is"
2249
                                 " not part of a cluster",
2250
                                 errors.ECODE_INVAL)
2251

    
2252
    master, myself = ssconf.GetMasterAndMyself(ss=ss)
2253
    if master != myself:
2254
      raise errors.OpPrereqError("This is not the master node, please connect"
2255
                                 " to node '%s' and rerun the command" %
2256
                                 master, errors.ECODE_INVAL)
2257
    raise
2258
  return client
2259

    
2260

    
2261
def FormatError(err):
2262
  """Return a formatted error message for a given error.
2263

2264
  This function takes an exception instance and returns a tuple
2265
  consisting of two values: first, the recommended exit code, and
2266
  second, a string describing the error message (not
2267
  newline-terminated).
2268

2269
  """
2270
  retcode = 1
2271
  obuf = StringIO()
2272
  msg = str(err)
2273
  if isinstance(err, errors.ConfigurationError):
2274
    txt = "Corrupt configuration file: %s" % msg
2275
    logging.error(txt)
2276
    obuf.write(txt + "\n")
2277
    obuf.write("Aborting.")
2278
    retcode = 2
2279
  elif isinstance(err, errors.HooksAbort):
2280
    obuf.write("Failure: hooks execution failed:\n")
2281
    for node, script, out in err.args[0]:
2282
      if out:
2283
        obuf.write("  node: %s, script: %s, output: %s\n" %
2284
                   (node, script, out))
2285
      else:
2286
        obuf.write("  node: %s, script: %s (no output)\n" %
2287
                   (node, script))
2288
  elif isinstance(err, errors.HooksFailure):
2289
    obuf.write("Failure: hooks general failure: %s" % msg)
2290
  elif isinstance(err, errors.ResolverError):
2291
    this_host = netutils.Hostname.GetSysName()
2292
    if err.args[0] == this_host:
2293
      msg = "Failure: can't resolve my own hostname ('%s')"
2294
    else:
2295
      msg = "Failure: can't resolve hostname '%s'"
2296
    obuf.write(msg % err.args[0])
2297
  elif isinstance(err, errors.OpPrereqError):
2298
    if len(err.args) == 2:
2299
      obuf.write("Failure: prerequisites not met for this"
2300
                 " operation:\nerror type: %s, error details:\n%s" %
2301
                 (err.args[1], err.args[0]))
2302
    else:
2303
      obuf.write("Failure: prerequisites not met for this"
2304
                 " operation:\n%s" % msg)
2305
  elif isinstance(err, errors.OpExecError):
2306
    obuf.write("Failure: command execution error:\n%s" % msg)
2307
  elif isinstance(err, errors.TagError):
2308
    obuf.write("Failure: invalid tag(s) given:\n%s" % msg)
2309
  elif isinstance(err, errors.JobQueueDrainError):
2310
    obuf.write("Failure: the job queue is marked for drain and doesn't"
2311
               " accept new requests\n")
2312
  elif isinstance(err, errors.JobQueueFull):
2313
    obuf.write("Failure: the job queue is full and doesn't accept new"
2314
               " job submissions until old jobs are archived\n")
2315
  elif isinstance(err, errors.TypeEnforcementError):
2316
    obuf.write("Parameter Error: %s" % msg)
2317
  elif isinstance(err, errors.ParameterError):
2318
    obuf.write("Failure: unknown/wrong parameter name '%s'" % msg)
2319
  elif isinstance(err, luxi.NoMasterError):
2320
    if err.args[0] == pathutils.MASTER_SOCKET:
2321
      daemon = "the master daemon"
2322
    elif err.args[0] == pathutils.QUERY_SOCKET:
2323
      daemon = "the config daemon"
2324
    else:
2325
      daemon = "socket '%s'" % str(err.args[0])
2326
    obuf.write("Cannot communicate with %s.\nIs the process running"
2327
               " and listening for connections?" % daemon)
2328
  elif isinstance(err, luxi.TimeoutError):
2329
    obuf.write("Timeout while talking to the master daemon. Jobs might have"
2330
               " been submitted and will continue to run even if the call"
2331
               " timed out. Useful commands in this situation are \"gnt-job"
2332
               " list\", \"gnt-job cancel\" and \"gnt-job watch\". Error:\n")
2333
    obuf.write(msg)
2334
  elif isinstance(err, luxi.PermissionError):
2335
    obuf.write("It seems you don't have permissions to connect to the"
2336
               " master daemon.\nPlease retry as a different user.")
2337
  elif isinstance(err, luxi.ProtocolError):
2338
    obuf.write("Unhandled protocol error while talking to the master daemon:\n"
2339
               "%s" % msg)
2340
  elif isinstance(err, errors.JobLost):
2341
    obuf.write("Error checking job status: %s" % msg)
2342
  elif isinstance(err, errors.QueryFilterParseError):
2343
    obuf.write("Error while parsing query filter: %s\n" % err.args[0])
2344
    obuf.write("\n".join(err.GetDetails()))
2345
  elif isinstance(err, errors.GenericError):
2346
    obuf.write("Unhandled Ganeti error: %s" % msg)
2347
  elif isinstance(err, JobSubmittedException):
2348
    obuf.write("JobID: %s\n" % err.args[0])
2349
    retcode = 0
2350
  else:
2351
    obuf.write("Unhandled exception: %s" % msg)
2352
  return retcode, obuf.getvalue().rstrip("\n")
2353

    
2354

    
2355
def GenericMain(commands, override=None, aliases=None,
2356
                env_override=frozenset()):
2357
  """Generic main function for all the gnt-* commands.
2358

2359
  @param commands: a dictionary with a special structure, see the design doc
2360
                   for command line handling.
2361
  @param override: if not None, we expect a dictionary with keys that will
2362
                   override command line options; this can be used to pass
2363
                   options from the scripts to generic functions
2364
  @param aliases: dictionary with command aliases {'alias': 'target, ...}
2365
  @param env_override: list of environment names which are allowed to submit
2366
                       default args for commands
2367

2368
  """
2369
  # save the program name and the entire command line for later logging
2370
  if sys.argv:
2371
    binary = os.path.basename(sys.argv[0])
2372
    if not binary:
2373
      binary = sys.argv[0]
2374

    
2375
    if len(sys.argv) >= 2:
2376
      logname = utils.ShellQuoteArgs([binary, sys.argv[1]])
2377
    else:
2378
      logname = binary
2379

    
2380
    cmdline = utils.ShellQuoteArgs([binary] + sys.argv[1:])
2381
  else:
2382
    binary = "<unknown program>"
2383
    cmdline = "<unknown>"
2384

    
2385
  if aliases is None:
2386
    aliases = {}
2387

    
2388
  try:
2389
    (func, options, args) = _ParseArgs(binary, sys.argv, commands, aliases,
2390
                                       env_override)
2391
  except _ShowVersion:
2392
    ToStdout("%s (ganeti %s) %s", binary, constants.VCS_VERSION,
2393
             constants.RELEASE_VERSION)
2394
    return constants.EXIT_SUCCESS
2395
  except _ShowUsage, err:
2396
    for line in _FormatUsage(binary, commands):
2397
      ToStdout(line)
2398

    
2399
    if err.exit_error:
2400
      return constants.EXIT_FAILURE
2401
    else:
2402
      return constants.EXIT_SUCCESS
2403
  except errors.ParameterError, err:
2404
    result, err_msg = FormatError(err)
2405
    ToStderr(err_msg)
2406
    return 1
2407

    
2408
  if func is None: # parse error
2409
    return 1
2410

    
2411
  if override is not None:
2412
    for key, val in override.iteritems():
2413
      setattr(options, key, val)
2414

    
2415
  utils.SetupLogging(pathutils.LOG_COMMANDS, logname, debug=options.debug,
2416
                     stderr_logging=True)
2417

    
2418
  logging.info("Command line: %s", cmdline)
2419

    
2420
  try:
2421
    result = func(options, args)
2422
  except (errors.GenericError, luxi.ProtocolError,
2423
          JobSubmittedException), err:
2424
    result, err_msg = FormatError(err)
2425
    logging.exception("Error during command processing")
2426
    ToStderr(err_msg)
2427
  except KeyboardInterrupt:
2428
    result = constants.EXIT_FAILURE
2429
    ToStderr("Aborted. Note that if the operation created any jobs, they"
2430
             " might have been submitted and"
2431
             " will continue to run in the background.")
2432
  except IOError, err:
2433
    if err.errno == errno.EPIPE:
2434
      # our terminal went away, we'll exit
2435
      sys.exit(constants.EXIT_FAILURE)
2436
    else:
2437
      raise
2438

    
2439
  return result
2440

    
2441

    
2442
def ParseNicOption(optvalue):
2443
  """Parses the value of the --net option(s).
2444

2445
  """
2446
  try:
2447
    nic_max = max(int(nidx[0]) + 1 for nidx in optvalue)
2448
  except (TypeError, ValueError), err:
2449
    raise errors.OpPrereqError("Invalid NIC index passed: %s" % str(err),
2450
                               errors.ECODE_INVAL)
2451

    
2452
  nics = [{}] * nic_max
2453
  for nidx, ndict in optvalue:
2454
    nidx = int(nidx)
2455

    
2456
    if not isinstance(ndict, dict):
2457
      raise errors.OpPrereqError("Invalid nic/%d value: expected dict,"
2458
                                 " got %s" % (nidx, ndict), errors.ECODE_INVAL)
2459

    
2460
    utils.ForceDictType(ndict, constants.INIC_PARAMS_TYPES)
2461

    
2462
    nics[nidx] = ndict
2463

    
2464
  return nics
2465

    
2466

    
2467
def GenericInstanceCreate(mode, opts, args):
2468
  """Add an instance to the cluster via either creation or import.
2469

2470
  @param mode: constants.INSTANCE_CREATE or constants.INSTANCE_IMPORT
2471
  @param opts: the command line options selected by the user
2472
  @type args: list
2473
  @param args: should contain only one element, the new instance name
2474
  @rtype: int
2475
  @return: the desired exit code
2476

2477
  """
2478
  instance = args[0]
2479

    
2480
  (pnode, snode) = SplitNodeOption(opts.node)
2481

    
2482
  hypervisor = None
2483
  hvparams = {}
2484
  if opts.hypervisor:
2485
    hypervisor, hvparams = opts.hypervisor
2486

    
2487
  if opts.nics:
2488
    nics = ParseNicOption(opts.nics)
2489
  elif opts.no_nics:
2490
    # no nics
2491
    nics = []
2492
  elif mode == constants.INSTANCE_CREATE:
2493
    # default of one nic, all auto
2494
    nics = [{}]
2495
  else:
2496
    # mode == import
2497
    nics = []
2498

    
2499
  if opts.disk_template == constants.DT_DISKLESS:
2500
    if opts.disks or opts.sd_size is not None:
2501
      raise errors.OpPrereqError("Diskless instance but disk"
2502
                                 " information passed", errors.ECODE_INVAL)
2503
    disks = []
2504
  else:
2505
    if (not opts.disks and not opts.sd_size
2506
        and mode == constants.INSTANCE_CREATE):
2507
      raise errors.OpPrereqError("No disk information specified",
2508
                                 errors.ECODE_INVAL)
2509
    if opts.disks and opts.sd_size is not None:
2510
      raise errors.OpPrereqError("Please use either the '--disk' or"
2511
                                 " '-s' option", errors.ECODE_INVAL)
2512
    if opts.sd_size is not None:
2513
      opts.disks = [(0, {constants.IDISK_SIZE: opts.sd_size})]
2514

    
2515
    if opts.disks:
2516
      try:
2517
        disk_max = max(int(didx[0]) + 1 for didx in opts.disks)
2518
      except ValueError, err:
2519
        raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err),
2520
                                   errors.ECODE_INVAL)
2521
      disks = [{}] * disk_max
2522
    else:
2523
      disks = []
2524
    for didx, ddict in opts.disks:
2525
      didx = int(didx)
2526
      if not isinstance(ddict, dict):
2527
        msg = "Invalid disk/%d value: expected dict, got %s" % (didx, ddict)
2528
        raise errors.OpPrereqError(msg, errors.ECODE_INVAL)
2529
      elif constants.IDISK_SIZE in ddict:
2530
        if constants.IDISK_ADOPT in ddict:
2531
          raise errors.OpPrereqError("Only one of 'size' and 'adopt' allowed"
2532
                                     " (disk %d)" % didx, errors.ECODE_INVAL)
2533
        try:
2534
          ddict[constants.IDISK_SIZE] = \
2535
            utils.ParseUnit(ddict[constants.IDISK_SIZE])
2536
        except ValueError, err:
2537
          raise errors.OpPrereqError("Invalid disk size for disk %d: %s" %
2538
                                     (didx, err), errors.ECODE_INVAL)
2539
      elif constants.IDISK_ADOPT in ddict:
2540
        if mode == constants.INSTANCE_IMPORT:
2541
          raise errors.OpPrereqError("Disk adoption not allowed for instance"
2542
                                     " import", errors.ECODE_INVAL)
2543
        ddict[constants.IDISK_SIZE] = 0
2544
      else:
2545
        raise errors.OpPrereqError("Missing size or adoption source for"
2546
                                   " disk %d" % didx, errors.ECODE_INVAL)
2547
      disks[didx] = ddict
2548

    
2549
  if opts.tags is not None:
2550
    tags = opts.tags.split(",")
2551
  else:
2552
    tags = []
2553

    
2554
  utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_COMPAT)
2555
  utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
2556

    
2557
  if mode == constants.INSTANCE_CREATE:
2558
    start = opts.start
2559
    os_type = opts.os
2560
    force_variant = opts.force_variant
2561
    src_node = None
2562
    src_path = None
2563
    no_install = opts.no_install
2564
    identify_defaults = False
2565
  elif mode == constants.INSTANCE_IMPORT:
2566
    start = False
2567
    os_type = None
2568
    force_variant = False
2569
    src_node = opts.src_node
2570
    src_path = opts.src_dir
2571
    no_install = None
2572
    identify_defaults = opts.identify_defaults
2573
  else:
2574
    raise errors.ProgrammerError("Invalid creation mode %s" % mode)
2575

    
2576
  op = opcodes.OpInstanceCreate(instance_name=instance,
2577
                                disks=disks,
2578
                                disk_template=opts.disk_template,
2579
                                nics=nics,
2580
                                conflicts_check=opts.conflicts_check,
2581
                                pnode=pnode, snode=snode,
2582
                                ip_check=opts.ip_check,
2583
                                name_check=opts.name_check,
2584
                                wait_for_sync=opts.wait_for_sync,
2585
                                file_storage_dir=opts.file_storage_dir,
2586
                                file_driver=opts.file_driver,
2587
                                iallocator=opts.iallocator,
2588
                                hypervisor=hypervisor,
2589
                                hvparams=hvparams,
2590
                                beparams=opts.beparams,
2591
                                osparams=opts.osparams,
2592
                                mode=mode,
2593
                                start=start,
2594
                                os_type=os_type,
2595
                                force_variant=force_variant,
2596
                                src_node=src_node,
2597
                                src_path=src_path,
2598
                                tags=tags,
2599
                                no_install=no_install,
2600
                                identify_defaults=identify_defaults,
2601
                                ignore_ipolicy=opts.ignore_ipolicy)
2602

    
2603
  SubmitOrSend(op, opts)
2604
  return 0
2605

    
2606

    
2607
class _RunWhileClusterStoppedHelper:
2608
  """Helper class for L{RunWhileClusterStopped} to simplify state management
2609

2610
  """
2611
  def __init__(self, feedback_fn, cluster_name, master_node, online_nodes):
2612
    """Initializes this class.
2613

2614
    @type feedback_fn: callable
2615
    @param feedback_fn: Feedback function
2616
    @type cluster_name: string
2617
    @param cluster_name: Cluster name
2618
    @type master_node: string
2619
    @param master_node Master node name
2620
    @type online_nodes: list
2621
    @param online_nodes: List of names of online nodes
2622

2623
    """
2624
    self.feedback_fn = feedback_fn
2625
    self.cluster_name = cluster_name
2626
    self.master_node = master_node
2627
    self.online_nodes = online_nodes
2628

    
2629
    self.ssh = ssh.SshRunner(self.cluster_name)
2630

    
2631
    self.nonmaster_nodes = [name for name in online_nodes
2632
                            if name != master_node]
2633

    
2634
    assert self.master_node not in self.nonmaster_nodes
2635

    
2636
  def _RunCmd(self, node_name, cmd):
2637
    """Runs a command on the local or a remote machine.
2638

2639
    @type node_name: string
2640
    @param node_name: Machine name
2641
    @type cmd: list
2642
    @param cmd: Command
2643

2644
    """
2645
    if node_name is None or node_name == self.master_node:
2646
      # No need to use SSH
2647
      result = utils.RunCmd(cmd)
2648
    else:
2649
      result = self.ssh.Run(node_name, constants.SSH_LOGIN_USER,
2650
                            utils.ShellQuoteArgs(cmd))
2651

    
2652
    if result.failed:
2653
      errmsg = ["Failed to run command %s" % result.cmd]
2654
      if node_name:
2655
        errmsg.append("on node %s" % node_name)
2656
      errmsg.append(": exitcode %s and error %s" %
2657
                    (result.exit_code, result.output))
2658
      raise errors.OpExecError(" ".join(errmsg))
2659

    
2660
  def Call(self, fn, *args):
2661
    """Call function while all daemons are stopped.
2662

2663
    @type fn: callable
2664
    @param fn: Function to be called
2665

2666
    """
2667
    # Pause watcher by acquiring an exclusive lock on watcher state file
2668
    self.feedback_fn("Blocking watcher")
2669
    watcher_block = utils.FileLock.Open(pathutils.WATCHER_LOCK_FILE)
2670
    try:
2671
      # TODO: Currently, this just blocks. There's no timeout.
2672
      # TODO: Should it be a shared lock?
2673
      watcher_block.Exclusive(blocking=True)
2674

    
2675
      # Stop master daemons, so that no new jobs can come in and all running
2676
      # ones are finished
2677
      self.feedback_fn("Stopping master daemons")
2678
      self._RunCmd(None, [pathutils.DAEMON_UTIL, "stop-master"])
2679
      try:
2680
        # Stop daemons on all nodes
2681
        for node_name in self.online_nodes:
2682
          self.feedback_fn("Stopping daemons on %s" % node_name)
2683
          self._RunCmd(node_name, [pathutils.DAEMON_UTIL, "stop-all"])
2684

    
2685
        # All daemons are shut down now
2686
        try:
2687
          return fn(self, *args)
2688
        except Exception, err:
2689
          _, errmsg = FormatError(err)
2690
          logging.exception("Caught exception")
2691
          self.feedback_fn(errmsg)
2692
          raise
2693
      finally:
2694
        # Start cluster again, master node last
2695
        for node_name in self.nonmaster_nodes + [self.master_node]:
2696
          self.feedback_fn("Starting daemons on %s" % node_name)
2697
          self._RunCmd(node_name, [pathutils.DAEMON_UTIL, "start-all"])
2698
    finally:
2699
      # Resume watcher
2700
      watcher_block.Close()
2701

    
2702

    
2703
def RunWhileClusterStopped(feedback_fn, fn, *args):
2704
  """Calls a function while all cluster daemons are stopped.
2705

2706
  @type feedback_fn: callable
2707
  @param feedback_fn: Feedback function
2708
  @type fn: callable
2709
  @param fn: Function to be called when daemons are stopped
2710

2711
  """
2712
  feedback_fn("Gathering cluster information")
2713

    
2714
  # This ensures we're running on the master daemon
2715
  cl = GetClient()
2716

    
2717
  (cluster_name, master_node) = \
2718
    cl.QueryConfigValues(["cluster_name", "master_node"])
2719

    
2720
  online_nodes = GetOnlineNodes([], cl=cl)
2721

    
2722
  # Don't keep a reference to the client. The master daemon will go away.
2723
  del cl
2724

    
2725
  assert master_node in online_nodes
2726

    
2727
  return _RunWhileClusterStoppedHelper(feedback_fn, cluster_name, master_node,
2728
                                       online_nodes).Call(fn, *args)
2729

    
2730

    
2731
def GenerateTable(headers, fields, separator, data,
2732
                  numfields=None, unitfields=None,
2733
                  units=None):
2734
  """Prints a table with headers and different fields.
2735

2736
  @type headers: dict
2737
  @param headers: dictionary mapping field names to headers for
2738
      the table
2739
  @type fields: list
2740
  @param fields: the field names corresponding to each row in
2741
      the data field
2742
  @param separator: the separator to be used; if this is None,
2743
      the default 'smart' algorithm is used which computes optimal
2744
      field width, otherwise just the separator is used between
2745
      each field
2746
  @type data: list
2747
  @param data: a list of lists, each sublist being one row to be output
2748
  @type numfields: list
2749
  @param numfields: a list with the fields that hold numeric
2750
      values and thus should be right-aligned
2751
  @type unitfields: list
2752
  @param unitfields: a list with the fields that hold numeric
2753
      values that should be formatted with the units field
2754
  @type units: string or None
2755
  @param units: the units we should use for formatting, or None for
2756
      automatic choice (human-readable for non-separator usage, otherwise
2757
      megabytes); this is a one-letter string
2758

2759
  """
2760
  if units is None:
2761
    if separator:
2762
      units = "m"
2763
    else:
2764
      units = "h"
2765

    
2766
  if numfields is None:
2767
    numfields = []
2768
  if unitfields is None:
2769
    unitfields = []
2770

    
2771
  numfields = utils.FieldSet(*numfields)   # pylint: disable=W0142
2772
  unitfields = utils.FieldSet(*unitfields) # pylint: disable=W0142
2773

    
2774
  format_fields = []
2775
  for field in fields:
2776
    if headers and field not in headers:
2777
      # TODO: handle better unknown fields (either revert to old
2778
      # style of raising exception, or deal more intelligently with
2779
      # variable fields)
2780
      headers[field] = field
2781
    if separator is not None:
2782
      format_fields.append("%s")
2783
    elif numfields.Matches(field):
2784
      format_fields.append("%*s")
2785
    else:
2786
      format_fields.append("%-*s")
2787

    
2788
  if separator is None:
2789
    mlens = [0 for name in fields]
2790
    format_str = " ".join(format_fields)
2791
  else:
2792
    format_str = separator.replace("%", "%%").join(format_fields)
2793

    
2794
  for row in data:
2795
    if row is None:
2796
      continue
2797
    for idx, val in enumerate(row):
2798
      if unitfields.Matches(fields[idx]):
2799
        try:
2800
          val = int(val)
2801
        except (TypeError, ValueError):
2802
          pass
2803
        else:
2804
          val = row[idx] = utils.FormatUnit(val, units)
2805
      val = row[idx] = str(val)
2806
      if separator is None:
2807
        mlens[idx] = max(mlens[idx], len(val))
2808

    
2809
  result = []
2810
  if headers:
2811
    args = []
2812
    for idx, name in enumerate(fields):
2813
      hdr = headers[name]
2814
      if separator is None:
2815
        mlens[idx] = max(mlens[idx], len(hdr))
2816
        args.append(mlens[idx])
2817
      args.append(hdr)
2818
    result.append(format_str % tuple(args))
2819

    
2820
  if separator is None:
2821
    assert len(mlens) == len(fields)
2822

    
2823
    if fields and not numfields.Matches(fields[-1]):
2824
      mlens[-1] = 0
2825

    
2826
  for line in data:
2827
    args = []
2828
    if line is None:
2829
      line = ["-" for _ in fields]
2830
    for idx in range(len(fields)):
2831
      if separator is None:
2832
        args.append(mlens[idx])
2833
      args.append(line[idx])
2834
    result.append(format_str % tuple(args))
2835

    
2836
  return result
2837

    
2838

    
2839
def _FormatBool(value):
2840
  """Formats a boolean value as a string.
2841

2842
  """
2843
  if value:
2844
    return "Y"
2845
  return "N"
2846

    
2847

    
2848
#: Default formatting for query results; (callback, align right)
2849
_DEFAULT_FORMAT_QUERY = {
2850
  constants.QFT_TEXT: (str, False),
2851
  constants.QFT_BOOL: (_FormatBool, False),
2852
  constants.QFT_NUMBER: (str, True),
2853
  constants.QFT_TIMESTAMP: (utils.FormatTime, False),
2854
  constants.QFT_OTHER: (str, False),
2855
  constants.QFT_UNKNOWN: (str, False),
2856
  }
2857

    
2858

    
2859
def _GetColumnFormatter(fdef, override, unit):
2860
  """Returns formatting function for a field.
2861

2862
  @type fdef: L{objects.QueryFieldDefinition}
2863
  @type override: dict
2864
  @param override: Dictionary for overriding field formatting functions,
2865
    indexed by field name, contents like L{_DEFAULT_FORMAT_QUERY}
2866
  @type unit: string
2867
  @param unit: Unit used for formatting fields of type L{constants.QFT_UNIT}
2868
  @rtype: tuple; (callable, bool)
2869
  @return: Returns the function to format a value (takes one parameter) and a
2870
    boolean for aligning the value on the right-hand side
2871

2872
  """
2873
  fmt = override.get(fdef.name, None)
2874
  if fmt is not None:
2875
    return fmt
2876

    
2877
  assert constants.QFT_UNIT not in _DEFAULT_FORMAT_QUERY
2878

    
2879
  if fdef.kind == constants.QFT_UNIT:
2880
    # Can't keep this information in the static dictionary
2881
    return (lambda value: utils.FormatUnit(value, unit), True)
2882

    
2883
  fmt = _DEFAULT_FORMAT_QUERY.get(fdef.kind, None)
2884
  if fmt is not None:
2885
    return fmt
2886

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

    
2889

    
2890
class _QueryColumnFormatter:
2891
  """Callable class for formatting fields of a query.
2892

2893
  """
2894
  def __init__(self, fn, status_fn, verbose):
2895
    """Initializes this class.
2896

2897
    @type fn: callable
2898
    @param fn: Formatting function
2899
    @type status_fn: callable
2900
    @param status_fn: Function to report fields' status
2901
    @type verbose: boolean
2902
    @param verbose: whether to use verbose field descriptions or not
2903

2904
    """
2905
    self._fn = fn
2906
    self._status_fn = status_fn
2907
    self._verbose = verbose
2908

    
2909
  def __call__(self, data):
2910
    """Returns a field's string representation.
2911

2912
    """
2913
    (status, value) = data
2914

    
2915
    # Report status
2916
    self._status_fn(status)
2917

    
2918
    if status == constants.RS_NORMAL:
2919
      return self._fn(value)
2920

    
2921
    assert value is None, \
2922
           "Found value %r for abnormal status %s" % (value, status)
2923

    
2924
    return FormatResultError(status, self._verbose)
2925

    
2926

    
2927
def FormatResultError(status, verbose):
2928
  """Formats result status other than L{constants.RS_NORMAL}.
2929

2930
  @param status: The result status
2931
  @type verbose: boolean
2932
  @param verbose: Whether to return the verbose text
2933
  @return: Text of result status
2934

2935
  """
2936
  assert status != constants.RS_NORMAL, \
2937
         "FormatResultError called with status equal to constants.RS_NORMAL"
2938
  try:
2939
    (verbose_text, normal_text) = constants.RSS_DESCRIPTION[status]
2940
  except KeyError:
2941
    raise NotImplementedError("Unknown status %s" % status)
2942
  else:
2943
    if verbose:
2944
      return verbose_text
2945
    return normal_text
2946

    
2947

    
2948
def FormatQueryResult(result, unit=None, format_override=None, separator=None,
2949
                      header=False, verbose=False):
2950
  """Formats data in L{objects.QueryResponse}.
2951

2952
  @type result: L{objects.QueryResponse}
2953
  @param result: result of query operation
2954
  @type unit: string
2955
  @param unit: Unit used for formatting fields of type L{constants.QFT_UNIT},
2956
    see L{utils.text.FormatUnit}
2957
  @type format_override: dict
2958
  @param format_override: Dictionary for overriding field formatting functions,
2959
    indexed by field name, contents like L{_DEFAULT_FORMAT_QUERY}
2960
  @type separator: string or None
2961
  @param separator: String used to separate fields
2962
  @type header: bool
2963
  @param header: Whether to output header row
2964
  @type verbose: boolean
2965
  @param verbose: whether to use verbose field descriptions or not
2966

2967
  """
2968
  if unit is None:
2969
    if separator:
2970
      unit = "m"
2971
    else:
2972
      unit = "h"
2973

    
2974
  if format_override is None:
2975
    format_override = {}
2976

    
2977
  stats = dict.fromkeys(constants.RS_ALL, 0)
2978

    
2979
  def _RecordStatus(status):
2980
    if status in stats:
2981
      stats[status] += 1
2982

    
2983
  columns = []
2984
  for fdef in result.fields:
2985
    assert fdef.title and fdef.name
2986
    (fn, align_right) = _GetColumnFormatter(fdef, format_override, unit)
2987
    columns.append(TableColumn(fdef.title,
2988
                               _QueryColumnFormatter(fn, _RecordStatus,
2989
                                                     verbose),
2990
                               align_right))
2991

    
2992
  table = FormatTable(result.data, columns, header, separator)
2993

    
2994
  # Collect statistics
2995
  assert len(stats) == len(constants.RS_ALL)
2996
  assert compat.all(count >= 0 for count in stats.values())
2997

    
2998
  # Determine overall status. If there was no data, unknown fields must be
2999
  # detected via the field definitions.
3000
  if (stats[constants.RS_UNKNOWN] or
3001
      (not result.data and _GetUnknownFields(result.fields))):
3002
    status = QR_UNKNOWN
3003
  elif compat.any(count > 0 for key, count in stats.items()
3004
                  if key != constants.RS_NORMAL):
3005
    status = QR_INCOMPLETE
3006
  else:
3007
    status = QR_NORMAL
3008

    
3009
  return (status, table)
3010

    
3011

    
3012
def _GetUnknownFields(fdefs):
3013
  """Returns list of unknown fields included in C{fdefs}.
3014

3015
  @type fdefs: list of L{objects.QueryFieldDefinition}
3016

3017
  """
3018
  return [fdef for fdef in fdefs
3019
          if fdef.kind == constants.QFT_UNKNOWN]
3020

    
3021

    
3022
def _WarnUnknownFields(fdefs):
3023
  """Prints a warning to stderr if a query included unknown fields.
3024

3025
  @type fdefs: list of L{objects.QueryFieldDefinition}
3026

3027
  """
3028
  unknown = _GetUnknownFields(fdefs)
3029
  if unknown:
3030
    ToStderr("Warning: Queried for unknown fields %s",
3031
             utils.CommaJoin(fdef.name for fdef in unknown))
3032
    return True
3033

    
3034
  return False
3035

    
3036

    
3037
def GenericList(resource, fields, names, unit, separator, header, cl=None,
3038
                format_override=None, verbose=False, force_filter=False,
3039
                namefield=None, qfilter=None, isnumeric=False):
3040
  """Generic implementation for listing all items of a resource.
3041

3042
  @param resource: One of L{constants.QR_VIA_LUXI}
3043
  @type fields: list of strings
3044
  @param fields: List of fields to query for
3045
  @type names: list of strings
3046
  @param names: Names of items to query for
3047
  @type unit: string or None
3048
  @param unit: Unit used for formatting fields of type L{constants.QFT_UNIT} or
3049
    None for automatic choice (human-readable for non-separator usage,
3050
    otherwise megabytes); this is a one-letter string
3051
  @type separator: string or None
3052
  @param separator: String used to separate fields
3053
  @type header: bool
3054
  @param header: Whether to show header row
3055
  @type force_filter: bool
3056
  @param force_filter: Whether to always treat names as filter
3057
  @type format_override: dict
3058
  @param format_override: Dictionary for overriding field formatting functions,
3059
    indexed by field name, contents like L{_DEFAULT_FORMAT_QUERY}
3060
  @type verbose: boolean
3061
  @param verbose: whether to use verbose field descriptions or not
3062
  @type namefield: string
3063
  @param namefield: Name of field to use for simple filters (see
3064
    L{qlang.MakeFilter} for details)
3065
  @type qfilter: list or None
3066
  @param qfilter: Query filter (in addition to names)
3067
  @param isnumeric: bool
3068
  @param isnumeric: Whether the namefield's type is numeric, and therefore
3069
    any simple filters built by namefield should use integer values to
3070
    reflect that
3071

3072
  """
3073
  if not names:
3074
    names = None
3075

    
3076
  namefilter = qlang.MakeFilter(names, force_filter, namefield=namefield,
3077
                                isnumeric=isnumeric)
3078

    
3079
  if qfilter is None:
3080
    qfilter = namefilter
3081
  elif namefilter is not None:
3082
    qfilter = [qlang.OP_AND, namefilter, qfilter]
3083

    
3084
  if cl is None:
3085
    cl = GetClient()
3086

    
3087
  response = cl.Query(resource, fields, qfilter)
3088

    
3089
  found_unknown = _WarnUnknownFields(response.fields)
3090

    
3091
  (status, data) = FormatQueryResult(response, unit=unit, separator=separator,
3092
                                     header=header,
3093
                                     format_override=format_override,
3094
                                     verbose=verbose)
3095

    
3096
  for line in data:
3097
    ToStdout(line)
3098

    
3099
  assert ((found_unknown and status == QR_UNKNOWN) or
3100
          (not found_unknown and status != QR_UNKNOWN))
3101

    
3102
  if status == QR_UNKNOWN:
3103
    return constants.EXIT_UNKNOWN_FIELD
3104

    
3105
  # TODO: Should the list command fail if not all data could be collected?
3106
  return constants.EXIT_SUCCESS
3107

    
3108

    
3109
def _FieldDescValues(fdef):
3110
  """Helper function for L{GenericListFields} to get query field description.
3111

3112
  @type fdef: L{objects.QueryFieldDefinition}
3113
  @rtype: list
3114

3115
  """
3116
  return [
3117
    fdef.name,
3118
    _QFT_NAMES.get(fdef.kind, fdef.kind),
3119
    fdef.title,
3120
    fdef.doc,
3121
    ]
3122

    
3123

    
3124
def GenericListFields(resource, fields, separator, header, cl=None):
3125
  """Generic implementation for listing fields for a resource.
3126

3127
  @param resource: One of L{constants.QR_VIA_LUXI}
3128
  @type fields: list of strings
3129
  @param fields: List of fields to query for
3130
  @type separator: string or None
3131
  @param separator: String used to separate fields
3132
  @type header: bool
3133
  @param header: Whether to show header row
3134

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

    
3139
  if not fields:
3140
    fields = None
3141

    
3142
  response = cl.QueryFields(resource, fields)
3143

    
3144
  found_unknown = _WarnUnknownFields(response.fields)
3145

    
3146
  columns = [
3147
    TableColumn("Name", str, False),
3148
    TableColumn("Type", str, False),
3149
    TableColumn("Title", str, False),
3150
    TableColumn("Description", str, False),
3151
    ]
3152

    
3153
  rows = map(_FieldDescValues, response.fields)
3154

    
3155
  for line in FormatTable(rows, columns, header, separator):
3156
    ToStdout(line)
3157

    
3158
  if found_unknown:
3159
    return constants.EXIT_UNKNOWN_FIELD
3160

    
3161
  return constants.EXIT_SUCCESS
3162

    
3163

    
3164
class TableColumn:
3165
  """Describes a column for L{FormatTable}.
3166

3167
  """
3168
  def __init__(self, title, fn, align_right):
3169
    """Initializes this class.
3170

3171
    @type title: string
3172
    @param title: Column title
3173
    @type fn: callable
3174
    @param fn: Formatting function
3175
    @type align_right: bool
3176
    @param align_right: Whether to align values on the right-hand side
3177

3178
    """
3179
    self.title = title
3180
    self.format = fn
3181
    self.align_right = align_right
3182

    
3183

    
3184
def _GetColFormatString(width, align_right):
3185
  """Returns the format string for a field.
3186

3187
  """
3188
  if align_right:
3189
    sign = ""
3190
  else:
3191
    sign = "-"
3192

    
3193
  return "%%%s%ss" % (sign, width)
3194

    
3195

    
3196
def FormatTable(rows, columns, header, separator):
3197
  """Formats data as a table.
3198

3199
  @type rows: list of lists
3200
  @param rows: Row data, one list per row
3201
  @type columns: list of L{TableColumn}
3202
  @param columns: Column descriptions
3203
  @type header: bool
3204
  @param header: Whether to show header row
3205
  @type separator: string or None
3206
  @param separator: String used to separate columns
3207

3208
  """
3209
  if header:
3210
    data = [[col.title for col in columns]]
3211
    colwidth = [len(col.title) for col in columns]
3212
  else:
3213
    data = []
3214
    colwidth = [0 for _ in columns]
3215

    
3216
  # Format row data
3217
  for row in rows:
3218
    assert len(row) == len(columns)
3219

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

    
3222
    if separator is None:
3223
      # Update column widths
3224
      for idx, (oldwidth, value) in enumerate(zip(colwidth, formatted)):
3225
        # Modifying a list's items while iterating is fine
3226
        colwidth[idx] = max(oldwidth, len(value))
3227

    
3228
    data.append(formatted)
3229

    
3230
  if separator is not None:
3231
    # Return early if a separator is used
3232
    return [separator.join(row) for row in data]
3233

    
3234
  if columns and not columns[-1].align_right:
3235
    # Avoid unnecessary spaces at end of line
3236
    colwidth[-1] = 0
3237

    
3238
  # Build format string
3239
  fmt = " ".join([_GetColFormatString(width, col.align_right)
3240
                  for col, width in zip(columns, colwidth)])
3241

    
3242
  return [fmt % tuple(row) for row in data]
3243

    
3244

    
3245
def FormatTimestamp(ts):
3246
  """Formats a given timestamp.
3247

3248
  @type ts: timestamp
3249
  @param ts: a timeval-type timestamp, a tuple of seconds and microseconds
3250

3251
  @rtype: string
3252
  @return: a string with the formatted timestamp
3253

3254
  """
3255
  if not isinstance(ts, (tuple, list)) or len(ts) != 2:
3256
    return "?"
3257

    
3258
  (sec, usecs) = ts
3259
  return utils.FormatTime(sec, usecs=usecs)
3260

    
3261

    
3262
def ParseTimespec(value):
3263
  """Parse a time specification.
3264

3265
  The following suffixed will be recognized:
3266

3267
    - s: seconds
3268
    - m: minutes
3269
    - h: hours
3270
    - d: day
3271
    - w: weeks
3272

3273
  Without any suffix, the value will be taken to be in seconds.
3274

3275
  """
3276
  value = str(value)
3277
  if not value:
3278
    raise errors.OpPrereqError("Empty time specification passed",
3279
                               errors.ECODE_INVAL)
3280
  suffix_map = {
3281
    "s": 1,
3282
    "m": 60,
3283
    "h": 3600,
3284
    "d": 86400,
3285
    "w": 604800,
3286
    }
3287
  if value[-1] not in suffix_map:
3288
    try:
3289
      value = int(value)
3290
    except (TypeError, ValueError):
3291
      raise errors.OpPrereqError("Invalid time specification '%s'" % value,
3292
                                 errors.ECODE_INVAL)
3293
  else:
3294
    multiplier = suffix_map[value[-1]]
3295
    value = value[:-1]
3296
    if not value: # no data left after stripping the suffix
3297
      raise errors.OpPrereqError("Invalid time specification (only"
3298
                                 " suffix passed)", errors.ECODE_INVAL)
3299
    try:
3300
      value = int(value) * multiplier
3301
    except (TypeError, ValueError):
3302
      raise errors.OpPrereqError("Invalid time specification '%s'" % value,
3303
                                 errors.ECODE_INVAL)
3304
  return value
3305

    
3306

    
3307
def GetOnlineNodes(nodes, cl=None, nowarn=False, secondary_ips=False,
3308
                   filter_master=False, nodegroup=None):
3309
  """Returns the names of online nodes.
3310

3311
  This function will also log a warning on stderr with the names of
3312
  the online nodes.
3313

3314
  @param nodes: if not empty, use only this subset of nodes (minus the
3315
      offline ones)
3316
  @param cl: if not None, luxi client to use
3317
  @type nowarn: boolean
3318
  @param nowarn: by default, this function will output a note with the
3319
      offline nodes that are skipped; if this parameter is True the
3320
      note is not displayed
3321
  @type secondary_ips: boolean
3322
  @param secondary_ips: if True, return the secondary IPs instead of the
3323
      names, useful for doing network traffic over the replication interface
3324
      (if any)
3325
  @type filter_master: boolean
3326
  @param filter_master: if True, do not return the master node in the list
3327
      (useful in coordination with secondary_ips where we cannot check our
3328
      node name against the list)
3329
  @type nodegroup: string
3330
  @param nodegroup: If set, only return nodes in this node group
3331

3332
  """
3333
  if cl is None:
3334
    cl = GetClient()
3335

    
3336
  qfilter = []
3337

    
3338
  if nodes:
3339
    qfilter.append(qlang.MakeSimpleFilter("name", nodes))
3340

    
3341
  if nodegroup is not None:
3342
    qfilter.append([qlang.OP_OR, [qlang.OP_EQUAL, "group", nodegroup],
3343
                                 [qlang.OP_EQUAL, "group.uuid", nodegroup]])
3344

    
3345
  if filter_master:
3346
    qfilter.append([qlang.OP_NOT, [qlang.OP_TRUE, "master"]])
3347

    
3348
  if qfilter:
3349
    if len(qfilter) > 1:
3350
      final_filter = [qlang.OP_AND] + qfilter
3351
    else:
3352
      assert len(qfilter) == 1
3353
      final_filter = qfilter[0]
3354
  else:
3355
    final_filter = None
3356

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

    
3359
  def _IsOffline(row):
3360
    (_, (_, offline), _) = row
3361
    return offline
3362

    
3363
  def _GetName(row):
3364
    ((_, name), _, _) = row
3365
    return name
3366

    
3367
  def _GetSip(row):
3368
    (_, _, (_, sip)) = row
3369
    return sip
3370

    
3371
  (offline, online) = compat.partition(result.data, _IsOffline)
3372

    
3373
  if offline and not nowarn:
3374
    ToStderr("Note: skipping offline node(s): %s" %
3375
             utils.CommaJoin(map(_GetName, offline)))
3376

    
3377
  if secondary_ips:
3378
    fn = _GetSip
3379
  else:
3380
    fn = _GetName
3381

    
3382
  return map(fn, online)
3383

    
3384

    
3385
def _ToStream(stream, txt, *args):
3386
  """Write a message to a stream, bypassing the logging system
3387

3388
  @type stream: file object
3389
  @param stream: the file to which we should write
3390
  @type txt: str
3391
  @param txt: the message
3392

3393
  """
3394
  try:
3395
    if args:
3396
      args = tuple(args)
3397
      stream.write(txt % args)
3398
    else:
3399
      stream.write(txt)
3400
    stream.write("\n")
3401
    stream.flush()
3402
  except IOError, err:
3403
    if err.errno == errno.EPIPE:
3404
      # our terminal went away, we'll exit
3405
      sys.exit(constants.EXIT_FAILURE)
3406
    else:
3407
      raise
3408

    
3409

    
3410
def ToStdout(txt, *args):
3411
  """Write a message to stdout only, bypassing the logging system
3412

3413
  This is just a wrapper over _ToStream.
3414

3415
  @type txt: str
3416
  @param txt: the message
3417

3418
  """
3419
  _ToStream(sys.stdout, txt, *args)
3420

    
3421

    
3422
def ToStderr(txt, *args):
3423
  """Write a message to stderr only, bypassing the logging system
3424

3425
  This is just a wrapper over _ToStream.
3426

3427
  @type txt: str
3428
  @param txt: the message
3429

3430
  """
3431
  _ToStream(sys.stderr, txt, *args)
3432

    
3433

    
3434
class JobExecutor(object):
3435
  """Class which manages the submission and execution of multiple jobs.
3436

3437
  Note that instances of this class should not be reused between
3438
  GetResults() calls.
3439

3440
  """
3441
  def __init__(self, cl=None, verbose=True, opts=None, feedback_fn=None):
3442
    self.queue = []
3443
    if cl is None:
3444
      cl = GetClient()
3445
    self.cl = cl
3446
    self.verbose = verbose
3447
    self.jobs = []
3448
    self.opts = opts
3449
    self.feedback_fn = feedback_fn
3450
    self._counter = itertools.count()
3451

    
3452
  @staticmethod
3453
  def _IfName(name, fmt):
3454
    """Helper function for formatting name.
3455

3456
    """
3457
    if name:
3458
      return fmt % name
3459

    
3460
    return ""
3461

    
3462
  def QueueJob(self, name, *ops):
3463
    """Record a job for later submit.
3464

3465
    @type name: string
3466
    @param name: a description of the job, will be used in WaitJobSet
3467

3468
    """
3469
    SetGenericOpcodeOpts(ops, self.opts)
3470
    self.queue.append((self._counter.next(), name, ops))
3471

    
3472
  def AddJobId(self, name, status, job_id):
3473
    """Adds a job ID to the internal queue.
3474

3475
    """
3476
    self.jobs.append((self._counter.next(), status, job_id, name))
3477

    
3478
  def SubmitPending(self, each=False):
3479
    """Submit all pending jobs.
3480

3481
    """
3482
    if each:
3483
      results = []
3484
      for (_, _, ops) in self.queue:
3485
        # SubmitJob will remove the success status, but raise an exception if
3486
        # the submission fails, so we'll notice that anyway.
3487
        results.append([True, self.cl.SubmitJob(ops)[0]])
3488
    else:
3489
      results = self.cl.SubmitManyJobs([ops for (_, _, ops) in self.queue])
3490
    for ((status, data), (idx, name, _)) in zip(results, self.queue):
3491
      self.jobs.append((idx, status, data, name))
3492

    
3493
  def _ChooseJob(self):
3494
    """Choose a non-waiting/queued job to poll next.
3495

3496
    """
3497
    assert self.jobs, "_ChooseJob called with empty job list"
3498

    
3499
    result = self.cl.QueryJobs([i[2] for i in self.jobs[:_CHOOSE_BATCH]],
3500
                               ["status"])
3501
    assert result
3502

    
3503
    for job_data, status in zip(self.jobs, result):
3504
      if (isinstance(status, list) and status and
3505
          status[0] in (constants.JOB_STATUS_QUEUED,
3506
                        constants.JOB_STATUS_WAITING,
3507
                        constants.JOB_STATUS_CANCELING)):
3508
        # job is still present and waiting
3509
        continue
3510
      # good candidate found (either running job or lost job)
3511
      self.jobs.remove(job_data)
3512
      return job_data
3513

    
3514
    # no job found
3515
    return self.jobs.pop(0)
3516

    
3517
  def GetResults(self):
3518
    """Wait for and return the results of all jobs.
3519

3520
    @rtype: list
3521
    @return: list of tuples (success, job results), in the same order
3522
        as the submitted jobs; if a job has failed, instead of the result
3523
        there will be the error message
3524

3525
    """
3526
    if not self.jobs:
3527
      self.SubmitPending()
3528
    results = []
3529
    if self.verbose:
3530
      ok_jobs = [row[2] for row in self.jobs if row[1]]
3531
      if ok_jobs:
3532
        ToStdout("Submitted jobs %s", utils.CommaJoin(ok_jobs))
3533

    
3534
    # first, remove any non-submitted jobs
3535
    self.jobs, failures = compat.partition(self.jobs, lambda x: x[1])
3536
    for idx, _, jid, name in failures:
3537
      ToStderr("Failed to submit job%s: %s", self._IfName(name, " for %s"), jid)
3538
      results.append((idx, False, jid))
3539

    
3540
    while self.jobs:
3541
      (idx, _, jid, name) = self._ChooseJob()
3542
      ToStdout("Waiting for job %s%s ...", jid, self._IfName(name, " for %s"))
3543
      try:
3544
        job_result = PollJob(jid, cl=self.cl, feedback_fn=self.feedback_fn)
3545
        success = True
3546
      except errors.JobLost, err:
3547
        _, job_result = FormatError(err)
3548
        ToStderr("Job %s%s has been archived, cannot check its result",
3549
                 jid, self._IfName(name, " for %s"))
3550
        success = False
3551
      except (errors.GenericError, luxi.ProtocolError), err:
3552
        _, job_result = FormatError(err)
3553
        success = False
3554
        # the error message will always be shown, verbose or not
3555
        ToStderr("Job %s%s has failed: %s",
3556
                 jid, self._IfName(name, " for %s"), job_result)
3557

    
3558
      results.append((idx, success, job_result))
3559

    
3560
    # sort based on the index, then drop it
3561
    results.sort()
3562
    results = [i[1:] for i in results]
3563

    
3564
    return results
3565

    
3566
  def WaitOrShow(self, wait):
3567
    """Wait for job results or only print the job IDs.
3568

3569
    @type wait: boolean
3570
    @param wait: whether to wait or not
3571

3572
    """
3573
    if wait:
3574
      return self.GetResults()
3575
    else:
3576
      if not self.jobs:
3577
        self.SubmitPending()
3578
      for _, status, result, name in self.jobs:
3579
        if status:
3580
          ToStdout("%s: %s", result, name)
3581
        else:
3582
          ToStderr("Failure for %s: %s", name, result)
3583
      return [row[1:3] for row in self.jobs]
3584

    
3585

    
3586
def FormatParameterDict(buf, param_dict, actual, level=1):
3587
  """Formats a parameter dictionary.
3588

3589
  @type buf: L{StringIO}
3590
  @param buf: the buffer into which to write
3591
  @type param_dict: dict
3592
  @param param_dict: the own parameters
3593
  @type actual: dict
3594
  @param actual: the current parameter set (including defaults)
3595
  @param level: Level of indent
3596

3597
  """
3598
  indent = "  " * level
3599

    
3600
  for key in sorted(actual):
3601
    data = actual[key]
3602
    buf.write("%s- %s:" % (indent, key))
3603

    
3604
    if isinstance(data, dict) and data:
3605
      buf.write("\n")
3606
      FormatParameterDict(buf, param_dict.get(key, {}), data,
3607
                          level=level + 1)
3608
    else:
3609
      val = param_dict.get(key, "default (%s)" % data)
3610
      buf.write(" %s\n" % val)
3611

    
3612

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

3616
  This function is used to request confirmation for doing an operation
3617
  on a given list of list_type.
3618

3619
  @type names: list
3620
  @param names: the list of names that we display when
3621
      we ask for confirmation
3622
  @type list_type: str
3623
  @param list_type: Human readable name for elements in the list (e.g. nodes)
3624
  @type text: str
3625
  @param text: the operation that the user should confirm
3626
  @rtype: boolean
3627
  @return: True or False depending on user's confirmation.
3628

3629
  """
3630
  count = len(names)
3631
  msg = ("The %s will operate on %d %s.\n%s"
3632
         "Do you want to continue?" % (text, count, list_type, extra))
3633
  affected = (("\nAffected %s:\n" % list_type) +
3634
              "\n".join(["  %s" % name for name in names]))
3635

    
3636
  choices = [("y", True, "Yes, execute the %s" % text),
3637
             ("n", False, "No, abort the %s" % text)]
3638

    
3639
  if count > 20:
3640
    choices.insert(1, ("v", "v", "View the list of affected %s" % list_type))
3641
    question = msg
3642
  else:
3643
    question = msg + affected
3644

    
3645
  choice = AskUser(question, choices)
3646
  if choice == "v":
3647
    choices.pop(1)
3648
    choice = AskUser(msg + affected, choices)
3649
  return choice
3650

    
3651

    
3652
def _MaybeParseUnit(elements):
3653
  """Parses and returns an array of potential values with units.
3654

3655
  """
3656
  parsed = {}
3657
  for k, v in elements.items():
3658
    if v == constants.VALUE_DEFAULT:
3659
      parsed[k] = v
3660
    else:
3661
      parsed[k] = utils.ParseUnit(v)
3662
  return parsed
3663

    
3664

    
3665
def CreateIPolicyFromOpts(ispecs_mem_size=None,
3666
                          ispecs_cpu_count=None,
3667
                          ispecs_disk_count=None,
3668
                          ispecs_disk_size=None,
3669
                          ispecs_nic_count=None,
3670
                          ipolicy_disk_templates=None,
3671
                          ipolicy_vcpu_ratio=None,
3672
                          ipolicy_spindle_ratio=None,
3673
                          group_ipolicy=False,
3674
                          allowed_values=None,
3675
                          fill_all=False):
3676
  """Creation of instance policy based on command line options.
3677

3678
  @param fill_all: whether for cluster policies we should ensure that
3679
    all values are filled
3680

3681

3682
  """
3683
  try:
3684
    if ispecs_mem_size:
3685
      ispecs_mem_size = _MaybeParseUnit(ispecs_mem_size)
3686
    if ispecs_disk_size:
3687
      ispecs_disk_size = _MaybeParseUnit(ispecs_disk_size)
3688
  except (TypeError, ValueError, errors.UnitParseError), err:
3689
    raise errors.OpPrereqError("Invalid disk (%s) or memory (%s) size"
3690
                               " in policy: %s" %
3691
                               (ispecs_disk_size, ispecs_mem_size, err),
3692
                               errors.ECODE_INVAL)
3693

    
3694
  # prepare ipolicy dict
3695
  ipolicy_transposed = {
3696
    constants.ISPEC_MEM_SIZE: ispecs_mem_size,
3697
    constants.ISPEC_CPU_COUNT: ispecs_cpu_count,
3698
    constants.ISPEC_DISK_COUNT: ispecs_disk_count,
3699
    constants.ISPEC_DISK_SIZE: ispecs_disk_size,
3700
    constants.ISPEC_NIC_COUNT: ispecs_nic_count,
3701
    }
3702

    
3703
  # first, check that the values given are correct
3704
  if group_ipolicy:
3705
    forced_type = TISPECS_GROUP_TYPES
3706
  else:
3707
    forced_type = TISPECS_CLUSTER_TYPES
3708

    
3709
  for specs in ipolicy_transposed.values():
3710
    utils.ForceDictType(specs, forced_type, allowed_values=allowed_values)
3711

    
3712
  # then transpose
3713
  ipolicy_out = objects.MakeEmptyIPolicy()
3714
  for name, specs in ipolicy_transposed.iteritems():
3715
    assert name in constants.ISPECS_PARAMETERS
3716
    for key, val in specs.items(): # {min: .. ,max: .., std: ..}
3717
      ipolicy_out[key][name] = val
3718

    
3719
  # no filldict for non-dicts
3720
  if not group_ipolicy and fill_all:
3721
    if ipolicy_disk_templates is None:
3722
      ipolicy_disk_templates = constants.DISK_TEMPLATES
3723
    if ipolicy_vcpu_ratio is None:
3724
      ipolicy_vcpu_ratio = \
3725
        constants.IPOLICY_DEFAULTS[constants.IPOLICY_VCPU_RATIO]
3726
    if ipolicy_spindle_ratio is None:
3727
      ipolicy_spindle_ratio = \
3728
        constants.IPOLICY_DEFAULTS[constants.IPOLICY_SPINDLE_RATIO]
3729
  if ipolicy_disk_templates is not None:
3730
    ipolicy_out[constants.IPOLICY_DTS] = list(ipolicy_disk_templates)
3731
  if ipolicy_vcpu_ratio is not None:
3732
    ipolicy_out[constants.IPOLICY_VCPU_RATIO] = ipolicy_vcpu_ratio
3733
  if ipolicy_spindle_ratio is not None:
3734
    ipolicy_out[constants.IPOLICY_SPINDLE_RATIO] = ipolicy_spindle_ratio
3735

    
3736
  assert not (frozenset(ipolicy_out.keys()) - constants.IPOLICY_ALL_KEYS)
3737

    
3738
  return ipolicy_out