hv_kvm.py 29.5 KB
Newer Older
Guido Trotter's avatar
Guido Trotter committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#
#

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


"""KVM hypervisor

"""

import os
import os.path
import re
import tempfile
30
import time
Guido Trotter's avatar
Guido Trotter committed
31
import logging
Guido Trotter's avatar
Guido Trotter committed
32
33
34
35
36
from cStringIO import StringIO

from ganeti import utils
from ganeti import constants
from ganeti import errors
37
38
from ganeti import serializer
from ganeti import objects
Guido Trotter's avatar
Guido Trotter committed
39
40
41
42
from ganeti.hypervisor import hv_base


class KVMHypervisor(hv_base.BaseHypervisor):
Guido Trotter's avatar
Guido Trotter committed
43
  """KVM hypervisor interface"""
Guido Trotter's avatar
Guido Trotter committed
44
45

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

51
52
53
  PARAMETERS = [
    constants.HV_KERNEL_PATH,
    constants.HV_INITRD_PATH,
54
    constants.HV_ROOT_PATH,
55
    constants.HV_ACPI,
56
    constants.HV_SERIAL_CONSOLE,
57
    constants.HV_VNC_BIND_ADDRESS,
58
59
60
    constants.HV_VNC_TLS,
    constants.HV_VNC_X509,
    constants.HV_VNC_X509_VERIFY,
61
62
    constants.HV_CDROM_IMAGE_PATH,
    constants.HV_BOOT_ORDER,
63
64
    constants.HV_NIC_TYPE,
    constants.HV_DISK_TYPE,
65
    constants.HV_USB_MOUSE,
66
67
    ]

Guido Trotter's avatar
Guido Trotter committed
68
69
70
  _MIGRATION_STATUS_RE = re.compile('Migration\s+status:\s+(\w+)',
                                    re.M | re.I)

Guido Trotter's avatar
Guido Trotter committed
71
72
73
74
  def __init__(self):
    hv_base.BaseHypervisor.__init__(self)
    # Let's make sure the directories we need exist, even if the RUN_DIR lives
    # in a tmpfs filesystem or has been otherwise wiped out.
75
76
77
    for mydir in self._DIRS:
      if not os.path.exists(mydir):
        os.mkdir(mydir)
Guido Trotter's avatar
Guido Trotter committed
78

79
80
81
82
83
84
85
86
87
88
  def _InstancePidAlive(self, instance_name):
    """Returns the instance pid and pidfile

    """
    pidfile = "%s/%s" % (self._PIDS_DIR, instance_name)
    pid = utils.ReadPidFile(pidfile)
    alive = utils.IsProcessAlive(pid)

    return (pidfile, pid, alive)

89
90
  @classmethod
  def _InstanceMonitor(cls, instance_name):
91
92
93
    """Returns the instance monitor socket name

    """
94
    return '%s/%s.monitor' % (cls._CTRL_DIR, instance_name)
95

96
97
  @classmethod
  def _InstanceSerial(cls, instance_name):
98
99
100
    """Returns the instance serial socket name

    """
101
    return '%s/%s.serial' % (cls._CTRL_DIR, instance_name)
102

103
104
  @classmethod
  def _InstanceKVMRuntime(cls, instance_name):
105
106
107
    """Returns the instance KVM runtime filename

    """
108
    return '%s/%s.runtime' % (cls._CONF_DIR, instance_name)
109

Guido Trotter's avatar
Guido Trotter committed
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
  def _WriteNetScript(self, instance, seq, nic):
    """Write a script to connect a net interface to the proper bridge.

    This can be used by any qemu-type hypervisor.

    @param instance: instance we're acting on
    @type instance: instance object
    @param seq: nic sequence number
    @type seq: int
    @param nic: nic we're acting on
    @type nic: nic object
    @return: netscript file name
    @rtype: string

    """
    script = StringIO()
    script.write("#!/bin/sh\n")
    script.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
    script.write("export INSTANCE=%s\n" % instance.name)
    script.write("export MAC=%s\n" % nic.mac)
    script.write("export IP=%s\n" % nic.ip)
    script.write("export BRIDGE=%s\n" % nic.bridge)
    script.write("export INTERFACE=$1\n")
    # TODO: make this configurable at ./configure time
    script.write("if [ -x /etc/ganeti/kvm-vif-bridge ]; then\n")
    script.write("  # Execute the user-specific vif file\n")
    script.write("  /etc/ganeti/kvm-vif-bridge\n")
    script.write("else\n")
    script.write("  # Connect the interface to the bridge\n")
    script.write("  /sbin/ifconfig $INTERFACE 0.0.0.0 up\n")
    script.write("  /usr/sbin/brctl addif $BRIDGE $INTERFACE\n")
    script.write("fi\n\n")
    # As much as we'd like to put this in our _ROOT_DIR, that will happen to be
    # mounted noexec sometimes, so we'll have to find another place.
    (tmpfd, tmpfile_name) = tempfile.mkstemp()
    tmpfile = os.fdopen(tmpfd, 'w')
    tmpfile.write(script.getvalue())
    tmpfile.close()
    os.chmod(tmpfile_name, 0755)
    return tmpfile_name

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

Iustin Pop's avatar
Iustin Pop committed
154
155
    We can do this by listing our live instances directory and
    checking whether the associated kvm process is still alive.
Guido Trotter's avatar
Guido Trotter committed
156
157
158
159

    """
    result = []
    for name in os.listdir(self._PIDS_DIR):
160
161
      filename = "%s/%s" % (self._PIDS_DIR, name)
      if utils.IsProcessAlive(utils.ReadPidFile(filename)):
Guido Trotter's avatar
Guido Trotter committed
162
163
164
165
166
167
        result.append(name)
    return result

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

Iustin Pop's avatar
Iustin Pop committed
168
169
170
    @param instance_name: the instance name

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

    """
173
174
    pidfile, pid, alive = self._InstancePidAlive(instance_name)
    if not alive:
Guido Trotter's avatar
Guido Trotter committed
175
176
177
178
179
180
181
182
183
      return None

    cmdline_file = "/proc/%s/cmdline" % pid
    try:
      fh = open(cmdline_file, 'r')
      try:
        cmdline = fh.read()
      finally:
        fh.close()
184
    except EnvironmentError, err:
Guido Trotter's avatar
Guido Trotter committed
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
      raise errors.HypervisorError("Failed to list instance %s: %s" %
                                   (instance_name, err))

    memory = 0
    vcpus = 0
    stat = "---b-"
    times = "0"

    arg_list = cmdline.split('\x00')
    while arg_list:
      arg =  arg_list.pop(0)
      if arg == '-m':
        memory = arg_list.pop(0)
      elif arg == '-smp':
        vcpus = arg_list.pop(0)

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

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

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

Guido Trotter's avatar
Guido Trotter committed
208
209
210
    """
    data = []
    for name in os.listdir(self._PIDS_DIR):
211
212
      filename = "%s/%s" % (self._PIDS_DIR, name)
      if utils.IsProcessAlive(utils.ReadPidFile(filename)):
213
214
215
216
217
218
        try:
          info = self.GetInstanceInfo(name)
        except errors.HypervisorError, err:
          continue
        if info:
          data.append(info)
Guido Trotter's avatar
Guido Trotter committed
219
220
221

    return data

222
223
  def _GenerateKVMRuntime(self, instance, block_devices, extra_args):
    """Generate KVM information to start an instance.
Guido Trotter's avatar
Guido Trotter committed
224
225

    """
226
    pidfile, pid, alive = self._InstancePidAlive(instance.name)
Guido Trotter's avatar
Guido Trotter committed
227
228
    kvm = constants.KVM_PATH
    kvm_cmd = [kvm]
229
230
    kvm_cmd.extend(['-m', instance.beparams[constants.BE_MEMORY]])
    kvm_cmd.extend(['-smp', instance.beparams[constants.BE_VCPUS]])
Guido Trotter's avatar
Guido Trotter committed
231
232
233
234
    kvm_cmd.extend(['-pidfile', pidfile])
    # used just by the vnc server, if enabled
    kvm_cmd.extend(['-name', instance.name])
    kvm_cmd.extend(['-daemonize'])
235
    if not instance.hvparams[constants.HV_ACPI]:
Guido Trotter's avatar
Guido Trotter committed
236
237
      kvm_cmd.extend(['-no-acpi'])

238
239
    boot_disk = (instance.hvparams[constants.HV_BOOT_ORDER] == "disk")
    boot_cdrom = (instance.hvparams[constants.HV_BOOT_ORDER] == "cdrom")
Guido Trotter's avatar
Guido Trotter committed
240
241
242
243
    boot_network = (instance.hvparams[constants.HV_BOOT_ORDER] == "network")

    if boot_network:
      kvm_cmd.extend(['-boot', 'n'])
244
245
246
247
248
249

    disk_type = instance.hvparams[constants.HV_DISK_TYPE]
    if disk_type == constants.HT_DISK_PARAVIRTUAL:
      if_val = ',if=virtio'
    else:
      if_val = ',if=%s' % disk_type
250
    for cfdev, dev_path in block_devices:
251
252
253
      if cfdev.mode != constants.DISK_RDWR:
        raise errors.HypervisorError("Instance has read-only disks which"
                                     " are not supported by KVM")
Guido Trotter's avatar
Guido Trotter committed
254
      # TODO: handle FD_LOOP and FD_BLKTAP (?)
255
      if boot_disk:
256
        kvm_cmd.extend(['-boot', 'c'])
Guido Trotter's avatar
Guido Trotter committed
257
        boot_val = ',boot=on'
Guido Trotter's avatar
Guido Trotter committed
258
        # We only boot from the first disk
259
        boot_disk = False
Guido Trotter's avatar
Guido Trotter committed
260
261
262
      else:
        boot_val = ''

263
      drive_val = 'file=%s,format=raw%s%s' % (dev_path, if_val, boot_val)
Guido Trotter's avatar
Guido Trotter committed
264
265
      kvm_cmd.extend(['-drive', drive_val])

266
267
    iso_image = instance.hvparams[constants.HV_CDROM_IMAGE_PATH]
    if iso_image:
268
      options = ',format=raw,media=cdrom'
269
      if boot_cdrom:
270
        kvm_cmd.extend(['-boot', 'd'])
271
        options = '%s,boot=on' % options
272
273
      else:
        options = '%s,if=virtio' % options
274
275
276
      drive_val = 'file=%s%s' % (iso_image, options)
      kvm_cmd.extend(['-drive', drive_val])

277
278
279
280
281
282
283
284
285
286
287
    kernel_path = instance.hvparams[constants.HV_KERNEL_PATH]
    if kernel_path:
      kvm_cmd.extend(['-kernel', kernel_path])
      initrd_path = instance.hvparams[constants.HV_INITRD_PATH]
      if initrd_path:
        kvm_cmd.extend(['-initrd', initrd_path])
      root_append = 'root=%s ro' % instance.hvparams[constants.HV_ROOT_PATH]
      if instance.hvparams[constants.HV_SERIAL_CONSOLE]:
        kvm_cmd.extend(['-append', 'console=ttyS0,38400 %s' % root_append])
      else:
        kvm_cmd.extend(['-append', root_append])
Guido Trotter's avatar
Guido Trotter committed
288

289
290
291
292
293
    mouse_type = instance.hvparams[constants.HV_USB_MOUSE]
    if mouse_type:
      kvm_cmd.extend(['-usb'])
      kvm_cmd.extend(['-usbdevice', mouse_type])

294
295
296
    # FIXME: handle vnc password
    vnc_bind_address = instance.hvparams[constants.HV_VNC_BIND_ADDRESS]
    if vnc_bind_address:
297
      if utils.IsValidIP(vnc_bind_address):
298
299
        if instance.network_port > constants.VNC_BASE_PORT:
          display = instance.network_port - constants.VNC_BASE_PORT
300
301
302
303
          if vnc_bind_address == '0.0.0.0':
            vnc_arg = ':%d' % (display)
          else:
            vnc_arg = '%s:%d' % (constants.HV_VNC_BIND_ADDRESS, display)
304
        else:
305
306
307
          logging.error("Network port is not a valid VNC display (%d < %d)."
                        " Not starting VNC" %
                        (instance.network_port,
308
                         constants.VNC_BASE_PORT))
309
          vnc_arg = 'none'
310
311
312
313
314
315
316
317
318
319
320
321
322
323

        # Only allow tls and other option when not binding to a file, for now.
        # kvm/qemu gets confused otherwise about the filename to use.
        vnc_append = ''
        if instance.hvparams[constants.HV_VNC_TLS]:
          vnc_append = '%s,tls' % vnc_append
          if instance.hvparams[constants.HV_VNC_X509_VERIFY]:
            vnc_append = '%s,x509verify=%s' % (vnc_append,
              instance.hvparams[constants.HV_VNC_X509])
          elif instance.hvparams[constants.HV_VNC_X509]:
            vnc_append = '%s,x509=%s' % (vnc_append,
              instance.hvparams[constants.HV_VNC_X509])
        vnc_arg = '%s%s' % (vnc_arg, vnc_append)

324
      else:
325
326
        vnc_arg = 'unix:%s/%s.vnc' % (vnc_bind_address, instance.name)

327
      kvm_cmd.extend(['-vnc', vnc_arg])
328
329
330
    else:
      kvm_cmd.extend(['-nographic'])

331
332
    monitor_dev = 'unix:%s,server,nowait' % \
      self._InstanceMonitor(instance.name)
Guido Trotter's avatar
Guido Trotter committed
333
    kvm_cmd.extend(['-monitor', monitor_dev])
334
335
336
337
338
    if instance.hvparams[constants.HV_SERIAL_CONSOLE]:
      serial_dev = 'unix:%s,server,nowait' % self._InstanceSerial(instance.name)
      kvm_cmd.extend(['-serial', serial_dev])
    else:
      kvm_cmd.extend(['-serial', 'none'])
Guido Trotter's avatar
Guido Trotter committed
339

340
341
342
    # Save the current instance nics, but defer their expansion as parameters,
    # as we'll need to generate executable temp files for them.
    kvm_nics = instance.nics
343
    hvparams = instance.hvparams
344

345
    return (kvm_cmd, kvm_nics, hvparams)
346

347
348
349
350
351
352
353
  def _WriteKVMRuntime(self, instance_name, data):
    """Write an instance's KVM runtime

    """
    try:
      utils.WriteFile(self._InstanceKVMRuntime(instance_name),
                      data=data)
354
    except EnvironmentError, err:
355
356
357
358
359
360
361
362
      raise errors.HypervisorError("Failed to save KVM runtime file: %s" % err)

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

    """
    try:
      file_content = utils.ReadFile(self._InstanceKVMRuntime(instance_name))
363
    except EnvironmentError, err:
364
365
366
367
368
369
370
      raise errors.HypervisorError("Failed to load KVM runtime file: %s" % err)
    return file_content

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

    """
371
    kvm_cmd, kvm_nics, hvparams = kvm_runtime
372
    serialized_nics = [nic.ToDict() for nic in kvm_nics]
373
    serialized_form = serializer.Dump((kvm_cmd, serialized_nics, hvparams))
374
375
    self._WriteKVMRuntime(instance.name, serialized_form)

Guido Trotter's avatar
Guido Trotter committed
376
  def _LoadKVMRuntime(self, instance, serialized_runtime=None):
377
378
379
    """Load an instance's KVM runtime

    """
Guido Trotter's avatar
Guido Trotter committed
380
381
382
    if not serialized_runtime:
      serialized_runtime = self._ReadKVMRuntime(instance.name)
    loaded_runtime = serializer.Load(serialized_runtime)
383
    kvm_cmd, serialized_nics, hvparams = loaded_runtime
384
    kvm_nics = [objects.NIC.FromDict(snic) for snic in serialized_nics]
385
    return (kvm_cmd, kvm_nics, hvparams)
386

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

Guido Trotter's avatar
Guido Trotter committed
390
391
392
    @type incoming: tuple of strings
    @param incoming: (target_host_ip, port)

393
    """
394
395
    pidfile, pid, alive = self._InstancePidAlive(instance.name)
    if alive:
396
397
398
399
400
      raise errors.HypervisorError("Failed to start instance %s: %s" %
                                   (instance.name, "already running"))

    temp_files = []

401
    kvm_cmd, kvm_nics, hvparams = kvm_runtime
402
403
404
405

    if not kvm_nics:
      kvm_cmd.extend(['-net', 'none'])
    else:
406
407
408
409
410
411
      nic_type = hvparams[constants.HV_NIC_TYPE]
      if nic_type == constants.HT_NIC_PARAVIRTUAL:
        nic_model = "model=virtio"
      else:
        nic_model = "model=%s" % nic_type

412
      for nic_seq, nic in enumerate(kvm_nics):
413
        nic_val = "nic,macaddr=%s,%s" % (nic.mac, nic_model)
414
415
416
417
418
        script = self._WriteNetScript(instance, nic_seq, nic)
        kvm_cmd.extend(['-net', nic_val])
        kvm_cmd.extend(['-net', 'tap,script=%s' % script])
        temp_files.append(script)

Guido Trotter's avatar
Guido Trotter committed
419
420
421
422
    if incoming:
      target, port = incoming
      kvm_cmd.extend(['-incoming', 'tcp:%s:%s' % (target, port)])

Guido Trotter's avatar
Guido Trotter committed
423
424
425
426
427
428
429
430
431
432
    result = utils.RunCmd(kvm_cmd)
    if result.failed:
      raise errors.HypervisorError("Failed to start instance %s: %s (%s)" %
                                   (instance.name, result.fail_reason,
                                    result.output))

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

433
434
    for filename in temp_files:
      utils.RemoveFile(filename)
Guido Trotter's avatar
Guido Trotter committed
435

436
437
438
439
  def StartInstance(self, instance, block_devices, extra_args):
    """Start an instance.

    """
440
441
    pidfile, pid, alive = self._InstancePidAlive(instance.name)
    if alive:
442
443
444
445
      raise errors.HypervisorError("Failed to start instance %s: %s" %
                                   (instance.name, "already running"))

    kvm_runtime = self._GenerateKVMRuntime(instance, block_devices, extra_args)
446
    self._SaveKVMRuntime(instance, kvm_runtime)
447
448
    self._ExecuteKVMRuntime(instance, kvm_runtime)

449
450
451
452
453
454
455
456
457
458
459
460
  def _CallMonitorCommand(self, instance_name, command):
    """Invoke a command on the instance monitor.

    """
    socat = ("echo %s | %s STDIO UNIX-CONNECT:%s" %
             (utils.ShellQuote(command),
              constants.SOCAT_PATH,
              utils.ShellQuote(self._InstanceMonitor(instance_name))))
    result = utils.RunCmd(socat)
    if result.failed:
      msg = ("Failed to send command '%s' to instance %s."
             " output: %s, error: %s, fail_reason: %s" %
461
462
             (command, instance_name,
              result.stdout, result.stderr, result.fail_reason))
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
      raise errors.HypervisorError(msg)

    return result

  def _RetryInstancePowerdown(self, instance, pid, timeout=30):
    """Wait for an instance  to power down.

    """
    # Wait up to $timeout seconds
    end = time.time() + timeout
    wait = 1
    while time.time() < end and utils.IsProcessAlive(pid):
      self._CallMonitorCommand(instance.name, 'system_powerdown')
      time.sleep(wait)
      # Make wait time longer for next try
      if wait < 5:
        wait *= 1.3

Guido Trotter's avatar
Guido Trotter committed
481
482
483
484
  def StopInstance(self, instance, force=False):
    """Stop an instance.

    """
485
486
    pidfile, pid, alive = self._InstancePidAlive(instance.name)
    if pid > 0 and alive:
487
      if force or not instance.hvparams[constants.HV_ACPI]:
Guido Trotter's avatar
Guido Trotter committed
488
489
        utils.KillProcess(pid)
      else:
490
        self._RetryInstancePowerdown(instance, pid)
Guido Trotter's avatar
Guido Trotter committed
491
492

    if not utils.IsProcessAlive(pid):
493
      utils.RemoveFile(pidfile)
494
495
      utils.RemoveFile(self._InstanceMonitor(instance.name))
      utils.RemoveFile(self._InstanceSerial(instance.name))
496
      utils.RemoveFile(self._InstanceKVMRuntime(instance.name))
497
498
499
      return True
    else:
      return False
Guido Trotter's avatar
Guido Trotter committed
500
501
502
503
504
505
506
507

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

    """
    # For some reason if we do a 'send-key ctrl-alt-delete' to the control
    # socket the instance will stop, but now power up again. So we'll resort
    # to shutdown and restart.
508
509
510
511
    pidfile, pid, alive = self._InstancePidAlive(instance.name)
    if not alive:
      raise errors.HypervisorError("Failed to reboot instance %s: not running" %
                                             (instance.name))
Guido Trotter's avatar
Guido Trotter committed
512
513
514
515
516
517
518
519
520
    # StopInstance will delete the saved KVM runtime so:
    # ...first load it...
    kvm_runtime = self._LoadKVMRuntime(instance)
    # ...now we can safely call StopInstance...
    if not self.StopInstance(instance):
      self.StopInstance(instance, force=True)
    # ...and finally we can save it again, and execute it...
    self._SaveKVMRuntime(instance, kvm_runtime)
    self._ExecuteKVMRuntime(instance, kvm_runtime)
Guido Trotter's avatar
Guido Trotter committed
521

Guido Trotter's avatar
Guido Trotter committed
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
  def MigrationInfo(self, instance):
    """Get instance information to perform a migration.

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

    """
    return self._ReadKVMRuntime(instance.name)

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

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

    """
    kvm_runtime = self._LoadKVMRuntime(instance, serialized_runtime=info)
    incoming_address = (target, constants.KVM_MIGRATION_PORT)
    self._ExecuteKVMRuntime(instance, kvm_runtime, incoming=incoming_address)

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

    Stop the incoming mode KVM.

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

    """
    if success:
      self._WriteKVMRuntime(instance.name, info)
    else:
      self.StopInstance(instance, force=True)

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

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

    @type instance_name: string
    @param instance_name: name of the instance to be migrated
    @type target: string
    @param target: ip address of the target node
    @type live: boolean
    @param live: perform a live migration

    """
    pidfile, pid, alive = self._InstancePidAlive(instance_name)
    if not alive:
      raise errors.HypervisorError("Instance not running, cannot migrate")

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

    migrate_command = ('migrate -d tcp:%s:%s' %
                       (target, constants.KVM_MIGRATION_PORT))
    self._CallMonitorCommand(instance_name, migrate_command)

    info_command = 'info migrate'
    done = False
    while not done:
      result = self._CallMonitorCommand(instance_name, info_command)
      match = self._MIGRATION_STATUS_RE.search(result.stdout)
      if not match:
        raise errors.HypervisorError("Unknown 'info migrate' result: %s" %
                                     result.stdout)
      else:
        status = match.group(1)
        if status == 'completed':
          done = True
        elif status == 'active':
          time.sleep(2)
601
602
603
604
605
        elif status == 'failed' or status == 'cancelled':
          if not live:
            self._CallMonitorCommand(instance_name, 'cont')
          raise errors.HypervisorError("Migration %s at the kvm level" %
                                       status)
Guido Trotter's avatar
Guido Trotter committed
606
607
608
609
610
611
612
613
614
615
        else:
          logging.info("KVM: unknown migration status '%s'" % status)
          time.sleep(2)

    utils.KillProcess(pid)
    utils.RemoveFile(pidfile)
    utils.RemoveFile(self._InstanceMonitor(instance_name))
    utils.RemoveFile(self._InstanceSerial(instance_name))
    utils.RemoveFile(self._InstanceKVMRuntime(instance_name))

Guido Trotter's avatar
Guido Trotter committed
616
617
618
  def GetNodeInfo(self):
    """Return information about the node.

Iustin Pop's avatar
Iustin Pop committed
619
620
621
622
    @return: a dict with the following keys (values in MiB):
          - memory_total: the total memory size on the node
          - memory_free: the available memory on the node for instances
          - memory_dom0: the memory used by the node itself, if available
Guido Trotter's avatar
Guido Trotter committed
623
624
625
626
627
628
629
630
631
632
633
634

    """
    # global ram usage from the xm info command
    # memory                 : 3583
    # free_memory            : 747
    # note: in xen 3, memory has changed to total_memory
    try:
      fh = file("/proc/meminfo")
      try:
        data = fh.readlines()
      finally:
        fh.close()
635
    except EnvironmentError, err:
Guido Trotter's avatar
Guido Trotter committed
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
      raise errors.HypervisorError("Failed to list node info: %s" % err)

    result = {}
    sum_free = 0
    for line in data:
      splitfields = line.split(":", 1)

      if len(splitfields) > 1:
        key = splitfields[0].strip()
        val = splitfields[1].strip()
        if key == 'MemTotal':
          result['memory_total'] = int(val.split()[0])/1024
        elif key in ('MemFree', 'Buffers', 'Cached'):
          sum_free += int(val.split()[0])/1024
        elif key == 'Active':
          result['memory_dom0'] = int(val.split()[0])/1024
    result['memory_free'] = sum_free

    cpu_total = 0
    try:
      fh = open("/proc/cpuinfo")
      try:
        cpu_total = len(re.findall("(?m)^processor\s*:\s*[0-9]+\s*$",
                                   fh.read()))
      finally:
        fh.close()
    except EnvironmentError, err:
      raise errors.HypervisorError("Failed to list node info: %s" % err)
    result['cpu_total'] = cpu_total
665
666
667
    # FIXME: export correct data here
    result['cpu_nodes'] = 1
    result['cpu_sockets'] = 1
Guido Trotter's avatar
Guido Trotter committed
668
669
670

    return result

671
  @classmethod
672
  def GetShellCommandForConsole(cls, instance, hvparams, beparams):
Guido Trotter's avatar
Guido Trotter committed
673
674
675
    """Return a command for connecting to the console of an instance.

    """
676
677
678
679
680
681
682
683
684
685
686
687
688
    if hvparams[constants.HV_SERIAL_CONSOLE]:
      # FIXME: The socat shell is not perfect. In particular the way we start
      # it ctrl+c will close it, rather than being passed to the other end.
      # On the other hand if we pass the option 'raw' (or ignbrk=1) there
      # will be no way of exiting socat (except killing it from another shell)
      # and ctrl+c doesn't work anyway, printing ^C rather than being
      # interpreted by kvm. For now we'll leave it this way, which at least
      # allows a minimal interaction and changes on the machine.
      shell_command = ("%s STDIO,echo=0,icanon=0 UNIX-CONNECT:%s" %
                       (constants.SOCAT_PATH,
                        utils.ShellQuote(cls._InstanceSerial(instance.name))))
    else:
      shell_command = "echo 'No serial shell for instance %s'" % instance.name
689
690
691

    vnc_bind_address = hvparams[constants.HV_VNC_BIND_ADDRESS]
    if vnc_bind_address:
692
693
      if instance.network_port > constants.VNC_BASE_PORT:
        display = instance.network_port - constants.VNC_BASE_PORT
694
695
696
697
698
699
        vnc_command = ("echo 'Instance has VNC listening on %s:%d"
                       " (display: %d)'" % (vnc_bind_address,
                                            instance.network_port,
                                            display))
        shell_command = "%s; %s" % (vnc_command, shell_command)

700
    return shell_command
Guido Trotter's avatar
Guido Trotter committed
701
702
703
704
705
706
707
708
709

  def Verify(self):
    """Verify the hypervisor.

    Check that the binary exists.

    """
    if not os.path.exists(constants.KVM_PATH):
      return "The kvm binary ('%s') does not exist." % constants.KVM_PATH
Guido Trotter's avatar
Guido Trotter committed
710
711
712
    if not os.path.exists(constants.SOCAT_PATH):
      return "The socat binary ('%s') does not exist." % constants.SOCAT_PATH

713
714
715
716
717
718
719
720
721
722
723
724
725

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

    For the KVM hypervisor, this only check the existence of the
    kernel.

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

    """
726
    super(KVMHypervisor, cls).CheckParameterSyntax(hvparams)
727

728
729
730
731
732
    kernel_path = hvparams[constants.HV_KERNEL_PATH]
    if kernel_path:
      if not os.path.isabs(hvparams[constants.HV_KERNEL_PATH]):
        raise errors.HypervisorError("The kernel path must be an absolute path"
                                     ", if defined")
733

734
735
736
      if not hvparams[constants.HV_ROOT_PATH]:
        raise errors.HypervisorError("Need a root partition for the instance"
                                     ", if a kernel is defined")
737

738
739
    if hvparams[constants.HV_INITRD_PATH]:
      if not os.path.isabs(hvparams[constants.HV_INITRD_PATH]):
740
        raise errors.HypervisorError("The initrd path must an absolute path"
741
742
                                     ", if defined")

743
744
745
    vnc_bind_address = hvparams[constants.HV_VNC_BIND_ADDRESS]
    if vnc_bind_address:
      if not utils.IsValidIP(vnc_bind_address):
746
747
748
749
750
        if not os.path.isabs(vnc_bind_address):
          raise errors.HypervisorError("The VNC bind address must be either"
                                       " a valid IP address or an absolute"
                                       " pathname. '%s' given" %
                                       vnc_bind_address)
751

752
753
754
755
756
757
758
759
760
761
762
    if hvparams[constants.HV_VNC_X509_VERIFY] and \
      not hvparams[constants.HV_VNC_X509]:
        raise errors.HypervisorError("%s must be defined, if %s is" %
                                     (constants.HV_VNC_X509,
                                      constants.HV_VNC_X509_VERIFY))

    if hvparams[constants.HV_VNC_X509]:
      if not os.path.isabs(hvparams[constants.HV_VNC_X509]):
        raise errors.HypervisorError("The vnc x509 path must an absolute path"
                                     ", if defined")

763
764
765
766
767
768
    iso_path = hvparams[constants.HV_CDROM_IMAGE_PATH]
    if iso_path and not os.path.isabs(iso_path):
      raise errors.HypervisorError("The path to the CDROM image must be"
                                   " an absolute path, if defined")

    boot_order = hvparams[constants.HV_BOOT_ORDER]
Guido Trotter's avatar
Guido Trotter committed
769
770
771
    if boot_order not in ('cdrom', 'disk', 'network'):
      raise errors.HypervisorError("The boot order must be 'cdrom', 'disk' or"
                                   " 'network'")
772

773
774
775
    if boot_order == 'cdrom' and not iso_path:
      raise errors.HypervisorError("Cannot boot from cdrom without an ISO path")

776
777
778
779
780
781
    nic_type = hvparams[constants.HV_NIC_TYPE]
    if nic_type not in constants.HT_KVM_VALID_NIC_TYPES:
      raise errors.HypervisorError("Invalid NIC type %s specified for the KVM"
                                   " hypervisor. Please choose one of: %s" %
                                   (nic_type,
                                    constants.HT_KVM_VALID_NIC_TYPES))
Guido Trotter's avatar
Guido Trotter committed
782
783
784
    elif boot_order == 'network' and nic_type == constants.HT_NIC_PARAVIRTUAL:
      raise errors.HypervisorError("Cannot boot from a paravirtual NIC. Please"
                                   " change the nic type.")
785
786
787
788
789
790
791
792

    disk_type = hvparams[constants.HV_DISK_TYPE]
    if disk_type not in constants.HT_KVM_VALID_DISK_TYPES:
      raise errors.HypervisorError("Invalid disk type %s specified for the KVM"
                                   " hypervisor. Please choose one of: %s" %
                                   (disk_type,
                                    constants.HT_KVM_VALID_DISK_TYPES))

793
794
795
796
797
798
    mouse_type = hvparams[constants.HV_USB_MOUSE]
    if mouse_type and mouse_type not in ('mouse', 'tablet'):
      raise errors.HypervisorError("Invalid usb mouse type %s specified for"
                                   " the KVM hyervisor. Please choose"
                                   " 'mouse' or 'tablet'" % mouse_type)

799
800
801
802
803
804
805
  def ValidateParameters(self, hvparams):
    """Check the given parameters for validity.

    For the KVM hypervisor, this checks the existence of the
    kernel.

    """
806
    super(KVMHypervisor, self).ValidateParameters(hvparams)
807
808

    kernel_path = hvparams[constants.HV_KERNEL_PATH]
809
    if kernel_path and not os.path.isfile(kernel_path):
810
811
812
813
814
815
      raise errors.HypervisorError("Instance kernel '%s' not found or"
                                   " not a file" % kernel_path)
    initrd_path = hvparams[constants.HV_INITRD_PATH]
    if initrd_path and not os.path.isfile(initrd_path):
      raise errors.HypervisorError("Instance initrd '%s' not found or"
                                   " not a file" % initrd_path)
816
817
818
819
820
821
822
823
824
825
826
827

    vnc_bind_address = hvparams[constants.HV_VNC_BIND_ADDRESS]
    if vnc_bind_address and not utils.IsValidIP(vnc_bind_address) and \
       not os.path.isdir(vnc_bind_address):
       raise errors.HypervisorError("Instance vnc bind address must be either"
                                    " an ip address or an existing directory")

    vnc_x509 = hvparams[constants.HV_VNC_X509]
    if vnc_x509 and not os.path.isdir(vnc_x509):
      raise errors.HypervisorError("Instance vnc x509 path '%s' not found"
                                   " or not a directory" % vnc_x509)

828
829
830
831
    iso_path = hvparams[constants.HV_CDROM_IMAGE_PATH]
    if iso_path and not os.path.isfile(iso_path):
      raise errors.HypervisorError("Instance cdrom image '%s' not found or"
                                   " not a file" % iso_path)