Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-instance @ f4ad2ef0

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

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

    
793

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

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

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

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

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

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

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

    
826

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

    
830
  The migrate is done without shutdown.
831

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

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

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

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

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

    
864

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

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

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

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

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

    
892

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

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

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

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

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

    
918

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

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

    
937
  return data
938

    
939

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

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

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

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

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

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

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

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

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

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

    
1052

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

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

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

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

    
1081

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

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

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

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

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

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

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

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

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

    
1175

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1246

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

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

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

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

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

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

    
1273

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

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

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

    
1423

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