Graphic Listen to me!

Erfahren Sie, wie Sie über Listener Module sämtliche Änderungen im UCS Verzeichnisdienst quer über alle angeschlossenen Services abgleichen und kontrollieren können.

Mit Sicherheit setzen auch Sie in Ihrem Unternehmen diverse (Cloud-)Services ein und diese Services nehmen bei Bedarf Änderungen in Ihrem Verzeichnisdienst wie Active Directory oder OpenLDAP vor. In heterogenen Umgebungen, in denen UCS typischerweise eingesetzt wird, stellt sich daher schnell die Frage, wie bekommt eigentlich Service A Änderungen mit, die Service B an Objekten im Verzeichnisdienst vorgenommen hat, die aber für beide Services relevant sind? Beispiel: Ein Drucker wird zum Netzwerk hinzugefügt und tritt der UCS Domäne bei, woraufhin dann die Druckerliste in der Konfigurationsdatei des Druckerservices (CUPS) aktualisiert und der Dienst neu gestartet wird.

Ich kann die obige Frage auch noch einfacher stellen, da UCS im Kern auf OpenLDAP basiert, das alle wichtigen Informationen verarbeitet und speichert, und zwar:

„Wie bekommt Service A überhaupt mit, dass sich etwas im OpenLDAP geändert hat?“

Direkte Abfragen über OpenLDAP

Ein sehr übliches Tool ist die direkte Abfrage (ldapsearch) im OpenLDAP, die mein Kollege Timo Denissen bereits in seinem Artikel How to Integrate with LDAP: „Generische LDAP-Anbindung“ beschrieb.

Der Vorteil ist, dass diese Art der Abfrage sehr weit verbreitet ist und damit auch schon von sehr vielen Open-Source-Bibliotheken in allen möglichen Programmiersprachen implementiert wurde. Für einfache Abfragen wie beispielsweise die Authentifizierung muss ein Service-Anbieter hier also fast nie etwas selbst entwickeln.

Der Nachteil ist jedoch, dass direkte Abfragen im OpenLDAP relativ starr in der Handhabung sind:

  1. Um Änderungen im LDAP abzufragen, muss man selbstständig Abfragen programmieren.
  2. Wenn man Änderungen von vielen Objekten mitbekommen möchte, muss man also auch jeweils viele Abfragen durchführen.
  3. Die Objekte sind immer nur im aktuellsten Zustand vorhanden. Das bedeutet, man kann nicht ohne Weiteres erkennen, ob sich eventuell nur bestimmte Attribute geändert haben.

Vorteile von Listener Modulen

Genau diese Nachteile lösen Listener-Module. Sie dienen in einer UCS Domäne als mächtiges Werkzeug, um Änderungen und Events im LDAP sehr granular zu monitoren und bei Bedarf die Zustände von Objekten abzufragen.

Die Vorteile von Listener-Modulen liegen dabei auf der Hand:

  1. Änderungen aus dem LDAP werden granular herausgeschrieben.
  2. Die Objekte stehen jeweils im „alten“ und „neuen“ Zustand zur Verfügung.
  3. Das Ganze funktioniert asynchron. Das heißt, wenn ein Service zeitweilig nicht erreichbar ist, werden Änderungen auch noch nachträglich eingespielt.

Grafik über die Interaktion der UMC, OpenLDAP und Listener Module

Überblick über Listener-Module

Sie selbst können auch Listener-Module in einer UCS-Domäne schreiben, anmelden und nutzen. Es sind – teilweise auch sehr einfache – Python-Skripte, die eine UCS-API erfüllen. Diese wurden so konzipiert, dass möglichst einfach Informationen über Änderungen im Verzeichnisdienst an die Listener-Module gegeben werden können. Eine detaillierte Beschreibung hierzu finden Sie im Univention Wiki.

Das UCS System verwendet selbst vielfach Listener-Module, da es eine sehr mächtige Schnittstelle darstellt. Beispielsweise findet die komplette Replikation von Master zu Backups oder Slaves per Listener-Mechanismus statt.

Außerdem viele weitere Services wie beispielsweise Folgende:

  1. Die Generierung von Logon-Skripten für Schüler und Lehrer in den Schulen bei UCS@school
  2. Die Druckerzuweisung zu Computerobjekten oder Gruppen in Windowsdomänen
  3. Das opsi – Client Management, um Rechner zu synchronisieren.

Grober Ablauf von Listener Modulen

Die Informationen selbst kommen aus dem LDAP. Ein UCS-spezifisches Overlay-Modul (TransLog) schreibt die (LDAP) Transaktionen im Verzeichnis heraus und übermittelt sie an den sogenannten Notifier, der auf dem UCS Master und den Backups läuft. Mit diesem Notifier baut der Listener eine Verbindung auf und wird somit benachrichtigt, wenn für ihn relevante Änderungen stattgefunden haben. Listener können sich dabei auch auf verschiedenen Systemen befinden. Hauptsache ist, dass es eine Verbindung zwischen Notifier und Listener gibt. Der Listener selbst wiederum gibt seine Informationen an die bei ihm angemeldeten Listener-Module weiter.

Graphic about processes in the UMC with notifier and listener

Detaillierter Ablauf

  1. Änderungen (Transaktionen) werden mittels eines Overlay-Moduls (TransLog) in eine Datei (/var/lib/univention-ldap/notify/transaction) geschrieben. Angegeben werden eine einmalige ID, die DN und der Event-Typ (Hinzufügen, Editieren, Entfernen).
  2. Der Notifier liest die Transaktions-Datei.
  3. Der Notifier unterrichtet die Listener und diese fragen den aktuellen Status des Objekts anhand der ID, DN und des Event-Typs ab.
  4. Der Listener hat (im lokalen Verzeichnis) das alte Objekt und jetzt das neue.
  5. Die Objekte werden vom Listener an die Listener-Module weitergegeben.
  6. Die Listener-Module entscheiden selbstständig anhand von LDAP-Filtern, ob sie auf diese LDAP-Änderungen reagieren müssen / wollen. Dies schließt die Replikation des neues Objektes ins lokale Verzeichnis mit ein.
  7. Der Listener speichert abschließend noch die ID der Transaktion, die er abgearbeitet hat.

Detailed listener module process in UCS

Hilfreiche Kommandos für Listener-Module

  • Für eine Übersicht der installierten Listener-Module eignet sich folgendes Kommando:
root@ucsma01:~# univention-directory-listener-ctrl modules
  • Um bestehende Listener-Module komplett neu zu resynchronisieren, eignet sich folgendes Kommando:
univention-directory-listener-ctrl resync modulname
  • Für die oben genannten Beispiele wären das:
root@ucsma01:~# univention-directory-listener-ctrl resync ucs-school-user-logonscript
root@ucsma01:~# univention-directory-listener-ctrl resync univention-printer-assignment
root@ucsma01:~# univention-directory-listener-ctrl resync opsilistener

„univention-directory-listener-ctrl resync ucs-school-user-logonscript“ generiert alle Logon-Skripte unter /var/lib/samba/netlogon/user/ neu.

„univention-directory-listener-ctrl resync univention-printer-assignment“ generiert alle VBS-Skripte unter /var/lib/samba/netlogon/printerassignment/ neu.

„univention-directory-listener-ctrl resync opsilistener“ resynchronisiert alle Rechner aus dem LDAP in die opsi-Datenbank.

Weitergehende Möglichkeiten finden Sie im Administrationshandbuch.

  • Neu starten kann man den Listener inklusive dem Laden aller Module mit:
root@ucsma01:~# service univention-directory-listener restart
  • Die Logs werden in das listener.log unter /var/log/univention/listener.log geschrieben.
  • Mit folgendem Kommando kann man das Log-Level {0,1,2,3,4} ändern:
root@ucsma01:~# ucr set listener/debug/level=XY

Wie baue ich ein Listener-Modul?

Um das Ganze etwas zu veranschaulichen, möchte ich Ihnen hiermit ein Praxisbeispiel zeigen, in dem ich ein kleines Listener-Modul schreibe, das eine Web-API anspricht, um Lehrer, die neu im LDAP angelegt wurden, auch in einem Web-Service anzulegen. Für eine weitere Dokumentation eignet sich darüber hinaus das Entwicklerhandbuch. Ich nenne das Listener-Modul „Web-Connector“.

1. Grundsätzlich reicht es zunächst, eine Datei in das Verzeichnis /usr/lib/univention-directory-listener/system/ zu legen:

root@ucsma01:~# vim /usr/lib/univention-directory-listener/system/web_connector.py

2. Jedes Listener-Modul benötigt ein paar Definitionen.

name = "web_connector"
description = "Der Web-Connector spricht eine Web-API an."
filter = "(objectClass=ucsschoolTeacher)"
attribute = ["ucsschoolSchool", "departmentNumber:"]
  • „name“ ist vermutlich selbsterklärend.
  • „description“ vermutlich ebenfalls.
  • „filter“ ist der LDAP-Filter, der die Objekte auswählt, die verarbeitet werden sollen. Hier kann man gut univention-ldapsearch zu Hilfe nehmen. In diesem Fall werden alle Lehrer (objectClass=ucsschoolTeacher) verarbeitet.

3. Außerdem muss mindestens eine Methode (handler) implementiert werden:

def handler(dn, new, old): pass

Das wäre nun schon ein „Hello World!“-Beispiel und funktioniert ohne Weiteres.

1. Mit dem folgenden Kommando (siehe auch oben „Hilfreiche Kommandos für Listener-Module“) würde man das Listener-Modul laden:

root@ucsma01:~# service univention-directory-listener restart

2. Mit dem folgenden Kommando kann man dies prüfen:

root@ucsma01:~# univention-directory-listener-ctrl modules
[...]
0       web_connector   /usr/lib/univention-directory-listener/system/web_connector.py
[...]

Erscheint das Listener-Modul nicht in der Liste der Module, sollte man zunächst im listener.log unter /var/log/univention/listener.log schauen, was die Ursache sein könnte.

1. Im nächsten Schritt möchte man nun noch das Listener-Modul ein klein wenig erweitern. Dazu folgt ein vollständiges Beispiel:


__package__ = ""  # workaround for PEP 366
import univention.debug as ud
import requests

name = "web_connector"
description = "Der Web-Connector spricht eine Web-API an."
filter = "(objectClass=ucsschoolTeacher)"
attribute = ["ucsschoolSchool", "departmentNumber:"]

def handler(dn, new, old):
        ud.debug(ud.LISTENER, ud.ERROR, 'New teacher "%s"' % new)
        ud.debug(ud.LISTENER, ud.ERROR, 'Old teacher "%s"' % old)
        if new and not old:
                r = requests.post("https://call.webservice.org", data={'name': new['uid'], 'type': 'teacher', 'action': 'create'})
        else:
                pass
  • Die Zeile 1 __package__ = „“ # workaround for PEP 366 ist für relative Imports.
  • Die Zeile 2 import univention.debug as ud importiert die Univention-eigenen Module zum Debuggen / Loggen. Hier könnten selbstverständlich auch direkt die Python-eigenen Logging-Werkzeuge verwendet werden.
  • Die Zeile 3 import requests importiert die Request-Bibliothek.
  • Die Definitionen in den Zeilen 4-7 wurden oben schon beschrieben.
  • Die Zeile 8 attribute = [„ucsschoolSchool“, „departmentNumber:“] definiert zusätzlich noch eine Python-Liste von Attributen des Objektes, in denen das Listener-Modul aufgerufen wird, wenn es Änderungen gibt.
  • Die Zeile 10 def handler(dn, new, old): definiert die handler-Funktion.
  • Die Zeile 11 ud.debug(ud.LISTENER, ud.ERROR, ‚New teacher „%s“‚ % new) loggt das neue Objekt im LDAP. In diesem Fall ist das Log-Level auf ud.ERROR gesetzt, um in jedem Fall etwas im listener.log zu sehen. Das könnte gegebenenfalls noch angepasst werden.
  • Die Zeile 12 ud.debug(ud.LISTENER, ud.ERROR, ‚Old teacher „%s“‚ % old) loggt das alte Objekt im LDAP.
  • Die Zeile 13 if new and not old: definiert ein IF-Statement, in das nur gegangen wird, wenn ein Objekt im LDAP neu angelegt wurde.
  • Die Zeile 14 r = requests.post(„https://call.webservice.org“, data={’name‘: new[‚uid‘], ‚type‘: ‚teacher‘, ‚action‘: ‚create‘}) definiert einen Beispielaufruf einer Web-API.
  • Die Zeilen 15 & 16 schließen das Beispiel ab.

Für weitergehende Beispiele können Sie auch gerne im Entwicklerhandbuch nachsehen.

Zusätzliche Links, die wir Ihnen empfehlen, sind:

1. Univention Directory Listener
2. Listener/Notifier-Domänenreplikation

Wir hoffen, Ihnen einen guten Überblick über die Funktion von Listener-Modulen gegeben zu haben und Tipps, wie Sie diese selbst bauen können. Bei weiteren Fragen helfen wir Ihnen gern über unser Forum weiter oder nutzen Sie einfach unsere Kommentarfunktion auf dieser Seite.

Ihr Kommentar

UCS Core Edition jetzt kostenfrei einsetzen!

Zum Downloadbereich
Michel Smidt

Michel arbeitet seit Januar 2014 bei Univention zunächst im Team des Professional Services als Projektmanager im Bildungsbereich. Hier war er in verschiedenen Projekten im Schulträger-Umfeld involviert. Aktuell verantwortet er als Produkt Manager Education den gesamten Bildungsbereich bei Univention und arbeitet daran, in Deutschland die digitale Bildung nachhaltig voranzubringen. Wenn er neben Familie und Arbeit mal Zeit findet, sind seine persönlichen Interessen Laufen, Fußball und Kochen.

Was ist Ihre Meinung? Hinterlassen Sie einen Kommentar!

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert