81e416ef724da66aabf64effd9800e6924a3cf6b
[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     """
103     result = []
104     for _, mountpoint, _, _ in utils.GetMounts():
105       if (mountpoint.startswith(path) and
106           mountpoint != path):
107         result.append(mountpoint)
108
109     result.sort(key=lambda x: x.count("/"), reverse=True)
110     return result
111
112   @classmethod
113   def _InstanceDir(cls, instance_name):
114     """Return the root directory for an instance.
115
116     """
117     return utils.PathJoin(cls._ROOT_DIR, instance_name)
118
119   @classmethod
120   def _InstanceConfFile(cls, instance_name):
121     """Return the configuration file for an instance.
122
123     """
124     return utils.PathJoin(cls._ROOT_DIR, instance_name + ".conf")
125
126   def ListInstances(self):
127     """Get the list of running instances.
128
129     """
130     result = utils.RunCmd(["lxc-ls"])
131     if result.failed:
132       raise errors.HypervisorError("Can't run lxc-ls: %s" % result.output)
133     return result.stdout.splitlines()
134
135   def GetInstanceInfo(self, instance_name):
136     """Get instance properties.
137
138     @type instance_name: string
139     @param instance_name: the instance name
140
141     @return: (name, id, memory, vcpus, stat, times)
142
143     """
144     # TODO: read container info from the cgroup mountpoint
145     return (instance_name, 0, 0, 0, 0, 0)
146
147   def GetAllInstancesInfo(self):
148     """Get properties of all instances.
149
150     @return: [(name, id, memory, vcpus, stat, times),...]
151
152     """
153     # TODO: read container info from the cgroup mountpoint
154     data = []
155     for name in self.ListInstances():
156       data.append((name, 0, 0, 0, 0, 0))
157     return data
158
159   def _CreateConfigFile(self, instance, root_dir):
160     """Create an lxc.conf file for an instance"""
161     out = []
162     # hostname
163     out.append("lxc.utsname = %s" % instance.name)
164
165     # separate pseudo-TTY instances
166     out.append("lxc.pts = 255")
167     # standard TTYs/console
168     out.append("lxc.tty = 6")
169
170     # root FS
171     out.append("lxc.rootfs = %s" % root_dir)
172
173     # TODO: additional mounts, if we disable CAP_SYS_ADMIN
174
175     # Device control
176     # deny direct device access
177     out.append("lxc.cgroup.devices.deny = a")
178     for devinfo in self._DEVS:
179       out.append("lxc.cgroup.devices.allow = %s rw" % devinfo)
180
181     # Networking
182     for idx, nic in enumerate(instance.nics):
183       out.append("# NIC %d" % idx)
184       mode = nic.nicparams[constants.NIC_MODE]
185       link = nic.nicparams[constants.NIC_LINK]
186       if mode == constants.NIC_MODE_BRIDGED:
187         out.append("lxc.network.type = veth")
188         out.append("lxc.network.link = %s" % link)
189       else:
190         raise errors.HypervisorError("LXC hypervisor only supports"
191                                      " bridged mode (NIC %d has mode %s)" %
192                                      (idx, mode))
193       out.append("lxc.network.hwaddr = %s" % nic.mac)
194       out.append("lxc.network.flags = up")
195
196     # Capabilities
197     for cap in self._DENIED_CAPABILITIES:
198       out.append("lxc.cap.drop = %s" % cap)
199
200     return "\n".join(out) + "\n"
201
202   def StartInstance(self, instance, block_devices):
203     """Start an instance.
204
205     For LCX, we try to mount the block device and execute 'lxc-start
206     start' (we use volatile containers).
207
208     """
209     root_dir = self._InstanceDir(instance.name)
210     try:
211       utils.EnsureDirs([(root_dir, self._DIR_MODE)])
212     except errors.GenericError, err:
213       raise HypervisorError("Cannot create instance directory: %s", str(err))
214
215     conf_file = self._InstanceConfFile(instance.name)
216     utils.WriteFile(conf_file, data=self._CreateConfigFile(instance, root_dir))
217
218     if not os.path.ismount(root_dir):
219       if not block_devices:
220         raise HypervisorError("LXC needs at least one disk")
221
222       sda_dev_path = block_devices[0][1]
223       result = utils.RunCmd(["mount", sda_dev_path, root_dir])
224       if result.failed:
225         raise HypervisorError("Can't mount the chroot dir: %s" % result.output)
226     # TODO: replace the global log file with a per-instance log file
227     result = utils.RunCmd(["lxc-start", "-n", instance.name,
228                            "-o", self._LOG_FILE, "-l", "DEBUG",
229                            "-f", conf_file, "-d"])
230     if result.failed:
231       raise HypervisorError("Running the lxc-start script failed: %s" %
232                             result.output)
233
234   def StopInstance(self, instance, force=False, retry=False, name=None):
235     """Stop an instance.
236
237     This method has complicated cleanup tests, as we must:
238       - try to kill all leftover processes
239       - try to unmount any additional sub-mountpoints
240       - finally unmount the instance dir
241
242     """
243     if name is None:
244       name = instance.name
245
246     root_dir = self._InstanceDir(name)
247     if not os.path.exists(root_dir):
248       return
249
250     if name in self.ListInstances():
251       # Signal init to shutdown; this is a hack
252       if not retry and not force:
253         result = utils.RunCmd(["chroot", root_dir, "poweroff"])
254         if result.failed:
255           raise HypervisorError("Can't run 'poweroff' for the instance: %s" %
256                                 result.output)
257       time.sleep(2)
258       result = utils.RunCmd(["lxc-stop", "-n", name])
259       if result.failed:
260         logging.warning("Error while doing lxc-stop for %s: %s", name,
261                         result.output)
262
263     for mpath in self._GetMountSubdirs(root_dir):
264       result = utils.RunCmd(["umount", mpath])
265       if result.failed:
266         logging.warning("Error while umounting subpath %s for instance %s: %s",
267                         mpath, name, result.output)
268
269     result = utils.RunCmd(["umount", root_dir])
270     if result.failed and force:
271       msg = ("Processes still alive in the chroot: %s" %
272              utils.RunCmd("fuser -vm %s" % root_dir).output)
273       logging.error(msg)
274       raise HypervisorError("Can't umount the chroot dir: %s (%s)" %
275                             (result.output, msg))
276
277   def RebootInstance(self, instance):
278     """Reboot an instance.
279
280     This is not (yet) implemented (in Ganeti) for the LXC hypervisor.
281
282     """
283     # TODO: implement reboot
284     raise HypervisorError("The LXC hypervisor doesn't implement the"
285                           " reboot functionality")
286
287   def GetNodeInfo(self):
288     """Return information about the node.
289
290     This is just a wrapper over the base GetLinuxNodeInfo method.
291
292     @return: a dict with the following keys (values in MiB):
293           - memory_total: the total memory size on the node
294           - memory_free: the available memory on the node for instances
295           - memory_dom0: the memory used by the node itself, if available
296
297     """
298     return self.GetLinuxNodeInfo()
299
300   @classmethod
301   def GetShellCommandForConsole(cls, instance, hvparams, beparams):
302     """Return a command for connecting to the console of an instance.
303
304     """
305     return "lxc-console -n %s" % instance.name
306
307   def Verify(self):
308     """Verify the hypervisor.
309
310     For the chroot manager, it just checks the existence of the base dir.
311
312     """
313     if not os.path.exists(self._ROOT_DIR):
314       return "The required directory '%s' does not exist." % self._ROOT_DIR
315
316   @classmethod
317   def PowercycleNode(cls):
318     """LXC powercycle, just a wrapper over Linux powercycle.
319
320     """
321     cls.LinuxPowercycle()
322
323   def MigrateInstance(self, instance, target, live):
324     """Migrate an instance.
325
326     @type instance: L{objects.Instance}
327     @param instance: the instance to be migrated
328     @type target: string
329     @param target: hostname (usually ip) of the target node
330     @type live: boolean
331     @param live: whether to do a live or non-live migration
332
333     """
334     raise HypervisorError("Migration not supported by the LXC hypervisor")