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, hvparams=None):
167 """Get instance properties.
169 @type instance_name: string
170 @param instance_name: the instance name
171 @type hvparams: dict of strings
172 @param hvparams: hvparams to be used with this instance
173 @rtype: tuple of strings
174 @return: (name, id, memory, vcpus, stat, times)
177 # TODO: read container info from the cgroup mountpoint
179 result = utils.RunCmd(["lxc-info", "-s", "-n", instance_name])
181 raise errors.HypervisorError("Running lxc-info failed: %s" %
183 # lxc-info output examples:
186 _, state = result.stdout.rsplit(None, 1)
187 if state != "RUNNING":
190 cpu_list = self._GetCgroupCpuList(instance_name)
191 memory = self._GetCgroupMemoryLimit(instance_name) / (1024 ** 2)
192 return (instance_name, 0, memory, len(cpu_list), 0, 0)
194 def GetAllInstancesInfo(self, hvparams=None):
195 """Get properties of all instances.
197 @type hvparams: dict of strings
198 @param hvparams: hypervisor parameter
199 @return: [(name, id, memory, vcpus, stat, times),...]
203 for name in os.listdir(self._ROOT_DIR):
205 info = self.GetInstanceInfo(name)
206 except errors.HypervisorError:
212 def _CreateConfigFile(self, instance, root_dir):
213 """Create an lxc.conf file for an instance.
218 out.append("lxc.utsname = %s" % instance.name)
220 # separate pseudo-TTY instances
221 out.append("lxc.pts = 255")
223 out.append("lxc.tty = 6")
225 console_log = utils.PathJoin(self._ROOT_DIR, instance.name + ".console")
227 utils.WriteFile(console_log, data="", mode=constants.SECURE_FILE_MODE)
228 except EnvironmentError, err:
229 raise errors.HypervisorError("Creating console log file %s for"
230 " instance %s failed: %s" %
231 (console_log, instance.name, err))
232 out.append("lxc.console = %s" % console_log)
235 out.append("lxc.rootfs = %s" % root_dir)
237 # TODO: additional mounts, if we disable CAP_SYS_ADMIN
240 if instance.hvparams[constants.HV_CPU_MASK]:
241 cpu_list = utils.ParseCpuMask(instance.hvparams[constants.HV_CPU_MASK])
242 cpus_in_mask = len(cpu_list)
243 if cpus_in_mask != instance.beparams["vcpus"]:
244 raise errors.HypervisorError("Number of VCPUs (%d) doesn't match"
245 " the number of CPUs in the"
247 (instance.beparams["vcpus"],
249 out.append("lxc.cgroup.cpuset.cpus = %s" %
250 instance.hvparams[constants.HV_CPU_MASK])
253 # Conditionally enable, memory resource controller might be disabled
254 cgroup = self._GetCgroupMountPoint()
255 if os.path.exists(utils.PathJoin(cgroup, 'memory.limit_in_bytes')):
256 out.append("lxc.cgroup.memory.limit_in_bytes = %dM" %
257 instance.beparams[constants.BE_MAXMEM])
259 if os.path.exists(utils.PathJoin(cgroup, 'memory.memsw.limit_in_bytes')):
260 out.append("lxc.cgroup.memory.memsw.limit_in_bytes = %dM" %
261 instance.beparams[constants.BE_MAXMEM])
264 # deny direct device access
265 out.append("lxc.cgroup.devices.deny = a")
266 for devinfo in self._DEVS:
267 out.append("lxc.cgroup.devices.allow = %s rw" % devinfo)
270 for idx, nic in enumerate(instance.nics):
271 out.append("# NIC %d" % idx)
272 mode = nic.nicparams[constants.NIC_MODE]
273 link = nic.nicparams[constants.NIC_LINK]
274 if mode == constants.NIC_MODE_BRIDGED:
275 out.append("lxc.network.type = veth")
276 out.append("lxc.network.link = %s" % link)
278 raise errors.HypervisorError("LXC hypervisor only supports"
279 " bridged mode (NIC %d has mode %s)" %
281 out.append("lxc.network.hwaddr = %s" % nic.mac)
282 out.append("lxc.network.flags = up")
285 for cap in self._DENIED_CAPABILITIES:
286 out.append("lxc.cap.drop = %s" % cap)
288 return "\n".join(out) + "\n"
290 def StartInstance(self, instance, block_devices, startup_paused):
291 """Start an instance.
293 For LXC, we try to mount the block device and execute 'lxc-start'.
294 We use volatile containers.
297 root_dir = self._InstanceDir(instance.name)
299 utils.EnsureDirs([(root_dir, self._DIR_MODE)])
300 except errors.GenericError, err:
301 raise HypervisorError("Creating instance directory failed: %s", str(err))
303 conf_file = self._InstanceConfFile(instance.name)
304 utils.WriteFile(conf_file, data=self._CreateConfigFile(instance, root_dir))
306 log_file = self._InstanceLogFile(instance.name)
307 if not os.path.exists(log_file):
309 utils.WriteFile(log_file, data="", mode=constants.SECURE_FILE_MODE)
310 except EnvironmentError, err:
311 raise errors.HypervisorError("Creating hypervisor log file %s for"
312 " instance %s failed: %s" %
313 (log_file, instance.name, err))
315 if not os.path.ismount(root_dir):
316 if not block_devices:
317 raise HypervisorError("LXC needs at least one disk")
319 sda_dev_path = block_devices[0][1]
320 result = utils.RunCmd(["mount", sda_dev_path, root_dir])
322 raise HypervisorError("Mounting the root dir of LXC instance %s"
323 " failed: %s" % (instance.name, result.output))
324 result = utils.RunCmd(["lxc-start", "-n", instance.name,
327 "-f", conf_file, "-d"])
329 raise HypervisorError("Running the lxc-start script failed: %s" %
332 def StopInstance(self, instance, force=False, retry=False, name=None):
335 This method has complicated cleanup tests, as we must:
336 - try to kill all leftover processes
337 - try to unmount any additional sub-mountpoints
338 - finally unmount the instance dir
344 root_dir = self._InstanceDir(name)
345 if not os.path.exists(root_dir):
348 if name in self.ListInstances():
349 # Signal init to shutdown; this is a hack
350 if not retry and not force:
351 result = utils.RunCmd(["chroot", root_dir, "poweroff"])
353 raise HypervisorError("Running 'poweroff' on the instance"
354 " failed: %s" % result.output)
356 result = utils.RunCmd(["lxc-stop", "-n", name])
358 logging.warning("Error while doing lxc-stop for %s: %s", name,
361 if not os.path.ismount(root_dir):
364 for mpath in self._GetMountSubdirs(root_dir):
365 result = utils.RunCmd(["umount", mpath])
367 logging.warning("Error while umounting subpath %s for instance %s: %s",
368 mpath, name, result.output)
370 result = utils.RunCmd(["umount", root_dir])
371 if result.failed and force:
372 msg = ("Processes still alive in the chroot: %s" %
373 utils.RunCmd("fuser -vm %s" % root_dir).output)
375 raise HypervisorError("Unmounting the chroot dir failed: %s (%s)" %
376 (result.output, msg))
378 def RebootInstance(self, instance):
379 """Reboot an instance.
381 This is not (yet) implemented (in Ganeti) for the LXC hypervisor.
384 # TODO: implement reboot
385 raise HypervisorError("The LXC hypervisor doesn't implement the"
386 " reboot functionality")
388 def BalloonInstanceMemory(self, instance, mem):
389 """Balloon an instance memory to a certain value.
391 @type instance: L{objects.Instance}
392 @param instance: instance to be accepted
394 @param mem: actual memory size to use for instance runtime
397 # Currently lxc instances don't have memory limits
400 def GetNodeInfo(self, hvparams=None):
401 """Return information about the node.
403 See L{BaseHypervisor.GetLinuxNodeInfo}.
406 return self.GetLinuxNodeInfo()
409 def GetInstanceConsole(cls, instance, primary_node, hvparams, beparams):
410 """Return a command for connecting to the console of an instance.
413 return objects.InstanceConsole(instance=instance.name,
414 kind=constants.CONS_SSH,
415 host=primary_node.name,
416 user=constants.SSH_CONSOLE_USER,
417 command=["lxc-console", "-n", instance.name])
419 def Verify(self, hvparams=None):
420 """Verify the hypervisor.
422 For the LXC manager, it just checks the existence of the base dir.
424 @type hvparams: dict of strings
425 @param hvparams: hypervisor parameters to be verified against; not used here
427 @return: Problem description if something is wrong, C{None} otherwise
432 if not os.path.exists(self._ROOT_DIR):
433 msgs.append("The required directory '%s' does not exist" %
437 self._GetCgroupMountPoint()
438 except errors.HypervisorError, err:
439 msgs.append(str(err))
441 return self._FormatVerifyResults(msgs)
444 def PowercycleNode(cls, hvparams=None):
445 """LXC powercycle, just a wrapper over Linux powercycle.
447 @type hvparams: dict of strings
448 @param hvparams: hypervisor params to be used on this node
451 cls.LinuxPowercycle()
453 def MigrateInstance(self, cluster_name, instance, target, live):
454 """Migrate an instance.
456 @type cluster_name: string
457 @param cluster_name: name of the cluster
458 @type instance: L{objects.Instance}
459 @param instance: the instance to be migrated
461 @param target: hostname (usually ip) of the target node
463 @param live: whether to do a live or non-live migration
466 raise HypervisorError("Migration is not supported by the LXC hypervisor")
468 def GetMigrationStatus(self, instance):
469 """Get the migration status
471 @type instance: L{objects.Instance}
472 @param instance: the instance that is being migrated
473 @rtype: L{objects.MigrationStatus}
474 @return: the status of the current migration (one of
475 L{constants.HV_MIGRATION_VALID_STATUSES}), plus any additional
476 progress info that can be retrieved from the hypervisor
479 raise HypervisorError("Migration is not supported by the LXC hypervisor")