Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_kvm.py @ cd42d0ad

History | View | Annotate | Download (15.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
from cStringIO import StringIO
31

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

    
39

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

139
    @param instance_name: the instance name
140

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

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

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

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

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

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

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

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

180
    """
181
    data = []
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
        data.append(self.GetInstanceInfo(name))
186

    
187
    return data
188

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

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

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

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

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

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

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

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

    
228
    #"hvm_boot_order",
229
    #"hvm_cdrom_image_path",
230

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

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

    
246
    return (kvm_cmd, kvm_nics)
247

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

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

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

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

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

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

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

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

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

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

    
296
    temp_files = []
297

    
298
    kvm_cmd, kvm_nics = kvm_runtime
299

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

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

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

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

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

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

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

    
336
  def StopInstance(self, instance, force=False):
337
    """Stop an instance.
338

339
    """
340
    socat_bin = constants.SOCAT_PATH
341
    pid_file = self._PIDS_DIR + "/%s" % instance.name
342
    pid = utils.ReadPidFile(pid_file)
343
    if pid > 0 and utils.IsProcessAlive(pid):
344
      if force or not instance.hvparams[constants.HV_ACPI]:
345
        utils.KillProcess(pid)
346
      else:
347
        # This only works if the instance os has acpi support
348
        monitor_socket = '%s/%s.monitor'  % (self._CTRL_DIR, instance.name)
349
        socat = '%s -u STDIN UNIX-CONNECT:%s' % (socat_bin, monitor_socket)
350
        command = "echo 'system_powerdown' | %s" % socat
351
        result = utils.RunCmd(command)
352
        if result.failed:
353
          raise errors.HypervisorError("Failed to stop instance %s: %s" %
354
                                       (instance.name, result.fail_reason))
355

    
356
    if not utils.IsProcessAlive(pid):
357
      utils.RemoveFile(pid_file)
358
      utils.RemoveFile(self._InstanceMonitor(instance.name))
359
      utils.RemoveFile(self._InstanceSerial(instance.name))
360
      utils.RemoveFile(self._InstanceKVMRuntime(instance.name))
361

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

365
    """
366
    # For some reason if we do a 'send-key ctrl-alt-delete' to the control
367
    # socket the instance will stop, but now power up again. So we'll resort
368
    # to shutdown and restart.
369
    self.StopInstance(instance)
370
    self.StartInstance(instance)
371

    
372
  def GetNodeInfo(self):
373
    """Return information about the node.
374

375
    @return: a dict with the following keys (values in MiB):
376
          - memory_total: the total memory size on the node
377
          - memory_free: the available memory on the node for instances
378
          - memory_dom0: the memory used by the node itself, if available
379

380
    """
381
    # global ram usage from the xm info command
382
    # memory                 : 3583
383
    # free_memory            : 747
384
    # note: in xen 3, memory has changed to total_memory
385
    try:
386
      fh = file("/proc/meminfo")
387
      try:
388
        data = fh.readlines()
389
      finally:
390
        fh.close()
391
    except IOError, err:
392
      raise errors.HypervisorError("Failed to list node info: %s" % err)
393

    
394
    result = {}
395
    sum_free = 0
396
    for line in data:
397
      splitfields = line.split(":", 1)
398

    
399
      if len(splitfields) > 1:
400
        key = splitfields[0].strip()
401
        val = splitfields[1].strip()
402
        if key == 'MemTotal':
403
          result['memory_total'] = int(val.split()[0])/1024
404
        elif key in ('MemFree', 'Buffers', 'Cached'):
405
          sum_free += int(val.split()[0])/1024
406
        elif key == 'Active':
407
          result['memory_dom0'] = int(val.split()[0])/1024
408
    result['memory_free'] = sum_free
409

    
410
    cpu_total = 0
411
    try:
412
      fh = open("/proc/cpuinfo")
413
      try:
414
        cpu_total = len(re.findall("(?m)^processor\s*:\s*[0-9]+\s*$",
415
                                   fh.read()))
416
      finally:
417
        fh.close()
418
    except EnvironmentError, err:
419
      raise errors.HypervisorError("Failed to list node info: %s" % err)
420
    result['cpu_total'] = cpu_total
421

    
422
    return result
423

    
424
  @staticmethod
425
  def GetShellCommandForConsole(instance):
426
    """Return a command for connecting to the console of an instance.
427

428
    """
429
    # TODO: we can either try the serial socket or suggest vnc
430
    return "echo Console not available for the kvm hypervisor yet"
431

    
432
  def Verify(self):
433
    """Verify the hypervisor.
434

435
    Check that the binary exists.
436

437
    """
438
    if not os.path.exists(constants.KVM_PATH):
439
      return "The kvm binary ('%s') does not exist." % constants.KVM_PATH
440
    if not os.path.exists(constants.SOCAT_PATH):
441
      return "The socat binary ('%s') does not exist." % constants.SOCAT_PATH
442

    
443

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

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

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

455
    """
456
    super(KVMHypervisor, cls).CheckParameterSyntax(hvparams)
457

    
458
    if not hvparams[constants.HV_KERNEL_PATH]:
459
      raise errors.HypervisorError("Need a kernel for the instance")
460

    
461
    if not os.path.isabs(hvparams[constants.HV_KERNEL_PATH]):
462
      raise errors.HypervisorError("The kernel path must an absolute path")
463

    
464
    if hvparams[constants.HV_INITRD_PATH]:
465
      if not os.path.isabs(hvparams[constants.HV_INITRD_PATH]):
466
        raise errors.HypervisorError("The initrd path must an absolute path"
467
                                     ", if defined")
468

    
469
  def ValidateParameters(self, hvparams):
470
    """Check the given parameters for validity.
471

472
    For the KVM hypervisor, this checks the existence of the
473
    kernel.
474

475
    """
476
    super(KVMHypervisor, self).ValidateParameters(hvparams)
477

    
478
    kernel_path = hvparams[constants.HV_KERNEL_PATH]
479
    if not os.path.isfile(kernel_path):
480
      raise errors.HypervisorError("Instance kernel '%s' not found or"
481
                                   " not a file" % kernel_path)
482
    initrd_path = hvparams[constants.HV_INITRD_PATH]
483
    if initrd_path and not os.path.isfile(initrd_path):
484
      raise errors.HypervisorError("Instance initrd '%s' not found or"
485
                                   " not a file" % initrd_path)