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 """Stop an instance.
330
331 This method has complicated cleanup tests, as we must:
332 - try to kill all leftover processes
333 - try to unmount any additional sub-mountpoints
334 - finally unmount the instance dir
335
336 """
337 if name is None:
338 name = instance.name
339
340 root_dir = self._InstanceDir(name)
341 if not os.path.exists(root_dir):
342 return
343
344 if name in self.ListInstances():
345
346 if not retry and not force:
347 result = utils.RunCmd(["chroot", root_dir, "poweroff"])
348 if result.failed:
349 raise HypervisorError("Running 'poweroff' on the instance"
350 " failed: %s" % result.output)
351 time.sleep(2)
352 result = utils.RunCmd(["lxc-stop", "-n", name])
353 if result.failed:
354 logging.warning("Error while doing lxc-stop for %s: %s", name,
355 result.output)
356
357 if not os.path.ismount(root_dir):
358 return
359
360 for mpath in self._GetMountSubdirs(root_dir):
361 result = utils.RunCmd(["umount", mpath])
362 if result.failed:
363 logging.warning("Error while umounting subpath %s for instance %s: %s",
364 mpath, name, result.output)
365
366 result = utils.RunCmd(["umount", root_dir])
367 if result.failed and force:
368 msg = ("Processes still alive in the chroot: %s" %
369 utils.RunCmd("fuser -vm %s" % root_dir).output)
370 logging.error(msg)
371 raise HypervisorError("Unmounting the chroot dir failed: %s (%s)" %
372 (result.output, msg))
373
375 """Reboot an instance.
376
377 This is not (yet) implemented (in Ganeti) for the LXC hypervisor.
378
379 """
380
381 raise HypervisorError("The LXC hypervisor doesn't implement the"
382 " reboot functionality")
383
385 """Balloon an instance memory to a certain value.
386
387 @type instance: L{objects.Instance}
388 @param instance: instance to be accepted
389 @type mem: int
390 @param mem: actual memory size to use for instance runtime
391
392 """
393
394 pass
395
397 """Return information about the node.
398
399 This is just a wrapper over the base GetLinuxNodeInfo method.
400
401 @return: a dict with the following keys (values in MiB):
402 - memory_total: the total memory size on the node
403 - memory_free: the available memory on the node for instances
404 - memory_dom0: the memory used by the node itself, if available
405
406 """
407 return self.GetLinuxNodeInfo()
408
409 @classmethod
419
421 """Verify the hypervisor.
422
423 For the LXC manager, it just checks the existence of the base dir.
424
425 @return: Problem description if something is wrong, C{None} otherwise
426
427 """
428 msgs = []
429
430 if not os.path.exists(self._ROOT_DIR):
431 msgs.append("The required directory '%s' does not exist" %
432 self._ROOT_DIR)
433
434 try:
435 self._GetCgroupMountPoint()
436 except errors.HypervisorError, err:
437 msgs.append(str(err))
438
439 return self._FormatVerifyResults(msgs)
440
441 @classmethod
443 """LXC powercycle, just a wrapper over Linux powercycle.
444
445 """
446 cls.LinuxPowercycle()
447
449 """Migrate an instance.
450
451 @type instance: L{objects.Instance}
452 @param instance: the instance to be migrated
453 @type target: string
454 @param target: hostname (usually ip) of the target node
455 @type live: boolean
456 @param live: whether to do a live or non-live migration
457
458 """
459 raise HypervisorError("Migration is not supported by the LXC hypervisor")
460
462 """Get the migration status
463
464 @type instance: L{objects.Instance}
465 @param instance: the instance that is being migrated
466 @rtype: L{objects.MigrationStatus}
467 @return: the status of the current migration (one of
468 L{constants.HV_MIGRATION_VALID_STATUSES}), plus any additional
469 progress info that can be retrieved from the hypervisor
470
471 """
472 raise HypervisorError("Migration is not supported by the LXC hypervisor")
473