Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor.py @ 01121d61

History | View | Annotate | Download (15.4 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_XEN_PVM30:
46
    cls = XenPvmHypervisor
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):
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 generic hypervisor interface
128

129
  This is the Xen base class used for both Xen PVM and HVM. It contains
130
  all the functionality that is identical for both.
131

132
  """
133

    
134
  @staticmethod
135
  def _WriteConfigFile(instance, block_devices, extra_args):
136
    """A Xen instance config file.
137

138
    """
139
    raise NotImplementedError
140

    
141
  @staticmethod
142
  def _RemoveConfigFile(instance):
143
    """Remove the xen configuration file.
144

145
    """
146
    utils.RemoveFile("/etc/xen/%s" % instance.name)
147

    
148
  @staticmethod
149
  def _GetXMList(include_node):
150
    """Return the list of running instances.
151

152
    If the `include_node` argument is True, then we return information
153
    for dom0 also, otherwise we filter that from the return value.
154

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

157
    """
158
    for dummy in range(5):
159
      result = utils.RunCmd(["xm", "list"])
160
      if not result.failed:
161
        break
162
      logger.Error("xm list failed (%s): %s" % (result.fail_reason,
163
                                                result.output))
164
      time.sleep(1)
165

    
166
    if result.failed:
167
      raise HypervisorError("xm list failed, retries exceeded (%s): %s" %
168
                            (result.fail_reason, result.stderr))
169

    
170
    # skip over the heading and the domain 0 line (optional)
171
    if include_node:
172
      to_skip = 1
173
    else:
174
      to_skip = 2
175
    lines = result.stdout.splitlines()[to_skip:]
176
    result = []
177
    for line in lines:
178
      # The format of lines is:
179
      # Name      ID Mem(MiB) VCPUs State  Time(s)
180
      # Domain-0   0  3418     4 r-----    266.2
181
      data = line.split()
182
      if len(data) != 6:
183
        raise HypervisorError("Can't parse output of xm list, line: %s" % line)
184
      try:
185
        data[1] = int(data[1])
186
        data[2] = int(data[2])
187
        data[3] = int(data[3])
188
        data[5] = float(data[5])
189
      except ValueError, err:
190
        raise HypervisorError("Can't parse output of xm list,"
191
                              " line: %s, error: %s" % (line, err))
192
      result.append(data)
193
    return result
194

    
195
  def ListInstances(self):
196
    """Get the list of running instances.
197

198
    """
199
    xm_list = self._GetXMList(False)
200
    names = [info[0] for info in xm_list]
201
    return names
202

    
203
  def GetInstanceInfo(self, instance_name):
204
    """Get instance properties.
205

206
    Args:
207
      instance_name: the instance name
208

209
    Returns:
210
      (name, id, memory, vcpus, stat, times)
211
    """
212
    xm_list = self._GetXMList(instance_name=="Domain-0")
213
    result = None
214
    for data in xm_list:
215
      if data[0] == instance_name:
216
        result = data
217
        break
218
    return result
219

    
220
  def GetAllInstancesInfo(self):
221
    """Get properties of all instances.
222

223
    Returns:
224
      [(name, id, memory, vcpus, stat, times),...]
225
    """
226
    xm_list = self._GetXMList(False)
227
    return xm_list
228

    
229
  def StartInstance(self, instance, block_devices, extra_args):
230
    """Start an instance."""
231
    self._WriteConfigFile(instance, block_devices, extra_args)
232
    result = utils.RunCmd(["xm", "create", instance.name])
233

    
234
    if result.failed:
235
      raise HypervisorError("Failed to start instance %s: %s (%s)" %
236
                            (instance.name, result.fail_reason, result.output))
237

    
238
  def StopInstance(self, instance, force=False):
239
    """Stop an instance."""
240
    self._RemoveConfigFile(instance)
241
    if force:
242
      command = ["xm", "destroy", instance.name]
243
    else:
244
      command = ["xm", "shutdown", instance.name]
245
    result = utils.RunCmd(command)
246

    
247
    if result.failed:
248
      raise HypervisorError("Failed to stop instance %s: %s" %
249
                            (instance.name, result.fail_reason))
250

    
251
  def RebootInstance(self, instance):
252
    """Reboot an instance."""
253
    result = utils.RunCmd(["xm", "reboot", instance.name])
254

    
255
    if result.failed:
256
      raise HypervisorError("Failed to reboot instance %s: %s" %
257
                            (instance.name, result.fail_reason))
258

    
259
  def GetNodeInfo(self):
260
    """Return information about the node.
261

262
    The return value is a dict, which has to have the following items:
263
      (all values in MiB)
264
      - memory_total: the total memory size on the node
265
      - memory_free: the available memory on the node for instances
266
      - memory_dom0: the memory used by the node itself, if available
267

268
    """
269
    # note: in xen 3, memory has changed to total_memory
270
    result = utils.RunCmd(["xm", "info"])
271
    if result.failed:
272
      logger.Error("Can't run 'xm info': %s" % result.fail_reason)
273
      return None
274

    
275
    xmoutput = result.stdout.splitlines()
276
    result = {}
277
    for line in xmoutput:
278
      splitfields = line.split(":", 1)
279

    
280
      if len(splitfields) > 1:
281
        key = splitfields[0].strip()
282
        val = splitfields[1].strip()
283
        if key == 'memory' or key == 'total_memory':
284
          result['memory_total'] = int(val)
285
        elif key == 'free_memory':
286
          result['memory_free'] = int(val)
287
    dom0_info = self.GetInstanceInfo("Domain-0")
288
    if dom0_info is not None:
289
      result['memory_dom0'] = dom0_info[2]
290

    
291
    return result
292

    
293
  @staticmethod
294
  def GetShellCommandForConsole(instance):
295
    """Return a command for connecting to the console of an instance.
296

297
    """
298
    raise NotImplementedError
299

    
300

    
301
  def Verify(self):
302
    """Verify the hypervisor.
303

304
    For Xen, this verifies that the xend process is running.
305

306
    """
307
    if not utils.CheckDaemonAlive('/var/run/xend.pid', 'xend'):
308
      return "xend daemon is not running"
309

    
310

    
311
class XenPvmHypervisor(XenHypervisor):
312
  """Xen PVM hypervisor interface"""
313

    
314
  @staticmethod
315
  def _WriteConfigFile(instance, block_devices, extra_args):
316
    """Create a Xen instance config file.
317

318
    """
319
    config = StringIO()
320
    config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
321
    config.write("kernel = '%s'\n" % constants.XEN_KERNEL)
322
    if os.path.exists(constants.XEN_INITRD):
323
      config.write("ramdisk = '%s'\n" % constants.XEN_INITRD)
324
    config.write("memory = %d\n" % instance.memory)
325
    config.write("vcpus = %d\n" % instance.vcpus)
326
    config.write("name = '%s'\n" % instance.name)
327

    
328
    vif_data = []
329
    for nic in instance.nics:
330
      nic_str = "mac=%s, bridge=%s" % (nic.mac, nic.bridge)
331
      ip = getattr(nic, "ip", None)
332
      if ip is not None:
333
        nic_str += ", ip=%s" % ip
334
      vif_data.append("'%s'" % nic_str)
335

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

    
338
    disk_data = ["'phy:%s,%s,w'" % (rldev.dev_path, cfdev.iv_name)
339
                 for cfdev, rldev in block_devices]
340
    config.write("disk = [%s]\n" % ",".join(disk_data))
341

    
342
    config.write("root = '/dev/sda ro'\n")
343
    config.write("on_poweroff = 'destroy'\n")
344
    config.write("on_reboot = 'restart'\n")
345
    config.write("on_crash = 'restart'\n")
346
    if extra_args:
347
      config.write("extra = '%s'\n" % extra_args)
348
    # just in case it exists
349
    utils.RemoveFile("/etc/xen/auto/%s" % instance.name)
350
    try:
351
      f = open("/etc/xen/%s" % instance.name, "w")
352
      try:
353
        f.write(config.getvalue())
354
      finally:
355
        f.close()
356
    except IOError, err:
357
      raise errors.OpExecError("Cannot write Xen instance confile"
358
                               " file /etc/xen/%s: %s" % (instance.name, err))
359
    return True
360

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

365
    """
366
    return "xm console %s" % instance.name
367

    
368

    
369
class FakeHypervisor(BaseHypervisor):
370
  """Fake hypervisor interface.
371

372
  This can be used for testing the ganeti code without having to have
373
  a real virtualisation software installed.
374

375
  """
376
  _ROOT_DIR = "/var/run/ganeti-fake-hypervisor"
377

    
378
  def __init__(self):
379
    BaseHypervisor.__init__(self)
380
    if not os.path.exists(self._ROOT_DIR):
381
      os.mkdir(self._ROOT_DIR)
382

    
383
  def ListInstances(self):
384
    """Get the list of running instances.
385

386
    """
387
    return os.listdir(self._ROOT_DIR)
388

    
389
  def GetInstanceInfo(self, instance_name):
390
    """Get instance properties.
391

392
    Args:
393
      instance_name: the instance name
394

395
    Returns:
396
      (name, id, memory, vcpus, stat, times)
397
    """
398
    file_name = "%s/%s" % (self._ROOT_DIR, instance_name)
399
    if not os.path.exists(file_name):
400
      return None
401
    try:
402
      fh = file(file_name, "r")
403
      try:
404
        inst_id = fh.readline().strip()
405
        memory = fh.readline().strip()
406
        vcpus = fh.readline().strip()
407
        stat = "---b-"
408
        times = "0"
409
        return (instance_name, inst_id, memory, vcpus, stat, times)
410
      finally:
411
        fh.close()
412
    except IOError, err:
413
      raise HypervisorError("Failed to list instance %s: %s" %
414
                            (instance_name, err))
415

    
416
  def GetAllInstancesInfo(self):
417
    """Get properties of all instances.
418

419
    Returns:
420
      [(name, id, memory, vcpus, stat, times),...]
421
    """
422
    data = []
423
    for file_name in os.listdir(self._ROOT_DIR):
424
      try:
425
        fh = file(self._ROOT_DIR+"/"+file_name, "r")
426
        inst_id = "-1"
427
        memory = "0"
428
        stat = "-----"
429
        times = "-1"
430
        try:
431
          inst_id = fh.readline().strip()
432
          memory = fh.readline().strip()
433
          vcpus = fh.readline().strip()
434
          stat = "---b-"
435
          times = "0"
436
        finally:
437
          fh.close()
438
        data.append((file_name, inst_id, memory, vcpus, stat, times))
439
      except IOError, err:
440
        raise HypervisorError("Failed to list instances: %s" % err)
441
    return data
442

    
443
  def StartInstance(self, instance, force, extra_args):
444
    """Start an instance.
445

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

450
    """
451
    file_name = self._ROOT_DIR + "/%s" % instance.name
452
    if os.path.exists(file_name):
453
      raise HypervisorError("Failed to start instance %s: %s" %
454
                            (instance.name, "already running"))
455
    try:
456
      fh = file(file_name, "w")
457
      try:
458
        fh.write("0\n%d\n%d\n" % (instance.memory, instance.vcpus))
459
      finally:
460
        fh.close()
461
    except IOError, err:
462
      raise HypervisorError("Failed to start instance %s: %s" %
463
                            (instance.name, err))
464

    
465
  def StopInstance(self, instance, force=False):
466
    """Stop an instance.
467

468
    For the fake hypervisor, this just removes the file in the base
469
    dir, if it exist, otherwise we raise an exception.
470

471
    """
472
    file_name = self._ROOT_DIR + "/%s" % instance.name
473
    if not os.path.exists(file_name):
474
      raise HypervisorError("Failed to stop instance %s: %s" %
475
                            (instance.name, "not running"))
476
    utils.RemoveFile(file_name)
477

    
478
  def RebootInstance(self, instance):
479
    """Reboot an instance.
480

481
    For the fake hypervisor, this does nothing.
482

483
    """
484
    return
485

    
486
  def GetNodeInfo(self):
487
    """Return information about the node.
488

489
    The return value is a dict, which has to have the following items:
490
      (all values in MiB)
491
      - memory_total: the total memory size on the node
492
      - memory_free: the available memory on the node for instances
493
      - memory_dom0: the memory used by the node itself, if available
494

495
    """
496
    # global ram usage from the xm info command
497
    # memory                 : 3583
498
    # free_memory            : 747
499
    # note: in xen 3, memory has changed to total_memory
500
    try:
501
      fh = file("/proc/meminfo")
502
      try:
503
        data = fh.readlines()
504
      finally:
505
        fh.close()
506
    except IOError, err:
507
      raise HypervisorError("Failed to list node info: %s" % err)
508

    
509
    result = {}
510
    sum_free = 0
511
    for line in data:
512
      splitfields = line.split(":", 1)
513

    
514
      if len(splitfields) > 1:
515
        key = splitfields[0].strip()
516
        val = splitfields[1].strip()
517
        if key == 'MemTotal':
518
          result['memory_total'] = int(val.split()[0])/1024
519
        elif key in ('MemFree', 'Buffers', 'Cached'):
520
          sum_free += int(val.split()[0])/1024
521
        elif key == 'Active':
522
          result['memory_dom0'] = int(val.split()[0])/1024
523

    
524
    result['memory_free'] = sum_free
525
    return result
526

    
527
  @staticmethod
528
  def GetShellCommandForConsole(instance):
529
    """Return a command for connecting to the console of an instance.
530

531
    """
532
    return "echo Console not available for fake hypervisor"
533

    
534
  def Verify(self):
535
    """Verify the hypervisor.
536

537
    For the fake hypervisor, it just checks the existence of the base
538
    dir.
539

540
    """
541
    if not os.path.exists(self._ROOT_DIR):
542
      return "The required directory '%s' does not exist." % self._ROOT_DIR