Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_kvm.py @ db169865

History | View | Annotate | Download (27.4 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
  def _CheckDown(self, instance_name):
115
    """Raises an error unless the given instance is down.
116

117
    """
118
    alive = self._InstancePidAlive(instance_name)[2]
119
    if alive:
120
      raise errors.HypervisorError("Failed to start instance %s: %s" %
121
                                   (instance_name, "already running"))
122

    
123
  @classmethod
124
  def _InstanceMonitor(cls, instance_name):
125
    """Returns the instance monitor socket name
126

127
    """
128
    return '%s/%s.monitor' % (cls._CTRL_DIR, instance_name)
129

    
130
  @classmethod
131
  def _InstanceSerial(cls, instance_name):
132
    """Returns the instance serial socket name
133

134
    """
135
    return '%s/%s.serial' % (cls._CTRL_DIR, instance_name)
136

    
137
  @staticmethod
138
  def _SocatUnixConsoleParams():
139
    """Returns the correct parameters for socat
140

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

143
    """
144
    if constants.SOCAT_USE_ESCAPE:
145
      return "raw,echo=0,escape=%s" % constants.SOCAT_ESCAPE_CODE
146
    else:
147
      return "echo=0,icanon=0"
148

    
149
  @classmethod
150
  def _InstanceKVMRuntime(cls, instance_name):
151
    """Returns the instance KVM runtime filename
152

153
    """
154
    return '%s/%s.runtime' % (cls._CONF_DIR, instance_name)
155

    
156
  @classmethod
157
  def _RemoveInstanceRuntimeFiles(cls, pidfile, instance_name):
158
    """Removes an instance's rutime sockets/files.
159

160
    """
161
    utils.RemoveFile(pidfile)
162
    utils.RemoveFile(cls._InstanceMonitor(instance_name))
163
    utils.RemoveFile(cls._InstanceSerial(instance_name))
164
    utils.RemoveFile(cls._InstanceKVMRuntime(instance_name))
165

    
166
  def _WriteNetScript(self, instance, seq, nic):
167
    """Write a script to connect a net interface to the proper bridge.
168

169
    This can be used by any qemu-type hypervisor.
170

171
    @param instance: instance we're acting on
172
    @type instance: instance object
173
    @param seq: nic sequence number
174
    @type seq: int
175
    @param nic: nic we're acting on
176
    @type nic: nic object
177
    @return: netscript file name
178
    @rtype: string
179

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

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

240
    We can do this by listing our live instances directory and
241
    checking whether the associated kvm process is still alive.
242

243
    """
244
    result = []
245
    for name in os.listdir(self._PIDS_DIR):
246
      filename = "%s/%s" % (self._PIDS_DIR, name)
247
      if utils.IsProcessAlive(utils.ReadPidFile(filename)):
248
        result.append(name)
249
    return result
250

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

254
    @param instance_name: the instance name
255

256
    @return: tuple (name, id, memory, vcpus, stat, times)
257

258
    """
259
    _, pid, alive = self._InstancePidAlive(instance_name)
260
    if not alive:
261
      return None
262

    
263
    cmdline_file = "/proc/%s/cmdline" % pid
264
    try:
265
      cmdline = utils.ReadFile(cmdline_file)
266
    except EnvironmentError, err:
267
      raise errors.HypervisorError("Failed to list instance %s: %s" %
268
                                   (instance_name, err))
269

    
270
    memory = 0
271
    vcpus = 0
272
    stat = "---b-"
273
    times = "0"
274

    
275
    arg_list = cmdline.split('\x00')
276
    while arg_list:
277
      arg =  arg_list.pop(0)
278
      if arg == '-m':
279
        memory = int(arg_list.pop(0))
280
      elif arg == '-smp':
281
        vcpus = int(arg_list.pop(0))
282

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

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

288
    @return: list of tuples (name, id, memory, vcpus, stat, times)
289

290
    """
291
    data = []
292
    for name in os.listdir(self._PIDS_DIR):
293
      filename = "%s/%s" % (self._PIDS_DIR, name)
294
      if utils.IsProcessAlive(utils.ReadPidFile(filename)):
295
        try:
296
          info = self.GetInstanceInfo(name)
297
        except errors.HypervisorError:
298
          continue
299
        if info:
300
          data.append(info)
301

    
302
    return data
303

    
304
  def _GenerateKVMRuntime(self, instance, block_devices):
305
    """Generate KVM information to start an instance.
306

307
    """
308
    pidfile  = self._InstancePidFile(instance.name)
309
    kvm = constants.KVM_PATH
310
    kvm_cmd = [kvm]
311
    kvm_cmd.extend(['-m', instance.beparams[constants.BE_MEMORY]])
312
    kvm_cmd.extend(['-smp', instance.beparams[constants.BE_VCPUS]])
313
    kvm_cmd.extend(['-pidfile', pidfile])
314
    # used just by the vnc server, if enabled
315
    kvm_cmd.extend(['-name', instance.name])
316
    kvm_cmd.extend(['-daemonize'])
317
    if not instance.hvparams[constants.HV_ACPI]:
318
      kvm_cmd.extend(['-no-acpi'])
319

    
320
    hvp = instance.hvparams
321
    boot_disk = hvp[constants.HV_BOOT_ORDER] == constants.HT_BO_DISK
322
    boot_cdrom = hvp[constants.HV_BOOT_ORDER] == constants.HT_BO_CDROM
323
    boot_network = hvp[constants.HV_BOOT_ORDER] == constants.HT_BO_NETWORK
324

    
325
    if boot_network:
326
      kvm_cmd.extend(['-boot', 'n'])
327

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

    
352
      drive_val = 'file=%s,format=raw%s%s%s' % (dev_path, if_val, boot_val,
353
                                                cache_val)
354
      kvm_cmd.extend(['-drive', drive_val])
355

    
356
    iso_image = hvp[constants.HV_CDROM_IMAGE_PATH]
357
    if iso_image:
358
      options = ',format=raw,media=cdrom'
359
      if boot_cdrom:
360
        kvm_cmd.extend(['-boot', 'd'])
361
        options = '%s,boot=on' % options
362
      else:
363
        options = '%s,if=virtio' % options
364
      drive_val = 'file=%s%s' % (iso_image, options)
365
      kvm_cmd.extend(['-drive', drive_val])
366

    
367
    kernel_path = hvp[constants.HV_KERNEL_PATH]
368
    if kernel_path:
369
      kvm_cmd.extend(['-kernel', kernel_path])
370
      initrd_path = hvp[constants.HV_INITRD_PATH]
371
      if initrd_path:
372
        kvm_cmd.extend(['-initrd', initrd_path])
373
      root_append = ['root=%s' % hvp[constants.HV_ROOT_PATH],
374
                     hvp[constants.HV_KERNEL_ARGS]]
375
      if hvp[constants.HV_SERIAL_CONSOLE]:
376
        root_append.append('console=ttyS0,38400')
377
      kvm_cmd.extend(['-append', ' '.join(root_append)])
378

    
379
    mouse_type = hvp[constants.HV_USB_MOUSE]
380
    if mouse_type:
381
      kvm_cmd.extend(['-usb'])
382
      kvm_cmd.extend(['-usbdevice', mouse_type])
383

    
384
    vnc_bind_address = hvp[constants.HV_VNC_BIND_ADDRESS]
385
    if vnc_bind_address:
386
      if utils.IsValidIP(vnc_bind_address):
387
        if instance.network_port > constants.VNC_BASE_PORT:
388
          display = instance.network_port - constants.VNC_BASE_PORT
389
          if vnc_bind_address == '0.0.0.0':
390
            vnc_arg = ':%d' % (display)
391
          else:
392
            vnc_arg = '%s:%d' % (vnc_bind_address, display)
393
        else:
394
          logging.error("Network port is not a valid VNC display (%d < %d)."
395
                        " Not starting VNC", instance.network_port,
396
                        constants.VNC_BASE_PORT)
397
          vnc_arg = 'none'
398

    
399
        # Only allow tls and other option when not binding to a file, for now.
400
        # kvm/qemu gets confused otherwise about the filename to use.
401
        vnc_append = ''
402
        if hvp[constants.HV_VNC_TLS]:
403
          vnc_append = '%s,tls' % vnc_append
404
          if hvp[constants.HV_VNC_X509_VERIFY]:
405
            vnc_append = '%s,x509verify=%s' % (vnc_append,
406
                                               hvp[constants.HV_VNC_X509])
407
          elif hvp[constants.HV_VNC_X509]:
408
            vnc_append = '%s,x509=%s' % (vnc_append,
409
                                         hvp[constants.HV_VNC_X509])
410
        if hvp[constants.HV_VNC_PASSWORD_FILE]:
411
          vnc_append = '%s,password' % vnc_append
412

    
413
        vnc_arg = '%s%s' % (vnc_arg, vnc_append)
414

    
415
      else:
416
        vnc_arg = 'unix:%s/%s.vnc' % (vnc_bind_address, instance.name)
417

    
418
      kvm_cmd.extend(['-vnc', vnc_arg])
419
    else:
420
      kvm_cmd.extend(['-nographic'])
421

    
422
    monitor_dev = ("unix:%s,server,nowait" %
423
                   self._InstanceMonitor(instance.name))
424
    kvm_cmd.extend(['-monitor', monitor_dev])
425
    if hvp[constants.HV_SERIAL_CONSOLE]:
426
      serial_dev = ('unix:%s,server,nowait' %
427
                    self._InstanceSerial(instance.name))
428
      kvm_cmd.extend(['-serial', serial_dev])
429
    else:
430
      kvm_cmd.extend(['-serial', 'none'])
431

    
432
    if hvp[constants.HV_USE_LOCALTIME]:
433
      kvm_cmd.extend(['-localtime'])
434

    
435
    # Save the current instance nics, but defer their expansion as parameters,
436
    # as we'll need to generate executable temp files for them.
437
    kvm_nics = instance.nics
438
    hvparams = hvp
439

    
440
    return (kvm_cmd, kvm_nics, hvparams)
441

    
442
  def _WriteKVMRuntime(self, instance_name, data):
443
    """Write an instance's KVM runtime
444

445
    """
446
    try:
447
      utils.WriteFile(self._InstanceKVMRuntime(instance_name),
448
                      data=data)
449
    except EnvironmentError, err:
450
      raise errors.HypervisorError("Failed to save KVM runtime file: %s" % err)
451

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

455
    """
456
    try:
457
      file_content = utils.ReadFile(self._InstanceKVMRuntime(instance_name))
458
    except EnvironmentError, err:
459
      raise errors.HypervisorError("Failed to load KVM runtime file: %s" % err)
460
    return file_content
461

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

465
    """
466
    kvm_cmd, kvm_nics, hvparams = kvm_runtime
467
    serialized_nics = [nic.ToDict() for nic in kvm_nics]
468
    serialized_form = serializer.Dump((kvm_cmd, serialized_nics, hvparams))
469
    self._WriteKVMRuntime(instance.name, serialized_form)
470

    
471
  def _LoadKVMRuntime(self, instance, serialized_runtime=None):
472
    """Load an instance's KVM runtime
473

474
    """
475
    if not serialized_runtime:
476
      serialized_runtime = self._ReadKVMRuntime(instance.name)
477
    loaded_runtime = serializer.Load(serialized_runtime)
478
    kvm_cmd, serialized_nics, hvparams = loaded_runtime
479
    kvm_nics = [objects.NIC.FromDict(snic) for snic in serialized_nics]
480
    return (kvm_cmd, kvm_nics, hvparams)
481

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

485
    @type incoming: tuple of strings
486
    @param incoming: (target_host_ip, port)
487

488
    """
489
    hvp = instance.hvparams
490
    name = instance.name
491
    self._CheckDown(name)
492

    
493
    temp_files = []
494

    
495
    kvm_cmd, kvm_nics, hvparams = kvm_runtime
496

    
497
    if not kvm_nics:
498
      kvm_cmd.extend(['-net', 'none'])
499
    else:
500
      nic_type = hvparams[constants.HV_NIC_TYPE]
501
      if nic_type == constants.HT_NIC_PARAVIRTUAL:
502
        nic_model = "model=virtio"
503
      else:
504
        nic_model = "model=%s" % nic_type
505

    
506
      for nic_seq, nic in enumerate(kvm_nics):
507
        nic_val = "nic,macaddr=%s,%s" % (nic.mac, nic_model)
508
        script = self._WriteNetScript(instance, nic_seq, nic)
509
        kvm_cmd.extend(['-net', nic_val])
510
        kvm_cmd.extend(['-net', 'tap,script=%s' % script])
511
        temp_files.append(script)
512

    
513
    if incoming:
514
      target, port = incoming
515
      kvm_cmd.extend(['-incoming', 'tcp:%s:%s' % (target, port)])
516

    
517
    vnc_pwd_file = hvp[constants.HV_VNC_PASSWORD_FILE]
518
    vnc_pwd = None
519
    if vnc_pwd_file:
520
      try:
521
        vnc_pwd = utils.ReadFile(vnc_pwd_file)
522
      except EnvironmentError, err:
523
        raise errors.HypervisorError("Failed to open VNC password file %s: %s"
524
                                     % (vnc_pwd_file, err))
525

    
526
    result = utils.RunCmd(kvm_cmd)
527
    if result.failed:
528
      raise errors.HypervisorError("Failed to start instance %s: %s (%s)" %
529
                                   (name, result.fail_reason, result.output))
530

    
531
    if not self._InstancePidAlive(name)[2]:
532
      raise errors.HypervisorError("Failed to start instance %s" % name)
533

    
534
    if vnc_pwd:
535
      change_cmd = 'change vnc password %s' % vnc_pwd
536
      self._CallMonitorCommand(instance.name, change_cmd)
537

    
538
    for filename in temp_files:
539
      utils.RemoveFile(filename)
540

    
541
  def StartInstance(self, instance, block_devices):
542
    """Start an instance.
543

544
    """
545
    self._CheckDown(instance.name)
546
    kvm_runtime = self._GenerateKVMRuntime(instance, block_devices)
547
    self._SaveKVMRuntime(instance, kvm_runtime)
548
    self._ExecuteKVMRuntime(instance, kvm_runtime)
549

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

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

    
566
    return result
567

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

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

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

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

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

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

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

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

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

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

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

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

635
    Stop the incoming mode KVM.
636

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
737
    return shell_command
738

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

742
    Check that the binary exists.
743

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

    
750

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

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

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

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

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

    
774
    boot_order = hvparams[constants.HV_BOOT_ORDER]
775

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

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

789
    """
790
    cls.LinuxPowercycle()