Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_kvm.py @ ee5f20b0

History | View | Annotate | Download (14.2 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.hypervisor import hv_base
36

    
37

    
38
class KVMHypervisor(hv_base.BaseHypervisor):
39
  """KVM hypervisor interface"""
40

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

    
47
  PARAMETERS = [
48
    constants.HV_KERNEL_PATH,
49
    constants.HV_INITRD_PATH,
50
    constants.HV_ACPI,
51
    ]
52

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

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

64
    """
65
    return '%s/%s.monitor' % (self._CTRL_DIR, instance_name)
66

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

70
    """
71
    return '%s/%s.serial' % (self._CTRL_DIR, instance_name)
72

    
73
  def _WriteNetScript(self, instance, seq, nic):
74
    """Write a script to connect a net interface to the proper bridge.
75

76
    This can be used by any qemu-type hypervisor.
77

78
    @param instance: instance we're acting on
79
    @type instance: instance object
80
    @param seq: nic sequence number
81
    @type seq: int
82
    @param nic: nic we're acting on
83
    @type nic: nic object
84
    @return: netscript file name
85
    @rtype: string
86

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

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

117
    We can do this by listing our live instances directory and
118
    checking whether the associated kvm process is still alive.
119

120
    """
121
    result = []
122
    for name in os.listdir(self._PIDS_DIR):
123
      filename = "%s/%s" % (self._PIDS_DIR, name)
124
      if utils.IsProcessAlive(utils.ReadPidFile(filename)):
125
        result.append(name)
126
    return result
127

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

131
    @param instance_name: the instance name
132

133
    @return: tuple (name, id, memory, vcpus, stat, times)
134

135
    """
136
    pidfile = "%s/%s" % (self._PIDS_DIR, instance_name)
137
    pid = utils.ReadPidFile(pidfile)
138
    if not utils.IsProcessAlive(pid):
139
      return None
140

    
141
    cmdline_file = "/proc/%s/cmdline" % pid
142
    try:
143
      fh = open(cmdline_file, 'r')
144
      try:
145
        cmdline = fh.read()
146
      finally:
147
        fh.close()
148
    except IOError, err:
149
      raise errors.HypervisorError("Failed to list instance %s: %s" %
150
                                   (instance_name, err))
151

    
152
    memory = 0
153
    vcpus = 0
154
    stat = "---b-"
155
    times = "0"
156

    
157
    arg_list = cmdline.split('\x00')
158
    while arg_list:
159
      arg =  arg_list.pop(0)
160
      if arg == '-m':
161
        memory = arg_list.pop(0)
162
      elif arg == '-smp':
163
        vcpus = arg_list.pop(0)
164

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

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

170
    @return: list of tuples (name, id, memory, vcpus, stat, times)
171

172
    """
173
    data = []
174
    for name in os.listdir(self._PIDS_DIR):
175
      filename = "%s/%s" % (self._PIDS_DIR, name)
176
      if utils.IsProcessAlive(utils.ReadPidFile(filename)):
177
        data.append(self.GetInstanceInfo(name))
178

    
179
    return data
180

    
181
  def _GenerateKVMRuntime(self, instance, block_devices, extra_args):
182
    """Generate KVM information to start an instance.
183

184
    """
185
    pidfile = self._PIDS_DIR + "/%s" % instance.name
186
    kvm = constants.KVM_PATH
187
    kvm_cmd = [kvm]
188
    kvm_cmd.extend(['-m', instance.beparams[constants.BE_MEMORY]])
189
    kvm_cmd.extend(['-smp', instance.beparams[constants.BE_VCPUS]])
190
    kvm_cmd.extend(['-pidfile', pidfile])
191
    # used just by the vnc server, if enabled
192
    kvm_cmd.extend(['-name', instance.name])
193
    kvm_cmd.extend(['-daemonize'])
194
    if not instance.hvparams[constants.HV_ACPI]:
195
      kvm_cmd.extend(['-no-acpi'])
196

    
197
    boot_drive = True
198
    for cfdev, dev_path in block_devices:
199
      # TODO: handle FD_LOOP and FD_BLKTAP (?)
200
      if boot_drive:
201
        boot_val = ',boot=on'
202
        boot_drive = False
203
      else:
204
        boot_val = ''
205

    
206
      # TODO: handle different if= types
207
      if_val = ',if=virtio'
208

    
209
      drive_val = 'file=%s,format=raw%s%s' % (dev_path, if_val, boot_val)
210
      kvm_cmd.extend(['-drive', drive_val])
211

    
212
    kvm_cmd.extend(['-kernel', instance.hvparams[constants.HV_KERNEL_PATH]])
213

    
214
    initrd_path = instance.hvparams[constants.HV_INITRD_PATH]
215
    if initrd_path:
216
      kvm_cmd.extend(['-initrd', initrd_path])
217

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

    
220
    #"hvm_boot_order",
221
    #"hvm_cdrom_image_path",
222

    
223
    kvm_cmd.extend(['-nographic'])
224
    # FIXME: handle vnc, if needed
225
    # How do we decide whether to have it or not?? :(
226
    #"vnc_bind_address",
227
    #"network_port"
228
    monitor_dev = 'unix:%s,server,nowait' % \
229
      self._InstanceMonitor(instance.name)
230
    kvm_cmd.extend(['-monitor', monitor_dev])
231
    serial_dev = 'unix:%s,server,nowait' % self._InstanceSerial(instance.name)
232
    kvm_cmd.extend(['-serial', serial_dev])
233

    
234
    # Save the current instance nics, but defer their expansion as parameters,
235
    # as we'll need to generate executable temp files for them.
236
    kvm_nics = instance.nics
237

    
238
    return (kvm_cmd, kvm_nics)
239

    
240
  def _ExecuteKVMRuntime(self, instance, kvm_runtime):
241
    """Execute a KVM cmd, after completing it with some last minute data
242

243
    """
244
    pidfile = self._PIDS_DIR + "/%s" % instance.name
245
    if utils.IsProcessAlive(utils.ReadPidFile(pidfile)):
246
      raise errors.HypervisorError("Failed to start instance %s: %s" %
247
                                   (instance.name, "already running"))
248

    
249
    temp_files = []
250

    
251
    kvm_cmd, kvm_nics = kvm_runtime
252

    
253
    if not kvm_nics:
254
      kvm_cmd.extend(['-net', 'none'])
255
    else:
256
      for nic_seq, nic in enumerate(kvm_nics):
257
        nic_val = "nic,macaddr=%s,model=virtio" % nic.mac
258
        script = self._WriteNetScript(instance, nic_seq, nic)
259
        kvm_cmd.extend(['-net', nic_val])
260
        kvm_cmd.extend(['-net', 'tap,script=%s' % script])
261
        temp_files.append(script)
262

    
263
    result = utils.RunCmd(kvm_cmd)
264
    if result.failed:
265
      raise errors.HypervisorError("Failed to start instance %s: %s (%s)" %
266
                                   (instance.name, result.fail_reason,
267
                                    result.output))
268

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

    
273
    for filename in temp_files:
274
      utils.RemoveFile(filename)
275

    
276
  def StartInstance(self, instance, block_devices, extra_args):
277
    """Start an instance.
278

279
    """
280
    pidfile = self._PIDS_DIR + "/%s" % instance.name
281
    if utils.IsProcessAlive(utils.ReadPidFile(pidfile)):
282
      raise errors.HypervisorError("Failed to start instance %s: %s" %
283
                                   (instance.name, "already running"))
284

    
285
    kvm_runtime = self._GenerateKVMRuntime(instance, block_devices, extra_args)
286
    self._ExecuteKVMRuntime(instance, kvm_runtime)
287

    
288
  def StopInstance(self, instance, force=False):
289
    """Stop an instance.
290

291
    """
292
    socat_bin = constants.SOCAT_PATH
293
    pid_file = self._PIDS_DIR + "/%s" % instance.name
294
    pid = utils.ReadPidFile(pid_file)
295
    if pid > 0 and utils.IsProcessAlive(pid):
296
      if force or not instance.hvparams[constants.HV_ACPI]:
297
        utils.KillProcess(pid)
298
      else:
299
        # This only works if the instance os has acpi support
300
        monitor_socket = '%s/%s.monitor'  % (self._CTRL_DIR, instance.name)
301
        socat = '%s -u STDIN UNIX-CONNECT:%s' % (socat_bin, monitor_socket)
302
        command = "echo 'system_powerdown' | %s" % socat
303
        result = utils.RunCmd(command)
304
        if result.failed:
305
          raise errors.HypervisorError("Failed to stop instance %s: %s" %
306
                                       (instance.name, result.fail_reason))
307

    
308
    if not utils.IsProcessAlive(pid):
309
      utils.RemoveFile(pid_file)
310
      utils.RemoveFile(self._InstanceMonitor(instance.name))
311
      utils.RemoveFile(self._InstanceSerial(instance.name))
312

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

316
    """
317
    # For some reason if we do a 'send-key ctrl-alt-delete' to the control
318
    # socket the instance will stop, but now power up again. So we'll resort
319
    # to shutdown and restart.
320
    self.StopInstance(instance)
321
    self.StartInstance(instance)
322

    
323
  def GetNodeInfo(self):
324
    """Return information about the node.
325

326
    @return: a dict with the following keys (values in MiB):
327
          - memory_total: the total memory size on the node
328
          - memory_free: the available memory on the node for instances
329
          - memory_dom0: the memory used by the node itself, if available
330

331
    """
332
    # global ram usage from the xm info command
333
    # memory                 : 3583
334
    # free_memory            : 747
335
    # note: in xen 3, memory has changed to total_memory
336
    try:
337
      fh = file("/proc/meminfo")
338
      try:
339
        data = fh.readlines()
340
      finally:
341
        fh.close()
342
    except IOError, err:
343
      raise errors.HypervisorError("Failed to list node info: %s" % err)
344

    
345
    result = {}
346
    sum_free = 0
347
    for line in data:
348
      splitfields = line.split(":", 1)
349

    
350
      if len(splitfields) > 1:
351
        key = splitfields[0].strip()
352
        val = splitfields[1].strip()
353
        if key == 'MemTotal':
354
          result['memory_total'] = int(val.split()[0])/1024
355
        elif key in ('MemFree', 'Buffers', 'Cached'):
356
          sum_free += int(val.split()[0])/1024
357
        elif key == 'Active':
358
          result['memory_dom0'] = int(val.split()[0])/1024
359
    result['memory_free'] = sum_free
360

    
361
    cpu_total = 0
362
    try:
363
      fh = open("/proc/cpuinfo")
364
      try:
365
        cpu_total = len(re.findall("(?m)^processor\s*:\s*[0-9]+\s*$",
366
                                   fh.read()))
367
      finally:
368
        fh.close()
369
    except EnvironmentError, err:
370
      raise errors.HypervisorError("Failed to list node info: %s" % err)
371
    result['cpu_total'] = cpu_total
372

    
373
    return result
374

    
375
  @staticmethod
376
  def GetShellCommandForConsole(instance):
377
    """Return a command for connecting to the console of an instance.
378

379
    """
380
    # TODO: we can either try the serial socket or suggest vnc
381
    return "echo Console not available for the kvm hypervisor yet"
382

    
383
  def Verify(self):
384
    """Verify the hypervisor.
385

386
    Check that the binary exists.
387

388
    """
389
    if not os.path.exists(constants.KVM_PATH):
390
      return "The kvm binary ('%s') does not exist." % constants.KVM_PATH
391
    if not os.path.exists(constants.SOCAT_PATH):
392
      return "The socat binary ('%s') does not exist." % constants.SOCAT_PATH
393

    
394

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

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

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

406
    """
407
    super(KVMHypervisor, cls).CheckParameterSyntax(hvparams)
408

    
409
    if not hvparams[constants.HV_KERNEL_PATH]:
410
      raise errors.HypervisorError("Need a kernel for the instance")
411

    
412
    if not os.path.isabs(hvparams[constants.HV_KERNEL_PATH]):
413
      raise errors.HypervisorError("The kernel path must an absolute path")
414

    
415
    if hvparams[constants.HV_INITRD_PATH]:
416
      if not os.path.isabs(hvparams[constants.HV_INITRD_PATH]):
417
        raise errors.HypervisorError("The initrd path must an absolute path"
418
                                     ", if defined")
419

    
420
  def ValidateParameters(self, hvparams):
421
    """Check the given parameters for validity.
422

423
    For the KVM hypervisor, this checks the existence of the
424
    kernel.
425

426
    """
427
    super(KVMHypervisor, self).ValidateParameters(hvparams)
428

    
429
    kernel_path = hvparams[constants.HV_KERNEL_PATH]
430
    if not os.path.isfile(kernel_path):
431
      raise errors.HypervisorError("Instance kernel '%s' not found or"
432
                                   " not a file" % kernel_path)
433
    initrd_path = hvparams[constants.HV_INITRD_PATH]
434
    if initrd_path and not os.path.isfile(initrd_path):
435
      raise errors.HypervisorError("Instance initrd '%s' not found or"
436
                                   " not a file" % initrd_path)