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 import objects
35 from ganeti import pathutils
36 from ganeti.hypervisor import hv_base
37 from ganeti.errors import HypervisorError
41 """LXC-based virtualization.
42
43 TODO:
44 - move hardcoded parameters into hypervisor parameters, once we
45 have the container-parameter support
46
47 Problems/issues:
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
52 twice
53
54 """
55 _ROOT_DIR = pathutils.RUN_DIR + "/lxc"
56 _DEVS = [
57 "c 1:3",
58 "c 1:5",
59 "c 1:7",
60 "c 1:8",
61 "c 1:9",
62 "c 1:10",
63 "c 5:0",
64 "c 5:1",
65 "c 5:2",
66 "c 136:*",
67 ]
68 _DENIED_CAPABILITIES = [
69 "mac_override",
70
71
72 "sys_boot",
73 "sys_module",
74 "sys_time",
75 ]
76 _DIR_MODE = 0755
77
78 PARAMETERS = {
79 constants.HV_CPU_MASK: hv_base.OPT_CPU_MASK_CHECK,
80 }
81
85
86 @staticmethod
88 """Return the list of mountpoints under a given path.
89
90 """
91 result = []
92 for _, mountpoint, _, _ in utils.GetMounts():
93 if (mountpoint.startswith(path) and
94 mountpoint != path):
95 result.append(mountpoint)
96
97 result.sort(key=lambda x: x.count("/"), reverse=True)
98 return result
99
100 @classmethod
102 """Return the root directory for an instance.
103
104 """
105 return utils.PathJoin(cls._ROOT_DIR, instance_name)
106
107 @classmethod
109 """Return the configuration file for an instance.
110
111 """
112 return utils.PathJoin(cls._ROOT_DIR, instance_name + ".conf")
113
114 @classmethod
116 """Return the log file for an instance.
117
118 """
119 return utils.PathJoin(cls._ROOT_DIR, instance_name + ".log")
120
121 @classmethod
123 for _, mountpoint, fstype, _ in utils.GetMounts():
124 if fstype == "cgroup":
125 return mountpoint
126 raise errors.HypervisorError("The cgroup filesystem is not mounted")
127
128 @classmethod
130 """Return the list of CPU ids for an instance.
131
132 """
133 cgroup = cls._GetCgroupMountPoint()
134 try:
135 cpus = utils.ReadFile(utils.PathJoin(cgroup, 'lxc',
136 instance_name,
137 "cpuset.cpus"))
138 except EnvironmentError, err:
139 raise errors.HypervisorError("Getting CPU list for instance"
140 " %s failed: %s" % (instance_name, err))
141
142 return utils.ParseCpuMask(cpus)
143
144 @classmethod
146 """Return the memory limit for an instance
147
148 """
149 cgroup = cls._GetCgroupMountPoint()
150 try:
151 memory = int(utils.ReadFile(utils.PathJoin(cgroup, 'lxc',
152 instance_name,
153 "memory.limit_in_bytes")))
154 except EnvironmentError:
155
156 memory = 0
157
158 return memory
159
161 """Get the list of running instances.
162
163 """
164 return [iinfo[0] for iinfo in self.GetAllInstancesInfo()]
165
167 """Get instance properties.
168
169 @type instance_name: string
170 @param instance_name: the instance name
171 @rtype: tuple of strings
172 @return: (name, id, memory, vcpus, stat, times)
173
174 """
175
176
177 result = utils.RunCmd(["lxc-info", "-s", "-n", instance_name])
178 if result.failed:
179 raise errors.HypervisorError("Running lxc-info failed: %s" %
180 result.output)
181
182
183
184 _, state = result.stdout.rsplit(None, 1)
185 if state != "RUNNING":
186 return None
187
188 cpu_list = self._GetCgroupCpuList(instance_name)
189 memory = self._GetCgroupMemoryLimit(instance_name) / (1024 ** 2)
190 return (instance_name, 0, memory, len(cpu_list), 0, 0)
191
193 """Get properties of all instances.
194
195 @return: [(name, id, memory, vcpus, stat, times),...]
196
197 """
198 data = []
199 for name in os.listdir(self._ROOT_DIR):
200 try:
201 info = self.GetInstanceInfo(name)
202 except errors.HypervisorError:
203 continue
204 if info:
205 data.append(info)
206 return data
207
209 """Create an lxc.conf file for an instance.
210
211 """
212 out = []
213
214 out.append("lxc.utsname = %s" % instance.name)
215
216
217 out.append("lxc.pts = 255")
218
219 out.append("lxc.tty = 6")
220
221 console_log = utils.PathJoin(self._ROOT_DIR, instance.name + ".console")
222 try:
223 utils.WriteFile(console_log, data="", mode=constants.SECURE_FILE_MODE)
224 except EnvironmentError, err:
225 raise errors.HypervisorError("Creating console log file %s for"
226 " instance %s failed: %s" %
227 (console_log, instance.name, err))
228 out.append("lxc.console = %s" % console_log)
229
230
231 out.append("lxc.rootfs = %s" % root_dir)
232
233
234
235
236 if instance.hvparams[constants.HV_CPU_MASK]:
237 cpu_list = utils.ParseCpuMask(instance.hvparams[constants.HV_CPU_MASK])
238 cpus_in_mask = len(cpu_list)
239 if cpus_in_mask != instance.beparams["vcpus"]:
240 raise errors.HypervisorError("Number of VCPUs (%d) doesn't match"
241 " the number of CPUs in the"
242 " cpu_mask (%d)" %
243 (instance.beparams["vcpus"],
244 cpus_in_mask))
245 out.append("lxc.cgroup.cpuset.cpus = %s" %
246 instance.hvparams[constants.HV_CPU_MASK])
247
248
249
250 cgroup = self._GetCgroupMountPoint()
251 if os.path.exists(utils.PathJoin(cgroup, 'memory.limit_in_bytes')):
252 out.append("lxc.cgroup.memory.limit_in_bytes = %dM" %
253 instance.beparams[constants.BE_MAXMEM])
254
255 if os.path.exists(utils.PathJoin(cgroup, 'memory.memsw.limit_in_bytes')):
256 out.append("lxc.cgroup.memory.memsw.limit_in_bytes = %dM" %
257 instance.beparams[constants.BE_MAXMEM])
258
259
260
261 out.append("lxc.cgroup.devices.deny = a")
262 for devinfo in self._DEVS:
263 out.append("lxc.cgroup.devices.allow = %s rw" % devinfo)
264
265
266 for idx, nic in enumerate(instance.nics):
267 out.append("# NIC %d" % idx)
268 mode = nic.nicparams[constants.NIC_MODE]
269 link = nic.nicparams[constants.NIC_LINK]
270 if mode == constants.NIC_MODE_BRIDGED:
271 out.append("lxc.network.type = veth")
272 out.append("lxc.network.link = %s" % link)
273 else:
274 raise errors.HypervisorError("LXC hypervisor only supports"
275 " bridged mode (NIC %d has mode %s)" %
276 (idx, mode))
277 out.append("lxc.network.hwaddr = %s" % nic.mac)
278 out.append("lxc.network.flags = up")
279
280
281 for cap in self._DENIED_CAPABILITIES:
282 out.append("lxc.cap.drop = %s" % cap)
283
284 return "\n".join(out) + "\n"
285
286 - def StartInstance(self, instance, block_devices, startup_paused):
287 """Start an instance.
288
289 For LXC, we try to mount the block device and execute 'lxc-start'.
290 We use volatile containers.
291
292 """
293 root_dir = self._InstanceDir(instance.name)
294 try:
295 utils.EnsureDirs([(root_dir, self._DIR_MODE)])
296 except errors.GenericError, err:
297 raise HypervisorError("Creating instance directory failed: %s", str(err))
298
299 conf_file = self._InstanceConfFile(instance.name)
300 utils.WriteFile(conf_file, data=self._CreateConfigFile(instance, root_dir))
301
302 log_file = self._InstanceLogFile(instance.name)
303 if not os.path.exists(log_file):
304 try:
305 utils.WriteFile(log_file, data="", mode=constants.SECURE_FILE_MODE)
306 except EnvironmentError, err:
307 raise errors.HypervisorError("Creating hypervisor log file %s for"
308 " instance %s failed: %s" %
309 (log_file, instance.name, err))
310
311 if not os.path.ismount(root_dir):
312 if not block_devices:
313 raise HypervisorError("LXC needs at least one disk")
314
315 sda_dev_path = block_devices[0][1]
316 result = utils.RunCmd(["mount", sda_dev_path, root_dir])
317 if result.failed:
318 raise HypervisorError("Mounting the root dir of LXC instance %s"
319 " failed: %s" % (instance.name, result.output))
320 result = utils.RunCmd(["lxc-start", "-n", instance.name,
321 "-o", log_file,
322 "-l", "DEBUG",
323 "-f", conf_file, "-d"])
324 if result.failed:
325 raise HypervisorError("Running the lxc-start script failed: %s" %
326 result.output)
327
328 - def StopInstance(self, instance, force=False, retry=False, name=None,
329 timeout=None):
330 """Stop an instance.
331
332 This method has complicated cleanup tests, as we must:
333 - try to kill all leftover processes
334 - try to unmount any additional sub-mountpoints
335 - finally unmount the instance dir
336
337 """
338 assert(timeout is None or force is not None)
339
340 if name is None:
341 name = instance.name
342
343 timeout_cmd = []
344 if timeout is not None:
345 timeout_cmd.extend(["timeout", str(timeout)])
346
347 root_dir = self._InstanceDir(name)
348 if not os.path.exists(root_dir):
349 return
350
351 if name in self.ListInstances():
352
353 if not retry and not force:
354 result = utils.RunCmd(["chroot", root_dir, "poweroff"])
355 if result.failed:
356 raise HypervisorError("Running 'poweroff' on the instance"
357 " failed: %s" % result.output)
358 time.sleep(2)
359 result = utils.RunCmd(timeout_cmd.extend(["lxc-stop", "-n", name]))
360 if result.failed:
361 logging.warning("Error while doing lxc-stop for %s: %s", name,
362 result.output)
363
364 if not os.path.ismount(root_dir):
365 return
366
367 for mpath in self._GetMountSubdirs(root_dir):
368 result = utils.RunCmd(timeout_cmd.extend(["umount", mpath]))
369 if result.failed:
370 logging.warning("Error while umounting subpath %s for instance %s: %s",
371 mpath, name, result.output)
372
373 result = utils.RunCmd(timeout_cmd.extend(["umount", root_dir]))
374 if result.failed and force:
375 msg = ("Processes still alive in the chroot: %s" %
376 utils.RunCmd("fuser -vm %s" % root_dir).output)
377 logging.error(msg)
378 raise HypervisorError("Unmounting the chroot dir failed: %s (%s)" %
379 (result.output, msg))
380
382 """Reboot an instance.
383
384 This is not (yet) implemented (in Ganeti) for the LXC hypervisor.
385
386 """
387
388 raise HypervisorError("The LXC hypervisor doesn't implement the"
389 " reboot functionality")
390
392 """Balloon an instance memory to a certain value.
393
394 @type instance: L{objects.Instance}
395 @param instance: instance to be accepted
396 @type mem: int
397 @param mem: actual memory size to use for instance runtime
398
399 """
400
401 pass
402
404 """Return information about the node.
405
406 This is just a wrapper over the base GetLinuxNodeInfo method.
407
408 @return: a dict with the following keys (values in MiB):
409 - memory_total: the total memory size on the node
410 - memory_free: the available memory on the node for instances
411 - memory_dom0: the memory used by the node itself, if available
412
413 """
414 return self.GetLinuxNodeInfo()
415
416 @classmethod
426
428 """Verify the hypervisor.
429
430 For the LXC manager, it just checks the existence of the base dir.
431
432 @return: Problem description if something is wrong, C{None} otherwise
433
434 """
435 msgs = []
436
437 if not os.path.exists(self._ROOT_DIR):
438 msgs.append("The required directory '%s' does not exist" %
439 self._ROOT_DIR)
440
441 try:
442 self._GetCgroupMountPoint()
443 except errors.HypervisorError, err:
444 msgs.append(str(err))
445
446 return self._FormatVerifyResults(msgs)
447
448 @classmethod
450 """LXC powercycle, just a wrapper over Linux powercycle.
451
452 """
453 cls.LinuxPowercycle()
454
456 """Migrate an instance.
457
458 @type instance: L{objects.Instance}
459 @param instance: the instance to be migrated
460 @type target: string
461 @param target: hostname (usually ip) of the target node
462 @type live: boolean
463 @param live: whether to do a live or non-live migration
464
465 """
466 raise HypervisorError("Migration is not supported by the LXC hypervisor")
467
469 """Get the migration status
470
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
477
478 """
479 raise HypervisorError("Migration is not supported by the LXC hypervisor")
480