Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_kvm.py @ eb58f9b1

History | View | Annotate | Download (12.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
  """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

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

157
    Returns:
158
      [(name, id, memory, vcpus, stat, times),...]
159
    """
160
    data = []
161
    for name in os.listdir(self._PIDS_DIR):
162
      file = "%s/%s" % (self._PIDS_DIR, name)
163
      if utils.IsProcessAlive(utils.ReadPidFile(file)):
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.memory])
181
    kvm_cmd.extend(['-smp', instance.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.hvm_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
        # if os.path.blah(script):
198
        kvm_cmd.extend(['-net', 'tap,script=%s' % script])
199
        temp_files.append(script)
200
        nic_seq += 1
201

    
202
    boot_drive = True
203
    for cfdev, rldev in block_devices:
204
      # TODO: handle FD_LOOP and FD_BLKTAP (?)
205
      # TODO: handle if= correctly
206
      #drive_val = 'file=%s,format=raw,if=virtio' % rldev.dev_path
207
      if boot_drive:
208
        boot_val = ',boot=on'
209
      else:
210
        boot_val = ''
211

    
212
      drive_val = 'file=%s,format=raw,if=virtio' % rldev.dev_path
213
      if boot_drive:
214
        drive_val = '%s,boot=on' % drive_val
215
        boot_drive = False
216
      #drive_val = 'file=%s,if=virtio' % rldev.dev_path
217
      kvm_cmd.extend(['-drive', drive_val])
218
      #flagname = cfdev.iv_name.replace('s', 'h', 1)
219
      #kvm_cmd.extend(['-%s' % flagname, drive_val])
220

    
221
    # kernel handling
222
    if instance.kernel_path in (None, constants.VALUE_DEFAULT):
223
      kpath = constants.XEN_KERNEL # FIXME: other name??
224
    else:
225
      if not os.path.exists(instance.kernel_path):
226
        raise errors.HypervisorError("The kernel %s for instance %s is"
227
                                     " missing" % (instance.kernel_path,
228
                                                   instance.name))
229
      kpath = instance.kernel_path
230

    
231
    kvm_cmd.extend(['-kernel', kpath])
232

    
233
    # initrd handling
234
    if instance.initrd_path in (None, constants.VALUE_DEFAULT):
235
      if os.path.exists(constants.XEN_INITRD):
236
        initrd_path = constants.XEN_INITRD
237
      else:
238
        initrd_path = None
239
    elif instance.initrd_path == constants.VALUE_NONE:
240
      initrd_path = None
241
    else:
242
      if not os.path.exists(instance.initrd_path):
243
        raise errors.HypervisorError("The initrd %s for instance %s is"
244
                                     " missing" % (instance.initrd_path,
245
                                                   instance.name))
246
      initrd_path = instance.initrd_path
247

    
248
    if initrd_path:
249
      kvm_cmd.extend(['-initrd', initrd_path])
250

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

    
253
    #"hvm_boot_order",
254
    #"hvm_pae",
255
    #"hvm_cdrom_image_path",
256

    
257
    kvm_cmd.extend(['-nographic'])
258
    # FIXME: handle vnc, if needed
259
    # How do we decide whether to have it or not?? :(
260
    #"vnc_bind_address",
261
    #"network_port"
262
    base_control = '%s/%s' % (self._CTRL_DIR, instance.name)
263
    monitor_dev = 'unix:%s.monitor,server,nowait' % base_control
264
    kvm_cmd.extend(['-monitor', monitor_dev])
265
    serial_dev = 'unix:%s.serial,server,nowait' % base_control
266
    kvm_cmd.extend(['-serial', serial_dev])
267

    
268
    result = utils.RunCmd(kvm_cmd)
269
    if result.failed:
270
      raise errors.HypervisorError("Failed to start instance %s: %s (%s)" %
271
                                   (instance.name, result.fail_reason,
272
                                    result.output))
273

    
274
    if not utils.IsProcessAlive(utils.ReadPidFile(pidfile)):
275
      raise errors.HypervisorError("Failed to start instance %s: %s" %
276
                                   (instance.name))
277

    
278
    for file in temp_files:
279
      utils.RemoveFile(file)
280

    
281
  def StopInstance(self, instance, force=False):
282
    """Stop an instance.
283

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

    
300
    if not utils.IsProcessAlive(pid):
301
      utils.RemoveFile(pid_file)
302

    
303
  def RebootInstance(self, instance):
304
    """Reboot an instance.
305

306
    """
307
    # For some reason if we do a 'send-key ctrl-alt-delete' to the control
308
    # socket the instance will stop, but now power up again. So we'll resort
309
    # to shutdown and restart.
310
    self.StopInstance(instance)
311
    self.StartInstance(instance)
312

    
313
  def GetNodeInfo(self):
314
    """Return information about the node.
315

316
    The return value is a dict, which has to have the following items:
317
      (all values in MiB)
318
      - memory_total: the total memory size on the node
319
      - memory_free: the available memory on the node for instances
320
      - memory_dom0: the memory used by the node itself, if available
321

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

    
336
    result = {}
337
    sum_free = 0
338
    for line in data:
339
      splitfields = line.split(":", 1)
340

    
341
      if len(splitfields) > 1:
342
        key = splitfields[0].strip()
343
        val = splitfields[1].strip()
344
        if key == 'MemTotal':
345
          result['memory_total'] = int(val.split()[0])/1024
346
        elif key in ('MemFree', 'Buffers', 'Cached'):
347
          sum_free += int(val.split()[0])/1024
348
        elif key == 'Active':
349
          result['memory_dom0'] = int(val.split()[0])/1024
350
    result['memory_free'] = sum_free
351

    
352
    cpu_total = 0
353
    try:
354
      fh = open("/proc/cpuinfo")
355
      try:
356
        cpu_total = len(re.findall("(?m)^processor\s*:\s*[0-9]+\s*$",
357
                                   fh.read()))
358
      finally:
359
        fh.close()
360
    except EnvironmentError, err:
361
      raise errors.HypervisorError("Failed to list node info: %s" % err)
362
    result['cpu_total'] = cpu_total
363

    
364
    return result
365

    
366
  @staticmethod
367
  def GetShellCommandForConsole(instance):
368
    """Return a command for connecting to the console of an instance.
369

370
    """
371
    # TODO: we can either try the serial socket or suggest vnc
372
    return "echo Console not available for the kvm hypervisor yet"
373

    
374
  def Verify(self):
375
    """Verify the hypervisor.
376

377
    Check that the binary exists.
378

379
    """
380
    if not os.path.exists(constants.KVM_PATH):
381
      return "The kvm binary ('%s') does not exist." % constants.KVM_PATH
382