1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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
33 from ganeti import utils
34 from ganeti.hypervisor import hv_base
35 from ganeti.errors import HypervisorError
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 _DEVS = [
69 "c 1:3",
70 "c 1:5",
71 "c 1:7",
72 "c 1:8",
73 "c 1:9",
74 "c 1:10",
75 "c 5:0",
76 "c 5:1",
77 "c 5:2",
78 "c 136:*",
79 ]
80 _DENIED_CAPABILITIES = [
81 "mac_override",
82
83
84 "sys_boot",
85 "sys_module",
86 "sys_time",
87 ]
88 _DIR_MODE = 0755
89
90 PARAMETERS = {
91 constants.HV_CPU_MASK: hv_base.OPT_CPU_MASK_CHECK,
92 }
93
97
98 @staticmethod
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
114 """Return the root directory for an instance.
115
116 """
117 return utils.PathJoin(cls._ROOT_DIR, instance_name)
118
119 @classmethod
121 """Return the configuration file for an instance.
122
123 """
124 return utils.PathJoin(cls._ROOT_DIR, instance_name + ".conf")
125
126 @classmethod
128 """Return the log file for an instance.
129
130 """
131 return utils.PathJoin(cls._ROOT_DIR, instance_name + ".log")
132
133 @classmethod
135 for _, mountpoint, fstype, _ in utils.GetMounts():
136 if fstype == "cgroup":
137 return mountpoint
138 raise errors.HypervisorError("The cgroup filesystem is not mounted")
139
140 @classmethod
155
157 """Get the list of running instances.
158
159 """
160 result = utils.RunCmd(["lxc-ls"])
161 if result.failed:
162 raise errors.HypervisorError("Running lxc-ls failed: %s" % result.output)
163 return result.stdout.splitlines()
164
166 """Get instance properties.
167
168 @type instance_name: string
169 @param instance_name: the instance name
170
171 @return: (name, id, memory, vcpus, stat, times)
172
173 """
174
175
176 result = utils.RunCmd(["lxc-info", "-n", instance_name])
177 if result.failed:
178 raise errors.HypervisorError("Running lxc-info failed: %s" %
179 result.output)
180
181
182
183 _, state = result.stdout.rsplit(None, 1)
184 if state != "RUNNING":
185 return None
186
187 cpu_list = self._GetCgroupCpuList(instance_name)
188 return (instance_name, 0, 0, len(cpu_list), 0, 0)
189
191 """Get properties of all instances.
192
193 @return: [(name, id, memory, vcpus, stat, times),...]
194
195 """
196 data = []
197 for name in self.ListInstances():
198 data.append(self.GetInstanceInfo(name))
199 return data
200
202 """Create an lxc.conf file for an instance.
203
204 """
205 out = []
206
207 out.append("lxc.utsname = %s" % instance.name)
208
209
210 out.append("lxc.pts = 255")
211
212 out.append("lxc.tty = 6")
213
214 console_log = utils.PathJoin(self._ROOT_DIR, instance.name + ".console")
215 try:
216 utils.WriteFile(console_log, data="", mode=constants.SECURE_FILE_MODE)
217 except EnvironmentError, err:
218 raise errors.HypervisorError("Creating console log file %s for"
219 " instance %s failed: %s" %
220 (console_log, instance.name, err))
221 out.append("lxc.console = %s" % console_log)
222
223
224 out.append("lxc.rootfs = %s" % root_dir)
225
226
227
228
229 if instance.hvparams[constants.HV_CPU_MASK]:
230 cpu_list = utils.ParseCpuMask(instance.hvparams[constants.HV_CPU_MASK])
231 cpus_in_mask = len(cpu_list)
232 if cpus_in_mask != instance.beparams["vcpus"]:
233 raise errors.HypervisorError("Number of VCPUs (%d) doesn't match"
234 " the number of CPUs in the"
235 " cpu_mask (%d)" %
236 (instance.beparams["vcpus"],
237 cpus_in_mask))
238 out.append("lxc.cgroup.cpuset.cpus = %s" %
239 instance.hvparams[constants.HV_CPU_MASK])
240
241
242
243 out.append("lxc.cgroup.devices.deny = a")
244 for devinfo in self._DEVS:
245 out.append("lxc.cgroup.devices.allow = %s rw" % devinfo)
246
247
248 for idx, nic in enumerate(instance.nics):
249 out.append("# NIC %d" % idx)
250 mode = nic.nicparams[constants.NIC_MODE]
251 link = nic.nicparams[constants.NIC_LINK]
252 if mode == constants.NIC_MODE_BRIDGED:
253 out.append("lxc.network.type = veth")
254 out.append("lxc.network.link = %s" % link)
255 else:
256 raise errors.HypervisorError("LXC hypervisor only supports"
257 " bridged mode (NIC %d has mode %s)" %
258 (idx, mode))
259 out.append("lxc.network.hwaddr = %s" % nic.mac)
260 out.append("lxc.network.flags = up")
261
262
263 for cap in self._DENIED_CAPABILITIES:
264 out.append("lxc.cap.drop = %s" % cap)
265
266 return "\n".join(out) + "\n"
267
269 """Start an instance.
270
271 For LCX, we try to mount the block device and execute 'lxc-start'.
272 We use volatile containers.
273
274 """
275 root_dir = self._InstanceDir(instance.name)
276 try:
277 utils.EnsureDirs([(root_dir, self._DIR_MODE)])
278 except errors.GenericError, err:
279 raise HypervisorError("Creating instance directory failed: %s", str(err))
280
281 conf_file = self._InstanceConfFile(instance.name)
282 utils.WriteFile(conf_file, data=self._CreateConfigFile(instance, root_dir))
283
284 log_file = self._InstanceLogFile(instance.name)
285 if not os.path.exists(log_file):
286 try:
287 utils.WriteFile(log_file, data="", mode=constants.SECURE_FILE_MODE)
288 except EnvironmentError, err:
289 raise errors.HypervisorError("Creating hypervisor log file %s for"
290 " instance %s failed: %s" %
291 (log_file, instance.name, err))
292
293 if not os.path.ismount(root_dir):
294 if not block_devices:
295 raise HypervisorError("LXC needs at least one disk")
296
297 sda_dev_path = block_devices[0][1]
298 result = utils.RunCmd(["mount", sda_dev_path, root_dir])
299 if result.failed:
300 raise HypervisorError("Mounting the root dir of LXC instance %s"
301 " failed: %s" % (instance.name, result.output))
302 result = utils.RunCmd(["lxc-start", "-n", instance.name,
303 "-o", log_file,
304 "-l", "DEBUG",
305 "-f", conf_file, "-d"])
306 if result.failed:
307 raise HypervisorError("Running the lxc-start script failed: %s" %
308 result.output)
309
310 - def StopInstance(self, instance, force=False, retry=False, name=None):
311 """Stop an instance.
312
313 This method has complicated cleanup tests, as we must:
314 - try to kill all leftover processes
315 - try to unmount any additional sub-mountpoints
316 - finally unmount the instance dir
317
318 """
319 if name is None:
320 name = instance.name
321
322 root_dir = self._InstanceDir(name)
323 if not os.path.exists(root_dir):
324 return
325
326 if name in self.ListInstances():
327
328 if not retry and not force:
329 result = utils.RunCmd(["chroot", root_dir, "poweroff"])
330 if result.failed:
331 raise HypervisorError("Running 'poweroff' on the instance"
332 " failed: %s" % result.output)
333 time.sleep(2)
334 result = utils.RunCmd(["lxc-stop", "-n", name])
335 if result.failed:
336 logging.warning("Error while doing lxc-stop for %s: %s", name,
337 result.output)
338
339 for mpath in self._GetMountSubdirs(root_dir):
340 result = utils.RunCmd(["umount", mpath])
341 if result.failed:
342 logging.warning("Error while umounting subpath %s for instance %s: %s",
343 mpath, name, result.output)
344
345 result = utils.RunCmd(["umount", root_dir])
346 if result.failed and force:
347 msg = ("Processes still alive in the chroot: %s" %
348 utils.RunCmd("fuser -vm %s" % root_dir).output)
349 logging.error(msg)
350 raise HypervisorError("Unmounting the chroot dir failed: %s (%s)" %
351 (result.output, msg))
352
354 """Reboot an instance.
355
356 This is not (yet) implemented (in Ganeti) for the LXC hypervisor.
357
358 """
359
360 raise HypervisorError("The LXC hypervisor doesn't implement the"
361 " reboot functionality")
362
364 """Return information about the node.
365
366 This is just a wrapper over the base GetLinuxNodeInfo method.
367
368 @return: a dict with the following keys (values in MiB):
369 - memory_total: the total memory size on the node
370 - memory_free: the available memory on the node for instances
371 - memory_dom0: the memory used by the node itself, if available
372
373 """
374 return self.GetLinuxNodeInfo()
375
376 @classmethod
378 """Return a command for connecting to the console of an instance.
379
380 """
381 return "lxc-console -n %s" % instance.name
382
384 """Verify the hypervisor.
385
386 For the chroot manager, it just checks the existence of the base dir.
387
388 """
389 if not os.path.exists(self._ROOT_DIR):
390 return "The required directory '%s' does not exist." % self._ROOT_DIR
391
392 @classmethod
394 """LXC powercycle, just a wrapper over Linux powercycle.
395
396 """
397 cls.LinuxPowercycle()
398
400 """Migrate an instance.
401
402 @type instance: L{objects.Instance}
403 @param instance: the instance to be migrated
404 @type target: string
405 @param target: hostname (usually ip) of the target node
406 @type live: boolean
407 @param live: whether to do a live or non-live migration
408
409 """
410 raise HypervisorError("Migration is not supported by the LXC hypervisor")
411