hv_kvm.py 27.4 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
    constants.HV_DISK_CACHE:
      hv_base.ParamInSet(True, constants.HT_VALID_CACHE_TYPES),
79
    }
80

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

84
85
  _KVM_NETWORK_SCRIPT = constants.SYSCONFDIR + "/ganeti/kvm-vif-bridge"

86
87
88
89
  ANCILLARY_FILES = [
    _KVM_NETWORK_SCRIPT,
    ]

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

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

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

    """
112
    return '%s/%s.monitor' % (cls._CTRL_DIR, instance_name)
113

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

    """
119
    return '%s/%s.serial' % (cls._CTRL_DIR, instance_name)
120

121
122
123
124
125
126
127
  @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.

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

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

    """
138
    return '%s/%s.runtime' % (cls._CONF_DIR, instance_name)
139

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

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

Iustin Pop's avatar
Iustin Pop committed
224
225
    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
226
227
228
229

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

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

Iustin Pop's avatar
Iustin Pop committed
238
239
240
    @param instance_name: the instance name

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

    """
243
244
    pidfile, pid, alive = self._InstancePidAlive(instance_name)
    if not alive:
Guido Trotter's avatar
Guido Trotter committed
245
246
247
248
      return None

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

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

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

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

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

    return data

288
  def _GenerateKVMRuntime(self, instance, block_devices):
289
    """Generate KVM information to start an instance.
Guido Trotter's avatar
Guido Trotter committed
290
291

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

304
    hvp = instance.hvparams
305
306
307
    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
308
309
310

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

312
    disk_type = hvp[constants.HV_DISK_TYPE]
313
314
315
316
    if disk_type == constants.HT_DISK_PARAVIRTUAL:
      if_val = ',if=virtio'
    else:
      if_val = ',if=%s' % disk_type
317
318
319
320
321
322
    # Cache mode
    disk_cache = hvp[constants.HV_DISK_CACHE]
    if disk_cache != constants.HT_CACHE_DEFAULT:
      cache_val = ",cache=%s" % disk_cache
    else:
      cache_val = ""
323
    for cfdev, dev_path in block_devices:
324
325
326
      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
327
      # TODO: handle FD_LOOP and FD_BLKTAP (?)
328
      if boot_disk:
329
        kvm_cmd.extend(['-boot', 'c'])
Guido Trotter's avatar
Guido Trotter committed
330
        boot_val = ',boot=on'
Guido Trotter's avatar
Guido Trotter committed
331
        # We only boot from the first disk
332
        boot_disk = False
Guido Trotter's avatar
Guido Trotter committed
333
334
335
      else:
        boot_val = ''

336
337
      drive_val = 'file=%s,format=raw%s%s%s' % (dev_path, if_val, boot_val,
                                                cache_val)
Guido Trotter's avatar
Guido Trotter committed
338
339
      kvm_cmd.extend(['-drive', drive_val])

340
    iso_image = hvp[constants.HV_CDROM_IMAGE_PATH]
341
    if iso_image:
342
      options = ',format=raw,media=cdrom'
343
      if boot_cdrom:
344
        kvm_cmd.extend(['-boot', 'd'])
345
        options = '%s,boot=on' % options
346
347
      else:
        options = '%s,if=virtio' % options
348
349
350
      drive_val = 'file=%s%s' % (iso_image, options)
      kvm_cmd.extend(['-drive', drive_val])

351
    kernel_path = hvp[constants.HV_KERNEL_PATH]
352
353
    if kernel_path:
      kvm_cmd.extend(['-kernel', kernel_path])
354
      initrd_path = hvp[constants.HV_INITRD_PATH]
355
356
      if initrd_path:
        kvm_cmd.extend(['-initrd', initrd_path])
357
358
359
360
361
      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
362

363
    mouse_type = hvp[constants.HV_USB_MOUSE]
364
365
366
367
    if mouse_type:
      kvm_cmd.extend(['-usb'])
      kvm_cmd.extend(['-usbdevice', mouse_type])

368
    vnc_bind_address = hvp[constants.HV_VNC_BIND_ADDRESS]
369
    if vnc_bind_address:
370
      if utils.IsValidIP(vnc_bind_address):
371
372
        if instance.network_port > constants.VNC_BASE_PORT:
          display = instance.network_port - constants.VNC_BASE_PORT
373
374
375
          if vnc_bind_address == '0.0.0.0':
            vnc_arg = ':%d' % (display)
          else:
376
            vnc_arg = '%s:%d' % (vnc_bind_address, display)
377
        else:
378
          logging.error("Network port is not a valid VNC display (%d < %d)."
Iustin Pop's avatar
Iustin Pop committed
379
380
                        " Not starting VNC", instance.network_port,
                        constants.VNC_BASE_PORT)
381
          vnc_arg = 'none'
382
383
384
385

        # 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 = ''
386
        if hvp[constants.HV_VNC_TLS]:
387
          vnc_append = '%s,tls' % vnc_append
388
          if hvp[constants.HV_VNC_X509_VERIFY]:
389
            vnc_append = '%s,x509verify=%s' % (vnc_append,
390
391
                                               hvp[constants.HV_VNC_X509])
          elif hvp[constants.HV_VNC_X509]:
392
            vnc_append = '%s,x509=%s' % (vnc_append,
393
                                         hvp[constants.HV_VNC_X509])
394
395
396
        if hvp[constants.HV_VNC_PASSWORD_FILE]:
          vnc_append = '%s,password' % vnc_append

397
398
        vnc_arg = '%s%s' % (vnc_arg, vnc_append)

399
      else:
400
401
        vnc_arg = 'unix:%s/%s.vnc' % (vnc_bind_address, instance.name)

402
      kvm_cmd.extend(['-vnc', vnc_arg])
403
404
405
    else:
      kvm_cmd.extend(['-nographic'])

406
407
    monitor_dev = ("unix:%s,server,nowait" %
                   self._InstanceMonitor(instance.name))
Guido Trotter's avatar
Guido Trotter committed
408
    kvm_cmd.extend(['-monitor', monitor_dev])
409
410
411
    if hvp[constants.HV_SERIAL_CONSOLE]:
      serial_dev = ('unix:%s,server,nowait' %
                    self._InstanceSerial(instance.name))
412
413
414
      kvm_cmd.extend(['-serial', serial_dev])
    else:
      kvm_cmd.extend(['-serial', 'none'])
Guido Trotter's avatar
Guido Trotter committed
415

416
417
418
    if hvp[constants.HV_USE_LOCALTIME]:
      kvm_cmd.extend(['-localtime'])

419
420
421
    # 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
422
    hvparams = hvp
423

424
    return (kvm_cmd, kvm_nics, hvparams)
425

426
427
428
429
430
431
432
  def _WriteKVMRuntime(self, instance_name, data):
    """Write an instance's KVM runtime

    """
    try:
      utils.WriteFile(self._InstanceKVMRuntime(instance_name),
                      data=data)
433
    except EnvironmentError, err:
434
435
436
437
438
439
440
441
      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))
442
    except EnvironmentError, err:
443
444
445
446
447
448
449
      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

    """
450
    kvm_cmd, kvm_nics, hvparams = kvm_runtime
451
    serialized_nics = [nic.ToDict() for nic in kvm_nics]
452
    serialized_form = serializer.Dump((kvm_cmd, serialized_nics, hvparams))
453
454
    self._WriteKVMRuntime(instance.name, serialized_form)

Guido Trotter's avatar
Guido Trotter committed
455
  def _LoadKVMRuntime(self, instance, serialized_runtime=None):
456
457
458
    """Load an instance's KVM runtime

    """
Guido Trotter's avatar
Guido Trotter committed
459
460
461
    if not serialized_runtime:
      serialized_runtime = self._ReadKVMRuntime(instance.name)
    loaded_runtime = serializer.Load(serialized_runtime)
462
    kvm_cmd, serialized_nics, hvparams = loaded_runtime
463
    kvm_nics = [objects.NIC.FromDict(snic) for snic in serialized_nics]
464
    return (kvm_cmd, kvm_nics, hvparams)
465

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

Guido Trotter's avatar
Guido Trotter committed
469
470
471
    @type incoming: tuple of strings
    @param incoming: (target_host_ip, port)

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

    temp_files = []

481
    kvm_cmd, kvm_nics, hvparams = kvm_runtime
482
483
484
485

    if not kvm_nics:
      kvm_cmd.extend(['-net', 'none'])
    else:
486
487
488
489
490
491
      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

492
      for nic_seq, nic in enumerate(kvm_nics):
493
        nic_val = "nic,macaddr=%s,%s" % (nic.mac, nic_model)
494
495
496
497
498
        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
499
500
501
502
    if incoming:
      target, port = incoming
      kvm_cmd.extend(['-incoming', 'tcp:%s:%s' % (target, port)])

503
504
505
506
507
508
509
510
511
    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
512
513
514
515
516
517
518
    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)):
519
      raise errors.HypervisorError("Failed to start instance %s" %
Guido Trotter's avatar
Guido Trotter committed
520
521
                                   (instance.name))

522
523
524
525
    if vnc_pwd:
      change_cmd = 'change vnc password %s' % vnc_pwd
      self._CallMonitorCommand(instance.name, change_cmd)

526
527
    for filename in temp_files:
      utils.RemoveFile(filename)
Guido Trotter's avatar
Guido Trotter committed
528

529
  def StartInstance(self, instance, block_devices):
530
531
532
    """Start an instance.

    """
533
534
    pidfile, pid, alive = self._InstancePidAlive(instance.name)
    if alive:
535
536
537
      raise errors.HypervisorError("Failed to start instance %s: %s" %
                                   (instance.name, "already running"))

538
    kvm_runtime = self._GenerateKVMRuntime(instance, block_devices)
539
    self._SaveKVMRuntime(instance, kvm_runtime)
540
541
    self._ExecuteKVMRuntime(instance, kvm_runtime)

542
543
544
545
546
547
548
549
550
551
552
553
  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" %
554
555
             (command, instance_name,
              result.stdout, result.stderr, result.fail_reason))
556
557
558
559
      raise errors.HypervisorError(msg)

    return result

560
  def StopInstance(self, instance, force=False, retry=False):
Guido Trotter's avatar
Guido Trotter committed
561
562
563
    """Stop an instance.

    """
564
565
    pidfile, pid, alive = self._InstancePidAlive(instance.name)
    if pid > 0 and alive:
566
      if force or not instance.hvparams[constants.HV_ACPI]:
Guido Trotter's avatar
Guido Trotter committed
567
568
        utils.KillProcess(pid)
      else:
569
        self._CallMonitorCommand(instance.name, 'system_powerdown')
Guido Trotter's avatar
Guido Trotter committed
570
571

    if not utils.IsProcessAlive(pid):
572
      self._RemoveInstanceRuntimeFiles(pidfile, instance.name)
573
574
575
      return True
    else:
      return False
Guido Trotter's avatar
Guido Trotter committed
576
577
578
579
580
581
582
583

  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.
584
585
    pidfile, pid, alive = self._InstancePidAlive(instance.name)
    if not alive:
586
587
      raise errors.HypervisorError("Failed to reboot instance %s:"
                                   " not running" % instance.name)
Guido Trotter's avatar
Guido Trotter committed
588
589
590
591
592
593
594
595
596
    # 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
597

Guido Trotter's avatar
Guido Trotter committed
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
  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)
621
    incoming_address = (target, instance.hvparams[constants.HV_MIGRATION_PORT])
Guido Trotter's avatar
Guido Trotter committed
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
    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)

638
  def MigrateInstance(self, instance, target, live):
Guido Trotter's avatar
Guido Trotter committed
639
640
641
642
643
    """Migrate an instance to a target node.

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

644
645
    @type instance: L{objects.Instance}
    @param instance: the instance to be migrated
Guido Trotter's avatar
Guido Trotter committed
646
647
648
649
650
651
    @type target: string
    @param target: ip address of the target node
    @type live: boolean
    @param live: perform a live migration

    """
652
    instance_name = instance.name
653
    port = instance.hvparams[constants.HV_MIGRATION_PORT]
Guido Trotter's avatar
Guido Trotter committed
654
655
656
657
    pidfile, pid, alive = self._InstancePidAlive(instance_name)
    if not alive:
      raise errors.HypervisorError("Instance not running, cannot migrate")

658
659
660
661
    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
662
663
664
    if not live:
      self._CallMonitorCommand(instance_name, 'stop')

665
    migrate_command = 'migrate -d tcp:%s:%s' % (target, port)
Guido Trotter's avatar
Guido Trotter committed
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
    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)
682
683
684
685
686
        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
687
        else:
Iustin Pop's avatar
Iustin Pop committed
688
          logging.info("KVM: unknown migration status '%s'", status)
Guido Trotter's avatar
Guido Trotter committed
689
690
691
          time.sleep(2)

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

Guido Trotter's avatar
Guido Trotter committed
694
695
696
  def GetNodeInfo(self):
    """Return information about the node.

697
698
    This is just a wrapper over the base GetLinuxNodeInfo method.

Iustin Pop's avatar
Iustin Pop committed
699
700
701
702
    @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
703
704

    """
705
    return self.GetLinuxNodeInfo()
Guido Trotter's avatar
Guido Trotter committed
706

707
  @classmethod
708
  def GetShellCommandForConsole(cls, instance, hvparams, beparams):
Guido Trotter's avatar
Guido Trotter committed
709
710
711
    """Return a command for connecting to the console of an instance.

    """
712
    if hvparams[constants.HV_SERIAL_CONSOLE]:
713
714
      shell_command = ("%s STDIO,%s UNIX-CONNECT:%s" %
                       (constants.SOCAT_PATH, cls._SocatUnixConsoleParams(),
715
716
717
                        utils.ShellQuote(cls._InstanceSerial(instance.name))))
    else:
      shell_command = "echo 'No serial shell for instance %s'" % instance.name
718
719
720

    vnc_bind_address = hvparams[constants.HV_VNC_BIND_ADDRESS]
    if vnc_bind_address:
721
722
      if instance.network_port > constants.VNC_BASE_PORT:
        display = instance.network_port - constants.VNC_BASE_PORT
723
724
725
726
727
728
        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)

729
    return shell_command
Guido Trotter's avatar
Guido Trotter committed
730
731
732
733
734
735
736
737
738

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

742
743
744
745
746
747
748
749
750
751

  @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

    """
752
    super(KVMHypervisor, cls).CheckParameterSyntax(hvparams)
753

754
755
756
    kernel_path = hvparams[constants.HV_KERNEL_PATH]
    if kernel_path:
      if not hvparams[constants.HV_ROOT_PATH]:
757
758
        raise errors.HypervisorError("Need a root partition for the instance,"
                                     " if a kernel is defined")
759

760
761
762
763
764
    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))
765
766
767

    boot_order = hvparams[constants.HV_BOOT_ORDER]

768
769
    if (boot_order == constants.HT_BO_CDROM and
        not hvparams[constants.HV_CDROM_IMAGE_PATH]):
770
771
      raise errors.HypervisorError("Cannot boot from cdrom without an"
                                   " ISO path")
772
773
    if (boot_order == constants.HT_BO_NETWORK and
        hvparams[constants.HV_NIC_TYPE] == constants.HT_NIC_PARAVIRTUAL):
Guido Trotter's avatar
Guido Trotter committed
774
      raise errors.HypervisorError("Cannot boot from a paravirtual NIC. Please"
775
                                   " change the NIC type.")
Iustin Pop's avatar
Iustin Pop committed
776
777
778
779
780
781
782

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

    """
    cls.LinuxPowercycle()