gnt-instance 50.1 KB
Newer Older
Iustin Pop's avatar
Iustin Pop committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#!/usr/bin/python
#

# Copyright (C) 2006, 2007 Google Inc.
#
# 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
37
from cStringIO import StringIO

from ganeti.cli import *
from ganeti import opcodes
from ganeti import constants
from ganeti import utils
38
39
40
41
42
43
44
from ganeti import errors


_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
229
230
231
232
233
    for name in inames:
      op = fn(name, opts)
      jex.QueueJob(name, op)
    jex.WaitOrShow(not opts.submit_only)
    return 0
  return realfn


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

237
238
239
240
241
242
  @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
243
244
  """
  if opts.output is None:
245
246
247
    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
248
249
250
  else:
    selected_fields = opts.output.split(",")

251
  output = GetClient().QueryInstances(args, selected_fields, opts.do_locking)
Iustin Pop's avatar
Iustin Pop committed
252
253

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

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

294
295
  list_type_fields = ("tags", "disk.sizes", "nic.macs", "nic.ips",
                      "nic.modes", "nic.links", "nic.bridges")
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
  # 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)"
      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
      row[idx] = str(val)

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

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

  return 0


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

Iustin Pop's avatar
Iustin Pop committed
341
  This is just a wrapper over GenericInstanceCreate.
Iustin Pop's avatar
Iustin Pop committed
342
343

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


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

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

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

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

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

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

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

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

426
  jex = JobExecutor(opts=opts)
427

428
429
430
  # Iterate over the instances and do:
  #  * Populate the specs with default value
  #  * Validate the instance specs
431
432
433
  i_names = utils.NiceSort(instance_data.keys())
  for name in i_names:
    specs = instance_data[name]
434
435
436
    specs = _PopulateWithDefaults(specs)
    _Validate(specs)

Iustin Pop's avatar
Iustin Pop committed
437
438
    hypervisor = specs['hypervisor']
    hvparams = specs['hvparams']
439

440
441
442
443
    disks = []
    for elem in specs['disk_size']:
      try:
        size = utils.ParseUnit(elem)
444
      except (TypeError, ValueError), err:
445
446
        raise errors.OpPrereqError("Invalid disk size '%s' for"
                                   " instance %s: %s" %
447
                                   (elem, name, err), errors.ECODE_INVAL)
448
449
      disks.append({"size": size})

450
451
452
    utils.ForceDictType(specs['backend'], constants.BES_PARAMETER_TYPES)
    utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)

453
454
455
456
457
458
459
460
461
    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"
462
463
                                 " individual nic fields as well",
                                 errors.ECODE_INVAL)
464
465
466
467
468
    elif specs['nics'] is not None:
      tmp_nics = specs['nics']
    elif not tmp_nics:
      tmp_nics = [{}]

469
    op = opcodes.OpCreateInstance(instance_name=name,
470
                                  disks=disks,
471
472
473
                                  disk_template=specs['template'],
                                  mode=constants.INSTANCE_CREATE,
                                  os_type=specs['os'],
474
                                  force_variant=opts.force_variant,
475
476
                                  pnode=specs['primary_node'],
                                  snode=specs['secondary_node'],
477
                                  nics=tmp_nics,
478
479
                                  start=specs['start'],
                                  ip_check=specs['ip_check'],
480
                                  name_check=specs['name_check'],
481
482
483
484
485
486
487
488
                                  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'])

489
490
491
    jex.QueueJob(name, op)
  # we never want to wait, just show the submitted job IDs
  jex.WaitOrShow(False)
492
493
494
495

  return 0


496
497
498
def ReinstallInstance(opts, args):
  """Reinstall an instance.

499
500
501
502
503
504
  @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
505
506

  """
507
508
509
510
511
512
  # 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:
513
514
    raise errors.OpPrereqError("Selection filter does not match any instances",
                               errors.ECODE_INVAL)
515

516
  # second, if requested, ask for an OS
517
  if opts.select_os is True:
518
519
    op = opcodes.OpDiagnoseOS(output_fields=["name", "valid", "variants"],
                              names=[])
520
    result = SubmitOpCode(op, opts=opts)
521
522

    if not result:
523
      ToStdout("Can't get the OS list")
524
525
      return 1

526
    ToStdout("Available OS templates:")
527
528
    number = 0
    choices = []
529
530
531
532
533
534
    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
535
536

    choices.append(('x', 'exit', 'Exit gnt-instance reinstall'))
537
    selected = AskUser("Enter OS template number (or x to abort):",
538
539
540
                       choices)

    if selected == 'exit':
541
      ToStderr("User aborted reinstall, exiting")
542
543
      return 1

544
    os_name = selected
545
  else:
546
    os_name = opts.os
547

548
549
550
551
552
553
554
  # 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)):
555
      return 1
556
557
558
  else:
    if not opts.force:
      usertext = ("This will reinstall the instance %s and remove"
Iustin Pop's avatar
Iustin Pop committed
559
                  " all data. Continue?") % inames[0]
560
561
562
      if not AskUser(usertext):
        return 1

563
  jex = JobExecutor(verbose=multi_on, opts=opts)
564
565
  for instance_name in inames:
    op = opcodes.OpReinstallInstance(instance_name=instance_name,
566
567
                                     os_type=os_name,
                                     force_variant=opts.force_variant)
568
    jex.QueueJob(instance_name, op)
569

570
  jex.WaitOrShow(not opts.submit_only)
571
572
573
  return 0


Iustin Pop's avatar
Iustin Pop committed
574
575
576
def RemoveInstance(opts, args):
  """Remove an instance.

577
578
579
580
581
582
  @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
583
584
585
586

  """
  instance_name = args[0]
  force = opts.force
587
  cl = GetClient()
Iustin Pop's avatar
Iustin Pop committed
588
589

  if not force:
590
591
    _EnsureInstancesExist(cl, [instance_name])

Iustin Pop's avatar
Iustin Pop committed
592
593
594
    usertext = ("This will remove the volumes of the instance %s"
                " (including mirrors), thus removing all the data"
                " of the instance. Continue?") % instance_name
595
    if not AskUser(usertext):
Iustin Pop's avatar
Iustin Pop committed
596
597
      return 1

Iustin Pop's avatar
Iustin Pop committed
598
  op = opcodes.OpRemoveInstance(instance_name=instance_name,
599
                                ignore_failures=opts.ignore_failures,
600
                                shutdown_timeout=opts.shutdown_timeout)
601
  SubmitOrSend(op, opts, cl=cl)
Iustin Pop's avatar
Iustin Pop committed
602
603
604
  return 0


605
def RenameInstance(opts, args):
Guido Trotter's avatar
Guido Trotter committed
606
  """Rename an instance.
607

608
609
610
611
612
613
  @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
614
615
616
617

  """
  op = opcodes.OpRenameInstance(instance_name=args[0],
                                new_name=args[1],
618
                                ignore_ip=not opts.ip_check)
619
  SubmitOrSend(op, opts)
620
621
622
  return 0


Iustin Pop's avatar
Iustin Pop committed
623
624
625
626
def ActivateDisks(opts, args):
  """Activate an instance's disks.

  This serves two purposes:
627
628
    - 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
629
630
    - it repairs inactive secondary drbds

631
632
633
634
635
636
  @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
637
638
  """
  instance_name = args[0]
639
640
  op = opcodes.OpActivateInstanceDisks(instance_name=instance_name,
                                       ignore_size=opts.ignore_size)
641
  disks_info = SubmitOrSend(op, opts)
Iustin Pop's avatar
Iustin Pop committed
642
  for host, iname, nname in disks_info:
643
    ToStdout("%s:%s:%s", host, iname, nname)
Iustin Pop's avatar
Iustin Pop committed
644
645
646
647
  return 0


def DeactivateDisks(opts, args):
Iustin Pop's avatar
Iustin Pop committed
648
  """Deactivate an instance's disks.
Iustin Pop's avatar
Iustin Pop committed
649
650
651
652

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

653
654
655
656
657
658
  @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
659
660
661
  """
  instance_name = args[0]
  op = opcodes.OpDeactivateInstanceDisks(instance_name=instance_name)
662
  SubmitOrSend(op, opts)
Iustin Pop's avatar
Iustin Pop committed
663
664
665
  return 0


Iustin Pop's avatar
Iustin Pop committed
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
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
692
def GrowDisk(opts, args):
693
  """Grow an instance's disks.
Iustin Pop's avatar
Iustin Pop committed
694

695
696
697
698
699
700
  @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
701
702
703
704

  """
  instance = args[0]
  disk = args[1]
705
706
  try:
    disk = int(disk)
707
  except (TypeError, ValueError), err:
708
709
    raise errors.OpPrereqError("Invalid disk index: %s" % str(err),
                               errors.ECODE_INVAL)
Iustin Pop's avatar
Iustin Pop committed
710
  amount = utils.ParseUnit(args[2])
711
712
  op = opcodes.OpGrowDisk(instance_name=instance, disk=disk, amount=amount,
                          wait_for_sync=opts.wait_for_sync)
713
  SubmitOrSend(op, opts)
Iustin Pop's avatar
Iustin Pop committed
714
715
716
  return 0


717
def _StartupInstance(name, opts):
718
  """Startup instances.
Iustin Pop's avatar
Iustin Pop committed
719

720
721
  This returns the opcode to start an instance, and its decorator will
  wrap this into a loop starting all desired instances.
722

723
  @param name: the name of the instance to act on
724
  @param opts: the command line options selected by the user
725
  @return: the opcode needed for the operation
Iustin Pop's avatar
Iustin Pop committed
726
727

  """
728
729
730
731
732
733
734
735
  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
736

737

738
def _RebootInstance(name, opts):
739
740
  """Reboot instance(s).

741
742
  This returns the opcode to reboot an instance, and its decorator
  will wrap this into a loop rebooting all desired instances.
743

744
  @param name: the name of the instance to act on
745
  @param opts: the command line options selected by the user
746
  @return: the opcode needed for the operation
747
748

  """
749
  return opcodes.OpRebootInstance(instance_name=name,
750
                                  reboot_type=opts.reboot_type,
751
                                  ignore_secondaries=opts.ignore_secondaries,
752
                                  shutdown_timeout=opts.shutdown_timeout)
Iustin Pop's avatar
Iustin Pop committed
753

754

755
def _ShutdownInstance(name, opts):
Iustin Pop's avatar
Iustin Pop committed
756
757
  """Shutdown an instance.

758
759
760
761
  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
762
  @param opts: the command line options selected by the user
763
  @return: the opcode needed for the operation
Iustin Pop's avatar
Iustin Pop committed
764
765

  """
766
767
  return opcodes.OpShutdownInstance(instance_name=name,
                                    timeout=opts.timeout)
Iustin Pop's avatar
Iustin Pop committed
768
769
770
771
772


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

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

  """
780
  new_2ndary = opts.dst_node
781
  iallocator = opts.iallocator
782
  if opts.disks is None:
783
    disks = []
784
  else:
785
786
    try:
      disks = [int(i) for i in opts.disks.split(",")]
787
    except (TypeError, ValueError), err:
788
789
      raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err),
                                 errors.ECODE_INVAL)
790
  cnt = [opts.on_primary, opts.on_secondary, opts.auto,
791
792
         new_2ndary is not None, iallocator is not None].count(True)
  if cnt != 1:
793
    raise errors.OpPrereqError("One and only one of the -p, -s, -a, -n and -i"
794
                               " options must be passed", errors.ECODE_INVAL)
795
  elif opts.on_primary:
796
    mode = constants.REPLACE_DISK_PRI
797
  elif opts.on_secondary:
798
    mode = constants.REPLACE_DISK_SEC
799
800
801
802
  elif opts.auto:
    mode = constants.REPLACE_DISK_AUTO
    if disks:
      raise errors.OpPrereqError("Cannot specify disks when using automatic"
803
                                 " mode", errors.ECODE_INVAL)
804
805
806
  elif new_2ndary is not None or iallocator is not None:
    # replace secondary
    mode = constants.REPLACE_DISK_CHG
807
808

  op = opcodes.OpReplaceDisks(instance_name=args[0], disks=disks,
809
                              remote_node=new_2ndary, mode=mode,
810
811
                              iallocator=iallocator,
                              early_release=opts.early_release)
812
  SubmitOrSend(op, opts)
Iustin Pop's avatar
Iustin Pop committed
813
814
815
816
817
818
819
820
821
  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.

822
823
824
825
826
  @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
827
828

  """
829
  cl = GetClient()
830
831
  instance_name = args[0]
  force = opts.force
Iustin Pop's avatar
Iustin Pop committed
832

833
  if not force:
834
835
    _EnsureInstancesExist(cl, [instance_name])

836
837
838
839
840
    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
841

842
  op = opcodes.OpFailoverInstance(instance_name=instance_name,
843
                                  ignore_consistency=opts.ignore_consistency,
844
                                  shutdown_timeout=opts.shutdown_timeout)
845
  SubmitOrSend(op, opts, cl=cl)
846
  return 0
Iustin Pop's avatar
Iustin Pop committed
847
848


849
850
851
852
853
def MigrateInstance(opts, args):
  """Migrate an instance.

  The migrate is done without shutdown.

Iustin Pop's avatar
Iustin Pop committed
854
855
856
857
858
  @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
859
860

  """
861
  cl = GetClient()
862
863
864
865
  instance_name = args[0]
  force = opts.force

  if not force:
866
867
    _EnsureInstancesExist(cl, [instance_name])

868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
    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,))
    usertext += (" is **experimental** in this version."
                " This might impact the instance if anything goes wrong."
                " Continue?")
    if not AskUser(usertext):
      return 1

  op = opcodes.OpMigrateInstance(instance_name=instance_name, live=opts.live,
                                 cleanup=opts.cleanup)
883
  SubmitOpCode(op, cl=cl, opts=opts)
884
885
886
  return 0


Iustin Pop's avatar
Iustin Pop committed
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
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,
909
                              target_node=opts.node,
910
                              shutdown_timeout=opts.shutdown_timeout)
Iustin Pop's avatar
Iustin Pop committed
911
912
913
914
  SubmitOrSend(op, opts, cl=cl)
  return 0


Iustin Pop's avatar
Iustin Pop committed
915
916
917
def ConnectToInstanceConsole(opts, args):
  """Connect to the console of an instance.

918
919
920
921
922
  @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
923
924
925
926
927

  """
  instance_name = args[0]

  op = opcodes.OpConnectConsole(instance_name=instance_name)
928
  cmd = SubmitOpCode(op, opts=opts)
929
930

  if opts.show_command:
931
    ToStdout("%s", utils.ShellQuoteArgs(cmd))
932
933
934
935
  else:
    try:
      os.execvp(cmd[0], cmd)
    finally:
936
      ToStderr("Can't run console command %s with arguments:\n'%s'",
937
               cmd[0], " ".join(cmd))
Iustin Pop's avatar
Iustin Pop committed
938
      os._exit(1) # pylint: disable-msg=W0212
Iustin Pop's avatar
Iustin Pop committed
939
940


941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
def _FormatLogicalID(dev_type, logical_id):
  """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 = [
      ("nodeA", "%s, minor=%s" % (node_a, minor_a)),
      ("nodeB", "%s, minor=%s" % (node_b, minor_b)),
      ("port", port),
      ("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


def _FormatBlockDevInfo(idx, top_level, dev, static):
Iustin Pop's avatar
Iustin Pop committed
963
964
  """Show block device information.

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

968
969
970
971
  @type idx: int
  @param idx: the index of the current disk
  @type top_level: boolean
  @param top_level: if this a top-level disk?
972
973
974
975
976
  @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
977
978
  @return: a list of either strings, tuples or lists
      (which should be formatted at a higher indent level)
979

Iustin Pop's avatar
Iustin Pop committed
980
  """
981
  def helper(dtype, status):
982
983
984
985
986
987
    """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}
988
    @return: the string representing the status
989
990

    """
Iustin Pop's avatar
Iustin Pop committed
991
    if not status:
992
993
      return "not active"
    txt = ""
994
    (path, major, minor, syncp, estt, degr, ldisk_status) = status
995
996
    if major is None:
      major_string = "N/A"
Iustin Pop's avatar
Iustin Pop committed
997
    else:
998
      major_string = str(major)
999

1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
    if minor is None:
      minor_string = "N/A"
    else:
      minor_string = str(minor)

    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:
          sync_text += " ETA %ds" % estt
1011
        else:
1012
1013
1014
1015
1016
1017
1018
          sync_text += " ETA unknown"
      else:
        sync_text = "in sync"
      if degr:
        degr_text = "*DEGRADED*"
      else:
        degr_text = "ok"
1019
      if ldisk_status == constants.LDS_FAULTY:
1020
        ldisk_text = " *MISSING DISK*"
1021
1022
      elif ldisk_status == constants.LDS_UNKNOWN:
        ldisk_text = " *UNCERTAIN STATE*"
1023
1024
1025
1026
      else:
        ldisk_text = ""
      txt += (" %s, status %s%s" % (sync_text, degr_text, ldisk_text))
    elif dtype == constants.LD_LV:
1027
      if ldisk_status == constants.LDS_FAULTY:
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
        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:
      txt = "disk %d" % idx
Iustin Pop's avatar
Iustin Pop committed
1040
  else:
1041
    txt = "child %d" % idx
Iustin Pop's avatar
Iustin Pop committed
1042
1043
1044
1045
1046
  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)]
1047
1048
1049
  data = []
  if top_level:
    data.append(("access mode", dev["mode"]))
Iustin Pop's avatar
Iustin Pop committed
1050
  if dev["logical_id"] is not None:
1051
1052
1053
1054
1055
1056
1057
1058
    try:
      l_id = _FormatLogicalID(dev["dev_type"], dev["logical_id"])
    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
1059
  elif dev["physical_id"] is not None:
1060
1061
    data.append("physical_id:")
    data.append([dev["physical_id"]])
1062
  if not static:
1063
    data.append(("on primary", helper(dev["dev_type"], dev["pstatus"])))
1064
  if dev["sstatus"] and not static:
1065
    data.append(("on secondary", helper(dev["dev_type"], dev["sstatus"])))
Iustin Pop's avatar
Iustin Pop committed
1066
1067

  if dev["children"]:
1068
1069
1070
1071
1072
    data.append("child devices:")
    for c_idx, child in enumerate(dev["children"]):
      data.append(_FormatBlockDevInfo(c_idx, False, child, static))
  d1.append(data)
  return d1
Iustin Pop's avatar
Iustin Pop committed
1073
1074


1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
def _FormatList(buf, data, indent_level):
  """Formats a list of data at a given indent level.

  If the element of the list is:
    - a string, it is simply formatted as is
    - a tuple, it will be split into key, value and the all the
      values in a list will be aligned all at the same start column
    - a list, will be recursively formatted

  @type buf: StringIO
  @param buf: the buffer into which we write the output
  @param data: the list to format
  @type indent_level: int
  @param indent_level: the indent level to format at

  """
  max_tlen = max([len(elem[0]) for elem in data
                 if isinstance(elem, tuple)] or [0])
  for elem in data:
    if isinstance(elem, basestring):
      buf.write("%*s%s\n" % (2*indent_level, "", elem))
    elif isinstance(elem, tuple):
      key, value = elem
      spacer = "%*s" % (max_tlen - len(key), "")
      buf.write("%*s%s:%s %s\n" % (2*indent_level, "", key, spacer, value))
    elif isinstance(elem, list):
      _FormatList(buf, elem, indent_level+1)

1103

Iustin Pop's avatar
Iustin Pop committed
1104
1105
1106
def ShowInstanceConfig(opts, args):
  """Compute instance run-time status.

1107
1108
1109
1110
1111
1112
1113
  @param opts: the command line options selected by the user
  @type args: list
  @param args: either an empty list, and then we query all
      instances, or should contain a list of instance names
  @rtype: int
  @return: the desired exit code

Iustin Pop's avatar
Iustin Pop committed
1114
  """
Guido Trotter's avatar
Guido Trotter committed
1115
1116
1117
1118
1119
1120
1121
1122
1123
  if not args and not opts.show_all:
    ToStderr("No instance selected."
             " Please pass in --all if you want to query all instances.\n"
             "Note that this can take a long time on a big cluster.")
    return 1
  elif args and opts.show_all:
    ToStderr("Cannot use --all if you specify instance names.")
    return 1

Iustin Pop's avatar
Iustin Pop committed
1124
  retcode = 0
1125
  op = opcodes.OpQueryInstanceData(instances=args, static=opts.static)
1126
  result = SubmitOpCode(op, opts=opts)
Iustin Pop's avatar
Iustin Pop committed
1127
  if not result:
1128
    ToStdout("No instances.")
Iustin Pop's avatar
Iustin Pop committed
1129
1130
1131
1132
1133
1134
1135
    return 1

  buf = StringIO()
  retcode = 0
  for instance_name in result:
    instance = result[instance_name]
    buf.write("Instance name: %s\n" % instance["name"])
1136