Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-instance @ 7260cfbe

History | View | Annotate | Download (47 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
import time
33
from cStringIO import StringIO
34

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

    
41

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

    
48

    
49
_VALUE_TRUE = "true"
50

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

    
56

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

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

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

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

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

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

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

    
120
  return inames
121

    
122

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

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

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

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

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

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

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

    
160

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

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

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

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

    
182

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

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

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

    
212

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

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

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

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

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

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

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

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

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

    
314
  return 0
315

    
316

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

    
320
  This is just a wrapper over GenericInstanceCreate.
321

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

    
325

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

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

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

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

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

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

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

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

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

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

    
405
  jex = JobExecutor()
406

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

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

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

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

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

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

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

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

    
472
  return 0
473

    
474

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

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

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

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

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

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

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

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

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

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

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

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

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

    
552

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

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

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

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

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

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

    
583

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

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

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

    
601

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

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

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

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

    
625

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

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

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

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

    
644

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

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

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

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

    
670

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

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

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

    
695

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

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

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

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

    
716

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

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

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

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

    
733

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

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

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

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

    
748

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

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

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

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

    
794

    
795
def FailoverInstance(opts, args):
796
  """Failover an instance.
797

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

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

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

    
812
  if not force:
813
    _EnsureInstancesExist(cl, [instance_name])
814

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

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

    
827

    
828
def MigrateInstance(opts, args):
829
  """Migrate an instance.
830

    
831
  The migrate is done without shutdown.
832

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

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

    
844
  if not force:
845
    _EnsureInstancesExist(cl, [instance_name])
846

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

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

    
865

    
866
def MoveInstance(opts, args):
867
  """Move an instance.
868

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

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

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

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

    
893

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

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

    
903
  """
904
  instance_name = args[0]
905

    
906
  op = opcodes.OpConnectConsole(instance_name=instance_name)
907
  cmd = SubmitOpCode(op)
908

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

    
919

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

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

    
938
  return data
939

    
940

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

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

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

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

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

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

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

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

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

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

    
1053

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

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

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

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

    
1082

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

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

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

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

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

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

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

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

    
1173
  ToStdout(buf.getvalue().rstrip('\n'))
1174
  return retcode
1175

    
1176

    
1177
def SetInstanceParams(opts, args):
1178
  """Modifies an instance.
1179

    
1180
  All parameters take effect only at the next restart of the instance.
1181

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

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

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

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

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

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

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

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

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

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

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

    
1247

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

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

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

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

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

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

    
1274

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

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

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

    
1424

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