Statistics
| Branch: | Tag: | Revision:

root / lib / cli.py @ 7ea7bcf6

History | View | Annotate | Download (58.4 kB)

1
#
2
#
3

    
4
# Copyright (C) 2006, 2007 Google Inc.
5
#
6
# This program is free software; you can redistribute it and/or modify
7
# it under the terms of the GNU General Public License as published by
8
# the Free Software Foundation; either version 2 of the License, or
9
# (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful, but
12
# WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14
# General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19
# 02110-1301, USA.
20

    
21

    
22
"""Module dealing with command line parsing"""
23

    
24

    
25
import sys
26
import textwrap
27
import os.path
28
import time
29
import logging
30
from cStringIO import StringIO
31

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

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

    
43

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

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

    
172

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

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

    
182

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

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

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

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

    
198

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

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

205
  """
206

    
207

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

211
  """
212

    
213

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

217
  """
218

    
219

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

223
  """
224

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

228
  """
229

    
230

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

234
  """
235

    
236

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

240
  """
241

    
242

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

246
  """
247

    
248

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

    
255

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

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

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

    
276

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

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

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

    
305

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

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

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

    
323

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

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

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

    
340

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

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

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

    
357

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

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

    
367

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

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

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

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

    
404

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

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

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

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

    
432

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

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

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

    
441

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

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

    
460

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

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

    
478

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

    
482

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

    
486
DEBUG_OPT = cli_option("-d", "--debug", default=False,
487
                       action="store_true",
488
                       help="Turn debugging on")
489

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
733

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

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

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

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

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

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

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

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

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

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

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

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

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

    
793

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

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

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

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

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

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

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

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

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

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

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

    
847

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

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

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

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

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

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

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

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

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

    
895
    ToStdout("")
896

    
897
    return None, None, None
898

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

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

    
910
    cmd = aliases[cmd]
911

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

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

    
923
  return func, options, args
924

    
925

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

929
  Algorithm:
930

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

933
    1. For each argument in definition
934

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

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

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

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

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

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

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

    
956
  last_idx = len(args_def) - 1
957

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

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

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

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

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

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

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

    
994
  return True
995

    
996

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

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

    
1006

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

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

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

    
1023

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

    
1033

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

1037
  @param text: the question to ask
1038

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

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

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

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

    
1090

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

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

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

1099
  """
1100

    
1101

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

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

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

    
1115
  job_id = cl.SubmitJob(ops)
1116

    
1117
  return job_id
1118

    
1119

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

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

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

    
1133
  prev_job_info = None
1134
  prev_logmsg_serial = None
1135

    
1136
  while True:
1137
    result = cl.WaitForJobChange(job_id, ["status"], prev_job_info,
1138
                                 prev_logmsg_serial)
1139
    if not result:
1140
      # job not found, go away!
1141
      raise errors.JobLost("Job with id %s lost" % job_id)
1142

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

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

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

    
1164
    prev_job_info = job_info
1165

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

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

    
1191

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

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

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

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

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

    
1207
  return op_results[0]
1208

    
1209

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

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

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

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

    
1229

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

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

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

    
1252

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

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

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

    
1329

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

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

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

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

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

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

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

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

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

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

    
1387
  return result
1388

    
1389

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

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

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

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

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

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

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

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

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

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

    
1497
  SubmitOrSend(op, opts)
1498
  return 0
1499

    
1500

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

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

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

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

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

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

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

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

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

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

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

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

    
1606
  return result
1607

    
1608

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

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

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

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

    
1624

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

1628
  The following suffixed will be recognized:
1629

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

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

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

    
1666

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

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

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

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

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

    
1692

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

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

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

    
1710

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

1714
  This is just a wrapper over _ToStream.
1715

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

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

    
1722

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

1726
  This is just a wrapper over _ToStream.
1727

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

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

    
1734

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

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

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

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

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

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

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

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

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

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

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

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

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

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