Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_kvm.py @ afee0879

History | View | Annotate | Download (12.9 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
  """Fake hypervisor interface.
40

41
  This can be used for testing the ganeti code without having to have
42
  a real virtualisation software installed.
43

44
  """
45
  _ROOT_DIR = constants.RUN_GANETI_DIR + "/kvm-hypervisor"
46
  _PIDS_DIR = _ROOT_DIR + "/pid"
47
  _CTRL_DIR = _ROOT_DIR + "/ctrl"
48
  _DIRS = [_ROOT_DIR, _PIDS_DIR, _CTRL_DIR]
49

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

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

    
64
  def _WriteNetScript(self, instance, seq, nic):
65
    """Write a script to connect a net interface to the proper bridge.
66

67
    This can be used by any qemu-type hypervisor.
68

69
    @param instance: instance we're acting on
70
    @type instance: instance object
71
    @param seq: nic sequence number
72
    @type seq: int
73
    @param nic: nic we're acting on
74
    @type nic: nic object
75
    @return: netscript file name
76
    @rtype: string
77

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

    
105
  def ListInstances(self):
106
    """Get the list of running instances.
107

108
    We can do this by listing our live instances directory and
109
    checking whether the associated kvm process is still alive.
110

111
    """
112
    result = []
113
    for name in os.listdir(self._PIDS_DIR):
114
      filename = "%s/%s" % (self._PIDS_DIR, name)
115
      if utils.IsProcessAlive(utils.ReadPidFile(filename)):
116
        result.append(name)
117
    return result
118

    
119
  def GetInstanceInfo(self, instance_name):
120
    """Get instance properties.
121

122
    @param instance_name: the instance name
123

124
    @return: tuple (name, id, memory, vcpus, stat, times)
125

126
    """
127
    pidfile = "%s/%s" % (self._PIDS_DIR, instance_name)
128
    pid = utils.ReadPidFile(pidfile)
129
    if not utils.IsProcessAlive(pid):
130
      return None
131

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

    
143
    memory = 0
144
    vcpus = 0
145
    stat = "---b-"
146
    times = "0"
147

    
148
    arg_list = cmdline.split('\x00')
149
    while arg_list:
150
      arg =  arg_list.pop(0)
151
      if arg == '-m':
152
        memory = arg_list.pop(0)
153
      elif arg == '-smp':
154
        vcpus = arg_list.pop(0)
155

    
156
    return (instance_name, pid, memory, vcpus, stat, times)
157

    
158
  def GetAllInstancesInfo(self):
159
    """Get properties of all instances.
160

161
    @return: list of tuples (name, id, memory, vcpus, stat, times)
162

163
    """
164
    data = []
165
    for name in os.listdir(self._PIDS_DIR):
166
      filename = "%s/%s" % (self._PIDS_DIR, name)
167
      if utils.IsProcessAlive(utils.ReadPidFile(filename)):
168
        data.append(self.GetInstanceInfo(name))
169

    
170
    return data
171

    
172
  def StartInstance(self, instance, block_devices, extra_args):
173
    """Start an instance.
174

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

    
182
    kvm = constants.KVM_PATH
183
    kvm_cmd = [kvm]
184
    kvm_cmd.extend(['-m', instance.beparams[constants.BE_MEMORY]])
185
    kvm_cmd.extend(['-smp', instance.beparams[constants.BE_VCPUS]])
186
    kvm_cmd.extend(['-pidfile', pidfile])
187
    # used just by the vnc server, if enabled
188
    kvm_cmd.extend(['-name', instance.name])
189
    kvm_cmd.extend(['-daemonize'])
190
    if not instance.hvparams[constants.HV_ACPI]:
191
      kvm_cmd.extend(['-no-acpi'])
192
    if not instance.nics:
193
      kvm_cmd.extend(['-net', 'none'])
194
    else:
195
      nic_seq = 0
196
      for nic in instance.nics:
197
        script = self._WriteNetScript(instance, nic_seq, nic)
198
        # FIXME: handle other models
199
        nic_val = "nic,macaddr=%s,model=virtio" % nic.mac
200
        kvm_cmd.extend(['-net', nic_val])
201
        kvm_cmd.extend(['-net', 'tap,script=%s' % script])
202
        temp_files.append(script)
203
        nic_seq += 1
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
    base_control = '%s/%s' % (self._CTRL_DIR, instance.name)
237
    monitor_dev = 'unix:%s.monitor,server,nowait' % base_control
238
    kvm_cmd.extend(['-monitor', monitor_dev])
239
    serial_dev = 'unix:%s.serial,server,nowait' % base_control
240
    kvm_cmd.extend(['-serial', serial_dev])
241

    
242
    result = utils.RunCmd(kvm_cmd)
243
    if result.failed:
244
      raise errors.HypervisorError("Failed to start instance %s: %s (%s)" %
245
                                   (instance.name, result.fail_reason,
246
                                    result.output))
247

    
248
    if not utils.IsProcessAlive(utils.ReadPidFile(pidfile)):
249
      raise errors.HypervisorError("Failed to start instance %s: %s" %
250
                                   (instance.name))
251

    
252
    for filename in temp_files:
253
      utils.RemoveFile(filename)
254

    
255
  def StopInstance(self, instance, force=False):
256
    """Stop an instance.
257

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

    
275
    if not utils.IsProcessAlive(pid):
276
      utils.RemoveFile(pid_file)
277

    
278
  def RebootInstance(self, instance):
279
    """Reboot an instance.
280

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

    
288
  def GetNodeInfo(self):
289
    """Return information about the node.
290

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

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

    
310
    result = {}
311
    sum_free = 0
312
    for line in data:
313
      splitfields = line.split(":", 1)
314

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

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

    
338
    return result
339

    
340
  @staticmethod
341
  def GetShellCommandForConsole(instance):
342
    """Return a command for connecting to the console of an instance.
343

344
    """
345
    # TODO: we can either try the serial socket or suggest vnc
346
    return "echo Console not available for the kvm hypervisor yet"
347

    
348
  def Verify(self):
349
    """Verify the hypervisor.
350

351
    Check that the binary exists.
352

353
    """
354
    if not os.path.exists(constants.KVM_PATH):
355
      return "The kvm binary ('%s') does not exist." % constants.KVM_PATH
356
    if not os.path.exists(constants.SOCAT_PATH):
357
      return "The socat binary ('%s') does not exist." % constants.SOCAT_PATH
358

    
359

    
360
  @classmethod
361
  def CheckParameterSyntax(cls, hvparams):
362
    """Check the given parameters for validity.
363

364
    For the KVM hypervisor, this only check the existence of the
365
    kernel.
366

367
    @type hvparams:  dict
368
    @param hvparams: dictionary with parameter names/value
369
    @raise errors.HypervisorError: when a parameter is not valid
370

371
    """
372
    super(KVMHypervisor, cls).CheckParameterSyntax(hvparams)
373

    
374
    if not hvparams[constants.HV_KERNEL_PATH]:
375
      raise errors.HypervisorError("Need a kernel for the instance")
376

    
377
    if not os.path.isabs(hvparams[constants.HV_KERNEL_PATH]):
378
      raise errors.HypervisorError("The kernel path must an absolute path")
379

    
380
    if hvparams[constants.HV_INITRD_PATH]:
381
      if not os.path.isabs(hvparams[constants.HV_INITRD_PATH]):
382
        raise errors.HypervisorError("The initrd path must an absolute path"
383
                                     ", if defined")
384

    
385
  def ValidateParameters(self, hvparams):
386
    """Check the given parameters for validity.
387

388
    For the KVM hypervisor, this checks the existence of the
389
    kernel.
390

391
    """
392
    super(KVMHypervisor, self).ValidateParameters(hvparams)
393

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