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

Iustin Pop's avatar
Iustin Pop committed
4
# Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011 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 itertools
29
import simplejson
30
import logging
Iustin Pop's avatar
Iustin Pop committed
31
32
33
34
35
from cStringIO import StringIO

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
43


44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
_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"

_EXPAND_NODES_TAGS_MODES = frozenset([
  _EXPAND_NODES_BOTH_BY_TAGS,
  _EXPAND_NODES_PRI_BY_TAGS,
  _EXPAND_NODES_SEC_BY_TAGS,
  ])
59

60

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

66

67
68
69
_ENV_OVERRIDE = frozenset(["list"])


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

73
74
75
  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
76
  instances having those nodes as either primary or secondary will be
77
  returned. For _EXPAND_INSTANCES, the given instances will be
78
79
  returned.

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

92
  """
93
  # pylint: disable=W0142
94

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

104
105
106
  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:
107
108
109
110
111
112
113
114
115
      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"],
116
                              False)
117

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

  return inames
Iustin Pop's avatar
Iustin Pop committed
146
147


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

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


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


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

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

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

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


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
241
242
243
244
245


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

Iustin Pop's avatar
Iustin Pop committed
246
  This is just a wrapper over GenericInstanceCreate.
Iustin Pop's avatar
Iustin Pop committed
247
248

  """
Iustin Pop's avatar
Iustin Pop committed
249
  return GenericInstanceCreate(constants.INSTANCE_CREATE, opts, args)
Iustin Pop's avatar
Iustin Pop committed
250
251


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

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

    {"instance-name":{
259
      "disk_size": [20480],
260
261
262
263
      "template": "drbd",
      "backend": {
        "memory": 512,
        "vcpus": 1 },
264
      "os": "debootstrap",
265
266
267
268
269
270
271
272
273
274
275
276
277
      "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
278
279

  """
280
  _DEFAULT_SPECS = {"disk_size": [20 * 1024],
281
282
283
284
                    "backend": {},
                    "iallocator": None,
                    "primary_node": None,
                    "secondary_node": None,
285
                    "nics": None,
286
287
                    "start": True,
                    "ip_check": True,
288
                    "name_check": True,
289
                    "hypervisor": None,
Iustin Pop's avatar
Iustin Pop committed
290
                    "hvparams": {},
291
                    "file_storage_dir": None,
292
                    "force_variant": False,
Iustin Pop's avatar
Iustin Pop committed
293
                    "file_driver": "loop"}
294
295
296

  def _PopulateWithDefaults(spec):
    """Returns a new hash combined with default values."""
297
298
299
    mydict = _DEFAULT_SPECS.copy()
    mydict.update(spec)
    return mydict
300
301
302
303

  def _Validate(spec):
    """Validate the instance specs."""
    # Validate fields required under any circumstances
Iustin Pop's avatar
Iustin Pop committed
304
    for required_field in ("os", "template"):
305
306
      if required_field not in spec:
        raise errors.OpPrereqError('Required field "%s" is missing.' %
307
                                   required_field, errors.ECODE_INVAL)
308
    # Validate special fields
Iustin Pop's avatar
Iustin Pop committed
309
310
311
312
313
    if spec["primary_node"] is not None:
      if (spec["template"] in constants.DTS_INT_MIRROR and
          spec["secondary_node"] is None):
        raise errors.OpPrereqError("Template requires secondary node, but"
                                   " there was no secondary provided.",
314
                                   errors.ECODE_INVAL)
Iustin Pop's avatar
Iustin Pop committed
315
316
317
    elif spec["iallocator"] is None:
      raise errors.OpPrereqError("You have to provide at least a primary_node"
                                 " or an iallocator.",
318
                                 errors.ECODE_INVAL)
319

Iustin Pop's avatar
Iustin Pop committed
320
321
322
    if (spec["hvparams"] and
        not isinstance(spec["hvparams"], dict)):
      raise errors.OpPrereqError("Hypervisor parameters must be a dict.",
323
                                 errors.ECODE_INVAL)
324
325
326

  json_filename = args[0]
  try:
327
    instance_data = simplejson.loads(utils.ReadFile(json_filename))
328
  except Exception, err: # pylint: disable=W0703
Iustin Pop's avatar
Iustin Pop committed
329
330
    ToStderr("Can't parse the instance definition file: %s" % str(err))
    return 1
331

Guido Trotter's avatar
Guido Trotter committed
332
333
334
335
  if not isinstance(instance_data, dict):
    ToStderr("The instance definition file is not in dict format.")
    return 1

336
  jex = JobExecutor(opts=opts)
337

338
339
340
  # Iterate over the instances and do:
  #  * Populate the specs with default value
  #  * Validate the instance specs
341
  i_names = utils.NiceSort(instance_data.keys()) # pylint: disable=E1103
342
343
  for name in i_names:
    specs = instance_data[name]
344
345
346
    specs = _PopulateWithDefaults(specs)
    _Validate(specs)

Iustin Pop's avatar
Iustin Pop committed
347
348
    hypervisor = specs["hypervisor"]
    hvparams = specs["hvparams"]
349

350
    disks = []
Iustin Pop's avatar
Iustin Pop committed
351
    for elem in specs["disk_size"]:
352
353
      try:
        size = utils.ParseUnit(elem)
354
      except (TypeError, ValueError), err:
355
356
        raise errors.OpPrereqError("Invalid disk size '%s' for"
                                   " instance %s: %s" %
357
                                   (elem, name, err), errors.ECODE_INVAL)
358
359
      disks.append({"size": size})

Iustin Pop's avatar
Iustin Pop committed
360
    utils.ForceDictType(specs["backend"], constants.BES_PARAMETER_TYPES)
361
362
    utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)

363
    tmp_nics = []
364
    for field in constants.INIC_PARAMS:
365
366
367
368
369
      if field in specs:
        if not tmp_nics:
          tmp_nics.append({})
        tmp_nics[0][field] = specs[field]

Iustin Pop's avatar
Iustin Pop committed
370
    if specs["nics"] is not None and tmp_nics:
371
      raise errors.OpPrereqError("'nics' list incompatible with using"
372
373
                                 " individual nic fields as well",
                                 errors.ECODE_INVAL)
Iustin Pop's avatar
Iustin Pop committed
374
375
    elif specs["nics"] is not None:
      tmp_nics = specs["nics"]
376
377
378
    elif not tmp_nics:
      tmp_nics = [{}]

379
    op = opcodes.OpInstanceCreate(instance_name=name,
380
                                  disks=disks,
Iustin Pop's avatar
Iustin Pop committed
381
                                  disk_template=specs["template"],
382
                                  mode=constants.INSTANCE_CREATE,
Iustin Pop's avatar
Iustin Pop committed
383
                                  os_type=specs["os"],
384
                                  force_variant=specs["force_variant"],
Iustin Pop's avatar
Iustin Pop committed
385
386
                                  pnode=specs["primary_node"],
                                  snode=specs["secondary_node"],
387
                                  nics=tmp_nics,
Iustin Pop's avatar
Iustin Pop committed
388
389
390
                                  start=specs["start"],
                                  ip_check=specs["ip_check"],
                                  name_check=specs["name_check"],
391
                                  wait_for_sync=True,
Iustin Pop's avatar
Iustin Pop committed
392
                                  iallocator=specs["iallocator"],
393
394
                                  hypervisor=hypervisor,
                                  hvparams=hvparams,
Iustin Pop's avatar
Iustin Pop committed
395
396
397
                                  beparams=specs["backend"],
                                  file_storage_dir=specs["file_storage_dir"],
                                  file_driver=specs["file_driver"])
398

399
400
401
    jex.QueueJob(name, op)
  # we never want to wait, just show the submitted job IDs
  jex.WaitOrShow(False)
402
403
404
405

  return 0


406
407
408
def ReinstallInstance(opts, args):
  """Reinstall 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 reinstalled
  @rtype: int
  @return: the desired exit code
415
416

  """
417
418
  # first, compute the desired name list
  if opts.multi_mode is None:
419
    opts.multi_mode = _EXPAND_INSTANCES
420
421
422

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

426
  # second, if requested, ask for an OS
427
  if opts.select_os is True:
428
    op = opcodes.OpOsDiagnose(output_fields=["name", "variants"], names=[])
429
    result = SubmitOpCode(op, opts=opts)
430
431

    if not result:
432
      ToStdout("Can't get the OS list")
433
434
      return 1

435
    ToStdout("Available OS templates:")
436
437
    number = 0
    choices = []
438
439
440
441
442
    for (name, variants) in result:
      for entry in CalculateOSNames(name, variants):
        ToStdout("%3s: %s", number, entry)
        choices.append(("%s" % number, entry, entry))
        number += 1
443

Iustin Pop's avatar
Iustin Pop committed
444
    choices.append(("x", "exit", "Exit gnt-instance reinstall"))
445
    selected = AskUser("Enter OS template number (or x to abort):",
446
447
                       choices)

Iustin Pop's avatar
Iustin Pop committed
448
    if selected == "exit":
449
      ToStderr("User aborted reinstall, exiting")
450
451
      return 1

452
    os_name = selected
453
    os_msg = "change the OS to '%s'" % selected
454
  else:
455
    os_name = opts.os
456
457
458
459
    if opts.os is not None:
      os_msg = "change the OS to '%s'" % os_name
    else:
      os_msg = "keep the same OS"
460

461
462
463
  # third, get confirmation: multi-reinstall requires --force-multi,
  # single-reinstall either --force or --force-multi (--force-multi is
  # a stronger --force)
464
  multi_on = opts.multi_mode != _EXPAND_INSTANCES or len(inames) > 1
465
  if multi_on:
466
467
    warn_msg = ("Note: this will remove *all* data for the"
                " below instances! It will %s.\n" % os_msg)
468
    if not (opts.force_multi or
469
            ConfirmOperation(inames, "instances", "reinstall", extra=warn_msg)):
470
      return 1
471
  else:
472
    if not (opts.force or opts.force_multi):
473
474
      usertext = ("This will reinstall the instance '%s' (and %s) which"
                  " removes all data. Continue?") % (inames[0], os_msg)
475
476
477
      if not AskUser(usertext):
        return 1

478
  jex = JobExecutor(verbose=multi_on, opts=opts)
479
  for instance_name in inames:
480
    op = opcodes.OpInstanceReinstall(instance_name=instance_name,
481
                                     os_type=os_name,
482
483
                                     force_variant=opts.force_variant,
                                     osparams=opts.osparams)
484
    jex.QueueJob(instance_name, op)
485

486
  jex.WaitOrShow(not opts.submit_only)
487
488
489
  return 0


Iustin Pop's avatar
Iustin Pop committed
490
491
492
def RemoveInstance(opts, args):
  """Remove an instance.

493
494
495
496
497
498
  @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
499
500
501
502

  """
  instance_name = args[0]
  force = opts.force
503
  cl = GetClient()
Iustin Pop's avatar
Iustin Pop committed
504
505

  if not force:
506
507
    _EnsureInstancesExist(cl, [instance_name])

Iustin Pop's avatar
Iustin Pop committed
508
509
510
    usertext = ("This will remove the volumes of the instance %s"
                " (including mirrors), thus removing all the data"
                " of the instance. Continue?") % instance_name
511
    if not AskUser(usertext):
Iustin Pop's avatar
Iustin Pop committed
512
513
      return 1

514
  op = opcodes.OpInstanceRemove(instance_name=instance_name,
515
                                ignore_failures=opts.ignore_failures,
516
                                shutdown_timeout=opts.shutdown_timeout)
517
  SubmitOrSend(op, opts, cl=cl)
Iustin Pop's avatar
Iustin Pop committed
518
519
520
  return 0


521
def RenameInstance(opts, args):
Guido Trotter's avatar
Guido Trotter committed
522
  """Rename an instance.
523

524
525
526
527
528
529
  @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
530
531

  """
532
  if not opts.name_check:
533
534
535
536
    if not AskUser("As you disabled the check of the DNS entry, please verify"
                   " that '%s' is a FQDN. Continue?" % args[1]):
      return 1

537
  op = opcodes.OpInstanceRename(instance_name=args[0],
538
                                new_name=args[1],
539
540
                                ip_check=opts.ip_check,
                                name_check=opts.name_check)
541
542
  result = SubmitOrSend(op, opts)

543
544
  if result:
    ToStdout("Instance '%s' renamed to '%s'", args[0], result)
545

546
547
548
  return 0


Iustin Pop's avatar
Iustin Pop committed
549
550
551
552
def ActivateDisks(opts, args):
  """Activate an instance's disks.

  This serves two purposes:
553
554
    - 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
555
556
    - it repairs inactive secondary drbds

557
558
559
560
561
562
  @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
563
564
  """
  instance_name = args[0]
565
  op = opcodes.OpInstanceActivateDisks(instance_name=instance_name,
566
                                       ignore_size=opts.ignore_size)
567
  disks_info = SubmitOrSend(op, opts)
Iustin Pop's avatar
Iustin Pop committed
568
  for host, iname, nname in disks_info:
569
    ToStdout("%s:%s:%s", host, iname, nname)
Iustin Pop's avatar
Iustin Pop committed
570
571
572
573
  return 0


def DeactivateDisks(opts, args):
Iustin Pop's avatar
Iustin Pop committed
574
  """Deactivate an instance's disks.
Iustin Pop's avatar
Iustin Pop committed
575
576
577
578

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

579
580
581
582
583
584
  @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
585
586
  """
  instance_name = args[0]
587
588
  op = opcodes.OpInstanceDeactivateDisks(instance_name=instance_name,
                                         force=opts.force)
589
  SubmitOrSend(op, opts)
Iustin Pop's avatar
Iustin Pop committed
590
591
592
  return 0


Iustin Pop's avatar
Iustin Pop committed
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
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 = []

613
614
615
616
617
618
619
620
  if opts.node:
    pnode, snode = SplitNodeOption(opts.node)
    nodes = [pnode]
    if snode is not None:
      nodes.append(snode)
  else:
    nodes = []

621
  op = opcodes.OpInstanceRecreateDisks(instance_name=instance_name,
622
623
                                       disks=opts.disks,
                                       nodes=nodes)
Iustin Pop's avatar
Iustin Pop committed
624
625
626
627
  SubmitOrSend(op, opts)
  return 0


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

631
632
633
634
635
636
  @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
637
638
639
640

  """
  instance = args[0]
  disk = args[1]
641
642
  try:
    disk = int(disk)
643
  except (TypeError, ValueError), err:
644
645
    raise errors.OpPrereqError("Invalid disk index: %s" % str(err),
                               errors.ECODE_INVAL)
Iustin Pop's avatar
Iustin Pop committed
646
  amount = utils.ParseUnit(args[2])
Iustin Pop's avatar
Iustin Pop committed
647
648
649
  op = opcodes.OpInstanceGrowDisk(instance_name=instance,
                                  disk=disk, amount=amount,
                                  wait_for_sync=opts.wait_for_sync)
650
  SubmitOrSend(op, opts)
Iustin Pop's avatar
Iustin Pop committed
651
652
653
  return 0


654
def _StartupInstance(name, opts):
655
  """Startup instances.
Iustin Pop's avatar
Iustin Pop committed
656

657
658
  This returns the opcode to start an instance, and its decorator will
  wrap this into a loop starting all desired instances.
659

660
  @param name: the name of the instance to act on
661
  @param opts: the command line options selected by the user
662
  @return: the opcode needed for the operation
Iustin Pop's avatar
Iustin Pop committed
663
664

  """
665
  op = opcodes.OpInstanceStartup(instance_name=name,
666
                                 force=opts.force,
667
                                 ignore_offline_nodes=opts.ignore_offline,
668
669
                                 no_remember=opts.no_remember,
                                 startup_paused=opts.startup_paused)
670
671
672
673
674
675
  # 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
676

677

678
def _RebootInstance(name, opts):
679
680
  """Reboot instance(s).

681
682
  This returns the opcode to reboot an instance, and its decorator
  will wrap this into a loop rebooting all desired instances.
683

684
  @param name: the name of the instance to act on
685
  @param opts: the command line options selected by the user
686
  @return: the opcode needed for the operation
687
688

  """
689
  return opcodes.OpInstanceReboot(instance_name=name,
690
                                  reboot_type=opts.reboot_type,
691
                                  ignore_secondaries=opts.ignore_secondaries,
692
                                  shutdown_timeout=opts.shutdown_timeout)
Iustin Pop's avatar
Iustin Pop committed
693

694

695
def _ShutdownInstance(name, opts):
Iustin Pop's avatar
Iustin Pop committed
696
697
  """Shutdown an instance.

698
699
700
701
  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
702
  @param opts: the command line options selected by the user
703
  @return: the opcode needed for the operation
Iustin Pop's avatar
Iustin Pop committed
704
705

  """
706
  return opcodes.OpInstanceShutdown(instance_name=name,
707
                                    timeout=opts.timeout,
708
709
                                    ignore_offline_nodes=opts.ignore_offline,
                                    no_remember=opts.no_remember)
Iustin Pop's avatar
Iustin Pop committed
710
711
712
713
714


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

715
716
717
718
719
  @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
720
721

  """
722
  new_2ndary = opts.dst_node
723
  iallocator = opts.iallocator
724
  if opts.disks is None:
725
    disks = []
726
  else:
727
728
    try:
      disks = [int(i) for i in opts.disks.split(",")]
729
    except (TypeError, ValueError), err:
730
731
      raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err),
                                 errors.ECODE_INVAL)
732
  cnt = [opts.on_primary, opts.on_secondary, opts.auto,
733
734
         new_2ndary is not None, iallocator is not None].count(True)
  if cnt != 1:
735
    raise errors.OpPrereqError("One and only one of the -p, -s, -a, -n and -I"
736
                               " options must be passed", errors.ECODE_INVAL)
737
  elif opts.on_primary:
738
    mode = constants.REPLACE_DISK_PRI
739
  elif opts.on_secondary:
740
    mode = constants.REPLACE_DISK_SEC
741
742
743
744
  elif opts.auto:
    mode = constants.REPLACE_DISK_AUTO
    if disks:
      raise errors.OpPrereqError("Cannot specify disks when using automatic"
745
                                 " mode", errors.ECODE_INVAL)
746
747
748
  elif new_2ndary is not None or iallocator is not None:
    # replace secondary
    mode = constants.REPLACE_DISK_CHG
749

750
751
752
753
  op = opcodes.OpInstanceReplaceDisks(instance_name=args[0], disks=disks,
                                      remote_node=new_2ndary, mode=mode,
                                      iallocator=iallocator,
                                      early_release=opts.early_release)
754
  SubmitOrSend(op, opts)
Iustin Pop's avatar
Iustin Pop committed
755
756
757
758
759
760
761
762
763
  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.

764
765
766
767
768
  @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
769
770

  """
771
  cl = GetClient()
772
773
  instance_name = args[0]
  force = opts.force
774
775
776
777
778
779
  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
780

781
  if not force:
782
783
    _EnsureInstancesExist(cl, [instance_name])

784
785
786
787
788
    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
789

790
  op = opcodes.OpInstanceFailover(instance_name=instance_name,
791
                                  ignore_consistency=opts.ignore_consistency,
792
793
794
                                  shutdown_timeout=opts.shutdown_timeout,
                                  iallocator=iallocator,
                                  target_node=target_node)
795
  SubmitOrSend(op, opts, cl=cl)
796
  return 0
Iustin Pop's avatar
Iustin Pop committed
797
798


799
800
801
802
803
def MigrateInstance(opts, args):
  """Migrate an instance.

  The migrate is done without shutdown.

Iustin Pop's avatar
Iustin Pop committed
804
805
806
807
808
  @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
809
810

  """
811
  cl = GetClient()
812
813
  instance_name = args[0]
  force = opts.force
814
815
816
817
818
819
  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)
820
821

  if not force:
822
823
    _EnsureInstancesExist(cl, [instance_name])

824
825
826
827
828
829
830
    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
831
832
    usertext += (" might impact the instance if anything goes wrong"
                 " (e.g. due to bugs in the hypervisor). Continue?")
833
834
835
    if not AskUser(usertext):
      return 1

836
  # this should be removed once --non-live is deprecated
837
  if not opts.live and opts.migration_mode is not None:
838
    raise errors.OpPrereqError("Only one of the --non-live and "
839
                               "--migration-mode options can be passed",
840
841
                               errors.ECODE_INVAL)
  if not opts.live: # --non-live passed
842
    mode = constants.HT_MIGRATION_NONLIVE
843
  else:
844
    mode = opts.migration_mode
845

846
  op = opcodes.OpInstanceMigrate(instance_name=instance_name, mode=mode,
847
                                 cleanup=opts.cleanup, iallocator=iallocator,
848
849
                                 target_node=target_node,
                                 allow_failover=opts.allow_failover)
850
  SubmitOpCode(op, cl=cl, opts=opts)
851
852
853
  return 0


Iustin Pop's avatar
Iustin Pop committed
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
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

875
  op = opcodes.OpInstanceMove(instance_name=instance_name,
876
                              target_node=opts.node,
Iustin Pop's avatar
Iustin Pop committed
877
878
                              shutdown_timeout=opts.shutdown_timeout,
                              ignore_consistency=opts.ignore_consistency)
Iustin Pop's avatar
Iustin Pop committed
879
880
881
882
  SubmitOrSend(op, opts, cl=cl)
  return 0


Iustin Pop's avatar
Iustin Pop committed
883
884
885
def ConnectToInstanceConsole(opts, args):
  """Connect to the console of an instance.

886
887
888
889
890
  @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
891
892
893
894

  """
  instance_name = args[0]

895
896
897
  cl = GetClient()
  try:
    cluster_name = cl.QueryConfigValues(["cluster_name"])[0]
898
899
    ((console_data, oper_state), ) = \
      cl.QueryInstances([instance_name], ["console", "oper_state"], False)
900
901
902
903
904
905
  finally:
    # Ensure client connection is closed while external commands are run
    cl.Close()

  del cl

906
907
908
909
910
911
912
913
914
  if not console_data:
    if oper_state:
      # Instance is running
      raise errors.OpExecError("Console information for instance %s is"
                               " unavailable" % instance_name)
    else:
      raise errors.OpExecError("Instance %s is not running, can't get console" %
                               instance_name)

915
916
917
918
919
920
  return _DoConsole(objects.InstanceConsole.FromDict(console_data),
                    opts.show_command, cluster_name)


def _DoConsole(console, show_command, cluster_name, feedback_fn=ToStdout,
               _runcmd_fn=utils.RunCmd):
921
  """Acts based on the result of L{opcodes.OpInstanceConsole}.
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939

  @type console: L{objects.InstanceConsole}
  @param console: Console object
  @type show_command: bool
  @param show_command: Whether to just display commands
  @type cluster_name: string
  @param cluster_name: Cluster name as retrieved from master daemon

  """
  assert console.Validate()

  if console.kind == constants.CONS_MESSAGE:
    feedback_fn(console.message)
  elif console.kind == constants.CONS_VNC:
    feedback_fn("Instance %s has VNC listening on %s:%s (display %s),"
                " URL <vnc://%s:%s/>",
                console.instance, console.host, console.port,
                console.display, console.host, console.port)
940
941
942
  elif console.kind == constants.CONS_SPICE:
    feedback_fn("Instance %s has SPICE listening on %s:%s", console.instance,
                console.host, console.port)
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
  elif console.kind == constants.CONS_SSH:
    # Convert to string if not already one
    if isinstance(console.command, basestring):
      cmd = console.command
    else:
      cmd = utils.ShellQuoteArgs(console.command)

    srun = ssh.SshRunner(cluster_name=cluster_name)
    ssh_cmd = srun.BuildCmd(console.host, console.user, cmd,
                            batch=True, quiet=False, tty=True)

    if show_command:
      feedback_fn(utils.ShellQuoteArgs(ssh_cmd))
    else:
      result = _runcmd_fn(ssh_cmd, interactive=True)
      if result.failed:
        logging.error("Console command \"%s\" failed with reason '%s' and"
                      " output %r", result.cmd, result.fail_reason,
                      result.output)
        raise errors.OpExecError("Connection to console of instance %s failed,"
                                 " please check cluster configuration" %
                                 console.instance)
965
  else:
966
    raise errors.GenericError("Unknown console type '%s'" % console.kind)
967
968

  return constants.EXIT_SUCCESS
Iustin Pop's avatar
Iustin Pop committed
969
970


971
def _FormatLogicalID(dev_type, logical_id, roman):
972
973
974
975
976
977
  """Formats the logical_id of a disk.

  """
  if dev_type == constants.LD_DRBD8:
    node_a, node_b, port, minor_a, minor_b, key = logical_id
    data = [
978
979
980
981
982
      ("nodeA", "%s, minor=%s" % (node_a, compat.TryToRoman(minor_a,
                                                            convert=roman))),
      ("nodeB", "%s, minor=%s" % (node_b, compat.TryToRoman(minor_b,
                                                            convert=roman))),
      ("port", compat.TryToRoman(port, convert=roman)),
983
984
985
986
987
988
989
990
991
992
993
      ("auth key", key),
      ]
  elif dev_type == constants.LD_LV:
    vg_name, lv_name = logical_id
    data = ["%s/%s" % (vg_name, lv_name)]
  else:
    data = [str(logical_id)]

  return data


994
def _FormatBlockDevInfo(idx, top_level, dev, roman):
Iustin Pop's avatar
Iustin Pop committed
995
996
  """Show block device information.

997
  This is only used by L{ShowInstanceConfig}, but it's too big