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