Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-instance @ e3ac208c

History | View | Annotate | Download (45.2 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
    idata = client.QueryInstances([], ["name"], False)
84
    inames = [row[0] for row in idata]
85

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

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

    
112
  else:
113
    raise errors.OpPrereqError("Unknown mode '%s'" % mode)
114

    
115
  return inames
116

    
117

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

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

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

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

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

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

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

    
155

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

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

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

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

    
176

    
177
def GenericManyOps(operation, fn):
178
  """Generic multi-instance operations.
179

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

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

    
206

    
207
def ListInstances(opts, args):
208
  """List instances and their properties.
209

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

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

    
224
  output = GetClient().QueryInstances(args, selected_fields, opts.do_locking)
225

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

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

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

    
301
  data = GenerateTable(separator=opts.separator, headers=headers,
302
                       fields=selected_fields, unitfields=unitfields,
303
                       numfields=numfields, data=output, units=opts.units)
304

    
305
  for line in data:
306
    ToStdout(line)
307

    
308
  return 0
309

    
310

    
311
def AddInstance(opts, args):
312
  """Add an instance to the cluster.
313

    
314
  This is just a wrapper over GenericInstanceCreate.
315

    
316
  """
317
  return GenericInstanceCreate(constants.INSTANCE_CREATE, opts, args)
318
  return 0
319

    
320

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

    
324
  This function reads a json file with instances defined
325
  in the form::
326

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

    
339
  Note that I{primary_node} and I{secondary_node} have precedence over
340
  I{iallocator}.
341

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

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

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

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

    
385
    if (spec['hvparams'] and
386
        not isinstance(spec['hvparams'], dict)):
387
      raise errors.OpPrereqError('Hypervisor parameters must be a dict.')
388

    
389
  json_filename = args[0]
390
  try:
391
    instance_data = simplejson.loads(utils.ReadFile(json_filename))
392
  except Exception, err:
393
    ToStderr("Can't parse the instance definition file: %s" % str(err))
394
    return 1
395

    
396
  jex = JobExecutor()
397

    
398
  # Iterate over the instances and do:
399
  #  * Populate the specs with default value
400
  #  * Validate the instance specs
401
  i_names = utils.NiceSort(instance_data.keys())
402
  for name in i_names:
403
    specs = instance_data[name]
404
    specs = _PopulateWithDefaults(specs)
405
    _Validate(specs)
406

    
407
    hypervisor = specs['hypervisor']
408
    hvparams = specs['hvparams']
409

    
410
    disks = []
411
    for elem in specs['disk_size']:
412
      try:
413
        size = utils.ParseUnit(elem)
414
      except ValueError, err:
415
        raise errors.OpPrereqError("Invalid disk size '%s' for"
416
                                   " instance %s: %s" %
417
                                   (elem, name, err))
418
      disks.append({"size": size})
419

    
420
    utils.ForceDictType(specs['backend'], constants.BES_PARAMETER_TYPES)
421
    utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
422

    
423
    tmp_nics = []
424
    for field in ('ip', 'mac', 'mode', 'link', 'bridge'):
425
      if field in specs:
426
        if not tmp_nics:
427
          tmp_nics.append({})
428
        tmp_nics[0][field] = specs[field]
429

    
430
    if specs['nics'] is not None and tmp_nics:
431
      raise errors.OpPrereqError("'nics' list incompatible with using"
432
                                 " individual nic fields as well")
433
    elif specs['nics'] is not None:
434
      tmp_nics = specs['nics']
435
    elif not tmp_nics:
436
      tmp_nics = [{}]
437

    
438
    op = opcodes.OpCreateInstance(instance_name=name,
439
                                  disks=disks,
440
                                  disk_template=specs['template'],
441
                                  mode=constants.INSTANCE_CREATE,
442
                                  os_type=specs['os'],
443
                                  pnode=specs['primary_node'],
444
                                  snode=specs['secondary_node'],
445
                                  nics=tmp_nics,
446
                                  start=specs['start'],
447
                                  ip_check=specs['ip_check'],
448
                                  wait_for_sync=True,
449
                                  iallocator=specs['iallocator'],
450
                                  hypervisor=hypervisor,
451
                                  hvparams=hvparams,
452
                                  beparams=specs['backend'],
453
                                  file_storage_dir=specs['file_storage_dir'],
454
                                  file_driver=specs['file_driver'])
455

    
456
    jex.QueueJob(name, op)
457
  # we never want to wait, just show the submitted job IDs
458
  jex.WaitOrShow(False)
459

    
460
  return 0
461

    
462

    
463
def ReinstallInstance(opts, args):
464
  """Reinstall an instance.
465

    
466
  @param opts: the command line options selected by the user
467
  @type args: list
468
  @param args: should contain only one element, the name of the
469
      instance to be reinstalled
470
  @rtype: int
471
  @return: the desired exit code
472

    
473
  """
474
  # first, compute the desired name list
475
  if opts.multi_mode is None:
476
    opts.multi_mode = _SHUTDOWN_INSTANCES
477

    
478
  inames = _ExpandMultiNames(opts.multi_mode, args)
479
  if not inames:
480
    raise errors.OpPrereqError("Selection filter does not match any instances")
481

    
482
  # second, if requested, ask for an OS
483
  if opts.select_os is True:
484
    op = opcodes.OpDiagnoseOS(output_fields=["name", "valid", "variants"],
485
                              names=[])
486
    result = SubmitOpCode(op)
487

    
488
    if not result:
489
      ToStdout("Can't get the OS list")
490
      return 1
491

    
492
    ToStdout("Available OS templates:")
493
    number = 0
494
    choices = []
495
    for (name, valid, variants) in result:
496
      if valid:
497
        for entry in CalculateOSNames(name, variants):
498
          ToStdout("%3s: %s", number, entry)
499
          choices.append(("%s" % number, entry, entry))
500
          number += 1
501

    
502
    choices.append(('x', 'exit', 'Exit gnt-instance reinstall'))
503
    selected = AskUser("Enter OS template number (or x to abort):",
504
                       choices)
505

    
506
    if selected == 'exit':
507
      ToStderr("User aborted reinstall, exiting")
508
      return 1
509

    
510
    os_name = selected
511
  else:
512
    os_name = opts.os
513

    
514
  # third, get confirmation: multi-reinstall requires --force-multi
515
  # *and* --force, single-reinstall just --force
516
  multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
517
  if multi_on:
518
    warn_msg = "Note: this will remove *all* data for the below instances!\n"
519
    if not ((opts.force_multi and opts.force) or
520
            _ConfirmOperation(inames, "reinstall", extra=warn_msg)):
521
      return 1
522
  else:
523
    if not opts.force:
524
      usertext = ("This will reinstall the instance %s and remove"
525
                  " all data. Continue?") % inames[0]
526
      if not AskUser(usertext):
527
        return 1
528

    
529
  jex = JobExecutor(verbose=multi_on)
530
  for instance_name in inames:
531
    op = opcodes.OpReinstallInstance(instance_name=instance_name,
532
                                     os_type=os_name)
533
    jex.QueueJob(instance_name, op)
534

    
535
  jex.WaitOrShow(not opts.submit_only)
536
  return 0
537

    
538

    
539
def RemoveInstance(opts, args):
540
  """Remove an instance.
541

    
542
  @param opts: the command line options selected by the user
543
  @type args: list
544
  @param args: should contain only one element, the name of
545
      the instance to be removed
546
  @rtype: int
547
  @return: the desired exit code
548

    
549
  """
550
  instance_name = args[0]
551
  force = opts.force
552
  cl = GetClient()
553

    
554
  if not force:
555
    _EnsureInstancesExist(cl, [instance_name])
556

    
557
    usertext = ("This will remove the volumes of the instance %s"
558
                " (including mirrors), thus removing all the data"
559
                " of the instance. Continue?") % instance_name
560
    if not AskUser(usertext):
561
      return 1
562

    
563
  op = opcodes.OpRemoveInstance(instance_name=instance_name,
564
                                ignore_failures=opts.ignore_failures)
565
  SubmitOrSend(op, opts, cl=cl)
566
  return 0
567

    
568

    
569
def RenameInstance(opts, args):
570
  """Rename an instance.
571

    
572
  @param opts: the command line options selected by the user
573
  @type args: list
574
  @param args: should contain two elements, the old and the
575
      new instance names
576
  @rtype: int
577
  @return: the desired exit code
578

    
579
  """
580
  op = opcodes.OpRenameInstance(instance_name=args[0],
581
                                new_name=args[1],
582
                                ignore_ip=opts.ignore_ip)
583
  SubmitOrSend(op, opts)
584
  return 0
585

    
586

    
587
def ActivateDisks(opts, args):
588
  """Activate an instance's disks.
589

    
590
  This serves two purposes:
591
    - it allows (as long as the instance is not running)
592
      mounting the disks and modifying them from the node
593
    - it repairs inactive secondary drbds
594

    
595
  @param opts: the command line options selected by the user
596
  @type args: list
597
  @param args: should contain only one element, the instance name
598
  @rtype: int
599
  @return: the desired exit code
600

    
601
  """
602
  instance_name = args[0]
603
  op = opcodes.OpActivateInstanceDisks(instance_name=instance_name,
604
                                       ignore_size=opts.ignore_size)
605
  disks_info = SubmitOrSend(op, opts)
606
  for host, iname, nname in disks_info:
607
    ToStdout("%s:%s:%s", host, iname, nname)
608
  return 0
609

    
610

    
611
def DeactivateDisks(opts, args):
612
  """Deactivate an instance's disks.
613

    
614
  This function takes the instance name, looks for its primary node
615
  and the tries to shutdown its block devices on that node.
616

    
617
  @param opts: the command line options selected by the user
618
  @type args: list
619
  @param args: should contain only one element, the instance name
620
  @rtype: int
621
  @return: the desired exit code
622

    
623
  """
624
  instance_name = args[0]
625
  op = opcodes.OpDeactivateInstanceDisks(instance_name=instance_name)
626
  SubmitOrSend(op, opts)
627
  return 0
628

    
629

    
630
def RecreateDisks(opts, args):
631
  """Recreate an instance's disks.
632

    
633
  @param opts: the command line options selected by the user
634
  @type args: list
635
  @param args: should contain only one element, the instance name
636
  @rtype: int
637
  @return: the desired exit code
638

    
639
  """
640
  instance_name = args[0]
641
  if opts.disks:
642
    try:
643
      opts.disks = [int(v) for v in opts.disks.split(",")]
644
    except (ValueError, TypeError), err:
645
      ToStderr("Invalid disks value: %s" % str(err))
646
      return 1
647
  else:
648
    opts.disks = []
649

    
650
  op = opcodes.OpRecreateInstanceDisks(instance_name=instance_name,
651
                                       disks=opts.disks)
652
  SubmitOrSend(op, opts)
653
  return 0
654

    
655

    
656
def GrowDisk(opts, args):
657
  """Grow an instance's disks.
658

    
659
  @param opts: the command line options selected by the user
660
  @type args: list
661
  @param args: should contain two elements, the instance name
662
      whose disks we grow and the disk name, e.g. I{sda}
663
  @rtype: int
664
  @return: the desired exit code
665

    
666
  """
667
  instance = args[0]
668
  disk = args[1]
669
  try:
670
    disk = int(disk)
671
  except ValueError, err:
672
    raise errors.OpPrereqError("Invalid disk index: %s" % str(err))
673
  amount = utils.ParseUnit(args[2])
674
  op = opcodes.OpGrowDisk(instance_name=instance, disk=disk, amount=amount,
675
                          wait_for_sync=opts.wait_for_sync)
676
  SubmitOrSend(op, opts)
677
  return 0
678

    
679

    
680
def _StartupInstance(name, opts):
681
  """Startup instances.
682

    
683
  This returns the opcode to start an instance, and its decorator will
684
  wrap this into a loop starting all desired instances.
685

    
686
  @param name: the name of the instance to act on
687
  @param opts: the command line options selected by the user
688
  @return: the opcode needed for the operation
689

    
690
  """
691
  op = opcodes.OpStartupInstance(instance_name=name,
692
                                 force=opts.force)
693
  # do not add these parameters to the opcode unless they're defined
694
  if opts.hvparams:
695
    op.hvparams = opts.hvparams
696
  if opts.beparams:
697
    op.beparams = opts.beparams
698
  return op
699

    
700

    
701
def _RebootInstance(name, opts):
702
  """Reboot instance(s).
703

    
704
  This returns the opcode to reboot an instance, and its decorator
705
  will wrap this into a loop rebooting all desired instances.
706

    
707
  @param name: the name of the instance to act on
708
  @param opts: the command line options selected by the user
709
  @return: the opcode needed for the operation
710

    
711
  """
712
  return opcodes.OpRebootInstance(instance_name=name,
713
                                  reboot_type=opts.reboot_type,
714
                                  ignore_secondaries=opts.ignore_secondaries)
715

    
716

    
717
def _ShutdownInstance(name, opts):
718
  """Shutdown an instance.
719

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

    
723
  @param name: the name of the instance to act on
724
  @param opts: the command line options selected by the user
725
  @return: the opcode needed for the operation
726

    
727
  """
728
  return opcodes.OpShutdownInstance(instance_name=name)
729

    
730

    
731
def ReplaceDisks(opts, args):
732
  """Replace the disks of an instance
733

    
734
  @param opts: the command line options selected by the user
735
  @type args: list
736
  @param args: should contain only one element, the instance name
737
  @rtype: int
738
  @return: the desired exit code
739

    
740
  """
741
  instance_name = args[0]
742
  new_2ndary = opts.dst_node
743
  iallocator = opts.iallocator
744
  if opts.disks is None:
745
    disks = []
746
  else:
747
    try:
748
      disks = [int(i) for i in opts.disks.split(",")]
749
    except ValueError, err:
750
      raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err))
751
  cnt = [opts.on_primary, opts.on_secondary, opts.auto,
752
         new_2ndary is not None, iallocator is not None].count(True)
753
  if cnt != 1:
754
    raise errors.OpPrereqError("One and only one of the -p, -s, -a, -n and -i"
755
                               " options must be passed")
756
  elif opts.on_primary:
757
    mode = constants.REPLACE_DISK_PRI
758
  elif opts.on_secondary:
759
    mode = constants.REPLACE_DISK_SEC
760
  elif opts.auto:
761
    mode = constants.REPLACE_DISK_AUTO
762
    if disks:
763
      raise errors.OpPrereqError("Cannot specify disks when using automatic"
764
                                 " mode")
765
  elif new_2ndary is not None or iallocator is not None:
766
    # replace secondary
767
    mode = constants.REPLACE_DISK_CHG
768

    
769
  op = opcodes.OpReplaceDisks(instance_name=args[0], disks=disks,
770
                              remote_node=new_2ndary, mode=mode,
771
                              iallocator=iallocator)
772
  SubmitOrSend(op, opts)
773
  return 0
774

    
775

    
776
def FailoverInstance(opts, args):
777
  """Failover an instance.
778

    
779
  The failover is done by shutting it down on its present node and
780
  starting it on the secondary.
781

    
782
  @param opts: the command line options selected by the user
783
  @type args: list
784
  @param args: should contain only one element, the instance name
785
  @rtype: int
786
  @return: the desired exit code
787

    
788
  """
789
  cl = GetClient()
790
  instance_name = args[0]
791
  force = opts.force
792

    
793
  if not force:
794
    _EnsureInstancesExist(cl, [instance_name])
795

    
796
    usertext = ("Failover will happen to image %s."
797
                " This requires a shutdown of the instance. Continue?" %
798
                (instance_name,))
799
    if not AskUser(usertext):
800
      return 1
801

    
802
  op = opcodes.OpFailoverInstance(instance_name=instance_name,
803
                                  ignore_consistency=opts.ignore_consistency)
804
  SubmitOrSend(op, opts, cl=cl)
805
  return 0
806

    
807

    
808
def MigrateInstance(opts, args):
809
  """Migrate an instance.
810

    
811
  The migrate is done without shutdown.
812

    
813
  @param opts: the command line options selected by the user
814
  @type args: list
815
  @param args: should contain only one element, the instance name
816
  @rtype: int
817
  @return: the desired exit code
818

    
819
  """
820
  cl = GetClient()
821
  instance_name = args[0]
822
  force = opts.force
823

    
824
  if not force:
825
    _EnsureInstancesExist(cl, [instance_name])
826

    
827
    if opts.cleanup:
828
      usertext = ("Instance %s will be recovered from a failed migration."
829
                  " Note that the migration procedure (including cleanup)" %
830
                  (instance_name,))
831
    else:
832
      usertext = ("Instance %s will be migrated. Note that migration" %
833
                  (instance_name,))
834
    usertext += (" is **experimental** in this version."
835
                " This might impact the instance if anything goes wrong."
836
                " Continue?")
837
    if not AskUser(usertext):
838
      return 1
839

    
840
  op = opcodes.OpMigrateInstance(instance_name=instance_name, live=opts.live,
841
                                 cleanup=opts.cleanup)
842
  SubmitOpCode(op, cl=cl)
843
  return 0
844

    
845

    
846
def MoveInstance(opts, args):
847
  """Move an instance.
848

    
849
  @param opts: the command line options selected by the user
850
  @type args: list
851
  @param args: should contain only one element, the instance name
852
  @rtype: int
853
  @return: the desired exit code
854

    
855
  """
856
  cl = GetClient()
857
  instance_name = args[0]
858
  force = opts.force
859

    
860
  if not force:
861
    usertext = ("Instance %s will be moved."
862
                " This requires a shutdown of the instance. Continue?" %
863
                (instance_name,))
864
    if not AskUser(usertext):
865
      return 1
866

    
867
  op = opcodes.OpMoveInstance(instance_name=instance_name,
868
                              target_node=opts.node)
869
  SubmitOrSend(op, opts, cl=cl)
870
  return 0
871

    
872

    
873
def ConnectToInstanceConsole(opts, args):
874
  """Connect to the console of an instance.
875

    
876
  @param opts: the command line options selected by the user
877
  @type args: list
878
  @param args: should contain only one element, the instance name
879
  @rtype: int
880
  @return: the desired exit code
881

    
882
  """
883
  instance_name = args[0]
884

    
885
  op = opcodes.OpConnectConsole(instance_name=instance_name)
886
  cmd = SubmitOpCode(op)
887

    
888
  if opts.show_command:
889
    ToStdout("%s", utils.ShellQuoteArgs(cmd))
890
  else:
891
    try:
892
      os.execvp(cmd[0], cmd)
893
    finally:
894
      ToStderr("Can't run console command %s with arguments:\n'%s'",
895
               cmd[0], " ".join(cmd))
896
      os._exit(1)
897

    
898

    
899
def _FormatLogicalID(dev_type, logical_id):
900
  """Formats the logical_id of a disk.
901

    
902
  """
903
  if dev_type == constants.LD_DRBD8:
904
    node_a, node_b, port, minor_a, minor_b, key = logical_id
905
    data = [
906
      ("nodeA", "%s, minor=%s" % (node_a, minor_a)),
907
      ("nodeB", "%s, minor=%s" % (node_b, minor_b)),
908
      ("port", port),
909
      ("auth key", key),
910
      ]
911
  elif dev_type == constants.LD_LV:
912
    vg_name, lv_name = logical_id
913
    data = ["%s/%s" % (vg_name, lv_name)]
914
  else:
915
    data = [str(logical_id)]
916

    
917
  return data
918

    
919

    
920
def _FormatBlockDevInfo(idx, top_level, dev, static):
921
  """Show block device information.
922

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

    
926
  @type idx: int
927
  @param idx: the index of the current disk
928
  @type top_level: boolean
929
  @param top_level: if this a top-level disk?
930
  @type dev: dict
931
  @param dev: dictionary with disk information
932
  @type static: boolean
933
  @param static: wheter the device information doesn't contain
934
      runtime information but only static data
935
  @return: a list of either strings, tuples or lists
936
      (which should be formatted at a higher indent level)
937

    
938
  """
939
  def helper(dtype, status):
940
    """Format one line for physical device status.
941

    
942
    @type dtype: str
943
    @param dtype: a constant from the L{constants.LDS_BLOCK} set
944
    @type status: tuple
945
    @param status: a tuple as returned from L{backend.FindBlockDevice}
946
    @return: the string representing the status
947

    
948
    """
949
    if not status:
950
      return "not active"
951
    txt = ""
952
    (path, major, minor, syncp, estt, degr, ldisk_status) = status
953
    if major is None:
954
      major_string = "N/A"
955
    else:
956
      major_string = str(major)
957

    
958
    if minor is None:
959
      minor_string = "N/A"
960
    else:
961
      minor_string = str(minor)
962

    
963
    txt += ("%s (%s:%s)" % (path, major_string, minor_string))
964
    if dtype in (constants.LD_DRBD8, ):
965
      if syncp is not None:
966
        sync_text = "*RECOVERING* %5.2f%%," % syncp
967
        if estt:
968
          sync_text += " ETA %ds" % estt
969
        else:
970
          sync_text += " ETA unknown"
971
      else:
972
        sync_text = "in sync"
973
      if degr:
974
        degr_text = "*DEGRADED*"
975
      else:
976
        degr_text = "ok"
977
      if ldisk_status == constants.LDS_FAULTY:
978
        ldisk_text = " *MISSING DISK*"
979
      elif ldisk_status == constants.LDS_UNKNOWN:
980
        ldisk_text = " *UNCERTAIN STATE*"
981
      else:
982
        ldisk_text = ""
983
      txt += (" %s, status %s%s" % (sync_text, degr_text, ldisk_text))
984
    elif dtype == constants.LD_LV:
985
      if ldisk_status == constants.LDS_FAULTY:
986
        ldisk_text = " *FAILED* (failed drive?)"
987
      else:
988
        ldisk_text = ""
989
      txt += ldisk_text
990
    return txt
991

    
992
  # the header
993
  if top_level:
994
    if dev["iv_name"] is not None:
995
      txt = dev["iv_name"]
996
    else:
997
      txt = "disk %d" % idx
998
  else:
999
    txt = "child %d" % idx
1000
  if isinstance(dev["size"], int):
1001
    nice_size = utils.FormatUnit(dev["size"], "h")
1002
  else:
1003
    nice_size = dev["size"]
1004
  d1 = ["- %s: %s, size %s" % (txt, dev["dev_type"], nice_size)]
1005
  data = []
1006
  if top_level:
1007
    data.append(("access mode", dev["mode"]))
1008
  if dev["logical_id"] is not None:
1009
    try:
1010
      l_id = _FormatLogicalID(dev["dev_type"], dev["logical_id"])
1011
    except ValueError:
1012
      l_id = [str(dev["logical_id"])]
1013
    if len(l_id) == 1:
1014
      data.append(("logical_id", l_id[0]))
1015
    else:
1016
      data.extend(l_id)
1017
  elif dev["physical_id"] is not None:
1018
    data.append("physical_id:")
1019
    data.append([dev["physical_id"]])
1020
  if not static:
1021
    data.append(("on primary", helper(dev["dev_type"], dev["pstatus"])))
1022
  if dev["sstatus"] and not static:
1023
    data.append(("on secondary", helper(dev["dev_type"], dev["sstatus"])))
1024

    
1025
  if dev["children"]:
1026
    data.append("child devices:")
1027
    for c_idx, child in enumerate(dev["children"]):
1028
      data.append(_FormatBlockDevInfo(c_idx, False, child, static))
1029
  d1.append(data)
1030
  return d1
1031

    
1032

    
1033
def _FormatList(buf, data, indent_level):
1034
  """Formats a list of data at a given indent level.
1035

    
1036
  If the element of the list is:
1037
    - a string, it is simply formatted as is
1038
    - a tuple, it will be split into key, value and the all the
1039
      values in a list will be aligned all at the same start column
1040
    - a list, will be recursively formatted
1041

    
1042
  @type buf: StringIO
1043
  @param buf: the buffer into which we write the output
1044
  @param data: the list to format
1045
  @type indent_level: int
1046
  @param indent_level: the indent level to format at
1047

    
1048
  """
1049
  max_tlen = max([len(elem[0]) for elem in data
1050
                 if isinstance(elem, tuple)] or [0])
1051
  for elem in data:
1052
    if isinstance(elem, basestring):
1053
      buf.write("%*s%s\n" % (2*indent_level, "", elem))
1054
    elif isinstance(elem, tuple):
1055
      key, value = elem
1056
      spacer = "%*s" % (max_tlen - len(key), "")
1057
      buf.write("%*s%s:%s %s\n" % (2*indent_level, "", key, spacer, value))
1058
    elif isinstance(elem, list):
1059
      _FormatList(buf, elem, indent_level+1)
1060

    
1061

    
1062
def ShowInstanceConfig(opts, args):
1063
  """Compute instance run-time status.
1064

    
1065
  @param opts: the command line options selected by the user
1066
  @type args: list
1067
  @param args: either an empty list, and then we query all
1068
      instances, or should contain a list of instance names
1069
  @rtype: int
1070
  @return: the desired exit code
1071

    
1072
  """
1073
  if not args and not opts.show_all:
1074
    ToStderr("No instance selected."
1075
             " Please pass in --all if you want to query all instances.\n"
1076
             "Note that this can take a long time on a big cluster.")
1077
    return 1
1078
  elif args and opts.show_all:
1079
    ToStderr("Cannot use --all if you specify instance names.")
1080
    return 1
1081

    
1082
  retcode = 0
1083
  op = opcodes.OpQueryInstanceData(instances=args, static=opts.static)
1084
  result = SubmitOpCode(op)
1085
  if not result:
1086
    ToStdout("No instances.")
1087
    return 1
1088

    
1089
  buf = StringIO()
1090
  retcode = 0
1091
  for instance_name in result:
1092
    instance = result[instance_name]
1093
    buf.write("Instance name: %s\n" % instance["name"])
1094
    buf.write("UUID: %s\n" % instance["uuid"])
1095
    buf.write("Serial number: %s\n" % instance["serial_no"])
1096
    buf.write("Creation time: %s\n" % utils.FormatTime(instance["ctime"]))
1097
    buf.write("Modification time: %s\n" % utils.FormatTime(instance["mtime"]))
1098
    buf.write("State: configured to be %s" % instance["config_state"])
1099
    if not opts.static:
1100
      buf.write(", actual state is %s" % instance["run_state"])
1101
    buf.write("\n")
1102
    ##buf.write("Considered for memory checks in cluster verify: %s\n" %
1103
    ##          instance["auto_balance"])
1104
    buf.write("  Nodes:\n")
1105
    buf.write("    - primary: %s\n" % instance["pnode"])
1106
    buf.write("    - secondaries: %s\n" % ", ".join(instance["snodes"]))
1107
    buf.write("  Operating system: %s\n" % instance["os"])
1108
    if instance.has_key("network_port"):
1109
      buf.write("  Allocated network port: %s\n" % instance["network_port"])
1110
    buf.write("  Hypervisor: %s\n" % instance["hypervisor"])
1111

    
1112
    # custom VNC console information
1113
    vnc_bind_address = instance["hv_actual"].get(constants.HV_VNC_BIND_ADDRESS,
1114
                                                 None)
1115
    if vnc_bind_address:
1116
      port = instance["network_port"]
1117
      display = int(port) - constants.VNC_BASE_PORT
1118
      if display > 0 and vnc_bind_address == constants.BIND_ADDRESS_GLOBAL:
1119
        vnc_console_port = "%s:%s (display %s)" % (instance["pnode"],
1120
                                                   port,
1121
                                                   display)
1122
      elif display > 0 and utils.IsValidIP(vnc_bind_address):
1123
        vnc_console_port = ("%s:%s (node %s) (display %s)" %
1124
                             (vnc_bind_address, port,
1125
                              instance["pnode"], display))
1126
      else:
1127
        # vnc bind address is a file
1128
        vnc_console_port = "%s:%s" % (instance["pnode"],
1129
                                      vnc_bind_address)
1130
      buf.write("    - console connection: vnc to %s\n" % vnc_console_port)
1131

    
1132
    for key in instance["hv_actual"]:
1133
      if key in instance["hv_instance"]:
1134
        val = instance["hv_instance"][key]
1135
      else:
1136
        val = "default (%s)" % instance["hv_actual"][key]
1137
      buf.write("    - %s: %s\n" % (key, val))
1138
    buf.write("  Hardware:\n")
1139
    buf.write("    - VCPUs: %d\n" %
1140
              instance["be_actual"][constants.BE_VCPUS])
1141
    buf.write("    - memory: %dMiB\n" %
1142
              instance["be_actual"][constants.BE_MEMORY])
1143
    buf.write("    - NICs:\n")
1144
    for idx, (ip, mac, mode, link) in enumerate(instance["nics"]):
1145
      buf.write("      - nic/%d: MAC: %s, IP: %s, mode: %s, link: %s\n" %
1146
                (idx, mac, ip, mode, link))
1147
    buf.write("  Disks:\n")
1148

    
1149
    for idx, device in enumerate(instance["disks"]):
1150
      _FormatList(buf, _FormatBlockDevInfo(idx, True, device, opts.static), 2)
1151

    
1152
  ToStdout(buf.getvalue().rstrip('\n'))
1153
  return retcode
1154

    
1155

    
1156
def SetInstanceParams(opts, args):
1157
  """Modifies an instance.
1158

    
1159
  All parameters take effect only at the next restart of the instance.
1160

    
1161
  @param opts: the command line options selected by the user
1162
  @type args: list
1163
  @param args: should contain only one element, the instance name
1164
  @rtype: int
1165
  @return: the desired exit code
1166

    
1167
  """
1168
  if not (opts.nics or opts.disks or
1169
          opts.hvparams or opts.beparams):
1170
    ToStderr("Please give at least one of the parameters.")
1171
    return 1
1172

    
1173
  for param in opts.beparams:
1174
    if isinstance(opts.beparams[param], basestring):
1175
      if opts.beparams[param].lower() == "default":
1176
        opts.beparams[param] = constants.VALUE_DEFAULT
1177

    
1178
  utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_TYPES,
1179
                      allowed_values=[constants.VALUE_DEFAULT])
1180

    
1181
  for param in opts.hvparams:
1182
    if isinstance(opts.hvparams[param], basestring):
1183
      if opts.hvparams[param].lower() == "default":
1184
        opts.hvparams[param] = constants.VALUE_DEFAULT
1185

    
1186
  utils.ForceDictType(opts.hvparams, constants.HVS_PARAMETER_TYPES,
1187
                      allowed_values=[constants.VALUE_DEFAULT])
1188

    
1189
  for idx, (nic_op, nic_dict) in enumerate(opts.nics):
1190
    try:
1191
      nic_op = int(nic_op)
1192
      opts.nics[idx] = (nic_op, nic_dict)
1193
    except ValueError:
1194
      pass
1195

    
1196
  for idx, (disk_op, disk_dict) in enumerate(opts.disks):
1197
    try:
1198
      disk_op = int(disk_op)
1199
      opts.disks[idx] = (disk_op, disk_dict)
1200
    except ValueError:
1201
      pass
1202
    if disk_op == constants.DDM_ADD:
1203
      if 'size' not in disk_dict:
1204
        raise errors.OpPrereqError("Missing required parameter 'size'")
1205
      disk_dict['size'] = utils.ParseUnit(disk_dict['size'])
1206

    
1207
  op = opcodes.OpSetInstanceParams(instance_name=args[0],
1208
                                   nics=opts.nics,
1209
                                   disks=opts.disks,
1210
                                   hvparams=opts.hvparams,
1211
                                   beparams=opts.beparams,
1212
                                   force=opts.force)
1213

    
1214
  # even if here we process the result, we allow submit only
1215
  result = SubmitOrSend(op, opts)
1216

    
1217
  if result:
1218
    ToStdout("Modified instance %s", args[0])
1219
    for param, data in result:
1220
      ToStdout(" - %-5s -> %s", param, data)
1221
    ToStdout("Please don't forget that these parameters take effect"
1222
             " only at the next start of the instance.")
1223
  return 0
1224

    
1225

    
1226
# multi-instance selection options
1227
m_force_multi = cli_option("--force-multiple", dest="force_multi",
1228
                           help="Do not ask for confirmation when more than"
1229
                           " one instance is affected",
1230
                           action="store_true", default=False)
1231

    
1232
m_pri_node_opt = cli_option("--primary", dest="multi_mode",
1233
                            help="Filter by nodes (primary only)",
1234
                            const=_SHUTDOWN_NODES_PRI, action="store_const")
1235

    
1236
m_sec_node_opt = cli_option("--secondary", dest="multi_mode",
1237
                            help="Filter by nodes (secondary only)",
1238
                            const=_SHUTDOWN_NODES_SEC, action="store_const")
1239

    
1240
m_node_opt = cli_option("--node", dest="multi_mode",
1241
                        help="Filter by nodes (primary and secondary)",
1242
                        const=_SHUTDOWN_NODES_BOTH, action="store_const")
1243

    
1244
m_clust_opt = cli_option("--all", dest="multi_mode",
1245
                         help="Select all instances in the cluster",
1246
                         const=_SHUTDOWN_CLUSTER, action="store_const")
1247

    
1248
m_inst_opt = cli_option("--instance", dest="multi_mode",
1249
                        help="Filter by instance name [default]",
1250
                        const=_SHUTDOWN_INSTANCES, action="store_const")
1251

    
1252

    
1253
# this is defined separately due to readability only
1254
add_opts = [
1255
  BACKEND_OPT,
1256
  DISK_OPT,
1257
  DISK_TEMPLATE_OPT,
1258
  FILESTORE_DIR_OPT,
1259
  FILESTORE_DRIVER_OPT,
1260
  HYPERVISOR_OPT,
1261
  IALLOCATOR_OPT,
1262
  NET_OPT,
1263
  NODE_PLACEMENT_OPT,
1264
  NOIPCHECK_OPT,
1265
  NONICS_OPT,
1266
  NOSTART_OPT,
1267
  NWSYNC_OPT,
1268
  OS_OPT,
1269
  OS_SIZE_OPT,
1270
  SUBMIT_OPT,
1271
  ]
1272

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

    
1386
#: dictionary with aliases for commands
1387
aliases = {
1388
  'activate_block_devs': 'activate-disks',
1389
  'replace_disks': 'replace-disks',
1390
  'start': 'startup',
1391
  'stop': 'shutdown',
1392
  }
1393

    
1394

    
1395
if __name__ == '__main__':
1396
  sys.exit(GenericMain(commands, aliases=aliases,
1397
                       override={"tag_type": constants.TAG_INSTANCE}))