Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_kvm.py @ 64bfbc08

History | View | Annotate | Download (24.9 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 os.path.isabs(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_CDROM_IMAGE_PATH: hv_base.OPT_FILE_CHECK,
66
    constants.HV_BOOT_ORDER: \
67
    hv_base.ParamInSet(True, constants.HT_KVM_VALID_BO_TYPES),
68
    constants.HV_NIC_TYPE: \
69
    hv_base.ParamInSet(True, constants.HT_KVM_VALID_NIC_TYPES),
70
    constants.HV_DISK_TYPE: \
71
    hv_base.ParamInSet(True, constants.HT_KVM_VALID_DISK_TYPES),
72
    constants.HV_USB_MOUSE: \
73
    hv_base.ParamInSet(False, constants.HT_KVM_VALID_MOUSE_TYPES),
74
    }
75

    
76
  _MIGRATION_STATUS_RE = re.compile('Migration\s+status:\s+(\w+)',
77
                                    re.M | re.I)
78

    
79
  _KVM_NETWORK_SCRIPT = constants.SYSCONFDIR + "/ganeti/kvm-vif-bridge"
80

    
81
  ANCILLARY_FILES = [
82
    _KVM_NETWORK_SCRIPT,
83
    ]
84

    
85
  def __init__(self):
86
    hv_base.BaseHypervisor.__init__(self)
87
    # Let's make sure the directories we need exist, even if the RUN_DIR lives
88
    # in a tmpfs filesystem or has been otherwise wiped out.
89
    dirs = [(dir, constants.RUN_DIRS_MODE) for dir in self._DIRS]
90
    utils.EnsureDirs(dirs)
91

    
92
  def _InstancePidAlive(self, instance_name):
93
    """Returns the instance pid and pidfile
94

95
    """
96
    pidfile = "%s/%s" % (self._PIDS_DIR, instance_name)
97
    pid = utils.ReadPidFile(pidfile)
98
    alive = utils.IsProcessAlive(pid)
99

    
100
    return (pidfile, pid, alive)
101

    
102
  @classmethod
103
  def _InstanceMonitor(cls, instance_name):
104
    """Returns the instance monitor socket name
105

106
    """
107
    return '%s/%s.monitor' % (cls._CTRL_DIR, instance_name)
108

    
109
  @classmethod
110
  def _InstanceSerial(cls, instance_name):
111
    """Returns the instance serial socket name
112

113
    """
114
    return '%s/%s.serial' % (cls._CTRL_DIR, instance_name)
115

    
116
  @classmethod
117
  def _InstanceKVMRuntime(cls, instance_name):
118
    """Returns the instance KVM runtime filename
119

120
    """
121
    return '%s/%s.runtime' % (cls._CONF_DIR, instance_name)
122

    
123
  @classmethod
124
  def _RemoveInstanceRuntimeFiles(cls, pidfile, instance_name):
125
    """Removes an instance's rutime sockets/files.
126

127
    """
128
    utils.RemoveFile(pidfile)
129
    utils.RemoveFile(cls._InstanceMonitor(instance_name))
130
    utils.RemoveFile(cls._InstanceSerial(instance_name))
131
    utils.RemoveFile(cls._InstanceKVMRuntime(instance_name))
132

    
133
  def _WriteNetScript(self, instance, seq, nic):
134
    """Write a script to connect a net interface to the proper bridge.
135

136
    This can be used by any qemu-type hypervisor.
137

138
    @param instance: instance we're acting on
139
    @type instance: instance object
140
    @param seq: nic sequence number
141
    @type seq: int
142
    @param nic: nic we're acting on
143
    @type nic: nic object
144
    @return: netscript file name
145
    @rtype: string
146

147
    """
148
    script = StringIO()
149
    script.write("#!/bin/sh\n")
150
    script.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
151
    script.write("export INSTANCE=%s\n" % instance.name)
152
    script.write("export MAC=%s\n" % nic.mac)
153
    script.write("export IP=%s\n" % nic.ip)
154
    script.write("export BRIDGE=%s\n" % nic.bridge)
155
    script.write("export INTERFACE=$1\n")
156
    # TODO: make this configurable at ./configure time
157
    script.write("if [ -x '%s' ]; then\n" % self._KVM_NETWORK_SCRIPT)
158
    script.write("  # Execute the user-specific vif file\n")
159
    script.write("  %s\n" % self._KVM_NETWORK_SCRIPT)
160
    script.write("else\n")
161
    script.write("  # Connect the interface to the bridge\n")
162
    script.write("  /sbin/ifconfig $INTERFACE 0.0.0.0 up\n")
163
    script.write("  /usr/sbin/brctl addif $BRIDGE $INTERFACE\n")
164
    script.write("fi\n\n")
165
    # As much as we'd like to put this in our _ROOT_DIR, that will happen to be
166
    # mounted noexec sometimes, so we'll have to find another place.
167
    (tmpfd, tmpfile_name) = tempfile.mkstemp()
168
    tmpfile = os.fdopen(tmpfd, 'w')
169
    tmpfile.write(script.getvalue())
170
    tmpfile.close()
171
    os.chmod(tmpfile_name, 0755)
172
    return tmpfile_name
173

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

177
    We can do this by listing our live instances directory and
178
    checking whether the associated kvm process is still alive.
179

180
    """
181
    result = []
182
    for name in os.listdir(self._PIDS_DIR):
183
      filename = "%s/%s" % (self._PIDS_DIR, name)
184
      if utils.IsProcessAlive(utils.ReadPidFile(filename)):
185
        result.append(name)
186
    return result
187

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

191
    @param instance_name: the instance name
192

193
    @return: tuple (name, id, memory, vcpus, stat, times)
194

195
    """
196
    pidfile, pid, alive = self._InstancePidAlive(instance_name)
197
    if not alive:
198
      return None
199

    
200
    cmdline_file = "/proc/%s/cmdline" % pid
201
    try:
202
      fh = open(cmdline_file, 'r')
203
      try:
204
        cmdline = fh.read()
205
      finally:
206
        fh.close()
207
    except EnvironmentError, err:
208
      raise errors.HypervisorError("Failed to list instance %s: %s" %
209
                                   (instance_name, err))
210

    
211
    memory = 0
212
    vcpus = 0
213
    stat = "---b-"
214
    times = "0"
215

    
216
    arg_list = cmdline.split('\x00')
217
    while arg_list:
218
      arg =  arg_list.pop(0)
219
      if arg == '-m':
220
        memory = int(arg_list.pop(0))
221
      elif arg == '-smp':
222
        vcpus = int(arg_list.pop(0))
223

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

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

229
    @return: list of tuples (name, id, memory, vcpus, stat, times)
230

231
    """
232
    data = []
233
    for name in os.listdir(self._PIDS_DIR):
234
      filename = "%s/%s" % (self._PIDS_DIR, name)
235
      if utils.IsProcessAlive(utils.ReadPidFile(filename)):
236
        try:
237
          info = self.GetInstanceInfo(name)
238
        except errors.HypervisorError, err:
239
          continue
240
        if info:
241
          data.append(info)
242

    
243
    return data
244

    
245
  def _GenerateKVMRuntime(self, instance, block_devices):
246
    """Generate KVM information to start an instance.
247

248
    """
249
    pidfile, pid, alive = self._InstancePidAlive(instance.name)
250
    kvm = constants.KVM_PATH
251
    kvm_cmd = [kvm]
252
    kvm_cmd.extend(['-m', instance.beparams[constants.BE_MEMORY]])
253
    kvm_cmd.extend(['-smp', instance.beparams[constants.BE_VCPUS]])
254
    kvm_cmd.extend(['-pidfile', pidfile])
255
    # used just by the vnc server, if enabled
256
    kvm_cmd.extend(['-name', instance.name])
257
    kvm_cmd.extend(['-daemonize'])
258
    if not instance.hvparams[constants.HV_ACPI]:
259
      kvm_cmd.extend(['-no-acpi'])
260

    
261
    hvp = instance.hvparams
262
    boot_disk = hvp[constants.HV_BOOT_ORDER] == constants.HT_BO_DISK
263
    boot_cdrom = hvp[constants.HV_BOOT_ORDER] == constants.HT_BO_CDROM
264
    boot_network = hvp[constants.HV_BOOT_ORDER] == constants.HT_BO_NETWORK
265

    
266
    if boot_network:
267
      kvm_cmd.extend(['-boot', 'n'])
268

    
269
    disk_type = hvp[constants.HV_DISK_TYPE]
270
    if disk_type == constants.HT_DISK_PARAVIRTUAL:
271
      if_val = ',if=virtio'
272
    else:
273
      if_val = ',if=%s' % disk_type
274
    for cfdev, dev_path in block_devices:
275
      if cfdev.mode != constants.DISK_RDWR:
276
        raise errors.HypervisorError("Instance has read-only disks which"
277
                                     " are not supported by KVM")
278
      # TODO: handle FD_LOOP and FD_BLKTAP (?)
279
      if boot_disk:
280
        kvm_cmd.extend(['-boot', 'c'])
281
        boot_val = ',boot=on'
282
        # We only boot from the first disk
283
        boot_disk = False
284
      else:
285
        boot_val = ''
286

    
287
      drive_val = 'file=%s,format=raw%s%s' % (dev_path, if_val, boot_val)
288
      kvm_cmd.extend(['-drive', drive_val])
289

    
290
    iso_image = hvp[constants.HV_CDROM_IMAGE_PATH]
291
    if iso_image:
292
      options = ',format=raw,media=cdrom'
293
      if boot_cdrom:
294
        kvm_cmd.extend(['-boot', 'd'])
295
        options = '%s,boot=on' % options
296
      else:
297
        options = '%s,if=virtio' % options
298
      drive_val = 'file=%s%s' % (iso_image, options)
299
      kvm_cmd.extend(['-drive', drive_val])
300

    
301
    kernel_path = hvp[constants.HV_KERNEL_PATH]
302
    if kernel_path:
303
      kvm_cmd.extend(['-kernel', kernel_path])
304
      initrd_path = hvp[constants.HV_INITRD_PATH]
305
      if initrd_path:
306
        kvm_cmd.extend(['-initrd', initrd_path])
307
      root_append = ['root=%s' % hvp[constants.HV_ROOT_PATH],
308
                     hvp[constants.HV_KERNEL_ARGS]]
309
      if hvp[constants.HV_SERIAL_CONSOLE]:
310
        root_append.append('console=ttyS0,38400')
311
      kvm_cmd.extend(['-append', ' '.join(root_append)])
312

    
313
    mouse_type = hvp[constants.HV_USB_MOUSE]
314
    if mouse_type:
315
      kvm_cmd.extend(['-usb'])
316
      kvm_cmd.extend(['-usbdevice', mouse_type])
317

    
318
    # FIXME: handle vnc password
319
    vnc_bind_address = hvp[constants.HV_VNC_BIND_ADDRESS]
320
    if vnc_bind_address:
321
      if utils.IsValidIP(vnc_bind_address):
322
        if instance.network_port > constants.VNC_BASE_PORT:
323
          display = instance.network_port - constants.VNC_BASE_PORT
324
          if vnc_bind_address == '0.0.0.0':
325
            vnc_arg = ':%d' % (display)
326
          else:
327
            vnc_arg = '%s:%d' % (vnc_bind_address, display)
328
        else:
329
          logging.error("Network port is not a valid VNC display (%d < %d)."
330
                        " Not starting VNC" %
331
                        (instance.network_port,
332
                         constants.VNC_BASE_PORT))
333
          vnc_arg = 'none'
334

    
335
        # Only allow tls and other option when not binding to a file, for now.
336
        # kvm/qemu gets confused otherwise about the filename to use.
337
        vnc_append = ''
338
        if hvp[constants.HV_VNC_TLS]:
339
          vnc_append = '%s,tls' % vnc_append
340
          if hvp[constants.HV_VNC_X509_VERIFY]:
341
            vnc_append = '%s,x509verify=%s' % (vnc_append,
342
                                               hvp[constants.HV_VNC_X509])
343
          elif hvp[constants.HV_VNC_X509]:
344
            vnc_append = '%s,x509=%s' % (vnc_append,
345
                                         hvp[constants.HV_VNC_X509])
346
        vnc_arg = '%s%s' % (vnc_arg, vnc_append)
347

    
348
      else:
349
        vnc_arg = 'unix:%s/%s.vnc' % (vnc_bind_address, instance.name)
350

    
351
      kvm_cmd.extend(['-vnc', vnc_arg])
352
    else:
353
      kvm_cmd.extend(['-nographic'])
354

    
355
    monitor_dev = 'unix:%s,server,nowait' % \
356
      self._InstanceMonitor(instance.name)
357
    kvm_cmd.extend(['-monitor', monitor_dev])
358
    if hvp[constants.HV_SERIAL_CONSOLE]:
359
      serial_dev = ('unix:%s,server,nowait' %
360
                    self._InstanceSerial(instance.name))
361
      kvm_cmd.extend(['-serial', serial_dev])
362
    else:
363
      kvm_cmd.extend(['-serial', 'none'])
364

    
365
    # Save the current instance nics, but defer their expansion as parameters,
366
    # as we'll need to generate executable temp files for them.
367
    kvm_nics = instance.nics
368
    hvparams = hvp
369

    
370
    return (kvm_cmd, kvm_nics, hvparams)
371

    
372
  def _WriteKVMRuntime(self, instance_name, data):
373
    """Write an instance's KVM runtime
374

375
    """
376
    try:
377
      utils.WriteFile(self._InstanceKVMRuntime(instance_name),
378
                      data=data)
379
    except EnvironmentError, err:
380
      raise errors.HypervisorError("Failed to save KVM runtime file: %s" % err)
381

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

385
    """
386
    try:
387
      file_content = utils.ReadFile(self._InstanceKVMRuntime(instance_name))
388
    except EnvironmentError, err:
389
      raise errors.HypervisorError("Failed to load KVM runtime file: %s" % err)
390
    return file_content
391

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

395
    """
396
    kvm_cmd, kvm_nics, hvparams = kvm_runtime
397
    serialized_nics = [nic.ToDict() for nic in kvm_nics]
398
    serialized_form = serializer.Dump((kvm_cmd, serialized_nics, hvparams))
399
    self._WriteKVMRuntime(instance.name, serialized_form)
400

    
401
  def _LoadKVMRuntime(self, instance, serialized_runtime=None):
402
    """Load an instance's KVM runtime
403

404
    """
405
    if not serialized_runtime:
406
      serialized_runtime = self._ReadKVMRuntime(instance.name)
407
    loaded_runtime = serializer.Load(serialized_runtime)
408
    kvm_cmd, serialized_nics, hvparams = loaded_runtime
409
    kvm_nics = [objects.NIC.FromDict(snic) for snic in serialized_nics]
410
    return (kvm_cmd, kvm_nics, hvparams)
411

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

415
    @type incoming: tuple of strings
416
    @param incoming: (target_host_ip, port)
417

418
    """
419
    pidfile, pid, alive = self._InstancePidAlive(instance.name)
420
    if alive:
421
      raise errors.HypervisorError("Failed to start instance %s: %s" %
422
                                   (instance.name, "already running"))
423

    
424
    temp_files = []
425

    
426
    kvm_cmd, kvm_nics, hvparams = kvm_runtime
427

    
428
    if not kvm_nics:
429
      kvm_cmd.extend(['-net', 'none'])
430
    else:
431
      nic_type = hvparams[constants.HV_NIC_TYPE]
432
      if nic_type == constants.HT_NIC_PARAVIRTUAL:
433
        nic_model = "model=virtio"
434
      else:
435
        nic_model = "model=%s" % nic_type
436

    
437
      for nic_seq, nic in enumerate(kvm_nics):
438
        nic_val = "nic,macaddr=%s,%s" % (nic.mac, nic_model)
439
        script = self._WriteNetScript(instance, nic_seq, nic)
440
        kvm_cmd.extend(['-net', nic_val])
441
        kvm_cmd.extend(['-net', 'tap,script=%s' % script])
442
        temp_files.append(script)
443

    
444
    if incoming:
445
      target, port = incoming
446
      kvm_cmd.extend(['-incoming', 'tcp:%s:%s' % (target, port)])
447

    
448
    result = utils.RunCmd(kvm_cmd)
449
    if result.failed:
450
      raise errors.HypervisorError("Failed to start instance %s: %s (%s)" %
451
                                   (instance.name, result.fail_reason,
452
                                    result.output))
453

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

    
458
    for filename in temp_files:
459
      utils.RemoveFile(filename)
460

    
461
  def StartInstance(self, instance, block_devices):
462
    """Start an instance.
463

464
    """
465
    pidfile, pid, alive = self._InstancePidAlive(instance.name)
466
    if alive:
467
      raise errors.HypervisorError("Failed to start instance %s: %s" %
468
                                   (instance.name, "already running"))
469

    
470
    kvm_runtime = self._GenerateKVMRuntime(instance, block_devices)
471
    self._SaveKVMRuntime(instance, kvm_runtime)
472
    self._ExecuteKVMRuntime(instance, kvm_runtime)
473

    
474
  def _CallMonitorCommand(self, instance_name, command):
475
    """Invoke a command on the instance monitor.
476

477
    """
478
    socat = ("echo %s | %s STDIO UNIX-CONNECT:%s" %
479
             (utils.ShellQuote(command),
480
              constants.SOCAT_PATH,
481
              utils.ShellQuote(self._InstanceMonitor(instance_name))))
482
    result = utils.RunCmd(socat)
483
    if result.failed:
484
      msg = ("Failed to send command '%s' to instance %s."
485
             " output: %s, error: %s, fail_reason: %s" %
486
             (command, instance_name,
487
              result.stdout, result.stderr, result.fail_reason))
488
      raise errors.HypervisorError(msg)
489

    
490
    return result
491

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

495
    """
496
    # Wait up to $timeout seconds
497
    end = time.time() + timeout
498
    wait = 1
499
    while time.time() < end and utils.IsProcessAlive(pid):
500
      self._CallMonitorCommand(instance.name, 'system_powerdown')
501
      time.sleep(wait)
502
      # Make wait time longer for next try
503
      if wait < 5:
504
        wait *= 1.3
505

    
506
  def StopInstance(self, instance, force=False):
507
    """Stop an instance.
508

509
    """
510
    pidfile, pid, alive = self._InstancePidAlive(instance.name)
511
    if pid > 0 and alive:
512
      if force or not instance.hvparams[constants.HV_ACPI]:
513
        utils.KillProcess(pid)
514
      else:
515
        self._RetryInstancePowerdown(instance, pid)
516

    
517
    if not utils.IsProcessAlive(pid):
518
      self._RemoveInstanceRuntimeFiles(pidfile, instance.name)
519
      return True
520
    else:
521
      return False
522

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

526
    """
527
    # For some reason if we do a 'send-key ctrl-alt-delete' to the control
528
    # socket the instance will stop, but now power up again. So we'll resort
529
    # to shutdown and restart.
530
    pidfile, pid, alive = self._InstancePidAlive(instance.name)
531
    if not alive:
532
      raise errors.HypervisorError("Failed to reboot instance %s: not running" %
533
                                             (instance.name))
534
    # StopInstance will delete the saved KVM runtime so:
535
    # ...first load it...
536
    kvm_runtime = self._LoadKVMRuntime(instance)
537
    # ...now we can safely call StopInstance...
538
    if not self.StopInstance(instance):
539
      self.StopInstance(instance, force=True)
540
    # ...and finally we can save it again, and execute it...
541
    self._SaveKVMRuntime(instance, kvm_runtime)
542
    self._ExecuteKVMRuntime(instance, kvm_runtime)
543

    
544
  def MigrationInfo(self, instance):
545
    """Get instance information to perform a migration.
546

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

552
    """
553
    return self._ReadKVMRuntime(instance.name)
554

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

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

565
    """
566
    kvm_runtime = self._LoadKVMRuntime(instance, serialized_runtime=info)
567
    incoming_address = (target, constants.KVM_MIGRATION_PORT)
568
    self._ExecuteKVMRuntime(instance, kvm_runtime, incoming=incoming_address)
569

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

573
    Stop the incoming mode KVM.
574

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

578
    """
579
    if success:
580
      self._WriteKVMRuntime(instance.name, info)
581
    else:
582
      self.StopInstance(instance, force=True)
583

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

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

590
    @type instance_name: string
591
    @param instance_name: name of the instance to be migrated
592
    @type target: string
593
    @param target: ip address of the target node
594
    @type live: boolean
595
    @param live: perform a live migration
596

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

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

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

    
609
    info_command = 'info migrate'
610
    done = False
611
    while not done:
612
      result = self._CallMonitorCommand(instance_name, info_command)
613
      match = self._MIGRATION_STATUS_RE.search(result.stdout)
614
      if not match:
615
        raise errors.HypervisorError("Unknown 'info migrate' result: %s" %
616
                                     result.stdout)
617
      else:
618
        status = match.group(1)
619
        if status == 'completed':
620
          done = True
621
        elif status == 'active':
622
          time.sleep(2)
623
        elif status == 'failed' or status == 'cancelled':
624
          if not live:
625
            self._CallMonitorCommand(instance_name, 'cont')
626
          raise errors.HypervisorError("Migration %s at the kvm level" %
627
                                       status)
628
        else:
629
          logging.info("KVM: unknown migration status '%s'" % status)
630
          time.sleep(2)
631

    
632
    utils.KillProcess(pid)
633
    self._RemoveInstanceRuntimeFiles(pidfile, instance.name)
634

    
635
  def GetNodeInfo(self):
636
    """Return information about the node.
637

638
    This is just a wrapper over the base GetLinuxNodeInfo method.
639

640
    @return: a dict with the following keys (values in MiB):
641
          - memory_total: the total memory size on the node
642
          - memory_free: the available memory on the node for instances
643
          - memory_dom0: the memory used by the node itself, if available
644

645
    """
646
    return self.GetLinuxNodeInfo()
647

    
648
  @classmethod
649
  def GetShellCommandForConsole(cls, instance, hvparams, beparams):
650
    """Return a command for connecting to the console of an instance.
651

652
    """
653
    if hvparams[constants.HV_SERIAL_CONSOLE]:
654
      # FIXME: The socat shell is not perfect. In particular the way we start
655
      # it ctrl+c will close it, rather than being passed to the other end.
656
      # On the other hand if we pass the option 'raw' (or ignbrk=1) there
657
      # will be no way of exiting socat (except killing it from another shell)
658
      # and ctrl+c doesn't work anyway, printing ^C rather than being
659
      # interpreted by kvm. For now we'll leave it this way, which at least
660
      # allows a minimal interaction and changes on the machine.
661
      shell_command = ("%s STDIO,echo=0,icanon=0 UNIX-CONNECT:%s" %
662
                       (constants.SOCAT_PATH,
663
                        utils.ShellQuote(cls._InstanceSerial(instance.name))))
664
    else:
665
      shell_command = "echo 'No serial shell for instance %s'" % instance.name
666

    
667
    vnc_bind_address = hvparams[constants.HV_VNC_BIND_ADDRESS]
668
    if vnc_bind_address:
669
      if instance.network_port > constants.VNC_BASE_PORT:
670
        display = instance.network_port - constants.VNC_BASE_PORT
671
        vnc_command = ("echo 'Instance has VNC listening on %s:%d"
672
                       " (display: %d)'" % (vnc_bind_address,
673
                                            instance.network_port,
674
                                            display))
675
        shell_command = "%s; %s" % (vnc_command, shell_command)
676

    
677
    return shell_command
678

    
679
  def Verify(self):
680
    """Verify the hypervisor.
681

682
    Check that the binary exists.
683

684
    """
685
    if not os.path.exists(constants.KVM_PATH):
686
      return "The kvm binary ('%s') does not exist." % constants.KVM_PATH
687
    if not os.path.exists(constants.SOCAT_PATH):
688
      return "The socat binary ('%s') does not exist." % constants.SOCAT_PATH
689

    
690

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

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

699
    """
700
    super(KVMHypervisor, cls).CheckParameterSyntax(hvparams)
701

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

    
708
    if (hvparams[constants.HV_VNC_X509_VERIFY] and
709
        not hvparams[constants.HV_VNC_X509]):
710
      raise errors.HypervisorError("%s must be defined, if %s is" %
711
                                   (constants.HV_VNC_X509,
712
                                    constants.HV_VNC_X509_VERIFY))
713

    
714
    boot_order = hvparams[constants.HV_BOOT_ORDER]
715

    
716
    if (boot_order == constants.HT_BO_CDROM and
717
        not hvparams[constants.HV_CDROM_IMAGE_PATH]):
718
      raise errors.HypervisorError("Cannot boot from cdrom without an"
719
                                   " ISO path")
720
    if (boot_order == constants.HT_BO_NETWORK and
721
        hvparams[constants.HV_NIC_TYPE] == constants.HT_NIC_PARAVIRTUAL):
722
      raise errors.HypervisorError("Cannot boot from a paravirtual NIC. Please"
723
                                   " change the NIC type.")