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"
72 "c 1:8", # /dev/random
73 "c 1:9", # /dev/urandom
76 "c 5:1", # /dev/console
78 "c 136:*", # first block of Unix98 PTY slaves
80 _DENIED_CAPABILITIES = [
81 "mac_override", # Allow MAC configuration or state changes
82 # TODO: remove sys_admin too, for safety
83 #"sys_admin", # Perform a range of system administration operations
84 "sys_boot", # Use reboot(2) and kexec_load(2)
85 "sys_module", # Load and unload kernel modules
86 "sys_time", # Set system clock, set real-time (hardware) clock
91 constants.HV_CPU_MASK: hv_base.OPT_CPU_MASK_CHECK,
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 _InstanceLogFile(cls, instance_name):
128 """Return the log file for an instance.
131 return utils.PathJoin(cls._ROOT_DIR, instance_name + ".log")
134 def _GetCgroupMountPoint(cls):
135 for _, mountpoint, fstype, _ in utils.GetMounts():
136 if fstype == "cgroup":
138 raise errors.HypervisorError("The cgroup filesystem is not mounted")
141 def _GetCgroupCpuList(cls, instance_name):
142 """Return the list of CPU ids for an instance.
145 cgroup = cls._GetCgroupMountPoint()
147 cpus = utils.ReadFile(utils.PathJoin(cgroup,
150 except EnvironmentError, err:
151 raise errors.HypervisorError("Getting CPU list for instance"
152 " %s failed: %s" % (instance_name, err))
154 return utils.ParseCpuMask(cpus)
156 def ListInstances(self):
157 """Get the list of running instances.
160 result = utils.RunCmd(["lxc-ls"])
162 raise errors.HypervisorError("Running lxc-ls failed: %s" % result.output)
163 return result.stdout.splitlines()
165 def GetInstanceInfo(self, instance_name):
166 """Get instance properties.
168 @type instance_name: string
169 @param instance_name: the instance name
171 @return: (name, id, memory, vcpus, stat, times)
174 # TODO: read container info from the cgroup mountpoint
176 result = utils.RunCmd(["lxc-info", "-n", instance_name])
178 raise errors.HypervisorError("Running lxc-info failed: %s" %
180 # lxc-info output examples:
181 # 'ganeti-lxc-test1' is STOPPED
182 # 'ganeti-lxc-test1' is RUNNING
183 _, state = result.stdout.rsplit(None, 1)
184 if state != "RUNNING":
187 cpu_list = self._GetCgroupCpuList(instance_name)
188 return (instance_name, 0, 0, len(cpu_list), 0, 0)
190 def GetAllInstancesInfo(self):
191 """Get properties of all instances.
193 @return: [(name, id, memory, vcpus, stat, times),...]
197 for name in self.ListInstances():
198 data.append(self.GetInstanceInfo(name))
201 def _CreateConfigFile(self, instance, root_dir):
202 """Create an lxc.conf file for an instance.
207 out.append("lxc.utsname = %s" % instance.name)
209 # separate pseudo-TTY instances
210 out.append("lxc.pts = 255")
212 out.append("lxc.tty = 6")
214 console_log = utils.PathJoin(self._ROOT_DIR, instance.name + ".console")
216 utils.WriteFile(console_log, data="", mode=constants.SECURE_FILE_MODE)
217 except EnvironmentError, err:
218 raise errors.HypervisorError("Creating console log file %s for"
219 " instance %s failed: %s" %
220 (console_log, instance.name, err))
221 out.append("lxc.console = %s" % console_log)
224 out.append("lxc.rootfs = %s" % root_dir)
226 # TODO: additional mounts, if we disable CAP_SYS_ADMIN
229 if instance.hvparams[constants.HV_CPU_MASK]:
230 cpu_list = utils.ParseCpuMask(instance.hvparams[constants.HV_CPU_MASK])
231 cpus_in_mask = len(cpu_list)
232 if cpus_in_mask != instance.beparams["vcpus"]:
233 raise errors.HypervisorError("Number of VCPUs (%d) doesn't match"
234 " the number of CPUs in the"
236 (instance.beparams["vcpus"],
238 out.append("lxc.cgroup.cpuset.cpus = %s" %
239 instance.hvparams[constants.HV_CPU_MASK])
242 # deny direct device access
243 out.append("lxc.cgroup.devices.deny = a")
244 for devinfo in self._DEVS:
245 out.append("lxc.cgroup.devices.allow = %s rw" % devinfo)
248 for idx, nic in enumerate(instance.nics):
249 out.append("# NIC %d" % idx)
250 mode = nic.nicparams[constants.NIC_MODE]
251 link = nic.nicparams[constants.NIC_LINK]
252 if mode == constants.NIC_MODE_BRIDGED:
253 out.append("lxc.network.type = veth")
254 out.append("lxc.network.link = %s" % link)
256 raise errors.HypervisorError("LXC hypervisor only supports"
257 " bridged mode (NIC %d has mode %s)" %
259 out.append("lxc.network.hwaddr = %s" % nic.mac)
260 out.append("lxc.network.flags = up")
263 for cap in self._DENIED_CAPABILITIES:
264 out.append("lxc.cap.drop = %s" % cap)
266 return "\n".join(out) + "\n"
268 def StartInstance(self, instance, block_devices):
269 """Start an instance.
271 For LCX, we try to mount the block device and execute 'lxc-start'.
272 We use volatile containers.
275 root_dir = self._InstanceDir(instance.name)
277 utils.EnsureDirs([(root_dir, self._DIR_MODE)])
278 except errors.GenericError, err:
279 raise HypervisorError("Creating instance directory failed: %s", str(err))
281 conf_file = self._InstanceConfFile(instance.name)
282 utils.WriteFile(conf_file, data=self._CreateConfigFile(instance, root_dir))
284 log_file = self._InstanceLogFile(instance.name)
285 if not os.path.exists(log_file):
287 utils.WriteFile(log_file, data="", mode=constants.SECURE_FILE_MODE)
288 except EnvironmentError, err:
289 raise errors.HypervisorError("Creating hypervisor log file %s for"
290 " instance %s failed: %s" %
291 (log_file, instance.name, err))
293 if not os.path.ismount(root_dir):
294 if not block_devices:
295 raise HypervisorError("LXC needs at least one disk")
297 sda_dev_path = block_devices[0][1]
298 result = utils.RunCmd(["mount", sda_dev_path, root_dir])
300 raise HypervisorError("Mounting the root dir of LXC instance %s"
301 " failed: %s" % (instance.name, result.output))
302 result = utils.RunCmd(["lxc-start", "-n", instance.name,
305 "-f", conf_file, "-d"])
307 raise HypervisorError("Running the lxc-start script failed: %s" %
310 def StopInstance(self, instance, force=False, retry=False, name=None):
313 This method has complicated cleanup tests, as we must:
314 - try to kill all leftover processes
315 - try to unmount any additional sub-mountpoints
316 - finally unmount the instance dir
322 root_dir = self._InstanceDir(name)
323 if not os.path.exists(root_dir):
326 if name in self.ListInstances():
327 # Signal init to shutdown; this is a hack
328 if not retry and not force:
329 result = utils.RunCmd(["chroot", root_dir, "poweroff"])
331 raise HypervisorError("Running 'poweroff' on the instance"
332 " failed: %s" % result.output)
334 result = utils.RunCmd(["lxc-stop", "-n", name])
336 logging.warning("Error while doing lxc-stop for %s: %s", name,
339 for mpath in self._GetMountSubdirs(root_dir):
340 result = utils.RunCmd(["umount", mpath])
342 logging.warning("Error while umounting subpath %s for instance %s: %s",
343 mpath, name, result.output)
345 result = utils.RunCmd(["umount", root_dir])
346 if result.failed and force:
347 msg = ("Processes still alive in the chroot: %s" %
348 utils.RunCmd("fuser -vm %s" % root_dir).output)
350 raise HypervisorError("Unmounting the chroot dir failed: %s (%s)" %
351 (result.output, msg))
353 def RebootInstance(self, instance):
354 """Reboot an instance.
356 This is not (yet) implemented (in Ganeti) for the LXC hypervisor.
359 # TODO: implement reboot
360 raise HypervisorError("The LXC hypervisor doesn't implement the"
361 " reboot functionality")
363 def GetNodeInfo(self):
364 """Return information about the node.
366 This is just a wrapper over the base GetLinuxNodeInfo method.
368 @return: a dict with the following keys (values in MiB):
369 - memory_total: the total memory size on the node
370 - memory_free: the available memory on the node for instances
371 - memory_dom0: the memory used by the node itself, if available
374 return self.GetLinuxNodeInfo()
377 def GetShellCommandForConsole(cls, instance, hvparams, beparams):
378 """Return a command for connecting to the console of an instance.
381 return "lxc-console -n %s" % instance.name
384 """Verify the hypervisor.
386 For the chroot manager, it just checks the existence of the base dir.
389 if not os.path.exists(self._ROOT_DIR):
390 return "The required directory '%s' does not exist." % self._ROOT_DIR
393 def PowercycleNode(cls):
394 """LXC powercycle, just a wrapper over Linux powercycle.
397 cls.LinuxPowercycle()
399 def MigrateInstance(self, instance, target, live):
400 """Migrate an instance.
402 @type instance: L{objects.Instance}
403 @param instance: the instance to be migrated
405 @param target: hostname (usually ip) of the target node
407 @param live: whether to do a live or non-live migration
410 raise HypervisorError("Migration is not supported by the LXC hypervisor")