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
94 hv_base.BaseHypervisor.__init__(self)
95 utils.EnsureDirs([(self._ROOT_DIR, self._DIR_MODE)])
98 def _GetMountSubdirs(path):
99 """Return the list of mountpoints under a given path.
103 for _, mountpoint, _, _ in utils.GetMounts():
104 if (mountpoint.startswith(path) and
106 result.append(mountpoint)
108 result.sort(key=lambda x: x.count("/"), reverse=True)
112 def _InstanceDir(cls, instance_name):
113 """Return the root directory for an instance.
116 return utils.PathJoin(cls._ROOT_DIR, instance_name)
119 def _InstanceConfFile(cls, instance_name):
120 """Return the configuration file for an instance.
123 return utils.PathJoin(cls._ROOT_DIR, instance_name + ".conf")
126 def _InstanceLogFile(cls, instance_name):
127 """Return the log file for an instance.
130 return utils.PathJoin(cls._ROOT_DIR, instance_name + ".log")
133 def _GetCgroupMountPoint(cls):
134 for _, mountpoint, fstype, _ in utils.GetMounts():
135 if fstype == "cgroup":
137 raise errors.HypervisorError("The cgroup filesystem is not mounted")
140 def _GetCgroupCpuList(cls, instance_name):
141 """Return the list of CPU ids for an instance.
144 cgroup = cls._GetCgroupMountPoint()
146 cpus = utils.ReadFile(utils.PathJoin(cgroup,
149 except EnvironmentError, err:
150 raise errors.HypervisorError("Getting CPU list for instance"
151 " %s failed: %s" % (instance_name, err))
152 # cpuset.cpus format: comma-separated list of CPU ids
153 # or dash-separated id ranges
156 for range_def in cpus.split(","):
157 boundaries = range_def.split("-")
158 n_elements = len(boundaries)
159 lower = int(boundaries[0])
160 higher = int(boundaries[n_elements - 1])
161 cpu_list.extend(range(lower, higher + 1))
164 def ListInstances(self):
165 """Get the list of running instances.
168 result = utils.RunCmd(["lxc-ls"])
170 raise errors.HypervisorError("Running lxc-ls failed: %s" % result.output)
171 return result.stdout.splitlines()
173 def GetInstanceInfo(self, instance_name):
174 """Get instance properties.
176 @type instance_name: string
177 @param instance_name: the instance name
179 @return: (name, id, memory, vcpus, stat, times)
182 # TODO: read container info from the cgroup mountpoint
184 result = utils.RunCmd(["lxc-info", "-n", instance_name])
186 raise errors.HypervisorError("Running lxc-info failed: %s" %
188 # lxc-info output examples:
189 # 'ganeti-lxc-test1' is STOPPED
190 # 'ganeti-lxc-test1' is RUNNING
191 _, state = result.stdout.rsplit(None, 1)
193 cpu_list = self._GetCgroupCpuList(instance_name)
195 if state == "RUNNING":
196 return (instance_name, 0, 0, len(cpu_list), 0, 0)
199 def GetAllInstancesInfo(self):
200 """Get properties of all instances.
202 @return: [(name, id, memory, vcpus, stat, times),...]
206 for name in self.ListInstances():
207 data.append(self.GetInstanceInfo(name))
210 def _CreateConfigFile(self, instance, root_dir):
211 """Create an lxc.conf file for an instance"""
214 out.append("lxc.utsname = %s" % instance.name)
216 # separate pseudo-TTY instances
217 out.append("lxc.pts = 255")
219 out.append("lxc.tty = 6")
221 console_log = utils.PathJoin(self._ROOT_DIR, instance.name + ".console")
223 utils.WriteFile(console_log, data="", mode=constants.SECURE_FILE_MODE)
224 except EnvironmentError, err:
225 raise errors.HypervisorError("Creating console log file %s for"
226 " instance %s failed: %s" %
227 (console_log, instance.name, err))
228 out.append("lxc.console = %s" % console_log)
231 out.append("lxc.rootfs = %s" % root_dir)
233 # TODO: additional mounts, if we disable CAP_SYS_ADMIN
236 # deny direct device access
237 out.append("lxc.cgroup.devices.deny = a")
238 for devinfo in self._DEVS:
239 out.append("lxc.cgroup.devices.allow = %s rw" % devinfo)
242 for idx, nic in enumerate(instance.nics):
243 out.append("# NIC %d" % idx)
244 mode = nic.nicparams[constants.NIC_MODE]
245 link = nic.nicparams[constants.NIC_LINK]
246 if mode == constants.NIC_MODE_BRIDGED:
247 out.append("lxc.network.type = veth")
248 out.append("lxc.network.link = %s" % link)
250 raise errors.HypervisorError("LXC hypervisor only supports"
251 " bridged mode (NIC %d has mode %s)" %
253 out.append("lxc.network.hwaddr = %s" % nic.mac)
254 out.append("lxc.network.flags = up")
257 for cap in self._DENIED_CAPABILITIES:
258 out.append("lxc.cap.drop = %s" % cap)
260 return "\n".join(out) + "\n"
262 def StartInstance(self, instance, block_devices):
263 """Start an instance.
265 For LCX, we try to mount the block device and execute 'lxc-start
266 start' (we use volatile containers).
269 root_dir = self._InstanceDir(instance.name)
271 utils.EnsureDirs([(root_dir, self._DIR_MODE)])
272 except errors.GenericError, err:
273 raise HypervisorError("Creating instance directory failed: %s", str(err))
275 conf_file = self._InstanceConfFile(instance.name)
276 utils.WriteFile(conf_file, data=self._CreateConfigFile(instance, root_dir))
278 log_file = self._InstanceLogFile(instance.name)
279 if not os.path.exists(log_file):
281 utils.WriteFile(log_file, data="", mode=constants.SECURE_FILE_MODE)
282 except EnvironmentError, err:
283 raise errors.HypervisorError("Creating hypervisor log file %s for"
284 " instance %s failed: %s" %
285 (log_file, instance.name, err))
287 if not os.path.ismount(root_dir):
288 if not block_devices:
289 raise HypervisorError("LXC needs at least one disk")
291 sda_dev_path = block_devices[0][1]
292 result = utils.RunCmd(["mount", sda_dev_path, root_dir])
294 raise HypervisorError("Mounting the root dir of LXC instance %s"
295 " failed: %s" % (instance.name, result.output))
296 result = utils.RunCmd(["lxc-start", "-n", instance.name,
299 "-f", conf_file, "-d"])
301 raise HypervisorError("Running the lxc-start script failed: %s" %
304 def StopInstance(self, instance, force=False, retry=False, name=None):
307 This method has complicated cleanup tests, as we must:
308 - try to kill all leftover processes
309 - try to unmount any additional sub-mountpoints
310 - finally unmount the instance dir
316 root_dir = self._InstanceDir(name)
317 if not os.path.exists(root_dir):
320 if name in self.ListInstances():
321 # Signal init to shutdown; this is a hack
322 if not retry and not force:
323 result = utils.RunCmd(["chroot", root_dir, "poweroff"])
325 raise HypervisorError("Running 'poweroff' on the instance"
326 " failed: %s" % result.output)
328 result = utils.RunCmd(["lxc-stop", "-n", name])
330 logging.warning("Error while doing lxc-stop for %s: %s", name,
333 for mpath in self._GetMountSubdirs(root_dir):
334 result = utils.RunCmd(["umount", mpath])
336 logging.warning("Error while umounting subpath %s for instance %s: %s",
337 mpath, name, result.output)
339 result = utils.RunCmd(["umount", root_dir])
340 if result.failed and force:
341 msg = ("Processes still alive in the chroot: %s" %
342 utils.RunCmd("fuser -vm %s" % root_dir).output)
344 raise HypervisorError("Unmounting the chroot dir failed: %s (%s)" %
345 (result.output, msg))
347 def RebootInstance(self, instance):
348 """Reboot an instance.
350 This is not (yet) implemented (in Ganeti) for the LXC hypervisor.
353 # TODO: implement reboot
354 raise HypervisorError("The LXC hypervisor doesn't implement the"
355 " reboot functionality")
357 def GetNodeInfo(self):
358 """Return information about the node.
360 This is just a wrapper over the base GetLinuxNodeInfo method.
362 @return: a dict with the following keys (values in MiB):
363 - memory_total: the total memory size on the node
364 - memory_free: the available memory on the node for instances
365 - memory_dom0: the memory used by the node itself, if available
368 return self.GetLinuxNodeInfo()
371 def GetShellCommandForConsole(cls, instance, hvparams, beparams):
372 """Return a command for connecting to the console of an instance.
375 return "lxc-console -n %s" % instance.name
378 """Verify the hypervisor.
380 For the chroot manager, it just checks the existence of the base dir.
383 if not os.path.exists(self._ROOT_DIR):
384 return "The required directory '%s' does not exist." % self._ROOT_DIR
387 def PowercycleNode(cls):
388 """LXC powercycle, just a wrapper over Linux powercycle.
391 cls.LinuxPowercycle()
393 def MigrateInstance(self, instance, target, live):
394 """Migrate an instance.
396 @type instance: L{objects.Instance}
397 @param instance: the instance to be migrated
399 @param target: hostname (usually ip) of the target node
401 @param live: whether to do a live or non-live migration
404 raise HypervisorError("Migration is not supported by the LXC hypervisor")