Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_kvm.py @ f02881e0

History | View | Annotate | Download (16.8 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
from cStringIO import StringIO
32

    
33
from ganeti import utils
34
from ganeti import constants
35
from ganeti import errors
36
from ganeti import serializer
37
from ganeti import objects
38
from ganeti.hypervisor import hv_base
39

    
40

    
41
class KVMHypervisor(hv_base.BaseHypervisor):
42
  """KVM hypervisor interface"""
43

    
44
  _ROOT_DIR = constants.RUN_GANETI_DIR + "/kvm-hypervisor"
45
  _PIDS_DIR = _ROOT_DIR + "/pid" # contains live instances pids
46
  _CTRL_DIR = _ROOT_DIR + "/ctrl" # contains instances control sockets
47
  _CONF_DIR = _ROOT_DIR + "/conf" # contains instances startup data
48
  _DIRS = [_ROOT_DIR, _PIDS_DIR, _CTRL_DIR, _CONF_DIR]
49

    
50
  PARAMETERS = [
51
    constants.HV_KERNEL_PATH,
52
    constants.HV_INITRD_PATH,
53
    constants.HV_ACPI,
54
    ]
55

    
56
  def __init__(self):
57
    hv_base.BaseHypervisor.__init__(self)
58
    # Let's make sure the directories we need exist, even if the RUN_DIR lives
59
    # in a tmpfs filesystem or has been otherwise wiped out.
60
    for mydir in self._DIRS:
61
      if not os.path.exists(mydir):
62
        os.mkdir(mydir)
63

    
64
  def _InstanceMonitor(self, instance_name):
65
    """Returns the instance monitor socket name
66

67
    """
68
    return '%s/%s.monitor' % (self._CTRL_DIR, instance_name)
69

    
70
  def _InstanceSerial(self, instance_name):
71
    """Returns the instance serial socket name
72

73
    """
74
    return '%s/%s.serial' % (self._CTRL_DIR, instance_name)
75

    
76
  def _InstanceKVMRuntime(self, instance_name):
77
    """Returns the instance KVM runtime filename
78

79
    """
80
    return '%s/%s.runtime' % (self._CONF_DIR, instance_name)
81

    
82
  def _WriteNetScript(self, instance, seq, nic):
83
    """Write a script to connect a net interface to the proper bridge.
84

85
    This can be used by any qemu-type hypervisor.
86

87
    @param instance: instance we're acting on
88
    @type instance: instance object
89
    @param seq: nic sequence number
90
    @type seq: int
91
    @param nic: nic we're acting on
92
    @type nic: nic object
93
    @return: netscript file name
94
    @rtype: string
95

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

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

126
    We can do this by listing our live instances directory and
127
    checking whether the associated kvm process is still alive.
128

129
    """
130
    result = []
131
    for name in os.listdir(self._PIDS_DIR):
132
      filename = "%s/%s" % (self._PIDS_DIR, name)
133
      if utils.IsProcessAlive(utils.ReadPidFile(filename)):
134
        result.append(name)
135
    return result
136

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

140
    @param instance_name: the instance name
141

142
    @return: tuple (name, id, memory, vcpus, stat, times)
143

144
    """
145
    pidfile = "%s/%s" % (self._PIDS_DIR, instance_name)
146
    pid = utils.ReadPidFile(pidfile)
147
    if not utils.IsProcessAlive(pid):
148
      return None
149

    
150
    cmdline_file = "/proc/%s/cmdline" % pid
151
    try:
152
      fh = open(cmdline_file, 'r')
153
      try:
154
        cmdline = fh.read()
155
      finally:
156
        fh.close()
157
    except IOError, err:
158
      raise errors.HypervisorError("Failed to list instance %s: %s" %
159
                                   (instance_name, err))
160

    
161
    memory = 0
162
    vcpus = 0
163
    stat = "---b-"
164
    times = "0"
165

    
166
    arg_list = cmdline.split('\x00')
167
    while arg_list:
168
      arg =  arg_list.pop(0)
169
      if arg == '-m':
170
        memory = arg_list.pop(0)
171
      elif arg == '-smp':
172
        vcpus = arg_list.pop(0)
173

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

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

179
    @return: list of tuples (name, id, memory, vcpus, stat, times)
180

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

    
188
    return data
189

    
190
  def _GenerateKVMRuntime(self, instance, block_devices, extra_args):
191
    """Generate KVM information to start an instance.
192

193
    """
194
    pidfile = self._PIDS_DIR + "/%s" % instance.name
195
    kvm = constants.KVM_PATH
196
    kvm_cmd = [kvm]
197
    kvm_cmd.extend(['-m', instance.beparams[constants.BE_MEMORY]])
198
    kvm_cmd.extend(['-smp', instance.beparams[constants.BE_VCPUS]])
199
    kvm_cmd.extend(['-pidfile', pidfile])
200
    # used just by the vnc server, if enabled
201
    kvm_cmd.extend(['-name', instance.name])
202
    kvm_cmd.extend(['-daemonize'])
203
    if not instance.hvparams[constants.HV_ACPI]:
204
      kvm_cmd.extend(['-no-acpi'])
205

    
206
    boot_drive = True
207
    for cfdev, dev_path in block_devices:
208
      # TODO: handle FD_LOOP and FD_BLKTAP (?)
209
      if boot_drive:
210
        boot_val = ',boot=on'
211
        boot_drive = False
212
      else:
213
        boot_val = ''
214

    
215
      # TODO: handle different if= types
216
      if_val = ',if=virtio'
217

    
218
      drive_val = 'file=%s,format=raw%s%s' % (dev_path, if_val, boot_val)
219
      kvm_cmd.extend(['-drive', drive_val])
220

    
221
    kvm_cmd.extend(['-kernel', instance.hvparams[constants.HV_KERNEL_PATH]])
222

    
223
    initrd_path = instance.hvparams[constants.HV_INITRD_PATH]
224
    if initrd_path:
225
      kvm_cmd.extend(['-initrd', initrd_path])
226

    
227
    kvm_cmd.extend(['-append', 'console=ttyS0,38400 root=/dev/vda'])
228

    
229
    #"hvm_boot_order",
230
    #"hvm_cdrom_image_path",
231

    
232
    kvm_cmd.extend(['-nographic'])
233
    # FIXME: handle vnc, if needed
234
    # How do we decide whether to have it or not?? :(
235
    #"vnc_bind_address",
236
    #"network_port"
237
    monitor_dev = 'unix:%s,server,nowait' % \
238
      self._InstanceMonitor(instance.name)
239
    kvm_cmd.extend(['-monitor', monitor_dev])
240
    serial_dev = 'unix:%s,server,nowait' % self._InstanceSerial(instance.name)
241
    kvm_cmd.extend(['-serial', serial_dev])
242

    
243
    # Save the current instance nics, but defer their expansion as parameters,
244
    # as we'll need to generate executable temp files for them.
245
    kvm_nics = instance.nics
246

    
247
    return (kvm_cmd, kvm_nics)
248

    
249
  def _WriteKVMRuntime(self, instance_name, data):
250
    """Write an instance's KVM runtime
251

252
    """
253
    try:
254
      utils.WriteFile(self._InstanceKVMRuntime(instance_name),
255
                      data=data)
256
    except IOError, err:
257
      raise errors.HypervisorError("Failed to save KVM runtime file: %s" % err)
258

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

262
    """
263
    try:
264
      file_content = utils.ReadFile(self._InstanceKVMRuntime(instance_name))
265
    except IOError, err:
266
      raise errors.HypervisorError("Failed to load KVM runtime file: %s" % err)
267
    return file_content
268

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

272
    """
273
    kvm_cmd, kvm_nics = kvm_runtime
274
    serialized_nics = [nic.ToDict() for nic in kvm_nics]
275
    serialized_form = serializer.Dump((kvm_cmd, serialized_nics))
276
    self._WriteKVMRuntime(instance.name, serialized_form)
277

    
278
  def _LoadKVMRuntime(self, instance):
279
    """Load an instance's KVM runtime
280

281
    """
282
    serialized_form = self._ReadKVMRuntime(instance.name)
283
    loaded_runtime = serializer.Load(serialized_form)
284
    kvm_cmd, serialized_nics = loaded_runtime
285
    kvm_nics = [objects.NIC.FromDict(snic) for snic in serialized_nics]
286
    return (kvm_cmd, kvm_nics)
287

    
288
  def _ExecuteKVMRuntime(self, instance, kvm_runtime):
289
    """Execute a KVM cmd, after completing it with some last minute data
290

291
    """
292
    pidfile = self._PIDS_DIR + "/%s" % instance.name
293
    if utils.IsProcessAlive(utils.ReadPidFile(pidfile)):
294
      raise errors.HypervisorError("Failed to start instance %s: %s" %
295
                                   (instance.name, "already running"))
296

    
297
    temp_files = []
298

    
299
    kvm_cmd, kvm_nics = kvm_runtime
300

    
301
    if not kvm_nics:
302
      kvm_cmd.extend(['-net', 'none'])
303
    else:
304
      for nic_seq, nic in enumerate(kvm_nics):
305
        nic_val = "nic,macaddr=%s,model=virtio" % nic.mac
306
        script = self._WriteNetScript(instance, nic_seq, nic)
307
        kvm_cmd.extend(['-net', nic_val])
308
        kvm_cmd.extend(['-net', 'tap,script=%s' % script])
309
        temp_files.append(script)
310

    
311
    result = utils.RunCmd(kvm_cmd)
312
    if result.failed:
313
      raise errors.HypervisorError("Failed to start instance %s: %s (%s)" %
314
                                   (instance.name, result.fail_reason,
315
                                    result.output))
316

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

    
321
    for filename in temp_files:
322
      utils.RemoveFile(filename)
323

    
324
  def StartInstance(self, instance, block_devices, extra_args):
325
    """Start an instance.
326

327
    """
328
    pidfile = self._PIDS_DIR + "/%s" % instance.name
329
    if utils.IsProcessAlive(utils.ReadPidFile(pidfile)):
330
      raise errors.HypervisorError("Failed to start instance %s: %s" %
331
                                   (instance.name, "already running"))
332

    
333
    kvm_runtime = self._GenerateKVMRuntime(instance, block_devices, extra_args)
334
    self._SaveKVMRuntime(instance, kvm_runtime)
335
    self._ExecuteKVMRuntime(instance, kvm_runtime)
336

    
337
  def _CallMonitorCommand(self, instance_name, command):
338
    """Invoke a command on the instance monitor.
339

340
    """
341
    socat = ("echo %s | %s STDIO UNIX-CONNECT:%s" %
342
             (utils.ShellQuote(command),
343
              constants.SOCAT_PATH,
344
              utils.ShellQuote(self._InstanceMonitor(instance_name))))
345
    result = utils.RunCmd(socat)
346
    if result.failed:
347
      msg = ("Failed to send command '%s' to instance %s."
348
             " output: %s, error: %s, fail_reason: %s" %
349
             (instance.name, result.stdout, result.stderr, result.fail_reason))
350
      raise errors.HypervisorError(msg)
351

    
352
    return result
353

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

357
    """
358
    # Wait up to $timeout seconds
359
    end = time.time() + timeout
360
    wait = 1
361
    while time.time() < end and utils.IsProcessAlive(pid):
362
      self._CallMonitorCommand(instance.name, 'system_powerdown')
363
      time.sleep(wait)
364
      # Make wait time longer for next try
365
      if wait < 5:
366
        wait *= 1.3
367

    
368
  def StopInstance(self, instance, force=False):
369
    """Stop an instance.
370

371
    """
372
    pid_file = self._PIDS_DIR + "/%s" % instance.name
373
    pid = utils.ReadPidFile(pid_file)
374
    if pid > 0 and utils.IsProcessAlive(pid):
375
      if force or not instance.hvparams[constants.HV_ACPI]:
376
        utils.KillProcess(pid)
377
      else:
378
        self._RetryInstancePowerdown(instance, pid)
379

    
380
    if not utils.IsProcessAlive(pid):
381
      utils.RemoveFile(pid_file)
382
      utils.RemoveFile(self._InstanceMonitor(instance.name))
383
      utils.RemoveFile(self._InstanceSerial(instance.name))
384
      utils.RemoveFile(self._InstanceKVMRuntime(instance.name))
385
      return True
386
    else:
387
      return False
388

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

392
    """
393
    # For some reason if we do a 'send-key ctrl-alt-delete' to the control
394
    # socket the instance will stop, but now power up again. So we'll resort
395
    # to shutdown and restart.
396

    
397
    # StopInstance will delete the saved KVM runtime so:
398
    # ...first load it...
399
    kvm_runtime = self._LoadKVMRuntime(instance)
400
    # ...now we can safely call StopInstance...
401
    if not self.StopInstance(instance):
402
      self.StopInstance(instance, force=True)
403
    # ...and finally we can save it again, and execute it...
404
    self._SaveKVMRuntime(instance, kvm_runtime)
405
    self._ExecuteKVMRuntime(instance, kvm_runtime)
406

    
407
  def GetNodeInfo(self):
408
    """Return information about the node.
409

410
    @return: a dict with the following keys (values in MiB):
411
          - memory_total: the total memory size on the node
412
          - memory_free: the available memory on the node for instances
413
          - memory_dom0: the memory used by the node itself, if available
414

415
    """
416
    # global ram usage from the xm info command
417
    # memory                 : 3583
418
    # free_memory            : 747
419
    # note: in xen 3, memory has changed to total_memory
420
    try:
421
      fh = file("/proc/meminfo")
422
      try:
423
        data = fh.readlines()
424
      finally:
425
        fh.close()
426
    except IOError, err:
427
      raise errors.HypervisorError("Failed to list node info: %s" % err)
428

    
429
    result = {}
430
    sum_free = 0
431
    for line in data:
432
      splitfields = line.split(":", 1)
433

    
434
      if len(splitfields) > 1:
435
        key = splitfields[0].strip()
436
        val = splitfields[1].strip()
437
        if key == 'MemTotal':
438
          result['memory_total'] = int(val.split()[0])/1024
439
        elif key in ('MemFree', 'Buffers', 'Cached'):
440
          sum_free += int(val.split()[0])/1024
441
        elif key == 'Active':
442
          result['memory_dom0'] = int(val.split()[0])/1024
443
    result['memory_free'] = sum_free
444

    
445
    cpu_total = 0
446
    try:
447
      fh = open("/proc/cpuinfo")
448
      try:
449
        cpu_total = len(re.findall("(?m)^processor\s*:\s*[0-9]+\s*$",
450
                                   fh.read()))
451
      finally:
452
        fh.close()
453
    except EnvironmentError, err:
454
      raise errors.HypervisorError("Failed to list node info: %s" % err)
455
    result['cpu_total'] = cpu_total
456

    
457
    return result
458

    
459
  @staticmethod
460
  def GetShellCommandForConsole(instance):
461
    """Return a command for connecting to the console of an instance.
462

463
    """
464
    # TODO: we can either try the serial socket or suggest vnc
465
    return "echo Console not available for the kvm hypervisor yet"
466

    
467
  def Verify(self):
468
    """Verify the hypervisor.
469

470
    Check that the binary exists.
471

472
    """
473
    if not os.path.exists(constants.KVM_PATH):
474
      return "The kvm binary ('%s') does not exist." % constants.KVM_PATH
475
    if not os.path.exists(constants.SOCAT_PATH):
476
      return "The socat binary ('%s') does not exist." % constants.SOCAT_PATH
477

    
478

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

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

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

490
    """
491
    super(KVMHypervisor, cls).CheckParameterSyntax(hvparams)
492

    
493
    if not hvparams[constants.HV_KERNEL_PATH]:
494
      raise errors.HypervisorError("Need a kernel for the instance")
495

    
496
    if not os.path.isabs(hvparams[constants.HV_KERNEL_PATH]):
497
      raise errors.HypervisorError("The kernel path must an absolute path")
498

    
499
    if hvparams[constants.HV_INITRD_PATH]:
500
      if not os.path.isabs(hvparams[constants.HV_INITRD_PATH]):
501
        raise errors.HypervisorError("The initrd path must an absolute path"
502
                                     ", if defined")
503

    
504
  def ValidateParameters(self, hvparams):
505
    """Check the given parameters for validity.
506

507
    For the KVM hypervisor, this checks the existence of the
508
    kernel.
509

510
    """
511
    super(KVMHypervisor, self).ValidateParameters(hvparams)
512

    
513
    kernel_path = hvparams[constants.HV_KERNEL_PATH]
514
    if not os.path.isfile(kernel_path):
515
      raise errors.HypervisorError("Instance kernel '%s' not found or"
516
                                   " not a file" % kernel_path)
517
    initrd_path = hvparams[constants.HV_INITRD_PATH]
518
    if initrd_path and not os.path.isfile(initrd_path):
519
      raise errors.HypervisorError("Instance initrd '%s' not found or"
520
                                   " not a file" % initrd_path)