E Beispill vun enger Event-Undriff Applikatioun baséiert op Webhooks an der S3 Objektlagerung vu Mail.ru Cloud Solutions

E Beispill vun enger Event-Undriff Applikatioun baséiert op Webhooks an der S3 Objektlagerung vu Mail.ru Cloud Solutions
Rube Goldberg Kaffismaschinn

Event-driven Architektur erhéicht d'Käschteeffizienz vun de benotzte Ressourcen well se nëmmen am Moment benotzt ginn wann se gebraucht ginn. Et gi vill Méiglechkeeten fir dëst ëmzesetzen an net zousätzlech Cloud Entitéiten als Aarbechterapplikatiounen ze kreéieren. An haut wäert ech net iwwer FaaS schwätzen, mee iwwer Webhooks. Ech weisen engem Tutorial Beispill vun Ëmgank Eventer benotzt Objet Stockage Webhooks.

E puer Wierder iwwer Objektlagerung a Webhooks. Objektspeicher erlaabt Iech all Daten an der Wollek a Form vun Objeten ze späicheren, zougänglech iwwer S3 oder eng aner API (ofhängeg vun der Implementatioun) iwwer HTTP/HTTPS. Webhooks sinn allgemeng personaliséiert HTTP Callbacks. Si ginn normalerweis vun engem Event ausgeléist, wéi zum Beispill Code an e Repository gedréckt oder e Kommentar op engem Blog gepost. Wann en Event geschitt, schéckt d'Origine Site eng HTTP-Ufro un d'URL, déi fir de Webhook spezifizéiert ass. Als Resultat kënnt Dir Eventer op engem Site maachen Aktiounen op engem aneren ausléisen (Wiki). Am Fall wou d'Quell Site en Objektlagerung ass, handelen d'Evenementer als Ännerunge fir säin Inhalt.

Beispiller vun einfache Fäll wou esou Automatisatioun ka benotzt ginn:

  1. Erstellt Kopie vun all Objeten an enger anerer Wolleklagerung. Exemplare mussen direkt erstallt ginn wann Dateien derbäigesat oder geännert ginn.
  2. Automatesch Schafung vun enger Serie vu Miniaturbiller vu Grafikdateien, Waasserzeechen op Fotoen bäizefügen, an aner Bildmodifikatiounen.
  3. Notifikatioun iwwer d'Arrivée vun neien Dokumenter (zum Beispill, e verdeelt Comptablesmethod Service lued Berichter op d'Wollek, a finanziell Iwwerwachung kritt Notifikatiounen iwwer nei Berichter, kontrolléiert an analyséiert se).
  4. E bëssi méi komplexe Fäll betrëfft zum Beispill eng Ufro un Kubernetes ze generéieren, déi e Pod mat den néidege Behälter erstellt, Aufgabeparameter un et passéiert, an no der Veraarbechtung de Container zesummeklappt.

Als Beispill wäerte mir eng Variant vun der Aufgab 1 maachen, wann Ännerungen am Mail.ru Cloud Solutions (MCS) Objektspeicher Eemer an der AWS Objektlagerung mat Webhooks synchroniséiert ginn. An engem richtege gelueden Fall soll asynchron Aarbecht geliwwert ginn andeems Dir Webhooks an enger Schlaang registréiert, awer fir d'Ausbildungsaufgab wäerte mir d'Ëmsetzung ouni dëst maachen.

Aarbecht Schema

Den Interaktiounsprotokoll gëtt am Detail beschriwwen Guide fir S3 Webhooks op MCS. D'Aarbechtsschema enthält déi folgend Elementer:

  • Verëffentlechungsservice, déi op der S3-Speichersäit ass a publizéiert HTTP-Ufroen wann de Webnhook ausgeléist gëtt.
  • Webhook Empfang Server, deen op Ufroe vum HTTP-Verëffentlechungsdéngscht lauschtert a passend Aktiounen ausféiert. De Server kann an all Sprooch geschriwwe ginn; an eisem Beispill schreiwen mir de Server op Go.

Eng speziell Feature vun der Implementatioun vu Webhooks an der S3 API ass d'Aschreiwung vum Webhook Empfangsserver um Verëffentlechungsservice. Besonnesch muss de Webhook Empfangsserver den Abonnement op Messagen vum Verëffentlechungsservice bestätegen (an anere Webhook Implementatiounen ass d'Bestätegung vum Abonnement normalerweis net erfuerderlech).

Deementspriechend muss de Webhook Empfangsserver zwee Haaptoperatiounen ënnerstëtzen:

  • reagéiert op d'Ufro vum Verëffentlechungsservice fir d'Aschreiwung ze bestätegen,
  • Prozess erakommen Evenementer.

Installéiere vun engem Webhook Empfangsserver

Fir de Webhook Empfangsserver auszeféieren, braucht Dir e Linux Server. An dësem Artikel, als Beispill, benotze mir eng virtuell Instanz déi mir op MCS ofsetzen.

Loosst eis déi néideg Software installéieren an de Webhook Empfangsserver starten.

ubuntu@ubuntu-basic-1-2-10gb:~$ sudo apt-get install git
Reading package lists... Done
Building dependency tree
Reading state information... Done
The following packages were automatically installed and are no longer required:
  bc dns-root-data dnsmasq-base ebtables landscape-common liblxc-common 
liblxc1 libuv1 lxcfs lxd lxd-client python3-attr python3-automat 
python3-click python3-constantly python3-hyperlink
  python3-incremental python3-pam python3-pyasn1-modules 
python3-service-identity python3-twisted python3-twisted-bin 
python3-zope.interface uidmap xdelta3
Use 'sudo apt autoremove' to remove them.
Suggested packages:
  git-daemon-run | git-daemon-sysvinit git-doc git-el git-email git-gui 
gitk gitweb git-cvs git-mediawiki git-svn
The following NEW packages will be installed:
  git
0 upgraded, 1 newly installed, 0 to remove and 46 not upgraded.
Need to get 3915 kB of archives.
After this operation, 32.3 MB of additional disk space will be used.
Get:1 http://MS1.clouds.archive.ubuntu.com/ubuntu bionic-updates/main 
amd64 git amd64 1:2.17.1-1ubuntu0.7 [3915 kB]
Fetched 3915 kB in 1s (5639 kB/s)
Selecting previously unselected package git.
(Reading database ... 53932 files and directories currently installed.)
Preparing to unpack .../git_1%3a2.17.1-1ubuntu0.7_amd64.deb ...
Unpacking git (1:2.17.1-1ubuntu0.7) ...
Setting up git (1:2.17.1-1ubuntu0.7) ...

Klon den Dossier mam Webhook Empfangsserver:

ubuntu@ubuntu-basic-1-2-10gb:~$ git clone
https://github.com/RomanenkoDenys/s3-webhook.git
Cloning into 's3-webhook'...
remote: Enumerating objects: 48, done.
remote: Counting objects: 100% (48/48), done.
remote: Compressing objects: 100% (27/27), done.
remote: Total 114 (delta 20), reused 45 (delta 18), pack-reused 66
Receiving objects: 100% (114/114), 23.77 MiB | 20.25 MiB/s, done.
Resolving deltas: 100% (49/49), done.

Loosst eis de Server starten:

ubuntu@ubuntu-basic-1-2-10gb:~$ cd s3-webhook/
ubuntu@ubuntu-basic-1-2-10gb:~/s3-webhook$ sudo ./s3-webhook -port 80

Abonnéiert Iech op de Verëffentlechungsservice

Dir kënnt Äre Webhook Empfangsserver iwwer d'API oder Webinterface registréieren. Fir d'Einfachheet registréiere mir eis iwwer de Webinterface:

  1. Loosst eis an d'Eemere Sektioun goen am Kontrollraum.
  2. Gitt op den Eemer fir dee mir Webhooks konfiguréieren a klickt op de Gang:

E Beispill vun enger Event-Undriff Applikatioun baséiert op Webhooks an der S3 Objektlagerung vu Mail.ru Cloud Solutions

Gitt op de Webhooks Tab a klickt Add:

E Beispill vun enger Event-Undriff Applikatioun baséiert op Webhooks an der S3 Objektlagerung vu Mail.ru Cloud Solutions
Fëllt d'Felder aus:

E Beispill vun enger Event-Undriff Applikatioun baséiert op Webhooks an der S3 Objektlagerung vu Mail.ru Cloud Solutions

ID - den Numm vum Webhook.

Event - wéi eng Eventer ze vermëttelen. Mir hunn d'Iwwerdroung vun all Eventer festgeluecht, déi optrieden wann Dir mat Dateien schafft (derbäisetzen a läschen).

URL - Webhook déi Serveradress kritt.

Filter Präfix / Suffix ass e Filter deen Iech erlaabt Webhooks nëmme fir Objeten ze generéieren deenen hir Nimm mat bestëmmte Reegele passen. Zum Beispill, fir datt de Webhook nëmmen Dateien mat der .png Extensioun ausléist, an Filter Suffix Dir musst "png" schreiwen.

De Moment ginn nëmmen Ports 80 an 443 ënnerstëtzt fir Zougang zum Webhook Empfangsserver ze kréien.

Loosst eis klickt Haken addéieren a mir wäerten déi folgend gesinn:

E Beispill vun enger Event-Undriff Applikatioun baséiert op Webhooks an der S3 Objektlagerung vu Mail.ru Cloud Solutions
Hook dobäi.

De Webhook Empfangsserver weist a senge Logbicher de Fortschrëtt vum Hakenregistrierungsprozess:

ubuntu@ubuntu-basic-1-2-10gb:~/s3-webhook$ sudo ./s3-webhook -port 80
2020/06/15 12:01:14 [POST] incoming HTTP request from 
95.163.216.92:42530
2020/06/15 12:01:14 Got timestamp: 2020-06-15T15:01:13+03:00 TopicArn: 
mcs5259999770|myfiles-ash|s3:ObjectCreated:*,s3:ObjectRemoved:* Token: 
E2itMqAMUVVZc51pUhFWSp13DoxezvRxkUh5P7LEuk1dEe9y URL: 
http://89.208.199.220/webhook
2020/06/15 12:01:14 Generate responce signature: 
3754ce36636f80dfd606c5254d64ecb2fd8d555c27962b70b4f759f32c76b66d

Aschreiwung ass ofgeschloss. An der nächster Sektioun wäerte mir den Algorithmus vun der Operatioun vum Webhook Empfangsserver méi no kucken.

Beschreiwung vum Webhook Empfangsserver

An eisem Beispill gëtt de Server a Go geschriwwen. Loosst eis d'Basisprinzipien vu senger Operatioun kucken.

package main

// Generate hmac_sha256_hex
func HmacSha256hex(message string, secret string) string {
}

// Generate hmac_sha256
func HmacSha256(message string, secret string) string {
}

// Send subscription confirmation
func SubscriptionConfirmation(w http.ResponseWriter, req *http.Request, body []byte) {
}

// Send subscription confirmation
func GotRecords(w http.ResponseWriter, req *http.Request, body []byte) {
}

// Liveness probe
func Ping(w http.ResponseWriter, req *http.Request) {
    // log request
    log.Printf("[%s] incoming HTTP Ping request from %sn", req.Method, req.RemoteAddr)
    fmt.Fprintf(w, "Pongn")
}

//Webhook
func Webhook(w http.ResponseWriter, req *http.Request) {
}

func main() {

    // get command line args
    bindPort := flag.Int("port", 80, "number between 1-65535")
    bindAddr := flag.String("address", "", "ip address in dot format")
    flag.StringVar(&actionScript, "script", "", "external script to execute")
    flag.Parse()

    http.HandleFunc("/ping", Ping)
    http.HandleFunc("/webhook", Webhook)

log.Fatal(http.ListenAndServe(*bindAddr+":"+strconv.Itoa(*bindPort), nil))
}

Bedenkt d'Haaptfunktiounen:

  • Ping () - e Wee deen iwwer URL / Ping reagéiert, déi einfachst Ëmsetzung vun enger Liveness Sonde.
  • Webhook() - Haaptroute, URL/Webhook Handler:
    • bestätegt d'Aschreiwung am Verëffentlechungsservice (gitt op d'AbonnementConfirmation Funktioun),
    • veraarbecht erakommen Webhooks (Gorecords Funktioun).
  • Funktioune HmacSha256 an HmacSha256hex sinn Implementatioune vun den HMAC-SHA256 an HMAC-SHA256 Verschlësselungsalgorithmen mat Ausgang als String vun hexadezimalen Zuelen fir d'Berechnung vun der Ënnerschrëft.
  • main ass d'Haaptfunktioun, veraarbecht Kommandozeilparameter a registréiert URL Handler.

Kommandozeilparameter akzeptéiert vum Server:

  • -port ass den Hafen op deem de Server lauschtert.
  • -Adress - IP Adress déi de Server nolauschtert.
  • -Script ass en externe Programm dee fir all erakommen Hook genannt gëtt.

Loosst eis e puer vun de Funktiounen méi genau kucken:

//Webhook
func Webhook(w http.ResponseWriter, req *http.Request) {

    // Read body
    body, err := ioutil.ReadAll(req.Body)
    defer req.Body.Close()
    if err != nil {
        http.Error(w, err.Error(), 500)
        return
    }

    // log request
    log.Printf("[%s] incoming HTTP request from %sn", req.Method, req.RemoteAddr)
    // check if we got subscription confirmation request
    if strings.Contains(string(body), 
""Type":"SubscriptionConfirmation"") {
        SubscriptionConfirmation(w, req, body)
    } else {
        GotRecords(w, req, body)
    }

}

Dës Funktioun bestëmmt ob eng Ufro fir d'Aschreiwung ze bestätegen oder e Webhook ukomm ass. Wéi follegt aus Dokumentatioun, wann d'Registrierung bestätegt ass, gëtt déi folgend Json Struktur an der Post Ufro kritt:

POST http://test.com HTTP/1.1
x-amz-sns-messages-type: SubscriptionConfirmation
content-type: application/json

{
    "Timestamp":"2019-12-26T19:29:12+03:00",
    "Type":"SubscriptionConfirmation",
    "Message":"You have chosen to subscribe to the topic $topic. To confirm the subscription you need to response with calculated signature",
    "TopicArn":"mcs2883541269|bucketA|s3:ObjectCreated:Put",
    "SignatureVersion":1,
    "Token":«RPE5UuG94rGgBH6kHXN9FUPugFxj1hs2aUQc99btJp3E49tA»
}

Dës Ufro muss beäntwert ginn:

content-type: application/json

{"signature":«ea3fce4bb15c6de4fec365d36bcebbc34ccddf54616d5ca12e1972f82b6d37af»}

Wou d'Ënnerschrëft berechent gëtt wéi:

signature = hmac_sha256(url, hmac_sha256(TopicArn, 
hmac_sha256(Timestamp, Token)))

Wann e Webhook ukomm ass, gesäit d'Struktur vun der Post Ufro esou aus:

POST <url> HTTP/1.1
x-amz-sns-messages-type: SubscriptionConfirmation

{ "Records":
    [
        {
            "s3": {
                "object": {
                    "eTag":"aed563ecafb4bcc5654c597a421547b2",
                    "sequencer":1577453615,
                    "key":"some-file-to-bucket",
                    "size":100
                },
            "configurationId":"1",
            "bucket": {
                "name": "bucketA",
                "ownerIdentity": {
                    "principalId":"mcs2883541269"}
                },
                "s3SchemaVersion":"1.0"
            },
            "eventVersion":"1.0",
            "requestParameters":{
                "sourceIPAddress":"185.6.245.156"
            },
            "userIdentity": {
                "principalId":"2407013e-cbc1-415f-9102-16fb9bd6946b"
            },
            "eventName":"s3:ObjectCreated:Put",
            "awsRegion":"ru-msk",
            "eventSource":"aws:s3",
            "responseElements": {
                "x-amz-request-id":"VGJR5rtJ"
            }
        }
    ]
}

Deementspriechend, ofhängeg vun der Demande, musst Dir verstoen wéi d'Daten veraarbecht ginn. Ech hunn den Entrée als Indikator gewielt "Type":"SubscriptionConfirmation", well et ass an der Abonnement Bestätegung Ufro präsent an ass net präsent am Webhook. Baséierend op der Präsenz/Feele vun dëser Entrée an der POST Ufro, weider Ausféierung vum Programm geet entweder op d'Funktioun SubscriptionConfirmation, oder an d'Funktioun GotRecords.

Mir wäerten d'AbonnementConfirmatiounsfunktioun net am Detail betruechten; et gëtt ëmgesat no de Prinzipien, déi an Dokumentatioun. Dir kënnt de Quellcode fir dës Funktioun kucken op Projet git Repositories.

D'GotRecords Funktioun parséiert eng erakommen Ufro a rifft fir all Record-Objet en externe Skript (deem säin Numm am -script Parameter passéiert gouf) mat de Parameteren:

  • Eemer Numm
  • Objet Schlëssel
  • Aktioun:
    • kopéieren - wann an der ursprénglecher Ufro EventName = ObjectCreated | PutObject | PutObjectCopy
    • läschen - wann an der Original Ufro EventName = ObjectRemoved | DeleteObject

Also, wann en Haken mat enger Post Ufro ukomm ass, wéi beschriwwen méi héich, an de Parameter -script=script.sh da gëtt de Skript wéi follegt genannt:

script.sh  bucketA some-file-to-bucket copy

Et sollt verstane ginn datt dëse Webhook Empfangsserver net eng komplett Produktiounsléisung ass, awer e vereinfacht Beispill vun enger méiglecher Ëmsetzung.

Beispill vun Aarbecht

Loosst eis d'Dateien aus dem Haapt Eemer am MCS an de Backup Eemer an AWS synchroniséieren. Den Haapt Eemer gëtt myfiles-ash genannt, de Backup gëtt myfiles-backup genannt (Eemerekonfiguratioun an AWS ass iwwer den Ëmfang vun dësem Artikel). Deementspriechend, wann eng Datei an den Haapt Eemer plazéiert ass, soll seng Kopie an der Backupsatellit erschéngen, a wann se aus der Haaptrei geläscht gëtt, soll se an der Backupsatellit geläscht ginn.

Mir wäerte mat Eemer schaffen mat der awscli Utility, déi mat MCS Cloud Storage an AWS Cloud Storage kompatibel ass.

ubuntu@ubuntu-basic-1-2-10gb:~$ sudo apt-get install awscli
Reading package lists... Done
Building dependency tree
Reading state information... Done
After this operation, 34.4 MB of additional disk space will be used.
Unpacking awscli (1.14.44-1ubuntu1) ...
Setting up awscli (1.14.44-1ubuntu1) ...

Loosst eis den Zougang zu der S3 MCS API konfiguréieren:

ubuntu@ubuntu-basic-1-2-10gb:~$ aws configure --profile mcs
AWS Access Key ID [None]: hdywEPtuuJTExxxxxxxxxxxxxx
AWS Secret Access Key [None]: hDz3SgxKwXoxxxxxxxxxxxxxxxxxx
Default region name [None]:
Default output format [None]:

Loosst eis den Zougang zum AWS S3 API konfiguréieren:

ubuntu@ubuntu-basic-1-2-10gb:~$ aws configure --profile aws
AWS Access Key ID [None]: AKIAJXXXXXXXXXXXX
AWS Secret Access Key [None]: dfuerphOLQwu0CreP5Z8l5fuXXXXXXXXXXXXXXXX
Default region name [None]:
Default output format [None]:

Loosst eis d'Zougäng iwwerpréiwen:

An AWS:

ubuntu@ubuntu-basic-1-2-10gb:~$ aws s3 ls --profile aws
2020-07-06 08:44:11 myfiles-backup

Fir MCS, wann Dir de Kommando leeft, musst Dir -endpoint-url addéieren:

ubuntu@ubuntu-basic-1-2-10gb:~$ aws s3 ls --profile mcs --endpoint-url 
https://hb.bizmrg.com
2020-02-04 06:38:05 databasebackups-0cdaaa6402d4424e9676c75a720afa85
2020-05-27 10:08:33 myfiles-ash

Zougang.

Loosst eis elo e Skript schreiwen fir den erakommende Hook ze veraarbecht, loosst eis et s3_backup_mcs_aws.sh nennen

#!/bin/bash
# Require aws cli
# if file added — copy it to backup bucket
# if file removed — remove it from backup bucket
# Variables
ENDPOINT_MCS="https://hb.bizmrg.com"
AWSCLI_MCS=`which aws`" --endpoint-url ${ENDPOINT_MCS} --profile mcs s3"
AWSCLI_AWS=`which aws`" --profile aws s3"
BACKUP_BUCKET="myfiles-backup"

SOURCE_BUCKET=""
SOURCE_FILE=""
ACTION=""

SOURCE="s3://${SOURCE_BUCKET}/${SOURCE_FILE}"
TARGET="s3://${BACKUP_BUCKET}/${SOURCE_FILE}"
TEMP="/tmp/${SOURCE_BUCKET}/${SOURCE_FILE}"

case ${ACTION} in
    "copy")
    ${AWSCLI_MCS} cp "${SOURCE}" "${TEMP}"
    ${AWSCLI_AWS} cp "${TEMP}" "${TARGET}"
    rm ${TEMP}
    ;;

    "delete")
    ${AWSCLI_AWS} rm ${TARGET}
    ;;

    *)
    echo "Usage: 
#!/bin/bash
# Require aws cli
# if file added — copy it to backup bucket
# if file removed — remove it from backup bucket
# Variables
ENDPOINT_MCS="https://hb.bizmrg.com"
AWSCLI_MCS=`which aws`" --endpoint-url ${ENDPOINT_MCS} --profile mcs s3"
AWSCLI_AWS=`which aws`" --profile aws s3"
BACKUP_BUCKET="myfiles-backup"
SOURCE_BUCKET="${1}"
SOURCE_FILE="${2}"
ACTION="${3}"
SOURCE="s3://${SOURCE_BUCKET}/${SOURCE_FILE}"
TARGET="s3://${BACKUP_BUCKET}/${SOURCE_FILE}"
TEMP="/tmp/${SOURCE_BUCKET}/${SOURCE_FILE}"
case ${ACTION} in
"copy")
${AWSCLI_MCS} cp "${SOURCE}" "${TEMP}"
${AWSCLI_AWS} cp "${TEMP}" "${TARGET}"
rm ${TEMP}
;;
"delete")
${AWSCLI_AWS} rm ${TARGET}
;;
*)
echo "Usage: ${0} sourcebucket sourcefile copy/delete"
exit 1
;;
esac
sourcebucket sourcefile copy/delete" exit 1 ;; esac

Loosst eis de Server starten:

ubuntu@ubuntu-basic-1-2-10gb:~/s3-webhook$ sudo ./s3-webhook -port 80 -
script scripts/s3_backup_mcs_aws.sh

Loosst eis kucken wéi et funktionnéiert. Duerch MCS Web Interface füügt d'Test.txt Datei an de myfiles-ash Eemer. D'Konsol Logbicher weisen datt eng Ufro un de Webhook Server gemaach gouf:

2020/07/06 09:43:08 [POST] incoming HTTP request from 
95.163.216.92:56612
download: s3://myfiles-ash/test.txt to ../../../tmp/myfiles-ash/test.txt
upload: ../../../tmp/myfiles-ash/test.txt to 
s3://myfiles-backup/test.txt

Loosst eis den Inhalt vum myfiles-Backup Eemer an AWS iwwerpréiwen:

ubuntu@ubuntu-basic-1-2-10gb:~/s3-webhook$ aws s3 --profile aws ls 
myfiles-backup
2020-07-06 09:43:10       1104 test.txt

Elo, duerch d'Web-Interface, wäerte mir d'Datei aus dem myfiles-Asche Eemer läschen.

Server Logbicher:

2020/07/06 09:44:46 [POST] incoming HTTP request from 
95.163.216.92:58224
delete: s3://myfiles-backup/test.txt

Inhalter vum Eemer:

ubuntu@ubuntu-basic-1-2-10gb:~/s3-webhook$ aws s3 --profile aws ls 
myfiles-backup
ubuntu@ubuntu-basic-1-2-10gb:~$

De Fichier gëtt geläscht, de Problem ass geléist.

Conclusioun an ToDo

All Code an dësem Artikel benotzt ass a mengem Repository. Et ginn och Beispiller vu Scripten a Beispiller fir Ënnerschrëften ze zielen fir Webhooks z'registréieren.

Dëse Code ass näischt méi wéi e Beispill vu wéi Dir S3 Webhooks an Ären Aktivitéiten benotze kënnt. Wéi ech am Ufank gesot hunn, wann Dir plangt esou e Server an der Produktioun ze benotzen, musst Dir op d'mannst de Server fir asynchron Aarbecht nei schreiwen: Entréeën Webhooks an enger Schlaang registréieren (RabbitMQ oder NATS), a vun do aus parséieren a veraarbechten mat Aarbechter Uwendungen. Soss, wann Webhooks massiv ukommen, kënnt Dir e Manktem u Serverressourcen begéinen fir Aufgaben ze kompletéieren. D'Präsenz vu Schlaangen erlaabt Iech de Server an d'Aarbechter ze verdeelen, wéi och d'Problemer mat Widderhuelungsaufgaben am Fall vu Feeler ze léisen. Et ass och ubruecht de Logbuch op eng méi detailléiert a méi standardiséiert ze änneren.

Vill Gléck!

Méi Liesungen zum Thema:

Source: will.com

Setzt e Commentaire