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=W0611
33 from ganeti import utils
34 from ganeti import objects
35 from ganeti import pathutils
36 from ganeti.hypervisor import hv_base
37 from ganeti.errors import HypervisorError
40 class LXCHypervisor(hv_base.BaseHypervisor):
41 """LXC-based virtualization.
43 Since current (Spring 2010) distributions are not yet ready for
44 running under a container, the following changes must be done
47 - disable the kernel log component of sysklogd/rsyslog/etc.,
48 otherwise they will fail to read the log, and at least rsyslog
49 will fill the filesystem with error messages
52 - move hardcoded parameters into hypervisor parameters, once we
53 have the container-parameter support
54 - implement memory limits, but only optionally, depending on host
58 - LXC is very temperamental; in daemon mode, it succeeds or fails
59 in launching the instance silently, without any error
60 indication, and when failing it can leave network interfaces
61 around, and future successful startups will list the instance
63 - shutdown sequence of containers leaves the init 'dead', and the
64 container effectively stopped, but LXC still believes the
65 container to be running; need to investigate using the
66 notify_on_release and release_agent feature of cgroups
69 _ROOT_DIR = pathutils.RUN_DIR + "/lxc"
74 "c 1:8", # /dev/random
75 "c 1:9", # /dev/urandom
78 "c 5:1", # /dev/console
80 "c 136:*", # first block of Unix98 PTY slaves
82 _DENIED_CAPABILITIES = [
83 "mac_override", # Allow MAC configuration or state changes
84 # TODO: remove sys_admin too, for safety
85 #"sys_admin", # Perform a range of system administration operations
86 "sys_boot", # Use reboot(2) and kexec_load(2)
87 "sys_module", # Load and unload kernel modules
88 "sys_time", # Set system clock, set real-time (hardware) clock
93 constants.HV_CPU_MASK: hv_base.OPT_CPU_MASK_CHECK,
97 hv_base.BaseHypervisor.__init__(self)
98 utils.EnsureDirs([(self._ROOT_DIR, self._DIR_MODE)])
101 def _GetMountSubdirs(path):
102 """Return the list of mountpoints under a given path.
106 for _, mountpoint, _, _ in utils.GetMounts():
107 if (mountpoint.startswith(path) and
109 result.append(mountpoint)
111 result.sort(key=lambda x: x.count("/"), reverse=True)
115 def _InstanceDir(cls, instance_name):
116 """Return the root directory for an instance.
119 return utils.PathJoin(cls._ROOT_DIR, instance_name)
122 def _InstanceConfFile(cls, instance_name):
123 """Return the configuration file for an instance.
126 return utils.PathJoin(cls._ROOT_DIR, instance_name + ".conf")
129 def _InstanceLogFile(cls, instance_name):
130 """Return the log file for an instance.
133 return utils.PathJoin(cls._ROOT_DIR, instance_name + ".log")
136 def _GetCgroupMountPoint(cls):
137 for _, mountpoint, fstype, _ in utils.GetMounts():
138 if fstype == "cgroup":
140 raise errors.HypervisorError("The cgroup filesystem is not mounted")
143 def _GetCgroupCpuList(cls, instance_name):
144 """Return the list of CPU ids for an instance.
147 cgroup = cls._GetCgroupMountPoint()
149 cpus = utils.ReadFile(utils.PathJoin(cgroup,
152 except EnvironmentError, err:
153 raise errors.HypervisorError("Getting CPU list for instance"
154 " %s failed: %s" % (instance_name, err))
156 return utils.ParseCpuMask(cpus)
158 def ListInstances(self):
159 """Get the list of running instances.
162 result = utils.RunCmd(["lxc-ls"])
164 raise errors.HypervisorError("Running lxc-ls failed: %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("Running lxc-info failed: %s" %
182 # lxc-info output examples:
183 # 'ganeti-lxc-test1' is STOPPED
184 # 'ganeti-lxc-test1' is RUNNING
185 _, state = result.stdout.rsplit(None, 1)
186 if state != "RUNNING":
189 cpu_list = self._GetCgroupCpuList(instance_name)
190 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),...]
199 for name in self.ListInstances():
200 data.append(self.GetInstanceInfo(name))
203 def _CreateConfigFile(self, instance, root_dir):
204 """Create an lxc.conf file for an instance.
209 out.append("lxc.utsname = %s" % instance.name)
211 # separate pseudo-TTY instances
212 out.append("lxc.pts = 255")
214 out.append("lxc.tty = 6")
216 console_log = utils.PathJoin(self._ROOT_DIR, instance.name + ".console")
218 utils.WriteFile(console_log, data="", mode=constants.SECURE_FILE_MODE)
219 except EnvironmentError, err:
220 raise errors.HypervisorError("Creating console log file %s for"
221 " instance %s failed: %s" %
222 (console_log, instance.name, err))
223 out.append("lxc.console = %s" % console_log)
226 out.append("lxc.rootfs = %s" % root_dir)
228 # TODO: additional mounts, if we disable CAP_SYS_ADMIN
231 if instance.hvparams[constants.HV_CPU_MASK]:
232 cpu_list = utils.ParseCpuMask(instance.hvparams[constants.HV_CPU_MASK])
233 cpus_in_mask = len(cpu_list)
234 if cpus_in_mask != instance.beparams["vcpus"]:
235 raise errors.HypervisorError("Number of VCPUs (%d) doesn't match"
236 " the number of CPUs in the"
238 (instance.beparams["vcpus"],
240 out.append("lxc.cgroup.cpuset.cpus = %s" %
241 instance.hvparams[constants.HV_CPU_MASK])
244 # deny direct device access
245 out.append("lxc.cgroup.devices.deny = a")
246 for devinfo in self._DEVS:
247 out.append("lxc.cgroup.devices.allow = %s rw" % devinfo)
250 for idx, nic in enumerate(instance.nics):
251 out.append("# NIC %d" % idx)
252 mode = nic.nicparams[constants.NIC_MODE]
253 link = nic.nicparams[constants.NIC_LINK]
254 if mode == constants.NIC_MODE_BRIDGED:
255 out.append("lxc.network.type = veth")
256 out.append("lxc.network.link = %s" % link)
258 raise errors.HypervisorError("LXC hypervisor only supports"
259 " bridged mode (NIC %d has mode %s)" %
261 out.append("lxc.network.hwaddr = %s" % nic.mac)
262 out.append("lxc.network.flags = up")
265 for cap in self._DENIED_CAPABILITIES:
266 out.append("lxc.cap.drop = %s" % cap)
268 return "\n".join(out) + "\n"
270 def StartInstance(self, instance, block_devices, startup_paused):
271 """Start an instance.
273 For LCX, we try to mount the block device and execute 'lxc-start'.
274 We use volatile containers.
277 root_dir = self._InstanceDir(instance.name)
279 utils.EnsureDirs([(root_dir, self._DIR_MODE)])
280 except errors.GenericError, err:
281 raise HypervisorError("Creating instance directory failed: %s", str(err))
283 conf_file = self._InstanceConfFile(instance.name)
284 utils.WriteFile(conf_file, data=self._CreateConfigFile(instance, root_dir))
286 log_file = self._InstanceLogFile(instance.name)
287 if not os.path.exists(log_file):
289 utils.WriteFile(log_file, data="", mode=constants.SECURE_FILE_MODE)
290 except EnvironmentError, err:
291 raise errors.HypervisorError("Creating hypervisor log file %s for"
292 " instance %s failed: %s" %
293 (log_file, instance.name, err))
295 if not os.path.ismount(root_dir):
296 if not block_devices:
297 raise HypervisorError("LXC needs at least one disk")
299 sda_dev_path = block_devices[0][1]
300 result = utils.RunCmd(["mount", sda_dev_path, root_dir])
302 raise HypervisorError("Mounting the root dir of LXC instance %s"
303 " failed: %s" % (instance.name, result.output))
304 result = utils.RunCmd(["lxc-start", "-n", instance.name,
307 "-f", conf_file, "-d"])
309 raise HypervisorError("Running the lxc-start script failed: %s" %
312 def StopInstance(self, instance, force=False, retry=False, name=None):
315 This method has complicated cleanup tests, as we must:
316 - try to kill all leftover processes
317 - try to unmount any additional sub-mountpoints
318 - finally unmount the instance dir
324 root_dir = self._InstanceDir(name)
325 if not os.path.exists(root_dir):
328 if name in self.ListInstances():
329 # Signal init to shutdown; this is a hack
330 if not retry and not force:
331 result = utils.RunCmd(["chroot", root_dir, "poweroff"])
333 raise HypervisorError("Running 'poweroff' on the instance"
334 " failed: %s" % result.output)
336 result = utils.RunCmd(["lxc-stop", "-n", name])
338 logging.warning("Error while doing lxc-stop for %s: %s", name,
341 for mpath in self._GetMountSubdirs(root_dir):
342 result = utils.RunCmd(["umount", mpath])
344 logging.warning("Error while umounting subpath %s for instance %s: %s",
345 mpath, name, result.output)
347 result = utils.RunCmd(["umount", root_dir])
348 if result.failed and force:
349 msg = ("Processes still alive in the chroot: %s" %
350 utils.RunCmd("fuser -vm %s" % root_dir).output)
352 raise HypervisorError("Unmounting the chroot dir failed: %s (%s)" %
353 (result.output, msg))
355 def RebootInstance(self, instance):
356 """Reboot an instance.
358 This is not (yet) implemented (in Ganeti) for the LXC hypervisor.
361 # TODO: implement reboot
362 raise HypervisorError("The LXC hypervisor doesn't implement the"
363 " reboot functionality")
365 def BalloonInstanceMemory(self, instance, mem):
366 """Balloon an instance memory to a certain value.
368 @type instance: L{objects.Instance}
369 @param instance: instance to be accepted
371 @param mem: actual memory size to use for instance runtime
374 # Currently lxc instances don't have memory limits
377 def GetNodeInfo(self):
378 """Return information about the node.
380 This is just a wrapper over the base GetLinuxNodeInfo method.
382 @return: a dict with the following keys (values in MiB):
383 - memory_total: the total memory size on the node
384 - memory_free: the available memory on the node for instances
385 - memory_dom0: the memory used by the node itself, if available
388 return self.GetLinuxNodeInfo()
391 def GetInstanceConsole(cls, instance, hvparams, beparams):
392 """Return a command for connecting to the console of an instance.
395 return objects.InstanceConsole(instance=instance.name,
396 kind=constants.CONS_SSH,
397 host=instance.primary_node,
398 user=constants.SSH_CONSOLE_USER,
399 command=["lxc-console", "-n", instance.name])
402 """Verify the hypervisor.
404 For the chroot manager, it just checks the existence of the base dir.
407 if not os.path.exists(self._ROOT_DIR):
408 return "The required directory '%s' does not exist." % self._ROOT_DIR
411 def PowercycleNode(cls):
412 """LXC powercycle, just a wrapper over Linux powercycle.
415 cls.LinuxPowercycle()
417 def MigrateInstance(self, instance, target, live):
418 """Migrate an instance.
420 @type instance: L{objects.Instance}
421 @param instance: the instance to be migrated
423 @param target: hostname (usually ip) of the target node
425 @param live: whether to do a live or non-live migration
428 raise HypervisorError("Migration is not supported by the LXC hypervisor")
430 def GetMigrationStatus(self, instance):
431 """Get the migration status
433 @type instance: L{objects.Instance}
434 @param instance: the instance that is being migrated
435 @rtype: L{objects.MigrationStatus}
436 @return: the status of the current migration (one of
437 L{constants.HV_MIGRATION_VALID_STATUSES}), plus any additional
438 progress info that can be retrieved from the hypervisor
441 raise HypervisorError("Migration is not supported by the LXC hypervisor")