Make Xen config path a build-time option
[ganeti-local] / lib / hypervisor / hv_lxc.py
1 #
2 #
3
4 # Copyright (C) 2010 Google Inc.
5 #
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.
10 #
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.
15 #
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
19 # 02110-1301, USA.
20
21
22 """LXC hypervisor
23
24 """
25
26 import os
27 import os.path
28 import time
29 import logging
30
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
38
39
40 class LXCHypervisor(hv_base.BaseHypervisor):
41   """LXC-based virtualization.
42
43   Since current (Spring 2010) distributions are not yet ready for
44   running under a container, the following changes must be done
45   manually:
46     - remove udev
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
50
51   TODO:
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
55       kernel support
56
57   Problems/issues:
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
62       twice
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
67
68   """
69   _ROOT_DIR = pathutils.RUN_DIR + "/lxc"
70   _DEVS = [
71     "c 1:3",   # /dev/null
72     "c 1:5",   # /dev/zero
73     "c 1:7",   # /dev/full
74     "c 1:8",   # /dev/random
75     "c 1:9",   # /dev/urandom
76     "c 1:10",  # /dev/aio
77     "c 5:0",   # /dev/tty
78     "c 5:1",   # /dev/console
79     "c 5:2",   # /dev/ptmx
80     "c 136:*", # first block of Unix98 PTY slaves
81     ]
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
89     ]
90   _DIR_MODE = 0755
91
92   PARAMETERS = {
93     constants.HV_CPU_MASK: hv_base.OPT_CPU_MASK_CHECK,
94     }
95
96   def __init__(self):
97     hv_base.BaseHypervisor.__init__(self)
98     utils.EnsureDirs([(self._ROOT_DIR, self._DIR_MODE)])
99
100   @staticmethod
101   def _GetMountSubdirs(path):
102     """Return the list of mountpoints under a given path.
103
104     """
105     result = []
106     for _, mountpoint, _, _ in utils.GetMounts():
107       if (mountpoint.startswith(path) and
108           mountpoint != path):
109         result.append(mountpoint)
110
111     result.sort(key=lambda x: x.count("/"), reverse=True)
112     return result
113
114   @classmethod
115   def _InstanceDir(cls, instance_name):
116     """Return the root directory for an instance.
117
118     """
119     return utils.PathJoin(cls._ROOT_DIR, instance_name)
120
121   @classmethod
122   def _InstanceConfFile(cls, instance_name):
123     """Return the configuration file for an instance.
124
125     """
126     return utils.PathJoin(cls._ROOT_DIR, instance_name + ".conf")
127
128   @classmethod
129   def _InstanceLogFile(cls, instance_name):
130     """Return the log file for an instance.
131
132     """
133     return utils.PathJoin(cls._ROOT_DIR, instance_name + ".log")
134
135   @classmethod
136   def _GetCgroupMountPoint(cls):
137     for _, mountpoint, fstype, _ in utils.GetMounts():
138       if fstype == "cgroup":
139         return mountpoint
140     raise errors.HypervisorError("The cgroup filesystem is not mounted")
141
142   @classmethod
143   def _GetCgroupCpuList(cls, instance_name):
144     """Return the list of CPU ids for an instance.
145
146     """
147     cgroup = cls._GetCgroupMountPoint()
148     try:
149       cpus = utils.ReadFile(utils.PathJoin(cgroup,
150                                            instance_name,
151                                            "cpuset.cpus"))
152     except EnvironmentError, err:
153       raise errors.HypervisorError("Getting CPU list for instance"
154                                    " %s failed: %s" % (instance_name, err))
155
156     return utils.ParseCpuMask(cpus)
157
158   def ListInstances(self):
159     """Get the list of running instances.
160
161     """
162     result = utils.RunCmd(["lxc-ls"])
163     if result.failed:
164       raise errors.HypervisorError("Running lxc-ls failed: %s" % result.output)
165     return result.stdout.splitlines()
166
167   def GetInstanceInfo(self, instance_name):
168     """Get instance properties.
169
170     @type instance_name: string
171     @param instance_name: the instance name
172
173     @return: (name, id, memory, vcpus, stat, times)
174
175     """
176     # TODO: read container info from the cgroup mountpoint
177
178     result = utils.RunCmd(["lxc-info", "-n", instance_name])
179     if result.failed:
180       raise errors.HypervisorError("Running lxc-info failed: %s" %
181                                    result.output)
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":
187       return None
188
189     cpu_list = self._GetCgroupCpuList(instance_name)
190     return (instance_name, 0, 0, len(cpu_list), 0, 0)
191
192   def GetAllInstancesInfo(self):
193     """Get properties of all instances.
194
195     @return: [(name, id, memory, vcpus, stat, times),...]
196
197     """
198     data = []
199     for name in self.ListInstances():
200       data.append(self.GetInstanceInfo(name))
201     return data
202
203   def _CreateConfigFile(self, instance, root_dir):
204     """Create an lxc.conf file for an instance.
205
206     """
207     out = []
208     # hostname
209     out.append("lxc.utsname = %s" % instance.name)
210
211     # separate pseudo-TTY instances
212     out.append("lxc.pts = 255")
213     # standard TTYs
214     out.append("lxc.tty = 6")
215     # console log file
216     console_log = utils.PathJoin(self._ROOT_DIR, instance.name + ".console")
217     try:
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)
224
225     # root FS
226     out.append("lxc.rootfs = %s" % root_dir)
227
228     # TODO: additional mounts, if we disable CAP_SYS_ADMIN
229
230     # CPUs
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"
237                                      " cpu_mask (%d)" %
238                                      (instance.beparams["vcpus"],
239                                       cpus_in_mask))
240       out.append("lxc.cgroup.cpuset.cpus = %s" %
241                  instance.hvparams[constants.HV_CPU_MASK])
242
243     # Device control
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)
248
249     # Networking
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)
257       else:
258         raise errors.HypervisorError("LXC hypervisor only supports"
259                                      " bridged mode (NIC %d has mode %s)" %
260                                      (idx, mode))
261       out.append("lxc.network.hwaddr = %s" % nic.mac)
262       out.append("lxc.network.flags = up")
263
264     # Capabilities
265     for cap in self._DENIED_CAPABILITIES:
266       out.append("lxc.cap.drop = %s" % cap)
267
268     return "\n".join(out) + "\n"
269
270   def StartInstance(self, instance, block_devices, startup_paused):
271     """Start an instance.
272
273     For LCX, we try to mount the block device and execute 'lxc-start'.
274     We use volatile containers.
275
276     """
277     root_dir = self._InstanceDir(instance.name)
278     try:
279       utils.EnsureDirs([(root_dir, self._DIR_MODE)])
280     except errors.GenericError, err:
281       raise HypervisorError("Creating instance directory failed: %s", str(err))
282
283     conf_file = self._InstanceConfFile(instance.name)
284     utils.WriteFile(conf_file, data=self._CreateConfigFile(instance, root_dir))
285
286     log_file = self._InstanceLogFile(instance.name)
287     if not os.path.exists(log_file):
288       try:
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))
294
295     if not os.path.ismount(root_dir):
296       if not block_devices:
297         raise HypervisorError("LXC needs at least one disk")
298
299       sda_dev_path = block_devices[0][1]
300       result = utils.RunCmd(["mount", sda_dev_path, root_dir])
301       if result.failed:
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,
305                            "-o", log_file,
306                            "-l", "DEBUG",
307                            "-f", conf_file, "-d"])
308     if result.failed:
309       raise HypervisorError("Running the lxc-start script failed: %s" %
310                             result.output)
311
312   def StopInstance(self, instance, force=False, retry=False, name=None):
313     """Stop an instance.
314
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
319
320     """
321     if name is None:
322       name = instance.name
323
324     root_dir = self._InstanceDir(name)
325     if not os.path.exists(root_dir):
326       return
327
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"])
332         if result.failed:
333           raise HypervisorError("Running 'poweroff' on the instance"
334                                 " failed: %s" % result.output)
335       time.sleep(2)
336       result = utils.RunCmd(["lxc-stop", "-n", name])
337       if result.failed:
338         logging.warning("Error while doing lxc-stop for %s: %s", name,
339                         result.output)
340
341     for mpath in self._GetMountSubdirs(root_dir):
342       result = utils.RunCmd(["umount", mpath])
343       if result.failed:
344         logging.warning("Error while umounting subpath %s for instance %s: %s",
345                         mpath, name, result.output)
346
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)
351       logging.error(msg)
352       raise HypervisorError("Unmounting the chroot dir failed: %s (%s)" %
353                             (result.output, msg))
354
355   def RebootInstance(self, instance):
356     """Reboot an instance.
357
358     This is not (yet) implemented (in Ganeti) for the LXC hypervisor.
359
360     """
361     # TODO: implement reboot
362     raise HypervisorError("The LXC hypervisor doesn't implement the"
363                           " reboot functionality")
364
365   def BalloonInstanceMemory(self, instance, mem):
366     """Balloon an instance memory to a certain value.
367
368     @type instance: L{objects.Instance}
369     @param instance: instance to be accepted
370     @type mem: int
371     @param mem: actual memory size to use for instance runtime
372
373     """
374     # Currently lxc instances don't have memory limits
375     pass
376
377   def GetNodeInfo(self):
378     """Return information about the node.
379
380     This is just a wrapper over the base GetLinuxNodeInfo method.
381
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
386
387     """
388     return self.GetLinuxNodeInfo()
389
390   @classmethod
391   def GetInstanceConsole(cls, instance, hvparams, beparams):
392     """Return a command for connecting to the console of an instance.
393
394     """
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])
400
401   def Verify(self):
402     """Verify the hypervisor.
403
404     For the chroot manager, it just checks the existence of the base dir.
405
406     """
407     if not os.path.exists(self._ROOT_DIR):
408       return "The required directory '%s' does not exist." % self._ROOT_DIR
409
410   @classmethod
411   def PowercycleNode(cls):
412     """LXC powercycle, just a wrapper over Linux powercycle.
413
414     """
415     cls.LinuxPowercycle()
416
417   def MigrateInstance(self, instance, target, live):
418     """Migrate an instance.
419
420     @type instance: L{objects.Instance}
421     @param instance: the instance to be migrated
422     @type target: string
423     @param target: hostname (usually ip) of the target node
424     @type live: boolean
425     @param live: whether to do a live or non-live migration
426
427     """
428     raise HypervisorError("Migration is not supported by the LXC hypervisor")
429
430   def GetMigrationStatus(self, instance):
431     """Get the migration status
432
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
439
440     """
441     raise HypervisorError("Migration is not supported by the LXC hypervisor")