Statistics
| Branch: | Tag: | Revision:

root / lib / cli.py @ cff5fa7f

History | View | Annotate | Download (59 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
  while True:
1136
    result = cl.WaitForJobChange(job_id, ["status"], prev_job_info,
1137
                                 prev_logmsg_serial)
1138
    if not result:
1139
      # job not found, go away!
1140
      raise errors.JobLost("Job with id %s lost" % job_id)
1141

    
1142
    # Split result, a tuple of (field values, log entries)
1143
    (job_info, log_entries) = result
1144
    (status, ) = job_info
1145

    
1146
    if log_entries:
1147
      for log_entry in log_entries:
1148
        (serial, timestamp, _, message) = log_entry
1149
        if callable(feedback_fn):
1150
          feedback_fn(log_entry[1:])
1151
        else:
1152
          encoded = utils.SafeEncode(message)
1153
          ToStdout("%s %s", time.ctime(utils.MergeTime(timestamp)), encoded)
1154
        prev_logmsg_serial = max(prev_logmsg_serial, serial)
1155

    
1156
    # TODO: Handle canceled and archived jobs
1157
    elif status in (constants.JOB_STATUS_SUCCESS,
1158
                    constants.JOB_STATUS_ERROR,
1159
                    constants.JOB_STATUS_CANCELING,
1160
                    constants.JOB_STATUS_CANCELED):
1161
      break
1162

    
1163
    prev_job_info = job_info
1164

    
1165
  jobs = cl.QueryJobs([job_id], ["status", "opstatus", "opresult"])
1166
  if not jobs:
1167
    raise errors.JobLost("Job with id %s lost" % job_id)
1168

    
1169
  status, opstatus, result = jobs[0]
1170
  if status == constants.JOB_STATUS_SUCCESS:
1171
    return result
1172
  elif status in (constants.JOB_STATUS_CANCELING,
1173
                  constants.JOB_STATUS_CANCELED):
1174
    raise errors.OpExecError("Job was canceled")
1175
  else:
1176
    has_ok = False
1177
    for idx, (status, msg) in enumerate(zip(opstatus, result)):
1178
      if status == constants.OP_STATUS_SUCCESS:
1179
        has_ok = True
1180
      elif status == constants.OP_STATUS_ERROR:
1181
        errors.MaybeRaise(msg)
1182
        if has_ok:
1183
          raise errors.OpExecError("partial failure (opcode %d): %s" %
1184
                                   (idx, msg))
1185
        else:
1186
          raise errors.OpExecError(str(msg))
1187
    # default failure mode
1188
    raise errors.OpExecError(result)
1189

    
1190

    
1191
def SubmitOpCode(op, cl=None, feedback_fn=None, opts=None):
1192
  """Legacy function to submit an opcode.
1193

1194
  This is just a simple wrapper over the construction of the processor
1195
  instance. It should be extended to better handle feedback and
1196
  interaction functions.
1197

1198
  """
1199
  if cl is None:
1200
    cl = GetClient()
1201

    
1202
  SetGenericOpcodeOpts([op], opts)
1203

    
1204
  job_id = SendJob([op], cl)
1205

    
1206
  op_results = PollJob(job_id, cl=cl, feedback_fn=feedback_fn)
1207

    
1208
  return op_results[0]
1209

    
1210

    
1211
def SubmitOrSend(op, opts, cl=None, feedback_fn=None):
1212
  """Wrapper around SubmitOpCode or SendJob.
1213

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

1219
  It will also process the opcodes if we're sending the via SendJob
1220
  (otherwise SubmitOpCode does it).
1221

1222
  """
1223
  if opts and opts.submit_only:
1224
    job = [op]
1225
    SetGenericOpcodeOpts(job, opts)
1226
    job_id = SendJob(job, cl=cl)
1227
    raise JobSubmittedException(job_id)
1228
  else:
1229
    return SubmitOpCode(op, cl=cl, feedback_fn=feedback_fn, opts=opts)
1230

    
1231

    
1232
def SetGenericOpcodeOpts(opcode_list, options):
1233
  """Processor for generic options.
1234

1235
  This function updates the given opcodes based on generic command
1236
  line options (like debug, dry-run, etc.).
1237

1238
  @param opcode_list: list of opcodes
1239
  @param options: command line options or None
1240
  @return: None (in-place modification)
1241

1242
  """
1243
  if not options:
1244
    return
1245
  for op in opcode_list:
1246
    op.dry_run = options.dry_run
1247
    op.debug_level = options.debug
1248

    
1249

    
1250
def GetClient():
1251
  # TODO: Cache object?
1252
  try:
1253
    client = luxi.Client()
1254
  except luxi.NoMasterError:
1255
    ss = ssconf.SimpleStore()
1256

    
1257
    # Try to read ssconf file
1258
    try:
1259
      ss.GetMasterNode()
1260
    except errors.ConfigurationError:
1261
      raise errors.OpPrereqError("Cluster not initialized or this machine is"
1262
                                 " not part of a cluster")
1263

    
1264
    master, myself = ssconf.GetMasterAndMyself(ss=ss)
1265
    if master != myself:
1266
      raise errors.OpPrereqError("This is not the master node, please connect"
1267
                                 " to node '%s' and rerun the command" %
1268
                                 master)
1269
    raise
1270
  return client
1271

    
1272

    
1273
def FormatError(err):
1274
  """Return a formatted error message for a given error.
1275

1276
  This function takes an exception instance and returns a tuple
1277
  consisting of two values: first, the recommended exit code, and
1278
  second, a string describing the error message (not
1279
  newline-terminated).
1280

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

    
1349

    
1350
def GenericMain(commands, override=None, aliases=None):
1351
  """Generic main function for all the gnt-* commands.
1352

1353
  Arguments:
1354
    - commands: a dictionary with a special structure, see the design doc
1355
                for command line handling.
1356
    - override: if not None, we expect a dictionary with keys that will
1357
                override command line options; this can be used to pass
1358
                options from the scripts to generic functions
1359
    - aliases: dictionary with command aliases {'alias': 'target, ...}
1360

1361
  """
1362
  # save the program name and the entire command line for later logging
1363
  if sys.argv:
1364
    binary = os.path.basename(sys.argv[0]) or sys.argv[0]
1365
    if len(sys.argv) >= 2:
1366
      binary += " " + sys.argv[1]
1367
      old_cmdline = " ".join(sys.argv[2:])
1368
    else:
1369
      old_cmdline = ""
1370
  else:
1371
    binary = "<unknown program>"
1372
    old_cmdline = ""
1373

    
1374
  if aliases is None:
1375
    aliases = {}
1376

    
1377
  try:
1378
    func, options, args = _ParseArgs(sys.argv, commands, aliases)
1379
  except errors.ParameterError, err:
1380
    result, err_msg = FormatError(err)
1381
    ToStderr(err_msg)
1382
    return 1
1383

    
1384
  if func is None: # parse error
1385
    return 1
1386

    
1387
  if override is not None:
1388
    for key, val in override.iteritems():
1389
      setattr(options, key, val)
1390

    
1391
  utils.SetupLogging(constants.LOG_COMMANDS, debug=options.debug,
1392
                     stderr_logging=True, program=binary)
1393

    
1394
  if old_cmdline:
1395
    logging.info("run with arguments '%s'", old_cmdline)
1396
  else:
1397
    logging.info("run with no arguments")
1398

    
1399
  try:
1400
    result = func(options, args)
1401
  except (errors.GenericError, luxi.ProtocolError,
1402
          JobSubmittedException), err:
1403
    result, err_msg = FormatError(err)
1404
    logging.exception("Error during command processing")
1405
    ToStderr(err_msg)
1406

    
1407
  return result
1408

    
1409

    
1410
def GenericInstanceCreate(mode, opts, args):
1411
  """Add an instance to the cluster via either creation or import.
1412

1413
  @param mode: constants.INSTANCE_CREATE or constants.INSTANCE_IMPORT
1414
  @param opts: the command line options selected by the user
1415
  @type args: list
1416
  @param args: should contain only one element, the new instance name
1417
  @rtype: int
1418
  @return: the desired exit code
1419

1420
  """
1421
  instance = args[0]
1422

    
1423
  (pnode, snode) = SplitNodeOption(opts.node)
1424

    
1425
  hypervisor = None
1426
  hvparams = {}
1427
  if opts.hypervisor:
1428
    hypervisor, hvparams = opts.hypervisor
1429

    
1430
  if opts.nics:
1431
    try:
1432
      nic_max = max(int(nidx[0]) + 1 for nidx in opts.nics)
1433
    except ValueError, err:
1434
      raise errors.OpPrereqError("Invalid NIC index passed: %s" % str(err))
1435
    nics = [{}] * nic_max
1436
    for nidx, ndict in opts.nics:
1437
      nidx = int(nidx)
1438
      if not isinstance(ndict, dict):
1439
        msg = "Invalid nic/%d value: expected dict, got %s" % (nidx, ndict)
1440
        raise errors.OpPrereqError(msg)
1441
      nics[nidx] = ndict
1442
  elif opts.no_nics:
1443
    # no nics
1444
    nics = []
1445
  else:
1446
    # default of one nic, all auto
1447
    nics = [{}]
1448

    
1449
  if opts.disk_template == constants.DT_DISKLESS:
1450
    if opts.disks or opts.sd_size is not None:
1451
      raise errors.OpPrereqError("Diskless instance but disk"
1452
                                 " information passed")
1453
    disks = []
1454
  else:
1455
    if not opts.disks and not opts.sd_size:
1456
      raise errors.OpPrereqError("No disk information specified")
1457
    if opts.disks and opts.sd_size is not None:
1458
      raise errors.OpPrereqError("Please use either the '--disk' or"
1459
                                 " '-s' option")
1460
    if opts.sd_size is not None:
1461
      opts.disks = [(0, {"size": opts.sd_size})]
1462
    try:
1463
      disk_max = max(int(didx[0]) + 1 for didx in opts.disks)
1464
    except ValueError, err:
1465
      raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err))
1466
    disks = [{}] * disk_max
1467
    for didx, ddict in opts.disks:
1468
      didx = int(didx)
1469
      if not isinstance(ddict, dict):
1470
        msg = "Invalid disk/%d value: expected dict, got %s" % (didx, ddict)
1471
        raise errors.OpPrereqError(msg)
1472
      elif "size" not in ddict:
1473
        raise errors.OpPrereqError("Missing size for disk %d" % didx)
1474
      try:
1475
        ddict["size"] = utils.ParseUnit(ddict["size"])
1476
      except ValueError, err:
1477
        raise errors.OpPrereqError("Invalid disk size for disk %d: %s" %
1478
                                   (didx, err))
1479
      disks[didx] = ddict
1480

    
1481
  utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_TYPES)
1482
  utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
1483

    
1484
  if mode == constants.INSTANCE_CREATE:
1485
    start = opts.start
1486
    os_type = opts.os
1487
    src_node = None
1488
    src_path = None
1489
  elif mode == constants.INSTANCE_IMPORT:
1490
    start = False
1491
    os_type = None
1492
    src_node = opts.src_node
1493
    src_path = opts.src_dir
1494
  else:
1495
    raise errors.ProgrammerError("Invalid creation mode %s" % mode)
1496

    
1497
  op = opcodes.OpCreateInstance(instance_name=instance,
1498
                                disks=disks,
1499
                                disk_template=opts.disk_template,
1500
                                nics=nics,
1501
                                pnode=pnode, snode=snode,
1502
                                ip_check=opts.ip_check,
1503
                                name_check=opts.name_check,
1504
                                wait_for_sync=opts.wait_for_sync,
1505
                                file_storage_dir=opts.file_storage_dir,
1506
                                file_driver=opts.file_driver,
1507
                                iallocator=opts.iallocator,
1508
                                hypervisor=hypervisor,
1509
                                hvparams=hvparams,
1510
                                beparams=opts.beparams,
1511
                                mode=mode,
1512
                                start=start,
1513
                                os_type=os_type,
1514
                                src_node=src_node,
1515
                                src_path=src_path)
1516

    
1517
  SubmitOrSend(op, opts)
1518
  return 0
1519

    
1520

    
1521
def GenerateTable(headers, fields, separator, data,
1522
                  numfields=None, unitfields=None,
1523
                  units=None):
1524
  """Prints a table with headers and different fields.
1525

1526
  @type headers: dict
1527
  @param headers: dictionary mapping field names to headers for
1528
      the table
1529
  @type fields: list
1530
  @param fields: the field names corresponding to each row in
1531
      the data field
1532
  @param separator: the separator to be used; if this is None,
1533
      the default 'smart' algorithm is used which computes optimal
1534
      field width, otherwise just the separator is used between
1535
      each field
1536
  @type data: list
1537
  @param data: a list of lists, each sublist being one row to be output
1538
  @type numfields: list
1539
  @param numfields: a list with the fields that hold numeric
1540
      values and thus should be right-aligned
1541
  @type unitfields: list
1542
  @param unitfields: a list with the fields that hold numeric
1543
      values that should be formatted with the units field
1544
  @type units: string or None
1545
  @param units: the units we should use for formatting, or None for
1546
      automatic choice (human-readable for non-separator usage, otherwise
1547
      megabytes); this is a one-letter string
1548

1549
  """
1550
  if units is None:
1551
    if separator:
1552
      units = "m"
1553
    else:
1554
      units = "h"
1555

    
1556
  if numfields is None:
1557
    numfields = []
1558
  if unitfields is None:
1559
    unitfields = []
1560

    
1561
  numfields = utils.FieldSet(*numfields)   # pylint: disable-msg=W0142
1562
  unitfields = utils.FieldSet(*unitfields) # pylint: disable-msg=W0142
1563

    
1564
  format_fields = []
1565
  for field in fields:
1566
    if headers and field not in headers:
1567
      # TODO: handle better unknown fields (either revert to old
1568
      # style of raising exception, or deal more intelligently with
1569
      # variable fields)
1570
      headers[field] = field
1571
    if separator is not None:
1572
      format_fields.append("%s")
1573
    elif numfields.Matches(field):
1574
      format_fields.append("%*s")
1575
    else:
1576
      format_fields.append("%-*s")
1577

    
1578
  if separator is None:
1579
    mlens = [0 for name in fields]
1580
    format = ' '.join(format_fields)
1581
  else:
1582
    format = separator.replace("%", "%%").join(format_fields)
1583

    
1584
  for row in data:
1585
    if row is None:
1586
      continue
1587
    for idx, val in enumerate(row):
1588
      if unitfields.Matches(fields[idx]):
1589
        try:
1590
          val = int(val)
1591
        except (TypeError, ValueError):
1592
          pass
1593
        else:
1594
          val = row[idx] = utils.FormatUnit(val, units)
1595
      val = row[idx] = str(val)
1596
      if separator is None:
1597
        mlens[idx] = max(mlens[idx], len(val))
1598

    
1599
  result = []
1600
  if headers:
1601
    args = []
1602
    for idx, name in enumerate(fields):
1603
      hdr = headers[name]
1604
      if separator is None:
1605
        mlens[idx] = max(mlens[idx], len(hdr))
1606
        args.append(mlens[idx])
1607
      args.append(hdr)
1608
    result.append(format % tuple(args))
1609

    
1610
  if separator is None:
1611
    assert len(mlens) == len(fields)
1612

    
1613
    if fields and not numfields.Matches(fields[-1]):
1614
      mlens[-1] = 0
1615

    
1616
  for line in data:
1617
    args = []
1618
    if line is None:
1619
      line = ['-' for _ in fields]
1620
    for idx in range(len(fields)):
1621
      if separator is None:
1622
        args.append(mlens[idx])
1623
      args.append(line[idx])
1624
    result.append(format % tuple(args))
1625

    
1626
  return result
1627

    
1628

    
1629
def FormatTimestamp(ts):
1630
  """Formats a given timestamp.
1631

1632
  @type ts: timestamp
1633
  @param ts: a timeval-type timestamp, a tuple of seconds and microseconds
1634

1635
  @rtype: string
1636
  @return: a string with the formatted timestamp
1637

1638
  """
1639
  if not isinstance (ts, (tuple, list)) or len(ts) != 2:
1640
    return '?'
1641
  sec, usec = ts
1642
  return time.strftime("%F %T", time.localtime(sec)) + ".%06d" % usec
1643

    
1644

    
1645
def ParseTimespec(value):
1646
  """Parse a time specification.
1647

1648
  The following suffixed will be recognized:
1649

1650
    - s: seconds
1651
    - m: minutes
1652
    - h: hours
1653
    - d: day
1654
    - w: weeks
1655

1656
  Without any suffix, the value will be taken to be in seconds.
1657

1658
  """
1659
  value = str(value)
1660
  if not value:
1661
    raise errors.OpPrereqError("Empty time specification passed")
1662
  suffix_map = {
1663
    's': 1,
1664
    'm': 60,
1665
    'h': 3600,
1666
    'd': 86400,
1667
    'w': 604800,
1668
    }
1669
  if value[-1] not in suffix_map:
1670
    try:
1671
      value = int(value)
1672
    except (TypeError, ValueError):
1673
      raise errors.OpPrereqError("Invalid time specification '%s'" % value)
1674
  else:
1675
    multiplier = suffix_map[value[-1]]
1676
    value = value[:-1]
1677
    if not value: # no data left after stripping the suffix
1678
      raise errors.OpPrereqError("Invalid time specification (only"
1679
                                 " suffix passed)")
1680
    try:
1681
      value = int(value) * multiplier
1682
    except (TypeError, ValueError):
1683
      raise errors.OpPrereqError("Invalid time specification '%s'" % value)
1684
  return value
1685

    
1686

    
1687
def GetOnlineNodes(nodes, cl=None, nowarn=False):
1688
  """Returns the names of online nodes.
1689

1690
  This function will also log a warning on stderr with the names of
1691
  the online nodes.
1692

1693
  @param nodes: if not empty, use only this subset of nodes (minus the
1694
      offline ones)
1695
  @param cl: if not None, luxi client to use
1696
  @type nowarn: boolean
1697
  @param nowarn: by default, this function will output a note with the
1698
      offline nodes that are skipped; if this parameter is True the
1699
      note is not displayed
1700

1701
  """
1702
  if cl is None:
1703
    cl = GetClient()
1704

    
1705
  result = cl.QueryNodes(names=nodes, fields=["name", "offline"],
1706
                         use_locking=False)
1707
  offline = [row[0] for row in result if row[1]]
1708
  if offline and not nowarn:
1709
    ToStderr("Note: skipping offline node(s): %s" % utils.CommaJoin(offline))
1710
  return [row[0] for row in result if not row[1]]
1711

    
1712

    
1713
def _ToStream(stream, txt, *args):
1714
  """Write a message to a stream, bypassing the logging system
1715

1716
  @type stream: file object
1717
  @param stream: the file to which we should write
1718
  @type txt: str
1719
  @param txt: the message
1720

1721
  """
1722
  if args:
1723
    args = tuple(args)
1724
    stream.write(txt % args)
1725
  else:
1726
    stream.write(txt)
1727
  stream.write('\n')
1728
  stream.flush()
1729

    
1730

    
1731
def ToStdout(txt, *args):
1732
  """Write a message to stdout only, bypassing the logging system
1733

1734
  This is just a wrapper over _ToStream.
1735

1736
  @type txt: str
1737
  @param txt: the message
1738

1739
  """
1740
  _ToStream(sys.stdout, txt, *args)
1741

    
1742

    
1743
def ToStderr(txt, *args):
1744
  """Write a message to stderr only, bypassing the logging system
1745

1746
  This is just a wrapper over _ToStream.
1747

1748
  @type txt: str
1749
  @param txt: the message
1750

1751
  """
1752
  _ToStream(sys.stderr, txt, *args)
1753

    
1754

    
1755
class JobExecutor(object):
1756
  """Class which manages the submission and execution of multiple jobs.
1757

1758
  Note that instances of this class should not be reused between
1759
  GetResults() calls.
1760

1761
  """
1762
  def __init__(self, cl=None, verbose=True, opts=None):
1763
    self.queue = []
1764
    if cl is None:
1765
      cl = GetClient()
1766
    self.cl = cl
1767
    self.verbose = verbose
1768
    self.jobs = []
1769
    self.opts = opts
1770

    
1771
  def QueueJob(self, name, *ops):
1772
    """Record a job for later submit.
1773

1774
    @type name: string
1775
    @param name: a description of the job, will be used in WaitJobSet
1776
    """
1777
    SetGenericOpcodeOpts(ops, self.opts)
1778
    self.queue.append((name, ops))
1779

    
1780
  def SubmitPending(self):
1781
    """Submit all pending jobs.
1782

1783
    """
1784
    results = self.cl.SubmitManyJobs([row[1] for row in self.queue])
1785
    for ((status, data), (name, _)) in zip(results, self.queue):
1786
      self.jobs.append((status, data, name))
1787

    
1788
  def GetResults(self):
1789
    """Wait for and return the results of all jobs.
1790

1791
    @rtype: list
1792
    @return: list of tuples (success, job results), in the same order
1793
        as the submitted jobs; if a job has failed, instead of the result
1794
        there will be the error message
1795

1796
    """
1797
    if not self.jobs:
1798
      self.SubmitPending()
1799
    results = []
1800
    if self.verbose:
1801
      ok_jobs = [row[1] for row in self.jobs if row[0]]
1802
      if ok_jobs:
1803
        ToStdout("Submitted jobs %s", utils.CommaJoin(ok_jobs))
1804
    for submit_status, jid, name in self.jobs:
1805
      if not submit_status:
1806
        ToStderr("Failed to submit job for %s: %s", name, jid)
1807
        results.append((False, jid))
1808
        continue
1809
      if self.verbose:
1810
        ToStdout("Waiting for job %s for %s...", jid, name)
1811
      try:
1812
        job_result = PollJob(jid, cl=self.cl)
1813
        success = True
1814
      except (errors.GenericError, luxi.ProtocolError), err:
1815
        _, job_result = FormatError(err)
1816
        success = False
1817
        # the error message will always be shown, verbose or not
1818
        ToStderr("Job %s for %s has failed: %s", jid, name, job_result)
1819

    
1820
      results.append((success, job_result))
1821
    return results
1822

    
1823
  def WaitOrShow(self, wait):
1824
    """Wait for job results or only print the job IDs.
1825

1826
    @type wait: boolean
1827
    @param wait: whether to wait or not
1828

1829
    """
1830
    if wait:
1831
      return self.GetResults()
1832
    else:
1833
      if not self.jobs:
1834
        self.SubmitPending()
1835
      for status, result, name in self.jobs:
1836
        if status:
1837
          ToStdout("%s: %s", result, name)
1838
        else:
1839
          ToStderr("Failure for %s: %s", name, result)