Statistics
| Branch: | Tag: | Revision:

root / lib / cli.py @ 4d98c565

History | View | Annotate | Download (56.7 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_SECONDARIES_OPT",
74
  "IGNORE_SIZE_OPT",
75
  "MAC_PREFIX_OPT",
76
  "MASTER_NETDEV_OPT",
77
  "MC_OPT",
78
  "NET_OPT",
79
  "NEW_SECONDARY_OPT",
80
  "NIC_PARAMS_OPT",
81
  "NODE_LIST_OPT",
82
  "NODE_PLACEMENT_OPT",
83
  "NOHDR_OPT",
84
  "NOIPCHECK_OPT",
85
  "NOLVM_STORAGE_OPT",
86
  "NOMODIFY_ETCHOSTS_OPT",
87
  "NONICS_OPT",
88
  "NONLIVE_OPT",
89
  "NONPLUS1_OPT",
90
  "NOSHUTDOWN_OPT",
91
  "NOSTART_OPT",
92
  "NOSSH_KEYCHECK_OPT",
93
  "NOVOTING_OPT",
94
  "NWSYNC_OPT",
95
  "ON_PRIMARY_OPT",
96
  "ON_SECONDARY_OPT",
97
  "OFFLINE_OPT",
98
  "OS_OPT",
99
  "OS_SIZE_OPT",
100
  "READD_OPT",
101
  "REBOOT_TYPE_OPT",
102
  "SECONDARY_IP_OPT",
103
  "SELECT_OS_OPT",
104
  "SEP_OPT",
105
  "SHOWCMD_OPT",
106
  "SHUTDOWN_TIMEOUT_OPT",
107
  "SINGLE_NODE_OPT",
108
  "SRC_DIR_OPT",
109
  "SRC_NODE_OPT",
110
  "SUBMIT_OPT",
111
  "STATIC_OPT",
112
  "SYNC_OPT",
113
  "TAG_SRC_OPT",
114
  "TIMEOUT_OPT",
115
  "USEUNITS_OPT",
116
  "VERBOSE_OPT",
117
  "VG_NAME_OPT",
118
  "YES_DOIT_OPT",
119
  # Generic functions for CLI programs
120
  "GenericMain",
121
  "GenericInstanceCreate",
122
  "GetClient",
123
  "GetOnlineNodes",
124
  "JobExecutor",
125
  "JobSubmittedException",
126
  "ParseTimespec",
127
  "SubmitOpCode",
128
  "SubmitOrSend",
129
  "UsesRPC",
130
  # Formatting functions
131
  "ToStderr", "ToStdout",
132
  "FormatError",
133
  "GenerateTable",
134
  "AskUser",
135
  "FormatTimestamp",
136
  # Tags functions
137
  "ListTags",
138
  "AddTags",
139
  "RemoveTags",
140
  # command line options support infrastructure
141
  "ARGS_MANY_INSTANCES",
142
  "ARGS_MANY_NODES",
143
  "ARGS_NONE",
144
  "ARGS_ONE_INSTANCE",
145
  "ARGS_ONE_NODE",
146
  "ArgChoice",
147
  "ArgCommand",
148
  "ArgFile",
149
  "ArgHost",
150
  "ArgInstance",
151
  "ArgJobId",
152
  "ArgNode",
153
  "ArgSuggest",
154
  "ArgUnknown",
155
  "OPT_COMPL_INST_ADD_NODES",
156
  "OPT_COMPL_MANY_NODES",
157
  "OPT_COMPL_ONE_IALLOCATOR",
158
  "OPT_COMPL_ONE_INSTANCE",
159
  "OPT_COMPL_ONE_NODE",
160
  "OPT_COMPL_ONE_OS",
161
  "cli_option",
162
  "SplitNodeOption",
163
  "CalculateOSNames",
164
  ]
165

    
166
NO_PREFIX = "no_"
167
UN_PREFIX = "-"
168

    
169

    
170
class _Argument:
171
  def __init__(self, min=0, max=None):
172
    self.min = min
173
    self.max = max
174

    
175
  def __repr__(self):
176
    return ("<%s min=%s max=%s>" %
177
            (self.__class__.__name__, self.min, self.max))
178

    
179

    
180
class ArgSuggest(_Argument):
181
  """Suggesting argument.
182

183
  Value can be any of the ones passed to the constructor.
184

185
  """
186
  def __init__(self, min=0, max=None, choices=None):
187
    _Argument.__init__(self, min=min, max=max)
188
    self.choices = choices
189

    
190
  def __repr__(self):
191
    return ("<%s min=%s max=%s choices=%r>" %
192
            (self.__class__.__name__, self.min, self.max, self.choices))
193

    
194

    
195
class ArgChoice(ArgSuggest):
196
  """Choice argument.
197

198
  Value can be any of the ones passed to the constructor. Like L{ArgSuggest},
199
  but value must be one of the choices.
200

201
  """
202

    
203

    
204
class ArgUnknown(_Argument):
205
  """Unknown argument to program (e.g. determined at runtime).
206

207
  """
208

    
209

    
210
class ArgInstance(_Argument):
211
  """Instances argument.
212

213
  """
214

    
215

    
216
class ArgNode(_Argument):
217
  """Node argument.
218

219
  """
220

    
221
class ArgJobId(_Argument):
222
  """Job ID argument.
223

224
  """
225

    
226

    
227
class ArgFile(_Argument):
228
  """File path argument.
229

230
  """
231

    
232

    
233
class ArgCommand(_Argument):
234
  """Command argument.
235

236
  """
237

    
238

    
239
class ArgHost(_Argument):
240
  """Host argument.
241

242
  """
243

    
244

    
245
ARGS_NONE = []
246
ARGS_MANY_INSTANCES = [ArgInstance()]
247
ARGS_MANY_NODES = [ArgNode()]
248
ARGS_ONE_INSTANCE = [ArgInstance(min=1, max=1)]
249
ARGS_ONE_NODE = [ArgNode(min=1, max=1)]
250

    
251

    
252

    
253
def _ExtractTagsObject(opts, args):
254
  """Extract the tag type object.
255

256
  Note that this function will modify its args parameter.
257

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

    
273

    
274
def _ExtendTags(opts, args):
275
  """Extend the args if a source file has been given.
276

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

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

    
302

    
303
def ListTags(opts, args):
304
  """List the tags on a given object.
305

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

311
  """
312
  kind, name = _ExtractTagsObject(opts, args)
313
  op = opcodes.OpGetTags(kind=kind, name=name)
314
  result = SubmitOpCode(op)
315
  result = list(result)
316
  result.sort()
317
  for tag in result:
318
    ToStdout(tag)
319

    
320

    
321
def AddTags(opts, args):
322
  """Add tags on a given object.
323

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

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

    
337

    
338
def RemoveTags(opts, args):
339
  """Remove tags from a given object.
340

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

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

    
354

    
355
def check_unit(option, opt, value):
356
  """OptParsers custom converter for units.
357

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

    
364

    
365
def _SplitKeyVal(opt, data):
366
  """Convert a KeyVal string into a dict.
367

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

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

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

    
401

    
402
def check_ident_key_val(option, opt, value):
403
  """Custom parser for ident:key=val,key=val options.
404

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

408
  """
409
  if ":" not in value:
410
    ident, rest = value, ''
411
  else:
412
    ident, rest = value.split(":", 1)
413

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

    
429

    
430
def check_key_val(option, opt, value):
431
  """Custom parser class for key=val,key=val options.
432

433
  This will store the parsed values as a dict {key: val}.
434

435
  """
436
  return _SplitKeyVal(opt, value)
437

    
438

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

    
448
OPT_COMPL_ALL = frozenset([
449
  OPT_COMPL_MANY_NODES,
450
  OPT_COMPL_ONE_NODE,
451
  OPT_COMPL_ONE_INSTANCE,
452
  OPT_COMPL_ONE_OS,
453
  OPT_COMPL_ONE_IALLOCATOR,
454
  OPT_COMPL_INST_ADD_NODES,
455
  ])
456

    
457

    
458
class CliOption(Option):
459
  """Custom option class for optparse.
460

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

    
475

    
476
# optparse.py sets make_option, so we do it for our own option class, too
477
cli_option = CliOption
478

    
479

    
480
_YESNO = ("yes", "no")
481
_YORNO = "yes|no"
482

    
483
DEBUG_OPT = cli_option("-d", "--debug", default=False,
484
                       action="store_true",
485
                       help="Turn debugging on")
486

    
487
NOHDR_OPT = cli_option("--no-headers", default=False,
488
                       action="store_true", dest="no_headers",
489
                       help="Don't display column headers")
490

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

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

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

    
504
FORCE_OPT = cli_option("-f", "--force", dest="force", action="store_true",
505
                       default=False, help="Force the operation")
506

    
507
CONFIRM_OPT = cli_option("--yes", dest="confirm", action="store_true",
508
                         default=False, help="Do not require confirmation")
509

    
510
TAG_SRC_OPT = cli_option("--from", dest="tags_source",
511
                         default=None, help="File with tag names")
512

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

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

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

    
529
VERBOSE_OPT = cli_option("-v", "--verbose", default=False,
530
                         action="store_true",
531
                         help="Increase the verbosity of the operation")
532

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

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

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

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

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

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

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

    
568
OS_OPT = cli_option("-o", "--os-type", dest="os", help="What OS to run",
569
                    metavar="<os>",
570
                    completion_suggest=OPT_COMPL_ONE_OS)
571

    
572
FORCE_VARIANT_OPT = cli_option("--force-variant", dest="force_variant",
573
                               action="store_true", default=False,
574
                               help="Force an unknown variant")
575

    
576
BACKEND_OPT = cli_option("-B", "--backend-parameters", dest="beparams",
577
                         type="keyval", default={},
578
                         help="Backend parameters")
579

    
580
HVOPTS_OPT =  cli_option("-H", "--hypervisor-parameters", type="keyval",
581
                         default={}, dest="hvparams",
582
                         help="Hypervisor parameters")
583

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

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

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

    
599
NET_OPT = cli_option("--net",
600
                     help="NIC parameters", default=[],
601
                     dest="nics", action="append", type="identkeyval")
602

    
603
DISK_OPT = cli_option("--disk", help="Disk parameters", default=[],
604
                      dest="disks", action="append", type="identkeyval")
605

    
606
DISKIDX_OPT = cli_option("--disks", dest="disks", default=None,
607
                         help="Comma-separated list of disks"
608
                         " indices to act on (e.g. 0,2) (optional,"
609
                         " defaults to all disks)")
610

    
611
OS_SIZE_OPT = cli_option("-s", "--os-size", dest="sd_size",
612
                         help="Enforces a single-disk configuration using the"
613
                         " given disk size, in MiB unless a suffix is used",
614
                         default=None, type="unit", metavar="<size>")
615

    
616
IGNORE_CONSIST_OPT = cli_option("--ignore-consistency",
617
                                dest="ignore_consistency",
618
                                action="store_true", default=False,
619
                                help="Ignore the consistency of the disks on"
620
                                " the secondary")
621

    
622
NONLIVE_OPT = cli_option("--non-live", dest="live",
623
                         default=True, action="store_false",
624
                         help="Do a non-live migration (this usually means"
625
                         " freeze the instance, save the state, transfer and"
626
                         " only then resume running on the secondary node)")
627

    
628
NODE_PLACEMENT_OPT = cli_option("-n", "--node", dest="node",
629
                                help="Target node and optional secondary node",
630
                                metavar="<pnode>[:<snode>]",
631
                                completion_suggest=OPT_COMPL_INST_ADD_NODES)
632

    
633
NODE_LIST_OPT = cli_option("-n", "--node", dest="nodes", default=[],
634
                           action="append", metavar="<node>",
635
                           help="Use only this node (can be used multiple"
636
                           " times, if not given defaults to all nodes)",
637
                           completion_suggest=OPT_COMPL_ONE_NODE)
638

    
639
SINGLE_NODE_OPT = cli_option("-n", "--node", dest="node", help="Target node",
640
                             metavar="<node>",
641
                             completion_suggest=OPT_COMPL_ONE_NODE)
642

    
643
NOSTART_OPT = cli_option("--no-start", dest="start", default=True,
644
                         action="store_false",
645
                         help="Don't start the instance after creation")
646

    
647
SHOWCMD_OPT = cli_option("--show-cmd", dest="show_command",
648
                         action="store_true", default=False,
649
                         help="Show command instead of executing it")
650

    
651
CLEANUP_OPT = cli_option("--cleanup", dest="cleanup",
652
                         default=False, action="store_true",
653
                         help="Instead of performing the migration, try to"
654
                         " recover from a failed cleanup. This is safe"
655
                         " to run even if the instance is healthy, but it"
656
                         " will create extra replication traffic and "
657
                         " disrupt briefly the replication (like during the"
658
                         " migration")
659

    
660
STATIC_OPT = cli_option("-s", "--static", dest="static",
661
                        action="store_true", default=False,
662
                        help="Only show configuration data, not runtime data")
663

    
664
ALL_OPT = cli_option("--all", dest="show_all",
665
                     default=False, action="store_true",
666
                     help="Show info on all instances on the cluster."
667
                     " This can take a long time to run, use wisely")
668

    
669
SELECT_OS_OPT = cli_option("--select-os", dest="select_os",
670
                           action="store_true", default=False,
671
                           help="Interactive OS reinstall, lists available"
672
                           " OS templates for selection")
673

    
674
IGNORE_FAILURES_OPT = cli_option("--ignore-failures", dest="ignore_failures",
675
                                 action="store_true", default=False,
676
                                 help="Remove the instance from the cluster"
677
                                 " configuration even if there are failures"
678
                                 " during the removal process")
679

    
680
NEW_SECONDARY_OPT = cli_option("-n", "--new-secondary", dest="dst_node",
681
                               help="Specifies the new secondary node",
682
                               metavar="NODE", default=None,
683
                               completion_suggest=OPT_COMPL_ONE_NODE)
684

    
685
ON_PRIMARY_OPT = cli_option("-p", "--on-primary", dest="on_primary",
686
                            default=False, action="store_true",
687
                            help="Replace the disk(s) on the primary"
688
                            " node (only for the drbd template)")
689

    
690
ON_SECONDARY_OPT = cli_option("-s", "--on-secondary", dest="on_secondary",
691
                              default=False, action="store_true",
692
                              help="Replace the disk(s) on the secondary"
693
                              " node (only for the drbd template)")
694

    
695
AUTO_REPLACE_OPT = cli_option("-a", "--auto", dest="auto",
696
                              default=False, action="store_true",
697
                              help="Automatically replace faulty disks"
698
                              " (only for the drbd template)")
699

    
700
IGNORE_SIZE_OPT = cli_option("--ignore-size", dest="ignore_size",
701
                             default=False, action="store_true",
702
                             help="Ignore current recorded size"
703
                             " (useful for forcing activation when"
704
                             " the recorded size is wrong)")
705

    
706
SRC_NODE_OPT = cli_option("--src-node", dest="src_node", help="Source node",
707
                          metavar="<node>",
708
                          completion_suggest=OPT_COMPL_ONE_NODE)
709

    
710
SRC_DIR_OPT = cli_option("--src-dir", dest="src_dir", help="Source directory",
711
                         metavar="<dir>")
712

    
713
SECONDARY_IP_OPT = cli_option("-s", "--secondary-ip", dest="secondary_ip",
714
                              help="Specify the secondary ip for the node",
715
                              metavar="ADDRESS", default=None)
716

    
717
READD_OPT = cli_option("--readd", dest="readd",
718
                       default=False, action="store_true",
719
                       help="Readd old node after replacing it")
720

    
721
NOSSH_KEYCHECK_OPT = cli_option("--no-ssh-key-check", dest="ssh_key_check",
722
                                default=True, action="store_false",
723
                                help="Disable SSH key fingerprint checking")
724

    
725

    
726
MC_OPT = cli_option("-C", "--master-candidate", dest="master_candidate",
727
                    choices=_YESNO, default=None, metavar=_YORNO,
728
                    help="Set the master_candidate flag on the node")
729

    
730
OFFLINE_OPT = cli_option("-O", "--offline", dest="offline", metavar=_YORNO,
731
                         choices=_YESNO, default=None,
732
                         help="Set the offline flag on the node")
733

    
734
DRAINED_OPT = cli_option("-D", "--drained", dest="drained", metavar=_YORNO,
735
                         choices=_YESNO, default=None,
736
                         help="Set the drained flag on the node")
737

    
738
ALLOCATABLE_OPT = cli_option("--allocatable", dest="allocatable",
739
                             choices=_YESNO, default=None, metavar=_YORNO,
740
                             help="Set the allocatable flag on a volume")
741

    
742
NOLVM_STORAGE_OPT = cli_option("--no-lvm-storage", dest="lvm_storage",
743
                               help="Disable support for lvm based instances"
744
                               " (cluster-wide)",
745
                               action="store_false", default=True)
746

    
747
ENABLED_HV_OPT = cli_option("--enabled-hypervisors",
748
                            dest="enabled_hypervisors",
749
                            help="Comma-separated list of hypervisors",
750
                            type="string", default=None)
751

    
752
NIC_PARAMS_OPT = cli_option("-N", "--nic-parameters", dest="nicparams",
753
                            type="keyval", default={},
754
                            help="NIC parameters")
755

    
756
CP_SIZE_OPT = cli_option("-C", "--candidate-pool-size", default=None,
757
                         dest="candidate_pool_size", type="int",
758
                         help="Set the candidate pool size")
759

    
760
VG_NAME_OPT = cli_option("-g", "--vg-name", dest="vg_name",
761
                         help="Enables LVM and specifies the volume group"
762
                         " name (cluster-wide) for disk allocation [xenvg]",
763
                         metavar="VG", default=None)
764

    
765
YES_DOIT_OPT = cli_option("--yes-do-it", dest="yes_do_it",
766
                          help="Destroy cluster", action="store_true")
767

    
768
NOVOTING_OPT = cli_option("--no-voting", dest="no_voting",
769
                          help="Skip node agreement check (dangerous)",
770
                          action="store_true", default=False)
771

    
772
MAC_PREFIX_OPT = cli_option("-m", "--mac-prefix", dest="mac_prefix",
773
                            help="Specify the mac prefix for the instance IP"
774
                            " addresses, in the format XX:XX:XX",
775
                            metavar="PREFIX",
776
                            default=None)
777

    
778
MASTER_NETDEV_OPT = cli_option("--master-netdev", dest="master_netdev",
779
                               help="Specify the node interface (cluster-wide)"
780
                               " on which the master IP address will be added "
781
                               " [%s]" % constants.DEFAULT_BRIDGE,
782
                               metavar="NETDEV",
783
                               default=constants.DEFAULT_BRIDGE)
784

    
785

    
786
GLOBAL_FILEDIR_OPT = cli_option("--file-storage-dir", dest="file_storage_dir",
787
                                help="Specify the default directory (cluster-"
788
                                "wide) for storing the file-based disks [%s]" %
789
                                constants.DEFAULT_FILE_STORAGE_DIR,
790
                                metavar="DIR",
791
                                default=constants.DEFAULT_FILE_STORAGE_DIR)
792

    
793
NOMODIFY_ETCHOSTS_OPT = cli_option("--no-etc-hosts", dest="modify_etc_hosts",
794
                                   help="Don't modify /etc/hosts",
795
                                   action="store_false", default=True)
796

    
797
ERROR_CODES_OPT = cli_option("--error-codes", dest="error_codes",
798
                             help="Enable parseable error messages",
799
                             action="store_true", default=False)
800

    
801
NONPLUS1_OPT = cli_option("--no-nplus1-mem", dest="skip_nplusone_mem",
802
                          help="Skip N+1 memory redundancy tests",
803
                          action="store_true", default=False)
804

    
805
REBOOT_TYPE_OPT = cli_option("-t", "--type", dest="reboot_type",
806
                             help="Type of reboot: soft/hard/full",
807
                             default=constants.INSTANCE_REBOOT_HARD,
808
                             metavar="<REBOOT>",
809
                             choices=list(constants.REBOOT_TYPES))
810

    
811
IGNORE_SECONDARIES_OPT = cli_option("--ignore-secondaries",
812
                                    dest="ignore_secondaries",
813
                                    default=False, action="store_true",
814
                                    help="Ignore errors from secondaries")
815

    
816
NOSHUTDOWN_OPT = cli_option("--noshutdown", dest="shutdown",
817
                            action="store_false", default=True,
818
                            help="Don't shutdown the instance (unsafe)")
819

    
820
TIMEOUT_OPT = cli_option("--timeout", dest="timeout", type="int",
821
                         default=constants.DEFAULT_SHUTDOWN_TIMEOUT,
822
                         help="Maximum time to wait")
823

    
824
SHUTDOWN_TIMEOUT_OPT = cli_option("--shutdown-timeout",
825
                         dest="shutdown_timeout", type="int",
826
                         default=constants.DEFAULT_SHUTDOWN_TIMEOUT,
827
                         help="Maximum time to wait for instance shutdown")
828

    
829

    
830
def _ParseArgs(argv, commands, aliases):
831
  """Parser for the command line arguments.
832

833
  This function parses the arguments and returns the function which
834
  must be executed together with its (modified) arguments.
835

836
  @param argv: the command line
837
  @param commands: dictionary with special contents, see the design
838
      doc for cmdline handling
839
  @param aliases: dictionary with command aliases {'alias': 'target, ...}
840

841
  """
842
  if len(argv) == 0:
843
    binary = "<command>"
844
  else:
845
    binary = argv[0].split("/")[-1]
846

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

    
853
  if len(argv) < 2 or not (argv[1] in commands or
854
                           argv[1] in aliases):
855
    # let's do a nice thing
856
    sortedcmds = commands.keys()
857
    sortedcmds.sort()
858

    
859
    ToStdout("Usage: %s {command} [options...] [argument...]", binary)
860
    ToStdout("%s <command> --help to see details, or man %s", binary, binary)
861
    ToStdout("")
862

    
863
    # compute the max line length for cmd + usage
864
    mlen = max([len(" %s" % cmd) for cmd in commands])
865
    mlen = min(60, mlen) # should not get here...
866

    
867
    # and format a nice command list
868
    ToStdout("Commands:")
869
    for cmd in sortedcmds:
870
      cmdstr = " %s" % (cmd,)
871
      help_text = commands[cmd][4]
872
      help_lines = textwrap.wrap(help_text, 79 - 3 - mlen)
873
      ToStdout("%-*s - %s", mlen, cmdstr, help_lines.pop(0))
874
      for line in help_lines:
875
        ToStdout("%-*s   %s", mlen, "", line)
876

    
877
    ToStdout("")
878

    
879
    return None, None, None
880

    
881
  # get command, unalias it, and look it up in commands
882
  cmd = argv.pop(1)
883
  if cmd in aliases:
884
    if cmd in commands:
885
      raise errors.ProgrammerError("Alias '%s' overrides an existing"
886
                                   " command" % cmd)
887

    
888
    if aliases[cmd] not in commands:
889
      raise errors.ProgrammerError("Alias '%s' maps to non-existing"
890
                                   " command '%s'" % (cmd, aliases[cmd]))
891

    
892
    cmd = aliases[cmd]
893

    
894
  func, args_def, parser_opts, usage, description = commands[cmd]
895
  parser = OptionParser(option_list=parser_opts + [_DRY_RUN_OPT, DEBUG_OPT],
896
                        description=description,
897
                        formatter=TitledHelpFormatter(),
898
                        usage="%%prog %s %s" % (cmd, usage))
899
  parser.disable_interspersed_args()
900
  options, args = parser.parse_args()
901

    
902
  if not _CheckArguments(cmd, args_def, args):
903
    return None, None, None
904

    
905
  return func, options, args
906

    
907

    
908
def _CheckArguments(cmd, args_def, args):
909
  """Verifies the arguments using the argument definition.
910

911
  Algorithm:
912

913
    1. Abort with error if values specified by user but none expected.
914

915
    1. For each argument in definition
916

917
      1. Keep running count of minimum number of values (min_count)
918
      1. Keep running count of maximum number of values (max_count)
919
      1. If it has an unlimited number of values
920

921
        1. Abort with error if it's not the last argument in the definition
922

923
    1. If last argument has limited number of values
924

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

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

929
  """
930
  if args and not args_def:
931
    ToStderr("Error: Command %s expects no arguments", cmd)
932
    return False
933

    
934
  min_count = None
935
  max_count = None
936
  check_max = None
937

    
938
  last_idx = len(args_def) - 1
939

    
940
  for idx, arg in enumerate(args_def):
941
    if min_count is None:
942
      min_count = arg.min
943
    elif arg.min is not None:
944
      min_count += arg.min
945

    
946
    if max_count is None:
947
      max_count = arg.max
948
    elif arg.max is not None:
949
      max_count += arg.max
950

    
951
    if idx == last_idx:
952
      check_max = (arg.max is not None)
953

    
954
    elif arg.max is None:
955
      raise errors.ProgrammerError("Only the last argument can have max=None")
956

    
957
  if check_max:
958
    # Command with exact number of arguments
959
    if (min_count is not None and max_count is not None and
960
        min_count == max_count and len(args) != min_count):
961
      ToStderr("Error: Command %s expects %d argument(s)", cmd, min_count)
962
      return False
963

    
964
    # Command with limited number of arguments
965
    if max_count is not None and len(args) > max_count:
966
      ToStderr("Error: Command %s expects only %d argument(s)",
967
               cmd, max_count)
968
      return False
969

    
970
  # Command with some required arguments
971
  if min_count is not None and len(args) < min_count:
972
    ToStderr("Error: Command %s expects at least %d argument(s)",
973
             cmd, min_count)
974
    return False
975

    
976
  return True
977

    
978

    
979
def SplitNodeOption(value):
980
  """Splits the value of a --node option.
981

982
  """
983
  if value and ':' in value:
984
    return value.split(':', 1)
985
  else:
986
    return (value, None)
987

    
988

    
989
def CalculateOSNames(os_name, os_variants):
990
  """Calculates all the names an OS can be called, according to its variants.
991

992
  @type os_name: string
993
  @param os_name: base name of the os
994
  @type os_variants: list or None
995
  @param os_variants: list of supported variants
996
  @rtype: list
997
  @return: list of valid names
998

999
  """
1000
  if os_variants:
1001
    return ['%s+%s' % (os_name, v) for v in os_variants]
1002
  else:
1003
    return [os_name]
1004

    
1005

    
1006
def UsesRPC(fn):
1007
  def wrapper(*args, **kwargs):
1008
    rpc.Init()
1009
    try:
1010
      return fn(*args, **kwargs)
1011
    finally:
1012
      rpc.Shutdown()
1013
  return wrapper
1014

    
1015

    
1016
def AskUser(text, choices=None):
1017
  """Ask the user a question.
1018

1019
  @param text: the question to ask
1020

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

1026
  @return: one of the return values from the choices list; if input is
1027
      not possible (i.e. not running with a tty, we return the last
1028
      entry from the list
1029

1030
  """
1031
  if choices is None:
1032
    choices = [('y', True, 'Perform the operation'),
1033
               ('n', False, 'Do not perform the operation')]
1034
  if not choices or not isinstance(choices, list):
1035
    raise errors.ProgrammerError("Invalid choices argument to AskUser")
1036
  for entry in choices:
1037
    if not isinstance(entry, tuple) or len(entry) < 3 or entry[0] == '?':
1038
      raise errors.ProgrammerError("Invalid choices element to AskUser")
1039

    
1040
  answer = choices[-1][1]
1041
  new_text = []
1042
  for line in text.splitlines():
1043
    new_text.append(textwrap.fill(line, 70, replace_whitespace=False))
1044
  text = "\n".join(new_text)
1045
  try:
1046
    f = file("/dev/tty", "a+")
1047
  except IOError:
1048
    return answer
1049
  try:
1050
    chars = [entry[0] for entry in choices]
1051
    chars[-1] = "[%s]" % chars[-1]
1052
    chars.append('?')
1053
    maps = dict([(entry[0], entry[1]) for entry in choices])
1054
    while True:
1055
      f.write(text)
1056
      f.write('\n')
1057
      f.write("/".join(chars))
1058
      f.write(": ")
1059
      line = f.readline(2).strip().lower()
1060
      if line in maps:
1061
        answer = maps[line]
1062
        break
1063
      elif line == '?':
1064
        for entry in choices:
1065
          f.write(" %s - %s\n" % (entry[0], entry[2]))
1066
        f.write("\n")
1067
        continue
1068
  finally:
1069
    f.close()
1070
  return answer
1071

    
1072

    
1073
class JobSubmittedException(Exception):
1074
  """Job was submitted, client should exit.
1075

1076
  This exception has one argument, the ID of the job that was
1077
  submitted. The handler should print this ID.
1078

1079
  This is not an error, just a structured way to exit from clients.
1080

1081
  """
1082

    
1083

    
1084
def SendJob(ops, cl=None):
1085
  """Function to submit an opcode without waiting for the results.
1086

1087
  @type ops: list
1088
  @param ops: list of opcodes
1089
  @type cl: luxi.Client
1090
  @param cl: the luxi client to use for communicating with the master;
1091
             if None, a new client will be created
1092

1093
  """
1094
  if cl is None:
1095
    cl = GetClient()
1096

    
1097
  job_id = cl.SubmitJob(ops)
1098

    
1099
  return job_id
1100

    
1101

    
1102
def PollJob(job_id, cl=None, feedback_fn=None):
1103
  """Function to poll for the result of a job.
1104

1105
  @type job_id: job identified
1106
  @param job_id: the job to poll for results
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
  prev_job_info = None
1116
  prev_logmsg_serial = None
1117

    
1118
  while True:
1119
    result = cl.WaitForJobChange(job_id, ["status"], prev_job_info,
1120
                                 prev_logmsg_serial)
1121
    if not result:
1122
      # job not found, go away!
1123
      raise errors.JobLost("Job with id %s lost" % job_id)
1124

    
1125
    # Split result, a tuple of (field values, log entries)
1126
    (job_info, log_entries) = result
1127
    (status, ) = job_info
1128

    
1129
    if log_entries:
1130
      for log_entry in log_entries:
1131
        (serial, timestamp, _, message) = log_entry
1132
        if callable(feedback_fn):
1133
          feedback_fn(log_entry[1:])
1134
        else:
1135
          encoded = utils.SafeEncode(message)
1136
          ToStdout("%s %s", time.ctime(utils.MergeTime(timestamp)), encoded)
1137
        prev_logmsg_serial = max(prev_logmsg_serial, serial)
1138

    
1139
    # TODO: Handle canceled and archived jobs
1140
    elif status in (constants.JOB_STATUS_SUCCESS,
1141
                    constants.JOB_STATUS_ERROR,
1142
                    constants.JOB_STATUS_CANCELING,
1143
                    constants.JOB_STATUS_CANCELED):
1144
      break
1145

    
1146
    prev_job_info = job_info
1147

    
1148
  jobs = cl.QueryJobs([job_id], ["status", "opstatus", "opresult"])
1149
  if not jobs:
1150
    raise errors.JobLost("Job with id %s lost" % job_id)
1151

    
1152
  status, opstatus, result = jobs[0]
1153
  if status == constants.JOB_STATUS_SUCCESS:
1154
    return result
1155
  elif status in (constants.JOB_STATUS_CANCELING,
1156
                  constants.JOB_STATUS_CANCELED):
1157
    raise errors.OpExecError("Job was canceled")
1158
  else:
1159
    has_ok = False
1160
    for idx, (status, msg) in enumerate(zip(opstatus, result)):
1161
      if status == constants.OP_STATUS_SUCCESS:
1162
        has_ok = True
1163
      elif status == constants.OP_STATUS_ERROR:
1164
        errors.MaybeRaise(msg)
1165
        if has_ok:
1166
          raise errors.OpExecError("partial failure (opcode %d): %s" %
1167
                                   (idx, msg))
1168
        else:
1169
          raise errors.OpExecError(str(msg))
1170
    # default failure mode
1171
    raise errors.OpExecError(result)
1172

    
1173

    
1174
def SubmitOpCode(op, cl=None, feedback_fn=None):
1175
  """Legacy function to submit an opcode.
1176

1177
  This is just a simple wrapper over the construction of the processor
1178
  instance. It should be extended to better handle feedback and
1179
  interaction functions.
1180

1181
  """
1182
  if cl is None:
1183
    cl = GetClient()
1184

    
1185
  job_id = SendJob([op], cl)
1186

    
1187
  op_results = PollJob(job_id, cl=cl, feedback_fn=feedback_fn)
1188

    
1189
  return op_results[0]
1190

    
1191

    
1192
def SubmitOrSend(op, opts, cl=None, feedback_fn=None):
1193
  """Wrapper around SubmitOpCode or SendJob.
1194

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

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

1202
  """
1203
  if opts and opts.dry_run:
1204
    op.dry_run = opts.dry_run
1205
  if opts and opts.submit_only:
1206
    job_id = SendJob([op], cl=cl)
1207
    raise JobSubmittedException(job_id)
1208
  else:
1209
    return SubmitOpCode(op, cl=cl, feedback_fn=feedback_fn)
1210

    
1211

    
1212
def GetClient():
1213
  # TODO: Cache object?
1214
  try:
1215
    client = luxi.Client()
1216
  except luxi.NoMasterError:
1217
    master, myself = ssconf.GetMasterAndMyself()
1218
    if master != myself:
1219
      raise errors.OpPrereqError("This is not the master node, please connect"
1220
                                 " to node '%s' and rerun the command" %
1221
                                 master)
1222
    else:
1223
      raise
1224
  return client
1225

    
1226

    
1227
def FormatError(err):
1228
  """Return a formatted error message for a given error.
1229

1230
  This function takes an exception instance and returns a tuple
1231
  consisting of two values: first, the recommended exit code, and
1232
  second, a string describing the error message (not
1233
  newline-terminated).
1234

1235
  """
1236
  retcode = 1
1237
  obuf = StringIO()
1238
  msg = str(err)
1239
  if isinstance(err, errors.ConfigurationError):
1240
    txt = "Corrupt configuration file: %s" % msg
1241
    logging.error(txt)
1242
    obuf.write(txt + "\n")
1243
    obuf.write("Aborting.")
1244
    retcode = 2
1245
  elif isinstance(err, errors.HooksAbort):
1246
    obuf.write("Failure: hooks execution failed:\n")
1247
    for node, script, out in err.args[0]:
1248
      if out:
1249
        obuf.write("  node: %s, script: %s, output: %s\n" %
1250
                   (node, script, out))
1251
      else:
1252
        obuf.write("  node: %s, script: %s (no output)\n" %
1253
                   (node, script))
1254
  elif isinstance(err, errors.HooksFailure):
1255
    obuf.write("Failure: hooks general failure: %s" % msg)
1256
  elif isinstance(err, errors.ResolverError):
1257
    this_host = utils.HostInfo.SysName()
1258
    if err.args[0] == this_host:
1259
      msg = "Failure: can't resolve my own hostname ('%s')"
1260
    else:
1261
      msg = "Failure: can't resolve hostname '%s'"
1262
    obuf.write(msg % err.args[0])
1263
  elif isinstance(err, errors.OpPrereqError):
1264
    obuf.write("Failure: prerequisites not met for this"
1265
               " operation:\n%s" % msg)
1266
  elif isinstance(err, errors.OpExecError):
1267
    obuf.write("Failure: command execution error:\n%s" % msg)
1268
  elif isinstance(err, errors.TagError):
1269
    obuf.write("Failure: invalid tag(s) given:\n%s" % msg)
1270
  elif isinstance(err, errors.JobQueueDrainError):
1271
    obuf.write("Failure: the job queue is marked for drain and doesn't"
1272
               " accept new requests\n")
1273
  elif isinstance(err, errors.JobQueueFull):
1274
    obuf.write("Failure: the job queue is full and doesn't accept new"
1275
               " job submissions until old jobs are archived\n")
1276
  elif isinstance(err, errors.TypeEnforcementError):
1277
    obuf.write("Parameter Error: %s" % msg)
1278
  elif isinstance(err, errors.ParameterError):
1279
    obuf.write("Failure: unknown/wrong parameter name '%s'" % msg)
1280
  elif isinstance(err, errors.GenericError):
1281
    obuf.write("Unhandled Ganeti error: %s" % msg)
1282
  elif isinstance(err, luxi.NoMasterError):
1283
    obuf.write("Cannot communicate with the master daemon.\nIs it running"
1284
               " and listening for connections?")
1285
  elif isinstance(err, luxi.TimeoutError):
1286
    obuf.write("Timeout while talking to the master daemon. Error:\n"
1287
               "%s" % msg)
1288
  elif isinstance(err, luxi.ProtocolError):
1289
    obuf.write("Unhandled protocol error while talking to the master daemon:\n"
1290
               "%s" % msg)
1291
  elif isinstance(err, JobSubmittedException):
1292
    obuf.write("JobID: %s\n" % err.args[0])
1293
    retcode = 0
1294
  else:
1295
    obuf.write("Unhandled exception: %s" % msg)
1296
  return retcode, obuf.getvalue().rstrip('\n')
1297

    
1298

    
1299
def GenericMain(commands, override=None, aliases=None):
1300
  """Generic main function for all the gnt-* commands.
1301

1302
  Arguments:
1303
    - commands: a dictionary with a special structure, see the design doc
1304
                for command line handling.
1305
    - override: if not None, we expect a dictionary with keys that will
1306
                override command line options; this can be used to pass
1307
                options from the scripts to generic functions
1308
    - aliases: dictionary with command aliases {'alias': 'target, ...}
1309

1310
  """
1311
  # save the program name and the entire command line for later logging
1312
  if sys.argv:
1313
    binary = os.path.basename(sys.argv[0]) or sys.argv[0]
1314
    if len(sys.argv) >= 2:
1315
      binary += " " + sys.argv[1]
1316
      old_cmdline = " ".join(sys.argv[2:])
1317
    else:
1318
      old_cmdline = ""
1319
  else:
1320
    binary = "<unknown program>"
1321
    old_cmdline = ""
1322

    
1323
  if aliases is None:
1324
    aliases = {}
1325

    
1326
  try:
1327
    func, options, args = _ParseArgs(sys.argv, commands, aliases)
1328
  except errors.ParameterError, err:
1329
    result, err_msg = FormatError(err)
1330
    ToStderr(err_msg)
1331
    return 1
1332

    
1333
  if func is None: # parse error
1334
    return 1
1335

    
1336
  if override is not None:
1337
    for key, val in override.iteritems():
1338
      setattr(options, key, val)
1339

    
1340
  utils.SetupLogging(constants.LOG_COMMANDS, debug=options.debug,
1341
                     stderr_logging=True, program=binary)
1342

    
1343
  if old_cmdline:
1344
    logging.info("run with arguments '%s'", old_cmdline)
1345
  else:
1346
    logging.info("run with no arguments")
1347

    
1348
  try:
1349
    result = func(options, args)
1350
  except (errors.GenericError, luxi.ProtocolError,
1351
          JobSubmittedException), err:
1352
    result, err_msg = FormatError(err)
1353
    logging.exception("Error during command processing")
1354
    ToStderr(err_msg)
1355

    
1356
  return result
1357

    
1358

    
1359
def GenericInstanceCreate(mode, opts, args):
1360
  """Add an instance to the cluster via either creation or import.
1361

1362
  @param mode: constants.INSTANCE_CREATE or constants.INSTANCE_IMPORT
1363
  @param opts: the command line options selected by the user
1364
  @type args: list
1365
  @param args: should contain only one element, the new instance name
1366
  @rtype: int
1367
  @return: the desired exit code
1368

1369
  """
1370
  instance = args[0]
1371

    
1372
  (pnode, snode) = SplitNodeOption(opts.node)
1373

    
1374
  hypervisor = None
1375
  hvparams = {}
1376
  if opts.hypervisor:
1377
    hypervisor, hvparams = opts.hypervisor
1378

    
1379
  if opts.nics:
1380
    try:
1381
      nic_max = max(int(nidx[0])+1 for nidx in opts.nics)
1382
    except ValueError, err:
1383
      raise errors.OpPrereqError("Invalid NIC index passed: %s" % str(err))
1384
    nics = [{}] * nic_max
1385
    for nidx, ndict in opts.nics:
1386
      nidx = int(nidx)
1387
      if not isinstance(ndict, dict):
1388
        msg = "Invalid nic/%d value: expected dict, got %s" % (nidx, ndict)
1389
        raise errors.OpPrereqError(msg)
1390
      nics[nidx] = ndict
1391
  elif opts.no_nics:
1392
    # no nics
1393
    nics = []
1394
  else:
1395
    # default of one nic, all auto
1396
    nics = [{}]
1397

    
1398
  if opts.disk_template == constants.DT_DISKLESS:
1399
    if opts.disks or opts.sd_size is not None:
1400
      raise errors.OpPrereqError("Diskless instance but disk"
1401
                                 " information passed")
1402
    disks = []
1403
  else:
1404
    if not opts.disks and not opts.sd_size:
1405
      raise errors.OpPrereqError("No disk information specified")
1406
    if opts.disks and opts.sd_size is not None:
1407
      raise errors.OpPrereqError("Please use either the '--disk' or"
1408
                                 " '-s' option")
1409
    if opts.sd_size is not None:
1410
      opts.disks = [(0, {"size": opts.sd_size})]
1411
    try:
1412
      disk_max = max(int(didx[0])+1 for didx in opts.disks)
1413
    except ValueError, err:
1414
      raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err))
1415
    disks = [{}] * disk_max
1416
    for didx, ddict in opts.disks:
1417
      didx = int(didx)
1418
      if not isinstance(ddict, dict):
1419
        msg = "Invalid disk/%d value: expected dict, got %s" % (didx, ddict)
1420
        raise errors.OpPrereqError(msg)
1421
      elif "size" not in ddict:
1422
        raise errors.OpPrereqError("Missing size for disk %d" % didx)
1423
      try:
1424
        ddict["size"] = utils.ParseUnit(ddict["size"])
1425
      except ValueError, err:
1426
        raise errors.OpPrereqError("Invalid disk size for disk %d: %s" %
1427
                                   (didx, err))
1428
      disks[didx] = ddict
1429

    
1430
  utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_TYPES)
1431
  utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
1432

    
1433
  if mode == constants.INSTANCE_CREATE:
1434
    start = opts.start
1435
    os_type = opts.os
1436
    src_node = None
1437
    src_path = None
1438
  elif mode == constants.INSTANCE_IMPORT:
1439
    start = False
1440
    os_type = None
1441
    src_node = opts.src_node
1442
    src_path = opts.src_dir
1443
  else:
1444
    raise errors.ProgrammerError("Invalid creation mode %s" % mode)
1445

    
1446
  op = opcodes.OpCreateInstance(instance_name=instance,
1447
                                disks=disks,
1448
                                disk_template=opts.disk_template,
1449
                                nics=nics,
1450
                                pnode=pnode, snode=snode,
1451
                                ip_check=opts.ip_check,
1452
                                wait_for_sync=opts.wait_for_sync,
1453
                                file_storage_dir=opts.file_storage_dir,
1454
                                file_driver=opts.file_driver,
1455
                                iallocator=opts.iallocator,
1456
                                hypervisor=hypervisor,
1457
                                hvparams=hvparams,
1458
                                beparams=opts.beparams,
1459
                                mode=mode,
1460
                                start=start,
1461
                                os_type=os_type,
1462
                                src_node=src_node,
1463
                                src_path=src_path)
1464

    
1465
  SubmitOrSend(op, opts)
1466
  return 0
1467

    
1468

    
1469
def GenerateTable(headers, fields, separator, data,
1470
                  numfields=None, unitfields=None,
1471
                  units=None):
1472
  """Prints a table with headers and different fields.
1473

1474
  @type headers: dict
1475
  @param headers: dictionary mapping field names to headers for
1476
      the table
1477
  @type fields: list
1478
  @param fields: the field names corresponding to each row in
1479
      the data field
1480
  @param separator: the separator to be used; if this is None,
1481
      the default 'smart' algorithm is used which computes optimal
1482
      field width, otherwise just the separator is used between
1483
      each field
1484
  @type data: list
1485
  @param data: a list of lists, each sublist being one row to be output
1486
  @type numfields: list
1487
  @param numfields: a list with the fields that hold numeric
1488
      values and thus should be right-aligned
1489
  @type unitfields: list
1490
  @param unitfields: a list with the fields that hold numeric
1491
      values that should be formatted with the units field
1492
  @type units: string or None
1493
  @param units: the units we should use for formatting, or None for
1494
      automatic choice (human-readable for non-separator usage, otherwise
1495
      megabytes); this is a one-letter string
1496

1497
  """
1498
  if units is None:
1499
    if separator:
1500
      units = "m"
1501
    else:
1502
      units = "h"
1503

    
1504
  if numfields is None:
1505
    numfields = []
1506
  if unitfields is None:
1507
    unitfields = []
1508

    
1509
  numfields = utils.FieldSet(*numfields)
1510
  unitfields = utils.FieldSet(*unitfields)
1511

    
1512
  format_fields = []
1513
  for field in fields:
1514
    if headers and field not in headers:
1515
      # TODO: handle better unknown fields (either revert to old
1516
      # style of raising exception, or deal more intelligently with
1517
      # variable fields)
1518
      headers[field] = field
1519
    if separator is not None:
1520
      format_fields.append("%s")
1521
    elif numfields.Matches(field):
1522
      format_fields.append("%*s")
1523
    else:
1524
      format_fields.append("%-*s")
1525

    
1526
  if separator is None:
1527
    mlens = [0 for name in fields]
1528
    format = ' '.join(format_fields)
1529
  else:
1530
    format = separator.replace("%", "%%").join(format_fields)
1531

    
1532
  for row in data:
1533
    if row is None:
1534
      continue
1535
    for idx, val in enumerate(row):
1536
      if unitfields.Matches(fields[idx]):
1537
        try:
1538
          val = int(val)
1539
        except ValueError:
1540
          pass
1541
        else:
1542
          val = row[idx] = utils.FormatUnit(val, units)
1543
      val = row[idx] = str(val)
1544
      if separator is None:
1545
        mlens[idx] = max(mlens[idx], len(val))
1546

    
1547
  result = []
1548
  if headers:
1549
    args = []
1550
    for idx, name in enumerate(fields):
1551
      hdr = headers[name]
1552
      if separator is None:
1553
        mlens[idx] = max(mlens[idx], len(hdr))
1554
        args.append(mlens[idx])
1555
      args.append(hdr)
1556
    result.append(format % tuple(args))
1557

    
1558
  for line in data:
1559
    args = []
1560
    if line is None:
1561
      line = ['-' for _ in fields]
1562
    for idx in range(len(fields)):
1563
      if separator is None:
1564
        args.append(mlens[idx])
1565
      args.append(line[idx])
1566
    result.append(format % tuple(args))
1567

    
1568
  return result
1569

    
1570

    
1571
def FormatTimestamp(ts):
1572
  """Formats a given timestamp.
1573

1574
  @type ts: timestamp
1575
  @param ts: a timeval-type timestamp, a tuple of seconds and microseconds
1576

1577
  @rtype: string
1578
  @return: a string with the formatted timestamp
1579

1580
  """
1581
  if not isinstance (ts, (tuple, list)) or len(ts) != 2:
1582
    return '?'
1583
  sec, usec = ts
1584
  return time.strftime("%F %T", time.localtime(sec)) + ".%06d" % usec
1585

    
1586

    
1587
def ParseTimespec(value):
1588
  """Parse a time specification.
1589

1590
  The following suffixed will be recognized:
1591

1592
    - s: seconds
1593
    - m: minutes
1594
    - h: hours
1595
    - d: day
1596
    - w: weeks
1597

1598
  Without any suffix, the value will be taken to be in seconds.
1599

1600
  """
1601
  value = str(value)
1602
  if not value:
1603
    raise errors.OpPrereqError("Empty time specification passed")
1604
  suffix_map = {
1605
    's': 1,
1606
    'm': 60,
1607
    'h': 3600,
1608
    'd': 86400,
1609
    'w': 604800,
1610
    }
1611
  if value[-1] not in suffix_map:
1612
    try:
1613
      value = int(value)
1614
    except ValueError:
1615
      raise errors.OpPrereqError("Invalid time specification '%s'" % value)
1616
  else:
1617
    multiplier = suffix_map[value[-1]]
1618
    value = value[:-1]
1619
    if not value: # no data left after stripping the suffix
1620
      raise errors.OpPrereqError("Invalid time specification (only"
1621
                                 " suffix passed)")
1622
    try:
1623
      value = int(value) * multiplier
1624
    except ValueError:
1625
      raise errors.OpPrereqError("Invalid time specification '%s'" % value)
1626
  return value
1627

    
1628

    
1629
def GetOnlineNodes(nodes, cl=None, nowarn=False):
1630
  """Returns the names of online nodes.
1631

1632
  This function will also log a warning on stderr with the names of
1633
  the online nodes.
1634

1635
  @param nodes: if not empty, use only this subset of nodes (minus the
1636
      offline ones)
1637
  @param cl: if not None, luxi client to use
1638
  @type nowarn: boolean
1639
  @param nowarn: by default, this function will output a note with the
1640
      offline nodes that are skipped; if this parameter is True the
1641
      note is not displayed
1642

1643
  """
1644
  if cl is None:
1645
    cl = GetClient()
1646

    
1647
  result = cl.QueryNodes(names=nodes, fields=["name", "offline"],
1648
                         use_locking=False)
1649
  offline = [row[0] for row in result if row[1]]
1650
  if offline and not nowarn:
1651
    ToStderr("Note: skipping offline node(s): %s" % ", ".join(offline))
1652
  return [row[0] for row in result if not row[1]]
1653

    
1654

    
1655
def _ToStream(stream, txt, *args):
1656
  """Write a message to a stream, bypassing the logging system
1657

1658
  @type stream: file object
1659
  @param stream: the file to which we should write
1660
  @type txt: str
1661
  @param txt: the message
1662

1663
  """
1664
  if args:
1665
    args = tuple(args)
1666
    stream.write(txt % args)
1667
  else:
1668
    stream.write(txt)
1669
  stream.write('\n')
1670
  stream.flush()
1671

    
1672

    
1673
def ToStdout(txt, *args):
1674
  """Write a message to stdout only, bypassing the logging system
1675

1676
  This is just a wrapper over _ToStream.
1677

1678
  @type txt: str
1679
  @param txt: the message
1680

1681
  """
1682
  _ToStream(sys.stdout, txt, *args)
1683

    
1684

    
1685
def ToStderr(txt, *args):
1686
  """Write a message to stderr only, bypassing the logging system
1687

1688
  This is just a wrapper over _ToStream.
1689

1690
  @type txt: str
1691
  @param txt: the message
1692

1693
  """
1694
  _ToStream(sys.stderr, txt, *args)
1695

    
1696

    
1697
class JobExecutor(object):
1698
  """Class which manages the submission and execution of multiple jobs.
1699

1700
  Note that instances of this class should not be reused between
1701
  GetResults() calls.
1702

1703
  """
1704
  def __init__(self, cl=None, verbose=True):
1705
    self.queue = []
1706
    if cl is None:
1707
      cl = GetClient()
1708
    self.cl = cl
1709
    self.verbose = verbose
1710
    self.jobs = []
1711

    
1712
  def QueueJob(self, name, *ops):
1713
    """Record a job for later submit.
1714

1715
    @type name: string
1716
    @param name: a description of the job, will be used in WaitJobSet
1717
    """
1718
    self.queue.append((name, ops))
1719

    
1720
  def SubmitPending(self):
1721
    """Submit all pending jobs.
1722

1723
    """
1724
    results = self.cl.SubmitManyJobs([row[1] for row in self.queue])
1725
    for ((status, data), (name, _)) in zip(results, self.queue):
1726
      self.jobs.append((status, data, name))
1727

    
1728
  def GetResults(self):
1729
    """Wait for and return the results of all jobs.
1730

1731
    @rtype: list
1732
    @return: list of tuples (success, job results), in the same order
1733
        as the submitted jobs; if a job has failed, instead of the result
1734
        there will be the error message
1735

1736
    """
1737
    if not self.jobs:
1738
      self.SubmitPending()
1739
    results = []
1740
    if self.verbose:
1741
      ok_jobs = [row[1] for row in self.jobs if row[0]]
1742
      if ok_jobs:
1743
        ToStdout("Submitted jobs %s", ", ".join(ok_jobs))
1744
    for submit_status, jid, name in self.jobs:
1745
      if not submit_status:
1746
        ToStderr("Failed to submit job for %s: %s", name, jid)
1747
        results.append((False, jid))
1748
        continue
1749
      if self.verbose:
1750
        ToStdout("Waiting for job %s for %s...", jid, name)
1751
      try:
1752
        job_result = PollJob(jid, cl=self.cl)
1753
        success = True
1754
      except (errors.GenericError, luxi.ProtocolError), err:
1755
        _, job_result = FormatError(err)
1756
        success = False
1757
        # the error message will always be shown, verbose or not
1758
        ToStderr("Job %s for %s has failed: %s", jid, name, job_result)
1759

    
1760
      results.append((success, job_result))
1761
    return results
1762

    
1763
  def WaitOrShow(self, wait):
1764
    """Wait for job results or only print the job IDs.
1765

1766
    @type wait: boolean
1767
    @param wait: whether to wait or not
1768

1769
    """
1770
    if wait:
1771
      return self.GetResults()
1772
    else:
1773
      if not self.jobs:
1774
        self.SubmitPending()
1775
      for status, result, name in self.jobs:
1776
        if status:
1777
          ToStdout("%s: %s", result, name)
1778
        else:
1779
          ToStderr("Failure for %s: %s", name, result)