hv_kvm.py 27 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
    ]

Guido Trotter's avatar
Guido Trotter committed
65
66
67
  _MIGRATION_STATUS_RE = re.compile('Migration\s+status:\s+(\w+)',
                                    re.M | re.I)

Guido Trotter's avatar
Guido Trotter committed
68
69
70
71
  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.
72
73
74
    for mydir in self._DIRS:
      if not os.path.exists(mydir):
        os.mkdir(mydir)
Guido Trotter's avatar
Guido Trotter committed
75

76
77
78
79
80
81
82
83
84
85
  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)

86
87
  @classmethod
  def _InstanceMonitor(cls, instance_name):
88
89
90
    """Returns the instance monitor socket name

    """
91
    return '%s/%s.monitor' % (cls._CTRL_DIR, instance_name)
92

93
94
  @classmethod
  def _InstanceSerial(cls, instance_name):
95
96
97
    """Returns the instance serial socket name

    """
98
    return '%s/%s.serial' % (cls._CTRL_DIR, instance_name)
99

100
101
  @classmethod
  def _InstanceKVMRuntime(cls, instance_name):
102
103
104
    """Returns the instance KVM runtime filename

    """
105
    return '%s/%s.runtime' % (cls._CONF_DIR, instance_name)
106

Guido Trotter's avatar
Guido Trotter committed
107
108
109
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
  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
151
152
    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
153
154
155
156

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

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

Iustin Pop's avatar
Iustin Pop committed
165
166
167
    @param instance_name: the instance name

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

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

    cmdline_file = "/proc/%s/cmdline" % pid
    try:
      fh = open(cmdline_file, 'r')
      try:
        cmdline = fh.read()
      finally:
        fh.close()
181
    except EnvironmentError, err:
Guido Trotter's avatar
Guido Trotter committed
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
      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
203
204
    @return: list of tuples (name, id, memory, vcpus, stat, times)

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

    return data

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

    """
223
    pidfile, pid, alive = self._InstancePidAlive(instance.name)
Guido Trotter's avatar
Guido Trotter committed
224
225
    kvm = constants.KVM_PATH
    kvm_cmd = [kvm]
226
227
    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
228
229
230
231
    kvm_cmd.extend(['-pidfile', pidfile])
    # used just by the vnc server, if enabled
    kvm_cmd.extend(['-name', instance.name])
    kvm_cmd.extend(['-daemonize'])
232
    if not instance.hvparams[constants.HV_ACPI]:
Guido Trotter's avatar
Guido Trotter committed
233
234
      kvm_cmd.extend(['-no-acpi'])

235
236
    boot_disk = (instance.hvparams[constants.HV_BOOT_ORDER] == "disk")
    boot_cdrom = (instance.hvparams[constants.HV_BOOT_ORDER] == "cdrom")
237
    for cfdev, dev_path in block_devices:
238
239
240
      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
241
      # TODO: handle FD_LOOP and FD_BLKTAP (?)
242
      if boot_disk:
Guido Trotter's avatar
Guido Trotter committed
243
        boot_val = ',boot=on'
244
        boot_disk = False
Guido Trotter's avatar
Guido Trotter committed
245
246
247
      else:
        boot_val = ''

Guido Trotter's avatar
Guido Trotter committed
248
249
250
      # TODO: handle different if= types
      if_val = ',if=virtio'

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

254
255
256
257
258
259
260
261
    iso_image = instance.hvparams[constants.HV_CDROM_IMAGE_PATH]
    if iso_image:
      options = ',format=raw,if=virtio,media=cdrom'
      if boot_cdrom:
        options = '%s,boot=on' % options
      drive_val = 'file=%s%s' % (iso_image, options)
      kvm_cmd.extend(['-drive', drive_val])

262
263
264
265
266
267
268
269
270
271
272
    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
273

274
275
276
277
    # FIXME: handle vnc password
    vnc_bind_address = instance.hvparams[constants.HV_VNC_BIND_ADDRESS]
    if vnc_bind_address:
      kvm_cmd.extend(['-usbdevice', 'tablet'])
278
279
280
281
282
283
284
      if utils.IsValidIP(vnc_bind_address):
        if instance.network_port > constants.HT_HVM_VNC_BASE_PORT:
          display = instance.network_port - constants.HT_HVM_VNC_BASE_PORT
          if vnc_bind_address == '0.0.0.0':
            vnc_arg = ':%d' % (display)
          else:
            vnc_arg = '%s:%d' % (constants.HV_VNC_BIND_ADDRESS, display)
285
        else:
286
287
288
289
290
          logging.error("Network port is not a valid VNC display (%d < %d)."
                        " Not starting VNC" %
                        (instance.network_port,
                         constants.HT_HVM_VNC_BASE_PORT))
          vnc_arg = 'none'
291
292
293
294
295
296
297
298
299
300
301
302
303
304

        # 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)

305
      else:
306
307
        vnc_arg = 'unix:%s/%s.vnc' % (vnc_bind_address, instance.name)

308
      kvm_cmd.extend(['-vnc', vnc_arg])
309
310
311
    else:
      kvm_cmd.extend(['-nographic'])

312
313
    monitor_dev = 'unix:%s,server,nowait' % \
      self._InstanceMonitor(instance.name)
Guido Trotter's avatar
Guido Trotter committed
314
    kvm_cmd.extend(['-monitor', monitor_dev])
315
316
317
318
319
    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
320

321
322
323
324
325
326
    # 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

    return (kvm_cmd, kvm_nics)

327
328
329
330
331
332
333
  def _WriteKVMRuntime(self, instance_name, data):
    """Write an instance's KVM runtime

    """
    try:
      utils.WriteFile(self._InstanceKVMRuntime(instance_name),
                      data=data)
334
    except EnvironmentError, err:
335
336
337
338
339
340
341
342
      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))
343
    except EnvironmentError, err:
344
345
346
347
348
349
350
351
352
353
354
355
      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

    """
    kvm_cmd, kvm_nics = kvm_runtime
    serialized_nics = [nic.ToDict() for nic in kvm_nics]
    serialized_form = serializer.Dump((kvm_cmd, serialized_nics))
    self._WriteKVMRuntime(instance.name, serialized_form)

Guido Trotter's avatar
Guido Trotter committed
356
  def _LoadKVMRuntime(self, instance, serialized_runtime=None):
357
358
359
    """Load an instance's KVM runtime

    """
Guido Trotter's avatar
Guido Trotter committed
360
361
362
    if not serialized_runtime:
      serialized_runtime = self._ReadKVMRuntime(instance.name)
    loaded_runtime = serializer.Load(serialized_runtime)
363
364
365
366
    kvm_cmd, serialized_nics = loaded_runtime
    kvm_nics = [objects.NIC.FromDict(snic) for snic in serialized_nics]
    return (kvm_cmd, kvm_nics)

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

Guido Trotter's avatar
Guido Trotter committed
370
371
372
    @type incoming: tuple of strings
    @param incoming: (target_host_ip, port)

373
    """
374
375
    pidfile, pid, alive = self._InstancePidAlive(instance.name)
    if alive:
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
      raise errors.HypervisorError("Failed to start instance %s: %s" %
                                   (instance.name, "already running"))

    temp_files = []

    kvm_cmd, kvm_nics = kvm_runtime

    if not kvm_nics:
      kvm_cmd.extend(['-net', 'none'])
    else:
      for nic_seq, nic in enumerate(kvm_nics):
        nic_val = "nic,macaddr=%s,model=virtio" % nic.mac
        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
393
394
395
396
    if incoming:
      target, port = incoming
      kvm_cmd.extend(['-incoming', 'tcp:%s:%s' % (target, port)])

Guido Trotter's avatar
Guido Trotter committed
397
398
399
400
401
402
403
404
405
406
    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))

407
408
    for filename in temp_files:
      utils.RemoveFile(filename)
Guido Trotter's avatar
Guido Trotter committed
409

410
411
412
413
  def StartInstance(self, instance, block_devices, extra_args):
    """Start an instance.

    """
414
415
    pidfile, pid, alive = self._InstancePidAlive(instance.name)
    if alive:
416
417
418
419
      raise errors.HypervisorError("Failed to start instance %s: %s" %
                                   (instance.name, "already running"))

    kvm_runtime = self._GenerateKVMRuntime(instance, block_devices, extra_args)
420
    self._SaveKVMRuntime(instance, kvm_runtime)
421
422
    self._ExecuteKVMRuntime(instance, kvm_runtime)

423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
  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" %
             (instance.name, result.stdout, result.stderr, result.fail_reason))
      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
454
455
456
457
  def StopInstance(self, instance, force=False):
    """Stop an instance.

    """
458
459
    pidfile, pid, alive = self._InstancePidAlive(instance.name)
    if pid > 0 and alive:
460
      if force or not instance.hvparams[constants.HV_ACPI]:
Guido Trotter's avatar
Guido Trotter committed
461
462
        utils.KillProcess(pid)
      else:
463
        self._RetryInstancePowerdown(instance, pid)
Guido Trotter's avatar
Guido Trotter committed
464
465

    if not utils.IsProcessAlive(pid):
466
      utils.RemoveFile(pidfile)
467
468
      utils.RemoveFile(self._InstanceMonitor(instance.name))
      utils.RemoveFile(self._InstanceSerial(instance.name))
469
      utils.RemoveFile(self._InstanceKVMRuntime(instance.name))
470
471
472
      return True
    else:
      return False
Guido Trotter's avatar
Guido Trotter committed
473
474
475
476
477
478
479
480

  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.
481
482
483
484
    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
485
486
487
488
489
490
491
492
493
    # 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
494

Guido Trotter's avatar
Guido Trotter committed
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
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
  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)
574
575
576
577
578
        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
579
580
581
582
583
584
585
586
587
588
        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
589
590
591
  def GetNodeInfo(self):
    """Return information about the node.

Iustin Pop's avatar
Iustin Pop committed
592
593
594
595
    @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
596
597
598
599
600
601
602
603
604
605
606
607

    """
    # 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()
608
    except EnvironmentError, err:
Guido Trotter's avatar
Guido Trotter committed
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
      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

    return result

641
  @classmethod
642
  def GetShellCommandForConsole(cls, instance, hvparams, beparams):
Guido Trotter's avatar
Guido Trotter committed
643
644
645
    """Return a command for connecting to the console of an instance.

    """
646
647
648
649
650
651
652
653
654
655
656
657
658
    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
659
660
661
662
663
664
665
666
667
668
669

    vnc_bind_address = hvparams[constants.HV_VNC_BIND_ADDRESS]
    if vnc_bind_address:
      if instance.network_port > constants.HT_HVM_VNC_BASE_PORT:
        display = instance.network_port - constants.HT_HVM_VNC_BASE_PORT
        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)

670
    return shell_command
Guido Trotter's avatar
Guido Trotter committed
671
672
673
674
675
676
677
678
679

  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
680
681
682
    if not os.path.exists(constants.SOCAT_PATH):
      return "The socat binary ('%s') does not exist." % constants.SOCAT_PATH

683
684
685
686
687
688
689
690
691
692
693
694
695

  @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

    """
696
    super(KVMHypervisor, cls).CheckParameterSyntax(hvparams)
697

698
699
700
701
702
    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")
703

704
705
706
      if not hvparams[constants.HV_ROOT_PATH]:
        raise errors.HypervisorError("Need a root partition for the instance"
                                     ", if a kernel is defined")
707

708
709
    if hvparams[constants.HV_INITRD_PATH]:
      if not os.path.isabs(hvparams[constants.HV_INITRD_PATH]):
710
        raise errors.HypervisorError("The initrd path must an absolute path"
711
712
                                     ", if defined")

713
714
715
    vnc_bind_address = hvparams[constants.HV_VNC_BIND_ADDRESS]
    if vnc_bind_address:
      if not utils.IsValidIP(vnc_bind_address):
716
717
718
719
720
        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)
721

722
723
724
725
726
727
728
729
730
731
732
    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")

733
734
735
736
737
738
739
740
741
    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]
    if boot_order not in ('cdrom', 'disk'):
      raise errors.HypervisorError("The boot order must be 'cdrom' or 'disk'")

742
743
744
745
746
747
748
  def ValidateParameters(self, hvparams):
    """Check the given parameters for validity.

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

    """
749
    super(KVMHypervisor, self).ValidateParameters(hvparams)
750
751

    kernel_path = hvparams[constants.HV_KERNEL_PATH]
752
    if kernel_path and not os.path.isfile(kernel_path):
753
754
755
756
757
758
      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)
759
760
761
762
763
764
765
766
767
768
769
770

    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)

771
772
773
774
775
776
    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)