Statistics
| Branch: | Tag: | Revision:

root / lib / cli.py @ eb279644

History | View | Annotate | Download (99.7 kB)

1
#
2
#
3

    
4
# Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011 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
from cStringIO import StringIO
32

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

    
45
from optparse import (OptionParser, TitledHelpFormatter,
46
                      Option, OptionValueError)
47

    
48

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

    
233
NO_PREFIX = "no_"
234
UN_PREFIX = "-"
235

    
236
#: Priorities (sorted)
237
_PRIORITY_NAMES = [
238
  ("low", constants.OP_PRIO_LOW),
239
  ("normal", constants.OP_PRIO_NORMAL),
240
  ("high", constants.OP_PRIO_HIGH),
241
  ]
242

    
243
#: Priority dictionary for easier lookup
244
# TODO: Replace this and _PRIORITY_NAMES with a single sorted dictionary once
245
# we migrate to Python 2.6
246
_PRIONAME_TO_VALUE = dict(_PRIORITY_NAMES)
247

    
248
# Query result status for clients
249
(QR_NORMAL,
250
 QR_UNKNOWN,
251
 QR_INCOMPLETE) = range(3)
252

    
253

    
254
class _Argument:
255
  def __init__(self, min=0, max=None): # pylint: disable-msg=W0622
256
    self.min = min
257
    self.max = max
258

    
259
  def __repr__(self):
260
    return ("<%s min=%s max=%s>" %
261
            (self.__class__.__name__, self.min, self.max))
262

    
263

    
264
class ArgSuggest(_Argument):
265
  """Suggesting argument.
266

267
  Value can be any of the ones passed to the constructor.
268

269
  """
270
  # pylint: disable-msg=W0622
271
  def __init__(self, min=0, max=None, choices=None):
272
    _Argument.__init__(self, min=min, max=max)
273
    self.choices = choices
274

    
275
  def __repr__(self):
276
    return ("<%s min=%s max=%s choices=%r>" %
277
            (self.__class__.__name__, self.min, self.max, self.choices))
278

    
279

    
280
class ArgChoice(ArgSuggest):
281
  """Choice argument.
282

283
  Value can be any of the ones passed to the constructor. Like L{ArgSuggest},
284
  but value must be one of the choices.
285

286
  """
287

    
288

    
289
class ArgUnknown(_Argument):
290
  """Unknown argument to program (e.g. determined at runtime).
291

292
  """
293

    
294

    
295
class ArgInstance(_Argument):
296
  """Instances argument.
297

298
  """
299

    
300

    
301
class ArgNode(_Argument):
302
  """Node argument.
303

304
  """
305

    
306

    
307
class ArgGroup(_Argument):
308
  """Node group argument.
309

310
  """
311

    
312

    
313
class ArgJobId(_Argument):
314
  """Job ID argument.
315

316
  """
317

    
318

    
319
class ArgFile(_Argument):
320
  """File path argument.
321

322
  """
323

    
324

    
325
class ArgCommand(_Argument):
326
  """Command argument.
327

328
  """
329

    
330

    
331
class ArgHost(_Argument):
332
  """Host argument.
333

334
  """
335

    
336

    
337
class ArgOs(_Argument):
338
  """OS argument.
339

340
  """
341

    
342

    
343
ARGS_NONE = []
344
ARGS_MANY_INSTANCES = [ArgInstance()]
345
ARGS_MANY_NODES = [ArgNode()]
346
ARGS_MANY_GROUPS = [ArgGroup()]
347
ARGS_ONE_INSTANCE = [ArgInstance(min=1, max=1)]
348
ARGS_ONE_NODE = [ArgNode(min=1, max=1)]
349
# TODO
350
ARGS_ONE_GROUP = [ArgGroup(min=1, max=1)]
351
ARGS_ONE_OS = [ArgOs(min=1, max=1)]
352

    
353

    
354
def _ExtractTagsObject(opts, args):
355
  """Extract the tag type object.
356

357
  Note that this function will modify its args parameter.
358

359
  """
360
  if not hasattr(opts, "tag_type"):
361
    raise errors.ProgrammerError("tag_type not passed to _ExtractTagsObject")
362
  kind = opts.tag_type
363
  if kind == constants.TAG_CLUSTER:
364
    retval = kind, kind
365
  elif kind in (constants.TAG_NODEGROUP,
366
                constants.TAG_NODE,
367
                constants.TAG_INSTANCE):
368
    if not args:
369
      raise errors.OpPrereqError("no arguments passed to the command")
370
    name = args.pop(0)
371
    retval = kind, name
372
  else:
373
    raise errors.ProgrammerError("Unhandled tag type '%s'" % kind)
374
  return retval
375

    
376

    
377
def _ExtendTags(opts, args):
378
  """Extend the args if a source file has been given.
379

380
  This function will extend the tags with the contents of the file
381
  passed in the 'tags_source' attribute of the opts parameter. A file
382
  named '-' will be replaced by stdin.
383

384
  """
385
  fname = opts.tags_source
386
  if fname is None:
387
    return
388
  if fname == "-":
389
    new_fh = sys.stdin
390
  else:
391
    new_fh = open(fname, "r")
392
  new_data = []
393
  try:
394
    # we don't use the nice 'new_data = [line.strip() for line in fh]'
395
    # because of python bug 1633941
396
    while True:
397
      line = new_fh.readline()
398
      if not line:
399
        break
400
      new_data.append(line.strip())
401
  finally:
402
    new_fh.close()
403
  args.extend(new_data)
404

    
405

    
406
def ListTags(opts, args):
407
  """List the tags on a given object.
408

409
  This is a generic implementation that knows how to deal with all
410
  three cases of tag objects (cluster, node, instance). The opts
411
  argument is expected to contain a tag_type field denoting what
412
  object type we work on.
413

414
  """
415
  kind, name = _ExtractTagsObject(opts, args)
416
  cl = GetClient()
417
  result = cl.QueryTags(kind, name)
418
  result = list(result)
419
  result.sort()
420
  for tag in result:
421
    ToStdout(tag)
422

    
423

    
424
def AddTags(opts, args):
425
  """Add tags on a given object.
426

427
  This is a generic implementation that knows how to deal with all
428
  three cases of tag objects (cluster, node, instance). The opts
429
  argument is expected to contain a tag_type field denoting what
430
  object type we work on.
431

432
  """
433
  kind, name = _ExtractTagsObject(opts, args)
434
  _ExtendTags(opts, args)
435
  if not args:
436
    raise errors.OpPrereqError("No tags to be added")
437
  op = opcodes.OpTagsSet(kind=kind, name=name, tags=args)
438
  SubmitOpCode(op, opts=opts)
439

    
440

    
441
def RemoveTags(opts, args):
442
  """Remove tags from a given object.
443

444
  This is a generic implementation that knows how to deal with all
445
  three cases of tag objects (cluster, node, instance). The opts
446
  argument is expected to contain a tag_type field denoting what
447
  object type we work on.
448

449
  """
450
  kind, name = _ExtractTagsObject(opts, args)
451
  _ExtendTags(opts, args)
452
  if not args:
453
    raise errors.OpPrereqError("No tags to be removed")
454
  op = opcodes.OpTagsDel(kind=kind, name=name, tags=args)
455
  SubmitOpCode(op, opts=opts)
456

    
457

    
458
def check_unit(option, opt, value): # pylint: disable-msg=W0613
459
  """OptParsers custom converter for units.
460

461
  """
462
  try:
463
    return utils.ParseUnit(value)
464
  except errors.UnitParseError, err:
465
    raise OptionValueError("option %s: %s" % (opt, err))
466

    
467

    
468
def _SplitKeyVal(opt, data):
469
  """Convert a KeyVal string into a dict.
470

471
  This function will convert a key=val[,...] string into a dict. Empty
472
  values will be converted specially: keys which have the prefix 'no_'
473
  will have the value=False and the prefix stripped, the others will
474
  have value=True.
475

476
  @type opt: string
477
  @param opt: a string holding the option name for which we process the
478
      data, used in building error messages
479
  @type data: string
480
  @param data: a string of the format key=val,key=val,...
481
  @rtype: dict
482
  @return: {key=val, key=val}
483
  @raises errors.ParameterError: if there are duplicate keys
484

485
  """
486
  kv_dict = {}
487
  if data:
488
    for elem in utils.UnescapeAndSplit(data, sep=","):
489
      if "=" in elem:
490
        key, val = elem.split("=", 1)
491
      else:
492
        if elem.startswith(NO_PREFIX):
493
          key, val = elem[len(NO_PREFIX):], False
494
        elif elem.startswith(UN_PREFIX):
495
          key, val = elem[len(UN_PREFIX):], None
496
        else:
497
          key, val = elem, True
498
      if key in kv_dict:
499
        raise errors.ParameterError("Duplicate key '%s' in option %s" %
500
                                    (key, opt))
501
      kv_dict[key] = val
502
  return kv_dict
503

    
504

    
505
def check_ident_key_val(option, opt, value):  # pylint: disable-msg=W0613
506
  """Custom parser for ident:key=val,key=val options.
507

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

511
  """
512
  if ":" not in value:
513
    ident, rest = value, ''
514
  else:
515
    ident, rest = value.split(":", 1)
516

    
517
  if ident.startswith(NO_PREFIX):
518
    if rest:
519
      msg = "Cannot pass options when removing parameter groups: %s" % value
520
      raise errors.ParameterError(msg)
521
    retval = (ident[len(NO_PREFIX):], False)
522
  elif ident.startswith(UN_PREFIX):
523
    if rest:
524
      msg = "Cannot pass options when removing parameter groups: %s" % value
525
      raise errors.ParameterError(msg)
526
    retval = (ident[len(UN_PREFIX):], None)
527
  else:
528
    kv_dict = _SplitKeyVal(opt, rest)
529
    retval = (ident, kv_dict)
530
  return retval
531

    
532

    
533
def check_key_val(option, opt, value):  # pylint: disable-msg=W0613
534
  """Custom parser class for key=val,key=val options.
535

536
  This will store the parsed values as a dict {key: val}.
537

538
  """
539
  return _SplitKeyVal(opt, value)
540

    
541

    
542
def check_bool(option, opt, value): # pylint: disable-msg=W0613
543
  """Custom parser for yes/no options.
544

545
  This will store the parsed value as either True or False.
546

547
  """
548
  value = value.lower()
549
  if value == constants.VALUE_FALSE or value == "no":
550
    return False
551
  elif value == constants.VALUE_TRUE or value == "yes":
552
    return True
553
  else:
554
    raise errors.ParameterError("Invalid boolean value '%s'" % value)
555

    
556

    
557
# completion_suggestion is normally a list. Using numeric values not evaluating
558
# to False for dynamic completion.
559
(OPT_COMPL_MANY_NODES,
560
 OPT_COMPL_ONE_NODE,
561
 OPT_COMPL_ONE_INSTANCE,
562
 OPT_COMPL_ONE_OS,
563
 OPT_COMPL_ONE_IALLOCATOR,
564
 OPT_COMPL_INST_ADD_NODES,
565
 OPT_COMPL_ONE_NODEGROUP) = range(100, 107)
566

    
567
OPT_COMPL_ALL = frozenset([
568
  OPT_COMPL_MANY_NODES,
569
  OPT_COMPL_ONE_NODE,
570
  OPT_COMPL_ONE_INSTANCE,
571
  OPT_COMPL_ONE_OS,
572
  OPT_COMPL_ONE_IALLOCATOR,
573
  OPT_COMPL_INST_ADD_NODES,
574
  OPT_COMPL_ONE_NODEGROUP,
575
  ])
576

    
577

    
578
class CliOption(Option):
579
  """Custom option class for optparse.
580

581
  """
582
  ATTRS = Option.ATTRS + [
583
    "completion_suggest",
584
    ]
585
  TYPES = Option.TYPES + (
586
    "identkeyval",
587
    "keyval",
588
    "unit",
589
    "bool",
590
    )
591
  TYPE_CHECKER = Option.TYPE_CHECKER.copy()
592
  TYPE_CHECKER["identkeyval"] = check_ident_key_val
593
  TYPE_CHECKER["keyval"] = check_key_val
594
  TYPE_CHECKER["unit"] = check_unit
595
  TYPE_CHECKER["bool"] = check_bool
596

    
597

    
598
# optparse.py sets make_option, so we do it for our own option class, too
599
cli_option = CliOption
600

    
601

    
602
_YORNO = "yes|no"
603

    
604
DEBUG_OPT = cli_option("-d", "--debug", default=0, action="count",
605
                       help="Increase debugging level")
606

    
607
NOHDR_OPT = cli_option("--no-headers", default=False,
608
                       action="store_true", dest="no_headers",
609
                       help="Don't display column headers")
610

    
611
SEP_OPT = cli_option("--separator", default=None,
612
                     action="store", dest="separator",
613
                     help=("Separator between output fields"
614
                           " (defaults to one space)"))
615

    
616
USEUNITS_OPT = cli_option("--units", default=None,
617
                          dest="units", choices=('h', 'm', 'g', 't'),
618
                          help="Specify units for output (one of h/m/g/t)")
619

    
620
FIELDS_OPT = cli_option("-o", "--output", dest="output", action="store",
621
                        type="string", metavar="FIELDS",
622
                        help="Comma separated list of output fields")
623

    
624
FORCE_OPT = cli_option("-f", "--force", dest="force", action="store_true",
625
                       default=False, help="Force the operation")
626

    
627
CONFIRM_OPT = cli_option("--yes", dest="confirm", action="store_true",
628
                         default=False, help="Do not require confirmation")
629

    
630
IGNORE_OFFLINE_OPT = cli_option("--ignore-offline", dest="ignore_offline",
631
                                  action="store_true", default=False,
632
                                  help=("Ignore offline nodes and do as much"
633
                                        " as possible"))
634

    
635
TAG_SRC_OPT = cli_option("--from", dest="tags_source",
636
                         default=None, help="File with tag names")
637

    
638
SUBMIT_OPT = cli_option("--submit", dest="submit_only",
639
                        default=False, action="store_true",
640
                        help=("Submit the job and return the job ID, but"
641
                              " don't wait for the job to finish"))
642

    
643
SYNC_OPT = cli_option("--sync", dest="do_locking",
644
                      default=False, action="store_true",
645
                      help=("Grab locks while doing the queries"
646
                            " in order to ensure more consistent results"))
647

    
648
DRY_RUN_OPT = cli_option("--dry-run", default=False,
649
                         action="store_true",
650
                         help=("Do not execute the operation, just run the"
651
                               " check steps and verify it it could be"
652
                               " executed"))
653

    
654
VERBOSE_OPT = cli_option("-v", "--verbose", default=False,
655
                         action="store_true",
656
                         help="Increase the verbosity of the operation")
657

    
658
DEBUG_SIMERR_OPT = cli_option("--debug-simulate-errors", default=False,
659
                              action="store_true", dest="simulate_errors",
660
                              help="Debugging option that makes the operation"
661
                              " treat most runtime checks as failed")
662

    
663
NWSYNC_OPT = cli_option("--no-wait-for-sync", dest="wait_for_sync",
664
                        default=True, action="store_false",
665
                        help="Don't wait for sync (DANGEROUS!)")
666

    
667
DISK_TEMPLATE_OPT = cli_option("-t", "--disk-template", dest="disk_template",
668
                               help="Custom disk setup (diskless, file,"
669
                               " plain or drbd)",
670
                               default=None, metavar="TEMPL",
671
                               choices=list(constants.DISK_TEMPLATES))
672

    
673
NONICS_OPT = cli_option("--no-nics", default=False, action="store_true",
674
                        help="Do not create any network cards for"
675
                        " the instance")
676

    
677
FILESTORE_DIR_OPT = cli_option("--file-storage-dir", dest="file_storage_dir",
678
                               help="Relative path under default cluster-wide"
679
                               " file storage dir to store file-based disks",
680
                               default=None, metavar="<DIR>")
681

    
682
FILESTORE_DRIVER_OPT = cli_option("--file-driver", dest="file_driver",
683
                                  help="Driver to use for image files",
684
                                  default="loop", metavar="<DRIVER>",
685
                                  choices=list(constants.FILE_DRIVER))
686

    
687
IALLOCATOR_OPT = cli_option("-I", "--iallocator", metavar="<NAME>",
688
                            help="Select nodes for the instance automatically"
689
                            " using the <NAME> iallocator plugin",
690
                            default=None, type="string",
691
                            completion_suggest=OPT_COMPL_ONE_IALLOCATOR)
692

    
693
DEFAULT_IALLOCATOR_OPT = cli_option("-I", "--default-iallocator",
694
                            metavar="<NAME>",
695
                            help="Set the default instance allocator plugin",
696
                            default=None, type="string",
697
                            completion_suggest=OPT_COMPL_ONE_IALLOCATOR)
698

    
699
OS_OPT = cli_option("-o", "--os-type", dest="os", help="What OS to run",
700
                    metavar="<os>",
701
                    completion_suggest=OPT_COMPL_ONE_OS)
702

    
703
OSPARAMS_OPT = cli_option("-O", "--os-parameters", dest="osparams",
704
                         type="keyval", default={},
705
                         help="OS parameters")
706

    
707
FORCE_VARIANT_OPT = cli_option("--force-variant", dest="force_variant",
708
                               action="store_true", default=False,
709
                               help="Force an unknown variant")
710

    
711
NO_INSTALL_OPT = cli_option("--no-install", dest="no_install",
712
                            action="store_true", default=False,
713
                            help="Do not install the OS (will"
714
                            " enable no-start)")
715

    
716
BACKEND_OPT = cli_option("-B", "--backend-parameters", dest="beparams",
717
                         type="keyval", default={},
718
                         help="Backend parameters")
719

    
720
HVOPTS_OPT =  cli_option("-H", "--hypervisor-parameters", type="keyval",
721
                         default={}, dest="hvparams",
722
                         help="Hypervisor parameters")
723

    
724
HYPERVISOR_OPT = cli_option("-H", "--hypervisor-parameters", dest="hypervisor",
725
                            help="Hypervisor and hypervisor options, in the"
726
                            " format hypervisor:option=value,option=value,...",
727
                            default=None, type="identkeyval")
728

    
729
HVLIST_OPT = cli_option("-H", "--hypervisor-parameters", dest="hvparams",
730
                        help="Hypervisor and hypervisor options, in the"
731
                        " format hypervisor:option=value,option=value,...",
732
                        default=[], action="append", type="identkeyval")
733

    
734
NOIPCHECK_OPT = cli_option("--no-ip-check", dest="ip_check", default=True,
735
                           action="store_false",
736
                           help="Don't check that the instance's IP"
737
                           " is alive")
738

    
739
NONAMECHECK_OPT = cli_option("--no-name-check", dest="name_check",
740
                             default=True, action="store_false",
741
                             help="Don't check that the instance's name"
742
                             " is resolvable")
743

    
744
NET_OPT = cli_option("--net",
745
                     help="NIC parameters", default=[],
746
                     dest="nics", action="append", type="identkeyval")
747

    
748
DISK_OPT = cli_option("--disk", help="Disk parameters", default=[],
749
                      dest="disks", action="append", type="identkeyval")
750

    
751
DISKIDX_OPT = cli_option("--disks", dest="disks", default=None,
752
                         help="Comma-separated list of disks"
753
                         " indices to act on (e.g. 0,2) (optional,"
754
                         " defaults to all disks)")
755

    
756
OS_SIZE_OPT = cli_option("-s", "--os-size", dest="sd_size",
757
                         help="Enforces a single-disk configuration using the"
758
                         " given disk size, in MiB unless a suffix is used",
759
                         default=None, type="unit", metavar="<size>")
760

    
761
IGNORE_CONSIST_OPT = cli_option("--ignore-consistency",
762
                                dest="ignore_consistency",
763
                                action="store_true", default=False,
764
                                help="Ignore the consistency of the disks on"
765
                                " the secondary")
766

    
767
ALLOW_FAILOVER_OPT = cli_option("--allow-failover",
768
                                dest="allow_failover",
769
                                action="store_true", default=False,
770
                                help="If migration is not possible fallback to"
771
                                     " failover")
772

    
773
NONLIVE_OPT = cli_option("--non-live", dest="live",
774
                         default=True, action="store_false",
775
                         help="Do a non-live migration (this usually means"
776
                         " freeze the instance, save the state, transfer and"
777
                         " only then resume running on the secondary node)")
778

    
779
MIGRATION_MODE_OPT = cli_option("--migration-mode", dest="migration_mode",
780
                                default=None,
781
                                choices=list(constants.HT_MIGRATION_MODES),
782
                                help="Override default migration mode (choose"
783
                                " either live or non-live")
784

    
785
NODE_PLACEMENT_OPT = cli_option("-n", "--node", dest="node",
786
                                help="Target node and optional secondary node",
787
                                metavar="<pnode>[:<snode>]",
788
                                completion_suggest=OPT_COMPL_INST_ADD_NODES)
789

    
790
NODE_LIST_OPT = cli_option("-n", "--node", dest="nodes", default=[],
791
                           action="append", metavar="<node>",
792
                           help="Use only this node (can be used multiple"
793
                           " times, if not given defaults to all nodes)",
794
                           completion_suggest=OPT_COMPL_ONE_NODE)
795

    
796
NODEGROUP_OPT = cli_option("-g", "--node-group",
797
                           dest="nodegroup",
798
                           help="Node group (name or uuid)",
799
                           metavar="<nodegroup>",
800
                           default=None, type="string",
801
                           completion_suggest=OPT_COMPL_ONE_NODEGROUP)
802

    
803
SINGLE_NODE_OPT = cli_option("-n", "--node", dest="node", help="Target node",
804
                             metavar="<node>",
805
                             completion_suggest=OPT_COMPL_ONE_NODE)
806

    
807
NOSTART_OPT = cli_option("--no-start", dest="start", default=True,
808
                         action="store_false",
809
                         help="Don't start the instance after creation")
810

    
811
SHOWCMD_OPT = cli_option("--show-cmd", dest="show_command",
812
                         action="store_true", default=False,
813
                         help="Show command instead of executing it")
814

    
815
CLEANUP_OPT = cli_option("--cleanup", dest="cleanup",
816
                         default=False, action="store_true",
817
                         help="Instead of performing the migration, try to"
818
                         " recover from a failed cleanup. This is safe"
819
                         " to run even if the instance is healthy, but it"
820
                         " will create extra replication traffic and "
821
                         " disrupt briefly the replication (like during the"
822
                         " migration")
823

    
824
STATIC_OPT = cli_option("-s", "--static", dest="static",
825
                        action="store_true", default=False,
826
                        help="Only show configuration data, not runtime data")
827

    
828
ALL_OPT = cli_option("--all", dest="show_all",
829
                     default=False, action="store_true",
830
                     help="Show info on all instances on the cluster."
831
                     " This can take a long time to run, use wisely")
832

    
833
SELECT_OS_OPT = cli_option("--select-os", dest="select_os",
834
                           action="store_true", default=False,
835
                           help="Interactive OS reinstall, lists available"
836
                           " OS templates for selection")
837

    
838
IGNORE_FAILURES_OPT = cli_option("--ignore-failures", dest="ignore_failures",
839
                                 action="store_true", default=False,
840
                                 help="Remove the instance from the cluster"
841
                                 " configuration even if there are failures"
842
                                 " during the removal process")
843

    
844
IGNORE_REMOVE_FAILURES_OPT = cli_option("--ignore-remove-failures",
845
                                        dest="ignore_remove_failures",
846
                                        action="store_true", default=False,
847
                                        help="Remove the instance from the"
848
                                        " cluster configuration even if there"
849
                                        " are failures during the removal"
850
                                        " process")
851

    
852
REMOVE_INSTANCE_OPT = cli_option("--remove-instance", dest="remove_instance",
853
                                 action="store_true", default=False,
854
                                 help="Remove the instance from the cluster")
855

    
856
DST_NODE_OPT = cli_option("-n", "--target-node", dest="dst_node",
857
                               help="Specifies the new node for the instance",
858
                               metavar="NODE", default=None,
859
                               completion_suggest=OPT_COMPL_ONE_NODE)
860

    
861
NEW_SECONDARY_OPT = cli_option("-n", "--new-secondary", dest="dst_node",
862
                               help="Specifies the new secondary node",
863
                               metavar="NODE", default=None,
864
                               completion_suggest=OPT_COMPL_ONE_NODE)
865

    
866
ON_PRIMARY_OPT = cli_option("-p", "--on-primary", dest="on_primary",
867
                            default=False, action="store_true",
868
                            help="Replace the disk(s) on the primary"
869
                            " node (only for the drbd template)")
870

    
871
ON_SECONDARY_OPT = cli_option("-s", "--on-secondary", dest="on_secondary",
872
                              default=False, action="store_true",
873
                              help="Replace the disk(s) on the secondary"
874
                              " node (only for the drbd template)")
875

    
876
AUTO_PROMOTE_OPT = cli_option("--auto-promote", dest="auto_promote",
877
                              default=False, action="store_true",
878
                              help="Lock all nodes and auto-promote as needed"
879
                              " to MC status")
880

    
881
AUTO_REPLACE_OPT = cli_option("-a", "--auto", dest="auto",
882
                              default=False, action="store_true",
883
                              help="Automatically replace faulty disks"
884
                              " (only for the drbd template)")
885

    
886
IGNORE_SIZE_OPT = cli_option("--ignore-size", dest="ignore_size",
887
                             default=False, action="store_true",
888
                             help="Ignore current recorded size"
889
                             " (useful for forcing activation when"
890
                             " the recorded size is wrong)")
891

    
892
SRC_NODE_OPT = cli_option("--src-node", dest="src_node", help="Source node",
893
                          metavar="<node>",
894
                          completion_suggest=OPT_COMPL_ONE_NODE)
895

    
896
SRC_DIR_OPT = cli_option("--src-dir", dest="src_dir", help="Source directory",
897
                         metavar="<dir>")
898

    
899
SECONDARY_IP_OPT = cli_option("-s", "--secondary-ip", dest="secondary_ip",
900
                              help="Specify the secondary ip for the node",
901
                              metavar="ADDRESS", default=None)
902

    
903
READD_OPT = cli_option("--readd", dest="readd",
904
                       default=False, action="store_true",
905
                       help="Readd old node after replacing it")
906

    
907
NOSSH_KEYCHECK_OPT = cli_option("--no-ssh-key-check", dest="ssh_key_check",
908
                                default=True, action="store_false",
909
                                help="Disable SSH key fingerprint checking")
910

    
911
NODE_FORCE_JOIN_OPT = cli_option("--force-join", dest="force_join",
912
                                 default=False, action="store_true",
913
                                 help="Force the joining of a node")
914

    
915
MC_OPT = cli_option("-C", "--master-candidate", dest="master_candidate",
916
                    type="bool", default=None, metavar=_YORNO,
917
                    help="Set the master_candidate flag on the node")
918

    
919
OFFLINE_OPT = cli_option("-O", "--offline", dest="offline", metavar=_YORNO,
920
                         type="bool", default=None,
921
                         help=("Set the offline flag on the node"
922
                               " (cluster does not communicate with offline"
923
                               " nodes)"))
924

    
925
DRAINED_OPT = cli_option("-D", "--drained", dest="drained", metavar=_YORNO,
926
                         type="bool", default=None,
927
                         help=("Set the drained flag on the node"
928
                               " (excluded from allocation operations)"))
929

    
930
CAPAB_MASTER_OPT = cli_option("--master-capable", dest="master_capable",
931
                    type="bool", default=None, metavar=_YORNO,
932
                    help="Set the master_capable flag on the node")
933

    
934
CAPAB_VM_OPT = cli_option("--vm-capable", dest="vm_capable",
935
                    type="bool", default=None, metavar=_YORNO,
936
                    help="Set the vm_capable flag on the node")
937

    
938
ALLOCATABLE_OPT = cli_option("--allocatable", dest="allocatable",
939
                             type="bool", default=None, metavar=_YORNO,
940
                             help="Set the allocatable flag on a volume")
941

    
942
NOLVM_STORAGE_OPT = cli_option("--no-lvm-storage", dest="lvm_storage",
943
                               help="Disable support for lvm based instances"
944
                               " (cluster-wide)",
945
                               action="store_false", default=True)
946

    
947
ENABLED_HV_OPT = cli_option("--enabled-hypervisors",
948
                            dest="enabled_hypervisors",
949
                            help="Comma-separated list of hypervisors",
950
                            type="string", default=None)
951

    
952
NIC_PARAMS_OPT = cli_option("-N", "--nic-parameters", dest="nicparams",
953
                            type="keyval", default={},
954
                            help="NIC parameters")
955

    
956
CP_SIZE_OPT = cli_option("-C", "--candidate-pool-size", default=None,
957
                         dest="candidate_pool_size", type="int",
958
                         help="Set the candidate pool size")
959

    
960
VG_NAME_OPT = cli_option("--vg-name", dest="vg_name",
961
                         help=("Enables LVM and specifies the volume group"
962
                               " name (cluster-wide) for disk allocation"
963
                               " [%s]" % constants.DEFAULT_VG),
964
                         metavar="VG", default=None)
965

    
966
YES_DOIT_OPT = cli_option("--yes-do-it", dest="yes_do_it",
967
                          help="Destroy cluster", action="store_true")
968

    
969
NOVOTING_OPT = cli_option("--no-voting", dest="no_voting",
970
                          help="Skip node agreement check (dangerous)",
971
                          action="store_true", default=False)
972

    
973
MAC_PREFIX_OPT = cli_option("-m", "--mac-prefix", dest="mac_prefix",
974
                            help="Specify the mac prefix for the instance IP"
975
                            " addresses, in the format XX:XX:XX",
976
                            metavar="PREFIX",
977
                            default=None)
978

    
979
MASTER_NETDEV_OPT = cli_option("--master-netdev", dest="master_netdev",
980
                               help="Specify the node interface (cluster-wide)"
981
                               " on which the master IP address will be added"
982
                               " (cluster init default: %s)" %
983
                               constants.DEFAULT_BRIDGE,
984
                               metavar="NETDEV",
985
                               default=None)
986

    
987
GLOBAL_FILEDIR_OPT = cli_option("--file-storage-dir", dest="file_storage_dir",
988
                                help="Specify the default directory (cluster-"
989
                                "wide) for storing the file-based disks [%s]" %
990
                                constants.DEFAULT_FILE_STORAGE_DIR,
991
                                metavar="DIR",
992
                                default=constants.DEFAULT_FILE_STORAGE_DIR)
993

    
994
GLOBAL_SHARED_FILEDIR_OPT = cli_option("--shared-file-storage-dir",
995
                            dest="shared_file_storage_dir",
996
                            help="Specify the default directory (cluster-"
997
                            "wide) for storing the shared file-based"
998
                            " disks [%s]" %
999
                            constants.DEFAULT_SHARED_FILE_STORAGE_DIR,
1000
                            metavar="SHAREDDIR",
1001
                            default=constants.DEFAULT_SHARED_FILE_STORAGE_DIR)
1002

    
1003
NOMODIFY_ETCHOSTS_OPT = cli_option("--no-etc-hosts", dest="modify_etc_hosts",
1004
                                   help="Don't modify /etc/hosts",
1005
                                   action="store_false", default=True)
1006

    
1007
NOMODIFY_SSH_SETUP_OPT = cli_option("--no-ssh-init", dest="modify_ssh_setup",
1008
                                    help="Don't initialize SSH keys",
1009
                                    action="store_false", default=True)
1010

    
1011
ERROR_CODES_OPT = cli_option("--error-codes", dest="error_codes",
1012
                             help="Enable parseable error messages",
1013
                             action="store_true", default=False)
1014

    
1015
NONPLUS1_OPT = cli_option("--no-nplus1-mem", dest="skip_nplusone_mem",
1016
                          help="Skip N+1 memory redundancy tests",
1017
                          action="store_true", default=False)
1018

    
1019
REBOOT_TYPE_OPT = cli_option("-t", "--type", dest="reboot_type",
1020
                             help="Type of reboot: soft/hard/full",
1021
                             default=constants.INSTANCE_REBOOT_HARD,
1022
                             metavar="<REBOOT>",
1023
                             choices=list(constants.REBOOT_TYPES))
1024

    
1025
IGNORE_SECONDARIES_OPT = cli_option("--ignore-secondaries",
1026
                                    dest="ignore_secondaries",
1027
                                    default=False, action="store_true",
1028
                                    help="Ignore errors from secondaries")
1029

    
1030
NOSHUTDOWN_OPT = cli_option("--noshutdown", dest="shutdown",
1031
                            action="store_false", default=True,
1032
                            help="Don't shutdown the instance (unsafe)")
1033

    
1034
TIMEOUT_OPT = cli_option("--timeout", dest="timeout", type="int",
1035
                         default=constants.DEFAULT_SHUTDOWN_TIMEOUT,
1036
                         help="Maximum time to wait")
1037

    
1038
SHUTDOWN_TIMEOUT_OPT = cli_option("--shutdown-timeout",
1039
                         dest="shutdown_timeout", type="int",
1040
                         default=constants.DEFAULT_SHUTDOWN_TIMEOUT,
1041
                         help="Maximum time to wait for instance shutdown")
1042

    
1043
INTERVAL_OPT = cli_option("--interval", dest="interval", type="int",
1044
                          default=None,
1045
                          help=("Number of seconds between repetions of the"
1046
                                " command"))
1047

    
1048
EARLY_RELEASE_OPT = cli_option("--early-release",
1049
                               dest="early_release", default=False,
1050
                               action="store_true",
1051
                               help="Release the locks on the secondary"
1052
                               " node(s) early")
1053

    
1054
NEW_CLUSTER_CERT_OPT = cli_option("--new-cluster-certificate",
1055
                                  dest="new_cluster_cert",
1056
                                  default=False, action="store_true",
1057
                                  help="Generate a new cluster certificate")
1058

    
1059
RAPI_CERT_OPT = cli_option("--rapi-certificate", dest="rapi_cert",
1060
                           default=None,
1061
                           help="File containing new RAPI certificate")
1062

    
1063
NEW_RAPI_CERT_OPT = cli_option("--new-rapi-certificate", dest="new_rapi_cert",
1064
                               default=None, action="store_true",
1065
                               help=("Generate a new self-signed RAPI"
1066
                                     " certificate"))
1067

    
1068
NEW_CONFD_HMAC_KEY_OPT = cli_option("--new-confd-hmac-key",
1069
                                    dest="new_confd_hmac_key",
1070
                                    default=False, action="store_true",
1071
                                    help=("Create a new HMAC key for %s" %
1072
                                          constants.CONFD))
1073

    
1074
CLUSTER_DOMAIN_SECRET_OPT = cli_option("--cluster-domain-secret",
1075
                                       dest="cluster_domain_secret",
1076
                                       default=None,
1077
                                       help=("Load new new cluster domain"
1078
                                             " secret from file"))
1079

    
1080
NEW_CLUSTER_DOMAIN_SECRET_OPT = cli_option("--new-cluster-domain-secret",
1081
                                           dest="new_cluster_domain_secret",
1082
                                           default=False, action="store_true",
1083
                                           help=("Create a new cluster domain"
1084
                                                 " secret"))
1085

    
1086
USE_REPL_NET_OPT = cli_option("--use-replication-network",
1087
                              dest="use_replication_network",
1088
                              help="Whether to use the replication network"
1089
                              " for talking to the nodes",
1090
                              action="store_true", default=False)
1091

    
1092
MAINTAIN_NODE_HEALTH_OPT = \
1093
    cli_option("--maintain-node-health", dest="maintain_node_health",
1094
               metavar=_YORNO, default=None, type="bool",
1095
               help="Configure the cluster to automatically maintain node"
1096
               " health, by shutting down unknown instances, shutting down"
1097
               " unknown DRBD devices, etc.")
1098

    
1099
IDENTIFY_DEFAULTS_OPT = \
1100
    cli_option("--identify-defaults", dest="identify_defaults",
1101
               default=False, action="store_true",
1102
               help="Identify which saved instance parameters are equal to"
1103
               " the current cluster defaults and set them as such, instead"
1104
               " of marking them as overridden")
1105

    
1106
UIDPOOL_OPT = cli_option("--uid-pool", default=None,
1107
                         action="store", dest="uid_pool",
1108
                         help=("A list of user-ids or user-id"
1109
                               " ranges separated by commas"))
1110

    
1111
ADD_UIDS_OPT = cli_option("--add-uids", default=None,
1112
                          action="store", dest="add_uids",
1113
                          help=("A list of user-ids or user-id"
1114
                                " ranges separated by commas, to be"
1115
                                " added to the user-id pool"))
1116

    
1117
REMOVE_UIDS_OPT = cli_option("--remove-uids", default=None,
1118
                             action="store", dest="remove_uids",
1119
                             help=("A list of user-ids or user-id"
1120
                                   " ranges separated by commas, to be"
1121
                                   " removed from the user-id pool"))
1122

    
1123
RESERVED_LVS_OPT = cli_option("--reserved-lvs", default=None,
1124
                             action="store", dest="reserved_lvs",
1125
                             help=("A comma-separated list of reserved"
1126
                                   " logical volumes names, that will be"
1127
                                   " ignored by cluster verify"))
1128

    
1129
ROMAN_OPT = cli_option("--roman",
1130
                       dest="roman_integers", default=False,
1131
                       action="store_true",
1132
                       help="Use roman numbers for positive integers")
1133

    
1134
DRBD_HELPER_OPT = cli_option("--drbd-usermode-helper", dest="drbd_helper",
1135
                             action="store", default=None,
1136
                             help="Specifies usermode helper for DRBD")
1137

    
1138
NODRBD_STORAGE_OPT = cli_option("--no-drbd-storage", dest="drbd_storage",
1139
                                action="store_false", default=True,
1140
                                help="Disable support for DRBD")
1141

    
1142
PRIMARY_IP_VERSION_OPT = \
1143
    cli_option("--primary-ip-version", default=constants.IP4_VERSION,
1144
               action="store", dest="primary_ip_version",
1145
               metavar="%d|%d" % (constants.IP4_VERSION,
1146
                                  constants.IP6_VERSION),
1147
               help="Cluster-wide IP version for primary IP")
1148

    
1149
PRIORITY_OPT = cli_option("--priority", default=None, dest="priority",
1150
                          metavar="|".join(name for name, _ in _PRIORITY_NAMES),
1151
                          choices=_PRIONAME_TO_VALUE.keys(),
1152
                          help="Priority for opcode processing")
1153

    
1154
HID_OS_OPT = cli_option("--hidden", dest="hidden",
1155
                        type="bool", default=None, metavar=_YORNO,
1156
                        help="Sets the hidden flag on the OS")
1157

    
1158
BLK_OS_OPT = cli_option("--blacklisted", dest="blacklisted",
1159
                        type="bool", default=None, metavar=_YORNO,
1160
                        help="Sets the blacklisted flag on the OS")
1161

    
1162
PREALLOC_WIPE_DISKS_OPT = cli_option("--prealloc-wipe-disks", default=None,
1163
                                     type="bool", metavar=_YORNO,
1164
                                     dest="prealloc_wipe_disks",
1165
                                     help=("Wipe disks prior to instance"
1166
                                           " creation"))
1167

    
1168
NODE_PARAMS_OPT = cli_option("--node-parameters", dest="ndparams",
1169
                             type="keyval", default=None,
1170
                             help="Node parameters")
1171

    
1172
ALLOC_POLICY_OPT = cli_option("--alloc-policy", dest="alloc_policy",
1173
                              action="store", metavar="POLICY", default=None,
1174
                              help="Allocation policy for the node group")
1175

    
1176
NODE_POWERED_OPT = cli_option("--node-powered", default=None,
1177
                              type="bool", metavar=_YORNO,
1178
                              dest="node_powered",
1179
                              help="Specify if the SoR for node is powered")
1180

    
1181
OOB_TIMEOUT_OPT = cli_option("--oob-timeout", dest="oob_timeout", type="int",
1182
                         default=constants.OOB_TIMEOUT,
1183
                         help="Maximum time to wait for out-of-band helper")
1184

    
1185
POWER_DELAY_OPT = cli_option("--power-delay", dest="power_delay", type="float",
1186
                             default=constants.OOB_POWER_DELAY,
1187
                             help="Time in seconds to wait between power-ons")
1188

    
1189
FORCE_FILTER_OPT = cli_option("-F", "--filter", dest="force_filter",
1190
                              action="store_true", default=False,
1191
                              help=("Whether command argument should be treated"
1192
                                    " as filter"))
1193

    
1194

    
1195
#: Options provided by all commands
1196
COMMON_OPTS = [DEBUG_OPT]
1197

    
1198
# common options for creating instances. add and import then add their own
1199
# specific ones.
1200
COMMON_CREATE_OPTS = [
1201
  BACKEND_OPT,
1202
  DISK_OPT,
1203
  DISK_TEMPLATE_OPT,
1204
  FILESTORE_DIR_OPT,
1205
  FILESTORE_DRIVER_OPT,
1206
  HYPERVISOR_OPT,
1207
  IALLOCATOR_OPT,
1208
  NET_OPT,
1209
  NODE_PLACEMENT_OPT,
1210
  NOIPCHECK_OPT,
1211
  NONAMECHECK_OPT,
1212
  NONICS_OPT,
1213
  NWSYNC_OPT,
1214
  OSPARAMS_OPT,
1215
  OS_SIZE_OPT,
1216
  SUBMIT_OPT,
1217
  DRY_RUN_OPT,
1218
  PRIORITY_OPT,
1219
  ]
1220

    
1221

    
1222
def _ParseArgs(argv, commands, aliases):
1223
  """Parser for the command line arguments.
1224

1225
  This function parses the arguments and returns the function which
1226
  must be executed together with its (modified) arguments.
1227

1228
  @param argv: the command line
1229
  @param commands: dictionary with special contents, see the design
1230
      doc for cmdline handling
1231
  @param aliases: dictionary with command aliases {'alias': 'target, ...}
1232

1233
  """
1234
  if len(argv) == 0:
1235
    binary = "<command>"
1236
  else:
1237
    binary = argv[0].split("/")[-1]
1238

    
1239
  if len(argv) > 1 and argv[1] == "--version":
1240
    ToStdout("%s (ganeti %s) %s", binary, constants.VCS_VERSION,
1241
             constants.RELEASE_VERSION)
1242
    # Quit right away. That way we don't have to care about this special
1243
    # argument. optparse.py does it the same.
1244
    sys.exit(0)
1245

    
1246
  if len(argv) < 2 or not (argv[1] in commands or
1247
                           argv[1] in aliases):
1248
    # let's do a nice thing
1249
    sortedcmds = commands.keys()
1250
    sortedcmds.sort()
1251

    
1252
    ToStdout("Usage: %s {command} [options...] [argument...]", binary)
1253
    ToStdout("%s <command> --help to see details, or man %s", binary, binary)
1254
    ToStdout("")
1255

    
1256
    # compute the max line length for cmd + usage
1257
    mlen = max([len(" %s" % cmd) for cmd in commands])
1258
    mlen = min(60, mlen) # should not get here...
1259

    
1260
    # and format a nice command list
1261
    ToStdout("Commands:")
1262
    for cmd in sortedcmds:
1263
      cmdstr = " %s" % (cmd,)
1264
      help_text = commands[cmd][4]
1265
      help_lines = textwrap.wrap(help_text, 79 - 3 - mlen)
1266
      ToStdout("%-*s - %s", mlen, cmdstr, help_lines.pop(0))
1267
      for line in help_lines:
1268
        ToStdout("%-*s   %s", mlen, "", line)
1269

    
1270
    ToStdout("")
1271

    
1272
    return None, None, None
1273

    
1274
  # get command, unalias it, and look it up in commands
1275
  cmd = argv.pop(1)
1276
  if cmd in aliases:
1277
    if cmd in commands:
1278
      raise errors.ProgrammerError("Alias '%s' overrides an existing"
1279
                                   " command" % cmd)
1280

    
1281
    if aliases[cmd] not in commands:
1282
      raise errors.ProgrammerError("Alias '%s' maps to non-existing"
1283
                                   " command '%s'" % (cmd, aliases[cmd]))
1284

    
1285
    cmd = aliases[cmd]
1286

    
1287
  func, args_def, parser_opts, usage, description = commands[cmd]
1288
  parser = OptionParser(option_list=parser_opts + COMMON_OPTS,
1289
                        description=description,
1290
                        formatter=TitledHelpFormatter(),
1291
                        usage="%%prog %s %s" % (cmd, usage))
1292
  parser.disable_interspersed_args()
1293
  options, args = parser.parse_args()
1294

    
1295
  if not _CheckArguments(cmd, args_def, args):
1296
    return None, None, None
1297

    
1298
  return func, options, args
1299

    
1300

    
1301
def _CheckArguments(cmd, args_def, args):
1302
  """Verifies the arguments using the argument definition.
1303

1304
  Algorithm:
1305

1306
    1. Abort with error if values specified by user but none expected.
1307

1308
    1. For each argument in definition
1309

1310
      1. Keep running count of minimum number of values (min_count)
1311
      1. Keep running count of maximum number of values (max_count)
1312
      1. If it has an unlimited number of values
1313

1314
        1. Abort with error if it's not the last argument in the definition
1315

1316
    1. If last argument has limited number of values
1317

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

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

1322
  """
1323
  if args and not args_def:
1324
    ToStderr("Error: Command %s expects no arguments", cmd)
1325
    return False
1326

    
1327
  min_count = None
1328
  max_count = None
1329
  check_max = None
1330

    
1331
  last_idx = len(args_def) - 1
1332

    
1333
  for idx, arg in enumerate(args_def):
1334
    if min_count is None:
1335
      min_count = arg.min
1336
    elif arg.min is not None:
1337
      min_count += arg.min
1338

    
1339
    if max_count is None:
1340
      max_count = arg.max
1341
    elif arg.max is not None:
1342
      max_count += arg.max
1343

    
1344
    if idx == last_idx:
1345
      check_max = (arg.max is not None)
1346

    
1347
    elif arg.max is None:
1348
      raise errors.ProgrammerError("Only the last argument can have max=None")
1349

    
1350
  if check_max:
1351
    # Command with exact number of arguments
1352
    if (min_count is not None and max_count is not None and
1353
        min_count == max_count and len(args) != min_count):
1354
      ToStderr("Error: Command %s expects %d argument(s)", cmd, min_count)
1355
      return False
1356

    
1357
    # Command with limited number of arguments
1358
    if max_count is not None and len(args) > max_count:
1359
      ToStderr("Error: Command %s expects only %d argument(s)",
1360
               cmd, max_count)
1361
      return False
1362

    
1363
  # Command with some required arguments
1364
  if min_count is not None and len(args) < min_count:
1365
    ToStderr("Error: Command %s expects at least %d argument(s)",
1366
             cmd, min_count)
1367
    return False
1368

    
1369
  return True
1370

    
1371

    
1372
def SplitNodeOption(value):
1373
  """Splits the value of a --node option.
1374

1375
  """
1376
  if value and ':' in value:
1377
    return value.split(':', 1)
1378
  else:
1379
    return (value, None)
1380

    
1381

    
1382
def CalculateOSNames(os_name, os_variants):
1383
  """Calculates all the names an OS can be called, according to its variants.
1384

1385
  @type os_name: string
1386
  @param os_name: base name of the os
1387
  @type os_variants: list or None
1388
  @param os_variants: list of supported variants
1389
  @rtype: list
1390
  @return: list of valid names
1391

1392
  """
1393
  if os_variants:
1394
    return ['%s+%s' % (os_name, v) for v in os_variants]
1395
  else:
1396
    return [os_name]
1397

    
1398

    
1399
def ParseFields(selected, default):
1400
  """Parses the values of "--field"-like options.
1401

1402
  @type selected: string or None
1403
  @param selected: User-selected options
1404
  @type default: list
1405
  @param default: Default fields
1406

1407
  """
1408
  if selected is None:
1409
    return default
1410

    
1411
  if selected.startswith("+"):
1412
    return default + selected[1:].split(",")
1413

    
1414
  return selected.split(",")
1415

    
1416

    
1417
UsesRPC = rpc.RunWithRPC
1418

    
1419

    
1420
def AskUser(text, choices=None):
1421
  """Ask the user a question.
1422

1423
  @param text: the question to ask
1424

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

1430
  @return: one of the return values from the choices list; if input is
1431
      not possible (i.e. not running with a tty, we return the last
1432
      entry from the list
1433

1434
  """
1435
  if choices is None:
1436
    choices = [('y', True, 'Perform the operation'),
1437
               ('n', False, 'Do not perform the operation')]
1438
  if not choices or not isinstance(choices, list):
1439
    raise errors.ProgrammerError("Invalid choices argument to AskUser")
1440
  for entry in choices:
1441
    if not isinstance(entry, tuple) or len(entry) < 3 or entry[0] == '?':
1442
      raise errors.ProgrammerError("Invalid choices element to AskUser")
1443

    
1444
  answer = choices[-1][1]
1445
  new_text = []
1446
  for line in text.splitlines():
1447
    new_text.append(textwrap.fill(line, 70, replace_whitespace=False))
1448
  text = "\n".join(new_text)
1449
  try:
1450
    f = file("/dev/tty", "a+")
1451
  except IOError:
1452
    return answer
1453
  try:
1454
    chars = [entry[0] for entry in choices]
1455
    chars[-1] = "[%s]" % chars[-1]
1456
    chars.append('?')
1457
    maps = dict([(entry[0], entry[1]) for entry in choices])
1458
    while True:
1459
      f.write(text)
1460
      f.write('\n')
1461
      f.write("/".join(chars))
1462
      f.write(": ")
1463
      line = f.readline(2).strip().lower()
1464
      if line in maps:
1465
        answer = maps[line]
1466
        break
1467
      elif line == '?':
1468
        for entry in choices:
1469
          f.write(" %s - %s\n" % (entry[0], entry[2]))
1470
        f.write("\n")
1471
        continue
1472
  finally:
1473
    f.close()
1474
  return answer
1475

    
1476

    
1477
class JobSubmittedException(Exception):
1478
  """Job was submitted, client should exit.
1479

1480
  This exception has one argument, the ID of the job that was
1481
  submitted. The handler should print this ID.
1482

1483
  This is not an error, just a structured way to exit from clients.
1484

1485
  """
1486

    
1487

    
1488
def SendJob(ops, cl=None):
1489
  """Function to submit an opcode without waiting for the results.
1490

1491
  @type ops: list
1492
  @param ops: list of opcodes
1493
  @type cl: luxi.Client
1494
  @param cl: the luxi client to use for communicating with the master;
1495
             if None, a new client will be created
1496

1497
  """
1498
  if cl is None:
1499
    cl = GetClient()
1500

    
1501
  job_id = cl.SubmitJob(ops)
1502

    
1503
  return job_id
1504

    
1505

    
1506
def GenericPollJob(job_id, cbs, report_cbs):
1507
  """Generic job-polling function.
1508

1509
  @type job_id: number
1510
  @param job_id: Job ID
1511
  @type cbs: Instance of L{JobPollCbBase}
1512
  @param cbs: Data callbacks
1513
  @type report_cbs: Instance of L{JobPollReportCbBase}
1514
  @param report_cbs: Reporting callbacks
1515

1516
  """
1517
  prev_job_info = None
1518
  prev_logmsg_serial = None
1519

    
1520
  status = None
1521

    
1522
  while True:
1523
    result = cbs.WaitForJobChangeOnce(job_id, ["status"], prev_job_info,
1524
                                      prev_logmsg_serial)
1525
    if not result:
1526
      # job not found, go away!
1527
      raise errors.JobLost("Job with id %s lost" % job_id)
1528

    
1529
    if result == constants.JOB_NOTCHANGED:
1530
      report_cbs.ReportNotChanged(job_id, status)
1531

    
1532
      # Wait again
1533
      continue
1534

    
1535
    # Split result, a tuple of (field values, log entries)
1536
    (job_info, log_entries) = result
1537
    (status, ) = job_info
1538

    
1539
    if log_entries:
1540
      for log_entry in log_entries:
1541
        (serial, timestamp, log_type, message) = log_entry
1542
        report_cbs.ReportLogMessage(job_id, serial, timestamp,
1543
                                    log_type, message)
1544
        prev_logmsg_serial = max(prev_logmsg_serial, serial)
1545

    
1546
    # TODO: Handle canceled and archived jobs
1547
    elif status in (constants.JOB_STATUS_SUCCESS,
1548
                    constants.JOB_STATUS_ERROR,
1549
                    constants.JOB_STATUS_CANCELING,
1550
                    constants.JOB_STATUS_CANCELED):
1551
      break
1552

    
1553
    prev_job_info = job_info
1554

    
1555
  jobs = cbs.QueryJobs([job_id], ["status", "opstatus", "opresult"])
1556
  if not jobs:
1557
    raise errors.JobLost("Job with id %s lost" % job_id)
1558

    
1559
  status, opstatus, result = jobs[0]
1560

    
1561
  if status == constants.JOB_STATUS_SUCCESS:
1562
    return result
1563

    
1564
  if status in (constants.JOB_STATUS_CANCELING, constants.JOB_STATUS_CANCELED):
1565
    raise errors.OpExecError("Job was canceled")
1566

    
1567
  has_ok = False
1568
  for idx, (status, msg) in enumerate(zip(opstatus, result)):
1569
    if status == constants.OP_STATUS_SUCCESS:
1570
      has_ok = True
1571
    elif status == constants.OP_STATUS_ERROR:
1572
      errors.MaybeRaise(msg)
1573

    
1574
      if has_ok:
1575
        raise errors.OpExecError("partial failure (opcode %d): %s" %
1576
                                 (idx, msg))
1577

    
1578
      raise errors.OpExecError(str(msg))
1579

    
1580
  # default failure mode
1581
  raise errors.OpExecError(result)
1582

    
1583

    
1584
class JobPollCbBase:
1585
  """Base class for L{GenericPollJob} callbacks.
1586

1587
  """
1588
  def __init__(self):
1589
    """Initializes this class.
1590

1591
    """
1592

    
1593
  def WaitForJobChangeOnce(self, job_id, fields,
1594
                           prev_job_info, prev_log_serial):
1595
    """Waits for changes on a job.
1596

1597
    """
1598
    raise NotImplementedError()
1599

    
1600
  def QueryJobs(self, job_ids, fields):
1601
    """Returns the selected fields for the selected job IDs.
1602

1603
    @type job_ids: list of numbers
1604
    @param job_ids: Job IDs
1605
    @type fields: list of strings
1606
    @param fields: Fields
1607

1608
    """
1609
    raise NotImplementedError()
1610

    
1611

    
1612
class JobPollReportCbBase:
1613
  """Base class for L{GenericPollJob} reporting callbacks.
1614

1615
  """
1616
  def __init__(self):
1617
    """Initializes this class.
1618

1619
    """
1620

    
1621
  def ReportLogMessage(self, job_id, serial, timestamp, log_type, log_msg):
1622
    """Handles a log message.
1623

1624
    """
1625
    raise NotImplementedError()
1626

    
1627
  def ReportNotChanged(self, job_id, status):
1628
    """Called for if a job hasn't changed in a while.
1629

1630
    @type job_id: number
1631
    @param job_id: Job ID
1632
    @type status: string or None
1633
    @param status: Job status if available
1634

1635
    """
1636
    raise NotImplementedError()
1637

    
1638

    
1639
class _LuxiJobPollCb(JobPollCbBase):
1640
  def __init__(self, cl):
1641
    """Initializes this class.
1642

1643
    """
1644
    JobPollCbBase.__init__(self)
1645
    self.cl = cl
1646

    
1647
  def WaitForJobChangeOnce(self, job_id, fields,
1648
                           prev_job_info, prev_log_serial):
1649
    """Waits for changes on a job.
1650

1651
    """
1652
    return self.cl.WaitForJobChangeOnce(job_id, fields,
1653
                                        prev_job_info, prev_log_serial)
1654

    
1655
  def QueryJobs(self, job_ids, fields):
1656
    """Returns the selected fields for the selected job IDs.
1657

1658
    """
1659
    return self.cl.QueryJobs(job_ids, fields)
1660

    
1661

    
1662
class FeedbackFnJobPollReportCb(JobPollReportCbBase):
1663
  def __init__(self, feedback_fn):
1664
    """Initializes this class.
1665

1666
    """
1667
    JobPollReportCbBase.__init__(self)
1668

    
1669
    self.feedback_fn = feedback_fn
1670

    
1671
    assert callable(feedback_fn)
1672

    
1673
  def ReportLogMessage(self, job_id, serial, timestamp, log_type, log_msg):
1674
    """Handles a log message.
1675

1676
    """
1677
    self.feedback_fn((timestamp, log_type, log_msg))
1678

    
1679
  def ReportNotChanged(self, job_id, status):
1680
    """Called if a job hasn't changed in a while.
1681

1682
    """
1683
    # Ignore
1684

    
1685

    
1686
class StdioJobPollReportCb(JobPollReportCbBase):
1687
  def __init__(self):
1688
    """Initializes this class.
1689

1690
    """
1691
    JobPollReportCbBase.__init__(self)
1692

    
1693
    self.notified_queued = False
1694
    self.notified_waitlock = False
1695

    
1696
  def ReportLogMessage(self, job_id, serial, timestamp, log_type, log_msg):
1697
    """Handles a log message.
1698

1699
    """
1700
    ToStdout("%s %s", time.ctime(utils.MergeTime(timestamp)),
1701
             FormatLogMessage(log_type, log_msg))
1702

    
1703
  def ReportNotChanged(self, job_id, status):
1704
    """Called if a job hasn't changed in a while.
1705

1706
    """
1707
    if status is None:
1708
      return
1709

    
1710
    if status == constants.JOB_STATUS_QUEUED and not self.notified_queued:
1711
      ToStderr("Job %s is waiting in queue", job_id)
1712
      self.notified_queued = True
1713

    
1714
    elif status == constants.JOB_STATUS_WAITLOCK and not self.notified_waitlock:
1715
      ToStderr("Job %s is trying to acquire all necessary locks", job_id)
1716
      self.notified_waitlock = True
1717

    
1718

    
1719
def FormatLogMessage(log_type, log_msg):
1720
  """Formats a job message according to its type.
1721

1722
  """
1723
  if log_type != constants.ELOG_MESSAGE:
1724
    log_msg = str(log_msg)
1725

    
1726
  return utils.SafeEncode(log_msg)
1727

    
1728

    
1729
def PollJob(job_id, cl=None, feedback_fn=None, reporter=None):
1730
  """Function to poll for the result of a job.
1731

1732
  @type job_id: job identified
1733
  @param job_id: the job to poll for results
1734
  @type cl: luxi.Client
1735
  @param cl: the luxi client to use for communicating with the master;
1736
             if None, a new client will be created
1737

1738
  """
1739
  if cl is None:
1740
    cl = GetClient()
1741

    
1742
  if reporter is None:
1743
    if feedback_fn:
1744
      reporter = FeedbackFnJobPollReportCb(feedback_fn)
1745
    else:
1746
      reporter = StdioJobPollReportCb()
1747
  elif feedback_fn:
1748
    raise errors.ProgrammerError("Can't specify reporter and feedback function")
1749

    
1750
  return GenericPollJob(job_id, _LuxiJobPollCb(cl), reporter)
1751

    
1752

    
1753
def SubmitOpCode(op, cl=None, feedback_fn=None, opts=None, reporter=None):
1754
  """Legacy function to submit an opcode.
1755

1756
  This is just a simple wrapper over the construction of the processor
1757
  instance. It should be extended to better handle feedback and
1758
  interaction functions.
1759

1760
  """
1761
  if cl is None:
1762
    cl = GetClient()
1763

    
1764
  SetGenericOpcodeOpts([op], opts)
1765

    
1766
  job_id = SendJob([op], cl=cl)
1767

    
1768
  op_results = PollJob(job_id, cl=cl, feedback_fn=feedback_fn,
1769
                       reporter=reporter)
1770

    
1771
  return op_results[0]
1772

    
1773

    
1774
def SubmitOrSend(op, opts, cl=None, feedback_fn=None):
1775
  """Wrapper around SubmitOpCode or SendJob.
1776

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

1782
  It will also process the opcodes if we're sending the via SendJob
1783
  (otherwise SubmitOpCode does it).
1784

1785
  """
1786
  if opts and opts.submit_only:
1787
    job = [op]
1788
    SetGenericOpcodeOpts(job, opts)
1789
    job_id = SendJob(job, cl=cl)
1790
    raise JobSubmittedException(job_id)
1791
  else:
1792
    return SubmitOpCode(op, cl=cl, feedback_fn=feedback_fn, opts=opts)
1793

    
1794

    
1795
def SetGenericOpcodeOpts(opcode_list, options):
1796
  """Processor for generic options.
1797

1798
  This function updates the given opcodes based on generic command
1799
  line options (like debug, dry-run, etc.).
1800

1801
  @param opcode_list: list of opcodes
1802
  @param options: command line options or None
1803
  @return: None (in-place modification)
1804

1805
  """
1806
  if not options:
1807
    return
1808
  for op in opcode_list:
1809
    op.debug_level = options.debug
1810
    if hasattr(options, "dry_run"):
1811
      op.dry_run = options.dry_run
1812
    if getattr(options, "priority", None) is not None:
1813
      op.priority = _PRIONAME_TO_VALUE[options.priority]
1814

    
1815

    
1816
def GetClient():
1817
  # TODO: Cache object?
1818
  try:
1819
    client = luxi.Client()
1820
  except luxi.NoMasterError:
1821
    ss = ssconf.SimpleStore()
1822

    
1823
    # Try to read ssconf file
1824
    try:
1825
      ss.GetMasterNode()
1826
    except errors.ConfigurationError:
1827
      raise errors.OpPrereqError("Cluster not initialized or this machine is"
1828
                                 " not part of a cluster")
1829

    
1830
    master, myself = ssconf.GetMasterAndMyself(ss=ss)
1831
    if master != myself:
1832
      raise errors.OpPrereqError("This is not the master node, please connect"
1833
                                 " to node '%s' and rerun the command" %
1834
                                 master)
1835
    raise
1836
  return client
1837

    
1838

    
1839
def FormatError(err):
1840
  """Return a formatted error message for a given error.
1841

1842
  This function takes an exception instance and returns a tuple
1843
  consisting of two values: first, the recommended exit code, and
1844
  second, a string describing the error message (not
1845
  newline-terminated).
1846

1847
  """
1848
  retcode = 1
1849
  obuf = StringIO()
1850
  msg = str(err)
1851
  if isinstance(err, errors.ConfigurationError):
1852
    txt = "Corrupt configuration file: %s" % msg
1853
    logging.error(txt)
1854
    obuf.write(txt + "\n")
1855
    obuf.write("Aborting.")
1856
    retcode = 2
1857
  elif isinstance(err, errors.HooksAbort):
1858
    obuf.write("Failure: hooks execution failed:\n")
1859
    for node, script, out in err.args[0]:
1860
      if out:
1861
        obuf.write("  node: %s, script: %s, output: %s\n" %
1862
                   (node, script, out))
1863
      else:
1864
        obuf.write("  node: %s, script: %s (no output)\n" %
1865
                   (node, script))
1866
  elif isinstance(err, errors.HooksFailure):
1867
    obuf.write("Failure: hooks general failure: %s" % msg)
1868
  elif isinstance(err, errors.ResolverError):
1869
    this_host = netutils.Hostname.GetSysName()
1870
    if err.args[0] == this_host:
1871
      msg = "Failure: can't resolve my own hostname ('%s')"
1872
    else:
1873
      msg = "Failure: can't resolve hostname '%s'"
1874
    obuf.write(msg % err.args[0])
1875
  elif isinstance(err, errors.OpPrereqError):
1876
    if len(err.args) == 2:
1877
      obuf.write("Failure: prerequisites not met for this"
1878
               " operation:\nerror type: %s, error details:\n%s" %
1879
                 (err.args[1], err.args[0]))
1880
    else:
1881
      obuf.write("Failure: prerequisites not met for this"
1882
                 " operation:\n%s" % msg)
1883
  elif isinstance(err, errors.OpExecError):
1884
    obuf.write("Failure: command execution error:\n%s" % msg)
1885
  elif isinstance(err, errors.TagError):
1886
    obuf.write("Failure: invalid tag(s) given:\n%s" % msg)
1887
  elif isinstance(err, errors.JobQueueDrainError):
1888
    obuf.write("Failure: the job queue is marked for drain and doesn't"
1889
               " accept new requests\n")
1890
  elif isinstance(err, errors.JobQueueFull):
1891
    obuf.write("Failure: the job queue is full and doesn't accept new"
1892
               " job submissions until old jobs are archived\n")
1893
  elif isinstance(err, errors.TypeEnforcementError):
1894
    obuf.write("Parameter Error: %s" % msg)
1895
  elif isinstance(err, errors.ParameterError):
1896
    obuf.write("Failure: unknown/wrong parameter name '%s'" % msg)
1897
  elif isinstance(err, luxi.NoMasterError):
1898
    obuf.write("Cannot communicate with the master daemon.\nIs it running"
1899
               " and listening for connections?")
1900
  elif isinstance(err, luxi.TimeoutError):
1901
    obuf.write("Timeout while talking to the master daemon. Jobs might have"
1902
               " been submitted and will continue to run even if the call"
1903
               " timed out. Useful commands in this situation are \"gnt-job"
1904
               " list\", \"gnt-job cancel\" and \"gnt-job watch\". Error:\n")
1905
    obuf.write(msg)
1906
  elif isinstance(err, luxi.PermissionError):
1907
    obuf.write("It seems you don't have permissions to connect to the"
1908
               " master daemon.\nPlease retry as a different user.")
1909
  elif isinstance(err, luxi.ProtocolError):
1910
    obuf.write("Unhandled protocol error while talking to the master daemon:\n"
1911
               "%s" % msg)
1912
  elif isinstance(err, errors.JobLost):
1913
    obuf.write("Error checking job status: %s" % msg)
1914
  elif isinstance(err, errors.QueryFilterParseError):
1915
    obuf.write("Error while parsing query filter: %s\n" % err.args[0])
1916
    obuf.write("\n".join(err.GetDetails()))
1917
  elif isinstance(err, errors.GenericError):
1918
    obuf.write("Unhandled Ganeti error: %s" % msg)
1919
  elif isinstance(err, JobSubmittedException):
1920
    obuf.write("JobID: %s\n" % err.args[0])
1921
    retcode = 0
1922
  else:
1923
    obuf.write("Unhandled exception: %s" % msg)
1924
  return retcode, obuf.getvalue().rstrip('\n')
1925

    
1926

    
1927
def GenericMain(commands, override=None, aliases=None):
1928
  """Generic main function for all the gnt-* commands.
1929

1930
  Arguments:
1931
    - commands: a dictionary with a special structure, see the design doc
1932
                for command line handling.
1933
    - override: if not None, we expect a dictionary with keys that will
1934
                override command line options; this can be used to pass
1935
                options from the scripts to generic functions
1936
    - aliases: dictionary with command aliases {'alias': 'target, ...}
1937

1938
  """
1939
  # save the program name and the entire command line for later logging
1940
  if sys.argv:
1941
    binary = os.path.basename(sys.argv[0]) or sys.argv[0]
1942
    if len(sys.argv) >= 2:
1943
      binary += " " + sys.argv[1]
1944
      old_cmdline = " ".join(sys.argv[2:])
1945
    else:
1946
      old_cmdline = ""
1947
  else:
1948
    binary = "<unknown program>"
1949
    old_cmdline = ""
1950

    
1951
  if aliases is None:
1952
    aliases = {}
1953

    
1954
  try:
1955
    func, options, args = _ParseArgs(sys.argv, commands, aliases)
1956
  except errors.ParameterError, err:
1957
    result, err_msg = FormatError(err)
1958
    ToStderr(err_msg)
1959
    return 1
1960

    
1961
  if func is None: # parse error
1962
    return 1
1963

    
1964
  if override is not None:
1965
    for key, val in override.iteritems():
1966
      setattr(options, key, val)
1967

    
1968
  utils.SetupLogging(constants.LOG_COMMANDS, binary, debug=options.debug,
1969
                     stderr_logging=True)
1970

    
1971
  if old_cmdline:
1972
    logging.info("run with arguments '%s'", old_cmdline)
1973
  else:
1974
    logging.info("run with no arguments")
1975

    
1976
  try:
1977
    result = func(options, args)
1978
  except (errors.GenericError, luxi.ProtocolError,
1979
          JobSubmittedException), err:
1980
    result, err_msg = FormatError(err)
1981
    logging.exception("Error during command processing")
1982
    ToStderr(err_msg)
1983
  except KeyboardInterrupt:
1984
    result = constants.EXIT_FAILURE
1985
    ToStderr("Aborted. Note that if the operation created any jobs, they"
1986
             " might have been submitted and"
1987
             " will continue to run in the background.")
1988
  except IOError, err:
1989
    if err.errno == errno.EPIPE:
1990
      # our terminal went away, we'll exit
1991
      sys.exit(constants.EXIT_FAILURE)
1992
    else:
1993
      raise
1994

    
1995
  return result
1996

    
1997

    
1998
def ParseNicOption(optvalue):
1999
  """Parses the value of the --net option(s).
2000

2001
  """
2002
  try:
2003
    nic_max = max(int(nidx[0]) + 1 for nidx in optvalue)
2004
  except (TypeError, ValueError), err:
2005
    raise errors.OpPrereqError("Invalid NIC index passed: %s" % str(err))
2006

    
2007
  nics = [{}] * nic_max
2008
  for nidx, ndict in optvalue:
2009
    nidx = int(nidx)
2010

    
2011
    if not isinstance(ndict, dict):
2012
      raise errors.OpPrereqError("Invalid nic/%d value: expected dict,"
2013
                                 " got %s" % (nidx, ndict))
2014

    
2015
    utils.ForceDictType(ndict, constants.INIC_PARAMS_TYPES)
2016

    
2017
    nics[nidx] = ndict
2018

    
2019
  return nics
2020

    
2021

    
2022
def GenericInstanceCreate(mode, opts, args):
2023
  """Add an instance to the cluster via either creation or import.
2024

2025
  @param mode: constants.INSTANCE_CREATE or constants.INSTANCE_IMPORT
2026
  @param opts: the command line options selected by the user
2027
  @type args: list
2028
  @param args: should contain only one element, the new instance name
2029
  @rtype: int
2030
  @return: the desired exit code
2031

2032
  """
2033
  instance = args[0]
2034

    
2035
  (pnode, snode) = SplitNodeOption(opts.node)
2036

    
2037
  hypervisor = None
2038
  hvparams = {}
2039
  if opts.hypervisor:
2040
    hypervisor, hvparams = opts.hypervisor
2041

    
2042
  if opts.nics:
2043
    nics = ParseNicOption(opts.nics)
2044
  elif opts.no_nics:
2045
    # no nics
2046
    nics = []
2047
  elif mode == constants.INSTANCE_CREATE:
2048
    # default of one nic, all auto
2049
    nics = [{}]
2050
  else:
2051
    # mode == import
2052
    nics = []
2053

    
2054
  if opts.disk_template == constants.DT_DISKLESS:
2055
    if opts.disks or opts.sd_size is not None:
2056
      raise errors.OpPrereqError("Diskless instance but disk"
2057
                                 " information passed")
2058
    disks = []
2059
  else:
2060
    if (not opts.disks and not opts.sd_size
2061
        and mode == constants.INSTANCE_CREATE):
2062
      raise errors.OpPrereqError("No disk information specified")
2063
    if opts.disks and opts.sd_size is not None:
2064
      raise errors.OpPrereqError("Please use either the '--disk' or"
2065
                                 " '-s' option")
2066
    if opts.sd_size is not None:
2067
      opts.disks = [(0, {constants.IDISK_SIZE: opts.sd_size})]
2068

    
2069
    if opts.disks:
2070
      try:
2071
        disk_max = max(int(didx[0]) + 1 for didx in opts.disks)
2072
      except ValueError, err:
2073
        raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err))
2074
      disks = [{}] * disk_max
2075
    else:
2076
      disks = []
2077
    for didx, ddict in opts.disks:
2078
      didx = int(didx)
2079
      if not isinstance(ddict, dict):
2080
        msg = "Invalid disk/%d value: expected dict, got %s" % (didx, ddict)
2081
        raise errors.OpPrereqError(msg)
2082
      elif constants.IDISK_SIZE in ddict:
2083
        if constants.IDISK_ADOPT in ddict:
2084
          raise errors.OpPrereqError("Only one of 'size' and 'adopt' allowed"
2085
                                     " (disk %d)" % didx)
2086
        try:
2087
          ddict[constants.IDISK_SIZE] = \
2088
            utils.ParseUnit(ddict[constants.IDISK_SIZE])
2089
        except ValueError, err:
2090
          raise errors.OpPrereqError("Invalid disk size for disk %d: %s" %
2091
                                     (didx, err))
2092
      elif constants.IDISK_ADOPT in ddict:
2093
        if mode == constants.INSTANCE_IMPORT:
2094
          raise errors.OpPrereqError("Disk adoption not allowed for instance"
2095
                                     " import")
2096
        ddict[constants.IDISK_SIZE] = 0
2097
      else:
2098
        raise errors.OpPrereqError("Missing size or adoption source for"
2099
                                   " disk %d" % didx)
2100
      disks[didx] = ddict
2101

    
2102
  utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_TYPES)
2103
  utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
2104

    
2105
  if mode == constants.INSTANCE_CREATE:
2106
    start = opts.start
2107
    os_type = opts.os
2108
    force_variant = opts.force_variant
2109
    src_node = None
2110
    src_path = None
2111
    no_install = opts.no_install
2112
    identify_defaults = False
2113
  elif mode == constants.INSTANCE_IMPORT:
2114
    start = False
2115
    os_type = None
2116
    force_variant = False
2117
    src_node = opts.src_node
2118
    src_path = opts.src_dir
2119
    no_install = None
2120
    identify_defaults = opts.identify_defaults
2121
  else:
2122
    raise errors.ProgrammerError("Invalid creation mode %s" % mode)
2123

    
2124
  op = opcodes.OpInstanceCreate(instance_name=instance,
2125
                                disks=disks,
2126
                                disk_template=opts.disk_template,
2127
                                nics=nics,
2128
                                pnode=pnode, snode=snode,
2129
                                ip_check=opts.ip_check,
2130
                                name_check=opts.name_check,
2131
                                wait_for_sync=opts.wait_for_sync,
2132
                                file_storage_dir=opts.file_storage_dir,
2133
                                file_driver=opts.file_driver,
2134
                                iallocator=opts.iallocator,
2135
                                hypervisor=hypervisor,
2136
                                hvparams=hvparams,
2137
                                beparams=opts.beparams,
2138
                                osparams=opts.osparams,
2139
                                mode=mode,
2140
                                start=start,
2141
                                os_type=os_type,
2142
                                force_variant=force_variant,
2143
                                src_node=src_node,
2144
                                src_path=src_path,
2145
                                no_install=no_install,
2146
                                identify_defaults=identify_defaults)
2147

    
2148
  SubmitOrSend(op, opts)
2149
  return 0
2150

    
2151

    
2152
class _RunWhileClusterStoppedHelper:
2153
  """Helper class for L{RunWhileClusterStopped} to simplify state management
2154

2155
  """
2156
  def __init__(self, feedback_fn, cluster_name, master_node, online_nodes):
2157
    """Initializes this class.
2158

2159
    @type feedback_fn: callable
2160
    @param feedback_fn: Feedback function
2161
    @type cluster_name: string
2162
    @param cluster_name: Cluster name
2163
    @type master_node: string
2164
    @param master_node Master node name
2165
    @type online_nodes: list
2166
    @param online_nodes: List of names of online nodes
2167

2168
    """
2169
    self.feedback_fn = feedback_fn
2170
    self.cluster_name = cluster_name
2171
    self.master_node = master_node
2172
    self.online_nodes = online_nodes
2173

    
2174
    self.ssh = ssh.SshRunner(self.cluster_name)
2175

    
2176
    self.nonmaster_nodes = [name for name in online_nodes
2177
                            if name != master_node]
2178

    
2179
    assert self.master_node not in self.nonmaster_nodes
2180

    
2181
  def _RunCmd(self, node_name, cmd):
2182
    """Runs a command on the local or a remote machine.
2183

2184
    @type node_name: string
2185
    @param node_name: Machine name
2186
    @type cmd: list
2187
    @param cmd: Command
2188

2189
    """
2190
    if node_name is None or node_name == self.master_node:
2191
      # No need to use SSH
2192
      result = utils.RunCmd(cmd)
2193
    else:
2194
      result = self.ssh.Run(node_name, "root", utils.ShellQuoteArgs(cmd))
2195

    
2196
    if result.failed:
2197
      errmsg = ["Failed to run command %s" % result.cmd]
2198
      if node_name:
2199
        errmsg.append("on node %s" % node_name)
2200
      errmsg.append(": exitcode %s and error %s" %
2201
                    (result.exit_code, result.output))
2202
      raise errors.OpExecError(" ".join(errmsg))
2203

    
2204
  def Call(self, fn, *args):
2205
    """Call function while all daemons are stopped.
2206

2207
    @type fn: callable
2208
    @param fn: Function to be called
2209

2210
    """
2211
    # Pause watcher by acquiring an exclusive lock on watcher state file
2212
    self.feedback_fn("Blocking watcher")
2213
    watcher_block = utils.FileLock.Open(constants.WATCHER_STATEFILE)
2214
    try:
2215
      # TODO: Currently, this just blocks. There's no timeout.
2216
      # TODO: Should it be a shared lock?
2217
      watcher_block.Exclusive(blocking=True)
2218

    
2219
      # Stop master daemons, so that no new jobs can come in and all running
2220
      # ones are finished
2221
      self.feedback_fn("Stopping master daemons")
2222
      self._RunCmd(None, [constants.DAEMON_UTIL, "stop-master"])
2223
      try:
2224
        # Stop daemons on all nodes
2225
        for node_name in self.online_nodes:
2226
          self.feedback_fn("Stopping daemons on %s" % node_name)
2227
          self._RunCmd(node_name, [constants.DAEMON_UTIL, "stop-all"])
2228

    
2229
        # All daemons are shut down now
2230
        try:
2231
          return fn(self, *args)
2232
        except Exception, err:
2233
          _, errmsg = FormatError(err)
2234
          logging.exception("Caught exception")
2235
          self.feedback_fn(errmsg)
2236
          raise
2237
      finally:
2238
        # Start cluster again, master node last
2239
        for node_name in self.nonmaster_nodes + [self.master_node]:
2240
          self.feedback_fn("Starting daemons on %s" % node_name)
2241
          self._RunCmd(node_name, [constants.DAEMON_UTIL, "start-all"])
2242
    finally:
2243
      # Resume watcher
2244
      watcher_block.Close()
2245

    
2246

    
2247
def RunWhileClusterStopped(feedback_fn, fn, *args):
2248
  """Calls a function while all cluster daemons are stopped.
2249

2250
  @type feedback_fn: callable
2251
  @param feedback_fn: Feedback function
2252
  @type fn: callable
2253
  @param fn: Function to be called when daemons are stopped
2254

2255
  """
2256
  feedback_fn("Gathering cluster information")
2257

    
2258
  # This ensures we're running on the master daemon
2259
  cl = GetClient()
2260

    
2261
  (cluster_name, master_node) = \
2262
    cl.QueryConfigValues(["cluster_name", "master_node"])
2263

    
2264
  online_nodes = GetOnlineNodes([], cl=cl)
2265

    
2266
  # Don't keep a reference to the client. The master daemon will go away.
2267
  del cl
2268

    
2269
  assert master_node in online_nodes
2270

    
2271
  return _RunWhileClusterStoppedHelper(feedback_fn, cluster_name, master_node,
2272
                                       online_nodes).Call(fn, *args)
2273

    
2274

    
2275
def GenerateTable(headers, fields, separator, data,
2276
                  numfields=None, unitfields=None,
2277
                  units=None):
2278
  """Prints a table with headers and different fields.
2279

2280
  @type headers: dict
2281
  @param headers: dictionary mapping field names to headers for
2282
      the table
2283
  @type fields: list
2284
  @param fields: the field names corresponding to each row in
2285
      the data field
2286
  @param separator: the separator to be used; if this is None,
2287
      the default 'smart' algorithm is used which computes optimal
2288
      field width, otherwise just the separator is used between
2289
      each field
2290
  @type data: list
2291
  @param data: a list of lists, each sublist being one row to be output
2292
  @type numfields: list
2293
  @param numfields: a list with the fields that hold numeric
2294
      values and thus should be right-aligned
2295
  @type unitfields: list
2296
  @param unitfields: a list with the fields that hold numeric
2297
      values that should be formatted with the units field
2298
  @type units: string or None
2299
  @param units: the units we should use for formatting, or None for
2300
      automatic choice (human-readable for non-separator usage, otherwise
2301
      megabytes); this is a one-letter string
2302

2303
  """
2304
  if units is None:
2305
    if separator:
2306
      units = "m"
2307
    else:
2308
      units = "h"
2309

    
2310
  if numfields is None:
2311
    numfields = []
2312
  if unitfields is None:
2313
    unitfields = []
2314

    
2315
  numfields = utils.FieldSet(*numfields)   # pylint: disable-msg=W0142
2316
  unitfields = utils.FieldSet(*unitfields) # pylint: disable-msg=W0142
2317

    
2318
  format_fields = []
2319
  for field in fields:
2320
    if headers and field not in headers:
2321
      # TODO: handle better unknown fields (either revert to old
2322
      # style of raising exception, or deal more intelligently with
2323
      # variable fields)
2324
      headers[field] = field
2325
    if separator is not None:
2326
      format_fields.append("%s")
2327
    elif numfields.Matches(field):
2328
      format_fields.append("%*s")
2329
    else:
2330
      format_fields.append("%-*s")
2331

    
2332
  if separator is None:
2333
    mlens = [0 for name in fields]
2334
    format_str = ' '.join(format_fields)
2335
  else:
2336
    format_str = separator.replace("%", "%%").join(format_fields)
2337

    
2338
  for row in data:
2339
    if row is None:
2340
      continue
2341
    for idx, val in enumerate(row):
2342
      if unitfields.Matches(fields[idx]):
2343
        try:
2344
          val = int(val)
2345
        except (TypeError, ValueError):
2346
          pass
2347
        else:
2348
          val = row[idx] = utils.FormatUnit(val, units)
2349
      val = row[idx] = str(val)
2350
      if separator is None:
2351
        mlens[idx] = max(mlens[idx], len(val))
2352

    
2353
  result = []
2354
  if headers:
2355
    args = []
2356
    for idx, name in enumerate(fields):
2357
      hdr = headers[name]
2358
      if separator is None:
2359
        mlens[idx] = max(mlens[idx], len(hdr))
2360
        args.append(mlens[idx])
2361
      args.append(hdr)
2362
    result.append(format_str % tuple(args))
2363

    
2364
  if separator is None:
2365
    assert len(mlens) == len(fields)
2366

    
2367
    if fields and not numfields.Matches(fields[-1]):
2368
      mlens[-1] = 0
2369

    
2370
  for line in data:
2371
    args = []
2372
    if line is None:
2373
      line = ['-' for _ in fields]
2374
    for idx in range(len(fields)):
2375
      if separator is None:
2376
        args.append(mlens[idx])
2377
      args.append(line[idx])
2378
    result.append(format_str % tuple(args))
2379

    
2380
  return result
2381

    
2382

    
2383
def _FormatBool(value):
2384
  """Formats a boolean value as a string.
2385

2386
  """
2387
  if value:
2388
    return "Y"
2389
  return "N"
2390

    
2391

    
2392
#: Default formatting for query results; (callback, align right)
2393
_DEFAULT_FORMAT_QUERY = {
2394
  constants.QFT_TEXT: (str, False),
2395
  constants.QFT_BOOL: (_FormatBool, False),
2396
  constants.QFT_NUMBER: (str, True),
2397
  constants.QFT_TIMESTAMP: (utils.FormatTime, False),
2398
  constants.QFT_OTHER: (str, False),
2399
  constants.QFT_UNKNOWN: (str, False),
2400
  }
2401

    
2402

    
2403
def _GetColumnFormatter(fdef, override, unit):
2404
  """Returns formatting function for a field.
2405

2406
  @type fdef: L{objects.QueryFieldDefinition}
2407
  @type override: dict
2408
  @param override: Dictionary for overriding field formatting functions,
2409
    indexed by field name, contents like L{_DEFAULT_FORMAT_QUERY}
2410
  @type unit: string
2411
  @param unit: Unit used for formatting fields of type L{constants.QFT_UNIT}
2412
  @rtype: tuple; (callable, bool)
2413
  @return: Returns the function to format a value (takes one parameter) and a
2414
    boolean for aligning the value on the right-hand side
2415

2416
  """
2417
  fmt = override.get(fdef.name, None)
2418
  if fmt is not None:
2419
    return fmt
2420

    
2421
  assert constants.QFT_UNIT not in _DEFAULT_FORMAT_QUERY
2422

    
2423
  if fdef.kind == constants.QFT_UNIT:
2424
    # Can't keep this information in the static dictionary
2425
    return (lambda value: utils.FormatUnit(value, unit), True)
2426

    
2427
  fmt = _DEFAULT_FORMAT_QUERY.get(fdef.kind, None)
2428
  if fmt is not None:
2429
    return fmt
2430

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

    
2433

    
2434
class _QueryColumnFormatter:
2435
  """Callable class for formatting fields of a query.
2436

2437
  """
2438
  def __init__(self, fn, status_fn, verbose):
2439
    """Initializes this class.
2440

2441
    @type fn: callable
2442
    @param fn: Formatting function
2443
    @type status_fn: callable
2444
    @param status_fn: Function to report fields' status
2445
    @type verbose: boolean
2446
    @param verbose: whether to use verbose field descriptions or not
2447

2448
    """
2449
    self._fn = fn
2450
    self._status_fn = status_fn
2451
    self._verbose = verbose
2452

    
2453
  def __call__(self, data):
2454
    """Returns a field's string representation.
2455

2456
    """
2457
    (status, value) = data
2458

    
2459
    # Report status
2460
    self._status_fn(status)
2461

    
2462
    if status == constants.RS_NORMAL:
2463
      return self._fn(value)
2464

    
2465
    assert value is None, \
2466
           "Found value %r for abnormal status %s" % (value, status)
2467

    
2468
    return FormatResultError(status, self._verbose)
2469

    
2470

    
2471
def FormatResultError(status, verbose):
2472
  """Formats result status other than L{constants.RS_NORMAL}.
2473

2474
  @param status: The result status
2475
  @type verbose: boolean
2476
  @param verbose: Whether to return the verbose text
2477
  @return: Text of result status
2478

2479
  """
2480
  assert status != constants.RS_NORMAL, \
2481
         "FormatResultError called with status equal to constants.RS_NORMAL"
2482
  try:
2483
    (verbose_text, normal_text) = constants.RSS_DESCRIPTION[status]
2484
  except KeyError:
2485
    raise NotImplementedError("Unknown status %s" % status)
2486
  else:
2487
    if verbose:
2488
      return verbose_text
2489
    return normal_text
2490

    
2491

    
2492
def FormatQueryResult(result, unit=None, format_override=None, separator=None,
2493
                      header=False, verbose=False):
2494
  """Formats data in L{objects.QueryResponse}.
2495

2496
  @type result: L{objects.QueryResponse}
2497
  @param result: result of query operation
2498
  @type unit: string
2499
  @param unit: Unit used for formatting fields of type L{constants.QFT_UNIT},
2500
    see L{utils.text.FormatUnit}
2501
  @type format_override: dict
2502
  @param format_override: Dictionary for overriding field formatting functions,
2503
    indexed by field name, contents like L{_DEFAULT_FORMAT_QUERY}
2504
  @type separator: string or None
2505
  @param separator: String used to separate fields
2506
  @type header: bool
2507
  @param header: Whether to output header row
2508
  @type verbose: boolean
2509
  @param verbose: whether to use verbose field descriptions or not
2510

2511
  """
2512
  if unit is None:
2513
    if separator:
2514
      unit = "m"
2515
    else:
2516
      unit = "h"
2517

    
2518
  if format_override is None:
2519
    format_override = {}
2520

    
2521
  stats = dict.fromkeys(constants.RS_ALL, 0)
2522

    
2523
  def _RecordStatus(status):
2524
    if status in stats:
2525
      stats[status] += 1
2526

    
2527
  columns = []
2528
  for fdef in result.fields:
2529
    assert fdef.title and fdef.name
2530
    (fn, align_right) = _GetColumnFormatter(fdef, format_override, unit)
2531
    columns.append(TableColumn(fdef.title,
2532
                               _QueryColumnFormatter(fn, _RecordStatus,
2533
                                                     verbose),
2534
                               align_right))
2535

    
2536
  table = FormatTable(result.data, columns, header, separator)
2537

    
2538
  # Collect statistics
2539
  assert len(stats) == len(constants.RS_ALL)
2540
  assert compat.all(count >= 0 for count in stats.values())
2541

    
2542
  # Determine overall status. If there was no data, unknown fields must be
2543
  # detected via the field definitions.
2544
  if (stats[constants.RS_UNKNOWN] or
2545
      (not result.data and _GetUnknownFields(result.fields))):
2546
    status = QR_UNKNOWN
2547
  elif compat.any(count > 0 for key, count in stats.items()
2548
                  if key != constants.RS_NORMAL):
2549
    status = QR_INCOMPLETE
2550
  else:
2551
    status = QR_NORMAL
2552

    
2553
  return (status, table)
2554

    
2555

    
2556
def _GetUnknownFields(fdefs):
2557
  """Returns list of unknown fields included in C{fdefs}.
2558

2559
  @type fdefs: list of L{objects.QueryFieldDefinition}
2560

2561
  """
2562
  return [fdef for fdef in fdefs
2563
          if fdef.kind == constants.QFT_UNKNOWN]
2564

    
2565

    
2566
def _WarnUnknownFields(fdefs):
2567
  """Prints a warning to stderr if a query included unknown fields.
2568

2569
  @type fdefs: list of L{objects.QueryFieldDefinition}
2570

2571
  """
2572
  unknown = _GetUnknownFields(fdefs)
2573
  if unknown:
2574
    ToStderr("Warning: Queried for unknown fields %s",
2575
             utils.CommaJoin(fdef.name for fdef in unknown))
2576
    return True
2577

    
2578
  return False
2579

    
2580

    
2581
def GenericList(resource, fields, names, unit, separator, header, cl=None,
2582
                format_override=None, verbose=False, force_filter=False):
2583
  """Generic implementation for listing all items of a resource.
2584

2585
  @param resource: One of L{constants.QR_VIA_LUXI}
2586
  @type fields: list of strings
2587
  @param fields: List of fields to query for
2588
  @type names: list of strings
2589
  @param names: Names of items to query for
2590
  @type unit: string or None
2591
  @param unit: Unit used for formatting fields of type L{constants.QFT_UNIT} or
2592
    None for automatic choice (human-readable for non-separator usage,
2593
    otherwise megabytes); this is a one-letter string
2594
  @type separator: string or None
2595
  @param separator: String used to separate fields
2596
  @type header: bool
2597
  @param header: Whether to show header row
2598
  @type force_filter: bool
2599
  @param force_filter: Whether to always treat names as filter
2600
  @type format_override: dict
2601
  @param format_override: Dictionary for overriding field formatting functions,
2602
    indexed by field name, contents like L{_DEFAULT_FORMAT_QUERY}
2603
  @type verbose: boolean
2604
  @param verbose: whether to use verbose field descriptions or not
2605

2606
  """
2607
  if cl is None:
2608
    cl = GetClient()
2609

    
2610
  if not names:
2611
    names = None
2612

    
2613
  if (force_filter or
2614
      (names and len(names) == 1 and qlang.MaybeFilter(names[0]))):
2615
    try:
2616
      (filter_text, ) = names
2617
    except ValueError:
2618
      raise errors.OpPrereqError("Exactly one argument must be given as a"
2619
                                 " filter")
2620

    
2621
    logging.debug("Parsing '%s' as filter", filter_text)
2622
    filter_ = qlang.ParseFilter(filter_text)
2623
  else:
2624
    filter_ = qlang.MakeSimpleFilter("name", names)
2625

    
2626
  response = cl.Query(resource, fields, filter_)
2627

    
2628
  found_unknown = _WarnUnknownFields(response.fields)
2629

    
2630
  (status, data) = FormatQueryResult(response, unit=unit, separator=separator,
2631
                                     header=header,
2632
                                     format_override=format_override,
2633
                                     verbose=verbose)
2634

    
2635
  for line in data:
2636
    ToStdout(line)
2637

    
2638
  assert ((found_unknown and status == QR_UNKNOWN) or
2639
          (not found_unknown and status != QR_UNKNOWN))
2640

    
2641
  if status == QR_UNKNOWN:
2642
    return constants.EXIT_UNKNOWN_FIELD
2643

    
2644
  # TODO: Should the list command fail if not all data could be collected?
2645
  return constants.EXIT_SUCCESS
2646

    
2647

    
2648
def GenericListFields(resource, fields, separator, header, cl=None):
2649
  """Generic implementation for listing fields for a resource.
2650

2651
  @param resource: One of L{constants.QR_VIA_LUXI}
2652
  @type fields: list of strings
2653
  @param fields: List of fields to query for
2654
  @type separator: string or None
2655
  @param separator: String used to separate fields
2656
  @type header: bool
2657
  @param header: Whether to show header row
2658

2659
  """
2660
  if cl is None:
2661
    cl = GetClient()
2662

    
2663
  if not fields:
2664
    fields = None
2665

    
2666
  response = cl.QueryFields(resource, fields)
2667

    
2668
  found_unknown = _WarnUnknownFields(response.fields)
2669

    
2670
  columns = [
2671
    TableColumn("Name", str, False),
2672
    TableColumn("Title", str, False),
2673
    TableColumn("Description", str, False),
2674
    ]
2675

    
2676
  rows = [[fdef.name, fdef.title, fdef.doc] for fdef in response.fields]
2677

    
2678
  for line in FormatTable(rows, columns, header, separator):
2679
    ToStdout(line)
2680

    
2681
  if found_unknown:
2682
    return constants.EXIT_UNKNOWN_FIELD
2683

    
2684
  return constants.EXIT_SUCCESS
2685

    
2686

    
2687
class TableColumn:
2688
  """Describes a column for L{FormatTable}.
2689

2690
  """
2691
  def __init__(self, title, fn, align_right):
2692
    """Initializes this class.
2693

2694
    @type title: string
2695
    @param title: Column title
2696
    @type fn: callable
2697
    @param fn: Formatting function
2698
    @type align_right: bool
2699
    @param align_right: Whether to align values on the right-hand side
2700

2701
    """
2702
    self.title = title
2703
    self.format = fn
2704
    self.align_right = align_right
2705

    
2706

    
2707
def _GetColFormatString(width, align_right):
2708
  """Returns the format string for a field.
2709

2710
  """
2711
  if align_right:
2712
    sign = ""
2713
  else:
2714
    sign = "-"
2715

    
2716
  return "%%%s%ss" % (sign, width)
2717

    
2718

    
2719
def FormatTable(rows, columns, header, separator):
2720
  """Formats data as a table.
2721

2722
  @type rows: list of lists
2723
  @param rows: Row data, one list per row
2724
  @type columns: list of L{TableColumn}
2725
  @param columns: Column descriptions
2726
  @type header: bool
2727
  @param header: Whether to show header row
2728
  @type separator: string or None
2729
  @param separator: String used to separate columns
2730

2731
  """
2732
  if header:
2733
    data = [[col.title for col in columns]]
2734
    colwidth = [len(col.title) for col in columns]
2735
  else:
2736
    data = []
2737
    colwidth = [0 for _ in columns]
2738

    
2739
  # Format row data
2740
  for row in rows:
2741
    assert len(row) == len(columns)
2742

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

    
2745
    if separator is None:
2746
      # Update column widths
2747
      for idx, (oldwidth, value) in enumerate(zip(colwidth, formatted)):
2748
        # Modifying a list's items while iterating is fine
2749
        colwidth[idx] = max(oldwidth, len(value))
2750

    
2751
    data.append(formatted)
2752

    
2753
  if separator is not None:
2754
    # Return early if a separator is used
2755
    return [separator.join(row) for row in data]
2756

    
2757
  if columns and not columns[-1].align_right:
2758
    # Avoid unnecessary spaces at end of line
2759
    colwidth[-1] = 0
2760

    
2761
  # Build format string
2762
  fmt = " ".join([_GetColFormatString(width, col.align_right)
2763
                  for col, width in zip(columns, colwidth)])
2764

    
2765
  return [fmt % tuple(row) for row in data]
2766

    
2767

    
2768
def FormatTimestamp(ts):
2769
  """Formats a given timestamp.
2770

2771
  @type ts: timestamp
2772
  @param ts: a timeval-type timestamp, a tuple of seconds and microseconds
2773

2774
  @rtype: string
2775
  @return: a string with the formatted timestamp
2776

2777
  """
2778
  if not isinstance (ts, (tuple, list)) or len(ts) != 2:
2779
    return '?'
2780
  sec, usec = ts
2781
  return time.strftime("%F %T", time.localtime(sec)) + ".%06d" % usec
2782

    
2783

    
2784
def ParseTimespec(value):
2785
  """Parse a time specification.
2786

2787
  The following suffixed will be recognized:
2788

2789
    - s: seconds
2790
    - m: minutes
2791
    - h: hours
2792
    - d: day
2793
    - w: weeks
2794

2795
  Without any suffix, the value will be taken to be in seconds.
2796

2797
  """
2798
  value = str(value)
2799
  if not value:
2800
    raise errors.OpPrereqError("Empty time specification passed")
2801
  suffix_map = {
2802
    's': 1,
2803
    'm': 60,
2804
    'h': 3600,
2805
    'd': 86400,
2806
    'w': 604800,
2807
    }
2808
  if value[-1] not in suffix_map:
2809
    try:
2810
      value = int(value)
2811
    except (TypeError, ValueError):
2812
      raise errors.OpPrereqError("Invalid time specification '%s'" % value)
2813
  else:
2814
    multiplier = suffix_map[value[-1]]
2815
    value = value[:-1]
2816
    if not value: # no data left after stripping the suffix
2817
      raise errors.OpPrereqError("Invalid time specification (only"
2818
                                 " suffix passed)")
2819
    try:
2820
      value = int(value) * multiplier
2821
    except (TypeError, ValueError):
2822
      raise errors.OpPrereqError("Invalid time specification '%s'" % value)
2823
  return value
2824

    
2825

    
2826
def GetOnlineNodes(nodes, cl=None, nowarn=False, secondary_ips=False,
2827
                   filter_master=False):
2828
  """Returns the names of online nodes.
2829

2830
  This function will also log a warning on stderr with the names of
2831
  the online nodes.
2832

2833
  @param nodes: if not empty, use only this subset of nodes (minus the
2834
      offline ones)
2835
  @param cl: if not None, luxi client to use
2836
  @type nowarn: boolean
2837
  @param nowarn: by default, this function will output a note with the
2838
      offline nodes that are skipped; if this parameter is True the
2839
      note is not displayed
2840
  @type secondary_ips: boolean
2841
  @param secondary_ips: if True, return the secondary IPs instead of the
2842
      names, useful for doing network traffic over the replication interface
2843
      (if any)
2844
  @type filter_master: boolean
2845
  @param filter_master: if True, do not return the master node in the list
2846
      (useful in coordination with secondary_ips where we cannot check our
2847
      node name against the list)
2848

2849
  """
2850
  if cl is None:
2851
    cl = GetClient()
2852

    
2853
  if secondary_ips:
2854
    name_idx = 2
2855
  else:
2856
    name_idx = 0
2857

    
2858
  if filter_master:
2859
    master_node = cl.QueryConfigValues(["master_node"])[0]
2860
    filter_fn = lambda x: x != master_node
2861
  else:
2862
    filter_fn = lambda _: True
2863

    
2864
  result = cl.QueryNodes(names=nodes, fields=["name", "offline", "sip"],
2865
                         use_locking=False)
2866
  offline = [row[0] for row in result if row[1]]
2867
  if offline and not nowarn:
2868
    ToStderr("Note: skipping offline node(s): %s" % utils.CommaJoin(offline))
2869
  return [row[name_idx] for row in result if not row[1] and filter_fn(row[0])]
2870

    
2871

    
2872
def _ToStream(stream, txt, *args):
2873
  """Write a message to a stream, bypassing the logging system
2874

2875
  @type stream: file object
2876
  @param stream: the file to which we should write
2877
  @type txt: str
2878
  @param txt: the message
2879

2880
  """
2881
  try:
2882
    if args:
2883
      args = tuple(args)
2884
      stream.write(txt % args)
2885
    else:
2886
      stream.write(txt)
2887
    stream.write('\n')
2888
    stream.flush()
2889
  except IOError, err:
2890
    if err.errno == errno.EPIPE:
2891
      # our terminal went away, we'll exit
2892
      sys.exit(constants.EXIT_FAILURE)
2893
    else:
2894
      raise
2895

    
2896

    
2897
def ToStdout(txt, *args):
2898
  """Write a message to stdout only, bypassing the logging system
2899

2900
  This is just a wrapper over _ToStream.
2901

2902
  @type txt: str
2903
  @param txt: the message
2904

2905
  """
2906
  _ToStream(sys.stdout, txt, *args)
2907

    
2908

    
2909
def ToStderr(txt, *args):
2910
  """Write a message to stderr only, bypassing the logging system
2911

2912
  This is just a wrapper over _ToStream.
2913

2914
  @type txt: str
2915
  @param txt: the message
2916

2917
  """
2918
  _ToStream(sys.stderr, txt, *args)
2919

    
2920

    
2921
class JobExecutor(object):
2922
  """Class which manages the submission and execution of multiple jobs.
2923

2924
  Note that instances of this class should not be reused between
2925
  GetResults() calls.
2926

2927
  """
2928
  def __init__(self, cl=None, verbose=True, opts=None, feedback_fn=None):
2929
    self.queue = []
2930
    if cl is None:
2931
      cl = GetClient()
2932
    self.cl = cl
2933
    self.verbose = verbose
2934
    self.jobs = []
2935
    self.opts = opts
2936
    self.feedback_fn = feedback_fn
2937

    
2938
  def QueueJob(self, name, *ops):
2939
    """Record a job for later submit.
2940

2941
    @type name: string
2942
    @param name: a description of the job, will be used in WaitJobSet
2943
    """
2944
    SetGenericOpcodeOpts(ops, self.opts)
2945
    self.queue.append((name, ops))
2946

    
2947
  def SubmitPending(self, each=False):
2948
    """Submit all pending jobs.
2949

2950
    """
2951
    if each:
2952
      results = []
2953
      for row in self.queue:
2954
        # SubmitJob will remove the success status, but raise an exception if
2955
        # the submission fails, so we'll notice that anyway.
2956
        results.append([True, self.cl.SubmitJob(row[1])])
2957
    else:
2958
      results = self.cl.SubmitManyJobs([row[1] for row in self.queue])
2959
    for (idx, ((status, data), (name, _))) in enumerate(zip(results,
2960
                                                            self.queue)):
2961
      self.jobs.append((idx, status, data, name))
2962

    
2963
  def _ChooseJob(self):
2964
    """Choose a non-waiting/queued job to poll next.
2965

2966
    """
2967
    assert self.jobs, "_ChooseJob called with empty job list"
2968

    
2969
    result = self.cl.QueryJobs([i[2] for i in self.jobs], ["status"])
2970
    assert result
2971

    
2972
    for job_data, status in zip(self.jobs, result):
2973
      if (isinstance(status, list) and status and
2974
          status[0] in (constants.JOB_STATUS_QUEUED,
2975
                        constants.JOB_STATUS_WAITLOCK,
2976
                        constants.JOB_STATUS_CANCELING)):
2977
        # job is still present and waiting
2978
        continue
2979
      # good candidate found (either running job or lost job)
2980
      self.jobs.remove(job_data)
2981
      return job_data
2982

    
2983
    # no job found
2984
    return self.jobs.pop(0)
2985

    
2986
  def GetResults(self):
2987
    """Wait for and return the results of all jobs.
2988

2989
    @rtype: list
2990
    @return: list of tuples (success, job results), in the same order
2991
        as the submitted jobs; if a job has failed, instead of the result
2992
        there will be the error message
2993

2994
    """
2995
    if not self.jobs:
2996
      self.SubmitPending()
2997
    results = []
2998
    if self.verbose:
2999
      ok_jobs = [row[2] for row in self.jobs if row[1]]
3000
      if ok_jobs:
3001
        ToStdout("Submitted jobs %s", utils.CommaJoin(ok_jobs))
3002

    
3003
    # first, remove any non-submitted jobs
3004
    self.jobs, failures = compat.partition(self.jobs, lambda x: x[1])
3005
    for idx, _, jid, name in failures:
3006
      ToStderr("Failed to submit job for %s: %s", name, jid)
3007
      results.append((idx, False, jid))
3008

    
3009
    while self.jobs:
3010
      (idx, _, jid, name) = self._ChooseJob()
3011
      ToStdout("Waiting for job %s for %s...", jid, name)
3012
      try:
3013
        job_result = PollJob(jid, cl=self.cl, feedback_fn=self.feedback_fn)
3014
        success = True
3015
      except errors.JobLost, err:
3016
        _, job_result = FormatError(err)
3017
        ToStderr("Job %s for %s has been archived, cannot check its result",
3018
                 jid, name)
3019
        success = False
3020
      except (errors.GenericError, luxi.ProtocolError), err:
3021
        _, job_result = FormatError(err)
3022
        success = False
3023
        # the error message will always be shown, verbose or not
3024
        ToStderr("Job %s for %s has failed: %s", jid, name, job_result)
3025

    
3026
      results.append((idx, success, job_result))
3027

    
3028
    # sort based on the index, then drop it
3029
    results.sort()
3030
    results = [i[1:] for i in results]
3031

    
3032
    return results
3033

    
3034
  def WaitOrShow(self, wait):
3035
    """Wait for job results or only print the job IDs.
3036

3037
    @type wait: boolean
3038
    @param wait: whether to wait or not
3039

3040
    """
3041
    if wait:
3042
      return self.GetResults()
3043
    else:
3044
      if not self.jobs:
3045
        self.SubmitPending()
3046
      for _, status, result, name in self.jobs:
3047
        if status:
3048
          ToStdout("%s: %s", result, name)
3049
        else:
3050
          ToStderr("Failure for %s: %s", name, result)
3051
      return [row[1:3] for row in self.jobs]
3052

    
3053

    
3054
def FormatParameterDict(buf, param_dict, actual, level=1):
3055
  """Formats a parameter dictionary.
3056

3057
  @type buf: L{StringIO}
3058
  @param buf: the buffer into which to write
3059
  @type param_dict: dict
3060
  @param param_dict: the own parameters
3061
  @type actual: dict
3062
  @param actual: the current parameter set (including defaults)
3063
  @param level: Level of indent
3064

3065
  """
3066
  indent = "  " * level
3067
  for key in sorted(actual):
3068
    val = param_dict.get(key, "default (%s)" % actual[key])
3069
    buf.write("%s- %s: %s\n" % (indent, key, val))
3070

    
3071

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

3075
  This function is used to request confirmation for doing an operation
3076
  on a given list of list_type.
3077

3078
  @type names: list
3079
  @param names: the list of names that we display when
3080
      we ask for confirmation
3081
  @type list_type: str
3082
  @param list_type: Human readable name for elements in the list (e.g. nodes)
3083
  @type text: str
3084
  @param text: the operation that the user should confirm
3085
  @rtype: boolean
3086
  @return: True or False depending on user's confirmation.
3087

3088
  """
3089
  count = len(names)
3090
  msg = ("The %s will operate on %d %s.\n%s"
3091
         "Do you want to continue?" % (text, count, list_type, extra))
3092
  affected = (("\nAffected %s:\n" % list_type) +
3093
              "\n".join(["  %s" % name for name in names]))
3094

    
3095
  choices = [("y", True, "Yes, execute the %s" % text),
3096
             ("n", False, "No, abort the %s" % text)]
3097

    
3098
  if count > 20:
3099
    choices.insert(1, ("v", "v", "View the list of affected %s" % list_type))
3100
    question = msg
3101
  else:
3102
    question = msg + affected
3103

    
3104
  choice = AskUser(question, choices)
3105
  if choice == "v":
3106
    choices.pop(1)
3107
    choice = AskUser(msg + affected, choices)
3108
  return choice