LXC: Use lxc-info to get instance info
[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-msg=W0611
33 from ganeti import utils
34 from ganeti.hypervisor import hv_base
35 from ganeti.errors import HypervisorError
36
37
38 class LXCHypervisor(hv_base.BaseHypervisor):
39   """LXC-based virtualization.
40
41   Since current (Spring 2010) distributions are not yet ready for
42   running under a container, the following changes must be done
43   manually:
44     - remove udev
45     - disable the kernel log component of sysklogd/rsyslog/etc.,
46       otherwise they will fail to read the log, and at least rsyslog
47       will fill the filesystem with error messages
48
49   TODO:
50     - move hardcoded parameters into hypervisor parameters, once we
51       have the container-parameter support
52     - implement memory limits, but only optionally, depending on host
53       kernel support
54
55   Problems/issues:
56     - LXC is very temperamental; in daemon mode, it succeeds or fails
57       in launching the instance silently, without any error
58       indication, and when failing it can leave network interfaces
59       around, and future successful startups will list the instance
60       twice
61     - shutdown sequence of containers leaves the init 'dead', and the
62       container effectively stopped, but LXC still believes the
63       container to be running; need to investigate using the
64       notify_on_release and release_agent feature of cgroups
65
66   """
67   _ROOT_DIR = constants.RUN_GANETI_DIR + "/lxc"
68   _LOG_FILE = constants.LOG_DIR + "hv_lxc.log"
69   _DEVS = [
70     "c 1:3",   # /dev/null
71     "c 1:5",   # /dev/zero
72     "c 1:7",   # /dev/full
73     "c 1:8",   # /dev/random
74     "c 1:9",   # /dev/urandom
75     "c 1:10",  # /dev/aio
76     "c 5:0",   # /dev/tty
77     "c 5:1",   # /dev/console
78     "c 5:2",   # /dev/ptmx
79     "c 136:*", # first block of Unix98 PTY slaves
80     ]
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
88     ]
89   _DIR_MODE = 0755
90
91   PARAMETERS = {
92     }
93
94   def __init__(self):
95     hv_base.BaseHypervisor.__init__(self)
96     utils.EnsureDirs([(self._ROOT_DIR, self._DIR_MODE)])
97
98   @staticmethod
99   def _GetMountSubdirs(path):
100     """Return the list of mountpoints under a given path.
101
102     """
103     result = []
104     for _, mountpoint, _, _ in utils.GetMounts():
105       if (mountpoint.startswith(path) and
106           mountpoint != path):
107         result.append(mountpoint)
108
109     result.sort(key=lambda x: x.count("/"), reverse=True)
110     return result
111
112   @classmethod
113   def _InstanceDir(cls, instance_name):
114     """Return the root directory for an instance.
115
116     """
117     return utils.PathJoin(cls._ROOT_DIR, instance_name)
118
119   @classmethod
120   def _InstanceConfFile(cls, instance_name):
121     """Return the configuration file for an instance.
122
123     """
124     return utils.PathJoin(cls._ROOT_DIR, instance_name + ".conf")
125
126   def ListInstances(self):
127     """Get the list of running instances.
128
129     """
130     result = utils.RunCmd(["lxc-ls"])
131     if result.failed:
132       raise errors.HypervisorError("Can't run lxc-ls: %s" % result.output)
133     return result.stdout.splitlines()
134
135   def GetInstanceInfo(self, instance_name):
136     """Get instance properties.
137
138     @type instance_name: string
139     @param instance_name: the instance name
140
141     @return: (name, id, memory, vcpus, stat, times)
142
143     """
144     # TODO: read container info from the cgroup mountpoint
145
146     result = utils.RunCmd(["lxc-info", "-n", instance_name])
147     if result.failed:
148       raise errors.HypervisorError("Can't run lxc-info: %s" % result.output)
149     # lxc-info output examples:
150     # 'ganeti-lxc-test1' is STOPPED
151     # 'ganeti-lxc-test1' is RUNNING
152     _, state = result.stdout.rsplit(None, 1)
153     if state == "RUNNING":
154       return (instance_name, 0, 0, 0, 0, 0)
155     return None
156
157   def GetAllInstancesInfo(self):
158     """Get properties of all instances.
159
160     @return: [(name, id, memory, vcpus, stat, times),...]
161
162     """
163     # TODO: read container info from the cgroup mountpoint
164     data = []
165     for name in self.ListInstances():
166       data.append((name, 0, 0, 0, 0, 0))
167     return data
168
169   def _CreateConfigFile(self, instance, root_dir):
170     """Create an lxc.conf file for an instance"""
171     out = []
172     # hostname
173     out.append("lxc.utsname = %s" % instance.name)
174
175     # separate pseudo-TTY instances
176     out.append("lxc.pts = 255")
177     # standard TTYs
178     out.append("lxc.tty = 6")
179     # console log file
180     console_log = utils.PathJoin(self._ROOT_DIR, instance.name + ".console")
181     try:
182       utils.WriteFile(console_log, data="", mode=constants.SECURE_FILE_MODE)
183     except EnvironmentError, err:
184       raise errors.HypervisorError("Creating console log file %s for"
185                                    " instance %s failed: %s" %
186                                    (console_log, instance.name, err))
187     out.append("lxc.console = %s" % console_log)
188
189     # root FS
190     out.append("lxc.rootfs = %s" % root_dir)
191
192     # TODO: additional mounts, if we disable CAP_SYS_ADMIN
193
194     # Device control
195     # deny direct device access
196     out.append("lxc.cgroup.devices.deny = a")
197     for devinfo in self._DEVS:
198       out.append("lxc.cgroup.devices.allow = %s rw" % devinfo)
199
200     # Networking
201     for idx, nic in enumerate(instance.nics):
202       out.append("# NIC %d" % idx)
203       mode = nic.nicparams[constants.NIC_MODE]
204       link = nic.nicparams[constants.NIC_LINK]
205       if mode == constants.NIC_MODE_BRIDGED:
206         out.append("lxc.network.type = veth")
207         out.append("lxc.network.link = %s" % link)
208       else:
209         raise errors.HypervisorError("LXC hypervisor only supports"
210                                      " bridged mode (NIC %d has mode %s)" %
211                                      (idx, mode))
212       out.append("lxc.network.hwaddr = %s" % nic.mac)
213       out.append("lxc.network.flags = up")
214
215     # Capabilities
216     for cap in self._DENIED_CAPABILITIES:
217       out.append("lxc.cap.drop = %s" % cap)
218
219     return "\n".join(out) + "\n"
220
221   def StartInstance(self, instance, block_devices):
222     """Start an instance.
223
224     For LCX, we try to mount the block device and execute 'lxc-start
225     start' (we use volatile containers).
226
227     """
228     root_dir = self._InstanceDir(instance.name)
229     try:
230       utils.EnsureDirs([(root_dir, self._DIR_MODE)])
231     except errors.GenericError, err:
232       raise HypervisorError("Cannot create instance directory: %s", str(err))
233
234     conf_file = self._InstanceConfFile(instance.name)
235     utils.WriteFile(conf_file, data=self._CreateConfigFile(instance, root_dir))
236
237     if not os.path.ismount(root_dir):
238       if not block_devices:
239         raise HypervisorError("LXC needs at least one disk")
240
241       sda_dev_path = block_devices[0][1]
242       result = utils.RunCmd(["mount", sda_dev_path, root_dir])
243       if result.failed:
244         raise HypervisorError("Can't mount the chroot dir: %s" % result.output)
245     # TODO: replace the global log file with a per-instance log file
246     result = utils.RunCmd(["lxc-start", "-n", instance.name,
247                            "-o", self._LOG_FILE, "-l", "DEBUG",
248                            "-f", conf_file, "-d"])
249     if result.failed:
250       raise HypervisorError("Running the lxc-start script failed: %s" %
251                             result.output)
252
253   def StopInstance(self, instance, force=False, retry=False, name=None):
254     """Stop an instance.
255
256     This method has complicated cleanup tests, as we must:
257       - try to kill all leftover processes
258       - try to unmount any additional sub-mountpoints
259       - finally unmount the instance dir
260
261     """
262     if name is None:
263       name = instance.name
264
265     root_dir = self._InstanceDir(name)
266     if not os.path.exists(root_dir):
267       return
268
269     if name in self.ListInstances():
270       # Signal init to shutdown; this is a hack
271       if not retry and not force:
272         result = utils.RunCmd(["chroot", root_dir, "poweroff"])
273         if result.failed:
274           raise HypervisorError("Can't run 'poweroff' for the instance: %s" %
275                                 result.output)
276       time.sleep(2)
277       result = utils.RunCmd(["lxc-stop", "-n", name])
278       if result.failed:
279         logging.warning("Error while doing lxc-stop for %s: %s", name,
280                         result.output)
281
282     for mpath in self._GetMountSubdirs(root_dir):
283       result = utils.RunCmd(["umount", mpath])
284       if result.failed:
285         logging.warning("Error while umounting subpath %s for instance %s: %s",
286                         mpath, name, result.output)
287
288     result = utils.RunCmd(["umount", root_dir])
289     if result.failed and force:
290       msg = ("Processes still alive in the chroot: %s" %
291              utils.RunCmd("fuser -vm %s" % root_dir).output)
292       logging.error(msg)
293       raise HypervisorError("Can't umount the chroot dir: %s (%s)" %
294                             (result.output, msg))
295
296   def RebootInstance(self, instance):
297     """Reboot an instance.
298
299     This is not (yet) implemented (in Ganeti) for the LXC hypervisor.
300
301     """
302     # TODO: implement reboot
303     raise HypervisorError("The LXC hypervisor doesn't implement the"
304                           " reboot functionality")
305
306   def GetNodeInfo(self):
307     """Return information about the node.
308
309     This is just a wrapper over the base GetLinuxNodeInfo method.
310
311     @return: a dict with the following keys (values in MiB):
312           - memory_total: the total memory size on the node
313           - memory_free: the available memory on the node for instances
314           - memory_dom0: the memory used by the node itself, if available
315
316     """
317     return self.GetLinuxNodeInfo()
318
319   @classmethod
320   def GetShellCommandForConsole(cls, instance, hvparams, beparams):
321     """Return a command for connecting to the console of an instance.
322
323     """
324     return "lxc-console -n %s" % instance.name
325
326   def Verify(self):
327     """Verify the hypervisor.
328
329     For the chroot manager, it just checks the existence of the base dir.
330
331     """
332     if not os.path.exists(self._ROOT_DIR):
333       return "The required directory '%s' does not exist." % self._ROOT_DIR
334
335   @classmethod
336   def PowercycleNode(cls):
337     """LXC powercycle, just a wrapper over Linux powercycle.
338
339     """
340     cls.LinuxPowercycle()
341
342   def MigrateInstance(self, instance, target, live):
343     """Migrate an instance.
344
345     @type instance: L{objects.Instance}
346     @param instance: the instance to be migrated
347     @type target: string
348     @param target: hostname (usually ip) of the target node
349     @type live: boolean
350     @param live: whether to do a live or non-live migration
351
352     """
353     raise HypervisorError("Migration not supported by the LXC hypervisor")