Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-instance @ 30e4e741

History | View | Annotate | Download (46.9 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
"""Instance related commands"""
22

    
23
# pylint: disable-msg=W0401,W0614,C0103
24
# W0401: Wildcard import ganeti.cli
25
# W0614: Unused import %s from wildcard import (since we need cli)
26
# C0103: Invalid name gnt-instance
27

    
28
import sys
29
import os
30
import itertools
31
import simplejson
32
from cStringIO import StringIO
33

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

    
40

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

    
47

    
48
_VALUE_TRUE = "true"
49

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

    
55

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

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

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

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

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

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

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

    
119
  return inames
120

    
121

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

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

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

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

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

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

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

    
159

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

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

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

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

    
181

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

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

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

    
211

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

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

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

    
229
  output = GetClient().QueryInstances(args, selected_fields, opts.do_locking)
230

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

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

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

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

    
310
  for line in data:
311
    ToStdout(line)
312

    
313
  return 0
314

    
315

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

    
319
  This is just a wrapper over GenericInstanceCreate.
320

    
321
  """
322
  return GenericInstanceCreate(constants.INSTANCE_CREATE, opts, args)
323

    
324

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

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

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

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

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

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

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

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

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

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

    
404
  jex = JobExecutor()
405

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

    
415
    hypervisor = specs['hypervisor']
416
    hvparams = specs['hvparams']
417

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

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

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

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

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

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

    
471
  return 0
472

    
473

    
474
def ReinstallInstance(opts, args):
475
  """Reinstall an instance.
476

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

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

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

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

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

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

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

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

    
522
    os_name = selected
523
  else:
524
    os_name = opts.os
525

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

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

    
548
  jex.WaitOrShow(not opts.submit_only)
549
  return 0
550

    
551

    
552
def RemoveInstance(opts, args):
553
  """Remove an instance.
554

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

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

    
567
  if not force:
568
    _EnsureInstancesExist(cl, [instance_name])
569

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

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

    
582

    
583
def RenameInstance(opts, args):
584
  """Rename an instance.
585

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

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

    
600

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

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

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

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

    
624

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

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

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

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

    
643

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

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

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

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

    
669

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

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

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

    
694

    
695
def _StartupInstance(name, opts):
696
  """Startup instances.
697

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

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

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

    
715

    
716
def _RebootInstance(name, opts):
717
  """Reboot instance(s).
718

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

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

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

    
732

    
733
def _ShutdownInstance(name, opts):
734
  """Shutdown an instance.
735

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

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

    
743
  """
744
  return opcodes.OpShutdownInstance(instance_name=name,
745
                                    timeout=opts.timeout)
746

    
747

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

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

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

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

    
792

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

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

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

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

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

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

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

    
825

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

    
829
  The migrate is done without shutdown.
830

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

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

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

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

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

    
863

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

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

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

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

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

    
891

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

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

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

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

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

    
917

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

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

    
936
  return data
937

    
938

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

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

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

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

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

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

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

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

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

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

    
1051

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

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

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

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

    
1080

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

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

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

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

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

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

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

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

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

    
1174

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1245

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

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

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

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

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

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

    
1272

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

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

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

    
1422

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