]>
Commit | Line | Data |
---|---|---|
be2e4e54 SG |
1 | # |
2 | # python-lxc: Python bindings for LXC | |
3 | # | |
4 | # (C) Copyright Canonical Ltd. 2012 | |
5 | # | |
6 | # Authors: | |
7 | # Stéphane Graber <stgraber@ubuntu.com> | |
8 | # | |
9 | # This library is free software; you can redistribute it and/or | |
10 | # modify it under the terms of the GNU Lesser General Public | |
11 | # License as published by the Free Software Foundation; either | |
12 | # version 2.1 of the License, or (at your option) any later version. | |
13 | # | |
14 | # This library is distributed in the hope that it will be useful, | |
15 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
17 | # Lesser General Public License for more details. | |
18 | # | |
19 | # You should have received a copy of the GNU Lesser General Public | |
20 | # License along with this library; if not, write to the Free Software | |
250b1eec | 21 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
be2e4e54 SG |
22 | # |
23 | ||
24 | import _lxc | |
25 | import glob | |
26 | import os | |
27 | import subprocess | |
2a41cf5d | 28 | import stat |
b0f9616f | 29 | import time |
be2e4e54 SG |
30 | import warnings |
31 | ||
32 | warnings.warn("The python-lxc API isn't yet stable " | |
33 | "and may change at any point in the future.", Warning, 2) | |
34 | ||
24fcdb39 | 35 | default_config_path = _lxc.get_default_config_path() |
b6adc92b | 36 | version = _lxc.get_version() |
edb09f8d | 37 | |
cbd4c464 | 38 | |
be2e4e54 SG |
39 | class ContainerNetwork(): |
40 | props = {} | |
41 | ||
42 | def __init__(self, container, index): | |
43 | self.container = container | |
44 | self.index = index | |
45 | ||
46 | for key in self.container.get_keys("lxc.network.%s" % self.index): | |
47 | if "." in key: | |
48 | self.props[key.replace(".", "_")] = key | |
49 | else: | |
50 | self.props[key] = key | |
51 | ||
52 | if not self.props: | |
53 | return False | |
54 | ||
55 | def __delattr__(self, key): | |
56 | if key in ["container", "index", "props"]: | |
57 | return object.__delattr__(self, key) | |
58 | ||
59 | if key not in self.props: | |
60 | raise AttributeError("'%s' network has no attribute '%s'" % ( | |
bde18539 | 61 | self.__get_network_item("type"), key)) |
be2e4e54 SG |
62 | |
63 | return self.__clear_network_item(self.props[key]) | |
64 | ||
65 | def __dir__(self): | |
66 | return sorted(self.props.keys()) | |
67 | ||
68 | def __getattr__(self, key): | |
69 | if key in ["container", "index", "props"]: | |
70 | return object.__getattribute__(self, key) | |
71 | ||
72 | if key not in self.props: | |
73 | raise AttributeError("'%s' network has no attribute '%s'" % ( | |
bde18539 | 74 | self.__get_network_item("type"), key)) |
be2e4e54 SG |
75 | |
76 | return self.__get_network_item(self.props[key]) | |
77 | ||
78 | def __hasattr__(self, key): | |
79 | if key in ["container", "index", "props"]: | |
80 | return object.__hasattr__(self, key) | |
81 | ||
82 | if key not in self.props: | |
83 | raise AttributeError("'%s' network has no attribute '%s'" % ( | |
bde18539 | 84 | self.__get_network_item("type"), key)) |
be2e4e54 SG |
85 | |
86 | return True | |
87 | ||
88 | def __repr__(self): | |
89 | return "'%s' network at index '%s'" % ( | |
90 | self.__get_network_item("type"), self.index) | |
91 | ||
92 | def __setattr__(self, key, value): | |
93 | if key in ["container", "index", "props"]: | |
94 | return object.__setattr__(self, key, value) | |
95 | ||
96 | if key not in self.props: | |
97 | raise AttributeError("'%s' network has no attribute '%s'" % ( | |
bde18539 | 98 | self.__get_network_item("type"), key)) |
be2e4e54 SG |
99 | |
100 | return self.__set_network_item(self.props[key], value) | |
101 | ||
102 | def __clear_network_item(self, key): | |
103 | return self.container.clear_config_item("lxc.network.%s.%s" % ( | |
bde18539 | 104 | self.index, key)) |
be2e4e54 SG |
105 | |
106 | def __get_network_item(self, key): | |
107 | return self.container.get_config_item("lxc.network.%s.%s" % ( | |
bde18539 | 108 | self.index, key)) |
be2e4e54 SG |
109 | |
110 | def __set_network_item(self, key, value): | |
111 | return self.container.set_config_item("lxc.network.%s.%s" % ( | |
bde18539 | 112 | self.index, key), value) |
be2e4e54 SG |
113 | |
114 | ||
115 | class ContainerNetworkList(): | |
116 | def __init__(self, container): | |
117 | self.container = container | |
118 | ||
119 | def __getitem__(self, index): | |
2cdb945b | 120 | if index >= len(self): |
be2e4e54 SG |
121 | raise IndexError("list index out of range") |
122 | ||
123 | return ContainerNetwork(self.container, index) | |
124 | ||
125 | def __len__(self): | |
2cdb945b SG |
126 | values = self.container.get_config_item("lxc.network") |
127 | ||
128 | if values: | |
129 | return len(values) | |
130 | else: | |
131 | return 0 | |
be2e4e54 SG |
132 | |
133 | def add(self, network_type): | |
2cdb945b | 134 | index = len(self) |
be2e4e54 SG |
135 | |
136 | return self.container.set_config_item("lxc.network.%s.type" % index, | |
bde18539 | 137 | network_type) |
be2e4e54 SG |
138 | |
139 | def remove(self, index): | |
2cdb945b | 140 | count = len(self) |
be2e4e54 SG |
141 | if index >= count: |
142 | raise IndexError("list index out of range") | |
143 | ||
144 | return self.container.clear_config_item("lxc.network.%s" % index) | |
145 | ||
146 | ||
147 | class Container(_lxc.Container): | |
edb09f8d | 148 | def __init__(self, name, config_path=None): |
be2e4e54 SG |
149 | """ |
150 | Creates a new Container instance. | |
151 | """ | |
152 | ||
cbd4c464 SG |
153 | if os.geteuid() != 0: |
154 | raise Exception("Running as non-root.") | |
155 | ||
edb09f8d SG |
156 | if config_path: |
157 | _lxc.Container.__init__(self, name, config_path) | |
158 | else: | |
159 | _lxc.Container.__init__(self, name) | |
160 | ||
be2e4e54 SG |
161 | self.network = ContainerNetworkList(self) |
162 | ||
20cf2e97 | 163 | def add_device_node(self, path, destpath=None): |
2a41cf5d | 164 | """ |
20cf2e97 | 165 | Add block/char device to running container. |
2a41cf5d SG |
166 | """ |
167 | ||
20cf2e97 SG |
168 | if not self.running: |
169 | return False | |
170 | ||
2a41cf5d SG |
171 | if not destpath: |
172 | destpath = path | |
173 | ||
174 | if not os.path.exists(path): | |
175 | return False | |
176 | ||
177 | # Lookup the source | |
178 | path_stat = os.stat(path) | |
179 | mode = stat.S_IMODE(path_stat.st_mode) | |
180 | ||
2a41cf5d | 181 | # Allow the target |
d8521cc3 SG |
182 | if stat.S_ISBLK(path_stat.st_mode): |
183 | self.set_cgroup_item("devices.allow", | |
184 | "b %s:%s rwm" % | |
185 | (int(path_stat.st_rdev / 256), | |
186 | int(path_stat.st_rdev % 256))) | |
187 | elif stat.S_ISCHR(path_stat.st_mode): | |
188 | self.set_cgroup_item("devices.allow", | |
189 | "c %s:%s rwm" % | |
190 | (int(path_stat.st_rdev / 256), | |
191 | int(path_stat.st_rdev % 256))) | |
2a41cf5d SG |
192 | |
193 | # Create the target | |
194 | rootfs = "/proc/%s/root/" % self.init_pid | |
195 | container_path = "%s/%s" % (rootfs, destpath) | |
196 | ||
197 | if os.path.exists(container_path): | |
198 | os.remove(container_path) | |
199 | ||
200 | os.mknod(container_path, path_stat.st_mode, path_stat.st_rdev) | |
201 | os.chmod(container_path, mode) | |
202 | os.chown(container_path, 0, 0) | |
203 | ||
204 | return True | |
205 | ||
20cf2e97 SG |
206 | def add_device_net(self, name, destname=None): |
207 | """ | |
208 | Add network device to running container. | |
209 | """ | |
210 | ||
211 | if not self.running: | |
212 | return False | |
213 | ||
214 | if not destname: | |
215 | destname = name | |
216 | ||
217 | if not os.path.exists("/sys/class/net/%s/" % name): | |
218 | return False | |
219 | ||
220 | return subprocess.call(['ip', 'link', 'set', | |
221 | 'dev', name, | |
222 | 'netns', str(self.init_pid), | |
223 | 'name', destname]) == 0 | |
224 | ||
be2e4e54 SG |
225 | def append_config_item(self, key, value): |
226 | """ | |
227 | Append 'value' to 'key', assuming 'key' is a list. | |
228 | If 'key' isn't a list, 'value' will be set as the value of 'key'. | |
229 | """ | |
230 | ||
231 | return _lxc.Container.set_config_item(self, key, value) | |
232 | ||
be2e4e54 SG |
233 | def create(self, template, args={}): |
234 | """ | |
235 | Create a new rootfs for the container. | |
236 | ||
237 | "template" must be a valid template name. | |
238 | ||
239 | "args" (optional) is a dictionary of parameters and values to pass | |
240 | to the template. | |
241 | """ | |
242 | ||
243 | template_args = [] | |
244 | for item in args.items(): | |
245 | template_args.append("--%s" % item[0]) | |
246 | template_args.append("%s" % item[1]) | |
247 | ||
248 | return _lxc.Container.create(self, template, tuple(template_args)) | |
249 | ||
250 | def clone(self, container): | |
251 | """ | |
252 | Clone an existing container into a new one. | |
253 | """ | |
254 | ||
255 | if self.defined: | |
256 | return False | |
257 | ||
258 | if isinstance(container, Container): | |
259 | source = container | |
260 | else: | |
261 | source = Container(container) | |
262 | ||
263 | if not source.defined: | |
264 | return False | |
265 | ||
bde18539 SG |
266 | if subprocess.call(["lxc-clone", "-o", source.name, "-n", self.name], |
267 | universal_newlines=True) != 0: | |
be2e4e54 SG |
268 | return False |
269 | ||
270 | self.load_config() | |
271 | return True | |
272 | ||
39ffde30 | 273 | def console(self, ttynum=-1, stdinfd=0, stdoutfd=1, stderrfd=2, escape=1): |
be2e4e54 | 274 | """ |
b5159817 | 275 | Attach to console of running container. |
be2e4e54 SG |
276 | """ |
277 | ||
278 | if not self.running: | |
279 | return False | |
280 | ||
39ffde30 SG |
281 | return _lxc.Container.console(self, ttynum, stdinfd, stdoutfd, |
282 | stderrfd, escape) | |
b5159817 | 283 | |
39ffde30 | 284 | def console_getfd(self, ttynum=-1): |
b5159817 DE |
285 | """ |
286 | Attach to console of running container. | |
287 | """ | |
288 | ||
289 | if not self.running: | |
be2e4e54 | 290 | return False |
b5159817 DE |
291 | |
292 | return _lxc.Container.console_getfd(self, ttynum) | |
be2e4e54 | 293 | |
f4d3a9fd SG |
294 | def get_cgroup_item(self, key): |
295 | """ | |
296 | Returns the value for a given cgroup entry. | |
297 | A list is returned when multiple values are set. | |
298 | """ | |
299 | value = _lxc.Container.get_cgroup_item(self, key) | |
300 | ||
301 | if value is False: | |
302 | return False | |
303 | else: | |
304 | return value.rstrip("\n") | |
305 | ||
be2e4e54 SG |
306 | def get_config_item(self, key): |
307 | """ | |
308 | Returns the value for a given config key. | |
309 | A list is returned when multiple values are set. | |
310 | """ | |
311 | value = _lxc.Container.get_config_item(self, key) | |
312 | ||
313 | if value is False: | |
314 | return False | |
315 | elif value.endswith("\n"): | |
316 | return value.rstrip("\n").split("\n") | |
317 | else: | |
318 | return value | |
319 | ||
703c562d | 320 | def get_keys(self, key=None): |
be2e4e54 SG |
321 | """ |
322 | Returns a list of valid sub-keys. | |
323 | """ | |
703c562d SG |
324 | if key: |
325 | value = _lxc.Container.get_keys(self, key) | |
326 | else: | |
327 | value = _lxc.Container.get_keys(self) | |
be2e4e54 SG |
328 | |
329 | if value is False: | |
330 | return False | |
331 | elif value.endswith("\n"): | |
332 | return value.rstrip("\n").split("\n") | |
333 | else: | |
334 | return value | |
335 | ||
b0f9616f SG |
336 | def get_ips(self, interface=None, family=None, scope=None, timeout=0): |
337 | """ | |
338 | Get a tuple of IPs for the container. | |
339 | """ | |
340 | ||
341 | kwargs = {} | |
342 | if interface: | |
343 | kwargs['interface'] = interface | |
344 | if family: | |
345 | kwargs['family'] = family | |
346 | if scope: | |
347 | kwargs['scope'] = scope | |
348 | ||
349 | ips = None | |
819554fe | 350 | timeout = int(os.environ.get('LXC_GETIP_TIMEOUT', timeout)) |
b0f9616f SG |
351 | |
352 | while not ips: | |
353 | ips = _lxc.Container.get_ips(self, **kwargs) | |
354 | if timeout == 0: | |
355 | break | |
356 | ||
357 | timeout -= 1 | |
358 | time.sleep(1) | |
359 | ||
360 | return ips | |
361 | ||
be2e4e54 SG |
362 | def set_config_item(self, key, value): |
363 | """ | |
364 | Set a config key to a provided value. | |
365 | The value can be a list for the keys supporting multiple values. | |
366 | """ | |
6c5db2af SG |
367 | try: |
368 | old_value = self.get_config_item(key) | |
369 | except KeyError: | |
370 | old_value = None | |
be2e4e54 SG |
371 | |
372 | # Check if it's a list | |
373 | def set_key(key, value): | |
374 | self.clear_config_item(key) | |
375 | if isinstance(value, list): | |
376 | for entry in value: | |
377 | if not _lxc.Container.set_config_item(self, key, entry): | |
378 | return False | |
379 | else: | |
380 | _lxc.Container.set_config_item(self, key, value) | |
381 | ||
382 | set_key(key, value) | |
383 | new_value = self.get_config_item(key) | |
384 | ||
bde18539 SG |
385 | if (isinstance(value, str) and isinstance(new_value, str) and |
386 | value == new_value): | |
be2e4e54 | 387 | return True |
bde18539 SG |
388 | elif (isinstance(value, list) and isinstance(new_value, list) and |
389 | set(value) == set(new_value)): | |
be2e4e54 | 390 | return True |
bde18539 SG |
391 | elif (isinstance(value, str) and isinstance(new_value, list) and |
392 | set([value]) == set(new_value)): | |
be2e4e54 SG |
393 | return True |
394 | elif old_value: | |
395 | set_key(key, old_value) | |
396 | return False | |
397 | else: | |
398 | self.clear_config_item(key) | |
399 | return False | |
400 | ||
bde18539 | 401 | def wait(self, state, timeout=-1): |
87540ad7 SG |
402 | """ |
403 | Wait for the container to reach a given state or timeout. | |
404 | """ | |
405 | ||
406 | if isinstance(state, str): | |
407 | state = state.upper() | |
408 | ||
225b52ef | 409 | return _lxc.Container.wait(self, state, timeout) |
be2e4e54 | 410 | |
bde18539 | 411 | |
edb09f8d | 412 | def list_containers(as_object=False, config_path=None): |
be2e4e54 SG |
413 | """ |
414 | List the containers on the system. | |
415 | """ | |
edb09f8d SG |
416 | |
417 | if not config_path: | |
418 | config_path = default_config_path | |
419 | ||
be2e4e54 | 420 | containers = [] |
edb09f8d | 421 | for entry in glob.glob("%s/*/config" % config_path): |
be2e4e54 | 422 | if as_object: |
edb09f8d | 423 | containers.append(Container(entry.split("/")[-2], config_path)) |
be2e4e54 SG |
424 | else: |
425 | containers.append(entry.split("/")[-2]) | |
426 | return containers | |
d7a09c63 CS |
427 | |
428 | def attach_run_command(cmd): | |
429 | """ | |
430 | Run a command when attaching | |
55c76589 | 431 | |
d7a09c63 CS |
432 | Please do not call directly, this will execvp the command. |
433 | This is to be used in conjunction with the attach method | |
434 | of a container. | |
435 | """ | |
436 | if isinstance(cmd, tuple): | |
437 | return _lxc.attach_run_command(cmd) | |
438 | elif isinstance(cmd, list): | |
439 | return _lxc.attach_run_command((cmd[0], cmd)) | |
440 | else: | |
441 | return _lxc.attach_run_command((cmd, [cmd])) | |
442 | ||
443 | def attach_run_shell(): | |
444 | """ | |
445 | Run a shell when attaching | |
55c76589 | 446 | |
d7a09c63 CS |
447 | Please do not call directly, this will execvp the shell. |
448 | This is to be used in conjunction with the attach method | |
449 | of a container. | |
450 | """ | |
451 | return _lxc.attach_run_shell(None) | |
452 | ||
c9ec9055 CS |
453 | def arch_to_personality(arch): |
454 | """ | |
455 | Determine the process personality corresponding to the architecture | |
456 | """ | |
457 | if isinstance(arch, bytes): | |
458 | arch = str(arch, 'utf-8') | |
459 | return _lxc.arch_to_personality(arch) | |
460 | ||
d7a09c63 CS |
461 | # Some constants for attach |
462 | LXC_ATTACH_KEEP_ENV = _lxc.LXC_ATTACH_KEEP_ENV | |
463 | LXC_ATTACH_CLEAR_ENV = _lxc.LXC_ATTACH_CLEAR_ENV | |
464 | LXC_ATTACH_MOVE_TO_CGROUP = _lxc.LXC_ATTACH_MOVE_TO_CGROUP | |
465 | LXC_ATTACH_DROP_CAPABILITIES = _lxc.LXC_ATTACH_DROP_CAPABILITIES | |
466 | LXC_ATTACH_SET_PERSONALITY = _lxc.LXC_ATTACH_SET_PERSONALITY | |
467 | LXC_ATTACH_APPARMOR = _lxc.LXC_ATTACH_APPARMOR | |
468 | LXC_ATTACH_REMOUNT_PROC_SYS = _lxc.LXC_ATTACH_REMOUNT_PROC_SYS | |
469 | LXC_ATTACH_DEFAULT = _lxc.LXC_ATTACH_DEFAULT | |
01bfae14 CS |
470 | CLONE_NEWUTS = _lxc.CLONE_NEWUTS |
471 | CLONE_NEWIPC = _lxc.CLONE_NEWIPC | |
472 | CLONE_NEWUSER = _lxc.CLONE_NEWUSER | |
473 | CLONE_NEWPID = _lxc.CLONE_NEWPID | |
474 | CLONE_NEWNET = _lxc.CLONE_NEWNET | |
475 | CLONE_NEWNS = _lxc.CLONE_NEWNS |