Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_kvm.py @ 8b3fd458

History | View | Annotate | Download (12 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
  def __init__(self):
51
    hv_base.BaseHypervisor.__init__(self)
52
    # Let's make sure the directories we need exist, even if the RUN_DIR lives
53
    # in a tmpfs filesystem or has been otherwise wiped out.
54
    for dir in self._DIRS:
55
      if not os.path.exists(dir):
56
        os.mkdir(dir)
57

    
58
  def _WriteNetScript(self, instance, seq, nic):
59
    """Write a script to connect a net interface to the proper bridge.
60

61
    This can be used by any qemu-type hypervisor.
62

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

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

    
99
  def ListInstances(self):
100
    """Get the list of running instances.
101

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

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

    
113
  def GetInstanceInfo(self, instance_name):
114
    """Get instance properties.
115

116
    Args:
117
      instance_name: the instance name
118

119
    Returns:
120
      (name, id, memory, vcpus, stat, times)
121
    """
122
    pidfile = "%s/%s" % (self._PIDS_DIR, instance_name)
123
    pid = utils.ReadPidFile(pidfile)
124
    if not utils.IsProcessAlive(pid):
125
      return None
126

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

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

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

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

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

156
    Returns:
157
      [(name, id, memory, vcpus, stat, times),...]
158
    """
159
    data = []
160
    for name in os.listdir(self._PIDS_DIR):
161
      file = "%s/%s" % (self._PIDS_DIR, name)
162
      if utils.IsProcessAlive(utils.ReadPidFile(file)):
163
        data.append(self.GetInstanceInfo(name))
164

    
165
    return data
166

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

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

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

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

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

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

    
215
    # kernel handling
216
    if instance.kernel_path in (None, constants.VALUE_DEFAULT):
217
      kpath = constants.XEN_KERNEL # FIXME: other name??
218
    else:
219
      if not os.path.exists(instance.kernel_path):
220
        raise errors.HypervisorError("The kernel %s for instance %s is"
221
                                     " missing" % (instance.kernel_path,
222
                                                   instance.name))
223
      kpath = instance.kernel_path
224

    
225
    kvm_cmd.extend(['-kernel', kpath])
226

    
227
    # initrd handling
228
    if instance.initrd_path in (None, constants.VALUE_DEFAULT):
229
      if os.path.exists(constants.XEN_INITRD):
230
        initrd_path = constants.XEN_INITRD
231
      else:
232
        initrd_path = None
233
    elif instance.initrd_path == constants.VALUE_NONE:
234
      initrd_path = None
235
    else:
236
      if not os.path.exists(instance.initrd_path):
237
        raise errors.HypervisorError("The initrd %s for instance %s is"
238
                                     " missing" % (instance.initrd_path,
239
                                                   instance.name))
240
      initrd_path = instance.initrd_path
241

    
242
    if initrd_path:
243
      kvm_cmd.extend(['-initrd', initrd_path])
244

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

    
247
    #"hvm_boot_order",
248
    #"hvm_cdrom_image_path",
249

    
250
    kvm_cmd.extend(['-nographic'])
251
    # FIXME: handle vnc, if needed
252
    # How do we decide whether to have it or not?? :(
253
    #"vnc_bind_address",
254
    #"network_port"
255
    base_control = '%s/%s' % (self._CTRL_DIR, instance.name)
256
    monitor_dev = 'unix:%s.monitor,server,nowait' % base_control
257
    kvm_cmd.extend(['-monitor', monitor_dev])
258
    serial_dev = 'unix:%s.serial,server,nowait' % base_control
259
    kvm_cmd.extend(['-serial', serial_dev])
260

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

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

    
271
    for file in temp_files:
272
      utils.RemoveFile(file)
273

    
274
  def StopInstance(self, instance, force=False):
275
    """Stop an instance.
276

277
    """
278
    pid_file = self._PIDS_DIR + "/%s" % instance.name
279
    pid = utils.ReadPidFile(pid_file)
280
    if pid > 0 and utils.IsProcessAlive(pid):
281
      if force or not instance.hvm_acpi:
282
        utils.KillProcess(pid)
283
      else:
284
        # This only works if the instance os has acpi support
285
        monitor_socket = '%s/%s.monitor'  % (self._CTRL_DIR, instance.name)
286
        socat = 'socat -u STDIN UNIX-CONNECT:%s' % monitor_socket
287
        command = "echo 'system_powerdown' | %s" % socat
288
        result = utils.RunCmd(command)
289
        if result.failed:
290
          raise errors.HypervisorError("Failed to stop instance %s: %s" %
291
                                       (instance.name, result.fail_reason))
292

    
293
    if not utils.IsProcessAlive(pid):
294
      utils.RemoveFile(pid_file)
295

    
296
  def RebootInstance(self, instance):
297
    """Reboot an instance.
298

299
    """
300
    # For some reason if we do a 'send-key ctrl-alt-delete' to the control
301
    # socket the instance will stop, but now power up again. So we'll resort
302
    # to shutdown and restart.
303
    self.StopInstance(instance)
304
    self.StartInstance(instance)
305

    
306
  def GetNodeInfo(self):
307
    """Return information about the node.
308

309
    The return value is a dict, which has to have the following items:
310
      (all values in MiB)
311
      - memory_total: the total memory size on the node
312
      - memory_free: the available memory on the node for instances
313
      - memory_dom0: the memory used by the node itself, if available
314

315
    """
316
    # global ram usage from the xm info command
317
    # memory                 : 3583
318
    # free_memory            : 747
319
    # note: in xen 3, memory has changed to total_memory
320
    try:
321
      fh = file("/proc/meminfo")
322
      try:
323
        data = fh.readlines()
324
      finally:
325
        fh.close()
326
    except IOError, err:
327
      raise errors.HypervisorError("Failed to list node info: %s" % err)
328

    
329
    result = {}
330
    sum_free = 0
331
    for line in data:
332
      splitfields = line.split(":", 1)
333

    
334
      if len(splitfields) > 1:
335
        key = splitfields[0].strip()
336
        val = splitfields[1].strip()
337
        if key == 'MemTotal':
338
          result['memory_total'] = int(val.split()[0])/1024
339
        elif key in ('MemFree', 'Buffers', 'Cached'):
340
          sum_free += int(val.split()[0])/1024
341
        elif key == 'Active':
342
          result['memory_dom0'] = int(val.split()[0])/1024
343
    result['memory_free'] = sum_free
344

    
345
    cpu_total = 0
346
    try:
347
      fh = open("/proc/cpuinfo")
348
      try:
349
        cpu_total = len(re.findall("(?m)^processor\s*:\s*[0-9]+\s*$",
350
                                   fh.read()))
351
      finally:
352
        fh.close()
353
    except EnvironmentError, err:
354
      raise errors.HypervisorError("Failed to list node info: %s" % err)
355
    result['cpu_total'] = cpu_total
356

    
357
    return result
358

    
359
  @staticmethod
360
  def GetShellCommandForConsole(instance):
361
    """Return a command for connecting to the console of an instance.
362

363
    """
364
    # TODO: we can either try the serial socket or suggest vnc
365
    return "echo Console not available for the kvm hypervisor yet"
366

    
367
  def Verify(self):
368
    """Verify the hypervisor.
369

370
    Check that the binary exists.
371

372
    """
373
    if not os.path.exists(constants.KVM_PATH):
374
      return "The kvm binary ('%s') does not exist." % constants.KVM_PATH