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.
44 - move hardcoded parameters into hypervisor parameters, once we
45 have the container-parameter support
46 - implement memory limits, but only optionally, depending on host
50 - LXC is very temperamental; in daemon mode, it succeeds or fails
51 in launching the instance silently, without any error
52 indication, and when failing it can leave network interfaces
53 around, and future successful startups will list the instance
57 _ROOT_DIR = pathutils.RUN_DIR + "/lxc"
62 "c 1:8", # /dev/random
63 "c 1:9", # /dev/urandom
66 "c 5:1", # /dev/console
68 "c 136:*", # first block of Unix98 PTY slaves
70 _DENIED_CAPABILITIES = [
71 "mac_override", # Allow MAC configuration or state changes
72 # TODO: remove sys_admin too, for safety
73 #"sys_admin", # Perform a range of system administration operations
74 "sys_boot", # Use reboot(2) and kexec_load(2)
75 "sys_module", # Load and unload kernel modules
76 "sys_time", # Set system clock, set real-time (hardware) clock
81 constants.HV_CPU_MASK: hv_base.OPT_CPU_MASK_CHECK,
85 hv_base.BaseHypervisor.__init__(self)
86 utils.EnsureDirs([(self._ROOT_DIR, self._DIR_MODE)])
89 def _GetMountSubdirs(path):
90 """Return the list of mountpoints under a given path.
94 for _, mountpoint, _, _ in utils.GetMounts():
95 if (mountpoint.startswith(path) and
97 result.append(mountpoint)
99 result.sort(key=lambda x: x.count("/"), reverse=True)
103 def _InstanceDir(cls, instance_name):
104 """Return the root directory for an instance.
107 return utils.PathJoin(cls._ROOT_DIR, instance_name)
110 def _InstanceConfFile(cls, instance_name):
111 """Return the configuration file for an instance.
114 return utils.PathJoin(cls._ROOT_DIR, instance_name + ".conf")
117 def _InstanceLogFile(cls, instance_name):
118 """Return the log file for an instance.
121 return utils.PathJoin(cls._ROOT_DIR, instance_name + ".log")
124 def _GetCgroupMountPoint(cls):
125 for _, mountpoint, fstype, _ in utils.GetMounts():
126 if fstype == "cgroup":
128 raise errors.HypervisorError("The cgroup filesystem is not mounted")
131 def _GetCgroupCpuList(cls, instance_name):
132 """Return the list of CPU ids for an instance.
135 cgroup = cls._GetCgroupMountPoint()
137 cpus = utils.ReadFile(utils.PathJoin(cgroup, 'lxc',
140 except EnvironmentError, err:
141 raise errors.HypervisorError("Getting CPU list for instance"
142 " %s failed: %s" % (instance_name, err))
144 return utils.ParseCpuMask(cpus)
146 def ListInstances(self):
147 """Get the list of running instances.
150 return [ iinfo[0] for iinfo in self.GetAllInstancesInfo() ]
152 def GetInstanceInfo(self, instance_name):
153 """Get instance properties.
155 @type instance_name: string
156 @param instance_name: the instance name
158 @return: (name, id, memory, vcpus, stat, times)
161 # TODO: read container info from the cgroup mountpoint
163 result = utils.RunCmd(["lxc-info", "-s", "-n", instance_name])
165 raise errors.HypervisorError("Running lxc-info failed: %s" %
167 # lxc-info output examples:
170 _, state = result.stdout.rsplit(None, 1)
171 if state != "RUNNING":
174 cpu_list = self._GetCgroupCpuList(instance_name)
175 return (instance_name, 0, 0, len(cpu_list), 0, 0)
177 def GetAllInstancesInfo(self):
178 """Get properties of all instances.
180 @return: [(name, id, memory, vcpus, stat, times),...]
184 for name in os.listdir(self._ROOT_DIR):
186 info = self.GetInstanceInfo(name)
187 except errors.HypervisorError:
193 def _CreateConfigFile(self, instance, root_dir):
194 """Create an lxc.conf file for an instance.
199 out.append("lxc.utsname = %s" % instance.name)
201 # separate pseudo-TTY instances
202 out.append("lxc.pts = 255")
204 out.append("lxc.tty = 6")
206 console_log = utils.PathJoin(self._ROOT_DIR, instance.name + ".console")
208 utils.WriteFile(console_log, data="", mode=constants.SECURE_FILE_MODE)
209 except EnvironmentError, err:
210 raise errors.HypervisorError("Creating console log file %s for"
211 " instance %s failed: %s" %
212 (console_log, instance.name, err))
213 out.append("lxc.console = %s" % console_log)
216 out.append("lxc.rootfs = %s" % root_dir)
218 # TODO: additional mounts, if we disable CAP_SYS_ADMIN
221 if instance.hvparams[constants.HV_CPU_MASK]:
222 cpu_list = utils.ParseCpuMask(instance.hvparams[constants.HV_CPU_MASK])
223 cpus_in_mask = len(cpu_list)
224 if cpus_in_mask != instance.beparams["vcpus"]:
225 raise errors.HypervisorError("Number of VCPUs (%d) doesn't match"
226 " the number of CPUs in the"
228 (instance.beparams["vcpus"],
230 out.append("lxc.cgroup.cpuset.cpus = %s" %
231 instance.hvparams[constants.HV_CPU_MASK])
234 # deny direct device access
235 out.append("lxc.cgroup.devices.deny = a")
236 for devinfo in self._DEVS:
237 out.append("lxc.cgroup.devices.allow = %s rw" % devinfo)
240 for idx, nic in enumerate(instance.nics):
241 out.append("# NIC %d" % idx)
242 mode = nic.nicparams[constants.NIC_MODE]
243 link = nic.nicparams[constants.NIC_LINK]
244 if mode == constants.NIC_MODE_BRIDGED:
245 out.append("lxc.network.type = veth")
246 out.append("lxc.network.link = %s" % link)
248 raise errors.HypervisorError("LXC hypervisor only supports"
249 " bridged mode (NIC %d has mode %s)" %
251 out.append("lxc.network.hwaddr = %s" % nic.mac)
252 out.append("lxc.network.flags = up")
255 for cap in self._DENIED_CAPABILITIES:
256 out.append("lxc.cap.drop = %s" % cap)
258 return "\n".join(out) + "\n"
260 def StartInstance(self, instance, block_devices, startup_paused):
261 """Start an instance.
263 For LXC, we try to mount the block device and execute 'lxc-start'.
264 We use volatile containers.
267 root_dir = self._InstanceDir(instance.name)
269 utils.EnsureDirs([(root_dir, self._DIR_MODE)])
270 except errors.GenericError, err:
271 raise HypervisorError("Creating instance directory failed: %s", str(err))
273 conf_file = self._InstanceConfFile(instance.name)
274 utils.WriteFile(conf_file, data=self._CreateConfigFile(instance, root_dir))
276 log_file = self._InstanceLogFile(instance.name)
277 if not os.path.exists(log_file):
279 utils.WriteFile(log_file, data="", mode=constants.SECURE_FILE_MODE)
280 except EnvironmentError, err:
281 raise errors.HypervisorError("Creating hypervisor log file %s for"
282 " instance %s failed: %s" %
283 (log_file, instance.name, err))
285 if not os.path.ismount(root_dir):
286 if not block_devices:
287 raise HypervisorError("LXC needs at least one disk")
289 sda_dev_path = block_devices[0][1]
290 result = utils.RunCmd(["mount", sda_dev_path, root_dir])
292 raise HypervisorError("Mounting the root dir of LXC instance %s"
293 " failed: %s" % (instance.name, result.output))
294 result = utils.RunCmd(["lxc-start", "-n", instance.name,
297 "-f", conf_file, "-d"])
299 raise HypervisorError("Running the lxc-start script failed: %s" %
302 def StopInstance(self, instance, force=False, retry=False, name=None):
305 This method has complicated cleanup tests, as we must:
306 - try to kill all leftover processes
307 - try to unmount any additional sub-mountpoints
308 - finally unmount the instance dir
314 root_dir = self._InstanceDir(name)
315 if not os.path.exists(root_dir):
318 if name in self.ListInstances():
319 # Signal init to shutdown; this is a hack
320 if not retry and not force:
321 result = utils.RunCmd(["chroot", root_dir, "poweroff"])
323 raise HypervisorError("Running 'poweroff' on the instance"
324 " failed: %s" % result.output)
326 result = utils.RunCmd(["lxc-stop", "-n", name])
328 logging.warning("Error while doing lxc-stop for %s: %s", name,
331 if not os.path.ismount(root_dir):
334 for mpath in self._GetMountSubdirs(root_dir):
335 result = utils.RunCmd(["umount", mpath])
337 logging.warning("Error while umounting subpath %s for instance %s: %s",
338 mpath, name, result.output)
340 result = utils.RunCmd(["umount", root_dir])
341 if result.failed and force:
342 msg = ("Processes still alive in the chroot: %s" %
343 utils.RunCmd("fuser -vm %s" % root_dir).output)
345 raise HypervisorError("Unmounting the chroot dir failed: %s (%s)" %
346 (result.output, msg))
348 def RebootInstance(self, instance):
349 """Reboot an instance.
351 This is not (yet) implemented (in Ganeti) for the LXC hypervisor.
354 # TODO: implement reboot
355 raise HypervisorError("The LXC hypervisor doesn't implement the"
356 " reboot functionality")
358 def BalloonInstanceMemory(self, instance, mem):
359 """Balloon an instance memory to a certain value.
361 @type instance: L{objects.Instance}
362 @param instance: instance to be accepted
364 @param mem: actual memory size to use for instance runtime
367 # Currently lxc instances don't have memory limits
370 def GetNodeInfo(self):
371 """Return information about the node.
373 This is just a wrapper over the base GetLinuxNodeInfo method.
375 @return: a dict with the following keys (values in MiB):
376 - memory_total: the total memory size on the node
377 - memory_free: the available memory on the node for instances
378 - memory_dom0: the memory used by the node itself, if available
381 return self.GetLinuxNodeInfo()
384 def GetInstanceConsole(cls, instance, hvparams, beparams):
385 """Return a command for connecting to the console of an instance.
388 return objects.InstanceConsole(instance=instance.name,
389 kind=constants.CONS_SSH,
390 host=instance.primary_node,
391 user=constants.SSH_CONSOLE_USER,
392 command=["lxc-console", "-n", instance.name])
395 """Verify the hypervisor.
397 For the LXC manager, it just checks the existence of the base dir.
399 @return: Problem description if something is wrong, C{None} otherwise
402 if os.path.exists(self._ROOT_DIR):
405 return "The required directory '%s' does not exist" % self._ROOT_DIR
408 def PowercycleNode(cls):
409 """LXC powercycle, just a wrapper over Linux powercycle.
412 cls.LinuxPowercycle()
414 def MigrateInstance(self, instance, target, live):
415 """Migrate an instance.
417 @type instance: L{objects.Instance}
418 @param instance: the instance to be migrated
420 @param target: hostname (usually ip) of the target node
422 @param live: whether to do a live or non-live migration
425 raise HypervisorError("Migration is not supported by the LXC hypervisor")
427 def GetMigrationStatus(self, instance):
428 """Get the migration status
430 @type instance: L{objects.Instance}
431 @param instance: the instance that is being migrated
432 @rtype: L{objects.MigrationStatus}
433 @return: the status of the current migration (one of
434 L{constants.HV_MIGRATION_VALID_STATUSES}), plus any additional
435 progress info that can be retrieved from the hypervisor
438 raise HypervisorError("Migration is not supported by the LXC hypervisor")