summaryrefslogtreecommitdiffstats
path: root/src/cephadm/cephadmlib/systemd_unit.py
blob: d3543174a8df39f7d4d4d77affaa2e10e8d389d1 (plain)
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
# systemd_unit.py - creating/managing systemd unit files

import contextlib
import os
import pathlib

from typing import IO, List, Optional, Union

from . import templating
from .call_wrappers import call_throws
from .container_engines import Docker, Podman
from .context import CephadmContext
from .daemon_identity import DaemonIdentity, DaemonSubIdentity
from .file_utils import write_new
from .logging import write_cluster_logrotate_config


_DROP_IN_FILENAME = '99-cephadm.conf'


class PathInfo:
    """Utility class to map basic service identities, to the paths used by
    their corresponding systemd unit files.
    """

    def __init__(
        self,
        unit_dir: Union[str, pathlib.Path],
        identity: DaemonIdentity,
        sidecar_ids: Optional[List[DaemonSubIdentity]] = None,
    ) -> None:
        self.identity = identity
        self.sidecar_ids = sidecar_ids or []

        unit_dir = pathlib.Path(unit_dir)
        self.default_unit_file = unit_dir / f'ceph-{identity.fsid}@.service'
        self.init_ctr_unit_file = unit_dir / identity.init_service_name
        self.sidecar_unit_files = {
            si: unit_dir / si.sidecar_service_name for si in self.sidecar_ids
        }
        dname = f'{identity.service_name}.d'
        self.drop_in_file = unit_dir / dname / _DROP_IN_FILENAME


def _write_drop_in(
    dest: IO,
    ctx: CephadmContext,
    identity: DaemonIdentity,
    enable_init_containers: bool,
    sidecar_ids: List[DaemonSubIdentity],
) -> None:
    templating.render_to_file(
        dest,
        ctx,
        templating.Templates.dropin_service,
        identity=identity,
        enable_init_containers=enable_init_containers,
        sidecar_ids=sidecar_ids,
    )


def _write_init_containers_unit_file(
    dest: IO, ctx: CephadmContext, identity: DaemonIdentity
) -> None:
    has_docker_engine = isinstance(ctx.container_engine, Docker)
    has_podman_engine = isinstance(ctx.container_engine, Podman)
    templating.render_to_file(
        dest,
        ctx,
        templating.Templates.init_ctr_service,
        identity=identity,
        has_docker_engine=has_docker_engine,
        has_podman_engine=has_podman_engine,
        has_podman_split_version=(
            has_podman_engine and ctx.container_engine.supports_split_cgroups
        ),
    )


def _write_sidecar_unit_file(
    dest: IO,
    ctx: CephadmContext,
    primary: DaemonIdentity,
    sidecar: DaemonSubIdentity,
) -> None:
    has_docker_engine = isinstance(ctx.container_engine, Docker)
    has_podman_engine = isinstance(ctx.container_engine, Podman)
    templating.render_to_file(
        dest,
        ctx,
        templating.Templates.sidecar_service,
        primary=primary,
        sidecar=sidecar,
        sidecar_script=sidecar.sidecar_script(ctx.data_dir),
        has_docker_engine=has_docker_engine,
        has_podman_engine=has_podman_engine,
        has_podman_split_version=(
            has_podman_engine and ctx.container_engine.supports_split_cgroups
        ),
    )


def _install_extended_systemd_services(
    ctx: CephadmContext,
    pinfo: PathInfo,
    identity: DaemonIdentity,
    enable_init_containers: bool = False,
) -> None:
    """Install the systemd unit files needed for more complex services
    that have init containers and/or sidecars.
    """
    with contextlib.ExitStack() as estack:
        # install the unit file to handle running init containers
        if enable_init_containers:
            icfh = estack.enter_context(
                write_new(pinfo.init_ctr_unit_file, perms=None)
            )
            _write_init_containers_unit_file(icfh, ctx, identity)

        # install the unit files to handle running sidecars
        sids = []
        for si, sup in pinfo.sidecar_unit_files.items():
            sufh = estack.enter_context(write_new(sup, perms=None))
            _write_sidecar_unit_file(sufh, ctx, identity, si)
            sids.append(si)

        # create a drop-in to create a relationship between the primary
        # service and the init- and sidecar-based services.
        if enable_init_containers or sids:
            pinfo.drop_in_file.parent.mkdir(parents=True, exist_ok=True)
            difh = estack.enter_context(
                write_new(pinfo.drop_in_file, perms=None)
            )
            _write_drop_in(difh, ctx, identity, enable_init_containers, sids)


def _get_unit_file(ctx: CephadmContext, fsid: str) -> str:
    has_docker_engine = isinstance(ctx.container_engine, Docker)
    has_podman_engine = isinstance(ctx.container_engine, Podman)
    has_podman_split_version = (
        has_podman_engine and ctx.container_engine.supports_split_cgroups
    )
    return templating.render(
        ctx,
        templating.Templates.ceph_service,
        fsid=fsid,
        has_docker_engine=has_docker_engine,
        has_podman_engine=has_podman_engine,
        has_podman_split_version=has_podman_split_version,
    )


def _install_base_units(ctx: CephadmContext, fsid: str) -> None:
    """
    Set up ceph.target and ceph-$fsid.target units.
    """
    # global unit
    existed = os.path.exists(ctx.unit_dir + '/ceph.target')
    with write_new(ctx.unit_dir + '/ceph.target', perms=None) as f:
        f.write(
            '[Unit]\n'
            'Description=All Ceph clusters and services\n'
            '\n'
            '[Install]\n'
            'WantedBy=multi-user.target\n'
        )
    if not existed:
        # we disable before enable in case a different ceph.target
        # (from the traditional package) is present; while newer
        # systemd is smart enough to disable the old
        # (/lib/systemd/...) and enable the new (/etc/systemd/...),
        # some older versions of systemd error out with EEXIST.
        call_throws(ctx, ['systemctl', 'disable', 'ceph.target'])
        call_throws(ctx, ['systemctl', 'enable', 'ceph.target'])
        call_throws(ctx, ['systemctl', 'start', 'ceph.target'])

    # cluster unit
    existed = os.path.exists(ctx.unit_dir + '/ceph-%s.target' % fsid)
    with write_new(ctx.unit_dir + f'/ceph-{fsid}.target', perms=None) as f:
        f.write(
            '[Unit]\n'
            'Description=Ceph cluster {fsid}\n'
            'PartOf=ceph.target\n'
            'Before=ceph.target\n'
            '\n'
            '[Install]\n'
            'WantedBy=multi-user.target ceph.target\n'.format(fsid=fsid)
        )
    if not existed:
        call_throws(ctx, ['systemctl', 'enable', 'ceph-%s.target' % fsid])
        call_throws(ctx, ['systemctl', 'start', 'ceph-%s.target' % fsid])

    # don't overwrite file in order to allow users to manipulate it
    if os.path.exists(ctx.logrotate_dir + f'/ceph-{fsid}'):
        return

    write_cluster_logrotate_config(ctx, fsid)


def update_files(
    ctx: CephadmContext,
    ident: DaemonIdentity,
    *,
    init_container_ids: Optional[List[DaemonSubIdentity]] = None,
    sidecar_ids: Optional[List[DaemonSubIdentity]] = None,
) -> None:
    _install_base_units(ctx, ident.fsid)
    unit = _get_unit_file(ctx, ident.fsid)
    pathinfo = PathInfo(ctx.unit_dir, ident, sidecar_ids=sidecar_ids)
    with write_new(pathinfo.default_unit_file, perms=None) as f:
        f.write(unit)
    _install_extended_systemd_services(
        ctx, pathinfo, ident, bool(init_container_ids)
    )


def sidecars_from_dropin(
    pathinfo: PathInfo, missing_ok: bool = False
) -> PathInfo:
    """Read the list of sidecars for a service from the service's drop in file."""
    # This is useful in the cases where the sidecars would be determined from
    # input data (deployment) but we lack the original deployment data (rm
    # daemon).
    sidecars = []
    try:
        with open(pathinfo.drop_in_file) as fh:
            lines = fh.readlines()
    except FileNotFoundError:
        if missing_ok:
            return pathinfo
        raise
    for line in lines:
        if not line.startswith('Wants='):
            continue
        for item in line[6:].strip().split():
            si, category = DaemonSubIdentity.from_service_name(item)
            if category == 'sidecar':
                sidecars.append(si)
    return PathInfo(
        pathinfo.default_unit_file.parent, pathinfo.identity, sidecars
    )