Statistics
| Branch: | Tag: | Revision:

root / lib / cli.py @ 07150497

History | View | Annotate | Download (56 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 copy
29
import time
30
import logging
31
from cStringIO import StringIO
32

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

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

    
44

    
45
__all__ = [
46
  # Command line options
47
  "ALLOCATABLE_OPT",
48
  "ALL_OPT",
49
  "AUTO_REPLACE_OPT",
50
  "BACKEND_OPT",
51
  "CLEANUP_OPT",
52
  "CONFIRM_OPT",
53
  "CP_SIZE_OPT",
54
  "DEBUG_OPT",
55
  "DEBUG_SIMERR_OPT",
56
  "DISKIDX_OPT",
57
  "DISK_OPT",
58
  "DISK_TEMPLATE_OPT",
59
  "DRAINED_OPT",
60
  "ENABLED_HV_OPT",
61
  "ERROR_CODES_OPT",
62
  "FIELDS_OPT",
63
  "FILESTORE_DIR_OPT",
64
  "FILESTORE_DRIVER_OPT",
65
  "GLOBAL_FILEDIR_OPT",
66
  "HVLIST_OPT",
67
  "HVOPTS_OPT",
68
  "HYPERVISOR_OPT",
69
  "IALLOCATOR_OPT",
70
  "IGNORE_CONSIST_OPT",
71
  "IGNORE_FAILURES_OPT",
72
  "IGNORE_SECONDARIES_OPT",
73
  "IGNORE_SIZE_OPT",
74
  "FORCE_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
  "SINGLE_NODE_OPT",
107
  "SRC_DIR_OPT",
108
  "SRC_NODE_OPT",
109
  "SUBMIT_OPT",
110
  "STATIC_OPT",
111
  "SYNC_OPT",
112
  "TAG_SRC_OPT",
113
  "USEUNITS_OPT",
114
  "VERBOSE_OPT",
115
  "VG_NAME_OPT",
116
  "YES_DOIT_OPT",
117
  # Generic functions for CLI programs
118
  "GenericMain",
119
  "GenericInstanceCreate",
120
  "GetClient",
121
  "GetOnlineNodes",
122
  "JobExecutor",
123
  "JobSubmittedException",
124
  "ParseTimespec",
125
  "SubmitOpCode",
126
  "SubmitOrSend",
127
  "UsesRPC",
128
  # Formatting functions
129
  "ToStderr", "ToStdout",
130
  "FormatError",
131
  "GenerateTable",
132
  "AskUser",
133
  "FormatTimestamp",
134
  # Tags functions
135
  "ListTags",
136
  "AddTags",
137
  "RemoveTags",
138
  # command line options support infrastructure
139
  "ARGS_MANY_INSTANCES",
140
  "ARGS_MANY_NODES",
141
  "ARGS_NONE",
142
  "ARGS_ONE_INSTANCE",
143
  "ARGS_ONE_NODE",
144
  "ArgChoice",
145
  "ArgCommand",
146
  "ArgFile",
147
  "ArgHost",
148
  "ArgInstance",
149
  "ArgJobId",
150
  "ArgNode",
151
  "ArgSuggest",
152
  "ArgUnknown",
153
  "OPT_COMPL_INST_ADD_NODES",
154
  "OPT_COMPL_MANY_NODES",
155
  "OPT_COMPL_ONE_IALLOCATOR",
156
  "OPT_COMPL_ONE_INSTANCE",
157
  "OPT_COMPL_ONE_NODE",
158
  "OPT_COMPL_ONE_OS",
159
  "cli_option",
160
  "SplitNodeOption",
161
  "CalculateOSNames",
162
  ]
163

    
164
NO_PREFIX = "no_"
165
UN_PREFIX = "-"
166

    
167

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

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

    
177

    
178
class ArgSuggest(_Argument):
179
  """Suggesting argument.
180

181
  Value can be any of the ones passed to the constructor.
182

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

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

    
192

    
193
class ArgChoice(ArgSuggest):
194
  """Choice argument.
195

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

199
  """
200

    
201

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

205
  """
206

    
207

    
208
class ArgInstance(_Argument):
209
  """Instances argument.
210

211
  """
212

    
213

    
214
class ArgNode(_Argument):
215
  """Node argument.
216

217
  """
218

    
219
class ArgJobId(_Argument):
220
  """Job ID argument.
221

222
  """
223

    
224

    
225
class ArgFile(_Argument):
226
  """File path argument.
227

228
  """
229

    
230

    
231
class ArgCommand(_Argument):
232
  """Command argument.
233

234
  """
235

    
236

    
237
class ArgHost(_Argument):
238
  """Host argument.
239

240
  """
241

    
242

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

    
249

    
250

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

254
  Note that this function will modify its args parameter.
255

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

    
271

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

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

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

    
300

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

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

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

    
318

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

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

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

    
335

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

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

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

    
352

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

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

    
362

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

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

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

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

    
399

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

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

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

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

    
427

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

431
  This will store the parsed values as a dict {key: val}.
432

433
  """
434
  return _SplitKeyVal(opt, value)
435

    
436

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

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

    
455

    
456
class CliOption(Option):
457
  """Custom option class for optparse.
458

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

    
473

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

    
477

    
478
_YESNO = ("yes", "no")
479
_YORNO = "yes|no"
480

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
570
BACKEND_OPT = cli_option("-B", "--backend-parameters", dest="beparams",
571
                         type="keyval", default={},
572
                         help="Backend parameters")
573

    
574
HVOPTS_OPT =  cli_option("-H", "--hypervisor-parameters", type="keyval",
575
                         default={}, dest="hvparams",
576
                         help="Hypervisor parameters")
577

    
578
HYPERVISOR_OPT = cli_option("-H", "--hypervisor-parameters", dest="hypervisor",
579
                            help="Hypervisor and hypervisor options, in the"
580
                            " format hypervisor:option=value,option=value,...",
581
                            default=None, type="identkeyval")
582

    
583
HVLIST_OPT = cli_option("-H", "--hypervisor-parameters", dest="hvparams",
584
                        help="Hypervisor and hypervisor options, in the"
585
                        " format hypervisor:option=value,option=value,...",
586
                        default=[], action="append", type="identkeyval")
587

    
588
NOIPCHECK_OPT = cli_option("--no-ip-check", dest="ip_check", default=True,
589
                           action="store_false",
590
                           help="Don't check that the instance's IP"
591
                           " is alive")
592

    
593
NET_OPT = cli_option("--net",
594
                     help="NIC parameters", default=[],
595
                     dest="nics", action="append", type="identkeyval")
596

    
597
DISK_OPT = cli_option("--disk", help="Disk parameters", default=[],
598
                      dest="disks", action="append", type="identkeyval")
599

    
600
DISKIDX_OPT = cli_option("--disks", dest="disks", default=None,
601
                         help="Comma-separated list of disks"
602
                         " indices to act on (e.g. 0,2) (optional,"
603
                         " defaults to all disks)")
604

    
605
OS_SIZE_OPT = cli_option("-s", "--os-size", dest="sd_size",
606
                         help="Enforces a single-disk configuration using the"
607
                         " given disk size, in MiB unless a suffix is used",
608
                         default=None, type="unit", metavar="<size>")
609

    
610
IGNORE_CONSIST_OPT = cli_option("--ignore-consistency",
611
                                dest="ignore_consistency",
612
                                action="store_true", default=False,
613
                                help="Ignore the consistency of the disks on"
614
                                " the secondary")
615

    
616
NONLIVE_OPT = cli_option("--non-live", dest="live",
617
                         default=True, action="store_false",
618
                         help="Do a non-live migration (this usually means"
619
                         " freeze the instance, save the state, transfer and"
620
                         " only then resume running on the secondary node)")
621

    
622
NODE_PLACEMENT_OPT = cli_option("-n", "--node", dest="node",
623
                                help="Target node and optional secondary node",
624
                                metavar="<pnode>[:<snode>]",
625
                                completion_suggest=OPT_COMPL_INST_ADD_NODES)
626

    
627
NODE_LIST_OPT = cli_option("-n", "--node", dest="nodes", default=[],
628
                           action="append", metavar="<node>",
629
                           help="Use only this node (can be used multiple"
630
                           " times, if not given defaults to all nodes)",
631
                           completion_suggest=OPT_COMPL_ONE_NODE)
632

    
633
SINGLE_NODE_OPT = cli_option("-n", "--node", dest="node", help="Target node",
634
                             metavar="<node>",
635
                             completion_suggest=OPT_COMPL_ONE_NODE)
636

    
637
NOSTART_OPT = cli_option("--no-start", dest="start", default=True,
638
                         action="store_false",
639
                         help="Don't start the instance after creation")
640

    
641
SHOWCMD_OPT = cli_option("--show-cmd", dest="show_command",
642
                         action="store_true", default=False,
643
                         help="Show command instead of executing it")
644

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

    
654
STATIC_OPT = cli_option("-s", "--static", dest="static",
655
                        action="store_true", default=False,
656
                        help="Only show configuration data, not runtime data")
657

    
658
ALL_OPT = cli_option("--all", dest="show_all",
659
                     default=False, action="store_true",
660
                     help="Show info on all instances on the cluster."
661
                     " This can take a long time to run, use wisely")
662

    
663
SELECT_OS_OPT = cli_option("--select-os", dest="select_os",
664
                           action="store_true", default=False,
665
                           help="Interactive OS reinstall, lists available"
666
                           " OS templates for selection")
667

    
668
IGNORE_FAILURES_OPT = cli_option("--ignore-failures", dest="ignore_failures",
669
                                 action="store_true", default=False,
670
                                 help="Remove the instance from the cluster"
671
                                 " configuration even if there are failures"
672
                                 " during the removal process")
673

    
674
NEW_SECONDARY_OPT = cli_option("-n", "--new-secondary", dest="dst_node",
675
                               help="Specifies the new secondary node",
676
                               metavar="NODE", default=None,
677
                               completion_suggest=OPT_COMPL_ONE_NODE)
678

    
679
ON_PRIMARY_OPT = cli_option("-p", "--on-primary", dest="on_primary",
680
                            default=False, action="store_true",
681
                            help="Replace the disk(s) on the primary"
682
                            " node (only for the drbd template)")
683

    
684
ON_SECONDARY_OPT = cli_option("-s", "--on-secondary", dest="on_secondary",
685
                              default=False, action="store_true",
686
                              help="Replace the disk(s) on the secondary"
687
                              " node (only for the drbd template)")
688

    
689
AUTO_REPLACE_OPT = cli_option("-a", "--auto", dest="auto",
690
                              default=False, action="store_true",
691
                              help="Automatically replace faulty disks"
692
                              " (only for the drbd template)")
693

    
694
IGNORE_SIZE_OPT = cli_option("--ignore-size", dest="ignore_size",
695
                             default=False, action="store_true",
696
                             help="Ignore current recorded size"
697
                             " (useful for forcing activation when"
698
                             " the recorded size is wrong)")
699

    
700
SRC_NODE_OPT = cli_option("--src-node", dest="src_node", help="Source node",
701
                          metavar="<node>",
702
                          completion_suggest=OPT_COMPL_ONE_NODE)
703

    
704
SRC_DIR_OPT = cli_option("--src-dir", dest="src_dir", help="Source directory",
705
                         metavar="<dir>")
706

    
707
SECONDARY_IP_OPT = cli_option("-s", "--secondary-ip", dest="secondary_ip",
708
                              help="Specify the secondary ip for the node",
709
                              metavar="ADDRESS", default=None)
710

    
711
READD_OPT = cli_option("--readd", dest="readd",
712
                       default=False, action="store_true",
713
                       help="Readd old node after replacing it")
714

    
715
NOSSH_KEYCHECK_OPT = cli_option("--no-ssh-key-check", dest="ssh_key_check",
716
                                default=True, action="store_false",
717
                                help="Disable SSH key fingerprint checking")
718

    
719

    
720
MC_OPT = cli_option("-C", "--master-candidate", dest="master_candidate",
721
                    choices=_YESNO, default=None, metavar=_YORNO,
722
                    help="Set the master_candidate flag on the node")
723

    
724
OFFLINE_OPT = cli_option("-O", "--offline", dest="offline", metavar=_YORNO,
725
                         choices=_YESNO, default=None,
726
                         help="Set the offline flag on the node")
727

    
728
DRAINED_OPT = cli_option("-D", "--drained", dest="drained", metavar=_YORNO,
729
                         choices=_YESNO, default=None,
730
                         help="Set the drained flag on the node")
731

    
732
ALLOCATABLE_OPT = cli_option("--allocatable", dest="allocatable",
733
                             choices=_YESNO, default=None, metavar=_YORNO,
734
                             help="Set the allocatable flag on a volume")
735

    
736
NOLVM_STORAGE_OPT = cli_option("--no-lvm-storage", dest="lvm_storage",
737
                               help="Disable support for lvm based instances"
738
                               " (cluster-wide)",
739
                               action="store_false", default=True)
740

    
741
ENABLED_HV_OPT = cli_option("--enabled-hypervisors",
742
                            dest="enabled_hypervisors",
743
                            help="Comma-separated list of hypervisors",
744
                            type="string", default=None)
745

    
746
NIC_PARAMS_OPT = cli_option("-N", "--nic-parameters", dest="nicparams",
747
                            type="keyval", default={},
748
                            help="NIC parameters")
749

    
750
CP_SIZE_OPT = cli_option("-C", "--candidate-pool-size", default=None,
751
                         dest="candidate_pool_size", type="int",
752
                         help="Set the candidate pool size")
753

    
754
VG_NAME_OPT = cli_option("-g", "--vg-name", dest="vg_name",
755
                         help="Enables LVM and specifies the volume group"
756
                         " name (cluster-wide) for disk allocation [xenvg]",
757
                         metavar="VG", default=None)
758

    
759
YES_DOIT_OPT = cli_option("--yes-do-it", dest="yes_do_it",
760
                          help="Destroy cluster", action="store_true")
761

    
762
NOVOTING_OPT = cli_option("--no-voting", dest="no_voting",
763
                          help="Skip node agreement check (dangerous)",
764
                          action="store_true", default=False)
765

    
766
MAC_PREFIX_OPT = cli_option("-m", "--mac-prefix", dest="mac_prefix",
767
                            help="Specify the mac prefix for the instance IP"
768
                            " addresses, in the format XX:XX:XX",
769
                            metavar="PREFIX",
770
                            default=None)
771

    
772
MASTER_NETDEV_OPT = cli_option("--master-netdev", dest="master_netdev",
773
                               help="Specify the node interface (cluster-wide)"
774
                               " on which the master IP address will be added "
775
                               " [%s]" % constants.DEFAULT_BRIDGE,
776
                               metavar="NETDEV",
777
                               default=constants.DEFAULT_BRIDGE)
778

    
779

    
780
GLOBAL_FILEDIR_OPT = cli_option("--file-storage-dir", dest="file_storage_dir",
781
                                help="Specify the default directory (cluster-"
782
                                "wide) for storing the file-based disks [%s]" %
783
                                constants.DEFAULT_FILE_STORAGE_DIR,
784
                                metavar="DIR",
785
                                default=constants.DEFAULT_FILE_STORAGE_DIR)
786

    
787
NOMODIFY_ETCHOSTS_OPT = cli_option("--no-etc-hosts", dest="modify_etc_hosts",
788
                                   help="Don't modify /etc/hosts",
789
                                   action="store_false", default=True)
790

    
791
ERROR_CODES_OPT = cli_option("--error-codes", dest="error_codes",
792
                             help="Enable parseable error messages",
793
                             action="store_true", default=False)
794

    
795
NONPLUS1_OPT = cli_option("--no-nplus1-mem", dest="skip_nplusone_mem",
796
                          help="Skip N+1 memory redundancy tests",
797
                          action="store_true", default=False)
798

    
799
REBOOT_TYPE_OPT = cli_option("-t", "--type", dest="reboot_type",
800
                             help="Type of reboot: soft/hard/full",
801
                             default=constants.INSTANCE_REBOOT_HARD,
802
                             metavar="<REBOOT>",
803
                             choices=list(constants.REBOOT_TYPES))
804

    
805
IGNORE_SECONDARIES_OPT = cli_option("--ignore-secondaries",
806
                                    dest="ignore_secondaries",
807
                                    default=False, action="store_true",
808
                                    help="Ignore errors from secondaries")
809

    
810
NOSHUTDOWN_OPT = cli_option("","--noshutdown", dest="shutdown",
811
                            action="store_false", default=True,
812
                            help="Don't shutdown the instance (unsafe)")
813

    
814

    
815

    
816
def _ParseArgs(argv, commands, aliases):
817
  """Parser for the command line arguments.
818

819
  This function parses the arguments and returns the function which
820
  must be executed together with its (modified) arguments.
821

822
  @param argv: the command line
823
  @param commands: dictionary with special contents, see the design
824
      doc for cmdline handling
825
  @param aliases: dictionary with command aliases {'alias': 'target, ...}
826

827
  """
828
  if len(argv) == 0:
829
    binary = "<command>"
830
  else:
831
    binary = argv[0].split("/")[-1]
832

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

    
839
  if len(argv) < 2 or not (argv[1] in commands or
840
                           argv[1] in aliases):
841
    # let's do a nice thing
842
    sortedcmds = commands.keys()
843
    sortedcmds.sort()
844

    
845
    ToStdout("Usage: %s {command} [options...] [argument...]", binary)
846
    ToStdout("%s <command> --help to see details, or man %s", binary, binary)
847
    ToStdout("")
848

    
849
    # compute the max line length for cmd + usage
850
    mlen = max([len(" %s" % cmd) for cmd in commands])
851
    mlen = min(60, mlen) # should not get here...
852

    
853
    # and format a nice command list
854
    ToStdout("Commands:")
855
    for cmd in sortedcmds:
856
      cmdstr = " %s" % (cmd,)
857
      help_text = commands[cmd][4]
858
      help_lines = textwrap.wrap(help_text, 79 - 3 - mlen)
859
      ToStdout("%-*s - %s", mlen, cmdstr, help_lines.pop(0))
860
      for line in help_lines:
861
        ToStdout("%-*s   %s", mlen, "", line)
862

    
863
    ToStdout("")
864

    
865
    return None, None, None
866

    
867
  # get command, unalias it, and look it up in commands
868
  cmd = argv.pop(1)
869
  if cmd in aliases:
870
    if cmd in commands:
871
      raise errors.ProgrammerError("Alias '%s' overrides an existing"
872
                                   " command" % cmd)
873

    
874
    if aliases[cmd] not in commands:
875
      raise errors.ProgrammerError("Alias '%s' maps to non-existing"
876
                                   " command '%s'" % (cmd, aliases[cmd]))
877

    
878
    cmd = aliases[cmd]
879

    
880
  func, args_def, parser_opts, usage, description = commands[cmd]
881
  parser = OptionParser(option_list=parser_opts + [_DRY_RUN_OPT, DEBUG_OPT],
882
                        description=description,
883
                        formatter=TitledHelpFormatter(),
884
                        usage="%%prog %s %s" % (cmd, usage))
885
  parser.disable_interspersed_args()
886
  options, args = parser.parse_args()
887

    
888
  if not _CheckArguments(cmd, args_def, args):
889
    return None, None, None
890

    
891
  return func, options, args
892

    
893

    
894
def _CheckArguments(cmd, args_def, args):
895
  """Verifies the arguments using the argument definition.
896

897
  Algorithm:
898

899
    1. Abort with error if values specified by user but none expected.
900

901
    1. For each argument in definition
902

903
      1. Keep running count of minimum number of values (min_count)
904
      1. Keep running count of maximum number of values (max_count)
905
      1. If it has an unlimited number of values
906

907
        1. Abort with error if it's not the last argument in the definition
908

909
    1. If last argument has limited number of values
910

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

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

915
  """
916
  if args and not args_def:
917
    ToStderr("Error: Command %s expects no arguments", cmd)
918
    return False
919

    
920
  min_count = None
921
  max_count = None
922
  check_max = None
923

    
924
  last_idx = len(args_def) - 1
925

    
926
  for idx, arg in enumerate(args_def):
927
    if min_count is None:
928
      min_count = arg.min
929
    elif arg.min is not None:
930
      min_count += arg.min
931

    
932
    if max_count is None:
933
      max_count = arg.max
934
    elif arg.max is not None:
935
      max_count += arg.max
936

    
937
    if idx == last_idx:
938
      check_max = (arg.max is not None)
939

    
940
    elif arg.max is None:
941
      raise errors.ProgrammerError("Only the last argument can have max=None")
942

    
943
  if check_max:
944
    # Command with exact number of arguments
945
    if (min_count is not None and max_count is not None and
946
        min_count == max_count and len(args) != min_count):
947
      ToStderr("Error: Command %s expects %d argument(s)", cmd, min_count)
948
      return False
949

    
950
    # Command with limited number of arguments
951
    if max_count is not None and len(args) > max_count:
952
      ToStderr("Error: Command %s expects only %d argument(s)",
953
               cmd, max_count)
954
      return False
955

    
956
  # Command with some required arguments
957
  if min_count is not None and len(args) < min_count:
958
    ToStderr("Error: Command %s expects at least %d argument(s)",
959
             cmd, min_count)
960
    return False
961

    
962
  return True
963

    
964

    
965
def SplitNodeOption(value):
966
  """Splits the value of a --node option.
967

968
  """
969
  if value and ':' in value:
970
    return value.split(':', 1)
971
  else:
972
    return (value, None)
973

    
974

    
975
def CalculateOSNames(os_name, os_variants):
976
  """Calculates all the names an OS can be called, according to its variants.
977

978
  @type os_name: string
979
  @param os_name: base name of the os
980
  @type os_variants: list or None
981
  @param os_variants: list of supported variants
982
  @rtype: list
983
  @return: list of valid names
984

985
  """
986
  if os_variants:
987
    return ['%s+%s' % (os_name, v) for v in os_variants]
988
  else:
989
    return [os_name]
990

    
991

    
992
def UsesRPC(fn):
993
  def wrapper(*args, **kwargs):
994
    rpc.Init()
995
    try:
996
      return fn(*args, **kwargs)
997
    finally:
998
      rpc.Shutdown()
999
  return wrapper
1000

    
1001

    
1002
def AskUser(text, choices=None):
1003
  """Ask the user a question.
1004

1005
  @param text: the question to ask
1006

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

1012
  @return: one of the return values from the choices list; if input is
1013
      not possible (i.e. not running with a tty, we return the last
1014
      entry from the list
1015

1016
  """
1017
  if choices is None:
1018
    choices = [('y', True, 'Perform the operation'),
1019
               ('n', False, 'Do not perform the operation')]
1020
  if not choices or not isinstance(choices, list):
1021
    raise errors.ProgrammerError("Invalid choices argument to AskUser")
1022
  for entry in choices:
1023
    if not isinstance(entry, tuple) or len(entry) < 3 or entry[0] == '?':
1024
      raise errors.ProgrammerError("Invalid choices element to AskUser")
1025

    
1026
  answer = choices[-1][1]
1027
  new_text = []
1028
  for line in text.splitlines():
1029
    new_text.append(textwrap.fill(line, 70, replace_whitespace=False))
1030
  text = "\n".join(new_text)
1031
  try:
1032
    f = file("/dev/tty", "a+")
1033
  except IOError:
1034
    return answer
1035
  try:
1036
    chars = [entry[0] for entry in choices]
1037
    chars[-1] = "[%s]" % chars[-1]
1038
    chars.append('?')
1039
    maps = dict([(entry[0], entry[1]) for entry in choices])
1040
    while True:
1041
      f.write(text)
1042
      f.write('\n')
1043
      f.write("/".join(chars))
1044
      f.write(": ")
1045
      line = f.readline(2).strip().lower()
1046
      if line in maps:
1047
        answer = maps[line]
1048
        break
1049
      elif line == '?':
1050
        for entry in choices:
1051
          f.write(" %s - %s\n" % (entry[0], entry[2]))
1052
        f.write("\n")
1053
        continue
1054
  finally:
1055
    f.close()
1056
  return answer
1057

    
1058

    
1059
class JobSubmittedException(Exception):
1060
  """Job was submitted, client should exit.
1061

1062
  This exception has one argument, the ID of the job that was
1063
  submitted. The handler should print this ID.
1064

1065
  This is not an error, just a structured way to exit from clients.
1066

1067
  """
1068

    
1069

    
1070
def SendJob(ops, cl=None):
1071
  """Function to submit an opcode without waiting for the results.
1072

1073
  @type ops: list
1074
  @param ops: list of opcodes
1075
  @type cl: luxi.Client
1076
  @param cl: the luxi client to use for communicating with the master;
1077
             if None, a new client will be created
1078

1079
  """
1080
  if cl is None:
1081
    cl = GetClient()
1082

    
1083
  job_id = cl.SubmitJob(ops)
1084

    
1085
  return job_id
1086

    
1087

    
1088
def PollJob(job_id, cl=None, feedback_fn=None):
1089
  """Function to poll for the result of a job.
1090

1091
  @type job_id: job identified
1092
  @param job_id: the job to poll for results
1093
  @type cl: luxi.Client
1094
  @param cl: the luxi client to use for communicating with the master;
1095
             if None, a new client will be created
1096

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

    
1101
  prev_job_info = None
1102
  prev_logmsg_serial = None
1103

    
1104
  while True:
1105
    result = cl.WaitForJobChange(job_id, ["status"], prev_job_info,
1106
                                 prev_logmsg_serial)
1107
    if not result:
1108
      # job not found, go away!
1109
      raise errors.JobLost("Job with id %s lost" % job_id)
1110

    
1111
    # Split result, a tuple of (field values, log entries)
1112
    (job_info, log_entries) = result
1113
    (status, ) = job_info
1114

    
1115
    if log_entries:
1116
      for log_entry in log_entries:
1117
        (serial, timestamp, _, message) = log_entry
1118
        if callable(feedback_fn):
1119
          feedback_fn(log_entry[1:])
1120
        else:
1121
          encoded = utils.SafeEncode(message)
1122
          ToStdout("%s %s", time.ctime(utils.MergeTime(timestamp)), encoded)
1123
        prev_logmsg_serial = max(prev_logmsg_serial, serial)
1124

    
1125
    # TODO: Handle canceled and archived jobs
1126
    elif status in (constants.JOB_STATUS_SUCCESS,
1127
                    constants.JOB_STATUS_ERROR,
1128
                    constants.JOB_STATUS_CANCELING,
1129
                    constants.JOB_STATUS_CANCELED):
1130
      break
1131

    
1132
    prev_job_info = job_info
1133

    
1134
  jobs = cl.QueryJobs([job_id], ["status", "opstatus", "opresult"])
1135
  if not jobs:
1136
    raise errors.JobLost("Job with id %s lost" % job_id)
1137

    
1138
  status, opstatus, result = jobs[0]
1139
  if status == constants.JOB_STATUS_SUCCESS:
1140
    return result
1141
  elif status in (constants.JOB_STATUS_CANCELING,
1142
                  constants.JOB_STATUS_CANCELED):
1143
    raise errors.OpExecError("Job was canceled")
1144
  else:
1145
    has_ok = False
1146
    for idx, (status, msg) in enumerate(zip(opstatus, result)):
1147
      if status == constants.OP_STATUS_SUCCESS:
1148
        has_ok = True
1149
      elif status == constants.OP_STATUS_ERROR:
1150
        errors.MaybeRaise(msg)
1151
        if has_ok:
1152
          raise errors.OpExecError("partial failure (opcode %d): %s" %
1153
                                   (idx, msg))
1154
        else:
1155
          raise errors.OpExecError(str(msg))
1156
    # default failure mode
1157
    raise errors.OpExecError(result)
1158

    
1159

    
1160
def SubmitOpCode(op, cl=None, feedback_fn=None):
1161
  """Legacy function to submit an opcode.
1162

1163
  This is just a simple wrapper over the construction of the processor
1164
  instance. It should be extended to better handle feedback and
1165
  interaction functions.
1166

1167
  """
1168
  if cl is None:
1169
    cl = GetClient()
1170

    
1171
  job_id = SendJob([op], cl)
1172

    
1173
  op_results = PollJob(job_id, cl=cl, feedback_fn=feedback_fn)
1174

    
1175
  return op_results[0]
1176

    
1177

    
1178
def SubmitOrSend(op, opts, cl=None, feedback_fn=None):
1179
  """Wrapper around SubmitOpCode or SendJob.
1180

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

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

1188
  """
1189
  if opts and opts.dry_run:
1190
    op.dry_run = opts.dry_run
1191
  if opts and opts.submit_only:
1192
    job_id = SendJob([op], cl=cl)
1193
    raise JobSubmittedException(job_id)
1194
  else:
1195
    return SubmitOpCode(op, cl=cl, feedback_fn=feedback_fn)
1196

    
1197

    
1198
def GetClient():
1199
  # TODO: Cache object?
1200
  try:
1201
    client = luxi.Client()
1202
  except luxi.NoMasterError:
1203
    master, myself = ssconf.GetMasterAndMyself()
1204
    if master != myself:
1205
      raise errors.OpPrereqError("This is not the master node, please connect"
1206
                                 " to node '%s' and rerun the command" %
1207
                                 master)
1208
    else:
1209
      raise
1210
  return client
1211

    
1212

    
1213
def FormatError(err):
1214
  """Return a formatted error message for a given error.
1215

1216
  This function takes an exception instance and returns a tuple
1217
  consisting of two values: first, the recommended exit code, and
1218
  second, a string describing the error message (not
1219
  newline-terminated).
1220

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

    
1284

    
1285
def GenericMain(commands, override=None, aliases=None):
1286
  """Generic main function for all the gnt-* commands.
1287

1288
  Arguments:
1289
    - commands: a dictionary with a special structure, see the design doc
1290
                for command line handling.
1291
    - override: if not None, we expect a dictionary with keys that will
1292
                override command line options; this can be used to pass
1293
                options from the scripts to generic functions
1294
    - aliases: dictionary with command aliases {'alias': 'target, ...}
1295

1296
  """
1297
  # save the program name and the entire command line for later logging
1298
  if sys.argv:
1299
    binary = os.path.basename(sys.argv[0]) or sys.argv[0]
1300
    if len(sys.argv) >= 2:
1301
      binary += " " + sys.argv[1]
1302
      old_cmdline = " ".join(sys.argv[2:])
1303
    else:
1304
      old_cmdline = ""
1305
  else:
1306
    binary = "<unknown program>"
1307
    old_cmdline = ""
1308

    
1309
  if aliases is None:
1310
    aliases = {}
1311

    
1312
  try:
1313
    func, options, args = _ParseArgs(sys.argv, commands, aliases)
1314
  except errors.ParameterError, err:
1315
    result, err_msg = FormatError(err)
1316
    ToStderr(err_msg)
1317
    return 1
1318

    
1319
  if func is None: # parse error
1320
    return 1
1321

    
1322
  if override is not None:
1323
    for key, val in override.iteritems():
1324
      setattr(options, key, val)
1325

    
1326
  utils.SetupLogging(constants.LOG_COMMANDS, debug=options.debug,
1327
                     stderr_logging=True, program=binary)
1328

    
1329
  if old_cmdline:
1330
    logging.info("run with arguments '%s'", old_cmdline)
1331
  else:
1332
    logging.info("run with no arguments")
1333

    
1334
  try:
1335
    result = func(options, args)
1336
  except (errors.GenericError, luxi.ProtocolError,
1337
          JobSubmittedException), err:
1338
    result, err_msg = FormatError(err)
1339
    logging.exception("Error during command processing")
1340
    ToStderr(err_msg)
1341

    
1342
  return result
1343

    
1344

    
1345
def GenericInstanceCreate(mode, opts, args):
1346
  """Add an instance to the cluster via either creation or import.
1347

1348
  @param mode: constants.INSTANCE_CREATE or constants.INSTANCE_IMPORT
1349
  @param opts: the command line options selected by the user
1350
  @type args: list
1351
  @param args: should contain only one element, the new instance name
1352
  @rtype: int
1353
  @return: the desired exit code
1354

1355
  """
1356
  instance = args[0]
1357

    
1358
  (pnode, snode) = SplitNodeOption(opts.node)
1359

    
1360
  hypervisor = None
1361
  hvparams = {}
1362
  if opts.hypervisor:
1363
    hypervisor, hvparams = opts.hypervisor
1364

    
1365
  if opts.nics:
1366
    try:
1367
      nic_max = max(int(nidx[0])+1 for nidx in opts.nics)
1368
    except ValueError, err:
1369
      raise errors.OpPrereqError("Invalid NIC index passed: %s" % str(err))
1370
    nics = [{}] * nic_max
1371
    for nidx, ndict in opts.nics:
1372
      nidx = int(nidx)
1373
      if not isinstance(ndict, dict):
1374
        msg = "Invalid nic/%d value: expected dict, got %s" % (nidx, ndict)
1375
        raise errors.OpPrereqError(msg)
1376
      nics[nidx] = ndict
1377
  elif opts.no_nics:
1378
    # no nics
1379
    nics = []
1380
  else:
1381
    # default of one nic, all auto
1382
    nics = [{}]
1383

    
1384
  if opts.disk_template == constants.DT_DISKLESS:
1385
    if opts.disks or opts.sd_size is not None:
1386
      raise errors.OpPrereqError("Diskless instance but disk"
1387
                                 " information passed")
1388
    disks = []
1389
  else:
1390
    if not opts.disks and not opts.sd_size:
1391
      raise errors.OpPrereqError("No disk information specified")
1392
    if opts.disks and opts.sd_size is not None:
1393
      raise errors.OpPrereqError("Please use either the '--disk' or"
1394
                                 " '-s' option")
1395
    if opts.sd_size is not None:
1396
      opts.disks = [(0, {"size": opts.sd_size})]
1397
    try:
1398
      disk_max = max(int(didx[0])+1 for didx in opts.disks)
1399
    except ValueError, err:
1400
      raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err))
1401
    disks = [{}] * disk_max
1402
    for didx, ddict in opts.disks:
1403
      didx = int(didx)
1404
      if not isinstance(ddict, dict):
1405
        msg = "Invalid disk/%d value: expected dict, got %s" % (didx, ddict)
1406
        raise errors.OpPrereqError(msg)
1407
      elif "size" not in ddict:
1408
        raise errors.OpPrereqError("Missing size for disk %d" % didx)
1409
      try:
1410
        ddict["size"] = utils.ParseUnit(ddict["size"])
1411
      except ValueError, err:
1412
        raise errors.OpPrereqError("Invalid disk size for disk %d: %s" %
1413
                                   (didx, err))
1414
      disks[didx] = ddict
1415

    
1416
  utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_TYPES)
1417
  utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
1418

    
1419
  if mode == constants.INSTANCE_CREATE:
1420
    start = opts.start
1421
    os_type = opts.os
1422
    src_node = None
1423
    src_path = None
1424
  elif mode == constants.INSTANCE_IMPORT:
1425
    start = False
1426
    os_type = None
1427
    src_node = opts.src_node
1428
    src_path = opts.src_dir
1429
  else:
1430
    raise errors.ProgrammerError("Invalid creation mode %s" % mode)
1431

    
1432
  op = opcodes.OpCreateInstance(instance_name=instance,
1433
                                disks=disks,
1434
                                disk_template=opts.disk_template,
1435
                                nics=nics,
1436
                                pnode=pnode, snode=snode,
1437
                                ip_check=opts.ip_check,
1438
                                wait_for_sync=opts.wait_for_sync,
1439
                                file_storage_dir=opts.file_storage_dir,
1440
                                file_driver=opts.file_driver,
1441
                                iallocator=opts.iallocator,
1442
                                hypervisor=hypervisor,
1443
                                hvparams=hvparams,
1444
                                beparams=opts.beparams,
1445
                                mode=mode,
1446
                                start=start,
1447
                                os_type=os_type,
1448
                                src_node=src_node,
1449
                                src_path=src_path)
1450

    
1451
  SubmitOrSend(op, opts)
1452
  return 0
1453

    
1454

    
1455
def GenerateTable(headers, fields, separator, data,
1456
                  numfields=None, unitfields=None,
1457
                  units=None):
1458
  """Prints a table with headers and different fields.
1459

1460
  @type headers: dict
1461
  @param headers: dictionary mapping field names to headers for
1462
      the table
1463
  @type fields: list
1464
  @param fields: the field names corresponding to each row in
1465
      the data field
1466
  @param separator: the separator to be used; if this is None,
1467
      the default 'smart' algorithm is used which computes optimal
1468
      field width, otherwise just the separator is used between
1469
      each field
1470
  @type data: list
1471
  @param data: a list of lists, each sublist being one row to be output
1472
  @type numfields: list
1473
  @param numfields: a list with the fields that hold numeric
1474
      values and thus should be right-aligned
1475
  @type unitfields: list
1476
  @param unitfields: a list with the fields that hold numeric
1477
      values that should be formatted with the units field
1478
  @type units: string or None
1479
  @param units: the units we should use for formatting, or None for
1480
      automatic choice (human-readable for non-separator usage, otherwise
1481
      megabytes); this is a one-letter string
1482

1483
  """
1484
  if units is None:
1485
    if separator:
1486
      units = "m"
1487
    else:
1488
      units = "h"
1489

    
1490
  if numfields is None:
1491
    numfields = []
1492
  if unitfields is None:
1493
    unitfields = []
1494

    
1495
  numfields = utils.FieldSet(*numfields)
1496
  unitfields = utils.FieldSet(*unitfields)
1497

    
1498
  format_fields = []
1499
  for field in fields:
1500
    if headers and field not in headers:
1501
      # TODO: handle better unknown fields (either revert to old
1502
      # style of raising exception, or deal more intelligently with
1503
      # variable fields)
1504
      headers[field] = field
1505
    if separator is not None:
1506
      format_fields.append("%s")
1507
    elif numfields.Matches(field):
1508
      format_fields.append("%*s")
1509
    else:
1510
      format_fields.append("%-*s")
1511

    
1512
  if separator is None:
1513
    mlens = [0 for name in fields]
1514
    format = ' '.join(format_fields)
1515
  else:
1516
    format = separator.replace("%", "%%").join(format_fields)
1517

    
1518
  for row in data:
1519
    if row is None:
1520
      continue
1521
    for idx, val in enumerate(row):
1522
      if unitfields.Matches(fields[idx]):
1523
        try:
1524
          val = int(val)
1525
        except ValueError:
1526
          pass
1527
        else:
1528
          val = row[idx] = utils.FormatUnit(val, units)
1529
      val = row[idx] = str(val)
1530
      if separator is None:
1531
        mlens[idx] = max(mlens[idx], len(val))
1532

    
1533
  result = []
1534
  if headers:
1535
    args = []
1536
    for idx, name in enumerate(fields):
1537
      hdr = headers[name]
1538
      if separator is None:
1539
        mlens[idx] = max(mlens[idx], len(hdr))
1540
        args.append(mlens[idx])
1541
      args.append(hdr)
1542
    result.append(format % tuple(args))
1543

    
1544
  for line in data:
1545
    args = []
1546
    if line is None:
1547
      line = ['-' for _ in fields]
1548
    for idx in range(len(fields)):
1549
      if separator is None:
1550
        args.append(mlens[idx])
1551
      args.append(line[idx])
1552
    result.append(format % tuple(args))
1553

    
1554
  return result
1555

    
1556

    
1557
def FormatTimestamp(ts):
1558
  """Formats a given timestamp.
1559

1560
  @type ts: timestamp
1561
  @param ts: a timeval-type timestamp, a tuple of seconds and microseconds
1562

1563
  @rtype: string
1564
  @return: a string with the formatted timestamp
1565

1566
  """
1567
  if not isinstance (ts, (tuple, list)) or len(ts) != 2:
1568
    return '?'
1569
  sec, usec = ts
1570
  return time.strftime("%F %T", time.localtime(sec)) + ".%06d" % usec
1571

    
1572

    
1573
def ParseTimespec(value):
1574
  """Parse a time specification.
1575

1576
  The following suffixed will be recognized:
1577

1578
    - s: seconds
1579
    - m: minutes
1580
    - h: hours
1581
    - d: day
1582
    - w: weeks
1583

1584
  Without any suffix, the value will be taken to be in seconds.
1585

1586
  """
1587
  value = str(value)
1588
  if not value:
1589
    raise errors.OpPrereqError("Empty time specification passed")
1590
  suffix_map = {
1591
    's': 1,
1592
    'm': 60,
1593
    'h': 3600,
1594
    'd': 86400,
1595
    'w': 604800,
1596
    }
1597
  if value[-1] not in suffix_map:
1598
    try:
1599
      value = int(value)
1600
    except ValueError:
1601
      raise errors.OpPrereqError("Invalid time specification '%s'" % value)
1602
  else:
1603
    multiplier = suffix_map[value[-1]]
1604
    value = value[:-1]
1605
    if not value: # no data left after stripping the suffix
1606
      raise errors.OpPrereqError("Invalid time specification (only"
1607
                                 " suffix passed)")
1608
    try:
1609
      value = int(value) * multiplier
1610
    except ValueError:
1611
      raise errors.OpPrereqError("Invalid time specification '%s'" % value)
1612
  return value
1613

    
1614

    
1615
def GetOnlineNodes(nodes, cl=None, nowarn=False):
1616
  """Returns the names of online nodes.
1617

1618
  This function will also log a warning on stderr with the names of
1619
  the online nodes.
1620

1621
  @param nodes: if not empty, use only this subset of nodes (minus the
1622
      offline ones)
1623
  @param cl: if not None, luxi client to use
1624
  @type nowarn: boolean
1625
  @param nowarn: by default, this function will output a note with the
1626
      offline nodes that are skipped; if this parameter is True the
1627
      note is not displayed
1628

1629
  """
1630
  if cl is None:
1631
    cl = GetClient()
1632

    
1633
  result = cl.QueryNodes(names=nodes, fields=["name", "offline"],
1634
                         use_locking=False)
1635
  offline = [row[0] for row in result if row[1]]
1636
  if offline and not nowarn:
1637
    ToStderr("Note: skipping offline node(s): %s" % ", ".join(offline))
1638
  return [row[0] for row in result if not row[1]]
1639

    
1640

    
1641
def _ToStream(stream, txt, *args):
1642
  """Write a message to a stream, bypassing the logging system
1643

1644
  @type stream: file object
1645
  @param stream: the file to which we should write
1646
  @type txt: str
1647
  @param txt: the message
1648

1649
  """
1650
  if args:
1651
    args = tuple(args)
1652
    stream.write(txt % args)
1653
  else:
1654
    stream.write(txt)
1655
  stream.write('\n')
1656
  stream.flush()
1657

    
1658

    
1659
def ToStdout(txt, *args):
1660
  """Write a message to stdout only, bypassing the logging system
1661

1662
  This is just a wrapper over _ToStream.
1663

1664
  @type txt: str
1665
  @param txt: the message
1666

1667
  """
1668
  _ToStream(sys.stdout, txt, *args)
1669

    
1670

    
1671
def ToStderr(txt, *args):
1672
  """Write a message to stderr only, bypassing the logging system
1673

1674
  This is just a wrapper over _ToStream.
1675

1676
  @type txt: str
1677
  @param txt: the message
1678

1679
  """
1680
  _ToStream(sys.stderr, txt, *args)
1681

    
1682

    
1683
class JobExecutor(object):
1684
  """Class which manages the submission and execution of multiple jobs.
1685

1686
  Note that instances of this class should not be reused between
1687
  GetResults() calls.
1688

1689
  """
1690
  def __init__(self, cl=None, verbose=True):
1691
    self.queue = []
1692
    if cl is None:
1693
      cl = GetClient()
1694
    self.cl = cl
1695
    self.verbose = verbose
1696
    self.jobs = []
1697

    
1698
  def QueueJob(self, name, *ops):
1699
    """Record a job for later submit.
1700

1701
    @type name: string
1702
    @param name: a description of the job, will be used in WaitJobSet
1703
    """
1704
    self.queue.append((name, ops))
1705

    
1706
  def SubmitPending(self):
1707
    """Submit all pending jobs.
1708

1709
    """
1710
    results = self.cl.SubmitManyJobs([row[1] for row in self.queue])
1711
    for ((status, data), (name, _)) in zip(results, self.queue):
1712
      self.jobs.append((status, data, name))
1713

    
1714
  def GetResults(self):
1715
    """Wait for and return the results of all jobs.
1716

1717
    @rtype: list
1718
    @return: list of tuples (success, job results), in the same order
1719
        as the submitted jobs; if a job has failed, instead of the result
1720
        there will be the error message
1721

1722
    """
1723
    if not self.jobs:
1724
      self.SubmitPending()
1725
    results = []
1726
    if self.verbose:
1727
      ok_jobs = [row[1] for row in self.jobs if row[0]]
1728
      if ok_jobs:
1729
        ToStdout("Submitted jobs %s", ", ".join(ok_jobs))
1730
    for submit_status, jid, name in self.jobs:
1731
      if not submit_status:
1732
        ToStderr("Failed to submit job for %s: %s", name, jid)
1733
        results.append((False, jid))
1734
        continue
1735
      if self.verbose:
1736
        ToStdout("Waiting for job %s for %s...", jid, name)
1737
      try:
1738
        job_result = PollJob(jid, cl=self.cl)
1739
        success = True
1740
      except (errors.GenericError, luxi.ProtocolError), err:
1741
        _, job_result = FormatError(err)
1742
        success = False
1743
        # the error message will always be shown, verbose or not
1744
        ToStderr("Job %s for %s has failed: %s", jid, name, job_result)
1745

    
1746
      results.append((success, job_result))
1747
    return results
1748

    
1749
  def WaitOrShow(self, wait):
1750
    """Wait for job results or only print the job IDs.
1751

1752
    @type wait: boolean
1753
    @param wait: whether to wait or not
1754

1755
    """
1756
    if wait:
1757
      return self.GetResults()
1758
    else:
1759
      if not self.jobs:
1760
        self.SubmitPending()
1761
      for status, result, name in self.jobs:
1762
        if status:
1763
          ToStdout("%s: %s", result, name)
1764
        else:
1765
          ToStderr("Failure for %s: %s", name, result)