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