Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_kvm.py @ c4fbefc8

History | View | Annotate | Download (13.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"
43
  _CTRL_DIR = _ROOT_DIR + "/ctrl"
44
  _DIRS = [_ROOT_DIR, _PIDS_DIR, _CTRL_DIR]
45

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

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

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

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

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

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

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

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

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

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

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

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

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

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

130
    @param instance_name: the instance name
131

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

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

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

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

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

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

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

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

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

    
178
    return data
179

    
180
  def StartInstance(self, instance, block_devices, extra_args):
181
    """Start an instance.
182

183
    """
184
    temp_files = []
185
    pidfile = self._PIDS_DIR + "/%s" % instance.name
186
    if utils.IsProcessAlive(utils.ReadPidFile(pidfile)):
187
      raise errors.HypervisorError("Failed to start instance %s: %s" %
188
                                   (instance.name, "already running"))
189

    
190
    kvm = constants.KVM_PATH
191
    kvm_cmd = [kvm]
192
    kvm_cmd.extend(['-m', instance.beparams[constants.BE_MEMORY]])
193
    kvm_cmd.extend(['-smp', instance.beparams[constants.BE_VCPUS]])
194
    kvm_cmd.extend(['-pidfile', pidfile])
195
    # used just by the vnc server, if enabled
196
    kvm_cmd.extend(['-name', instance.name])
197
    kvm_cmd.extend(['-daemonize'])
198
    if not instance.hvparams[constants.HV_ACPI]:
199
      kvm_cmd.extend(['-no-acpi'])
200
    if not instance.nics:
201
      kvm_cmd.extend(['-net', 'none'])
202
    else:
203
      nic_seq = 0
204
      for nic in instance.nics:
205
        script = self._WriteNetScript(instance, nic_seq, nic)
206
        # FIXME: handle other models
207
        nic_val = "nic,macaddr=%s,model=virtio" % nic.mac
208
        kvm_cmd.extend(['-net', nic_val])
209
        kvm_cmd.extend(['-net', 'tap,script=%s' % script])
210
        temp_files.append(script)
211
        nic_seq += 1
212

    
213
    boot_drive = True
214
    for cfdev, dev_path in block_devices:
215
      # TODO: handle FD_LOOP and FD_BLKTAP (?)
216
      if boot_drive:
217
        boot_val = ',boot=on'
218
        boot_drive = False
219
      else:
220
        boot_val = ''
221

    
222
      # TODO: handle different if= types
223
      if_val = ',if=virtio'
224

    
225
      drive_val = 'file=%s,format=raw%s%s' % (dev_path, if_val, boot_val)
226
      kvm_cmd.extend(['-drive', drive_val])
227

    
228
    kvm_cmd.extend(['-kernel', instance.hvparams[constants.HV_KERNEL_PATH]])
229

    
230
    initrd_path = instance.hvparams[constants.HV_INITRD_PATH]
231
    if initrd_path:
232
      kvm_cmd.extend(['-initrd', initrd_path])
233

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

    
236
    #"hvm_boot_order",
237
    #"hvm_cdrom_image_path",
238

    
239
    kvm_cmd.extend(['-nographic'])
240
    # FIXME: handle vnc, if needed
241
    # How do we decide whether to have it or not?? :(
242
    #"vnc_bind_address",
243
    #"network_port"
244
    monitor_dev = 'unix:%s,server,nowait' % \
245
      self._InstanceMonitor(instance.name)
246
    kvm_cmd.extend(['-monitor', monitor_dev])
247
    serial_dev = 'unix:%s,server,nowait' % self._InstanceSerial(instance.name)
248
    kvm_cmd.extend(['-serial', serial_dev])
249

    
250
    result = utils.RunCmd(kvm_cmd)
251
    if result.failed:
252
      raise errors.HypervisorError("Failed to start instance %s: %s (%s)" %
253
                                   (instance.name, result.fail_reason,
254
                                    result.output))
255

    
256
    if not utils.IsProcessAlive(utils.ReadPidFile(pidfile)):
257
      raise errors.HypervisorError("Failed to start instance %s: %s" %
258
                                   (instance.name))
259

    
260
    for filename in temp_files:
261
      utils.RemoveFile(filename)
262

    
263
  def StopInstance(self, instance, force=False):
264
    """Stop an instance.
265

266
    """
267
    socat_bin = constants.SOCAT_PATH
268
    pid_file = self._PIDS_DIR + "/%s" % instance.name
269
    pid = utils.ReadPidFile(pid_file)
270
    if pid > 0 and utils.IsProcessAlive(pid):
271
      if force or not instance.hvparams[constants.HV_ACPI]:
272
        utils.KillProcess(pid)
273
      else:
274
        # This only works if the instance os has acpi support
275
        monitor_socket = '%s/%s.monitor'  % (self._CTRL_DIR, instance.name)
276
        socat = '%s -u STDIN UNIX-CONNECT:%s' % (socat_bin, monitor_socket)
277
        command = "echo 'system_powerdown' | %s" % socat
278
        result = utils.RunCmd(command)
279
        if result.failed:
280
          raise errors.HypervisorError("Failed to stop instance %s: %s" %
281
                                       (instance.name, result.fail_reason))
282

    
283
    if not utils.IsProcessAlive(pid):
284
      utils.RemoveFile(pid_file)
285
      utils.RemoveFile(self._InstanceMonitor(instance.name))
286
      utils.RemoveFile(self._InstanceSerial(instance.name))
287

    
288
  def RebootInstance(self, instance):
289
    """Reboot an instance.
290

291
    """
292
    # For some reason if we do a 'send-key ctrl-alt-delete' to the control
293
    # socket the instance will stop, but now power up again. So we'll resort
294
    # to shutdown and restart.
295
    self.StopInstance(instance)
296
    self.StartInstance(instance)
297

    
298
  def GetNodeInfo(self):
299
    """Return information about the node.
300

301
    @return: a dict with the following keys (values in MiB):
302
          - memory_total: the total memory size on the node
303
          - memory_free: the available memory on the node for instances
304
          - memory_dom0: the memory used by the node itself, if available
305

306
    """
307
    # global ram usage from the xm info command
308
    # memory                 : 3583
309
    # free_memory            : 747
310
    # note: in xen 3, memory has changed to total_memory
311
    try:
312
      fh = file("/proc/meminfo")
313
      try:
314
        data = fh.readlines()
315
      finally:
316
        fh.close()
317
    except IOError, err:
318
      raise errors.HypervisorError("Failed to list node info: %s" % err)
319

    
320
    result = {}
321
    sum_free = 0
322
    for line in data:
323
      splitfields = line.split(":", 1)
324

    
325
      if len(splitfields) > 1:
326
        key = splitfields[0].strip()
327
        val = splitfields[1].strip()
328
        if key == 'MemTotal':
329
          result['memory_total'] = int(val.split()[0])/1024
330
        elif key in ('MemFree', 'Buffers', 'Cached'):
331
          sum_free += int(val.split()[0])/1024
332
        elif key == 'Active':
333
          result['memory_dom0'] = int(val.split()[0])/1024
334
    result['memory_free'] = sum_free
335

    
336
    cpu_total = 0
337
    try:
338
      fh = open("/proc/cpuinfo")
339
      try:
340
        cpu_total = len(re.findall("(?m)^processor\s*:\s*[0-9]+\s*$",
341
                                   fh.read()))
342
      finally:
343
        fh.close()
344
    except EnvironmentError, err:
345
      raise errors.HypervisorError("Failed to list node info: %s" % err)
346
    result['cpu_total'] = cpu_total
347

    
348
    return result
349

    
350
  @staticmethod
351
  def GetShellCommandForConsole(instance):
352
    """Return a command for connecting to the console of an instance.
353

354
    """
355
    # TODO: we can either try the serial socket or suggest vnc
356
    return "echo Console not available for the kvm hypervisor yet"
357

    
358
  def Verify(self):
359
    """Verify the hypervisor.
360

361
    Check that the binary exists.
362

363
    """
364
    if not os.path.exists(constants.KVM_PATH):
365
      return "The kvm binary ('%s') does not exist." % constants.KVM_PATH
366
    if not os.path.exists(constants.SOCAT_PATH):
367
      return "The socat binary ('%s') does not exist." % constants.SOCAT_PATH
368

    
369

    
370
  @classmethod
371
  def CheckParameterSyntax(cls, hvparams):
372
    """Check the given parameters for validity.
373

374
    For the KVM hypervisor, this only check the existence of the
375
    kernel.
376

377
    @type hvparams:  dict
378
    @param hvparams: dictionary with parameter names/value
379
    @raise errors.HypervisorError: when a parameter is not valid
380

381
    """
382
    super(KVMHypervisor, cls).CheckParameterSyntax(hvparams)
383

    
384
    if not hvparams[constants.HV_KERNEL_PATH]:
385
      raise errors.HypervisorError("Need a kernel for the instance")
386

    
387
    if not os.path.isabs(hvparams[constants.HV_KERNEL_PATH]):
388
      raise errors.HypervisorError("The kernel path must an absolute path")
389

    
390
    if hvparams[constants.HV_INITRD_PATH]:
391
      if not os.path.isabs(hvparams[constants.HV_INITRD_PATH]):
392
        raise errors.HypervisorError("The initrd path must an absolute path"
393
                                     ", if defined")
394

    
395
  def ValidateParameters(self, hvparams):
396
    """Check the given parameters for validity.
397

398
    For the KVM hypervisor, this checks the existence of the
399
    kernel.
400

401
    """
402
    super(KVMHypervisor, self).ValidateParameters(hvparams)
403

    
404
    kernel_path = hvparams[constants.HV_KERNEL_PATH]
405
    if not os.path.isfile(kernel_path):
406
      raise errors.HypervisorError("Instance kernel '%s' not found or"
407
                                   " not a file" % kernel_path)
408
    initrd_path = hvparams[constants.HV_INITRD_PATH]
409
    if initrd_path and not os.path.isfile(initrd_path):
410
      raise errors.HypervisorError("Instance initrd '%s' not found or"
411
                                   " not a file" % initrd_path)