gnt-instance 51.3 KB
Newer Older
Iustin Pop's avatar
Iustin Pop committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
#!/usr/bin/python
#

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

Iustin Pop's avatar
Iustin Pop committed
21
"""Instance related commands"""
Iustin Pop's avatar
Iustin Pop committed
22

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

Iustin Pop's avatar
Iustin Pop committed
28 29
import sys
import os
30
import itertools
31
import simplejson
Iustin Pop's avatar
Iustin Pop committed
32 33 34 35 36
from cStringIO import StringIO

from ganeti.cli import *
from ganeti import opcodes
from ganeti import constants
37
from ganeti import compat
Iustin Pop's avatar
Iustin Pop committed
38
from ganeti import utils
39 40 41 42 43 44 45
from ganeti import errors


_SHUTDOWN_CLUSTER = "cluster"
_SHUTDOWN_NODES_BOTH = "nodes"
_SHUTDOWN_NODES_PRI = "nodes-pri"
_SHUTDOWN_NODES_SEC = "nodes-sec"
46 47 48
_SHUTDOWN_NODES_BOTH_BY_TAGS = "nodes-by-tags"
_SHUTDOWN_NODES_PRI_BY_TAGS = "nodes-pri-by-tags"
_SHUTDOWN_NODES_SEC_BY_TAGS = "nodes-sec-by-tags"
49
_SHUTDOWN_INSTANCES = "instances"
50 51 52 53 54 55
_SHUTDOWN_INSTANCES_BY_TAGS = "instances-by-tags"

_SHUTDOWN_NODES_TAGS_MODES = (
    _SHUTDOWN_NODES_BOTH_BY_TAGS,
    _SHUTDOWN_NODES_PRI_BY_TAGS,
    _SHUTDOWN_NODES_SEC_BY_TAGS)
56

57

58 59
_VALUE_TRUE = "true"

60
#: default list of options for L{ListInstances}
61
_LIST_DEF_FIELDS = [
62
  "name", "hypervisor", "os", "pnode", "status", "oper_ram",
63 64
  ]

65

66
def _ExpandMultiNames(mode, names, client=None):
67 68 69 70
  """Expand the given names using the passed mode.

  For _SHUTDOWN_CLUSTER, all instances will be returned. For
  _SHUTDOWN_NODES_PRI/SEC, all instances having those nodes as
71
  primary/secondary will be returned. For _SHUTDOWN_NODES_BOTH, all
72 73 74 75
  instances having those nodes as either primary or secondary will be
  returned. For _SHUTDOWN_INSTANCES, the given instances will be
  returned.

76 77 78 79 80 81 82 83 84 85 86 87
  @param mode: one of L{_SHUTDOWN_CLUSTER}, L{_SHUTDOWN_NODES_BOTH},
      L{_SHUTDOWN_NODES_PRI}, L{_SHUTDOWN_NODES_SEC} or
      L{_SHUTDOWN_INSTANCES}
  @param names: a list of names; for cluster, it must be empty,
      and for node and instance it must be a list of valid item
      names (short names are valid as usual, e.g. node1 instead of
      node1.example.com)
  @rtype: list
  @return: the list of names after the expansion
  @raise errors.ProgrammerError: for unknown selection type
  @raise errors.OpPrereqError: for invalid input parameters

88
  """
Iustin Pop's avatar
Iustin Pop committed
89
  # pylint: disable-msg=W0142
90

91 92
  if client is None:
    client = GetClient()
93 94
  if mode == _SHUTDOWN_CLUSTER:
    if names:
95 96
      raise errors.OpPrereqError("Cluster filter mode takes no arguments",
                                 errors.ECODE_INVAL)
97
    idata = client.QueryInstances([], ["name"], False)
98 99 100 101
    inames = [row[0] for row in idata]

  elif mode in (_SHUTDOWN_NODES_BOTH,
                _SHUTDOWN_NODES_PRI,
102 103 104 105 106 107 108 109 110 111 112
                _SHUTDOWN_NODES_SEC) + _SHUTDOWN_NODES_TAGS_MODES:
    if mode in _SHUTDOWN_NODES_TAGS_MODES:
      if not names:
        raise errors.OpPrereqError("No node tags passed", errors.ECODE_INVAL)
      ndata = client.QueryNodes([], ["name", "pinst_list",
                                     "sinst_list", "tags"], False)
      ndata = [row for row in ndata if set(row[3]).intersection(names)]
    else:
      if not names:
        raise errors.OpPrereqError("No node names passed", errors.ECODE_INVAL)
      ndata = client.QueryNodes(names, ["name", "pinst_list", "sinst_list"],
113
                              False)
114

115 116 117 118
    ipri = [row[1] for row in ndata]
    pri_names = list(itertools.chain(*ipri))
    isec = [row[2] for row in ndata]
    sec_names = list(itertools.chain(*isec))
119
    if mode in (_SHUTDOWN_NODES_BOTH, _SHUTDOWN_NODES_BOTH_BY_TAGS):
120
      inames = pri_names + sec_names
121
    elif mode in (_SHUTDOWN_NODES_PRI, _SHUTDOWN_NODES_PRI_BY_TAGS):
122
      inames = pri_names
123
    elif mode in (_SHUTDOWN_NODES_SEC, _SHUTDOWN_NODES_SEC_BY_TAGS):
124 125 126 127 128
      inames = sec_names
    else:
      raise errors.ProgrammerError("Unhandled shutdown type")
  elif mode == _SHUTDOWN_INSTANCES:
    if not names:
129 130
      raise errors.OpPrereqError("No instance names passed",
                                 errors.ECODE_INVAL)
131
    idata = client.QueryInstances(names, ["name"], False)
132
    inames = [row[0] for row in idata]
133 134 135 136 137 138
  elif mode == _SHUTDOWN_INSTANCES_BY_TAGS:
    if not names:
      raise errors.OpPrereqError("No instance tags passed",
                                 errors.ECODE_INVAL)
    idata = client.QueryInstances([], ["name", "tags"], False)
    inames = [row[0] for row in idata if set(row[1]).intersection(names)]
139
  else:
140
    raise errors.OpPrereqError("Unknown mode '%s'" % mode, errors.ECODE_INVAL)
141 142

  return inames
Iustin Pop's avatar
Iustin Pop committed
143 144


145
def _ConfirmOperation(inames, text, extra=""):
146 147 148 149 150
  """Ask the user to confirm an operation on a list of instances.

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

151 152 153 154 155 156 157 158
  @type inames: list
  @param inames: the list of names that we display when
      we ask for confirmation
  @type text: str
  @param text: the operation that the user should confirm
      (e.g. I{shutdown} or I{startup})
  @rtype: boolean
  @return: True or False depending on user's confirmation.
159 160 161

  """
  count = len(inames)
162 163
  msg = ("The %s will operate on %d instances.\n%s"
         "Do you want to continue?" % (text, count, extra))
164 165 166 167 168 169 170 171 172 173 174 175 176 177 178
  affected = ("\nAffected instances:\n" +
              "\n".join(["  %s" % name for name in inames]))

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

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

  choice = AskUser(ask, choices)
  if choice == 'v':
    choices.pop(1)
179
    choice = AskUser(msg + affected, choices)
180 181 182
  return choice


183 184 185 186 187 188
def _EnsureInstancesExist(client, names):
  """Check for and ensure the given instance names exist.

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

Iustin Pop's avatar
Iustin Pop committed
189
  @type client: L{ganeti.luxi.Client}
190 191 192 193 194 195 196 197
  @param client: the client to use for the query
  @type names: list
  @param names: the list of instance names to query
  @raise errors.OpPrereqError: in case any instance is missing

  """
  # TODO: change LUQueryInstances to that it actually returns None
  # instead of raising an exception, or devise a better mechanism
198
  result = client.QueryInstances(names, ["name"], False)
199 200
  for orig_name, row in zip(names, result):
    if row[0] is None:
201 202
      raise errors.OpPrereqError("Instance '%s' does not exist" % orig_name,
                                 errors.ECODE_NOENT)
203 204


205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220
def GenericManyOps(operation, fn):
  """Generic multi-instance operations.

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

  """
  def realfn(opts, args):
    if opts.multi_mode is None:
      opts.multi_mode = _SHUTDOWN_INSTANCES
    cl = GetClient()
    inames = _ExpandMultiNames(opts.multi_mode, args, client=cl)
    if not inames:
      raise errors.OpPrereqError("Selection filter does not match"
221
                                 " any instances", errors.ECODE_INVAL)
222 223 224 225
    multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
    if not (opts.force_multi or not multi_on
            or _ConfirmOperation(inames, operation)):
      return 1
226
    jex = JobExecutor(verbose=multi_on, cl=cl, opts=opts)
227 228 229 230 231 232 233 234
    for name in inames:
      op = fn(name, opts)
      jex.QueueJob(name, op)
    jex.WaitOrShow(not opts.submit_only)
    return 0
  return realfn


Iustin Pop's avatar
Iustin Pop committed
235
def ListInstances(opts, args):
Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
236
  """List instances and their properties.
Iustin Pop's avatar
Iustin Pop committed
237

238 239 240 241 242 243
  @param opts: the command line options selected by the user
  @type args: list
  @param args: should be an empty list
  @rtype: int
  @return: the desired exit code

Iustin Pop's avatar
Iustin Pop committed
244 245
  """
  if opts.output is None:
246 247 248
    selected_fields = _LIST_DEF_FIELDS
  elif opts.output.startswith("+"):
    selected_fields = _LIST_DEF_FIELDS + opts.output[1:].split(",")
Iustin Pop's avatar
Iustin Pop committed
249 250 251
  else:
    selected_fields = opts.output.split(",")

252
  output = GetClient().QueryInstances(args, selected_fields, opts.do_locking)
Iustin Pop's avatar
Iustin Pop committed
253 254

  if not opts.no_headers:
255 256 257
    headers = {
      "name": "Instance", "os": "OS", "pnode": "Primary_node",
      "snodes": "Secondary_Nodes", "admin_state": "Autostart",
Iustin Pop's avatar
Iustin Pop committed
258
      "oper_state": "Running",
259
      "oper_ram": "Memory", "disk_template": "Disk_template",
260
      "ip": "IP_address", "mac": "MAC_address",
261
      "nic_mode": "NIC_Mode", "nic_link": "NIC_Link",
Iustin Pop's avatar
Iustin Pop committed
262
      "bridge": "Bridge",
263
      "sda_size": "Disk/0", "sdb_size": "Disk/1",
264
      "disk_usage": "DiskUsage",
265
      "status": "Status", "tags": "Tags",
266
      "network_port": "Network_port",
267 268
      "hv/kernel_path": "Kernel_path",
      "hv/initrd_path": "Initrd_path",
Iustin Pop's avatar
Iustin Pop committed
269 270 271 272 273 274
      "hv/boot_order": "Boot_order",
      "hv/acpi": "ACPI",
      "hv/pae": "PAE",
      "hv/cdrom_image_path": "CDROM_image_path",
      "hv/nic_type": "NIC_type",
      "hv/disk_type": "Disk_type",
275
      "hv/vnc_bind_address": "VNC_bind_address",
276
      "serial_no": "SerialNo", "hypervisor": "Hypervisor",
277
      "hvparams": "Hypervisor_parameters",
Iustin Pop's avatar
Iustin Pop committed
278 279
      "be/memory": "Configured_memory",
      "be/vcpus": "VCPUs",
280
      "vcpus": "VCPUs",
281
      "be/auto_balance": "Auto_balance",
Iustin Pop's avatar
Iustin Pop committed
282 283
      "disk.count": "Disks", "disk.sizes": "Disk_sizes",
      "nic.count": "NICs", "nic.ips": "NIC_IPs",
284
      "nic.modes": "NIC_modes", "nic.links": "NIC_links",
Iustin Pop's avatar
Iustin Pop committed
285
      "nic.bridges": "NIC_bridges", "nic.macs": "NIC_MACs",
286
      "ctime": "CTime", "mtime": "MTime", "uuid": "UUID",
287
      }
288 289 290
  else:
    headers = None

291
  unitfields = ["be/memory", "oper_ram", "sd(a|b)_size", "disk\.size/.*"]
292
  numfields = ["be/memory", "oper_ram", "sd(a|b)_size", "be/vcpus",
Iustin Pop's avatar
Iustin Pop committed
293
               "serial_no", "(disk|nic)\.count", "disk\.size/.*"]
294

295 296
  list_type_fields = ("tags", "disk.sizes", "nic.macs", "nic.ips",
                      "nic.modes", "nic.links", "nic.bridges")
297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320
  # change raw values to nicer strings
  for row in output:
    for idx, field in enumerate(selected_fields):
      val = row[idx]
      if field == "snodes":
        val = ",".join(val) or "-"
      elif field == "admin_state":
        if val:
          val = "yes"
        else:
          val = "no"
      elif field == "oper_state":
        if val is None:
          val = "(node down)"
        elif val: # True
          val = "running"
        else:
          val = "stopped"
      elif field == "oper_ram":
        if val is None:
          val = "(node down)"
      elif field == "sda_size" or field == "sdb_size":
        if val is None:
          val = "N/A"
321 322
      elif field == "ctime" or field == "mtime":
        val = utils.FormatTime(val)
323
      elif field in list_type_fields:
Iustin Pop's avatar
Iustin Pop committed
324
        val = ",".join(str(item) for item in val)
325 326
      elif val is None:
        val = "-"
327 328
      if opts.roman_integers and isinstance(val, int):
        val = compat.TryToRoman(val)
329 330
      row[idx] = str(val)

331 332
  data = GenerateTable(separator=opts.separator, headers=headers,
                       fields=selected_fields, unitfields=unitfields,
333
                       numfields=numfields, data=output, units=opts.units)
334 335

  for line in data:
336
    ToStdout(line)
Iustin Pop's avatar
Iustin Pop committed
337 338 339 340 341 342 343

  return 0


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

Iustin Pop's avatar
Iustin Pop committed
344
  This is just a wrapper over GenericInstanceCreate.
Iustin Pop's avatar
Iustin Pop committed
345 346

  """
Iustin Pop's avatar
Iustin Pop committed
347
  return GenericInstanceCreate(constants.INSTANCE_CREATE, opts, args)
Iustin Pop's avatar
Iustin Pop committed
348 349


350
def BatchCreate(opts, args):
351 352 353 354 355 356
  """Create instances using a definition file.

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

    {"instance-name":{
357
      "disk_size": [20480],
358 359 360 361
      "template": "drbd",
      "backend": {
        "memory": 512,
        "vcpus": 1 },
362
      "os": "debootstrap",
363 364 365 366 367 368 369 370 371 372 373 374 375
      "primary_node": "firstnode",
      "secondary_node": "secondnode",
      "iallocator": "dumb"}
    }

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

  @param opts: the command line options selected by the user
  @type args: list
  @param args: should contain one element, the json filename
  @rtype: int
  @return: the desired exit code
376 377

  """
378
  _DEFAULT_SPECS = {"disk_size": [20 * 1024],
379 380 381 382
                    "backend": {},
                    "iallocator": None,
                    "primary_node": None,
                    "secondary_node": None,
383
                    "nics": None,
384 385
                    "start": True,
                    "ip_check": True,
386
                    "name_check": True,
387
                    "hypervisor": None,
Iustin Pop's avatar
Iustin Pop committed
388
                    "hvparams": {},
389
                    "file_storage_dir": None,
390
                    "force_variant": False,
391 392 393 394
                    "file_driver": 'loop'}

  def _PopulateWithDefaults(spec):
    """Returns a new hash combined with default values."""
395 396 397
    mydict = _DEFAULT_SPECS.copy()
    mydict.update(spec)
    return mydict
398 399 400 401 402 403 404

  def _Validate(spec):
    """Validate the instance specs."""
    # Validate fields required under any circumstances
    for required_field in ('os', 'template'):
      if required_field not in spec:
        raise errors.OpPrereqError('Required field "%s" is missing.' %
405
                                   required_field, errors.ECODE_INVAL)
406 407 408 409 410
    # Validate special fields
    if spec['primary_node'] is not None:
      if (spec['template'] in constants.DTS_NET_MIRROR and
          spec['secondary_node'] is None):
        raise errors.OpPrereqError('Template requires secondary node, but'
411 412
                                   ' there was no secondary provided.',
                                   errors.ECODE_INVAL)
413 414
    elif spec['iallocator'] is None:
      raise errors.OpPrereqError('You have to provide at least a primary_node'
415 416
                                 ' or an iallocator.',
                                 errors.ECODE_INVAL)
417

Iustin Pop's avatar
Iustin Pop committed
418 419
    if (spec['hvparams'] and
        not isinstance(spec['hvparams'], dict)):
420 421
      raise errors.OpPrereqError('Hypervisor parameters must be a dict.',
                                 errors.ECODE_INVAL)
422 423 424

  json_filename = args[0]
  try:
425
    instance_data = simplejson.loads(utils.ReadFile(json_filename))
Iustin Pop's avatar
Iustin Pop committed
426
  except Exception, err: # pylint: disable-msg=W0703
Iustin Pop's avatar
Iustin Pop committed
427 428
    ToStderr("Can't parse the instance definition file: %s" % str(err))
    return 1
429

Guido Trotter's avatar
Guido Trotter committed
430 431 432 433
  if not isinstance(instance_data, dict):
    ToStderr("The instance definition file is not in dict format.")
    return 1

434
  jex = JobExecutor(opts=opts)
435

436 437 438
  # Iterate over the instances and do:
  #  * Populate the specs with default value
  #  * Validate the instance specs
Guido Trotter's avatar
Guido Trotter committed
439
  i_names = utils.NiceSort(instance_data.keys()) # pylint: disable-msg=E1103
440 441
  for name in i_names:
    specs = instance_data[name]
442 443 444
    specs = _PopulateWithDefaults(specs)
    _Validate(specs)

Iustin Pop's avatar
Iustin Pop committed
445 446
    hypervisor = specs['hypervisor']
    hvparams = specs['hvparams']
447

448 449 450 451
    disks = []
    for elem in specs['disk_size']:
      try:
        size = utils.ParseUnit(elem)
452
      except (TypeError, ValueError), err:
453 454
        raise errors.OpPrereqError("Invalid disk size '%s' for"
                                   " instance %s: %s" %
455
                                   (elem, name, err), errors.ECODE_INVAL)
456 457
      disks.append({"size": size})

458 459 460
    utils.ForceDictType(specs['backend'], constants.BES_PARAMETER_TYPES)
    utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)

461 462 463 464 465 466 467 468 469
    tmp_nics = []
    for field in ('ip', 'mac', 'mode', 'link', 'bridge'):
      if field in specs:
        if not tmp_nics:
          tmp_nics.append({})
        tmp_nics[0][field] = specs[field]

    if specs['nics'] is not None and tmp_nics:
      raise errors.OpPrereqError("'nics' list incompatible with using"
470 471
                                 " individual nic fields as well",
                                 errors.ECODE_INVAL)
472 473 474 475 476
    elif specs['nics'] is not None:
      tmp_nics = specs['nics']
    elif not tmp_nics:
      tmp_nics = [{}]

477
    op = opcodes.OpCreateInstance(instance_name=name,
478
                                  disks=disks,
479 480 481
                                  disk_template=specs['template'],
                                  mode=constants.INSTANCE_CREATE,
                                  os_type=specs['os'],
482
                                  force_variant=opts.force_variant,
483 484
                                  pnode=specs['primary_node'],
                                  snode=specs['secondary_node'],
485
                                  nics=tmp_nics,
486 487
                                  start=specs['start'],
                                  ip_check=specs['ip_check'],
488
                                  name_check=specs['name_check'],
489 490 491 492 493 494 495 496
                                  wait_for_sync=True,
                                  iallocator=specs['iallocator'],
                                  hypervisor=hypervisor,
                                  hvparams=hvparams,
                                  beparams=specs['backend'],
                                  file_storage_dir=specs['file_storage_dir'],
                                  file_driver=specs['file_driver'])

497 498 499
    jex.QueueJob(name, op)
  # we never want to wait, just show the submitted job IDs
  jex.WaitOrShow(False)
500 501 502 503

  return 0


504 505 506
def ReinstallInstance(opts, args):
  """Reinstall an instance.

507 508 509 510 511 512
  @param opts: the command line options selected by the user
  @type args: list
  @param args: should contain only one element, the name of the
      instance to be reinstalled
  @rtype: int
  @return: the desired exit code
513 514

  """
515 516 517 518 519 520
  # first, compute the desired name list
  if opts.multi_mode is None:
    opts.multi_mode = _SHUTDOWN_INSTANCES

  inames = _ExpandMultiNames(opts.multi_mode, args)
  if not inames:
521 522
    raise errors.OpPrereqError("Selection filter does not match any instances",
                               errors.ECODE_INVAL)
523

524
  # second, if requested, ask for an OS
525
  if opts.select_os is True:
526 527
    op = opcodes.OpDiagnoseOS(output_fields=["name", "valid", "variants"],
                              names=[])
528
    result = SubmitOpCode(op, opts=opts)
529 530

    if not result:
531
      ToStdout("Can't get the OS list")
532 533
      return 1

534
    ToStdout("Available OS templates:")
535 536
    number = 0
    choices = []
537 538 539 540 541 542
    for (name, valid, variants) in result:
      if valid:
        for entry in CalculateOSNames(name, variants):
          ToStdout("%3s: %s", number, entry)
          choices.append(("%s" % number, entry, entry))
          number += 1
543 544

    choices.append(('x', 'exit', 'Exit gnt-instance reinstall'))
545
    selected = AskUser("Enter OS template number (or x to abort):",
546 547 548
                       choices)

    if selected == 'exit':
549
      ToStderr("User aborted reinstall, exiting")
550 551
      return 1

552
    os_name = selected
553
  else:
554
    os_name = opts.os
555

556 557 558 559 560 561 562
  # third, get confirmation: multi-reinstall requires --force-multi
  # *and* --force, single-reinstall just --force
  multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
  if multi_on:
    warn_msg = "Note: this will remove *all* data for the below instances!\n"
    if not ((opts.force_multi and opts.force) or
            _ConfirmOperation(inames, "reinstall", extra=warn_msg)):
563
      return 1
564 565 566
  else:
    if not opts.force:
      usertext = ("This will reinstall the instance %s and remove"
Iustin Pop's avatar
Iustin Pop committed
567
                  " all data. Continue?") % inames[0]
568 569 570
      if not AskUser(usertext):
        return 1

571
  jex = JobExecutor(verbose=multi_on, opts=opts)
572 573
  for instance_name in inames:
    op = opcodes.OpReinstallInstance(instance_name=instance_name,
574 575
                                     os_type=os_name,
                                     force_variant=opts.force_variant)
576
    jex.QueueJob(instance_name, op)
577

578
  jex.WaitOrShow(not opts.submit_only)
579 580 581
  return 0


Iustin Pop's avatar
Iustin Pop committed
582 583 584
def RemoveInstance(opts, args):
  """Remove an instance.

585 586 587 588 589 590
  @param opts: the command line options selected by the user
  @type args: list
  @param args: should contain only one element, the name of
      the instance to be removed
  @rtype: int
  @return: the desired exit code
Iustin Pop's avatar
Iustin Pop committed
591 592 593 594

  """
  instance_name = args[0]
  force = opts.force
595
  cl = GetClient()
Iustin Pop's avatar
Iustin Pop committed
596 597

  if not force:
598 599
    _EnsureInstancesExist(cl, [instance_name])

Iustin Pop's avatar
Iustin Pop committed
600 601 602
    usertext = ("This will remove the volumes of the instance %s"
                " (including mirrors), thus removing all the data"
                " of the instance. Continue?") % instance_name
603
    if not AskUser(usertext):
Iustin Pop's avatar
Iustin Pop committed
604 605
      return 1

Iustin Pop's avatar
Iustin Pop committed
606
  op = opcodes.OpRemoveInstance(instance_name=instance_name,
607
                                ignore_failures=opts.ignore_failures,
608
                                shutdown_timeout=opts.shutdown_timeout)
609
  SubmitOrSend(op, opts, cl=cl)
Iustin Pop's avatar
Iustin Pop committed
610 611 612
  return 0


613
def RenameInstance(opts, args):
Guido Trotter's avatar
Guido Trotter committed
614
  """Rename an instance.
615

616 617 618 619 620 621
  @param opts: the command line options selected by the user
  @type args: list
  @param args: should contain two elements, the old and the
      new instance names
  @rtype: int
  @return: the desired exit code
622 623 624 625

  """
  op = opcodes.OpRenameInstance(instance_name=args[0],
                                new_name=args[1],
626
                                ignore_ip=not opts.ip_check)
627
  SubmitOrSend(op, opts)
628 629 630
  return 0


Iustin Pop's avatar
Iustin Pop committed
631 632 633 634
def ActivateDisks(opts, args):
  """Activate an instance's disks.

  This serves two purposes:
635 636
    - it allows (as long as the instance is not running)
      mounting the disks and modifying them from the node
Iustin Pop's avatar
Iustin Pop committed
637 638
    - it repairs inactive secondary drbds

639 640 641 642 643 644
  @param opts: the command line options selected by the user
  @type args: list
  @param args: should contain only one element, the instance name
  @rtype: int
  @return: the desired exit code

Iustin Pop's avatar
Iustin Pop committed
645 646
  """
  instance_name = args[0]
647 648
  op = opcodes.OpActivateInstanceDisks(instance_name=instance_name,
                                       ignore_size=opts.ignore_size)
649
  disks_info = SubmitOrSend(op, opts)
Iustin Pop's avatar
Iustin Pop committed
650
  for host, iname, nname in disks_info:
651
    ToStdout("%s:%s:%s", host, iname, nname)
Iustin Pop's avatar
Iustin Pop committed
652 653 654 655
  return 0


def DeactivateDisks(opts, args):
Iustin Pop's avatar
Iustin Pop committed
656
  """Deactivate an instance's disks.
Iustin Pop's avatar
Iustin Pop committed
657 658 659 660

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

661 662 663 664 665 666
  @param opts: the command line options selected by the user
  @type args: list
  @param args: should contain only one element, the instance name
  @rtype: int
  @return: the desired exit code

Iustin Pop's avatar
Iustin Pop committed
667 668 669
  """
  instance_name = args[0]
  op = opcodes.OpDeactivateInstanceDisks(instance_name=instance_name)
670
  SubmitOrSend(op, opts)
Iustin Pop's avatar
Iustin Pop committed
671 672 673
  return 0


Iustin Pop's avatar
Iustin Pop committed
674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699
def RecreateDisks(opts, args):
  """Recreate an instance's disks.

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

  """
  instance_name = args[0]
  if opts.disks:
    try:
      opts.disks = [int(v) for v in opts.disks.split(",")]
    except (ValueError, TypeError), err:
      ToStderr("Invalid disks value: %s" % str(err))
      return 1
  else:
    opts.disks = []

  op = opcodes.OpRecreateInstanceDisks(instance_name=instance_name,
                                       disks=opts.disks)
  SubmitOrSend(op, opts)
  return 0


Iustin Pop's avatar
Iustin Pop committed
700
def GrowDisk(opts, args):
701
  """Grow an instance's disks.
Iustin Pop's avatar
Iustin Pop committed
702

703 704 705 706 707 708
  @param opts: the command line options selected by the user
  @type args: list
  @param args: should contain two elements, the instance name
      whose disks we grow and the disk name, e.g. I{sda}
  @rtype: int
  @return: the desired exit code
Iustin Pop's avatar
Iustin Pop committed
709 710 711 712

  """
  instance = args[0]
  disk = args[1]
713 714
  try:
    disk = int(disk)
715
  except (TypeError, ValueError), err:
716 717
    raise errors.OpPrereqError("Invalid disk index: %s" % str(err),
                               errors.ECODE_INVAL)
Iustin Pop's avatar
Iustin Pop committed
718
  amount = utils.ParseUnit(args[2])
719 720
  op = opcodes.OpGrowDisk(instance_name=instance, disk=disk, amount=amount,
                          wait_for_sync=opts.wait_for_sync)
721
  SubmitOrSend(op, opts)
Iustin Pop's avatar
Iustin Pop committed
722 723 724
  return 0


725
def _StartupInstance(name, opts):
726
  """Startup instances.
Iustin Pop's avatar
Iustin Pop committed
727

728 729
  This returns the opcode to start an instance, and its decorator will
  wrap this into a loop starting all desired instances.
730

731
  @param name: the name of the instance to act on
732
  @param opts: the command line options selected by the user
733
  @return: the opcode needed for the operation
Iustin Pop's avatar
Iustin Pop committed
734 735

  """
736 737 738 739 740 741 742 743
  op = opcodes.OpStartupInstance(instance_name=name,
                                 force=opts.force)
  # do not add these parameters to the opcode unless they're defined
  if opts.hvparams:
    op.hvparams = opts.hvparams
  if opts.beparams:
    op.beparams = opts.beparams
  return op
Iustin Pop's avatar
Iustin Pop committed
744

745

746
def _RebootInstance(name, opts):
747 748
  """Reboot instance(s).

749 750
  This returns the opcode to reboot an instance, and its decorator
  will wrap this into a loop rebooting all desired instances.
751

752
  @param name: the name of the instance to act on
753
  @param opts: the command line options selected by the user
754
  @return: the opcode needed for the operation
755 756

  """
757
  return opcodes.OpRebootInstance(instance_name=name,
758
                                  reboot_type=opts.reboot_type,
759
                                  ignore_secondaries=opts.ignore_secondaries,
760
                                  shutdown_timeout=opts.shutdown_timeout)
Iustin Pop's avatar
Iustin Pop committed
761

762

763
def _ShutdownInstance(name, opts):
Iustin Pop's avatar
Iustin Pop committed
764 765
  """Shutdown an instance.

766 767 768 769
  This returns the opcode to shutdown an instance, and its decorator
  will wrap this into a loop shutting down all desired instances.

  @param name: the name of the instance to act on
770
  @param opts: the command line options selected by the user
771
  @return: the opcode needed for the operation
Iustin Pop's avatar
Iustin Pop committed
772 773

  """
774 775
  return opcodes.OpShutdownInstance(instance_name=name,
                                    timeout=opts.timeout)
Iustin Pop's avatar
Iustin Pop committed
776 777 778 779 780


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

781 782 783 784 785
  @param opts: the command line options selected by the user
  @type args: list
  @param args: should contain only one element, the instance name
  @rtype: int
  @return: the desired exit code
Iustin Pop's avatar
Iustin Pop committed
786 787

  """
788
  new_2ndary = opts.dst_node
789
  iallocator = opts.iallocator
790
  if opts.disks is None:
791
    disks = []
792
  else:
793 794
    try:
      disks = [int(i) for i in opts.disks.split(",")]
795
    except (TypeError, ValueError), err:
796 797
      raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err),
                                 errors.ECODE_INVAL)
798
  cnt = [opts.on_primary, opts.on_secondary, opts.auto,
799 800
         new_2ndary is not None, iallocator is not None].count(True)
  if cnt != 1:
801
    raise errors.OpPrereqError("One and only one of the -p, -s, -a, -n and -i"
802
                               " options must be passed", errors.ECODE_INVAL)
803
  elif opts.on_primary:
804
    mode = constants.REPLACE_DISK_PRI
805
  elif opts.on_secondary:
806
    mode = constants.REPLACE_DISK_SEC
807 808 809 810
  elif opts.auto:
    mode = constants.REPLACE_DISK_AUTO
    if disks:
      raise errors.OpPrereqError("Cannot specify disks when using automatic"
811
                                 " mode", errors.ECODE_INVAL)
812 813 814
  elif new_2ndary is not None or iallocator is not None:
    # replace secondary
    mode = constants.REPLACE_DISK_CHG
815 816

  op = opcodes.OpReplaceDisks(instance_name=args[0], disks=disks,
817
                              remote_node=new_2ndary, mode=mode,
818 819
                              iallocator=iallocator,
                              early_release=opts.early_release)
820
  SubmitOrSend(op, opts)
Iustin Pop's avatar
Iustin Pop committed
821 822 823 824 825 826 827 828 829
  return 0


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

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

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

  """
837
  cl = GetClient()
838 839
  instance_name = args[0]
  force = opts.force
Iustin Pop's avatar
Iustin Pop committed
840

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

844 845 846 847 848
    usertext = ("Failover will happen to image %s."
                " This requires a shutdown of the instance. Continue?" %
                (instance_name,))
    if not AskUser(usertext):
      return 1
Iustin Pop's avatar
Iustin Pop committed
849

850
  op = opcodes.OpFailoverInstance(instance_name=instance_name,
851
                                  ignore_consistency=opts.ignore_consistency,
852
                                  shutdown_timeout=opts.shutdown_timeout)
853
  SubmitOrSend(op, opts, cl=cl)
854
  return 0
Iustin Pop's avatar
Iustin Pop committed
855 856


857 858 859 860 861
def MigrateInstance(opts, args):
  """Migrate an instance.

  The migrate is done without shutdown.

Iustin Pop's avatar
Iustin Pop committed
862 863 864 865 866
  @param opts: the command line options selected by the user
  @type args: list
  @param args: should contain only one element, the instance name
  @rtype: int
  @return: the desired exit code
867 868

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

  if not force:
874 875
    _EnsureInstancesExist(cl, [instance_name])

876 877 878 879 880 881 882
    if opts.cleanup:
      usertext = ("Instance %s will be recovered from a failed migration."
                  " Note that the migration procedure (including cleanup)" %
                  (instance_name,))
    else:
      usertext = ("Instance %s will be migrated. Note that migration" %
                  (instance_name,))
Iustin Pop's avatar
Iustin Pop committed
883 884
    usertext += (" might impact the instance if anything goes wrong"
                 " (e.g. due to bugs in the hypervisor). Continue?")
885 886 887 888 889
    if not AskUser(usertext):
      return 1

  op = opcodes.OpMigrateInstance(instance_name=instance_name, live=opts.live,
                                 cleanup=opts.cleanup)
890
  SubmitOpCode(op, cl=cl, opts=opts)
891 892 893
  return 0


Iustin Pop's avatar
Iustin Pop committed
894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915
def MoveInstance(opts, args):
  """Move an instance.

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

  """
  cl = GetClient()
  instance_name = args[0]
  force = opts.force

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

  op = opcodes.OpMoveInstance(instance_name=instance_name,
916
                              target_node=opts.node,
917
                              shutdown_timeout=opts.shutdown_timeout)
Iustin Pop's avatar
Iustin Pop committed
918 919 920 921
  SubmitOrSend(op, opts, cl=cl)
  return 0


Iustin Pop's avatar
Iustin Pop committed
922 923 924
def ConnectToInstanceConsole(opts, args):
  """Connect to the console of an instance.

925 926 927 928 929
  @param opts: the command line options selected by the user
  @type args: list
  @param args: should contain only one element, the instance name
  @rtype: int
  @return: the desired exit code
Iustin Pop's avatar
Iustin Pop committed
930 931 932 933 934

  """
  instance_name = args[0]

  op = opcodes.OpConnectConsole(instance_name=instance_name)
935
  cmd = SubmitOpCode(op, opts=opts)
936 937

  if opts.show_command: