Statistics
| Branch: | Tag: | Revision:

root / lib / cli.py @ 8d8d650c

History | View | Annotate | Download (58.8 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
  "ENABLED_HV_OPT",
60
  "ERROR_CODES_OPT",
61
  "FIELDS_OPT",
62
  "FILESTORE_DIR_OPT",
63
  "FILESTORE_DRIVER_OPT",
64
  "FORCE_OPT",
65
  "FORCE_VARIANT_OPT",
66
  "GLOBAL_FILEDIR_OPT",
67
  "HVLIST_OPT",
68
  "HVOPTS_OPT",
69
  "HYPERVISOR_OPT",
70
  "IALLOCATOR_OPT",
71
  "IGNORE_CONSIST_OPT",
72
  "IGNORE_FAILURES_OPT",
73
  "IGNORE_REMOVE_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
  "REMOVE_INSTANCE_OPT",
106
  "SECONDARY_IP_OPT",
107
  "SELECT_OS_OPT",
108
  "SEP_OPT",
109
  "SHOWCMD_OPT",
110
  "SHUTDOWN_TIMEOUT_OPT",
111
  "SINGLE_NODE_OPT",
112
  "SRC_DIR_OPT",
113
  "SRC_NODE_OPT",
114
  "SUBMIT_OPT",
115
  "STATIC_OPT",
116
  "SYNC_OPT",
117
  "TAG_SRC_OPT",
118
  "TIMEOUT_OPT",
119
  "USEUNITS_OPT",
120
  "VERBOSE_OPT",
121
  "VG_NAME_OPT",
122
  "YES_DOIT_OPT",
123
  # Generic functions for CLI programs
124
  "GenericMain",
125
  "GenericInstanceCreate",
126
  "GetClient",
127
  "GetOnlineNodes",
128
  "JobExecutor",
129
  "JobSubmittedException",
130
  "ParseTimespec",
131
  "SubmitOpCode",
132
  "SubmitOrSend",
133
  "UsesRPC",
134
  # Formatting functions
135
  "ToStderr", "ToStdout",
136
  "FormatError",
137
  "GenerateTable",
138
  "AskUser",
139
  "FormatTimestamp",
140
  # Tags functions
141
  "ListTags",
142
  "AddTags",
143
  "RemoveTags",
144
  # command line options support infrastructure
145
  "ARGS_MANY_INSTANCES",
146
  "ARGS_MANY_NODES",
147
  "ARGS_NONE",
148
  "ARGS_ONE_INSTANCE",
149
  "ARGS_ONE_NODE",
150
  "ArgChoice",
151
  "ArgCommand",
152
  "ArgFile",
153
  "ArgHost",
154
  "ArgInstance",
155
  "ArgJobId",
156
  "ArgNode",
157
  "ArgSuggest",
158
  "ArgUnknown",
159
  "OPT_COMPL_INST_ADD_NODES",
160
  "OPT_COMPL_MANY_NODES",
161
  "OPT_COMPL_ONE_IALLOCATOR",
162
  "OPT_COMPL_ONE_INSTANCE",
163
  "OPT_COMPL_ONE_NODE",
164
  "OPT_COMPL_ONE_OS",
165
  "cli_option",
166
  "SplitNodeOption",
167
  "CalculateOSNames",
168
  ]
169

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

    
173

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

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

    
183

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

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

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

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

    
199

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

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

206
  """
207

    
208

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

212
  """
213

    
214

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

218
  """
219

    
220

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

224
  """
225

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

229
  """
230

    
231

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

235
  """
236

    
237

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

241
  """
242

    
243

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

247
  """
248

    
249

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

    
256

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

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

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

    
277

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

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

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

    
306

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

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

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

    
324

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

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

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

    
341

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

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

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

    
358

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

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

    
368

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

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

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

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

    
405

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

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

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

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

    
433

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

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

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

    
442

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

    
452
OPT_COMPL_ALL = frozenset([
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,
459
  ])
460

    
461

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

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

    
479

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

    
483

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
697
REMOVE_INSTANCE_OPT = cli_option("--remove-instance", dest="remove_instance",
698
                                 action="store_true", default=False,
699
                                 help="Remove the instance from the cluster")
700

    
701
NEW_SECONDARY_OPT = cli_option("-n", "--new-secondary", dest="dst_node",
702
                               help="Specifies the new secondary node",
703
                               metavar="NODE", default=None,
704
                               completion_suggest=OPT_COMPL_ONE_NODE)
705

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

    
711
ON_SECONDARY_OPT = cli_option("-s", "--on-secondary", dest="on_secondary",
712
                              default=False, action="store_true",
713
                              help="Replace the disk(s) on the secondary"
714
                              " node (only for the drbd template)")
715

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

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

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

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

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

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

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

    
746

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

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

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

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

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

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

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

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

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

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

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

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

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

    
806

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

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

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

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

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

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

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

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

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

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

    
854

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

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

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

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

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

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

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

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

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

    
902
    ToStdout("")
903

    
904
    return None, None, None
905

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

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

    
917
    cmd = aliases[cmd]
918

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

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

    
930
  return func, options, args
931

    
932

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

936
  Algorithm:
937

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

940
    1. For each argument in definition
941

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

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

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

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

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

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

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

    
963
  last_idx = len(args_def) - 1
964

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

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

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

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

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

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

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

    
1001
  return True
1002

    
1003

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

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

    
1013

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

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

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

    
1030

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

    
1040

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

1044
  @param text: the question to ask
1045

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

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

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

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

    
1097

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

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

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

1106
  """
1107

    
1108

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

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

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

    
1122
  job_id = cl.SubmitJob(ops)
1123

    
1124
  return job_id
1125

    
1126

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

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

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

    
1140
  prev_job_info = None
1141
  prev_logmsg_serial = None
1142

    
1143
  while True:
1144
    result = cl.WaitForJobChange(job_id, ["status"], prev_job_info,
1145
                                 prev_logmsg_serial)
1146
    if not result:
1147
      # job not found, go away!
1148
      raise errors.JobLost("Job with id %s lost" % job_id)
1149

    
1150
    # Split result, a tuple of (field values, log entries)
1151
    (job_info, log_entries) = result
1152
    (status, ) = job_info
1153

    
1154
    if log_entries:
1155
      for log_entry in log_entries:
1156
        (serial, timestamp, _, message) = log_entry
1157
        if callable(feedback_fn):
1158
          feedback_fn(log_entry[1:])
1159
        else:
1160
          encoded = utils.SafeEncode(message)
1161
          ToStdout("%s %s", time.ctime(utils.MergeTime(timestamp)), encoded)
1162
        prev_logmsg_serial = max(prev_logmsg_serial, serial)
1163

    
1164
    # TODO: Handle canceled and archived jobs
1165
    elif status in (constants.JOB_STATUS_SUCCESS,
1166
                    constants.JOB_STATUS_ERROR,
1167
                    constants.JOB_STATUS_CANCELING,
1168
                    constants.JOB_STATUS_CANCELED):
1169
      break
1170

    
1171
    prev_job_info = job_info
1172

    
1173
  jobs = cl.QueryJobs([job_id], ["status", "opstatus", "opresult"])
1174
  if not jobs:
1175
    raise errors.JobLost("Job with id %s lost" % job_id)
1176

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

    
1198

    
1199
def SubmitOpCode(op, cl=None, feedback_fn=None):
1200
  """Legacy function to submit an opcode.
1201

1202
  This is just a simple wrapper over the construction of the processor
1203
  instance. It should be extended to better handle feedback and
1204
  interaction functions.
1205

1206
  """
1207
  if cl is None:
1208
    cl = GetClient()
1209

    
1210
  job_id = SendJob([op], cl)
1211

    
1212
  op_results = PollJob(job_id, cl=cl, feedback_fn=feedback_fn)
1213

    
1214
  return op_results[0]
1215

    
1216

    
1217
def SubmitOrSend(op, opts, cl=None, feedback_fn=None):
1218
  """Wrapper around SubmitOpCode or SendJob.
1219

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

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

1227
  """
1228
  if opts and opts.dry_run:
1229
    op.dry_run = opts.dry_run
1230
  if opts and opts.submit_only:
1231
    job_id = SendJob([op], cl=cl)
1232
    raise JobSubmittedException(job_id)
1233
  else:
1234
    return SubmitOpCode(op, cl=cl, feedback_fn=feedback_fn)
1235

    
1236

    
1237
def GetClient():
1238
  # TODO: Cache object?
1239
  try:
1240
    client = luxi.Client()
1241
  except luxi.NoMasterError:
1242
    ss = ssconf.SimpleStore()
1243

    
1244
    # Try to read ssconf file
1245
    try:
1246
      ss.GetMasterNode()
1247
    except errors.ConfigurationError:
1248
      raise errors.OpPrereqError("Cluster not initialized or this machine is"
1249
                                 " not part of a cluster")
1250

    
1251
    master, myself = ssconf.GetMasterAndMyself(ss=ss)
1252
    if master != myself:
1253
      raise errors.OpPrereqError("This is not the master node, please connect"
1254
                                 " to node '%s' and rerun the command" %
1255
                                 master)
1256
    raise
1257
  return client
1258

    
1259

    
1260
def FormatError(err):
1261
  """Return a formatted error message for a given error.
1262

1263
  This function takes an exception instance and returns a tuple
1264
  consisting of two values: first, the recommended exit code, and
1265
  second, a string describing the error message (not
1266
  newline-terminated).
1267

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

    
1336

    
1337
def GenericMain(commands, override=None, aliases=None):
1338
  """Generic main function for all the gnt-* commands.
1339

1340
  Arguments:
1341
    - commands: a dictionary with a special structure, see the design doc
1342
                for command line handling.
1343
    - override: if not None, we expect a dictionary with keys that will
1344
                override command line options; this can be used to pass
1345
                options from the scripts to generic functions
1346
    - aliases: dictionary with command aliases {'alias': 'target, ...}
1347

1348
  """
1349
  # save the program name and the entire command line for later logging
1350
  if sys.argv:
1351
    binary = os.path.basename(sys.argv[0]) or sys.argv[0]
1352
    if len(sys.argv) >= 2:
1353
      binary += " " + sys.argv[1]
1354
      old_cmdline = " ".join(sys.argv[2:])
1355
    else:
1356
      old_cmdline = ""
1357
  else:
1358
    binary = "<unknown program>"
1359
    old_cmdline = ""
1360

    
1361
  if aliases is None:
1362
    aliases = {}
1363

    
1364
  try:
1365
    func, options, args = _ParseArgs(sys.argv, commands, aliases)
1366
  except errors.ParameterError, err:
1367
    result, err_msg = FormatError(err)
1368
    ToStderr(err_msg)
1369
    return 1
1370

    
1371
  if func is None: # parse error
1372
    return 1
1373

    
1374
  if override is not None:
1375
    for key, val in override.iteritems():
1376
      setattr(options, key, val)
1377

    
1378
  utils.SetupLogging(constants.LOG_COMMANDS, debug=options.debug,
1379
                     stderr_logging=True, program=binary)
1380

    
1381
  if old_cmdline:
1382
    logging.info("run with arguments '%s'", old_cmdline)
1383
  else:
1384
    logging.info("run with no arguments")
1385

    
1386
  try:
1387
    result = func(options, args)
1388
  except (errors.GenericError, luxi.ProtocolError,
1389
          JobSubmittedException), err:
1390
    result, err_msg = FormatError(err)
1391
    logging.exception("Error during command processing")
1392
    ToStderr(err_msg)
1393

    
1394
  return result
1395

    
1396

    
1397
def GenericInstanceCreate(mode, opts, args):
1398
  """Add an instance to the cluster via either creation or import.
1399

1400
  @param mode: constants.INSTANCE_CREATE or constants.INSTANCE_IMPORT
1401
  @param opts: the command line options selected by the user
1402
  @type args: list
1403
  @param args: should contain only one element, the new instance name
1404
  @rtype: int
1405
  @return: the desired exit code
1406

1407
  """
1408
  instance = args[0]
1409

    
1410
  (pnode, snode) = SplitNodeOption(opts.node)
1411

    
1412
  hypervisor = None
1413
  hvparams = {}
1414
  if opts.hypervisor:
1415
    hypervisor, hvparams = opts.hypervisor
1416

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

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

    
1468
  utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_TYPES)
1469
  utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
1470

    
1471
  if mode == constants.INSTANCE_CREATE:
1472
    start = opts.start
1473
    os_type = opts.os
1474
    src_node = None
1475
    src_path = None
1476
  elif mode == constants.INSTANCE_IMPORT:
1477
    start = False
1478
    os_type = None
1479
    src_node = opts.src_node
1480
    src_path = opts.src_dir
1481
  else:
1482
    raise errors.ProgrammerError("Invalid creation mode %s" % mode)
1483

    
1484
  op = opcodes.OpCreateInstance(instance_name=instance,
1485
                                disks=disks,
1486
                                disk_template=opts.disk_template,
1487
                                nics=nics,
1488
                                pnode=pnode, snode=snode,
1489
                                ip_check=opts.ip_check,
1490
                                name_check=opts.name_check,
1491
                                wait_for_sync=opts.wait_for_sync,
1492
                                file_storage_dir=opts.file_storage_dir,
1493
                                file_driver=opts.file_driver,
1494
                                iallocator=opts.iallocator,
1495
                                hypervisor=hypervisor,
1496
                                hvparams=hvparams,
1497
                                beparams=opts.beparams,
1498
                                mode=mode,
1499
                                start=start,
1500
                                os_type=os_type,
1501
                                src_node=src_node,
1502
                                src_path=src_path)
1503

    
1504
  SubmitOrSend(op, opts)
1505
  return 0
1506

    
1507

    
1508
def GenerateTable(headers, fields, separator, data,
1509
                  numfields=None, unitfields=None,
1510
                  units=None):
1511
  """Prints a table with headers and different fields.
1512

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

1536
  """
1537
  if units is None:
1538
    if separator:
1539
      units = "m"
1540
    else:
1541
      units = "h"
1542

    
1543
  if numfields is None:
1544
    numfields = []
1545
  if unitfields is None:
1546
    unitfields = []
1547

    
1548
  numfields = utils.FieldSet(*numfields)   # pylint: disable-msg=W0142
1549
  unitfields = utils.FieldSet(*unitfields) # pylint: disable-msg=W0142
1550

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

    
1565
  if separator is None:
1566
    mlens = [0 for name in fields]
1567
    format = ' '.join(format_fields)
1568
  else:
1569
    format = separator.replace("%", "%%").join(format_fields)
1570

    
1571
  for row in data:
1572
    if row is None:
1573
      continue
1574
    for idx, val in enumerate(row):
1575
      if unitfields.Matches(fields[idx]):
1576
        try:
1577
          val = int(val)
1578
        except (TypeError, ValueError):
1579
          pass
1580
        else:
1581
          val = row[idx] = utils.FormatUnit(val, units)
1582
      val = row[idx] = str(val)
1583
      if separator is None:
1584
        mlens[idx] = max(mlens[idx], len(val))
1585

    
1586
  result = []
1587
  if headers:
1588
    args = []
1589
    for idx, name in enumerate(fields):
1590
      hdr = headers[name]
1591
      if separator is None:
1592
        mlens[idx] = max(mlens[idx], len(hdr))
1593
        args.append(mlens[idx])
1594
      args.append(hdr)
1595
    result.append(format % tuple(args))
1596

    
1597
  if separator is None:
1598
    assert len(mlens) == len(fields)
1599

    
1600
    if fields and not numfields.Matches(fields[-1]):
1601
      mlens[-1] = 0
1602

    
1603
  for line in data:
1604
    args = []
1605
    if line is None:
1606
      line = ['-' for _ in fields]
1607
    for idx in range(len(fields)):
1608
      if separator is None:
1609
        args.append(mlens[idx])
1610
      args.append(line[idx])
1611
    result.append(format % tuple(args))
1612

    
1613
  return result
1614

    
1615

    
1616
def FormatTimestamp(ts):
1617
  """Formats a given timestamp.
1618

1619
  @type ts: timestamp
1620
  @param ts: a timeval-type timestamp, a tuple of seconds and microseconds
1621

1622
  @rtype: string
1623
  @return: a string with the formatted timestamp
1624

1625
  """
1626
  if not isinstance (ts, (tuple, list)) or len(ts) != 2:
1627
    return '?'
1628
  sec, usec = ts
1629
  return time.strftime("%F %T", time.localtime(sec)) + ".%06d" % usec
1630

    
1631

    
1632
def ParseTimespec(value):
1633
  """Parse a time specification.
1634

1635
  The following suffixed will be recognized:
1636

1637
    - s: seconds
1638
    - m: minutes
1639
    - h: hours
1640
    - d: day
1641
    - w: weeks
1642

1643
  Without any suffix, the value will be taken to be in seconds.
1644

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

    
1673

    
1674
def GetOnlineNodes(nodes, cl=None, nowarn=False):
1675
  """Returns the names of online nodes.
1676

1677
  This function will also log a warning on stderr with the names of
1678
  the online nodes.
1679

1680
  @param nodes: if not empty, use only this subset of nodes (minus the
1681
      offline ones)
1682
  @param cl: if not None, luxi client to use
1683
  @type nowarn: boolean
1684
  @param nowarn: by default, this function will output a note with the
1685
      offline nodes that are skipped; if this parameter is True the
1686
      note is not displayed
1687

1688
  """
1689
  if cl is None:
1690
    cl = GetClient()
1691

    
1692
  result = cl.QueryNodes(names=nodes, fields=["name", "offline"],
1693
                         use_locking=False)
1694
  offline = [row[0] for row in result if row[1]]
1695
  if offline and not nowarn:
1696
    ToStderr("Note: skipping offline node(s): %s" % utils.CommaJoin(offline))
1697
  return [row[0] for row in result if not row[1]]
1698

    
1699

    
1700
def _ToStream(stream, txt, *args):
1701
  """Write a message to a stream, bypassing the logging system
1702

1703
  @type stream: file object
1704
  @param stream: the file to which we should write
1705
  @type txt: str
1706
  @param txt: the message
1707

1708
  """
1709
  if args:
1710
    args = tuple(args)
1711
    stream.write(txt % args)
1712
  else:
1713
    stream.write(txt)
1714
  stream.write('\n')
1715
  stream.flush()
1716

    
1717

    
1718
def ToStdout(txt, *args):
1719
  """Write a message to stdout only, bypassing the logging system
1720

1721
  This is just a wrapper over _ToStream.
1722

1723
  @type txt: str
1724
  @param txt: the message
1725

1726
  """
1727
  _ToStream(sys.stdout, txt, *args)
1728

    
1729

    
1730
def ToStderr(txt, *args):
1731
  """Write a message to stderr only, bypassing the logging system
1732

1733
  This is just a wrapper over _ToStream.
1734

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

1738
  """
1739
  _ToStream(sys.stderr, txt, *args)
1740

    
1741

    
1742
class JobExecutor(object):
1743
  """Class which manages the submission and execution of multiple jobs.
1744

1745
  Note that instances of this class should not be reused between
1746
  GetResults() calls.
1747

1748
  """
1749
  def __init__(self, cl=None, verbose=True):
1750
    self.queue = []
1751
    if cl is None:
1752
      cl = GetClient()
1753
    self.cl = cl
1754
    self.verbose = verbose
1755
    self.jobs = []
1756

    
1757
  def QueueJob(self, name, *ops):
1758
    """Record a job for later submit.
1759

1760
    @type name: string
1761
    @param name: a description of the job, will be used in WaitJobSet
1762
    """
1763
    self.queue.append((name, ops))
1764

    
1765
  def SubmitPending(self):
1766
    """Submit all pending jobs.
1767

1768
    """
1769
    results = self.cl.SubmitManyJobs([row[1] for row in self.queue])
1770
    for ((status, data), (name, _)) in zip(results, self.queue):
1771
      self.jobs.append((status, data, name))
1772

    
1773
  def GetResults(self):
1774
    """Wait for and return the results of all jobs.
1775

1776
    @rtype: list
1777
    @return: list of tuples (success, job results), in the same order
1778
        as the submitted jobs; if a job has failed, instead of the result
1779
        there will be the error message
1780

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

    
1805
      results.append((success, job_result))
1806
    return results
1807

    
1808
  def WaitOrShow(self, wait):
1809
    """Wait for job results or only print the job IDs.
1810

1811
    @type wait: boolean
1812
    @param wait: whether to wait or not
1813

1814
    """
1815
    if wait:
1816
      return self.GetResults()
1817
    else:
1818
      if not self.jobs:
1819
        self.SubmitPending()
1820
      for status, result, name in self.jobs:
1821
        if status:
1822
          ToStdout("%s: %s", result, name)
1823
        else:
1824
          ToStderr("Failure for %s: %s", name, result)