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

4
# Copyright (C) 2006, 2007, 2008, 2009, 2010 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

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

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

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


_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
    for name in inames:
      op = fn(name, opts)
      jex.QueueJob(name, op)
Iustin Pop's avatar
Iustin Pop committed
229
230
231
    results = jex.WaitOrShow(not opts.submit_only)
    rcode = compat.all(row[0] for row in results)
    return int(not rcode)
232
233
234
  return realfn


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

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

Iustin Pop's avatar
Iustin Pop committed
244
  """
245
  selected_fields = ParseFields(opts.output, _LIST_DEF_FIELDS)
Iustin Pop's avatar
Iustin Pop committed
246

247
248
249
250
251
252
  fmtoverride = dict.fromkeys(["tags", "disk.sizes", "nic.macs", "nic.ips",
                               "nic.modes", "nic.links", "nic.bridges",
                               "snodes"],
                              (lambda value: ",".join(str(item)
                                                      for item in value),
                               False))
Iustin Pop's avatar
Iustin Pop committed
253

254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
  return GenericList(constants.QR_INSTANCE, selected_fields, args, opts.units,
                     opts.separator, not opts.no_headers,
                     format_override=fmtoverride)


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
271
272
273
274
275


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

Iustin Pop's avatar
Iustin Pop committed
276
  This is just a wrapper over GenericInstanceCreate.
Iustin Pop's avatar
Iustin Pop committed
277
278

  """
Iustin Pop's avatar
Iustin Pop committed
279
  return GenericInstanceCreate(constants.INSTANCE_CREATE, opts, args)
Iustin Pop's avatar
Iustin Pop committed
280
281


282
def BatchCreate(opts, args):
283
284
285
286
287
288
  """Create instances using a definition file.

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

    {"instance-name":{
289
      "disk_size": [20480],
290
291
292
293
      "template": "drbd",
      "backend": {
        "memory": 512,
        "vcpus": 1 },
294
      "os": "debootstrap",
295
296
297
298
299
300
301
302
303
304
305
306
307
      "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
308
309

  """
310
  _DEFAULT_SPECS = {"disk_size": [20 * 1024],
311
312
313
314
                    "backend": {},
                    "iallocator": None,
                    "primary_node": None,
                    "secondary_node": None,
315
                    "nics": None,
316
317
                    "start": True,
                    "ip_check": True,
318
                    "name_check": True,
319
                    "hypervisor": None,
Iustin Pop's avatar
Iustin Pop committed
320
                    "hvparams": {},
321
                    "file_storage_dir": None,
322
                    "force_variant": False,
323
324
325
326
                    "file_driver": 'loop'}

  def _PopulateWithDefaults(spec):
    """Returns a new hash combined with default values."""
327
328
329
    mydict = _DEFAULT_SPECS.copy()
    mydict.update(spec)
    return mydict
330
331
332
333
334
335
336

  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.' %
337
                                   required_field, errors.ECODE_INVAL)
338
339
340
341
342
    # 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'
343
344
                                   ' there was no secondary provided.',
                                   errors.ECODE_INVAL)
345
346
    elif spec['iallocator'] is None:
      raise errors.OpPrereqError('You have to provide at least a primary_node'
347
348
                                 ' or an iallocator.',
                                 errors.ECODE_INVAL)
349

Iustin Pop's avatar
Iustin Pop committed
350
351
    if (spec['hvparams'] and
        not isinstance(spec['hvparams'], dict)):
352
353
      raise errors.OpPrereqError('Hypervisor parameters must be a dict.',
                                 errors.ECODE_INVAL)
354
355
356

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

Guido Trotter's avatar
Guido Trotter committed
362
363
364
365
  if not isinstance(instance_data, dict):
    ToStderr("The instance definition file is not in dict format.")
    return 1

366
  jex = JobExecutor(opts=opts)
367

368
369
370
  # Iterate over the instances and do:
  #  * Populate the specs with default value
  #  * Validate the instance specs
Guido Trotter's avatar
Guido Trotter committed
371
  i_names = utils.NiceSort(instance_data.keys()) # pylint: disable-msg=E1103
372
373
  for name in i_names:
    specs = instance_data[name]
374
375
376
    specs = _PopulateWithDefaults(specs)
    _Validate(specs)

Iustin Pop's avatar
Iustin Pop committed
377
378
    hypervisor = specs['hypervisor']
    hvparams = specs['hvparams']
379

380
381
382
383
    disks = []
    for elem in specs['disk_size']:
      try:
        size = utils.ParseUnit(elem)
384
      except (TypeError, ValueError), err:
385
386
        raise errors.OpPrereqError("Invalid disk size '%s' for"
                                   " instance %s: %s" %
387
                                   (elem, name, err), errors.ECODE_INVAL)
388
389
      disks.append({"size": size})

390
391
392
    utils.ForceDictType(specs['backend'], constants.BES_PARAMETER_TYPES)
    utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)

393
394
395
396
397
398
399
400
401
    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"
402
403
                                 " individual nic fields as well",
                                 errors.ECODE_INVAL)
404
405
406
407
408
    elif specs['nics'] is not None:
      tmp_nics = specs['nics']
    elif not tmp_nics:
      tmp_nics = [{}]

409
    op = opcodes.OpCreateInstance(instance_name=name,
410
                                  disks=disks,
411
412
413
                                  disk_template=specs['template'],
                                  mode=constants.INSTANCE_CREATE,
                                  os_type=specs['os'],
414
                                  force_variant=specs["force_variant"],
415
416
                                  pnode=specs['primary_node'],
                                  snode=specs['secondary_node'],
417
                                  nics=tmp_nics,
418
419
                                  start=specs['start'],
                                  ip_check=specs['ip_check'],
420
                                  name_check=specs['name_check'],
421
422
423
424
425
426
427
428
                                  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'])

429
430
431
    jex.QueueJob(name, op)
  # we never want to wait, just show the submitted job IDs
  jex.WaitOrShow(False)
432
433
434
435

  return 0


436
437
438
def ReinstallInstance(opts, args):
  """Reinstall an instance.

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

  """
447
448
449
450
451
452
  # 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:
453
454
    raise errors.OpPrereqError("Selection filter does not match any instances",
                               errors.ECODE_INVAL)
455

456
  # second, if requested, ask for an OS
457
  if opts.select_os is True:
458
    op = opcodes.OpDiagnoseOS(output_fields=["name", "variants"], names=[])
459
    result = SubmitOpCode(op, opts=opts)
460
461

    if not result:
462
      ToStdout("Can't get the OS list")
463
464
      return 1

465
    ToStdout("Available OS templates:")
466
467
    number = 0
    choices = []
468
469
470
471
472
    for (name, variants) in result:
      for entry in CalculateOSNames(name, variants):
        ToStdout("%3s: %s", number, entry)
        choices.append(("%s" % number, entry, entry))
        number += 1
473
474

    choices.append(('x', 'exit', 'Exit gnt-instance reinstall'))
475
    selected = AskUser("Enter OS template number (or x to abort):",
476
477
478
                       choices)

    if selected == 'exit':
479
      ToStderr("User aborted reinstall, exiting")
480
481
      return 1

482
    os_name = selected
483
  else:
484
    os_name = opts.os
485

486
487
488
  # third, get confirmation: multi-reinstall requires --force-multi,
  # single-reinstall either --force or --force-multi (--force-multi is
  # a stronger --force)
489
490
491
  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"
492
    if not (opts.force_multi or
493
            _ConfirmOperation(inames, "reinstall", extra=warn_msg)):
494
      return 1
495
  else:
496
    if not (opts.force or opts.force_multi):
497
      usertext = ("This will reinstall the instance %s and remove"
Iustin Pop's avatar
Iustin Pop committed
498
                  " all data. Continue?") % inames[0]
499
500
501
      if not AskUser(usertext):
        return 1

502
  jex = JobExecutor(verbose=multi_on, opts=opts)
503
504
  for instance_name in inames:
    op = opcodes.OpReinstallInstance(instance_name=instance_name,
505
                                     os_type=os_name,
506
507
                                     force_variant=opts.force_variant,
                                     osparams=opts.osparams)
508
    jex.QueueJob(instance_name, op)
509

510
  jex.WaitOrShow(not opts.submit_only)
511
512
513
  return 0


Iustin Pop's avatar
Iustin Pop committed
514
515
516
def RemoveInstance(opts, args):
  """Remove an instance.

517
518
519
520
521
522
  @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
523
524
525
526

  """
  instance_name = args[0]
  force = opts.force
527
  cl = GetClient()
Iustin Pop's avatar
Iustin Pop committed
528
529

  if not force:
530
531
    _EnsureInstancesExist(cl, [instance_name])

Iustin Pop's avatar
Iustin Pop committed
532
533
534
    usertext = ("This will remove the volumes of the instance %s"
                " (including mirrors), thus removing all the data"
                " of the instance. Continue?") % instance_name
535
    if not AskUser(usertext):
Iustin Pop's avatar
Iustin Pop committed
536
537
      return 1

Iustin Pop's avatar
Iustin Pop committed
538
  op = opcodes.OpRemoveInstance(instance_name=instance_name,
539
                                ignore_failures=opts.ignore_failures,
540
                                shutdown_timeout=opts.shutdown_timeout)
541
  SubmitOrSend(op, opts, cl=cl)
Iustin Pop's avatar
Iustin Pop committed
542
543
544
  return 0


545
def RenameInstance(opts, args):
Guido Trotter's avatar
Guido Trotter committed
546
  """Rename an instance.
547

548
549
550
551
552
553
  @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
554
555

  """
556
  if not opts.name_check:
557
558
559
560
    if not AskUser("As you disabled the check of the DNS entry, please verify"
                   " that '%s' is a FQDN. Continue?" % args[1]):
      return 1

561
562
  op = opcodes.OpRenameInstance(instance_name=args[0],
                                new_name=args[1],
563
564
                                ip_check=opts.ip_check,
                                name_check=opts.name_check)
565
566
  result = SubmitOrSend(op, opts)

567
568
  if result:
    ToStdout("Instance '%s' renamed to '%s'", args[0], result)
569

570
571
572
  return 0


Iustin Pop's avatar
Iustin Pop committed
573
574
575
576
def ActivateDisks(opts, args):
  """Activate an instance's disks.

  This serves two purposes:
577
578
    - 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
579
580
    - it repairs inactive secondary drbds

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 instance name
  @rtype: int
  @return: the desired exit code

Iustin Pop's avatar
Iustin Pop committed
587
588
  """
  instance_name = args[0]
589
590
  op = opcodes.OpActivateInstanceDisks(instance_name=instance_name,
                                       ignore_size=opts.ignore_size)
591
  disks_info = SubmitOrSend(op, opts)
Iustin Pop's avatar
Iustin Pop committed
592
  for host, iname, nname in disks_info:
593
    ToStdout("%s:%s:%s", host, iname, nname)
Iustin Pop's avatar
Iustin Pop committed
594
595
596
597
  return 0


def DeactivateDisks(opts, args):
Iustin Pop's avatar
Iustin Pop committed
598
  """Deactivate an instance's disks.
Iustin Pop's avatar
Iustin Pop committed
599
600
601
602

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

603
604
605
606
607
608
  @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
609
610
611
  """
  instance_name = args[0]
  op = opcodes.OpDeactivateInstanceDisks(instance_name=instance_name)
612
  SubmitOrSend(op, opts)
Iustin Pop's avatar
Iustin Pop committed
613
614
615
  return 0


Iustin Pop's avatar
Iustin Pop committed
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
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
642
def GrowDisk(opts, args):
643
  """Grow an instance's disks.
Iustin Pop's avatar
Iustin Pop committed
644

645
646
647
648
649
650
  @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
651
652
653
654

  """
  instance = args[0]
  disk = args[1]
655
656
  try:
    disk = int(disk)
657
  except (TypeError, ValueError), err:
658
659
    raise errors.OpPrereqError("Invalid disk index: %s" % str(err),
                               errors.ECODE_INVAL)
Iustin Pop's avatar
Iustin Pop committed
660
  amount = utils.ParseUnit(args[2])
661
662
  op = opcodes.OpGrowDisk(instance_name=instance, disk=disk, amount=amount,
                          wait_for_sync=opts.wait_for_sync)
663
  SubmitOrSend(op, opts)
Iustin Pop's avatar
Iustin Pop committed
664
665
666
  return 0


667
def _StartupInstance(name, opts):
668
  """Startup instances.
Iustin Pop's avatar
Iustin Pop committed
669

670
671
  This returns the opcode to start an instance, and its decorator will
  wrap this into a loop starting all desired instances.
672

673
  @param name: the name of the instance to act on
674
  @param opts: the command line options selected by the user
675
  @return: the opcode needed for the operation
Iustin Pop's avatar
Iustin Pop committed
676
677

  """
678
  op = opcodes.OpStartupInstance(instance_name=name,
679
680
                                 force=opts.force,
                                 ignore_offline_nodes=opts.ignore_offline)
681
682
683
684
685
686
  # 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
687

688

689
def _RebootInstance(name, opts):
690
691
  """Reboot instance(s).

692
693
  This returns the opcode to reboot an instance, and its decorator
  will wrap this into a loop rebooting all desired instances.
694

695
  @param name: the name of the instance to act on
696
  @param opts: the command line options selected by the user
697
  @return: the opcode needed for the operation
698
699

  """
700
  return opcodes.OpRebootInstance(instance_name=name,
701
                                  reboot_type=opts.reboot_type,
702
                                  ignore_secondaries=opts.ignore_secondaries,
703
                                  shutdown_timeout=opts.shutdown_timeout)
Iustin Pop's avatar
Iustin Pop committed
704

705

706
def _ShutdownInstance(name, opts):
Iustin Pop's avatar
Iustin Pop committed
707
708
  """Shutdown an instance.

709
710
711
712
  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
713
  @param opts: the command line options selected by the user
714
  @return: the opcode needed for the operation
Iustin Pop's avatar
Iustin Pop committed
715
716

  """
717
  return opcodes.OpShutdownInstance(instance_name=name,
718
719
                                    timeout=opts.timeout,
                                    ignore_offline_nodes=opts.ignore_offline)
Iustin Pop's avatar
Iustin Pop committed
720
721
722
723
724


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

725
726
727
728
729
  @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
730
731

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

  op = opcodes.OpReplaceDisks(instance_name=args[0], disks=disks,
761
                              remote_node=new_2ndary, mode=mode,
762
763
                              iallocator=iallocator,
                              early_release=opts.early_release)
764
  SubmitOrSend(op, opts)
Iustin Pop's avatar
Iustin Pop committed
765
766
767
768
769
770
771
772
773
  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.

774
775
776
777
778
  @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
779
780

  """
781
  cl = GetClient()
782
783
  instance_name = args[0]
  force = opts.force
Iustin Pop's avatar
Iustin Pop committed
784

785
  if not force:
786
787
    _EnsureInstancesExist(cl, [instance_name])

788
789
790
791
792
    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
793

794
  op = opcodes.OpFailoverInstance(instance_name=instance_name,
795
                                  ignore_consistency=opts.ignore_consistency,
796
                                  shutdown_timeout=opts.shutdown_timeout)
797
  SubmitOrSend(op, opts, cl=cl)
798
  return 0
Iustin Pop's avatar
Iustin Pop committed
799
800


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

  The migrate is done without shutdown.

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

  """
813
  cl = GetClient()
814
815
816
817
  instance_name = args[0]
  force = opts.force

  if not force:
818
819
    _EnsureInstancesExist(cl, [instance_name])

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

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

842
  op = opcodes.OpMigrateInstance(instance_name=instance_name, mode=mode,
843
                                 cleanup=opts.cleanup)
844
  SubmitOpCode(op, cl=cl, opts=opts)
845
846
847
  return 0


Iustin Pop's avatar
Iustin Pop committed
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
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,
870
                              target_node=opts.node,
871
                              shutdown_timeout=opts.shutdown_timeout)
Iustin Pop's avatar
Iustin Pop committed
872
873
874
875
  SubmitOrSend(op, opts, cl=cl)
  return 0


Iustin Pop's avatar
Iustin Pop committed
876
877
878
def ConnectToInstanceConsole(opts, args):
  """Connect to the console of an instance.

879
880
881
882
883
  @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
884
885
886
887
888

  """
  instance_name = args[0]

  op = opcodes.OpConnectConsole(instance_name=instance_name)
889
  cmd = SubmitOpCode(op, opts=opts)
890
891

  if opts.show_command:
892
    ToStdout("%s", utils.ShellQuoteArgs(cmd))
893
  else:
894
895
896
897
898
899
    result = utils.RunCmd(cmd, interactive=True)
    if result.failed:
      raise errors.OpExecError("Console command \"%s\" failed: %s" %
                               (utils.ShellQuoteArgs(cmd), result.fail_reason))

  return constants.EXIT_SUCCESS
Iustin Pop's avatar
Iustin Pop committed
900
901


902
def _FormatLogicalID(dev_type, logical_id, roman):
903
904
905
906
907
908
  """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 = [
909
910
911
912
913
      ("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)),
914
915
916
917
918
919
920
921
922
923
924
      ("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


925
def _FormatBlockDevInfo(idx, top_level, dev, static, roman):
Iustin Pop's avatar
Iustin Pop committed
926
927
  """Show block device information.

928
  This is only used by L{ShowInstanceConfig}, but it's too big to be
Iustin Pop's avatar
Iustin Pop committed
929
930
  left for an inline definition.

931
932
933
934
  @type idx: int
  @param idx: the index of the current disk
  @type top_level: boolean
  @param top_level: if this a top-level disk?
935
936
937
938
939
  @type dev: dict
  @param dev: dictionary with disk information
  @type static: boolean
  @param static: wheter the device information doesn't contain
      runtime information but only static data
940
941
  @type roman: boolean
  @param roman: whether to try to use roman integers
942
943
  @return: a list of either strings, tuples or lists
      (which should be formatted at a higher indent level)
944

Iustin Pop's avatar
Iustin Pop committed
945
  """
946
  def helper(dtype, status):
947
948
949
950
951
952
    """Format one line for physical device status.

    @type dtype: str
    @param dtype: a constant from the L{constants.LDS_BLOCK} set
    @type status: tuple
    @param status: a tuple as returned from L{backend.FindBlockDevice}
953
    @return: the string representing the status
954
955

    """
Iustin Pop's avatar
Iustin Pop committed
956
    if not status:
957
958
      return "not active"
    txt = ""
959
    (path, major, minor, syncp, estt, degr, ldisk_status) = status
960
961
    if major is None:
      major_string = "N/A"
Iustin Pop's avatar
Iustin Pop committed
962
    else:
963
      major_string = str(compat.TryToRoman(major, convert=roman))
964

965
966
967
    if minor is None:
      minor_string = "N/A"
    else:
968
      minor_string = str(compat.TryToRoman(minor, convert=roman))
969
970
971
972
973
974

    txt += ("%s (%s:%s)" % (path, major_string, minor_string))
    if dtype in (constants.LD_DRBD8, ):
      if syncp is not None:
        sync_text = "*RECOVERING* %5.2f%%," % syncp
        if estt:
975
          sync_text += " ETA %ss" % compat.TryToRoman(estt, convert=roman)
976
        else:
977
978
979
980
981
982
983
          sync_text += " ETA unknown"
      else:
        sync_text = "in sync"
      if degr:
        degr_text = "*DEGRADED*"
      else:
        degr_text = "ok"
984
      if ldisk_status == constants.LDS_FAULTY:
985
        ldisk_text = " *MISSING DISK*"
986
987
      elif ldisk_status == constants.LDS_UNKNOWN:
        ldisk_text = " *UNCERTAIN STATE*"
988
989
990
991
      else:
        ldisk_text = ""
      txt += (" %s, status %s%s" % (sync_text, degr_text, ldisk_text))
    elif dtype == constants.LD_LV:
992
      if ldisk_status == constants.LDS_FAULTY:
993
994
995
996
997
998
999
1000
1001
1002
1003
        ldisk_text = " *FAILED* (failed drive?)"
      else:
        ldisk_text = ""
      txt += ldisk_text
    return txt

  # the header
  if top_level:
    if dev["iv_name"] is not None:
      txt = dev["iv_name"]
    else:
1004
      txt = "disk %s" % compat.TryToRoman(idx, convert=roman)
Iustin Pop's avatar
Iustin Pop committed
1005
  else:
1006
    txt = "child %s" % compat.TryToRoman(idx, convert=roman)
Iustin Pop's avatar
Iustin Pop committed
1007
1008
1009
1010
1011
  if isinstance(dev["size"], int):
    nice_size = utils.FormatUnit(dev["size"], "h")
  else:
    nice_size = dev["size"]
  d1 = ["- %s: %s, size %s" % (txt, dev["dev_type"], nice_size)]
1012
1013
1014
  data = []
  if top_level:
    data.append(("access mode", dev["mode"]))
Iustin Pop's avatar
Iustin Pop committed
1015
  if dev["logical_id"] is not None:
1016
    try:
1017
      l_id = _FormatLogicalID(dev["dev_type"], dev["logical_id"], roman)
1018
1019
1020
1021
1022
1023
    except ValueError:
      l_id = [str(dev["logical_id"])]
    if len(l_id) == 1:
      data.append(("logical_id", l_id[0]))
    else:
      data.extend(l_id)
Iustin Pop's avatar
Iustin Pop committed
1024
  elif dev["physical_id"] is not None:
1025
1026
    data.append("physical_id:")
    data.append([dev["physical_id"]])
1027
  if not static:
1028
    data.append(("on primary", helper(dev["dev_type"], dev["pstatus"])))
1029
  if dev["sstatus"] and not static:
1030
    data.append(("on secondary", helper(dev["dev_type"], dev["sstatus"])))
Iustin Pop's avatar
Iustin Pop committed
1031
1032

  if dev["children"]:
1033
1034
    data.append("child devices:")
    for c_idx, child in enumerate(dev["children"]):
1035
      data.append(_FormatBlockDevInfo(c_idx, False, child, static, roman))
1036
1037
  d1.append(data)
  return d1
Iustin Pop's avatar
Iustin Pop committed
1038
1039


1040
1041
1042
1043
1044
1045