Statistics
| Branch: | Tag: | Revision:

root / lib / cli.py @ 919ca415

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

    
172
NO_PREFIX = "no_"
173
UN_PREFIX = "-"
174

    
175

    
176
class _Argument:
177
  def __init__(self, min=0, max=None): # pylint: disable-msg=W0622
178
    self.min = min
179
    self.max = max
180

    
181
  def __repr__(self):
182
    return ("<%s min=%s max=%s>" %
183
            (self.__class__.__name__, self.min, self.max))
184

    
185

    
186
class ArgSuggest(_Argument):
187
  """Suggesting argument.
188

189
  Value can be any of the ones passed to the constructor.
190

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

    
197
  def __repr__(self):
198
    return ("<%s min=%s max=%s choices=%r>" %
199
            (self.__class__.__name__, self.min, self.max, self.choices))
200

    
201

    
202
class ArgChoice(ArgSuggest):
203
  """Choice argument.
204

205
  Value can be any of the ones passed to the constructor. Like L{ArgSuggest},
206
  but value must be one of the choices.
207

208
  """
209

    
210

    
211
class ArgUnknown(_Argument):
212
  """Unknown argument to program (e.g. determined at runtime).
213

214
  """
215

    
216

    
217
class ArgInstance(_Argument):
218
  """Instances argument.
219

220
  """
221

    
222

    
223
class ArgNode(_Argument):
224
  """Node argument.
225

226
  """
227

    
228
class ArgJobId(_Argument):
229
  """Job ID argument.
230

231
  """
232

    
233

    
234
class ArgFile(_Argument):
235
  """File path argument.
236

237
  """
238

    
239

    
240
class ArgCommand(_Argument):
241
  """Command argument.
242

243
  """
244

    
245

    
246
class ArgHost(_Argument):
247
  """Host argument.
248

249
  """
250

    
251

    
252
class ArgOs(_Argument):
253
  """OS argument.
254

255
  """
256

    
257

    
258
ARGS_NONE = []
259
ARGS_MANY_INSTANCES = [ArgInstance()]
260
ARGS_MANY_NODES = [ArgNode()]
261
ARGS_ONE_INSTANCE = [ArgInstance(min=1, max=1)]
262
ARGS_ONE_NODE = [ArgNode(min=1, max=1)]
263
ARGS_ONE_OS = [ArgOs(min=1, max=1)]
264

    
265

    
266
def _ExtractTagsObject(opts, args):
267
  """Extract the tag type object.
268

269
  Note that this function will modify its args parameter.
270

271
  """
272
  if not hasattr(opts, "tag_type"):
273
    raise errors.ProgrammerError("tag_type not passed to _ExtractTagsObject")
274
  kind = opts.tag_type
275
  if kind == constants.TAG_CLUSTER:
276
    retval = kind, kind
277
  elif kind == constants.TAG_NODE or kind == constants.TAG_INSTANCE:
278
    if not args:
279
      raise errors.OpPrereqError("no arguments passed to the command")
280
    name = args.pop(0)
281
    retval = kind, name
282
  else:
283
    raise errors.ProgrammerError("Unhandled tag type '%s'" % kind)
284
  return retval
285

    
286

    
287
def _ExtendTags(opts, args):
288
  """Extend the args if a source file has been given.
289

290
  This function will extend the tags with the contents of the file
291
  passed in the 'tags_source' attribute of the opts parameter. A file
292
  named '-' will be replaced by stdin.
293

294
  """
295
  fname = opts.tags_source
296
  if fname is None:
297
    return
298
  if fname == "-":
299
    new_fh = sys.stdin
300
  else:
301
    new_fh = open(fname, "r")
302
  new_data = []
303
  try:
304
    # we don't use the nice 'new_data = [line.strip() for line in fh]'
305
    # because of python bug 1633941
306
    while True:
307
      line = new_fh.readline()
308
      if not line:
309
        break
310
      new_data.append(line.strip())
311
  finally:
312
    new_fh.close()
313
  args.extend(new_data)
314

    
315

    
316
def ListTags(opts, args):
317
  """List the tags on a given object.
318

319
  This is a generic implementation that knows how to deal with all
320
  three cases of tag objects (cluster, node, instance). The opts
321
  argument is expected to contain a tag_type field denoting what
322
  object type we work on.
323

324
  """
325
  kind, name = _ExtractTagsObject(opts, args)
326
  cl = GetClient()
327
  result = cl.QueryTags(kind, name)
328
  result = list(result)
329
  result.sort()
330
  for tag in result:
331
    ToStdout(tag)
332

    
333

    
334
def AddTags(opts, args):
335
  """Add tags on a given object.
336

337
  This is a generic implementation that knows how to deal with all
338
  three cases of tag objects (cluster, node, instance). The opts
339
  argument is expected to contain a tag_type field denoting what
340
  object type we work on.
341

342
  """
343
  kind, name = _ExtractTagsObject(opts, args)
344
  _ExtendTags(opts, args)
345
  if not args:
346
    raise errors.OpPrereqError("No tags to be added")
347
  op = opcodes.OpAddTags(kind=kind, name=name, tags=args)
348
  SubmitOpCode(op)
349

    
350

    
351
def RemoveTags(opts, args):
352
  """Remove tags from a given object.
353

354
  This is a generic implementation that knows how to deal with all
355
  three cases of tag objects (cluster, node, instance). The opts
356
  argument is expected to contain a tag_type field denoting what
357
  object type we work on.
358

359
  """
360
  kind, name = _ExtractTagsObject(opts, args)
361
  _ExtendTags(opts, args)
362
  if not args:
363
    raise errors.OpPrereqError("No tags to be removed")
364
  op = opcodes.OpDelTags(kind=kind, name=name, tags=args)
365
  SubmitOpCode(op)
366

    
367

    
368
def check_unit(option, opt, value): # pylint: disable-msg=W0613
369
  """OptParsers custom converter for units.
370

371
  """
372
  try:
373
    return utils.ParseUnit(value)
374
  except errors.UnitParseError, err:
375
    raise OptionValueError("option %s: %s" % (opt, err))
376

    
377

    
378
def _SplitKeyVal(opt, data):
379
  """Convert a KeyVal string into a dict.
380

381
  This function will convert a key=val[,...] string into a dict. Empty
382
  values will be converted specially: keys which have the prefix 'no_'
383
  will have the value=False and the prefix stripped, the others will
384
  have value=True.
385

386
  @type opt: string
387
  @param opt: a string holding the option name for which we process the
388
      data, used in building error messages
389
  @type data: string
390
  @param data: a string of the format key=val,key=val,...
391
  @rtype: dict
392
  @return: {key=val, key=val}
393
  @raises errors.ParameterError: if there are duplicate keys
394

395
  """
396
  kv_dict = {}
397
  if data:
398
    for elem in utils.UnescapeAndSplit(data, sep=","):
399
      if "=" in elem:
400
        key, val = elem.split("=", 1)
401
      else:
402
        if elem.startswith(NO_PREFIX):
403
          key, val = elem[len(NO_PREFIX):], False
404
        elif elem.startswith(UN_PREFIX):
405
          key, val = elem[len(UN_PREFIX):], None
406
        else:
407
          key, val = elem, True
408
      if key in kv_dict:
409
        raise errors.ParameterError("Duplicate key '%s' in option %s" %
410
                                    (key, opt))
411
      kv_dict[key] = val
412
  return kv_dict
413

    
414

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

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

421
  """
422
  if ":" not in value:
423
    ident, rest = value, ''
424
  else:
425
    ident, rest = value.split(":", 1)
426

    
427
  if ident.startswith(NO_PREFIX):
428
    if rest:
429
      msg = "Cannot pass options when removing parameter groups: %s" % value
430
      raise errors.ParameterError(msg)
431
    retval = (ident[len(NO_PREFIX):], False)
432
  elif ident.startswith(UN_PREFIX):
433
    if rest:
434
      msg = "Cannot pass options when removing parameter groups: %s" % value
435
      raise errors.ParameterError(msg)
436
    retval = (ident[len(UN_PREFIX):], None)
437
  else:
438
    kv_dict = _SplitKeyVal(opt, rest)
439
    retval = (ident, kv_dict)
440
  return retval
441

    
442

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

446
  This will store the parsed values as a dict {key: val}.
447

448
  """
449
  return _SplitKeyVal(opt, value)
450

    
451

    
452
# completion_suggestion is normally a list. Using numeric values not evaluating
453
# to False for dynamic completion.
454
(OPT_COMPL_MANY_NODES,
455
 OPT_COMPL_ONE_NODE,
456
 OPT_COMPL_ONE_INSTANCE,
457
 OPT_COMPL_ONE_OS,
458
 OPT_COMPL_ONE_IALLOCATOR,
459
 OPT_COMPL_INST_ADD_NODES) = range(100, 106)
460

    
461
OPT_COMPL_ALL = frozenset([
462
  OPT_COMPL_MANY_NODES,
463
  OPT_COMPL_ONE_NODE,
464
  OPT_COMPL_ONE_INSTANCE,
465
  OPT_COMPL_ONE_OS,
466
  OPT_COMPL_ONE_IALLOCATOR,
467
  OPT_COMPL_INST_ADD_NODES,
468
  ])
469

    
470

    
471
class CliOption(Option):
472
  """Custom option class for optparse.
473

474
  """
475
  ATTRS = Option.ATTRS + [
476
    "completion_suggest",
477
    ]
478
  TYPES = Option.TYPES + (
479
    "identkeyval",
480
    "keyval",
481
    "unit",
482
    )
483
  TYPE_CHECKER = Option.TYPE_CHECKER.copy()
484
  TYPE_CHECKER["identkeyval"] = check_ident_key_val
485
  TYPE_CHECKER["keyval"] = check_key_val
486
  TYPE_CHECKER["unit"] = check_unit
487

    
488

    
489
# optparse.py sets make_option, so we do it for our own option class, too
490
cli_option = CliOption
491

    
492

    
493
_YESNO = ("yes", "no")
494
_YORNO = "yes|no"
495

    
496
DEBUG_OPT = cli_option("-d", "--debug", default=0, action="count",
497
                       help="Increase debugging level")
498

    
499
NOHDR_OPT = cli_option("--no-headers", default=False,
500
                       action="store_true", dest="no_headers",
501
                       help="Don't display column headers")
502

    
503
SEP_OPT = cli_option("--separator", default=None,
504
                     action="store", dest="separator",
505
                     help=("Separator between output fields"
506
                           " (defaults to one space)"))
507

    
508
USEUNITS_OPT = cli_option("--units", default=None,
509
                          dest="units", choices=('h', 'm', 'g', 't'),
510
                          help="Specify units for output (one of hmgt)")
511

    
512
FIELDS_OPT = cli_option("-o", "--output", dest="output", action="store",
513
                        type="string", metavar="FIELDS",
514
                        help="Comma separated list of output fields")
515

    
516
FORCE_OPT = cli_option("-f", "--force", dest="force", action="store_true",
517
                       default=False, help="Force the operation")
518

    
519
CONFIRM_OPT = cli_option("--yes", dest="confirm", action="store_true",
520
                         default=False, help="Do not require confirmation")
521

    
522
TAG_SRC_OPT = cli_option("--from", dest="tags_source",
523
                         default=None, help="File with tag names")
524

    
525
SUBMIT_OPT = cli_option("--submit", dest="submit_only",
526
                        default=False, action="store_true",
527
                        help=("Submit the job and return the job ID, but"
528
                              " don't wait for the job to finish"))
529

    
530
SYNC_OPT = cli_option("--sync", dest="do_locking",
531
                      default=False, action="store_true",
532
                      help=("Grab locks while doing the queries"
533
                            " in order to ensure more consistent results"))
534

    
535
_DRY_RUN_OPT = cli_option("--dry-run", default=False,
536
                          action="store_true",
537
                          help=("Do not execute the operation, just run the"
538
                                " check steps and verify it it could be"
539
                                " executed"))
540

    
541
VERBOSE_OPT = cli_option("-v", "--verbose", default=False,
542
                         action="store_true",
543
                         help="Increase the verbosity of the operation")
544

    
545
DEBUG_SIMERR_OPT = cli_option("--debug-simulate-errors", default=False,
546
                              action="store_true", dest="simulate_errors",
547
                              help="Debugging option that makes the operation"
548
                              " treat most runtime checks as failed")
549

    
550
NWSYNC_OPT = cli_option("--no-wait-for-sync", dest="wait_for_sync",
551
                        default=True, action="store_false",
552
                        help="Don't wait for sync (DANGEROUS!)")
553

    
554
DISK_TEMPLATE_OPT = cli_option("-t", "--disk-template", dest="disk_template",
555
                               help="Custom disk setup (diskless, file,"
556
                               " plain or drbd)",
557
                               default=None, metavar="TEMPL",
558
                               choices=list(constants.DISK_TEMPLATES))
559

    
560
NONICS_OPT = cli_option("--no-nics", default=False, action="store_true",
561
                        help="Do not create any network cards for"
562
                        " the instance")
563

    
564
FILESTORE_DIR_OPT = cli_option("--file-storage-dir", dest="file_storage_dir",
565
                               help="Relative path under default cluster-wide"
566
                               " file storage dir to store file-based disks",
567
                               default=None, metavar="<DIR>")
568

    
569
FILESTORE_DRIVER_OPT = cli_option("--file-driver", dest="file_driver",
570
                                  help="Driver to use for image files",
571
                                  default="loop", metavar="<DRIVER>",
572
                                  choices=list(constants.FILE_DRIVER))
573

    
574
IALLOCATOR_OPT = cli_option("-I", "--iallocator", metavar="<NAME>",
575
                            help="Select nodes for the instance automatically"
576
                            " using the <NAME> iallocator plugin",
577
                            default=None, type="string",
578
                            completion_suggest=OPT_COMPL_ONE_IALLOCATOR)
579

    
580
OS_OPT = cli_option("-o", "--os-type", dest="os", help="What OS to run",
581
                    metavar="<os>",
582
                    completion_suggest=OPT_COMPL_ONE_OS)
583

    
584
FORCE_VARIANT_OPT = cli_option("--force-variant", dest="force_variant",
585
                               action="store_true", default=False,
586
                               help="Force an unknown variant")
587

    
588
BACKEND_OPT = cli_option("-B", "--backend-parameters", dest="beparams",
589
                         type="keyval", default={},
590
                         help="Backend parameters")
591

    
592
HVOPTS_OPT =  cli_option("-H", "--hypervisor-parameters", type="keyval",
593
                         default={}, dest="hvparams",
594
                         help="Hypervisor parameters")
595

    
596
HYPERVISOR_OPT = cli_option("-H", "--hypervisor-parameters", dest="hypervisor",
597
                            help="Hypervisor and hypervisor options, in the"
598
                            " format hypervisor:option=value,option=value,...",
599
                            default=None, type="identkeyval")
600

    
601
HVLIST_OPT = cli_option("-H", "--hypervisor-parameters", dest="hvparams",
602
                        help="Hypervisor and hypervisor options, in the"
603
                        " format hypervisor:option=value,option=value,...",
604
                        default=[], action="append", type="identkeyval")
605

    
606
NOIPCHECK_OPT = cli_option("--no-ip-check", dest="ip_check", default=True,
607
                           action="store_false",
608
                           help="Don't check that the instance's IP"
609
                           " is alive")
610

    
611
NONAMECHECK_OPT = cli_option("--no-name-check", dest="name_check",
612
                             default=True, action="store_false",
613
                             help="Don't check that the instance's name"
614
                             " is resolvable")
615

    
616
NET_OPT = cli_option("--net",
617
                     help="NIC parameters", default=[],
618
                     dest="nics", action="append", type="identkeyval")
619

    
620
DISK_OPT = cli_option("--disk", help="Disk parameters", default=[],
621
                      dest="disks", action="append", type="identkeyval")
622

    
623
DISKIDX_OPT = cli_option("--disks", dest="disks", default=None,
624
                         help="Comma-separated list of disks"
625
                         " indices to act on (e.g. 0,2) (optional,"
626
                         " defaults to all disks)")
627

    
628
OS_SIZE_OPT = cli_option("-s", "--os-size", dest="sd_size",
629
                         help="Enforces a single-disk configuration using the"
630
                         " given disk size, in MiB unless a suffix is used",
631
                         default=None, type="unit", metavar="<size>")
632

    
633
IGNORE_CONSIST_OPT = cli_option("--ignore-consistency",
634
                                dest="ignore_consistency",
635
                                action="store_true", default=False,
636
                                help="Ignore the consistency of the disks on"
637
                                " the secondary")
638

    
639
NONLIVE_OPT = cli_option("--non-live", dest="live",
640
                         default=True, action="store_false",
641
                         help="Do a non-live migration (this usually means"
642
                         " freeze the instance, save the state, transfer and"
643
                         " only then resume running on the secondary node)")
644

    
645
NODE_PLACEMENT_OPT = cli_option("-n", "--node", dest="node",
646
                                help="Target node and optional secondary node",
647
                                metavar="<pnode>[:<snode>]",
648
                                completion_suggest=OPT_COMPL_INST_ADD_NODES)
649

    
650
NODE_LIST_OPT = cli_option("-n", "--node", dest="nodes", default=[],
651
                           action="append", metavar="<node>",
652
                           help="Use only this node (can be used multiple"
653
                           " times, if not given defaults to all nodes)",
654
                           completion_suggest=OPT_COMPL_ONE_NODE)
655

    
656
SINGLE_NODE_OPT = cli_option("-n", "--node", dest="node", help="Target node",
657
                             metavar="<node>",
658
                             completion_suggest=OPT_COMPL_ONE_NODE)
659

    
660
NOSTART_OPT = cli_option("--no-start", dest="start", default=True,
661
                         action="store_false",
662
                         help="Don't start the instance after creation")
663

    
664
SHOWCMD_OPT = cli_option("--show-cmd", dest="show_command",
665
                         action="store_true", default=False,
666
                         help="Show command instead of executing it")
667

    
668
CLEANUP_OPT = cli_option("--cleanup", dest="cleanup",
669
                         default=False, action="store_true",
670
                         help="Instead of performing the migration, try to"
671
                         " recover from a failed cleanup. This is safe"
672
                         " to run even if the instance is healthy, but it"
673
                         " will create extra replication traffic and "
674
                         " disrupt briefly the replication (like during the"
675
                         " migration")
676

    
677
STATIC_OPT = cli_option("-s", "--static", dest="static",
678
                        action="store_true", default=False,
679
                        help="Only show configuration data, not runtime data")
680

    
681
ALL_OPT = cli_option("--all", dest="show_all",
682
                     default=False, action="store_true",
683
                     help="Show info on all instances on the cluster."
684
                     " This can take a long time to run, use wisely")
685

    
686
SELECT_OS_OPT = cli_option("--select-os", dest="select_os",
687
                           action="store_true", default=False,
688
                           help="Interactive OS reinstall, lists available"
689
                           " OS templates for selection")
690

    
691
IGNORE_FAILURES_OPT = cli_option("--ignore-failures", dest="ignore_failures",
692
                                 action="store_true", default=False,
693
                                 help="Remove the instance from the cluster"
694
                                 " configuration even if there are failures"
695
                                 " during the removal process")
696

    
697
NEW_SECONDARY_OPT = cli_option("-n", "--new-secondary", dest="dst_node",
698
                               help="Specifies the new secondary node",
699
                               metavar="NODE", default=None,
700
                               completion_suggest=OPT_COMPL_ONE_NODE)
701

    
702
ON_PRIMARY_OPT = cli_option("-p", "--on-primary", dest="on_primary",
703
                            default=False, action="store_true",
704
                            help="Replace the disk(s) on the primary"
705
                            " node (only for the drbd template)")
706

    
707
ON_SECONDARY_OPT = cli_option("-s", "--on-secondary", dest="on_secondary",
708
                              default=False, action="store_true",
709
                              help="Replace the disk(s) on the secondary"
710
                              " node (only for the drbd template)")
711

    
712
AUTO_PROMOTE_OPT = cli_option("--auto-promote", dest="auto_promote",
713
                              default=False, action="store_true",
714
                              help="Lock all nodes and auto-promote as needed"
715
                              " to MC status")
716

    
717
AUTO_REPLACE_OPT = cli_option("-a", "--auto", dest="auto",
718
                              default=False, action="store_true",
719
                              help="Automatically replace faulty disks"
720
                              " (only for the drbd template)")
721

    
722
IGNORE_SIZE_OPT = cli_option("--ignore-size", dest="ignore_size",
723
                             default=False, action="store_true",
724
                             help="Ignore current recorded size"
725
                             " (useful for forcing activation when"
726
                             " the recorded size is wrong)")
727

    
728
SRC_NODE_OPT = cli_option("--src-node", dest="src_node", help="Source node",
729
                          metavar="<node>",
730
                          completion_suggest=OPT_COMPL_ONE_NODE)
731

    
732
SRC_DIR_OPT = cli_option("--src-dir", dest="src_dir", help="Source directory",
733
                         metavar="<dir>")
734

    
735
SECONDARY_IP_OPT = cli_option("-s", "--secondary-ip", dest="secondary_ip",
736
                              help="Specify the secondary ip for the node",
737
                              metavar="ADDRESS", default=None)
738

    
739
READD_OPT = cli_option("--readd", dest="readd",
740
                       default=False, action="store_true",
741
                       help="Readd old node after replacing it")
742

    
743
NOSSH_KEYCHECK_OPT = cli_option("--no-ssh-key-check", dest="ssh_key_check",
744
                                default=True, action="store_false",
745
                                help="Disable SSH key fingerprint checking")
746

    
747

    
748
MC_OPT = cli_option("-C", "--master-candidate", dest="master_candidate",
749
                    choices=_YESNO, default=None, metavar=_YORNO,
750
                    help="Set the master_candidate flag on the node")
751

    
752
OFFLINE_OPT = cli_option("-O", "--offline", dest="offline", metavar=_YORNO,
753
                         choices=_YESNO, default=None,
754
                         help="Set the offline flag on the node")
755

    
756
DRAINED_OPT = cli_option("-D", "--drained", dest="drained", metavar=_YORNO,
757
                         choices=_YESNO, default=None,
758
                         help="Set the drained flag on the node")
759

    
760
ALLOCATABLE_OPT = cli_option("--allocatable", dest="allocatable",
761
                             choices=_YESNO, default=None, metavar=_YORNO,
762
                             help="Set the allocatable flag on a volume")
763

    
764
NOLVM_STORAGE_OPT = cli_option("--no-lvm-storage", dest="lvm_storage",
765
                               help="Disable support for lvm based instances"
766
                               " (cluster-wide)",
767
                               action="store_false", default=True)
768

    
769
ENABLED_HV_OPT = cli_option("--enabled-hypervisors",
770
                            dest="enabled_hypervisors",
771
                            help="Comma-separated list of hypervisors",
772
                            type="string", default=None)
773

    
774
NIC_PARAMS_OPT = cli_option("-N", "--nic-parameters", dest="nicparams",
775
                            type="keyval", default={},
776
                            help="NIC parameters")
777

    
778
CP_SIZE_OPT = cli_option("-C", "--candidate-pool-size", default=None,
779
                         dest="candidate_pool_size", type="int",
780
                         help="Set the candidate pool size")
781

    
782
VG_NAME_OPT = cli_option("-g", "--vg-name", dest="vg_name",
783
                         help="Enables LVM and specifies the volume group"
784
                         " name (cluster-wide) for disk allocation [xenvg]",
785
                         metavar="VG", default=None)
786

    
787
YES_DOIT_OPT = cli_option("--yes-do-it", dest="yes_do_it",
788
                          help="Destroy cluster", action="store_true")
789

    
790
NOVOTING_OPT = cli_option("--no-voting", dest="no_voting",
791
                          help="Skip node agreement check (dangerous)",
792
                          action="store_true", default=False)
793

    
794
MAC_PREFIX_OPT = cli_option("-m", "--mac-prefix", dest="mac_prefix",
795
                            help="Specify the mac prefix for the instance IP"
796
                            " addresses, in the format XX:XX:XX",
797
                            metavar="PREFIX",
798
                            default=None)
799

    
800
MASTER_NETDEV_OPT = cli_option("--master-netdev", dest="master_netdev",
801
                               help="Specify the node interface (cluster-wide)"
802
                               " on which the master IP address will be added "
803
                               " [%s]" % constants.DEFAULT_BRIDGE,
804
                               metavar="NETDEV",
805
                               default=constants.DEFAULT_BRIDGE)
806

    
807

    
808
GLOBAL_FILEDIR_OPT = cli_option("--file-storage-dir", dest="file_storage_dir",
809
                                help="Specify the default directory (cluster-"
810
                                "wide) for storing the file-based disks [%s]" %
811
                                constants.DEFAULT_FILE_STORAGE_DIR,
812
                                metavar="DIR",
813
                                default=constants.DEFAULT_FILE_STORAGE_DIR)
814

    
815
NOMODIFY_ETCHOSTS_OPT = cli_option("--no-etc-hosts", dest="modify_etc_hosts",
816
                                   help="Don't modify /etc/hosts",
817
                                   action="store_false", default=True)
818

    
819
NOMODIFY_SSH_SETUP_OPT = cli_option("--no-ssh-init", dest="modify_ssh_setup",
820
                                    help="Don't initialize SSH keys",
821
                                    action="store_false", default=True)
822

    
823
ERROR_CODES_OPT = cli_option("--error-codes", dest="error_codes",
824
                             help="Enable parseable error messages",
825
                             action="store_true", default=False)
826

    
827
NONPLUS1_OPT = cli_option("--no-nplus1-mem", dest="skip_nplusone_mem",
828
                          help="Skip N+1 memory redundancy tests",
829
                          action="store_true", default=False)
830

    
831
REBOOT_TYPE_OPT = cli_option("-t", "--type", dest="reboot_type",
832
                             help="Type of reboot: soft/hard/full",
833
                             default=constants.INSTANCE_REBOOT_HARD,
834
                             metavar="<REBOOT>",
835
                             choices=list(constants.REBOOT_TYPES))
836

    
837
IGNORE_SECONDARIES_OPT = cli_option("--ignore-secondaries",
838
                                    dest="ignore_secondaries",
839
                                    default=False, action="store_true",
840
                                    help="Ignore errors from secondaries")
841

    
842
NOSHUTDOWN_OPT = cli_option("--noshutdown", dest="shutdown",
843
                            action="store_false", default=True,
844
                            help="Don't shutdown the instance (unsafe)")
845

    
846
TIMEOUT_OPT = cli_option("--timeout", dest="timeout", type="int",
847
                         default=constants.DEFAULT_SHUTDOWN_TIMEOUT,
848
                         help="Maximum time to wait")
849

    
850
SHUTDOWN_TIMEOUT_OPT = cli_option("--shutdown-timeout",
851
                         dest="shutdown_timeout", type="int",
852
                         default=constants.DEFAULT_SHUTDOWN_TIMEOUT,
853
                         help="Maximum time to wait for instance shutdown")
854

    
855
EARLY_RELEASE_OPT = cli_option("--early-release",
856
                               dest="early_release", default=False,
857
                               action="store_true",
858
                               help="Release the locks on the secondary"
859
                               " node(s) early")
860

    
861

    
862
def _ParseArgs(argv, commands, aliases):
863
  """Parser for the command line arguments.
864

865
  This function parses the arguments and returns the function which
866
  must be executed together with its (modified) arguments.
867

868
  @param argv: the command line
869
  @param commands: dictionary with special contents, see the design
870
      doc for cmdline handling
871
  @param aliases: dictionary with command aliases {'alias': 'target, ...}
872

873
  """
874
  if len(argv) == 0:
875
    binary = "<command>"
876
  else:
877
    binary = argv[0].split("/")[-1]
878

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

    
885
  if len(argv) < 2 or not (argv[1] in commands or
886
                           argv[1] in aliases):
887
    # let's do a nice thing
888
    sortedcmds = commands.keys()
889
    sortedcmds.sort()
890

    
891
    ToStdout("Usage: %s {command} [options...] [argument...]", binary)
892
    ToStdout("%s <command> --help to see details, or man %s", binary, binary)
893
    ToStdout("")
894

    
895
    # compute the max line length for cmd + usage
896
    mlen = max([len(" %s" % cmd) for cmd in commands])
897
    mlen = min(60, mlen) # should not get here...
898

    
899
    # and format a nice command list
900
    ToStdout("Commands:")
901
    for cmd in sortedcmds:
902
      cmdstr = " %s" % (cmd,)
903
      help_text = commands[cmd][4]
904
      help_lines = textwrap.wrap(help_text, 79 - 3 - mlen)
905
      ToStdout("%-*s - %s", mlen, cmdstr, help_lines.pop(0))
906
      for line in help_lines:
907
        ToStdout("%-*s   %s", mlen, "", line)
908

    
909
    ToStdout("")
910

    
911
    return None, None, None
912

    
913
  # get command, unalias it, and look it up in commands
914
  cmd = argv.pop(1)
915
  if cmd in aliases:
916
    if cmd in commands:
917
      raise errors.ProgrammerError("Alias '%s' overrides an existing"
918
                                   " command" % cmd)
919

    
920
    if aliases[cmd] not in commands:
921
      raise errors.ProgrammerError("Alias '%s' maps to non-existing"
922
                                   " command '%s'" % (cmd, aliases[cmd]))
923

    
924
    cmd = aliases[cmd]
925

    
926
  func, args_def, parser_opts, usage, description = commands[cmd]
927
  parser = OptionParser(option_list=parser_opts + [_DRY_RUN_OPT, DEBUG_OPT],
928
                        description=description,
929
                        formatter=TitledHelpFormatter(),
930
                        usage="%%prog %s %s" % (cmd, usage))
931
  parser.disable_interspersed_args()
932
  options, args = parser.parse_args()
933

    
934
  if not _CheckArguments(cmd, args_def, args):
935
    return None, None, None
936

    
937
  return func, options, args
938

    
939

    
940
def _CheckArguments(cmd, args_def, args):
941
  """Verifies the arguments using the argument definition.
942

943
  Algorithm:
944

945
    1. Abort with error if values specified by user but none expected.
946

947
    1. For each argument in definition
948

949
      1. Keep running count of minimum number of values (min_count)
950
      1. Keep running count of maximum number of values (max_count)
951
      1. If it has an unlimited number of values
952

953
        1. Abort with error if it's not the last argument in the definition
954

955
    1. If last argument has limited number of values
956

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

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

961
  """
962
  if args and not args_def:
963
    ToStderr("Error: Command %s expects no arguments", cmd)
964
    return False
965

    
966
  min_count = None
967
  max_count = None
968
  check_max = None
969

    
970
  last_idx = len(args_def) - 1
971

    
972
  for idx, arg in enumerate(args_def):
973
    if min_count is None:
974
      min_count = arg.min
975
    elif arg.min is not None:
976
      min_count += arg.min
977

    
978
    if max_count is None:
979
      max_count = arg.max
980
    elif arg.max is not None:
981
      max_count += arg.max
982

    
983
    if idx == last_idx:
984
      check_max = (arg.max is not None)
985

    
986
    elif arg.max is None:
987
      raise errors.ProgrammerError("Only the last argument can have max=None")
988

    
989
  if check_max:
990
    # Command with exact number of arguments
991
    if (min_count is not None and max_count is not None and
992
        min_count == max_count and len(args) != min_count):
993
      ToStderr("Error: Command %s expects %d argument(s)", cmd, min_count)
994
      return False
995

    
996
    # Command with limited number of arguments
997
    if max_count is not None and len(args) > max_count:
998
      ToStderr("Error: Command %s expects only %d argument(s)",
999
               cmd, max_count)
1000
      return False
1001

    
1002
  # Command with some required arguments
1003
  if min_count is not None and len(args) < min_count:
1004
    ToStderr("Error: Command %s expects at least %d argument(s)",
1005
             cmd, min_count)
1006
    return False
1007

    
1008
  return True
1009

    
1010

    
1011
def SplitNodeOption(value):
1012
  """Splits the value of a --node option.
1013

1014
  """
1015
  if value and ':' in value:
1016
    return value.split(':', 1)
1017
  else:
1018
    return (value, None)
1019

    
1020

    
1021
def CalculateOSNames(os_name, os_variants):
1022
  """Calculates all the names an OS can be called, according to its variants.
1023

1024
  @type os_name: string
1025
  @param os_name: base name of the os
1026
  @type os_variants: list or None
1027
  @param os_variants: list of supported variants
1028
  @rtype: list
1029
  @return: list of valid names
1030

1031
  """
1032
  if os_variants:
1033
    return ['%s+%s' % (os_name, v) for v in os_variants]
1034
  else:
1035
    return [os_name]
1036

    
1037

    
1038
def UsesRPC(fn):
1039
  def wrapper(*args, **kwargs):
1040
    rpc.Init()
1041
    try:
1042
      return fn(*args, **kwargs)
1043
    finally:
1044
      rpc.Shutdown()
1045
  return wrapper
1046

    
1047

    
1048
def AskUser(text, choices=None):
1049
  """Ask the user a question.
1050

1051
  @param text: the question to ask
1052

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

1058
  @return: one of the return values from the choices list; if input is
1059
      not possible (i.e. not running with a tty, we return the last
1060
      entry from the list
1061

1062
  """
1063
  if choices is None:
1064
    choices = [('y', True, 'Perform the operation'),
1065
               ('n', False, 'Do not perform the operation')]
1066
  if not choices or not isinstance(choices, list):
1067
    raise errors.ProgrammerError("Invalid choices argument to AskUser")
1068
  for entry in choices:
1069
    if not isinstance(entry, tuple) or len(entry) < 3 or entry[0] == '?':
1070
      raise errors.ProgrammerError("Invalid choices element to AskUser")
1071

    
1072
  answer = choices[-1][1]
1073
  new_text = []
1074
  for line in text.splitlines():
1075
    new_text.append(textwrap.fill(line, 70, replace_whitespace=False))
1076
  text = "\n".join(new_text)
1077
  try:
1078
    f = file("/dev/tty", "a+")
1079
  except IOError:
1080
    return answer
1081
  try:
1082
    chars = [entry[0] for entry in choices]
1083
    chars[-1] = "[%s]" % chars[-1]
1084
    chars.append('?')
1085
    maps = dict([(entry[0], entry[1]) for entry in choices])
1086
    while True:
1087
      f.write(text)
1088
      f.write('\n')
1089
      f.write("/".join(chars))
1090
      f.write(": ")
1091
      line = f.readline(2).strip().lower()
1092
      if line in maps:
1093
        answer = maps[line]
1094
        break
1095
      elif line == '?':
1096
        for entry in choices:
1097
          f.write(" %s - %s\n" % (entry[0], entry[2]))
1098
        f.write("\n")
1099
        continue
1100
  finally:
1101
    f.close()
1102
  return answer
1103

    
1104

    
1105
class JobSubmittedException(Exception):
1106
  """Job was submitted, client should exit.
1107

1108
  This exception has one argument, the ID of the job that was
1109
  submitted. The handler should print this ID.
1110

1111
  This is not an error, just a structured way to exit from clients.
1112

1113
  """
1114

    
1115

    
1116
def SendJob(ops, cl=None):
1117
  """Function to submit an opcode without waiting for the results.
1118

1119
  @type ops: list
1120
  @param ops: list of opcodes
1121
  @type cl: luxi.Client
1122
  @param cl: the luxi client to use for communicating with the master;
1123
             if None, a new client will be created
1124

1125
  """
1126
  if cl is None:
1127
    cl = GetClient()
1128

    
1129
  job_id = cl.SubmitJob(ops)
1130

    
1131
  return job_id
1132

    
1133

    
1134
def PollJob(job_id, cl=None, feedback_fn=None):
1135
  """Function to poll for the result of a job.
1136

1137
  @type job_id: job identified
1138
  @param job_id: the job to poll for results
1139
  @type cl: luxi.Client
1140
  @param cl: the luxi client to use for communicating with the master;
1141
             if None, a new client will be created
1142

1143
  """
1144
  if cl is None:
1145
    cl = GetClient()
1146

    
1147
  prev_job_info = None
1148
  prev_logmsg_serial = None
1149

    
1150
  status = None
1151

    
1152
  notified_queued = False
1153
  notified_waitlock = False
1154

    
1155
  while True:
1156
    result = cl.WaitForJobChangeOnce(job_id, ["status"], prev_job_info,
1157
                                     prev_logmsg_serial)
1158
    if not result:
1159
      # job not found, go away!
1160
      raise errors.JobLost("Job with id %s lost" % job_id)
1161
    elif result == constants.JOB_NOTCHANGED:
1162
      if status is not None and not callable(feedback_fn):
1163
        if status == constants.JOB_STATUS_QUEUED and not notified_queued:
1164
          ToStderr("Job %s is waiting in queue", job_id)
1165
          notified_queued = True
1166
        elif status == constants.JOB_STATUS_WAITLOCK and not notified_waitlock:
1167
          ToStderr("Job %s is trying to acquire all necessary locks", job_id)
1168
          notified_waitlock = True
1169

    
1170
      # Wait again
1171
      continue
1172

    
1173
    # Split result, a tuple of (field values, log entries)
1174
    (job_info, log_entries) = result
1175
    (status, ) = job_info
1176

    
1177
    if log_entries:
1178
      for log_entry in log_entries:
1179
        (serial, timestamp, _, message) = log_entry
1180
        if callable(feedback_fn):
1181
          feedback_fn(log_entry[1:])
1182
        else:
1183
          encoded = utils.SafeEncode(message)
1184
          ToStdout("%s %s", time.ctime(utils.MergeTime(timestamp)), encoded)
1185
        prev_logmsg_serial = max(prev_logmsg_serial, serial)
1186

    
1187
    # TODO: Handle canceled and archived jobs
1188
    elif status in (constants.JOB_STATUS_SUCCESS,
1189
                    constants.JOB_STATUS_ERROR,
1190
                    constants.JOB_STATUS_CANCELING,
1191
                    constants.JOB_STATUS_CANCELED):
1192
      break
1193

    
1194
    prev_job_info = job_info
1195

    
1196
  jobs = cl.QueryJobs([job_id], ["status", "opstatus", "opresult"])
1197
  if not jobs:
1198
    raise errors.JobLost("Job with id %s lost" % job_id)
1199

    
1200
  status, opstatus, result = jobs[0]
1201
  if status == constants.JOB_STATUS_SUCCESS:
1202
    return result
1203
  elif status in (constants.JOB_STATUS_CANCELING,
1204
                  constants.JOB_STATUS_CANCELED):
1205
    raise errors.OpExecError("Job was canceled")
1206
  else:
1207
    has_ok = False
1208
    for idx, (status, msg) in enumerate(zip(opstatus, result)):
1209
      if status == constants.OP_STATUS_SUCCESS:
1210
        has_ok = True
1211
      elif status == constants.OP_STATUS_ERROR:
1212
        errors.MaybeRaise(msg)
1213
        if has_ok:
1214
          raise errors.OpExecError("partial failure (opcode %d): %s" %
1215
                                   (idx, msg))
1216
        else:
1217
          raise errors.OpExecError(str(msg))
1218
    # default failure mode
1219
    raise errors.OpExecError(result)
1220

    
1221

    
1222
def SubmitOpCode(op, cl=None, feedback_fn=None, opts=None):
1223
  """Legacy function to submit an opcode.
1224

1225
  This is just a simple wrapper over the construction of the processor
1226
  instance. It should be extended to better handle feedback and
1227
  interaction functions.
1228

1229
  """
1230
  if cl is None:
1231
    cl = GetClient()
1232

    
1233
  SetGenericOpcodeOpts([op], opts)
1234

    
1235
  job_id = SendJob([op], cl)
1236

    
1237
  op_results = PollJob(job_id, cl=cl, feedback_fn=feedback_fn)
1238

    
1239
  return op_results[0]
1240

    
1241

    
1242
def SubmitOrSend(op, opts, cl=None, feedback_fn=None):
1243
  """Wrapper around SubmitOpCode or SendJob.
1244

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

1250
  It will also process the opcodes if we're sending the via SendJob
1251
  (otherwise SubmitOpCode does it).
1252

1253
  """
1254
  if opts and opts.submit_only:
1255
    job = [op]
1256
    SetGenericOpcodeOpts(job, opts)
1257
    job_id = SendJob(job, cl=cl)
1258
    raise JobSubmittedException(job_id)
1259
  else:
1260
    return SubmitOpCode(op, cl=cl, feedback_fn=feedback_fn, opts=opts)
1261

    
1262

    
1263
def SetGenericOpcodeOpts(opcode_list, options):
1264
  """Processor for generic options.
1265

1266
  This function updates the given opcodes based on generic command
1267
  line options (like debug, dry-run, etc.).
1268

1269
  @param opcode_list: list of opcodes
1270
  @param options: command line options or None
1271
  @return: None (in-place modification)
1272

1273
  """
1274
  if not options:
1275
    return
1276
  for op in opcode_list:
1277
    op.dry_run = options.dry_run
1278
    op.debug_level = options.debug
1279

    
1280

    
1281
def GetClient():
1282
  # TODO: Cache object?
1283
  try:
1284
    client = luxi.Client()
1285
  except luxi.NoMasterError:
1286
    ss = ssconf.SimpleStore()
1287

    
1288
    # Try to read ssconf file
1289
    try:
1290
      ss.GetMasterNode()
1291
    except errors.ConfigurationError:
1292
      raise errors.OpPrereqError("Cluster not initialized or this machine is"
1293
                                 " not part of a cluster")
1294

    
1295
    master, myself = ssconf.GetMasterAndMyself(ss=ss)
1296
    if master != myself:
1297
      raise errors.OpPrereqError("This is not the master node, please connect"
1298
                                 " to node '%s' and rerun the command" %
1299
                                 master)
1300
    raise
1301
  return client
1302

    
1303

    
1304
def FormatError(err):
1305
  """Return a formatted error message for a given error.
1306

1307
  This function takes an exception instance and returns a tuple
1308
  consisting of two values: first, the recommended exit code, and
1309
  second, a string describing the error message (not
1310
  newline-terminated).
1311

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

    
1380

    
1381
def GenericMain(commands, override=None, aliases=None):
1382
  """Generic main function for all the gnt-* commands.
1383

1384
  Arguments:
1385
    - commands: a dictionary with a special structure, see the design doc
1386
                for command line handling.
1387
    - override: if not None, we expect a dictionary with keys that will
1388
                override command line options; this can be used to pass
1389
                options from the scripts to generic functions
1390
    - aliases: dictionary with command aliases {'alias': 'target, ...}
1391

1392
  """
1393
  # save the program name and the entire command line for later logging
1394
  if sys.argv:
1395
    binary = os.path.basename(sys.argv[0]) or sys.argv[0]
1396
    if len(sys.argv) >= 2:
1397
      binary += " " + sys.argv[1]
1398
      old_cmdline = " ".join(sys.argv[2:])
1399
    else:
1400
      old_cmdline = ""
1401
  else:
1402
    binary = "<unknown program>"
1403
    old_cmdline = ""
1404

    
1405
  if aliases is None:
1406
    aliases = {}
1407

    
1408
  try:
1409
    func, options, args = _ParseArgs(sys.argv, commands, aliases)
1410
  except errors.ParameterError, err:
1411
    result, err_msg = FormatError(err)
1412
    ToStderr(err_msg)
1413
    return 1
1414

    
1415
  if func is None: # parse error
1416
    return 1
1417

    
1418
  if override is not None:
1419
    for key, val in override.iteritems():
1420
      setattr(options, key, val)
1421

    
1422
  utils.SetupLogging(constants.LOG_COMMANDS, debug=options.debug,
1423
                     stderr_logging=True, program=binary)
1424

    
1425
  if old_cmdline:
1426
    logging.info("run with arguments '%s'", old_cmdline)
1427
  else:
1428
    logging.info("run with no arguments")
1429

    
1430
  try:
1431
    result = func(options, args)
1432
  except (errors.GenericError, luxi.ProtocolError,
1433
          JobSubmittedException), err:
1434
    result, err_msg = FormatError(err)
1435
    logging.exception("Error during command processing")
1436
    ToStderr(err_msg)
1437

    
1438
  return result
1439

    
1440

    
1441
def GenericInstanceCreate(mode, opts, args):
1442
  """Add an instance to the cluster via either creation or import.
1443

1444
  @param mode: constants.INSTANCE_CREATE or constants.INSTANCE_IMPORT
1445
  @param opts: the command line options selected by the user
1446
  @type args: list
1447
  @param args: should contain only one element, the new instance name
1448
  @rtype: int
1449
  @return: the desired exit code
1450

1451
  """
1452
  instance = args[0]
1453

    
1454
  (pnode, snode) = SplitNodeOption(opts.node)
1455

    
1456
  hypervisor = None
1457
  hvparams = {}
1458
  if opts.hypervisor:
1459
    hypervisor, hvparams = opts.hypervisor
1460

    
1461
  if opts.nics:
1462
    try:
1463
      nic_max = max(int(nidx[0]) + 1 for nidx in opts.nics)
1464
    except ValueError, err:
1465
      raise errors.OpPrereqError("Invalid NIC index passed: %s" % str(err))
1466
    nics = [{}] * nic_max
1467
    for nidx, ndict in opts.nics:
1468
      nidx = int(nidx)
1469
      if not isinstance(ndict, dict):
1470
        msg = "Invalid nic/%d value: expected dict, got %s" % (nidx, ndict)
1471
        raise errors.OpPrereqError(msg)
1472
      nics[nidx] = ndict
1473
  elif opts.no_nics:
1474
    # no nics
1475
    nics = []
1476
  else:
1477
    # default of one nic, all auto
1478
    nics = [{}]
1479

    
1480
  if opts.disk_template == constants.DT_DISKLESS:
1481
    if opts.disks or opts.sd_size is not None:
1482
      raise errors.OpPrereqError("Diskless instance but disk"
1483
                                 " information passed")
1484
    disks = []
1485
  else:
1486
    if not opts.disks and not opts.sd_size:
1487
      raise errors.OpPrereqError("No disk information specified")
1488
    if opts.disks and opts.sd_size is not None:
1489
      raise errors.OpPrereqError("Please use either the '--disk' or"
1490
                                 " '-s' option")
1491
    if opts.sd_size is not None:
1492
      opts.disks = [(0, {"size": opts.sd_size})]
1493
    try:
1494
      disk_max = max(int(didx[0]) + 1 for didx in opts.disks)
1495
    except ValueError, err:
1496
      raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err))
1497
    disks = [{}] * disk_max
1498
    for didx, ddict in opts.disks:
1499
      didx = int(didx)
1500
      if not isinstance(ddict, dict):
1501
        msg = "Invalid disk/%d value: expected dict, got %s" % (didx, ddict)
1502
        raise errors.OpPrereqError(msg)
1503
      elif "size" not in ddict:
1504
        raise errors.OpPrereqError("Missing size for disk %d" % didx)
1505
      try:
1506
        ddict["size"] = utils.ParseUnit(ddict["size"])
1507
      except ValueError, err:
1508
        raise errors.OpPrereqError("Invalid disk size for disk %d: %s" %
1509
                                   (didx, err))
1510
      disks[didx] = ddict
1511

    
1512
  utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_TYPES)
1513
  utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
1514

    
1515
  if mode == constants.INSTANCE_CREATE:
1516
    start = opts.start
1517
    os_type = opts.os
1518
    src_node = None
1519
    src_path = None
1520
  elif mode == constants.INSTANCE_IMPORT:
1521
    start = False
1522
    os_type = None
1523
    src_node = opts.src_node
1524
    src_path = opts.src_dir
1525
  else:
1526
    raise errors.ProgrammerError("Invalid creation mode %s" % mode)
1527

    
1528
  op = opcodes.OpCreateInstance(instance_name=instance,
1529
                                disks=disks,
1530
                                disk_template=opts.disk_template,
1531
                                nics=nics,
1532
                                pnode=pnode, snode=snode,
1533
                                ip_check=opts.ip_check,
1534
                                name_check=opts.name_check,
1535
                                wait_for_sync=opts.wait_for_sync,
1536
                                file_storage_dir=opts.file_storage_dir,
1537
                                file_driver=opts.file_driver,
1538
                                iallocator=opts.iallocator,
1539
                                hypervisor=hypervisor,
1540
                                hvparams=hvparams,
1541
                                beparams=opts.beparams,
1542
                                mode=mode,
1543
                                start=start,
1544
                                os_type=os_type,
1545
                                src_node=src_node,
1546
                                src_path=src_path)
1547

    
1548
  SubmitOrSend(op, opts)
1549
  return 0
1550

    
1551

    
1552
def GenerateTable(headers, fields, separator, data,
1553
                  numfields=None, unitfields=None,
1554
                  units=None):
1555
  """Prints a table with headers and different fields.
1556

1557
  @type headers: dict
1558
  @param headers: dictionary mapping field names to headers for
1559
      the table
1560
  @type fields: list
1561
  @param fields: the field names corresponding to each row in
1562
      the data field
1563
  @param separator: the separator to be used; if this is None,
1564
      the default 'smart' algorithm is used which computes optimal
1565
      field width, otherwise just the separator is used between
1566
      each field
1567
  @type data: list
1568
  @param data: a list of lists, each sublist being one row to be output
1569
  @type numfields: list
1570
  @param numfields: a list with the fields that hold numeric
1571
      values and thus should be right-aligned
1572
  @type unitfields: list
1573
  @param unitfields: a list with the fields that hold numeric
1574
      values that should be formatted with the units field
1575
  @type units: string or None
1576
  @param units: the units we should use for formatting, or None for
1577
      automatic choice (human-readable for non-separator usage, otherwise
1578
      megabytes); this is a one-letter string
1579

1580
  """
1581
  if units is None:
1582
    if separator:
1583
      units = "m"
1584
    else:
1585
      units = "h"
1586

    
1587
  if numfields is None:
1588
    numfields = []
1589
  if unitfields is None:
1590
    unitfields = []
1591

    
1592
  numfields = utils.FieldSet(*numfields)   # pylint: disable-msg=W0142
1593
  unitfields = utils.FieldSet(*unitfields) # pylint: disable-msg=W0142
1594

    
1595
  format_fields = []
1596
  for field in fields:
1597
    if headers and field not in headers:
1598
      # TODO: handle better unknown fields (either revert to old
1599
      # style of raising exception, or deal more intelligently with
1600
      # variable fields)
1601
      headers[field] = field
1602
    if separator is not None:
1603
      format_fields.append("%s")
1604
    elif numfields.Matches(field):
1605
      format_fields.append("%*s")
1606
    else:
1607
      format_fields.append("%-*s")
1608

    
1609
  if separator is None:
1610
    mlens = [0 for name in fields]
1611
    format = ' '.join(format_fields)
1612
  else:
1613
    format = separator.replace("%", "%%").join(format_fields)
1614

    
1615
  for row in data:
1616
    if row is None:
1617
      continue
1618
    for idx, val in enumerate(row):
1619
      if unitfields.Matches(fields[idx]):
1620
        try:
1621
          val = int(val)
1622
        except (TypeError, ValueError):
1623
          pass
1624
        else:
1625
          val = row[idx] = utils.FormatUnit(val, units)
1626
      val = row[idx] = str(val)
1627
      if separator is None:
1628
        mlens[idx] = max(mlens[idx], len(val))
1629

    
1630
  result = []
1631
  if headers:
1632
    args = []
1633
    for idx, name in enumerate(fields):
1634
      hdr = headers[name]
1635
      if separator is None:
1636
        mlens[idx] = max(mlens[idx], len(hdr))
1637
        args.append(mlens[idx])
1638
      args.append(hdr)
1639
    result.append(format % tuple(args))
1640

    
1641
  if separator is None:
1642
    assert len(mlens) == len(fields)
1643

    
1644
    if fields and not numfields.Matches(fields[-1]):
1645
      mlens[-1] = 0
1646

    
1647
  for line in data:
1648
    args = []
1649
    if line is None:
1650
      line = ['-' for _ in fields]
1651
    for idx in range(len(fields)):
1652
      if separator is None:
1653
        args.append(mlens[idx])
1654
      args.append(line[idx])
1655
    result.append(format % tuple(args))
1656

    
1657
  return result
1658

    
1659

    
1660
def FormatTimestamp(ts):
1661
  """Formats a given timestamp.
1662

1663
  @type ts: timestamp
1664
  @param ts: a timeval-type timestamp, a tuple of seconds and microseconds
1665

1666
  @rtype: string
1667
  @return: a string with the formatted timestamp
1668

1669
  """
1670
  if not isinstance (ts, (tuple, list)) or len(ts) != 2:
1671
    return '?'
1672
  sec, usec = ts
1673
  return time.strftime("%F %T", time.localtime(sec)) + ".%06d" % usec
1674

    
1675

    
1676
def ParseTimespec(value):
1677
  """Parse a time specification.
1678

1679
  The following suffixed will be recognized:
1680

1681
    - s: seconds
1682
    - m: minutes
1683
    - h: hours
1684
    - d: day
1685
    - w: weeks
1686

1687
  Without any suffix, the value will be taken to be in seconds.
1688

1689
  """
1690
  value = str(value)
1691
  if not value:
1692
    raise errors.OpPrereqError("Empty time specification passed")
1693
  suffix_map = {
1694
    's': 1,
1695
    'm': 60,
1696
    'h': 3600,
1697
    'd': 86400,
1698
    'w': 604800,
1699
    }
1700
  if value[-1] not in suffix_map:
1701
    try:
1702
      value = int(value)
1703
    except (TypeError, ValueError):
1704
      raise errors.OpPrereqError("Invalid time specification '%s'" % value)
1705
  else:
1706
    multiplier = suffix_map[value[-1]]
1707
    value = value[:-1]
1708
    if not value: # no data left after stripping the suffix
1709
      raise errors.OpPrereqError("Invalid time specification (only"
1710
                                 " suffix passed)")
1711
    try:
1712
      value = int(value) * multiplier
1713
    except (TypeError, ValueError):
1714
      raise errors.OpPrereqError("Invalid time specification '%s'" % value)
1715
  return value
1716

    
1717

    
1718
def GetOnlineNodes(nodes, cl=None, nowarn=False):
1719
  """Returns the names of online nodes.
1720

1721
  This function will also log a warning on stderr with the names of
1722
  the online nodes.
1723

1724
  @param nodes: if not empty, use only this subset of nodes (minus the
1725
      offline ones)
1726
  @param cl: if not None, luxi client to use
1727
  @type nowarn: boolean
1728
  @param nowarn: by default, this function will output a note with the
1729
      offline nodes that are skipped; if this parameter is True the
1730
      note is not displayed
1731

1732
  """
1733
  if cl is None:
1734
    cl = GetClient()
1735

    
1736
  result = cl.QueryNodes(names=nodes, fields=["name", "offline"],
1737
                         use_locking=False)
1738
  offline = [row[0] for row in result if row[1]]
1739
  if offline and not nowarn:
1740
    ToStderr("Note: skipping offline node(s): %s" % utils.CommaJoin(offline))
1741
  return [row[0] for row in result if not row[1]]
1742

    
1743

    
1744
def _ToStream(stream, txt, *args):
1745
  """Write a message to a stream, bypassing the logging system
1746

1747
  @type stream: file object
1748
  @param stream: the file to which we should write
1749
  @type txt: str
1750
  @param txt: the message
1751

1752
  """
1753
  if args:
1754
    args = tuple(args)
1755
    stream.write(txt % args)
1756
  else:
1757
    stream.write(txt)
1758
  stream.write('\n')
1759
  stream.flush()
1760

    
1761

    
1762
def ToStdout(txt, *args):
1763
  """Write a message to stdout only, bypassing the logging system
1764

1765
  This is just a wrapper over _ToStream.
1766

1767
  @type txt: str
1768
  @param txt: the message
1769

1770
  """
1771
  _ToStream(sys.stdout, txt, *args)
1772

    
1773

    
1774
def ToStderr(txt, *args):
1775
  """Write a message to stderr only, bypassing the logging system
1776

1777
  This is just a wrapper over _ToStream.
1778

1779
  @type txt: str
1780
  @param txt: the message
1781

1782
  """
1783
  _ToStream(sys.stderr, txt, *args)
1784

    
1785

    
1786
class JobExecutor(object):
1787
  """Class which manages the submission and execution of multiple jobs.
1788

1789
  Note that instances of this class should not be reused between
1790
  GetResults() calls.
1791

1792
  """
1793
  def __init__(self, cl=None, verbose=True, opts=None, feedback_fn=None):
1794
    self.queue = []
1795
    if cl is None:
1796
      cl = GetClient()
1797
    self.cl = cl
1798
    self.verbose = verbose
1799
    self.jobs = []
1800
    self.opts = opts
1801
    self.feedback_fn = feedback_fn
1802

    
1803
  def QueueJob(self, name, *ops):
1804
    """Record a job for later submit.
1805

1806
    @type name: string
1807
    @param name: a description of the job, will be used in WaitJobSet
1808
    """
1809
    SetGenericOpcodeOpts(ops, self.opts)
1810
    self.queue.append((name, ops))
1811

    
1812
  def SubmitPending(self):
1813
    """Submit all pending jobs.
1814

1815
    """
1816
    results = self.cl.SubmitManyJobs([row[1] for row in self.queue])
1817
    for (idx, ((status, data), (name, _))) in enumerate(zip(results,
1818
                                                            self.queue)):
1819
      self.jobs.append((idx, status, data, name))
1820

    
1821
  def _ChooseJob(self):
1822
    """Choose a non-waiting/queued job to poll next.
1823

1824
    """
1825
    assert self.jobs, "_ChooseJob called with empty job list"
1826

    
1827
    result = self.cl.QueryJobs([i[2] for i in self.jobs], ["status"])
1828
    assert result
1829

    
1830
    for job_data, status in zip(self.jobs, result):
1831
      if status[0] in (constants.JOB_STATUS_QUEUED,
1832
                    constants.JOB_STATUS_WAITLOCK,
1833
                    constants.JOB_STATUS_CANCELING):
1834
        # job is still waiting
1835
        continue
1836
      # good candidate found
1837
      self.jobs.remove(job_data)
1838
      return job_data
1839

    
1840
    # no job found
1841
    return self.jobs.pop(0)
1842

    
1843
  def GetResults(self):
1844
    """Wait for and return the results of all jobs.
1845

1846
    @rtype: list
1847
    @return: list of tuples (success, job results), in the same order
1848
        as the submitted jobs; if a job has failed, instead of the result
1849
        there will be the error message
1850

1851
    """
1852
    if not self.jobs:
1853
      self.SubmitPending()
1854
    results = []
1855
    if self.verbose:
1856
      ok_jobs = [row[2] for row in self.jobs if row[1]]
1857
      if ok_jobs:
1858
        ToStdout("Submitted jobs %s", utils.CommaJoin(ok_jobs))
1859

    
1860
    # first, remove any non-submitted jobs
1861
    self.jobs, failures = utils.partition(self.jobs, lambda x: x[1])
1862
    for idx, _, jid, name in failures:
1863
        ToStderr("Failed to submit job for %s: %s", name, jid)
1864
        results.append((idx, False, jid))
1865

    
1866
    while self.jobs:
1867
      (idx, _, jid, name) = self._ChooseJob()
1868
      ToStdout("Waiting for job %s for %s...", jid, name)
1869
      try:
1870
        job_result = PollJob(jid, cl=self.cl, feedback_fn=self.feedback_fn)
1871
        success = True
1872
      except (errors.GenericError, luxi.ProtocolError), err:
1873
        _, job_result = FormatError(err)
1874
        success = False
1875
        # the error message will always be shown, verbose or not
1876
        ToStderr("Job %s for %s has failed: %s", jid, name, job_result)
1877

    
1878
      results.append((idx, success, job_result))
1879

    
1880
    # sort based on the index, then drop it
1881
    results.sort()
1882
    results = [i[1:] for i in results]
1883

    
1884
    return results
1885

    
1886
  def WaitOrShow(self, wait):
1887
    """Wait for job results or only print the job IDs.
1888

1889
    @type wait: boolean
1890
    @param wait: whether to wait or not
1891

1892
    """
1893
    if wait:
1894
      return self.GetResults()
1895
    else:
1896
      if not self.jobs:
1897
        self.SubmitPending()
1898
      for status, result, name in self.jobs:
1899
        if status:
1900
          ToStdout("%s: %s", result, name)
1901
        else:
1902
          ToStderr("Failure for %s: %s", name, result)