Statistics
| Branch: | Tag: | Revision:

root / lib / cli.py @ f9faf9c3

History | View | Annotate | Download (59.6 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
  "ARGS_ONE_OS",
150
  "ArgChoice",
151
  "ArgCommand",
152
  "ArgFile",
153
  "ArgHost",
154
  "ArgInstance",
155
  "ArgJobId",
156
  "ArgNode",
157
  "ArgOs",
158
  "ArgSuggest",
159
  "ArgUnknown",
160
  "OPT_COMPL_INST_ADD_NODES",
161
  "OPT_COMPL_MANY_NODES",
162
  "OPT_COMPL_ONE_IALLOCATOR",
163
  "OPT_COMPL_ONE_INSTANCE",
164
  "OPT_COMPL_ONE_NODE",
165
  "OPT_COMPL_ONE_OS",
166
  "cli_option",
167
  "SplitNodeOption",
168
  "CalculateOSNames",
169
  ]
170

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

    
174

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

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

    
184

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

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

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

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

    
200

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

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

207
  """
208

    
209

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

213
  """
214

    
215

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

219
  """
220

    
221

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

225
  """
226

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

230
  """
231

    
232

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

236
  """
237

    
238

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

242
  """
243

    
244

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

248
  """
249

    
250

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

254
  """
255

    
256

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

    
264

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

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

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

    
285

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

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

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

    
314

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

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

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

    
332

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

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

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

    
349

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

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

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

    
366

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

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

    
376

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

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

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

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

    
413

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

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

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

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

    
441

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

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

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

    
450

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

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

    
469

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

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

    
487

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

    
491

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
711
AUTO_REPLACE_OPT = cli_option("-a", "--auto", dest="auto",
712
                              default=False, action="store_true",
713
                              help="Automatically replace faulty disks"
714
                              " (only for the drbd template)")
715

    
716
IGNORE_SIZE_OPT = cli_option("--ignore-size", dest="ignore_size",
717
                             default=False, action="store_true",
718
                             help="Ignore current recorded size"
719
                             " (useful for forcing activation when"
720
                             " the recorded size is wrong)")
721

    
722
SRC_NODE_OPT = cli_option("--src-node", dest="src_node", help="Source node",
723
                          metavar="<node>",
724
                          completion_suggest=OPT_COMPL_ONE_NODE)
725

    
726
SRC_DIR_OPT = cli_option("--src-dir", dest="src_dir", help="Source directory",
727
                         metavar="<dir>")
728

    
729
SECONDARY_IP_OPT = cli_option("-s", "--secondary-ip", dest="secondary_ip",
730
                              help="Specify the secondary ip for the node",
731
                              metavar="ADDRESS", default=None)
732

    
733
READD_OPT = cli_option("--readd", dest="readd",
734
                       default=False, action="store_true",
735
                       help="Readd old node after replacing it")
736

    
737
NOSSH_KEYCHECK_OPT = cli_option("--no-ssh-key-check", dest="ssh_key_check",
738
                                default=True, action="store_false",
739
                                help="Disable SSH key fingerprint checking")
740

    
741

    
742
MC_OPT = cli_option("-C", "--master-candidate", dest="master_candidate",
743
                    choices=_YESNO, default=None, metavar=_YORNO,
744
                    help="Set the master_candidate flag on the node")
745

    
746
OFFLINE_OPT = cli_option("-O", "--offline", dest="offline", metavar=_YORNO,
747
                         choices=_YESNO, default=None,
748
                         help="Set the offline flag on the node")
749

    
750
DRAINED_OPT = cli_option("-D", "--drained", dest="drained", metavar=_YORNO,
751
                         choices=_YESNO, default=None,
752
                         help="Set the drained flag on the node")
753

    
754
ALLOCATABLE_OPT = cli_option("--allocatable", dest="allocatable",
755
                             choices=_YESNO, default=None, metavar=_YORNO,
756
                             help="Set the allocatable flag on a volume")
757

    
758
NOLVM_STORAGE_OPT = cli_option("--no-lvm-storage", dest="lvm_storage",
759
                               help="Disable support for lvm based instances"
760
                               " (cluster-wide)",
761
                               action="store_false", default=True)
762

    
763
ENABLED_HV_OPT = cli_option("--enabled-hypervisors",
764
                            dest="enabled_hypervisors",
765
                            help="Comma-separated list of hypervisors",
766
                            type="string", default=None)
767

    
768
NIC_PARAMS_OPT = cli_option("-N", "--nic-parameters", dest="nicparams",
769
                            type="keyval", default={},
770
                            help="NIC parameters")
771

    
772
CP_SIZE_OPT = cli_option("-C", "--candidate-pool-size", default=None,
773
                         dest="candidate_pool_size", type="int",
774
                         help="Set the candidate pool size")
775

    
776
VG_NAME_OPT = cli_option("-g", "--vg-name", dest="vg_name",
777
                         help="Enables LVM and specifies the volume group"
778
                         " name (cluster-wide) for disk allocation [xenvg]",
779
                         metavar="VG", default=None)
780

    
781
YES_DOIT_OPT = cli_option("--yes-do-it", dest="yes_do_it",
782
                          help="Destroy cluster", action="store_true")
783

    
784
NOVOTING_OPT = cli_option("--no-voting", dest="no_voting",
785
                          help="Skip node agreement check (dangerous)",
786
                          action="store_true", default=False)
787

    
788
MAC_PREFIX_OPT = cli_option("-m", "--mac-prefix", dest="mac_prefix",
789
                            help="Specify the mac prefix for the instance IP"
790
                            " addresses, in the format XX:XX:XX",
791
                            metavar="PREFIX",
792
                            default=None)
793

    
794
MASTER_NETDEV_OPT = cli_option("--master-netdev", dest="master_netdev",
795
                               help="Specify the node interface (cluster-wide)"
796
                               " on which the master IP address will be added "
797
                               " [%s]" % constants.DEFAULT_BRIDGE,
798
                               metavar="NETDEV",
799
                               default=constants.DEFAULT_BRIDGE)
800

    
801

    
802
GLOBAL_FILEDIR_OPT = cli_option("--file-storage-dir", dest="file_storage_dir",
803
                                help="Specify the default directory (cluster-"
804
                                "wide) for storing the file-based disks [%s]" %
805
                                constants.DEFAULT_FILE_STORAGE_DIR,
806
                                metavar="DIR",
807
                                default=constants.DEFAULT_FILE_STORAGE_DIR)
808

    
809
NOMODIFY_ETCHOSTS_OPT = cli_option("--no-etc-hosts", dest="modify_etc_hosts",
810
                                   help="Don't modify /etc/hosts",
811
                                   action="store_false", default=True)
812

    
813
NOMODIFY_SSH_SETUP_OPT = cli_option("--no-ssh-init", dest="modify_ssh_setup",
814
                                    help="Don't initialize SSH keys",
815
                                    action="store_false", default=True)
816

    
817
ERROR_CODES_OPT = cli_option("--error-codes", dest="error_codes",
818
                             help="Enable parseable error messages",
819
                             action="store_true", default=False)
820

    
821
NONPLUS1_OPT = cli_option("--no-nplus1-mem", dest="skip_nplusone_mem",
822
                          help="Skip N+1 memory redundancy tests",
823
                          action="store_true", default=False)
824

    
825
REBOOT_TYPE_OPT = cli_option("-t", "--type", dest="reboot_type",
826
                             help="Type of reboot: soft/hard/full",
827
                             default=constants.INSTANCE_REBOOT_HARD,
828
                             metavar="<REBOOT>",
829
                             choices=list(constants.REBOOT_TYPES))
830

    
831
IGNORE_SECONDARIES_OPT = cli_option("--ignore-secondaries",
832
                                    dest="ignore_secondaries",
833
                                    default=False, action="store_true",
834
                                    help="Ignore errors from secondaries")
835

    
836
NOSHUTDOWN_OPT = cli_option("--noshutdown", dest="shutdown",
837
                            action="store_false", default=True,
838
                            help="Don't shutdown the instance (unsafe)")
839

    
840
TIMEOUT_OPT = cli_option("--timeout", dest="timeout", type="int",
841
                         default=constants.DEFAULT_SHUTDOWN_TIMEOUT,
842
                         help="Maximum time to wait")
843

    
844
SHUTDOWN_TIMEOUT_OPT = cli_option("--shutdown-timeout",
845
                         dest="shutdown_timeout", type="int",
846
                         default=constants.DEFAULT_SHUTDOWN_TIMEOUT,
847
                         help="Maximum time to wait for instance shutdown")
848

    
849
EARLY_RELEASE_OPT = cli_option("--early-release",
850
                               dest="early_release", default=False,
851
                               action="store_true",
852
                               help="Release the locks on the secondary"
853
                               " node(s) early")
854

    
855

    
856
def _ParseArgs(argv, commands, aliases):
857
  """Parser for the command line arguments.
858

859
  This function parses the arguments and returns the function which
860
  must be executed together with its (modified) arguments.
861

862
  @param argv: the command line
863
  @param commands: dictionary with special contents, see the design
864
      doc for cmdline handling
865
  @param aliases: dictionary with command aliases {'alias': 'target, ...}
866

867
  """
868
  if len(argv) == 0:
869
    binary = "<command>"
870
  else:
871
    binary = argv[0].split("/")[-1]
872

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

    
879
  if len(argv) < 2 or not (argv[1] in commands or
880
                           argv[1] in aliases):
881
    # let's do a nice thing
882
    sortedcmds = commands.keys()
883
    sortedcmds.sort()
884

    
885
    ToStdout("Usage: %s {command} [options...] [argument...]", binary)
886
    ToStdout("%s <command> --help to see details, or man %s", binary, binary)
887
    ToStdout("")
888

    
889
    # compute the max line length for cmd + usage
890
    mlen = max([len(" %s" % cmd) for cmd in commands])
891
    mlen = min(60, mlen) # should not get here...
892

    
893
    # and format a nice command list
894
    ToStdout("Commands:")
895
    for cmd in sortedcmds:
896
      cmdstr = " %s" % (cmd,)
897
      help_text = commands[cmd][4]
898
      help_lines = textwrap.wrap(help_text, 79 - 3 - mlen)
899
      ToStdout("%-*s - %s", mlen, cmdstr, help_lines.pop(0))
900
      for line in help_lines:
901
        ToStdout("%-*s   %s", mlen, "", line)
902

    
903
    ToStdout("")
904

    
905
    return None, None, None
906

    
907
  # get command, unalias it, and look it up in commands
908
  cmd = argv.pop(1)
909
  if cmd in aliases:
910
    if cmd in commands:
911
      raise errors.ProgrammerError("Alias '%s' overrides an existing"
912
                                   " command" % cmd)
913

    
914
    if aliases[cmd] not in commands:
915
      raise errors.ProgrammerError("Alias '%s' maps to non-existing"
916
                                   " command '%s'" % (cmd, aliases[cmd]))
917

    
918
    cmd = aliases[cmd]
919

    
920
  func, args_def, parser_opts, usage, description = commands[cmd]
921
  parser = OptionParser(option_list=parser_opts + [_DRY_RUN_OPT, DEBUG_OPT],
922
                        description=description,
923
                        formatter=TitledHelpFormatter(),
924
                        usage="%%prog %s %s" % (cmd, usage))
925
  parser.disable_interspersed_args()
926
  options, args = parser.parse_args()
927

    
928
  if not _CheckArguments(cmd, args_def, args):
929
    return None, None, None
930

    
931
  return func, options, args
932

    
933

    
934
def _CheckArguments(cmd, args_def, args):
935
  """Verifies the arguments using the argument definition.
936

937
  Algorithm:
938

939
    1. Abort with error if values specified by user but none expected.
940

941
    1. For each argument in definition
942

943
      1. Keep running count of minimum number of values (min_count)
944
      1. Keep running count of maximum number of values (max_count)
945
      1. If it has an unlimited number of values
946

947
        1. Abort with error if it's not the last argument in the definition
948

949
    1. If last argument has limited number of values
950

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

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

955
  """
956
  if args and not args_def:
957
    ToStderr("Error: Command %s expects no arguments", cmd)
958
    return False
959

    
960
  min_count = None
961
  max_count = None
962
  check_max = None
963

    
964
  last_idx = len(args_def) - 1
965

    
966
  for idx, arg in enumerate(args_def):
967
    if min_count is None:
968
      min_count = arg.min
969
    elif arg.min is not None:
970
      min_count += arg.min
971

    
972
    if max_count is None:
973
      max_count = arg.max
974
    elif arg.max is not None:
975
      max_count += arg.max
976

    
977
    if idx == last_idx:
978
      check_max = (arg.max is not None)
979

    
980
    elif arg.max is None:
981
      raise errors.ProgrammerError("Only the last argument can have max=None")
982

    
983
  if check_max:
984
    # Command with exact number of arguments
985
    if (min_count is not None and max_count is not None and
986
        min_count == max_count and len(args) != min_count):
987
      ToStderr("Error: Command %s expects %d argument(s)", cmd, min_count)
988
      return False
989

    
990
    # Command with limited number of arguments
991
    if max_count is not None and len(args) > max_count:
992
      ToStderr("Error: Command %s expects only %d argument(s)",
993
               cmd, max_count)
994
      return False
995

    
996
  # Command with some required arguments
997
  if min_count is not None and len(args) < min_count:
998
    ToStderr("Error: Command %s expects at least %d argument(s)",
999
             cmd, min_count)
1000
    return False
1001

    
1002
  return True
1003

    
1004

    
1005
def SplitNodeOption(value):
1006
  """Splits the value of a --node option.
1007

1008
  """
1009
  if value and ':' in value:
1010
    return value.split(':', 1)
1011
  else:
1012
    return (value, None)
1013

    
1014

    
1015
def CalculateOSNames(os_name, os_variants):
1016
  """Calculates all the names an OS can be called, according to its variants.
1017

1018
  @type os_name: string
1019
  @param os_name: base name of the os
1020
  @type os_variants: list or None
1021
  @param os_variants: list of supported variants
1022
  @rtype: list
1023
  @return: list of valid names
1024

1025
  """
1026
  if os_variants:
1027
    return ['%s+%s' % (os_name, v) for v in os_variants]
1028
  else:
1029
    return [os_name]
1030

    
1031

    
1032
def UsesRPC(fn):
1033
  def wrapper(*args, **kwargs):
1034
    rpc.Init()
1035
    try:
1036
      return fn(*args, **kwargs)
1037
    finally:
1038
      rpc.Shutdown()
1039
  return wrapper
1040

    
1041

    
1042
def AskUser(text, choices=None):
1043
  """Ask the user a question.
1044

1045
  @param text: the question to ask
1046

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

1052
  @return: one of the return values from the choices list; if input is
1053
      not possible (i.e. not running with a tty, we return the last
1054
      entry from the list
1055

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

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

    
1098

    
1099
class JobSubmittedException(Exception):
1100
  """Job was submitted, client should exit.
1101

1102
  This exception has one argument, the ID of the job that was
1103
  submitted. The handler should print this ID.
1104

1105
  This is not an error, just a structured way to exit from clients.
1106

1107
  """
1108

    
1109

    
1110
def SendJob(ops, cl=None):
1111
  """Function to submit an opcode without waiting for the results.
1112

1113
  @type ops: list
1114
  @param ops: list of opcodes
1115
  @type cl: luxi.Client
1116
  @param cl: the luxi client to use for communicating with the master;
1117
             if None, a new client will be created
1118

1119
  """
1120
  if cl is None:
1121
    cl = GetClient()
1122

    
1123
  job_id = cl.SubmitJob(ops)
1124

    
1125
  return job_id
1126

    
1127

    
1128
def PollJob(job_id, cl=None, feedback_fn=None):
1129
  """Function to poll for the result of a job.
1130

1131
  @type job_id: job identified
1132
  @param job_id: the job to poll for results
1133
  @type cl: luxi.Client
1134
  @param cl: the luxi client to use for communicating with the master;
1135
             if None, a new client will be created
1136

1137
  """
1138
  if cl is None:
1139
    cl = GetClient()
1140

    
1141
  prev_job_info = None
1142
  prev_logmsg_serial = None
1143

    
1144
  status = None
1145

    
1146
  notified_queued = False
1147
  notified_waitlock = False
1148

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

    
1164
      # Wait again
1165
      continue
1166

    
1167
    # Split result, a tuple of (field values, log entries)
1168
    (job_info, log_entries) = result
1169
    (status, ) = job_info
1170

    
1171
    if log_entries:
1172
      for log_entry in log_entries:
1173
        (serial, timestamp, _, message) = log_entry
1174
        if callable(feedback_fn):
1175
          feedback_fn(log_entry[1:])
1176
        else:
1177
          encoded = utils.SafeEncode(message)
1178
          ToStdout("%s %s", time.ctime(utils.MergeTime(timestamp)), encoded)
1179
        prev_logmsg_serial = max(prev_logmsg_serial, serial)
1180

    
1181
    # TODO: Handle canceled and archived jobs
1182
    elif status in (constants.JOB_STATUS_SUCCESS,
1183
                    constants.JOB_STATUS_ERROR,
1184
                    constants.JOB_STATUS_CANCELING,
1185
                    constants.JOB_STATUS_CANCELED):
1186
      break
1187

    
1188
    prev_job_info = job_info
1189

    
1190
  jobs = cl.QueryJobs([job_id], ["status", "opstatus", "opresult"])
1191
  if not jobs:
1192
    raise errors.JobLost("Job with id %s lost" % job_id)
1193

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

    
1215

    
1216
def SubmitOpCode(op, cl=None, feedback_fn=None, opts=None):
1217
  """Legacy function to submit an opcode.
1218

1219
  This is just a simple wrapper over the construction of the processor
1220
  instance. It should be extended to better handle feedback and
1221
  interaction functions.
1222

1223
  """
1224
  if cl is None:
1225
    cl = GetClient()
1226

    
1227
  SetGenericOpcodeOpts([op], opts)
1228

    
1229
  job_id = SendJob([op], cl)
1230

    
1231
  op_results = PollJob(job_id, cl=cl, feedback_fn=feedback_fn)
1232

    
1233
  return op_results[0]
1234

    
1235

    
1236
def SubmitOrSend(op, opts, cl=None, feedback_fn=None):
1237
  """Wrapper around SubmitOpCode or SendJob.
1238

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

1244
  It will also process the opcodes if we're sending the via SendJob
1245
  (otherwise SubmitOpCode does it).
1246

1247
  """
1248
  if opts and opts.submit_only:
1249
    job = [op]
1250
    SetGenericOpcodeOpts(job, opts)
1251
    job_id = SendJob(job, cl=cl)
1252
    raise JobSubmittedException(job_id)
1253
  else:
1254
    return SubmitOpCode(op, cl=cl, feedback_fn=feedback_fn, opts=opts)
1255

    
1256

    
1257
def SetGenericOpcodeOpts(opcode_list, options):
1258
  """Processor for generic options.
1259

1260
  This function updates the given opcodes based on generic command
1261
  line options (like debug, dry-run, etc.).
1262

1263
  @param opcode_list: list of opcodes
1264
  @param options: command line options or None
1265
  @return: None (in-place modification)
1266

1267
  """
1268
  if not options:
1269
    return
1270
  for op in opcode_list:
1271
    op.dry_run = options.dry_run
1272
    op.debug_level = options.debug
1273

    
1274

    
1275
def GetClient():
1276
  # TODO: Cache object?
1277
  try:
1278
    client = luxi.Client()
1279
  except luxi.NoMasterError:
1280
    ss = ssconf.SimpleStore()
1281

    
1282
    # Try to read ssconf file
1283
    try:
1284
      ss.GetMasterNode()
1285
    except errors.ConfigurationError:
1286
      raise errors.OpPrereqError("Cluster not initialized or this machine is"
1287
                                 " not part of a cluster")
1288

    
1289
    master, myself = ssconf.GetMasterAndMyself(ss=ss)
1290
    if master != myself:
1291
      raise errors.OpPrereqError("This is not the master node, please connect"
1292
                                 " to node '%s' and rerun the command" %
1293
                                 master)
1294
    raise
1295
  return client
1296

    
1297

    
1298
def FormatError(err):
1299
  """Return a formatted error message for a given error.
1300

1301
  This function takes an exception instance and returns a tuple
1302
  consisting of two values: first, the recommended exit code, and
1303
  second, a string describing the error message (not
1304
  newline-terminated).
1305

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

    
1374

    
1375
def GenericMain(commands, override=None, aliases=None):
1376
  """Generic main function for all the gnt-* commands.
1377

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

1386
  """
1387
  # save the program name and the entire command line for later logging
1388
  if sys.argv:
1389
    binary = os.path.basename(sys.argv[0]) or sys.argv[0]
1390
    if len(sys.argv) >= 2:
1391
      binary += " " + sys.argv[1]
1392
      old_cmdline = " ".join(sys.argv[2:])
1393
    else:
1394
      old_cmdline = ""
1395
  else:
1396
    binary = "<unknown program>"
1397
    old_cmdline = ""
1398

    
1399
  if aliases is None:
1400
    aliases = {}
1401

    
1402
  try:
1403
    func, options, args = _ParseArgs(sys.argv, commands, aliases)
1404
  except errors.ParameterError, err:
1405
    result, err_msg = FormatError(err)
1406
    ToStderr(err_msg)
1407
    return 1
1408

    
1409
  if func is None: # parse error
1410
    return 1
1411

    
1412
  if override is not None:
1413
    for key, val in override.iteritems():
1414
      setattr(options, key, val)
1415

    
1416
  utils.SetupLogging(constants.LOG_COMMANDS, debug=options.debug,
1417
                     stderr_logging=True, program=binary)
1418

    
1419
  if old_cmdline:
1420
    logging.info("run with arguments '%s'", old_cmdline)
1421
  else:
1422
    logging.info("run with no arguments")
1423

    
1424
  try:
1425
    result = func(options, args)
1426
  except (errors.GenericError, luxi.ProtocolError,
1427
          JobSubmittedException), err:
1428
    result, err_msg = FormatError(err)
1429
    logging.exception("Error during command processing")
1430
    ToStderr(err_msg)
1431

    
1432
  return result
1433

    
1434

    
1435
def GenericInstanceCreate(mode, opts, args):
1436
  """Add an instance to the cluster via either creation or import.
1437

1438
  @param mode: constants.INSTANCE_CREATE or constants.INSTANCE_IMPORT
1439
  @param opts: the command line options selected by the user
1440
  @type args: list
1441
  @param args: should contain only one element, the new instance name
1442
  @rtype: int
1443
  @return: the desired exit code
1444

1445
  """
1446
  instance = args[0]
1447

    
1448
  (pnode, snode) = SplitNodeOption(opts.node)
1449

    
1450
  hypervisor = None
1451
  hvparams = {}
1452
  if opts.hypervisor:
1453
    hypervisor, hvparams = opts.hypervisor
1454

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

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

    
1506
  utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_TYPES)
1507
  utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
1508

    
1509
  if mode == constants.INSTANCE_CREATE:
1510
    start = opts.start
1511
    os_type = opts.os
1512
    src_node = None
1513
    src_path = None
1514
  elif mode == constants.INSTANCE_IMPORT:
1515
    start = False
1516
    os_type = None
1517
    src_node = opts.src_node
1518
    src_path = opts.src_dir
1519
  else:
1520
    raise errors.ProgrammerError("Invalid creation mode %s" % mode)
1521

    
1522
  op = opcodes.OpCreateInstance(instance_name=instance,
1523
                                disks=disks,
1524
                                disk_template=opts.disk_template,
1525
                                nics=nics,
1526
                                pnode=pnode, snode=snode,
1527
                                ip_check=opts.ip_check,
1528
                                name_check=opts.name_check,
1529
                                wait_for_sync=opts.wait_for_sync,
1530
                                file_storage_dir=opts.file_storage_dir,
1531
                                file_driver=opts.file_driver,
1532
                                iallocator=opts.iallocator,
1533
                                hypervisor=hypervisor,
1534
                                hvparams=hvparams,
1535
                                beparams=opts.beparams,
1536
                                mode=mode,
1537
                                start=start,
1538
                                os_type=os_type,
1539
                                src_node=src_node,
1540
                                src_path=src_path)
1541

    
1542
  SubmitOrSend(op, opts)
1543
  return 0
1544

    
1545

    
1546
def GenerateTable(headers, fields, separator, data,
1547
                  numfields=None, unitfields=None,
1548
                  units=None):
1549
  """Prints a table with headers and different fields.
1550

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

1574
  """
1575
  if units is None:
1576
    if separator:
1577
      units = "m"
1578
    else:
1579
      units = "h"
1580

    
1581
  if numfields is None:
1582
    numfields = []
1583
  if unitfields is None:
1584
    unitfields = []
1585

    
1586
  numfields = utils.FieldSet(*numfields)   # pylint: disable-msg=W0142
1587
  unitfields = utils.FieldSet(*unitfields) # pylint: disable-msg=W0142
1588

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

    
1603
  if separator is None:
1604
    mlens = [0 for name in fields]
1605
    format = ' '.join(format_fields)
1606
  else:
1607
    format = separator.replace("%", "%%").join(format_fields)
1608

    
1609
  for row in data:
1610
    if row is None:
1611
      continue
1612
    for idx, val in enumerate(row):
1613
      if unitfields.Matches(fields[idx]):
1614
        try:
1615
          val = int(val)
1616
        except (TypeError, ValueError):
1617
          pass
1618
        else:
1619
          val = row[idx] = utils.FormatUnit(val, units)
1620
      val = row[idx] = str(val)
1621
      if separator is None:
1622
        mlens[idx] = max(mlens[idx], len(val))
1623

    
1624
  result = []
1625
  if headers:
1626
    args = []
1627
    for idx, name in enumerate(fields):
1628
      hdr = headers[name]
1629
      if separator is None:
1630
        mlens[idx] = max(mlens[idx], len(hdr))
1631
        args.append(mlens[idx])
1632
      args.append(hdr)
1633
    result.append(format % tuple(args))
1634

    
1635
  if separator is None:
1636
    assert len(mlens) == len(fields)
1637

    
1638
    if fields and not numfields.Matches(fields[-1]):
1639
      mlens[-1] = 0
1640

    
1641
  for line in data:
1642
    args = []
1643
    if line is None:
1644
      line = ['-' for _ in fields]
1645
    for idx in range(len(fields)):
1646
      if separator is None:
1647
        args.append(mlens[idx])
1648
      args.append(line[idx])
1649
    result.append(format % tuple(args))
1650

    
1651
  return result
1652

    
1653

    
1654
def FormatTimestamp(ts):
1655
  """Formats a given timestamp.
1656

1657
  @type ts: timestamp
1658
  @param ts: a timeval-type timestamp, a tuple of seconds and microseconds
1659

1660
  @rtype: string
1661
  @return: a string with the formatted timestamp
1662

1663
  """
1664
  if not isinstance (ts, (tuple, list)) or len(ts) != 2:
1665
    return '?'
1666
  sec, usec = ts
1667
  return time.strftime("%F %T", time.localtime(sec)) + ".%06d" % usec
1668

    
1669

    
1670
def ParseTimespec(value):
1671
  """Parse a time specification.
1672

1673
  The following suffixed will be recognized:
1674

1675
    - s: seconds
1676
    - m: minutes
1677
    - h: hours
1678
    - d: day
1679
    - w: weeks
1680

1681
  Without any suffix, the value will be taken to be in seconds.
1682

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

    
1711

    
1712
def GetOnlineNodes(nodes, cl=None, nowarn=False):
1713
  """Returns the names of online nodes.
1714

1715
  This function will also log a warning on stderr with the names of
1716
  the online nodes.
1717

1718
  @param nodes: if not empty, use only this subset of nodes (minus the
1719
      offline ones)
1720
  @param cl: if not None, luxi client to use
1721
  @type nowarn: boolean
1722
  @param nowarn: by default, this function will output a note with the
1723
      offline nodes that are skipped; if this parameter is True the
1724
      note is not displayed
1725

1726
  """
1727
  if cl is None:
1728
    cl = GetClient()
1729

    
1730
  result = cl.QueryNodes(names=nodes, fields=["name", "offline"],
1731
                         use_locking=False)
1732
  offline = [row[0] for row in result if row[1]]
1733
  if offline and not nowarn:
1734
    ToStderr("Note: skipping offline node(s): %s" % utils.CommaJoin(offline))
1735
  return [row[0] for row in result if not row[1]]
1736

    
1737

    
1738
def _ToStream(stream, txt, *args):
1739
  """Write a message to a stream, bypassing the logging system
1740

1741
  @type stream: file object
1742
  @param stream: the file to which we should write
1743
  @type txt: str
1744
  @param txt: the message
1745

1746
  """
1747
  if args:
1748
    args = tuple(args)
1749
    stream.write(txt % args)
1750
  else:
1751
    stream.write(txt)
1752
  stream.write('\n')
1753
  stream.flush()
1754

    
1755

    
1756
def ToStdout(txt, *args):
1757
  """Write a message to stdout only, bypassing the logging system
1758

1759
  This is just a wrapper over _ToStream.
1760

1761
  @type txt: str
1762
  @param txt: the message
1763

1764
  """
1765
  _ToStream(sys.stdout, txt, *args)
1766

    
1767

    
1768
def ToStderr(txt, *args):
1769
  """Write a message to stderr only, bypassing the logging system
1770

1771
  This is just a wrapper over _ToStream.
1772

1773
  @type txt: str
1774
  @param txt: the message
1775

1776
  """
1777
  _ToStream(sys.stderr, txt, *args)
1778

    
1779

    
1780
class JobExecutor(object):
1781
  """Class which manages the submission and execution of multiple jobs.
1782

1783
  Note that instances of this class should not be reused between
1784
  GetResults() calls.
1785

1786
  """
1787
  def __init__(self, cl=None, verbose=True, opts=None):
1788
    self.queue = []
1789
    if cl is None:
1790
      cl = GetClient()
1791
    self.cl = cl
1792
    self.verbose = verbose
1793
    self.jobs = []
1794
    self.opts = opts
1795

    
1796
  def QueueJob(self, name, *ops):
1797
    """Record a job for later submit.
1798

1799
    @type name: string
1800
    @param name: a description of the job, will be used in WaitJobSet
1801
    """
1802
    SetGenericOpcodeOpts(ops, self.opts)
1803
    self.queue.append((name, ops))
1804

    
1805
  def SubmitPending(self):
1806
    """Submit all pending jobs.
1807

1808
    """
1809
    results = self.cl.SubmitManyJobs([row[1] for row in self.queue])
1810
    for ((status, data), (name, _)) in zip(results, self.queue):
1811
      self.jobs.append((status, data, name))
1812

    
1813
  def GetResults(self):
1814
    """Wait for and return the results of all jobs.
1815

1816
    @rtype: list
1817
    @return: list of tuples (success, job results), in the same order
1818
        as the submitted jobs; if a job has failed, instead of the result
1819
        there will be the error message
1820

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

    
1845
      results.append((success, job_result))
1846
    return results
1847

    
1848
  def WaitOrShow(self, wait):
1849
    """Wait for job results or only print the job IDs.
1850

1851
    @type wait: boolean
1852
    @param wait: whether to wait or not
1853

1854
    """
1855
    if wait:
1856
      return self.GetResults()
1857
    else:
1858
      if not self.jobs:
1859
        self.SubmitPending()
1860
      for status, result, name in self.jobs:
1861
        if status:
1862
          ToStdout("%s: %s", result, name)
1863
        else:
1864
          ToStderr("Failure for %s: %s", name, result)