dnbd3-fuse-cow

Diese Erweiterung des fuse dnbd3-Clients ermöglicht, Images (virt. Abbilder) beschreibbar zu mounten. Änderungen werden in einer separaten Datei auf dem Client-Computer gespeichert (auch Copy on Write, kurz cow genannt) und im Hintergrund auf den cow-Server hochgeladen. Verbleibende Änderungen werden hochgeladen, sobald das Image ausgehängt wird. Nach Upload aller Änderungen werden sie in einer Kopie des Original-Images auf dem Cow-Server zusammengeführt, wenn dies in den Startparametern eingestellt wurde.

Ein typischer Anwendungsfall ist das Aktualisieren oder Hinzufügen von Software zu einem bestehenden Image. Der Vorteil ist, dass nicht erst das komplette Abbild heruntergeladen, lokal bearbeitet und anschließend wieder hochgeladen werden muss. Stattdessen wird das Image beschreibbar per dnbd3 gemountet; die Uploadzeit ist deutlich kürzer, da Änderungen bereits während der Session hochgeladen werden.

Installation

Diese Anleitung umfasst die Installation der nötigen Komponenten sowie die Anwendung der Tests.

Installation dnbd3-Server, dnbd3-fuse-Client, Testprogramm

Installation der Abhängigkeiten

Beispiel Ubuntu 22.04 oder Debian 11, andere Distributionen mit evtl. abweichenden Paketnamen:

apt-get update -y
apt-get install -y make cmake clang-format linux-headers-generic libfuse-dev libjansson-dev libcurl4-openssl-dev

Download Quellcode

Download des Quellcodes aus dem git-Repository:

git clone -b fuse_cow_2 git://git.openslx.org/dnbd3.git

Bau und Installation

Vor dem Bau in das nach dem Download erzeugte Verzeichnis dnbd3 wechseln. Dann zur Konfiguration des Baus ausführen:

cmake -D DNBD3_KERNEL_MODULE=OFF \
      -D DNBD3_BENCHMARK=OFF \
      -D DNBD3_CLIENT_FUSE=ON \
      -D DNBD3_CLIENT_FUSE_COW_TEST=ON \
      -D DNBD3_SERVER=ON \
      -D DNBD3_SERVER_FUSE=OFF \
      -D DNBD3_SERVER_AFL=OFF \
      -D DNBD3_SERVER_DEBUG_LOCKS=OFF \
      -D DNBD3_SERVER_DEBUG_THREADS=OFF \
      -D DNBD3_RELEASE_HARDEN=OFF \
      -D DNBD3_PACKAGE_DOCKER=OFF

… und anstoßen:

make && make install

Ein ausgeführtes 'make install' installiert die ausführbaren Dateien dnbd3-fuse, dnbd3-fuse-cow-test sowie dnbd3-server nach /usr/local/bin. '/usr/local/bin' sollte im Pfad enthalten sein.

Autostart

Der dnbd3-Server wird am besten direkt beim Systemstart über systemd gestartet. Dazu folgende Datei unter /etc/systemd/system/dnbd3-server.service ablegen …

[Unit]
Description=DNBD3 server
Wants=network-online.target
After=network-online.target

[Service]
Type=simple
ExecStart=/usr/local/bin/dnbd3-server -n -c /etc/dnbd3-server
Restart=on-failure
User=dnbd3

[Install]
WantedBy=multi-user.target

… und mittels 'systemctl enable dnbd3-server' zum automatischen Start registrieren. Ein Direktstart kann mittels 'systemctl daemon-reload', 'systemctl start dnbd3-server' erfolgen.

Installation cow_merger_service

Installation von .NET 6 SDK

Ubuntu 22.04

Beispiel Ubuntu 22.04

apt install dotnet6
Debian

Folgende Befehle gelten für Debian 10 und 11; für Debian 10 einfach vorkommende '11' durch '10' ersetzen.

wget https://packages.microsoft.com/config/debian/11/packages-microsoft-prod.deb -O packages-microsoft-prod.deb && \
dpkg -i packages-microsoft-prod.deb && \
rm packages-microsoft-prod.deb

Diese Befehle erzeugen einen Repositoriumseintrag unter /etc/apt/sources.list.d/microsoft-prod.list. Dieser kann nach erfolgter Installation gelöscht werden. Die eigentlich Installation erfolgt mit

apt-get update && apt-get install -y dotnet-sdk-6.0
Achtung: Dotnet telefoniert wie viele MS-Produkte sehr gern heim zu Microsoft. Es ist daher sehr empfehlenswert, stets die Umgebungsvariable DOTNET_CLI_TELEMETRY_OPTOUT auf '1' oder 'true' zu setzen, um dies zu verhindern.
Andere Distributionen

Für andere Distributionen kann Dotnet ggf. nach Eintrag einen Microsoft-Repositoriums per jeweiligem Paketmanager oder nach direktem Download der Paketdate erfolgen. Informationen dazu finden Sie bei Microsoft.

Download Quellcode

git clone "https://github.com/z0Kng/cow_merger_service.git"

Bau und Installation

cd cow_merger_service
dotnet build cow_merger_service.csproj --runtime linux-x64

Empfehlenswert sind die Optionen -p:PublishSingleFile=true und -p:PublishTrimmed=True. Diese bewirken, dass der Server als einzelne Datei gebaut und die Dateigröße reduziert wird, –self-contained true bewirkt, dass .NET nicht auf dem Zielsystem installiert sein muss.

dotnet publish -c Release -o publish -p:PublishReadyToRun=true -p:PublishSingleFile=true -p:PublishTrimmed=True -p:PublishReadyToRunShowWarnings=true --self-contained true --runtime linux-x64

Erzeugte Dateien (Serverprogramm und Konfigurationsdatei) werden in cow_merger_service/publish/ abgelegt. Kopieren Sie die ausführbare Datei cow_merger_service und die Konfiguration appsettings.json an einen geeigneten Ort, beispielsweise in ein eigenes Verzeichnis hinter /opt. Hier wird angenommen, daß die beiden Datein in ein Verzeichnis /opt/cow_merger_service kopiert wurden.

Autostart

Der Start des cow_merger_services kann automatisiert werden. Erzeugen Sie dazu eine Datei in /etc/systemd/system/cow_merger_service.service folgenden Inhalts:

[Unit]
Description=CoW merger service
Wants=network-online.target
After=network-online.target

[Service]
Type=simple
WorkingDirectory=/opt/cow_merger_service
Environment="DOTNET_CLI_TELEMETRY_OPTOUT=TRUE"
ExecStart=/opt/cow_merger_service/cow_merger_service
Restart=on_failure
User=root

[Install]
WantedBy=multi-user.target

… und führen anschließend den Befehl 'systemctl enable cow_merger_service' aus. 'systemctl daemon-reload && systemctl start cow_merger_service' startet den Dienst direkt.

Konfiguration

dnbd3-Server

Die Konfigurationsdatei server.conf wird standardmässig in /etc/dnbd3 abgelegt. Eine Beispieldatei ist in im bauverzeichnis unter pkg/config/server.conf zu finden. Diese kann nach /etc/dnbd3 kopiert werden; der Pfad wird kann dem dnbd3-Server mit dem Startparamerter –config übergeben. Die wichtigste Konfigurationsoption ist basePath, die das Verzeichnis bezeichnet, in dem die vom Server bereitgestellten Images liegen. Die Abbilddateien müssen auf .r[1-9][0-9]* enden.

Der dnbd3-Server wird üblicherweise beim Systemstart aktiviert. Ein Neustart des dnbd3-Servers erfolgt am zweckmäßigsten mittels 'systemctl restart dnbd3-server'.

cow_merger_service

Serverseitig sollten 3 Verzeichnisse erstellt werden:

mkdir [WorkingDirectory]
mkdir [OriginalImageDirectory]
mkdir [DestinationDirectory]

cow_merger_service

Die Konfiguration des cow_merger_service erfolgt über die Datei appsettings.json, welche sich im gleichen Order wie die Executable befindet.

Schlüssel Wert Beschreibung
WorkingDirectory Pfad Order zur Zwischenspeicherung und Bearbeitung aktiver Images.
OriginalImageDirectory Pfad Ordner, der die originalen Imagedateien enthält.
DestinationDirectory Pfad In diesen Ordner werden die fertigen Images kopiert.
Urls url:port,..,url:port URL-Liste, auf die der Server hören soll.

Die genannten Werte müssen konfiguriert werden. Sollten sich die Serverprozesse cow_merger_service und der dnbd3-server auf dem selben System befinden, ergibt es Sinn, als OriginalImageDirectory das entsprechende Verzeichnis des dnbd3-Servers (basePath) zu verwenden.

Serverstart

cow_merger_service

dnbd3_fuse_cow

Es werden zwei leere Verzeichnise benötigt. Der mountPfad muss ein leerer Ordner sein, in diesen wird das Image gemounted. Im tmpCowPfad speichert die COW Erweiterung die Änderungen und Metadaten des Images zwischen und bei Benutzung des –cowStatFile Parametrs wird dort auch die status.txt angelegt welche debug informationen zur COW Erweiterung enthält.

mkdir [mountPfad]
mkdir [tmpCowPfad]
opt longopt Paramter Beschreibung
-f Kein fork, Log wird in stdout geschrieben.
-h –host <address1 address2..> host Adressen der dnbd3 Server.
-i –image <imagename> Name des images das geladen werden soll.
-c –cow <tmpCowPfad> Erstellt eine neue Cow session,
-L –loadCow <tmpCowPfad> Läd eine cow Session von dem Pfad welche noch kein merge hatte.
-C –cowServer <url:port> Url/Ip und Port des cow_merger_service
-m –merge Merged das Image sobald die Session beendet wird.
-x –cowStatStdout Schreibt den Cow status in stdout.
-y –cowStatFile Schreibt den Cow status in status.txt in <tmpCowPfad> .

Beispiel Startparameter: Laden eines Images im cow-Modus mittels fuse-Client; Image wird nach erfolgtem unmount gemerged.

dnbd3-fuse [mountPfad] -f -h [dnbd3 server ip] -i test -c [tmpCowPfad] " -C [url:port] -m 

Tests

Für die Tests wird ein komplettes Setup, bestehend aus dnbd3 server, cow_merger_service und dnbd3_fuse client benötigt. Das Testprogramm enthält zwei verschiedene Tests; bei beiden Test wird ein per dnbd3_fuse gemountetes Testabbild benötigt. Der Standardtest enthält verschiedene Tests, die allgemeine Funktionen und Edge Cases testen. Hierbei werden Schreib-, Lese- und Größenanderungen durchgeführt. Diese werden anschließend mit den erwarteten Werten verglichen.

Der Random Test führt zufällige Schreibvorgänge und Größenänderungen durch. Diese werden sowohl auf dem gemounteten Image als auch auf einer im Dateisystem befindlichen Kopie desselben durchgeführt. Beide Abbilder werden zum Abschluss auf Gleichheit getestet.

Folgende Pfade werden bei den Tests verwendet:

  • mountPfad Beliebiger Pfad an dem das Image gemounted wird, wichtig der Ordner muss leer sein.
  • tmpCowPfad Beliebiger Pfad an dem die CoW Extension die CoW Session Data und die Status Datei speichert.
  • Pfad im lokalen Dateisystem Beliebiger Pfad im lokalen Dateisystem an dem eine Kopie des Test Images abgespeichert werden kann. Dies wird im Random Test als „Ground Truth“ verwendet.

Allgemeine Testvorbereitungen

dnbd3-server, dnbd3-fuse-cow-test, dnbd3-fuse, und cow_merger_service sollten wie oben beschrieben erstellt und konfiguriert sein. Anschließend kann mit folgendem Befehl ein Testimage erstellt werden:

dnbd3-fuse-cow-test -c [Imagename].r1

Dieses Abbild kann anschließend in den basePath-Ordner des dnbd3-Servers verschoben werden und, falls der cow_merger_service-[OriginalImageDirectory]-Order nicht auf denselben zeigt, in diesen kopiert werden. Die Prozesse dnbd3-server und cow_merger_service müssen natürlich ebenfalls gestartet werden, falls noch nicht geschehen .

dnbd3-server --config [Pfad zur Config]
cow_merger_service

Sollte der dnbd3-server schon laufen muss dieser neu gestartet werden.

Parameter des Testprogrammes

optlongopt Arguments Beschreibung
-c–testFile <path> Generiert eine Test Datei dem angegebenen Pfad, dieses Datei wird für die Tests benötigt.
-t–test <file> Führt den Standard Test an der angegebenen Datei durch.
-d–delay <seconds> Setzt den Warte Wert im Standart Test für den MultipleWrites test.
-v–verify <file> Dient zum verifizieren des Datei nachdem der Standard Test ausgeführt wurde.
-r–randomTest <mountedFile> <normalFile> Dieser Test schreibt und ändert die Größe beider Dateien zufällig, solange bis der Test beendet wird (ctrl+c). Dann werden beide Dateien auf gleichheit getestet.
-x–compare <mergedFile> <normalFile> Prüft beide Dateien auf Gleichheit
-y–minSizePercent [1-200] Minimaler Prozentualer wert auf die der Random Test das Image bei einer Größenänderung reduziert.
-z–maxSizePercent [1-200] Maximaler Prozentualer wert auf die der Random Test das Image bei einer Größenänderung erhöht.

Standard-Test

1. Mit dem Fuse Client im cow modus eine neue Session starten:

dnbd3-fuse [mountPfad] -f -h [dnbd3 server ip] -i [imageName] -c [tmpCowPfad] -C [url:port] -y -x 

2. Standard-Test starten:

cowtest/dnbd3-fuse-cow-test -t [mountPfad]  

3. Nach Abschluss des Tests unmounten:

umount [mountPfad]

4. A alte cow-Sitzung wieder laden und merge (-m) aktivieren:

dnbd3-fuse [mountPfad] -f -h [dnbd3 server ip] -i [imageName] -c [tmpCowPfad] -C [url:port] -y -x -m

5. Image auf Fehler prüfen:

dnbd3-fuse-cow-test -v [mountPfad]  

6. Unmount nach Abschluss der der Prüfung:

umount [mountPfad]

7. Nach Beendigung des merge-Vorgangs prüfen, ob im [DestinationDirectory] das neue Image vorhanden ist, dann das fertige Image auf Fehler prüfen:

dnbd3-fuse-cow-test -v "[DestinationDirectory]/[imageName].r2" 

Random-Test

1. Kopie des Testimages auf einen beliebigen Ort innerhalb des lokalen Dateisystems als Vergleichsbasis des Random-Tests erstellen:

cp [OriginalImageDirectoryPfad]/[imageName].r1 [Pfad im lokalen Dateisystem]

2. Start einer neues Session mittels Fuse-Client im cow-Modus:

dnbd3-fuse [mountPfad] -f -h [dnbd3 server ip] -i [imageName] -c [tmpCowPfad] -C [url:port] -y -x

3. Teststart per dnbd3-fuse-cow-test (neues Terminal empfehlenswert):

dnbd3-fuse-cow-test --randomTest "[mountPfad]/img" "[Pfad im lokalen Dateisystem]/[imageName].r1"

4. Nach beliebiger Zeit den Random-Test beenden (Strg+C). Darauf werden beide Abbilder auf Gleichheit getestet.

5. Image des dnbd3-fuse-Clients unmounten:

umount [mountPfad]

6. Laden der alten cow-Session (erstes Terminal) und Merge (-m) aktivieren:

dnbd3-fuse [mountPfad] -f -h [dnbd3 server ip] -i [imageName] -L [tmpCowPfad] -C [url:port] -m -y -x

7. Beide Abbilder auf Gleichheit testen, um sicherzustellen, dass das Laden funktioniert:

dnbd3-fuse-cow-test --compare [mountPfad] "[Pfad im lokalen Dateisystem]/[imageName].r1"

8. Optional: Random-Test erneut ausführen (siehe Schritt 3 und 4).

9. Image des dnbd3-fuse clients unmounten; dies löst den Merge-Vorgang des cow_merger_service automatisch aus:

umount [mountPfad]

10. Nach Beendigung des Merge-Vorgangs sollte im [n DestinationDirectory] das neue Image vorhanden sein. Anschliessend können beide Abbilder verglichen werden:

dnbd3-fuse-cow-test --compare "[DestinationDirectory]/[imageName].r2" "[Pfad im lokalen Dateisystem]/[imageName].r1"

Funktionsweise

Struktur der Daten

Die Datenstruktur ist in zwei Hauptteile unterteilt. Die eigentlichen Daten die die Änderungen enthalten und die dazugehörenden Metadaten. Die Änderungen werden in der data Datei gespeichert während die Meta daten in der meta Datei gespeichert werden. Die Daten werden in Blöcken gespeichert, der dnbd3-Block ist 4096 Byte groß und 320 dnbd3-Blöcke werden in einen cow-Block zusammengefasst. Ein cow block hat eine cow_block_metadata_t Struktur, die die entsprechenden Metadaten enthält. Die Metadaten werden verwendet, um festzustellen, ob ein dnbd3-Block beschrieben wurde, wo dieser Block in der Datendatei gespeichert ist, wann er zuletzt geändert und wann er hochgeladen wurde. Aber dazu später mehr.

Blockmetadata

Die Datenstruktur zur Speicherung von „cow_block_metadata_t“ enthält eine Schicht 1 (L1) und eine Schicht 2 (L2). L1 enthält Pointer auf die L2's. Das gesamte L1-Array wird zu Beginn initialisiert und kann nicht in der Größe verändert werden, daher begrenzt die Größe des L1-Arrays die Gesamtgröße des Images. Die L2 werden dynamisch erstellt, wenn sie benötigt werden. Zu Beginn sind also alle L1-Pointer Null. Die L2's sind Arrays, die 1024 cow_block_metadata_t structs enthalten.

typedef struct cow_block_metadata
{
    atomic_int_least64_t offset;
    atomic_uint_least64_t timeChanged;
    atomic_uint_least64_t uploads;
    atomic_char bitfield[40];
} cow_block_metadata_t;
COW_L2_STORAGE_CAPACITY = 1024 * 320 * 4096 
COW_METADATA_STORAGE_CAPACITY = 320 * 4096 

Jeder cow_block_metadata_t enthält ein 40 Byte langes, also 320 Bit großes Bitfeld. Das Bitfeld zeigt an, ob der entsprechende dnbd3-Block Daten enthält oder nicht. Beginnt das Bitfeld z.B. mit 01…, so enthalten die ersten 4096 Byte keine Daten und die nächsten 4096 Byte enthalten Daten. Jeder cow_block_metadata_t speichert also die Metadaten von bis zu 320*4096 Bytes, wenn alle Bits auf 1 gesetzt sind. Das Offset-Feld enthält das Offset, an welcher Stelle in der Datendatei die entsprechenden Daten gespeichert sind. Die timeChanged Feld enthält die Unix-Zeit, zu der der Block zuletzt geändert wurde. Sie ist 0, wenn er nie geändert wurde oder wenn die letzten Änderungen bereits hochgeladen wurden.

Die L2-Arrays und cow_block_metadata_t sind nach den ursprünglichen Offsets des Images sortiert. So adressiert der erste L1-Zeiger, d.h. das erste L2-Array, die ersten 1024 * 320 * 4096 Bytes (L2Size * bitfieldsize * DNBD3Blocksize) der Daten und so weiter.

Um zum Beispiel die „cow_block_metadata_t“ für den Offset 4033085440 zu erhalten, würde man L1[3] nehmen, da

4033085440 / ( COW_L2_STORAGE_CAPACITY ) ≈ 3.005

Dann würde man den fünften cow_block_metadata_t im L2-Array nehmen wegen

(4033085440 mod COW_L2_STORAGE_CAPACITY) / COW_METADATA_STORAGE_CAPACITY = 5

Leseanfrage

Wird eine Lese anfrage gestellt werden alle benötigten „cow_block_metadata_t“ berechnet. In den bitfields wird dann geschaut ob die benötigten dnbd3 Blöcke Daten enthalten, also ob das bitfield an der stelle 1 ist. Falls ja werden die daten aus der Daten Datei geladen. Das Offset ist dabei das cow_block_metadata_t Offset plus 4096 * blocknummer im Cow Block. Falls das bit im bitfield jedoch 0 ist, gibt es zwei Fälle. Ist das offset kleiner als die Origial Image Größe werden die Daten mit 0 aufegfüllt (die Origial Image Größe Variable kann bei einem truncate die kleiner ist als das uhrprünglich Image weiter reuziert werden). Abdernfalls werden die Daten von dem dnbd3 server geladen. Da die Leseanfragen an den dnbd3 server asyncron ausgeführt werden, gibt es eine workCounter variable, die Anzahl der Parallelen anfragen plus der einzelnen lokalen enthält. Erst wenn diese Variable 0 ist, wird die Fuse-Anfrage beendet.

Schreibanfrage

Wenn bei einer Schreibanfrage der Anfang oder das Ende nicht mit einem Vielfachen von 4096 übereinstimmt, muss der Anfangs- und/oder Endblock aufgefüllt werden. Das liegt daran, dass jeder 4096-Byte-Block vollständige Daten benötigt, denn wenn das Bit im Bitfeld für diesen Block gesetzt ist, werden alle Daten lokal gelesen. Um den Block aufzufüllen, werden die fehlenden Bytes vom dnbd3-Server angefordert, wenn er sich noch im Bereich der ursprünglichen Imagegröße befindet. Liegt er außerhalb der ursprünglichen Imagegröße (weil z.B. das Image größer geworden ist), werden die fehlenden Bytes mit 0 aufgefüllt. Die Schreibanfrage errechnet aus dem Offset die entsprechenden cow_block_metadata_t. Wenn die entsprechende cow_block_metadata_t noch nicht existiert, wird sie angelegt. Die Daten werden in die Datendatei geschrieben, und zwar an den in cow_block_metadata_t gespeicherten Offset. Dann wird das entsprechende Bit in den Bitfeldern gesetzt und die timeChanged aktualisiert. Wenn mehr Daten zu schreiben sind, wird die nächste cow_block_metadata_t berechnet und die obigen Schritte werden wiederholt. Die Variable workCounter wird auch hier verwendet, um sicherzustellen, dass das Auffüllen der Daten erfolgt, bevor die Fuse-Anfrage bendet wird.

Block-Upload

Für das Hochladen von Blöcken gibt es einen Hintergrund-Thread, der periodisch eine Schleife über alle Cow-Blöcke zieht und prüft, ob timeChanged ungleich 0 ist und die Zeitdifferenz zwischen now und timeChanged größer als COW_MIN_UPLOAD_DELAY ist. Ist dies der Fall, wird der Block hochgeladen. Die timeChanged Variable vor dem Upload wird zwischengespeichert. Nach dem Hochladen wird timeChanged auf 0 gesetzt, wenn es noch die gleiche Zeit wie die zwischengespeicherte hat (wenn nicht, gab es eine Änderung während des Hochladens und es muss erneut hochgeladen werden). Sobald das Image ausgehängt ist, wird COW_MIN_UPLOAD_DELAY ignoriert und alle Blöcke, die eine andere Zeit als 0 haben, werden hochgeladen. Das Hochladen erfolgt über einen REST-Request. Es gibt zwei verschiedene Limits für die Anzahl der parallelen Uploads, diese können in der config.h konfiguriert werden.

Dateien

Wenn eine neue CoW-Sitzung gestartet wird, werden eine neue Meta-, Daten- und, falls in den Befehlszeilenargumenten festgelegt, eine status.txt-Datei erstellt.

status.txt

Die Datei status.txt kann mit dem Kommandozeilenparameter –cowStatFile aktiviert werden.

Die Datei enthält die folgende Informationen:

uuid=<uuid>
state=backgroundUpload
inQueue=0
modifiedBlocks=0
idleBlocks=0
totalBlocksUploaded=0
activeUploads:0
ulspeed=0.00
  • Die uuid ist die Sitzungs-Uuid, die vom Cow-Server zur Identifizierung der Sitzung verwendet wird.
  • Der Status ist backgroundUpload, wenn das Image noch eingehängt ist und die Cow- Blöcke im Hintergrund hochgeladen werden. Er ist uploading, wenn das Image ausgehängt wurde und alle noch nicht hochgeladenen Blöcke hochgeladen werden. Es ist done, wenn das Image ausgehängt wurde und alle Blöcke hochgeladen wurden.
  • Queue sind die CoW Blöcke, die gerade hochgeladen werden oder auf einen freien Slot warten.
  • ModifiedBlocks sind CoW Blöcke, die Änderungen aufweisen, die noch nicht auf den Server hochgeladen wurden, weil die Änderungen zu aktuell sind.
  • totalBlocksUploaded die Gesamtanzahl der CoW Blöcke, die seit dem Einhängen des Images hochgeladen wurden.
  • activeUploads ist die Anzahl der Blöcke, die gerade hochgeladen werden.
  • ulspeed die aktuelle Upload-Geschwindigkeit in kb/s.

Sobald alle Blöcke hochgeladen wurden, wird der Status auf erledigt gesetzt. Wenn Sie COW_DUMP_BLOCK_UPLOADS festlegen (in config.h), wird eine Liste aller Blöcke, sortiert nach der Anzahl der Uploads, in die Datei status.txt kopiert, nachdem der Block-Upload abgeschlossen ist.

Mit dem Kommandozeilenparameter –cowStatStdout wird die gleiche Ausgabe der Statistikdatei in stdout ausgegeben.

meta

Die Metadatei enthält die folgenden Header:

// cowfile.h
typedef struct cowfile_metadata_header
{
    uint64_t magicValue;                    // 8byte
    atomic_uint_least64_t imageSize;        // 8byte
    int32_t version;                        // 4byte
    int32_t blocksize;                      // 4byte
    uint64_t originalImageSize;             // 8byte
    uint64_t metaDataStart;                 // 8byte
    int32_t bitfieldSize;                   // 4byte
    int32_t nextL2;                         // 4byte
    atomic_uint_least64_t metadataFileSize; // 8byte
    atomic_uint_least64_t dataFileSize;     // 8byte
    uint64_t maxImageSize;                  // 8byte
    uint64_t creationTime;                  // 8byte
    char uuid[40];                          // 40byte
    char imageName[200];                    // 200byte
} cowfile_metadata_header_t;

Nach diesem Header beginnt bei Byte 8192 die oben erwähnte l1- und dann die l2-Datenstruktur.

data

Die Datendatei enthält den magicValue und am Offset 40 * 8 * 4096 (Kapazität eines cowfile_metadata_header_t) beginnt der erste Datenblock.

magic values in den Headern der Dateien

Die magic values in beiden Dateien werden verwendet, um sicherzustellen, dass eine geeignete Datei gelesen wird und dass der Rechner die richtige Endianness hat.

//config.h
#define COW_FILE_META_MAGIC_VALUE ((uint64_t)0xEBE44D6E72F7825E) // Magic Value to recognize a Cow meta file
#define COW_FILE_DATA_MAGIC_VALUE ((uint64_t)0xEBE44D6E72F7825F) // Magic Value to recognize a Cow data file

Threads

Diese Erweiterung verwendet zwei neue Threads:

tidCowUploader
tidStatUpdater
  • tidCowUploader ist der Thread, der die Blöcke auf den Cow-Server hochlädt.
  • tidStatUpdater aktualisiert die Statistiken in stdout oder die Statistikdateien (je nach Parametern).

Locks

Diese Erweiterung verwendet einen neuen Lock cow.l2CreateLock. Er wird verwendet, wenn ein neues L2-Array zugewiesen wird.

Config Variablen

Die folgenden Konfigurationsvariablen wurden zu config.h hinzugefügt. Eine Änderung wird nur erfahrenen Nutzern empfohlen.

//config.h
// +++++ COW +++++
#define COW_BITFIELD_SIZE 40 // NEVER CHANGE THIS OR THE WORLD WILL ALSO END!
#define COW_FILE_META_MAGIC_VALUE ((uint64_t)0xEBE44D6E72F7825E) // Magic Value to recognize a Cow meta file
#define COW_FILE_DATA_MAGIC_VALUE ((uint64_t)0xEBE44D6E72F7825F) // Magic Value to recognize a Cow data file
#define COW_MIN_UPLOAD_DELAY 60 // in seconds
#define COW_STATS_UPDATE_TIME 5 // time in seconds the cow status files gets updated (while uploading blocks)
#define COW_MAX_PARALLEL_UPLOADS 10 // maximum number of parallel uploads
#define COW_MAX_PARALLEL_BACKGROUND_UPLOADS 2 // maximum number of parallel uploads while the image is still mounted
#define COW_URL_STRING_SIZE 500 // Max string size for an url
#define COW_SHOW_UL_SPEED 1 // enable display of ul speed in cow status file
#define COW_MAX_IMAGE_SIZE 1000LL * 1000LL * 1000LL * 1000LL; // Maximum size an image can have(tb*gb*mb*kb)
// +++++ COW API Endpoints +++++
#define COW_API_CREATE "%s/api/File/Create"
#define COW_API_UPDATE "%s/api/File/Update?guid=%s&BlockNumber=%lu"
#define COW_API_START_MERGE "%s/api/File/StartMerge"
  • COW_MIN_UPLOAD_DELAY legt die Mindestzeit in Sekunden fest, die seit der letzten Änderung eines CoW Blocks verstrichen sein muss, bevor er hochgeladen wird. Ein größerer Wert verringert normalerweise das doppelte Hochladen von Blöcken. Ein kleinerer Wert verringert die Zeit für das abschließende Hochladen, nachdem das Image ausgehängt wurde. Wenn Sie COW_DUMP_BLOCK_UPLOADS setzen und den Kommandozeilenparameter –cowStatFile angeben, wird eine Liste aller Blöcke, sortiert nach der Anzahl der Uploads, in die Datei status.txt geschrieben, nachdem der Block-Upload abgeschlossen ist. Dies kann bei der Feinabstimmung von COW_MIN_UPLOAD_DELAY helfen.
  • COW_STATS_UPDATE_TIME definiert die Aktualisierungsfrequenz der stdout-Ausgabe/Statistikdatei in Sekunden. Ein zu niedriger Wert könnte die Leistung beeinträchtigen, da eine Schleife über alle Blöcke läuft.
  • COW_MAX_PARALLEL_BACKGROUND_UPLOADS definiert die maximale Anzahl der parallelen Block-Uploads. Diese Zahl wird verwendet, wenn das Image noch eingehängt ist und der Benutzer es noch nutzt.
  • COW_MAX_PARALLEL_UPLOADS definiert die maximale Anzahl der parallelen Block-Uploads. Diese Zahl wird verwendet, sobald das Image ausgehängt wurde, um die restlichen geänderten Blöcke hochzuladen.

REST-API

Die folgende REST API wird genutzt zur Komunikation und Datenübertragung mit dem cow_merger_service.

/api/File/Create

POST Responses

Code Description
200 Success

Diese Anfrage wird verwendet, sobald eine neue COW Session erstellt wird. Die zurückgegebene GUID wird in allen nachfolgenden Anfragen zur Identifizierung der Session verwendet.

/api/File/Update

POST Parameters

Name Located in Description Required Schema
guid query Yes string (uuid)
blockNumber query Yes integer

Responses

Code Description
200 Success

Dient zum Hochladen eines COW Block. Die Blocknummer ist die absolute Blocknummer. Der Body enthält einen „application/octet-stream“, wobei die ersten Bytes das Bitfeld sind, direkt gefolgt von den eigentlichen Blockdaten.

/api/File/StartMerge

POST Parameters

Name Located in Description Required Schema
guid Form Yes string (uuid)
originalFileSize Form Yes integer
newFileSize Form Yes integer

Responses

Code Description
200 Success

Wird verwendet, um einen Merge auf dem Server zu starten.

/api/File/GetTopModifiedBlocks

GET Parameters

Name Located in Description Required Schema
guid query Yes string (uuid)
amount query Yes integer

Responses

Code Description
200 Success

Diese Anfrage liefert eine Liste mit den Block-IDs und der Anzahl der Uploads dieses Blocks, sortiert nach der Anzahl der Uploads. Dies ist nützlich, um die `COW_MIN_UPLOAD_DELAY` anzupassen.

/api/File/Status

GET Parameters

Name Located in Description Required Schema
guid query Yes string (uuid)

Responses

Code Description
200 Success

Gibt das SessionStatus-Modell zurück, das Informationen über die Session liefert.

Models

BlockStatistics

Name Type Description Required
blockNumber integer Yes
modifications integer Yes

SessionState

Name Type Description Required
SessionState string

SessionStatus

Name Type Description Required
state string _Enum:_ `„Copying“`, `„Active“`, `„Merging“`, `„Done“`, `„Failed“` Yes
imageName string Yes
originalImageVersion integer Yes
newImageVersion integer Yes
mergedBlocks integer Yes
totalBlocks integer Yes

Tests

Standard Test

TestSingleBit

Setzt das erste Bit des ersten dnbd3 Blocks auf 1 und das mittlere Bit des zweiten dnbd3 Blocks auf 1.

WriteOverTwoBlocks

Dieser Test schreibt über zwei dnbd3 Blöcke.

WriteNotOnBlockBorder

Dieser Test schreibt über drei Blöcke, jedoch beginnt er nicht an der Block grenze.

InterleavedTest

Dieser Test schreibt mehrere dnbd3 Blöck mit verschiedenen Daten, jedoch lässt er Blöcke zwischendrin unbeschrieben.

WriteOverL2

Dieser Test, testet das schreiben über eine L2 grenze.

MultipleWrites

Dieser Test schreibt mehrmals auf dieselben Blöcke verschiedene Daten. Mit dem –delay Paramter kann die Wartezeit zwischen den Schreibvorgängen definiert werden. Dies ist nützlich, da je nach Delay die Blöcke zwischenzeitlich hochgeladen werden.

fileSizeChanges

Testet Änderungen an der Dateigröße. Zuerst wird die Dateigröße um 2 * l2Capacity mit einem Truncate erhöht. Dann wird geprüft, ob alle Bits in dem neu zugewiesenen Speicherplatz auf 0 gesetzt sind. Dann werden Daten in die Datei geschrieben, um zu prüfen, ob ein Schreiben möglich ist. Danach wird die Datei wieder auf die ursprüngliche Größe zurückgeschnitten. Dann wird sie wieder auf die ursprüngliche Größe + 2 * l2Capacity verkleinert und geprüft, ob alle Bits im neu zugewiesenen Speicherbereich wieder 0 sind (so dass die zuvor geschriebenen Daten wieder auf 0 gesetzt werden).

LongNonAlignedPattern

Dieser Test schreibt ein langes Muster über 3 l2-Blöcke. Das Muster wiederholt Zeichen von 0 bis 254, ist also kein Vielfaches von 4096, was dazu führt, dass alle Blöcke mit unterschiedlichen Daten gefüllt werden. Außerdem ist dieser Test nicht blockorientiert.

Random Test

Dieser Test führt wie oben beschrieben zufällig Größenänderungen und Schreibvorgänge durch. Die Wahrscheinlichkeit für eine Größenänderung wird mit dem Macro RND_TRUNCATE_PROBABILITY definiert und ist standardmäßig 5 %. Ansonsten wird ein Schreibvorgang ausgeführt. Des Weiteren gibt es noch das Macro RND_UNALIGNED_WRITE_PROBABILITY, dies definiert die Wahrscheinlichkeit, dass der Schreibvorgang nicht auf einer Blockgrenze beginnt und endet. Die maximale prozentuale Größenänderung wird mit den Startparametern minSizePercent und maxSizePercent wie oben beschrieben festgelegt.

Alter Schrott

[Auswanzen, was verwendbar, Rest weg]

dnbd3 Branch fuse_cow_2 gebaut mit:

cmake -D DNBD3_KERNEL_MODULE=OFF -D DNBD3_BENCHMARK=OFF -D DNBD3_CLIENT_FUSE=ON -D DNBD3_CLIENT_FUSE_COW_TEST=ON -D DNBD3_SERVER=ON -D DNBD3_SERVER_FUSE=OFF -D DNBD3_SERVER_AFL=OFF -D DNBD3_SERVER_DEBUG_LOCKS=OFF -D DNBD3_SERVER_DEBUG_THREADS=OFF -D DNBD3_RELEASE_HARDEN=OFF -D DNBD3_PACKAGE_DOCKER=OFF

cow_merger_service-Bau:

dotnet build cow_merger_service.csproj --runtime linux-x64
dotnet publish -c Release -o publish -p:PublishReadyToRun=true -p:PublishSingleFile=true -p:PublishTrimmed=True -p:PublishReadyToRunShowWarnings=true --self-contained true --runtime linux-x64
{
    "Logging": {
        "LogLevel": {
            "Default": "Information",
            "Microsoft": "Warning",
            "Microsoft.Hosting.Lifetime": "Information"
        }
    },
    "Settings": {
        "WorkingDirectory": "/srv/cow_merger_service/WorkingDirectory",
        "OriginalImageDirectory": "/srv/dnbd3",
        "DestinationDirectory": "/srv/cow_merger_service/Output"
    },
    "AllowedHosts": "*",
    "Urls": "http://localhost:5000"

(/usr/local/bin/)dnbd3-fuse-cow-test

Image generieren:

./dnbd3-fuse „/home/user/VMs/mount“ -f -h localhost -i [imageName] -c „/home/user/temp“ -C „192.168.178.20:5000“

Also für „cow mode“ -c <pfad > setzt halt den ordner wo er die temporären daten lagern kann (änderungen etc) -C <adress> sagt ihm wo er den cow server findet.

Sobald die beiden parameter gesetzt sind ist der fuse client quasi im „cow mode“.

Statt -c <pfad> geht auch -L <pfad> wenn man die alte session laden will. Und mit -m merged er nach dem unmounten.

dnbd3-fuse „/srv/cow_merger_service/mount“ -f -h localhost -i /srv/dnbd3/test.dnbd3 -c „/srv/cow_merger_service/WorkingDirectory“ -C „192.168.2.130:5000“

Nachfolgend wird davon ausgegangen, dass '/usr/local/bin' im Pfad enthalten ist.

Drucken/exportieren