Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor.py @ 7e1394dc

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
_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 RebootInstance(self, instance):
77
    """Reboot an instance."""
78
    raise NotImplementedError
79

    
80
  def ListInstances(self):
81
    """Get the list of running instances."""
82
    raise NotImplementedError
83

    
84
  def GetInstanceInfo(self, instance_name):
85
    """Get instance properties.
86

87
    Args:
88
      instance_name: the instance name
89

90
    Returns:
91
      (name, id, memory, vcpus, state, times)
92

93
    """
94
    raise NotImplementedError
95

    
96
  def GetAllInstancesInfo(self):
97
    """Get properties of all instances.
98

99
    Returns:
100
      [(name, id, memory, vcpus, stat, times),...]
101
    """
102
    raise NotImplementedError
103

    
104
  def GetNodeInfo(self):
105
    """Return information about the node.
106

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

113
    """
114
    raise NotImplementedError
115

    
116
  @staticmethod
117
  def GetShellCommandForConsole(instance_name):
118
    """Return a command for connecting to the console of an instance.
119

120
    """
121
    raise NotImplementedError
122

    
123
  def Verify(self):
124
    """Verify the hypervisor.
125

126
    """
127
    raise NotImplementedError
128

    
129

    
130
class XenHypervisor(BaseHypervisor):
131
  """Xen hypervisor interface"""
132

    
133
  @staticmethod
134
  def _WriteConfigFile(instance, block_devices, extra_args):
135
    """Create a Xen 3.0 config file.
136

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

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

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

    
157
    disk_data = ["'phy:%s,%s,w'" % (rldev.dev_path, cfdev.iv_name)
158
                 for cfdev, rldev in block_devices]
159
    config.write("disk = [%s]\n" % ",".join(disk_data))
160

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

    
174
  @staticmethod
175
  def _RemoveConfigFile(instance):
176
    """Remove the xen configuration file.
177

178
    """
179
    utils.RemoveFile("/etc/xen/%s" % instance.name)
180

    
181
  @staticmethod
182
  def _GetXMList(include_node):
183
    """Return the list of running instances.
184

185
    If the `include_node` argument is True, then we return information
186
    for dom0 also, otherwise we filter that from the return value.
187

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

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

    
199
    if result.failed:
200
      raise HypervisorError("xm list failed, retries exceeded (%s): %s" %
201
                            (result.fail_reason, result.stderr))
202

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

    
228
  def ListInstances(self):
229
    """Get the list of running instances.
230

231
    """
232
    xm_list = self._GetXMList(False)
233
    names = [info[0] for info in xm_list]
234
    return names
235

    
236
  def GetInstanceInfo(self, instance_name):
237
    """Get instance properties.
238

239
    Args:
240
      instance_name: the instance name
241

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

    
253
  def GetAllInstancesInfo(self):
254
    """Get properties of all instances.
255

256
    Returns:
257
      [(name, id, memory, vcpus, stat, times),...]
258
    """
259
    xm_list = self._GetXMList(False)
260
    return xm_list
261

    
262
  def StartInstance(self, instance, block_devices, extra_args):
263
    """Start an instance."""
264
    self._WriteConfigFile(instance, block_devices, extra_args)
265
    result = utils.RunCmd(["xm", "create", instance.name])
266

    
267
    if result.failed:
268
      raise HypervisorError("Failed to start instance %s: %s" %
269
                            (instance.name, result.fail_reason))
270

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

    
280
    if result.failed:
281
      raise HypervisorError("Failed to stop instance %s: %s" %
282
                            (instance.name, result.fail_reason))
283

    
284
  def RebootInstance(self, instance):
285
    """Reboot an instance."""
286
    result = utils.RunCmd(["xm", "reboot", instance.name])
287

    
288
    if result.failed:
289
      raise HypervisorError("Failed to reboot instance %s: %s" %
290
                            (instance.name, result.fail_reason))
291

    
292
  def GetNodeInfo(self):
293
    """Return information about the node.
294

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

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

    
308
    xmoutput = result.stdout.splitlines()
309
    result = {}
310
    for line in xmoutput:
311
      splitfields = line.split(":", 1)
312

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

    
324
    return result
325

    
326
  @staticmethod
327
  def GetShellCommandForConsole(instance_name):
328
    """Return a command for connecting to the console of an instance.
329

330
    """
331
    return "xm console %s" % instance_name
332

    
333

    
334
  def Verify(self):
335
    """Verify the hypervisor.
336

337
    For Xen, this verifies that the xend process is running.
338

339
    """
340
    if not utils.CheckDaemonAlive('/var/run/xend.pid', 'xend'):
341
      return "xend daemon is not running"
342

    
343

    
344
class FakeHypervisor(BaseHypervisor):
345
  """Fake hypervisor interface.
346

347
  This can be used for testing the ganeti code without having to have
348
  a real virtualisation software installed.
349

350
  """
351
  _ROOT_DIR = "/var/run/ganeti-fake-hypervisor"
352

    
353
  def __init__(self):
354
    BaseHypervisor.__init__(self)
355
    if not os.path.exists(self._ROOT_DIR):
356
      os.mkdir(self._ROOT_DIR)
357

    
358
  def ListInstances(self):
359
    """Get the list of running instances.
360

361
    """
362
    return os.listdir(self._ROOT_DIR)
363

    
364
  def GetInstanceInfo(self, instance_name):
365
    """Get instance properties.
366

367
    Args:
368
      instance_name: the instance name
369

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

    
391
  def GetAllInstancesInfo(self):
392
    """Get properties of all instances.
393

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

    
418
  def StartInstance(self, instance, force, extra_args):
419
    """Start an instance.
420

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

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

    
440
  def StopInstance(self, instance, force=False):
441
    """Stop an instance.
442

443
    For the fake hypervisor, this just removes the file in the base
444
    dir, if it exist, otherwise we raise an exception.
445

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

    
453
  def RebootInstance(self, instance):
454
    """Reboot an instance.
455

456
    For the fake hypervisor, this does nothing.
457

458
    """
459
    return
460

    
461
  def GetNodeInfo(self):
462
    """Return information about the node.
463

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

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

    
484
    result = {}
485
    sum_free = 0
486
    for line in data:
487
      splitfields = line.split(":", 1)
488

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

    
499
    result['memory_free'] = sum_free
500
    return result
501

    
502
  @staticmethod
503
  def GetShellCommandForConsole(instance_name):
504
    """Return a command for connecting to the console of an instance.
505

506
    """
507
    return "echo Console not available for fake hypervisor"
508

    
509
  def Verify(self):
510
    """Verify the hypervisor.
511

512
    For the fake hypervisor, it just checks the existence of the base
513
    dir.
514

515
    """
516
    if not os.path.exists(self._ROOT_DIR):
517
      return "The required directory '%s' does not exist." % self._ROOT_DIR