gnt_instance.py 53.1 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
  output = GetClient().QueryInstances(args, selected_fields, opts.do_locking)
Iustin Pop's avatar
Iustin Pop committed
248
249

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

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

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

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

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

  return 0


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

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

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


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

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

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

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

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

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

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

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

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

433
  jex = JobExecutor(opts=opts)
434

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

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

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

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

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

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

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

  return 0


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

506
507
508
509
510
511
  @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
512
513

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

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

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

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

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

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

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

553
554
555
  # third, get confirmation: multi-reinstall requires --force-multi,
  # single-reinstall either --force or --force-multi (--force-multi is
  # a stronger --force)
556
557
558
  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"
559
    if not (opts.force_multi or
560
            _ConfirmOperation(inames, "reinstall", extra=warn_msg)):
561
      return 1
562
  else:
563
    if not (opts.force or opts.force_multi):
564
      usertext = ("This will reinstall the instance %s and remove"
Iustin Pop's avatar
Iustin Pop committed
565
                  " all data. Continue?") % inames[0]
566
567
568
      if not AskUser(usertext):
        return 1

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

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


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

584
585
586
587
588
589
  @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
590
591
592
593

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

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

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

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


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

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

  """
623
  if not opts.name_check:
624
625
626
627
    if not AskUser("As you disabled the check of the DNS entry, please verify"
                   " that '%s' is a FQDN. Continue?" % args[1]):
      return 1

628
629
  op = opcodes.OpRenameInstance(instance_name=args[0],
                                new_name=args[1],
630
631
                                ip_check=opts.ip_check,
                                name_check=opts.name_check)
632
633
  result = SubmitOrSend(op, opts)

634
635
  if result:
    ToStdout("Instance '%s' renamed to '%s'", args[0], result)
636

637
638
639
  return 0


Iustin Pop's avatar
Iustin Pop committed
640
641
642
643
def ActivateDisks(opts, args):
  """Activate an instance's disks.

  This serves two purposes:
644
645
    - 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
646
647
    - it repairs inactive secondary drbds

648
649
650
651
652
653
  @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
654
655
  """
  instance_name = args[0]
656
657
  op = opcodes.OpActivateInstanceDisks(instance_name=instance_name,
                                       ignore_size=opts.ignore_size)
658
  disks_info = SubmitOrSend(op, opts)
Iustin Pop's avatar
Iustin Pop committed
659
  for host, iname, nname in disks_info:
660
    ToStdout("%s:%s:%s", host, iname, nname)
Iustin Pop's avatar
Iustin Pop committed
661
662
663
664
  return 0


def DeactivateDisks(opts, args):
Iustin Pop's avatar
Iustin Pop committed
665
  """Deactivate an instance's disks.
Iustin Pop's avatar
Iustin Pop committed
666
667
668
669

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

670
671
672
673
674
675
  @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
676
677
678
  """
  instance_name = args[0]
  op = opcodes.OpDeactivateInstanceDisks(instance_name=instance_name)
679
  SubmitOrSend(op, opts)
Iustin Pop's avatar
Iustin Pop committed
680
681
682
  return 0


Iustin Pop's avatar
Iustin Pop committed
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
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
709
def GrowDisk(opts, args):
710
  """Grow an instance's disks.
Iustin Pop's avatar
Iustin Pop committed
711

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

  """
  instance = args[0]
  disk = args[1]
722
723
  try:
    disk = int(disk)
724
  except (TypeError, ValueError), err:
725
726
    raise errors.OpPrereqError("Invalid disk index: %s" % str(err),
                               errors.ECODE_INVAL)
Iustin Pop's avatar
Iustin Pop committed
727
  amount = utils.ParseUnit(args[2])
728
729
  op = opcodes.OpGrowDisk(instance_name=instance, disk=disk, amount=amount,
                          wait_for_sync=opts.wait_for_sync)
730
  SubmitOrSend(op, opts)
Iustin Pop's avatar
Iustin Pop committed
731
732
733
  return 0


734
def _StartupInstance(name, opts):
735
  """Startup instances.
Iustin Pop's avatar
Iustin Pop committed
736

737
738
  This returns the opcode to start an instance, and its decorator will
  wrap this into a loop starting all desired instances.
739

740
  @param name: the name of the instance to act on
741
  @param opts: the command line options selected by the user
742
  @return: the opcode needed for the operation
Iustin Pop's avatar
Iustin Pop committed
743
744

  """
745
  op = opcodes.OpStartupInstance(instance_name=name,
746
747
                                 force=opts.force,
                                 ignore_offline_nodes=opts.ignore_offline)
748
749
750
751
752
753
  # 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
754

755

756
def _RebootInstance(name, opts):
757
758
  """Reboot instance(s).

759
760
  This returns the opcode to reboot an instance, and its decorator
  will wrap this into a loop rebooting all desired instances.
761

762
  @param name: the name of the instance to act on
763
  @param opts: the command line options selected by the user
764
  @return: the opcode needed for the operation
765
766

  """
767
  return opcodes.OpRebootInstance(instance_name=name,
768
                                  reboot_type=opts.reboot_type,
769
                                  ignore_secondaries=opts.ignore_secondaries,
770
                                  shutdown_timeout=opts.shutdown_timeout)
Iustin Pop's avatar
Iustin Pop committed
771

772

773
def _ShutdownInstance(name, opts):
Iustin Pop's avatar
Iustin Pop committed
774
775
  """Shutdown an instance.

776
777
778
779
  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
780
  @param opts: the command line options selected by the user
781
  @return: the opcode needed for the operation
Iustin Pop's avatar
Iustin Pop committed
782
783

  """
784
  return opcodes.OpShutdownInstance(instance_name=name,
785
786
                                    timeout=opts.timeout,
                                    ignore_offline_nodes=opts.ignore_offline)
Iustin Pop's avatar
Iustin Pop committed
787
788
789
790
791


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

792
793
794
795
796
  @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
797
798

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

  op = opcodes.OpReplaceDisks(instance_name=args[0], disks=disks,
828
                              remote_node=new_2ndary, mode=mode,
829
830
                              iallocator=iallocator,
                              early_release=opts.early_release)
831
  SubmitOrSend(op, opts)
Iustin Pop's avatar
Iustin Pop committed
832
833
834
835
836
837
838
839
840
  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.

841
842
843
844
845
  @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
846
847

  """
848
  cl = GetClient()
849
850
  instance_name = args[0]
  force = opts.force
Iustin Pop's avatar
Iustin Pop committed
851

852
  if not force:
853
854
    _EnsureInstancesExist(cl, [instance_name])

855
856
857
858
859
    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
860

861
  op = opcodes.OpFailoverInstance(instance_name=instance_name,
862
                                  ignore_consistency=opts.ignore_consistency,
863
                                  shutdown_timeout=opts.shutdown_timeout)
864
  SubmitOrSend(op, opts, cl=cl)
865
  return 0
Iustin Pop's avatar
Iustin Pop committed
866
867


868
869
870
871
872
def MigrateInstance(opts, args):
  """Migrate an instance.

  The migrate is done without shutdown.

Iustin Pop's avatar
Iustin Pop committed
873
874
875
876
877
  @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
878
879

  """
880
  cl = GetClient()
881
882
883
884
  instance_name = args[0]
  force = opts.force

  if not force:
885
886
    _EnsureInstancesExist(cl, [instance_name])

887
888
889
890
891
892
893
    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
894
895
    usertext += (" might impact the instance if anything goes wrong"
                 " (e.g. due to bugs in the hypervisor). Continue?")
896
897
898
    if not AskUser(usertext):
      return 1

899
  # this should be removed once --non-live is deprecated
900
  if not opts.live and opts.migration_mode is not None:
901
    raise errors.OpPrereqError("Only one of the --non-live and "
902
                               "--migration-mode options can be passed",
903
904
                               errors.ECODE_INVAL)
  if not opts.live: # --non-live passed
905
    mode = constants.HT_MIGRATION_NONLIVE
906
  else:
907
    mode = opts.migration_mode
908

909
  op = opcodes.OpMigrateInstance(instance_name=instance_name, mode=mode,
910
                                 cleanup=opts.cleanup)
911
  SubmitOpCode(op, cl=cl, opts=opts)
912
913
914
  return 0


Iustin Pop's avatar
Iustin Pop committed
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
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,
937
                              target_node=opts.node,
938
                              shutdown_timeout=opts.shutdown_timeout)
Iustin Pop's avatar
Iustin Pop committed
939
940
941
942
  SubmitOrSend(op, opts, cl=cl)
  return 0


Iustin Pop's avatar
Iustin Pop committed
943
944
945
def ConnectToInstanceConsole(opts, args):
  """Connect to the console of an instance.

946
947
948
949
950
  @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
951
952
953
954
955

  """
  instance_name = args[0]

  op = opcodes.OpConnectConsole(instance_name=instance_name)
956
  cmd = SubmitOpCode(op, opts=opts)
957
958

  if opts.show_command:
959
    ToStdout("%s", utils.ShellQuoteArgs(cmd))
960
  else:
961
962
963
964
965
966
    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
967
968


969
def _FormatLogicalID(dev_type, logical_id, roman):
970
971
972
973
974
975
  """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 = [
976
977
978
979
980
      ("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)),
981
982
983
984
985
986
987
988
989
990
991
      ("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


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

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

998
999
1000
1001
  @type idx: int
  @param idx: the index of the current disk
  @type top_level: boolean
  @param top_level: if this a top-level disk?
1002
1003
1004
1005
1006
  @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
1007
1008
  @type roman: boolean
  @param roman: whether to try to use roman integers
1009
1010
  @return: a list of either strings, tuples or lists
      (which should be formatted at a higher indent level)
1011

Iustin Pop's avatar
Iustin Pop committed
1012
  """
1013
  def helper(dtype, status):
1014
1015
1016
1017
1018
1019
    """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}
1020
    @return: the string representing the status
1021
1022

    """
Iustin Pop's avatar
Iustin Pop committed
1023
    if not status:
1024
1025
      return "not active"
    txt = ""