gnt-instance 52.8 KB
Newer Older
Iustin Pop's avatar
Iustin Pop committed
1
2
3
#!/usr/bin/python
#

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

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

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


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

58

59
60
_VALUE_TRUE = "true"

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

66

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

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

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

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

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

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

  return inames
Iustin Pop's avatar
Iustin Pop committed
144
145


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

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

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


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


206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
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"
222
                                 " any instances", errors.ECODE_INVAL)
223
224
225
226
    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
227
    jex = JobExecutor(verbose=multi_on, cl=cl, opts=opts)
228
229
230
    for name in inames:
      op = fn(name, opts)
      jex.QueueJob(name, op)
Iustin Pop's avatar
Iustin Pop committed
231
232
233
    results = jex.WaitOrShow(not opts.submit_only)
    rcode = compat.all(row[0] for row in results)
    return int(not rcode)
234
235
236
  return realfn


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

240
241
242
243
244
245
  @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
246
247
  """
  if opts.output is None:
248
249
250
    selected_fields = _LIST_DEF_FIELDS
  elif opts.output.startswith("+"):
    selected_fields = _LIST_DEF_FIELDS + opts.output[1:].split(",")
Iustin Pop's avatar
Iustin Pop committed
251
252
253
  else:
    selected_fields = opts.output.split(",")

254
  output = GetClient().QueryInstances(args, selected_fields, opts.do_locking)
Iustin Pop's avatar
Iustin Pop committed
255
256

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

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

298
299
  list_type_fields = ("tags", "disk.sizes", "nic.macs", "nic.ips",
                      "nic.modes", "nic.links", "nic.bridges")
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
  # 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)"
321
322
323
      elif field == "oper_vcpus":
        if val is None:
          val = "(node down)"
324
325
326
      elif field == "sda_size" or field == "sdb_size":
        if val is None:
          val = "N/A"
327
328
      elif field == "ctime" or field == "mtime":
        val = utils.FormatTime(val)
329
      elif field in list_type_fields:
Iustin Pop's avatar
Iustin Pop committed
330
        val = ",".join(str(item) for item in val)
331
332
      elif val is None:
        val = "-"
333
334
      if opts.roman_integers and isinstance(val, int):
        val = compat.TryToRoman(val)
335
336
      row[idx] = str(val)

337
338
  data = GenerateTable(separator=opts.separator, headers=headers,
                       fields=selected_fields, unitfields=unitfields,
339
                       numfields=numfields, data=output, units=opts.units)
340
341

  for line in data:
342
    ToStdout(line)
Iustin Pop's avatar
Iustin Pop committed
343
344
345
346
347
348
349

  return 0


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

Iustin Pop's avatar
Iustin Pop committed
350
  This is just a wrapper over GenericInstanceCreate.
Iustin Pop's avatar
Iustin Pop committed
351
352

  """
Iustin Pop's avatar
Iustin Pop committed
353
  return GenericInstanceCreate(constants.INSTANCE_CREATE, opts, args)
Iustin Pop's avatar
Iustin Pop committed
354
355


356
def BatchCreate(opts, args):
357
358
359
360
361
362
  """Create instances using a definition file.

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

    {"instance-name":{
363
      "disk_size": [20480],
364
365
366
367
      "template": "drbd",
      "backend": {
        "memory": 512,
        "vcpus": 1 },
368
      "os": "debootstrap",
369
370
371
372
373
374
375
376
377
378
379
380
381
      "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
382
383

  """
384
  _DEFAULT_SPECS = {"disk_size": [20 * 1024],
385
386
387
388
                    "backend": {},
                    "iallocator": None,
                    "primary_node": None,
                    "secondary_node": None,
389
                    "nics": None,
390
391
                    "start": True,
                    "ip_check": True,
392
                    "name_check": True,
393
                    "hypervisor": None,
Iustin Pop's avatar
Iustin Pop committed
394
                    "hvparams": {},
395
                    "file_storage_dir": None,
396
                    "force_variant": False,
397
398
399
400
                    "file_driver": 'loop'}

  def _PopulateWithDefaults(spec):
    """Returns a new hash combined with default values."""
401
402
403
    mydict = _DEFAULT_SPECS.copy()
    mydict.update(spec)
    return mydict
404
405
406
407
408
409
410

  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.' %
411
                                   required_field, errors.ECODE_INVAL)
412
413
414
415
416
    # 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'
417
418
                                   ' there was no secondary provided.',
                                   errors.ECODE_INVAL)
419
420
    elif spec['iallocator'] is None:
      raise errors.OpPrereqError('You have to provide at least a primary_node'
421
422
                                 ' or an iallocator.',
                                 errors.ECODE_INVAL)
423

Iustin Pop's avatar
Iustin Pop committed
424
425
    if (spec['hvparams'] and
        not isinstance(spec['hvparams'], dict)):
426
427
      raise errors.OpPrereqError('Hypervisor parameters must be a dict.',
                                 errors.ECODE_INVAL)
428
429
430

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

Guido Trotter's avatar
Guido Trotter committed
436
437
438
439
  if not isinstance(instance_data, dict):
    ToStderr("The instance definition file is not in dict format.")
    return 1

440
  jex = JobExecutor(opts=opts)
441

442
443
444
  # Iterate over the instances and do:
  #  * Populate the specs with default value
  #  * Validate the instance specs
Guido Trotter's avatar
Guido Trotter committed
445
  i_names = utils.NiceSort(instance_data.keys()) # pylint: disable-msg=E1103
446
447
  for name in i_names:
    specs = instance_data[name]
448
449
450
    specs = _PopulateWithDefaults(specs)
    _Validate(specs)

Iustin Pop's avatar
Iustin Pop committed
451
452
    hypervisor = specs['hypervisor']
    hvparams = specs['hvparams']
453

454
455
456
457
    disks = []
    for elem in specs['disk_size']:
      try:
        size = utils.ParseUnit(elem)
458
      except (TypeError, ValueError), err:
459
460
        raise errors.OpPrereqError("Invalid disk size '%s' for"
                                   " instance %s: %s" %
461
                                   (elem, name, err), errors.ECODE_INVAL)
462
463
      disks.append({"size": size})

464
465
466
    utils.ForceDictType(specs['backend'], constants.BES_PARAMETER_TYPES)
    utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)

467
468
469
470
471
472
473
474
475
    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"
476
477
                                 " individual nic fields as well",
                                 errors.ECODE_INVAL)
478
479
480
481
482
    elif specs['nics'] is not None:
      tmp_nics = specs['nics']
    elif not tmp_nics:
      tmp_nics = [{}]

483
    op = opcodes.OpCreateInstance(instance_name=name,
484
                                  disks=disks,
485
486
487
                                  disk_template=specs['template'],
                                  mode=constants.INSTANCE_CREATE,
                                  os_type=specs['os'],
488
                                  force_variant=specs["force_variant"],
489
490
                                  pnode=specs['primary_node'],
                                  snode=specs['secondary_node'],
491
                                  nics=tmp_nics,
492
493
                                  start=specs['start'],
                                  ip_check=specs['ip_check'],
494
                                  name_check=specs['name_check'],
495
496
497
498
499
500
501
502
                                  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'])

503
504
505
    jex.QueueJob(name, op)
  # we never want to wait, just show the submitted job IDs
  jex.WaitOrShow(False)
506
507
508
509

  return 0


510
511
512
def ReinstallInstance(opts, args):
  """Reinstall an instance.

513
514
515
516
517
518
  @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
519
520

  """
521
522
523
524
525
526
  # 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:
527
528
    raise errors.OpPrereqError("Selection filter does not match any instances",
                               errors.ECODE_INVAL)
529

530
  # second, if requested, ask for an OS
531
  if opts.select_os is True:
532
533
    op = opcodes.OpDiagnoseOS(output_fields=["name", "valid", "variants"],
                              names=[])
534
    result = SubmitOpCode(op, opts=opts)
535
536

    if not result:
537
      ToStdout("Can't get the OS list")
538
539
      return 1

540
    ToStdout("Available OS templates:")
541
542
    number = 0
    choices = []
543
544
545
546
547
548
    for (name, valid, variants) in result:
      if valid:
        for entry in CalculateOSNames(name, variants):
          ToStdout("%3s: %s", number, entry)
          choices.append(("%s" % number, entry, entry))
          number += 1
549
550

    choices.append(('x', 'exit', 'Exit gnt-instance reinstall'))
551
    selected = AskUser("Enter OS template number (or x to abort):",
552
553
554
                       choices)

    if selected == 'exit':
555
      ToStderr("User aborted reinstall, exiting")
556
557
      return 1

558
    os_name = selected
559
  else:
560
    os_name = opts.os
561

562
563
564
565
566
567
568
  # third, get confirmation: multi-reinstall requires --force-multi
  # *and* --force, single-reinstall just --force
  multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
  if multi_on:
    warn_msg = "Note: this will remove *all* data for the below instances!\n"
    if not ((opts.force_multi and opts.force) or
            _ConfirmOperation(inames, "reinstall", extra=warn_msg)):
569
      return 1
570
571
572
  else:
    if not opts.force:
      usertext = ("This will reinstall the instance %s and remove"
Iustin Pop's avatar
Iustin Pop committed
573
                  " all data. Continue?") % inames[0]
574
575
576
      if not AskUser(usertext):
        return 1

577
  jex = JobExecutor(verbose=multi_on, opts=opts)
578
579
  for instance_name in inames:
    op = opcodes.OpReinstallInstance(instance_name=instance_name,
580
581
                                     os_type=os_name,
                                     force_variant=opts.force_variant)
582
    jex.QueueJob(instance_name, op)
583

584
  jex.WaitOrShow(not opts.submit_only)
585
586
587
  return 0


Iustin Pop's avatar
Iustin Pop committed
588
589
590
def RemoveInstance(opts, args):
  """Remove an instance.

591
592
593
594
595
596
  @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
597
598
599
600

  """
  instance_name = args[0]
  force = opts.force
601
  cl = GetClient()
Iustin Pop's avatar
Iustin Pop committed
602
603

  if not force:
604
605
    _EnsureInstancesExist(cl, [instance_name])

Iustin Pop's avatar
Iustin Pop committed
606
607
608
    usertext = ("This will remove the volumes of the instance %s"
                " (including mirrors), thus removing all the data"
                " of the instance. Continue?") % instance_name
609
    if not AskUser(usertext):
Iustin Pop's avatar
Iustin Pop committed
610
611
      return 1

Iustin Pop's avatar
Iustin Pop committed
612
  op = opcodes.OpRemoveInstance(instance_name=instance_name,
613
                                ignore_failures=opts.ignore_failures,
614
                                shutdown_timeout=opts.shutdown_timeout)
615
  SubmitOrSend(op, opts, cl=cl)
Iustin Pop's avatar
Iustin Pop committed
616
617
618
  return 0


619
def RenameInstance(opts, args):
Guido Trotter's avatar
Guido Trotter committed
620
  """Rename an instance.
621

622
623
624
625
626
627
  @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
628
629

  """
630
  if not opts.name_check:
631
632
633
634
    if not AskUser("As you disabled the check of the DNS entry, please verify"
                   " that '%s' is a FQDN. Continue?" % args[1]):
      return 1

635
636
  op = opcodes.OpRenameInstance(instance_name=args[0],
                                new_name=args[1],
637
638
                                ip_check=opts.ip_check,
                                name_check=opts.name_check)
639
640
641
642
  result = SubmitOrSend(op, opts)

  ToStdout("Instance '%s' renamed to '%s'", args[0], result)

643
644
645
  return 0


Iustin Pop's avatar
Iustin Pop committed
646
647
648
649
def ActivateDisks(opts, args):
  """Activate an instance's disks.

  This serves two purposes:
650
651
    - 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
652
653
    - it repairs inactive secondary drbds

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


def DeactivateDisks(opts, args):
Iustin Pop's avatar
Iustin Pop committed
671
  """Deactivate an instance's disks.
Iustin Pop's avatar
Iustin Pop committed
672
673
674
675

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

676
677
678
679
680
681
  @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
682
683
684
  """
  instance_name = args[0]
  op = opcodes.OpDeactivateInstanceDisks(instance_name=instance_name)
685
  SubmitOrSend(op, opts)
Iustin Pop's avatar
Iustin Pop committed
686
687
688
  return 0


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

718
719
720
721
722
723
  @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
724
725
726
727

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


740
def _StartupInstance(name, opts):
741
  """Startup instances.
Iustin Pop's avatar
Iustin Pop committed
742

743
744
  This returns the opcode to start an instance, and its decorator will
  wrap this into a loop starting all desired instances.
745

746
  @param name: the name of the instance to act on
747
  @param opts: the command line options selected by the user
748
  @return: the opcode needed for the operation
Iustin Pop's avatar
Iustin Pop committed
749
750

  """
751
752
753
754
755
756
757
758
  op = opcodes.OpStartupInstance(instance_name=name,
                                 force=opts.force)
  # do not add these parameters to the opcode unless they're defined
  if opts.hvparams:
    op.hvparams = opts.hvparams
  if opts.beparams:
    op.beparams = opts.beparams
  return op
Iustin Pop's avatar
Iustin Pop committed
759

760

761
def _RebootInstance(name, opts):
762
763
  """Reboot instance(s).

764
765
  This returns the opcode to reboot an instance, and its decorator
  will wrap this into a loop rebooting all desired instances.
766

767
  @param name: the name of the instance to act on
768
  @param opts: the command line options selected by the user
769
  @return: the opcode needed for the operation
770
771

  """
772
  return opcodes.OpRebootInstance(instance_name=name,
773
                                  reboot_type=opts.reboot_type,
774
                                  ignore_secondaries=opts.ignore_secondaries,
775
                                  shutdown_timeout=opts.shutdown_timeout)
Iustin Pop's avatar
Iustin Pop committed
776

777

778
def _ShutdownInstance(name, opts):
Iustin Pop's avatar
Iustin Pop committed
779
780
  """Shutdown an instance.

781
782
783
784
  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
785
  @param opts: the command line options selected by the user
786
  @return: the opcode needed for the operation
Iustin Pop's avatar
Iustin Pop committed
787
788

  """
789
790
  return opcodes.OpShutdownInstance(instance_name=name,
                                    timeout=opts.timeout)
Iustin Pop's avatar
Iustin Pop committed
791
792
793
794
795


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

796
797
798
799
800
  @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
801
802

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

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

845
846
847
848
849
  @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
850
851

  """
852
  cl = GetClient()
853
854
  instance_name = args[0]
  force = opts.force
Iustin Pop's avatar
Iustin Pop committed
855

856
  if not force:
857
858
    _EnsureInstancesExist(cl, [instance_name])

859
860
861
862
863
    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
864

865
  op = opcodes.OpFailoverInstance(instance_name=instance_name,
866
                                  ignore_consistency=opts.ignore_consistency,
867
                                  shutdown_timeout=opts.shutdown_timeout)
868
  SubmitOrSend(op, opts, cl=cl)
869
  return 0
Iustin Pop's avatar
Iustin Pop committed
870
871


872
873
874
875
876
def MigrateInstance(opts, args):
  """Migrate an instance.

  The migrate is done without shutdown.

Iustin Pop's avatar
Iustin Pop committed
877
878
879
880
881
  @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
882
883

  """
884
  cl = GetClient()
885
886
887
888
  instance_name = args[0]
  force = opts.force

  if not force:
889
890
    _EnsureInstancesExist(cl, [instance_name])

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

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

913
  op = opcodes.OpMigrateInstance(instance_name=instance_name, mode=mode,
914
                                 cleanup=opts.cleanup)
915
  SubmitOpCode(op, cl=cl, opts=opts)
916
917
918
  return 0


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


Iustin Pop's avatar
Iustin Pop committed
947
948
949
def ConnectToInstanceConsole(opts, args):
  """Connect to the console of an instance.

950
951
952
953
954
  @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
955
956
957
958
959

  """
  instance_name = args[0]

  op = opcodes.OpConnectConsole(instance_name=instance_name)
960
  cmd = SubmitOpCode(op, opts=opts)
961
962

  if opts.show_command:
963
    ToStdout("%s", utils.ShellQuoteArgs(cmd))
964
965
966
967
  else:
    try:
      os.execvp(cmd[0], cmd)
    finally:
968
      ToStderr("Can't run console command %s with arguments:\n'%s'",
969
               cmd[0], " ".join(cmd))
Iustin Pop's avatar
Iustin Pop committed
970
      os._exit(1) # pylint: disable-msg=W0212
Iustin Pop's avatar
Iustin Pop committed
971
972


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


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

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

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

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

    """
Iustin Pop's avatar
Iustin Pop committed
1027
    if not status:
1028
1029
      return "not active"
    txt = ""
1030
    (path, major, minor, syncp, estt, degr, ldisk_status) = status
1031
1032
    if major is None:
      major_string = "N/A"
Iustin Pop's avatar
Iustin Pop committed
1033
    else:
1034
      major_string = str(compat.TryToRoman(major, convert=roman))
1035

1036
1037
1038
    if minor is None:
      minor_string = "N/A"
    else:
1039
      minor_string = str(compat.TryToRoman(minor, convert=roman))
1040
1041
1042
1043
1044
1045

    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:
1046
          sync_text += " ETA %ss" % compat.TryToRoman(estt, convert=roman)
1047
        else:
1048
1049
1050
1051
1052
1053
1054
          sync_text += " ETA unknown"
      else:
        sync_text = "in sync"
      if degr:
        degr_text = "*DEGRADED*"
      else:
        degr_text = "ok"
1055
      if ldisk_status == constants.LDS_FAULTY:
1056
        ldisk_text = " *MISSING DISK*"
1057
1058
      elif ldisk_status == constants.LDS_UNKNOWN:
        ldisk_text = " *UNCERTAIN STATE*"
1059
1060
1061
1062
      else:
        ldisk_text = ""
      txt += (" %s, status %s%s" % (sync_text, degr_text, ldisk_text))
    elif dtype == constants.LD_LV:
1063
      if ldisk_status == constants.LDS_FAULTY:
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
        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:
1075
      txt = "disk %s" % compat.TryToRoman(idx, convert=roman)
Iustin Pop's avatar
Iustin Pop committed