Statistics
| Branch: | Tag: | Revision:

root / lib / cli.py @ b989b9d9

History | View | Annotate | Download (56.9 kB)

1
#
2
#
3

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

    
21

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

    
24

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

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

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

    
43

    
44
__all__ = [
45
  # Command line options
46
  "ALLOCATABLE_OPT",
47
  "ALL_OPT",
48
  "AUTO_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
  "NOMODIFY_SSH_SETUP_OPT",
88
  "NONICS_OPT",
89
  "NONLIVE_OPT",
90
  "NONPLUS1_OPT",
91
  "NOSHUTDOWN_OPT",
92
  "NOSTART_OPT",
93
  "NOSSH_KEYCHECK_OPT",
94
  "NOVOTING_OPT",
95
  "NWSYNC_OPT",
96
  "ON_PRIMARY_OPT",
97
  "ON_SECONDARY_OPT",
98
  "OFFLINE_OPT",
99
  "OS_OPT",
100
  "OS_SIZE_OPT",
101
  "READD_OPT",
102
  "REBOOT_TYPE_OPT",
103
  "SECONDARY_IP_OPT",
104
  "SELECT_OS_OPT",
105
  "SEP_OPT",
106
  "SHOWCMD_OPT",
107
  "SHUTDOWN_TIMEOUT_OPT",
108
  "SINGLE_NODE_OPT",
109
  "SRC_DIR_OPT",
110
  "SRC_NODE_OPT",
111
  "SUBMIT_OPT",
112
  "STATIC_OPT",
113
  "SYNC_OPT",
114
  "TAG_SRC_OPT",
115
  "TIMEOUT_OPT",
116
  "USEUNITS_OPT",
117
  "VERBOSE_OPT",
118
  "VG_NAME_OPT",
119
  "YES_DOIT_OPT",
120
  # Generic functions for CLI programs
121
  "GenericMain",
122
  "GenericInstanceCreate",
123
  "GetClient",
124
  "GetOnlineNodes",
125
  "JobExecutor",
126
  "JobSubmittedException",
127
  "ParseTimespec",
128
  "SubmitOpCode",
129
  "SubmitOrSend",
130
  "UsesRPC",
131
  # Formatting functions
132
  "ToStderr", "ToStdout",
133
  "FormatError",
134
  "GenerateTable",
135
  "AskUser",
136
  "FormatTimestamp",
137
  # Tags functions
138
  "ListTags",
139
  "AddTags",
140
  "RemoveTags",
141
  # command line options support infrastructure
142
  "ARGS_MANY_INSTANCES",
143
  "ARGS_MANY_NODES",
144
  "ARGS_NONE",
145
  "ARGS_ONE_INSTANCE",
146
  "ARGS_ONE_NODE",
147
  "ArgChoice",
148
  "ArgCommand",
149
  "ArgFile",
150
  "ArgHost",
151
  "ArgInstance",
152
  "ArgJobId",
153
  "ArgNode",
154
  "ArgSuggest",
155
  "ArgUnknown",
156
  "OPT_COMPL_INST_ADD_NODES",
157
  "OPT_COMPL_MANY_NODES",
158
  "OPT_COMPL_ONE_IALLOCATOR",
159
  "OPT_COMPL_ONE_INSTANCE",
160
  "OPT_COMPL_ONE_NODE",
161
  "OPT_COMPL_ONE_OS",
162
  "cli_option",
163
  "SplitNodeOption",
164
  "CalculateOSNames",
165
  ]
166

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

    
170

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

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

    
180

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

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

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

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

    
195

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

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

202
  """
203

    
204

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

208
  """
209

    
210

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

214
  """
215

    
216

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

220
  """
221

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

225
  """
226

    
227

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

231
  """
232

    
233

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

237
  """
238

    
239

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

243
  """
244

    
245

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

    
252

    
253

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

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

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

    
274

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

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

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

    
303

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

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

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

    
321

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

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

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

    
338

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

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

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

    
355

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

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

    
365

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

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

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

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

    
402

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

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

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

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

    
430

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

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

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

    
439

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

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

    
458

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

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

    
476

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

    
480

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
726

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

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

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

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

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

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

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

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

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

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

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

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

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

    
786

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

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

    
798
NOMODIFY_SSH_SETUP_OPT = cli_option("--no-ssh-init", dest="modify_ssh_setup",
799
                                    help="Don't initialize SSH keys",
800
                                    action="store_false", default=True)
801

    
802
ERROR_CODES_OPT = cli_option("--error-codes", dest="error_codes",
803
                             help="Enable parseable error messages",
804
                             action="store_true", default=False)
805

    
806
NONPLUS1_OPT = cli_option("--no-nplus1-mem", dest="skip_nplusone_mem",
807
                          help="Skip N+1 memory redundancy tests",
808
                          action="store_true", default=False)
809

    
810
REBOOT_TYPE_OPT = cli_option("-t", "--type", dest="reboot_type",
811
                             help="Type of reboot: soft/hard/full",
812
                             default=constants.INSTANCE_REBOOT_HARD,
813
                             metavar="<REBOOT>",
814
                             choices=list(constants.REBOOT_TYPES))
815

    
816
IGNORE_SECONDARIES_OPT = cli_option("--ignore-secondaries",
817
                                    dest="ignore_secondaries",
818
                                    default=False, action="store_true",
819
                                    help="Ignore errors from secondaries")
820

    
821
NOSHUTDOWN_OPT = cli_option("--noshutdown", dest="shutdown",
822
                            action="store_false", default=True,
823
                            help="Don't shutdown the instance (unsafe)")
824

    
825
TIMEOUT_OPT = cli_option("--timeout", dest="timeout", type="int",
826
                         default=constants.DEFAULT_SHUTDOWN_TIMEOUT,
827
                         help="Maximum time to wait")
828

    
829
SHUTDOWN_TIMEOUT_OPT = cli_option("--shutdown-timeout",
830
                         dest="shutdown_timeout", type="int",
831
                         default=constants.DEFAULT_SHUTDOWN_TIMEOUT,
832
                         help="Maximum time to wait for instance shutdown")
833

    
834

    
835
def _ParseArgs(argv, commands, aliases):
836
  """Parser for the command line arguments.
837

838
  This function parses the arguments and returns the function which
839
  must be executed together with its (modified) arguments.
840

841
  @param argv: the command line
842
  @param commands: dictionary with special contents, see the design
843
      doc for cmdline handling
844
  @param aliases: dictionary with command aliases {'alias': 'target, ...}
845

846
  """
847
  if len(argv) == 0:
848
    binary = "<command>"
849
  else:
850
    binary = argv[0].split("/")[-1]
851

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

    
858
  if len(argv) < 2 or not (argv[1] in commands or
859
                           argv[1] in aliases):
860
    # let's do a nice thing
861
    sortedcmds = commands.keys()
862
    sortedcmds.sort()
863

    
864
    ToStdout("Usage: %s {command} [options...] [argument...]", binary)
865
    ToStdout("%s <command> --help to see details, or man %s", binary, binary)
866
    ToStdout("")
867

    
868
    # compute the max line length for cmd + usage
869
    mlen = max([len(" %s" % cmd) for cmd in commands])
870
    mlen = min(60, mlen) # should not get here...
871

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

    
882
    ToStdout("")
883

    
884
    return None, None, None
885

    
886
  # get command, unalias it, and look it up in commands
887
  cmd = argv.pop(1)
888
  if cmd in aliases:
889
    if cmd in commands:
890
      raise errors.ProgrammerError("Alias '%s' overrides an existing"
891
                                   " command" % cmd)
892

    
893
    if aliases[cmd] not in commands:
894
      raise errors.ProgrammerError("Alias '%s' maps to non-existing"
895
                                   " command '%s'" % (cmd, aliases[cmd]))
896

    
897
    cmd = aliases[cmd]
898

    
899
  func, args_def, parser_opts, usage, description = commands[cmd]
900
  parser = OptionParser(option_list=parser_opts + [_DRY_RUN_OPT, DEBUG_OPT],
901
                        description=description,
902
                        formatter=TitledHelpFormatter(),
903
                        usage="%%prog %s %s" % (cmd, usage))
904
  parser.disable_interspersed_args()
905
  options, args = parser.parse_args()
906

    
907
  if not _CheckArguments(cmd, args_def, args):
908
    return None, None, None
909

    
910
  return func, options, args
911

    
912

    
913
def _CheckArguments(cmd, args_def, args):
914
  """Verifies the arguments using the argument definition.
915

916
  Algorithm:
917

918
    1. Abort with error if values specified by user but none expected.
919

920
    1. For each argument in definition
921

922
      1. Keep running count of minimum number of values (min_count)
923
      1. Keep running count of maximum number of values (max_count)
924
      1. If it has an unlimited number of values
925

926
        1. Abort with error if it's not the last argument in the definition
927

928
    1. If last argument has limited number of values
929

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

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

934
  """
935
  if args and not args_def:
936
    ToStderr("Error: Command %s expects no arguments", cmd)
937
    return False
938

    
939
  min_count = None
940
  max_count = None
941
  check_max = None
942

    
943
  last_idx = len(args_def) - 1
944

    
945
  for idx, arg in enumerate(args_def):
946
    if min_count is None:
947
      min_count = arg.min
948
    elif arg.min is not None:
949
      min_count += arg.min
950

    
951
    if max_count is None:
952
      max_count = arg.max
953
    elif arg.max is not None:
954
      max_count += arg.max
955

    
956
    if idx == last_idx:
957
      check_max = (arg.max is not None)
958

    
959
    elif arg.max is None:
960
      raise errors.ProgrammerError("Only the last argument can have max=None")
961

    
962
  if check_max:
963
    # Command with exact number of arguments
964
    if (min_count is not None and max_count is not None and
965
        min_count == max_count and len(args) != min_count):
966
      ToStderr("Error: Command %s expects %d argument(s)", cmd, min_count)
967
      return False
968

    
969
    # Command with limited number of arguments
970
    if max_count is not None and len(args) > max_count:
971
      ToStderr("Error: Command %s expects only %d argument(s)",
972
               cmd, max_count)
973
      return False
974

    
975
  # Command with some required arguments
976
  if min_count is not None and len(args) < min_count:
977
    ToStderr("Error: Command %s expects at least %d argument(s)",
978
             cmd, min_count)
979
    return False
980

    
981
  return True
982

    
983

    
984
def SplitNodeOption(value):
985
  """Splits the value of a --node option.
986

987
  """
988
  if value and ':' in value:
989
    return value.split(':', 1)
990
  else:
991
    return (value, None)
992

    
993

    
994
def CalculateOSNames(os_name, os_variants):
995
  """Calculates all the names an OS can be called, according to its variants.
996

997
  @type os_name: string
998
  @param os_name: base name of the os
999
  @type os_variants: list or None
1000
  @param os_variants: list of supported variants
1001
  @rtype: list
1002
  @return: list of valid names
1003

1004
  """
1005
  if os_variants:
1006
    return ['%s+%s' % (os_name, v) for v in os_variants]
1007
  else:
1008
    return [os_name]
1009

    
1010

    
1011
def UsesRPC(fn):
1012
  def wrapper(*args, **kwargs):
1013
    rpc.Init()
1014
    try:
1015
      return fn(*args, **kwargs)
1016
    finally:
1017
      rpc.Shutdown()
1018
  return wrapper
1019

    
1020

    
1021
def AskUser(text, choices=None):
1022
  """Ask the user a question.
1023

1024
  @param text: the question to ask
1025

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

1031
  @return: one of the return values from the choices list; if input is
1032
      not possible (i.e. not running with a tty, we return the last
1033
      entry from the list
1034

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

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

    
1077

    
1078
class JobSubmittedException(Exception):
1079
  """Job was submitted, client should exit.
1080

1081
  This exception has one argument, the ID of the job that was
1082
  submitted. The handler should print this ID.
1083

1084
  This is not an error, just a structured way to exit from clients.
1085

1086
  """
1087

    
1088

    
1089
def SendJob(ops, cl=None):
1090
  """Function to submit an opcode without waiting for the results.
1091

1092
  @type ops: list
1093
  @param ops: list of opcodes
1094
  @type cl: luxi.Client
1095
  @param cl: the luxi client to use for communicating with the master;
1096
             if None, a new client will be created
1097

1098
  """
1099
  if cl is None:
1100
    cl = GetClient()
1101

    
1102
  job_id = cl.SubmitJob(ops)
1103

    
1104
  return job_id
1105

    
1106

    
1107
def PollJob(job_id, cl=None, feedback_fn=None):
1108
  """Function to poll for the result of a job.
1109

1110
  @type job_id: job identified
1111
  @param job_id: the job to poll for results
1112
  @type cl: luxi.Client
1113
  @param cl: the luxi client to use for communicating with the master;
1114
             if None, a new client will be created
1115

1116
  """
1117
  if cl is None:
1118
    cl = GetClient()
1119

    
1120
  prev_job_info = None
1121
  prev_logmsg_serial = None
1122

    
1123
  while True:
1124
    result = cl.WaitForJobChange(job_id, ["status"], prev_job_info,
1125
                                 prev_logmsg_serial)
1126
    if not result:
1127
      # job not found, go away!
1128
      raise errors.JobLost("Job with id %s lost" % job_id)
1129

    
1130
    # Split result, a tuple of (field values, log entries)
1131
    (job_info, log_entries) = result
1132
    (status, ) = job_info
1133

    
1134
    if log_entries:
1135
      for log_entry in log_entries:
1136
        (serial, timestamp, _, message) = log_entry
1137
        if callable(feedback_fn):
1138
          feedback_fn(log_entry[1:])
1139
        else:
1140
          encoded = utils.SafeEncode(message)
1141
          ToStdout("%s %s", time.ctime(utils.MergeTime(timestamp)), encoded)
1142
        prev_logmsg_serial = max(prev_logmsg_serial, serial)
1143

    
1144
    # TODO: Handle canceled and archived jobs
1145
    elif status in (constants.JOB_STATUS_SUCCESS,
1146
                    constants.JOB_STATUS_ERROR,
1147
                    constants.JOB_STATUS_CANCELING,
1148
                    constants.JOB_STATUS_CANCELED):
1149
      break
1150

    
1151
    prev_job_info = job_info
1152

    
1153
  jobs = cl.QueryJobs([job_id], ["status", "opstatus", "opresult"])
1154
  if not jobs:
1155
    raise errors.JobLost("Job with id %s lost" % job_id)
1156

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

    
1178

    
1179
def SubmitOpCode(op, cl=None, feedback_fn=None):
1180
  """Legacy function to submit an opcode.
1181

1182
  This is just a simple wrapper over the construction of the processor
1183
  instance. It should be extended to better handle feedback and
1184
  interaction functions.
1185

1186
  """
1187
  if cl is None:
1188
    cl = GetClient()
1189

    
1190
  job_id = SendJob([op], cl)
1191

    
1192
  op_results = PollJob(job_id, cl=cl, feedback_fn=feedback_fn)
1193

    
1194
  return op_results[0]
1195

    
1196

    
1197
def SubmitOrSend(op, opts, cl=None, feedback_fn=None):
1198
  """Wrapper around SubmitOpCode or SendJob.
1199

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

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

1207
  """
1208
  if opts and opts.dry_run:
1209
    op.dry_run = opts.dry_run
1210
  if opts and opts.submit_only:
1211
    job_id = SendJob([op], cl=cl)
1212
    raise JobSubmittedException(job_id)
1213
  else:
1214
    return SubmitOpCode(op, cl=cl, feedback_fn=feedback_fn)
1215

    
1216

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

    
1231

    
1232
def FormatError(err):
1233
  """Return a formatted error message for a given error.
1234

1235
  This function takes an exception instance and returns a tuple
1236
  consisting of two values: first, the recommended exit code, and
1237
  second, a string describing the error message (not
1238
  newline-terminated).
1239

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

    
1303

    
1304
def GenericMain(commands, override=None, aliases=None):
1305
  """Generic main function for all the gnt-* commands.
1306

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

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

    
1328
  if aliases is None:
1329
    aliases = {}
1330

    
1331
  try:
1332
    func, options, args = _ParseArgs(sys.argv, commands, aliases)
1333
  except errors.ParameterError, err:
1334
    result, err_msg = FormatError(err)
1335
    ToStderr(err_msg)
1336
    return 1
1337

    
1338
  if func is None: # parse error
1339
    return 1
1340

    
1341
  if override is not None:
1342
    for key, val in override.iteritems():
1343
      setattr(options, key, val)
1344

    
1345
  utils.SetupLogging(constants.LOG_COMMANDS, debug=options.debug,
1346
                     stderr_logging=True, program=binary)
1347

    
1348
  if old_cmdline:
1349
    logging.info("run with arguments '%s'", old_cmdline)
1350
  else:
1351
    logging.info("run with no arguments")
1352

    
1353
  try:
1354
    result = func(options, args)
1355
  except (errors.GenericError, luxi.ProtocolError,
1356
          JobSubmittedException), err:
1357
    result, err_msg = FormatError(err)
1358
    logging.exception("Error during command processing")
1359
    ToStderr(err_msg)
1360

    
1361
  return result
1362

    
1363

    
1364
def GenericInstanceCreate(mode, opts, args):
1365
  """Add an instance to the cluster via either creation or import.
1366

1367
  @param mode: constants.INSTANCE_CREATE or constants.INSTANCE_IMPORT
1368
  @param opts: the command line options selected by the user
1369
  @type args: list
1370
  @param args: should contain only one element, the new instance name
1371
  @rtype: int
1372
  @return: the desired exit code
1373

1374
  """
1375
  instance = args[0]
1376

    
1377
  (pnode, snode) = SplitNodeOption(opts.node)
1378

    
1379
  hypervisor = None
1380
  hvparams = {}
1381
  if opts.hypervisor:
1382
    hypervisor, hvparams = opts.hypervisor
1383

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

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

    
1435
  utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_TYPES)
1436
  utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
1437

    
1438
  if mode == constants.INSTANCE_CREATE:
1439
    start = opts.start
1440
    os_type = opts.os
1441
    src_node = None
1442
    src_path = None
1443
  elif mode == constants.INSTANCE_IMPORT:
1444
    start = False
1445
    os_type = None
1446
    src_node = opts.src_node
1447
    src_path = opts.src_dir
1448
  else:
1449
    raise errors.ProgrammerError("Invalid creation mode %s" % mode)
1450

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

    
1470
  SubmitOrSend(op, opts)
1471
  return 0
1472

    
1473

    
1474
def GenerateTable(headers, fields, separator, data,
1475
                  numfields=None, unitfields=None,
1476
                  units=None):
1477
  """Prints a table with headers and different fields.
1478

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

1502
  """
1503
  if units is None:
1504
    if separator:
1505
      units = "m"
1506
    else:
1507
      units = "h"
1508

    
1509
  if numfields is None:
1510
    numfields = []
1511
  if unitfields is None:
1512
    unitfields = []
1513

    
1514
  numfields = utils.FieldSet(*numfields)
1515
  unitfields = utils.FieldSet(*unitfields)
1516

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

    
1531
  if separator is None:
1532
    mlens = [0 for name in fields]
1533
    format = ' '.join(format_fields)
1534
  else:
1535
    format = separator.replace("%", "%%").join(format_fields)
1536

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

    
1552
  result = []
1553
  if headers:
1554
    args = []
1555
    for idx, name in enumerate(fields):
1556
      hdr = headers[name]
1557
      if separator is None:
1558
        mlens[idx] = max(mlens[idx], len(hdr))
1559
        args.append(mlens[idx])
1560
      args.append(hdr)
1561
    result.append(format % tuple(args))
1562

    
1563
  for line in data:
1564
    args = []
1565
    if line is None:
1566
      line = ['-' for _ in fields]
1567
    for idx in range(len(fields)):
1568
      if separator is None:
1569
        args.append(mlens[idx])
1570
      args.append(line[idx])
1571
    result.append(format % tuple(args))
1572

    
1573
  return result
1574

    
1575

    
1576
def FormatTimestamp(ts):
1577
  """Formats a given timestamp.
1578

1579
  @type ts: timestamp
1580
  @param ts: a timeval-type timestamp, a tuple of seconds and microseconds
1581

1582
  @rtype: string
1583
  @return: a string with the formatted timestamp
1584

1585
  """
1586
  if not isinstance (ts, (tuple, list)) or len(ts) != 2:
1587
    return '?'
1588
  sec, usec = ts
1589
  return time.strftime("%F %T", time.localtime(sec)) + ".%06d" % usec
1590

    
1591

    
1592
def ParseTimespec(value):
1593
  """Parse a time specification.
1594

1595
  The following suffixed will be recognized:
1596

1597
    - s: seconds
1598
    - m: minutes
1599
    - h: hours
1600
    - d: day
1601
    - w: weeks
1602

1603
  Without any suffix, the value will be taken to be in seconds.
1604

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

    
1633

    
1634
def GetOnlineNodes(nodes, cl=None, nowarn=False):
1635
  """Returns the names of online nodes.
1636

1637
  This function will also log a warning on stderr with the names of
1638
  the online nodes.
1639

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

1648
  """
1649
  if cl is None:
1650
    cl = GetClient()
1651

    
1652
  result = cl.QueryNodes(names=nodes, fields=["name", "offline"],
1653
                         use_locking=False)
1654
  offline = [row[0] for row in result if row[1]]
1655
  if offline and not nowarn:
1656
    ToStderr("Note: skipping offline node(s): %s" % ", ".join(offline))
1657
  return [row[0] for row in result if not row[1]]
1658

    
1659

    
1660
def _ToStream(stream, txt, *args):
1661
  """Write a message to a stream, bypassing the logging system
1662

1663
  @type stream: file object
1664
  @param stream: the file to which we should write
1665
  @type txt: str
1666
  @param txt: the message
1667

1668
  """
1669
  if args:
1670
    args = tuple(args)
1671
    stream.write(txt % args)
1672
  else:
1673
    stream.write(txt)
1674
  stream.write('\n')
1675
  stream.flush()
1676

    
1677

    
1678
def ToStdout(txt, *args):
1679
  """Write a message to stdout only, bypassing the logging system
1680

1681
  This is just a wrapper over _ToStream.
1682

1683
  @type txt: str
1684
  @param txt: the message
1685

1686
  """
1687
  _ToStream(sys.stdout, txt, *args)
1688

    
1689

    
1690
def ToStderr(txt, *args):
1691
  """Write a message to stderr only, bypassing the logging system
1692

1693
  This is just a wrapper over _ToStream.
1694

1695
  @type txt: str
1696
  @param txt: the message
1697

1698
  """
1699
  _ToStream(sys.stderr, txt, *args)
1700

    
1701

    
1702
class JobExecutor(object):
1703
  """Class which manages the submission and execution of multiple jobs.
1704

1705
  Note that instances of this class should not be reused between
1706
  GetResults() calls.
1707

1708
  """
1709
  def __init__(self, cl=None, verbose=True):
1710
    self.queue = []
1711
    if cl is None:
1712
      cl = GetClient()
1713
    self.cl = cl
1714
    self.verbose = verbose
1715
    self.jobs = []
1716

    
1717
  def QueueJob(self, name, *ops):
1718
    """Record a job for later submit.
1719

1720
    @type name: string
1721
    @param name: a description of the job, will be used in WaitJobSet
1722
    """
1723
    self.queue.append((name, ops))
1724

    
1725
  def SubmitPending(self):
1726
    """Submit all pending jobs.
1727

1728
    """
1729
    results = self.cl.SubmitManyJobs([row[1] for row in self.queue])
1730
    for ((status, data), (name, _)) in zip(results, self.queue):
1731
      self.jobs.append((status, data, name))
1732

    
1733
  def GetResults(self):
1734
    """Wait for and return the results of all jobs.
1735

1736
    @rtype: list
1737
    @return: list of tuples (success, job results), in the same order
1738
        as the submitted jobs; if a job has failed, instead of the result
1739
        there will be the error message
1740

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

    
1765
      results.append((success, job_result))
1766
    return results
1767

    
1768
  def WaitOrShow(self, wait):
1769
    """Wait for job results or only print the job IDs.
1770

1771
    @type wait: boolean
1772
    @param wait: whether to wait or not
1773

1774
    """
1775
    if wait:
1776
      return self.GetResults()
1777
    else:
1778
      if not self.jobs:
1779
        self.SubmitPending()
1780
      for status, result, name in self.jobs:
1781
        if status:
1782
          ToStdout("%s: %s", result, name)
1783
        else:
1784
          ToStderr("Failure for %s: %s", name, result)