Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_kvm.py @ 98ec75d6

History | View | Annotate | Download (27.5 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
    constants.HV_DISK_CACHE:
78
      hv_base.ParamInSet(True, constants.HT_VALID_CACHE_TYPES),
79
    }
80

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

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

    
86
  ANCILLARY_FILES = [
87
    _KVM_NETWORK_SCRIPT,
88
    ]
89

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

    
97
  def _InstancePidFile(self, instance_name):
98
    """Returns the instance pidfile.
99

100
    """
101
    pidfile = "%s/%s" % (self._PIDS_DIR, instance_name)
102
    return pidfile
103

    
104
  def _InstancePidAlive(self, instance_name):
105
    """Returns the instance pid and pidfile
106

107
    """
108
    pidfile = self._InstancePidFile(instance_name)
109
    pid = utils.ReadPidFile(pidfile)
110
    alive = utils.IsProcessAlive(pid)
111

    
112
    return (pidfile, pid, alive)
113

    
114
  @classmethod
115
  def _InstanceMonitor(cls, instance_name):
116
    """Returns the instance monitor socket name
117

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

    
121
  @classmethod
122
  def _InstanceSerial(cls, instance_name):
123
    """Returns the instance serial socket name
124

125
    """
126
    return '%s/%s.serial' % (cls._CTRL_DIR, instance_name)
127

    
128
  @staticmethod
129
  def _SocatUnixConsoleParams():
130
    """Returns the correct parameters for socat
131

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

134
    """
135
    if constants.SOCAT_USE_ESCAPE:
136
      return "raw,echo=0,escape=%s" % constants.SOCAT_ESCAPE_CODE
137
    else:
138
      return "echo=0,icanon=0"
139

    
140
  @classmethod
141
  def _InstanceKVMRuntime(cls, instance_name):
142
    """Returns the instance KVM runtime filename
143

144
    """
145
    return '%s/%s.runtime' % (cls._CONF_DIR, instance_name)
146

    
147
  @classmethod
148
  def _RemoveInstanceRuntimeFiles(cls, pidfile, instance_name):
149
    """Removes an instance's rutime sockets/files.
150

151
    """
152
    utils.RemoveFile(pidfile)
153
    utils.RemoveFile(cls._InstanceMonitor(instance_name))
154
    utils.RemoveFile(cls._InstanceSerial(instance_name))
155
    utils.RemoveFile(cls._InstanceKVMRuntime(instance_name))
156

    
157
  def _WriteNetScript(self, instance, seq, nic):
158
    """Write a script to connect a net interface to the proper bridge.
159

160
    This can be used by any qemu-type hypervisor.
161

162
    @param instance: instance we're acting on
163
    @type instance: instance object
164
    @param seq: nic sequence number
165
    @type seq: int
166
    @param nic: nic we're acting on
167
    @type nic: nic object
168
    @return: netscript file name
169
    @rtype: string
170

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

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

231
    We can do this by listing our live instances directory and
232
    checking whether the associated kvm process is still alive.
233

234
    """
235
    result = []
236
    for name in os.listdir(self._PIDS_DIR):
237
      filename = "%s/%s" % (self._PIDS_DIR, name)
238
      if utils.IsProcessAlive(utils.ReadPidFile(filename)):
239
        result.append(name)
240
    return result
241

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

245
    @param instance_name: the instance name
246

247
    @return: tuple (name, id, memory, vcpus, stat, times)
248

249
    """
250
    _, pid, alive = self._InstancePidAlive(instance_name)
251
    if not alive:
252
      return None
253

    
254
    cmdline_file = "/proc/%s/cmdline" % pid
255
    try:
256
      cmdline = utils.ReadFile(cmdline_file)
257
    except EnvironmentError, err:
258
      raise errors.HypervisorError("Failed to list instance %s: %s" %
259
                                   (instance_name, err))
260

    
261
    memory = 0
262
    vcpus = 0
263
    stat = "---b-"
264
    times = "0"
265

    
266
    arg_list = cmdline.split('\x00')
267
    while arg_list:
268
      arg =  arg_list.pop(0)
269
      if arg == '-m':
270
        memory = int(arg_list.pop(0))
271
      elif arg == '-smp':
272
        vcpus = int(arg_list.pop(0))
273

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

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

279
    @return: list of tuples (name, id, memory, vcpus, stat, times)
280

281
    """
282
    data = []
283
    for name in os.listdir(self._PIDS_DIR):
284
      filename = "%s/%s" % (self._PIDS_DIR, name)
285
      if utils.IsProcessAlive(utils.ReadPidFile(filename)):
286
        try:
287
          info = self.GetInstanceInfo(name)
288
        except errors.HypervisorError:
289
          continue
290
        if info:
291
          data.append(info)
292

    
293
    return data
294

    
295
  def _GenerateKVMRuntime(self, instance, block_devices):
296
    """Generate KVM information to start an instance.
297

298
    """
299
    pidfile  = self._InstancePidFile(instance.name)
300
    kvm = constants.KVM_PATH
301
    kvm_cmd = [kvm]
302
    kvm_cmd.extend(['-m', instance.beparams[constants.BE_MEMORY]])
303
    kvm_cmd.extend(['-smp', instance.beparams[constants.BE_VCPUS]])
304
    kvm_cmd.extend(['-pidfile', pidfile])
305
    # used just by the vnc server, if enabled
306
    kvm_cmd.extend(['-name', instance.name])
307
    kvm_cmd.extend(['-daemonize'])
308
    if not instance.hvparams[constants.HV_ACPI]:
309
      kvm_cmd.extend(['-no-acpi'])
310

    
311
    hvp = instance.hvparams
312
    boot_disk = hvp[constants.HV_BOOT_ORDER] == constants.HT_BO_DISK
313
    boot_cdrom = hvp[constants.HV_BOOT_ORDER] == constants.HT_BO_CDROM
314
    boot_network = hvp[constants.HV_BOOT_ORDER] == constants.HT_BO_NETWORK
315

    
316
    if boot_network:
317
      kvm_cmd.extend(['-boot', 'n'])
318

    
319
    disk_type = hvp[constants.HV_DISK_TYPE]
320
    if disk_type == constants.HT_DISK_PARAVIRTUAL:
321
      if_val = ',if=virtio'
322
    else:
323
      if_val = ',if=%s' % disk_type
324
    # Cache mode
325
    disk_cache = hvp[constants.HV_DISK_CACHE]
326
    if disk_cache != constants.HT_CACHE_DEFAULT:
327
      cache_val = ",cache=%s" % disk_cache
328
    else:
329
      cache_val = ""
330
    for cfdev, dev_path in block_devices:
331
      if cfdev.mode != constants.DISK_RDWR:
332
        raise errors.HypervisorError("Instance has read-only disks which"
333
                                     " are not supported by KVM")
334
      # TODO: handle FD_LOOP and FD_BLKTAP (?)
335
      if boot_disk:
336
        kvm_cmd.extend(['-boot', 'c'])
337
        boot_val = ',boot=on'
338
        # We only boot from the first disk
339
        boot_disk = False
340
      else:
341
        boot_val = ''
342

    
343
      drive_val = 'file=%s,format=raw%s%s%s' % (dev_path, if_val, boot_val,
344
                                                cache_val)
345
      kvm_cmd.extend(['-drive', drive_val])
346

    
347
    iso_image = hvp[constants.HV_CDROM_IMAGE_PATH]
348
    if iso_image:
349
      options = ',format=raw,media=cdrom'
350
      if boot_cdrom:
351
        kvm_cmd.extend(['-boot', 'd'])
352
        options = '%s,boot=on' % options
353
      else:
354
        options = '%s,if=virtio' % options
355
      drive_val = 'file=%s%s' % (iso_image, options)
356
      kvm_cmd.extend(['-drive', drive_val])
357

    
358
    kernel_path = hvp[constants.HV_KERNEL_PATH]
359
    if kernel_path:
360
      kvm_cmd.extend(['-kernel', kernel_path])
361
      initrd_path = hvp[constants.HV_INITRD_PATH]
362
      if initrd_path:
363
        kvm_cmd.extend(['-initrd', initrd_path])
364
      root_append = ['root=%s' % hvp[constants.HV_ROOT_PATH],
365
                     hvp[constants.HV_KERNEL_ARGS]]
366
      if hvp[constants.HV_SERIAL_CONSOLE]:
367
        root_append.append('console=ttyS0,38400')
368
      kvm_cmd.extend(['-append', ' '.join(root_append)])
369

    
370
    mouse_type = hvp[constants.HV_USB_MOUSE]
371
    if mouse_type:
372
      kvm_cmd.extend(['-usb'])
373
      kvm_cmd.extend(['-usbdevice', mouse_type])
374

    
375
    vnc_bind_address = hvp[constants.HV_VNC_BIND_ADDRESS]
376
    if vnc_bind_address:
377
      if utils.IsValidIP(vnc_bind_address):
378
        if instance.network_port > constants.VNC_BASE_PORT:
379
          display = instance.network_port - constants.VNC_BASE_PORT
380
          if vnc_bind_address == '0.0.0.0':
381
            vnc_arg = ':%d' % (display)
382
          else:
383
            vnc_arg = '%s:%d' % (vnc_bind_address, display)
384
        else:
385
          logging.error("Network port is not a valid VNC display (%d < %d)."
386
                        " Not starting VNC", instance.network_port,
387
                        constants.VNC_BASE_PORT)
388
          vnc_arg = 'none'
389

    
390
        # Only allow tls and other option when not binding to a file, for now.
391
        # kvm/qemu gets confused otherwise about the filename to use.
392
        vnc_append = ''
393
        if hvp[constants.HV_VNC_TLS]:
394
          vnc_append = '%s,tls' % vnc_append
395
          if hvp[constants.HV_VNC_X509_VERIFY]:
396
            vnc_append = '%s,x509verify=%s' % (vnc_append,
397
                                               hvp[constants.HV_VNC_X509])
398
          elif hvp[constants.HV_VNC_X509]:
399
            vnc_append = '%s,x509=%s' % (vnc_append,
400
                                         hvp[constants.HV_VNC_X509])
401
        if hvp[constants.HV_VNC_PASSWORD_FILE]:
402
          vnc_append = '%s,password' % vnc_append
403

    
404
        vnc_arg = '%s%s' % (vnc_arg, vnc_append)
405

    
406
      else:
407
        vnc_arg = 'unix:%s/%s.vnc' % (vnc_bind_address, instance.name)
408

    
409
      kvm_cmd.extend(['-vnc', vnc_arg])
410
    else:
411
      kvm_cmd.extend(['-nographic'])
412

    
413
    monitor_dev = ("unix:%s,server,nowait" %
414
                   self._InstanceMonitor(instance.name))
415
    kvm_cmd.extend(['-monitor', monitor_dev])
416
    if hvp[constants.HV_SERIAL_CONSOLE]:
417
      serial_dev = ('unix:%s,server,nowait' %
418
                    self._InstanceSerial(instance.name))
419
      kvm_cmd.extend(['-serial', serial_dev])
420
    else:
421
      kvm_cmd.extend(['-serial', 'none'])
422

    
423
    if hvp[constants.HV_USE_LOCALTIME]:
424
      kvm_cmd.extend(['-localtime'])
425

    
426
    # Save the current instance nics, but defer their expansion as parameters,
427
    # as we'll need to generate executable temp files for them.
428
    kvm_nics = instance.nics
429
    hvparams = hvp
430

    
431
    return (kvm_cmd, kvm_nics, hvparams)
432

    
433
  def _WriteKVMRuntime(self, instance_name, data):
434
    """Write an instance's KVM runtime
435

436
    """
437
    try:
438
      utils.WriteFile(self._InstanceKVMRuntime(instance_name),
439
                      data=data)
440
    except EnvironmentError, err:
441
      raise errors.HypervisorError("Failed to save KVM runtime file: %s" % err)
442

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

446
    """
447
    try:
448
      file_content = utils.ReadFile(self._InstanceKVMRuntime(instance_name))
449
    except EnvironmentError, err:
450
      raise errors.HypervisorError("Failed to load KVM runtime file: %s" % err)
451
    return file_content
452

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

456
    """
457
    kvm_cmd, kvm_nics, hvparams = kvm_runtime
458
    serialized_nics = [nic.ToDict() for nic in kvm_nics]
459
    serialized_form = serializer.Dump((kvm_cmd, serialized_nics, hvparams))
460
    self._WriteKVMRuntime(instance.name, serialized_form)
461

    
462
  def _LoadKVMRuntime(self, instance, serialized_runtime=None):
463
    """Load an instance's KVM runtime
464

465
    """
466
    if not serialized_runtime:
467
      serialized_runtime = self._ReadKVMRuntime(instance.name)
468
    loaded_runtime = serializer.Load(serialized_runtime)
469
    kvm_cmd, serialized_nics, hvparams = loaded_runtime
470
    kvm_nics = [objects.NIC.FromDict(snic) for snic in serialized_nics]
471
    return (kvm_cmd, kvm_nics, hvparams)
472

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

476
    @type incoming: tuple of strings
477
    @param incoming: (target_host_ip, port)
478

479
    """
480
    pidfile, _, alive = self._InstancePidAlive(instance.name)
481
    hvp = instance.hvparams
482
    if alive:
483
      raise errors.HypervisorError("Failed to start instance %s: %s" %
484
                                   (instance.name, "already running"))
485

    
486
    temp_files = []
487

    
488
    kvm_cmd, kvm_nics, hvparams = kvm_runtime
489

    
490
    if not kvm_nics:
491
      kvm_cmd.extend(['-net', 'none'])
492
    else:
493
      nic_type = hvparams[constants.HV_NIC_TYPE]
494
      if nic_type == constants.HT_NIC_PARAVIRTUAL:
495
        nic_model = "model=virtio"
496
      else:
497
        nic_model = "model=%s" % nic_type
498

    
499
      for nic_seq, nic in enumerate(kvm_nics):
500
        nic_val = "nic,macaddr=%s,%s" % (nic.mac, nic_model)
501
        script = self._WriteNetScript(instance, nic_seq, nic)
502
        kvm_cmd.extend(['-net', nic_val])
503
        kvm_cmd.extend(['-net', 'tap,script=%s' % script])
504
        temp_files.append(script)
505

    
506
    if incoming:
507
      target, port = incoming
508
      kvm_cmd.extend(['-incoming', 'tcp:%s:%s' % (target, port)])
509

    
510
    vnc_pwd_file = hvp[constants.HV_VNC_PASSWORD_FILE]
511
    vnc_pwd = None
512
    if vnc_pwd_file:
513
      try:
514
        vnc_pwd = utils.ReadFile(vnc_pwd_file)
515
      except EnvironmentError, err:
516
        raise errors.HypervisorError("Failed to open VNC password file %s: %s"
517
                                     % (vnc_pwd_file, err))
518

    
519
    result = utils.RunCmd(kvm_cmd)
520
    if result.failed:
521
      raise errors.HypervisorError("Failed to start instance %s: %s (%s)" %
522
                                   (instance.name, result.fail_reason,
523
                                    result.output))
524

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

    
529
    if vnc_pwd:
530
      change_cmd = 'change vnc password %s' % vnc_pwd
531
      self._CallMonitorCommand(instance.name, change_cmd)
532

    
533
    for filename in temp_files:
534
      utils.RemoveFile(filename)
535

    
536
  def StartInstance(self, instance, block_devices):
537
    """Start an instance.
538

539
    """
540
    pidfile, pid, alive = self._InstancePidAlive(instance.name)
541
    if alive:
542
      raise errors.HypervisorError("Failed to start instance %s: %s" %
543
                                   (instance.name, "already running"))
544

    
545
    kvm_runtime = self._GenerateKVMRuntime(instance, block_devices)
546
    self._SaveKVMRuntime(instance, kvm_runtime)
547
    self._ExecuteKVMRuntime(instance, kvm_runtime)
548

    
549
  def _CallMonitorCommand(self, instance_name, command):
550
    """Invoke a command on the instance monitor.
551

552
    """
553
    socat = ("echo %s | %s STDIO UNIX-CONNECT:%s" %
554
             (utils.ShellQuote(command),
555
              constants.SOCAT_PATH,
556
              utils.ShellQuote(self._InstanceMonitor(instance_name))))
557
    result = utils.RunCmd(socat)
558
    if result.failed:
559
      msg = ("Failed to send command '%s' to instance %s."
560
             " output: %s, error: %s, fail_reason: %s" %
561
             (command, instance_name,
562
              result.stdout, result.stderr, result.fail_reason))
563
      raise errors.HypervisorError(msg)
564

    
565
    return result
566

    
567
  def StopInstance(self, instance, force=False, retry=False):
568
    """Stop an instance.
569

570
    """
571
    pidfile, pid, alive = self._InstancePidAlive(instance.name)
572
    if pid > 0 and alive:
573
      if force or not instance.hvparams[constants.HV_ACPI]:
574
        utils.KillProcess(pid)
575
      else:
576
        self._CallMonitorCommand(instance.name, 'system_powerdown')
577

    
578
    if not utils.IsProcessAlive(pid):
579
      self._RemoveInstanceRuntimeFiles(pidfile, instance.name)
580
      return True
581
    else:
582
      return False
583

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

587
    """
588
    # For some reason if we do a 'send-key ctrl-alt-delete' to the control
589
    # socket the instance will stop, but now power up again. So we'll resort
590
    # to shutdown and restart.
591
    _, _, alive = self._InstancePidAlive(instance.name)
592
    if not alive:
593
      raise errors.HypervisorError("Failed to reboot instance %s:"
594
                                   " not running" % instance.name)
595
    # StopInstance will delete the saved KVM runtime so:
596
    # ...first load it...
597
    kvm_runtime = self._LoadKVMRuntime(instance)
598
    # ...now we can safely call StopInstance...
599
    if not self.StopInstance(instance):
600
      self.StopInstance(instance, force=True)
601
    # ...and finally we can save it again, and execute it...
602
    self._SaveKVMRuntime(instance, kvm_runtime)
603
    self._ExecuteKVMRuntime(instance, kvm_runtime)
604

    
605
  def MigrationInfo(self, instance):
606
    """Get instance information to perform a migration.
607

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

613
    """
614
    return self._ReadKVMRuntime(instance.name)
615

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

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

626
    """
627
    kvm_runtime = self._LoadKVMRuntime(instance, serialized_runtime=info)
628
    incoming_address = (target, instance.hvparams[constants.HV_MIGRATION_PORT])
629
    self._ExecuteKVMRuntime(instance, kvm_runtime, incoming=incoming_address)
630

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

634
    Stop the incoming mode KVM.
635

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

639
    """
640
    if success:
641
      self._WriteKVMRuntime(instance.name, info)
642
    else:
643
      self.StopInstance(instance, force=True)
644

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

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

651
    @type instance: L{objects.Instance}
652
    @param instance: the instance to be migrated
653
    @type target: string
654
    @param target: ip address of the target node
655
    @type live: boolean
656
    @param live: perform a live migration
657

658
    """
659
    instance_name = instance.name
660
    port = instance.hvparams[constants.HV_MIGRATION_PORT]
661
    pidfile, pid, alive = self._InstancePidAlive(instance_name)
662
    if not alive:
663
      raise errors.HypervisorError("Instance not running, cannot migrate")
664

    
665
    if not utils.TcpPing(target, port, live_port_needed=True):
666
      raise errors.HypervisorError("Remote host %s not listening on port"
667
                                   " %s, cannot migrate" % (target, port))
668

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

    
672
    migrate_command = 'migrate -d tcp:%s:%s' % (target, port)
673
    self._CallMonitorCommand(instance_name, migrate_command)
674

    
675
    info_command = 'info migrate'
676
    done = False
677
    while not done:
678
      result = self._CallMonitorCommand(instance_name, info_command)
679
      match = self._MIGRATION_STATUS_RE.search(result.stdout)
680
      if not match:
681
        raise errors.HypervisorError("Unknown 'info migrate' result: %s" %
682
                                     result.stdout)
683
      else:
684
        status = match.group(1)
685
        if status == 'completed':
686
          done = True
687
        elif status == 'active':
688
          time.sleep(2)
689
        elif status == 'failed' or status == 'cancelled':
690
          if not live:
691
            self._CallMonitorCommand(instance_name, 'cont')
692
          raise errors.HypervisorError("Migration %s at the kvm level" %
693
                                       status)
694
        else:
695
          logging.info("KVM: unknown migration status '%s'", status)
696
          time.sleep(2)
697

    
698
    utils.KillProcess(pid)
699
    self._RemoveInstanceRuntimeFiles(pidfile, instance_name)
700

    
701
  def GetNodeInfo(self):
702
    """Return information about the node.
703

704
    This is just a wrapper over the base GetLinuxNodeInfo method.
705

706
    @return: a dict with the following keys (values in MiB):
707
          - memory_total: the total memory size on the node
708
          - memory_free: the available memory on the node for instances
709
          - memory_dom0: the memory used by the node itself, if available
710

711
    """
712
    return self.GetLinuxNodeInfo()
713

    
714
  @classmethod
715
  def GetShellCommandForConsole(cls, instance, hvparams, beparams):
716
    """Return a command for connecting to the console of an instance.
717

718
    """
719
    if hvparams[constants.HV_SERIAL_CONSOLE]:
720
      shell_command = ("%s STDIO,%s UNIX-CONNECT:%s" %
721
                       (constants.SOCAT_PATH, cls._SocatUnixConsoleParams(),
722
                        utils.ShellQuote(cls._InstanceSerial(instance.name))))
723
    else:
724
      shell_command = "echo 'No serial shell for instance %s'" % instance.name
725

    
726
    vnc_bind_address = hvparams[constants.HV_VNC_BIND_ADDRESS]
727
    if vnc_bind_address:
728
      if instance.network_port > constants.VNC_BASE_PORT:
729
        display = instance.network_port - constants.VNC_BASE_PORT
730
        vnc_command = ("echo 'Instance has VNC listening on %s:%d"
731
                       " (display: %d)'" % (vnc_bind_address,
732
                                            instance.network_port,
733
                                            display))
734
        shell_command = "%s; %s" % (vnc_command, shell_command)
735

    
736
    return shell_command
737

    
738
  def Verify(self):
739
    """Verify the hypervisor.
740

741
    Check that the binary exists.
742

743
    """
744
    if not os.path.exists(constants.KVM_PATH):
745
      return "The kvm binary ('%s') does not exist." % constants.KVM_PATH
746
    if not os.path.exists(constants.SOCAT_PATH):
747
      return "The socat binary ('%s') does not exist." % constants.SOCAT_PATH
748

    
749

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

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

758
    """
759
    super(KVMHypervisor, cls).CheckParameterSyntax(hvparams)
760

    
761
    kernel_path = hvparams[constants.HV_KERNEL_PATH]
762
    if kernel_path:
763
      if not hvparams[constants.HV_ROOT_PATH]:
764
        raise errors.HypervisorError("Need a root partition for the instance,"
765
                                     " if a kernel is defined")
766

    
767
    if (hvparams[constants.HV_VNC_X509_VERIFY] and
768
        not hvparams[constants.HV_VNC_X509]):
769
      raise errors.HypervisorError("%s must be defined, if %s is" %
770
                                   (constants.HV_VNC_X509,
771
                                    constants.HV_VNC_X509_VERIFY))
772

    
773
    boot_order = hvparams[constants.HV_BOOT_ORDER]
774

    
775
    if (boot_order == constants.HT_BO_CDROM and
776
        not hvparams[constants.HV_CDROM_IMAGE_PATH]):
777
      raise errors.HypervisorError("Cannot boot from cdrom without an"
778
                                   " ISO path")
779
    if (boot_order == constants.HT_BO_NETWORK and
780
        hvparams[constants.HV_NIC_TYPE] == constants.HT_NIC_PARAVIRTUAL):
781
      raise errors.HypervisorError("Cannot boot from a paravirtual NIC. Please"
782
                                   " change the NIC type.")
783

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

788
    """
789
    cls.LinuxPowercycle()