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.hypervisor import hv_base
36 from ganeti.errors import HypervisorError
39 class LXCHypervisor(hv_base.BaseHypervisor):
40 """LXC-based virtualization.
42 Since current (Spring 2010) distributions are not yet ready for
43 running under a container, the following changes must be done
46 - disable the kernel log component of sysklogd/rsyslog/etc.,
47 otherwise they will fail to read the log, and at least rsyslog
48 will fill the filesystem with error messages
51 - move hardcoded parameters into hypervisor parameters, once we
52 have the container-parameter support
53 - implement memory limits, but only optionally, depending on host
57 - LXC is very temperamental; in daemon mode, it succeeds or fails
58 in launching the instance silently, without any error
59 indication, and when failing it can leave network interfaces
60 around, and future successful startups will list the instance
62 - shutdown sequence of containers leaves the init 'dead', and the
63 container effectively stopped, but LXC still believes the
64 container to be running; need to investigate using the
65 notify_on_release and release_agent feature of cgroups
68 _ROOT_DIR = constants.RUN_GANETI_DIR + "/lxc"
73 "c 1:8", # /dev/random
74 "c 1:9", # /dev/urandom
77 "c 5:1", # /dev/console
79 "c 136:*", # first block of Unix98 PTY slaves
81 _DENIED_CAPABILITIES = [
82 "mac_override", # Allow MAC configuration or state changes
83 # TODO: remove sys_admin too, for safety
84 #"sys_admin", # Perform a range of system administration operations
85 "sys_boot", # Use reboot(2) and kexec_load(2)
86 "sys_module", # Load and unload kernel modules
87 "sys_time", # Set system clock, set real-time (hardware) clock
92 constants.HV_CPU_MASK: hv_base.OPT_CPU_MASK_CHECK,
96 hv_base.BaseHypervisor.__init__(self)
97 utils.EnsureDirs([(self._ROOT_DIR, self._DIR_MODE)])
100 def _GetMountSubdirs(path):
101 """Return the list of mountpoints under a given path.
105 for _, mountpoint, _, _ in utils.GetMounts():
106 if (mountpoint.startswith(path) and
108 result.append(mountpoint)
110 result.sort(key=lambda x: x.count("/"), reverse=True)
114 def _InstanceDir(cls, instance_name):
115 """Return the root directory for an instance.
118 return utils.PathJoin(cls._ROOT_DIR, instance_name)
121 def _InstanceConfFile(cls, instance_name):
122 """Return the configuration file for an instance.
125 return utils.PathJoin(cls._ROOT_DIR, instance_name + ".conf")
128 def _InstanceLogFile(cls, instance_name):
129 """Return the log file for an instance.
132 return utils.PathJoin(cls._ROOT_DIR, instance_name + ".log")
135 def _GetCgroupMountPoint(cls):
136 for _, mountpoint, fstype, _ in utils.GetMounts():
137 if fstype == "cgroup":
139 raise errors.HypervisorError("The cgroup filesystem is not mounted")
142 def _GetCgroupCpuList(cls, instance_name):
143 """Return the list of CPU ids for an instance.
146 cgroup = cls._GetCgroupMountPoint()
148 cpus = utils.ReadFile(utils.PathJoin(cgroup,
151 except EnvironmentError, err:
152 raise errors.HypervisorError("Getting CPU list for instance"
153 " %s failed: %s" % (instance_name, err))
155 return utils.ParseCpuMask(cpus)
157 def ListInstances(self):
158 """Get the list of running instances.
161 result = utils.RunCmd(["lxc-ls"])
163 raise errors.HypervisorError("Running lxc-ls failed: %s" % result.output)
164 return result.stdout.splitlines()
166 def GetInstanceInfo(self, instance_name):
167 """Get instance properties.
169 @type instance_name: string
170 @param instance_name: the instance name
172 @return: (name, id, memory, vcpus, stat, times)
175 # TODO: read container info from the cgroup mountpoint
177 result = utils.RunCmd(["lxc-info", "-n", instance_name])
179 raise errors.HypervisorError("Running lxc-info failed: %s" %
181 # lxc-info output examples:
182 # 'ganeti-lxc-test1' is STOPPED
183 # 'ganeti-lxc-test1' is RUNNING
184 _, state = result.stdout.rsplit(None, 1)
185 if state != "RUNNING":
188 cpu_list = self._GetCgroupCpuList(instance_name)
189 return (instance_name, 0, 0, len(cpu_list), 0, 0)
191 def GetAllInstancesInfo(self):
192 """Get properties of all instances.
194 @return: [(name, id, memory, vcpus, stat, times),...]
198 for name in self.ListInstances():
199 data.append(self.GetInstanceInfo(name))
202 def _CreateConfigFile(self, instance, root_dir):
203 """Create an lxc.conf file for an instance.
208 out.append("lxc.utsname = %s" % instance.name)
210 # separate pseudo-TTY instances
211 out.append("lxc.pts = 255")
213 out.append("lxc.tty = 6")
215 console_log = utils.PathJoin(self._ROOT_DIR, instance.name + ".console")
217 utils.WriteFile(console_log, data="", mode=constants.SECURE_FILE_MODE)
218 except EnvironmentError, err:
219 raise errors.HypervisorError("Creating console log file %s for"
220 " instance %s failed: %s" %
221 (console_log, instance.name, err))
222 out.append("lxc.console = %s" % console_log)
225 out.append("lxc.rootfs = %s" % root_dir)
227 # TODO: additional mounts, if we disable CAP_SYS_ADMIN
230 if instance.hvparams[constants.HV_CPU_MASK]:
231 cpu_list = utils.ParseCpuMask(instance.hvparams[constants.HV_CPU_MASK])
232 cpus_in_mask = len(cpu_list)
233 if cpus_in_mask != instance.beparams["vcpus"]:
234 raise errors.HypervisorError("Number of VCPUs (%d) doesn't match"
235 " the number of CPUs in the"
237 (instance.beparams["vcpus"],
239 out.append("lxc.cgroup.cpuset.cpus = %s" %
240 instance.hvparams[constants.HV_CPU_MASK])
243 # deny direct device access
244 out.append("lxc.cgroup.devices.deny = a")
245 for devinfo in self._DEVS:
246 out.append("lxc.cgroup.devices.allow = %s rw" % devinfo)
249 for idx, nic in enumerate(instance.nics):
250 out.append("# NIC %d" % idx)
251 mode = nic.nicparams[constants.NIC_MODE]
252 link = nic.nicparams[constants.NIC_LINK]
253 if mode == constants.NIC_MODE_BRIDGED:
254 out.append("lxc.network.type = veth")
255 out.append("lxc.network.link = %s" % link)
257 raise errors.HypervisorError("LXC hypervisor only supports"
258 " bridged mode (NIC %d has mode %s)" %
260 out.append("lxc.network.hwaddr = %s" % nic.mac)
261 out.append("lxc.network.flags = up")
264 for cap in self._DENIED_CAPABILITIES:
265 out.append("lxc.cap.drop = %s" % cap)
267 return "\n".join(out) + "\n"
269 def StartInstance(self, instance, block_devices, startup_paused):
270 """Start an instance.
272 For LCX, we try to mount the block device and execute 'lxc-start'.
273 We use volatile containers.
276 root_dir = self._InstanceDir(instance.name)
278 utils.EnsureDirs([(root_dir, self._DIR_MODE)])
279 except errors.GenericError, err:
280 raise HypervisorError("Creating instance directory failed: %s", str(err))
282 conf_file = self._InstanceConfFile(instance.name)
283 utils.WriteFile(conf_file, data=self._CreateConfigFile(instance, root_dir))
285 log_file = self._InstanceLogFile(instance.name)
286 if not os.path.exists(log_file):
288 utils.WriteFile(log_file, data="", mode=constants.SECURE_FILE_MODE)
289 except EnvironmentError, err:
290 raise errors.HypervisorError("Creating hypervisor log file %s for"
291 " instance %s failed: %s" %
292 (log_file, instance.name, err))
294 if not os.path.ismount(root_dir):
295 if not block_devices:
296 raise HypervisorError("LXC needs at least one disk")
298 sda_dev_path = block_devices[0][1]
299 result = utils.RunCmd(["mount", sda_dev_path, root_dir])
301 raise HypervisorError("Mounting the root dir of LXC instance %s"
302 " failed: %s" % (instance.name, result.output))
303 result = utils.RunCmd(["lxc-start", "-n", instance.name,
306 "-f", conf_file, "-d"])
308 raise HypervisorError("Running the lxc-start script failed: %s" %
311 def StopInstance(self, instance, force=False, retry=False, name=None):
314 This method has complicated cleanup tests, as we must:
315 - try to kill all leftover processes
316 - try to unmount any additional sub-mountpoints
317 - finally unmount the instance dir
323 root_dir = self._InstanceDir(name)
324 if not os.path.exists(root_dir):
327 if name in self.ListInstances():
328 # Signal init to shutdown; this is a hack
329 if not retry and not force:
330 result = utils.RunCmd(["chroot", root_dir, "poweroff"])
332 raise HypervisorError("Running 'poweroff' on the instance"
333 " failed: %s" % result.output)
335 result = utils.RunCmd(["lxc-stop", "-n", name])
337 logging.warning("Error while doing lxc-stop for %s: %s", name,
340 for mpath in self._GetMountSubdirs(root_dir):
341 result = utils.RunCmd(["umount", mpath])
343 logging.warning("Error while umounting subpath %s for instance %s: %s",
344 mpath, name, result.output)
346 result = utils.RunCmd(["umount", root_dir])
347 if result.failed and force:
348 msg = ("Processes still alive in the chroot: %s" %
349 utils.RunCmd("fuser -vm %s" % root_dir).output)
351 raise HypervisorError("Unmounting the chroot dir failed: %s (%s)" %
352 (result.output, msg))
354 def RebootInstance(self, instance):
355 """Reboot an instance.
357 This is not (yet) implemented (in Ganeti) for the LXC hypervisor.
360 # TODO: implement reboot
361 raise HypervisorError("The LXC hypervisor doesn't implement the"
362 " reboot functionality")
364 def GetNodeInfo(self):
365 """Return information about the node.
367 This is just a wrapper over the base GetLinuxNodeInfo method.
369 @return: a dict with the following keys (values in MiB):
370 - memory_total: the total memory size on the node
371 - memory_free: the available memory on the node for instances
372 - memory_dom0: the memory used by the node itself, if available
375 return self.GetLinuxNodeInfo()
378 def GetInstanceConsole(cls, instance, hvparams, beparams):
379 """Return a command for connecting to the console of an instance.
382 return objects.InstanceConsole(instance=instance.name,
383 kind=constants.CONS_SSH,
384 host=instance.primary_node,
385 user=constants.GANETI_RUNAS,
386 command=["lxc-console", "-n", instance.name])
389 """Verify the hypervisor.
391 For the chroot manager, it just checks the existence of the base dir.
394 if not os.path.exists(self._ROOT_DIR):
395 return "The required directory '%s' does not exist." % self._ROOT_DIR
398 def PowercycleNode(cls):
399 """LXC powercycle, just a wrapper over Linux powercycle.
402 cls.LinuxPowercycle()
404 def MigrateInstance(self, instance, target, live):
405 """Migrate an instance.
407 @type instance: L{objects.Instance}
408 @param instance: the instance to be migrated
410 @param target: hostname (usually ip) of the target node
412 @param live: whether to do a live or non-live migration
415 raise HypervisorError("Migration is not supported by the LXC hypervisor")
417 def GetMigrationStatus(self, instance):
418 """Get the migration status
420 @type instance: L{objects.Instance}
421 @param instance: the instance that is being migrated
422 @rtype: L{objects.MigrationStatus}
423 @return: the status of the current migration (one of
424 L{constants.HV_MIGRATION_VALID_STATUSES}), plus any additional
425 progress info that can be retrieved from the hypervisor
428 raise HypervisorError("Migration is not supported by the LXC hypervisor")