Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor.py @ f00b46bc

History | View | Annotate | Download (14.2 kB)

1
#
2
#
3

    
4
# Copyright (C) 2006, 2007 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
"""Module that abstracts the virtualisation interface
23

24
"""
25

    
26
import time
27
import os
28
from cStringIO import StringIO
29

    
30
from ganeti import utils
31
from ganeti import logger
32
from ganeti import ssconf
33
from ganeti import constants
34
from ganeti.errors import HypervisorError
35

    
36
_HT_XEN30 = "xen-3.0"
37
_HT_FAKE = "fake"
38

    
39
VALID_HTYPES = (_HT_XEN30, _HT_FAKE)
40

    
41
def GetHypervisor():
42
  """Return a Hypervisor instance.
43

44
  This function parses the cluster hypervisor configuration file and
45
  instantiates a class based on the value of this file.
46

47
  """
48
  ht_kind = ssconf.SimpleStore().GetHypervisorType()
49
  if ht_kind == _HT_XEN30:
50
    cls = XenHypervisor
51
  elif ht_kind == _HT_FAKE:
52
    cls = FakeHypervisor
53
  else:
54
    raise HypervisorError("Unknown hypervisor type '%s'" % ht_kind)
55
  return cls()
56

    
57

    
58
class BaseHypervisor(object):
59
  """Abstract virtualisation technology interface
60

61
  The goal is that all aspects of the virtualisation technology must
62
  be abstracted away from the rest of code.
63

64
  """
65
  def __init__(self):
66
    pass
67

    
68
  def StartInstance(self, instance, block_devices, extra_args):
69
    """Start an instance."""
70
    raise NotImplementedError
71

    
72
  def StopInstance(self, instance, force=False):
73
    """Stop an instance."""
74
    raise NotImplementedError
75

    
76
  def ListInstances(self):
77
    """Get the list of running instances."""
78
    raise NotImplementedError
79

    
80
  def GetInstanceInfo(self, instance_name):
81
    """Get instance properties.
82

83
    Args:
84
      instance_name: the instance name
85

86
    Returns:
87
      (name, id, memory, vcpus, state, times)
88

89
    """
90
    raise NotImplementedError
91

    
92
  def GetAllInstancesInfo(self):
93
    """Get properties of all instances.
94

95
    Returns:
96
      [(name, id, memory, vcpus, stat, times),...]
97
    """
98
    raise NotImplementedError
99

    
100
  def GetNodeInfo(self):
101
    """Return information about the node.
102

103
    The return value is a dict, which has to have the following items:
104
      (all values in MiB)
105
      - memory_total: the total memory size on the node
106
      - memory_free: the available memory on the node for instances
107
      - memory_dom0: the memory used by the node itself, if available
108

109
    """
110
    raise NotImplementedError
111

    
112
  @staticmethod
113
  def GetShellCommandForConsole(instance_name):
114
    """Return a command for connecting to the console of an instance.
115

116
    """
117
    raise NotImplementedError
118

    
119
  def Verify(self):
120
    """Verify the hypervisor.
121

122
    """
123
    raise NotImplementedError
124

    
125

    
126
class XenHypervisor(BaseHypervisor):
127
  """Xen hypervisor interface"""
128

    
129
  @staticmethod
130
  def _WriteConfigFile(instance, block_devices, extra_args):
131
    """Create a Xen 3.0 config file.
132

133
    """
134
    config = StringIO()
135
    config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
136
    config.write("kernel = '%s'\n" % constants.XEN_KERNEL)
137
    if os.path.exists(constants.XEN_INITRD):
138
      config.write("ramdisk = '%s'\n" % constants.XEN_INITRD)
139
    config.write("memory = %d\n" % instance.memory)
140
    config.write("vcpus = %d\n" % instance.vcpus)
141
    config.write("name = '%s'\n" % instance.name)
142

    
143
    vif_data = []
144
    for nic in instance.nics:
145
      nic_str = "mac=%s, bridge=%s" % (nic.mac, nic.bridge)
146
      ip = getattr(nic, "ip", None)
147
      if ip is not None:
148
        nic_str += ", ip=%s" % ip
149
      vif_data.append("'%s'" % nic_str)
150

    
151
    config.write("vif = [%s]\n" % ",".join(vif_data))
152

    
153
    disk_data = ["'phy:%s,%s,w'" % (rldev.dev_path, cfdev.iv_name)
154
                 for cfdev, rldev in block_devices]
155
    config.write("disk = [%s]\n" % ",".join(disk_data))
156

    
157
    config.write("root = '/dev/sda ro'\n")
158
    config.write("on_poweroff = 'destroy'\n")
159
    config.write("on_reboot = 'restart'\n")
160
    config.write("on_crash = 'restart'\n")
161
    if extra_args:
162
      config.write("extra = '%s'\n" % extra_args)
163
    # just in case it exists
164
    utils.RemoveFile("/etc/xen/auto/%s" % instance.name)
165
    f = open("/etc/xen/%s" % instance.name, "w")
166
    f.write(config.getvalue())
167
    f.close()
168
    return True
169

    
170
  @staticmethod
171
  def _RemoveConfigFile(instance):
172
    """Remove the xen configuration file.
173

174
    """
175
    utils.RemoveFile("/etc/xen/%s" % instance.name)
176

    
177
  @staticmethod
178
  def _GetXMList(include_node):
179
    """Return the list of running instances.
180

181
    If the `include_node` argument is True, then we return information
182
    for dom0 also, otherwise we filter that from the return value.
183

184
    The return value is a list of (name, id, memory, vcpus, state, time spent)
185

186
    """
187
    for dummy in range(5):
188
      result = utils.RunCmd(["xm", "list"])
189
      if not result.failed:
190
        break
191
      logger.Error("xm list failed (%s): %s" % (result.fail_reason,
192
                                                result.output))
193
      time.sleep(1)
194

    
195
    if result.failed:
196
      raise HypervisorError("xm list failed, retries exceeded (%s): %s" %
197
                            (result.fail_reason, result.stderr))
198

    
199
    # skip over the heading and the domain 0 line (optional)
200
    if include_node:
201
      to_skip = 1
202
    else:
203
      to_skip = 2
204
    lines = result.stdout.splitlines()[to_skip:]
205
    result = []
206
    for line in lines:
207
      # The format of lines is:
208
      # Name      ID Mem(MiB) VCPUs State  Time(s)
209
      # Domain-0   0  3418     4 r-----    266.2
210
      data = line.split()
211
      if len(data) != 6:
212
        raise HypervisorError("Can't parse output of xm list, line: %s" % line)
213
      try:
214
        data[1] = int(data[1])
215
        data[2] = int(data[2])
216
        data[3] = int(data[3])
217
        data[5] = float(data[5])
218
      except ValueError, err:
219
        raise HypervisorError("Can't parse output of xm list,"
220
                              " line: %s, error: %s" % (line, err))
221
      result.append(data)
222
    return result
223

    
224
  def ListInstances(self):
225
    """Get the list of running instances.
226

227
    """
228
    xm_list = self._GetXMList(False)
229
    names = [info[0] for info in xm_list]
230
    return names
231

    
232
  def GetInstanceInfo(self, instance_name):
233
    """Get instance properties.
234

235
    Args:
236
      instance_name: the instance name
237

238
    Returns:
239
      (name, id, memory, vcpus, stat, times)
240
    """
241
    xm_list = self._GetXMList(instance_name=="Domain-0")
242
    result = None
243
    for data in xm_list:
244
      if data[0] == instance_name:
245
        result = data
246
        break
247
    return result
248

    
249
  def GetAllInstancesInfo(self):
250
    """Get properties of all instances.
251

252
    Returns:
253
      [(name, id, memory, vcpus, stat, times),...]
254
    """
255
    xm_list = self._GetXMList(False)
256
    return xm_list
257

    
258
  def StartInstance(self, instance, block_devices, extra_args):
259
    """Start an instance."""
260
    self._WriteConfigFile(instance, block_devices, extra_args)
261
    result = utils.RunCmd(["xm", "create", instance.name])
262

    
263
    if result.failed:
264
      raise HypervisorError("Failed to start instance %s: %s" %
265
                            (instance.name, result.fail_reason))
266

    
267
  def StopInstance(self, instance, force=False):
268
    """Stop an instance."""
269
    self._RemoveConfigFile(instance)
270
    if force:
271
      command = ["xm", "destroy", instance.name]
272
    else:
273
      command = ["xm", "shutdown", instance.name]
274
    result = utils.RunCmd(command)
275

    
276
    if result.failed:
277
      raise HypervisorError("Failed to stop instance %s: %s" %
278
                            (instance.name, result.fail_reason))
279

    
280
  def GetNodeInfo(self):
281
    """Return information about the node.
282

283
    The return value is a dict, which has to have the following items:
284
      (all values in MiB)
285
      - memory_total: the total memory size on the node
286
      - memory_free: the available memory on the node for instances
287
      - memory_dom0: the memory used by the node itself, if available
288

289
    """
290
    # note: in xen 3, memory has changed to total_memory
291
    result = utils.RunCmd(["xm", "info"])
292
    if result.failed:
293
      logger.Error("Can't run 'xm info': %s" % result.fail_reason)
294
      return None
295

    
296
    xmoutput = result.stdout.splitlines()
297
    result = {}
298
    for line in xmoutput:
299
      splitfields = line.split(":", 1)
300

    
301
      if len(splitfields) > 1:
302
        key = splitfields[0].strip()
303
        val = splitfields[1].strip()
304
        if key == 'memory' or key == 'total_memory':
305
          result['memory_total'] = int(val)
306
        elif key == 'free_memory':
307
          result['memory_free'] = int(val)
308
    dom0_info = self.GetInstanceInfo("Domain-0")
309
    if dom0_info is not None:
310
      result['memory_dom0'] = dom0_info[2]
311

    
312
    return result
313

    
314
  @staticmethod
315
  def GetShellCommandForConsole(instance_name):
316
    """Return a command for connecting to the console of an instance.
317

318
    """
319
    return "xm console %s" % instance_name
320

    
321

    
322
  def Verify(self):
323
    """Verify the hypervisor.
324

325
    For Xen, this verifies that the xend process is running.
326

327
    """
328
    if not utils.CheckDaemonAlive('/var/run/xend.pid', 'xend'):
329
      return "xend daemon is not running"
330

    
331

    
332
class FakeHypervisor(BaseHypervisor):
333
  """Fake hypervisor interface.
334

335
  This can be used for testing the ganeti code without having to have
336
  a real virtualisation software installed.
337

338
  """
339
  _ROOT_DIR = "/var/run/ganeti-fake-hypervisor"
340

    
341
  def __init__(self):
342
    BaseHypervisor.__init__(self)
343
    if not os.path.exists(self._ROOT_DIR):
344
      os.mkdir(self._ROOT_DIR)
345

    
346
  def ListInstances(self):
347
    """Get the list of running instances.
348

349
    """
350
    return os.listdir(self._ROOT_DIR)
351

    
352
  def GetInstanceInfo(self, instance_name):
353
    """Get instance properties.
354

355
    Args:
356
      instance_name: the instance name
357

358
    Returns:
359
      (name, id, memory, vcpus, stat, times)
360
    """
361
    file_name = "%s/%s" % (self._ROOT_DIR, instance_name)
362
    if not os.path.exists(file_name):
363
      return None
364
    try:
365
      fh = file(file_name, "r")
366
      try:
367
        inst_id = fh.readline().strip()
368
        memory = fh.readline().strip()
369
        vcpus = fh.readline().strip()
370
        stat = "---b-"
371
        times = "0"
372
        return (instance_name, inst_id, memory, vcpus, stat, times)
373
      finally:
374
        fh.close()
375
    except IOError, err:
376
      raise HypervisorError("Failed to list instance %s: %s" %
377
                            (instance_name, err))
378

    
379
  def GetAllInstancesInfo(self):
380
    """Get properties of all instances.
381

382
    Returns:
383
      [(name, id, memory, vcpus, stat, times),...]
384
    """
385
    data = []
386
    for file_name in os.listdir(self._ROOT_DIR):
387
      try:
388
        fh = file(self._ROOT_DIR+"/"+file_name, "r")
389
        inst_id = "-1"
390
        memory = "0"
391
        stat = "-----"
392
        times = "-1"
393
        try:
394
          inst_id = fh.readline().strip()
395
          memory = fh.readline().strip()
396
          vcpus = fh.readline().strip()
397
          stat = "---b-"
398
          times = "0"
399
        finally:
400
          fh.close()
401
        data.append((file_name, inst_id, memory, vcpus, stat, times))
402
      except IOError, err:
403
        raise HypervisorError("Failed to list instances: %s" % err)
404
    return data
405

    
406
  def StartInstance(self, instance, force, extra_args):
407
    """Start an instance.
408

409
    For the fake hypervisor, it just creates a file in the base dir,
410
    creating an exception if it already exists. We don't actually
411
    handle race conditions properly, since these are *FAKE* instances.
412

413
    """
414
    file_name = self._ROOT_DIR + "/%s" % instance.name
415
    if os.path.exists(file_name):
416
      raise HypervisorError("Failed to start instance %s: %s" %
417
                            (instance.name, "already running"))
418
    try:
419
      fh = file(file_name, "w")
420
      try:
421
        fh.write("0\n%d\n%d\n" % (instance.memory, instance.vcpus))
422
      finally:
423
        fh.close()
424
    except IOError, err:
425
      raise HypervisorError("Failed to start instance %s: %s" %
426
                            (instance.name, err))
427

    
428
  def StopInstance(self, instance, force=False):
429
    """Stop an instance.
430

431
    For the fake hypervisor, this just removes the file in the base
432
    dir, if it exist, otherwise we raise an exception.
433

434
    """
435
    file_name = self._ROOT_DIR + "/%s" % instance.name
436
    if not os.path.exists(file_name):
437
      raise HypervisorError("Failed to stop instance %s: %s" %
438
                            (instance.name, "not running"))
439
    utils.RemoveFile(file_name)
440

    
441
  def GetNodeInfo(self):
442
    """Return information about the node.
443

444
    The return value is a dict, which has to have the following items:
445
      (all values in MiB)
446
      - memory_total: the total memory size on the node
447
      - memory_free: the available memory on the node for instances
448
      - memory_dom0: the memory used by the node itself, if available
449

450
    """
451
    # global ram usage from the xm info command
452
    # memory                 : 3583
453
    # free_memory            : 747
454
    # note: in xen 3, memory has changed to total_memory
455
    try:
456
      fh = file("/proc/meminfo")
457
      try:
458
        data = fh.readlines()
459
      finally:
460
        fh.close()
461
    except IOError, err:
462
      raise HypervisorError("Failed to list node info: %s" % err)
463

    
464
    result = {}
465
    sum_free = 0
466
    for line in data:
467
      splitfields = line.split(":", 1)
468

    
469
      if len(splitfields) > 1:
470
        key = splitfields[0].strip()
471
        val = splitfields[1].strip()
472
        if key == 'MemTotal':
473
          result['memory_total'] = int(val.split()[0])/1024
474
        elif key in ('MemFree', 'Buffers', 'Cached'):
475
          sum_free += int(val.split()[0])/1024
476
        elif key == 'Active':
477
          result['memory_dom0'] = int(val.split()[0])/1024
478

    
479
    result['memory_free'] = sum_free
480
    return result
481

    
482
  @staticmethod
483
  def GetShellCommandForConsole(instance_name):
484
    """Return a command for connecting to the console of an instance.
485

486
    """
487
    return "echo Console not available for fake hypervisor"
488

    
489
  def Verify(self):
490
    """Verify the hypervisor.
491

492
    For the fake hypervisor, it just checks the existence of the base
493
    dir.
494

495
    """
496
    if not os.path.exists(self._ROOT_DIR):
497
      return "The required directory '%s' does not exist." % self._ROOT_DIR