Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-instance @ 8619a3bd

History | View | Annotate | Download (46.7 kB)

1
#!/usr/bin/python
2
#
3

    
4
# Copyright (C) 2006, 2007 Google Inc.
5
#
6
# This program is free software; you can redistribute it and/or modify
7
# it under the terms of the GNU General Public License as published by
8
# the Free Software Foundation; either version 2 of the License, or
9
# (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful, but
12
# WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14
# General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19
# 02110-1301, USA.
20

    
21

    
22
# pylint: disable-msg=W0401,W0614
23
# W0401: Wildcard import ganeti.cli
24
# W0614: Unused import %s from wildcard import (since we need cli)
25

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

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

    
39

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

    
46

    
47
_VALUE_TRUE = "true"
48

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

    
54

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

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

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

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

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

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

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

    
117
  return inames
118

    
119

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

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

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

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

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

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

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

    
157

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

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

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

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

    
179

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

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

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

    
209

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

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

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

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

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

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

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

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

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

    
311
  return 0
312

    
313

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

    
317
  This is just a wrapper over GenericInstanceCreate.
318

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

    
323

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

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

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

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

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

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

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

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

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

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

    
402
  jex = JobExecutor()
403

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

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

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

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

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

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

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

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

    
468
  return 0
469

    
470

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

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

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

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

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

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

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

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

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

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

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

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

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

    
548

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

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

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

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

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

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

    
579

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

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

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

    
597

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

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

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

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

    
621

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

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

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

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

    
640

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

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

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

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

    
666

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

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

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

    
691

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

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

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

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

    
712

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

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

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

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

    
729

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

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

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

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

    
744

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

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

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

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

    
790

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

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

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

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

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

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

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

    
823

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

    
827
  The migrate is done without shutdown.
828

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

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

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

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

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

    
861

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

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

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

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

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

    
889

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

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

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

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

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

    
915

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

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

    
934
  return data
935

    
936

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

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

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

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

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

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

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

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

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

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

    
1049

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

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

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

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

    
1078

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

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

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

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

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

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

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

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

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

    
1172

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1243

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

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

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

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

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

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

    
1270

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

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

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

    
1419

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