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 @type hvparams: dict of strings
172 @param hvparams: hvparams to be used with this instance
173 @rtype: tuple of strings
174 @return: (name, id, memory, vcpus, stat, times)
175
176 """
177
178
179 result = utils.RunCmd(["lxc-info", "-s", "-n", instance_name])
180 if result.failed:
181 raise errors.HypervisorError("Running lxc-info failed: %s" %
182 result.output)
183
184
185
186 _, state = result.stdout.rsplit(None, 1)
187 if state != "RUNNING":
188 return None
189
190 cpu_list = self._GetCgroupCpuList(instance_name)
191 memory = self._GetCgroupMemoryLimit(instance_name) / (1024 ** 2)
192 return (instance_name, 0, memory, len(cpu_list), 0, 0)
193
195 """Get properties of all instances.
196
197 @type hvparams: dict of strings
198 @param hvparams: hypervisor parameter
199 @return: [(name, id, memory, vcpus, stat, times),...]
200
201 """
202 data = []
203 for name in os.listdir(self._ROOT_DIR):
204 try:
205 info = self.GetInstanceInfo(name)
206 except errors.HypervisorError:
207 continue
208 if info:
209 data.append(info)
210 return data
211
213 """Create an lxc.conf file for an instance.
214
215 """
216 out = []
217
218 out.append("lxc.utsname = %s" % instance.name)
219
220
221 out.append("lxc.pts = 255")
222
223 out.append("lxc.tty = 6")
224
225 console_log = utils.PathJoin(self._ROOT_DIR, instance.name + ".console")
226 try:
227 utils.WriteFile(console_log, data="", mode=constants.SECURE_FILE_MODE)
228 except EnvironmentError, err:
229 raise errors.HypervisorError("Creating console log file %s for"
230 " instance %s failed: %s" %
231 (console_log, instance.name, err))
232 out.append("lxc.console = %s" % console_log)
233
234
235 out.append("lxc.rootfs = %s" % root_dir)
236
237
238
239
240 if instance.hvparams[constants.HV_CPU_MASK]:
241 cpu_list = utils.ParseCpuMask(instance.hvparams[constants.HV_CPU_MASK])
242 cpus_in_mask = len(cpu_list)
243 if cpus_in_mask != instance.beparams["vcpus"]:
244 raise errors.HypervisorError("Number of VCPUs (%d) doesn't match"
245 " the number of CPUs in the"
246 " cpu_mask (%d)" %
247 (instance.beparams["vcpus"],
248 cpus_in_mask))
249 out.append("lxc.cgroup.cpuset.cpus = %s" %
250 instance.hvparams[constants.HV_CPU_MASK])
251
252
253
254 cgroup = self._GetCgroupMountPoint()
255 if os.path.exists(utils.PathJoin(cgroup, 'memory.limit_in_bytes')):
256 out.append("lxc.cgroup.memory.limit_in_bytes = %dM" %
257 instance.beparams[constants.BE_MAXMEM])
258
259 if os.path.exists(utils.PathJoin(cgroup, 'memory.memsw.limit_in_bytes')):
260 out.append("lxc.cgroup.memory.memsw.limit_in_bytes = %dM" %
261 instance.beparams[constants.BE_MAXMEM])
262
263
264
265 out.append("lxc.cgroup.devices.deny = a")
266 for devinfo in self._DEVS:
267 out.append("lxc.cgroup.devices.allow = %s rw" % devinfo)
268
269
270 for idx, nic in enumerate(instance.nics):
271 out.append("# NIC %d" % idx)
272 mode = nic.nicparams[constants.NIC_MODE]
273 link = nic.nicparams[constants.NIC_LINK]
274 if mode == constants.NIC_MODE_BRIDGED:
275 out.append("lxc.network.type = veth")
276 out.append("lxc.network.link = %s" % link)
277 else:
278 raise errors.HypervisorError("LXC hypervisor only supports"
279 " bridged mode (NIC %d has mode %s)" %
280 (idx, mode))
281 out.append("lxc.network.hwaddr = %s" % nic.mac)
282 out.append("lxc.network.flags = up")
283
284
285 for cap in self._DENIED_CAPABILITIES:
286 out.append("lxc.cap.drop = %s" % cap)
287
288 return "\n".join(out) + "\n"
289
290 - def StartInstance(self, instance, block_devices, startup_paused):
291 """Start an instance.
292
293 For LXC, we try to mount the block device and execute 'lxc-start'.
294 We use volatile containers.
295
296 """
297 root_dir = self._InstanceDir(instance.name)
298 try:
299 utils.EnsureDirs([(root_dir, self._DIR_MODE)])
300 except errors.GenericError, err:
301 raise HypervisorError("Creating instance directory failed: %s", str(err))
302
303 conf_file = self._InstanceConfFile(instance.name)
304 utils.WriteFile(conf_file, data=self._CreateConfigFile(instance, root_dir))
305
306 log_file = self._InstanceLogFile(instance.name)
307 if not os.path.exists(log_file):
308 try:
309 utils.WriteFile(log_file, data="", mode=constants.SECURE_FILE_MODE)
310 except EnvironmentError, err:
311 raise errors.HypervisorError("Creating hypervisor log file %s for"
312 " instance %s failed: %s" %
313 (log_file, instance.name, err))
314
315 if not os.path.ismount(root_dir):
316 if not block_devices:
317 raise HypervisorError("LXC needs at least one disk")
318
319 sda_dev_path = block_devices[0][1]
320 result = utils.RunCmd(["mount", sda_dev_path, root_dir])
321 if result.failed:
322 raise HypervisorError("Mounting the root dir of LXC instance %s"
323 " failed: %s" % (instance.name, result.output))
324 result = utils.RunCmd(["lxc-start", "-n", instance.name,
325 "-o", log_file,
326 "-l", "DEBUG",
327 "-f", conf_file, "-d"])
328 if result.failed:
329 raise HypervisorError("Running the lxc-start script failed: %s" %
330 result.output)
331
332 - def StopInstance(self, instance, force=False, retry=False, name=None,
333 timeout=None):
334 """Stop an instance.
335
336 This method has complicated cleanup tests, as we must:
337 - try to kill all leftover processes
338 - try to unmount any additional sub-mountpoints
339 - finally unmount the instance dir
340
341 """
342 assert(timeout is None or force is not None)
343
344 if name is None:
345 name = instance.name
346
347 timeout_cmd = []
348 if timeout is not None:
349 timeout_cmd.extend(["timeout", str(timeout)])
350
351 root_dir = self._InstanceDir(name)
352 if not os.path.exists(root_dir):
353 return
354
355 if name in self.ListInstances():
356
357 if not retry and not force:
358 result = utils.RunCmd(["chroot", root_dir, "poweroff"])
359 if result.failed:
360 raise HypervisorError("Running 'poweroff' on the instance"
361 " failed: %s" % result.output)
362 time.sleep(2)
363 result = utils.RunCmd(timeout_cmd.extend(["lxc-stop", "-n", name]))
364 if result.failed:
365 logging.warning("Error while doing lxc-stop for %s: %s", name,
366 result.output)
367
368 if not os.path.ismount(root_dir):
369 return
370
371 for mpath in self._GetMountSubdirs(root_dir):
372 result = utils.RunCmd(timeout_cmd.extend(["umount", mpath]))
373 if result.failed:
374 logging.warning("Error while umounting subpath %s for instance %s: %s",
375 mpath, name, result.output)
376
377 result = utils.RunCmd(timeout_cmd.extend(["umount", root_dir]))
378 if result.failed and force:
379 msg = ("Processes still alive in the chroot: %s" %
380 utils.RunCmd("fuser -vm %s" % root_dir).output)
381 logging.error(msg)
382 raise HypervisorError("Unmounting the chroot dir failed: %s (%s)" %
383 (result.output, msg))
384
386 """Reboot an instance.
387
388 This is not (yet) implemented (in Ganeti) for the LXC hypervisor.
389
390 """
391
392 raise HypervisorError("The LXC hypervisor doesn't implement the"
393 " reboot functionality")
394
396 """Balloon an instance memory to a certain value.
397
398 @type instance: L{objects.Instance}
399 @param instance: instance to be accepted
400 @type mem: int
401 @param mem: actual memory size to use for instance runtime
402
403 """
404
405 pass
406
408 """Return information about the node.
409
410 See L{BaseHypervisor.GetLinuxNodeInfo}.
411
412 """
413 return self.GetLinuxNodeInfo()
414
415 @classmethod
425
426 - def Verify(self, hvparams=None):
427 """Verify the hypervisor.
428
429 For the LXC manager, it just checks the existence of the base dir.
430
431 @type hvparams: dict of strings
432 @param hvparams: hypervisor parameters to be verified against; not used here
433
434 @return: Problem description if something is wrong, C{None} otherwise
435
436 """
437 msgs = []
438
439 if not os.path.exists(self._ROOT_DIR):
440 msgs.append("The required directory '%s' does not exist" %
441 self._ROOT_DIR)
442
443 try:
444 self._GetCgroupMountPoint()
445 except errors.HypervisorError, err:
446 msgs.append(str(err))
447
448 return self._FormatVerifyResults(msgs)
449
450 @classmethod
452 """LXC powercycle, just a wrapper over Linux powercycle.
453
454 @type hvparams: dict of strings
455 @param hvparams: hypervisor params to be used on this node
456
457 """
458 cls.LinuxPowercycle()
459
461 """Migrate an instance.
462
463 @type cluster_name: string
464 @param cluster_name: name of the cluster
465 @type instance: L{objects.Instance}
466 @param instance: the instance to be migrated
467 @type target: string
468 @param target: hostname (usually ip) of the target node
469 @type live: boolean
470 @param live: whether to do a live or non-live migration
471
472 """
473 raise HypervisorError("Migration is not supported by the LXC hypervisor")
474
476 """Get the migration status
477
478 @type instance: L{objects.Instance}
479 @param instance: the instance that is being migrated
480 @rtype: L{objects.MigrationStatus}
481 @return: the status of the current migration (one of
482 L{constants.HV_MIGRATION_VALID_STATUSES}), plus any additional
483 progress info that can be retrieved from the hypervisor
484
485 """
486 raise HypervisorError("Migration is not supported by the LXC hypervisor")
487