|
|
|
@ -1,7 +1,7 @@
|
|
|
|
|
# coding=utf8 |
|
|
|
|
# |
|
|
|
|
# systemd.py: systemd wrapper and container related functions. |
|
|
|
|
# Copyright (C) 2016 Sam Black <samwwwblack@lapwing.org> |
|
|
|
|
# Copyright (C) 2016, 2018 Sam Black <samwwwblack@lapwing.org> |
|
|
|
|
# |
|
|
|
|
# This program is free software: you can redistribute it and/or modify |
|
|
|
|
# it under the terms of the GNU General Public License as published by |
|
|
|
@ -20,7 +20,7 @@ import os.path
|
|
|
|
|
import subprocess |
|
|
|
|
|
|
|
|
|
from sponson.constants import FIREWALL_INTERFACE |
|
|
|
|
from sponson.constants import MOUNT_OVERLAY_CONTAINER |
|
|
|
|
from sponson.constants import BASE_CONTAINER_DIR |
|
|
|
|
from sponson.constants import SYSTEMD_MACHINE_DIR |
|
|
|
|
from sponson.constants import SYSTEMD_NSPAWN_DIR |
|
|
|
|
from sponson.constants import SYSTEMD_SYSTEM_DIR |
|
|
|
@ -49,15 +49,78 @@ class SystemdContainer(Systemd):
|
|
|
|
|
super().__init__(runtime_config) |
|
|
|
|
self.firewall = Firewall(runtime_config) |
|
|
|
|
|
|
|
|
|
def mount_path_inside(self, mount_path, image, container, container_path, |
|
|
|
|
def _mount_create_overlay(self, where, lower, upper=None, workdir=None, |
|
|
|
|
automount=True, requires=None): |
|
|
|
|
""" |
|
|
|
|
Create an ``overlay`` ``mount`` systemd unit file. |
|
|
|
|
|
|
|
|
|
:param where: Path to mount to. |
|
|
|
|
Can be a ':' separated list of paths to mount in order. |
|
|
|
|
:type where: str |
|
|
|
|
:param lower: Lower device or path to mount as read-only |
|
|
|
|
:type lower: str |
|
|
|
|
:param upper: Upper directory to mount in the image. |
|
|
|
|
This should be the same as the ``where`` argument. |
|
|
|
|
If omitted or None, the mount is considered read-only. |
|
|
|
|
:type upper: str or None |
|
|
|
|
:param workdir: Workdir path. |
|
|
|
|
If omitted or None, the mount is considered read-only. |
|
|
|
|
:type workdir: str or None |
|
|
|
|
:param automount: enable as an automounted directory |
|
|
|
|
:type automount: bool |
|
|
|
|
:param requires: required unit file, such that this overlay mount |
|
|
|
|
will pull in another service or mount |
|
|
|
|
:type requires: str or None |
|
|
|
|
""" |
|
|
|
|
if upper and not workdir: |
|
|
|
|
raise SystemdException("Missing required argument 'workdir'") |
|
|
|
|
|
|
|
|
|
if not upper and ":" not in lower: |
|
|
|
|
raise SystemdException("Missing multiple lowerdir directives") |
|
|
|
|
|
|
|
|
|
opt = "lowerdir={}".format(lower) |
|
|
|
|
|
|
|
|
|
if not os.path.isdir(where): |
|
|
|
|
os.makedirs(where, 0o750) |
|
|
|
|
|
|
|
|
|
if upper: |
|
|
|
|
opt = "{},upperdir={},workdir={}".format(opt, upper, workdir) |
|
|
|
|
if not os.path.isdir(upper): |
|
|
|
|
os.makedirs(upper, 0o755) |
|
|
|
|
if not os.path.isdir(workdir): |
|
|
|
|
os.makedirs(workdir, 0o755) |
|
|
|
|
|
|
|
|
|
self.mount_create_unit("overlay", where, "overlay", opt, automount, |
|
|
|
|
requires) |
|
|
|
|
|
|
|
|
|
def _mount_create_bind(self, image_path, mount_path, mode="ro", |
|
|
|
|
automount=True): |
|
|
|
|
""" |
|
|
|
|
Create an ``bind`` ``mount`` systemd unit file. |
|
|
|
|
|
|
|
|
|
:param image_path: Path to mount to. |
|
|
|
|
:type image_path: str |
|
|
|
|
:param mount_path: Path to mount from. |
|
|
|
|
:type mount_path: str |
|
|
|
|
:param mode: Mount source as either "read-only" (default) |
|
|
|
|
or "read-write" |
|
|
|
|
:type mode: str |
|
|
|
|
:param automount: Create corresponding automount unit file |
|
|
|
|
:type automount: bool |
|
|
|
|
""" |
|
|
|
|
if mode == "rw": |
|
|
|
|
opt = "bind,rw" |
|
|
|
|
else: |
|
|
|
|
opt = "bind,ro" |
|
|
|
|
self.mount_create_unit(mount_path, image_path, "none", opt, automount) |
|
|
|
|
|
|
|
|
|
def mount_path_inside(self, mount_path, container, container_path, |
|
|
|
|
mount_type="overlay", mode="rw"): |
|
|
|
|
""" |
|
|
|
|
Mount a path into a container. |
|
|
|
|
|
|
|
|
|
:param mount_path: path to mount from |
|
|
|
|
:type mount_path: str |
|
|
|
|
:param image: image base of the container |
|
|
|
|
:type image: str |
|
|
|
|
:param container: container to mount into |
|
|
|
|
:type container: str |
|
|
|
|
:param container_path: path to mount into. |
|
|
|
@ -72,17 +135,18 @@ class SystemdContainer(Systemd):
|
|
|
|
|
""" |
|
|
|
|
if os.path.abspath(container_path) == "/": |
|
|
|
|
raise SystemdException("Cannot mount '/' to a container") |
|
|
|
|
container_mount = os.path.join(SYSTEMD_MACHINE_DIR, container, |
|
|
|
|
|
|
|
|
|
container_base = os.path.join(BASE_CONTAINER_DIR, container) |
|
|
|
|
container_mount = os.path.join(container_base, "mnt", |
|
|
|
|
container_path.lstrip("/")) |
|
|
|
|
container_volumes = os.path.join(container_base, "volumes") |
|
|
|
|
|
|
|
|
|
if mode == "rw" and mount_type == "overlay": |
|
|
|
|
overlay_path = os.path.join(MOUNT_OVERLAY_CONTAINER, |
|
|
|
|
image, container) |
|
|
|
|
escape_image_path = self._escape(container_mount) |
|
|
|
|
|
|
|
|
|
upper = os.path.join(overlay_path, escape_image_path, |
|
|
|
|
upper = os.path.join(container_volumes, escape_image_path, |
|
|
|
|
"upper") |
|
|
|
|
workdir = os.path.join(overlay_path, escape_image_path, |
|
|
|
|
workdir = os.path.join(container_volumes, escape_image_path, |
|
|
|
|
"workdir") |
|
|
|
|
|
|
|
|
|
self._mount_create_overlay(container_mount, mount_path, upper, |
|
|
|
@ -92,26 +156,49 @@ class SystemdContainer(Systemd):
|
|
|
|
|
else: |
|
|
|
|
self._mount_create_bind(container_mount, mount_path, mode) |
|
|
|
|
|
|
|
|
|
def mount_image(self, container, container_version, container_path, |
|
|
|
|
image, image_path): |
|
|
|
|
def base_mount(self, container, image_path, container_version=None, |
|
|
|
|
ro_mounts=None): |
|
|
|
|
""" |
|
|
|
|
Mount an image for a container. |
|
|
|
|
Set up mounts for the container, including the image, first run, config |
|
|
|
|
and layer setups. |
|
|
|
|
|
|
|
|
|
:param container: container name |
|
|
|
|
:type container: str |
|
|
|
|
:param container_version: container version |
|
|
|
|
:type container_version: str |
|
|
|
|
:param container_path: path to the container mount |
|
|
|
|
:type container_path: str |
|
|
|
|
:param image: image name |
|
|
|
|
:type image: str |
|
|
|
|
:param image_path: image path to mount |
|
|
|
|
:type image_path: str |
|
|
|
|
:param container_version: version of the container to mount for, |
|
|
|
|
defaults to None for a read only container. |
|
|
|
|
:type container_version: str or None |
|
|
|
|
:param ro_mounts: extra mounts to add to the |
|
|
|
|
read only part of the overlay mount, |
|
|
|
|
including config, layer or container instance versions. |
|
|
|
|
:type ro_mounts: list |
|
|
|
|
""" |
|
|
|
|
container_overlay = os.path.join(MOUNT_OVERLAY_CONTAINER, image, |
|
|
|
|
container, container_version) |
|
|
|
|
container_mount = os.path.join(BASE_CONTAINER_DIR, container, "mnt") |
|
|
|
|
container_first_run = os.path.join(BASE_CONTAINER_DIR, container, |
|
|
|
|
"config", "firstrun") |
|
|
|
|
|
|
|
|
|
if ro_mounts: |
|
|
|
|
# Don't mount the image or first run directories twice |
|
|
|
|
ro_mounts.remove(container_mount) |
|
|
|
|
ro_mounts.remove(container_first_run) |
|
|
|
|
|
|
|
|
|
self._lower_overlay(container_overlay, image_path, container_path) |
|
|
|
|
lower_mount = "{}:{}:{}".format(image_path, container_first_run, |
|
|
|
|
":".join(ro_mounts)) |
|
|
|
|
else: |
|
|
|
|
lower_mount = "{}:{}".format(image_path, container_first_run) |
|
|
|
|
|
|
|
|
|
if container_version: |
|
|
|
|
workdir = os.path.join(BASE_CONTAINER_DIR, container, "instances", |
|
|
|
|
"workdir") |
|
|
|
|
upper = os.path.join(BASE_CONTAINER_DIR, container, "instances", |
|
|
|
|
container_version) |
|
|
|
|
else: |
|
|
|
|
workdir = None |
|
|
|
|
upper = None |
|
|
|
|
|
|
|
|
|
self._mount_create_overlay(container_mount, lower_mount, upper, |
|
|
|
|
workdir) |
|
|
|
|
|
|
|
|
|
def _limits(self, limits): |
|
|
|
|
""" |
|
|
|
@ -283,10 +370,7 @@ class SystemdContainer(Systemd):
|
|
|
|
|
:return: True if running, False otherwise |
|
|
|
|
:rtype: bool |
|
|
|
|
""" |
|
|
|
|
if self.version < 229: |
|
|
|
|
unit_name = "sponson-container-{}.service".format(name) |
|
|
|
|
else: |
|
|
|
|
unit_name = "systemd-nspawn@{}.service".format(name) |
|
|
|
|
unit_name = "systemd-nspawn@{}.service".format(name) |
|
|
|
|
unit_obj = self._unit_properties(unit_name) |
|
|
|
|
return (unit_obj.Get(self.DBUS_UNIT_PATH, "ActiveState") in |
|
|
|
|
("active", "activating", "reloading")) |
|
|
|
|