summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMichael Scherle2022-05-30 03:12:23 +0200
committerMichael Scherle2022-05-30 03:12:23 +0200
commit9353950d652ce9d485e3f3e871e1a40ddbfb1ad6 (patch)
tree19db110a63e463a8fd840eaec7bf9ebf8704983c
parentimplemented block upload via rest (diff)
downloaddnbd3-9353950d652ce9d485e3f3e871e1a40ddbfb1ad6.tar.gz
dnbd3-9353950d652ce9d485e3f3e871e1a40ddbfb1ad6.tar.xz
dnbd3-9353950d652ce9d485e3f3e871e1a40ddbfb1ad6.zip
added cow upload status file & started doc
-rw-r--r--inc/dnbd3/config.h4
-rw-r--r--src/fuse/cowDoc/img/Bild1.jpgbin0 -> 398959 bytes
-rw-r--r--src/fuse/cowDoc/readme.md152
-rw-r--r--src/fuse/cowfile.c137
-rw-r--r--src/fuse/cowfile.h2
-rw-r--r--src/fuse/main.c21
-rw-r--r--src/fuse/main.h1
7 files changed, 285 insertions, 32 deletions
diff --git a/inc/dnbd3/config.h b/inc/dnbd3/config.h
index b4afcf1..88ba4cc 100644
--- a/inc/dnbd3/config.h
+++ b/inc/dnbd3/config.h
@@ -44,8 +44,8 @@
#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_MIN_UPLOAD_DELAY 60 // in seconds
+#define COW_STATS_UPDATE_TIME 5 // time in seconds the cow status files gets updated (while uploading blocks)
// +++++ COW API Endpoints +++++
#define COW_API_CREATE "%s/api/File/Create"
#define COW_API_UPDATE "%s/api/File/Update?guid=%s&BlockNumber=%u"
diff --git a/src/fuse/cowDoc/img/Bild1.jpg b/src/fuse/cowDoc/img/Bild1.jpg
new file mode 100644
index 0000000..3171c9a
--- /dev/null
+++ b/src/fuse/cowDoc/img/Bild1.jpg
Binary files differ
diff --git a/src/fuse/cowDoc/readme.md b/src/fuse/cowDoc/readme.md
new file mode 100644
index 0000000..2eeafb0
--- /dev/null
+++ b/src/fuse/cowDoc/readme.md
@@ -0,0 +1,152 @@
+
+# Fuse Copy on Write (CoW)
+
+### Table of Contents
+1. [Introduction](#introduction)
+2. [Usage](#usage)
+3. [Implementation Details](#implementation-details)
+4. [REST Api](#rest-api)
+
+
+# Introduction
+
+This extension to the fuse dnbd3 client allows it to mount images writable. The changes to an writeable mounted image will be stored in a separate files (also called Copy on Write (Cow)) on the client computer. These changes are uploaded in the background to the cow server. Once the user unmounts the images all remaining changes will be uploaded. Then the image will be merged on the server (depending on the startup parameters).
+
+
+# Usage
+
+### New Parameters
+- `-c <path>` Enables the cow functionality, the argument sets the path for the temporary `.meta` and `.data` file in which the writes are stored
+- `-C <address>` sets the address of the cow server. The cow server is responsible for merging the original image with the changes from the client.
+- `-L <path>` Similar to `-c <path>` but instead of creating a new session it loads an existing from the given path.
+- `-m` if set, the client will request a merge after the image is unmounted and all change are uploaded.
+
+Example parameters for creating a new cow session:
+```
+./dnbd3-fuse "/home/user/VMs/mount" -f -h localhost -i test -c "/home/user/temp" -C "192.168.178.20:5000"
+
+```
+
+# Implementation Details
+
+
+## Files
+If a new CoW session is started, a new `.meta` and `.data` file is created.
+
+### .meta
+The `.meta` file contains the following header:
+```C
+// cowfile.h
+typedef struct __attribute__( ( packed ) ) cowfile_metadata_header
+{
+ uint64_t magicValue; // 8byte
+ atomic_uint_fast64_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_size_t metadataFileSize; // 8byte
+ atomic_size_t dataFileSize; // 8byte
+ uint64_t maxImageSize; // 8byte
+ uint64_t creationTime; // 8byte
+ uuid_t uuid; // 16byte
+ char imageName[200]; // 200byte
+} cowfile_metadata_header_t;
+
+```
+
+### .data
+The `.data` files contains
+
+
+
+### magic values in the file headers
+The magic values in both files are used to ensure that a suitable file is read and that the machine has the correct endianness.
+```C
+//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
+```
+## Data strucure
+![Datastructure](img/Bild1.jpg "")
+
+
+# REST Api
+To transfer the data to the cow server, the following rest api is used:
+
+### /api/File/Create
+
+#### POST
+##### Responses
+
+| Code | Description |
+| ---- | ----------- |
+| 200 | Success |
+
+### /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 |
+
+### /api/File/StartMerge
+
+#### GET
+##### Parameters
+
+| Name | Located in | Description | Required | Schema |
+| ---- | ---------- | ----------- | -------- | ---- |
+| guid | query | | Yes | string (uuid) |
+| fileSize | query | | Yes | long |
+
+##### Responses
+
+| Code | Description |
+| ---- | ----------- |
+| 200 | Success |
+
+### /api/File/Satus
+
+#### GET
+##### Parameters
+
+| Name | Located in | Description | Required | Schema |
+| ---- | ---------- | ----------- | -------- | ---- |
+| guid | query | | Yes | string (uuid) |
+
+##### Responses
+
+| Code | Description |
+| ---- | ----------- |
+| 200 | Success |
+
+## Models
+
+#### SessionState
+
+| Name | Type | Description | Required |
+| ---- | ---- | ----------- | -------- |
+| SessionState | string | | Yes |
+
+#### SessionStatus
+
+| Name | Type | Description | Required |
+| ---- | ---- | ----------- | -------- |
+| state | string | _Enum:_ `"Copying"`, `"Active"`, `"Merging"`, `"Done"` | Yes |
+| imageName | string | | Yes |
+| originalImageVersion | integer | | Yes |
+| newImageVersion | integer | | Yes |
+| mergedBlocks | integer | | Yes |
+| totalBlocks | integer | | Yes |
diff --git a/src/fuse/cowfile.c b/src/fuse/cowfile.c
index 4e8b67f..fb900d8 100644
--- a/src/fuse/cowfile.c
+++ b/src/fuse/cowfile.c
@@ -7,13 +7,19 @@ static pthread_t tidCowUploader;
static char *cowServerAddress;
static CURL *curl;
static cowfile_metadata_header_t *metadata = NULL;
+static char uuidStr[37];
atomic_bool uploadLoop = true;
+//both variables are only relevant for the upload after the image is dismounted
+static atomic_uint_fast32_t blocksForCompleteUpload = 0;
+static atomic_uint_fast32_t blocksUploaded = 0;
+
static struct cow
{
pthread_mutex_t l2CreateLock;
int fhm;
int fhd;
+ int fhs;
char *metadata_mmap;
l1 *l1;
l2 *firstL2;
@@ -172,6 +178,7 @@ bool createSession( char *imageName, uint16_t version )
logadd( LOG_ERROR, "uuid_parse failed\n" );
return false;
}
+ strcpy( uuidStr, response );
return true;
}
@@ -195,7 +202,7 @@ void print_bin_arr( char *ptr, int size )
/**
* @brief Implementation of CURLOPT_READFUNCTION, this function will first send the bitfield and
* then the block data in one bitstream. this function is usually called multible times per block,
- * since the buffer is usually not large for one block and its bitfield.
+ * because the buffer is usually not large for one block and its bitfield.
* for more details see: https://curl.se/libcurl/c/CURLOPT_READFUNCTION.html
*
* @param ptr to the buffer
@@ -229,7 +236,7 @@ size_t curlReadCallbackUploadBlock( char *ptr, size_t size, size_t nmemb, void *
* @brief uploads the given block to the cow server.
*
* @param block pointer to the cow_block_metadata_t
- * @param blocknumber is the abosulte block number from the beginning.
+ * @param blocknumber is the absolute block number from the beginning.
* @param time relative Time since the creation of the cow file. will be used to
* set the block->timeUploaded.
*/
@@ -239,9 +246,8 @@ bool uploadBlock( cow_block_metadata_t *block, uint32_t blocknumber, uint32_t ti
cow_curl_read_upload_t curlUploadBlock;
char url[400];
- char uuid[37];
- uuid_unparse( metadata->uuid, uuid );
- sprintf( url, COW_API_UPDATE, cowServerAddress, uuid, blocknumber );
+
+ sprintf( url, COW_API_UPDATE, cowServerAddress, uuidStr, blocknumber );
curlUploadBlock.block = block;
curlUploadBlock.position = 0;
@@ -293,9 +299,7 @@ bool mergeRequest()
curl_easy_setopt( curl, CURLOPT_CUSTOMREQUEST, "GET" );
char url[400];
- char uuid[37];
- uuid_unparse( metadata->uuid, uuid );
- sprintf( url, COW_API_START_MERGE, cowServerAddress, uuid, metadata->imageSize );
+ sprintf( url, COW_API_START_MERGE, cowServerAddress, uuidStr, metadata->imageSize );
curl_easy_setopt( curl, CURLOPT_URL, url );
res = curl_easy_perform( curl );
@@ -339,7 +343,9 @@ void startMerge()
bool uploaderLoop( bool lastLoop )
{
bool success = true;
+ uint64_t lastUpdateTime = time( NULL );
int l1MaxOffset = ceil( metadata->imageSize / COW_L2_STORAGE_CAPACITY );
+ int fails = 0;
for ( int l1Offset = 0; l1Offset < l1MaxOffset; l1Offset++ ) {
if ( cow.l1[l1Offset] == -1 ) {
continue;
@@ -351,20 +357,24 @@ bool uploaderLoop( bool lastLoop )
}
if ( block->timeUploaded < block->timeChanged ) {
uint32_t relativeTime = (uint32_t)( time( NULL ) - metadata->creationTime );
- if ( ( ( ( relativeTime - block->timeChanged ) > COW_MIN_UPLOAD_DELAY ) || lastLoop )
- && block->timeUploaded < block->timeChanged ) {
- if ( !uploadBlock( block, l1Offset * COW_L2_SIZE + l2Offset, relativeTime ) ) {
- int fails = 1;
- while ( fails <= 5 ) {
- if ( uploadBlock( block, l1Offset * COW_L2_SIZE + l2Offset, relativeTime ) ) {
- break;
+ if ( ( ( relativeTime - block->timeChanged ) > COW_MIN_UPLOAD_DELAY ) || lastLoop ) {
+
+ fails = 0;
+ while(!uploadBlock( block, l1Offset * COW_L2_SIZE + l2Offset, relativeTime ) && fails > 5 ) {
+ logadd( LOG_WARNING, "Trying again. %i/5", fails );
+ fails++;
+ }
+ if ( fails >= 5 ) {
+ logadd( LOG_ERROR, "Block upload failed" );
+ success = false;
+ }
+ else{
+ if( lastLoop ) {
+ blocksUploaded++;
+ if( time(NULL) - lastUpdateTime > COW_STATS_UPDATE_TIME ) {
+ updateCowStatsFile( blocksUploaded,blocksForCompleteUpload , false );
+ lastUpdateTime = time( NULL );
}
- logadd( LOG_WARNING, "Trying again. %i/5", fails );
- fails++;
- }
- if ( fails > 5 ) {
- logadd( LOG_ERROR, "Block upload failed" );
- success = false;
}
}
}
@@ -373,6 +383,28 @@ bool uploaderLoop( bool lastLoop )
}
return success;
}
+/**
+Counts blocks that have changes that have not yet been uploaded
+*/
+int countBlocksForUpload() {
+ int res = 0;
+ int l1MaxOffset = ceil( metadata->imageSize / COW_L2_STORAGE_CAPACITY );
+ for ( int l1Offset = 0; l1Offset < l1MaxOffset; l1Offset++ ) {
+ if ( cow.l1[l1Offset] == -1 ) {
+ continue;
+ }
+ for ( int l2Offset = 0; l2Offset < COW_L2_SIZE; l2Offset++ ) {
+ cow_block_metadata_t *block = ( cow.firstL2[cow.l1[l1Offset]] + l2Offset );
+ if ( block->offset == -1 ) {
+ continue;
+ }
+ if ( block->timeUploaded < block->timeChanged ) {
+ res++;
+ }
+ }
+ }
+ return res;
+}
/**
* @brief main loop for blockupload in the background
@@ -385,13 +417,19 @@ void cowfile_uploader( void *something )
}
logadd( LOG_DEBUG1, "start uploading the remaining blocks." );
- // force the upload of all remaining blocks since the user dismounted the image
+ blocksForCompleteUpload = countBlocksForUpload();
+ updateCowStatsFile( blocksUploaded,blocksForCompleteUpload , false );
+ // force the upload of all remaining blocks because the user dismounted the image
if ( !uploaderLoop( true ) ) {
- logadd( LOG_ERROR, "Can't merge, since one or more blocks failed to upload" );
+ logadd( LOG_ERROR, "one or more blocks failed to upload" );
return;
}
- logadd( LOG_DEBUG1, "Requesting merge." );
- startMerge();
+ updateCowStatsFile( blocksUploaded,blocksForCompleteUpload , true );
+ logadd( LOG_DEBUG1, "all blocks uploaded" );
+ if( cow_merge_after_upload ) {
+ startMerge();
+ logadd( LOG_DEBUG1, "Requesting merge." );
+ }
}
/**
@@ -495,6 +533,7 @@ bool cowfile_init(
return false;
}
+ createCowStatsFile( path );
pthread_create( &tidCowUploader, NULL, &cowfile_uploader, NULL );
return true;
}
@@ -592,6 +631,12 @@ bool cowfile_load( char *path, size_t **imageSizePtr, char *serverAddress )
cow.l1Size = ( ( cow.maxImageSize + COW_L2_STORAGE_CAPACITY - 1LL ) / COW_L2_STORAGE_CAPACITY );
cow.firstL2 = (l2 *)( ( (char *)cow.l1 ) + cow.l1Size );
+
+
+ uuid_unparse( metadata->uuid, uuidStr );
+
+ createCowStatsFile( path );
+
pthread_mutex_init( &cow.l2CreateLock, NULL );
return true;
@@ -1030,6 +1075,48 @@ fail:;
}
}
+
+void createCowStatsFile( char* path ) {
+ char pathStatus[strlen( path ) + 11];
+ strcpy( pathStatus, path );
+ strcat( pathStatus, "status.txt" );
+ if ( ( cow.fhs = open( pathStatus, O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR ) ) == -1 ) {
+ logadd( LOG_ERROR, "Could not create cow status file. Bye.\n" );
+ return false;
+ }
+ char buffer[100];
+ int len = snprintf( buffer, 100, "uuid: %s\nstate: active\n",uuidStr );
+ pwrite(cow.fhs, buffer, len, 0);
+}
+
+void updateCowStatsFile( uint blocks, uint totalBlocks, bool done ) {
+ char buffer[300];
+
+ int len = snprintf( buffer, 100, "state: %s\nuploaded: %lu\ntotalBlocks: %lu\n", done?"done":"uploading" ,blocks, totalBlocks );
+ pwrite( cow.fhs, buffer, len, 43 );
+ ftruncate( cow.fhs, 43 + len );
+}
+
+
+int cow_printStats( char *buffer, const size_t len ) {
+
+ int ret = 0;
+ if(uploadLoop){
+ ret = snprintf( buffer, len, "uuid: %s\nstate: %s\ntotalBlocks: \n",
+ uuidStr, "active");
+ }
+
+ if(!uploadLoop){
+ ret = snprintf( buffer, len, "uuidS %s\nstate: %s\ntotalBlocks: \nuploading: %lu/%lu\n",
+ uuidStr, "uploading", blocksUploaded, blocksForCompleteUpload );
+ }
+ if ( ret < 0 ) {
+ ret = 0;
+ }
+
+ return ret;
+}
+
void cowfile_close()
{
uploadLoop = false;
diff --git a/src/fuse/cowfile.h b/src/fuse/cowfile.h
index 8ee7c54..48ba849 100644
--- a/src/fuse/cowfile.h
+++ b/src/fuse/cowfile.h
@@ -99,6 +99,8 @@ void cowfile_write( fuse_req_t req, cow_request_t *cowRequest, off_t offset, siz
void cowfile_handleCallback( dnbd3_async_t *request );
+int cow_printStats( char *buffer, const size_t len );
+
void cowfile_close();
#endif /* COWFILE_H_ */ \ No newline at end of file
diff --git a/src/fuse/main.c b/src/fuse/main.c
index 3089a3a..936c779 100644
--- a/src/fuse/main.c
+++ b/src/fuse/main.c
@@ -17,6 +17,7 @@ static const char *STATS_NAME = "status";
static struct fuse_session *_fuseSession = NULL;
bool useCow = false;
+bool cow_merge_after_upload = false;
static uint64_t imageSize;
static uint64_t *imageSizePtr =&imageSize;
@@ -44,16 +45,16 @@ static int image_stat( fuse_ino_t ino, struct stat *stbuf )
switch ( ino ) {
case INO_ROOT:
stbuf->st_mode = S_IFDIR | 0550;
- if(useCow){
+ if( useCow ) {
stbuf->st_mode = S_IFDIR | 0777;
}
stbuf->st_nlink = 2;
stbuf->st_mtim = startupTime;
break;
case INO_IMAGE:
- if(useCow){
+ if ( useCow ) {
stbuf->st_mode = S_IFREG | 0777;
- }else{
+ } else {
stbuf->st_mode = S_IFREG | 0440;
}
stbuf->st_nlink = 1;
@@ -152,7 +153,7 @@ static void image_ll_readdir( fuse_req_t req, fuse_ino_t ino, size_t size, off_t
static void image_ll_open( fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi )
{
if ( ino != INO_IMAGE && ino != INO_STATS ) {
- fuse_reply_err( req, EISDIR );
+ fuse_reply_err( req, EISDIR );
} else if ( ( fi->flags & 3 ) != O_RDONLY && !useCow ) {
fuse_reply_err( req, EACCES );
} else {
@@ -358,10 +359,11 @@ static void printUsage( char *argv0, int exitCode )
printf( " -c <path> Enables cow, creates the cow files at given path\n" );
printf( " -L <path> Loads the cow files from a given path\n" );
printf( " -C --host Host address of the cow server\n" );
+ printf( " -m merge changes on the server after unmount (only works with -c)\n" );
exit( exitCode );
}
-static const char *optString = "dfHh:i:l:o:r:SsVvc:L:C:";
+static const char *optString = "dfHh:i:l:o:r:SsVvc:L:C:m";
static const struct option longOpts[] = {
{ "debug", no_argument, NULL, 'd' },
{ "help", no_argument, NULL, 'H' },
@@ -375,6 +377,7 @@ static const struct option longOpts[] = {
{ "cow", required_argument, NULL, 'c' },
{ "loadcow", required_argument, NULL, 'L' },
{ "cowServer", required_argument, NULL, 'C' },
+ { "merge", no_argument, NULL, 'm' },
{ 0, 0, 0, 0 }
};
@@ -384,6 +387,7 @@ int main( int argc, char *argv[] )
char *cow_server_address = NULL;
char *image_Name = NULL;
char *log_file = NULL;
+ cow_merge_after_upload = false;
uint16_t rid = 0;
char **newArgv;
int newArgc;
@@ -467,6 +471,9 @@ int main( int argc, char *argv[] )
case 'C':
cow_server_address = optarg;
break;
+ case 'm':
+ cow_merge_after_upload = true;
+ break;
case 'L':
cow_file_path = optarg;
useCow = true;
@@ -493,6 +500,10 @@ int main( int argc, char *argv[] )
if( useCow && cow_server_address == NULL ) {
printUsage( argv[0], EXIT_FAILURE );
}
+ if( cow_merge_after_upload && !useCow ) {
+ printf( "-m only works if cow is enabled. \n" );
+ printUsage( argv[0], EXIT_FAILURE );
+ }
if ( loadCow ) {
if ( !cowfile_load( cow_file_path, &imageSizePtr, cow_server_address ) ) {
return EXIT_FAILURE;
diff --git a/src/fuse/main.h b/src/fuse/main.h
index d444cea..e2514bd 100644
--- a/src/fuse/main.h
+++ b/src/fuse/main.h
@@ -34,6 +34,7 @@
#define INO_IMAGE (3)
extern bool useCow;
+extern bool cow_merge_after_upload;
void image_ll_getattr( fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi );
#endif /* main_H_ */ \ No newline at end of file