Statistics
| Branch: | Tag: | Revision:

root / lib / cli.py @ 7f5edc60

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

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

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

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

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

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

    
307

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

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

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

    
331

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

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

    
341

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

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

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

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

    
357

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

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

364
  """
365

    
366

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

370
  """
371

    
372

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

376
  """
377

    
378

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

382
  """
383

    
384

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

388
  """
389

    
390

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

394
  """
395

    
396

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

400
  """
401

    
402

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

406
  """
407

    
408

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

412
  """
413

    
414

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

418
  """
419

    
420

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

424
  """
425

    
426

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

430
  """
431

    
432

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

    
445

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

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

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

    
470

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

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

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

    
499

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

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

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

    
517

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

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

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

    
534

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

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

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

    
551

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

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

    
561

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

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

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

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

    
598

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

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

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

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

    
628

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

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

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

    
637

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

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

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

    
652

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

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

    
664

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

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

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

    
676

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

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

    
701

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

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

    
725

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

    
729

    
730
_YORNO = "yes|no"
731

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1159
NIC_PARAMS_OPT = cli_option("-N", "--nic-parameters", dest="nicparams",
1160
                            type="keyval", default={},
1161
                            help="NIC parameters")
1162

    
1163
CP_SIZE_OPT = cli_option("-C", "--candidate-pool-size", default=None,
1164
                         dest="candidate_pool_size", type="int",
1165
                         help="Set the candidate pool size")
1166

    
1167
VG_NAME_OPT = cli_option("--vg-name", dest="vg_name",
1168
                         help=("Enables LVM and specifies the volume group"
1169
                               " name (cluster-wide) for disk allocation"
1170
                               " [%s]" % constants.DEFAULT_VG),
1171
                         metavar="VG", default=None)
1172

    
1173
YES_DOIT_OPT = cli_option("--yes-do-it", "--ya-rly", dest="yes_do_it",
1174
                          help="Destroy cluster", action="store_true")
1175

    
1176
NOVOTING_OPT = cli_option("--no-voting", dest="no_voting",
1177
                          help="Skip node agreement check (dangerous)",
1178
                          action="store_true", default=False)
1179

    
1180
MAC_PREFIX_OPT = cli_option("-m", "--mac-prefix", dest="mac_prefix",
1181
                            help="Specify the mac prefix for the instance IP"
1182
                            " addresses, in the format XX:XX:XX",
1183
                            metavar="PREFIX",
1184
                            default=None)
1185

    
1186
MASTER_NETDEV_OPT = cli_option("--master-netdev", dest="master_netdev",
1187
                               help="Specify the node interface (cluster-wide)"
1188
                               " on which the master IP address will be added"
1189
                               " (cluster init default: %s)" %
1190
                               constants.DEFAULT_BRIDGE,
1191
                               metavar="NETDEV",
1192
                               default=None)
1193

    
1194
MASTER_NETMASK_OPT = cli_option("--master-netmask", dest="master_netmask",
1195
                                help="Specify the netmask of the master IP",
1196
                                metavar="NETMASK",
1197
                                default=None)
1198

    
1199
USE_EXTERNAL_MIP_SCRIPT = cli_option("--use-external-mip-script",
1200
                                     dest="use_external_mip_script",
1201
                                     help="Specify whether to run a"
1202
                                     " user-provided script for the master"
1203
                                     " IP address turnup and"
1204
                                     " turndown operations",
1205
                                     type="bool", metavar=_YORNO, default=None)
1206

    
1207
GLOBAL_FILEDIR_OPT = cli_option("--file-storage-dir", dest="file_storage_dir",
1208
                                help="Specify the default directory (cluster-"
1209
                                "wide) for storing the file-based disks [%s]" %
1210
                                pathutils.DEFAULT_FILE_STORAGE_DIR,
1211
                                metavar="DIR",
1212
                                default=pathutils.DEFAULT_FILE_STORAGE_DIR)
1213

    
1214
GLOBAL_SHARED_FILEDIR_OPT = cli_option(
1215
  "--shared-file-storage-dir",
1216
  dest="shared_file_storage_dir",
1217
  help="Specify the default directory (cluster-wide) for storing the"
1218
  " shared file-based disks [%s]" %
1219
  pathutils.DEFAULT_SHARED_FILE_STORAGE_DIR,
1220
  metavar="SHAREDDIR", default=pathutils.DEFAULT_SHARED_FILE_STORAGE_DIR)
1221

    
1222
NOMODIFY_ETCHOSTS_OPT = cli_option("--no-etc-hosts", dest="modify_etc_hosts",
1223
                                   help="Don't modify %s" % pathutils.ETC_HOSTS,
1224
                                   action="store_false", default=True)
1225

    
1226
NOMODIFY_SSH_SETUP_OPT = cli_option("--no-ssh-init", dest="modify_ssh_setup",
1227
                                    help="Don't initialize SSH keys",
1228
                                    action="store_false", default=True)
1229

    
1230
ERROR_CODES_OPT = cli_option("--error-codes", dest="error_codes",
1231
                             help="Enable parseable error messages",
1232
                             action="store_true", default=False)
1233

    
1234
NONPLUS1_OPT = cli_option("--no-nplus1-mem", dest="skip_nplusone_mem",
1235
                          help="Skip N+1 memory redundancy tests",
1236
                          action="store_true", default=False)
1237

    
1238
REBOOT_TYPE_OPT = cli_option("-t", "--type", dest="reboot_type",
1239
                             help="Type of reboot: soft/hard/full",
1240
                             default=constants.INSTANCE_REBOOT_HARD,
1241
                             metavar="<REBOOT>",
1242
                             choices=list(constants.REBOOT_TYPES))
1243

    
1244
IGNORE_SECONDARIES_OPT = cli_option("--ignore-secondaries",
1245
                                    dest="ignore_secondaries",
1246
                                    default=False, action="store_true",
1247
                                    help="Ignore errors from secondaries")
1248

    
1249
NOSHUTDOWN_OPT = cli_option("--noshutdown", dest="shutdown",
1250
                            action="store_false", default=True,
1251
                            help="Don't shutdown the instance (unsafe)")
1252

    
1253
TIMEOUT_OPT = cli_option("--timeout", dest="timeout", type="int",
1254
                         default=constants.DEFAULT_SHUTDOWN_TIMEOUT,
1255
                         help="Maximum time to wait")
1256

    
1257
SHUTDOWN_TIMEOUT_OPT = cli_option("--shutdown-timeout",
1258
                                  dest="shutdown_timeout", type="int",
1259
                                  default=constants.DEFAULT_SHUTDOWN_TIMEOUT,
1260
                                  help="Maximum time to wait for instance"
1261
                                  " shutdown")
1262

    
1263
INTERVAL_OPT = cli_option("--interval", dest="interval", type="int",
1264
                          default=None,
1265
                          help=("Number of seconds between repetions of the"
1266
                                " command"))
1267

    
1268
EARLY_RELEASE_OPT = cli_option("--early-release",
1269
                               dest="early_release", default=False,
1270
                               action="store_true",
1271
                               help="Release the locks on the secondary"
1272
                               " node(s) early")
1273

    
1274
NEW_CLUSTER_CERT_OPT = cli_option("--new-cluster-certificate",
1275
                                  dest="new_cluster_cert",
1276
                                  default=False, action="store_true",
1277
                                  help="Generate a new cluster certificate")
1278

    
1279
RAPI_CERT_OPT = cli_option("--rapi-certificate", dest="rapi_cert",
1280
                           default=None,
1281
                           help="File containing new RAPI certificate")
1282

    
1283
NEW_RAPI_CERT_OPT = cli_option("--new-rapi-certificate", dest="new_rapi_cert",
1284
                               default=None, action="store_true",
1285
                               help=("Generate a new self-signed RAPI"
1286
                                     " certificate"))
1287

    
1288
SPICE_CERT_OPT = cli_option("--spice-certificate", dest="spice_cert",
1289
                            default=None,
1290
                            help="File containing new SPICE certificate")
1291

    
1292
SPICE_CACERT_OPT = cli_option("--spice-ca-certificate", dest="spice_cacert",
1293
                              default=None,
1294
                              help="File containing the certificate of the CA"
1295
                              " which signed the SPICE certificate")
1296

    
1297
NEW_SPICE_CERT_OPT = cli_option("--new-spice-certificate",
1298
                                dest="new_spice_cert", default=None,
1299
                                action="store_true",
1300
                                help=("Generate a new self-signed SPICE"
1301
                                      " certificate"))
1302

    
1303
NEW_CONFD_HMAC_KEY_OPT = cli_option("--new-confd-hmac-key",
1304
                                    dest="new_confd_hmac_key",
1305
                                    default=False, action="store_true",
1306
                                    help=("Create a new HMAC key for %s" %
1307
                                          constants.CONFD))
1308

    
1309
CLUSTER_DOMAIN_SECRET_OPT = cli_option("--cluster-domain-secret",
1310
                                       dest="cluster_domain_secret",
1311
                                       default=None,
1312
                                       help=("Load new new cluster domain"
1313
                                             " secret from file"))
1314

    
1315
NEW_CLUSTER_DOMAIN_SECRET_OPT = cli_option("--new-cluster-domain-secret",
1316
                                           dest="new_cluster_domain_secret",
1317
                                           default=False, action="store_true",
1318
                                           help=("Create a new cluster domain"
1319
                                                 " secret"))
1320

    
1321
USE_REPL_NET_OPT = cli_option("--use-replication-network",
1322
                              dest="use_replication_network",
1323
                              help="Whether to use the replication network"
1324
                              " for talking to the nodes",
1325
                              action="store_true", default=False)
1326

    
1327
MAINTAIN_NODE_HEALTH_OPT = \
1328
    cli_option("--maintain-node-health", dest="maintain_node_health",
1329
               metavar=_YORNO, default=None, type="bool",
1330
               help="Configure the cluster to automatically maintain node"
1331
               " health, by shutting down unknown instances, shutting down"
1332
               " unknown DRBD devices, etc.")
1333

    
1334
IDENTIFY_DEFAULTS_OPT = \
1335
    cli_option("--identify-defaults", dest="identify_defaults",
1336
               default=False, action="store_true",
1337
               help="Identify which saved instance parameters are equal to"
1338
               " the current cluster defaults and set them as such, instead"
1339
               " of marking them as overridden")
1340

    
1341
UIDPOOL_OPT = cli_option("--uid-pool", default=None,
1342
                         action="store", dest="uid_pool",
1343
                         help=("A list of user-ids or user-id"
1344
                               " ranges separated by commas"))
1345

    
1346
ADD_UIDS_OPT = cli_option("--add-uids", default=None,
1347
                          action="store", dest="add_uids",
1348
                          help=("A list of user-ids or user-id"
1349
                                " ranges separated by commas, to be"
1350
                                " added to the user-id pool"))
1351

    
1352
REMOVE_UIDS_OPT = cli_option("--remove-uids", default=None,
1353
                             action="store", dest="remove_uids",
1354
                             help=("A list of user-ids or user-id"
1355
                                   " ranges separated by commas, to be"
1356
                                   " removed from the user-id pool"))
1357

    
1358
RESERVED_LVS_OPT = cli_option("--reserved-lvs", default=None,
1359
                              action="store", dest="reserved_lvs",
1360
                              help=("A comma-separated list of reserved"
1361
                                    " logical volumes names, that will be"
1362
                                    " ignored by cluster verify"))
1363

    
1364
ROMAN_OPT = cli_option("--roman",
1365
                       dest="roman_integers", default=False,
1366
                       action="store_true",
1367
                       help="Use roman numbers for positive integers")
1368

    
1369
DRBD_HELPER_OPT = cli_option("--drbd-usermode-helper", dest="drbd_helper",
1370
                             action="store", default=None,
1371
                             help="Specifies usermode helper for DRBD")
1372

    
1373
NODRBD_STORAGE_OPT = cli_option("--no-drbd-storage", dest="drbd_storage",
1374
                                action="store_false", default=True,
1375
                                help="Disable support for DRBD")
1376

    
1377
PRIMARY_IP_VERSION_OPT = \
1378
    cli_option("--primary-ip-version", default=constants.IP4_VERSION,
1379
               action="store", dest="primary_ip_version",
1380
               metavar="%d|%d" % (constants.IP4_VERSION,
1381
                                  constants.IP6_VERSION),
1382
               help="Cluster-wide IP version for primary IP")
1383

    
1384
SHOW_MACHINE_OPT = cli_option("-M", "--show-machine-names", default=False,
1385
                              action="store_true",
1386
                              help="Show machine name for every line in output")
1387

    
1388
FAILURE_ONLY_OPT = cli_option("--failure-only", default=False,
1389
                              action="store_true",
1390
                              help=("Hide successful results and show failures"
1391
                                    " only (determined by the exit code)"))
1392

    
1393

    
1394
def _PriorityOptionCb(option, _, value, parser):
1395
  """Callback for processing C{--priority} option.
1396

1397
  """
1398
  value = _PRIONAME_TO_VALUE[value]
1399

    
1400
  setattr(parser.values, option.dest, value)
1401

    
1402

    
1403
PRIORITY_OPT = cli_option("--priority", default=None, dest="priority",
1404
                          metavar="|".join(name for name, _ in _PRIORITY_NAMES),
1405
                          choices=_PRIONAME_TO_VALUE.keys(),
1406
                          action="callback", type="choice",
1407
                          callback=_PriorityOptionCb,
1408
                          help="Priority for opcode processing")
1409

    
1410
HID_OS_OPT = cli_option("--hidden", dest="hidden",
1411
                        type="bool", default=None, metavar=_YORNO,
1412
                        help="Sets the hidden flag on the OS")
1413

    
1414
BLK_OS_OPT = cli_option("--blacklisted", dest="blacklisted",
1415
                        type="bool", default=None, metavar=_YORNO,
1416
                        help="Sets the blacklisted flag on the OS")
1417

    
1418
PREALLOC_WIPE_DISKS_OPT = cli_option("--prealloc-wipe-disks", default=None,
1419
                                     type="bool", metavar=_YORNO,
1420
                                     dest="prealloc_wipe_disks",
1421
                                     help=("Wipe disks prior to instance"
1422
                                           " creation"))
1423

    
1424
NODE_PARAMS_OPT = cli_option("--node-parameters", dest="ndparams",
1425
                             type="keyval", default=None,
1426
                             help="Node parameters")
1427

    
1428
ALLOC_POLICY_OPT = cli_option("--alloc-policy", dest="alloc_policy",
1429
                              action="store", metavar="POLICY", default=None,
1430
                              help="Allocation policy for the node group")
1431

    
1432
NODE_POWERED_OPT = cli_option("--node-powered", default=None,
1433
                              type="bool", metavar=_YORNO,
1434
                              dest="node_powered",
1435
                              help="Specify if the SoR for node is powered")
1436

    
1437
OOB_TIMEOUT_OPT = cli_option("--oob-timeout", dest="oob_timeout", type="int",
1438
                             default=constants.OOB_TIMEOUT,
1439
                             help="Maximum time to wait for out-of-band helper")
1440

    
1441
POWER_DELAY_OPT = cli_option("--power-delay", dest="power_delay", type="float",
1442
                             default=constants.OOB_POWER_DELAY,
1443
                             help="Time in seconds to wait between power-ons")
1444

    
1445
FORCE_FILTER_OPT = cli_option("-F", "--filter", dest="force_filter",
1446
                              action="store_true", default=False,
1447
                              help=("Whether command argument should be treated"
1448
                                    " as filter"))
1449

    
1450
NO_REMEMBER_OPT = cli_option("--no-remember",
1451
                             dest="no_remember",
1452
                             action="store_true", default=False,
1453
                             help="Perform but do not record the change"
1454
                             " in the configuration")
1455

    
1456
PRIMARY_ONLY_OPT = cli_option("-p", "--primary-only",
1457
                              default=False, action="store_true",
1458
                              help="Evacuate primary instances only")
1459

    
1460
SECONDARY_ONLY_OPT = cli_option("-s", "--secondary-only",
1461
                                default=False, action="store_true",
1462
                                help="Evacuate secondary instances only"
1463
                                     " (applies only to internally mirrored"
1464
                                     " disk templates, e.g. %s)" %
1465
                                     utils.CommaJoin(constants.DTS_INT_MIRROR))
1466

    
1467
STARTUP_PAUSED_OPT = cli_option("--paused", dest="startup_paused",
1468
                                action="store_true", default=False,
1469
                                help="Pause instance at startup")
1470

    
1471
TO_GROUP_OPT = cli_option("--to", dest="to", metavar="<group>",
1472
                          help="Destination node group (name or uuid)",
1473
                          default=None, action="append",
1474
                          completion_suggest=OPT_COMPL_ONE_NODEGROUP)
1475

    
1476
IGNORE_ERRORS_OPT = cli_option("-I", "--ignore-errors", default=[],
1477
                               action="append", dest="ignore_errors",
1478
                               choices=list(constants.CV_ALL_ECODES_STRINGS),
1479
                               help="Error code to be ignored")
1480

    
1481
DISK_STATE_OPT = cli_option("--disk-state", default=[], dest="disk_state",
1482
                            action="append",
1483
                            help=("Specify disk state information in the"
1484
                                  " format"
1485
                                  " storage_type/identifier:option=value,...;"
1486
                                  " note this is unused for now"),
1487
                            type="identkeyval")
1488

    
1489
HV_STATE_OPT = cli_option("--hypervisor-state", default=[], dest="hv_state",
1490
                          action="append",
1491
                          help=("Specify hypervisor state information in the"
1492
                                " format hypervisor:option=value,...;"
1493
                                " note this is unused for now"),
1494
                          type="identkeyval")
1495

    
1496
IGNORE_IPOLICY_OPT = cli_option("--ignore-ipolicy", dest="ignore_ipolicy",
1497
                                action="store_true", default=False,
1498
                                help="Ignore instance policy violations")
1499

    
1500
RUNTIME_MEM_OPT = cli_option("-m", "--runtime-memory", dest="runtime_mem",
1501
                             help="Sets the instance's runtime memory,"
1502
                             " ballooning it up or down to the new value",
1503
                             default=None, type="unit", metavar="<size>")
1504

    
1505
ABSOLUTE_OPT = cli_option("--absolute", dest="absolute",
1506
                          action="store_true", default=False,
1507
                          help="Marks the grow as absolute instead of the"
1508
                          " (default) relative mode")
1509

    
1510
NETWORK_OPT = cli_option("--network",
1511
                         action="store", default=None, dest="network",
1512
                         help="IP network in CIDR notation")
1513

    
1514
GATEWAY_OPT = cli_option("--gateway",
1515
                         action="store", default=None, dest="gateway",
1516
                         help="IP address of the router (gateway)")
1517

    
1518
ADD_RESERVED_IPS_OPT = cli_option("--add-reserved-ips",
1519
                                  action="store", default=None,
1520
                                  dest="add_reserved_ips",
1521
                                  help="Comma-separated list of"
1522
                                  " reserved IPs to add")
1523

    
1524
REMOVE_RESERVED_IPS_OPT = cli_option("--remove-reserved-ips",
1525
                                     action="store", default=None,
1526
                                     dest="remove_reserved_ips",
1527
                                     help="Comma-delimited list of"
1528
                                     " reserved IPs to remove")
1529

    
1530
NETWORK_TYPE_OPT = cli_option("--network-type",
1531
                              action="store", default=None, dest="network_type",
1532
                              help="Network type: private, public, None")
1533

    
1534
NETWORK6_OPT = cli_option("--network6",
1535
                          action="store", default=None, dest="network6",
1536
                          help="IP network in CIDR notation")
1537

    
1538
GATEWAY6_OPT = cli_option("--gateway6",
1539
                          action="store", default=None, dest="gateway6",
1540
                          help="IP6 address of the router (gateway)")
1541

    
1542
NOCONFLICTSCHECK_OPT = cli_option("--no-conflicts-check",
1543
                                  dest="conflicts_check",
1544
                                  default=True,
1545
                                  action="store_false",
1546
                                  help="Don't check for conflicting IPs")
1547

    
1548
#: Options provided by all commands
1549
COMMON_OPTS = [DEBUG_OPT]
1550

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

    
1576
# common instance policy options
1577
INSTANCE_POLICY_OPTS = [
1578
  SPECS_CPU_COUNT_OPT,
1579
  SPECS_DISK_COUNT_OPT,
1580
  SPECS_DISK_SIZE_OPT,
1581
  SPECS_MEM_SIZE_OPT,
1582
  SPECS_NIC_COUNT_OPT,
1583
  IPOLICY_DISK_TEMPLATES,
1584
  IPOLICY_VCPU_RATIO,
1585
  IPOLICY_SPINDLE_RATIO,
1586
  ]
1587

    
1588

    
1589
class _ShowUsage(Exception):
1590
  """Exception class for L{_ParseArgs}.
1591

1592
  """
1593
  def __init__(self, exit_error):
1594
    """Initializes instances of this class.
1595

1596
    @type exit_error: bool
1597
    @param exit_error: Whether to report failure on exit
1598

1599
    """
1600
    Exception.__init__(self)
1601
    self.exit_error = exit_error
1602

    
1603

    
1604
class _ShowVersion(Exception):
1605
  """Exception class for L{_ParseArgs}.
1606

1607
  """
1608

    
1609

    
1610
def _ParseArgs(binary, argv, commands, aliases, env_override):
1611
  """Parser for the command line arguments.
1612

1613
  This function parses the arguments and returns the function which
1614
  must be executed together with its (modified) arguments.
1615

1616
  @param binary: Script name
1617
  @param argv: Command line arguments
1618
  @param commands: Dictionary containing command definitions
1619
  @param aliases: dictionary with command aliases {"alias": "target", ...}
1620
  @param env_override: list of env variables allowed for default args
1621
  @raise _ShowUsage: If usage description should be shown
1622
  @raise _ShowVersion: If version should be shown
1623

1624
  """
1625
  assert not (env_override - set(commands))
1626
  assert not (set(aliases.keys()) & set(commands.keys()))
1627

    
1628
  if len(argv) > 1:
1629
    cmd = argv[1]
1630
  else:
1631
    # No option or command given
1632
    raise _ShowUsage(exit_error=True)
1633

    
1634
  if cmd == "--version":
1635
    raise _ShowVersion()
1636
  elif cmd == "--help":
1637
    raise _ShowUsage(exit_error=False)
1638
  elif not (cmd in commands or cmd in aliases):
1639
    raise _ShowUsage(exit_error=True)
1640

    
1641
  # get command, unalias it, and look it up in commands
1642
  if cmd in aliases:
1643
    if aliases[cmd] not in commands:
1644
      raise errors.ProgrammerError("Alias '%s' maps to non-existing"
1645
                                   " command '%s'" % (cmd, aliases[cmd]))
1646

    
1647
    cmd = aliases[cmd]
1648

    
1649
  if cmd in env_override:
1650
    args_env_name = ("%s_%s" % (binary.replace("-", "_"), cmd)).upper()
1651
    env_args = os.environ.get(args_env_name)
1652
    if env_args:
1653
      argv = utils.InsertAtPos(argv, 2, shlex.split(env_args))
1654

    
1655
  func, args_def, parser_opts, usage, description = commands[cmd]
1656
  parser = OptionParser(option_list=parser_opts + COMMON_OPTS,
1657
                        description=description,
1658
                        formatter=TitledHelpFormatter(),
1659
                        usage="%%prog %s %s" % (cmd, usage))
1660
  parser.disable_interspersed_args()
1661
  options, args = parser.parse_args(args=argv[2:])
1662

    
1663
  if not _CheckArguments(cmd, args_def, args):
1664
    return None, None, None
1665

    
1666
  return func, options, args
1667

    
1668

    
1669
def _FormatUsage(binary, commands):
1670
  """Generates a nice description of all commands.
1671

1672
  @param binary: Script name
1673
  @param commands: Dictionary containing command definitions
1674

1675
  """
1676
  # compute the max line length for cmd + usage
1677
  mlen = min(60, max(map(len, commands)))
1678

    
1679
  yield "Usage: %s {command} [options...] [argument...]" % binary
1680
  yield "%s <command> --help to see details, or man %s" % (binary, binary)
1681
  yield ""
1682
  yield "Commands:"
1683

    
1684
  # and format a nice command list
1685
  for (cmd, (_, _, _, _, help_text)) in sorted(commands.items()):
1686
    help_lines = textwrap.wrap(help_text, 79 - 3 - mlen)
1687
    yield " %-*s - %s" % (mlen, cmd, help_lines.pop(0))
1688
    for line in help_lines:
1689
      yield " %-*s   %s" % (mlen, "", line)
1690

    
1691
  yield ""
1692

    
1693

    
1694
def _CheckArguments(cmd, args_def, args):
1695
  """Verifies the arguments using the argument definition.
1696

1697
  Algorithm:
1698

1699
    1. Abort with error if values specified by user but none expected.
1700

1701
    1. For each argument in definition
1702

1703
      1. Keep running count of minimum number of values (min_count)
1704
      1. Keep running count of maximum number of values (max_count)
1705
      1. If it has an unlimited number of values
1706

1707
        1. Abort with error if it's not the last argument in the definition
1708

1709
    1. If last argument has limited number of values
1710

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

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

1715
  """
1716
  if args and not args_def:
1717
    ToStderr("Error: Command %s expects no arguments", cmd)
1718
    return False
1719

    
1720
  min_count = None
1721
  max_count = None
1722
  check_max = None
1723

    
1724
  last_idx = len(args_def) - 1
1725

    
1726
  for idx, arg in enumerate(args_def):
1727
    if min_count is None:
1728
      min_count = arg.min
1729
    elif arg.min is not None:
1730
      min_count += arg.min
1731

    
1732
    if max_count is None:
1733
      max_count = arg.max
1734
    elif arg.max is not None:
1735
      max_count += arg.max
1736

    
1737
    if idx == last_idx:
1738
      check_max = (arg.max is not None)
1739

    
1740
    elif arg.max is None:
1741
      raise errors.ProgrammerError("Only the last argument can have max=None")
1742

    
1743
  if check_max:
1744
    # Command with exact number of arguments
1745
    if (min_count is not None and max_count is not None and
1746
        min_count == max_count and len(args) != min_count):
1747
      ToStderr("Error: Command %s expects %d argument(s)", cmd, min_count)
1748
      return False
1749

    
1750
    # Command with limited number of arguments
1751
    if max_count is not None and len(args) > max_count:
1752
      ToStderr("Error: Command %s expects only %d argument(s)",
1753
               cmd, max_count)
1754
      return False
1755

    
1756
  # Command with some required arguments
1757
  if min_count is not None and len(args) < min_count:
1758
    ToStderr("Error: Command %s expects at least %d argument(s)",
1759
             cmd, min_count)
1760
    return False
1761

    
1762
  return True
1763

    
1764

    
1765
def SplitNodeOption(value):
1766
  """Splits the value of a --node option.
1767

1768
  """
1769
  if value and ":" in value:
1770
    return value.split(":", 1)
1771
  else:
1772
    return (value, None)
1773

    
1774

    
1775
def CalculateOSNames(os_name, os_variants):
1776
  """Calculates all the names an OS can be called, according to its variants.
1777

1778
  @type os_name: string
1779
  @param os_name: base name of the os
1780
  @type os_variants: list or None
1781
  @param os_variants: list of supported variants
1782
  @rtype: list
1783
  @return: list of valid names
1784

1785
  """
1786
  if os_variants:
1787
    return ["%s+%s" % (os_name, v) for v in os_variants]
1788
  else:
1789
    return [os_name]
1790

    
1791

    
1792
def ParseFields(selected, default):
1793
  """Parses the values of "--field"-like options.
1794

1795
  @type selected: string or None
1796
  @param selected: User-selected options
1797
  @type default: list
1798
  @param default: Default fields
1799

1800
  """
1801
  if selected is None:
1802
    return default
1803

    
1804
  if selected.startswith("+"):
1805
    return default + selected[1:].split(",")
1806

    
1807
  return selected.split(",")
1808

    
1809

    
1810
UsesRPC = rpc.RunWithRPC
1811

    
1812

    
1813
def AskUser(text, choices=None):
1814
  """Ask the user a question.
1815

1816
  @param text: the question to ask
1817

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

1823
  @return: one of the return values from the choices list; if input is
1824
      not possible (i.e. not running with a tty, we return the last
1825
      entry from the list
1826

1827
  """
1828
  if choices is None:
1829
    choices = [("y", True, "Perform the operation"),
1830
               ("n", False, "Do not perform the operation")]
1831
  if not choices or not isinstance(choices, list):
1832
    raise errors.ProgrammerError("Invalid choices argument to AskUser")
1833
  for entry in choices:
1834
    if not isinstance(entry, tuple) or len(entry) < 3 or entry[0] == "?":
1835
      raise errors.ProgrammerError("Invalid choices element to AskUser")
1836

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

    
1869

    
1870
class JobSubmittedException(Exception):
1871
  """Job was submitted, client should exit.
1872

1873
  This exception has one argument, the ID of the job that was
1874
  submitted. The handler should print this ID.
1875

1876
  This is not an error, just a structured way to exit from clients.
1877

1878
  """
1879

    
1880

    
1881
def SendJob(ops, cl=None):
1882
  """Function to submit an opcode without waiting for the results.
1883

1884
  @type ops: list
1885
  @param ops: list of opcodes
1886
  @type cl: luxi.Client
1887
  @param cl: the luxi client to use for communicating with the master;
1888
             if None, a new client will be created
1889

1890
  """
1891
  if cl is None:
1892
    cl = GetClient()
1893

    
1894
  job_id = cl.SubmitJob(ops)
1895

    
1896
  return job_id
1897

    
1898

    
1899
def GenericPollJob(job_id, cbs, report_cbs):
1900
  """Generic job-polling function.
1901

1902
  @type job_id: number
1903
  @param job_id: Job ID
1904
  @type cbs: Instance of L{JobPollCbBase}
1905
  @param cbs: Data callbacks
1906
  @type report_cbs: Instance of L{JobPollReportCbBase}
1907
  @param report_cbs: Reporting callbacks
1908

1909
  """
1910
  prev_job_info = None
1911
  prev_logmsg_serial = None
1912

    
1913
  status = None
1914

    
1915
  while True:
1916
    result = cbs.WaitForJobChangeOnce(job_id, ["status"], prev_job_info,
1917
                                      prev_logmsg_serial)
1918
    if not result:
1919
      # job not found, go away!
1920
      raise errors.JobLost("Job with id %s lost" % job_id)
1921

    
1922
    if result == constants.JOB_NOTCHANGED:
1923
      report_cbs.ReportNotChanged(job_id, status)
1924

    
1925
      # Wait again
1926
      continue
1927

    
1928
    # Split result, a tuple of (field values, log entries)
1929
    (job_info, log_entries) = result
1930
    (status, ) = job_info
1931

    
1932
    if log_entries:
1933
      for log_entry in log_entries:
1934
        (serial, timestamp, log_type, message) = log_entry
1935
        report_cbs.ReportLogMessage(job_id, serial, timestamp,
1936
                                    log_type, message)
1937
        prev_logmsg_serial = max(prev_logmsg_serial, serial)
1938

    
1939
    # TODO: Handle canceled and archived jobs
1940
    elif status in (constants.JOB_STATUS_SUCCESS,
1941
                    constants.JOB_STATUS_ERROR,
1942
                    constants.JOB_STATUS_CANCELING,
1943
                    constants.JOB_STATUS_CANCELED):
1944
      break
1945

    
1946
    prev_job_info = job_info
1947

    
1948
  jobs = cbs.QueryJobs([job_id], ["status", "opstatus", "opresult"])
1949
  if not jobs:
1950
    raise errors.JobLost("Job with id %s lost" % job_id)
1951

    
1952
  status, opstatus, result = jobs[0]
1953

    
1954
  if status == constants.JOB_STATUS_SUCCESS:
1955
    return result
1956

    
1957
  if status in (constants.JOB_STATUS_CANCELING, constants.JOB_STATUS_CANCELED):
1958
    raise errors.OpExecError("Job was canceled")
1959

    
1960
  has_ok = False
1961
  for idx, (status, msg) in enumerate(zip(opstatus, result)):
1962
    if status == constants.OP_STATUS_SUCCESS:
1963
      has_ok = True
1964
    elif status == constants.OP_STATUS_ERROR:
1965
      errors.MaybeRaise(msg)
1966

    
1967
      if has_ok:
1968
        raise errors.OpExecError("partial failure (opcode %d): %s" %
1969
                                 (idx, msg))
1970

    
1971
      raise errors.OpExecError(str(msg))
1972

    
1973
  # default failure mode
1974
  raise errors.OpExecError(result)
1975

    
1976

    
1977
class JobPollCbBase:
1978
  """Base class for L{GenericPollJob} callbacks.
1979

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

1984
    """
1985

    
1986
  def WaitForJobChangeOnce(self, job_id, fields,
1987
                           prev_job_info, prev_log_serial):
1988
    """Waits for changes on a job.
1989

1990
    """
1991
    raise NotImplementedError()
1992

    
1993
  def QueryJobs(self, job_ids, fields):
1994
    """Returns the selected fields for the selected job IDs.
1995

1996
    @type job_ids: list of numbers
1997
    @param job_ids: Job IDs
1998
    @type fields: list of strings
1999
    @param fields: Fields
2000

2001
    """
2002
    raise NotImplementedError()
2003

    
2004

    
2005
class JobPollReportCbBase:
2006
  """Base class for L{GenericPollJob} reporting callbacks.
2007

2008
  """
2009
  def __init__(self):
2010
    """Initializes this class.
2011

2012
    """
2013

    
2014
  def ReportLogMessage(self, job_id, serial, timestamp, log_type, log_msg):
2015
    """Handles a log message.
2016

2017
    """
2018
    raise NotImplementedError()
2019

    
2020
  def ReportNotChanged(self, job_id, status):
2021
    """Called for if a job hasn't changed in a while.
2022

2023
    @type job_id: number
2024
    @param job_id: Job ID
2025
    @type status: string or None
2026
    @param status: Job status if available
2027

2028
    """
2029
    raise NotImplementedError()
2030

    
2031

    
2032
class _LuxiJobPollCb(JobPollCbBase):
2033
  def __init__(self, cl):
2034
    """Initializes this class.
2035

2036
    """
2037
    JobPollCbBase.__init__(self)
2038
    self.cl = cl
2039

    
2040
  def WaitForJobChangeOnce(self, job_id, fields,
2041
                           prev_job_info, prev_log_serial):
2042
    """Waits for changes on a job.
2043

2044
    """
2045
    return self.cl.WaitForJobChangeOnce(job_id, fields,
2046
                                        prev_job_info, prev_log_serial)
2047

    
2048
  def QueryJobs(self, job_ids, fields):
2049
    """Returns the selected fields for the selected job IDs.
2050

2051
    """
2052
    return self.cl.QueryJobs(job_ids, fields)
2053

    
2054

    
2055
class FeedbackFnJobPollReportCb(JobPollReportCbBase):
2056
  def __init__(self, feedback_fn):
2057
    """Initializes this class.
2058

2059
    """
2060
    JobPollReportCbBase.__init__(self)
2061

    
2062
    self.feedback_fn = feedback_fn
2063

    
2064
    assert callable(feedback_fn)
2065

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

2069
    """
2070
    self.feedback_fn((timestamp, log_type, log_msg))
2071

    
2072
  def ReportNotChanged(self, job_id, status):
2073
    """Called if a job hasn't changed in a while.
2074

2075
    """
2076
    # Ignore
2077

    
2078

    
2079
class StdioJobPollReportCb(JobPollReportCbBase):
2080
  def __init__(self):
2081
    """Initializes this class.
2082

2083
    """
2084
    JobPollReportCbBase.__init__(self)
2085

    
2086
    self.notified_queued = False
2087
    self.notified_waitlock = False
2088

    
2089
  def ReportLogMessage(self, job_id, serial, timestamp, log_type, log_msg):
2090
    """Handles a log message.
2091

2092
    """
2093
    ToStdout("%s %s", time.ctime(utils.MergeTime(timestamp)),
2094
             FormatLogMessage(log_type, log_msg))
2095

    
2096
  def ReportNotChanged(self, job_id, status):
2097
    """Called if a job hasn't changed in a while.
2098

2099
    """
2100
    if status is None:
2101
      return
2102

    
2103
    if status == constants.JOB_STATUS_QUEUED and not self.notified_queued:
2104
      ToStderr("Job %s is waiting in queue", job_id)
2105
      self.notified_queued = True
2106

    
2107
    elif status == constants.JOB_STATUS_WAITING and not self.notified_waitlock:
2108
      ToStderr("Job %s is trying to acquire all necessary locks", job_id)
2109
      self.notified_waitlock = True
2110

    
2111

    
2112
def FormatLogMessage(log_type, log_msg):
2113
  """Formats a job message according to its type.
2114

2115
  """
2116
  if log_type != constants.ELOG_MESSAGE:
2117
    log_msg = str(log_msg)
2118

    
2119
  return utils.SafeEncode(log_msg)
2120

    
2121

    
2122
def PollJob(job_id, cl=None, feedback_fn=None, reporter=None):
2123
  """Function to poll for the result of a job.
2124

2125
  @type job_id: job identified
2126
  @param job_id: the job to poll for results
2127
  @type cl: luxi.Client
2128
  @param cl: the luxi client to use for communicating with the master;
2129
             if None, a new client will be created
2130

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

    
2135
  if reporter is None:
2136
    if feedback_fn:
2137
      reporter = FeedbackFnJobPollReportCb(feedback_fn)
2138
    else:
2139
      reporter = StdioJobPollReportCb()
2140
  elif feedback_fn:
2141
    raise errors.ProgrammerError("Can't specify reporter and feedback function")
2142

    
2143
  return GenericPollJob(job_id, _LuxiJobPollCb(cl), reporter)
2144

    
2145

    
2146
def SubmitOpCode(op, cl=None, feedback_fn=None, opts=None, reporter=None):
2147
  """Legacy function to submit an opcode.
2148

2149
  This is just a simple wrapper over the construction of the processor
2150
  instance. It should be extended to better handle feedback and
2151
  interaction functions.
2152

2153
  """
2154
  if cl is None:
2155
    cl = GetClient()
2156

    
2157
  SetGenericOpcodeOpts([op], opts)
2158

    
2159
  job_id = SendJob([op], cl=cl)
2160

    
2161
  op_results = PollJob(job_id, cl=cl, feedback_fn=feedback_fn,
2162
                       reporter=reporter)
2163

    
2164
  return op_results[0]
2165

    
2166

    
2167
def SubmitOrSend(op, opts, cl=None, feedback_fn=None):
2168
  """Wrapper around SubmitOpCode or SendJob.
2169

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

2175
  It will also process the opcodes if we're sending the via SendJob
2176
  (otherwise SubmitOpCode does it).
2177

2178
  """
2179
  if opts and opts.submit_only:
2180
    job = [op]
2181
    SetGenericOpcodeOpts(job, opts)
2182
    job_id = SendJob(job, cl=cl)
2183
    raise JobSubmittedException(job_id)
2184
  else:
2185
    return SubmitOpCode(op, cl=cl, feedback_fn=feedback_fn, opts=opts)
2186

    
2187

    
2188
def SetGenericOpcodeOpts(opcode_list, options):
2189
  """Processor for generic options.
2190

2191
  This function updates the given opcodes based on generic command
2192
  line options (like debug, dry-run, etc.).
2193

2194
  @param opcode_list: list of opcodes
2195
  @param options: command line options or None
2196
  @return: None (in-place modification)
2197

2198
  """
2199
  if not options:
2200
    return
2201
  for op in opcode_list:
2202
    op.debug_level = options.debug
2203
    if hasattr(options, "dry_run"):
2204
      op.dry_run = options.dry_run
2205
    if getattr(options, "priority", None) is not None:
2206
      op.priority = options.priority
2207

    
2208

    
2209
def GetClient(query=False):
2210
  """Connects to the a luxi socket and returns a client.
2211

2212
  @type query: boolean
2213
  @param query: this signifies that the client will only be
2214
      used for queries; if the build-time parameter
2215
      enable-split-queries is enabled, then the client will be
2216
      connected to the query socket instead of the masterd socket
2217

2218
  """
2219
  if query and constants.ENABLE_SPLIT_QUERY:
2220
    address = pathutils.QUERY_SOCKET
2221
  else:
2222
    address = None
2223
  # TODO: Cache object?
2224
  try:
2225
    client = luxi.Client(address=address)
2226
  except luxi.NoMasterError:
2227
    ss = ssconf.SimpleStore()
2228

    
2229
    # Try to read ssconf file
2230
    try:
2231
      ss.GetMasterNode()
2232
    except errors.ConfigurationError:
2233
      raise errors.OpPrereqError("Cluster not initialized or this machine is"
2234
                                 " not part of a cluster",
2235
                                 errors.ECODE_INVAL)
2236

    
2237
    master, myself = ssconf.GetMasterAndMyself(ss=ss)
2238
    if master != myself:
2239
      raise errors.OpPrereqError("This is not the master node, please connect"
2240
                                 " to node '%s' and rerun the command" %
2241
                                 master, errors.ECODE_INVAL)
2242
    raise
2243
  return client
2244

    
2245

    
2246
def FormatError(err):
2247
  """Return a formatted error message for a given error.
2248

2249
  This function takes an exception instance and returns a tuple
2250
  consisting of two values: first, the recommended exit code, and
2251
  second, a string describing the error message (not
2252
  newline-terminated).
2253

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

    
2337

    
2338
def GenericMain(commands, override=None, aliases=None,
2339
                env_override=frozenset()):
2340
  """Generic main function for all the gnt-* commands.
2341

2342
  @param commands: a dictionary with a special structure, see the design doc
2343
                   for command line handling.
2344
  @param override: if not None, we expect a dictionary with keys that will
2345
                   override command line options; this can be used to pass
2346
                   options from the scripts to generic functions
2347
  @param aliases: dictionary with command aliases {'alias': 'target, ...}
2348
  @param env_override: list of environment names which are allowed to submit
2349
                       default args for commands
2350

2351
  """
2352
  # save the program name and the entire command line for later logging
2353
  if sys.argv:
2354
    binary = os.path.basename(sys.argv[0])
2355
    if not binary:
2356
      binary = sys.argv[0]
2357

    
2358
    if len(sys.argv) >= 2:
2359
      logname = utils.ShellQuoteArgs([binary, sys.argv[1]])
2360
    else:
2361
      logname = binary
2362

    
2363
    cmdline = utils.ShellQuoteArgs([binary] + sys.argv[1:])
2364
  else:
2365
    binary = "<unknown program>"
2366
    cmdline = "<unknown>"
2367

    
2368
  if aliases is None:
2369
    aliases = {}
2370

    
2371
  try:
2372
    (func, options, args) = _ParseArgs(binary, sys.argv, commands, aliases,
2373
                                       env_override)
2374
  except _ShowVersion:
2375
    ToStdout("%s (ganeti %s) %s", binary, constants.VCS_VERSION,
2376
             constants.RELEASE_VERSION)
2377
    return constants.EXIT_SUCCESS
2378
  except _ShowUsage, err:
2379
    for line in _FormatUsage(binary, commands):
2380
      ToStdout(line)
2381

    
2382
    if err.exit_error:
2383
      return constants.EXIT_FAILURE
2384
    else:
2385
      return constants.EXIT_SUCCESS
2386
  except errors.ParameterError, err:
2387
    result, err_msg = FormatError(err)
2388
    ToStderr(err_msg)
2389
    return 1
2390

    
2391
  if func is None: # parse error
2392
    return 1
2393

    
2394
  if override is not None:
2395
    for key, val in override.iteritems():
2396
      setattr(options, key, val)
2397

    
2398
  utils.SetupLogging(pathutils.LOG_COMMANDS, logname, debug=options.debug,
2399
                     stderr_logging=True)
2400

    
2401
  logging.info("Command line: %s", cmdline)
2402

    
2403
  try:
2404
    result = func(options, args)
2405
  except (errors.GenericError, luxi.ProtocolError,
2406
          JobSubmittedException), err:
2407
    result, err_msg = FormatError(err)
2408
    logging.exception("Error during command processing")
2409
    ToStderr(err_msg)
2410
  except KeyboardInterrupt:
2411
    result = constants.EXIT_FAILURE
2412
    ToStderr("Aborted. Note that if the operation created any jobs, they"
2413
             " might have been submitted and"
2414
             " will continue to run in the background.")
2415
  except IOError, err:
2416
    if err.errno == errno.EPIPE:
2417
      # our terminal went away, we'll exit
2418
      sys.exit(constants.EXIT_FAILURE)
2419
    else:
2420
      raise
2421

    
2422
  return result
2423

    
2424

    
2425
def ParseNicOption(optvalue):
2426
  """Parses the value of the --net option(s).
2427

2428
  """
2429
  try:
2430
    nic_max = max(int(nidx[0]) + 1 for nidx in optvalue)
2431
  except (TypeError, ValueError), err:
2432
    raise errors.OpPrereqError("Invalid NIC index passed: %s" % str(err),
2433
                               errors.ECODE_INVAL)
2434

    
2435
  nics = [{}] * nic_max
2436
  for nidx, ndict in optvalue:
2437
    nidx = int(nidx)
2438

    
2439
    if not isinstance(ndict, dict):
2440
      raise errors.OpPrereqError("Invalid nic/%d value: expected dict,"
2441
                                 " got %s" % (nidx, ndict), errors.ECODE_INVAL)
2442

    
2443
    utils.ForceDictType(ndict, constants.INIC_PARAMS_TYPES)
2444

    
2445
    nics[nidx] = ndict
2446

    
2447
  return nics
2448

    
2449

    
2450
def GenericInstanceCreate(mode, opts, args):
2451
  """Add an instance to the cluster via either creation or import.
2452

2453
  @param mode: constants.INSTANCE_CREATE or constants.INSTANCE_IMPORT
2454
  @param opts: the command line options selected by the user
2455
  @type args: list
2456
  @param args: should contain only one element, the new instance name
2457
  @rtype: int
2458
  @return: the desired exit code
2459

2460
  """
2461
  instance = args[0]
2462

    
2463
  (pnode, snode) = SplitNodeOption(opts.node)
2464

    
2465
  hypervisor = None
2466
  hvparams = {}
2467
  if opts.hypervisor:
2468
    hypervisor, hvparams = opts.hypervisor
2469

    
2470
  if opts.nics:
2471
    nics = ParseNicOption(opts.nics)
2472
  elif opts.no_nics:
2473
    # no nics
2474
    nics = []
2475
  elif mode == constants.INSTANCE_CREATE:
2476
    # default of one nic, all auto
2477
    nics = [{}]
2478
  else:
2479
    # mode == import
2480
    nics = []
2481

    
2482
  if opts.disk_template == constants.DT_DISKLESS:
2483
    if opts.disks or opts.sd_size is not None:
2484
      raise errors.OpPrereqError("Diskless instance but disk"
2485
                                 " information passed", errors.ECODE_INVAL)
2486
    disks = []
2487
  else:
2488
    if (not opts.disks and not opts.sd_size
2489
        and mode == constants.INSTANCE_CREATE):
2490
      raise errors.OpPrereqError("No disk information specified",
2491
                                 errors.ECODE_INVAL)
2492
    if opts.disks and opts.sd_size is not None:
2493
      raise errors.OpPrereqError("Please use either the '--disk' or"
2494
                                 " '-s' option", errors.ECODE_INVAL)
2495
    if opts.sd_size is not None:
2496
      opts.disks = [(0, {constants.IDISK_SIZE: opts.sd_size})]
2497

    
2498
    if opts.disks:
2499
      try:
2500
        disk_max = max(int(didx[0]) + 1 for didx in opts.disks)
2501
      except ValueError, err:
2502
        raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err),
2503
                                   errors.ECODE_INVAL)
2504
      disks = [{}] * disk_max
2505
    else:
2506
      disks = []
2507
    for didx, ddict in opts.disks:
2508
      didx = int(didx)
2509
      if not isinstance(ddict, dict):
2510
        msg = "Invalid disk/%d value: expected dict, got %s" % (didx, ddict)
2511
        raise errors.OpPrereqError(msg, errors.ECODE_INVAL)
2512
      elif constants.IDISK_SIZE in ddict:
2513
        if constants.IDISK_ADOPT in ddict:
2514
          raise errors.OpPrereqError("Only one of 'size' and 'adopt' allowed"
2515
                                     " (disk %d)" % didx, errors.ECODE_INVAL)
2516
        try:
2517
          ddict[constants.IDISK_SIZE] = \
2518
            utils.ParseUnit(ddict[constants.IDISK_SIZE])
2519
        except ValueError, err:
2520
          raise errors.OpPrereqError("Invalid disk size for disk %d: %s" %
2521
                                     (didx, err), errors.ECODE_INVAL)
2522
      elif constants.IDISK_ADOPT in ddict:
2523
        if mode == constants.INSTANCE_IMPORT:
2524
          raise errors.OpPrereqError("Disk adoption not allowed for instance"
2525
                                     " import", errors.ECODE_INVAL)
2526
        ddict[constants.IDISK_SIZE] = 0
2527
      else:
2528
        raise errors.OpPrereqError("Missing size or adoption source for"
2529
                                   " disk %d" % didx, errors.ECODE_INVAL)
2530
      disks[didx] = ddict
2531

    
2532
  if opts.tags is not None:
2533
    tags = opts.tags.split(",")
2534
  else:
2535
    tags = []
2536

    
2537
  utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_COMPAT)
2538
  utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
2539

    
2540
  if mode == constants.INSTANCE_CREATE:
2541
    start = opts.start
2542
    os_type = opts.os
2543
    force_variant = opts.force_variant
2544
    src_node = None
2545
    src_path = None
2546
    no_install = opts.no_install
2547
    identify_defaults = False
2548
  elif mode == constants.INSTANCE_IMPORT:
2549
    start = False
2550
    os_type = None
2551
    force_variant = False
2552
    src_node = opts.src_node
2553
    src_path = opts.src_dir
2554
    no_install = None
2555
    identify_defaults = opts.identify_defaults
2556
  else:
2557
    raise errors.ProgrammerError("Invalid creation mode %s" % mode)
2558

    
2559
  op = opcodes.OpInstanceCreate(instance_name=instance,
2560
                                disks=disks,
2561
                                disk_template=opts.disk_template,
2562
                                nics=nics,
2563
                                conflicts_check=opts.conflicts_check,
2564
                                pnode=pnode, snode=snode,
2565
                                ip_check=opts.ip_check,
2566
                                name_check=opts.name_check,
2567
                                wait_for_sync=opts.wait_for_sync,
2568
                                file_storage_dir=opts.file_storage_dir,
2569
                                file_driver=opts.file_driver,
2570
                                iallocator=opts.iallocator,
2571
                                hypervisor=hypervisor,
2572
                                hvparams=hvparams,
2573
                                beparams=opts.beparams,
2574
                                osparams=opts.osparams,
2575
                                mode=mode,
2576
                                start=start,
2577
                                os_type=os_type,
2578
                                force_variant=force_variant,
2579
                                src_node=src_node,
2580
                                src_path=src_path,
2581
                                tags=tags,
2582
                                no_install=no_install,
2583
                                identify_defaults=identify_defaults,
2584
                                ignore_ipolicy=opts.ignore_ipolicy)
2585

    
2586
  SubmitOrSend(op, opts)
2587
  return 0
2588

    
2589

    
2590
class _RunWhileClusterStoppedHelper:
2591
  """Helper class for L{RunWhileClusterStopped} to simplify state management
2592

2593
  """
2594
  def __init__(self, feedback_fn, cluster_name, master_node, online_nodes):
2595
    """Initializes this class.
2596

2597
    @type feedback_fn: callable
2598
    @param feedback_fn: Feedback function
2599
    @type cluster_name: string
2600
    @param cluster_name: Cluster name
2601
    @type master_node: string
2602
    @param master_node Master node name
2603
    @type online_nodes: list
2604
    @param online_nodes: List of names of online nodes
2605

2606
    """
2607
    self.feedback_fn = feedback_fn
2608
    self.cluster_name = cluster_name
2609
    self.master_node = master_node
2610
    self.online_nodes = online_nodes
2611

    
2612
    self.ssh = ssh.SshRunner(self.cluster_name)
2613

    
2614
    self.nonmaster_nodes = [name for name in online_nodes
2615
                            if name != master_node]
2616

    
2617
    assert self.master_node not in self.nonmaster_nodes
2618

    
2619
  def _RunCmd(self, node_name, cmd):
2620
    """Runs a command on the local or a remote machine.
2621

2622
    @type node_name: string
2623
    @param node_name: Machine name
2624
    @type cmd: list
2625
    @param cmd: Command
2626

2627
    """
2628
    if node_name is None or node_name == self.master_node:
2629
      # No need to use SSH
2630
      result = utils.RunCmd(cmd)
2631
    else:
2632
      result = self.ssh.Run(node_name, constants.SSH_LOGIN_USER,
2633
                            utils.ShellQuoteArgs(cmd))
2634

    
2635
    if result.failed:
2636
      errmsg = ["Failed to run command %s" % result.cmd]
2637
      if node_name:
2638
        errmsg.append("on node %s" % node_name)
2639
      errmsg.append(": exitcode %s and error %s" %
2640
                    (result.exit_code, result.output))
2641
      raise errors.OpExecError(" ".join(errmsg))
2642

    
2643
  def Call(self, fn, *args):
2644
    """Call function while all daemons are stopped.
2645

2646
    @type fn: callable
2647
    @param fn: Function to be called
2648

2649
    """
2650
    # Pause watcher by acquiring an exclusive lock on watcher state file
2651
    self.feedback_fn("Blocking watcher")
2652
    watcher_block = utils.FileLock.Open(pathutils.WATCHER_LOCK_FILE)
2653
    try:
2654
      # TODO: Currently, this just blocks. There's no timeout.
2655
      # TODO: Should it be a shared lock?
2656
      watcher_block.Exclusive(blocking=True)
2657

    
2658
      # Stop master daemons, so that no new jobs can come in and all running
2659
      # ones are finished
2660
      self.feedback_fn("Stopping master daemons")
2661
      self._RunCmd(None, [pathutils.DAEMON_UTIL, "stop-master"])
2662
      try:
2663
        # Stop daemons on all nodes
2664
        for node_name in self.online_nodes:
2665
          self.feedback_fn("Stopping daemons on %s" % node_name)
2666
          self._RunCmd(node_name, [pathutils.DAEMON_UTIL, "stop-all"])
2667

    
2668
        # All daemons are shut down now
2669
        try:
2670
          return fn(self, *args)
2671
        except Exception, err:
2672
          _, errmsg = FormatError(err)
2673
          logging.exception("Caught exception")
2674
          self.feedback_fn(errmsg)
2675
          raise
2676
      finally:
2677
        # Start cluster again, master node last
2678
        for node_name in self.nonmaster_nodes + [self.master_node]:
2679
          self.feedback_fn("Starting daemons on %s" % node_name)
2680
          self._RunCmd(node_name, [pathutils.DAEMON_UTIL, "start-all"])
2681
    finally:
2682
      # Resume watcher
2683
      watcher_block.Close()
2684

    
2685

    
2686
def RunWhileClusterStopped(feedback_fn, fn, *args):
2687
  """Calls a function while all cluster daemons are stopped.
2688

2689
  @type feedback_fn: callable
2690
  @param feedback_fn: Feedback function
2691
  @type fn: callable
2692
  @param fn: Function to be called when daemons are stopped
2693

2694
  """
2695
  feedback_fn("Gathering cluster information")
2696

    
2697
  # This ensures we're running on the master daemon
2698
  cl = GetClient()
2699

    
2700
  (cluster_name, master_node) = \
2701
    cl.QueryConfigValues(["cluster_name", "master_node"])
2702

    
2703
  online_nodes = GetOnlineNodes([], cl=cl)
2704

    
2705
  # Don't keep a reference to the client. The master daemon will go away.
2706
  del cl
2707

    
2708
  assert master_node in online_nodes
2709

    
2710
  return _RunWhileClusterStoppedHelper(feedback_fn, cluster_name, master_node,
2711
                                       online_nodes).Call(fn, *args)
2712

    
2713

    
2714
def GenerateTable(headers, fields, separator, data,
2715
                  numfields=None, unitfields=None,
2716
                  units=None):
2717
  """Prints a table with headers and different fields.
2718

2719
  @type headers: dict
2720
  @param headers: dictionary mapping field names to headers for
2721
      the table
2722
  @type fields: list
2723
  @param fields: the field names corresponding to each row in
2724
      the data field
2725
  @param separator: the separator to be used; if this is None,
2726
      the default 'smart' algorithm is used which computes optimal
2727
      field width, otherwise just the separator is used between
2728
      each field
2729
  @type data: list
2730
  @param data: a list of lists, each sublist being one row to be output
2731
  @type numfields: list
2732
  @param numfields: a list with the fields that hold numeric
2733
      values and thus should be right-aligned
2734
  @type unitfields: list
2735
  @param unitfields: a list with the fields that hold numeric
2736
      values that should be formatted with the units field
2737
  @type units: string or None
2738
  @param units: the units we should use for formatting, or None for
2739
      automatic choice (human-readable for non-separator usage, otherwise
2740
      megabytes); this is a one-letter string
2741

2742
  """
2743
  if units is None:
2744
    if separator:
2745
      units = "m"
2746
    else:
2747
      units = "h"
2748

    
2749
  if numfields is None:
2750
    numfields = []
2751
  if unitfields is None:
2752
    unitfields = []
2753

    
2754
  numfields = utils.FieldSet(*numfields)   # pylint: disable=W0142
2755
  unitfields = utils.FieldSet(*unitfields) # pylint: disable=W0142
2756

    
2757
  format_fields = []
2758
  for field in fields:
2759
    if headers and field not in headers:
2760
      # TODO: handle better unknown fields (either revert to old
2761
      # style of raising exception, or deal more intelligently with
2762
      # variable fields)
2763
      headers[field] = field
2764
    if separator is not None:
2765
      format_fields.append("%s")
2766
    elif numfields.Matches(field):
2767
      format_fields.append("%*s")
2768
    else:
2769
      format_fields.append("%-*s")
2770

    
2771
  if separator is None:
2772
    mlens = [0 for name in fields]
2773
    format_str = " ".join(format_fields)
2774
  else:
2775
    format_str = separator.replace("%", "%%").join(format_fields)
2776

    
2777
  for row in data:
2778
    if row is None:
2779
      continue
2780
    for idx, val in enumerate(row):
2781
      if unitfields.Matches(fields[idx]):
2782
        try:
2783
          val = int(val)
2784
        except (TypeError, ValueError):
2785
          pass
2786
        else:
2787
          val = row[idx] = utils.FormatUnit(val, units)
2788
      val = row[idx] = str(val)
2789
      if separator is None:
2790
        mlens[idx] = max(mlens[idx], len(val))
2791

    
2792
  result = []
2793
  if headers:
2794
    args = []
2795
    for idx, name in enumerate(fields):
2796
      hdr = headers[name]
2797
      if separator is None:
2798
        mlens[idx] = max(mlens[idx], len(hdr))
2799
        args.append(mlens[idx])
2800
      args.append(hdr)
2801
    result.append(format_str % tuple(args))
2802

    
2803
  if separator is None:
2804
    assert len(mlens) == len(fields)
2805

    
2806
    if fields and not numfields.Matches(fields[-1]):
2807
      mlens[-1] = 0
2808

    
2809
  for line in data:
2810
    args = []
2811
    if line is None:
2812
      line = ["-" for _ in fields]
2813
    for idx in range(len(fields)):
2814
      if separator is None:
2815
        args.append(mlens[idx])
2816
      args.append(line[idx])
2817
    result.append(format_str % tuple(args))
2818

    
2819
  return result
2820

    
2821

    
2822
def _FormatBool(value):
2823
  """Formats a boolean value as a string.
2824

2825
  """
2826
  if value:
2827
    return "Y"
2828
  return "N"
2829

    
2830

    
2831
#: Default formatting for query results; (callback, align right)
2832
_DEFAULT_FORMAT_QUERY = {
2833
  constants.QFT_TEXT: (str, False),
2834
  constants.QFT_BOOL: (_FormatBool, False),
2835
  constants.QFT_NUMBER: (str, True),
2836
  constants.QFT_TIMESTAMP: (utils.FormatTime, False),
2837
  constants.QFT_OTHER: (str, False),
2838
  constants.QFT_UNKNOWN: (str, False),
2839
  }
2840

    
2841

    
2842
def _GetColumnFormatter(fdef, override, unit):
2843
  """Returns formatting function for a field.
2844

2845
  @type fdef: L{objects.QueryFieldDefinition}
2846
  @type override: dict
2847
  @param override: Dictionary for overriding field formatting functions,
2848
    indexed by field name, contents like L{_DEFAULT_FORMAT_QUERY}
2849
  @type unit: string
2850
  @param unit: Unit used for formatting fields of type L{constants.QFT_UNIT}
2851
  @rtype: tuple; (callable, bool)
2852
  @return: Returns the function to format a value (takes one parameter) and a
2853
    boolean for aligning the value on the right-hand side
2854

2855
  """
2856
  fmt = override.get(fdef.name, None)
2857
  if fmt is not None:
2858
    return fmt
2859

    
2860
  assert constants.QFT_UNIT not in _DEFAULT_FORMAT_QUERY
2861

    
2862
  if fdef.kind == constants.QFT_UNIT:
2863
    # Can't keep this information in the static dictionary
2864
    return (lambda value: utils.FormatUnit(value, unit), True)
2865

    
2866
  fmt = _DEFAULT_FORMAT_QUERY.get(fdef.kind, None)
2867
  if fmt is not None:
2868
    return fmt
2869

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

    
2872

    
2873
class _QueryColumnFormatter:
2874
  """Callable class for formatting fields of a query.
2875

2876
  """
2877
  def __init__(self, fn, status_fn, verbose):
2878
    """Initializes this class.
2879

2880
    @type fn: callable
2881
    @param fn: Formatting function
2882
    @type status_fn: callable
2883
    @param status_fn: Function to report fields' status
2884
    @type verbose: boolean
2885
    @param verbose: whether to use verbose field descriptions or not
2886

2887
    """
2888
    self._fn = fn
2889
    self._status_fn = status_fn
2890
    self._verbose = verbose
2891

    
2892
  def __call__(self, data):
2893
    """Returns a field's string representation.
2894

2895
    """
2896
    (status, value) = data
2897

    
2898
    # Report status
2899
    self._status_fn(status)
2900

    
2901
    if status == constants.RS_NORMAL:
2902
      return self._fn(value)
2903

    
2904
    assert value is None, \
2905
           "Found value %r for abnormal status %s" % (value, status)
2906

    
2907
    return FormatResultError(status, self._verbose)
2908

    
2909

    
2910
def FormatResultError(status, verbose):
2911
  """Formats result status other than L{constants.RS_NORMAL}.
2912

2913
  @param status: The result status
2914
  @type verbose: boolean
2915
  @param verbose: Whether to return the verbose text
2916
  @return: Text of result status
2917

2918
  """
2919
  assert status != constants.RS_NORMAL, \
2920
         "FormatResultError called with status equal to constants.RS_NORMAL"
2921
  try:
2922
    (verbose_text, normal_text) = constants.RSS_DESCRIPTION[status]
2923
  except KeyError:
2924
    raise NotImplementedError("Unknown status %s" % status)
2925
  else:
2926
    if verbose:
2927
      return verbose_text
2928
    return normal_text
2929

    
2930

    
2931
def FormatQueryResult(result, unit=None, format_override=None, separator=None,
2932
                      header=False, verbose=False):
2933
  """Formats data in L{objects.QueryResponse}.
2934

2935
  @type result: L{objects.QueryResponse}
2936
  @param result: result of query operation
2937
  @type unit: string
2938
  @param unit: Unit used for formatting fields of type L{constants.QFT_UNIT},
2939
    see L{utils.text.FormatUnit}
2940
  @type format_override: dict
2941
  @param format_override: Dictionary for overriding field formatting functions,
2942
    indexed by field name, contents like L{_DEFAULT_FORMAT_QUERY}
2943
  @type separator: string or None
2944
  @param separator: String used to separate fields
2945
  @type header: bool
2946
  @param header: Whether to output header row
2947
  @type verbose: boolean
2948
  @param verbose: whether to use verbose field descriptions or not
2949

2950
  """
2951
  if unit is None:
2952
    if separator:
2953
      unit = "m"
2954
    else:
2955
      unit = "h"
2956

    
2957
  if format_override is None:
2958
    format_override = {}
2959

    
2960
  stats = dict.fromkeys(constants.RS_ALL, 0)
2961

    
2962
  def _RecordStatus(status):
2963
    if status in stats:
2964
      stats[status] += 1
2965

    
2966
  columns = []
2967
  for fdef in result.fields:
2968
    assert fdef.title and fdef.name
2969
    (fn, align_right) = _GetColumnFormatter(fdef, format_override, unit)
2970
    columns.append(TableColumn(fdef.title,
2971
                               _QueryColumnFormatter(fn, _RecordStatus,
2972
                                                     verbose),
2973
                               align_right))
2974

    
2975
  table = FormatTable(result.data, columns, header, separator)
2976

    
2977
  # Collect statistics
2978
  assert len(stats) == len(constants.RS_ALL)
2979
  assert compat.all(count >= 0 for count in stats.values())
2980

    
2981
  # Determine overall status. If there was no data, unknown fields must be
2982
  # detected via the field definitions.
2983
  if (stats[constants.RS_UNKNOWN] or
2984
      (not result.data and _GetUnknownFields(result.fields))):
2985
    status = QR_UNKNOWN
2986
  elif compat.any(count > 0 for key, count in stats.items()
2987
                  if key != constants.RS_NORMAL):
2988
    status = QR_INCOMPLETE
2989
  else:
2990
    status = QR_NORMAL
2991

    
2992
  return (status, table)
2993

    
2994

    
2995
def _GetUnknownFields(fdefs):
2996
  """Returns list of unknown fields included in C{fdefs}.
2997

2998
  @type fdefs: list of L{objects.QueryFieldDefinition}
2999

3000
  """
3001
  return [fdef for fdef in fdefs
3002
          if fdef.kind == constants.QFT_UNKNOWN]
3003

    
3004

    
3005
def _WarnUnknownFields(fdefs):
3006
  """Prints a warning to stderr if a query included unknown fields.
3007

3008
  @type fdefs: list of L{objects.QueryFieldDefinition}
3009

3010
  """
3011
  unknown = _GetUnknownFields(fdefs)
3012
  if unknown:
3013
    ToStderr("Warning: Queried for unknown fields %s",
3014
             utils.CommaJoin(fdef.name for fdef in unknown))
3015
    return True
3016

    
3017
  return False
3018

    
3019

    
3020
def GenericList(resource, fields, names, unit, separator, header, cl=None,
3021
                format_override=None, verbose=False, force_filter=False,
3022
                namefield=None, qfilter=None, isnumeric=False):
3023
  """Generic implementation for listing all items of a resource.
3024

3025
  @param resource: One of L{constants.QR_VIA_LUXI}
3026
  @type fields: list of strings
3027
  @param fields: List of fields to query for
3028
  @type names: list of strings
3029
  @param names: Names of items to query for
3030
  @type unit: string or None
3031
  @param unit: Unit used for formatting fields of type L{constants.QFT_UNIT} or
3032
    None for automatic choice (human-readable for non-separator usage,
3033
    otherwise megabytes); this is a one-letter string
3034
  @type separator: string or None
3035
  @param separator: String used to separate fields
3036
  @type header: bool
3037
  @param header: Whether to show header row
3038
  @type force_filter: bool
3039
  @param force_filter: Whether to always treat names as filter
3040
  @type format_override: dict
3041
  @param format_override: Dictionary for overriding field formatting functions,
3042
    indexed by field name, contents like L{_DEFAULT_FORMAT_QUERY}
3043
  @type verbose: boolean
3044
  @param verbose: whether to use verbose field descriptions or not
3045
  @type namefield: string
3046
  @param namefield: Name of field to use for simple filters (see
3047
    L{qlang.MakeFilter} for details)
3048
  @type qfilter: list or None
3049
  @param qfilter: Query filter (in addition to names)
3050
  @param isnumeric: bool
3051
  @param isnumeric: Whether the namefield's type is numeric, and therefore
3052
    any simple filters built by namefield should use integer values to
3053
    reflect that
3054

3055
  """
3056
  if not names:
3057
    names = None
3058

    
3059
  namefilter = qlang.MakeFilter(names, force_filter, namefield=namefield,
3060
                                isnumeric=isnumeric)
3061

    
3062
  if qfilter is None:
3063
    qfilter = namefilter
3064
  elif namefilter is not None:
3065
    qfilter = [qlang.OP_AND, namefilter, qfilter]
3066

    
3067
  if cl is None:
3068
    cl = GetClient()
3069

    
3070
  response = cl.Query(resource, fields, qfilter)
3071

    
3072
  found_unknown = _WarnUnknownFields(response.fields)
3073

    
3074
  (status, data) = FormatQueryResult(response, unit=unit, separator=separator,
3075
                                     header=header,
3076
                                     format_override=format_override,
3077
                                     verbose=verbose)
3078

    
3079
  for line in data:
3080
    ToStdout(line)
3081

    
3082
  assert ((found_unknown and status == QR_UNKNOWN) or
3083
          (not found_unknown and status != QR_UNKNOWN))
3084

    
3085
  if status == QR_UNKNOWN:
3086
    return constants.EXIT_UNKNOWN_FIELD
3087

    
3088
  # TODO: Should the list command fail if not all data could be collected?
3089
  return constants.EXIT_SUCCESS
3090

    
3091

    
3092
def _FieldDescValues(fdef):
3093
  """Helper function for L{GenericListFields} to get query field description.
3094

3095
  @type fdef: L{objects.QueryFieldDefinition}
3096
  @rtype: list
3097

3098
  """
3099
  return [
3100
    fdef.name,
3101
    _QFT_NAMES.get(fdef.kind, fdef.kind),
3102
    fdef.title,
3103
    fdef.doc,
3104
    ]
3105

    
3106

    
3107
def GenericListFields(resource, fields, separator, header, cl=None):
3108
  """Generic implementation for listing fields for a resource.
3109

3110
  @param resource: One of L{constants.QR_VIA_LUXI}
3111
  @type fields: list of strings
3112
  @param fields: List of fields to query for
3113
  @type separator: string or None
3114
  @param separator: String used to separate fields
3115
  @type header: bool
3116
  @param header: Whether to show header row
3117

3118
  """
3119
  if cl is None:
3120
    cl = GetClient()
3121

    
3122
  if not fields:
3123
    fields = None
3124

    
3125
  response = cl.QueryFields(resource, fields)
3126

    
3127
  found_unknown = _WarnUnknownFields(response.fields)
3128

    
3129
  columns = [
3130
    TableColumn("Name", str, False),
3131
    TableColumn("Type", str, False),
3132
    TableColumn("Title", str, False),
3133
    TableColumn("Description", str, False),
3134
    ]
3135

    
3136
  rows = map(_FieldDescValues, response.fields)
3137

    
3138
  for line in FormatTable(rows, columns, header, separator):
3139
    ToStdout(line)
3140

    
3141
  if found_unknown:
3142
    return constants.EXIT_UNKNOWN_FIELD
3143

    
3144
  return constants.EXIT_SUCCESS
3145

    
3146

    
3147
class TableColumn:
3148
  """Describes a column for L{FormatTable}.
3149

3150
  """
3151
  def __init__(self, title, fn, align_right):
3152
    """Initializes this class.
3153

3154
    @type title: string
3155
    @param title: Column title
3156
    @type fn: callable
3157
    @param fn: Formatting function
3158
    @type align_right: bool
3159
    @param align_right: Whether to align values on the right-hand side
3160

3161
    """
3162
    self.title = title
3163
    self.format = fn
3164
    self.align_right = align_right
3165

    
3166

    
3167
def _GetColFormatString(width, align_right):
3168
  """Returns the format string for a field.
3169

3170
  """
3171
  if align_right:
3172
    sign = ""
3173
  else:
3174
    sign = "-"
3175

    
3176
  return "%%%s%ss" % (sign, width)
3177

    
3178

    
3179
def FormatTable(rows, columns, header, separator):
3180
  """Formats data as a table.
3181

3182
  @type rows: list of lists
3183
  @param rows: Row data, one list per row
3184
  @type columns: list of L{TableColumn}
3185
  @param columns: Column descriptions
3186
  @type header: bool
3187
  @param header: Whether to show header row
3188
  @type separator: string or None
3189
  @param separator: String used to separate columns
3190

3191
  """
3192
  if header:
3193
    data = [[col.title for col in columns]]
3194
    colwidth = [len(col.title) for col in columns]
3195
  else:
3196
    data = []
3197
    colwidth = [0 for _ in columns]
3198

    
3199
  # Format row data
3200
  for row in rows:
3201
    assert len(row) == len(columns)
3202

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

    
3205
    if separator is None:
3206
      # Update column widths
3207
      for idx, (oldwidth, value) in enumerate(zip(colwidth, formatted)):
3208
        # Modifying a list's items while iterating is fine
3209
        colwidth[idx] = max(oldwidth, len(value))
3210

    
3211
    data.append(formatted)
3212

    
3213
  if separator is not None:
3214
    # Return early if a separator is used
3215
    return [separator.join(row) for row in data]
3216

    
3217
  if columns and not columns[-1].align_right:
3218
    # Avoid unnecessary spaces at end of line
3219
    colwidth[-1] = 0
3220

    
3221
  # Build format string
3222
  fmt = " ".join([_GetColFormatString(width, col.align_right)
3223
                  for col, width in zip(columns, colwidth)])
3224

    
3225
  return [fmt % tuple(row) for row in data]
3226

    
3227

    
3228
def FormatTimestamp(ts):
3229
  """Formats a given timestamp.
3230

3231
  @type ts: timestamp
3232
  @param ts: a timeval-type timestamp, a tuple of seconds and microseconds
3233

3234
  @rtype: string
3235
  @return: a string with the formatted timestamp
3236

3237
  """
3238
  if not isinstance(ts, (tuple, list)) or len(ts) != 2:
3239
    return "?"
3240

    
3241
  (sec, usecs) = ts
3242
  return utils.FormatTime(sec, usecs=usecs)
3243

    
3244

    
3245
def ParseTimespec(value):
3246
  """Parse a time specification.
3247

3248
  The following suffixed will be recognized:
3249

3250
    - s: seconds
3251
    - m: minutes
3252
    - h: hours
3253
    - d: day
3254
    - w: weeks
3255

3256
  Without any suffix, the value will be taken to be in seconds.
3257

3258
  """
3259
  value = str(value)
3260
  if not value:
3261
    raise errors.OpPrereqError("Empty time specification passed",
3262
                               errors.ECODE_INVAL)
3263
  suffix_map = {
3264
    "s": 1,
3265
    "m": 60,
3266
    "h": 3600,
3267
    "d": 86400,
3268
    "w": 604800,
3269
    }
3270
  if value[-1] not in suffix_map:
3271
    try:
3272
      value = int(value)
3273
    except (TypeError, ValueError):
3274
      raise errors.OpPrereqError("Invalid time specification '%s'" % value,
3275
                                 errors.ECODE_INVAL)
3276
  else:
3277
    multiplier = suffix_map[value[-1]]
3278
    value = value[:-1]
3279
    if not value: # no data left after stripping the suffix
3280
      raise errors.OpPrereqError("Invalid time specification (only"
3281
                                 " suffix passed)", errors.ECODE_INVAL)
3282
    try:
3283
      value = int(value) * multiplier
3284
    except (TypeError, ValueError):
3285
      raise errors.OpPrereqError("Invalid time specification '%s'" % value,
3286
                                 errors.ECODE_INVAL)
3287
  return value
3288

    
3289

    
3290
def GetOnlineNodes(nodes, cl=None, nowarn=False, secondary_ips=False,
3291
                   filter_master=False, nodegroup=None):
3292
  """Returns the names of online nodes.
3293

3294
  This function will also log a warning on stderr with the names of
3295
  the online nodes.
3296

3297
  @param nodes: if not empty, use only this subset of nodes (minus the
3298
      offline ones)
3299
  @param cl: if not None, luxi client to use
3300
  @type nowarn: boolean
3301
  @param nowarn: by default, this function will output a note with the
3302
      offline nodes that are skipped; if this parameter is True the
3303
      note is not displayed
3304
  @type secondary_ips: boolean
3305
  @param secondary_ips: if True, return the secondary IPs instead of the
3306
      names, useful for doing network traffic over the replication interface
3307
      (if any)
3308
  @type filter_master: boolean
3309
  @param filter_master: if True, do not return the master node in the list
3310
      (useful in coordination with secondary_ips where we cannot check our
3311
      node name against the list)
3312
  @type nodegroup: string
3313
  @param nodegroup: If set, only return nodes in this node group
3314

3315
  """
3316
  if cl is None:
3317
    cl = GetClient()
3318

    
3319
  qfilter = []
3320

    
3321
  if nodes:
3322
    qfilter.append(qlang.MakeSimpleFilter("name", nodes))
3323

    
3324
  if nodegroup is not None:
3325
    qfilter.append([qlang.OP_OR, [qlang.OP_EQUAL, "group", nodegroup],
3326
                                 [qlang.OP_EQUAL, "group.uuid", nodegroup]])
3327

    
3328
  if filter_master:
3329
    qfilter.append([qlang.OP_NOT, [qlang.OP_TRUE, "master"]])
3330

    
3331
  if qfilter:
3332
    if len(qfilter) > 1:
3333
      final_filter = [qlang.OP_AND] + qfilter
3334
    else:
3335
      assert len(qfilter) == 1
3336
      final_filter = qfilter[0]
3337
  else:
3338
    final_filter = None
3339

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

    
3342
  def _IsOffline(row):
3343
    (_, (_, offline), _) = row
3344
    return offline
3345

    
3346
  def _GetName(row):
3347
    ((_, name), _, _) = row
3348
    return name
3349

    
3350
  def _GetSip(row):
3351
    (_, _, (_, sip)) = row
3352
    return sip
3353

    
3354
  (offline, online) = compat.partition(result.data, _IsOffline)
3355

    
3356
  if offline and not nowarn:
3357
    ToStderr("Note: skipping offline node(s): %s" %
3358
             utils.CommaJoin(map(_GetName, offline)))
3359

    
3360
  if secondary_ips:
3361
    fn = _GetSip
3362
  else:
3363
    fn = _GetName
3364

    
3365
  return map(fn, online)
3366

    
3367

    
3368
def _ToStream(stream, txt, *args):
3369
  """Write a message to a stream, bypassing the logging system
3370

3371
  @type stream: file object
3372
  @param stream: the file to which we should write
3373
  @type txt: str
3374
  @param txt: the message
3375

3376
  """
3377
  try:
3378
    if args:
3379
      args = tuple(args)
3380
      stream.write(txt % args)
3381
    else:
3382
      stream.write(txt)
3383
    stream.write("\n")
3384
    stream.flush()
3385
  except IOError, err:
3386
    if err.errno == errno.EPIPE:
3387
      # our terminal went away, we'll exit
3388
      sys.exit(constants.EXIT_FAILURE)
3389
    else:
3390
      raise
3391

    
3392

    
3393
def ToStdout(txt, *args):
3394
  """Write a message to stdout only, bypassing the logging system
3395

3396
  This is just a wrapper over _ToStream.
3397

3398
  @type txt: str
3399
  @param txt: the message
3400

3401
  """
3402
  _ToStream(sys.stdout, txt, *args)
3403

    
3404

    
3405
def ToStderr(txt, *args):
3406
  """Write a message to stderr only, bypassing the logging system
3407

3408
  This is just a wrapper over _ToStream.
3409

3410
  @type txt: str
3411
  @param txt: the message
3412

3413
  """
3414
  _ToStream(sys.stderr, txt, *args)
3415

    
3416

    
3417
class JobExecutor(object):
3418
  """Class which manages the submission and execution of multiple jobs.
3419

3420
  Note that instances of this class should not be reused between
3421
  GetResults() calls.
3422

3423
  """
3424
  def __init__(self, cl=None, verbose=True, opts=None, feedback_fn=None):
3425
    self.queue = []
3426
    if cl is None:
3427
      cl = GetClient()
3428
    self.cl = cl
3429
    self.verbose = verbose
3430
    self.jobs = []
3431
    self.opts = opts
3432
    self.feedback_fn = feedback_fn
3433
    self._counter = itertools.count()
3434

    
3435
  @staticmethod
3436
  def _IfName(name, fmt):
3437
    """Helper function for formatting name.
3438

3439
    """
3440
    if name:
3441
      return fmt % name
3442

    
3443
    return ""
3444

    
3445
  def QueueJob(self, name, *ops):
3446
    """Record a job for later submit.
3447

3448
    @type name: string
3449
    @param name: a description of the job, will be used in WaitJobSet
3450

3451
    """
3452
    SetGenericOpcodeOpts(ops, self.opts)
3453
    self.queue.append((self._counter.next(), name, ops))
3454

    
3455
  def AddJobId(self, name, status, job_id):
3456
    """Adds a job ID to the internal queue.
3457

3458
    """
3459
    self.jobs.append((self._counter.next(), status, job_id, name))
3460

    
3461
  def SubmitPending(self, each=False):
3462
    """Submit all pending jobs.
3463

3464
    """
3465
    if each:
3466
      results = []
3467
      for (_, _, ops) in self.queue:
3468
        # SubmitJob will remove the success status, but raise an exception if
3469
        # the submission fails, so we'll notice that anyway.
3470
        results.append([True, self.cl.SubmitJob(ops)[0]])
3471
    else:
3472
      results = self.cl.SubmitManyJobs([ops for (_, _, ops) in self.queue])
3473
    for ((status, data), (idx, name, _)) in zip(results, self.queue):
3474
      self.jobs.append((idx, status, data, name))
3475

    
3476
  def _ChooseJob(self):
3477
    """Choose a non-waiting/queued job to poll next.
3478

3479
    """
3480
    assert self.jobs, "_ChooseJob called with empty job list"
3481

    
3482
    result = self.cl.QueryJobs([i[2] for i in self.jobs[:_CHOOSE_BATCH]],
3483
                               ["status"])
3484
    assert result
3485

    
3486
    for job_data, status in zip(self.jobs, result):
3487
      if (isinstance(status, list) and status and
3488
          status[0] in (constants.JOB_STATUS_QUEUED,
3489
                        constants.JOB_STATUS_WAITING,
3490
                        constants.JOB_STATUS_CANCELING)):
3491
        # job is still present and waiting
3492
        continue
3493
      # good candidate found (either running job or lost job)
3494
      self.jobs.remove(job_data)
3495
      return job_data
3496

    
3497
    # no job found
3498
    return self.jobs.pop(0)
3499

    
3500
  def GetResults(self):
3501
    """Wait for and return the results of all jobs.
3502

3503
    @rtype: list
3504
    @return: list of tuples (success, job results), in the same order
3505
        as the submitted jobs; if a job has failed, instead of the result
3506
        there will be the error message
3507

3508
    """
3509
    if not self.jobs:
3510
      self.SubmitPending()
3511
    results = []
3512
    if self.verbose:
3513
      ok_jobs = [row[2] for row in self.jobs if row[1]]
3514
      if ok_jobs:
3515
        ToStdout("Submitted jobs %s", utils.CommaJoin(ok_jobs))
3516

    
3517
    # first, remove any non-submitted jobs
3518
    self.jobs, failures = compat.partition(self.jobs, lambda x: x[1])
3519
    for idx, _, jid, name in failures:
3520
      ToStderr("Failed to submit job%s: %s", self._IfName(name, " for %s"), jid)
3521
      results.append((idx, False, jid))
3522

    
3523
    while self.jobs:
3524
      (idx, _, jid, name) = self._ChooseJob()
3525
      ToStdout("Waiting for job %s%s ...", jid, self._IfName(name, " for %s"))
3526
      try:
3527
        job_result = PollJob(jid, cl=self.cl, feedback_fn=self.feedback_fn)
3528
        success = True
3529
      except errors.JobLost, err:
3530
        _, job_result = FormatError(err)
3531
        ToStderr("Job %s%s has been archived, cannot check its result",
3532
                 jid, self._IfName(name, " for %s"))
3533
        success = False
3534
      except (errors.GenericError, luxi.ProtocolError), err:
3535
        _, job_result = FormatError(err)
3536
        success = False
3537
        # the error message will always be shown, verbose or not
3538
        ToStderr("Job %s%s has failed: %s",
3539
                 jid, self._IfName(name, " for %s"), job_result)
3540

    
3541
      results.append((idx, success, job_result))
3542

    
3543
    # sort based on the index, then drop it
3544
    results.sort()
3545
    results = [i[1:] for i in results]
3546

    
3547
    return results
3548

    
3549
  def WaitOrShow(self, wait):
3550
    """Wait for job results or only print the job IDs.
3551

3552
    @type wait: boolean
3553
    @param wait: whether to wait or not
3554

3555
    """
3556
    if wait:
3557
      return self.GetResults()
3558
    else:
3559
      if not self.jobs:
3560
        self.SubmitPending()
3561
      for _, status, result, name in self.jobs:
3562
        if status:
3563
          ToStdout("%s: %s", result, name)
3564
        else:
3565
          ToStderr("Failure for %s: %s", name, result)
3566
      return [row[1:3] for row in self.jobs]
3567

    
3568

    
3569
def FormatParameterDict(buf, param_dict, actual, level=1):
3570
  """Formats a parameter dictionary.
3571

3572
  @type buf: L{StringIO}
3573
  @param buf: the buffer into which to write
3574
  @type param_dict: dict
3575
  @param param_dict: the own parameters
3576
  @type actual: dict
3577
  @param actual: the current parameter set (including defaults)
3578
  @param level: Level of indent
3579

3580
  """
3581
  indent = "  " * level
3582

    
3583
  for key in sorted(actual):
3584
    data = actual[key]
3585
    buf.write("%s- %s:" % (indent, key))
3586

    
3587
    if isinstance(data, dict) and data:
3588
      buf.write("\n")
3589
      FormatParameterDict(buf, param_dict.get(key, {}), data,
3590
                          level=level + 1)
3591
    else:
3592
      val = param_dict.get(key, "default (%s)" % data)
3593
      buf.write(" %s\n" % val)
3594

    
3595

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

3599
  This function is used to request confirmation for doing an operation
3600
  on a given list of list_type.
3601

3602
  @type names: list
3603
  @param names: the list of names that we display when
3604
      we ask for confirmation
3605
  @type list_type: str
3606
  @param list_type: Human readable name for elements in the list (e.g. nodes)
3607
  @type text: str
3608
  @param text: the operation that the user should confirm
3609
  @rtype: boolean
3610
  @return: True or False depending on user's confirmation.
3611

3612
  """
3613
  count = len(names)
3614
  msg = ("The %s will operate on %d %s.\n%s"
3615
         "Do you want to continue?" % (text, count, list_type, extra))
3616
  affected = (("\nAffected %s:\n" % list_type) +
3617
              "\n".join(["  %s" % name for name in names]))
3618

    
3619
  choices = [("y", True, "Yes, execute the %s" % text),
3620
             ("n", False, "No, abort the %s" % text)]
3621

    
3622
  if count > 20:
3623
    choices.insert(1, ("v", "v", "View the list of affected %s" % list_type))
3624
    question = msg
3625
  else:
3626
    question = msg + affected
3627

    
3628
  choice = AskUser(question, choices)
3629
  if choice == "v":
3630
    choices.pop(1)
3631
    choice = AskUser(msg + affected, choices)
3632
  return choice
3633

    
3634

    
3635
def _MaybeParseUnit(elements):
3636
  """Parses and returns an array of potential values with units.
3637

3638
  """
3639
  parsed = {}
3640
  for k, v in elements.items():
3641
    if v == constants.VALUE_DEFAULT:
3642
      parsed[k] = v
3643
    else:
3644
      parsed[k] = utils.ParseUnit(v)
3645
  return parsed
3646

    
3647

    
3648
def CreateIPolicyFromOpts(ispecs_mem_size=None,
3649
                          ispecs_cpu_count=None,
3650
                          ispecs_disk_count=None,
3651
                          ispecs_disk_size=None,
3652
                          ispecs_nic_count=None,
3653
                          ipolicy_disk_templates=None,
3654
                          ipolicy_vcpu_ratio=None,
3655
                          ipolicy_spindle_ratio=None,
3656
                          group_ipolicy=False,
3657
                          allowed_values=None,
3658
                          fill_all=False):
3659
  """Creation of instance policy based on command line options.
3660

3661
  @param fill_all: whether for cluster policies we should ensure that
3662
    all values are filled
3663

3664

3665
  """
3666
  try:
3667
    if ispecs_mem_size:
3668
      ispecs_mem_size = _MaybeParseUnit(ispecs_mem_size)
3669
    if ispecs_disk_size:
3670
      ispecs_disk_size = _MaybeParseUnit(ispecs_disk_size)
3671
  except (TypeError, ValueError, errors.UnitParseError), err:
3672
    raise errors.OpPrereqError("Invalid disk (%s) or memory (%s) size"
3673
                               " in policy: %s" %
3674
                               (ispecs_disk_size, ispecs_mem_size, err),
3675
                               errors.ECODE_INVAL)
3676

    
3677
  # prepare ipolicy dict
3678
  ipolicy_transposed = {
3679
    constants.ISPEC_MEM_SIZE: ispecs_mem_size,
3680
    constants.ISPEC_CPU_COUNT: ispecs_cpu_count,
3681
    constants.ISPEC_DISK_COUNT: ispecs_disk_count,
3682
    constants.ISPEC_DISK_SIZE: ispecs_disk_size,
3683
    constants.ISPEC_NIC_COUNT: ispecs_nic_count,
3684
    }
3685

    
3686
  # first, check that the values given are correct
3687
  if group_ipolicy:
3688
    forced_type = TISPECS_GROUP_TYPES
3689
  else:
3690
    forced_type = TISPECS_CLUSTER_TYPES
3691

    
3692
  for specs in ipolicy_transposed.values():
3693
    utils.ForceDictType(specs, forced_type, allowed_values=allowed_values)
3694

    
3695
  # then transpose
3696
  ipolicy_out = objects.MakeEmptyIPolicy()
3697
  for name, specs in ipolicy_transposed.iteritems():
3698
    assert name in constants.ISPECS_PARAMETERS
3699
    for key, val in specs.items(): # {min: .. ,max: .., std: ..}
3700
      ipolicy_out[key][name] = val
3701

    
3702
  # no filldict for non-dicts
3703
  if not group_ipolicy and fill_all:
3704
    if ipolicy_disk_templates is None:
3705
      ipolicy_disk_templates = constants.DISK_TEMPLATES
3706
    if ipolicy_vcpu_ratio is None:
3707
      ipolicy_vcpu_ratio = \
3708
        constants.IPOLICY_DEFAULTS[constants.IPOLICY_VCPU_RATIO]
3709
    if ipolicy_spindle_ratio is None:
3710
      ipolicy_spindle_ratio = \
3711
        constants.IPOLICY_DEFAULTS[constants.IPOLICY_SPINDLE_RATIO]
3712
  if ipolicy_disk_templates is not None:
3713
    ipolicy_out[constants.IPOLICY_DTS] = list(ipolicy_disk_templates)
3714
  if ipolicy_vcpu_ratio is not None:
3715
    ipolicy_out[constants.IPOLICY_VCPU_RATIO] = ipolicy_vcpu_ratio
3716
  if ipolicy_spindle_ratio is not None:
3717
    ipolicy_out[constants.IPOLICY_SPINDLE_RATIO] = ipolicy_spindle_ratio
3718

    
3719
  assert not (frozenset(ipolicy_out.keys()) - constants.IPOLICY_ALL_KEYS)
3720

    
3721
  return ipolicy_out