gnt-instance 52 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
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
40
41
42
43
44
45
from ganeti import errors


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

57

58
59
_VALUE_TRUE = "true"

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

65

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

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

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

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

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

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

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


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

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

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


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


205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
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"
221
                                 " any instances", errors.ECODE_INVAL)
222
223
224
225
    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
226
    jex = JobExecutor(verbose=multi_on, cl=cl, opts=opts)
227
228
229
230
231
232
233
234
    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
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
  """
  if opts.output is None:
246
247
248
    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
249
250
251
  else:
    selected_fields = opts.output.split(",")

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

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

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

295
296
  list_type_fields = ("tags", "disk.sizes", "nic.macs", "nic.ips",
                      "nic.modes", "nic.links", "nic.bridges")
297
298
299
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)"
      elif field == "sda_size" or field == "sdb_size":
        if val is None:
          val = "N/A"
321
322
      elif field == "ctime" or field == "mtime":
        val = utils.FormatTime(val)
323
      elif field in list_type_fields:
Iustin Pop's avatar
Iustin Pop committed
324
        val = ",".join(str(item) for item in val)
325
326
      elif val is None:
        val = "-"
327
328
      if opts.roman_integers and isinstance(val, int):
        val = compat.TryToRoman(val)
329
330
      row[idx] = str(val)

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

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

  return 0


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

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

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


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

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

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

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

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

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

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

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

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

434
  jex = JobExecutor(opts=opts)
435

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

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

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

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

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

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

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

  return 0


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

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

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

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

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

534
    ToStdout("Available OS templates:")
535
536
    number = 0
    choices = []
537
538
539
540
541
542
    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
543
544

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

    if selected == 'exit':
549
      ToStderr("User aborted reinstall, exiting")
550
551
      return 1

552
    os_name = selected
553
  else:
554
    os_name = opts.os
555

556
557
558
559
560
561
562
  # 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)):
563
      return 1
564
565
566
  else:
    if not opts.force:
      usertext = ("This will reinstall the instance %s and remove"
Iustin Pop's avatar
Iustin Pop committed
567
                  " all data. Continue?") % inames[0]
568
569
570
      if not AskUser(usertext):
        return 1

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

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


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

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

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

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

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

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


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

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

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

629
630
  op = opcodes.OpRenameInstance(instance_name=args[0],
                                new_name=args[1],
631
632
                                ignore_ip=not opts.ip_check,
                                check_name=opts.check_name)
633
  SubmitOrSend(op, opts)
634
635
636
  return 0


Iustin Pop's avatar
Iustin Pop committed
637
638
639
640
def ActivateDisks(opts, args):
  """Activate an instance's disks.

  This serves two purposes:
641
642
    - 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
643
644
    - it repairs inactive secondary drbds

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


def DeactivateDisks(opts, args):
Iustin Pop's avatar
Iustin Pop committed
662
  """Deactivate an instance's disks.
Iustin Pop's avatar
Iustin Pop committed
663
664
665
666

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

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


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

709
710
711
712
713
714
  @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
715
716
717
718

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


731
def _StartupInstance(name, opts):
732
  """Startup instances.
Iustin Pop's avatar
Iustin Pop committed
733

734
735
  This returns the opcode to start an instance, and its decorator will
  wrap this into a loop starting all desired instances.
736

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

  """
742
743
744
745
746
747
748
749
  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
750

751

752
def _RebootInstance(name, opts):
753
754
  """Reboot instance(s).

755
756
  This returns the opcode to reboot an instance, and its decorator
  will wrap this into a loop rebooting all desired instances.
757

758
  @param name: the name of the instance to act on
759
  @param opts: the command line options selected by the user
760
  @return: the opcode needed for the operation
761
762

  """
763
  return opcodes.OpRebootInstance(instance_name=name,
764
                                  reboot_type=opts.reboot_type,
765
                                  ignore_secondaries=opts.ignore_secondaries,
766
                                  shutdown_timeout=opts.shutdown_timeout)
Iustin Pop's avatar
Iustin Pop committed
767

768

769
def _ShutdownInstance(name, opts):
Iustin Pop's avatar
Iustin Pop committed
770
771
  """Shutdown an instance.

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

  """
780
781
  return opcodes.OpShutdownInstance(instance_name=name,
                                    timeout=opts.timeout)
Iustin Pop's avatar
Iustin Pop committed
782
783
784
785
786


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

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

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

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

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

  """
843
  cl = GetClient()
844
845
  instance_name = args[0]
  force = opts.force
Iustin Pop's avatar
Iustin Pop committed
846

847
  if not force:
848
849
    _EnsureInstancesExist(cl, [instance_name])

850
851
852
853
854
    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
855

856
  op = opcodes.OpFailoverInstance(instance_name=instance_name,
857
                                  ignore_consistency=opts.ignore_consistency,
858
                                  shutdown_timeout=opts.shutdown_timeout)
859
  SubmitOrSend(op, opts, cl=cl)
860
  return 0
Iustin Pop's avatar
Iustin Pop committed
861
862


863
864
865
866
867
def MigrateInstance(opts, args):
  """Migrate an instance.

  The migrate is done without shutdown.

Iustin Pop's avatar
Iustin Pop committed
868
869
870
871
872
  @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
873
874

  """
875
  cl = GetClient()
876
877
878
879
  instance_name = args[0]
  force = opts.force

  if not force:
880
881
    _EnsureInstancesExist(cl, [instance_name])

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

  op = opcodes.OpMigrateInstance(instance_name=instance_name, live=opts.live,
                                 cleanup=opts.cleanup)
896
  SubmitOpCode(op, cl=cl, opts=opts)
897
898
899
  return 0


Iustin Pop's avatar
Iustin Pop committed
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
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,
922
                              target_node=opts.node,
923
                              shutdown_timeout=opts.shutdown_timeout)
Iustin Pop's avatar
Iustin Pop committed
924
925
926
927
  SubmitOrSend(op, opts, cl=cl)
  return 0


Iustin Pop's avatar
Iustin Pop committed
928
929
930
def ConnectToInstanceConsole(opts, args):
  """Connect to the console of an instance.

931
932
933
934
935
  @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
936
937
938
939
940

  """
  instance_name = args[0]

  op = opcodes.OpConnectConsole(instance_name=instance_name)
941
  cmd = SubmitOpCode(op, opts=opts)
942
943

  if opts.show_command:
944
    ToStdout("%s", utils.ShellQuoteArgs(cmd))
945
946
947
948
  else:
    try:
      os.execvp(cmd[0], cmd)
    finally:
949
      ToStderr("Can't run console command %s with arguments:\n'%s'",
950
               cmd[0], " ".join(cmd))
Iustin Pop's avatar
Iustin Pop committed
951
      os._exit(1) # pylint: disable-msg=W0212
Iustin Pop's avatar
Iustin Pop committed
952
953


954
def _FormatLogicalID(dev_type, logical_id, roman):
955
956
957
958
959
960
  """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 = [
961
962
963
964
965
      ("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)),
966
967
968
969
970
971
972
973
974
975
976
      ("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


977
def _FormatBlockDevInfo(idx, top_level, dev, static, roman):
Iustin Pop's avatar
Iustin Pop committed
978
979
  """Show block device information.

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

983
984
985
986
  @type idx: int
  @param idx: the index of the current disk
  @type top_level: boolean
  @param top_level: if this a top-level disk?
987
988
989
990
991
  @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
992
993
  @type roman: boolean
  @param roman: whether to try to use roman integers
994
995
  @return: a list of either strings, tuples or lists
      (which should be formatted at a higher indent level)
996

Iustin Pop's avatar
Iustin Pop committed
997
  """
998
  def helper(dtype, status):
999
1000
1001
1002
1003
1004
    """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}
1005
    @return: the string representing the status
1006
1007

    """
Iustin Pop's avatar
Iustin Pop committed
1008
    if not status:
1009
1010
      return "not active"
    txt = ""
1011
    (path, major, minor, syncp, estt, degr, ldisk_status) = status
1012
1013
    if major is None:
      major_string = "N/A"
Iustin Pop's avatar
Iustin Pop committed
1014
    else:
1015
      major_string = str(compat.TryToRoman(major, convert=roman))
1016

1017
1018
1019
    if minor is None:
      minor_string = "N/A"
    else:
1020
      minor_string = str(compat.TryToRoman(minor, convert=roman))
1021
1022
1023
1024
1025
1026

    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:
1027
          sync_text += " ETA %ss" % compat.TryToRoman(estt, convert=roman)
1028
        else:
1029
1030
1031
1032
1033
1034
1035
          sync_text += " ETA unknown"
      else:
        sync_text = "in sync"
      if degr:
        degr_text = "*DEGRADED*"
      else:
        degr_text = "ok"
1036
      if ldisk_status == constants.LDS_FAULTY:
1037
        ldisk_text = " *MISSING DISK*"
1038
1039
      elif ldisk_status == constants.LDS_UNKNOWN:
        ldisk_text = " *UNCERTAIN STATE*"
1040
1041
1042
1043
      else:
        ldisk_text = ""
      txt += (" %s, status %s%s" % (sync_text, degr_text, ldisk_text))
    elif dtype == constants.LD_LV:
1044
      if ldisk_status == constants.LDS_FAULTY:
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
        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:
1056
      txt = "disk %s" % compat.TryToRoman(idx, convert=roman)
Iustin Pop's avatar
Iustin Pop committed
1057
  else:
1058
    txt = "child %s" % compat.TryToRoman(idx, convert=roman)
Iustin Pop's avatar
Iustin Pop committed
1059
1060
1061
1062
1063
  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)]
1064
1065
1066
  data = []
  if top_level:
    data.append(("access mode", dev["mode"]))
Iustin Pop's avatar
Iustin Pop committed
1067
  if dev["logical_id"] is not None:
1068
    try:
1069
      l_id = _FormatLogicalID(dev["dev_type"], dev["logical_id"], roman)
1070
1071
1072
1073
1074
1075
    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
1076
  elif dev["physical_id"] is not None:
1077
1078
    data.append("physical_id:")
    data.append([dev["physical_id"]])
1079
  if not static:
1080
    data.append(("on primary", helper(dev["dev_type"], dev["pstatus"])))
1081
  if dev["sstatus"] and not static:
1082
    data.append(("on secondary", helper(dev["dev_type"], dev["sstatus"])))
Iustin Pop's avatar
Iustin Pop committed
1083
1084

  if dev["children"]:
1085
1086
    data.append("child devices:")
    for c_idx, child in enumerate(dev["children"]):
1087
      data.append(_FormatBlockDevInfo(c_idx, False, child, static, roman))