gnt-instance 50.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 37
from cStringIO import StringIO

from ganeti.cli import *
from ganeti import opcodes
from ganeti import constants
from ganeti import utils
38 39 40 41 42 43 44
from ganeti import errors


_SHUTDOWN_CLUSTER = "cluster"
_SHUTDOWN_NODES_BOTH = "nodes"
_SHUTDOWN_NODES_PRI = "nodes-pri"
_SHUTDOWN_NODES_SEC = "nodes-sec"
45 46 47
_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"
48
_SHUTDOWN_INSTANCES = "instances"
49 50 51 52 53 54
_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)
55

56

57 58
_VALUE_TRUE = "true"

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

64

65
def _ExpandMultiNames(mode, names, client=None):
66 67 68 69
  """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
70
  primary/secondary will be returned. For _SHUTDOWN_NODES_BOTH, all
71 72 73 74
  instances having those nodes as either primary or secondary will be
  returned. For _SHUTDOWN_INSTANCES, the given instances will be
  returned.

75 76 77 78 79 80 81 82 83 84 85 86
  @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

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

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

  elif mode in (_SHUTDOWN_NODES_BOTH,
                _SHUTDOWN_NODES_PRI,
101 102 103 104 105 106 107 108 109 110 111
                _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"],
112
                              False)
113

114 115 116 117
    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))
118
    if mode in (_SHUTDOWN_NODES_BOTH, _SHUTDOWN_NODES_BOTH_BY_TAGS):
119
      inames = pri_names + sec_names
120
    elif mode in (_SHUTDOWN_NODES_PRI, _SHUTDOWN_NODES_PRI_BY_TAGS):
121
      inames = pri_names
122
    elif mode in (_SHUTDOWN_NODES_SEC, _SHUTDOWN_NODES_SEC_BY_TAGS):
123 124 125 126 127
      inames = sec_names
    else:
      raise errors.ProgrammerError("Unhandled shutdown type")
  elif mode == _SHUTDOWN_INSTANCES:
    if not names:
128 129
      raise errors.OpPrereqError("No instance names passed",
                                 errors.ECODE_INVAL)
130
    idata = client.QueryInstances(names, ["name"], False)
131
    inames = [row[0] for row in idata]
132 133 134 135 136 137
  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)]
138
  else:
139
    raise errors.OpPrereqError("Unknown mode '%s'" % mode, errors.ECODE_INVAL)
140 141

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


144
def _ConfirmOperation(inames, text, extra=""):
145 146 147 148 149
  """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.

150 151 152 153 154 155 156 157
  @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.
158 159 160

  """
  count = len(inames)
161 162
  msg = ("The %s will operate on %d instances.\n%s"
         "Do you want to continue?" % (text, count, extra))
163 164 165 166 167 168 169 170 171 172 173 174 175 176 177
  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)
178
    choice = AskUser(msg + affected, choices)
179 180 181
  return choice


182 183 184 185 186 187
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
188
  @type client: L{ganeti.luxi.Client}
189 190 191 192 193 194 195 196
  @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
197
  result = client.QueryInstances(names, ["name"], False)
198 199
  for orig_name, row in zip(names, result):
    if row[0] is None:
200 201
      raise errors.OpPrereqError("Instance '%s' does not exist" % orig_name,
                                 errors.ECODE_NOENT)
202 203


204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219
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"
220
                                 " any instances", errors.ECODE_INVAL)
221 222 223 224
    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
225
    jex = JobExecutor(verbose=multi_on, cl=cl, opts=opts)
226 227 228 229 230 231 232 233
    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
234
def ListInstances(opts, args):
Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
235
  """List instances and their properties.
Iustin Pop's avatar
Iustin Pop committed
236

237 238 239 240 241 242
  @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
243 244
  """
  if opts.output is None:
245 246 247
    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
248 249 250
  else:
    selected_fields = opts.output.split(",")

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

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

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

294 295
  list_type_fields = ("tags", "disk.sizes", "nic.macs", "nic.ips",
                      "nic.modes", "nic.links", "nic.bridges")
296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319
  # 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"
320 321
      elif field == "ctime" or field == "mtime":
        val = utils.FormatTime(val)
322
      elif field in list_type_fields:
Iustin Pop's avatar
Iustin Pop committed
323
        val = ",".join(str(item) for item in val)
324 325
      elif val is None:
        val = "-"
326 327
      row[idx] = str(val)

328 329
  data = GenerateTable(separator=opts.separator, headers=headers,
                       fields=selected_fields, unitfields=unitfields,
330
                       numfields=numfields, data=output, units=opts.units)
331 332

  for line in data:
333
    ToStdout(line)
Iustin Pop's avatar
Iustin Pop committed
334 335 336 337 338 339 340

  return 0


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

Iustin Pop's avatar
Iustin Pop committed
341
  This is just a wrapper over GenericInstanceCreate.
Iustin Pop's avatar
Iustin Pop committed
342 343

  """
Iustin Pop's avatar
Iustin Pop committed
344
  return GenericInstanceCreate(constants.INSTANCE_CREATE, opts, args)
Iustin Pop's avatar
Iustin Pop committed
345 346


347
def BatchCreate(opts, args):
348 349 350 351 352 353
  """Create instances using a definition file.

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

    {"instance-name":{
354
      "disk_size": [20480],
355 356 357 358
      "template": "drbd",
      "backend": {
        "memory": 512,
        "vcpus": 1 },
359
      "os": "debootstrap",
360 361 362 363 364 365 366 367 368 369 370 371 372
      "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
373 374

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

  def _PopulateWithDefaults(spec):
    """Returns a new hash combined with default values."""
391 392 393
    mydict = _DEFAULT_SPECS.copy()
    mydict.update(spec)
    return mydict
394 395 396 397 398 399 400

  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.' %
401
                                   required_field, errors.ECODE_INVAL)
402 403 404 405 406
    # 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'
407 408
                                   ' there was no secondary provided.',
                                   errors.ECODE_INVAL)
409 410
    elif spec['iallocator'] is None:
      raise errors.OpPrereqError('You have to provide at least a primary_node'
411 412
                                 ' or an iallocator.',
                                 errors.ECODE_INVAL)
413

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

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

Guido Trotter's avatar
Guido Trotter committed
426 427 428 429
  if not isinstance(instance_data, dict):
    ToStderr("The instance definition file is not in dict format.")
    return 1

430
  jex = JobExecutor(opts=opts)
431

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

Iustin Pop's avatar
Iustin Pop committed
441 442
    hypervisor = specs['hypervisor']
    hvparams = specs['hvparams']
443

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

454 455 456
    utils.ForceDictType(specs['backend'], constants.BES_PARAMETER_TYPES)
    utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)

457 458 459 460 461 462 463 464 465
    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"
466 467
                                 " individual nic fields as well",
                                 errors.ECODE_INVAL)
468 469 470 471 472
    elif specs['nics'] is not None:
      tmp_nics = specs['nics']
    elif not tmp_nics:
      tmp_nics = [{}]

473
    op = opcodes.OpCreateInstance(instance_name=name,
474
                                  disks=disks,
475 476 477
                                  disk_template=specs['template'],
                                  mode=constants.INSTANCE_CREATE,
                                  os_type=specs['os'],
478
                                  force_variant=opts.force_variant,
479 480
                                  pnode=specs['primary_node'],
                                  snode=specs['secondary_node'],
481
                                  nics=tmp_nics,
482 483
                                  start=specs['start'],
                                  ip_check=specs['ip_check'],
484
                                  name_check=specs['name_check'],
485 486 487 488 489 490 491 492
                                  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'])

493 494 495
    jex.QueueJob(name, op)
  # we never want to wait, just show the submitted job IDs
  jex.WaitOrShow(False)
496 497 498 499

  return 0


500 501 502
def ReinstallInstance(opts, args):
  """Reinstall an instance.

503 504 505 506 507 508
  @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
509 510

  """
511 512 513 514 515 516
  # 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:
517 518
    raise errors.OpPrereqError("Selection filter does not match any instances",
                               errors.ECODE_INVAL)
519

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

    if not result:
527
      ToStdout("Can't get the OS list")
528 529
      return 1

530
    ToStdout("Available OS templates:")
531 532
    number = 0
    choices = []
533 534 535 536 537 538
    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
539 540

    choices.append(('x', 'exit', 'Exit gnt-instance reinstall'))
541
    selected = AskUser("Enter OS template number (or x to abort):",
542 543 544
                       choices)

    if selected == 'exit':
545
      ToStderr("User aborted reinstall, exiting")
546 547
      return 1

548
    os_name = selected
549
  else:
550
    os_name = opts.os
551

552 553 554 555 556 557 558
  # 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)):
559
      return 1
560 561 562
  else:
    if not opts.force:
      usertext = ("This will reinstall the instance %s and remove"
Iustin Pop's avatar
Iustin Pop committed
563
                  " all data. Continue?") % inames[0]
564 565 566
      if not AskUser(usertext):
        return 1

567
  jex = JobExecutor(verbose=multi_on, opts=opts)
568 569
  for instance_name in inames:
    op = opcodes.OpReinstallInstance(instance_name=instance_name,
570 571
                                     os_type=os_name,
                                     force_variant=opts.force_variant)
572
    jex.QueueJob(instance_name, op)
573

574
  jex.WaitOrShow(not opts.submit_only)
575 576 577
  return 0


Iustin Pop's avatar
Iustin Pop committed
578 579 580
def RemoveInstance(opts, args):
  """Remove an instance.

581 582 583 584 585 586
  @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
587 588 589 590

  """
  instance_name = args[0]
  force = opts.force
591
  cl = GetClient()
Iustin Pop's avatar
Iustin Pop committed
592 593

  if not force:
594 595
    _EnsureInstancesExist(cl, [instance_name])

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

Iustin Pop's avatar
Iustin Pop committed
602
  op = opcodes.OpRemoveInstance(instance_name=instance_name,
603
                                ignore_failures=opts.ignore_failures,
604
                                shutdown_timeout=opts.shutdown_timeout)
605
  SubmitOrSend(op, opts, cl=cl)
Iustin Pop's avatar
Iustin Pop committed
606 607 608
  return 0


609
def RenameInstance(opts, args):
Guido Trotter's avatar
Guido Trotter committed
610
  """Rename an instance.
611

612 613 614 615 616 617
  @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
618 619 620 621

  """
  op = opcodes.OpRenameInstance(instance_name=args[0],
                                new_name=args[1],
622
                                ignore_ip=not opts.ip_check)
623
  SubmitOrSend(op, opts)
624 625 626
  return 0


Iustin Pop's avatar
Iustin Pop committed
627 628 629 630
def ActivateDisks(opts, args):
  """Activate an instance's disks.

  This serves two purposes:
631 632
    - 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
633 634
    - it repairs inactive secondary drbds

635 636 637 638 639 640
  @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
641 642
  """
  instance_name = args[0]
643 644
  op = opcodes.OpActivateInstanceDisks(instance_name=instance_name,
                                       ignore_size=opts.ignore_size)
645
  disks_info = SubmitOrSend(op, opts)
Iustin Pop's avatar
Iustin Pop committed
646
  for host, iname, nname in disks_info:
647
    ToStdout("%s:%s:%s", host, iname, nname)
Iustin Pop's avatar
Iustin Pop committed
648 649 650 651
  return 0


def DeactivateDisks(opts, args):
Iustin Pop's avatar
Iustin Pop committed
652
  """Deactivate an instance's disks.
Iustin Pop's avatar
Iustin Pop committed
653 654 655 656

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

657 658 659 660 661 662
  @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
663 664 665
  """
  instance_name = args[0]
  op = opcodes.OpDeactivateInstanceDisks(instance_name=instance_name)
666
  SubmitOrSend(op, opts)
Iustin Pop's avatar
Iustin Pop committed
667 668 669
  return 0


Iustin Pop's avatar
Iustin Pop committed
670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695
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
696
def GrowDisk(opts, args):
697
  """Grow an instance's disks.
Iustin Pop's avatar
Iustin Pop committed
698

699 700 701 702 703 704
  @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
705 706 707 708

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


721
def _StartupInstance(name, opts):
722
  """Startup instances.
Iustin Pop's avatar
Iustin Pop committed
723

724 725
  This returns the opcode to start an instance, and its decorator will
  wrap this into a loop starting all desired instances.
726

727
  @param name: the name of the instance to act on
728
  @param opts: the command line options selected by the user
729
  @return: the opcode needed for the operation
Iustin Pop's avatar
Iustin Pop committed
730 731

  """
732 733 734 735 736 737 738 739
  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
740

741

742
def _RebootInstance(name, opts):
743 744
  """Reboot instance(s).

745 746
  This returns the opcode to reboot an instance, and its decorator
  will wrap this into a loop rebooting all desired instances.
747

748
  @param name: the name of the instance to act on
749
  @param opts: the command line options selected by the user
750
  @return: the opcode needed for the operation
751 752

  """
753
  return opcodes.OpRebootInstance(instance_name=name,
754
                                  reboot_type=opts.reboot_type,
755
                                  ignore_secondaries=opts.ignore_secondaries,
756
                                  shutdown_timeout=opts.shutdown_timeout)
Iustin Pop's avatar
Iustin Pop committed
757

758

759
def _ShutdownInstance(name, opts):
Iustin Pop's avatar
Iustin Pop committed
760 761
  """Shutdown an instance.

762 763 764 765
  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
766
  @param opts: the command line options selected by the user
767
  @return: the opcode needed for the operation
Iustin Pop's avatar
Iustin Pop committed
768 769

  """
770 771
  return opcodes.OpShutdownInstance(instance_name=name,
                                    timeout=opts.timeout)
Iustin Pop's avatar
Iustin Pop committed
772 773 774 775 776


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

777 778 779 780 781
  @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
782 783

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

  op = opcodes.OpReplaceDisks(instance_name=args[0], disks=disks,
813
                              remote_node=new_2ndary, mode=mode,
814 815
                              iallocator=iallocator,
                              early_release=opts.early_release)
816
  SubmitOrSend(op, opts)
Iustin Pop's avatar
Iustin Pop committed
817 818 819 820 821 822 823 824 825
  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.

826 827 828 829 830
  @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
831 832

  """
833
  cl = GetClient()
834 835
  instance_name = args[0]
  force = opts.force
Iustin Pop's avatar
Iustin Pop committed
836

837
  if not force:
838 839
    _EnsureInstancesExist(cl, [instance_name])

840 841 842 843 844
    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
845

846
  op = opcodes.OpFailoverInstance(instance_name=instance_name,
847
                                  ignore_consistency=opts.ignore_consistency,
848
                                  shutdown_timeout=opts.shutdown_timeout)
849
  SubmitOrSend(op, opts, cl=cl)
850
  return 0
Iustin Pop's avatar
Iustin Pop committed
851 852


853 854 855 856 857
def MigrateInstance(opts, args):
  """Migrate an instance.

  The migrate is done without shutdown.

Iustin Pop's avatar
Iustin Pop committed
858 859 860 861 862
  @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
863 864

  """
865
  cl = GetClient()
866 867 868 869
  instance_name = args[0]
  force = opts.force

  if not force:
870 871
    _EnsureInstancesExist(cl, [instance_name])

872 873 874 875 876 877 878 879 880 881 882 883 884 885 886
    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,))
    usertext += (" is **experimental** in this version."
                " This might impact the instance if anything goes wrong."
                " Continue?")
    if not AskUser(usertext):
      return 1

  op = opcodes.OpMigrateInstance(instance_name=instance_name, live=opts.live,
                                 cleanup=opts.cleanup)
887
  SubmitOpCode(op, cl=cl, opts=opts)
888 889 890
  return 0


Iustin Pop's avatar
Iustin Pop committed
891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912
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,
913
                              target_node=opts.node,
914
                              shutdown_timeout=opts.shutdown_timeout)
Iustin Pop's avatar
Iustin Pop committed
915 916 917 918
  SubmitOrSend(op, opts, cl=cl)
  return 0


Iustin Pop's avatar
Iustin Pop committed
919 920 921
def ConnectToInstanceConsole(opts, args):
  """Connect to the console of an instance.

922 923 924 925 926
  @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
927 928 929 930 931

  """
  instance_name = args[0]

  op = opcodes.OpConnectConsole(instance_name=instance_name)
932
  cmd = SubmitOpCode(op, opts=opts)
933 934

  if opts.show_command:
935
    ToStdout("%s", utils.ShellQuoteArgs(cmd))
936 937 938 939
  else:
    try:
      os.execvp(cmd[0], cmd)
    finally:
940
      ToStderr("Can't run console command %s with arguments:\n'%s'",
941
               cmd[0], " ".join(cmd))
Iustin Pop's avatar
Iustin Pop committed
942
      os._exit(1) # pylint: disable-msg=W0212
Iustin Pop's avatar
Iustin Pop committed