|
#!/usr/bin/python3 |
|
|
|
from gi.repository import GLib |
|
from gi.repository import Gio |
|
import sys |
|
|
|
SYSTEMD_BUS_NAME = "org.freedesktop.systemd1" |
|
SYSTEMD_MANAGER_PATH = "/org/freedesktop/systemd1" |
|
SYSTEMD_MANAGER_IFACE = "org.freedesktop.systemd1.Manager" |
|
SYSTEMD_UNIT_IFACE = "org.freedesktop.systemd1.Unit" |
|
PROPERTIES_IFACE = "org.freedesktop.DBus.Properties" |
|
|
|
introspection_xml = """ |
|
<node> |
|
<interface name="org.test.ProxyManager"> |
|
<method name='EnsureProxy'> |
|
<arg type='s' name='proxyservice' direction='in'/> |
|
<arg type='s' name='service' direction='in'/> |
|
<arg type='s' name='res' direction='out'/> |
|
</method> |
|
</interface> |
|
</node> |
|
""" |
|
|
|
def active_state_is_active(state): |
|
return state == "active" or state == "reloading" |
|
|
|
|
|
|
|
class Proxy(object): |
|
proxies = [] |
|
|
|
def stop(self): |
|
if self.subscribe_id: |
|
bus.signal_unsubscribe(self.subscribe_id) |
|
assert self in Proxy.proxies |
|
Proxy.proxies.remove(self) |
|
print("Proxy done") |
|
|
|
def fail_ensure(self, error, message): |
|
print("Ensure failed: ", message) |
|
assert self.ensure_invocation != None |
|
self.ensure_invocation.return_dbus_error(error, message) |
|
self.ensure_invocation = None |
|
self.stop() |
|
|
|
def complete_ensure(self): |
|
assert self.ensure_invocation != None |
|
self.ensure_invocation.return_value(GLib.Variant("(s)", ("done",))) |
|
self.ensure_invocation = None |
|
|
|
def __init__(self, proxyservicename, servicename, invocation): |
|
print("New service ", proxyservicename, servicename) |
|
self.service = proxyservicename # Name of the proxy service |
|
self.proxied_service = servicename # Name of the service whos state we proxy |
|
self.ensure_invocation = invocation |
|
self.proxied_service_path = None # Object path of proxied service in systemd |
|
self.initialized = False # On init we do a bunch of async stuff, then set this to true |
|
self.proxy_started = False |
|
self.waiting_for_activation = True |
|
self.subscribe_id = None |
|
self.properties = {} |
|
|
|
Proxy.proxies.append(self) |
|
|
|
bus.call(SYSTEMD_BUS_NAME, SYSTEMD_MANAGER_PATH, |
|
SYSTEMD_MANAGER_IFACE, "LoadUnit", |
|
GLib.Variant("(s)", ( self.proxied_service, )), |
|
GLib.VariantType.new("(o)"), |
|
0, -1, None, |
|
self.unit_loaded_cb) |
|
|
|
def unit_loaded_cb(self, obj, res): |
|
try: |
|
reply = bus.call_finish(res) |
|
except: |
|
return self.fail_ensure("org.freedesktop.DBus.Error.Failed", "LoadUnit failed") |
|
|
|
self.proxied_service_path = reply[0] |
|
|
|
self.subscribe_id = bus.signal_subscribe(SYSTEMD_BUS_NAME, PROPERTIES_IFACE, |
|
"PropertiesChanged", self.proxied_service_path, None, 0, |
|
self._properties_changed_cb) |
|
|
|
bus.call(SYSTEMD_BUS_NAME, self.proxied_service_path, |
|
PROPERTIES_IFACE, "GetAll", |
|
GLib.Variant("(s)", (SYSTEMD_UNIT_IFACE, )), |
|
GLib.VariantType.new("(a{sv})"), |
|
0, -1, None, |
|
self.get_all_props_cb) |
|
|
|
def get_all_props_cb(self, obj, res): |
|
try: |
|
reply = bus.call_finish(res) |
|
except: |
|
return self.fail_ensure("org.freedesktop.DBus.Error.Failed", "GetAll failed") |
|
|
|
self.properties = reply.unpack()[0] |
|
|
|
print("Initialized") |
|
# We got initial GetAll, now start listening on property changes |
|
self.initialized = True |
|
|
|
# Check if service already active, then we don't need to start |
|
if active_state_is_active(self.get_active_state()): |
|
self.service_activated() |
|
else: |
|
# Otherwise we need to start it |
|
print("Starting service ", self.proxied_service) |
|
bus.call(SYSTEMD_BUS_NAME, self.proxied_service_path, |
|
SYSTEMD_UNIT_IFACE, "Start", |
|
GLib.Variant("(s)", ("replace", )), |
|
GLib.VariantType.new("(o)"), |
|
0, -1, None, |
|
self.start_service_cb) |
|
|
|
def service_activated(self): |
|
print("Service is now active") |
|
self.proxy_started = True |
|
self.complete_ensure() |
|
|
|
def start_service_cb(self, obj, res): |
|
try: |
|
reply = bus.call_finish(res) |
|
except: |
|
(te, e, tracback) = sys.exc_info() |
|
Gio.dbus_error_strip_remote_error(e) |
|
return self.fail_ensure("org.freedesktop.DBus.Error.Failed", "Start failed: %s" % (e.message, )) |
|
|
|
print("Service started") |
|
|
|
# Maybe we were activated started while running the start job |
|
if active_state_is_active(self.get_active_state()): |
|
return self.service_activated() |
|
else: |
|
# Else wait for it to change to an active state via property change |
|
self.waiting_for_activation = True |
|
|
|
def _properties_changed_cb(self, connection, sender, path, iface, signal, parameters): |
|
prop_iface = parameters[0] |
|
changes = parameters[1] |
|
|
|
if not self.initialized or prop_iface != SYSTEMD_UNIT_IFACE: |
|
return |
|
|
|
last_properties = self.properties |
|
self.properties = {**self.properties, **changes} |
|
|
|
self.properties_changed(last_properties) |
|
|
|
def properties_changed(self, last_properties): |
|
old_active = last_properties.get("ActiveState") |
|
new_active = self.properties.get("ActiveState") |
|
if old_active != new_active: |
|
self.active_state_changed(new_active, old_active) |
|
|
|
def get_active_state(self): |
|
return self.properties.get("ActiveState") |
|
|
|
def active_state_changed(self, new_active_state, old_active_state): |
|
print("Active changed %s -> %s" % (old_active_state, new_active_state)) |
|
|
|
if self.waiting_for_activation: |
|
if active_state_is_active(new_active_state): |
|
self.waiting_for_activation = False |
|
self.service_activated() |
|
elif new_active_state == "failed": |
|
self.waiting_for_activation = False |
|
self.fail_ensure("org.freedesktop.DBus.Error.Failed", "Starting target failed") |
|
|
|
if self.proxy_started and not active_state_is_active(new_active_state): |
|
print("Stopping service ", self.service) |
|
bus.call(SYSTEMD_BUS_NAME, SYSTEMD_MANAGER_PATH, |
|
SYSTEMD_MANAGER_IFACE, "StopUnit", |
|
GLib.Variant("(ss)", (self.service, "replace", )), |
|
GLib.VariantType.new("(o)"), |
|
0, -1, None, |
|
self.stop_service_cb) |
|
|
|
def stop_service_cb(self, obj, res): |
|
try: |
|
reply = bus.call_finish(res) |
|
except: |
|
pass |
|
print("stop failed", sys.exc_info()) |
|
|
|
self.stop() |
|
|
|
|
|
def handle_method_call(connection, sender, object_path, interface_name, |
|
method_name, parameters, invocation): |
|
if method_name == "EnsureProxy": |
|
proxyservicename = parameters.unpack()[0] |
|
servicename = parameters.unpack()[1] |
|
|
|
proxy = Proxy(proxyservicename, servicename, invocation) |
|
else: |
|
invocation.return_dbus_error("org.freedesktop.DBus.Error.UnknownMethod", |
|
"No such method") |
|
|
|
def handle_get_property(connection, sender, object_path, interface, value): |
|
pass |
|
|
|
def handle_set_property(connection, sender, object_path, interface_name, key, |
|
value): |
|
pass |
|
|
|
def on_bus_acquired(connection, name, *args): |
|
reg_id = Gio.DBusConnection.register_object( |
|
connection, |
|
"/org/test/Proxy", |
|
introspection_data.interfaces[0], |
|
handle_method_call, |
|
handle_get_property, |
|
handle_set_property) |
|
|
|
if reg_id == 0: |
|
print('Error while registering object!') |
|
sys.exit(1) |
|
|
|
def on_name_lost(connection, name, *args): |
|
sys.exit(1) |
|
|
|
def on_name_acquired(connection, name, *args): |
|
pass |
|
|
|
bus = Gio.bus_get_sync(Gio.BusType.SYSTEM, None) |
|
|
|
bus.call(SYSTEMD_BUS_NAME, SYSTEMD_MANAGER_PATH, |
|
SYSTEMD_MANAGER_IFACE, "Subscribe", |
|
GLib.Variant("()", None), None, |
|
0, -1, None) |
|
|
|
introspection_data = Gio.DBusNodeInfo.new_for_xml(introspection_xml) |
|
|
|
owner_id = Gio.bus_own_name(Gio.BusType.SYSTEM, |
|
"org.test.Proxy", |
|
Gio.BusNameOwnerFlags.NONE, |
|
on_bus_acquired, |
|
on_name_acquired, |
|
on_name_lost) |
|
try: |
|
GLib.MainLoop().run() |
|
except KeyboardInterrupt: |
|
pass |
|
|
|
sys.exit(0) |