Statistics
| Branch: | Tag: | Revision:

root / lib / cli.py @ ea34193f

History | View | Annotate | Download (58.4 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):
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
  job_id = SendJob([op], cl)
1203

    
1204
  op_results = PollJob(job_id, cl=cl, feedback_fn=feedback_fn)
1205

    
1206
  return op_results[0]
1207

    
1208

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

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

1217
  It will also add the dry-run parameter from the options passed, if true.
1218

1219
  """
1220
  if opts and opts.dry_run:
1221
    op.dry_run = opts.dry_run
1222
  if opts and opts.submit_only:
1223
    job_id = SendJob([op], cl=cl)
1224
    raise JobSubmittedException(job_id)
1225
  else:
1226
    return SubmitOpCode(op, cl=cl, feedback_fn=feedback_fn)
1227

    
1228

    
1229
def GetClient():
1230
  # TODO: Cache object?
1231
  try:
1232
    client = luxi.Client()
1233
  except luxi.NoMasterError:
1234
    ss = ssconf.SimpleStore()
1235

    
1236
    # Try to read ssconf file
1237
    try:
1238
      ss.GetMasterNode()
1239
    except errors.ConfigurationError:
1240
      raise errors.OpPrereqError("Cluster not initialized or this machine is"
1241
                                 " not part of a cluster")
1242

    
1243
    master, myself = ssconf.GetMasterAndMyself(ss=ss)
1244
    if master != myself:
1245
      raise errors.OpPrereqError("This is not the master node, please connect"
1246
                                 " to node '%s' and rerun the command" %
1247
                                 master)
1248
    raise
1249
  return client
1250

    
1251

    
1252
def FormatError(err):
1253
  """Return a formatted error message for a given error.
1254

1255
  This function takes an exception instance and returns a tuple
1256
  consisting of two values: first, the recommended exit code, and
1257
  second, a string describing the error message (not
1258
  newline-terminated).
1259

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

    
1328

    
1329
def GenericMain(commands, override=None, aliases=None):
1330
  """Generic main function for all the gnt-* commands.
1331

1332
  Arguments:
1333
    - commands: a dictionary with a special structure, see the design doc
1334
                for command line handling.
1335
    - override: if not None, we expect a dictionary with keys that will
1336
                override command line options; this can be used to pass
1337
                options from the scripts to generic functions
1338
    - aliases: dictionary with command aliases {'alias': 'target, ...}
1339

1340
  """
1341
  # save the program name and the entire command line for later logging
1342
  if sys.argv:
1343
    binary = os.path.basename(sys.argv[0]) or sys.argv[0]
1344
    if len(sys.argv) >= 2:
1345
      binary += " " + sys.argv[1]
1346
      old_cmdline = " ".join(sys.argv[2:])
1347
    else:
1348
      old_cmdline = ""
1349
  else:
1350
    binary = "<unknown program>"
1351
    old_cmdline = ""
1352

    
1353
  if aliases is None:
1354
    aliases = {}
1355

    
1356
  try:
1357
    func, options, args = _ParseArgs(sys.argv, commands, aliases)
1358
  except errors.ParameterError, err:
1359
    result, err_msg = FormatError(err)
1360
    ToStderr(err_msg)
1361
    return 1
1362

    
1363
  if func is None: # parse error
1364
    return 1
1365

    
1366
  if override is not None:
1367
    for key, val in override.iteritems():
1368
      setattr(options, key, val)
1369

    
1370
  utils.SetupLogging(constants.LOG_COMMANDS, debug=options.debug,
1371
                     stderr_logging=True, program=binary)
1372

    
1373
  if old_cmdline:
1374
    logging.info("run with arguments '%s'", old_cmdline)
1375
  else:
1376
    logging.info("run with no arguments")
1377

    
1378
  try:
1379
    result = func(options, args)
1380
  except (errors.GenericError, luxi.ProtocolError,
1381
          JobSubmittedException), err:
1382
    result, err_msg = FormatError(err)
1383
    logging.exception("Error during command processing")
1384
    ToStderr(err_msg)
1385

    
1386
  return result
1387

    
1388

    
1389
def GenericInstanceCreate(mode, opts, args):
1390
  """Add an instance to the cluster via either creation or import.
1391

1392
  @param mode: constants.INSTANCE_CREATE or constants.INSTANCE_IMPORT
1393
  @param opts: the command line options selected by the user
1394
  @type args: list
1395
  @param args: should contain only one element, the new instance name
1396
  @rtype: int
1397
  @return: the desired exit code
1398

1399
  """
1400
  instance = args[0]
1401

    
1402
  (pnode, snode) = SplitNodeOption(opts.node)
1403

    
1404
  hypervisor = None
1405
  hvparams = {}
1406
  if opts.hypervisor:
1407
    hypervisor, hvparams = opts.hypervisor
1408

    
1409
  if opts.nics:
1410
    try:
1411
      nic_max = max(int(nidx[0]) + 1 for nidx in opts.nics)
1412
    except ValueError, err:
1413
      raise errors.OpPrereqError("Invalid NIC index passed: %s" % str(err))
1414
    nics = [{}] * nic_max
1415
    for nidx, ndict in opts.nics:
1416
      nidx = int(nidx)
1417
      if not isinstance(ndict, dict):
1418
        msg = "Invalid nic/%d value: expected dict, got %s" % (nidx, ndict)
1419
        raise errors.OpPrereqError(msg)
1420
      nics[nidx] = ndict
1421
  elif opts.no_nics:
1422
    # no nics
1423
    nics = []
1424
  else:
1425
    # default of one nic, all auto
1426
    nics = [{}]
1427

    
1428
  if opts.disk_template == constants.DT_DISKLESS:
1429
    if opts.disks or opts.sd_size is not None:
1430
      raise errors.OpPrereqError("Diskless instance but disk"
1431
                                 " information passed")
1432
    disks = []
1433
  else:
1434
    if not opts.disks and not opts.sd_size:
1435
      raise errors.OpPrereqError("No disk information specified")
1436
    if opts.disks and opts.sd_size is not None:
1437
      raise errors.OpPrereqError("Please use either the '--disk' or"
1438
                                 " '-s' option")
1439
    if opts.sd_size is not None:
1440
      opts.disks = [(0, {"size": opts.sd_size})]
1441
    try:
1442
      disk_max = max(int(didx[0]) + 1 for didx in opts.disks)
1443
    except ValueError, err:
1444
      raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err))
1445
    disks = [{}] * disk_max
1446
    for didx, ddict in opts.disks:
1447
      didx = int(didx)
1448
      if not isinstance(ddict, dict):
1449
        msg = "Invalid disk/%d value: expected dict, got %s" % (didx, ddict)
1450
        raise errors.OpPrereqError(msg)
1451
      elif "size" not in ddict:
1452
        raise errors.OpPrereqError("Missing size for disk %d" % didx)
1453
      try:
1454
        ddict["size"] = utils.ParseUnit(ddict["size"])
1455
      except ValueError, err:
1456
        raise errors.OpPrereqError("Invalid disk size for disk %d: %s" %
1457
                                   (didx, err))
1458
      disks[didx] = ddict
1459

    
1460
  utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_TYPES)
1461
  utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
1462

    
1463
  if mode == constants.INSTANCE_CREATE:
1464
    start = opts.start
1465
    os_type = opts.os
1466
    src_node = None
1467
    src_path = None
1468
  elif mode == constants.INSTANCE_IMPORT:
1469
    start = False
1470
    os_type = None
1471
    src_node = opts.src_node
1472
    src_path = opts.src_dir
1473
  else:
1474
    raise errors.ProgrammerError("Invalid creation mode %s" % mode)
1475

    
1476
  op = opcodes.OpCreateInstance(instance_name=instance,
1477
                                disks=disks,
1478
                                disk_template=opts.disk_template,
1479
                                nics=nics,
1480
                                pnode=pnode, snode=snode,
1481
                                ip_check=opts.ip_check,
1482
                                name_check=opts.name_check,
1483
                                wait_for_sync=opts.wait_for_sync,
1484
                                file_storage_dir=opts.file_storage_dir,
1485
                                file_driver=opts.file_driver,
1486
                                iallocator=opts.iallocator,
1487
                                hypervisor=hypervisor,
1488
                                hvparams=hvparams,
1489
                                beparams=opts.beparams,
1490
                                mode=mode,
1491
                                start=start,
1492
                                os_type=os_type,
1493
                                src_node=src_node,
1494
                                src_path=src_path)
1495

    
1496
  SubmitOrSend(op, opts)
1497
  return 0
1498

    
1499

    
1500
def GenerateTable(headers, fields, separator, data,
1501
                  numfields=None, unitfields=None,
1502
                  units=None):
1503
  """Prints a table with headers and different fields.
1504

1505
  @type headers: dict
1506
  @param headers: dictionary mapping field names to headers for
1507
      the table
1508
  @type fields: list
1509
  @param fields: the field names corresponding to each row in
1510
      the data field
1511
  @param separator: the separator to be used; if this is None,
1512
      the default 'smart' algorithm is used which computes optimal
1513
      field width, otherwise just the separator is used between
1514
      each field
1515
  @type data: list
1516
  @param data: a list of lists, each sublist being one row to be output
1517
  @type numfields: list
1518
  @param numfields: a list with the fields that hold numeric
1519
      values and thus should be right-aligned
1520
  @type unitfields: list
1521
  @param unitfields: a list with the fields that hold numeric
1522
      values that should be formatted with the units field
1523
  @type units: string or None
1524
  @param units: the units we should use for formatting, or None for
1525
      automatic choice (human-readable for non-separator usage, otherwise
1526
      megabytes); this is a one-letter string
1527

1528
  """
1529
  if units is None:
1530
    if separator:
1531
      units = "m"
1532
    else:
1533
      units = "h"
1534

    
1535
  if numfields is None:
1536
    numfields = []
1537
  if unitfields is None:
1538
    unitfields = []
1539

    
1540
  numfields = utils.FieldSet(*numfields)   # pylint: disable-msg=W0142
1541
  unitfields = utils.FieldSet(*unitfields) # pylint: disable-msg=W0142
1542

    
1543
  format_fields = []
1544
  for field in fields:
1545
    if headers and field not in headers:
1546
      # TODO: handle better unknown fields (either revert to old
1547
      # style of raising exception, or deal more intelligently with
1548
      # variable fields)
1549
      headers[field] = field
1550
    if separator is not None:
1551
      format_fields.append("%s")
1552
    elif numfields.Matches(field):
1553
      format_fields.append("%*s")
1554
    else:
1555
      format_fields.append("%-*s")
1556

    
1557
  if separator is None:
1558
    mlens = [0 for name in fields]
1559
    format = ' '.join(format_fields)
1560
  else:
1561
    format = separator.replace("%", "%%").join(format_fields)
1562

    
1563
  for row in data:
1564
    if row is None:
1565
      continue
1566
    for idx, val in enumerate(row):
1567
      if unitfields.Matches(fields[idx]):
1568
        try:
1569
          val = int(val)
1570
        except (TypeError, ValueError):
1571
          pass
1572
        else:
1573
          val = row[idx] = utils.FormatUnit(val, units)
1574
      val = row[idx] = str(val)
1575
      if separator is None:
1576
        mlens[idx] = max(mlens[idx], len(val))
1577

    
1578
  result = []
1579
  if headers:
1580
    args = []
1581
    for idx, name in enumerate(fields):
1582
      hdr = headers[name]
1583
      if separator is None:
1584
        mlens[idx] = max(mlens[idx], len(hdr))
1585
        args.append(mlens[idx])
1586
      args.append(hdr)
1587
    result.append(format % tuple(args))
1588

    
1589
  if separator is None:
1590
    assert len(mlens) == len(fields)
1591

    
1592
    if fields and not numfields.Matches(fields[-1]):
1593
      mlens[-1] = 0
1594

    
1595
  for line in data:
1596
    args = []
1597
    if line is None:
1598
      line = ['-' for _ in fields]
1599
    for idx in range(len(fields)):
1600
      if separator is None:
1601
        args.append(mlens[idx])
1602
      args.append(line[idx])
1603
    result.append(format % tuple(args))
1604

    
1605
  return result
1606

    
1607

    
1608
def FormatTimestamp(ts):
1609
  """Formats a given timestamp.
1610

1611
  @type ts: timestamp
1612
  @param ts: a timeval-type timestamp, a tuple of seconds and microseconds
1613

1614
  @rtype: string
1615
  @return: a string with the formatted timestamp
1616

1617
  """
1618
  if not isinstance (ts, (tuple, list)) or len(ts) != 2:
1619
    return '?'
1620
  sec, usec = ts
1621
  return time.strftime("%F %T", time.localtime(sec)) + ".%06d" % usec
1622

    
1623

    
1624
def ParseTimespec(value):
1625
  """Parse a time specification.
1626

1627
  The following suffixed will be recognized:
1628

1629
    - s: seconds
1630
    - m: minutes
1631
    - h: hours
1632
    - d: day
1633
    - w: weeks
1634

1635
  Without any suffix, the value will be taken to be in seconds.
1636

1637
  """
1638
  value = str(value)
1639
  if not value:
1640
    raise errors.OpPrereqError("Empty time specification passed")
1641
  suffix_map = {
1642
    's': 1,
1643
    'm': 60,
1644
    'h': 3600,
1645
    'd': 86400,
1646
    'w': 604800,
1647
    }
1648
  if value[-1] not in suffix_map:
1649
    try:
1650
      value = int(value)
1651
    except (TypeError, ValueError):
1652
      raise errors.OpPrereqError("Invalid time specification '%s'" % value)
1653
  else:
1654
    multiplier = suffix_map[value[-1]]
1655
    value = value[:-1]
1656
    if not value: # no data left after stripping the suffix
1657
      raise errors.OpPrereqError("Invalid time specification (only"
1658
                                 " suffix passed)")
1659
    try:
1660
      value = int(value) * multiplier
1661
    except (TypeError, ValueError):
1662
      raise errors.OpPrereqError("Invalid time specification '%s'" % value)
1663
  return value
1664

    
1665

    
1666
def GetOnlineNodes(nodes, cl=None, nowarn=False):
1667
  """Returns the names of online nodes.
1668

1669
  This function will also log a warning on stderr with the names of
1670
  the online nodes.
1671

1672
  @param nodes: if not empty, use only this subset of nodes (minus the
1673
      offline ones)
1674
  @param cl: if not None, luxi client to use
1675
  @type nowarn: boolean
1676
  @param nowarn: by default, this function will output a note with the
1677
      offline nodes that are skipped; if this parameter is True the
1678
      note is not displayed
1679

1680
  """
1681
  if cl is None:
1682
    cl = GetClient()
1683

    
1684
  result = cl.QueryNodes(names=nodes, fields=["name", "offline"],
1685
                         use_locking=False)
1686
  offline = [row[0] for row in result if row[1]]
1687
  if offline and not nowarn:
1688
    ToStderr("Note: skipping offline node(s): %s" % utils.CommaJoin(offline))
1689
  return [row[0] for row in result if not row[1]]
1690

    
1691

    
1692
def _ToStream(stream, txt, *args):
1693
  """Write a message to a stream, bypassing the logging system
1694

1695
  @type stream: file object
1696
  @param stream: the file to which we should write
1697
  @type txt: str
1698
  @param txt: the message
1699

1700
  """
1701
  if args:
1702
    args = tuple(args)
1703
    stream.write(txt % args)
1704
  else:
1705
    stream.write(txt)
1706
  stream.write('\n')
1707
  stream.flush()
1708

    
1709

    
1710
def ToStdout(txt, *args):
1711
  """Write a message to stdout only, bypassing the logging system
1712

1713
  This is just a wrapper over _ToStream.
1714

1715
  @type txt: str
1716
  @param txt: the message
1717

1718
  """
1719
  _ToStream(sys.stdout, txt, *args)
1720

    
1721

    
1722
def ToStderr(txt, *args):
1723
  """Write a message to stderr only, bypassing the logging system
1724

1725
  This is just a wrapper over _ToStream.
1726

1727
  @type txt: str
1728
  @param txt: the message
1729

1730
  """
1731
  _ToStream(sys.stderr, txt, *args)
1732

    
1733

    
1734
class JobExecutor(object):
1735
  """Class which manages the submission and execution of multiple jobs.
1736

1737
  Note that instances of this class should not be reused between
1738
  GetResults() calls.
1739

1740
  """
1741
  def __init__(self, cl=None, verbose=True):
1742
    self.queue = []
1743
    if cl is None:
1744
      cl = GetClient()
1745
    self.cl = cl
1746
    self.verbose = verbose
1747
    self.jobs = []
1748

    
1749
  def QueueJob(self, name, *ops):
1750
    """Record a job for later submit.
1751

1752
    @type name: string
1753
    @param name: a description of the job, will be used in WaitJobSet
1754
    """
1755
    self.queue.append((name, ops))
1756

    
1757
  def SubmitPending(self):
1758
    """Submit all pending jobs.
1759

1760
    """
1761
    results = self.cl.SubmitManyJobs([row[1] for row in self.queue])
1762
    for ((status, data), (name, _)) in zip(results, self.queue):
1763
      self.jobs.append((status, data, name))
1764

    
1765
  def GetResults(self):
1766
    """Wait for and return the results of all jobs.
1767

1768
    @rtype: list
1769
    @return: list of tuples (success, job results), in the same order
1770
        as the submitted jobs; if a job has failed, instead of the result
1771
        there will be the error message
1772

1773
    """
1774
    if not self.jobs:
1775
      self.SubmitPending()
1776
    results = []
1777
    if self.verbose:
1778
      ok_jobs = [row[1] for row in self.jobs if row[0]]
1779
      if ok_jobs:
1780
        ToStdout("Submitted jobs %s", utils.CommaJoin(ok_jobs))
1781
    for submit_status, jid, name in self.jobs:
1782
      if not submit_status:
1783
        ToStderr("Failed to submit job for %s: %s", name, jid)
1784
        results.append((False, jid))
1785
        continue
1786
      if self.verbose:
1787
        ToStdout("Waiting for job %s for %s...", jid, name)
1788
      try:
1789
        job_result = PollJob(jid, cl=self.cl)
1790
        success = True
1791
      except (errors.GenericError, luxi.ProtocolError), err:
1792
        _, job_result = FormatError(err)
1793
        success = False
1794
        # the error message will always be shown, verbose or not
1795
        ToStderr("Job %s for %s has failed: %s", jid, name, job_result)
1796

    
1797
      results.append((success, job_result))
1798
    return results
1799

    
1800
  def WaitOrShow(self, wait):
1801
    """Wait for job results or only print the job IDs.
1802

1803
    @type wait: boolean
1804
    @param wait: whether to wait or not
1805

1806
    """
1807
    if wait:
1808
      return self.GetResults()
1809
    else:
1810
      if not self.jobs:
1811
        self.SubmitPending()
1812
      for status, result, name in self.jobs:
1813
        if status:
1814
          ToStdout("%s: %s", result, name)
1815
        else:
1816
          ToStderr("Failure for %s: %s", name, result)