Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_kvm.py @ 07b8a2b5

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 _InstancePidAlive(self, instance_name):
98
    """Returns the instance pid and pidfile
99

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

    
105
    return (pidfile, pid, alive)
106

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

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

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

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

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

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

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

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

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

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

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

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

153
    This can be used by any qemu-type hypervisor.
154

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

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

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

224
    We can do this by listing our live instances directory and
225
    checking whether the associated kvm process is still alive.
226

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

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

238
    @param instance_name: the instance name
239

240
    @return: tuple (name, id, memory, vcpus, stat, times)
241

242
    """
243
    pidfile, pid, alive = self._InstancePidAlive(instance_name)
244
    if not alive:
245
      return None
246

    
247
    cmdline_file = "/proc/%s/cmdline" % pid
248
    try:
249
      cmdline = utils.ReadFile(cmdline_file)
250
    except EnvironmentError, err:
251
      raise errors.HypervisorError("Failed to list instance %s: %s" %
252
                                   (instance_name, err))
253

    
254
    memory = 0
255
    vcpus = 0
256
    stat = "---b-"
257
    times = "0"
258

    
259
    arg_list = cmdline.split('\x00')
260
    while arg_list:
261
      arg =  arg_list.pop(0)
262
      if arg == '-m':
263
        memory = int(arg_list.pop(0))
264
      elif arg == '-smp':
265
        vcpus = int(arg_list.pop(0))
266

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

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

272
    @return: list of tuples (name, id, memory, vcpus, stat, times)
273

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

    
286
    return data
287

    
288
  def _GenerateKVMRuntime(self, instance, block_devices):
289
    """Generate KVM information to start an instance.
290

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

    
304
    hvp = instance.hvparams
305
    boot_disk = hvp[constants.HV_BOOT_ORDER] == constants.HT_BO_DISK
306
    boot_cdrom = hvp[constants.HV_BOOT_ORDER] == constants.HT_BO_CDROM
307
    boot_network = hvp[constants.HV_BOOT_ORDER] == constants.HT_BO_NETWORK
308

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

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

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

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

    
351
    kernel_path = hvp[constants.HV_KERNEL_PATH]
352
    if kernel_path:
353
      kvm_cmd.extend(['-kernel', kernel_path])
354
      initrd_path = hvp[constants.HV_INITRD_PATH]
355
      if initrd_path:
356
        kvm_cmd.extend(['-initrd', initrd_path])
357
      root_append = ['root=%s' % hvp[constants.HV_ROOT_PATH],
358
                     hvp[constants.HV_KERNEL_ARGS]]
359
      if hvp[constants.HV_SERIAL_CONSOLE]:
360
        root_append.append('console=ttyS0,38400')
361
      kvm_cmd.extend(['-append', ' '.join(root_append)])
362

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

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

    
383
        # Only allow tls and other option when not binding to a file, for now.
384
        # kvm/qemu gets confused otherwise about the filename to use.
385
        vnc_append = ''
386
        if hvp[constants.HV_VNC_TLS]:
387
          vnc_append = '%s,tls' % vnc_append
388
          if hvp[constants.HV_VNC_X509_VERIFY]:
389
            vnc_append = '%s,x509verify=%s' % (vnc_append,
390
                                               hvp[constants.HV_VNC_X509])
391
          elif hvp[constants.HV_VNC_X509]:
392
            vnc_append = '%s,x509=%s' % (vnc_append,
393
                                         hvp[constants.HV_VNC_X509])
394
        if hvp[constants.HV_VNC_PASSWORD_FILE]:
395
          vnc_append = '%s,password' % vnc_append
396

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

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

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

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

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

    
419
    # Save the current instance nics, but defer their expansion as parameters,
420
    # as we'll need to generate executable temp files for them.
421
    kvm_nics = instance.nics
422
    hvparams = hvp
423

    
424
    return (kvm_cmd, kvm_nics, hvparams)
425

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

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

    
436
  def _ReadKVMRuntime(self, instance_name):
437
    """Read an instance's KVM runtime
438

439
    """
440
    try:
441
      file_content = utils.ReadFile(self._InstanceKVMRuntime(instance_name))
442
    except EnvironmentError, err:
443
      raise errors.HypervisorError("Failed to load KVM runtime file: %s" % err)
444
    return file_content
445

    
446
  def _SaveKVMRuntime(self, instance, kvm_runtime):
447
    """Save an instance's KVM runtime
448

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

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

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

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

469
    @type incoming: tuple of strings
470
    @param incoming: (target_host_ip, port)
471

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

    
479
    temp_files = []
480

    
481
    kvm_cmd, kvm_nics, hvparams = kvm_runtime
482

    
483
    if not kvm_nics:
484
      kvm_cmd.extend(['-net', 'none'])
485
    else:
486
      nic_type = hvparams[constants.HV_NIC_TYPE]
487
      if nic_type == constants.HT_NIC_PARAVIRTUAL:
488
        nic_model = "model=virtio"
489
      else:
490
        nic_model = "model=%s" % nic_type
491

    
492
      for nic_seq, nic in enumerate(kvm_nics):
493
        nic_val = "nic,macaddr=%s,%s" % (nic.mac, nic_model)
494
        script = self._WriteNetScript(instance, nic_seq, nic)
495
        kvm_cmd.extend(['-net', nic_val])
496
        kvm_cmd.extend(['-net', 'tap,script=%s' % script])
497
        temp_files.append(script)
498

    
499
    if incoming:
500
      target, port = incoming
501
      kvm_cmd.extend(['-incoming', 'tcp:%s:%s' % (target, port)])
502

    
503
    vnc_pwd_file = hvp[constants.HV_VNC_PASSWORD_FILE]
504
    vnc_pwd = None
505
    if vnc_pwd_file:
506
      try:
507
        vnc_pwd = utils.ReadFile(vnc_pwd_file)
508
      except EnvironmentError, err:
509
        raise errors.HypervisorError("Failed to open VNC password file %s: %s"
510
                                     % (vnc_pwd_file, err))
511

    
512
    result = utils.RunCmd(kvm_cmd)
513
    if result.failed:
514
      raise errors.HypervisorError("Failed to start instance %s: %s (%s)" %
515
                                   (instance.name, result.fail_reason,
516
                                    result.output))
517

    
518
    if not utils.IsProcessAlive(utils.ReadPidFile(pidfile)):
519
      raise errors.HypervisorError("Failed to start instance %s" %
520
                                   (instance.name))
521

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

    
526
    for filename in temp_files:
527
      utils.RemoveFile(filename)
528

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

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

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

    
542
  def _CallMonitorCommand(self, instance_name, command):
543
    """Invoke a command on the instance monitor.
544

545
    """
546
    socat = ("echo %s | %s STDIO UNIX-CONNECT:%s" %
547
             (utils.ShellQuote(command),
548
              constants.SOCAT_PATH,
549
              utils.ShellQuote(self._InstanceMonitor(instance_name))))
550
    result = utils.RunCmd(socat)
551
    if result.failed:
552
      msg = ("Failed to send command '%s' to instance %s."
553
             " output: %s, error: %s, fail_reason: %s" %
554
             (command, instance_name,
555
              result.stdout, result.stderr, result.fail_reason))
556
      raise errors.HypervisorError(msg)
557

    
558
    return result
559

    
560
  def StopInstance(self, instance, force=False, retry=False):
561
    """Stop an instance.
562

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

    
571
    if not utils.IsProcessAlive(pid):
572
      self._RemoveInstanceRuntimeFiles(pidfile, instance.name)
573
      return True
574
    else:
575
      return False
576

    
577
  def RebootInstance(self, instance):
578
    """Reboot an instance.
579

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

    
598
  def MigrationInfo(self, instance):
599
    """Get instance information to perform a migration.
600

601
    @type instance: L{objects.Instance}
602
    @param instance: instance to be migrated
603
    @rtype: string
604
    @return: content of the KVM runtime file
605

606
    """
607
    return self._ReadKVMRuntime(instance.name)
608

    
609
  def AcceptInstance(self, instance, info, target):
610
    """Prepare to accept an instance.
611

612
    @type instance: L{objects.Instance}
613
    @param instance: instance to be accepted
614
    @type info: string
615
    @param info: content of the KVM runtime file on the source node
616
    @type target: string
617
    @param target: target host (usually ip), on this node
618

619
    """
620
    kvm_runtime = self._LoadKVMRuntime(instance, serialized_runtime=info)
621
    incoming_address = (target, instance.hvparams[constants.HV_MIGRATION_PORT])
622
    self._ExecuteKVMRuntime(instance, kvm_runtime, incoming=incoming_address)
623

    
624
  def FinalizeMigration(self, instance, info, success):
625
    """Finalize an instance migration.
626

627
    Stop the incoming mode KVM.
628

629
    @type instance: L{objects.Instance}
630
    @param instance: instance whose migration is being aborted
631

632
    """
633
    if success:
634
      self._WriteKVMRuntime(instance.name, info)
635
    else:
636
      self.StopInstance(instance, force=True)
637

    
638
  def MigrateInstance(self, instance, target, live):
639
    """Migrate an instance to a target node.
640

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

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

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

    
658
    if not utils.TcpPing(target, port, live_port_needed=True):
659
      raise errors.HypervisorError("Remote host %s not listening on port"
660
                                   " %s, cannot migrate" % (target, port))
661

    
662
    if not live:
663
      self._CallMonitorCommand(instance_name, 'stop')
664

    
665
    migrate_command = 'migrate -d tcp:%s:%s' % (target, port)
666
    self._CallMonitorCommand(instance_name, migrate_command)
667

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

    
691
    utils.KillProcess(pid)
692
    self._RemoveInstanceRuntimeFiles(pidfile, instance_name)
693

    
694
  def GetNodeInfo(self):
695
    """Return information about the node.
696

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

699
    @return: a dict with the following keys (values in MiB):
700
          - memory_total: the total memory size on the node
701
          - memory_free: the available memory on the node for instances
702
          - memory_dom0: the memory used by the node itself, if available
703

704
    """
705
    return self.GetLinuxNodeInfo()
706

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

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

    
719
    vnc_bind_address = hvparams[constants.HV_VNC_BIND_ADDRESS]
720
    if vnc_bind_address:
721
      if instance.network_port > constants.VNC_BASE_PORT:
722
        display = instance.network_port - constants.VNC_BASE_PORT
723
        vnc_command = ("echo 'Instance has VNC listening on %s:%d"
724
                       " (display: %d)'" % (vnc_bind_address,
725
                                            instance.network_port,
726
                                            display))
727
        shell_command = "%s; %s" % (vnc_command, shell_command)
728

    
729
    return shell_command
730

    
731
  def Verify(self):
732
    """Verify the hypervisor.
733

734
    Check that the binary exists.
735

736
    """
737
    if not os.path.exists(constants.KVM_PATH):
738
      return "The kvm binary ('%s') does not exist." % constants.KVM_PATH
739
    if not os.path.exists(constants.SOCAT_PATH):
740
      return "The socat binary ('%s') does not exist." % constants.SOCAT_PATH
741

    
742

    
743
  @classmethod
744
  def CheckParameterSyntax(cls, hvparams):
745
    """Check the given parameters for validity.
746

747
    @type hvparams:  dict
748
    @param hvparams: dictionary with parameter names/value
749
    @raise errors.HypervisorError: when a parameter is not valid
750

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

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

    
760
    if (hvparams[constants.HV_VNC_X509_VERIFY] and
761
        not hvparams[constants.HV_VNC_X509]):
762
      raise errors.HypervisorError("%s must be defined, if %s is" %
763
                                   (constants.HV_VNC_X509,
764
                                    constants.HV_VNC_X509_VERIFY))
765

    
766
    boot_order = hvparams[constants.HV_BOOT_ORDER]
767

    
768
    if (boot_order == constants.HT_BO_CDROM and
769
        not hvparams[constants.HV_CDROM_IMAGE_PATH]):
770
      raise errors.HypervisorError("Cannot boot from cdrom without an"
771
                                   " ISO path")
772
    if (boot_order == constants.HT_BO_NETWORK and
773
        hvparams[constants.HV_NIC_TYPE] == constants.HT_NIC_PARAVIRTUAL):
774
      raise errors.HypervisorError("Cannot boot from a paravirtual NIC. Please"
775
                                   " change the NIC type.")
776

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

781
    """
782
    cls.LinuxPowercycle()