gnt_instance.py 55.8 KB
Newer Older
1
#
Iustin Pop's avatar
Iustin Pop committed
2 3
#

4
# Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012 Google Inc.
Iustin Pop's avatar
Iustin Pop committed
5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
#
# 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

23
# pylint: disable=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

28
import copy
29
import itertools
30
import simplejson
31
import logging
Iustin Pop's avatar
Iustin Pop committed
32 33 34 35

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


45 46 47 48 49 50 51 52 53 54
_EXPAND_CLUSTER = "cluster"
_EXPAND_NODES_BOTH = "nodes"
_EXPAND_NODES_PRI = "nodes-pri"
_EXPAND_NODES_SEC = "nodes-sec"
_EXPAND_NODES_BOTH_BY_TAGS = "nodes-by-tags"
_EXPAND_NODES_PRI_BY_TAGS = "nodes-pri-by-tags"
_EXPAND_NODES_SEC_BY_TAGS = "nodes-sec-by-tags"
_EXPAND_INSTANCES = "instances"
_EXPAND_INSTANCES_BY_TAGS = "instances-by-tags"

55
_EXPAND_NODES_TAGS_MODES = compat.UniqueFrozenset([
56 57 58 59
  _EXPAND_NODES_BOTH_BY_TAGS,
  _EXPAND_NODES_PRI_BY_TAGS,
  _EXPAND_NODES_SEC_BY_TAGS,
  ])
60

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

66
_MISSING = object()
67
_ENV_OVERRIDE = compat.UniqueFrozenset(["list"])
68

69 70
_INST_DATA_VAL = ht.TListOf(ht.TDict)

71

72
def _ExpandMultiNames(mode, names, client=None):
73 74
  """Expand the given names using the passed mode.

75 76 77
  For _EXPAND_CLUSTER, all instances will be returned. For
  _EXPAND_NODES_PRI/SEC, all instances having those nodes as
  primary/secondary will be returned. For _EXPAND_NODES_BOTH, all
78
  instances having those nodes as either primary or secondary will be
79
  returned. For _EXPAND_INSTANCES, the given instances will be
80 81
  returned.

82 83 84
  @param mode: one of L{_EXPAND_CLUSTER}, L{_EXPAND_NODES_BOTH},
      L{_EXPAND_NODES_PRI}, L{_EXPAND_NODES_SEC} or
      L{_EXPAND_INSTANCES}
85 86 87 88 89 90 91 92 93
  @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

94
  """
95
  # pylint: disable=W0142
96

97 98
  if client is None:
    client = GetClient()
99
  if mode == _EXPAND_CLUSTER:
100
    if names:
101 102
      raise errors.OpPrereqError("Cluster filter mode takes no arguments",
                                 errors.ECODE_INVAL)
103
    idata = client.QueryInstances([], ["name"], False)
104 105
    inames = [row[0] for row in idata]

106 107 108
  elif (mode in _EXPAND_NODES_TAGS_MODES or
        mode in (_EXPAND_NODES_BOTH, _EXPAND_NODES_PRI, _EXPAND_NODES_SEC)):
    if mode in _EXPAND_NODES_TAGS_MODES:
109 110 111 112 113 114 115 116 117
      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"],
Iustin Pop's avatar
Iustin Pop committed
118
                                False)
119

120 121 122 123
    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))
124
    if mode in (_EXPAND_NODES_BOTH, _EXPAND_NODES_BOTH_BY_TAGS):
125
      inames = pri_names + sec_names
126
    elif mode in (_EXPAND_NODES_PRI, _EXPAND_NODES_PRI_BY_TAGS):
127
      inames = pri_names
128
    elif mode in (_EXPAND_NODES_SEC, _EXPAND_NODES_SEC_BY_TAGS):
129 130 131
      inames = sec_names
    else:
      raise errors.ProgrammerError("Unhandled shutdown type")
132
  elif mode == _EXPAND_INSTANCES:
133
    if not names:
134 135
      raise errors.OpPrereqError("No instance names passed",
                                 errors.ECODE_INVAL)
136
    idata = client.QueryInstances(names, ["name"], False)
137
    inames = [row[0] for row in idata]
138
  elif mode == _EXPAND_INSTANCES_BY_TAGS:
139 140 141 142 143
    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)]
144
  else:
145
    raise errors.OpPrereqError("Unknown mode '%s'" % mode, errors.ECODE_INVAL)
146 147

  return inames
Iustin Pop's avatar
Iustin Pop committed
148 149


150 151 152 153 154 155
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
156
  @type client: L{ganeti.luxi.Client}
157 158 159 160 161 162
  @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

  """
163
  # TODO: change LUInstanceQuery to that it actually returns None
164
  # instead of raising an exception, or devise a better mechanism
165
  result = client.QueryInstances(names, ["name"], False)
166 167
  for orig_name, row in zip(names, result):
    if row[0] is None:
168 169
      raise errors.OpPrereqError("Instance '%s' does not exist" % orig_name,
                                 errors.ECODE_NOENT)
170 171


172 173 174 175 176 177 178 179 180 181 182
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:
183
      opts.multi_mode = _EXPAND_INSTANCES
184 185 186
    cl = GetClient()
    inames = _ExpandMultiNames(opts.multi_mode, args, client=cl)
    if not inames:
187
      if opts.multi_mode == _EXPAND_CLUSTER:
188 189
        ToStdout("Cluster is empty, no instances to shutdown")
        return 0
190
      raise errors.OpPrereqError("Selection filter does not match"
191
                                 " any instances", errors.ECODE_INVAL)
192
    multi_on = opts.multi_mode != _EXPAND_INSTANCES or len(inames) > 1
193
    if not (opts.force_multi or not multi_on
194
            or ConfirmOperation(inames, "instances", operation)):
195
      return 1
196
    jex = JobExecutor(verbose=multi_on, cl=cl, opts=opts)
197 198 199
    for name in inames:
      op = fn(name, opts)
      jex.QueueJob(name, op)
Iustin Pop's avatar
Iustin Pop committed
200 201 202
    results = jex.WaitOrShow(not opts.submit_only)
    rcode = compat.all(row[0] for row in results)
    return int(not rcode)
203 204 205
  return realfn


Iustin Pop's avatar
Iustin Pop committed
206
def ListInstances(opts, args):
Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
207
  """List instances and their properties.
Iustin Pop's avatar
Iustin Pop committed
208

209 210 211 212 213 214
  @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
215
  """
216
  selected_fields = ParseFields(opts.output, _LIST_DEF_FIELDS)
Iustin Pop's avatar
Iustin Pop committed
217

218 219
  fmtoverride = dict.fromkeys(["tags", "disk.sizes", "nic.macs", "nic.ips",
                               "nic.modes", "nic.links", "nic.bridges",
220
                               "nic.networks",
221
                               "snodes", "snodes.group", "snodes.group.uuid"],
222 223 224
                              (lambda value: ",".join(str(item)
                                                      for item in value),
                               False))
Iustin Pop's avatar
Iustin Pop committed
225

226 227
  return GenericList(constants.QR_INSTANCE, selected_fields, args, opts.units,
                     opts.separator, not opts.no_headers,
228 229
                     format_override=fmtoverride, verbose=opts.verbose,
                     force_filter=opts.force_filter)
230 231 232 233 234 235 236 237 238 239 240 241 242 243


def ListInstanceFields(opts, args):
  """List instance fields.

  @param opts: the command line options selected by the user
  @type args: list
  @param args: fields to list, or empty for all
  @rtype: int
  @return: the desired exit code

  """
  return GenericListFields(constants.QR_INSTANCE, args, opts.separator,
                           not opts.no_headers)
Iustin Pop's avatar
Iustin Pop committed
244 245 246 247 248


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

Iustin Pop's avatar
Iustin Pop committed
249
  This is just a wrapper over GenericInstanceCreate.
Iustin Pop's avatar
Iustin Pop committed
250 251

  """
Iustin Pop's avatar
Iustin Pop committed
252
  return GenericInstanceCreate(constants.INSTANCE_CREATE, opts, args)
Iustin Pop's avatar
Iustin Pop committed
253 254


255
def BatchCreate(opts, args):
256 257
  """Create instances using a definition file.

258 259
  This function reads a json file with L{opcodes.OpInstanceCreate}
  serialisations.
260 261 262 263 264 265

  @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
266 267

  """
268 269
  (json_filename,) = args
  cl = GetClient()
270 271

  try:
272
    instance_data = simplejson.loads(utils.ReadFile(json_filename))
273
  except Exception, err: # pylint: disable=W0703
Iustin Pop's avatar
Iustin Pop committed
274 275
    ToStderr("Can't parse the instance definition file: %s" % str(err))
    return 1
276

277 278
  if not _INST_DATA_VAL(instance_data):
    ToStderr("The instance definition file is not %s" % _INST_DATA_VAL)
Guido Trotter's avatar
Guido Trotter committed
279 280
    return 1

281 282 283 284
  instances = []
  possible_params = set(opcodes.OpInstanceCreate.GetAllSlots())
  for (idx, inst) in enumerate(instance_data):
    unknown = set(inst.keys()) - possible_params
285

286 287
    if unknown:
      # TODO: Suggest closest match for more user friendly experience
René Nussbaumer's avatar
René Nussbaumer committed
288 289 290
      raise errors.OpPrereqError("Unknown fields in definition %s: %s" %
                                 (idx, utils.CommaJoin(unknown)),
                                 errors.ECODE_INVAL)
291

René Nussbaumer's avatar
René Nussbaumer committed
292
    op = opcodes.OpInstanceCreate(**inst) # pylint: disable=W0142
293 294
    op.Validate(False)
    instances.append(op)
295

296 297 298
  op = opcodes.OpInstanceMultiAlloc(iallocator=opts.iallocator,
                                    instances=instances)
  result = SubmitOrSend(op, opts, cl=cl)
299

300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315
  # Keep track of submitted jobs
  jex = JobExecutor(cl=cl, opts=opts)

  for (status, job_id) in result[constants.JOB_IDS_KEY]:
    jex.AddJobId(None, status, job_id)

  results = jex.GetResults()
  bad_cnt = len([row for row in results if not row[0]])
  if bad_cnt == 0:
    ToStdout("All instances created successfully.")
    rcode = constants.EXIT_SUCCESS
  else:
    ToStdout("There were %s errors during the creation.", bad_cnt)
    rcode = constants.EXIT_FAILURE

  return rcode
316 317


318 319 320
def ReinstallInstance(opts, args):
  """Reinstall an instance.

321 322 323 324 325 326
  @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
327 328

  """
329 330
  # first, compute the desired name list
  if opts.multi_mode is None:
331
    opts.multi_mode = _EXPAND_INSTANCES
332 333 334

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

338
  # second, if requested, ask for an OS
339
  if opts.select_os is True:
340
    op = opcodes.OpOsDiagnose(output_fields=["name", "variants"], names=[])
341
    result = SubmitOpCode(op, opts=opts)
342 343

    if not result:
344
      ToStdout("Can't get the OS list")
345 346
      return 1

347
    ToStdout("Available OS templates:")
348 349
    number = 0
    choices = []
350 351 352 353 354
    for (name, variants) in result:
      for entry in CalculateOSNames(name, variants):
        ToStdout("%3s: %s", number, entry)
        choices.append(("%s" % number, entry, entry))
        number += 1
355

Iustin Pop's avatar
Iustin Pop committed
356
    choices.append(("x", "exit", "Exit gnt-instance reinstall"))
357
    selected = AskUser("Enter OS template number (or x to abort):",
358 359
                       choices)

Iustin Pop's avatar
Iustin Pop committed
360
    if selected == "exit":
361
      ToStderr("User aborted reinstall, exiting")
362 363
      return 1

364
    os_name = selected
365
    os_msg = "change the OS to '%s'" % selected
366
  else:
367
    os_name = opts.os
368 369 370 371
    if opts.os is not None:
      os_msg = "change the OS to '%s'" % os_name
    else:
      os_msg = "keep the same OS"
372

373 374 375
  # third, get confirmation: multi-reinstall requires --force-multi,
  # single-reinstall either --force or --force-multi (--force-multi is
  # a stronger --force)
376
  multi_on = opts.multi_mode != _EXPAND_INSTANCES or len(inames) > 1
377
  if multi_on:
378 379
    warn_msg = ("Note: this will remove *all* data for the"
                " below instances! It will %s.\n" % os_msg)
380
    if not (opts.force_multi or
381
            ConfirmOperation(inames, "instances", "reinstall", extra=warn_msg)):
382
      return 1
383
  else:
384
    if not (opts.force or opts.force_multi):
385 386
      usertext = ("This will reinstall the instance '%s' (and %s) which"
                  " removes all data. Continue?") % (inames[0], os_msg)
387 388 389
      if not AskUser(usertext):
        return 1

390
  jex = JobExecutor(verbose=multi_on, opts=opts)
391
  for instance_name in inames:
392
    op = opcodes.OpInstanceReinstall(instance_name=instance_name,
393
                                     os_type=os_name,
394 395
                                     force_variant=opts.force_variant,
                                     osparams=opts.osparams)
396
    jex.QueueJob(instance_name, op)
397

398 399 400 401 402 403
  results = jex.WaitOrShow(not opts.submit_only)

  if compat.all(map(compat.fst, results)):
    return constants.EXIT_SUCCESS
  else:
    return constants.EXIT_FAILURE
404 405


Iustin Pop's avatar
Iustin Pop committed
406 407 408
def RemoveInstance(opts, args):
  """Remove an instance.

409 410 411 412 413 414
  @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
415 416 417 418

  """
  instance_name = args[0]
  force = opts.force
419
  cl = GetClient()
Iustin Pop's avatar
Iustin Pop committed
420 421

  if not force:
422 423
    _EnsureInstancesExist(cl, [instance_name])

Iustin Pop's avatar
Iustin Pop committed
424 425 426
    usertext = ("This will remove the volumes of the instance %s"
                " (including mirrors), thus removing all the data"
                " of the instance. Continue?") % instance_name
427
    if not AskUser(usertext):
Iustin Pop's avatar
Iustin Pop committed
428 429
      return 1

430
  op = opcodes.OpInstanceRemove(instance_name=instance_name,
431
                                ignore_failures=opts.ignore_failures,
432
                                shutdown_timeout=opts.shutdown_timeout)
433
  SubmitOrSend(op, opts, cl=cl)
Iustin Pop's avatar
Iustin Pop committed
434 435 436
  return 0


437
def RenameInstance(opts, args):
Guido Trotter's avatar
Guido Trotter committed
438
  """Rename an instance.
439

440 441 442 443 444 445
  @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
446 447

  """
448
  if not opts.name_check:
449 450 451 452
    if not AskUser("As you disabled the check of the DNS entry, please verify"
                   " that '%s' is a FQDN. Continue?" % args[1]):
      return 1

453
  op = opcodes.OpInstanceRename(instance_name=args[0],
454
                                new_name=args[1],
455 456
                                ip_check=opts.ip_check,
                                name_check=opts.name_check)
457 458
  result = SubmitOrSend(op, opts)

459 460
  if result:
    ToStdout("Instance '%s' renamed to '%s'", args[0], result)
461

462 463 464
  return 0


Iustin Pop's avatar
Iustin Pop committed
465 466 467 468
def ActivateDisks(opts, args):
  """Activate an instance's disks.

  This serves two purposes:
469 470
    - 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
471 472
    - it repairs inactive secondary drbds

473 474 475 476 477 478
  @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
479 480
  """
  instance_name = args[0]
481
  op = opcodes.OpInstanceActivateDisks(instance_name=instance_name,
482 483
                                       ignore_size=opts.ignore_size,
                                       wait_for_sync=opts.wait_for_sync)
484
  disks_info = SubmitOrSend(op, opts)
Iustin Pop's avatar
Iustin Pop committed
485
  for host, iname, nname in disks_info:
486
    ToStdout("%s:%s:%s", host, iname, nname)
Iustin Pop's avatar
Iustin Pop committed
487 488 489 490
  return 0


def DeactivateDisks(opts, args):
Iustin Pop's avatar
Iustin Pop committed
491
  """Deactivate an instance's disks.
Iustin Pop's avatar
Iustin Pop committed
492 493 494 495

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

496 497 498 499 500 501
  @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
502 503
  """
  instance_name = args[0]
504 505
  op = opcodes.OpInstanceDeactivateDisks(instance_name=instance_name,
                                         force=opts.force)
506
  SubmitOrSend(op, opts)
Iustin Pop's avatar
Iustin Pop committed
507 508 509
  return 0


Iustin Pop's avatar
Iustin Pop committed
510 511 512 513 514 515 516 517 518 519 520
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]
521 522 523

  disks = []

Iustin Pop's avatar
Iustin Pop committed
524
  if opts.disks:
525 526 527 528 529
    for didx, ddict in opts.disks:
      didx = int(didx)

      if not ht.TDict(ddict):
        msg = "Invalid disk/%d value: expected dict, got %s" % (didx, ddict)
530
        raise errors.OpPrereqError(msg, errors.ECODE_INVAL)
531 532 533 534 535 536 537

      if constants.IDISK_SIZE in ddict:
        try:
          ddict[constants.IDISK_SIZE] = \
            utils.ParseUnit(ddict[constants.IDISK_SIZE])
        except ValueError, err:
          raise errors.OpPrereqError("Invalid disk size for disk %d: %s" %
538
                                     (didx, err), errors.ECODE_INVAL)
539 540 541 542 543

      disks.append((didx, ddict))

    # TODO: Verify modifyable parameters (already done in
    # LUInstanceRecreateDisks, but it'd be nice to have in the client)
Iustin Pop's avatar
Iustin Pop committed
544

545
  if opts.node:
546 547 548
    if opts.iallocator:
      msg = "At most one of either --nodes or --iallocator can be passed"
      raise errors.OpPrereqError(msg, errors.ECODE_INVAL)
549 550 551 552 553 554 555
    pnode, snode = SplitNodeOption(opts.node)
    nodes = [pnode]
    if snode is not None:
      nodes.append(snode)
  else:
    nodes = []

556
  op = opcodes.OpInstanceRecreateDisks(instance_name=instance_name,
557 558
                                       disks=disks, nodes=nodes,
                                       iallocator=opts.iallocator)
Iustin Pop's avatar
Iustin Pop committed
559
  SubmitOrSend(op, opts)
560

Iustin Pop's avatar
Iustin Pop committed
561 562 563
  return 0


Iustin Pop's avatar
Iustin Pop committed
564
def GrowDisk(opts, args):
565
  """Grow an instance's disks.
Iustin Pop's avatar
Iustin Pop committed
566

567 568
  @param opts: the command line options selected by the user
  @type args: list
Guido Trotter's avatar
Guido Trotter committed
569 570
  @param args: should contain three elements, the target instance name,
      the target disk id, and the target growth
571 572
  @rtype: int
  @return: the desired exit code
Iustin Pop's avatar
Iustin Pop committed
573 574 575 576

  """
  instance = args[0]
  disk = args[1]
577 578
  try:
    disk = int(disk)
579
  except (TypeError, ValueError), err:
580 581
    raise errors.OpPrereqError("Invalid disk index: %s" % str(err),
                               errors.ECODE_INVAL)
582 583 584 585 586
  try:
    amount = utils.ParseUnit(args[2])
  except errors.UnitParseError:
    raise errors.OpPrereqError("Can't parse the given amount '%s'" % args[2],
                               errors.ECODE_INVAL)
Iustin Pop's avatar
Iustin Pop committed
587 588
  op = opcodes.OpInstanceGrowDisk(instance_name=instance,
                                  disk=disk, amount=amount,
589 590
                                  wait_for_sync=opts.wait_for_sync,
                                  absolute=opts.absolute)
591
  SubmitOrSend(op, opts)
Iustin Pop's avatar
Iustin Pop committed
592 593 594
  return 0


595
def _StartupInstance(name, opts):
596
  """Startup instances.
Iustin Pop's avatar
Iustin Pop committed
597

598 599
  This returns the opcode to start an instance, and its decorator will
  wrap this into a loop starting all desired instances.
600

601
  @param name: the name of the instance to act on
602
  @param opts: the command line options selected by the user
603
  @return: the opcode needed for the operation
Iustin Pop's avatar
Iustin Pop committed
604 605

  """
606
  op = opcodes.OpInstanceStartup(instance_name=name,
607
                                 force=opts.force,
608
                                 ignore_offline_nodes=opts.ignore_offline,
609 610
                                 no_remember=opts.no_remember,
                                 startup_paused=opts.startup_paused)
611 612 613 614 615 616
  # 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
617

618

619
def _RebootInstance(name, opts):
620 621
  """Reboot instance(s).

622 623
  This returns the opcode to reboot an instance, and its decorator
  will wrap this into a loop rebooting all desired instances.
624

625
  @param name: the name of the instance to act on
626
  @param opts: the command line options selected by the user
627
  @return: the opcode needed for the operation
628 629

  """
630
  return opcodes.OpInstanceReboot(instance_name=name,
631
                                  reboot_type=opts.reboot_type,
632
                                  ignore_secondaries=opts.ignore_secondaries,
633 634 635
                                  shutdown_timeout=opts.shutdown_timeout,
                                  reason=(constants.INSTANCE_REASON_SOURCE_CLI,
                                          opts.reason))
Iustin Pop's avatar
Iustin Pop committed
636

637

638
def _ShutdownInstance(name, opts):
Iustin Pop's avatar
Iustin Pop committed
639 640
  """Shutdown an instance.

641 642 643 644
  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
645
  @param opts: the command line options selected by the user
646
  @return: the opcode needed for the operation
Iustin Pop's avatar
Iustin Pop committed
647 648

  """
649
  return opcodes.OpInstanceShutdown(instance_name=name,
650
                                    force=opts.force,
651
                                    timeout=opts.timeout,
652 653
                                    ignore_offline_nodes=opts.ignore_offline,
                                    no_remember=opts.no_remember)
Iustin Pop's avatar
Iustin Pop committed
654 655 656 657 658


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

659 660 661 662 663
  @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
664 665

  """
666
  new_2ndary = opts.dst_node
667
  iallocator = opts.iallocator
668
  if opts.disks is None:
669
    disks = []
670
  else:
671 672
    try:
      disks = [int(i) for i in opts.disks.split(",")]
673
    except (TypeError, ValueError), err:
674 675
      raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err),
                                 errors.ECODE_INVAL)
676
  cnt = [opts.on_primary, opts.on_secondary, opts.auto,
677 678
         new_2ndary is not None, iallocator is not None].count(True)
  if cnt != 1:
679
    raise errors.OpPrereqError("One and only one of the -p, -s, -a, -n and -I"
680
                               " options must be passed", errors.ECODE_INVAL)
681
  elif opts.on_primary:
682
    mode = constants.REPLACE_DISK_PRI
683
  elif opts.on_secondary:
684
    mode = constants.REPLACE_DISK_SEC
685 686 687 688
  elif opts.auto:
    mode = constants.REPLACE_DISK_AUTO
    if disks:
      raise errors.OpPrereqError("Cannot specify disks when using automatic"
689
                                 " mode", errors.ECODE_INVAL)
690 691 692
  elif new_2ndary is not None or iallocator is not None:
    # replace secondary
    mode = constants.REPLACE_DISK_CHG
693

694 695 696
  op = opcodes.OpInstanceReplaceDisks(instance_name=args[0], disks=disks,
                                      remote_node=new_2ndary, mode=mode,
                                      iallocator=iallocator,
697 698
                                      early_release=opts.early_release,
                                      ignore_ipolicy=opts.ignore_ipolicy)
699
  SubmitOrSend(op, opts)
Iustin Pop's avatar
Iustin Pop committed
700 701 702 703 704 705 706 707 708
  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.

709 710 711 712 713
  @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
714 715

  """
716
  cl = GetClient()
717 718
  instance_name = args[0]
  force = opts.force
719 720 721 722 723 724
  iallocator = opts.iallocator
  target_node = opts.dst_node

  if iallocator and target_node:
    raise errors.OpPrereqError("Specify either an iallocator (-I), or a target"
                               " node (-n) but not both", errors.ECODE_INVAL)
Iustin Pop's avatar
Iustin Pop committed
725

726
  if not force:
727 728
    _EnsureInstancesExist(cl, [instance_name])

729 730 731 732 733
    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
734

735
  op = opcodes.OpInstanceFailover(instance_name=instance_name,
736
                                  ignore_consistency=opts.ignore_consistency,
737 738
                                  shutdown_timeout=opts.shutdown_timeout,
                                  iallocator=iallocator,
739 740
                                  target_node=target_node,
                                  ignore_ipolicy=opts.ignore_ipolicy)
741
  SubmitOrSend(op, opts, cl=cl)
742
  return 0
Iustin Pop's avatar
Iustin Pop committed
743 744


745 746 747 748 749
def MigrateInstance(opts, args):
  """Migrate an instance.

  The migrate is done without shutdown.

Iustin Pop's avatar
Iustin Pop committed
750 751 752 753 754
  @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
755 756

  """
757
  cl = GetClient()
758 759
  instance_name = args[0]
  force = opts.force
760 761 762 763 764 765
  iallocator = opts.iallocator
  target_node = opts.dst_node

  if iallocator and target_node:
    raise errors.OpPrereqError("Specify either an iallocator (-I), or a target"
                               " node (-n) but not both", errors.ECODE_INVAL)
766 767

  if not force:
768 769
    _EnsureInstancesExist(cl, [instance_name])

770 771 772 773 774 775 776
    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
777 778
    usertext += (" might impact the instance if anything goes wrong"
                 " (e.g. due to bugs in the hypervisor). Continue?")
779 780 781
    if not AskUser(usertext):
      return 1

782
  # this should be removed once --non-live is deprecated
783
  if not opts.live and opts.migration_mode is not None:
784
    raise errors.OpPrereqError("Only one of the --non-live and "
785
                               "--migration-mode options can be passed",
786 787
                               errors.ECODE_INVAL)
  if not opts.live: # --non-live passed
788
    mode = constants.HT_MIGRATION_NONLIVE
789
  else:
790
    mode = opts.migration_mode
791

792
  op = opcodes.OpInstanceMigrate(instance_name=instance_name, mode=mode,
793
                                 cleanup=opts.cleanup, iallocator=iallocator,
794
                                 target_node=target_node,
795
                                 allow_failover=opts.allow_failover,
796
                                 allow_runtime_changes=opts.allow_runtime_chgs,
797
                                 ignore_ipolicy=opts.ignore_ipolicy)
798
  SubmitOrSend(op, cl=cl, opts=opts)
799 800 801
  return 0


Iustin Pop's avatar
Iustin Pop committed
802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822
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

823
  op = opcodes.OpInstanceMove(instance_name=instance_name,
824
                              target_node=opts.node,
Iustin Pop's avatar
Iustin Pop committed
825
                              shutdown_timeout=opts.shutdown_timeout,
826 827
                              ignore_consistency=opts.ignore_consistency,
                              ignore_ipolicy=opts.ignore_ipolicy)
Iustin Pop's avatar
Iustin Pop committed
828 829 830 831
  SubmitOrSend(op, opts, cl=cl)
  return 0


Iustin Pop's avatar
Iustin Pop committed
832 833 834
def ConnectToInstanceConsole(opts, args):
  """Connect to the console of an instance.

835 836 837 838 839
  @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
840 841 842 843

  """
  instance_name = args[0]

844 845 846
  cl = GetClient()
  try:
    cluster_name = cl.QueryConfigValues(["cluster_name"])[0]
847 848
    ((console_data, oper_state), ) = \
      cl.QueryInstances([instance_name], ["console", "oper_state"], False)
849 850 851 852 853 854
  finally:
    # Ensure client connection is closed while external commands are run
    cl.Close()

  del cl