4 # Copyright (C) 2010, 2013 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
48 - LXC is very temperamental; in daemon mode, it succeeds or fails
49 in launching the instance silently, without any error
50 indication, and when failing it can leave network interfaces
51 around, and future successful startups will list the instance
55 _ROOT_DIR = pathutils.RUN_DIR + "/lxc"
60 "c 1:8", # /dev/random
61 "c 1:9", # /dev/urandom
64 "c 5:1", # /dev/console
66 "c 136:*", # first block of Unix98 PTY slaves
68 _DENIED_CAPABILITIES = [
69 "mac_override", # Allow MAC configuration or state changes
70 # TODO: remove sys_admin too, for safety
71 #"sys_admin", # Perform a range of system administration operations
72 "sys_boot", # Use reboot(2) and kexec_load(2)
73 "sys_module", # Load and unload kernel modules
74 "sys_time", # Set system clock, set real-time (hardware) clock
79 constants.HV_CPU_MASK: hv_base.OPT_CPU_MASK_CHECK,
83 hv_base.BaseHypervisor.__init__(self)
84 utils.EnsureDirs([(self._ROOT_DIR, self._DIR_MODE)])
87 def _GetMountSubdirs(path):
88 """Return the list of mountpoints under a given path.
92 for _, mountpoint, _, _ in utils.GetMounts():
93 if (mountpoint.startswith(path) and
95 result.append(mountpoint)
97 result.sort(key=lambda x: x.count("/"), reverse=True)
101 def _InstanceDir(cls, instance_name):
102 """Return the root directory for an instance.
105 return utils.PathJoin(cls._ROOT_DIR, instance_name)
108 def _InstanceConfFile(cls, instance_name):
109 """Return the configuration file for an instance.
112 return utils.PathJoin(cls._ROOT_DIR, instance_name + ".conf")
115 def _InstanceLogFile(cls, instance_name):
116 """Return the log file for an instance.
119 return utils.PathJoin(cls._ROOT_DIR, instance_name + ".log")
122 def _GetCgroupMountPoint(cls):
123 for _, mountpoint, fstype, _ in utils.GetMounts():
124 if fstype == "cgroup":
126 raise errors.HypervisorError("The cgroup filesystem is not mounted")
129 def _GetCgroupCpuList(cls, instance_name):
130 """Return the list of CPU ids for an instance.
133 cgroup = cls._GetCgroupMountPoint()
135 cpus = utils.ReadFile(utils.PathJoin(cgroup, 'lxc',
138 except EnvironmentError, err:
139 raise errors.HypervisorError("Getting CPU list for instance"
140 " %s failed: %s" % (instance_name, err))
142 return utils.ParseCpuMask(cpus)
145 def _GetCgroupMemoryLimit(cls, instance_name):
146 """Return the memory limit for an instance
149 cgroup = cls._GetCgroupMountPoint()
151 memory = int(utils.ReadFile(utils.PathJoin(cgroup, 'lxc',
153 "memory.limit_in_bytes")))
154 except EnvironmentError:
155 # memory resource controller may be disabled, ignore
160 def ListInstances(self, hvparams=None):
161 """Get the list of running instances.
164 return [iinfo[0] for iinfo in self.GetAllInstancesInfo()]
166 def GetInstanceInfo(self, instance_name):
167 """Get instance properties.
169 @type instance_name: string
170 @param instance_name: the instance name
171 @rtype: tuple of strings
172 @return: (name, id, memory, vcpus, stat, times)
175 # TODO: read container info from the cgroup mountpoint
177 result = utils.RunCmd(["lxc-info", "-s", "-n", instance_name])
179 raise errors.HypervisorError("Running lxc-info failed: %s" %
181 # lxc-info output examples:
184 _, state = result.stdout.rsplit(None, 1)
185 if state != "RUNNING":
188 cpu_list = self._GetCgroupCpuList(instance_name)
189 memory = self._GetCgroupMemoryLimit(instance_name) / (1024 ** 2)
190 return (instance_name, 0, memory, 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 os.listdir(self._ROOT_DIR):
201 info = self.GetInstanceInfo(name)
202 except errors.HypervisorError:
208 def _CreateConfigFile(self, instance, root_dir):
209 """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 if instance.hvparams[constants.HV_CPU_MASK]:
237 cpu_list = utils.ParseCpuMask(instance.hvparams[constants.HV_CPU_MASK])
238 cpus_in_mask = len(cpu_list)
239 if cpus_in_mask != instance.beparams["vcpus"]:
240 raise errors.HypervisorError("Number of VCPUs (%d) doesn't match"
241 " the number of CPUs in the"
243 (instance.beparams["vcpus"],
245 out.append("lxc.cgroup.cpuset.cpus = %s" %
246 instance.hvparams[constants.HV_CPU_MASK])
249 # Conditionally enable, memory resource controller might be disabled
250 cgroup = self._GetCgroupMountPoint()
251 if os.path.exists(utils.PathJoin(cgroup, 'memory.limit_in_bytes')):
252 out.append("lxc.cgroup.memory.limit_in_bytes = %dM" %
253 instance.beparams[constants.BE_MAXMEM])
255 if os.path.exists(utils.PathJoin(cgroup, 'memory.memsw.limit_in_bytes')):
256 out.append("lxc.cgroup.memory.memsw.limit_in_bytes = %dM" %
257 instance.beparams[constants.BE_MAXMEM])
260 # deny direct device access
261 out.append("lxc.cgroup.devices.deny = a")
262 for devinfo in self._DEVS:
263 out.append("lxc.cgroup.devices.allow = %s rw" % devinfo)
266 for idx, nic in enumerate(instance.nics):
267 out.append("# NIC %d" % idx)
268 mode = nic.nicparams[constants.NIC_MODE]
269 link = nic.nicparams[constants.NIC_LINK]
270 if mode == constants.NIC_MODE_BRIDGED:
271 out.append("lxc.network.type = veth")
272 out.append("lxc.network.link = %s" % link)
274 raise errors.HypervisorError("LXC hypervisor only supports"
275 " bridged mode (NIC %d has mode %s)" %
277 out.append("lxc.network.hwaddr = %s" % nic.mac)
278 out.append("lxc.network.flags = up")
281 for cap in self._DENIED_CAPABILITIES:
282 out.append("lxc.cap.drop = %s" % cap)
284 return "\n".join(out) + "\n"
286 def StartInstance(self, instance, block_devices, startup_paused):
287 """Start an instance.
289 For LXC, we try to mount the block device and execute 'lxc-start'.
290 We use volatile containers.
293 root_dir = self._InstanceDir(instance.name)
295 utils.EnsureDirs([(root_dir, self._DIR_MODE)])
296 except errors.GenericError, err:
297 raise HypervisorError("Creating instance directory failed: %s", str(err))
299 conf_file = self._InstanceConfFile(instance.name)
300 utils.WriteFile(conf_file, data=self._CreateConfigFile(instance, root_dir))
302 log_file = self._InstanceLogFile(instance.name)
303 if not os.path.exists(log_file):
305 utils.WriteFile(log_file, data="", mode=constants.SECURE_FILE_MODE)
306 except EnvironmentError, err:
307 raise errors.HypervisorError("Creating hypervisor log file %s for"
308 " instance %s failed: %s" %
309 (log_file, instance.name, err))
311 if not os.path.ismount(root_dir):
312 if not block_devices:
313 raise HypervisorError("LXC needs at least one disk")
315 sda_dev_path = block_devices[0][1]
316 result = utils.RunCmd(["mount", sda_dev_path, root_dir])
318 raise HypervisorError("Mounting the root dir of LXC instance %s"
319 " failed: %s" % (instance.name, result.output))
320 result = utils.RunCmd(["lxc-start", "-n", instance.name,
323 "-f", conf_file, "-d"])
325 raise HypervisorError("Running the lxc-start script failed: %s" %
328 def StopInstance(self, instance, force=False, retry=False, name=None):
331 This method has complicated cleanup tests, as we must:
332 - try to kill all leftover processes
333 - try to unmount any additional sub-mountpoints
334 - finally unmount the instance dir
340 root_dir = self._InstanceDir(name)
341 if not os.path.exists(root_dir):
344 if name in self.ListInstances():
345 # Signal init to shutdown; this is a hack
346 if not retry and not force:
347 result = utils.RunCmd(["chroot", root_dir, "poweroff"])
349 raise HypervisorError("Running 'poweroff' on the instance"
350 " failed: %s" % result.output)
352 result = utils.RunCmd(["lxc-stop", "-n", name])
354 logging.warning("Error while doing lxc-stop for %s: %s", name,
357 if not os.path.ismount(root_dir):
360 for mpath in self._GetMountSubdirs(root_dir):
361 result = utils.RunCmd(["umount", mpath])
363 logging.warning("Error while umounting subpath %s for instance %s: %s",
364 mpath, name, result.output)
366 result = utils.RunCmd(["umount", root_dir])
367 if result.failed and force:
368 msg = ("Processes still alive in the chroot: %s" %
369 utils.RunCmd("fuser -vm %s" % root_dir).output)
371 raise HypervisorError("Unmounting the chroot dir failed: %s (%s)" %
372 (result.output, msg))
374 def RebootInstance(self, instance):
375 """Reboot an instance.
377 This is not (yet) implemented (in Ganeti) for the LXC hypervisor.
380 # TODO: implement reboot
381 raise HypervisorError("The LXC hypervisor doesn't implement the"
382 " reboot functionality")
384 def BalloonInstanceMemory(self, instance, mem):
385 """Balloon an instance memory to a certain value.
387 @type instance: L{objects.Instance}
388 @param instance: instance to be accepted
390 @param mem: actual memory size to use for instance runtime
393 # Currently lxc instances don't have memory limits
396 def GetNodeInfo(self):
397 """Return information about the node.
399 This is just a wrapper over the base GetLinuxNodeInfo method.
401 @return: a dict with the following keys (values in MiB):
402 - memory_total: the total memory size on the node
403 - memory_free: the available memory on the node for instances
404 - memory_dom0: the memory used by the node itself, if available
407 return self.GetLinuxNodeInfo()
410 def GetInstanceConsole(cls, instance, hvparams, beparams):
411 """Return a command for connecting to the console of an instance.
414 return objects.InstanceConsole(instance=instance.name,
415 kind=constants.CONS_SSH,
416 host=instance.primary_node,
417 user=constants.SSH_CONSOLE_USER,
418 command=["lxc-console", "-n", instance.name])
420 def Verify(self, hvparams=None):
421 """Verify the hypervisor.
423 For the LXC manager, it just checks the existence of the base dir.
425 @type hvparams: dict of strings
426 @param hvparams: hypervisor parameters to be verified against; not used here
428 @return: Problem description if something is wrong, C{None} otherwise
433 if not os.path.exists(self._ROOT_DIR):
434 msgs.append("The required directory '%s' does not exist" %
438 self._GetCgroupMountPoint()
439 except errors.HypervisorError, err:
440 msgs.append(str(err))
442 return self._FormatVerifyResults(msgs)
445 def PowercycleNode(cls):
446 """LXC powercycle, just a wrapper over Linux powercycle.
449 cls.LinuxPowercycle()
451 def MigrateInstance(self, instance, target, live):
452 """Migrate an instance.
454 @type instance: L{objects.Instance}
455 @param instance: the instance to be migrated
457 @param target: hostname (usually ip) of the target node
459 @param live: whether to do a live or non-live migration
462 raise HypervisorError("Migration is not supported by the LXC hypervisor")
464 def GetMigrationStatus(self, instance):
465 """Get the migration status
467 @type instance: L{objects.Instance}
468 @param instance: the instance that is being migrated
469 @rtype: L{objects.MigrationStatus}
470 @return: the status of the current migration (one of
471 L{constants.HV_MIGRATION_VALID_STATUSES}), plus any additional
472 progress info that can be retrieved from the hypervisor
475 raise HypervisorError("Migration is not supported by the LXC hypervisor")