Barebones LXC hypervisor
[ganeti-local] / 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")