Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-instance @ 460d22be

History | View | Annotate | Download (46.8 kB)

1
#!/usr/bin/python
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
# pylint: disable-msg=W0401,W0614
23
# W0401: Wildcard import ganeti.cli
24
# W0614: Unused import %s from wildcard import (since we need cli)
25

    
26
import sys
27
import os
28
import itertools
29
import simplejson
30
import time
31
from cStringIO import StringIO
32

    
33
from ganeti.cli import *
34
from ganeti import opcodes
35
from ganeti import constants
36
from ganeti import utils
37
from ganeti import errors
38

    
39

    
40
_SHUTDOWN_CLUSTER = "cluster"
41
_SHUTDOWN_NODES_BOTH = "nodes"
42
_SHUTDOWN_NODES_PRI = "nodes-pri"
43
_SHUTDOWN_NODES_SEC = "nodes-sec"
44
_SHUTDOWN_INSTANCES = "instances"
45

    
46

    
47
_VALUE_TRUE = "true"
48

    
49
#: default list of options for L{ListInstances}
50
_LIST_DEF_FIELDS = [
51
  "name", "hypervisor", "os", "pnode", "status", "oper_ram",
52
  ]
53

    
54

    
55
def _ExpandMultiNames(mode, names, client=None):
56
  """Expand the given names using the passed mode.
57

    
58
  For _SHUTDOWN_CLUSTER, all instances will be returned. For
59
  _SHUTDOWN_NODES_PRI/SEC, all instances having those nodes as
60
  primary/secondary will be returned. For _SHUTDOWN_NODES_BOTH, all
61
  instances having those nodes as either primary or secondary will be
62
  returned. For _SHUTDOWN_INSTANCES, the given instances will be
63
  returned.
64

    
65
  @param mode: one of L{_SHUTDOWN_CLUSTER}, L{_SHUTDOWN_NODES_BOTH},
66
      L{_SHUTDOWN_NODES_PRI}, L{_SHUTDOWN_NODES_SEC} or
67
      L{_SHUTDOWN_INSTANCES}
68
  @param names: a list of names; for cluster, it must be empty,
69
      and for node and instance it must be a list of valid item
70
      names (short names are valid as usual, e.g. node1 instead of
71
      node1.example.com)
72
  @rtype: list
73
  @return: the list of names after the expansion
74
  @raise errors.ProgrammerError: for unknown selection type
75
  @raise errors.OpPrereqError: for invalid input parameters
76

    
77
  """
78
  if client is None:
79
    client = GetClient()
80
  if mode == _SHUTDOWN_CLUSTER:
81
    if names:
82
      raise errors.OpPrereqError("Cluster filter mode takes no arguments",
83
                                 errors.ECODE_INVAL)
84
    idata = client.QueryInstances([], ["name"], False)
85
    inames = [row[0] for row in idata]
86

    
87
  elif mode in (_SHUTDOWN_NODES_BOTH,
88
                _SHUTDOWN_NODES_PRI,
89
                _SHUTDOWN_NODES_SEC):
90
    if not names:
91
      raise errors.OpPrereqError("No node names passed", errors.ECODE_INVAL)
92
    ndata = client.QueryNodes(names, ["name", "pinst_list", "sinst_list"],
93
                              False)
94
    ipri = [row[1] for row in ndata]
95
    pri_names = list(itertools.chain(*ipri))
96
    isec = [row[2] for row in ndata]
97
    sec_names = list(itertools.chain(*isec))
98
    if mode == _SHUTDOWN_NODES_BOTH:
99
      inames = pri_names + sec_names
100
    elif mode == _SHUTDOWN_NODES_PRI:
101
      inames = pri_names
102
    elif mode == _SHUTDOWN_NODES_SEC:
103
      inames = sec_names
104
    else:
105
      raise errors.ProgrammerError("Unhandled shutdown type")
106

    
107
  elif mode == _SHUTDOWN_INSTANCES:
108
    if not names:
109
      raise errors.OpPrereqError("No instance names passed",
110
                                 errors.ECODE_INVAL)
111
    idata = client.QueryInstances(names, ["name"], False)
112
    inames = [row[0] for row in idata]
113

    
114
  else:
115
    raise errors.OpPrereqError("Unknown mode '%s'" % mode, errors.ECODE_INVAL)
116

    
117
  return inames
118

    
119

    
120
def _ConfirmOperation(inames, text, extra=""):
121
  """Ask the user to confirm an operation on a list of instances.
122

    
123
  This function is used to request confirmation for doing an operation
124
  on a given list of instances.
125

    
126
  @type inames: list
127
  @param inames: the list of names that we display when
128
      we ask for confirmation
129
  @type text: str
130
  @param text: the operation that the user should confirm
131
      (e.g. I{shutdown} or I{startup})
132
  @rtype: boolean
133
  @return: True or False depending on user's confirmation.
134

    
135
  """
136
  count = len(inames)
137
  msg = ("The %s will operate on %d instances.\n%s"
138
         "Do you want to continue?" % (text, count, extra))
139
  affected = ("\nAffected instances:\n" +
140
              "\n".join(["  %s" % name for name in inames]))
141

    
142
  choices = [('y', True, 'Yes, execute the %s' % text),
143
             ('n', False, 'No, abort the %s' % text)]
144

    
145
  if count > 20:
146
    choices.insert(1, ('v', 'v', 'View the list of affected instances'))
147
    ask = msg
148
  else:
149
    ask = msg + affected
150

    
151
  choice = AskUser(ask, choices)
152
  if choice == 'v':
153
    choices.pop(1)
154
    choice = AskUser(msg + affected, choices)
155
  return choice
156

    
157

    
158
def _EnsureInstancesExist(client, names):
159
  """Check for and ensure the given instance names exist.
160

    
161
  This function will raise an OpPrereqError in case they don't
162
  exist. Otherwise it will exit cleanly.
163

    
164
  @type client: L{ganeti.luxi.Client}
165
  @param client: the client to use for the query
166
  @type names: list
167
  @param names: the list of instance names to query
168
  @raise errors.OpPrereqError: in case any instance is missing
169

    
170
  """
171
  # TODO: change LUQueryInstances to that it actually returns None
172
  # instead of raising an exception, or devise a better mechanism
173
  result = client.QueryInstances(names, ["name"], False)
174
  for orig_name, row in zip(names, result):
175
    if row[0] is None:
176
      raise errors.OpPrereqError("Instance '%s' does not exist" % orig_name,
177
                                 errors.ECODE_NOENT)
178

    
179

    
180
def GenericManyOps(operation, fn):
181
  """Generic multi-instance operations.
182

    
183
  The will return a wrapper that processes the options and arguments
184
  given, and uses the passed function to build the opcode needed for
185
  the specific operation. Thus all the generic loop/confirmation code
186
  is abstracted into this function.
187

    
188
  """
189
  def realfn(opts, args):
190
    if opts.multi_mode is None:
191
      opts.multi_mode = _SHUTDOWN_INSTANCES
192
    cl = GetClient()
193
    inames = _ExpandMultiNames(opts.multi_mode, args, client=cl)
194
    if not inames:
195
      raise errors.OpPrereqError("Selection filter does not match"
196
                                 " any instances", errors.ECODE_INVAL)
197
    multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
198
    if not (opts.force_multi or not multi_on
199
            or _ConfirmOperation(inames, operation)):
200
      return 1
201
    jex = JobExecutor(verbose=multi_on, cl=cl)
202
    for name in inames:
203
      op = fn(name, opts)
204
      jex.QueueJob(name, op)
205
    jex.WaitOrShow(not opts.submit_only)
206
    return 0
207
  return realfn
208

    
209

    
210
def ListInstances(opts, args):
211
  """List instances and their properties.
212

    
213
  @param opts: the command line options selected by the user
214
  @type args: list
215
  @param args: should be an empty list
216
  @rtype: int
217
  @return: the desired exit code
218

    
219
  """
220
  if opts.output is None:
221
    selected_fields = _LIST_DEF_FIELDS
222
  elif opts.output.startswith("+"):
223
    selected_fields = _LIST_DEF_FIELDS + opts.output[1:].split(",")
224
  else:
225
    selected_fields = opts.output.split(",")
226

    
227
  output = GetClient().QueryInstances(args, selected_fields, opts.do_locking)
228

    
229
  if not opts.no_headers:
230
    headers = {
231
      "name": "Instance", "os": "OS", "pnode": "Primary_node",
232
      "snodes": "Secondary_Nodes", "admin_state": "Autostart",
233
      "oper_state": "Running",
234
      "oper_ram": "Memory", "disk_template": "Disk_template",
235
      "ip": "IP_address", "mac": "MAC_address",
236
      "nic_mode": "NIC_Mode", "nic_link": "NIC_Link",
237
      "bridge": "Bridge",
238
      "sda_size": "Disk/0", "sdb_size": "Disk/1",
239
      "disk_usage": "DiskUsage",
240
      "status": "Status", "tags": "Tags",
241
      "network_port": "Network_port",
242
      "hv/kernel_path": "Kernel_path",
243
      "hv/initrd_path": "Initrd_path",
244
      "hv/boot_order": "Boot_order",
245
      "hv/acpi": "ACPI",
246
      "hv/pae": "PAE",
247
      "hv/cdrom_image_path": "CDROM_image_path",
248
      "hv/nic_type": "NIC_type",
249
      "hv/disk_type": "Disk_type",
250
      "hv/vnc_bind_address": "VNC_bind_address",
251
      "serial_no": "SerialNo", "hypervisor": "Hypervisor",
252
      "hvparams": "Hypervisor_parameters",
253
      "be/memory": "Configured_memory",
254
      "be/vcpus": "VCPUs",
255
      "vcpus": "VCPUs",
256
      "be/auto_balance": "Auto_balance",
257
      "disk.count": "Disks", "disk.sizes": "Disk_sizes",
258
      "nic.count": "NICs", "nic.ips": "NIC_IPs",
259
      "nic.modes": "NIC_modes", "nic.links": "NIC_links",
260
      "nic.bridges": "NIC_bridges", "nic.macs": "NIC_MACs",
261
      "ctime": "CTime", "mtime": "MTime", "uuid": "UUID",
262
      }
263
  else:
264
    headers = None
265

    
266
  unitfields = ["be/memory", "oper_ram", "sd(a|b)_size", "disk\.size/.*"]
267
  numfields = ["be/memory", "oper_ram", "sd(a|b)_size", "be/vcpus",
268
               "serial_no", "(disk|nic)\.count", "disk\.size/.*"]
269

    
270
  list_type_fields = ("tags", "disk.sizes", "nic.macs", "nic.ips",
271
                      "nic.modes", "nic.links", "nic.bridges")
272
  # change raw values to nicer strings
273
  for row in output:
274
    for idx, field in enumerate(selected_fields):
275
      val = row[idx]
276
      if field == "snodes":
277
        val = ",".join(val) or "-"
278
      elif field == "admin_state":
279
        if val:
280
          val = "yes"
281
        else:
282
          val = "no"
283
      elif field == "oper_state":
284
        if val is None:
285
          val = "(node down)"
286
        elif val: # True
287
          val = "running"
288
        else:
289
          val = "stopped"
290
      elif field == "oper_ram":
291
        if val is None:
292
          val = "(node down)"
293
      elif field == "sda_size" or field == "sdb_size":
294
        if val is None:
295
          val = "N/A"
296
      elif field == "ctime" or field == "mtime":
297
        val = utils.FormatTime(val)
298
      elif field in list_type_fields:
299
        val = ",".join(str(item) for item in val)
300
      elif val is None:
301
        val = "-"
302
      row[idx] = str(val)
303

    
304
  data = GenerateTable(separator=opts.separator, headers=headers,
305
                       fields=selected_fields, unitfields=unitfields,
306
                       numfields=numfields, data=output, units=opts.units)
307

    
308
  for line in data:
309
    ToStdout(line)
310

    
311
  return 0
312

    
313

    
314
def AddInstance(opts, args):
315
  """Add an instance to the cluster.
316

    
317
  This is just a wrapper over GenericInstanceCreate.
318

    
319
  """
320
  return GenericInstanceCreate(constants.INSTANCE_CREATE, opts, args)
321

    
322

    
323
def BatchCreate(opts, args):
324
  """Create instances using a definition file.
325

    
326
  This function reads a json file with instances defined
327
  in the form::
328

    
329
    {"instance-name":{
330
      "disk_size": [20480],
331
      "template": "drbd",
332
      "backend": {
333
        "memory": 512,
334
        "vcpus": 1 },
335
      "os": "debootstrap",
336
      "primary_node": "firstnode",
337
      "secondary_node": "secondnode",
338
      "iallocator": "dumb"}
339
    }
340

    
341
  Note that I{primary_node} and I{secondary_node} have precedence over
342
  I{iallocator}.
343

    
344
  @param opts: the command line options selected by the user
345
  @type args: list
346
  @param args: should contain one element, the json filename
347
  @rtype: int
348
  @return: the desired exit code
349

    
350
  """
351
  _DEFAULT_SPECS = {"disk_size": [20 * 1024],
352
                    "backend": {},
353
                    "iallocator": None,
354
                    "primary_node": None,
355
                    "secondary_node": None,
356
                    "nics": None,
357
                    "start": True,
358
                    "ip_check": True,
359
                    "name_check": True,
360
                    "hypervisor": None,
361
                    "hvparams": {},
362
                    "file_storage_dir": None,
363
                    "file_driver": 'loop'}
364

    
365
  def _PopulateWithDefaults(spec):
366
    """Returns a new hash combined with default values."""
367
    mydict = _DEFAULT_SPECS.copy()
368
    mydict.update(spec)
369
    return mydict
370

    
371
  def _Validate(spec):
372
    """Validate the instance specs."""
373
    # Validate fields required under any circumstances
374
    for required_field in ('os', 'template'):
375
      if required_field not in spec:
376
        raise errors.OpPrereqError('Required field "%s" is missing.' %
377
                                   required_field, errors.ECODE_INVAL)
378
    # Validate special fields
379
    if spec['primary_node'] is not None:
380
      if (spec['template'] in constants.DTS_NET_MIRROR and
381
          spec['secondary_node'] is None):
382
        raise errors.OpPrereqError('Template requires secondary node, but'
383
                                   ' there was no secondary provided.',
384
                                   errors.ECODE_INVAL)
385
    elif spec['iallocator'] is None:
386
      raise errors.OpPrereqError('You have to provide at least a primary_node'
387
                                 ' or an iallocator.',
388
                                 errors.ECODE_INVAL)
389

    
390
    if (spec['hvparams'] and
391
        not isinstance(spec['hvparams'], dict)):
392
      raise errors.OpPrereqError('Hypervisor parameters must be a dict.',
393
                                 errors.ECODE_INVAL)
394

    
395
  json_filename = args[0]
396
  try:
397
    instance_data = simplejson.loads(utils.ReadFile(json_filename))
398
  except Exception, err:
399
    ToStderr("Can't parse the instance definition file: %s" % str(err))
400
    return 1
401

    
402
  jex = JobExecutor()
403

    
404
  # Iterate over the instances and do:
405
  #  * Populate the specs with default value
406
  #  * Validate the instance specs
407
  i_names = utils.NiceSort(instance_data.keys())
408
  for name in i_names:
409
    specs = instance_data[name]
410
    specs = _PopulateWithDefaults(specs)
411
    _Validate(specs)
412

    
413
    hypervisor = specs['hypervisor']
414
    hvparams = specs['hvparams']
415

    
416
    disks = []
417
    for elem in specs['disk_size']:
418
      try:
419
        size = utils.ParseUnit(elem)
420
      except ValueError, err:
421
        raise errors.OpPrereqError("Invalid disk size '%s' for"
422
                                   " instance %s: %s" %
423
                                   (elem, name, err), errors.ECODE_INVAL)
424
      disks.append({"size": size})
425

    
426
    utils.ForceDictType(specs['backend'], constants.BES_PARAMETER_TYPES)
427
    utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
428

    
429
    tmp_nics = []
430
    for field in ('ip', 'mac', 'mode', 'link', 'bridge'):
431
      if field in specs:
432
        if not tmp_nics:
433
          tmp_nics.append({})
434
        tmp_nics[0][field] = specs[field]
435

    
436
    if specs['nics'] is not None and tmp_nics:
437
      raise errors.OpPrereqError("'nics' list incompatible with using"
438
                                 " individual nic fields as well",
439
                                 errors.ECODE_INVAL)
440
    elif specs['nics'] is not None:
441
      tmp_nics = specs['nics']
442
    elif not tmp_nics:
443
      tmp_nics = [{}]
444

    
445
    op = opcodes.OpCreateInstance(instance_name=name,
446
                                  disks=disks,
447
                                  disk_template=specs['template'],
448
                                  mode=constants.INSTANCE_CREATE,
449
                                  os_type=specs['os'],
450
                                  force_variant=opts.force_variant,
451
                                  pnode=specs['primary_node'],
452
                                  snode=specs['secondary_node'],
453
                                  nics=tmp_nics,
454
                                  start=specs['start'],
455
                                  ip_check=specs['ip_check'],
456
                                  name_check=specs['name_check'],
457
                                  wait_for_sync=True,
458
                                  iallocator=specs['iallocator'],
459
                                  hypervisor=hypervisor,
460
                                  hvparams=hvparams,
461
                                  beparams=specs['backend'],
462
                                  file_storage_dir=specs['file_storage_dir'],
463
                                  file_driver=specs['file_driver'])
464

    
465
    jex.QueueJob(name, op)
466
  # we never want to wait, just show the submitted job IDs
467
  jex.WaitOrShow(False)
468

    
469
  return 0
470

    
471

    
472
def ReinstallInstance(opts, args):
473
  """Reinstall an instance.
474

    
475
  @param opts: the command line options selected by the user
476
  @type args: list
477
  @param args: should contain only one element, the name of the
478
      instance to be reinstalled
479
  @rtype: int
480
  @return: the desired exit code
481

    
482
  """
483
  # first, compute the desired name list
484
  if opts.multi_mode is None:
485
    opts.multi_mode = _SHUTDOWN_INSTANCES
486

    
487
  inames = _ExpandMultiNames(opts.multi_mode, args)
488
  if not inames:
489
    raise errors.OpPrereqError("Selection filter does not match any instances",
490
                               errors.ECODE_INVAL)
491

    
492
  # second, if requested, ask for an OS
493
  if opts.select_os is True:
494
    op = opcodes.OpDiagnoseOS(output_fields=["name", "valid", "variants"],
495
                              names=[])
496
    result = SubmitOpCode(op)
497

    
498
    if not result:
499
      ToStdout("Can't get the OS list")
500
      return 1
501

    
502
    ToStdout("Available OS templates:")
503
    number = 0
504
    choices = []
505
    for (name, valid, variants) in result:
506
      if valid:
507
        for entry in CalculateOSNames(name, variants):
508
          ToStdout("%3s: %s", number, entry)
509
          choices.append(("%s" % number, entry, entry))
510
          number += 1
511

    
512
    choices.append(('x', 'exit', 'Exit gnt-instance reinstall'))
513
    selected = AskUser("Enter OS template number (or x to abort):",
514
                       choices)
515

    
516
    if selected == 'exit':
517
      ToStderr("User aborted reinstall, exiting")
518
      return 1
519

    
520
    os_name = selected
521
  else:
522
    os_name = opts.os
523

    
524
  # third, get confirmation: multi-reinstall requires --force-multi
525
  # *and* --force, single-reinstall just --force
526
  multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
527
  if multi_on:
528
    warn_msg = "Note: this will remove *all* data for the below instances!\n"
529
    if not ((opts.force_multi and opts.force) or
530
            _ConfirmOperation(inames, "reinstall", extra=warn_msg)):
531
      return 1
532
  else:
533
    if not opts.force:
534
      usertext = ("This will reinstall the instance %s and remove"
535
                  " all data. Continue?") % inames[0]
536
      if not AskUser(usertext):
537
        return 1
538

    
539
  jex = JobExecutor(verbose=multi_on)
540
  for instance_name in inames:
541
    op = opcodes.OpReinstallInstance(instance_name=instance_name,
542
                                     os_type=os_name,
543
                                     force_variant=opts.force_variant)
544
    jex.QueueJob(instance_name, op)
545

    
546
  jex.WaitOrShow(not opts.submit_only)
547
  return 0
548

    
549

    
550
def RemoveInstance(opts, args):
551
  """Remove an instance.
552

    
553
  @param opts: the command line options selected by the user
554
  @type args: list
555
  @param args: should contain only one element, the name of
556
      the instance to be removed
557
  @rtype: int
558
  @return: the desired exit code
559

    
560
  """
561
  instance_name = args[0]
562
  force = opts.force
563
  cl = GetClient()
564

    
565
  if not force:
566
    _EnsureInstancesExist(cl, [instance_name])
567

    
568
    usertext = ("This will remove the volumes of the instance %s"
569
                " (including mirrors), thus removing all the data"
570
                " of the instance. Continue?") % instance_name
571
    if not AskUser(usertext):
572
      return 1
573

    
574
  op = opcodes.OpRemoveInstance(instance_name=instance_name,
575
                                ignore_failures=opts.ignore_failures,
576
                                shutdown_timeout=opts.shutdown_timeout)
577
  SubmitOrSend(op, opts, cl=cl)
578
  return 0
579

    
580

    
581
def RenameInstance(opts, args):
582
  """Rename an instance.
583

    
584
  @param opts: the command line options selected by the user
585
  @type args: list
586
  @param args: should contain two elements, the old and the
587
      new instance names
588
  @rtype: int
589
  @return: the desired exit code
590

    
591
  """
592
  op = opcodes.OpRenameInstance(instance_name=args[0],
593
                                new_name=args[1],
594
                                ignore_ip=opts.ignore_ip)
595
  SubmitOrSend(op, opts)
596
  return 0
597

    
598

    
599
def ActivateDisks(opts, args):
600
  """Activate an instance's disks.
601

    
602
  This serves two purposes:
603
    - it allows (as long as the instance is not running)
604
      mounting the disks and modifying them from the node
605
    - it repairs inactive secondary drbds
606

    
607
  @param opts: the command line options selected by the user
608
  @type args: list
609
  @param args: should contain only one element, the instance name
610
  @rtype: int
611
  @return: the desired exit code
612

    
613
  """
614
  instance_name = args[0]
615
  op = opcodes.OpActivateInstanceDisks(instance_name=instance_name,
616
                                       ignore_size=opts.ignore_size)
617
  disks_info = SubmitOrSend(op, opts)
618
  for host, iname, nname in disks_info:
619
    ToStdout("%s:%s:%s", host, iname, nname)
620
  return 0
621

    
622

    
623
def DeactivateDisks(opts, args):
624
  """Deactivate an instance's disks.
625

    
626
  This function takes the instance name, looks for its primary node
627
  and the tries to shutdown its block devices on that node.
628

    
629
  @param opts: the command line options selected by the user
630
  @type args: list
631
  @param args: should contain only one element, the instance name
632
  @rtype: int
633
  @return: the desired exit code
634

    
635
  """
636
  instance_name = args[0]
637
  op = opcodes.OpDeactivateInstanceDisks(instance_name=instance_name)
638
  SubmitOrSend(op, opts)
639
  return 0
640

    
641

    
642
def RecreateDisks(opts, args):
643
  """Recreate an instance's disks.
644

    
645
  @param opts: the command line options selected by the user
646
  @type args: list
647
  @param args: should contain only one element, the instance name
648
  @rtype: int
649
  @return: the desired exit code
650

    
651
  """
652
  instance_name = args[0]
653
  if opts.disks:
654
    try:
655
      opts.disks = [int(v) for v in opts.disks.split(",")]
656
    except (ValueError, TypeError), err:
657
      ToStderr("Invalid disks value: %s" % str(err))
658
      return 1
659
  else:
660
    opts.disks = []
661

    
662
  op = opcodes.OpRecreateInstanceDisks(instance_name=instance_name,
663
                                       disks=opts.disks)
664
  SubmitOrSend(op, opts)
665
  return 0
666

    
667

    
668
def GrowDisk(opts, args):
669
  """Grow an instance's disks.
670

    
671
  @param opts: the command line options selected by the user
672
  @type args: list
673
  @param args: should contain two elements, the instance name
674
      whose disks we grow and the disk name, e.g. I{sda}
675
  @rtype: int
676
  @return: the desired exit code
677

    
678
  """
679
  instance = args[0]
680
  disk = args[1]
681
  try:
682
    disk = int(disk)
683
  except ValueError, err:
684
    raise errors.OpPrereqError("Invalid disk index: %s" % str(err),
685
                               errors.ECODE_INVAL)
686
  amount = utils.ParseUnit(args[2])
687
  op = opcodes.OpGrowDisk(instance_name=instance, disk=disk, amount=amount,
688
                          wait_for_sync=opts.wait_for_sync)
689
  SubmitOrSend(op, opts)
690
  return 0
691

    
692

    
693
def _StartupInstance(name, opts):
694
  """Startup instances.
695

    
696
  This returns the opcode to start an instance, and its decorator will
697
  wrap this into a loop starting all desired instances.
698

    
699
  @param name: the name of the instance to act on
700
  @param opts: the command line options selected by the user
701
  @return: the opcode needed for the operation
702

    
703
  """
704
  op = opcodes.OpStartupInstance(instance_name=name,
705
                                 force=opts.force)
706
  # do not add these parameters to the opcode unless they're defined
707
  if opts.hvparams:
708
    op.hvparams = opts.hvparams
709
  if opts.beparams:
710
    op.beparams = opts.beparams
711
  return op
712

    
713

    
714
def _RebootInstance(name, opts):
715
  """Reboot instance(s).
716

    
717
  This returns the opcode to reboot an instance, and its decorator
718
  will wrap this into a loop rebooting all desired instances.
719

    
720
  @param name: the name of the instance to act on
721
  @param opts: the command line options selected by the user
722
  @return: the opcode needed for the operation
723

    
724
  """
725
  return opcodes.OpRebootInstance(instance_name=name,
726
                                  reboot_type=opts.reboot_type,
727
                                  ignore_secondaries=opts.ignore_secondaries,
728
                                  shutdown_timeout=opts.shutdown_timeout)
729

    
730

    
731
def _ShutdownInstance(name, opts):
732
  """Shutdown an instance.
733

    
734
  This returns the opcode to shutdown an instance, and its decorator
735
  will wrap this into a loop shutting down all desired instances.
736

    
737
  @param name: the name of the instance to act on
738
  @param opts: the command line options selected by the user
739
  @return: the opcode needed for the operation
740

    
741
  """
742
  return opcodes.OpShutdownInstance(instance_name=name,
743
                                    timeout=opts.timeout)
744

    
745

    
746
def ReplaceDisks(opts, args):
747
  """Replace the disks of an instance
748

    
749
  @param opts: the command line options selected by the user
750
  @type args: list
751
  @param args: should contain only one element, the instance name
752
  @rtype: int
753
  @return: the desired exit code
754

    
755
  """
756
  instance_name = args[0]
757
  new_2ndary = opts.dst_node
758
  iallocator = opts.iallocator
759
  if opts.disks is None:
760
    disks = []
761
  else:
762
    try:
763
      disks = [int(i) for i in opts.disks.split(",")]
764
    except ValueError, err:
765
      raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err),
766
                                 errors.ECODE_INVAL)
767
  cnt = [opts.on_primary, opts.on_secondary, opts.auto,
768
         new_2ndary is not None, iallocator is not None].count(True)
769
  if cnt != 1:
770
    raise errors.OpPrereqError("One and only one of the -p, -s, -a, -n and -i"
771
                               " options must be passed", errors.ECODE_INVAL)
772
  elif opts.on_primary:
773
    mode = constants.REPLACE_DISK_PRI
774
  elif opts.on_secondary:
775
    mode = constants.REPLACE_DISK_SEC
776
  elif opts.auto:
777
    mode = constants.REPLACE_DISK_AUTO
778
    if disks:
779
      raise errors.OpPrereqError("Cannot specify disks when using automatic"
780
                                 " mode", errors.ECODE_INVAL)
781
  elif new_2ndary is not None or iallocator is not None:
782
    # replace secondary
783
    mode = constants.REPLACE_DISK_CHG
784

    
785
  op = opcodes.OpReplaceDisks(instance_name=args[0], disks=disks,
786
                              remote_node=new_2ndary, mode=mode,
787
                              iallocator=iallocator)
788
  SubmitOrSend(op, opts)
789
  return 0
790

    
791

    
792
def FailoverInstance(opts, args):
793
  """Failover an instance.
794

    
795
  The failover is done by shutting it down on its present node and
796
  starting it on the secondary.
797

    
798
  @param opts: the command line options selected by the user
799
  @type args: list
800
  @param args: should contain only one element, the instance name
801
  @rtype: int
802
  @return: the desired exit code
803

    
804
  """
805
  cl = GetClient()
806
  instance_name = args[0]
807
  force = opts.force
808

    
809
  if not force:
810
    _EnsureInstancesExist(cl, [instance_name])
811

    
812
    usertext = ("Failover will happen to image %s."
813
                " This requires a shutdown of the instance. Continue?" %
814
                (instance_name,))
815
    if not AskUser(usertext):
816
      return 1
817

    
818
  op = opcodes.OpFailoverInstance(instance_name=instance_name,
819
                                  ignore_consistency=opts.ignore_consistency,
820
                                  shutdown_timeout=opts.shutdown_timeout)
821
  SubmitOrSend(op, opts, cl=cl)
822
  return 0
823

    
824

    
825
def MigrateInstance(opts, args):
826
  """Migrate an instance.
827

    
828
  The migrate is done without shutdown.
829

    
830
  @param opts: the command line options selected by the user
831
  @type args: list
832
  @param args: should contain only one element, the instance name
833
  @rtype: int
834
  @return: the desired exit code
835

    
836
  """
837
  cl = GetClient()
838
  instance_name = args[0]
839
  force = opts.force
840

    
841
  if not force:
842
    _EnsureInstancesExist(cl, [instance_name])
843

    
844
    if opts.cleanup:
845
      usertext = ("Instance %s will be recovered from a failed migration."
846
                  " Note that the migration procedure (including cleanup)" %
847
                  (instance_name,))
848
    else:
849
      usertext = ("Instance %s will be migrated. Note that migration" %
850
                  (instance_name,))
851
    usertext += (" is **experimental** in this version."
852
                " This might impact the instance if anything goes wrong."
853
                " Continue?")
854
    if not AskUser(usertext):
855
      return 1
856

    
857
  op = opcodes.OpMigrateInstance(instance_name=instance_name, live=opts.live,
858
                                 cleanup=opts.cleanup)
859
  SubmitOpCode(op, cl=cl)
860
  return 0
861

    
862

    
863
def MoveInstance(opts, args):
864
  """Move an instance.
865

    
866
  @param opts: the command line options selected by the user
867
  @type args: list
868
  @param args: should contain only one element, the instance name
869
  @rtype: int
870
  @return: the desired exit code
871

    
872
  """
873
  cl = GetClient()
874
  instance_name = args[0]
875
  force = opts.force
876

    
877
  if not force:
878
    usertext = ("Instance %s will be moved."
879
                " This requires a shutdown of the instance. Continue?" %
880
                (instance_name,))
881
    if not AskUser(usertext):
882
      return 1
883

    
884
  op = opcodes.OpMoveInstance(instance_name=instance_name,
885
                              target_node=opts.node,
886
                              shutdown_timeout=opts.shutdown_timeout)
887
  SubmitOrSend(op, opts, cl=cl)
888
  return 0
889

    
890

    
891
def ConnectToInstanceConsole(opts, args):
892
  """Connect to the console of an instance.
893

    
894
  @param opts: the command line options selected by the user
895
  @type args: list
896
  @param args: should contain only one element, the instance name
897
  @rtype: int
898
  @return: the desired exit code
899

    
900
  """
901
  instance_name = args[0]
902

    
903
  op = opcodes.OpConnectConsole(instance_name=instance_name)
904
  cmd = SubmitOpCode(op)
905

    
906
  if opts.show_command:
907
    ToStdout("%s", utils.ShellQuoteArgs(cmd))
908
  else:
909
    try:
910
      os.execvp(cmd[0], cmd)
911
    finally:
912
      ToStderr("Can't run console command %s with arguments:\n'%s'",
913
               cmd[0], " ".join(cmd))
914
      os._exit(1)
915

    
916

    
917
def _FormatLogicalID(dev_type, logical_id):
918
  """Formats the logical_id of a disk.
919

    
920
  """
921
  if dev_type == constants.LD_DRBD8:
922
    node_a, node_b, port, minor_a, minor_b, key = logical_id
923
    data = [
924
      ("nodeA", "%s, minor=%s" % (node_a, minor_a)),
925
      ("nodeB", "%s, minor=%s" % (node_b, minor_b)),
926
      ("port", port),
927
      ("auth key", key),
928
      ]
929
  elif dev_type == constants.LD_LV:
930
    vg_name, lv_name = logical_id
931
    data = ["%s/%s" % (vg_name, lv_name)]
932
  else:
933
    data = [str(logical_id)]
934

    
935
  return data
936

    
937

    
938
def _FormatBlockDevInfo(idx, top_level, dev, static):
939
  """Show block device information.
940

    
941
  This is only used by L{ShowInstanceConfig}, but it's too big to be
942
  left for an inline definition.
943

    
944
  @type idx: int
945
  @param idx: the index of the current disk
946
  @type top_level: boolean
947
  @param top_level: if this a top-level disk?
948
  @type dev: dict
949
  @param dev: dictionary with disk information
950
  @type static: boolean
951
  @param static: wheter the device information doesn't contain
952
      runtime information but only static data
953
  @return: a list of either strings, tuples or lists
954
      (which should be formatted at a higher indent level)
955

    
956
  """
957
  def helper(dtype, status):
958
    """Format one line for physical device status.
959

    
960
    @type dtype: str
961
    @param dtype: a constant from the L{constants.LDS_BLOCK} set
962
    @type status: tuple
963
    @param status: a tuple as returned from L{backend.FindBlockDevice}
964
    @return: the string representing the status
965

    
966
    """
967
    if not status:
968
      return "not active"
969
    txt = ""
970
    (path, major, minor, syncp, estt, degr, ldisk_status) = status
971
    if major is None:
972
      major_string = "N/A"
973
    else:
974
      major_string = str(major)
975

    
976
    if minor is None:
977
      minor_string = "N/A"
978
    else:
979
      minor_string = str(minor)
980

    
981
    txt += ("%s (%s:%s)" % (path, major_string, minor_string))
982
    if dtype in (constants.LD_DRBD8, ):
983
      if syncp is not None:
984
        sync_text = "*RECOVERING* %5.2f%%," % syncp
985
        if estt:
986
          sync_text += " ETA %ds" % estt
987
        else:
988
          sync_text += " ETA unknown"
989
      else:
990
        sync_text = "in sync"
991
      if degr:
992
        degr_text = "*DEGRADED*"
993
      else:
994
        degr_text = "ok"
995
      if ldisk_status == constants.LDS_FAULTY:
996
        ldisk_text = " *MISSING DISK*"
997
      elif ldisk_status == constants.LDS_UNKNOWN:
998
        ldisk_text = " *UNCERTAIN STATE*"
999
      else:
1000
        ldisk_text = ""
1001
      txt += (" %s, status %s%s" % (sync_text, degr_text, ldisk_text))
1002
    elif dtype == constants.LD_LV:
1003
      if ldisk_status == constants.LDS_FAULTY:
1004
        ldisk_text = " *FAILED* (failed drive?)"
1005
      else:
1006
        ldisk_text = ""
1007
      txt += ldisk_text
1008
    return txt
1009

    
1010
  # the header
1011
  if top_level:
1012
    if dev["iv_name"] is not None:
1013
      txt = dev["iv_name"]
1014
    else:
1015
      txt = "disk %d" % idx
1016
  else:
1017
    txt = "child %d" % idx
1018
  if isinstance(dev["size"], int):
1019
    nice_size = utils.FormatUnit(dev["size"], "h")
1020
  else:
1021
    nice_size = dev["size"]
1022
  d1 = ["- %s: %s, size %s" % (txt, dev["dev_type"], nice_size)]
1023
  data = []
1024
  if top_level:
1025
    data.append(("access mode", dev["mode"]))
1026
  if dev["logical_id"] is not None:
1027
    try:
1028
      l_id = _FormatLogicalID(dev["dev_type"], dev["logical_id"])
1029
    except ValueError:
1030
      l_id = [str(dev["logical_id"])]
1031
    if len(l_id) == 1:
1032
      data.append(("logical_id", l_id[0]))
1033
    else:
1034
      data.extend(l_id)
1035
  elif dev["physical_id"] is not None:
1036
    data.append("physical_id:")
1037
    data.append([dev["physical_id"]])
1038
  if not static:
1039
    data.append(("on primary", helper(dev["dev_type"], dev["pstatus"])))
1040
  if dev["sstatus"] and not static:
1041
    data.append(("on secondary", helper(dev["dev_type"], dev["sstatus"])))
1042

    
1043
  if dev["children"]:
1044
    data.append("child devices:")
1045
    for c_idx, child in enumerate(dev["children"]):
1046
      data.append(_FormatBlockDevInfo(c_idx, False, child, static))
1047
  d1.append(data)
1048
  return d1
1049

    
1050

    
1051
def _FormatList(buf, data, indent_level):
1052
  """Formats a list of data at a given indent level.
1053

    
1054
  If the element of the list is:
1055
    - a string, it is simply formatted as is
1056
    - a tuple, it will be split into key, value and the all the
1057
      values in a list will be aligned all at the same start column
1058
    - a list, will be recursively formatted
1059

    
1060
  @type buf: StringIO
1061
  @param buf: the buffer into which we write the output
1062
  @param data: the list to format
1063
  @type indent_level: int
1064
  @param indent_level: the indent level to format at
1065

    
1066
  """
1067
  max_tlen = max([len(elem[0]) for elem in data
1068
                 if isinstance(elem, tuple)] or [0])
1069
  for elem in data:
1070
    if isinstance(elem, basestring):
1071
      buf.write("%*s%s\n" % (2*indent_level, "", elem))
1072
    elif isinstance(elem, tuple):
1073
      key, value = elem
1074
      spacer = "%*s" % (max_tlen - len(key), "")
1075
      buf.write("%*s%s:%s %s\n" % (2*indent_level, "", key, spacer, value))
1076
    elif isinstance(elem, list):
1077
      _FormatList(buf, elem, indent_level+1)
1078

    
1079

    
1080
def ShowInstanceConfig(opts, args):
1081
  """Compute instance run-time status.
1082

    
1083
  @param opts: the command line options selected by the user
1084
  @type args: list
1085
  @param args: either an empty list, and then we query all
1086
      instances, or should contain a list of instance names
1087
  @rtype: int
1088
  @return: the desired exit code
1089

    
1090
  """
1091
  if not args and not opts.show_all:
1092
    ToStderr("No instance selected."
1093
             " Please pass in --all if you want to query all instances.\n"
1094
             "Note that this can take a long time on a big cluster.")
1095
    return 1
1096
  elif args and opts.show_all:
1097
    ToStderr("Cannot use --all if you specify instance names.")
1098
    return 1
1099

    
1100
  retcode = 0
1101
  op = opcodes.OpQueryInstanceData(instances=args, static=opts.static)
1102
  result = SubmitOpCode(op)
1103
  if not result:
1104
    ToStdout("No instances.")
1105
    return 1
1106

    
1107
  buf = StringIO()
1108
  retcode = 0
1109
  for instance_name in result:
1110
    instance = result[instance_name]
1111
    buf.write("Instance name: %s\n" % instance["name"])
1112
    buf.write("UUID: %s\n" % instance["uuid"])
1113
    buf.write("Serial number: %s\n" % instance["serial_no"])
1114
    buf.write("Creation time: %s\n" % utils.FormatTime(instance["ctime"]))
1115
    buf.write("Modification time: %s\n" % utils.FormatTime(instance["mtime"]))
1116
    buf.write("State: configured to be %s" % instance["config_state"])
1117
    if not opts.static:
1118
      buf.write(", actual state is %s" % instance["run_state"])
1119
    buf.write("\n")
1120
    ##buf.write("Considered for memory checks in cluster verify: %s\n" %
1121
    ##          instance["auto_balance"])
1122
    buf.write("  Nodes:\n")
1123
    buf.write("    - primary: %s\n" % instance["pnode"])
1124
    buf.write("    - secondaries: %s\n" % utils.CommaJoin(instance["snodes"]))
1125
    buf.write("  Operating system: %s\n" % instance["os"])
1126
    if instance.has_key("network_port"):
1127
      buf.write("  Allocated network port: %s\n" % instance["network_port"])
1128
    buf.write("  Hypervisor: %s\n" % instance["hypervisor"])
1129

    
1130
    # custom VNC console information
1131
    vnc_bind_address = instance["hv_actual"].get(constants.HV_VNC_BIND_ADDRESS,
1132
                                                 None)
1133
    if vnc_bind_address:
1134
      port = instance["network_port"]
1135
      display = int(port) - constants.VNC_BASE_PORT
1136
      if display > 0 and vnc_bind_address == constants.BIND_ADDRESS_GLOBAL:
1137
        vnc_console_port = "%s:%s (display %s)" % (instance["pnode"],
1138
                                                   port,
1139
                                                   display)
1140
      elif display > 0 and utils.IsValidIP(vnc_bind_address):
1141
        vnc_console_port = ("%s:%s (node %s) (display %s)" %
1142
                             (vnc_bind_address, port,
1143
                              instance["pnode"], display))
1144
      else:
1145
        # vnc bind address is a file
1146
        vnc_console_port = "%s:%s" % (instance["pnode"],
1147
                                      vnc_bind_address)
1148
      buf.write("    - console connection: vnc to %s\n" % vnc_console_port)
1149

    
1150
    for key in instance["hv_actual"]:
1151
      if key in instance["hv_instance"]:
1152
        val = instance["hv_instance"][key]
1153
      else:
1154
        val = "default (%s)" % instance["hv_actual"][key]
1155
      buf.write("    - %s: %s\n" % (key, val))
1156
    buf.write("  Hardware:\n")
1157
    buf.write("    - VCPUs: %d\n" %
1158
              instance["be_actual"][constants.BE_VCPUS])
1159
    buf.write("    - memory: %dMiB\n" %
1160
              instance["be_actual"][constants.BE_MEMORY])
1161
    buf.write("    - NICs:\n")
1162
    for idx, (ip, mac, mode, link) in enumerate(instance["nics"]):
1163
      buf.write("      - nic/%d: MAC: %s, IP: %s, mode: %s, link: %s\n" %
1164
                (idx, mac, ip, mode, link))
1165
    buf.write("  Disks:\n")
1166

    
1167
    for idx, device in enumerate(instance["disks"]):
1168
      _FormatList(buf, _FormatBlockDevInfo(idx, True, device, opts.static), 2)
1169

    
1170
  ToStdout(buf.getvalue().rstrip('\n'))
1171
  return retcode
1172

    
1173

    
1174
def SetInstanceParams(opts, args):
1175
  """Modifies an instance.
1176

    
1177
  All parameters take effect only at the next restart of the instance.
1178

    
1179
  @param opts: the command line options selected by the user
1180
  @type args: list
1181
  @param args: should contain only one element, the instance name
1182
  @rtype: int
1183
  @return: the desired exit code
1184

    
1185
  """
1186
  if not (opts.nics or opts.disks or
1187
          opts.hvparams or opts.beparams):
1188
    ToStderr("Please give at least one of the parameters.")
1189
    return 1
1190

    
1191
  for param in opts.beparams:
1192
    if isinstance(opts.beparams[param], basestring):
1193
      if opts.beparams[param].lower() == "default":
1194
        opts.beparams[param] = constants.VALUE_DEFAULT
1195

    
1196
  utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_TYPES,
1197
                      allowed_values=[constants.VALUE_DEFAULT])
1198

    
1199
  for param in opts.hvparams:
1200
    if isinstance(opts.hvparams[param], basestring):
1201
      if opts.hvparams[param].lower() == "default":
1202
        opts.hvparams[param] = constants.VALUE_DEFAULT
1203

    
1204
  utils.ForceDictType(opts.hvparams, constants.HVS_PARAMETER_TYPES,
1205
                      allowed_values=[constants.VALUE_DEFAULT])
1206

    
1207
  for idx, (nic_op, nic_dict) in enumerate(opts.nics):
1208
    try:
1209
      nic_op = int(nic_op)
1210
      opts.nics[idx] = (nic_op, nic_dict)
1211
    except ValueError:
1212
      pass
1213

    
1214
  for idx, (disk_op, disk_dict) in enumerate(opts.disks):
1215
    try:
1216
      disk_op = int(disk_op)
1217
      opts.disks[idx] = (disk_op, disk_dict)
1218
    except ValueError:
1219
      pass
1220
    if disk_op == constants.DDM_ADD:
1221
      if 'size' not in disk_dict:
1222
        raise errors.OpPrereqError("Missing required parameter 'size'",
1223
                                   errors.ECODE_INVAL)
1224
      disk_dict['size'] = utils.ParseUnit(disk_dict['size'])
1225

    
1226
  op = opcodes.OpSetInstanceParams(instance_name=args[0],
1227
                                   nics=opts.nics,
1228
                                   disks=opts.disks,
1229
                                   hvparams=opts.hvparams,
1230
                                   beparams=opts.beparams,
1231
                                   force=opts.force)
1232

    
1233
  # even if here we process the result, we allow submit only
1234
  result = SubmitOrSend(op, opts)
1235

    
1236
  if result:
1237
    ToStdout("Modified instance %s", args[0])
1238
    for param, data in result:
1239
      ToStdout(" - %-5s -> %s", param, data)
1240
    ToStdout("Please don't forget that these parameters take effect"
1241
             " only at the next start of the instance.")
1242
  return 0
1243

    
1244

    
1245
# multi-instance selection options
1246
m_force_multi = cli_option("--force-multiple", dest="force_multi",
1247
                           help="Do not ask for confirmation when more than"
1248
                           " one instance is affected",
1249
                           action="store_true", default=False)
1250

    
1251
m_pri_node_opt = cli_option("--primary", dest="multi_mode",
1252
                            help="Filter by nodes (primary only)",
1253
                            const=_SHUTDOWN_NODES_PRI, action="store_const")
1254

    
1255
m_sec_node_opt = cli_option("--secondary", dest="multi_mode",
1256
                            help="Filter by nodes (secondary only)",
1257
                            const=_SHUTDOWN_NODES_SEC, action="store_const")
1258

    
1259
m_node_opt = cli_option("--node", dest="multi_mode",
1260
                        help="Filter by nodes (primary and secondary)",
1261
                        const=_SHUTDOWN_NODES_BOTH, action="store_const")
1262

    
1263
m_clust_opt = cli_option("--all", dest="multi_mode",
1264
                         help="Select all instances in the cluster",
1265
                         const=_SHUTDOWN_CLUSTER, action="store_const")
1266

    
1267
m_inst_opt = cli_option("--instance", dest="multi_mode",
1268
                        help="Filter by instance name [default]",
1269
                        const=_SHUTDOWN_INSTANCES, action="store_const")
1270

    
1271

    
1272
# this is defined separately due to readability only
1273
add_opts = [
1274
  BACKEND_OPT,
1275
  DISK_OPT,
1276
  DISK_TEMPLATE_OPT,
1277
  FILESTORE_DIR_OPT,
1278
  FILESTORE_DRIVER_OPT,
1279
  HYPERVISOR_OPT,
1280
  IALLOCATOR_OPT,
1281
  NET_OPT,
1282
  NODE_PLACEMENT_OPT,
1283
  NOIPCHECK_OPT,
1284
  NONAMECHECK_OPT,
1285
  NONICS_OPT,
1286
  NOSTART_OPT,
1287
  NWSYNC_OPT,
1288
  OS_OPT,
1289
  FORCE_VARIANT_OPT,
1290
  OS_SIZE_OPT,
1291
  SUBMIT_OPT,
1292
  ]
1293

    
1294
commands = {
1295
  'add': (
1296
    AddInstance, [ArgHost(min=1, max=1)], add_opts,
1297
    "[...] -t disk-type -n node[:secondary-node] -o os-type <name>",
1298
    "Creates and adds a new instance to the cluster"),
1299
  'batch-create': (
1300
    BatchCreate, [ArgFile(min=1, max=1)], [],
1301
    "<instances.json>",
1302
    "Create a bunch of instances based on specs in the file."),
1303
  'console': (
1304
    ConnectToInstanceConsole, ARGS_ONE_INSTANCE,
1305
    [SHOWCMD_OPT],
1306
    "[--show-cmd] <instance>", "Opens a console on the specified instance"),
1307
  'failover': (
1308
    FailoverInstance, ARGS_ONE_INSTANCE,
1309
    [FORCE_OPT, IGNORE_CONSIST_OPT, SUBMIT_OPT, SHUTDOWN_TIMEOUT_OPT],
1310
    "[-f] <instance>", "Stops the instance and starts it on the backup node,"
1311
    " using the remote mirror (only for instances of type drbd)"),
1312
  'migrate': (
1313
    MigrateInstance, ARGS_ONE_INSTANCE,
1314
    [FORCE_OPT, NONLIVE_OPT, CLEANUP_OPT],
1315
    "[-f] <instance>", "Migrate instance to its secondary node"
1316
    " (only for instances of type drbd)"),
1317
  'move': (
1318
    MoveInstance, ARGS_ONE_INSTANCE,
1319
    [FORCE_OPT, SUBMIT_OPT, SINGLE_NODE_OPT, SHUTDOWN_TIMEOUT_OPT],
1320
    "[-f] <instance>", "Move instance to an arbitrary node"
1321
    " (only for instances of type file and lv)"),
1322
  'info': (
1323
    ShowInstanceConfig, ARGS_MANY_INSTANCES,
1324
    [STATIC_OPT, ALL_OPT],
1325
    "[-s] {--all | <instance>...}",
1326
    "Show information on the specified instance(s)"),
1327
  'list': (
1328
    ListInstances, ARGS_MANY_INSTANCES,
1329
    [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, SYNC_OPT],
1330
    "[<instance>...]",
1331
    "Lists the instances and their status. The available fields are"
1332
    " (see the man page for details): status, oper_state, oper_ram,"
1333
    " name, os, pnode, snodes, admin_state, admin_ram, disk_template,"
1334
    " ip, mac, nic_mode, nic_link, sda_size, sdb_size, vcpus, serial_no,"
1335
    " nic.count, nic.mac/N, nic.ip/N, nic.mode/N, nic.link/N,"
1336
    " nic.macs, nic.ips, nic.modes, nic.links,"
1337
    " disk.count, disk.size/N, disk.sizes,"
1338
    " hv/NAME, be/memory, be/vcpus, be/auto_balance,"
1339
    " hypervisor."
1340
    " The default field"
1341
    " list is (in order): %s." % utils.CommaJoin(_LIST_DEF_FIELDS),
1342
    ),
1343
  'reinstall': (
1344
    ReinstallInstance, [ArgInstance()],
1345
    [FORCE_OPT, OS_OPT, FORCE_VARIANT_OPT, m_force_multi, m_node_opt,
1346
     m_pri_node_opt, m_sec_node_opt, m_clust_opt, m_inst_opt, SELECT_OS_OPT,
1347
     SUBMIT_OPT],
1348
    "[-f] <instance>", "Reinstall a stopped instance"),
1349
  'remove': (
1350
    RemoveInstance, ARGS_ONE_INSTANCE,
1351
    [FORCE_OPT, SHUTDOWN_TIMEOUT_OPT, IGNORE_FAILURES_OPT, SUBMIT_OPT],
1352
    "[-f] <instance>", "Shuts down the instance and removes it"),
1353
  'rename': (
1354
    RenameInstance,
1355
    [ArgInstance(min=1, max=1), ArgHost(min=1, max=1)],
1356
    [NOIPCHECK_OPT, SUBMIT_OPT],
1357
    "<instance> <new_name>", "Rename the instance"),
1358
  'replace-disks': (
1359
    ReplaceDisks, ARGS_ONE_INSTANCE,
1360
    [AUTO_REPLACE_OPT, DISKIDX_OPT, IALLOCATOR_OPT,
1361
     NEW_SECONDARY_OPT, ON_PRIMARY_OPT, ON_SECONDARY_OPT, SUBMIT_OPT],
1362
    "[-s|-p|-n NODE|-I NAME] <instance>",
1363
    "Replaces all disks for the instance"),
1364
  'modify': (
1365
    SetInstanceParams, ARGS_ONE_INSTANCE,
1366
    [BACKEND_OPT, DISK_OPT, FORCE_OPT, HVOPTS_OPT, NET_OPT, SUBMIT_OPT],
1367
    "<instance>", "Alters the parameters of an instance"),
1368
  'shutdown': (
1369
    GenericManyOps("shutdown", _ShutdownInstance), [ArgInstance()],
1370
    [m_node_opt, m_pri_node_opt, m_sec_node_opt, m_clust_opt,
1371
     m_inst_opt, m_force_multi, TIMEOUT_OPT, SUBMIT_OPT],
1372
    "<instance>", "Stops an instance"),
1373
  'startup': (
1374
    GenericManyOps("startup", _StartupInstance), [ArgInstance()],
1375
    [FORCE_OPT, m_force_multi, m_node_opt, m_pri_node_opt,
1376
     m_sec_node_opt, m_clust_opt, m_inst_opt, SUBMIT_OPT, HVOPTS_OPT,
1377
     BACKEND_OPT],
1378
    "<instance>", "Starts an instance"),
1379
  'reboot': (
1380
    GenericManyOps("reboot", _RebootInstance), [ArgInstance()],
1381
    [m_force_multi, REBOOT_TYPE_OPT, IGNORE_SECONDARIES_OPT, m_node_opt,
1382
     m_pri_node_opt, m_sec_node_opt, m_clust_opt, m_inst_opt, SUBMIT_OPT,
1383
     SHUTDOWN_TIMEOUT_OPT],
1384
    "<instance>", "Reboots an instance"),
1385
  'activate-disks': (
1386
    ActivateDisks, ARGS_ONE_INSTANCE, [SUBMIT_OPT, IGNORE_SIZE_OPT],
1387
    "<instance>", "Activate an instance's disks"),
1388
  'deactivate-disks': (
1389
    DeactivateDisks, ARGS_ONE_INSTANCE, [SUBMIT_OPT],
1390
    "<instance>", "Deactivate an instance's disks"),
1391
  'recreate-disks': (
1392
    RecreateDisks, ARGS_ONE_INSTANCE, [SUBMIT_OPT, DISKIDX_OPT],
1393
    "<instance>", "Recreate an instance's disks"),
1394
  'grow-disk': (
1395
    GrowDisk,
1396
    [ArgInstance(min=1, max=1), ArgUnknown(min=1, max=1),
1397
     ArgUnknown(min=1, max=1)],
1398
    [SUBMIT_OPT, NWSYNC_OPT],
1399
    "<instance> <disk> <size>", "Grow an instance's disk"),
1400
  'list-tags': (
1401
    ListTags, ARGS_ONE_INSTANCE, [],
1402
    "<instance_name>", "List the tags of the given instance"),
1403
  'add-tags': (
1404
    AddTags, [ArgInstance(min=1, max=1), ArgUnknown()],
1405
    [TAG_SRC_OPT],
1406
    "<instance_name> tag...", "Add tags to the given instance"),
1407
  'remove-tags': (
1408
    RemoveTags, [ArgInstance(min=1, max=1), ArgUnknown()],
1409
    [TAG_SRC_OPT],
1410
    "<instance_name> tag...", "Remove tags from given instance"),
1411
  }
1412

    
1413
#: dictionary with aliases for commands
1414
aliases = {
1415
  'activate_block_devs': 'activate-disks',
1416
  'replace_disks': 'replace-disks',
1417
  'start': 'startup',
1418
  'stop': 'shutdown',
1419
  }
1420

    
1421

    
1422
if __name__ == '__main__':
1423
  sys.exit(GenericMain(commands, aliases=aliases,
1424
                       override={"tag_type": constants.TAG_INSTANCE}))