Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_kvm.py @ c4469f75

History | View | Annotate | Download (12.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.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 _WriteNetScript(self, instance, seq, nic):
61
    """Write a script to connect a net interface to the proper bridge.
62

63
    This can be used by any qemu-type hypervisor.
64

65
    @param instance: instance we're acting on
66
    @type instance: instance object
67
    @param seq: nic sequence number
68
    @type seq: int
69
    @param nic: nic we're acting on
70
    @type nic: nic object
71
    @return: netscript file name
72
    @rtype: string
73

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

    
101
  def ListInstances(self):
102
    """Get the list of running instances.
103

104
    We can do this by listing our live instances directory and
105
    checking whether the associated kvm process is still alive.
106

107
    """
108
    result = []
109
    for name in os.listdir(self._PIDS_DIR):
110
      filename = "%s/%s" % (self._PIDS_DIR, name)
111
      if utils.IsProcessAlive(utils.ReadPidFile(filename)):
112
        result.append(name)
113
    return result
114

    
115
  def GetInstanceInfo(self, instance_name):
116
    """Get instance properties.
117

118
    @param instance_name: the instance name
119

120
    @return: tuple (name, id, memory, vcpus, stat, times)
121

122
    """
123
    pidfile = "%s/%s" % (self._PIDS_DIR, instance_name)
124
    pid = utils.ReadPidFile(pidfile)
125
    if not utils.IsProcessAlive(pid):
126
      return None
127

    
128
    cmdline_file = "/proc/%s/cmdline" % pid
129
    try:
130
      fh = open(cmdline_file, 'r')
131
      try:
132
        cmdline = fh.read()
133
      finally:
134
        fh.close()
135
    except IOError, err:
136
      raise errors.HypervisorError("Failed to list instance %s: %s" %
137
                                   (instance_name, err))
138

    
139
    memory = 0
140
    vcpus = 0
141
    stat = "---b-"
142
    times = "0"
143

    
144
    arg_list = cmdline.split('\x00')
145
    while arg_list:
146
      arg =  arg_list.pop(0)
147
      if arg == '-m':
148
        memory = arg_list.pop(0)
149
      elif arg == '-smp':
150
        vcpus = arg_list.pop(0)
151

    
152
    return (instance_name, pid, memory, vcpus, stat, times)
153

    
154
  def GetAllInstancesInfo(self):
155
    """Get properties of all instances.
156

157
    @return: list of tuples (name, id, memory, vcpus, stat, times)
158

159
    """
160
    data = []
161
    for name in os.listdir(self._PIDS_DIR):
162
      filename = "%s/%s" % (self._PIDS_DIR, name)
163
      if utils.IsProcessAlive(utils.ReadPidFile(filename)):
164
        data.append(self.GetInstanceInfo(name))
165

    
166
    return data
167

    
168
  def StartInstance(self, instance, block_devices, extra_args):
169
    """Start an instance.
170

171
    """
172
    temp_files = []
173
    pidfile = self._PIDS_DIR + "/%s" % instance.name
174
    if utils.IsProcessAlive(utils.ReadPidFile(pidfile)):
175
      raise errors.HypervisorError("Failed to start instance %s: %s" %
176
                                   (instance.name, "already running"))
177

    
178
    kvm = constants.KVM_PATH
179
    kvm_cmd = [kvm]
180
    kvm_cmd.extend(['-m', instance.beparams[constants.BE_MEMORY]])
181
    kvm_cmd.extend(['-smp', instance.beparams[constants.BE_VCPUS]])
182
    kvm_cmd.extend(['-pidfile', pidfile])
183
    # used just by the vnc server, if enabled
184
    kvm_cmd.extend(['-name', instance.name])
185
    kvm_cmd.extend(['-daemonize'])
186
    if not instance.hvparams[constants.HV_ACPI]:
187
      kvm_cmd.extend(['-no-acpi'])
188
    if not instance.nics:
189
      kvm_cmd.extend(['-net', 'none'])
190
    else:
191
      nic_seq = 0
192
      for nic in instance.nics:
193
        script = self._WriteNetScript(instance, nic_seq, nic)
194
        # FIXME: handle other models
195
        nic_val = "nic,macaddr=%s,model=virtio" % nic.mac
196
        kvm_cmd.extend(['-net', nic_val])
197
        kvm_cmd.extend(['-net', 'tap,script=%s' % script])
198
        temp_files.append(script)
199
        nic_seq += 1
200

    
201
    boot_drive = True
202
    for cfdev, dev_path in block_devices:
203
      # TODO: handle FD_LOOP and FD_BLKTAP (?)
204
      if boot_drive:
205
        boot_val = ',boot=on'
206
        boot_drive = False
207
      else:
208
        boot_val = ''
209

    
210
      # TODO: handle different if= types
211
      if_val = ',if=virtio'
212

    
213
      drive_val = 'file=%s,format=raw%s%s' % (dev_path, if_val, boot_val)
214
      kvm_cmd.extend(['-drive', drive_val])
215

    
216
    kvm_cmd.extend(['-kernel', instance.hvparams[constants.HV_KERNEL_PATH]])
217

    
218
    initrd_path = instance.hvparams[constants.HV_INITRD_PATH]
219
    if initrd_path:
220
      kvm_cmd.extend(['-initrd', initrd_path])
221

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

    
224
    #"hvm_boot_order",
225
    #"hvm_cdrom_image_path",
226

    
227
    kvm_cmd.extend(['-nographic'])
228
    # FIXME: handle vnc, if needed
229
    # How do we decide whether to have it or not?? :(
230
    #"vnc_bind_address",
231
    #"network_port"
232
    base_control = '%s/%s' % (self._CTRL_DIR, instance.name)
233
    monitor_dev = 'unix:%s.monitor,server,nowait' % base_control
234
    kvm_cmd.extend(['-monitor', monitor_dev])
235
    serial_dev = 'unix:%s.serial,server,nowait' % base_control
236
    kvm_cmd.extend(['-serial', serial_dev])
237

    
238
    result = utils.RunCmd(kvm_cmd)
239
    if result.failed:
240
      raise errors.HypervisorError("Failed to start instance %s: %s (%s)" %
241
                                   (instance.name, result.fail_reason,
242
                                    result.output))
243

    
244
    if not utils.IsProcessAlive(utils.ReadPidFile(pidfile)):
245
      raise errors.HypervisorError("Failed to start instance %s: %s" %
246
                                   (instance.name))
247

    
248
    for filename in temp_files:
249
      utils.RemoveFile(filename)
250

    
251
  def StopInstance(self, instance, force=False):
252
    """Stop an instance.
253

254
    """
255
    socat_bin = constants.SOCAT_PATH
256
    pid_file = self._PIDS_DIR + "/%s" % instance.name
257
    pid = utils.ReadPidFile(pid_file)
258
    if pid > 0 and utils.IsProcessAlive(pid):
259
      if force or not instance.hvparams[constants.HV_ACPI]:
260
        utils.KillProcess(pid)
261
      else:
262
        # This only works if the instance os has acpi support
263
        monitor_socket = '%s/%s.monitor'  % (self._CTRL_DIR, instance.name)
264
        socat = '%s -u STDIN UNIX-CONNECT:%s' % (socat_bin, monitor_socket)
265
        command = "echo 'system_powerdown' | %s" % socat
266
        result = utils.RunCmd(command)
267
        if result.failed:
268
          raise errors.HypervisorError("Failed to stop instance %s: %s" %
269
                                       (instance.name, result.fail_reason))
270

    
271
    if not utils.IsProcessAlive(pid):
272
      utils.RemoveFile(pid_file)
273

    
274
  def RebootInstance(self, instance):
275
    """Reboot an instance.
276

277
    """
278
    # For some reason if we do a 'send-key ctrl-alt-delete' to the control
279
    # socket the instance will stop, but now power up again. So we'll resort
280
    # to shutdown and restart.
281
    self.StopInstance(instance)
282
    self.StartInstance(instance)
283

    
284
  def GetNodeInfo(self):
285
    """Return information about the node.
286

287
    @return: a dict with the following keys (values in MiB):
288
          - memory_total: the total memory size on the node
289
          - memory_free: the available memory on the node for instances
290
          - memory_dom0: the memory used by the node itself, if available
291

292
    """
293
    # global ram usage from the xm info command
294
    # memory                 : 3583
295
    # free_memory            : 747
296
    # note: in xen 3, memory has changed to total_memory
297
    try:
298
      fh = file("/proc/meminfo")
299
      try:
300
        data = fh.readlines()
301
      finally:
302
        fh.close()
303
    except IOError, err:
304
      raise errors.HypervisorError("Failed to list node info: %s" % err)
305

    
306
    result = {}
307
    sum_free = 0
308
    for line in data:
309
      splitfields = line.split(":", 1)
310

    
311
      if len(splitfields) > 1:
312
        key = splitfields[0].strip()
313
        val = splitfields[1].strip()
314
        if key == 'MemTotal':
315
          result['memory_total'] = int(val.split()[0])/1024
316
        elif key in ('MemFree', 'Buffers', 'Cached'):
317
          sum_free += int(val.split()[0])/1024
318
        elif key == 'Active':
319
          result['memory_dom0'] = int(val.split()[0])/1024
320
    result['memory_free'] = sum_free
321

    
322
    cpu_total = 0
323
    try:
324
      fh = open("/proc/cpuinfo")
325
      try:
326
        cpu_total = len(re.findall("(?m)^processor\s*:\s*[0-9]+\s*$",
327
                                   fh.read()))
328
      finally:
329
        fh.close()
330
    except EnvironmentError, err:
331
      raise errors.HypervisorError("Failed to list node info: %s" % err)
332
    result['cpu_total'] = cpu_total
333

    
334
    return result
335

    
336
  @staticmethod
337
  def GetShellCommandForConsole(instance):
338
    """Return a command for connecting to the console of an instance.
339

340
    """
341
    # TODO: we can either try the serial socket or suggest vnc
342
    return "echo Console not available for the kvm hypervisor yet"
343

    
344
  def Verify(self):
345
    """Verify the hypervisor.
346

347
    Check that the binary exists.
348

349
    """
350
    if not os.path.exists(constants.KVM_PATH):
351
      return "The kvm binary ('%s') does not exist." % constants.KVM_PATH
352
    if not os.path.exists(constants.SOCAT_PATH):
353
      return "The socat binary ('%s') does not exist." % constants.SOCAT_PATH
354

    
355

    
356
  @classmethod
357
  def CheckParameterSyntax(cls, hvparams):
358
    """Check the given parameters for validity.
359

360
    For the KVM hypervisor, this only check the existence of the
361
    kernel.
362

363
    @type hvparams:  dict
364
    @param hvparams: dictionary with parameter names/value
365
    @raise errors.HypervisorError: when a parameter is not valid
366

367
    """
368
    super(KVMHypervisor, cls).CheckParameterSyntax(hvparams)
369

    
370
    if not hvparams[constants.HV_KERNEL_PATH]:
371
      raise errors.HypervisorError("Need a kernel for the instance")
372

    
373
    if not os.path.isabs(hvparams[constants.HV_KERNEL_PATH]):
374
      raise errors.HypervisorError("The kernel path must an absolute path")
375

    
376
    if hvparams[constants.HV_INITRD_PATH]:
377
      if not os.path.isabs(hvparams[constants.HV_INITRD_PATH]):
378
        raise errors.HypervisorError("The initrd path must an absolute path"
379
                                     ", if defined")
380

    
381
  def ValidateParameters(self, hvparams):
382
    """Check the given parameters for validity.
383

384
    For the KVM hypervisor, this checks the existence of the
385
    kernel.
386

387
    """
388
    super(KVMHypervisor, self).ValidateParameters(hvparams)
389

    
390
    kernel_path = hvparams[constants.HV_KERNEL_PATH]
391
    if not os.path.isfile(kernel_path):
392
      raise errors.HypervisorError("Instance kernel '%s' not found or"
393
                                   " not a file" % kernel_path)
394
    initrd_path = hvparams[constants.HV_INITRD_PATH]
395
    if initrd_path and not os.path.isfile(initrd_path):
396
      raise errors.HypervisorError("Instance initrd '%s' not found or"
397
                                   " not a file" % initrd_path)