Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor.py @ 631eb662

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

    
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
    f = open("/etc/xen/%s" % instance.name, "w")
351
    f.write(config.getvalue())
352
    f.close()
353
    return True
354

    
355
  @staticmethod
356
  def GetShellCommandForConsole(instance):
357
    """Return a command for connecting to the console of an instance.
358

359
    """
360
    return "xm console %s" % instance.name
361

    
362

    
363
class FakeHypervisor(BaseHypervisor):
364
  """Fake hypervisor interface.
365

366
  This can be used for testing the ganeti code without having to have
367
  a real virtualisation software installed.
368

369
  """
370
  _ROOT_DIR = "/var/run/ganeti-fake-hypervisor"
371

    
372
  def __init__(self):
373
    BaseHypervisor.__init__(self)
374
    if not os.path.exists(self._ROOT_DIR):
375
      os.mkdir(self._ROOT_DIR)
376

    
377
  def ListInstances(self):
378
    """Get the list of running instances.
379

380
    """
381
    return os.listdir(self._ROOT_DIR)
382

    
383
  def GetInstanceInfo(self, instance_name):
384
    """Get instance properties.
385

386
    Args:
387
      instance_name: the instance name
388

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

    
410
  def GetAllInstancesInfo(self):
411
    """Get properties of all instances.
412

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

    
437
  def StartInstance(self, instance, force, extra_args):
438
    """Start an instance.
439

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

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

    
459
  def StopInstance(self, instance, force=False):
460
    """Stop an instance.
461

462
    For the fake hypervisor, this just removes the file in the base
463
    dir, if it exist, otherwise we raise an exception.
464

465
    """
466
    file_name = self._ROOT_DIR + "/%s" % instance.name
467
    if not os.path.exists(file_name):
468
      raise HypervisorError("Failed to stop instance %s: %s" %
469
                            (instance.name, "not running"))
470
    utils.RemoveFile(file_name)
471

    
472
  def RebootInstance(self, instance):
473
    """Reboot an instance.
474

475
    For the fake hypervisor, this does nothing.
476

477
    """
478
    return
479

    
480
  def GetNodeInfo(self):
481
    """Return information about the node.
482

483
    The return value is a dict, which has to have the following items:
484
      (all values in MiB)
485
      - memory_total: the total memory size on the node
486
      - memory_free: the available memory on the node for instances
487
      - memory_dom0: the memory used by the node itself, if available
488

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

    
503
    result = {}
504
    sum_free = 0
505
    for line in data:
506
      splitfields = line.split(":", 1)
507

    
508
      if len(splitfields) > 1:
509
        key = splitfields[0].strip()
510
        val = splitfields[1].strip()
511
        if key == 'MemTotal':
512
          result['memory_total'] = int(val.split()[0])/1024
513
        elif key in ('MemFree', 'Buffers', 'Cached'):
514
          sum_free += int(val.split()[0])/1024
515
        elif key == 'Active':
516
          result['memory_dom0'] = int(val.split()[0])/1024
517

    
518
    result['memory_free'] = sum_free
519
    return result
520

    
521
  @staticmethod
522
  def GetShellCommandForConsole(instance):
523
    """Return a command for connecting to the console of an instance.
524

525
    """
526
    return "echo Console not available for fake hypervisor"
527

    
528
  def Verify(self):
529
    """Verify the hypervisor.
530

531
    For the fake hypervisor, it just checks the existence of the base
532
    dir.
533

534
    """
535
    if not os.path.exists(self._ROOT_DIR):
536
      return "The required directory '%s' does not exist." % self._ROOT_DIR