Revision 4b5e40a5

b/Makefile.am
136 136
	lib/hypervisor/hv_chroot.py \
137 137
	lib/hypervisor/hv_fake.py \
138 138
	lib/hypervisor/hv_kvm.py \
139
	lib/hypervisor/hv_lxc.py \
139 140
	lib/hypervisor/hv_xen.py
140 141

  
141 142
rapi_PYTHON = \
b/lib/constants.py
632 632
HT_XEN_HVM = "xen-hvm"
633 633
HT_KVM = "kvm"
634 634
HT_CHROOT = "chroot"
635
HYPER_TYPES = frozenset([HT_XEN_PVM, HT_FAKE, HT_XEN_HVM, HT_KVM, HT_CHROOT])
635
HT_LXC = "lxc"
636
HYPER_TYPES = frozenset([
637
  HT_XEN_PVM,
638
  HT_FAKE,
639
  HT_XEN_HVM,
640
  HT_KVM,
641
  HT_CHROOT,
642
  HT_LXC,
643
  ])
636 644
HTS_REQ_PORT = frozenset([HT_XEN_HVM, HT_KVM])
637 645

  
638 646
VNC_BASE_PORT = 5900
......
877 885
  HT_CHROOT: {
878 886
    HV_INIT_SCRIPT: "/ganeti-chroot",
879 887
    },
888
  HT_LXC: {
889
    },
880 890
  }
881 891

  
882 892
HVC_GLOBALS = frozenset([
b/lib/hypervisor/__init__.py
30 30
from ganeti.hypervisor import hv_xen
31 31
from ganeti.hypervisor import hv_kvm
32 32
from ganeti.hypervisor import hv_chroot
33
from ganeti.hypervisor import hv_lxc
33 34

  
34 35

  
35 36
_HYPERVISOR_MAP = {
......
38 39
  constants.HT_FAKE: hv_fake.FakeHypervisor,
39 40
  constants.HT_KVM: hv_kvm.KVMHypervisor,
40 41
  constants.HT_CHROOT: hv_chroot.ChrootManager,
42
  constants.HT_LXC: hv_lxc.LXCHypervisor,
41 43
  }
42 44

  
43 45

  
b/lib/hypervisor/hv_lxc.py
1
#
2
#
3

  
4
# Copyright (C) 2010 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
"""LXC hypervisor
23

  
24
"""
25

  
26
import os
27
import os.path
28
import time
29
import logging
30

  
31
from ganeti import constants
32
from ganeti import errors # pylint: disable-msg=W0611
33
from ganeti import utils
34
from ganeti.hypervisor import hv_base
35
from ganeti.errors import HypervisorError
36

  
37

  
38
class LXCHypervisor(hv_base.BaseHypervisor):
39
  """LXC-based virtualization.
40

  
41
  Since current (Spring 2010) distributions are not yet ready for
42
  running under a container, the following changes must be done
43
  manually:
44
    - remove udev
45
    - disable the kernel log component of sysklogd/rsyslog/etc.,
46
      otherwise they will fail to read the log, and at least rsyslog
47
      will fill the filesystem with error messages
48

  
49
  TODO:
50
    - move hardcoded parameters into hypervisor parameters, once we
51
      have the container-parameter support
52
    - implement memory limits, but only optionally, depending on host
53
      kernel support
54

  
55
  Problems/issues:
56
    - LXC is very temperamental; in daemon mode, it succeeds or fails
57
      in launching the instance silently, without any error
58
      indication, and when failing it can leave network interfaces
59
      around, and future successful startups will list the instance
60
      twice
61
    - shutdown sequence of containers leaves the init 'dead', and the
62
      container effectively stopped, but LXC still believes the
63
      container to be running; need to investigate using the
64
      notify_on_release and release_agent feature of cgroups
65

  
66
  """
67
  _ROOT_DIR = constants.RUN_GANETI_DIR + "/lxc"
68
  _LOG_FILE = constants.LOG_DIR + "hv_lxc.log"
69
  _DEVS = [
70
    "c 1:3",   # /dev/null
71
    "c 1:5",   # /dev/zero
72
    "c 1:7",   # /dev/full
73
    "c 1:8",   # /dev/random
74
    "c 1:9",   # /dev/urandom
75
    "c 1:10",  # /dev/aio
76
    "c 5:0",   # /dev/tty
77
    "c 5:1",   # /dev/console
78
    "c 5:2",   # /dev/ptmx
79
    "c 136:*", # first block of Unix98 PTY slaves
80
    ]
81
  _DENIED_CAPABILITIES = [
82
    "mac_override",    # Allow MAC configuration or state changes
83
    # TODO: remove sys_admin too, for safety
84
    #"sys_admin",       # Perform  a range of system administration operations
85
    "sys_boot",        # Use reboot(2) and kexec_load(2)
86
    "sys_module",      # Load  and  unload kernel modules
87
    "sys_time",        # Set  system  clock, set real-time (hardware) clock
88
    ]
89
  _DIR_MODE = 0755
90

  
91
  PARAMETERS = {
92
    }
93

  
94
  def __init__(self):
95
    hv_base.BaseHypervisor.__init__(self)
96
    utils.EnsureDirs([(self._ROOT_DIR, self._DIR_MODE)])
97

  
98
  @staticmethod
99
  def _GetMountSubdirs(path):
100
    """Return the list of mountpoints under a given path.
101

  
102
    This function is Linux-specific.
103

  
104
    """
105
    #TODO(iustin): investigate and document non-linux options
106
    #(e.g. via mount output)
107
    data = []
108
    fh = open("/proc/mounts", "r")
109
    try:
110
      for line in fh:
111
        _, mountpoint, _ = line.split(" ", 2)
112
        if (mountpoint.startswith(path) and
113
            mountpoint != path):
114
          data.append(mountpoint)
115
    finally:
116
      fh.close()
117
    data.sort(key=lambda x: x.count("/"), reverse=True)
118
    return data
119

  
120
  @classmethod
121
  def _InstanceDir(cls, instance_name):
122
    """Return the root directory for an instance.
123

  
124
    """
125
    return utils.PathJoin(cls._ROOT_DIR, instance_name)
126

  
127
  @classmethod
128
  def _InstanceConfFile(cls, instance_name):
129
    """Return the configuration file for an instance.
130

  
131
    """
132
    return utils.PathJoin(cls._ROOT_DIR, instance_name + ".conf")
133

  
134
  def ListInstances(self):
135
    """Get the list of running instances.
136

  
137
    """
138
    result = utils.RunCmd(["lxc-ls"])
139
    if result.failed:
140
      raise errors.HypervisorError("Can't run lxc-ls: %s" % result.output)
141
    return result.stdout.splitlines()
142

  
143
  def GetInstanceInfo(self, instance_name):
144
    """Get instance properties.
145

  
146
    @type instance_name: string
147
    @param instance_name: the instance name
148

  
149
    @return: (name, id, memory, vcpus, stat, times)
150

  
151
    """
152
    # TODO: read container info from the cgroup mountpoint
153
    return (instance_name, 0, 0, 0, 0, 0)
154

  
155
  def GetAllInstancesInfo(self):
156
    """Get properties of all instances.
157

  
158
    @return: [(name, id, memory, vcpus, stat, times),...]
159

  
160
    """
161
    # TODO: read container info from the cgroup mountpoint
162
    data = []
163
    for name in self.ListInstances():
164
      data.append((name, 0, 0, 0, 0, 0))
165
    return data
166

  
167
  def _CreateConfigFile(self, instance, root_dir):
168
    """Create an lxc.conf file for an instance"""
169
    out = []
170
    # hostname
171
    out.append("lxc.utsname = %s" % instance.name)
172

  
173
    # separate pseudo-TTY instances
174
    out.append("lxc.pts = 255")
175
    # standard TTYs/console
176
    out.append("lxc.tty = 6")
177

  
178
    # root FS
179
    out.append("lxc.rootfs = %s" % root_dir)
180

  
181
    # TODO: additional mounts, if we disable CAP_SYS_ADMIN
182

  
183
    # Device control
184
    # deny direct device access
185
    out.append("lxc.cgroup.devices.deny = a")
186
    for devinfo in self._DEVS:
187
      out.append("lxc.cgroup.devices.allow = %s rw" % devinfo)
188

  
189
    # Networking
190
    for idx, nic in enumerate(instance.nics):
191
      out.append("# NIC %d" % idx)
192
      mode = nic.nicparams[constants.NIC_MODE]
193
      link = nic.nicparams[constants.NIC_LINK]
194
      if mode == constants.NIC_MODE_BRIDGED:
195
        out.append("lxc.network.type = veth")
196
        out.append("lxc.network.link = %s" % link)
197
      else:
198
        raise errors.HypervisorError("LXC hypervisor only supports"
199
                                     " bridged mode (NIC %d has mode %s)" %
200
                                     (idx, mode))
201
      out.append("lxc.network.hwaddr = %s" % nic.mac)
202
      out.append("lxc.network.flags = up")
203

  
204
    # Capabilities
205
    for cap in self._DENIED_CAPABILITIES:
206
      out.append("lxc.cap.drop = %s" % cap)
207

  
208
    return "\n".join(out) + "\n"
209

  
210
  def StartInstance(self, instance, block_devices):
211
    """Start an instance.
212

  
213
    For LCX, we try to mount the block device and execute 'lxc-start
214
    start' (we use volatile containers).
215

  
216
    """
217
    root_dir = self._InstanceDir(instance.name)
218
    try:
219
      utils.EnsureDirs([(root_dir, self._DIR_MODE)])
220
    except errors.GenericError, err:
221
      raise HypervisorError("Cannot create instance directory: %s", str(err))
222

  
223
    conf_file = self._InstanceConfFile(instance.name)
224
    utils.WriteFile(conf_file, data=self._CreateConfigFile(instance, root_dir))
225

  
226
    if not os.path.ismount(root_dir):
227
      if not block_devices:
228
        raise HypervisorError("LXC needs at least one disk")
229

  
230
      sda_dev_path = block_devices[0][1]
231
      result = utils.RunCmd(["mount", sda_dev_path, root_dir])
232
      if result.failed:
233
        raise HypervisorError("Can't mount the chroot dir: %s" % result.output)
234
    # TODO: replace the global log file with a per-instance log file
235
    result = utils.RunCmd(["lxc-start", "-n", instance.name,
236
                           "-o", self._LOG_FILE, "-l", "DEBUG",
237
                           "-f", conf_file, "-d"])
238
    if result.failed:
239
      raise HypervisorError("Running the lxc-start script failed: %s" %
240
                            result.output)
241

  
242
  def StopInstance(self, instance, force=False, retry=False, name=None):
243
    """Stop an instance.
244

  
245
    This method has complicated cleanup tests, as we must:
246
      - try to kill all leftover processes
247
      - try to unmount any additional sub-mountpoints
248
      - finally unmount the instance dir
249

  
250
    """
251
    if name is None:
252
      name = instance.name
253

  
254
    root_dir = self._InstanceDir(name)
255
    if not os.path.exists(root_dir):
256
      return
257

  
258
    if name in self.ListInstances():
259
      # Signal init to shutdown; this is a hack
260
      if not retry and not force:
261
        result = utils.RunCmd(["chroot", root_dir, "poweroff"])
262
        if result.failed:
263
          raise HypervisorError("Can't run 'poweroff' for the instance: %s" %
264
                                result.output)
265
      time.sleep(2)
266
      result = utils.RunCmd(["lxc-stop", "-n", name])
267
      if result.failed:
268
        logging.warning("Error while doing lxc-stop for %s: %s", name,
269
                        result.output)
270

  
271
    for mpath in self._GetMountSubdirs(root_dir):
272
      result = utils.RunCmd(["umount", mpath])
273
      if result.failed:
274
        logging.warning("Error while umounting subpath %s for instance %s: %s",
275
                        mpath, name, result.output)
276

  
277
    result = utils.RunCmd(["umount", root_dir])
278
    if result.failed and force:
279
      msg = ("Processes still alive in the chroot: %s" %
280
             utils.RunCmd("fuser -vm %s" % root_dir).output)
281
      logging.error(msg)
282
      raise HypervisorError("Can't umount the chroot dir: %s (%s)" %
283
                            (result.output, msg))
284

  
285
  def RebootInstance(self, instance):
286
    """Reboot an instance.
287

  
288
    This is not (yet) implemented (in Ganeti) for the LXC hypervisor.
289

  
290
    """
291
    # TODO: implement reboot
292
    raise HypervisorError("The LXC hypervisor doesn't implement the"
293
                          " reboot functionality")
294

  
295
  def GetNodeInfo(self):
296
    """Return information about the node.
297

  
298
    This is just a wrapper over the base GetLinuxNodeInfo method.
299

  
300
    @return: a dict with the following keys (values in MiB):
301
          - memory_total: the total memory size on the node
302
          - memory_free: the available memory on the node for instances
303
          - memory_dom0: the memory used by the node itself, if available
304

  
305
    """
306
    return self.GetLinuxNodeInfo()
307

  
308
  @classmethod
309
  def GetShellCommandForConsole(cls, instance, hvparams, beparams):
310
    """Return a command for connecting to the console of an instance.
311

  
312
    """
313
    return "lxc-console -n %s" % instance.name
314

  
315
  def Verify(self):
316
    """Verify the hypervisor.
317

  
318
    For the chroot manager, it just checks the existence of the base dir.
319

  
320
    """
321
    if not os.path.exists(self._ROOT_DIR):
322
      return "The required directory '%s' does not exist." % self._ROOT_DIR
323

  
324
  @classmethod
325
  def PowercycleNode(cls):
326
    """LXC powercycle, just a wrapper over Linux powercycle.
327

  
328
    """
329
    cls.LinuxPowercycle()
330

  
331
  def MigrateInstance(self, instance, target, live):
332
    """Migrate an instance.
333

  
334
    @type instance: L{objects.Instance}
335
    @param instance: the instance to be migrated
336
    @type target: string
337
    @param target: hostname (usually ip) of the target node
338
    @type live: boolean
339
    @param live: whether to do a live or non-live migration
340

  
341
    """
342
    raise HypervisorError("Migration not supported by the LXC hypervisor")

Also available in: Unified diff