Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_kvm.py @ a1d79fc6

History | View | Annotate | Download (13.3 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 StartInstance(self, instance, block_devices, extra_args):
182
    """Start an instance.
183

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

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

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

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

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

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

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

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

    
237
    #"hvm_boot_order",
238
    #"hvm_cdrom_image_path",
239

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
349
    return result
350

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

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

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

362
    Check that the binary exists.
363

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

    
370

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

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

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

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

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

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

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

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

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

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

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