Statistics
| Branch: | Tag: | Revision:

root / lib / cli.py @ 714ea7ca

History | View | Annotate | Download (59.5 kB)

1
#
2
#
3

    
4
# Copyright (C) 2006, 2007 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
from cStringIO import StringIO
31

    
32
from ganeti import utils
33
from ganeti import errors
34
from ganeti import constants
35
from ganeti import opcodes
36
from ganeti import luxi
37
from ganeti import ssconf
38
from ganeti import rpc
39

    
40
from optparse import (OptionParser, TitledHelpFormatter,
41
                      Option, OptionValueError)
42

    
43

    
44
__all__ = [
45
  # Command line options
46
  "ALLOCATABLE_OPT",
47
  "ALL_OPT",
48
  "AUTO_REPLACE_OPT",
49
  "BACKEND_OPT",
50
  "CLEANUP_OPT",
51
  "CONFIRM_OPT",
52
  "CP_SIZE_OPT",
53
  "DEBUG_OPT",
54
  "DEBUG_SIMERR_OPT",
55
  "DISKIDX_OPT",
56
  "DISK_OPT",
57
  "DISK_TEMPLATE_OPT",
58
  "DRAINED_OPT",
59
  "EARLY_RELEASE_OPT",
60
  "ENABLED_HV_OPT",
61
  "ERROR_CODES_OPT",
62
  "FIELDS_OPT",
63
  "FILESTORE_DIR_OPT",
64
  "FILESTORE_DRIVER_OPT",
65
  "FORCE_OPT",
66
  "FORCE_VARIANT_OPT",
67
  "GLOBAL_FILEDIR_OPT",
68
  "HVLIST_OPT",
69
  "HVOPTS_OPT",
70
  "HYPERVISOR_OPT",
71
  "IALLOCATOR_OPT",
72
  "IGNORE_CONSIST_OPT",
73
  "IGNORE_FAILURES_OPT",
74
  "IGNORE_SECONDARIES_OPT",
75
  "IGNORE_SIZE_OPT",
76
  "MAC_PREFIX_OPT",
77
  "MASTER_NETDEV_OPT",
78
  "MC_OPT",
79
  "NET_OPT",
80
  "NEW_SECONDARY_OPT",
81
  "NIC_PARAMS_OPT",
82
  "NODE_LIST_OPT",
83
  "NODE_PLACEMENT_OPT",
84
  "NOHDR_OPT",
85
  "NOIPCHECK_OPT",
86
  "NONAMECHECK_OPT",
87
  "NOLVM_STORAGE_OPT",
88
  "NOMODIFY_ETCHOSTS_OPT",
89
  "NOMODIFY_SSH_SETUP_OPT",
90
  "NONICS_OPT",
91
  "NONLIVE_OPT",
92
  "NONPLUS1_OPT",
93
  "NOSHUTDOWN_OPT",
94
  "NOSTART_OPT",
95
  "NOSSH_KEYCHECK_OPT",
96
  "NOVOTING_OPT",
97
  "NWSYNC_OPT",
98
  "ON_PRIMARY_OPT",
99
  "ON_SECONDARY_OPT",
100
  "OFFLINE_OPT",
101
  "OS_OPT",
102
  "OS_SIZE_OPT",
103
  "READD_OPT",
104
  "REBOOT_TYPE_OPT",
105
  "SECONDARY_IP_OPT",
106
  "SELECT_OS_OPT",
107
  "SEP_OPT",
108
  "SHOWCMD_OPT",
109
  "SHUTDOWN_TIMEOUT_OPT",
110
  "SINGLE_NODE_OPT",
111
  "SRC_DIR_OPT",
112
  "SRC_NODE_OPT",
113
  "SUBMIT_OPT",
114
  "STATIC_OPT",
115
  "SYNC_OPT",
116
  "TAG_SRC_OPT",
117
  "TIMEOUT_OPT",
118
  "USEUNITS_OPT",
119
  "VERBOSE_OPT",
120
  "VG_NAME_OPT",
121
  "YES_DOIT_OPT",
122
  # Generic functions for CLI programs
123
  "GenericMain",
124
  "GenericInstanceCreate",
125
  "GetClient",
126
  "GetOnlineNodes",
127
  "JobExecutor",
128
  "JobSubmittedException",
129
  "ParseTimespec",
130
  "SubmitOpCode",
131
  "SubmitOrSend",
132
  "UsesRPC",
133
  # Formatting functions
134
  "ToStderr", "ToStdout",
135
  "FormatError",
136
  "GenerateTable",
137
  "AskUser",
138
  "FormatTimestamp",
139
  # Tags functions
140
  "ListTags",
141
  "AddTags",
142
  "RemoveTags",
143
  # command line options support infrastructure
144
  "ARGS_MANY_INSTANCES",
145
  "ARGS_MANY_NODES",
146
  "ARGS_NONE",
147
  "ARGS_ONE_INSTANCE",
148
  "ARGS_ONE_NODE",
149
  "ArgChoice",
150
  "ArgCommand",
151
  "ArgFile",
152
  "ArgHost",
153
  "ArgInstance",
154
  "ArgJobId",
155
  "ArgNode",
156
  "ArgSuggest",
157
  "ArgUnknown",
158
  "OPT_COMPL_INST_ADD_NODES",
159
  "OPT_COMPL_MANY_NODES",
160
  "OPT_COMPL_ONE_IALLOCATOR",
161
  "OPT_COMPL_ONE_INSTANCE",
162
  "OPT_COMPL_ONE_NODE",
163
  "OPT_COMPL_ONE_OS",
164
  "cli_option",
165
  "SplitNodeOption",
166
  "CalculateOSNames",
167
  ]
168

    
169
NO_PREFIX = "no_"
170
UN_PREFIX = "-"
171

    
172

    
173
class _Argument:
174
  def __init__(self, min=0, max=None): # pylint: disable-msg=W0622
175
    self.min = min
176
    self.max = max
177

    
178
  def __repr__(self):
179
    return ("<%s min=%s max=%s>" %
180
            (self.__class__.__name__, self.min, self.max))
181

    
182

    
183
class ArgSuggest(_Argument):
184
  """Suggesting argument.
185

186
  Value can be any of the ones passed to the constructor.
187

188
  """
189
  # pylint: disable-msg=W0622
190
  def __init__(self, min=0, max=None, choices=None):
191
    _Argument.__init__(self, min=min, max=max)
192
    self.choices = choices
193

    
194
  def __repr__(self):
195
    return ("<%s min=%s max=%s choices=%r>" %
196
            (self.__class__.__name__, self.min, self.max, self.choices))
197

    
198

    
199
class ArgChoice(ArgSuggest):
200
  """Choice argument.
201

202
  Value can be any of the ones passed to the constructor. Like L{ArgSuggest},
203
  but value must be one of the choices.
204

205
  """
206

    
207

    
208
class ArgUnknown(_Argument):
209
  """Unknown argument to program (e.g. determined at runtime).
210

211
  """
212

    
213

    
214
class ArgInstance(_Argument):
215
  """Instances argument.
216

217
  """
218

    
219

    
220
class ArgNode(_Argument):
221
  """Node argument.
222

223
  """
224

    
225
class ArgJobId(_Argument):
226
  """Job ID argument.
227

228
  """
229

    
230

    
231
class ArgFile(_Argument):
232
  """File path argument.
233

234
  """
235

    
236

    
237
class ArgCommand(_Argument):
238
  """Command argument.
239

240
  """
241

    
242

    
243
class ArgHost(_Argument):
244
  """Host argument.
245

246
  """
247

    
248

    
249
ARGS_NONE = []
250
ARGS_MANY_INSTANCES = [ArgInstance()]
251
ARGS_MANY_NODES = [ArgNode()]
252
ARGS_ONE_INSTANCE = [ArgInstance(min=1, max=1)]
253
ARGS_ONE_NODE = [ArgNode(min=1, max=1)]
254

    
255

    
256
def _ExtractTagsObject(opts, args):
257
  """Extract the tag type object.
258

259
  Note that this function will modify its args parameter.
260

261
  """
262
  if not hasattr(opts, "tag_type"):
263
    raise errors.ProgrammerError("tag_type not passed to _ExtractTagsObject")
264
  kind = opts.tag_type
265
  if kind == constants.TAG_CLUSTER:
266
    retval = kind, kind
267
  elif kind == constants.TAG_NODE or kind == constants.TAG_INSTANCE:
268
    if not args:
269
      raise errors.OpPrereqError("no arguments passed to the command")
270
    name = args.pop(0)
271
    retval = kind, name
272
  else:
273
    raise errors.ProgrammerError("Unhandled tag type '%s'" % kind)
274
  return retval
275

    
276

    
277
def _ExtendTags(opts, args):
278
  """Extend the args if a source file has been given.
279

280
  This function will extend the tags with the contents of the file
281
  passed in the 'tags_source' attribute of the opts parameter. A file
282
  named '-' will be replaced by stdin.
283

284
  """
285
  fname = opts.tags_source
286
  if fname is None:
287
    return
288
  if fname == "-":
289
    new_fh = sys.stdin
290
  else:
291
    new_fh = open(fname, "r")
292
  new_data = []
293
  try:
294
    # we don't use the nice 'new_data = [line.strip() for line in fh]'
295
    # because of python bug 1633941
296
    while True:
297
      line = new_fh.readline()
298
      if not line:
299
        break
300
      new_data.append(line.strip())
301
  finally:
302
    new_fh.close()
303
  args.extend(new_data)
304

    
305

    
306
def ListTags(opts, args):
307
  """List the tags on a given object.
308

309
  This is a generic implementation that knows how to deal with all
310
  three cases of tag objects (cluster, node, instance). The opts
311
  argument is expected to contain a tag_type field denoting what
312
  object type we work on.
313

314
  """
315
  kind, name = _ExtractTagsObject(opts, args)
316
  cl = GetClient()
317
  result = cl.QueryTags(kind, name)
318
  result = list(result)
319
  result.sort()
320
  for tag in result:
321
    ToStdout(tag)
322

    
323

    
324
def AddTags(opts, args):
325
  """Add tags on a given object.
326

327
  This is a generic implementation that knows how to deal with all
328
  three cases of tag objects (cluster, node, instance). The opts
329
  argument is expected to contain a tag_type field denoting what
330
  object type we work on.
331

332
  """
333
  kind, name = _ExtractTagsObject(opts, args)
334
  _ExtendTags(opts, args)
335
  if not args:
336
    raise errors.OpPrereqError("No tags to be added")
337
  op = opcodes.OpAddTags(kind=kind, name=name, tags=args)
338
  SubmitOpCode(op)
339

    
340

    
341
def RemoveTags(opts, args):
342
  """Remove tags from a given object.
343

344
  This is a generic implementation that knows how to deal with all
345
  three cases of tag objects (cluster, node, instance). The opts
346
  argument is expected to contain a tag_type field denoting what
347
  object type we work on.
348

349
  """
350
  kind, name = _ExtractTagsObject(opts, args)
351
  _ExtendTags(opts, args)
352
  if not args:
353
    raise errors.OpPrereqError("No tags to be removed")
354
  op = opcodes.OpDelTags(kind=kind, name=name, tags=args)
355
  SubmitOpCode(op)
356

    
357

    
358
def check_unit(option, opt, value): # pylint: disable-msg=W0613
359
  """OptParsers custom converter for units.
360

361
  """
362
  try:
363
    return utils.ParseUnit(value)
364
  except errors.UnitParseError, err:
365
    raise OptionValueError("option %s: %s" % (opt, err))
366

    
367

    
368
def _SplitKeyVal(opt, data):
369
  """Convert a KeyVal string into a dict.
370

371
  This function will convert a key=val[,...] string into a dict. Empty
372
  values will be converted specially: keys which have the prefix 'no_'
373
  will have the value=False and the prefix stripped, the others will
374
  have value=True.
375

376
  @type opt: string
377
  @param opt: a string holding the option name for which we process the
378
      data, used in building error messages
379
  @type data: string
380
  @param data: a string of the format key=val,key=val,...
381
  @rtype: dict
382
  @return: {key=val, key=val}
383
  @raises errors.ParameterError: if there are duplicate keys
384

385
  """
386
  kv_dict = {}
387
  if data:
388
    for elem in utils.UnescapeAndSplit(data, sep=","):
389
      if "=" in elem:
390
        key, val = elem.split("=", 1)
391
      else:
392
        if elem.startswith(NO_PREFIX):
393
          key, val = elem[len(NO_PREFIX):], False
394
        elif elem.startswith(UN_PREFIX):
395
          key, val = elem[len(UN_PREFIX):], None
396
        else:
397
          key, val = elem, True
398
      if key in kv_dict:
399
        raise errors.ParameterError("Duplicate key '%s' in option %s" %
400
                                    (key, opt))
401
      kv_dict[key] = val
402
  return kv_dict
403

    
404

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

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

411
  """
412
  if ":" not in value:
413
    ident, rest = value, ''
414
  else:
415
    ident, rest = value.split(":", 1)
416

    
417
  if ident.startswith(NO_PREFIX):
418
    if rest:
419
      msg = "Cannot pass options when removing parameter groups: %s" % value
420
      raise errors.ParameterError(msg)
421
    retval = (ident[len(NO_PREFIX):], False)
422
  elif ident.startswith(UN_PREFIX):
423
    if rest:
424
      msg = "Cannot pass options when removing parameter groups: %s" % value
425
      raise errors.ParameterError(msg)
426
    retval = (ident[len(UN_PREFIX):], None)
427
  else:
428
    kv_dict = _SplitKeyVal(opt, rest)
429
    retval = (ident, kv_dict)
430
  return retval
431

    
432

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

436
  This will store the parsed values as a dict {key: val}.
437

438
  """
439
  return _SplitKeyVal(opt, value)
440

    
441

    
442
# completion_suggestion is normally a list. Using numeric values not evaluating
443
# to False for dynamic completion.
444
(OPT_COMPL_MANY_NODES,
445
 OPT_COMPL_ONE_NODE,
446
 OPT_COMPL_ONE_INSTANCE,
447
 OPT_COMPL_ONE_OS,
448
 OPT_COMPL_ONE_IALLOCATOR,
449
 OPT_COMPL_INST_ADD_NODES) = range(100, 106)
450

    
451
OPT_COMPL_ALL = frozenset([
452
  OPT_COMPL_MANY_NODES,
453
  OPT_COMPL_ONE_NODE,
454
  OPT_COMPL_ONE_INSTANCE,
455
  OPT_COMPL_ONE_OS,
456
  OPT_COMPL_ONE_IALLOCATOR,
457
  OPT_COMPL_INST_ADD_NODES,
458
  ])
459

    
460

    
461
class CliOption(Option):
462
  """Custom option class for optparse.
463

464
  """
465
  ATTRS = Option.ATTRS + [
466
    "completion_suggest",
467
    ]
468
  TYPES = Option.TYPES + (
469
    "identkeyval",
470
    "keyval",
471
    "unit",
472
    )
473
  TYPE_CHECKER = Option.TYPE_CHECKER.copy()
474
  TYPE_CHECKER["identkeyval"] = check_ident_key_val
475
  TYPE_CHECKER["keyval"] = check_key_val
476
  TYPE_CHECKER["unit"] = check_unit
477

    
478

    
479
# optparse.py sets make_option, so we do it for our own option class, too
480
cli_option = CliOption
481

    
482

    
483
_YESNO = ("yes", "no")
484
_YORNO = "yes|no"
485

    
486
DEBUG_OPT = cli_option("-d", "--debug", default=0, action="count",
487
                       help="Increase debugging level")
488

    
489
NOHDR_OPT = cli_option("--no-headers", default=False,
490
                       action="store_true", dest="no_headers",
491
                       help="Don't display column headers")
492

    
493
SEP_OPT = cli_option("--separator", default=None,
494
                     action="store", dest="separator",
495
                     help=("Separator between output fields"
496
                           " (defaults to one space)"))
497

    
498
USEUNITS_OPT = cli_option("--units", default=None,
499
                          dest="units", choices=('h', 'm', 'g', 't'),
500
                          help="Specify units for output (one of hmgt)")
501

    
502
FIELDS_OPT = cli_option("-o", "--output", dest="output", action="store",
503
                        type="string", metavar="FIELDS",
504
                        help="Comma separated list of output fields")
505

    
506
FORCE_OPT = cli_option("-f", "--force", dest="force", action="store_true",
507
                       default=False, help="Force the operation")
508

    
509
CONFIRM_OPT = cli_option("--yes", dest="confirm", action="store_true",
510
                         default=False, help="Do not require confirmation")
511

    
512
TAG_SRC_OPT = cli_option("--from", dest="tags_source",
513
                         default=None, help="File with tag names")
514

    
515
SUBMIT_OPT = cli_option("--submit", dest="submit_only",
516
                        default=False, action="store_true",
517
                        help=("Submit the job and return the job ID, but"
518
                              " don't wait for the job to finish"))
519

    
520
SYNC_OPT = cli_option("--sync", dest="do_locking",
521
                      default=False, action="store_true",
522
                      help=("Grab locks while doing the queries"
523
                            " in order to ensure more consistent results"))
524

    
525
_DRY_RUN_OPT = cli_option("--dry-run", default=False,
526
                          action="store_true",
527
                          help=("Do not execute the operation, just run the"
528
                                " check steps and verify it it could be"
529
                                " executed"))
530

    
531
VERBOSE_OPT = cli_option("-v", "--verbose", default=False,
532
                         action="store_true",
533
                         help="Increase the verbosity of the operation")
534

    
535
DEBUG_SIMERR_OPT = cli_option("--debug-simulate-errors", default=False,
536
                              action="store_true", dest="simulate_errors",
537
                              help="Debugging option that makes the operation"
538
                              " treat most runtime checks as failed")
539

    
540
NWSYNC_OPT = cli_option("--no-wait-for-sync", dest="wait_for_sync",
541
                        default=True, action="store_false",
542
                        help="Don't wait for sync (DANGEROUS!)")
543

    
544
DISK_TEMPLATE_OPT = cli_option("-t", "--disk-template", dest="disk_template",
545
                               help="Custom disk setup (diskless, file,"
546
                               " plain or drbd)",
547
                               default=None, metavar="TEMPL",
548
                               choices=list(constants.DISK_TEMPLATES))
549

    
550
NONICS_OPT = cli_option("--no-nics", default=False, action="store_true",
551
                        help="Do not create any network cards for"
552
                        " the instance")
553

    
554
FILESTORE_DIR_OPT = cli_option("--file-storage-dir", dest="file_storage_dir",
555
                               help="Relative path under default cluster-wide"
556
                               " file storage dir to store file-based disks",
557
                               default=None, metavar="<DIR>")
558

    
559
FILESTORE_DRIVER_OPT = cli_option("--file-driver", dest="file_driver",
560
                                  help="Driver to use for image files",
561
                                  default="loop", metavar="<DRIVER>",
562
                                  choices=list(constants.FILE_DRIVER))
563

    
564
IALLOCATOR_OPT = cli_option("-I", "--iallocator", metavar="<NAME>",
565
                            help="Select nodes for the instance automatically"
566
                            " using the <NAME> iallocator plugin",
567
                            default=None, type="string",
568
                            completion_suggest=OPT_COMPL_ONE_IALLOCATOR)
569

    
570
OS_OPT = cli_option("-o", "--os-type", dest="os", help="What OS to run",
571
                    metavar="<os>",
572
                    completion_suggest=OPT_COMPL_ONE_OS)
573

    
574
FORCE_VARIANT_OPT = cli_option("--force-variant", dest="force_variant",
575
                               action="store_true", default=False,
576
                               help="Force an unknown variant")
577

    
578
BACKEND_OPT = cli_option("-B", "--backend-parameters", dest="beparams",
579
                         type="keyval", default={},
580
                         help="Backend parameters")
581

    
582
HVOPTS_OPT =  cli_option("-H", "--hypervisor-parameters", type="keyval",
583
                         default={}, dest="hvparams",
584
                         help="Hypervisor parameters")
585

    
586
HYPERVISOR_OPT = cli_option("-H", "--hypervisor-parameters", dest="hypervisor",
587
                            help="Hypervisor and hypervisor options, in the"
588
                            " format hypervisor:option=value,option=value,...",
589
                            default=None, type="identkeyval")
590

    
591
HVLIST_OPT = cli_option("-H", "--hypervisor-parameters", dest="hvparams",
592
                        help="Hypervisor and hypervisor options, in the"
593
                        " format hypervisor:option=value,option=value,...",
594
                        default=[], action="append", type="identkeyval")
595

    
596
NOIPCHECK_OPT = cli_option("--no-ip-check", dest="ip_check", default=True,
597
                           action="store_false",
598
                           help="Don't check that the instance's IP"
599
                           " is alive")
600

    
601
NONAMECHECK_OPT = cli_option("--no-name-check", dest="name_check",
602
                             default=True, action="store_false",
603
                             help="Don't check that the instance's name"
604
                             " is resolvable")
605

    
606
NET_OPT = cli_option("--net",
607
                     help="NIC parameters", default=[],
608
                     dest="nics", action="append", type="identkeyval")
609

    
610
DISK_OPT = cli_option("--disk", help="Disk parameters", default=[],
611
                      dest="disks", action="append", type="identkeyval")
612

    
613
DISKIDX_OPT = cli_option("--disks", dest="disks", default=None,
614
                         help="Comma-separated list of disks"
615
                         " indices to act on (e.g. 0,2) (optional,"
616
                         " defaults to all disks)")
617

    
618
OS_SIZE_OPT = cli_option("-s", "--os-size", dest="sd_size",
619
                         help="Enforces a single-disk configuration using the"
620
                         " given disk size, in MiB unless a suffix is used",
621
                         default=None, type="unit", metavar="<size>")
622

    
623
IGNORE_CONSIST_OPT = cli_option("--ignore-consistency",
624
                                dest="ignore_consistency",
625
                                action="store_true", default=False,
626
                                help="Ignore the consistency of the disks on"
627
                                " the secondary")
628

    
629
NONLIVE_OPT = cli_option("--non-live", dest="live",
630
                         default=True, action="store_false",
631
                         help="Do a non-live migration (this usually means"
632
                         " freeze the instance, save the state, transfer and"
633
                         " only then resume running on the secondary node)")
634

    
635
NODE_PLACEMENT_OPT = cli_option("-n", "--node", dest="node",
636
                                help="Target node and optional secondary node",
637
                                metavar="<pnode>[:<snode>]",
638
                                completion_suggest=OPT_COMPL_INST_ADD_NODES)
639

    
640
NODE_LIST_OPT = cli_option("-n", "--node", dest="nodes", default=[],
641
                           action="append", metavar="<node>",
642
                           help="Use only this node (can be used multiple"
643
                           " times, if not given defaults to all nodes)",
644
                           completion_suggest=OPT_COMPL_ONE_NODE)
645

    
646
SINGLE_NODE_OPT = cli_option("-n", "--node", dest="node", help="Target node",
647
                             metavar="<node>",
648
                             completion_suggest=OPT_COMPL_ONE_NODE)
649

    
650
NOSTART_OPT = cli_option("--no-start", dest="start", default=True,
651
                         action="store_false",
652
                         help="Don't start the instance after creation")
653

    
654
SHOWCMD_OPT = cli_option("--show-cmd", dest="show_command",
655
                         action="store_true", default=False,
656
                         help="Show command instead of executing it")
657

    
658
CLEANUP_OPT = cli_option("--cleanup", dest="cleanup",
659
                         default=False, action="store_true",
660
                         help="Instead of performing the migration, try to"
661
                         " recover from a failed cleanup. This is safe"
662
                         " to run even if the instance is healthy, but it"
663
                         " will create extra replication traffic and "
664
                         " disrupt briefly the replication (like during the"
665
                         " migration")
666

    
667
STATIC_OPT = cli_option("-s", "--static", dest="static",
668
                        action="store_true", default=False,
669
                        help="Only show configuration data, not runtime data")
670

    
671
ALL_OPT = cli_option("--all", dest="show_all",
672
                     default=False, action="store_true",
673
                     help="Show info on all instances on the cluster."
674
                     " This can take a long time to run, use wisely")
675

    
676
SELECT_OS_OPT = cli_option("--select-os", dest="select_os",
677
                           action="store_true", default=False,
678
                           help="Interactive OS reinstall, lists available"
679
                           " OS templates for selection")
680

    
681
IGNORE_FAILURES_OPT = cli_option("--ignore-failures", dest="ignore_failures",
682
                                 action="store_true", default=False,
683
                                 help="Remove the instance from the cluster"
684
                                 " configuration even if there are failures"
685
                                 " during the removal process")
686

    
687
NEW_SECONDARY_OPT = cli_option("-n", "--new-secondary", dest="dst_node",
688
                               help="Specifies the new secondary node",
689
                               metavar="NODE", default=None,
690
                               completion_suggest=OPT_COMPL_ONE_NODE)
691

    
692
ON_PRIMARY_OPT = cli_option("-p", "--on-primary", dest="on_primary",
693
                            default=False, action="store_true",
694
                            help="Replace the disk(s) on the primary"
695
                            " node (only for the drbd template)")
696

    
697
ON_SECONDARY_OPT = cli_option("-s", "--on-secondary", dest="on_secondary",
698
                              default=False, action="store_true",
699
                              help="Replace the disk(s) on the secondary"
700
                              " node (only for the drbd template)")
701

    
702
AUTO_REPLACE_OPT = cli_option("-a", "--auto", dest="auto",
703
                              default=False, action="store_true",
704
                              help="Automatically replace faulty disks"
705
                              " (only for the drbd template)")
706

    
707
IGNORE_SIZE_OPT = cli_option("--ignore-size", dest="ignore_size",
708
                             default=False, action="store_true",
709
                             help="Ignore current recorded size"
710
                             " (useful for forcing activation when"
711
                             " the recorded size is wrong)")
712

    
713
SRC_NODE_OPT = cli_option("--src-node", dest="src_node", help="Source node",
714
                          metavar="<node>",
715
                          completion_suggest=OPT_COMPL_ONE_NODE)
716

    
717
SRC_DIR_OPT = cli_option("--src-dir", dest="src_dir", help="Source directory",
718
                         metavar="<dir>")
719

    
720
SECONDARY_IP_OPT = cli_option("-s", "--secondary-ip", dest="secondary_ip",
721
                              help="Specify the secondary ip for the node",
722
                              metavar="ADDRESS", default=None)
723

    
724
READD_OPT = cli_option("--readd", dest="readd",
725
                       default=False, action="store_true",
726
                       help="Readd old node after replacing it")
727

    
728
NOSSH_KEYCHECK_OPT = cli_option("--no-ssh-key-check", dest="ssh_key_check",
729
                                default=True, action="store_false",
730
                                help="Disable SSH key fingerprint checking")
731

    
732

    
733
MC_OPT = cli_option("-C", "--master-candidate", dest="master_candidate",
734
                    choices=_YESNO, default=None, metavar=_YORNO,
735
                    help="Set the master_candidate flag on the node")
736

    
737
OFFLINE_OPT = cli_option("-O", "--offline", dest="offline", metavar=_YORNO,
738
                         choices=_YESNO, default=None,
739
                         help="Set the offline flag on the node")
740

    
741
DRAINED_OPT = cli_option("-D", "--drained", dest="drained", metavar=_YORNO,
742
                         choices=_YESNO, default=None,
743
                         help="Set the drained flag on the node")
744

    
745
ALLOCATABLE_OPT = cli_option("--allocatable", dest="allocatable",
746
                             choices=_YESNO, default=None, metavar=_YORNO,
747
                             help="Set the allocatable flag on a volume")
748

    
749
NOLVM_STORAGE_OPT = cli_option("--no-lvm-storage", dest="lvm_storage",
750
                               help="Disable support for lvm based instances"
751
                               " (cluster-wide)",
752
                               action="store_false", default=True)
753

    
754
ENABLED_HV_OPT = cli_option("--enabled-hypervisors",
755
                            dest="enabled_hypervisors",
756
                            help="Comma-separated list of hypervisors",
757
                            type="string", default=None)
758

    
759
NIC_PARAMS_OPT = cli_option("-N", "--nic-parameters", dest="nicparams",
760
                            type="keyval", default={},
761
                            help="NIC parameters")
762

    
763
CP_SIZE_OPT = cli_option("-C", "--candidate-pool-size", default=None,
764
                         dest="candidate_pool_size", type="int",
765
                         help="Set the candidate pool size")
766

    
767
VG_NAME_OPT = cli_option("-g", "--vg-name", dest="vg_name",
768
                         help="Enables LVM and specifies the volume group"
769
                         " name (cluster-wide) for disk allocation [xenvg]",
770
                         metavar="VG", default=None)
771

    
772
YES_DOIT_OPT = cli_option("--yes-do-it", dest="yes_do_it",
773
                          help="Destroy cluster", action="store_true")
774

    
775
NOVOTING_OPT = cli_option("--no-voting", dest="no_voting",
776
                          help="Skip node agreement check (dangerous)",
777
                          action="store_true", default=False)
778

    
779
MAC_PREFIX_OPT = cli_option("-m", "--mac-prefix", dest="mac_prefix",
780
                            help="Specify the mac prefix for the instance IP"
781
                            " addresses, in the format XX:XX:XX",
782
                            metavar="PREFIX",
783
                            default=None)
784

    
785
MASTER_NETDEV_OPT = cli_option("--master-netdev", dest="master_netdev",
786
                               help="Specify the node interface (cluster-wide)"
787
                               " on which the master IP address will be added "
788
                               " [%s]" % constants.DEFAULT_BRIDGE,
789
                               metavar="NETDEV",
790
                               default=constants.DEFAULT_BRIDGE)
791

    
792

    
793
GLOBAL_FILEDIR_OPT = cli_option("--file-storage-dir", dest="file_storage_dir",
794
                                help="Specify the default directory (cluster-"
795
                                "wide) for storing the file-based disks [%s]" %
796
                                constants.DEFAULT_FILE_STORAGE_DIR,
797
                                metavar="DIR",
798
                                default=constants.DEFAULT_FILE_STORAGE_DIR)
799

    
800
NOMODIFY_ETCHOSTS_OPT = cli_option("--no-etc-hosts", dest="modify_etc_hosts",
801
                                   help="Don't modify /etc/hosts",
802
                                   action="store_false", default=True)
803

    
804
NOMODIFY_SSH_SETUP_OPT = cli_option("--no-ssh-init", dest="modify_ssh_setup",
805
                                    help="Don't initialize SSH keys",
806
                                    action="store_false", default=True)
807

    
808
ERROR_CODES_OPT = cli_option("--error-codes", dest="error_codes",
809
                             help="Enable parseable error messages",
810
                             action="store_true", default=False)
811

    
812
NONPLUS1_OPT = cli_option("--no-nplus1-mem", dest="skip_nplusone_mem",
813
                          help="Skip N+1 memory redundancy tests",
814
                          action="store_true", default=False)
815

    
816
REBOOT_TYPE_OPT = cli_option("-t", "--type", dest="reboot_type",
817
                             help="Type of reboot: soft/hard/full",
818
                             default=constants.INSTANCE_REBOOT_HARD,
819
                             metavar="<REBOOT>",
820
                             choices=list(constants.REBOOT_TYPES))
821

    
822
IGNORE_SECONDARIES_OPT = cli_option("--ignore-secondaries",
823
                                    dest="ignore_secondaries",
824
                                    default=False, action="store_true",
825
                                    help="Ignore errors from secondaries")
826

    
827
NOSHUTDOWN_OPT = cli_option("--noshutdown", dest="shutdown",
828
                            action="store_false", default=True,
829
                            help="Don't shutdown the instance (unsafe)")
830

    
831
TIMEOUT_OPT = cli_option("--timeout", dest="timeout", type="int",
832
                         default=constants.DEFAULT_SHUTDOWN_TIMEOUT,
833
                         help="Maximum time to wait")
834

    
835
SHUTDOWN_TIMEOUT_OPT = cli_option("--shutdown-timeout",
836
                         dest="shutdown_timeout", type="int",
837
                         default=constants.DEFAULT_SHUTDOWN_TIMEOUT,
838
                         help="Maximum time to wait for instance shutdown")
839

    
840
EARLY_RELEASE_OPT = cli_option("--early-release",
841
                               dest="early_release", default=False,
842
                               action="store_true",
843
                               help="Release the locks on the secondary"
844
                               " node(s) early")
845

    
846

    
847
def _ParseArgs(argv, commands, aliases):
848
  """Parser for the command line arguments.
849

850
  This function parses the arguments and returns the function which
851
  must be executed together with its (modified) arguments.
852

853
  @param argv: the command line
854
  @param commands: dictionary with special contents, see the design
855
      doc for cmdline handling
856
  @param aliases: dictionary with command aliases {'alias': 'target, ...}
857

858
  """
859
  if len(argv) == 0:
860
    binary = "<command>"
861
  else:
862
    binary = argv[0].split("/")[-1]
863

    
864
  if len(argv) > 1 and argv[1] == "--version":
865
    ToStdout("%s (ganeti) %s", binary, constants.RELEASE_VERSION)
866
    # Quit right away. That way we don't have to care about this special
867
    # argument. optparse.py does it the same.
868
    sys.exit(0)
869

    
870
  if len(argv) < 2 or not (argv[1] in commands or
871
                           argv[1] in aliases):
872
    # let's do a nice thing
873
    sortedcmds = commands.keys()
874
    sortedcmds.sort()
875

    
876
    ToStdout("Usage: %s {command} [options...] [argument...]", binary)
877
    ToStdout("%s <command> --help to see details, or man %s", binary, binary)
878
    ToStdout("")
879

    
880
    # compute the max line length for cmd + usage
881
    mlen = max([len(" %s" % cmd) for cmd in commands])
882
    mlen = min(60, mlen) # should not get here...
883

    
884
    # and format a nice command list
885
    ToStdout("Commands:")
886
    for cmd in sortedcmds:
887
      cmdstr = " %s" % (cmd,)
888
      help_text = commands[cmd][4]
889
      help_lines = textwrap.wrap(help_text, 79 - 3 - mlen)
890
      ToStdout("%-*s - %s", mlen, cmdstr, help_lines.pop(0))
891
      for line in help_lines:
892
        ToStdout("%-*s   %s", mlen, "", line)
893

    
894
    ToStdout("")
895

    
896
    return None, None, None
897

    
898
  # get command, unalias it, and look it up in commands
899
  cmd = argv.pop(1)
900
  if cmd in aliases:
901
    if cmd in commands:
902
      raise errors.ProgrammerError("Alias '%s' overrides an existing"
903
                                   " command" % cmd)
904

    
905
    if aliases[cmd] not in commands:
906
      raise errors.ProgrammerError("Alias '%s' maps to non-existing"
907
                                   " command '%s'" % (cmd, aliases[cmd]))
908

    
909
    cmd = aliases[cmd]
910

    
911
  func, args_def, parser_opts, usage, description = commands[cmd]
912
  parser = OptionParser(option_list=parser_opts + [_DRY_RUN_OPT, DEBUG_OPT],
913
                        description=description,
914
                        formatter=TitledHelpFormatter(),
915
                        usage="%%prog %s %s" % (cmd, usage))
916
  parser.disable_interspersed_args()
917
  options, args = parser.parse_args()
918

    
919
  if not _CheckArguments(cmd, args_def, args):
920
    return None, None, None
921

    
922
  return func, options, args
923

    
924

    
925
def _CheckArguments(cmd, args_def, args):
926
  """Verifies the arguments using the argument definition.
927

928
  Algorithm:
929

930
    1. Abort with error if values specified by user but none expected.
931

932
    1. For each argument in definition
933

934
      1. Keep running count of minimum number of values (min_count)
935
      1. Keep running count of maximum number of values (max_count)
936
      1. If it has an unlimited number of values
937

938
        1. Abort with error if it's not the last argument in the definition
939

940
    1. If last argument has limited number of values
941

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

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

946
  """
947
  if args and not args_def:
948
    ToStderr("Error: Command %s expects no arguments", cmd)
949
    return False
950

    
951
  min_count = None
952
  max_count = None
953
  check_max = None
954

    
955
  last_idx = len(args_def) - 1
956

    
957
  for idx, arg in enumerate(args_def):
958
    if min_count is None:
959
      min_count = arg.min
960
    elif arg.min is not None:
961
      min_count += arg.min
962

    
963
    if max_count is None:
964
      max_count = arg.max
965
    elif arg.max is not None:
966
      max_count += arg.max
967

    
968
    if idx == last_idx:
969
      check_max = (arg.max is not None)
970

    
971
    elif arg.max is None:
972
      raise errors.ProgrammerError("Only the last argument can have max=None")
973

    
974
  if check_max:
975
    # Command with exact number of arguments
976
    if (min_count is not None and max_count is not None and
977
        min_count == max_count and len(args) != min_count):
978
      ToStderr("Error: Command %s expects %d argument(s)", cmd, min_count)
979
      return False
980

    
981
    # Command with limited number of arguments
982
    if max_count is not None and len(args) > max_count:
983
      ToStderr("Error: Command %s expects only %d argument(s)",
984
               cmd, max_count)
985
      return False
986

    
987
  # Command with some required arguments
988
  if min_count is not None and len(args) < min_count:
989
    ToStderr("Error: Command %s expects at least %d argument(s)",
990
             cmd, min_count)
991
    return False
992

    
993
  return True
994

    
995

    
996
def SplitNodeOption(value):
997
  """Splits the value of a --node option.
998

999
  """
1000
  if value and ':' in value:
1001
    return value.split(':', 1)
1002
  else:
1003
    return (value, None)
1004

    
1005

    
1006
def CalculateOSNames(os_name, os_variants):
1007
  """Calculates all the names an OS can be called, according to its variants.
1008

1009
  @type os_name: string
1010
  @param os_name: base name of the os
1011
  @type os_variants: list or None
1012
  @param os_variants: list of supported variants
1013
  @rtype: list
1014
  @return: list of valid names
1015

1016
  """
1017
  if os_variants:
1018
    return ['%s+%s' % (os_name, v) for v in os_variants]
1019
  else:
1020
    return [os_name]
1021

    
1022

    
1023
def UsesRPC(fn):
1024
  def wrapper(*args, **kwargs):
1025
    rpc.Init()
1026
    try:
1027
      return fn(*args, **kwargs)
1028
    finally:
1029
      rpc.Shutdown()
1030
  return wrapper
1031

    
1032

    
1033
def AskUser(text, choices=None):
1034
  """Ask the user a question.
1035

1036
  @param text: the question to ask
1037

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

1043
  @return: one of the return values from the choices list; if input is
1044
      not possible (i.e. not running with a tty, we return the last
1045
      entry from the list
1046

1047
  """
1048
  if choices is None:
1049
    choices = [('y', True, 'Perform the operation'),
1050
               ('n', False, 'Do not perform the operation')]
1051
  if not choices or not isinstance(choices, list):
1052
    raise errors.ProgrammerError("Invalid choices argument to AskUser")
1053
  for entry in choices:
1054
    if not isinstance(entry, tuple) or len(entry) < 3 or entry[0] == '?':
1055
      raise errors.ProgrammerError("Invalid choices element to AskUser")
1056

    
1057
  answer = choices[-1][1]
1058
  new_text = []
1059
  for line in text.splitlines():
1060
    new_text.append(textwrap.fill(line, 70, replace_whitespace=False))
1061
  text = "\n".join(new_text)
1062
  try:
1063
    f = file("/dev/tty", "a+")
1064
  except IOError:
1065
    return answer
1066
  try:
1067
    chars = [entry[0] for entry in choices]
1068
    chars[-1] = "[%s]" % chars[-1]
1069
    chars.append('?')
1070
    maps = dict([(entry[0], entry[1]) for entry in choices])
1071
    while True:
1072
      f.write(text)
1073
      f.write('\n')
1074
      f.write("/".join(chars))
1075
      f.write(": ")
1076
      line = f.readline(2).strip().lower()
1077
      if line in maps:
1078
        answer = maps[line]
1079
        break
1080
      elif line == '?':
1081
        for entry in choices:
1082
          f.write(" %s - %s\n" % (entry[0], entry[2]))
1083
        f.write("\n")
1084
        continue
1085
  finally:
1086
    f.close()
1087
  return answer
1088

    
1089

    
1090
class JobSubmittedException(Exception):
1091
  """Job was submitted, client should exit.
1092

1093
  This exception has one argument, the ID of the job that was
1094
  submitted. The handler should print this ID.
1095

1096
  This is not an error, just a structured way to exit from clients.
1097

1098
  """
1099

    
1100

    
1101
def SendJob(ops, cl=None):
1102
  """Function to submit an opcode without waiting for the results.
1103

1104
  @type ops: list
1105
  @param ops: list of opcodes
1106
  @type cl: luxi.Client
1107
  @param cl: the luxi client to use for communicating with the master;
1108
             if None, a new client will be created
1109

1110
  """
1111
  if cl is None:
1112
    cl = GetClient()
1113

    
1114
  job_id = cl.SubmitJob(ops)
1115

    
1116
  return job_id
1117

    
1118

    
1119
def PollJob(job_id, cl=None, feedback_fn=None):
1120
  """Function to poll for the result of a job.
1121

1122
  @type job_id: job identified
1123
  @param job_id: the job to poll for results
1124
  @type cl: luxi.Client
1125
  @param cl: the luxi client to use for communicating with the master;
1126
             if None, a new client will be created
1127

1128
  """
1129
  if cl is None:
1130
    cl = GetClient()
1131

    
1132
  prev_job_info = None
1133
  prev_logmsg_serial = None
1134

    
1135
  status = None
1136

    
1137
  notified_queued = False
1138
  notified_waitlock = False
1139

    
1140
  while True:
1141
    result = cl.WaitForJobChangeOnce(job_id, ["status"], prev_job_info,
1142
                                     prev_logmsg_serial)
1143
    if not result:
1144
      # job not found, go away!
1145
      raise errors.JobLost("Job with id %s lost" % job_id)
1146
    elif result == constants.JOB_NOTCHANGED:
1147
      if status is not None and not callable(feedback_fn):
1148
        if status == constants.JOB_STATUS_QUEUED and not notified_queued:
1149
          ToStderr("Job %s is waiting in queue", job_id)
1150
          notified_queued = True
1151
        elif status == constants.JOB_STATUS_WAITLOCK and not notified_waitlock:
1152
          ToStderr("Job %s is trying to acquire all necessary locks", job_id)
1153
          notified_waitlock = True
1154

    
1155
      # Wait again
1156
      continue
1157

    
1158
    # Split result, a tuple of (field values, log entries)
1159
    (job_info, log_entries) = result
1160
    (status, ) = job_info
1161

    
1162
    if log_entries:
1163
      for log_entry in log_entries:
1164
        (serial, timestamp, _, message) = log_entry
1165
        if callable(feedback_fn):
1166
          feedback_fn(log_entry[1:])
1167
        else:
1168
          encoded = utils.SafeEncode(message)
1169
          ToStdout("%s %s", time.ctime(utils.MergeTime(timestamp)), encoded)
1170
        prev_logmsg_serial = max(prev_logmsg_serial, serial)
1171

    
1172
    # TODO: Handle canceled and archived jobs
1173
    elif status in (constants.JOB_STATUS_SUCCESS,
1174
                    constants.JOB_STATUS_ERROR,
1175
                    constants.JOB_STATUS_CANCELING,
1176
                    constants.JOB_STATUS_CANCELED):
1177
      break
1178

    
1179
    prev_job_info = job_info
1180

    
1181
  jobs = cl.QueryJobs([job_id], ["status", "opstatus", "opresult"])
1182
  if not jobs:
1183
    raise errors.JobLost("Job with id %s lost" % job_id)
1184

    
1185
  status, opstatus, result = jobs[0]
1186
  if status == constants.JOB_STATUS_SUCCESS:
1187
    return result
1188
  elif status in (constants.JOB_STATUS_CANCELING,
1189
                  constants.JOB_STATUS_CANCELED):
1190
    raise errors.OpExecError("Job was canceled")
1191
  else:
1192
    has_ok = False
1193
    for idx, (status, msg) in enumerate(zip(opstatus, result)):
1194
      if status == constants.OP_STATUS_SUCCESS:
1195
        has_ok = True
1196
      elif status == constants.OP_STATUS_ERROR:
1197
        errors.MaybeRaise(msg)
1198
        if has_ok:
1199
          raise errors.OpExecError("partial failure (opcode %d): %s" %
1200
                                   (idx, msg))
1201
        else:
1202
          raise errors.OpExecError(str(msg))
1203
    # default failure mode
1204
    raise errors.OpExecError(result)
1205

    
1206

    
1207
def SubmitOpCode(op, cl=None, feedback_fn=None, opts=None):
1208
  """Legacy function to submit an opcode.
1209

1210
  This is just a simple wrapper over the construction of the processor
1211
  instance. It should be extended to better handle feedback and
1212
  interaction functions.
1213

1214
  """
1215
  if cl is None:
1216
    cl = GetClient()
1217

    
1218
  SetGenericOpcodeOpts([op], opts)
1219

    
1220
  job_id = SendJob([op], cl)
1221

    
1222
  op_results = PollJob(job_id, cl=cl, feedback_fn=feedback_fn)
1223

    
1224
  return op_results[0]
1225

    
1226

    
1227
def SubmitOrSend(op, opts, cl=None, feedback_fn=None):
1228
  """Wrapper around SubmitOpCode or SendJob.
1229

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

1235
  It will also process the opcodes if we're sending the via SendJob
1236
  (otherwise SubmitOpCode does it).
1237

1238
  """
1239
  if opts and opts.submit_only:
1240
    job = [op]
1241
    SetGenericOpcodeOpts(job, opts)
1242
    job_id = SendJob(job, cl=cl)
1243
    raise JobSubmittedException(job_id)
1244
  else:
1245
    return SubmitOpCode(op, cl=cl, feedback_fn=feedback_fn, opts=opts)
1246

    
1247

    
1248
def SetGenericOpcodeOpts(opcode_list, options):
1249
  """Processor for generic options.
1250

1251
  This function updates the given opcodes based on generic command
1252
  line options (like debug, dry-run, etc.).
1253

1254
  @param opcode_list: list of opcodes
1255
  @param options: command line options or None
1256
  @return: None (in-place modification)
1257

1258
  """
1259
  if not options:
1260
    return
1261
  for op in opcode_list:
1262
    op.dry_run = options.dry_run
1263
    op.debug_level = options.debug
1264

    
1265

    
1266
def GetClient():
1267
  # TODO: Cache object?
1268
  try:
1269
    client = luxi.Client()
1270
  except luxi.NoMasterError:
1271
    ss = ssconf.SimpleStore()
1272

    
1273
    # Try to read ssconf file
1274
    try:
1275
      ss.GetMasterNode()
1276
    except errors.ConfigurationError:
1277
      raise errors.OpPrereqError("Cluster not initialized or this machine is"
1278
                                 " not part of a cluster")
1279

    
1280
    master, myself = ssconf.GetMasterAndMyself(ss=ss)
1281
    if master != myself:
1282
      raise errors.OpPrereqError("This is not the master node, please connect"
1283
                                 " to node '%s' and rerun the command" %
1284
                                 master)
1285
    raise
1286
  return client
1287

    
1288

    
1289
def FormatError(err):
1290
  """Return a formatted error message for a given error.
1291

1292
  This function takes an exception instance and returns a tuple
1293
  consisting of two values: first, the recommended exit code, and
1294
  second, a string describing the error message (not
1295
  newline-terminated).
1296

1297
  """
1298
  retcode = 1
1299
  obuf = StringIO()
1300
  msg = str(err)
1301
  if isinstance(err, errors.ConfigurationError):
1302
    txt = "Corrupt configuration file: %s" % msg
1303
    logging.error(txt)
1304
    obuf.write(txt + "\n")
1305
    obuf.write("Aborting.")
1306
    retcode = 2
1307
  elif isinstance(err, errors.HooksAbort):
1308
    obuf.write("Failure: hooks execution failed:\n")
1309
    for node, script, out in err.args[0]:
1310
      if out:
1311
        obuf.write("  node: %s, script: %s, output: %s\n" %
1312
                   (node, script, out))
1313
      else:
1314
        obuf.write("  node: %s, script: %s (no output)\n" %
1315
                   (node, script))
1316
  elif isinstance(err, errors.HooksFailure):
1317
    obuf.write("Failure: hooks general failure: %s" % msg)
1318
  elif isinstance(err, errors.ResolverError):
1319
    this_host = utils.HostInfo.SysName()
1320
    if err.args[0] == this_host:
1321
      msg = "Failure: can't resolve my own hostname ('%s')"
1322
    else:
1323
      msg = "Failure: can't resolve hostname '%s'"
1324
    obuf.write(msg % err.args[0])
1325
  elif isinstance(err, errors.OpPrereqError):
1326
    if len(err.args) == 2:
1327
      obuf.write("Failure: prerequisites not met for this"
1328
               " operation:\nerror type: %s, error details:\n%s" %
1329
                 (err.args[1], err.args[0]))
1330
    else:
1331
      obuf.write("Failure: prerequisites not met for this"
1332
                 " operation:\n%s" % msg)
1333
  elif isinstance(err, errors.OpExecError):
1334
    obuf.write("Failure: command execution error:\n%s" % msg)
1335
  elif isinstance(err, errors.TagError):
1336
    obuf.write("Failure: invalid tag(s) given:\n%s" % msg)
1337
  elif isinstance(err, errors.JobQueueDrainError):
1338
    obuf.write("Failure: the job queue is marked for drain and doesn't"
1339
               " accept new requests\n")
1340
  elif isinstance(err, errors.JobQueueFull):
1341
    obuf.write("Failure: the job queue is full and doesn't accept new"
1342
               " job submissions until old jobs are archived\n")
1343
  elif isinstance(err, errors.TypeEnforcementError):
1344
    obuf.write("Parameter Error: %s" % msg)
1345
  elif isinstance(err, errors.ParameterError):
1346
    obuf.write("Failure: unknown/wrong parameter name '%s'" % msg)
1347
  elif isinstance(err, errors.GenericError):
1348
    obuf.write("Unhandled Ganeti error: %s" % msg)
1349
  elif isinstance(err, luxi.NoMasterError):
1350
    obuf.write("Cannot communicate with the master daemon.\nIs it running"
1351
               " and listening for connections?")
1352
  elif isinstance(err, luxi.TimeoutError):
1353
    obuf.write("Timeout while talking to the master daemon. Error:\n"
1354
               "%s" % msg)
1355
  elif isinstance(err, luxi.ProtocolError):
1356
    obuf.write("Unhandled protocol error while talking to the master daemon:\n"
1357
               "%s" % msg)
1358
  elif isinstance(err, JobSubmittedException):
1359
    obuf.write("JobID: %s\n" % err.args[0])
1360
    retcode = 0
1361
  else:
1362
    obuf.write("Unhandled exception: %s" % msg)
1363
  return retcode, obuf.getvalue().rstrip('\n')
1364

    
1365

    
1366
def GenericMain(commands, override=None, aliases=None):
1367
  """Generic main function for all the gnt-* commands.
1368

1369
  Arguments:
1370
    - commands: a dictionary with a special structure, see the design doc
1371
                for command line handling.
1372
    - override: if not None, we expect a dictionary with keys that will
1373
                override command line options; this can be used to pass
1374
                options from the scripts to generic functions
1375
    - aliases: dictionary with command aliases {'alias': 'target, ...}
1376

1377
  """
1378
  # save the program name and the entire command line for later logging
1379
  if sys.argv:
1380
    binary = os.path.basename(sys.argv[0]) or sys.argv[0]
1381
    if len(sys.argv) >= 2:
1382
      binary += " " + sys.argv[1]
1383
      old_cmdline = " ".join(sys.argv[2:])
1384
    else:
1385
      old_cmdline = ""
1386
  else:
1387
    binary = "<unknown program>"
1388
    old_cmdline = ""
1389

    
1390
  if aliases is None:
1391
    aliases = {}
1392

    
1393
  try:
1394
    func, options, args = _ParseArgs(sys.argv, commands, aliases)
1395
  except errors.ParameterError, err:
1396
    result, err_msg = FormatError(err)
1397
    ToStderr(err_msg)
1398
    return 1
1399

    
1400
  if func is None: # parse error
1401
    return 1
1402

    
1403
  if override is not None:
1404
    for key, val in override.iteritems():
1405
      setattr(options, key, val)
1406

    
1407
  utils.SetupLogging(constants.LOG_COMMANDS, debug=options.debug,
1408
                     stderr_logging=True, program=binary)
1409

    
1410
  if old_cmdline:
1411
    logging.info("run with arguments '%s'", old_cmdline)
1412
  else:
1413
    logging.info("run with no arguments")
1414

    
1415
  try:
1416
    result = func(options, args)
1417
  except (errors.GenericError, luxi.ProtocolError,
1418
          JobSubmittedException), err:
1419
    result, err_msg = FormatError(err)
1420
    logging.exception("Error during command processing")
1421
    ToStderr(err_msg)
1422

    
1423
  return result
1424

    
1425

    
1426
def GenericInstanceCreate(mode, opts, args):
1427
  """Add an instance to the cluster via either creation or import.
1428

1429
  @param mode: constants.INSTANCE_CREATE or constants.INSTANCE_IMPORT
1430
  @param opts: the command line options selected by the user
1431
  @type args: list
1432
  @param args: should contain only one element, the new instance name
1433
  @rtype: int
1434
  @return: the desired exit code
1435

1436
  """
1437
  instance = args[0]
1438

    
1439
  (pnode, snode) = SplitNodeOption(opts.node)
1440

    
1441
  hypervisor = None
1442
  hvparams = {}
1443
  if opts.hypervisor:
1444
    hypervisor, hvparams = opts.hypervisor
1445

    
1446
  if opts.nics:
1447
    try:
1448
      nic_max = max(int(nidx[0]) + 1 for nidx in opts.nics)
1449
    except ValueError, err:
1450
      raise errors.OpPrereqError("Invalid NIC index passed: %s" % str(err))
1451
    nics = [{}] * nic_max
1452
    for nidx, ndict in opts.nics:
1453
      nidx = int(nidx)
1454
      if not isinstance(ndict, dict):
1455
        msg = "Invalid nic/%d value: expected dict, got %s" % (nidx, ndict)
1456
        raise errors.OpPrereqError(msg)
1457
      nics[nidx] = ndict
1458
  elif opts.no_nics:
1459
    # no nics
1460
    nics = []
1461
  else:
1462
    # default of one nic, all auto
1463
    nics = [{}]
1464

    
1465
  if opts.disk_template == constants.DT_DISKLESS:
1466
    if opts.disks or opts.sd_size is not None:
1467
      raise errors.OpPrereqError("Diskless instance but disk"
1468
                                 " information passed")
1469
    disks = []
1470
  else:
1471
    if not opts.disks and not opts.sd_size:
1472
      raise errors.OpPrereqError("No disk information specified")
1473
    if opts.disks and opts.sd_size is not None:
1474
      raise errors.OpPrereqError("Please use either the '--disk' or"
1475
                                 " '-s' option")
1476
    if opts.sd_size is not None:
1477
      opts.disks = [(0, {"size": opts.sd_size})]
1478
    try:
1479
      disk_max = max(int(didx[0]) + 1 for didx in opts.disks)
1480
    except ValueError, err:
1481
      raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err))
1482
    disks = [{}] * disk_max
1483
    for didx, ddict in opts.disks:
1484
      didx = int(didx)
1485
      if not isinstance(ddict, dict):
1486
        msg = "Invalid disk/%d value: expected dict, got %s" % (didx, ddict)
1487
        raise errors.OpPrereqError(msg)
1488
      elif "size" not in ddict:
1489
        raise errors.OpPrereqError("Missing size for disk %d" % didx)
1490
      try:
1491
        ddict["size"] = utils.ParseUnit(ddict["size"])
1492
      except ValueError, err:
1493
        raise errors.OpPrereqError("Invalid disk size for disk %d: %s" %
1494
                                   (didx, err))
1495
      disks[didx] = ddict
1496

    
1497
  utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_TYPES)
1498
  utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
1499

    
1500
  if mode == constants.INSTANCE_CREATE:
1501
    start = opts.start
1502
    os_type = opts.os
1503
    src_node = None
1504
    src_path = None
1505
  elif mode == constants.INSTANCE_IMPORT:
1506
    start = False
1507
    os_type = None
1508
    src_node = opts.src_node
1509
    src_path = opts.src_dir
1510
  else:
1511
    raise errors.ProgrammerError("Invalid creation mode %s" % mode)
1512

    
1513
  op = opcodes.OpCreateInstance(instance_name=instance,
1514
                                disks=disks,
1515
                                disk_template=opts.disk_template,
1516
                                nics=nics,
1517
                                pnode=pnode, snode=snode,
1518
                                ip_check=opts.ip_check,
1519
                                name_check=opts.name_check,
1520
                                wait_for_sync=opts.wait_for_sync,
1521
                                file_storage_dir=opts.file_storage_dir,
1522
                                file_driver=opts.file_driver,
1523
                                iallocator=opts.iallocator,
1524
                                hypervisor=hypervisor,
1525
                                hvparams=hvparams,
1526
                                beparams=opts.beparams,
1527
                                mode=mode,
1528
                                start=start,
1529
                                os_type=os_type,
1530
                                src_node=src_node,
1531
                                src_path=src_path)
1532

    
1533
  SubmitOrSend(op, opts)
1534
  return 0
1535

    
1536

    
1537
def GenerateTable(headers, fields, separator, data,
1538
                  numfields=None, unitfields=None,
1539
                  units=None):
1540
  """Prints a table with headers and different fields.
1541

1542
  @type headers: dict
1543
  @param headers: dictionary mapping field names to headers for
1544
      the table
1545
  @type fields: list
1546
  @param fields: the field names corresponding to each row in
1547
      the data field
1548
  @param separator: the separator to be used; if this is None,
1549
      the default 'smart' algorithm is used which computes optimal
1550
      field width, otherwise just the separator is used between
1551
      each field
1552
  @type data: list
1553
  @param data: a list of lists, each sublist being one row to be output
1554
  @type numfields: list
1555
  @param numfields: a list with the fields that hold numeric
1556
      values and thus should be right-aligned
1557
  @type unitfields: list
1558
  @param unitfields: a list with the fields that hold numeric
1559
      values that should be formatted with the units field
1560
  @type units: string or None
1561
  @param units: the units we should use for formatting, or None for
1562
      automatic choice (human-readable for non-separator usage, otherwise
1563
      megabytes); this is a one-letter string
1564

1565
  """
1566
  if units is None:
1567
    if separator:
1568
      units = "m"
1569
    else:
1570
      units = "h"
1571

    
1572
  if numfields is None:
1573
    numfields = []
1574
  if unitfields is None:
1575
    unitfields = []
1576

    
1577
  numfields = utils.FieldSet(*numfields)   # pylint: disable-msg=W0142
1578
  unitfields = utils.FieldSet(*unitfields) # pylint: disable-msg=W0142
1579

    
1580
  format_fields = []
1581
  for field in fields:
1582
    if headers and field not in headers:
1583
      # TODO: handle better unknown fields (either revert to old
1584
      # style of raising exception, or deal more intelligently with
1585
      # variable fields)
1586
      headers[field] = field
1587
    if separator is not None:
1588
      format_fields.append("%s")
1589
    elif numfields.Matches(field):
1590
      format_fields.append("%*s")
1591
    else:
1592
      format_fields.append("%-*s")
1593

    
1594
  if separator is None:
1595
    mlens = [0 for name in fields]
1596
    format = ' '.join(format_fields)
1597
  else:
1598
    format = separator.replace("%", "%%").join(format_fields)
1599

    
1600
  for row in data:
1601
    if row is None:
1602
      continue
1603
    for idx, val in enumerate(row):
1604
      if unitfields.Matches(fields[idx]):
1605
        try:
1606
          val = int(val)
1607
        except (TypeError, ValueError):
1608
          pass
1609
        else:
1610
          val = row[idx] = utils.FormatUnit(val, units)
1611
      val = row[idx] = str(val)
1612
      if separator is None:
1613
        mlens[idx] = max(mlens[idx], len(val))
1614

    
1615
  result = []
1616
  if headers:
1617
    args = []
1618
    for idx, name in enumerate(fields):
1619
      hdr = headers[name]
1620
      if separator is None:
1621
        mlens[idx] = max(mlens[idx], len(hdr))
1622
        args.append(mlens[idx])
1623
      args.append(hdr)
1624
    result.append(format % tuple(args))
1625

    
1626
  if separator is None:
1627
    assert len(mlens) == len(fields)
1628

    
1629
    if fields and not numfields.Matches(fields[-1]):
1630
      mlens[-1] = 0
1631

    
1632
  for line in data:
1633
    args = []
1634
    if line is None:
1635
      line = ['-' for _ in fields]
1636
    for idx in range(len(fields)):
1637
      if separator is None:
1638
        args.append(mlens[idx])
1639
      args.append(line[idx])
1640
    result.append(format % tuple(args))
1641

    
1642
  return result
1643

    
1644

    
1645
def FormatTimestamp(ts):
1646
  """Formats a given timestamp.
1647

1648
  @type ts: timestamp
1649
  @param ts: a timeval-type timestamp, a tuple of seconds and microseconds
1650

1651
  @rtype: string
1652
  @return: a string with the formatted timestamp
1653

1654
  """
1655
  if not isinstance (ts, (tuple, list)) or len(ts) != 2:
1656
    return '?'
1657
  sec, usec = ts
1658
  return time.strftime("%F %T", time.localtime(sec)) + ".%06d" % usec
1659

    
1660

    
1661
def ParseTimespec(value):
1662
  """Parse a time specification.
1663

1664
  The following suffixed will be recognized:
1665

1666
    - s: seconds
1667
    - m: minutes
1668
    - h: hours
1669
    - d: day
1670
    - w: weeks
1671

1672
  Without any suffix, the value will be taken to be in seconds.
1673

1674
  """
1675
  value = str(value)
1676
  if not value:
1677
    raise errors.OpPrereqError("Empty time specification passed")
1678
  suffix_map = {
1679
    's': 1,
1680
    'm': 60,
1681
    'h': 3600,
1682
    'd': 86400,
1683
    'w': 604800,
1684
    }
1685
  if value[-1] not in suffix_map:
1686
    try:
1687
      value = int(value)
1688
    except (TypeError, ValueError):
1689
      raise errors.OpPrereqError("Invalid time specification '%s'" % value)
1690
  else:
1691
    multiplier = suffix_map[value[-1]]
1692
    value = value[:-1]
1693
    if not value: # no data left after stripping the suffix
1694
      raise errors.OpPrereqError("Invalid time specification (only"
1695
                                 " suffix passed)")
1696
    try:
1697
      value = int(value) * multiplier
1698
    except (TypeError, ValueError):
1699
      raise errors.OpPrereqError("Invalid time specification '%s'" % value)
1700
  return value
1701

    
1702

    
1703
def GetOnlineNodes(nodes, cl=None, nowarn=False):
1704
  """Returns the names of online nodes.
1705

1706
  This function will also log a warning on stderr with the names of
1707
  the online nodes.
1708

1709
  @param nodes: if not empty, use only this subset of nodes (minus the
1710
      offline ones)
1711
  @param cl: if not None, luxi client to use
1712
  @type nowarn: boolean
1713
  @param nowarn: by default, this function will output a note with the
1714
      offline nodes that are skipped; if this parameter is True the
1715
      note is not displayed
1716

1717
  """
1718
  if cl is None:
1719
    cl = GetClient()
1720

    
1721
  result = cl.QueryNodes(names=nodes, fields=["name", "offline"],
1722
                         use_locking=False)
1723
  offline = [row[0] for row in result if row[1]]
1724
  if offline and not nowarn:
1725
    ToStderr("Note: skipping offline node(s): %s" % utils.CommaJoin(offline))
1726
  return [row[0] for row in result if not row[1]]
1727

    
1728

    
1729
def _ToStream(stream, txt, *args):
1730
  """Write a message to a stream, bypassing the logging system
1731

1732
  @type stream: file object
1733
  @param stream: the file to which we should write
1734
  @type txt: str
1735
  @param txt: the message
1736

1737
  """
1738
  if args:
1739
    args = tuple(args)
1740
    stream.write(txt % args)
1741
  else:
1742
    stream.write(txt)
1743
  stream.write('\n')
1744
  stream.flush()
1745

    
1746

    
1747
def ToStdout(txt, *args):
1748
  """Write a message to stdout only, bypassing the logging system
1749

1750
  This is just a wrapper over _ToStream.
1751

1752
  @type txt: str
1753
  @param txt: the message
1754

1755
  """
1756
  _ToStream(sys.stdout, txt, *args)
1757

    
1758

    
1759
def ToStderr(txt, *args):
1760
  """Write a message to stderr only, bypassing the logging system
1761

1762
  This is just a wrapper over _ToStream.
1763

1764
  @type txt: str
1765
  @param txt: the message
1766

1767
  """
1768
  _ToStream(sys.stderr, txt, *args)
1769

    
1770

    
1771
class JobExecutor(object):
1772
  """Class which manages the submission and execution of multiple jobs.
1773

1774
  Note that instances of this class should not be reused between
1775
  GetResults() calls.
1776

1777
  """
1778
  def __init__(self, cl=None, verbose=True, opts=None):
1779
    self.queue = []
1780
    if cl is None:
1781
      cl = GetClient()
1782
    self.cl = cl
1783
    self.verbose = verbose
1784
    self.jobs = []
1785
    self.opts = opts
1786

    
1787
  def QueueJob(self, name, *ops):
1788
    """Record a job for later submit.
1789

1790
    @type name: string
1791
    @param name: a description of the job, will be used in WaitJobSet
1792
    """
1793
    SetGenericOpcodeOpts(ops, self.opts)
1794
    self.queue.append((name, ops))
1795

    
1796
  def SubmitPending(self):
1797
    """Submit all pending jobs.
1798

1799
    """
1800
    results = self.cl.SubmitManyJobs([row[1] for row in self.queue])
1801
    for ((status, data), (name, _)) in zip(results, self.queue):
1802
      self.jobs.append((status, data, name))
1803

    
1804
  def GetResults(self):
1805
    """Wait for and return the results of all jobs.
1806

1807
    @rtype: list
1808
    @return: list of tuples (success, job results), in the same order
1809
        as the submitted jobs; if a job has failed, instead of the result
1810
        there will be the error message
1811

1812
    """
1813
    if not self.jobs:
1814
      self.SubmitPending()
1815
    results = []
1816
    if self.verbose:
1817
      ok_jobs = [row[1] for row in self.jobs if row[0]]
1818
      if ok_jobs:
1819
        ToStdout("Submitted jobs %s", utils.CommaJoin(ok_jobs))
1820
    for submit_status, jid, name in self.jobs:
1821
      if not submit_status:
1822
        ToStderr("Failed to submit job for %s: %s", name, jid)
1823
        results.append((False, jid))
1824
        continue
1825
      if self.verbose:
1826
        ToStdout("Waiting for job %s for %s...", jid, name)
1827
      try:
1828
        job_result = PollJob(jid, cl=self.cl)
1829
        success = True
1830
      except (errors.GenericError, luxi.ProtocolError), err:
1831
        _, job_result = FormatError(err)
1832
        success = False
1833
        # the error message will always be shown, verbose or not
1834
        ToStderr("Job %s for %s has failed: %s", jid, name, job_result)
1835

    
1836
      results.append((success, job_result))
1837
    return results
1838

    
1839
  def WaitOrShow(self, wait):
1840
    """Wait for job results or only print the job IDs.
1841

1842
    @type wait: boolean
1843
    @param wait: whether to wait or not
1844

1845
    """
1846
    if wait:
1847
      return self.GetResults()
1848
    else:
1849
      if not self.jobs:
1850
        self.SubmitPending()
1851
      for status, result, name in self.jobs:
1852
        if status:
1853
          ToStdout("%s: %s", result, name)
1854
        else:
1855
          ToStderr("Failure for %s: %s", name, result)