hv_kvm.py 27 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
  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,
Michael Hanselmann's avatar
Michael Hanselmann committed
58
59
60
61
    constants.HV_VNC_BIND_ADDRESS:
      (False, lambda x: (utils.IsValidIP(x) or utils.IsNormAbsPath(x)),
       "the VNC bind address must be either a valid IP address or an absolute"
       " pathname", None, None),
62
63
64
    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,
65
    constants.HV_VNC_PASSWORD_FILE: hv_base.OPT_FILE_CHECK,
66
    constants.HV_CDROM_IMAGE_PATH: hv_base.OPT_FILE_CHECK,
Michael Hanselmann's avatar
Michael Hanselmann committed
67
68
69
70
71
72
73
74
    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
    constants.HV_MIGRATION_PORT: hv_base.NET_PORT_CHECK,
76
    constants.HV_USE_LOCALTIME: hv_base.NO_CHECK,
77
    }
78

Guido Trotter's avatar
Guido Trotter committed
79
80
81
  _MIGRATION_STATUS_RE = re.compile('Migration\s+status:\s+(\w+)',
                                    re.M | re.I)

82
83
  _KVM_NETWORK_SCRIPT = constants.SYSCONFDIR + "/ganeti/kvm-vif-bridge"

84
85
86
87
  ANCILLARY_FILES = [
    _KVM_NETWORK_SCRIPT,
    ]

Guido Trotter's avatar
Guido Trotter committed
88
89
90
91
  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
92
    dirs = [(dname, constants.RUN_DIRS_MODE) for dname in self._DIRS]
Guido Trotter's avatar
Guido Trotter committed
93
    utils.EnsureDirs(dirs)
Guido Trotter's avatar
Guido Trotter committed
94

95
96
97
98
99
100
101
102
103
104
  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)

105
106
  @classmethod
  def _InstanceMonitor(cls, instance_name):
107
108
109
    """Returns the instance monitor socket name

    """
110
    return '%s/%s.monitor' % (cls._CTRL_DIR, instance_name)
111

112
113
  @classmethod
  def _InstanceSerial(cls, instance_name):
114
115
116
    """Returns the instance serial socket name

    """
117
    return '%s/%s.serial' % (cls._CTRL_DIR, instance_name)
118

119
120
121
122
123
124
125
  @staticmethod
  def _SocatUnixConsoleParams():
    """Returns the correct parameters for socat

    If we have a new-enough socat we can use raw mode with an escape character.

    """
126
    if constants.SOCAT_USE_ESCAPE:
127
128
129
130
      return "raw,echo=0,escape=%s" % constants.SOCAT_ESCAPE_CODE
    else:
      return "echo=0,icanon=0"

131
132
  @classmethod
  def _InstanceKVMRuntime(cls, instance_name):
133
134
135
    """Returns the instance KVM runtime filename

    """
136
    return '%s/%s.runtime' % (cls._CONF_DIR, instance_name)
137

138
139
140
141
142
143
144
145
146
147
  @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
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
  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
168
169
170
171
172
173
174
    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
175
176
    script.write("export INTERFACE=$1\n")
    # TODO: make this configurable at ./configure time
177
    script.write("if [ -x '%s' ]; then\n" % self._KVM_NETWORK_SCRIPT)
Guido Trotter's avatar
Guido Trotter committed
178
    script.write("  # Execute the user-specific vif file\n")
179
    script.write("  %s\n" % self._KVM_NETWORK_SCRIPT)
Guido Trotter's avatar
Guido Trotter committed
180
181
    script.write("else\n")
    script.write("  /sbin/ifconfig $INTERFACE 0.0.0.0 up\n")
Guido Trotter's avatar
Guido Trotter committed
182
183
184
185
186
    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")
187
      if nic.nicparams[constants.NIC_LINK]:
188
189
        script.write("  while /sbin/ip rule del dev $INTERFACE; do :; done\n")
        script.write("  /sbin/ip rule add dev $INTERFACE table $LINK\n")
190
        script.write("  /sbin/ip route replace $IP table $LINK proto static"
Guido Trotter's avatar
Guido Trotter committed
191
                     " dev $INTERFACE\n")
192
      else:
193
194
        script.write("  /sbin/ip route replace $IP proto static"
                     " dev $INTERFACE\n")
195
196
197
198
199
200
201
202
203
204
      interface_v4_conf = "/proc/sys/net/ipv4/conf/$INTERFACE"
      interface_v6_conf = "/proc/sys/net/ipv6/conf/$INTERFACE"
      script.write("  if [ -d %s ]; then\n" % interface_v4_conf)
      script.write("    echo 1 > %s/proxy_arp\n" % interface_v4_conf)
      script.write("    echo 1 > %s/forwarding\n" % interface_v4_conf)
      script.write("  fi\n")
      script.write("  if [ -d %s ]; then\n" % interface_v6_conf)
      script.write("    echo 1 > %s/proxy_ndp\n" % interface_v6_conf)
      script.write("    echo 1 > %s/forwarding\n" % interface_v6_conf)
      script.write("  fi\n")
Guido Trotter's avatar
Guido Trotter committed
205
206
207
208
209
    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')
Michael Hanselmann's avatar
Michael Hanselmann committed
210
211
212
213
    try:
      tmpfile.write(script.getvalue())
    finally:
      tmpfile.close()
Guido Trotter's avatar
Guido Trotter committed
214
215
216
217
218
219
    os.chmod(tmpfile_name, 0755)
    return tmpfile_name

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

Iustin Pop's avatar
Iustin Pop committed
220
221
    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
222
223
224
225

    """
    result = []
    for name in os.listdir(self._PIDS_DIR):
226
227
      filename = "%s/%s" % (self._PIDS_DIR, name)
      if utils.IsProcessAlive(utils.ReadPidFile(filename)):
Guido Trotter's avatar
Guido Trotter committed
228
229
230
231
232
233
        result.append(name)
    return result

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

Iustin Pop's avatar
Iustin Pop committed
234
235
236
    @param instance_name: the instance name

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

    """
239
240
    pidfile, pid, alive = self._InstancePidAlive(instance_name)
    if not alive:
Guido Trotter's avatar
Guido Trotter committed
241
242
243
244
      return None

    cmdline_file = "/proc/%s/cmdline" % pid
    try:
245
      cmdline = utils.ReadFile(cmdline_file)
246
    except EnvironmentError, err:
Guido Trotter's avatar
Guido Trotter committed
247
248
249
250
251
252
253
254
255
256
257
258
      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':
259
        memory = int(arg_list.pop(0))
Guido Trotter's avatar
Guido Trotter committed
260
      elif arg == '-smp':
261
        vcpus = int(arg_list.pop(0))
Guido Trotter's avatar
Guido Trotter committed
262
263
264
265
266
267

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

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

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

Guido Trotter's avatar
Guido Trotter committed
270
271
272
    """
    data = []
    for name in os.listdir(self._PIDS_DIR):
273
274
      filename = "%s/%s" % (self._PIDS_DIR, name)
      if utils.IsProcessAlive(utils.ReadPidFile(filename)):
275
276
277
278
279
280
        try:
          info = self.GetInstanceInfo(name)
        except errors.HypervisorError, err:
          continue
        if info:
          data.append(info)
Guido Trotter's avatar
Guido Trotter committed
281
282
283

    return data

284
  def _GenerateKVMRuntime(self, instance, block_devices):
285
    """Generate KVM information to start an instance.
Guido Trotter's avatar
Guido Trotter committed
286
287

    """
288
    pidfile, pid, alive = self._InstancePidAlive(instance.name)
Guido Trotter's avatar
Guido Trotter committed
289
290
    kvm = constants.KVM_PATH
    kvm_cmd = [kvm]
291
292
    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
293
294
295
296
    kvm_cmd.extend(['-pidfile', pidfile])
    # used just by the vnc server, if enabled
    kvm_cmd.extend(['-name', instance.name])
    kvm_cmd.extend(['-daemonize'])
297
    if not instance.hvparams[constants.HV_ACPI]:
Guido Trotter's avatar
Guido Trotter committed
298
299
      kvm_cmd.extend(['-no-acpi'])

300
    hvp = instance.hvparams
301
302
303
    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
304
305
306

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

308
    disk_type = hvp[constants.HV_DISK_TYPE]
309
310
311
312
    if disk_type == constants.HT_DISK_PARAVIRTUAL:
      if_val = ',if=virtio'
    else:
      if_val = ',if=%s' % disk_type
313
    for cfdev, dev_path in block_devices:
314
315
316
      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
317
      # TODO: handle FD_LOOP and FD_BLKTAP (?)
318
      if boot_disk:
319
        kvm_cmd.extend(['-boot', 'c'])
Guido Trotter's avatar
Guido Trotter committed
320
        boot_val = ',boot=on'
Guido Trotter's avatar
Guido Trotter committed
321
        # We only boot from the first disk
322
        boot_disk = False
Guido Trotter's avatar
Guido Trotter committed
323
324
325
      else:
        boot_val = ''

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

329
    iso_image = hvp[constants.HV_CDROM_IMAGE_PATH]
330
    if iso_image:
331
      options = ',format=raw,media=cdrom'
332
      if boot_cdrom:
333
        kvm_cmd.extend(['-boot', 'd'])
334
        options = '%s,boot=on' % options
335
336
      else:
        options = '%s,if=virtio' % options
337
338
339
      drive_val = 'file=%s%s' % (iso_image, options)
      kvm_cmd.extend(['-drive', drive_val])

340
    kernel_path = hvp[constants.HV_KERNEL_PATH]
341
342
    if kernel_path:
      kvm_cmd.extend(['-kernel', kernel_path])
343
      initrd_path = hvp[constants.HV_INITRD_PATH]
344
345
      if initrd_path:
        kvm_cmd.extend(['-initrd', initrd_path])
346
347
348
349
350
      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
351

352
    mouse_type = hvp[constants.HV_USB_MOUSE]
353
354
355
356
    if mouse_type:
      kvm_cmd.extend(['-usb'])
      kvm_cmd.extend(['-usbdevice', mouse_type])

357
    vnc_bind_address = hvp[constants.HV_VNC_BIND_ADDRESS]
358
    if vnc_bind_address:
359
      if utils.IsValidIP(vnc_bind_address):
360
361
        if instance.network_port > constants.VNC_BASE_PORT:
          display = instance.network_port - constants.VNC_BASE_PORT
362
363
364
          if vnc_bind_address == '0.0.0.0':
            vnc_arg = ':%d' % (display)
          else:
365
            vnc_arg = '%s:%d' % (vnc_bind_address, display)
366
        else:
367
368
369
          logging.error("Network port is not a valid VNC display (%d < %d)."
                        " Not starting VNC" %
                        (instance.network_port,
370
                         constants.VNC_BASE_PORT))
371
          vnc_arg = 'none'
372
373
374
375

        # 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 = ''
376
        if hvp[constants.HV_VNC_TLS]:
377
          vnc_append = '%s,tls' % vnc_append
378
          if hvp[constants.HV_VNC_X509_VERIFY]:
379
            vnc_append = '%s,x509verify=%s' % (vnc_append,
380
381
                                               hvp[constants.HV_VNC_X509])
          elif hvp[constants.HV_VNC_X509]:
382
            vnc_append = '%s,x509=%s' % (vnc_append,
383
                                         hvp[constants.HV_VNC_X509])
384
385
386
        if hvp[constants.HV_VNC_PASSWORD_FILE]:
          vnc_append = '%s,password' % vnc_append

387
388
        vnc_arg = '%s%s' % (vnc_arg, vnc_append)

389
      else:
390
391
        vnc_arg = 'unix:%s/%s.vnc' % (vnc_bind_address, instance.name)

392
      kvm_cmd.extend(['-vnc', vnc_arg])
393
394
395
    else:
      kvm_cmd.extend(['-nographic'])

396
397
    monitor_dev = ("unix:%s,server,nowait" %
                   self._InstanceMonitor(instance.name))
Guido Trotter's avatar
Guido Trotter committed
398
    kvm_cmd.extend(['-monitor', monitor_dev])
399
400
401
    if hvp[constants.HV_SERIAL_CONSOLE]:
      serial_dev = ('unix:%s,server,nowait' %
                    self._InstanceSerial(instance.name))
402
403
404
      kvm_cmd.extend(['-serial', serial_dev])
    else:
      kvm_cmd.extend(['-serial', 'none'])
Guido Trotter's avatar
Guido Trotter committed
405

406
407
408
    if hvp[constants.HV_USE_LOCALTIME]:
      kvm_cmd.extend(['-localtime'])

409
410
411
    # 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
412
    hvparams = hvp
413

414
    return (kvm_cmd, kvm_nics, hvparams)
415

416
417
418
419
420
421
422
  def _WriteKVMRuntime(self, instance_name, data):
    """Write an instance's KVM runtime

    """
    try:
      utils.WriteFile(self._InstanceKVMRuntime(instance_name),
                      data=data)
423
    except EnvironmentError, err:
424
425
426
427
428
429
430
431
      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))
432
    except EnvironmentError, err:
433
434
435
436
437
438
439
      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

    """
440
    kvm_cmd, kvm_nics, hvparams = kvm_runtime
441
    serialized_nics = [nic.ToDict() for nic in kvm_nics]
442
    serialized_form = serializer.Dump((kvm_cmd, serialized_nics, hvparams))
443
444
    self._WriteKVMRuntime(instance.name, serialized_form)

Guido Trotter's avatar
Guido Trotter committed
445
  def _LoadKVMRuntime(self, instance, serialized_runtime=None):
446
447
448
    """Load an instance's KVM runtime

    """
Guido Trotter's avatar
Guido Trotter committed
449
450
451
    if not serialized_runtime:
      serialized_runtime = self._ReadKVMRuntime(instance.name)
    loaded_runtime = serializer.Load(serialized_runtime)
452
    kvm_cmd, serialized_nics, hvparams = loaded_runtime
453
    kvm_nics = [objects.NIC.FromDict(snic) for snic in serialized_nics]
454
    return (kvm_cmd, kvm_nics, hvparams)
455

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

Guido Trotter's avatar
Guido Trotter committed
459
460
461
    @type incoming: tuple of strings
    @param incoming: (target_host_ip, port)

462
    """
463
    pidfile, pid, alive = self._InstancePidAlive(instance.name)
464
    hvp = instance.hvparams
465
    if alive:
466
467
468
469
470
      raise errors.HypervisorError("Failed to start instance %s: %s" %
                                   (instance.name, "already running"))

    temp_files = []

471
    kvm_cmd, kvm_nics, hvparams = kvm_runtime
472
473
474
475

    if not kvm_nics:
      kvm_cmd.extend(['-net', 'none'])
    else:
476
477
478
479
480
481
      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

482
      for nic_seq, nic in enumerate(kvm_nics):
483
        nic_val = "nic,macaddr=%s,%s" % (nic.mac, nic_model)
484
485
486
487
488
        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
489
490
491
492
    if incoming:
      target, port = incoming
      kvm_cmd.extend(['-incoming', 'tcp:%s:%s' % (target, port)])

493
494
495
496
497
498
499
500
501
    vnc_pwd_file = hvp[constants.HV_VNC_PASSWORD_FILE]
    vnc_pwd = None
    if vnc_pwd_file:
      try:
        vnc_pwd = utils.ReadFile(vnc_pwd_file)
      except EnvironmentError, err:
        raise errors.HypervisorError("Failed to open VNC password file %s: %s"
                                     % (vnc_pwd_file, err))

Guido Trotter's avatar
Guido Trotter committed
502
503
504
505
506
507
508
    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)):
509
      raise errors.HypervisorError("Failed to start instance %s" %
Guido Trotter's avatar
Guido Trotter committed
510
511
                                   (instance.name))

512
513
514
515
    if vnc_pwd:
      change_cmd = 'change vnc password %s' % vnc_pwd
      self._CallMonitorCommand(instance.name, change_cmd)

516
517
    for filename in temp_files:
      utils.RemoveFile(filename)
Guido Trotter's avatar
Guido Trotter committed
518

519
  def StartInstance(self, instance, block_devices):
520
521
522
    """Start an instance.

    """
523
524
    pidfile, pid, alive = self._InstancePidAlive(instance.name)
    if alive:
525
526
527
      raise errors.HypervisorError("Failed to start instance %s: %s" %
                                   (instance.name, "already running"))

528
    kvm_runtime = self._GenerateKVMRuntime(instance, block_devices)
529
    self._SaveKVMRuntime(instance, kvm_runtime)
530
531
    self._ExecuteKVMRuntime(instance, kvm_runtime)

532
533
534
535
536
537
538
539
540
541
542
543
  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" %
544
545
             (command, instance_name,
              result.stdout, result.stderr, result.fail_reason))
546
547
548
549
      raise errors.HypervisorError(msg)

    return result

550
  def StopInstance(self, instance, force=False, retry=False):
Guido Trotter's avatar
Guido Trotter committed
551
552
553
    """Stop an instance.

    """
554
555
    pidfile, pid, alive = self._InstancePidAlive(instance.name)
    if pid > 0 and alive:
556
      if force or not instance.hvparams[constants.HV_ACPI]:
Guido Trotter's avatar
Guido Trotter committed
557
558
        utils.KillProcess(pid)
      else:
559
        self._CallMonitorCommand(instance.name, 'system_powerdown')
Guido Trotter's avatar
Guido Trotter committed
560
561

    if not utils.IsProcessAlive(pid):
562
      self._RemoveInstanceRuntimeFiles(pidfile, instance.name)
563
564
565
      return True
    else:
      return False
Guido Trotter's avatar
Guido Trotter committed
566
567
568
569
570
571
572
573

  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.
574
575
    pidfile, pid, alive = self._InstancePidAlive(instance.name)
    if not alive:
576
577
      raise errors.HypervisorError("Failed to reboot instance %s:"
                                   " not running" % instance.name)
Guido Trotter's avatar
Guido Trotter committed
578
579
580
581
582
583
584
585
586
    # 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
587

Guido Trotter's avatar
Guido Trotter committed
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
  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)
611
    incoming_address = (target, instance.hvparams[constants.HV_MIGRATION_PORT])
Guido Trotter's avatar
Guido Trotter committed
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
    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)

628
  def MigrateInstance(self, instance, target, live):
Guido Trotter's avatar
Guido Trotter committed
629
630
631
632
633
    """Migrate an instance to a target node.

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

634
635
    @type instance: L{objects.Instance}
    @param instance: the instance to be migrated
Guido Trotter's avatar
Guido Trotter committed
636
637
638
639
640
641
    @type target: string
    @param target: ip address of the target node
    @type live: boolean
    @param live: perform a live migration

    """
642
    instance_name = instance.name
643
    port = instance.hvparams[constants.HV_MIGRATION_PORT]
Guido Trotter's avatar
Guido Trotter committed
644
645
646
647
    pidfile, pid, alive = self._InstancePidAlive(instance_name)
    if not alive:
      raise errors.HypervisorError("Instance not running, cannot migrate")

648
649
650
651
    if not utils.TcpPing(target, port, live_port_needed=True):
      raise errors.HypervisorError("Remote host %s not listening on port"
                                   " %s, cannot migrate" % (target, port))

Guido Trotter's avatar
Guido Trotter committed
652
653
654
    if not live:
      self._CallMonitorCommand(instance_name, 'stop')

655
    migrate_command = 'migrate -d tcp:%s:%s' % (target, port)
Guido Trotter's avatar
Guido Trotter committed
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
    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)
672
673
674
675
676
        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
677
678
679
680
681
        else:
          logging.info("KVM: unknown migration status '%s'" % status)
          time.sleep(2)

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

Guido Trotter's avatar
Guido Trotter committed
684
685
686
  def GetNodeInfo(self):
    """Return information about the node.

687
688
    This is just a wrapper over the base GetLinuxNodeInfo method.

Iustin Pop's avatar
Iustin Pop committed
689
690
691
692
    @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
693
694

    """
695
    return self.GetLinuxNodeInfo()
Guido Trotter's avatar
Guido Trotter committed
696

697
  @classmethod
698
  def GetShellCommandForConsole(cls, instance, hvparams, beparams):
Guido Trotter's avatar
Guido Trotter committed
699
700
701
    """Return a command for connecting to the console of an instance.

    """
702
    if hvparams[constants.HV_SERIAL_CONSOLE]:
703
704
      shell_command = ("%s STDIO,%s UNIX-CONNECT:%s" %
                       (constants.SOCAT_PATH, cls._SocatUnixConsoleParams(),
705
706
707
                        utils.ShellQuote(cls._InstanceSerial(instance.name))))
    else:
      shell_command = "echo 'No serial shell for instance %s'" % instance.name
708
709
710

    vnc_bind_address = hvparams[constants.HV_VNC_BIND_ADDRESS]
    if vnc_bind_address:
711
712
      if instance.network_port > constants.VNC_BASE_PORT:
        display = instance.network_port - constants.VNC_BASE_PORT
713
714
715
716
717
718
        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)

719
    return shell_command
Guido Trotter's avatar
Guido Trotter committed
720
721
722
723
724
725
726
727
728

  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
729
730
731
    if not os.path.exists(constants.SOCAT_PATH):
      return "The socat binary ('%s') does not exist." % constants.SOCAT_PATH

732
733
734
735
736
737
738
739
740
741

  @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

    """
742
    super(KVMHypervisor, cls).CheckParameterSyntax(hvparams)
743

744
745
746
    kernel_path = hvparams[constants.HV_KERNEL_PATH]
    if kernel_path:
      if not hvparams[constants.HV_ROOT_PATH]:
747
748
        raise errors.HypervisorError("Need a root partition for the instance,"
                                     " if a kernel is defined")
749

750
751
752
753
754
    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))
755
756
757

    boot_order = hvparams[constants.HV_BOOT_ORDER]

758
759
    if (boot_order == constants.HT_BO_CDROM and
        not hvparams[constants.HV_CDROM_IMAGE_PATH]):
760
761
      raise errors.HypervisorError("Cannot boot from cdrom without an"
                                   " ISO path")
762
763
    if (boot_order == constants.HT_BO_NETWORK and
        hvparams[constants.HV_NIC_TYPE] == constants.HT_NIC_PARAVIRTUAL):
Guido Trotter's avatar
Guido Trotter committed
764
      raise errors.HypervisorError("Cannot boot from a paravirtual NIC. Please"
765
                                   " change the NIC type.")
Iustin Pop's avatar
Iustin Pop committed
766
767
768
769
770
771
772

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

    """
    cls.LinuxPowercycle()