Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor.py @ 2584d4a4

History | View | Annotate | Download (14.7 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

    
37
def GetHypervisor():
38
  """Return a Hypervisor instance.
39

40
  This function parses the cluster hypervisor configuration file and
41
  instantiates a class based on the value of this file.
42

43
  """
44
  ht_kind = ssconf.SimpleStore().GetHypervisorType()
45
  if ht_kind == constants.HT_XEN30:
46
    cls = XenHypervisor
47
  elif ht_kind == constants.HT_FAKE:
48
    cls = FakeHypervisor
49
  else:
50
    raise HypervisorError("Unknown hypervisor type '%s'" % ht_kind)
51
  return cls()
52

    
53

    
54
class BaseHypervisor(object):
55
  """Abstract virtualisation technology interface
56

57
  The goal is that all aspects of the virtualisation technology must
58
  be abstracted away from the rest of code.
59

60
  """
61
  def __init__(self):
62
    pass
63

    
64
  def StartInstance(self, instance, block_devices, extra_args):
65
    """Start an instance."""
66
    raise NotImplementedError
67

    
68
  def StopInstance(self, instance, force=False):
69
    """Stop an instance."""
70
    raise NotImplementedError
71

    
72
  def RebootInstance(self, instance):
73
    """Reboot 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 (%s)" %
265
                            (instance.name, result.fail_reason, result.output))
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 RebootInstance(self, instance):
281
    """Reboot an instance."""
282
    result = utils.RunCmd(["xm", "reboot", instance.name])
283

    
284
    if result.failed:
285
      raise HypervisorError("Failed to reboot instance %s: %s" %
286
                            (instance.name, result.fail_reason))
287

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

291
    The return value is a dict, which has to have the following items:
292
      (all values in MiB)
293
      - memory_total: the total memory size on the node
294
      - memory_free: the available memory on the node for instances
295
      - memory_dom0: the memory used by the node itself, if available
296

297
    """
298
    # note: in xen 3, memory has changed to total_memory
299
    result = utils.RunCmd(["xm", "info"])
300
    if result.failed:
301
      logger.Error("Can't run 'xm info': %s" % result.fail_reason)
302
      return None
303

    
304
    xmoutput = result.stdout.splitlines()
305
    result = {}
306
    for line in xmoutput:
307
      splitfields = line.split(":", 1)
308

    
309
      if len(splitfields) > 1:
310
        key = splitfields[0].strip()
311
        val = splitfields[1].strip()
312
        if key == 'memory' or key == 'total_memory':
313
          result['memory_total'] = int(val)
314
        elif key == 'free_memory':
315
          result['memory_free'] = int(val)
316
    dom0_info = self.GetInstanceInfo("Domain-0")
317
    if dom0_info is not None:
318
      result['memory_dom0'] = dom0_info[2]
319

    
320
    return result
321

    
322
  @staticmethod
323
  def GetShellCommandForConsole(instance_name):
324
    """Return a command for connecting to the console of an instance.
325

326
    """
327
    return "xm console %s" % instance_name
328

    
329

    
330
  def Verify(self):
331
    """Verify the hypervisor.
332

333
    For Xen, this verifies that the xend process is running.
334

335
    """
336
    if not utils.CheckDaemonAlive('/var/run/xend.pid', 'xend'):
337
      return "xend daemon is not running"
338

    
339

    
340
class FakeHypervisor(BaseHypervisor):
341
  """Fake hypervisor interface.
342

343
  This can be used for testing the ganeti code without having to have
344
  a real virtualisation software installed.
345

346
  """
347
  _ROOT_DIR = "/var/run/ganeti-fake-hypervisor"
348

    
349
  def __init__(self):
350
    BaseHypervisor.__init__(self)
351
    if not os.path.exists(self._ROOT_DIR):
352
      os.mkdir(self._ROOT_DIR)
353

    
354
  def ListInstances(self):
355
    """Get the list of running instances.
356

357
    """
358
    return os.listdir(self._ROOT_DIR)
359

    
360
  def GetInstanceInfo(self, instance_name):
361
    """Get instance properties.
362

363
    Args:
364
      instance_name: the instance name
365

366
    Returns:
367
      (name, id, memory, vcpus, stat, times)
368
    """
369
    file_name = "%s/%s" % (self._ROOT_DIR, instance_name)
370
    if not os.path.exists(file_name):
371
      return None
372
    try:
373
      fh = file(file_name, "r")
374
      try:
375
        inst_id = fh.readline().strip()
376
        memory = fh.readline().strip()
377
        vcpus = fh.readline().strip()
378
        stat = "---b-"
379
        times = "0"
380
        return (instance_name, inst_id, memory, vcpus, stat, times)
381
      finally:
382
        fh.close()
383
    except IOError, err:
384
      raise HypervisorError("Failed to list instance %s: %s" %
385
                            (instance_name, err))
386

    
387
  def GetAllInstancesInfo(self):
388
    """Get properties of all instances.
389

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

    
414
  def StartInstance(self, instance, force, extra_args):
415
    """Start an instance.
416

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

421
    """
422
    file_name = self._ROOT_DIR + "/%s" % instance.name
423
    if os.path.exists(file_name):
424
      raise HypervisorError("Failed to start instance %s: %s" %
425
                            (instance.name, "already running"))
426
    try:
427
      fh = file(file_name, "w")
428
      try:
429
        fh.write("0\n%d\n%d\n" % (instance.memory, instance.vcpus))
430
      finally:
431
        fh.close()
432
    except IOError, err:
433
      raise HypervisorError("Failed to start instance %s: %s" %
434
                            (instance.name, err))
435

    
436
  def StopInstance(self, instance, force=False):
437
    """Stop an instance.
438

439
    For the fake hypervisor, this just removes the file in the base
440
    dir, if it exist, otherwise we raise an exception.
441

442
    """
443
    file_name = self._ROOT_DIR + "/%s" % instance.name
444
    if not os.path.exists(file_name):
445
      raise HypervisorError("Failed to stop instance %s: %s" %
446
                            (instance.name, "not running"))
447
    utils.RemoveFile(file_name)
448

    
449
  def RebootInstance(self, instance):
450
    """Reboot an instance.
451

452
    For the fake hypervisor, this does nothing.
453

454
    """
455
    return
456

    
457
  def GetNodeInfo(self):
458
    """Return information about the node.
459

460
    The return value is a dict, which has to have the following items:
461
      (all values in MiB)
462
      - memory_total: the total memory size on the node
463
      - memory_free: the available memory on the node for instances
464
      - memory_dom0: the memory used by the node itself, if available
465

466
    """
467
    # global ram usage from the xm info command
468
    # memory                 : 3583
469
    # free_memory            : 747
470
    # note: in xen 3, memory has changed to total_memory
471
    try:
472
      fh = file("/proc/meminfo")
473
      try:
474
        data = fh.readlines()
475
      finally:
476
        fh.close()
477
    except IOError, err:
478
      raise HypervisorError("Failed to list node info: %s" % err)
479

    
480
    result = {}
481
    sum_free = 0
482
    for line in data:
483
      splitfields = line.split(":", 1)
484

    
485
      if len(splitfields) > 1:
486
        key = splitfields[0].strip()
487
        val = splitfields[1].strip()
488
        if key == 'MemTotal':
489
          result['memory_total'] = int(val.split()[0])/1024
490
        elif key in ('MemFree', 'Buffers', 'Cached'):
491
          sum_free += int(val.split()[0])/1024
492
        elif key == 'Active':
493
          result['memory_dom0'] = int(val.split()[0])/1024
494

    
495
    result['memory_free'] = sum_free
496
    return result
497

    
498
  @staticmethod
499
  def GetShellCommandForConsole(instance_name):
500
    """Return a command for connecting to the console of an instance.
501

502
    """
503
    return "echo Console not available for fake hypervisor"
504

    
505
  def Verify(self):
506
    """Verify the hypervisor.
507

508
    For the fake hypervisor, it just checks the existence of the base
509
    dir.
510

511
    """
512
    if not os.path.exists(self._ROOT_DIR):
513
      return "The required directory '%s' does not exist." % self._ROOT_DIR