Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-instance @ d77490c5

History | View | Annotate | Download (45 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",
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"], names=[])
485
    result = SubmitOpCode(op)
486

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

    
491
    ToStdout("Available OS templates:")
492
    number = 0
493
    choices = []
494
    for entry in result:
495
      ToStdout("%3s: %s", number, entry[0])
496
      choices.append(("%s" % number, entry[0], entry[0]))
497
      number = number + 1
498

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

    
503
    if selected == 'exit':
504
      ToStderr("User aborted reinstall, exiting")
505
      return 1
506

    
507
    os_name = selected
508
  else:
509
    os_name = opts.os
510

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

    
526
  jex = JobExecutor(verbose=multi_on)
527
  for instance_name in inames:
528
    op = opcodes.OpReinstallInstance(instance_name=instance_name,
529
                                     os_type=os_name)
530
    jex.QueueJob(instance_name, op)
531

    
532
  jex.WaitOrShow(not opts.submit_only)
533
  return 0
534

    
535

    
536
def RemoveInstance(opts, args):
537
  """Remove an instance.
538

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

    
546
  """
547
  instance_name = args[0]
548
  force = opts.force
549
  cl = GetClient()
550

    
551
  if not force:
552
    _EnsureInstancesExist(cl, [instance_name])
553

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

    
560
  op = opcodes.OpRemoveInstance(instance_name=instance_name,
561
                                ignore_failures=opts.ignore_failures)
562
  SubmitOrSend(op, opts, cl=cl)
563
  return 0
564

    
565

    
566
def RenameInstance(opts, args):
567
  """Rename an instance.
568

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

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

    
583

    
584
def ActivateDisks(opts, args):
585
  """Activate an instance's disks.
586

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

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

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

    
607

    
608
def DeactivateDisks(opts, args):
609
  """Deactivate an instance's disks.
610

    
611
  This function takes the instance name, looks for its primary node
612
  and the tries to shutdown its block devices on that node.
613

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

    
620
  """
621
  instance_name = args[0]
622
  op = opcodes.OpDeactivateInstanceDisks(instance_name=instance_name)
623
  SubmitOrSend(op, opts)
624
  return 0
625

    
626

    
627
def RecreateDisks(opts, args):
628
  """Recreate an instance's disks.
629

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

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

    
647
  op = opcodes.OpRecreateInstanceDisks(instance_name=instance_name,
648
                                       disks=opts.disks)
649
  SubmitOrSend(op, opts)
650
  return 0
651

    
652

    
653
def GrowDisk(opts, args):
654
  """Grow an instance's disks.
655

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

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

    
676

    
677
def _StartupInstance(name, opts):
678
  """Startup instances.
679

    
680
  This returns the opcode to start an instance, and its decorator will
681
  wrap this into a loop starting all desired instances.
682

    
683
  @param name: the name of the instance to act on
684
  @param opts: the command line options selected by the user
685
  @return: the opcode needed for the operation
686

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

    
697

    
698
def _RebootInstance(name, opts):
699
  """Reboot instance(s).
700

    
701
  This returns the opcode to reboot an instance, and its decorator
702
  will wrap this into a loop rebooting all desired instances.
703

    
704
  @param name: the name of the instance to act on
705
  @param opts: the command line options selected by the user
706
  @return: the opcode needed for the operation
707

    
708
  """
709
  return opcodes.OpRebootInstance(instance_name=name,
710
                                  reboot_type=opts.reboot_type,
711
                                  ignore_secondaries=opts.ignore_secondaries)
712

    
713

    
714
def _ShutdownInstance(name, opts):
715
  """Shutdown an instance.
716

    
717
  This returns the opcode to shutdown an instance, and its decorator
718
  will wrap this into a loop shutting down 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.OpShutdownInstance(instance_name=name)
726

    
727

    
728
def ReplaceDisks(opts, args):
729
  """Replace the disks of an instance
730

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

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

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

    
772

    
773
def FailoverInstance(opts, args):
774
  """Failover an instance.
775

    
776
  The failover is done by shutting it down on its present node and
777
  starting it on the secondary.
778

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

    
785
  """
786
  cl = GetClient()
787
  instance_name = args[0]
788
  force = opts.force
789

    
790
  if not force:
791
    _EnsureInstancesExist(cl, [instance_name])
792

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

    
799
  op = opcodes.OpFailoverInstance(instance_name=instance_name,
800
                                  ignore_consistency=opts.ignore_consistency)
801
  SubmitOrSend(op, opts, cl=cl)
802
  return 0
803

    
804

    
805
def MigrateInstance(opts, args):
806
  """Migrate an instance.
807

    
808
  The migrate is done without shutdown.
809

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

    
816
  """
817
  cl = GetClient()
818
  instance_name = args[0]
819
  force = opts.force
820

    
821
  if not force:
822
    _EnsureInstancesExist(cl, [instance_name])
823

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

    
837
  op = opcodes.OpMigrateInstance(instance_name=instance_name, live=opts.live,
838
                                 cleanup=opts.cleanup)
839
  SubmitOpCode(op, cl=cl)
840
  return 0
841

    
842

    
843
def MoveInstance(opts, args):
844
  """Move an instance.
845

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

    
852
  """
853
  cl = GetClient()
854
  instance_name = args[0]
855
  force = opts.force
856

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

    
864
  op = opcodes.OpMoveInstance(instance_name=instance_name,
865
                              target_node=opts.node)
866
  SubmitOrSend(op, opts, cl=cl)
867
  return 0
868

    
869

    
870
def ConnectToInstanceConsole(opts, args):
871
  """Connect to the console of an instance.
872

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

    
879
  """
880
  instance_name = args[0]
881

    
882
  op = opcodes.OpConnectConsole(instance_name=instance_name)
883
  cmd = SubmitOpCode(op)
884

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

    
895

    
896
def _FormatLogicalID(dev_type, logical_id):
897
  """Formats the logical_id of a disk.
898

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

    
914
  return data
915

    
916

    
917
def _FormatBlockDevInfo(idx, top_level, dev, static):
918
  """Show block device information.
919

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

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

    
935
  """
936
  def helper(dtype, status):
937
    """Format one line for physical device status.
938

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

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

    
955
    if minor is None:
956
      minor_string = "N/A"
957
    else:
958
      minor_string = str(minor)
959

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

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

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

    
1029

    
1030
def _FormatList(buf, data, indent_level):
1031
  """Formats a list of data at a given indent level.
1032

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

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

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

    
1058

    
1059
def ShowInstanceConfig(opts, args):
1060
  """Compute instance run-time status.
1061

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

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

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

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

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

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

    
1145
    for idx, device in enumerate(instance["disks"]):
1146
      _FormatList(buf, _FormatBlockDevInfo(idx, True, device, opts.static), 2)
1147

    
1148
  ToStdout(buf.getvalue().rstrip('\n'))
1149
  return retcode
1150

    
1151

    
1152
def SetInstanceParams(opts, args):
1153
  """Modifies an instance.
1154

    
1155
  All parameters take effect only at the next restart of the instance.
1156

    
1157
  @param opts: the command line options selected by the user
1158
  @type args: list
1159
  @param args: should contain only one element, the instance name
1160
  @rtype: int
1161
  @return: the desired exit code
1162

    
1163
  """
1164
  if not (opts.nics or opts.disks or
1165
          opts.hvparams or opts.beparams):
1166
    ToStderr("Please give at least one of the parameters.")
1167
    return 1
1168

    
1169
  for param in opts.beparams:
1170
    if isinstance(opts.beparams[param], basestring):
1171
      if opts.beparams[param].lower() == "default":
1172
        opts.beparams[param] = constants.VALUE_DEFAULT
1173

    
1174
  utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_TYPES,
1175
                      allowed_values=[constants.VALUE_DEFAULT])
1176

    
1177
  for param in opts.hvparams:
1178
    if isinstance(opts.hvparams[param], basestring):
1179
      if opts.hvparams[param].lower() == "default":
1180
        opts.hvparams[param] = constants.VALUE_DEFAULT
1181

    
1182
  utils.ForceDictType(opts.hvparams, constants.HVS_PARAMETER_TYPES,
1183
                      allowed_values=[constants.VALUE_DEFAULT])
1184

    
1185
  for idx, (nic_op, nic_dict) in enumerate(opts.nics):
1186
    try:
1187
      nic_op = int(nic_op)
1188
      opts.nics[idx] = (nic_op, nic_dict)
1189
    except ValueError:
1190
      pass
1191

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

    
1203
  op = opcodes.OpSetInstanceParams(instance_name=args[0],
1204
                                   nics=opts.nics,
1205
                                   disks=opts.disks,
1206
                                   hvparams=opts.hvparams,
1207
                                   beparams=opts.beparams,
1208
                                   force=opts.force)
1209

    
1210
  # even if here we process the result, we allow submit only
1211
  result = SubmitOrSend(op, opts)
1212

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

    
1221

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

    
1228
m_pri_node_opt = cli_option("--primary", dest="multi_mode",
1229
                            help="Filter by nodes (primary only)",
1230
                            const=_SHUTDOWN_NODES_PRI, action="store_const")
1231

    
1232
m_sec_node_opt = cli_option("--secondary", dest="multi_mode",
1233
                            help="Filter by nodes (secondary only)",
1234
                            const=_SHUTDOWN_NODES_SEC, action="store_const")
1235

    
1236
m_node_opt = cli_option("--node", dest="multi_mode",
1237
                        help="Filter by nodes (primary and secondary)",
1238
                        const=_SHUTDOWN_NODES_BOTH, action="store_const")
1239

    
1240
m_clust_opt = cli_option("--all", dest="multi_mode",
1241
                         help="Select all instances in the cluster",
1242
                         const=_SHUTDOWN_CLUSTER, action="store_const")
1243

    
1244
m_inst_opt = cli_option("--instance", dest="multi_mode",
1245
                        help="Filter by instance name [default]",
1246
                        const=_SHUTDOWN_INSTANCES, action="store_const")
1247

    
1248

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

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

    
1382
#: dictionary with aliases for commands
1383
aliases = {
1384
  'activate_block_devs': 'activate-disks',
1385
  'replace_disks': 'replace-disks',
1386
  'start': 'startup',
1387
  'stop': 'shutdown',
1388
  }
1389

    
1390

    
1391
if __name__ == '__main__':
1392
  sys.exit(GenericMain(commands, aliases=aliases,
1393
                       override={"tag_type": constants.TAG_INSTANCE}))