4 # Copyright (C) 2010 Google Inc.
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.
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.
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
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
38 class LXCHypervisor(hv_base.BaseHypervisor):
39 """LXC-based virtualization.
41 Since current (Spring 2010) distributions are not yet ready for
42 running under a container, the following changes must be done
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
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
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
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
67 _ROOT_DIR = constants.RUN_GANETI_DIR + "/lxc"
68 _LOG_FILE = constants.LOG_DIR + "hv_lxc.log"
73 "c 1:8", # /dev/random
74 "c 1:9", # /dev/urandom
77 "c 5:1", # /dev/console
79 "c 136:*", # first block of Unix98 PTY slaves
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
95 hv_base.BaseHypervisor.__init__(self)
96 utils.EnsureDirs([(self._ROOT_DIR, self._DIR_MODE)])
99 def _GetMountSubdirs(path):
100 """Return the list of mountpoints under a given path.
104 for _, mountpoint, _, _ in utils.GetMounts():
105 if (mountpoint.startswith(path) and
107 result.append(mountpoint)
109 result.sort(key=lambda x: x.count("/"), reverse=True)
113 def _InstanceDir(cls, instance_name):
114 """Return the root directory for an instance.
117 return utils.PathJoin(cls._ROOT_DIR, instance_name)
120 def _InstanceConfFile(cls, instance_name):
121 """Return the configuration file for an instance.
124 return utils.PathJoin(cls._ROOT_DIR, instance_name + ".conf")
127 def _GetCgroupMountPoint(cls):
128 for _, mountpoint, fstype, _ in utils.GetMounts():
129 if fstype == "cgroup":
131 raise errors.HypervisorError("The cgroup filesystem is not mounted")
134 def _GetCgroupCpuList(cls, instance_name):
135 """Return the list of CPU ids for an instance.
138 cgroup = cls._GetCgroupMountPoint()
140 cpus = utils.ReadFile(utils.PathJoin(cgroup,
143 except EnvironmentError, err:
144 raise errors.HypervisorError("Getting CPU list for instance"
145 " %s failed: %s" % (instance_name, err))
146 # cpuset.cpus format: comma-separated list of CPU ids
147 # or dash-separated id ranges
150 for range_def in cpus.split(","):
151 boundaries = range_def.split("-")
152 n_elements = len(boundaries)
153 lower = int(boundaries[0])
154 higher = int(boundaries[n_elements - 1])
155 cpu_list.extend(range(lower, higher + 1))
158 def ListInstances(self):
159 """Get the list of running instances.
162 result = utils.RunCmd(["lxc-ls"])
164 raise errors.HypervisorError("Can't run lxc-ls: %s" % result.output)
165 return result.stdout.splitlines()
167 def GetInstanceInfo(self, instance_name):
168 """Get instance properties.
170 @type instance_name: string
171 @param instance_name: the instance name
173 @return: (name, id, memory, vcpus, stat, times)
176 # TODO: read container info from the cgroup mountpoint
178 result = utils.RunCmd(["lxc-info", "-n", instance_name])
180 raise errors.HypervisorError("Can't run lxc-info: %s" % result.output)
181 # lxc-info output examples:
182 # 'ganeti-lxc-test1' is STOPPED
183 # 'ganeti-lxc-test1' is RUNNING
184 _, state = result.stdout.rsplit(None, 1)
186 cpu_list = self._GetCgroupCpuList(instance_name)
188 if state == "RUNNING":
189 return (instance_name, 0, 0, len(cpu_list), 0, 0)
192 def GetAllInstancesInfo(self):
193 """Get properties of all instances.
195 @return: [(name, id, memory, vcpus, stat, times),...]
198 # TODO: read container info from the cgroup mountpoint
200 for name in self.ListInstances():
201 data.append((name, 0, 0, 0, 0, 0))
204 def _CreateConfigFile(self, instance, root_dir):
205 """Create an lxc.conf file for an instance"""
208 out.append("lxc.utsname = %s" % instance.name)
210 # separate pseudo-TTY instances
211 out.append("lxc.pts = 255")
213 out.append("lxc.tty = 6")
215 console_log = utils.PathJoin(self._ROOT_DIR, instance.name + ".console")
217 utils.WriteFile(console_log, data="", mode=constants.SECURE_FILE_MODE)
218 except EnvironmentError, err:
219 raise errors.HypervisorError("Creating console log file %s for"
220 " instance %s failed: %s" %
221 (console_log, instance.name, err))
222 out.append("lxc.console = %s" % console_log)
225 out.append("lxc.rootfs = %s" % root_dir)
227 # TODO: additional mounts, if we disable CAP_SYS_ADMIN
230 # deny direct device access
231 out.append("lxc.cgroup.devices.deny = a")
232 for devinfo in self._DEVS:
233 out.append("lxc.cgroup.devices.allow = %s rw" % devinfo)
236 for idx, nic in enumerate(instance.nics):
237 out.append("# NIC %d" % idx)
238 mode = nic.nicparams[constants.NIC_MODE]
239 link = nic.nicparams[constants.NIC_LINK]
240 if mode == constants.NIC_MODE_BRIDGED:
241 out.append("lxc.network.type = veth")
242 out.append("lxc.network.link = %s" % link)
244 raise errors.HypervisorError("LXC hypervisor only supports"
245 " bridged mode (NIC %d has mode %s)" %
247 out.append("lxc.network.hwaddr = %s" % nic.mac)
248 out.append("lxc.network.flags = up")
251 for cap in self._DENIED_CAPABILITIES:
252 out.append("lxc.cap.drop = %s" % cap)
254 return "\n".join(out) + "\n"
256 def StartInstance(self, instance, block_devices):
257 """Start an instance.
259 For LCX, we try to mount the block device and execute 'lxc-start
260 start' (we use volatile containers).
263 root_dir = self._InstanceDir(instance.name)
265 utils.EnsureDirs([(root_dir, self._DIR_MODE)])
266 except errors.GenericError, err:
267 raise HypervisorError("Cannot create instance directory: %s", str(err))
269 conf_file = self._InstanceConfFile(instance.name)
270 utils.WriteFile(conf_file, data=self._CreateConfigFile(instance, root_dir))
272 if not os.path.ismount(root_dir):
273 if not block_devices:
274 raise HypervisorError("LXC needs at least one disk")
276 sda_dev_path = block_devices[0][1]
277 result = utils.RunCmd(["mount", sda_dev_path, root_dir])
279 raise HypervisorError("Can't mount the chroot dir: %s" % result.output)
280 # TODO: replace the global log file with a per-instance log file
281 result = utils.RunCmd(["lxc-start", "-n", instance.name,
282 "-o", self._LOG_FILE, "-l", "DEBUG",
283 "-f", conf_file, "-d"])
285 raise HypervisorError("Running the lxc-start script failed: %s" %
288 def StopInstance(self, instance, force=False, retry=False, name=None):
291 This method has complicated cleanup tests, as we must:
292 - try to kill all leftover processes
293 - try to unmount any additional sub-mountpoints
294 - finally unmount the instance dir
300 root_dir = self._InstanceDir(name)
301 if not os.path.exists(root_dir):
304 if name in self.ListInstances():
305 # Signal init to shutdown; this is a hack
306 if not retry and not force:
307 result = utils.RunCmd(["chroot", root_dir, "poweroff"])
309 raise HypervisorError("Can't run 'poweroff' for the instance: %s" %
312 result = utils.RunCmd(["lxc-stop", "-n", name])
314 logging.warning("Error while doing lxc-stop for %s: %s", name,
317 for mpath in self._GetMountSubdirs(root_dir):
318 result = utils.RunCmd(["umount", mpath])
320 logging.warning("Error while umounting subpath %s for instance %s: %s",
321 mpath, name, result.output)
323 result = utils.RunCmd(["umount", root_dir])
324 if result.failed and force:
325 msg = ("Processes still alive in the chroot: %s" %
326 utils.RunCmd("fuser -vm %s" % root_dir).output)
328 raise HypervisorError("Can't umount the chroot dir: %s (%s)" %
329 (result.output, msg))
331 def RebootInstance(self, instance):
332 """Reboot an instance.
334 This is not (yet) implemented (in Ganeti) for the LXC hypervisor.
337 # TODO: implement reboot
338 raise HypervisorError("The LXC hypervisor doesn't implement the"
339 " reboot functionality")
341 def GetNodeInfo(self):
342 """Return information about the node.
344 This is just a wrapper over the base GetLinuxNodeInfo method.
346 @return: a dict with the following keys (values in MiB):
347 - memory_total: the total memory size on the node
348 - memory_free: the available memory on the node for instances
349 - memory_dom0: the memory used by the node itself, if available
352 return self.GetLinuxNodeInfo()
355 def GetShellCommandForConsole(cls, instance, hvparams, beparams):
356 """Return a command for connecting to the console of an instance.
359 return "lxc-console -n %s" % instance.name
362 """Verify the hypervisor.
364 For the chroot manager, it just checks the existence of the base dir.
367 if not os.path.exists(self._ROOT_DIR):
368 return "The required directory '%s' does not exist." % self._ROOT_DIR
371 def PowercycleNode(cls):
372 """LXC powercycle, just a wrapper over Linux powercycle.
375 cls.LinuxPowercycle()
377 def MigrateInstance(self, instance, target, live):
378 """Migrate an instance.
380 @type instance: L{objects.Instance}
381 @param instance: the instance to be migrated
383 @param target: hostname (usually ip) of the target node
385 @param live: whether to do a live or non-live migration
388 raise HypervisorError("Migration not supported by the LXC hypervisor")