hv_kvm.py 25.7 KB
Newer Older
Guido Trotter's avatar
Guido Trotter committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#
#

# Copyright (C) 2008 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.


"""KVM hypervisor

"""

import os
import os.path
import re
import tempfile
30
import time
Guido Trotter's avatar
Guido Trotter committed
31
import logging
Guido Trotter's avatar
Guido Trotter committed
32
33
34
35
36
from cStringIO import StringIO

from ganeti import utils
from ganeti import constants
from ganeti import errors
37
38
from ganeti import serializer
from ganeti import objects
Guido Trotter's avatar
Guido Trotter committed
39
40
41
42
from ganeti.hypervisor import hv_base


class KVMHypervisor(hv_base.BaseHypervisor):
Guido Trotter's avatar
Guido Trotter committed
43
  """KVM hypervisor interface"""
Guido Trotter's avatar
Guido Trotter committed
44
45

  _ROOT_DIR = constants.RUN_GANETI_DIR + "/kvm-hypervisor"
Guido Trotter's avatar
Guido Trotter committed
46
47
48
49
  _PIDS_DIR = _ROOT_DIR + "/pid" # contains live instances pids
  _CTRL_DIR = _ROOT_DIR + "/ctrl" # contains instances control sockets
  _CONF_DIR = _ROOT_DIR + "/conf" # contains instances startup data
  _DIRS = [_ROOT_DIR, _PIDS_DIR, _CTRL_DIR, _CONF_DIR]
Guido Trotter's avatar
Guido Trotter committed
50

51
52
53
54
55
56
57
58
  PARAMETERS = {
    constants.HV_KERNEL_PATH: hv_base.OPT_FILE_CHECK,
    constants.HV_INITRD_PATH: hv_base.OPT_FILE_CHECK,
    constants.HV_ROOT_PATH: hv_base.NO_CHECK,
    constants.HV_KERNEL_ARGS: hv_base.NO_CHECK,
    constants.HV_ACPI: hv_base.NO_CHECK,
    constants.HV_SERIAL_CONSOLE: hv_base.NO_CHECK,
    constants.HV_VNC_BIND_ADDRESS: \
Iustin Pop's avatar
Iustin Pop committed
59
    (False, lambda x: (utils.IsValidIP(x) or utils.IsNormAbsPath(x)),
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
     "the VNC bind address must be either a valid IP address or an absolute"
     " pathname", None, None),
    constants.HV_VNC_TLS: hv_base.NO_CHECK,
    constants.HV_VNC_X509: hv_base.OPT_DIR_CHECK,
    constants.HV_VNC_X509_VERIFY: hv_base.NO_CHECK,
    constants.HV_CDROM_IMAGE_PATH: hv_base.OPT_FILE_CHECK,
    constants.HV_BOOT_ORDER: \
    hv_base.ParamInSet(True, constants.HT_KVM_VALID_BO_TYPES),
    constants.HV_NIC_TYPE: \
    hv_base.ParamInSet(True, constants.HT_KVM_VALID_NIC_TYPES),
    constants.HV_DISK_TYPE: \
    hv_base.ParamInSet(True, constants.HT_KVM_VALID_DISK_TYPES),
    constants.HV_USB_MOUSE: \
    hv_base.ParamInSet(False, constants.HT_KVM_VALID_MOUSE_TYPES),
    }
75

Guido Trotter's avatar
Guido Trotter committed
76
77
78
  _MIGRATION_STATUS_RE = re.compile('Migration\s+status:\s+(\w+)',
                                    re.M | re.I)

79
80
  _KVM_NETWORK_SCRIPT = constants.SYSCONFDIR + "/ganeti/kvm-vif-bridge"

81
82
83
84
  ANCILLARY_FILES = [
    _KVM_NETWORK_SCRIPT,
    ]

Guido Trotter's avatar
Guido Trotter committed
85
86
87
88
  def __init__(self):
    hv_base.BaseHypervisor.__init__(self)
    # Let's make sure the directories we need exist, even if the RUN_DIR lives
    # in a tmpfs filesystem or has been otherwise wiped out.
Iustin Pop's avatar
Iustin Pop committed
89
    dirs = [(dname, constants.RUN_DIRS_MODE) for dname in self._DIRS]
Guido Trotter's avatar
Guido Trotter committed
90
    utils.EnsureDirs(dirs)
Guido Trotter's avatar
Guido Trotter committed
91

92
93
94
95
96
97
98
99
100
101
  def _InstancePidAlive(self, instance_name):
    """Returns the instance pid and pidfile

    """
    pidfile = "%s/%s" % (self._PIDS_DIR, instance_name)
    pid = utils.ReadPidFile(pidfile)
    alive = utils.IsProcessAlive(pid)

    return (pidfile, pid, alive)

102
103
  @classmethod
  def _InstanceMonitor(cls, instance_name):
104
105
106
    """Returns the instance monitor socket name

    """
107
    return '%s/%s.monitor' % (cls._CTRL_DIR, instance_name)
108

109
110
  @classmethod
  def _InstanceSerial(cls, instance_name):
111
112
113
    """Returns the instance serial socket name

    """
114
    return '%s/%s.serial' % (cls._CTRL_DIR, instance_name)
115

116
117
  @classmethod
  def _InstanceKVMRuntime(cls, instance_name):
118
119
120
    """Returns the instance KVM runtime filename

    """
121
    return '%s/%s.runtime' % (cls._CONF_DIR, instance_name)
122

123
124
125
126
127
128
129
130
131
132
  @classmethod
  def _RemoveInstanceRuntimeFiles(cls, pidfile, instance_name):
    """Removes an instance's rutime sockets/files.

    """
    utils.RemoveFile(pidfile)
    utils.RemoveFile(cls._InstanceMonitor(instance_name))
    utils.RemoveFile(cls._InstanceSerial(instance_name))
    utils.RemoveFile(cls._InstanceKVMRuntime(instance_name))

Guido Trotter's avatar
Guido Trotter committed
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
  def _WriteNetScript(self, instance, seq, nic):
    """Write a script to connect a net interface to the proper bridge.

    This can be used by any qemu-type hypervisor.

    @param instance: instance we're acting on
    @type instance: instance object
    @param seq: nic sequence number
    @type seq: int
    @param nic: nic we're acting on
    @type nic: nic object
    @return: netscript file name
    @rtype: string

    """
    script = StringIO()
    script.write("#!/bin/sh\n")
    script.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
    script.write("export INSTANCE=%s\n" % instance.name)
    script.write("export MAC=%s\n" % nic.mac)
Guido Trotter's avatar
Guido Trotter committed
153
154
155
156
157
158
159
    if nic.ip:
      script.write("export IP=%s\n" % nic.ip)
    script.write("export MODE=%s\n" % nic.nicparams[constants.NIC_MODE])
    if nic.nicparams[constants.NIC_LINK]:
      script.write("export LINK=%s\n" % nic.nicparams[constants.NIC_LINK])
    if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
      script.write("export BRIDGE=%s\n" % nic.nicparams[constants.NIC_LINK])
Guido Trotter's avatar
Guido Trotter committed
160
161
    script.write("export INTERFACE=$1\n")
    # TODO: make this configurable at ./configure time
162
    script.write("if [ -x '%s' ]; then\n" % self._KVM_NETWORK_SCRIPT)
Guido Trotter's avatar
Guido Trotter committed
163
    script.write("  # Execute the user-specific vif file\n")
164
    script.write("  %s\n" % self._KVM_NETWORK_SCRIPT)
Guido Trotter's avatar
Guido Trotter committed
165
166
    script.write("else\n")
    script.write("  /sbin/ifconfig $INTERFACE 0.0.0.0 up\n")
Guido Trotter's avatar
Guido Trotter committed
167
168
169
170
171
172
173
174
    if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
      script.write("  # Connect the interface to the bridge\n")
      script.write("  /usr/sbin/brctl addif $BRIDGE $INTERFACE\n")
    elif nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_ROUTED:
      script.write("  # Route traffic targeted at the IP to the interface\n")
      script.write("  /sbin/ip route add $IP/32 dev $INTERFACE\n")
      interface_proxy_arp = "/proc/sys/net/ipv4/conf/$INTERFACE/proxy_arp"
      script.write("  /bin/echo 1 > %s\n" % interface_proxy_arp)
Guido Trotter's avatar
Guido Trotter committed
175
176
177
178
179
180
181
182
183
184
185
186
187
    script.write("fi\n\n")
    # As much as we'd like to put this in our _ROOT_DIR, that will happen to be
    # mounted noexec sometimes, so we'll have to find another place.
    (tmpfd, tmpfile_name) = tempfile.mkstemp()
    tmpfile = os.fdopen(tmpfd, 'w')
    tmpfile.write(script.getvalue())
    tmpfile.close()
    os.chmod(tmpfile_name, 0755)
    return tmpfile_name

  def ListInstances(self):
    """Get the list of running instances.

Iustin Pop's avatar
Iustin Pop committed
188
189
    We can do this by listing our live instances directory and
    checking whether the associated kvm process is still alive.
Guido Trotter's avatar
Guido Trotter committed
190
191
192
193

    """
    result = []
    for name in os.listdir(self._PIDS_DIR):
194
195
      filename = "%s/%s" % (self._PIDS_DIR, name)
      if utils.IsProcessAlive(utils.ReadPidFile(filename)):
Guido Trotter's avatar
Guido Trotter committed
196
197
198
199
200
201
        result.append(name)
    return result

  def GetInstanceInfo(self, instance_name):
    """Get instance properties.

Iustin Pop's avatar
Iustin Pop committed
202
203
204
    @param instance_name: the instance name

    @return: tuple (name, id, memory, vcpus, stat, times)
Guido Trotter's avatar
Guido Trotter committed
205
206

    """
207
208
    pidfile, pid, alive = self._InstancePidAlive(instance_name)
    if not alive:
Guido Trotter's avatar
Guido Trotter committed
209
210
211
212
213
214
215
216
217
      return None

    cmdline_file = "/proc/%s/cmdline" % pid
    try:
      fh = open(cmdline_file, 'r')
      try:
        cmdline = fh.read()
      finally:
        fh.close()
218
    except EnvironmentError, err:
Guido Trotter's avatar
Guido Trotter committed
219
220
221
222
223
224
225
226
227
228
229
230
      raise errors.HypervisorError("Failed to list instance %s: %s" %
                                   (instance_name, err))

    memory = 0
    vcpus = 0
    stat = "---b-"
    times = "0"

    arg_list = cmdline.split('\x00')
    while arg_list:
      arg =  arg_list.pop(0)
      if arg == '-m':
231
        memory = int(arg_list.pop(0))
Guido Trotter's avatar
Guido Trotter committed
232
      elif arg == '-smp':
233
        vcpus = int(arg_list.pop(0))
Guido Trotter's avatar
Guido Trotter committed
234
235
236
237
238
239

    return (instance_name, pid, memory, vcpus, stat, times)

  def GetAllInstancesInfo(self):
    """Get properties of all instances.

Iustin Pop's avatar
Iustin Pop committed
240
241
    @return: list of tuples (name, id, memory, vcpus, stat, times)

Guido Trotter's avatar
Guido Trotter committed
242
243
244
    """
    data = []
    for name in os.listdir(self._PIDS_DIR):
245
246
      filename = "%s/%s" % (self._PIDS_DIR, name)
      if utils.IsProcessAlive(utils.ReadPidFile(filename)):
247
248
249
250
251
252
        try:
          info = self.GetInstanceInfo(name)
        except errors.HypervisorError, err:
          continue
        if info:
          data.append(info)
Guido Trotter's avatar
Guido Trotter committed
253
254
255

    return data

256
  def _GenerateKVMRuntime(self, instance, block_devices):
257
    """Generate KVM information to start an instance.
Guido Trotter's avatar
Guido Trotter committed
258
259

    """
260
    pidfile, pid, alive = self._InstancePidAlive(instance.name)
Guido Trotter's avatar
Guido Trotter committed
261
262
    kvm = constants.KVM_PATH
    kvm_cmd = [kvm]
263
264
    kvm_cmd.extend(['-m', instance.beparams[constants.BE_MEMORY]])
    kvm_cmd.extend(['-smp', instance.beparams[constants.BE_VCPUS]])
Guido Trotter's avatar
Guido Trotter committed
265
266
267
268
    kvm_cmd.extend(['-pidfile', pidfile])
    # used just by the vnc server, if enabled
    kvm_cmd.extend(['-name', instance.name])
    kvm_cmd.extend(['-daemonize'])
269
    if not instance.hvparams[constants.HV_ACPI]:
Guido Trotter's avatar
Guido Trotter committed
270
271
      kvm_cmd.extend(['-no-acpi'])

272
    hvp = instance.hvparams
273
274
275
    boot_disk = hvp[constants.HV_BOOT_ORDER] == constants.HT_BO_DISK
    boot_cdrom = hvp[constants.HV_BOOT_ORDER] == constants.HT_BO_CDROM
    boot_network = hvp[constants.HV_BOOT_ORDER] == constants.HT_BO_NETWORK
Guido Trotter's avatar
Guido Trotter committed
276
277
278

    if boot_network:
      kvm_cmd.extend(['-boot', 'n'])
279

280
    disk_type = hvp[constants.HV_DISK_TYPE]
281
282
283
284
    if disk_type == constants.HT_DISK_PARAVIRTUAL:
      if_val = ',if=virtio'
    else:
      if_val = ',if=%s' % disk_type
285
    for cfdev, dev_path in block_devices:
286
287
288
      if cfdev.mode != constants.DISK_RDWR:
        raise errors.HypervisorError("Instance has read-only disks which"
                                     " are not supported by KVM")
Guido Trotter's avatar
Guido Trotter committed
289
      # TODO: handle FD_LOOP and FD_BLKTAP (?)
290
      if boot_disk:
291
        kvm_cmd.extend(['-boot', 'c'])
Guido Trotter's avatar
Guido Trotter committed
292
        boot_val = ',boot=on'
Guido Trotter's avatar
Guido Trotter committed
293
        # We only boot from the first disk
294
        boot_disk = False
Guido Trotter's avatar
Guido Trotter committed
295
296
297
      else:
        boot_val = ''

298
      drive_val = 'file=%s,format=raw%s%s' % (dev_path, if_val, boot_val)
Guido Trotter's avatar
Guido Trotter committed
299
300
      kvm_cmd.extend(['-drive', drive_val])

301
    iso_image = hvp[constants.HV_CDROM_IMAGE_PATH]
302
    if iso_image:
303
      options = ',format=raw,media=cdrom'
304
      if boot_cdrom:
305
        kvm_cmd.extend(['-boot', 'd'])
306
        options = '%s,boot=on' % options
307
308
      else:
        options = '%s,if=virtio' % options
309
310
311
      drive_val = 'file=%s%s' % (iso_image, options)
      kvm_cmd.extend(['-drive', drive_val])

312
    kernel_path = hvp[constants.HV_KERNEL_PATH]
313
314
    if kernel_path:
      kvm_cmd.extend(['-kernel', kernel_path])
315
      initrd_path = hvp[constants.HV_INITRD_PATH]
316
317
      if initrd_path:
        kvm_cmd.extend(['-initrd', initrd_path])
318
319
320
321
322
      root_append = ['root=%s' % hvp[constants.HV_ROOT_PATH],
                     hvp[constants.HV_KERNEL_ARGS]]
      if hvp[constants.HV_SERIAL_CONSOLE]:
        root_append.append('console=ttyS0,38400')
      kvm_cmd.extend(['-append', ' '.join(root_append)])
Guido Trotter's avatar
Guido Trotter committed
323

324
    mouse_type = hvp[constants.HV_USB_MOUSE]
325
326
327
328
    if mouse_type:
      kvm_cmd.extend(['-usb'])
      kvm_cmd.extend(['-usbdevice', mouse_type])

329
    # FIXME: handle vnc password
330
    vnc_bind_address = hvp[constants.HV_VNC_BIND_ADDRESS]
331
    if vnc_bind_address:
332
      if utils.IsValidIP(vnc_bind_address):
333
334
        if instance.network_port > constants.VNC_BASE_PORT:
          display = instance.network_port - constants.VNC_BASE_PORT
335
336
337
          if vnc_bind_address == '0.0.0.0':
            vnc_arg = ':%d' % (display)
          else:
338
            vnc_arg = '%s:%d' % (vnc_bind_address, display)
339
        else:
340
341
342
          logging.error("Network port is not a valid VNC display (%d < %d)."
                        " Not starting VNC" %
                        (instance.network_port,
343
                         constants.VNC_BASE_PORT))
344
          vnc_arg = 'none'
345
346
347
348

        # Only allow tls and other option when not binding to a file, for now.
        # kvm/qemu gets confused otherwise about the filename to use.
        vnc_append = ''
349
        if hvp[constants.HV_VNC_TLS]:
350
          vnc_append = '%s,tls' % vnc_append
351
          if hvp[constants.HV_VNC_X509_VERIFY]:
352
            vnc_append = '%s,x509verify=%s' % (vnc_append,
353
354
                                               hvp[constants.HV_VNC_X509])
          elif hvp[constants.HV_VNC_X509]:
355
            vnc_append = '%s,x509=%s' % (vnc_append,
356
                                         hvp[constants.HV_VNC_X509])
357
358
        vnc_arg = '%s%s' % (vnc_arg, vnc_append)

359
      else:
360
361
        vnc_arg = 'unix:%s/%s.vnc' % (vnc_bind_address, instance.name)

362
      kvm_cmd.extend(['-vnc', vnc_arg])
363
364
365
    else:
      kvm_cmd.extend(['-nographic'])

366
367
    monitor_dev = 'unix:%s,server,nowait' % \
      self._InstanceMonitor(instance.name)
Guido Trotter's avatar
Guido Trotter committed
368
    kvm_cmd.extend(['-monitor', monitor_dev])
369
370
371
    if hvp[constants.HV_SERIAL_CONSOLE]:
      serial_dev = ('unix:%s,server,nowait' %
                    self._InstanceSerial(instance.name))
372
373
374
      kvm_cmd.extend(['-serial', serial_dev])
    else:
      kvm_cmd.extend(['-serial', 'none'])
Guido Trotter's avatar
Guido Trotter committed
375

376
377
378
    # Save the current instance nics, but defer their expansion as parameters,
    # as we'll need to generate executable temp files for them.
    kvm_nics = instance.nics
379
    hvparams = hvp
380

381
    return (kvm_cmd, kvm_nics, hvparams)
382

383
384
385
386
387
388
389
  def _WriteKVMRuntime(self, instance_name, data):
    """Write an instance's KVM runtime

    """
    try:
      utils.WriteFile(self._InstanceKVMRuntime(instance_name),
                      data=data)
390
    except EnvironmentError, err:
391
392
393
394
395
396
397
398
      raise errors.HypervisorError("Failed to save KVM runtime file: %s" % err)

  def _ReadKVMRuntime(self, instance_name):
    """Read an instance's KVM runtime

    """
    try:
      file_content = utils.ReadFile(self._InstanceKVMRuntime(instance_name))
399
    except EnvironmentError, err:
400
401
402
403
404
405
406
      raise errors.HypervisorError("Failed to load KVM runtime file: %s" % err)
    return file_content

  def _SaveKVMRuntime(self, instance, kvm_runtime):
    """Save an instance's KVM runtime

    """
407
    kvm_cmd, kvm_nics, hvparams = kvm_runtime
408
    serialized_nics = [nic.ToDict() for nic in kvm_nics]
409
    serialized_form = serializer.Dump((kvm_cmd, serialized_nics, hvparams))
410
411
    self._WriteKVMRuntime(instance.name, serialized_form)

Guido Trotter's avatar
Guido Trotter committed
412
  def _LoadKVMRuntime(self, instance, serialized_runtime=None):
413
414
415
    """Load an instance's KVM runtime

    """
Guido Trotter's avatar
Guido Trotter committed
416
417
418
    if not serialized_runtime:
      serialized_runtime = self._ReadKVMRuntime(instance.name)
    loaded_runtime = serializer.Load(serialized_runtime)
419
    kvm_cmd, serialized_nics, hvparams = loaded_runtime
420
    kvm_nics = [objects.NIC.FromDict(snic) for snic in serialized_nics]
421
    return (kvm_cmd, kvm_nics, hvparams)
422

Guido Trotter's avatar
Guido Trotter committed
423
  def _ExecuteKVMRuntime(self, instance, kvm_runtime, incoming=None):
424
425
    """Execute a KVM cmd, after completing it with some last minute data

Guido Trotter's avatar
Guido Trotter committed
426
427
428
    @type incoming: tuple of strings
    @param incoming: (target_host_ip, port)

429
    """
430
431
    pidfile, pid, alive = self._InstancePidAlive(instance.name)
    if alive:
432
433
434
435
436
      raise errors.HypervisorError("Failed to start instance %s: %s" %
                                   (instance.name, "already running"))

    temp_files = []

437
    kvm_cmd, kvm_nics, hvparams = kvm_runtime
438
439
440
441

    if not kvm_nics:
      kvm_cmd.extend(['-net', 'none'])
    else:
442
443
444
445
446
447
      nic_type = hvparams[constants.HV_NIC_TYPE]
      if nic_type == constants.HT_NIC_PARAVIRTUAL:
        nic_model = "model=virtio"
      else:
        nic_model = "model=%s" % nic_type

448
      for nic_seq, nic in enumerate(kvm_nics):
449
        nic_val = "nic,macaddr=%s,%s" % (nic.mac, nic_model)
450
451
452
453
454
        script = self._WriteNetScript(instance, nic_seq, nic)
        kvm_cmd.extend(['-net', nic_val])
        kvm_cmd.extend(['-net', 'tap,script=%s' % script])
        temp_files.append(script)

Guido Trotter's avatar
Guido Trotter committed
455
456
457
458
    if incoming:
      target, port = incoming
      kvm_cmd.extend(['-incoming', 'tcp:%s:%s' % (target, port)])

Guido Trotter's avatar
Guido Trotter committed
459
460
461
462
463
464
465
466
467
468
    result = utils.RunCmd(kvm_cmd)
    if result.failed:
      raise errors.HypervisorError("Failed to start instance %s: %s (%s)" %
                                   (instance.name, result.fail_reason,
                                    result.output))

    if not utils.IsProcessAlive(utils.ReadPidFile(pidfile)):
      raise errors.HypervisorError("Failed to start instance %s: %s" %
                                   (instance.name))

469
470
    for filename in temp_files:
      utils.RemoveFile(filename)
Guido Trotter's avatar
Guido Trotter committed
471

472
  def StartInstance(self, instance, block_devices):
473
474
475
    """Start an instance.

    """
476
477
    pidfile, pid, alive = self._InstancePidAlive(instance.name)
    if alive:
478
479
480
      raise errors.HypervisorError("Failed to start instance %s: %s" %
                                   (instance.name, "already running"))

481
    kvm_runtime = self._GenerateKVMRuntime(instance, block_devices)
482
    self._SaveKVMRuntime(instance, kvm_runtime)
483
484
    self._ExecuteKVMRuntime(instance, kvm_runtime)

485
486
487
488
489
490
491
492
493
494
495
496
  def _CallMonitorCommand(self, instance_name, command):
    """Invoke a command on the instance monitor.

    """
    socat = ("echo %s | %s STDIO UNIX-CONNECT:%s" %
             (utils.ShellQuote(command),
              constants.SOCAT_PATH,
              utils.ShellQuote(self._InstanceMonitor(instance_name))))
    result = utils.RunCmd(socat)
    if result.failed:
      msg = ("Failed to send command '%s' to instance %s."
             " output: %s, error: %s, fail_reason: %s" %
497
498
             (command, instance_name,
              result.stdout, result.stderr, result.fail_reason))
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
      raise errors.HypervisorError(msg)

    return result

  def _RetryInstancePowerdown(self, instance, pid, timeout=30):
    """Wait for an instance  to power down.

    """
    # Wait up to $timeout seconds
    end = time.time() + timeout
    wait = 1
    while time.time() < end and utils.IsProcessAlive(pid):
      self._CallMonitorCommand(instance.name, 'system_powerdown')
      time.sleep(wait)
      # Make wait time longer for next try
      if wait < 5:
        wait *= 1.3

Guido Trotter's avatar
Guido Trotter committed
517
518
519
520
  def StopInstance(self, instance, force=False):
    """Stop an instance.

    """
521
522
    pidfile, pid, alive = self._InstancePidAlive(instance.name)
    if pid > 0 and alive:
523
      if force or not instance.hvparams[constants.HV_ACPI]:
Guido Trotter's avatar
Guido Trotter committed
524
525
        utils.KillProcess(pid)
      else:
526
        self._RetryInstancePowerdown(instance, pid)
Guido Trotter's avatar
Guido Trotter committed
527
528

    if not utils.IsProcessAlive(pid):
529
      self._RemoveInstanceRuntimeFiles(pidfile, instance.name)
530
531
532
      return True
    else:
      return False
Guido Trotter's avatar
Guido Trotter committed
533
534
535
536
537
538
539
540

  def RebootInstance(self, instance):
    """Reboot an instance.

    """
    # For some reason if we do a 'send-key ctrl-alt-delete' to the control
    # socket the instance will stop, but now power up again. So we'll resort
    # to shutdown and restart.
541
542
543
544
    pidfile, pid, alive = self._InstancePidAlive(instance.name)
    if not alive:
      raise errors.HypervisorError("Failed to reboot instance %s: not running" %
                                             (instance.name))
Guido Trotter's avatar
Guido Trotter committed
545
546
547
548
549
550
551
552
553
    # StopInstance will delete the saved KVM runtime so:
    # ...first load it...
    kvm_runtime = self._LoadKVMRuntime(instance)
    # ...now we can safely call StopInstance...
    if not self.StopInstance(instance):
      self.StopInstance(instance, force=True)
    # ...and finally we can save it again, and execute it...
    self._SaveKVMRuntime(instance, kvm_runtime)
    self._ExecuteKVMRuntime(instance, kvm_runtime)
Guido Trotter's avatar
Guido Trotter committed
554

Guido Trotter's avatar
Guido Trotter committed
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
  def MigrationInfo(self, instance):
    """Get instance information to perform a migration.

    @type instance: L{objects.Instance}
    @param instance: instance to be migrated
    @rtype: string
    @return: content of the KVM runtime file

    """
    return self._ReadKVMRuntime(instance.name)

  def AcceptInstance(self, instance, info, target):
    """Prepare to accept an instance.

    @type instance: L{objects.Instance}
    @param instance: instance to be accepted
    @type info: string
    @param info: content of the KVM runtime file on the source node
    @type target: string
    @param target: target host (usually ip), on this node

    """
    kvm_runtime = self._LoadKVMRuntime(instance, serialized_runtime=info)
    incoming_address = (target, constants.KVM_MIGRATION_PORT)
    self._ExecuteKVMRuntime(instance, kvm_runtime, incoming=incoming_address)

  def FinalizeMigration(self, instance, info, success):
    """Finalize an instance migration.

    Stop the incoming mode KVM.

    @type instance: L{objects.Instance}
    @param instance: instance whose migration is being aborted

    """
    if success:
      self._WriteKVMRuntime(instance.name, info)
    else:
      self.StopInstance(instance, force=True)

  def MigrateInstance(self, instance_name, target, live):
    """Migrate an instance to a target node.

    The migration will not be attempted if the instance is not
    currently running.

    @type instance_name: string
    @param instance_name: name of the instance to be migrated
    @type target: string
    @param target: ip address of the target node
    @type live: boolean
    @param live: perform a live migration

    """
    pidfile, pid, alive = self._InstancePidAlive(instance_name)
    if not alive:
      raise errors.HypervisorError("Instance not running, cannot migrate")

    if not live:
      self._CallMonitorCommand(instance_name, 'stop')

    migrate_command = ('migrate -d tcp:%s:%s' %
                       (target, constants.KVM_MIGRATION_PORT))
    self._CallMonitorCommand(instance_name, migrate_command)

    info_command = 'info migrate'
    done = False
    while not done:
      result = self._CallMonitorCommand(instance_name, info_command)
      match = self._MIGRATION_STATUS_RE.search(result.stdout)
      if not match:
        raise errors.HypervisorError("Unknown 'info migrate' result: %s" %
                                     result.stdout)
      else:
        status = match.group(1)
        if status == 'completed':
          done = True
        elif status == 'active':
          time.sleep(2)
634
635
636
637
638
        elif status == 'failed' or status == 'cancelled':
          if not live:
            self._CallMonitorCommand(instance_name, 'cont')
          raise errors.HypervisorError("Migration %s at the kvm level" %
                                       status)
Guido Trotter's avatar
Guido Trotter committed
639
640
641
642
643
        else:
          logging.info("KVM: unknown migration status '%s'" % status)
          time.sleep(2)

    utils.KillProcess(pid)
644
    self._RemoveInstanceRuntimeFiles(pidfile, instance_name)
Guido Trotter's avatar
Guido Trotter committed
645

Guido Trotter's avatar
Guido Trotter committed
646
647
648
  def GetNodeInfo(self):
    """Return information about the node.

649
650
    This is just a wrapper over the base GetLinuxNodeInfo method.

Iustin Pop's avatar
Iustin Pop committed
651
652
653
654
    @return: a dict with the following keys (values in MiB):
          - memory_total: the total memory size on the node
          - memory_free: the available memory on the node for instances
          - memory_dom0: the memory used by the node itself, if available
Guido Trotter's avatar
Guido Trotter committed
655
656

    """
657
    return self.GetLinuxNodeInfo()
Guido Trotter's avatar
Guido Trotter committed
658

659
  @classmethod
660
  def GetShellCommandForConsole(cls, instance, hvparams, beparams):
Guido Trotter's avatar
Guido Trotter committed
661
662
663
    """Return a command for connecting to the console of an instance.

    """
664
665
666
667
668
669
670
671
672
673
674
675
676
    if hvparams[constants.HV_SERIAL_CONSOLE]:
      # FIXME: The socat shell is not perfect. In particular the way we start
      # it ctrl+c will close it, rather than being passed to the other end.
      # On the other hand if we pass the option 'raw' (or ignbrk=1) there
      # will be no way of exiting socat (except killing it from another shell)
      # and ctrl+c doesn't work anyway, printing ^C rather than being
      # interpreted by kvm. For now we'll leave it this way, which at least
      # allows a minimal interaction and changes on the machine.
      shell_command = ("%s STDIO,echo=0,icanon=0 UNIX-CONNECT:%s" %
                       (constants.SOCAT_PATH,
                        utils.ShellQuote(cls._InstanceSerial(instance.name))))
    else:
      shell_command = "echo 'No serial shell for instance %s'" % instance.name
677
678
679

    vnc_bind_address = hvparams[constants.HV_VNC_BIND_ADDRESS]
    if vnc_bind_address:
680
681
      if instance.network_port > constants.VNC_BASE_PORT:
        display = instance.network_port - constants.VNC_BASE_PORT
682
683
684
685
686
687
        vnc_command = ("echo 'Instance has VNC listening on %s:%d"
                       " (display: %d)'" % (vnc_bind_address,
                                            instance.network_port,
                                            display))
        shell_command = "%s; %s" % (vnc_command, shell_command)

688
    return shell_command
Guido Trotter's avatar
Guido Trotter committed
689
690
691
692
693
694
695
696
697

  def Verify(self):
    """Verify the hypervisor.

    Check that the binary exists.

    """
    if not os.path.exists(constants.KVM_PATH):
      return "The kvm binary ('%s') does not exist." % constants.KVM_PATH
Guido Trotter's avatar
Guido Trotter committed
698
699
700
    if not os.path.exists(constants.SOCAT_PATH):
      return "The socat binary ('%s') does not exist." % constants.SOCAT_PATH

701
702
703
704
705
706
707
708
709
710

  @classmethod
  def CheckParameterSyntax(cls, hvparams):
    """Check the given parameters for validity.

    @type hvparams:  dict
    @param hvparams: dictionary with parameter names/value
    @raise errors.HypervisorError: when a parameter is not valid

    """
711
    super(KVMHypervisor, cls).CheckParameterSyntax(hvparams)
712

713
714
715
    kernel_path = hvparams[constants.HV_KERNEL_PATH]
    if kernel_path:
      if not hvparams[constants.HV_ROOT_PATH]:
716
717
        raise errors.HypervisorError("Need a root partition for the instance,"
                                     " if a kernel is defined")
718

719
720
721
722
723
    if (hvparams[constants.HV_VNC_X509_VERIFY] and
        not hvparams[constants.HV_VNC_X509]):
      raise errors.HypervisorError("%s must be defined, if %s is" %
                                   (constants.HV_VNC_X509,
                                    constants.HV_VNC_X509_VERIFY))
724
725
726

    boot_order = hvparams[constants.HV_BOOT_ORDER]

727
728
    if (boot_order == constants.HT_BO_CDROM and
        not hvparams[constants.HV_CDROM_IMAGE_PATH]):
729
730
      raise errors.HypervisorError("Cannot boot from cdrom without an"
                                   " ISO path")
731
732
    if (boot_order == constants.HT_BO_NETWORK and
        hvparams[constants.HV_NIC_TYPE] == constants.HT_NIC_PARAVIRTUAL):
Guido Trotter's avatar
Guido Trotter committed
733
      raise errors.HypervisorError("Cannot boot from a paravirtual NIC. Please"
734
                                   " change the NIC type.")
Iustin Pop's avatar
Iustin Pop committed
735
736
737
738
739
740
741

  @classmethod
  def PowercycleNode(cls):
    """KVM powercycle, just a wrapper over Linux powercycle.

    """
    cls.LinuxPowercycle()