Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_kvm.py @ fe5b0c42

History | View | Annotate | Download (27 kB)

1
#
2
#
3

    
4
# Copyright (C) 2008 Google Inc.
5
#
6
# This program is free software; you can redistribute it and/or modify
7
# it under the terms of the GNU General Public License as published by
8
# the Free Software Foundation; either version 2 of the License, or
9
# (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful, but
12
# WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14
# General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19
# 02110-1301, USA.
20

    
21

    
22
"""KVM hypervisor
23

24
"""
25

    
26
import os
27
import os.path
28
import re
29
import tempfile
30
import time
31
import logging
32
from cStringIO import StringIO
33

    
34
from ganeti import utils
35
from ganeti import constants
36
from ganeti import errors
37
from ganeti import serializer
38
from ganeti import objects
39
from ganeti.hypervisor import hv_base
40

    
41

    
42
class KVMHypervisor(hv_base.BaseHypervisor):
43
  """KVM hypervisor interface"""
44

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

    
51
  PARAMETERS = {
52
    constants.HV_KERNEL_PATH: hv_base.OPT_FILE_CHECK,
53
    constants.HV_INITRD_PATH: hv_base.OPT_FILE_CHECK,
54
    constants.HV_ROOT_PATH: hv_base.NO_CHECK,
55
    constants.HV_KERNEL_ARGS: hv_base.NO_CHECK,
56
    constants.HV_ACPI: hv_base.NO_CHECK,
57
    constants.HV_SERIAL_CONSOLE: hv_base.NO_CHECK,
58
    constants.HV_VNC_BIND_ADDRESS:
59
      (False, lambda x: (utils.IsValidIP(x) or utils.IsNormAbsPath(x)),
60
       "the VNC bind address must be either a valid IP address or an absolute"
61
       " pathname", None, None),
62
    constants.HV_VNC_TLS: hv_base.NO_CHECK,
63
    constants.HV_VNC_X509: hv_base.OPT_DIR_CHECK,
64
    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,
67
    constants.HV_BOOT_ORDER:
68
      hv_base.ParamInSet(True, constants.HT_KVM_VALID_BO_TYPES),
69
    constants.HV_NIC_TYPE:
70
      hv_base.ParamInSet(True, constants.HT_KVM_VALID_NIC_TYPES),
71
    constants.HV_DISK_TYPE:
72
      hv_base.ParamInSet(True, constants.HT_KVM_VALID_DISK_TYPES),
73
    constants.HV_USB_MOUSE:
74
      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

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

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

    
84
  ANCILLARY_FILES = [
85
    _KVM_NETWORK_SCRIPT,
86
    ]
87

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

    
95
  def _InstancePidAlive(self, instance_name):
96
    """Returns the instance pid and pidfile
97

98
    """
99
    pidfile = "%s/%s" % (self._PIDS_DIR, instance_name)
100
    pid = utils.ReadPidFile(pidfile)
101
    alive = utils.IsProcessAlive(pid)
102

    
103
    return (pidfile, pid, alive)
104

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

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

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

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

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

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

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

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

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

    
138
  @classmethod
139
  def _RemoveInstanceRuntimeFiles(cls, pidfile, instance_name):
140
    """Removes an instance's rutime sockets/files.
141

142
    """
143
    utils.RemoveFile(pidfile)
144
    utils.RemoveFile(cls._InstanceMonitor(instance_name))
145
    utils.RemoveFile(cls._InstanceSerial(instance_name))
146
    utils.RemoveFile(cls._InstanceKVMRuntime(instance_name))
147

    
148
  def _WriteNetScript(self, instance, seq, nic):
149
    """Write a script to connect a net interface to the proper bridge.
150

151
    This can be used by any qemu-type hypervisor.
152

153
    @param instance: instance we're acting on
154
    @type instance: instance object
155
    @param seq: nic sequence number
156
    @type seq: int
157
    @param nic: nic we're acting on
158
    @type nic: nic object
159
    @return: netscript file name
160
    @rtype: string
161

162
    """
163
    script = StringIO()
164
    script.write("#!/bin/sh\n")
165
    script.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
166
    script.write("export INSTANCE=%s\n" % instance.name)
167
    script.write("export MAC=%s\n" % nic.mac)
168
    if nic.ip:
169
      script.write("export IP=%s\n" % nic.ip)
170
    script.write("export MODE=%s\n" % nic.nicparams[constants.NIC_MODE])
171
    if nic.nicparams[constants.NIC_LINK]:
172
      script.write("export LINK=%s\n" % nic.nicparams[constants.NIC_LINK])
173
    if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
174
      script.write("export BRIDGE=%s\n" % nic.nicparams[constants.NIC_LINK])
175
    script.write("export INTERFACE=$1\n")
176
    # TODO: make this configurable at ./configure time
177
    script.write("if [ -x '%s' ]; then\n" % self._KVM_NETWORK_SCRIPT)
178
    script.write("  # Execute the user-specific vif file\n")
179
    script.write("  %s\n" % self._KVM_NETWORK_SCRIPT)
180
    script.write("else\n")
181
    script.write("  /sbin/ifconfig $INTERFACE 0.0.0.0 up\n")
182
    if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
183
      script.write("  # Connect the interface to the bridge\n")
184
      script.write("  /usr/sbin/brctl addif $BRIDGE $INTERFACE\n")
185
    elif nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_ROUTED:
186
      script.write("  # Route traffic targeted at the IP to the interface\n")
187
      if nic.nicparams[constants.NIC_LINK]:
188
        script.write("  while /sbin/ip rule del dev $INTERFACE; do :; done\n")
189
        script.write("  /sbin/ip rule add dev $INTERFACE table $LINK\n")
190
        script.write("  /sbin/ip route replace $IP table $LINK proto static"
191
                     " dev $INTERFACE\n")
192
      else:
193
        script.write("  /sbin/ip route replace $IP proto static"
194
                     " dev $INTERFACE\n")
195
      interface_v4_conf = "/proc/sys/net/ipv4/conf/$INTERFACE"
196
      interface_v6_conf = "/proc/sys/net/ipv6/conf/$INTERFACE"
197
      script.write("  if [ -d %s ]; then\n" % interface_v4_conf)
198
      script.write("    echo 1 > %s/proxy_arp\n" % interface_v4_conf)
199
      script.write("    echo 1 > %s/forwarding\n" % interface_v4_conf)
200
      script.write("  fi\n")
201
      script.write("  if [ -d %s ]; then\n" % interface_v6_conf)
202
      script.write("    echo 1 > %s/proxy_ndp\n" % interface_v6_conf)
203
      script.write("    echo 1 > %s/forwarding\n" % interface_v6_conf)
204
      script.write("  fi\n")
205
    script.write("fi\n\n")
206
    # As much as we'd like to put this in our _ROOT_DIR, that will happen to be
207
    # mounted noexec sometimes, so we'll have to find another place.
208
    (tmpfd, tmpfile_name) = tempfile.mkstemp()
209
    tmpfile = os.fdopen(tmpfd, 'w')
210
    try:
211
      tmpfile.write(script.getvalue())
212
    finally:
213
      tmpfile.close()
214
    os.chmod(tmpfile_name, 0755)
215
    return tmpfile_name
216

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

220
    We can do this by listing our live instances directory and
221
    checking whether the associated kvm process is still alive.
222

223
    """
224
    result = []
225
    for name in os.listdir(self._PIDS_DIR):
226
      filename = "%s/%s" % (self._PIDS_DIR, name)
227
      if utils.IsProcessAlive(utils.ReadPidFile(filename)):
228
        result.append(name)
229
    return result
230

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

234
    @param instance_name: the instance name
235

236
    @return: tuple (name, id, memory, vcpus, stat, times)
237

238
    """
239
    pidfile, pid, alive = self._InstancePidAlive(instance_name)
240
    if not alive:
241
      return None
242

    
243
    cmdline_file = "/proc/%s/cmdline" % pid
244
    try:
245
      cmdline = utils.ReadFile(cmdline_file)
246
    except EnvironmentError, err:
247
      raise errors.HypervisorError("Failed to list instance %s: %s" %
248
                                   (instance_name, err))
249

    
250
    memory = 0
251
    vcpus = 0
252
    stat = "---b-"
253
    times = "0"
254

    
255
    arg_list = cmdline.split('\x00')
256
    while arg_list:
257
      arg =  arg_list.pop(0)
258
      if arg == '-m':
259
        memory = int(arg_list.pop(0))
260
      elif arg == '-smp':
261
        vcpus = int(arg_list.pop(0))
262

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

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

268
    @return: list of tuples (name, id, memory, vcpus, stat, times)
269

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

    
282
    return data
283

    
284
  def _GenerateKVMRuntime(self, instance, block_devices):
285
    """Generate KVM information to start an instance.
286

287
    """
288
    pidfile, pid, alive = self._InstancePidAlive(instance.name)
289
    kvm = constants.KVM_PATH
290
    kvm_cmd = [kvm]
291
    kvm_cmd.extend(['-m', instance.beparams[constants.BE_MEMORY]])
292
    kvm_cmd.extend(['-smp', instance.beparams[constants.BE_VCPUS]])
293
    kvm_cmd.extend(['-pidfile', pidfile])
294
    # used just by the vnc server, if enabled
295
    kvm_cmd.extend(['-name', instance.name])
296
    kvm_cmd.extend(['-daemonize'])
297
    if not instance.hvparams[constants.HV_ACPI]:
298
      kvm_cmd.extend(['-no-acpi'])
299

    
300
    hvp = instance.hvparams
301
    boot_disk = hvp[constants.HV_BOOT_ORDER] == constants.HT_BO_DISK
302
    boot_cdrom = hvp[constants.HV_BOOT_ORDER] == constants.HT_BO_CDROM
303
    boot_network = hvp[constants.HV_BOOT_ORDER] == constants.HT_BO_NETWORK
304

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

    
308
    disk_type = hvp[constants.HV_DISK_TYPE]
309
    if disk_type == constants.HT_DISK_PARAVIRTUAL:
310
      if_val = ',if=virtio'
311
    else:
312
      if_val = ',if=%s' % disk_type
313
    for cfdev, dev_path in block_devices:
314
      if cfdev.mode != constants.DISK_RDWR:
315
        raise errors.HypervisorError("Instance has read-only disks which"
316
                                     " are not supported by KVM")
317
      # TODO: handle FD_LOOP and FD_BLKTAP (?)
318
      if boot_disk:
319
        kvm_cmd.extend(['-boot', 'c'])
320
        boot_val = ',boot=on'
321
        # We only boot from the first disk
322
        boot_disk = False
323
      else:
324
        boot_val = ''
325

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

    
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
      else:
336
        options = '%s,if=virtio' % options
337
      drive_val = 'file=%s%s' % (iso_image, options)
338
      kvm_cmd.extend(['-drive', drive_val])
339

    
340
    kernel_path = hvp[constants.HV_KERNEL_PATH]
341
    if kernel_path:
342
      kvm_cmd.extend(['-kernel', kernel_path])
343
      initrd_path = hvp[constants.HV_INITRD_PATH]
344
      if initrd_path:
345
        kvm_cmd.extend(['-initrd', initrd_path])
346
      root_append = ['root=%s' % hvp[constants.HV_ROOT_PATH],
347
                     hvp[constants.HV_KERNEL_ARGS]]
348
      if hvp[constants.HV_SERIAL_CONSOLE]:
349
        root_append.append('console=ttyS0,38400')
350
      kvm_cmd.extend(['-append', ' '.join(root_append)])
351

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

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

    
373
        # Only allow tls and other option when not binding to a file, for now.
374
        # kvm/qemu gets confused otherwise about the filename to use.
375
        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
                                               hvp[constants.HV_VNC_X509])
381
          elif hvp[constants.HV_VNC_X509]:
382
            vnc_append = '%s,x509=%s' % (vnc_append,
383
                                         hvp[constants.HV_VNC_X509])
384
        if hvp[constants.HV_VNC_PASSWORD_FILE]:
385
          vnc_append = '%s,password' % vnc_append
386

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

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

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

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

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

    
409
    # Save the current instance nics, but defer their expansion as parameters,
410
    # as we'll need to generate executable temp files for them.
411
    kvm_nics = instance.nics
412
    hvparams = hvp
413

    
414
    return (kvm_cmd, kvm_nics, hvparams)
415

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

419
    """
420
    try:
421
      utils.WriteFile(self._InstanceKVMRuntime(instance_name),
422
                      data=data)
423
    except EnvironmentError, err:
424
      raise errors.HypervisorError("Failed to save KVM runtime file: %s" % err)
425

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

429
    """
430
    try:
431
      file_content = utils.ReadFile(self._InstanceKVMRuntime(instance_name))
432
    except EnvironmentError, err:
433
      raise errors.HypervisorError("Failed to load KVM runtime file: %s" % err)
434
    return file_content
435

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

439
    """
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
    self._WriteKVMRuntime(instance.name, serialized_form)
444

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

448
    """
449
    if not serialized_runtime:
450
      serialized_runtime = self._ReadKVMRuntime(instance.name)
451
    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

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

459
    @type incoming: tuple of strings
460
    @param incoming: (target_host_ip, port)
461

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

    
469
    temp_files = []
470

    
471
    kvm_cmd, kvm_nics, hvparams = kvm_runtime
472

    
473
    if not kvm_nics:
474
      kvm_cmd.extend(['-net', 'none'])
475
    else:
476
      nic_type = hvparams[constants.HV_NIC_TYPE]
477
      if nic_type == constants.HT_NIC_PARAVIRTUAL:
478
        nic_model = "model=virtio"
479
      else:
480
        nic_model = "model=%s" % nic_type
481

    
482
      for nic_seq, nic in enumerate(kvm_nics):
483
        nic_val = "nic,macaddr=%s,%s" % (nic.mac, nic_model)
484
        script = self._WriteNetScript(instance, nic_seq, nic)
485
        kvm_cmd.extend(['-net', nic_val])
486
        kvm_cmd.extend(['-net', 'tap,script=%s' % script])
487
        temp_files.append(script)
488

    
489
    if incoming:
490
      target, port = incoming
491
      kvm_cmd.extend(['-incoming', 'tcp:%s:%s' % (target, port)])
492

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

    
502
    result = utils.RunCmd(kvm_cmd)
503
    if result.failed:
504
      raise errors.HypervisorError("Failed to start instance %s: %s (%s)" %
505
                                   (instance.name, result.fail_reason,
506
                                    result.output))
507

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

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

    
516
    for filename in temp_files:
517
      utils.RemoveFile(filename)
518

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

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

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

    
532
  def _CallMonitorCommand(self, instance_name, command):
533
    """Invoke a command on the instance monitor.
534

535
    """
536
    socat = ("echo %s | %s STDIO UNIX-CONNECT:%s" %
537
             (utils.ShellQuote(command),
538
              constants.SOCAT_PATH,
539
              utils.ShellQuote(self._InstanceMonitor(instance_name))))
540
    result = utils.RunCmd(socat)
541
    if result.failed:
542
      msg = ("Failed to send command '%s' to instance %s."
543
             " output: %s, error: %s, fail_reason: %s" %
544
             (command, instance_name,
545
              result.stdout, result.stderr, result.fail_reason))
546
      raise errors.HypervisorError(msg)
547

    
548
    return result
549

    
550
  def StopInstance(self, instance, force=False, retry=False):
551
    """Stop an instance.
552

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

    
561
    if not utils.IsProcessAlive(pid):
562
      self._RemoveInstanceRuntimeFiles(pidfile, instance.name)
563
      return True
564
    else:
565
      return False
566

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

570
    """
571
    # For some reason if we do a 'send-key ctrl-alt-delete' to the control
572
    # socket the instance will stop, but now power up again. So we'll resort
573
    # to shutdown and restart.
574
    pidfile, pid, alive = self._InstancePidAlive(instance.name)
575
    if not alive:
576
      raise errors.HypervisorError("Failed to reboot instance %s:"
577
                                   " not running" % instance.name)
578
    # StopInstance will delete the saved KVM runtime so:
579
    # ...first load it...
580
    kvm_runtime = self._LoadKVMRuntime(instance)
581
    # ...now we can safely call StopInstance...
582
    if not self.StopInstance(instance):
583
      self.StopInstance(instance, force=True)
584
    # ...and finally we can save it again, and execute it...
585
    self._SaveKVMRuntime(instance, kvm_runtime)
586
    self._ExecuteKVMRuntime(instance, kvm_runtime)
587

    
588
  def MigrationInfo(self, instance):
589
    """Get instance information to perform a migration.
590

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

596
    """
597
    return self._ReadKVMRuntime(instance.name)
598

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

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

609
    """
610
    kvm_runtime = self._LoadKVMRuntime(instance, serialized_runtime=info)
611
    incoming_address = (target, instance.hvparams[constants.HV_MIGRATION_PORT])
612
    self._ExecuteKVMRuntime(instance, kvm_runtime, incoming=incoming_address)
613

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

617
    Stop the incoming mode KVM.
618

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

622
    """
623
    if success:
624
      self._WriteKVMRuntime(instance.name, info)
625
    else:
626
      self.StopInstance(instance, force=True)
627

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

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

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

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

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

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

    
655
    migrate_command = 'migrate -d tcp:%s:%s' % (target, port)
656
    self._CallMonitorCommand(instance_name, migrate_command)
657

    
658
    info_command = 'info migrate'
659
    done = False
660
    while not done:
661
      result = self._CallMonitorCommand(instance_name, info_command)
662
      match = self._MIGRATION_STATUS_RE.search(result.stdout)
663
      if not match:
664
        raise errors.HypervisorError("Unknown 'info migrate' result: %s" %
665
                                     result.stdout)
666
      else:
667
        status = match.group(1)
668
        if status == 'completed':
669
          done = True
670
        elif status == 'active':
671
          time.sleep(2)
672
        elif status == 'failed' or status == 'cancelled':
673
          if not live:
674
            self._CallMonitorCommand(instance_name, 'cont')
675
          raise errors.HypervisorError("Migration %s at the kvm level" %
676
                                       status)
677
        else:
678
          logging.info("KVM: unknown migration status '%s'" % status)
679
          time.sleep(2)
680

    
681
    utils.KillProcess(pid)
682
    self._RemoveInstanceRuntimeFiles(pidfile, instance_name)
683

    
684
  def GetNodeInfo(self):
685
    """Return information about the node.
686

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

689
    @return: a dict with the following keys (values in MiB):
690
          - memory_total: the total memory size on the node
691
          - memory_free: the available memory on the node for instances
692
          - memory_dom0: the memory used by the node itself, if available
693

694
    """
695
    return self.GetLinuxNodeInfo()
696

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

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

    
709
    vnc_bind_address = hvparams[constants.HV_VNC_BIND_ADDRESS]
710
    if vnc_bind_address:
711
      if instance.network_port > constants.VNC_BASE_PORT:
712
        display = instance.network_port - constants.VNC_BASE_PORT
713
        vnc_command = ("echo 'Instance has VNC listening on %s:%d"
714
                       " (display: %d)'" % (vnc_bind_address,
715
                                            instance.network_port,
716
                                            display))
717
        shell_command = "%s; %s" % (vnc_command, shell_command)
718

    
719
    return shell_command
720

    
721
  def Verify(self):
722
    """Verify the hypervisor.
723

724
    Check that the binary exists.
725

726
    """
727
    if not os.path.exists(constants.KVM_PATH):
728
      return "The kvm binary ('%s') does not exist." % constants.KVM_PATH
729
    if not os.path.exists(constants.SOCAT_PATH):
730
      return "The socat binary ('%s') does not exist." % constants.SOCAT_PATH
731

    
732

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

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

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

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

    
750
    if (hvparams[constants.HV_VNC_X509_VERIFY] and
751
        not hvparams[constants.HV_VNC_X509]):
752
      raise errors.HypervisorError("%s must be defined, if %s is" %
753
                                   (constants.HV_VNC_X509,
754
                                    constants.HV_VNC_X509_VERIFY))
755

    
756
    boot_order = hvparams[constants.HV_BOOT_ORDER]
757

    
758
    if (boot_order == constants.HT_BO_CDROM and
759
        not hvparams[constants.HV_CDROM_IMAGE_PATH]):
760
      raise errors.HypervisorError("Cannot boot from cdrom without an"
761
                                   " ISO path")
762
    if (boot_order == constants.HT_BO_NETWORK and
763
        hvparams[constants.HV_NIC_TYPE] == constants.HT_NIC_PARAVIRTUAL):
764
      raise errors.HypervisorError("Cannot boot from a paravirtual NIC. Please"
765
                                   " change the NIC type.")
766

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

771
    """
772
    cls.LinuxPowercycle()