Statistics
| Branch: | Tag: | Revision:

root / lib / cli.py @ 88cd08aa

History | View | Annotate | Download (56.2 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
  "FORCE_OPT",
66
  "FORCE_VARIANT_OPT",
67
  "GLOBAL_FILEDIR_OPT",
68
  "HVLIST_OPT",
69
  "HVOPTS_OPT",
70
  "HYPERVISOR_OPT",
71
  "IALLOCATOR_OPT",
72
  "IGNORE_CONSIST_OPT",
73
  "IGNORE_FAILURES_OPT",
74
  "IGNORE_SECONDARIES_OPT",
75
  "IGNORE_SIZE_OPT",
76
  "MAC_PREFIX_OPT",
77
  "MASTER_NETDEV_OPT",
78
  "MC_OPT",
79
  "NET_OPT",
80
  "NEW_SECONDARY_OPT",
81
  "NIC_PARAMS_OPT",
82
  "NODE_LIST_OPT",
83
  "NODE_PLACEMENT_OPT",
84
  "NOHDR_OPT",
85
  "NOIPCHECK_OPT",
86
  "NOLVM_STORAGE_OPT",
87
  "NOMODIFY_ETCHOSTS_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
  "SINGLE_NODE_OPT",
108
  "SRC_DIR_OPT",
109
  "SRC_NODE_OPT",
110
  "SUBMIT_OPT",
111
  "STATIC_OPT",
112
  "SYNC_OPT",
113
  "TAG_SRC_OPT",
114
  "USEUNITS_OPT",
115
  "VERBOSE_OPT",
116
  "VG_NAME_OPT",
117
  "YES_DOIT_OPT",
118
  # Generic functions for CLI programs
119
  "GenericMain",
120
  "GenericInstanceCreate",
121
  "GetClient",
122
  "GetOnlineNodes",
123
  "JobExecutor",
124
  "JobSubmittedException",
125
  "ParseTimespec",
126
  "SubmitOpCode",
127
  "SubmitOrSend",
128
  "UsesRPC",
129
  # Formatting functions
130
  "ToStderr", "ToStdout",
131
  "FormatError",
132
  "GenerateTable",
133
  "AskUser",
134
  "FormatTimestamp",
135
  # Tags functions
136
  "ListTags",
137
  "AddTags",
138
  "RemoveTags",
139
  # command line options support infrastructure
140
  "ARGS_MANY_INSTANCES",
141
  "ARGS_MANY_NODES",
142
  "ARGS_NONE",
143
  "ARGS_ONE_INSTANCE",
144
  "ARGS_ONE_NODE",
145
  "ArgChoice",
146
  "ArgCommand",
147
  "ArgFile",
148
  "ArgHost",
149
  "ArgInstance",
150
  "ArgJobId",
151
  "ArgNode",
152
  "ArgSuggest",
153
  "ArgUnknown",
154
  "OPT_COMPL_INST_ADD_NODES",
155
  "OPT_COMPL_MANY_NODES",
156
  "OPT_COMPL_ONE_IALLOCATOR",
157
  "OPT_COMPL_ONE_INSTANCE",
158
  "OPT_COMPL_ONE_NODE",
159
  "OPT_COMPL_ONE_OS",
160
  "cli_option",
161
  "SplitNodeOption",
162
  "CalculateOSNames",
163
  ]
164

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

    
168

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

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

    
178

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

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

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

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

    
193

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

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

200
  """
201

    
202

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

206
  """
207

    
208

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

212
  """
213

    
214

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

218
  """
219

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

223
  """
224

    
225

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

229
  """
230

    
231

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

235
  """
236

    
237

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

241
  """
242

    
243

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

    
250

    
251

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

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

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

    
272

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

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

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

    
301

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

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

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

    
319

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

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

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

    
336

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

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

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

    
353

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

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

    
363

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

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

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

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

    
400

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

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

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

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

    
428

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

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

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

    
437

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

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

    
456

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

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

    
474

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

    
478

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
724

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

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

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

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

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

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

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

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

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

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

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

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

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

    
784

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

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

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

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

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

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

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

    
819

    
820

    
821
def _ParseArgs(argv, commands, aliases):
822
  """Parser for the command line arguments.
823

824
  This function parses the arguments and returns the function which
825
  must be executed together with its (modified) arguments.
826

827
  @param argv: the command line
828
  @param commands: dictionary with special contents, see the design
829
      doc for cmdline handling
830
  @param aliases: dictionary with command aliases {'alias': 'target, ...}
831

832
  """
833
  if len(argv) == 0:
834
    binary = "<command>"
835
  else:
836
    binary = argv[0].split("/")[-1]
837

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

    
844
  if len(argv) < 2 or not (argv[1] in commands or
845
                           argv[1] in aliases):
846
    # let's do a nice thing
847
    sortedcmds = commands.keys()
848
    sortedcmds.sort()
849

    
850
    ToStdout("Usage: %s {command} [options...] [argument...]", binary)
851
    ToStdout("%s <command> --help to see details, or man %s", binary, binary)
852
    ToStdout("")
853

    
854
    # compute the max line length for cmd + usage
855
    mlen = max([len(" %s" % cmd) for cmd in commands])
856
    mlen = min(60, mlen) # should not get here...
857

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

    
868
    ToStdout("")
869

    
870
    return None, None, None
871

    
872
  # get command, unalias it, and look it up in commands
873
  cmd = argv.pop(1)
874
  if cmd in aliases:
875
    if cmd in commands:
876
      raise errors.ProgrammerError("Alias '%s' overrides an existing"
877
                                   " command" % cmd)
878

    
879
    if aliases[cmd] not in commands:
880
      raise errors.ProgrammerError("Alias '%s' maps to non-existing"
881
                                   " command '%s'" % (cmd, aliases[cmd]))
882

    
883
    cmd = aliases[cmd]
884

    
885
  func, args_def, parser_opts, usage, description = commands[cmd]
886
  parser = OptionParser(option_list=parser_opts + [_DRY_RUN_OPT, DEBUG_OPT],
887
                        description=description,
888
                        formatter=TitledHelpFormatter(),
889
                        usage="%%prog %s %s" % (cmd, usage))
890
  parser.disable_interspersed_args()
891
  options, args = parser.parse_args()
892

    
893
  if not _CheckArguments(cmd, args_def, args):
894
    return None, None, None
895

    
896
  return func, options, args
897

    
898

    
899
def _CheckArguments(cmd, args_def, args):
900
  """Verifies the arguments using the argument definition.
901

902
  Algorithm:
903

904
    1. Abort with error if values specified by user but none expected.
905

906
    1. For each argument in definition
907

908
      1. Keep running count of minimum number of values (min_count)
909
      1. Keep running count of maximum number of values (max_count)
910
      1. If it has an unlimited number of values
911

912
        1. Abort with error if it's not the last argument in the definition
913

914
    1. If last argument has limited number of values
915

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

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

920
  """
921
  if args and not args_def:
922
    ToStderr("Error: Command %s expects no arguments", cmd)
923
    return False
924

    
925
  min_count = None
926
  max_count = None
927
  check_max = None
928

    
929
  last_idx = len(args_def) - 1
930

    
931
  for idx, arg in enumerate(args_def):
932
    if min_count is None:
933
      min_count = arg.min
934
    elif arg.min is not None:
935
      min_count += arg.min
936

    
937
    if max_count is None:
938
      max_count = arg.max
939
    elif arg.max is not None:
940
      max_count += arg.max
941

    
942
    if idx == last_idx:
943
      check_max = (arg.max is not None)
944

    
945
    elif arg.max is None:
946
      raise errors.ProgrammerError("Only the last argument can have max=None")
947

    
948
  if check_max:
949
    # Command with exact number of arguments
950
    if (min_count is not None and max_count is not None and
951
        min_count == max_count and len(args) != min_count):
952
      ToStderr("Error: Command %s expects %d argument(s)", cmd, min_count)
953
      return False
954

    
955
    # Command with limited number of arguments
956
    if max_count is not None and len(args) > max_count:
957
      ToStderr("Error: Command %s expects only %d argument(s)",
958
               cmd, max_count)
959
      return False
960

    
961
  # Command with some required arguments
962
  if min_count is not None and len(args) < min_count:
963
    ToStderr("Error: Command %s expects at least %d argument(s)",
964
             cmd, min_count)
965
    return False
966

    
967
  return True
968

    
969

    
970
def SplitNodeOption(value):
971
  """Splits the value of a --node option.
972

973
  """
974
  if value and ':' in value:
975
    return value.split(':', 1)
976
  else:
977
    return (value, None)
978

    
979

    
980
def CalculateOSNames(os_name, os_variants):
981
  """Calculates all the names an OS can be called, according to its variants.
982

983
  @type os_name: string
984
  @param os_name: base name of the os
985
  @type os_variants: list or None
986
  @param os_variants: list of supported variants
987
  @rtype: list
988
  @return: list of valid names
989

990
  """
991
  if os_variants:
992
    return ['%s+%s' % (os_name, v) for v in os_variants]
993
  else:
994
    return [os_name]
995

    
996

    
997
def UsesRPC(fn):
998
  def wrapper(*args, **kwargs):
999
    rpc.Init()
1000
    try:
1001
      return fn(*args, **kwargs)
1002
    finally:
1003
      rpc.Shutdown()
1004
  return wrapper
1005

    
1006

    
1007
def AskUser(text, choices=None):
1008
  """Ask the user a question.
1009

1010
  @param text: the question to ask
1011

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

1017
  @return: one of the return values from the choices list; if input is
1018
      not possible (i.e. not running with a tty, we return the last
1019
      entry from the list
1020

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

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

    
1063

    
1064
class JobSubmittedException(Exception):
1065
  """Job was submitted, client should exit.
1066

1067
  This exception has one argument, the ID of the job that was
1068
  submitted. The handler should print this ID.
1069

1070
  This is not an error, just a structured way to exit from clients.
1071

1072
  """
1073

    
1074

    
1075
def SendJob(ops, cl=None):
1076
  """Function to submit an opcode without waiting for the results.
1077

1078
  @type ops: list
1079
  @param ops: list of opcodes
1080
  @type cl: luxi.Client
1081
  @param cl: the luxi client to use for communicating with the master;
1082
             if None, a new client will be created
1083

1084
  """
1085
  if cl is None:
1086
    cl = GetClient()
1087

    
1088
  job_id = cl.SubmitJob(ops)
1089

    
1090
  return job_id
1091

    
1092

    
1093
def PollJob(job_id, cl=None, feedback_fn=None):
1094
  """Function to poll for the result of a job.
1095

1096
  @type job_id: job identified
1097
  @param job_id: the job to poll for results
1098
  @type cl: luxi.Client
1099
  @param cl: the luxi client to use for communicating with the master;
1100
             if None, a new client will be created
1101

1102
  """
1103
  if cl is None:
1104
    cl = GetClient()
1105

    
1106
  prev_job_info = None
1107
  prev_logmsg_serial = None
1108

    
1109
  while True:
1110
    result = cl.WaitForJobChange(job_id, ["status"], prev_job_info,
1111
                                 prev_logmsg_serial)
1112
    if not result:
1113
      # job not found, go away!
1114
      raise errors.JobLost("Job with id %s lost" % job_id)
1115

    
1116
    # Split result, a tuple of (field values, log entries)
1117
    (job_info, log_entries) = result
1118
    (status, ) = job_info
1119

    
1120
    if log_entries:
1121
      for log_entry in log_entries:
1122
        (serial, timestamp, _, message) = log_entry
1123
        if callable(feedback_fn):
1124
          feedback_fn(log_entry[1:])
1125
        else:
1126
          encoded = utils.SafeEncode(message)
1127
          ToStdout("%s %s", time.ctime(utils.MergeTime(timestamp)), encoded)
1128
        prev_logmsg_serial = max(prev_logmsg_serial, serial)
1129

    
1130
    # TODO: Handle canceled and archived jobs
1131
    elif status in (constants.JOB_STATUS_SUCCESS,
1132
                    constants.JOB_STATUS_ERROR,
1133
                    constants.JOB_STATUS_CANCELING,
1134
                    constants.JOB_STATUS_CANCELED):
1135
      break
1136

    
1137
    prev_job_info = job_info
1138

    
1139
  jobs = cl.QueryJobs([job_id], ["status", "opstatus", "opresult"])
1140
  if not jobs:
1141
    raise errors.JobLost("Job with id %s lost" % job_id)
1142

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

    
1164

    
1165
def SubmitOpCode(op, cl=None, feedback_fn=None):
1166
  """Legacy function to submit an opcode.
1167

1168
  This is just a simple wrapper over the construction of the processor
1169
  instance. It should be extended to better handle feedback and
1170
  interaction functions.
1171

1172
  """
1173
  if cl is None:
1174
    cl = GetClient()
1175

    
1176
  job_id = SendJob([op], cl)
1177

    
1178
  op_results = PollJob(job_id, cl=cl, feedback_fn=feedback_fn)
1179

    
1180
  return op_results[0]
1181

    
1182

    
1183
def SubmitOrSend(op, opts, cl=None, feedback_fn=None):
1184
  """Wrapper around SubmitOpCode or SendJob.
1185

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

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

1193
  """
1194
  if opts and opts.dry_run:
1195
    op.dry_run = opts.dry_run
1196
  if opts and opts.submit_only:
1197
    job_id = SendJob([op], cl=cl)
1198
    raise JobSubmittedException(job_id)
1199
  else:
1200
    return SubmitOpCode(op, cl=cl, feedback_fn=feedback_fn)
1201

    
1202

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

    
1217

    
1218
def FormatError(err):
1219
  """Return a formatted error message for a given error.
1220

1221
  This function takes an exception instance and returns a tuple
1222
  consisting of two values: first, the recommended exit code, and
1223
  second, a string describing the error message (not
1224
  newline-terminated).
1225

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

    
1289

    
1290
def GenericMain(commands, override=None, aliases=None):
1291
  """Generic main function for all the gnt-* commands.
1292

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

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

    
1314
  if aliases is None:
1315
    aliases = {}
1316

    
1317
  try:
1318
    func, options, args = _ParseArgs(sys.argv, commands, aliases)
1319
  except errors.ParameterError, err:
1320
    result, err_msg = FormatError(err)
1321
    ToStderr(err_msg)
1322
    return 1
1323

    
1324
  if func is None: # parse error
1325
    return 1
1326

    
1327
  if override is not None:
1328
    for key, val in override.iteritems():
1329
      setattr(options, key, val)
1330

    
1331
  utils.SetupLogging(constants.LOG_COMMANDS, debug=options.debug,
1332
                     stderr_logging=True, program=binary)
1333

    
1334
  if old_cmdline:
1335
    logging.info("run with arguments '%s'", old_cmdline)
1336
  else:
1337
    logging.info("run with no arguments")
1338

    
1339
  try:
1340
    result = func(options, args)
1341
  except (errors.GenericError, luxi.ProtocolError,
1342
          JobSubmittedException), err:
1343
    result, err_msg = FormatError(err)
1344
    logging.exception("Error during command processing")
1345
    ToStderr(err_msg)
1346

    
1347
  return result
1348

    
1349

    
1350
def GenericInstanceCreate(mode, opts, args):
1351
  """Add an instance to the cluster via either creation or import.
1352

1353
  @param mode: constants.INSTANCE_CREATE or constants.INSTANCE_IMPORT
1354
  @param opts: the command line options selected by the user
1355
  @type args: list
1356
  @param args: should contain only one element, the new instance name
1357
  @rtype: int
1358
  @return: the desired exit code
1359

1360
  """
1361
  instance = args[0]
1362

    
1363
  (pnode, snode) = SplitNodeOption(opts.node)
1364

    
1365
  hypervisor = None
1366
  hvparams = {}
1367
  if opts.hypervisor:
1368
    hypervisor, hvparams = opts.hypervisor
1369

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

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

    
1421
  utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_TYPES)
1422
  utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
1423

    
1424
  if mode == constants.INSTANCE_CREATE:
1425
    start = opts.start
1426
    os_type = opts.os
1427
    src_node = None
1428
    src_path = None
1429
  elif mode == constants.INSTANCE_IMPORT:
1430
    start = False
1431
    os_type = None
1432
    src_node = opts.src_node
1433
    src_path = opts.src_dir
1434
  else:
1435
    raise errors.ProgrammerError("Invalid creation mode %s" % mode)
1436

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

    
1456
  SubmitOrSend(op, opts)
1457
  return 0
1458

    
1459

    
1460
def GenerateTable(headers, fields, separator, data,
1461
                  numfields=None, unitfields=None,
1462
                  units=None):
1463
  """Prints a table with headers and different fields.
1464

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

1488
  """
1489
  if units is None:
1490
    if separator:
1491
      units = "m"
1492
    else:
1493
      units = "h"
1494

    
1495
  if numfields is None:
1496
    numfields = []
1497
  if unitfields is None:
1498
    unitfields = []
1499

    
1500
  numfields = utils.FieldSet(*numfields)
1501
  unitfields = utils.FieldSet(*unitfields)
1502

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

    
1517
  if separator is None:
1518
    mlens = [0 for name in fields]
1519
    format = ' '.join(format_fields)
1520
  else:
1521
    format = separator.replace("%", "%%").join(format_fields)
1522

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

    
1538
  result = []
1539
  if headers:
1540
    args = []
1541
    for idx, name in enumerate(fields):
1542
      hdr = headers[name]
1543
      if separator is None:
1544
        mlens[idx] = max(mlens[idx], len(hdr))
1545
        args.append(mlens[idx])
1546
      args.append(hdr)
1547
    result.append(format % tuple(args))
1548

    
1549
  for line in data:
1550
    args = []
1551
    if line is None:
1552
      line = ['-' for _ in fields]
1553
    for idx in range(len(fields)):
1554
      if separator is None:
1555
        args.append(mlens[idx])
1556
      args.append(line[idx])
1557
    result.append(format % tuple(args))
1558

    
1559
  return result
1560

    
1561

    
1562
def FormatTimestamp(ts):
1563
  """Formats a given timestamp.
1564

1565
  @type ts: timestamp
1566
  @param ts: a timeval-type timestamp, a tuple of seconds and microseconds
1567

1568
  @rtype: string
1569
  @return: a string with the formatted timestamp
1570

1571
  """
1572
  if not isinstance (ts, (tuple, list)) or len(ts) != 2:
1573
    return '?'
1574
  sec, usec = ts
1575
  return time.strftime("%F %T", time.localtime(sec)) + ".%06d" % usec
1576

    
1577

    
1578
def ParseTimespec(value):
1579
  """Parse a time specification.
1580

1581
  The following suffixed will be recognized:
1582

1583
    - s: seconds
1584
    - m: minutes
1585
    - h: hours
1586
    - d: day
1587
    - w: weeks
1588

1589
  Without any suffix, the value will be taken to be in seconds.
1590

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

    
1619

    
1620
def GetOnlineNodes(nodes, cl=None, nowarn=False):
1621
  """Returns the names of online nodes.
1622

1623
  This function will also log a warning on stderr with the names of
1624
  the online nodes.
1625

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

1634
  """
1635
  if cl is None:
1636
    cl = GetClient()
1637

    
1638
  result = cl.QueryNodes(names=nodes, fields=["name", "offline"],
1639
                         use_locking=False)
1640
  offline = [row[0] for row in result if row[1]]
1641
  if offline and not nowarn:
1642
    ToStderr("Note: skipping offline node(s): %s" % ", ".join(offline))
1643
  return [row[0] for row in result if not row[1]]
1644

    
1645

    
1646
def _ToStream(stream, txt, *args):
1647
  """Write a message to a stream, bypassing the logging system
1648

1649
  @type stream: file object
1650
  @param stream: the file to which we should write
1651
  @type txt: str
1652
  @param txt: the message
1653

1654
  """
1655
  if args:
1656
    args = tuple(args)
1657
    stream.write(txt % args)
1658
  else:
1659
    stream.write(txt)
1660
  stream.write('\n')
1661
  stream.flush()
1662

    
1663

    
1664
def ToStdout(txt, *args):
1665
  """Write a message to stdout only, bypassing the logging system
1666

1667
  This is just a wrapper over _ToStream.
1668

1669
  @type txt: str
1670
  @param txt: the message
1671

1672
  """
1673
  _ToStream(sys.stdout, txt, *args)
1674

    
1675

    
1676
def ToStderr(txt, *args):
1677
  """Write a message to stderr only, bypassing the logging system
1678

1679
  This is just a wrapper over _ToStream.
1680

1681
  @type txt: str
1682
  @param txt: the message
1683

1684
  """
1685
  _ToStream(sys.stderr, txt, *args)
1686

    
1687

    
1688
class JobExecutor(object):
1689
  """Class which manages the submission and execution of multiple jobs.
1690

1691
  Note that instances of this class should not be reused between
1692
  GetResults() calls.
1693

1694
  """
1695
  def __init__(self, cl=None, verbose=True):
1696
    self.queue = []
1697
    if cl is None:
1698
      cl = GetClient()
1699
    self.cl = cl
1700
    self.verbose = verbose
1701
    self.jobs = []
1702

    
1703
  def QueueJob(self, name, *ops):
1704
    """Record a job for later submit.
1705

1706
    @type name: string
1707
    @param name: a description of the job, will be used in WaitJobSet
1708
    """
1709
    self.queue.append((name, ops))
1710

    
1711
  def SubmitPending(self):
1712
    """Submit all pending jobs.
1713

1714
    """
1715
    results = self.cl.SubmitManyJobs([row[1] for row in self.queue])
1716
    for ((status, data), (name, _)) in zip(results, self.queue):
1717
      self.jobs.append((status, data, name))
1718

    
1719
  def GetResults(self):
1720
    """Wait for and return the results of all jobs.
1721

1722
    @rtype: list
1723
    @return: list of tuples (success, job results), in the same order
1724
        as the submitted jobs; if a job has failed, instead of the result
1725
        there will be the error message
1726

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

    
1751
      results.append((success, job_result))
1752
    return results
1753

    
1754
  def WaitOrShow(self, wait):
1755
    """Wait for job results or only print the job IDs.
1756

1757
    @type wait: boolean
1758
    @param wait: whether to wait or not
1759

1760
    """
1761
    if wait:
1762
      return self.GetResults()
1763
    else:
1764
      if not self.jobs:
1765
        self.SubmitPending()
1766
      for status, result, name in self.jobs:
1767
        if status:
1768
          ToStdout("%s: %s", result, name)
1769
        else:
1770
          ToStderr("Failure for %s: %s", name, result)