Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor.py @ 2a6469d5

History | View | Annotate | Download (18.9 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 import errors
35
from ganeti.errors import HypervisorError
36

    
37

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

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

44
  """
45
  ht_kind = ssconf.SimpleStore().GetHypervisorType()
46
  if ht_kind == constants.HT_XEN_PVM30:
47
    cls = XenPvmHypervisor
48
  elif ht_kind == constants.HT_FAKE:
49
    cls = FakeHypervisor
50
  elif ht_kind == constants.HT_XEN_HVM31:
51
    cls = XenHvmHypervisor
52
  else:
53
    raise HypervisorError("Unknown hypervisor type '%s'" % ht_kind)
54
  return cls()
55

    
56

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

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

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

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

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

    
75
  def RebootInstance(self, instance):
76
    """Reboot an instance."""
77
    raise NotImplementedError
78

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

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

86
    Args:
87
      instance_name: the instance name
88

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

92
    """
93
    raise NotImplementedError
94

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

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

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

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

112
    """
113
    raise NotImplementedError
114

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

119
    """
120
    raise NotImplementedError
121

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

125
    """
126
    raise NotImplementedError
127

    
128

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

132
  This is the Xen base class used for both Xen PVM and HVM. It contains
133
  all the functionality that is identical for both.
134

135
  """
136

    
137
  @staticmethod
138
  def _WriteConfigFile(instance, block_devices, extra_args):
139
    """A Xen instance config file.
140

141
    """
142
    raise NotImplementedError
143

    
144
  @staticmethod
145
  def _RemoveConfigFile(instance):
146
    """Remove the xen configuration file.
147

148
    """
149
    utils.RemoveFile("/etc/xen/%s" % instance.name)
150

    
151
  @staticmethod
152
  def _GetXMList(include_node):
153
    """Return the list of running instances.
154

155
    If the `include_node` argument is True, then we return information
156
    for dom0 also, otherwise we filter that from the return value.
157

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

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

    
169
    if result.failed:
170
      raise HypervisorError("xm list failed, retries exceeded (%s): %s" %
171
                            (result.fail_reason, result.stderr))
172

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

    
198
  def ListInstances(self):
199
    """Get the list of running instances.
200

201
    """
202
    xm_list = self._GetXMList(False)
203
    names = [info[0] for info in xm_list]
204
    return names
205

    
206
  def GetInstanceInfo(self, instance_name):
207
    """Get instance properties.
208

209
    Args:
210
      instance_name: the instance name
211

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

    
223
  def GetAllInstancesInfo(self):
224
    """Get properties of all instances.
225

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

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

    
237
    if result.failed:
238
      raise HypervisorError("Failed to start instance %s: %s (%s)" %
239
                            (instance.name, result.fail_reason, result.output))
240

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

    
250
    if result.failed:
251
      raise HypervisorError("Failed to stop instance %s: %s" %
252
                            (instance.name, result.fail_reason))
253

    
254
  def RebootInstance(self, instance):
255
    """Reboot an instance."""
256
    result = utils.RunCmd(["xm", "reboot", instance.name])
257

    
258
    if result.failed:
259
      raise HypervisorError("Failed to reboot instance %s: %s" %
260
                            (instance.name, result.fail_reason))
261

    
262
  def GetNodeInfo(self):
263
    """Return information about the node.
264

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

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

    
278
    xmoutput = result.stdout.splitlines()
279
    result = {}
280
    for line in xmoutput:
281
      splitfields = line.split(":", 1)
282

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

    
294
    return result
295

    
296
  @staticmethod
297
  def GetShellCommandForConsole(instance):
298
    """Return a command for connecting to the console of an instance.
299

300
    """
301
    raise NotImplementedError
302

    
303

    
304
  def Verify(self):
305
    """Verify the hypervisor.
306

307
    For Xen, this verifies that the xend process is running.
308

309
    """
310
    if not utils.CheckDaemonAlive('/var/run/xend.pid', 'xend'):
311
      return "xend daemon is not running"
312

    
313

    
314
class XenPvmHypervisor(XenHypervisor):
315
  """Xen PVM hypervisor interface"""
316

    
317
  @staticmethod
318
  def _WriteConfigFile(instance, block_devices, extra_args):
319
    """Create a Xen instance config file.
320

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

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

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

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

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

    
364
  @staticmethod
365
  def GetShellCommandForConsole(instance):
366
    """Return a command for connecting to the console of an instance.
367

368
    """
369
    return "xm console %s" % instance.name
370

    
371

    
372
class FakeHypervisor(BaseHypervisor):
373
  """Fake hypervisor interface.
374

375
  This can be used for testing the ganeti code without having to have
376
  a real virtualisation software installed.
377

378
  """
379
  _ROOT_DIR = "/var/run/ganeti-fake-hypervisor"
380

    
381
  def __init__(self):
382
    BaseHypervisor.__init__(self)
383
    if not os.path.exists(self._ROOT_DIR):
384
      os.mkdir(self._ROOT_DIR)
385

    
386
  def ListInstances(self):
387
    """Get the list of running instances.
388

389
    """
390
    return os.listdir(self._ROOT_DIR)
391

    
392
  def GetInstanceInfo(self, instance_name):
393
    """Get instance properties.
394

395
    Args:
396
      instance_name: the instance name
397

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

    
419
  def GetAllInstancesInfo(self):
420
    """Get properties of all instances.
421

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

    
446
  def StartInstance(self, instance, force, extra_args):
447
    """Start an instance.
448

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

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

    
468
  def StopInstance(self, instance, force=False):
469
    """Stop an instance.
470

471
    For the fake hypervisor, this just removes the file in the base
472
    dir, if it exist, otherwise we raise an exception.
473

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

    
481
  def RebootInstance(self, instance):
482
    """Reboot an instance.
483

484
    For the fake hypervisor, this does nothing.
485

486
    """
487
    return
488

    
489
  def GetNodeInfo(self):
490
    """Return information about the node.
491

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

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

    
512
    result = {}
513
    sum_free = 0
514
    for line in data:
515
      splitfields = line.split(":", 1)
516

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

    
527
    result['memory_free'] = sum_free
528
    return result
529

    
530
  @staticmethod
531
  def GetShellCommandForConsole(instance):
532
    """Return a command for connecting to the console of an instance.
533

534
    """
535
    return "echo Console not available for fake hypervisor"
536

    
537
  def Verify(self):
538
    """Verify the hypervisor.
539

540
    For the fake hypervisor, it just checks the existence of the base
541
    dir.
542

543
    """
544
    if not os.path.exists(self._ROOT_DIR):
545
      return "The required directory '%s' does not exist." % self._ROOT_DIR
546

    
547

    
548
class XenHvmHypervisor(XenHypervisor):
549
  """Xen HVM hypervisor interface"""
550

    
551
  @staticmethod
552
  def _WriteConfigFile(instance, block_devices, extra_args):
553
    """Create a Xen 3.1 HVM config file.
554

555
    """
556
    config = StringIO()
557
    config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
558
    config.write("kernel = '/usr/lib/xen/boot/hvmloader'\n")
559
    config.write("builder = 'hvm'\n")
560
    config.write("memory = %d\n" % instance.memory)
561
    config.write("vcpus = %d\n" % instance.vcpus)
562
    config.write("name = '%s'\n" % instance.name)
563
    config.write("pae = 1\n")
564
    config.write("acpi = 1\n")
565
    config.write("apic = 1\n")
566
    arch = os.uname()[4]
567
    if '64' in arch:
568
      config.write("device_model = '/usr/lib64/xen/bin/qemu-dm'\n")
569
    else:
570
      config.write("device_model = '/usr/lib/xen/bin/qemu-dm'\n")
571
    config.write("boot = 'dc'\n")
572
    config.write("sdl = 0\n")
573
    config.write("vnc = 1\n")
574
    config.write("vnclisten = '0.0.0.0'\n")
575

    
576
    if instance.network_port > constants.HT_HVM_VNC_BASE_PORT:
577
      display = instance.network_port - constants.HT_HVM_VNC_BASE_PORT
578
      config.write("vncdisplay = %s\n" % display)
579
      config.write("vncunused = 0\n")
580
    else:
581
      config.write("# vncdisplay = 1\n")
582
      config.write("vncunused = 1\n")
583

    
584
    try:
585
      password_file = open(constants.VNC_PASSWORD_FILE, "r")
586
      try:
587
        password = password_file.readline()
588
      finally:
589
        password_file.close()
590
    except IOError:
591
      raise errors.OpExecError("failed to open VNC password file %s " %
592
                               constants.VNC_PASSWORD_FILE)
593

    
594
    config.write("vncpasswd = '%s'\n" % password.rstrip())
595

    
596
    config.write("serial = 'pty'\n")
597
    config.write("localtime = 1\n")
598

    
599
    vif_data = []
600
    for nic in instance.nics:
601
      nic_str = "mac=%s, bridge=%s, type=ioemu" % (nic.mac, nic.bridge)
602
      ip = getattr(nic, "ip", None)
603
      if ip is not None:
604
        nic_str += ", ip=%s" % ip
605
      vif_data.append("'%s'" % nic_str)
606

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

    
609
    disk_data = ["'phy:%s,%s,w'" %
610
                 (rldev.dev_path, cfdev.iv_name.replace("sd", "ioemu:hd"))
611
                 for cfdev, rldev in block_devices]
612
    iso = "'file:/srv/ganeti/iso/hvm-install.iso,hdc:cdrom,r'"
613
    config.write("disk = [%s, %s]\n" % (",".join(disk_data), iso) )
614

    
615
    config.write("on_poweroff = 'destroy'\n")
616
    config.write("on_reboot = 'restart'\n")
617
    config.write("on_crash = 'restart'\n")
618
    if extra_args:
619
      config.write("extra = '%s'\n" % extra_args)
620
    # just in case it exists
621
    utils.RemoveFile("/etc/xen/auto/%s" % instance.name)
622
    try:
623
      f = open("/etc/xen/%s" % instance.name, "w")
624
      try:
625
        f.write(config.getvalue())
626
      finally:
627
        f.close()
628
    except IOError, err:
629
      raise errors.OpExecError("Cannot write Xen instance confile"
630
                               " file /etc/xen/%s: %s" % (instance.name, err))
631
    return True
632

    
633
  @staticmethod
634
  def GetShellCommandForConsole(instance):
635
    """Return a command for connecting to the console of an instance.
636

637
    """
638
    if instance.network_port is None:
639
      raise errors.OpExecError("no console port defined for %s"
640
                               % instance.name)
641
    else:
642
      raise errors.OpExecError("no PTY console, connect to %s:%s via VNC"
643
                               % (instance.primary_node,
644
                                  instance.network_port))