File: check_systemd_dbus.py

package info (click to toggle)
monitoring-plugins-systemd 2.3.1-4
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, forky, sid, trixie
  • size: 556 kB
  • sloc: python: 1,157; sh: 863; makefile: 25
file content (256 lines) | stat: -rwxr-xr-x 8,779 bytes parent folder | download
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
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
#! /usr/bin/python3

"""
Work in progress! Do not use this script at the moment! Use the file
`check_systemd.py` instead. It is a rewrite of the plugin using D-Bus instead
of parsing the CLI output.
"""

import argparse

import nagiosplugin
from nagiosplugin import Metric

__version__ = '2.3.0'


data_source = 'dbus'
"""This variable indicates which data source should be used for the acquisition
of monitoring informations. It accepts the values ``dbus`` or ``cli``. It
preferes the D-Bus source. """


try:
    # Look for gi https://pygobject.readthedocs.io/en/latest/index.html
    from gi.repository.Gio import DBusProxy, BusType
except ImportError:
    try:
        # Fallback to pgi Pure Python GObject Introspection Bindings
        # https://github.com/pygobject/pgi
        from pgi.repository.Gio import DBusProxy, BusType
    except ImportError:
        # Fallback to the command line interface source.
        data_source = 'cli'


class DbusManager:
    """
    This class holds the main entry point object of the D-Bus systemd API. See
    the section `The Manager Object
    <https://www.freedesktop.org/software/systemd/man/org.freedesktop.systemd1.html#The%20Manager%20Object>`_
    in the systemd D-Bus API.
    """

    def __init__(self):
        self.__manager = DBusProxy.new_for_bus_sync(
            BusType.SYSTEM, 0, None, 'org.freedesktop.systemd1',
            '/org/freedesktop/systemd1', 'org.freedesktop.systemd1.Manager',
            None)

    def load_unit(self, unit_name):
        """
        Load a systemd D-Bus unit object by it’s name.

        :param str unit_name: A systemd unit name like ``tor.service``,
        ``mnt-nextcloud.automount`` or ``update-motd.timer``.
        """
        try:
            return self.__manager.LoadUnit('(s)', unit_name)
        except Exception as e:
            raise e


dbus_manager = DbusManager()
"""
The systemd D-Bus API main entry point object, the so called “manager”.
"""


class SystemdUnitState:
    """
    Class that provides easy access to the three state properties
    ``ActiveState``, ``SubState`` and ``LoadState`` of the Dbus systemd API.
    """

    def __init__(self, unit_name):
        """
        :param str unit_name: A systemd unit name like ``tor.service``,
        ``mnt-nextcloud.automount`` or ``update-motd.timer``.
        """
        try:
            loaded_unit = dbus_manager.load_unit(unit_name)
        except Exception as e:
            raise e

        self.__dbus_unit = DBusProxy.new_for_bus_sync(
            BusType.SYSTEM, 0, None, 'org.freedesktop.systemd1',
            loaded_unit, 'org.freedesktop.systemd1.Unit', None)
        """
        The systemd D-Bus unit object is fetched by the method
        `Gio.DBusProxy.new_for_bus_sync
        <https://lazka.github.io/pgi-docs/#Gio-2.0/classes/DBusProxy.html#Gio.DBusProxy.new_for_bus_sync>`_.
        """

    def __get_dbus_property(self, property_name):
        """
        Get the property of a systemd D-Bus unit object. This method uses the
        methods `Gio.DBusProxy.get_cached_property
        <https://lazka.github.io/pgi-docs/#Gio-2.0/classes/DBusProxy.html#Gio.DBusProxy.get_cached_property>`_
        and
        `GLib.Variant.unpack
        <https://lazka.github.io/pgi-docs/#GLib-2.0/classes/Variant.html#GLib.Variant.unpack>`_
        for the lookup.
        """
        return self.__dbus_unit.get_cached_property(property_name).unpack()

    @property
    def active_state(self):
        """From the `D-Bus interface of systemd documentation
        <https://www.freedesktop.org/software/systemd/man/org.freedesktop.systemd1.html#Properties1>`_:

        ``ActiveState`` contains a state value that reflects whether the unit
        is currently active or not. The following states are currently defined:

        * ``active``,
        * ``reloading``,
        * ``inactive``,
        * ``failed``,
        * ``activating``, and ``deactivating``.

        ``active`` indicates that unit is active (obviously...).

        ``reloading`` indicates that the unit is active and currently reloading
        its configuration.

        ``inactive`` indicates that it is inactive and the previous run was
        successful or no previous run has taken place yet.

        ``failed`` indicates that it is inactive and the previous run was not
        successful (more information about the reason for this is available on
        the unit type specific interfaces, for example for services in the
        Result property, see below).

        ``activating`` indicates that the unit has previously been inactive but
        is currently in the process of entering an active state.

        Conversely ``deactivating`` indicates that the unit is currently in the
        process of deactivation.
        """
        return self.__get_dbus_property('ActiveState')

    @property
    def sub_state(self):
        """From the `D-Bus interface of systemd documentation
        <https://www.freedesktop.org/software/systemd/man/org.freedesktop.systemd1.html#Properties1>`_:

        ``SubState`` encodes states of the same state machine that
        ``ActiveState`` covers, but knows more fine-grained states that are
        unit-type-specific. Where ``ActiveState`` only covers six high-level
        states, ``SubState`` covers possibly many more low-level
        unit-type-specific states that are mapped to the six high-level states.
        Note that multiple low-level states might map to the same high-level
        state, but not vice versa. Not all high-level states have low-level
        counterparts on all unit types.
        """
        return self.__get_dbus_property('SubState')

    @property
    def load_state(self):
        """From the `D-Bus interface of systemd documentation
        <https://www.freedesktop.org/software/systemd/man/org.freedesktop.systemd1.html#Properties1>`_:

        ``LoadState`` contains a state value that reflects whether the
        configuration file of this unit has been loaded. The following states
        are currently defined:

        * ``loaded``,
        * ``error`` and
        * ``masked``.

        ``loaded`` indicates that the configuration was successfully loaded.

        ``error`` indicates that the configuration failed to load, the
        ``LoadError`` field contains information about the cause of this
        failure.

        ``masked`` indicates that the unit is currently masked out (i.e.
        symlinked to /dev/null or suchlike).

        Note that the ``LoadState`` is fully orthogonal to the ``ActiveState``
        (see below) as units without valid loaded configuration might be active
        (because configuration might have been reloaded at a time where a unit
        was already active).
        """
        return self.__get_dbus_property('LoadState')


class UnitResource(nagiosplugin.Resource):
    """Get informations about one specific systemd unit."""

    name = 'SYSTEMD'

    def __init__(self, *args, **kwargs):
        self.unit = kwargs.pop('unit')
        super().__init__(*args, **kwargs)

    def probe(self):
        return Metric(name=self.unit, value=SystemdUnitState(self.unit),
                      context='unit')


class UnitContext(nagiosplugin.Context):

    def __init__(self):
        super(UnitContext, self).__init__('unit')

    def evaluate(self, metric, resource):
        print(metric.value.active_state)
        return self.result_cls(nagiosplugin.Ok, metric=metric)


def get_argparser():
    parser = argparse.ArgumentParser(
        prog='check_systemd',  # To get the right command name in the README.
        formatter_class=lambda prog: argparse.RawDescriptionHelpFormatter(prog, width=80),  # noqa: E501
        description=  # noqa: E251
        'Copyright (c) 2014-18 Andrea Briganti <kbytesys@gmail.com>\n'
        'Copyright (c) 2019-21 Josef Friedrich <josef@friedrich.rocks>\n'
        '\n'
        'Nagios / Icinga monitoring plugin to check systemd.\n',  # noqa: E501
        epilog=  # noqa: E251
        'Performance data:\n'
        '  - count_units\n'
        '  - startup_time\n'
        '  - units_activating\n'
        '  - units_active\n'
        '  - units_failed\n'
        '  - units_inactive\n',
    )

    exclusive_group = parser.add_mutually_exclusive_group()

    exclusive_group.add_argument(
        '-u', '--unit',
        type=str,
        dest='unit',
        help='Name of the systemd unit that is being tested.',
    )

    return parser


def main():
    """The main function"""
    args = get_argparser().parse_args()

    objects = [UnitContext()]

    if args.unit:
        objects.append(UnitResource(unit=args.unit))

    check = nagiosplugin.Check(*objects)
    check.main()


if __name__ == '__main__':
    main()