cardo.Agent

Der cardo Agent ist ein Windows-Dienst, der für die Ausführung asynchroner, länger dauernder Aktionen bestimmt ist.

Inhalt:

Arbeitsweise

Der cardo.Agent verwaltet Aufgaben, die von einer cardo Instanz zur Ausführung übergeben wurden. Schwerpunkt der Entwicklung ist die Ausführung von Code, der sonst nur in der im IIS verfügbaren Anwendung "cardo4" bereitgestellt wird.

Dadurch kann das Deploy der cardo Anwendungen ohne weitere Anpassungen an der Infrastruktur erfolgen.

Der Agent hat eine Bindung zum http-sys Dienst des Windows Servers und stellt alle Methoden per Http Zugang zur Verfügung. Für die Kommunikation gibt es eine fertige Komponente in cardo, als Transport-Protokoll kommt ausschließlich http zum Einsatz.

Potentiell kann der Dienst auch auf einem anderen Server ausgeführt werden, dies ist z.Z. nicht Gegenstand der Umsetzung.

Eine Abseitsaufgabe oder "Job" stellt eine einmalige oder wiederkehrende Aktion dar.

Jobs können durch die Anwendungsentwickler erstellt werden.

Grob skizziert ist die Arbeitsweise des Dienstes:

  • Registrierung eines cardo Projekts für die Ausführung von Arbeitsaufgaben.

    Hier übermittelt cardo einige Daten, die für die Simulation der cardo Instanz erforderlich sind, für die Wahrung der Datensicherheit werden alle Daten verschlüsselt abgelegt.

  • Entgegennehmen einer Arbeitsaufgabe für die Ausführung in dem Projekt.

    Die Arbeitsaufgabe ist dabei Programmcode in Form einer Implementierung des Interfaces IduIT.cardo.Agent.Shared.IRemoteAsyncWorkerBase oder einer der Ableitungen. Übermittelt wird diese Arbeitsaufgabe in Form einer Jobkonfiguration, die den .NET-Typename der Arbeitsaufgabe (und anderes ...) beinhaltet.

  • Einstellen der übermittelten Arbeitsaufgabe in eine "Warteschlange".

    Es werden (z.Z.) maximal zwei Aufgaben gleichzeitig ausgeführt. Beim Beenden/Neustart des Dienstes werden die ausstehenden Aufgaben automatisch für die Abarbeitung neu aufgenommen.

  • Ausführen der Aufgabe in einer "simulierten" cardo Umgebung.

    Die "simulierte" cardo Umgebung von der hier die Rede ist, ist eine vom Agent erstellte .Net App-Domain pro Projekt, in der automatisch (nur...) die für die Ausführung der Aufgabe erforderlichen DLLs in diese App-Domain geladen werden (siehe weiter unten, Arbeitsverzeichnis).

    Damit stehen (fast ...) alle API Funktionen aus cardo zur Verfügung.

    Änderungen an den DLLs (bspw. nach cardo Update) werden derzeit nicht automatisch erkannt, aber nach einem Neustart des Agents.

    Wichtig: Der Agent wird immer als 64 Bit Prozess ausgeführt, cardo sollte also auch als 64 Bit Anwendung konfiguriert sein.

    Wichtig: Als Benutzer-Account für die Ausführung werden die Daten des Nutzer unter dem der JOB erstellt wurde, verwendet.

  • Warten auf den Abruf des Ergebnisses, bzw. Bereitstellen von Funktionen zum Abruf von Fortschritt, Status und Ergebnis der Aktion.

Für die Datensicherheit wird an einigen Stellen Verschlüsselung eingesetzt. Dazu generiert der Agent und cardo jeweils ein Zertifikat, die Public-Keys werden dabei gegenseitig ausgetauscht.

Installation/Konfiguration des Agents

Die Konfiguration des Agents beschränkt sich auf die Angabe des Arbeitsverzeichnisses (Parameter baseDir).

Die Konfiguration für eine cardo Instanz, in dessen Auftrag dann Aufgaben ausgeführt werden, wird von cardo aus an den Dienst übermittelt. Dazu gibt es in den cardo4 Systemeinstellungen den Bereich "cardo-Agent".

Installation als Dienst

Um den Agent als Dienst zu installieren, ist auf der Kommandozeile folgender Befehl mit Administrator-Rechten auszuführen:

IduIT.cardo.Agent.Service.exe -op:RegisterService -baseDir:d:\temp\cardo.Agent

Hinweis: Das unter baseDir angegebene Verzeichnis muss vorhanden sein.

Zum Deinstallieren des Dienstes kann statt RegisterService UnRegisterService als Operation (-op) angegeben werden. Um das Verzeichnis nachträglich zu ändern, kann der Pfad in der Dienstekonfiguration in der Registry angepasst werden.

Ausführung in einer Konsole

Der Agent kann auch ad hoc als Anwendung in einer Konsole ausgeführt werden:

IduIT.cardo.Agent.Service.exe -op:Debug -baseDir:d:\temp\cardo.Agent

Wenn die Agent-Programmdatei im DEBUG-Modus erstellt wurde, kann mit dem Argument -debugger:true das Anfügen eines Debuggers zum Programmstart ausgelöst werden.

Inhalte des Arbeitsverzeichnis des Agents

Das Arbeitsverzeichnis enthält folgende Unterordner (die alle automatisch bei Bedarf erstellt werden), auf die der Dienst lesenden und schreibenden Zugriff erhalten muss:

  • cert: Beim ersten Start des Agents wird ein selbstsigniertes Zertifikat mit Kennwortschutz erstellt. Dieses dient der Verschlüsselung von Daten und für die Signierung von JSON Dateien.

    Wichtiger Hinweis: Wenn das Zertifikat gelöscht wird, sind keine der gespeicherten Konfigurationsdaten oder Jobs mehr lesbar.

  • cardo.configs: Enthält pro cardo Instanz einen weiteren Unterordner. Der Ordnername entspricht dem Präfix des cardo Projektes und enthält folgende Dateien:

    • projectName.der: Der öffentliche Schlüssel des Zertifikats der cardo Instanz.
    • config.json: Die Konfiguration der cardo Instanz als JSON Datei, wie vom Server übermittelt.

      Die Daten der Konfiguration, bspw. Connection-String zur Datenbank, sind mit dem privaten Schlüssel des Agents verschlüsselt und werden bei der Instanziierung der cardo Instanz benötigt. Enthalten ist auch der Speicherort der Assembly's des aufrufenden cardos. Von diesem Verzeichnis werden die Anwendungsdateien (*.dll) bei Bedarf kopiert.

    Wichtiger Hinweis: Die Daten werden nur explizit neu übertragen. Wenn sich also Änderungen an der cardo Konfiguration ergeben, sollte die Registrierung des Agents aktualisiert werden.

  • temp\cardo.Apps: Enthält pro cardo Instanz einen weiteren Unterordner, der als Basis-Verzeichnis der .Net App-Domain dient. Der Ordnername entspricht dem Präfix des cardo Projektes. Der Projektordner wird mit dem Start des ersten Jobs in diesem Projekt angelegt und enthält folgenden Inhalte:

    • app.config: Eine Kopie von Teilen der web.config der zugeordneten cardo Instanz (wird über den bin-Pfad der Konfiguration der cardo Instanz ermittelt).
    • bin (Ordner): Hier werden die Anwendungs-DLLs abgelegt, Quelle ist dabei der Bin-Ordner der zugeordneten cardo-Instanz, der Umfang der DLLs richtet sich nach den in den Jobs verwendeten Komponenten.
    • 00000000-0000-0000-0000-000000000000 (GUID, dynamisch): Dieser Ordner wird als Temp-Folder für cardo bereitgestellt, normalerweise sollte es nur einen Ordner mit einer GUID geben.

    Wie der Name "temp" verrät, kann der Ordner jederzeit gelöscht werden. Dies sollte allerdings immer mit einem Neustart des Dienstes einhergehen.

    Normalerweise sollte das nicht nötig sein, da die Inhalte in cardo.apps bei jedem Dienststart neu erstellt werden.

  • cardo.jobs: Enthält pro cardo Instanz einen weiteren Unterordner. Der Ordnername entspricht dem Präfix des cardo Projektes und enthält die Dateien der "Jobs". Jeder Job bekommt eine eindeutige GUID. Es existieren dabei 1...3 Dateien pro Job:

    • 00000000-0000-0000-0000-000000000000.json: Hier ist die Definition der Arbeitsaufgabe und die Statusdaten enthalten. Wichtiger Hinweis: Der Inhalt sollte nicht manuell geändert werden. Die Eigenschaften im Element SignedContent sind mit einer Signatur versehen, manuelle Änderungen in diesem Block führen dazu, dass der Job nicht mehr geladen werden kann.

      Bestandteil der Konfiguration ist auch der Login-Name und die Gruppen des Nutzers, unter dem die Aufgabe erstellt wurde. Diese Informationen sind zwar im Klartext hinterlegt, da diese aber Bestandteil des SignedContent sind, ist eine Änderung in der .json Datei nicht möglich.

      • 00000000-0000-0000-0000-000000000000.data: Die Ergebnisdaten des Jobs. Dies kann entweder eine Datei oder ein (als JSON) serialisiertes Datenobjekt sein. Die Art des Inhalts ist in der .json Datei im Element "Result" gekennzeichnet. Wenn bei der Job-Definition "EncryptResult" true ist, dann sind die Daten mit dem Public-Key des cardo Servers zu diesem Projekt verschlüsselt.
    • 00000000-0000-0000-0000-000000000000.log: Ein Array von Log-Messages des Jobs aus dem letzten Durchlauf.

      Außer bei wiederkehrenden Aufgaben sollten die Dateien vom Aufrufer automatisch nach der Verwendung des Ergebnisses gelöscht werden.

API

Low-Level

Die Low-Level API für die Kommunikation mit dem Dienst ist in der Klasse IduIT.cardo.Agent.Shared.Client.AgentClient implementiert. Die direkte Verwendung wird hier nicht näher erläutert. Die Funktionen dazu sind im Assembly IduIT.cardo.Agent.Shared.dll ausgelagert, welche Bestandteil der cardo Distribution sind.

Die Erstellung einer Arbeitsaufgabe erfolgt in (cardo...) Anwendungen durch Bereitstellung einer Klasse, die eines der Interfaces implementiert:

  • IduIT.cardo.Agent.Shared.cardo.Agent.Shared.IRemoteAsyncWorkerBase

    Eine einfache Arbeitsaufgabe, ohne Rückgabetyp oder Konfiguration. Hier können bspw. einfache Datenbankaufgaben erledigt werden, die sich direkt im Programmcode ausdrücken.

  • IduIT.cardo.Agent.Shared.IRemoteAsyncWorker<TConfigType,TResultType>

    Diese Arbeitsaufgaben können durch die Konfigurationsmöglichkeit in verschiedenen Kontexten eingesetzt werden und produzieren ein Ergebnis, welches der Aufrufer dann nach Fertigstellung weiterverarbeiten kann.

    Der Rückgabetyp kann dabei ein .Net Objekt sein, welches JSON serialisierbar ist (sein muss) oder eine Ergebnisdatei. Der ResultType muss im Falle einer Datei vom Typ IduIT.Core.IO.IDocumentInput sein.

    Gemischte Typen (MultiPart) werden z.Z. nicht unterstützt.

    Werden die Daten als sicherheitsrelevant (ResultContainsSensitiveData => true) markiert, werden die Daten auf dem Agent Server mit dem Public-Key des cardo Servers verschlüsselt abgelegt und erst auf cardo Seite wieder entschlüsselt.

Implementierung der Klasse für die Arbeitsaufgabe

Hier erklärt mit einem kleinen Beispiel.

Dargestellt ist die Umsetzung einer Aufgabe, die in der Konfiguration die Anzahl der Durchläufe zum "Schlafen" übergeben bekommt und einen String zurückgibt:

Wichtig: Die Hinweise zu der Ausführungsumgebung und dem damit verbundenen Nutzerkontext sollte verstanden werden.

internal class SampleRemoteWorkitem1 : IduIT.cardo.Agent.Shared.IRemoteAsyncWorker<int, string>
{
    public void Configure(int data)
    {
        _numRuns = data;
    }

    public bool Run(cardo.Agent.Shared.ITaskRunState state)
    {
        for (_current = 0; _current < _numRuns && !state.Cancellation.IsCancellationRequested; _current++)
        {
            System.Threading.Thread.Sleep(250);
        }
        return _current == _numRuns - 1;
    }

    public bool ResultContainsSensitiveData => false;

    public cardo.Agent.Shared.WorkerProgress GetProgress()
    {
        return new cardo.Agent.Shared.WorkerProgress()
        {
            Current = _current,
            Max = _numRuns,
            Message = "Laufe..."
        };
    }

    public void Dispose()
    {
        //nix ...
    }


    public string GetResult()
    {
        return _current.ToString() + " ausgeführt";
    }

    private int _numRuns;
    private int _current;
}

Durch die Bereitstellung der simulierten cardo Umgebung können natürlich noch weiterführende Konstrukte verwendet werden, hier als Beispiel mit Zugriff auf eine cardo4 Anwendung durch Verwendung der cardo - ApplicationAccessor Basisklasse, hier sei noch einmal betont: Beachte die Hinweise zu der Ausführungsumgebung und dem damit verbundenen Nutzerkontext.

internal class SampleRemoteWorkitem1 : 
cardo.Core.Api.Applications.ApplicationAccessor<MeineCardoApplication>,
IduIT.cardo.Agent.Shared.IRemoteAsyncWorker<int, string>
{
    public bool Run(cardo.Agent.Shared.ITaskRunState state)
    {
        //Zugriff auf die cardo4 Anwendung
        if(this.App.Config == null)
            throw new Exception("...");

        for (_current = 0; _current < _numRuns && !state.Cancellation.IsCancellationRequested; _current++)
        {
            System.Threading.Thread.Sleep(250);
        }
        return _current == _numRuns - 1;
    }
}

Kommunikation mit dem Agent-Dienst

Für den vereinfachten Umgang mit der Low-Level API steht die Klasse IduIT.cardo.Core.Api.Agent.AgentJobWrapper<T,TConfig,TResultType> zur Verfügung. (leider bekommt man die spitzen Klammern in C# nicht weiter reduziert).

Der folgende Code stellt die Erstellung der Arbeitsaufgabe aus dem obigen Beispiel auf dem Agent-Server dar.

Hier wird einen Moment auf die Fertigstellung der Aufgabe gewartet.

Erfolgt die Fertigstellung nicht in der vorgegebenen Wartezeit, dann muss der Aufrufer die GUID des Jobs für den späteren Abruf speichern.

[IduIT.Core.Web.Ajax.AjaxMethod]
public void AxTest()
{
    //Erstellt den Job
    using var sleepJob = new cardo.Core.Api.Agent.AgentJobWrapper<SampleRemoteWorkitem1, int, string>(
        title: "Einfach nur fünfmal Schlafen",
        config: 5);

    if(sleepJob.Wait(new TimeSpan(hours:0,minutes:0,seconds:20)))
    {
        if(sleepJob.TryGetResult(out var strResult))
        {
            //alles am Stück erledigt
            return;
        }
    }

    //Speichern der Guid, 
    var jobGuid = sleepJob.JobGuid.Value;

    //Etwas später ... in einem anderen Request ...
    using var sleepJobLater = new cardo.Core.Api.Agent.AgentJobWrapper<SampleRemoteWorkitem1, int, string>(jobGuid);
    if (sleepJob.TryGetResult(out var strResult))
    {
        //dann fertig ...
    }
}

Zuletzt geändert: 22.04.2024 18:19:51 (erstmals erstellt 31.12.2023)