Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/bareos/bareos.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPhilipp Storz <philipp.storz@bareos.com>2022-09-19 13:40:42 +0300
committerGitHub <noreply@github.com>2022-09-19 13:40:42 +0300
commitd8277245c29ca8e10d8dfb024739a08a0970d10e (patch)
treef5eab3d108de071b939c73b61913444e89d88f0a
parentf391ac27328d4e2dfaa94a46b1126f9ae8cc3d86 (diff)
parent9a68d1c376b4c395c36ff1536370cb2038e80804 (diff)
Merge pull request #1074
stored: dird: add backup checkpoints that save backup metadata to the Catalog during the execution of the backup
-rw-r--r--CHANGELOG.md3
-rw-r--r--core/src/cats/cats.h1
-rw-r--r--core/src/cats/sql_create.cc69
-rw-r--r--core/src/cats/sql_update.cc15
-rw-r--r--core/src/dird/catreq.cc32
-rw-r--r--core/src/stored/append.cc287
-rw-r--r--core/src/stored/append.h42
-rw-r--r--core/src/stored/askdir.cc20
-rw-r--r--core/src/stored/block.cc3
-rw-r--r--core/src/stored/device_control_record.h4
-rw-r--r--core/src/stored/dir_cmd.cc4
-rw-r--r--core/src/stored/mac.cc4
-rw-r--r--core/src/stored/record.cc20
-rw-r--r--core/src/stored/sd_device_control_record.h4
-rw-r--r--core/src/stored/stored_conf.cc1
-rw-r--r--core/src/stored/stored_conf.h1
-rw-r--r--core/src/tests/CMakeLists.txt6
-rw-r--r--core/src/tests/append_test.cc58
-rw-r--r--docs/manuals/source/include/autogenerated/bareos-sd-config-schema.json6
-rw-r--r--docs/manuals/source/manually_added_config_directive_descriptions/sd-storage-CheckpointInterval.rst.inc1
-rw-r--r--systemtests/environment.in1
-rw-r--r--systemtests/scripts/functions2
-rw-r--r--systemtests/tests/CMakeLists.txt3
-rw-r--r--systemtests/tests/checkpoints/CMakeLists.txt30
-rw-r--r--systemtests/tests/checkpoints/etc/bareos/bareos-dir.d/catalog/MyCatalog.conf.in6
-rw-r--r--systemtests/tests/checkpoints/etc/bareos/bareos-dir.d/client/bareos-fd.conf.in7
-rw-r--r--systemtests/tests/checkpoints/etc/bareos/bareos-dir.d/director/bareos-dir.conf.in26
-rw-r--r--systemtests/tests/checkpoints/etc/bareos/bareos-dir.d/fileset/Catalog.conf.in11
-rw-r--r--systemtests/tests/checkpoints/etc/bareos/bareos-dir.d/fileset/SelfTest.conf.in20
-rw-r--r--systemtests/tests/checkpoints/etc/bareos/bareos-dir.d/job/BackupCatalog.conf.in20
-rw-r--r--systemtests/tests/checkpoints/etc/bareos/bareos-dir.d/job/RestoreFiles.conf.in11
-rw-r--r--systemtests/tests/checkpoints/etc/bareos/bareos-dir.d/job/backup-bareos-fd.conf5
-rw-r--r--systemtests/tests/checkpoints/etc/bareos/bareos-dir.d/job/slow-backup-bareos-fd.conf12
-rw-r--r--systemtests/tests/checkpoints/etc/bareos/bareos-dir.d/jobdefs/DefaultJob.conf.in15
-rw-r--r--systemtests/tests/checkpoints/etc/bareos/bareos-dir.d/messages/Daemon.conf.in7
-rw-r--r--systemtests/tests/checkpoints/etc/bareos/bareos-dir.d/messages/Standard.conf.in7
-rw-r--r--systemtests/tests/checkpoints/etc/bareos/bareos-dir.d/pool/Differential.conf10
-rw-r--r--systemtests/tests/checkpoints/etc/bareos/bareos-dir.d/pool/Full.conf10
-rw-r--r--systemtests/tests/checkpoints/etc/bareos/bareos-dir.d/pool/FullSmallvolumes.conf11
-rw-r--r--systemtests/tests/checkpoints/etc/bareos/bareos-dir.d/pool/Incremental.conf10
-rw-r--r--systemtests/tests/checkpoints/etc/bareos/bareos-dir.d/pool/Scratch.conf4
-rw-r--r--systemtests/tests/checkpoints/etc/bareos/bareos-dir.d/profile/operator.conf18
-rw-r--r--systemtests/tests/checkpoints/etc/bareos/bareos-dir.d/storage/File.conf.in8
-rw-r--r--systemtests/tests/checkpoints/etc/bareos/bareos-fd.d/client/myself.conf.in19
-rw-r--r--systemtests/tests/checkpoints/etc/bareos/bareos-fd.d/director/bareos-dir.conf.in5
-rw-r--r--systemtests/tests/checkpoints/etc/bareos/bareos-fd.d/messages/Standard.conf5
-rw-r--r--systemtests/tests/checkpoints/etc/bareos/bareos-sd.d/device/FileStorage.conf11
-rw-r--r--systemtests/tests/checkpoints/etc/bareos/bareos-sd.d/director/bareos-dir.conf.in5
-rw-r--r--systemtests/tests/checkpoints/etc/bareos/bareos-sd.d/messages/Standard.conf5
-rw-r--r--systemtests/tests/checkpoints/etc/bareos/bareos-sd.d/storage/bareos-sd.conf.in12
-rw-r--r--systemtests/tests/checkpoints/etc/bareos/bconsole.conf.in10
-rwxr-xr-xsystemtests/tests/checkpoints/test-setup40
-rwxr-xr-xsystemtests/tests/checkpoints/testrunner-checkpoints-on-cancel111
-rwxr-xr-xsystemtests/tests/checkpoints/testrunner-checkpoints-on-kill109
-rwxr-xr-xsystemtests/tests/checkpoints/testrunner-checkpoints-on-stop104
-rw-r--r--systemtests/tests/parallel-jobs/CMakeLists.txt21
-rw-r--r--systemtests/tests/parallel-jobs/etc/bareos/bareos-dir.d/catalog/MyCatalog.conf.in6
-rw-r--r--systemtests/tests/parallel-jobs/etc/bareos/bareos-dir.d/client/bareos-fd.conf.in8
-rw-r--r--systemtests/tests/parallel-jobs/etc/bareos/bareos-dir.d/director/bareos-dir.conf.in26
-rw-r--r--systemtests/tests/parallel-jobs/etc/bareos/bareos-dir.d/fileset/Catalog.conf.in11
-rw-r--r--systemtests/tests/parallel-jobs/etc/bareos/bareos-dir.d/fileset/SelfTest.conf.in20
-rw-r--r--systemtests/tests/parallel-jobs/etc/bareos/bareos-dir.d/job/BackupCatalog.conf.in20
-rw-r--r--systemtests/tests/parallel-jobs/etc/bareos/bareos-dir.d/job/RestoreFiles.conf.in11
-rw-r--r--systemtests/tests/parallel-jobs/etc/bareos/bareos-dir.d/job/backup-bareos-fd.conf6
-rw-r--r--systemtests/tests/parallel-jobs/etc/bareos/bareos-dir.d/jobdefs/DefaultJob.conf.in15
-rw-r--r--systemtests/tests/parallel-jobs/etc/bareos/bareos-dir.d/messages/Daemon.conf.in7
-rw-r--r--systemtests/tests/parallel-jobs/etc/bareos/bareos-dir.d/messages/Standard.conf.in7
-rw-r--r--systemtests/tests/parallel-jobs/etc/bareos/bareos-dir.d/pool/Differential.conf10
-rw-r--r--systemtests/tests/parallel-jobs/etc/bareos/bareos-dir.d/pool/Full.conf10
-rw-r--r--systemtests/tests/parallel-jobs/etc/bareos/bareos-dir.d/pool/FullSmallvolumes.conf11
-rw-r--r--systemtests/tests/parallel-jobs/etc/bareos/bareos-dir.d/pool/Incremental.conf10
-rw-r--r--systemtests/tests/parallel-jobs/etc/bareos/bareos-dir.d/pool/Scratch.conf4
-rw-r--r--systemtests/tests/parallel-jobs/etc/bareos/bareos-dir.d/profile/operator.conf18
-rw-r--r--systemtests/tests/parallel-jobs/etc/bareos/bareos-dir.d/storage/File.conf.in9
-rw-r--r--systemtests/tests/parallel-jobs/etc/bareos/bareos-fd.d/client/myself.conf.in19
-rw-r--r--systemtests/tests/parallel-jobs/etc/bareos/bareos-fd.d/director/bareos-dir.conf.in5
-rw-r--r--systemtests/tests/parallel-jobs/etc/bareos/bareos-fd.d/messages/Standard.conf5
-rw-r--r--systemtests/tests/parallel-jobs/etc/bareos/bareos-sd.d/device/FileStorage.conf11
-rw-r--r--systemtests/tests/parallel-jobs/etc/bareos/bareos-sd.d/director/bareos-dir.conf.in5
-rw-r--r--systemtests/tests/parallel-jobs/etc/bareos/bareos-sd.d/messages/Standard.conf5
-rw-r--r--systemtests/tests/parallel-jobs/etc/bareos/bareos-sd.d/storage/bareos-sd.conf.in11
-rw-r--r--systemtests/tests/parallel-jobs/etc/bareos/bconsole.conf.in10
-rwxr-xr-xsystemtests/tests/parallel-jobs/parallel66
-rwxr-xr-xsystemtests/tests/parallel-jobs/test-setup40
-rwxr-xr-xsystemtests/tests/parallel-jobs/testrunner-parallel-jobs48
-rw-r--r--systemtests/tests/stresstest/CMakeLists.txt21
-rw-r--r--systemtests/tests/stresstest/cloneandcopykernelcode36
-rw-r--r--systemtests/tests/stresstest/etc/bareos/bareos-dir.d/catalog/MyCatalog.conf.in6
-rw-r--r--systemtests/tests/stresstest/etc/bareos/bareos-dir.d/client/bareos-fd.conf.in7
-rw-r--r--systemtests/tests/stresstest/etc/bareos/bareos-dir.d/director/bareos-dir.conf.in26
-rw-r--r--systemtests/tests/stresstest/etc/bareos/bareos-dir.d/fileset/Catalog.conf.in11
-rw-r--r--systemtests/tests/stresstest/etc/bareos/bareos-dir.d/fileset/SelfTest.conf.in20
-rw-r--r--systemtests/tests/stresstest/etc/bareos/bareos-dir.d/job/BackupCatalog.conf.in20
-rw-r--r--systemtests/tests/stresstest/etc/bareos/bareos-dir.d/job/RestoreFiles.conf.in11
-rw-r--r--systemtests/tests/stresstest/etc/bareos/bareos-dir.d/job/backup-bareos-fd.conf5
-rw-r--r--systemtests/tests/stresstest/etc/bareos/bareos-dir.d/job/slow-backup-bareos-fd.conf12
-rw-r--r--systemtests/tests/stresstest/etc/bareos/bareos-dir.d/jobdefs/DefaultJob.conf.in15
-rw-r--r--systemtests/tests/stresstest/etc/bareos/bareos-dir.d/messages/Daemon.conf.in7
-rw-r--r--systemtests/tests/stresstest/etc/bareos/bareos-dir.d/messages/Standard.conf.in7
-rw-r--r--systemtests/tests/stresstest/etc/bareos/bareos-dir.d/pool/Differential.conf10
-rw-r--r--systemtests/tests/stresstest/etc/bareos/bareos-dir.d/pool/Full.conf10
-rw-r--r--systemtests/tests/stresstest/etc/bareos/bareos-dir.d/pool/FullSmallvolumes.conf11
-rw-r--r--systemtests/tests/stresstest/etc/bareos/bareos-dir.d/pool/Incremental.conf10
-rw-r--r--systemtests/tests/stresstest/etc/bareos/bareos-dir.d/pool/Scratch.conf4
-rw-r--r--systemtests/tests/stresstest/etc/bareos/bareos-dir.d/profile/operator.conf18
-rw-r--r--systemtests/tests/stresstest/etc/bareos/bareos-dir.d/storage/File.conf.in8
-rw-r--r--systemtests/tests/stresstest/etc/bareos/bareos-fd.d/client/myself.conf.in19
-rw-r--r--systemtests/tests/stresstest/etc/bareos/bareos-fd.d/director/bareos-dir.conf.in5
-rw-r--r--systemtests/tests/stresstest/etc/bareos/bareos-fd.d/messages/Standard.conf5
-rw-r--r--systemtests/tests/stresstest/etc/bareos/bareos-sd.d/device/FileStorage.conf11
-rw-r--r--systemtests/tests/stresstest/etc/bareos/bareos-sd.d/director/bareos-dir.conf.in5
-rw-r--r--systemtests/tests/stresstest/etc/bareos/bareos-sd.d/messages/Standard.conf5
-rw-r--r--systemtests/tests/stresstest/etc/bareos/bareos-sd.d/storage/bareos-sd.conf.in12
-rw-r--r--systemtests/tests/stresstest/etc/bareos/bconsole.conf.in10
-rwxr-xr-xsystemtests/tests/stresstest/test-setup43
-rwxr-xr-xsystemtests/tests/stresstest/testrunner-stresstest48
116 files changed, 2047 insertions, 117 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2e7a0c95f..629dc225e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -47,6 +47,8 @@ and since Bareos version 20 this project adheres to [Semantic Versioning](https:
- bareos tools: reintegrate testfind binary [PR #1176]
- fd: add support for role switching on PostgreSQL add-on [BUG #4607] [PR #1178]
- build: switch from FreeBSD 13.0 to 13.1 [PR #1253]
+- stored: dird: added backup checkpoints that save backup metadata to the Catalog during the execution of the backup. [PR#1074]
+- stored: dird: add backup checkpoints that save backup metadata to the Catalog during the execution of the backup. [PR #1074]
### Fixed
- dird: RunScript fixes [PR #1217]
@@ -204,6 +206,7 @@ and since Bareos version 20 this project adheres to [Semantic Versioning](https:
[PR #1067]: https://github.com/bareos/bareos/pull/1067
[PR #1070]: https://github.com/bareos/bareos/pull/1070
[PR #1072]: https://github.com/bareos/bareos/pull/1072
+[PR #1074]: https://github.com/bareos/bareos/pull/1074
[PR #1079]: https://github.com/bareos/bareos/pull/1079
[PR #1081]: https://github.com/bareos/bareos/pull/1081
[PR #1082]: https://github.com/bareos/bareos/pull/1082
diff --git a/core/src/cats/cats.h b/core/src/cats/cats.h
index acb2034f0..37f6c8fdc 100644
--- a/core/src/cats/cats.h
+++ b/core/src/cats/cats.h
@@ -893,6 +893,7 @@ class BareosDb : public BareosDbQueryEnum {
/* sql_update.c */
bool UpdateJobStartRecord(JobControlRecord* jcr, JobDbRecord* jr);
+ bool UpdateRunningJobRecord(JobControlRecord* jcr);
bool UpdateJobEndRecord(JobControlRecord* jcr, JobDbRecord* jr);
bool UpdateClientRecord(JobControlRecord* jcr, ClientDbRecord* cr);
bool UpdatePoolRecord(JobControlRecord* jcr, PoolDbRecord* pr);
diff --git a/core/src/cats/sql_create.cc b/core/src/cats/sql_create.cc
index fd4c291e7..3d26ceba9 100644
--- a/core/src/cats/sql_create.cc
+++ b/core/src/cats/sql_create.cc
@@ -3,7 +3,7 @@
Copyright (C) 2000-2012 Free Software Foundation Europe e.V.
Copyright (C) 2011-2016 Planets Communications B.V.
- Copyright (C) 2013-2020 Bareos GmbH & Co. KG
+ Copyright (C) 2013-2022 Bareos GmbH & Co. KG
This program is Free Software; you can redistribute it and/or
modify it under the terms of version three of the GNU Affero General Public
@@ -107,31 +107,58 @@ bool BareosDb::CreateJobmediaRecord(JobControlRecord* jcr, JobMediaDbRecord* jm)
DbLocker _{this};
- Mmsg(cmd, "SELECT count(*) from JobMedia WHERE JobId=%s",
- edit_int64(jm->JobId, ed1));
- count = GetSqlRecordMax(jcr);
- if (count < 0) { count = 0; }
- count++;
-
/* clang-format off */
Mmsg(cmd,
- "INSERT INTO JobMedia (JobId,MediaId,FirstIndex,LastIndex,"
- "StartFile,EndFile,StartBlock,EndBlock,VolIndex,JobBytes) "
- "VALUES (%s,%s,%u,%u,%u,%u,%u,%u,%u,%s)",
+ "UPDATE JobMedia SET LastIndex=%lu, EndFile=%lu, EndBlock=%lu, JobBytes=%s "
+ "WHERE JobId=%s AND MediaId=%s",
+ jm->LastIndex,
+ jm->EndFile,
+ jm->EndBlock,
+ edit_uint64(jm->JobBytes, ed3),
edit_int64(jm->JobId, ed1),
- edit_int64(jm->MediaId, ed2),
- jm->FirstIndex, jm->LastIndex,
- jm->StartFile, jm->EndFile,
- jm->StartBlock, jm->EndBlock,
- count,
- edit_uint64(jm->JobBytes, ed3));
+ edit_int64(jm->MediaId, ed2));
/* clang-format on */
+ bool update_result = false;
+ bool insert_result = false;
Dmsg0(300, cmd);
- if (INSERT_DB(jcr, cmd) != 1) {
- Mmsg2(errmsg, _("Create JobMedia record %s failed: ERR=%s\n"), cmd,
- sql_strerror());
+
+ if (UPDATE_DB(jcr, cmd) <= 0) {
+ Mmsg2(errmsg,
+ _("Update JobMedia record %s failed: ERR=%s\n Trying to insert: \n"),
+ cmd, sql_strerror());
+
+ Mmsg(cmd, "SELECT count(*) from JobMedia WHERE JobId=%s",
+ edit_int64(jm->JobId, ed1));
+ count = GetSqlRecordMax(jcr);
+ if (count < 0) { count = 0; }
+ count++;
+
+ /* clang-format off */
+ Mmsg(cmd,
+ "INSERT INTO JobMedia (JobId,MediaId,FirstIndex,LastIndex,"
+ "StartFile,EndFile,StartBlock,EndBlock,VolIndex,JobBytes) "
+ "VALUES (%s,%s,%lu,%lu,%lu,%lu,%lu,%lu,%lu,%s)",
+ edit_int64(jm->JobId, ed1),
+ edit_int64(jm->MediaId, ed2),
+ jm->FirstIndex, jm->LastIndex,
+ jm->StartFile, jm->EndFile,
+ jm->StartBlock, jm->EndBlock,
+ count,
+ edit_uint64(jm->JobBytes, ed3));
+ /* clang-format on */
+
+ if (INSERT_DB(jcr, cmd) != 1) {
+ Mmsg2(errmsg, _("Create JobMedia record %s failed: ERR=%s\n"), cmd,
+ sql_strerror());
+ } else {
+ insert_result = true;
+ }
} else {
+ update_result = true;
+ }
+
+ if (update_result || insert_result) {
// Worked, now update the Media record with the EndFile and EndBlock
Mmsg(cmd, "UPDATE Media SET EndFile=%u, EndBlock=%u WHERE MediaId=%u",
jm->EndFile, jm->EndBlock, jm->MediaId);
@@ -759,8 +786,6 @@ bool BareosDb::WriteBatchFileRecords(JobControlRecord* jcr)
return true;
}
- if (JobCanceled(jcr)) { goto bail_out; }
-
Dmsg1(50, "db_create_file_record changes=%u\n", changes);
jcr->JobStatus = JS_AttrInserting;
@@ -774,8 +799,6 @@ bool BareosDb::WriteBatchFileRecords(JobControlRecord* jcr)
goto bail_out;
}
- if (JobCanceled(jcr)) { goto bail_out; }
-
if (!jcr->db_batch->SqlQuery(SQL_QUERY::batch_lock_path_query)) {
Jmsg1(jcr, M_FATAL, 0, "Lock Path table %s\n", errmsg);
goto bail_out;
diff --git a/core/src/cats/sql_update.cc b/core/src/cats/sql_update.cc
index b924e643d..525d9dadf 100644
--- a/core/src/cats/sql_update.cc
+++ b/core/src/cats/sql_update.cc
@@ -100,16 +100,26 @@ bool BareosDb::UpdateJobStartRecord(JobControlRecord* jcr, JobDbRecord* jr)
DbLocker _{this};
Mmsg(cmd,
"UPDATE Job SET JobStatus='%c',Level='%c',StartTime='%s',"
- "ClientId=%s,JobTDate=%s,PoolId=%s,FileSetId=%s WHERE JobId=%s",
+ "ClientId=%s,JobTDate=%s,PoolId=%s,FileSetId=%s,VolSessionId=%lu,"
+ "VolSessionTime=%lu WHERE JobId=%s",
(char)(jcr->JobStatus), (char)(jr->JobLevel), dt,
edit_int64(jr->ClientId, ed1), edit_uint64(JobTDate, ed2),
edit_int64(jr->PoolId, ed3), edit_int64(jr->FileSetId, ed4),
- edit_int64(jr->JobId, ed5));
+ jcr->VolSessionId, jcr->VolSessionTime, edit_int64(jr->JobId, ed5));
changes = 0;
return UPDATE_DB(jcr, cmd) > 0;
}
+bool BareosDb::UpdateRunningJobRecord(JobControlRecord* jcr)
+{
+ DbLocker _{this};
+ Mmsg(cmd, "UPDATE Job SET JobBytes=%llu,JobFiles=%lu WHERE JobId=%lu",
+ jcr->JobBytes, jcr->JobFiles, jcr->JobId);
+
+ return UPDATE_DB(jcr, cmd) > 0;
+}
+
// Update Long term statistics with all jobs that were run before age seconds
int BareosDb::UpdateStats(JobControlRecord* jcr, utime_t age)
{
@@ -161,6 +171,7 @@ bool BareosDb::UpdateJobEndRecord(JobControlRecord* jcr, JobDbRecord* jr)
JobTDate = ttime;
DbLocker _{this};
+
Mmsg(
cmd,
"UPDATE Job SET JobStatus='%c',Level='%c',EndTime='%s',"
diff --git a/core/src/dird/catreq.cc b/core/src/dird/catreq.cc
index abe418ddb..f2e712a5d 100644
--- a/core/src/dird/catreq.cc
+++ b/core/src/dird/catreq.cc
@@ -3,7 +3,7 @@
Copyright (C) 2001-2012 Free Software Foundation Europe e.V.
Copyright (C) 2011-2016 Planets Communications B.V.
- Copyright (C) 2013-2020 Bareos GmbH & Co. KG
+ Copyright (C) 2013-2022 Bareos GmbH & Co. KG
This program is Free Software; you can redistribute it and/or
modify it under the terms of version three of the GNU Affero General Public
@@ -66,6 +66,11 @@ static char Create_job_media[]
" FirstIndex=%u LastIndex=%u StartFile=%u EndFile=%u "
" StartBlock=%u EndBlock=%u Copy=%d Strip=%d MediaId=%lld\n";
+static char Update_filelist[] = "Catreq Job=%127s UpdateFileList\n";
+
+static char Update_jobrecord[]
+ = "Catreq Job=%127s UpdateJobRecord JobFiles=%lu JobBytes=%llu\n";
+
// Responses sent to Storage daemon
static char OK_media[]
= "1000 OK VolName=%s VolJobs=%u VolFiles=%u"
@@ -126,6 +131,9 @@ void CatalogRequest(JobControlRecord* jcr, BareosSocket* bs)
return;
}
+ uint32_t update_jobfiles = 0;
+ uint64_t update_jobbytes = 0;
+
// Find next appendable medium for SD
unwanted_volumes.check_size(bs->message_length);
if (sscanf(bs->msg, Find_media, &Job, &index, &pool_name, &mr.MediaType,
@@ -340,6 +348,28 @@ void CatalogRequest(JobControlRecord* jcr, BareosSocket* bs)
Dmsg0(400, "JobMedia record created\n");
bs->fsend(OK_create);
}
+ } else if (sscanf(bs->msg, Update_filelist, &Job) == 1) {
+ Dmsg0(0, "Updating filelist\n");
+
+ if (!jcr->db_batch->WriteBatchFileRecords(jcr)) {
+ Jmsg(jcr, M_FATAL, 0, _("Catalog error updating File table. %s\n"),
+ jcr->db_batch->strerror());
+ bs->fsend(_("1992 Update File table error\n"));
+ }
+
+ } else if (sscanf(bs->msg, Update_jobrecord, &Job, &update_jobfiles,
+ &update_jobbytes)
+ == 3) {
+ Dmsg0(0, "Updating job record\n");
+
+ jcr->JobFiles = update_jobfiles;
+ jcr->JobBytes = update_jobbytes;
+
+ if (!jcr->db->UpdateRunningJobRecord(jcr)) {
+ Jmsg(jcr, M_FATAL, 0, _("Catalog error updating Job record. %s\n"),
+ jcr->db->strerror());
+ bs->fsend(_("1992 Update job record error\n"));
+ }
} else {
omsg = GetMemory(bs->message_length + 1);
PmStrcpy(omsg, bs->msg);
diff --git a/core/src/stored/append.cc b/core/src/stored/append.cc
index fe9e18dd7..e6bc002e5 100644
--- a/core/src/stored/append.cc
+++ b/core/src/stored/append.cc
@@ -25,19 +25,17 @@
* Append code for Storage daemon
*/
-#include "include/bareos.h"
+#include "stored/append.h"
#include "stored/stored.h"
#include "stored/acquire.h"
-#include "stored/append.h"
-#include "stored/device_control_record.h"
#include "stored/fd_cmds.h"
+#include "stored/stored_globals.h"
#include "stored/jcr_private.h"
#include "stored/label.h"
#include "stored/spool.h"
#include "lib/bget_msg.h"
#include "lib/edit.h"
#include "include/jcr.h"
-#include "lib/bsock.h"
#include "lib/berrno.h"
#include "lib/berrno.h"
@@ -52,22 +50,127 @@ static char OK_replicate[] = "3000 OK replicate data\n";
void PossibleIncompleteJob(JobControlRecord* jcr, int32_t last_file_index) {}
+
+ProcessedFileData::ProcessedFileData(DeviceRecord* record)
+ : volsessionid_(record->VolSessionId)
+ , volsessiontime_(record->VolSessionTime)
+ , fileindex_(record->FileIndex)
+ , stream_(record->Stream)
+ , data_len_(record->data_len)
+ , data_(record->data, record->data + record->data_len)
+{
+}
+
+DeviceRecord ProcessedFileData::GetData()
+{
+ DeviceRecord devicerecord{};
+ devicerecord.VolSessionId = volsessionid_;
+ devicerecord.VolSessionTime = volsessiontime_;
+ devicerecord.FileIndex = fileindex_;
+ devicerecord.Stream = stream_;
+ devicerecord.data_len = data_len_;
+ devicerecord.data = data_.data();
+
+ return devicerecord;
+}
+
+ProcessedFile::ProcessedFile(int32_t fileindex) : fileindex_(fileindex) {}
+
+void ProcessedFile::SendAttributesToDirector(JobControlRecord* jcr)
+{
+ for_each(attributes_.begin(), attributes_.end(),
+ [&jcr](ProcessedFileData& attribute) {
+ DeviceRecord devicerecord = attribute.GetData();
+ SendAttrsToDir(jcr, &devicerecord);
+ });
+}
+
+void ProcessedFile::AddAttribute(DeviceRecord* record)
+{
+ attributes_.emplace_back(ProcessedFileData(record));
+}
+
+bool IsAttribute(DeviceRecord* record)
+{
+ return record->maskedStream == STREAM_UNIX_ATTRIBUTES
+ || record->maskedStream == STREAM_UNIX_ATTRIBUTES_EX
+ || record->maskedStream == STREAM_RESTORE_OBJECT
+ || CryptoDigestStreamType(record->maskedStream) != CRYPTO_DIGEST_NONE;
+}
+
+static void UpdateFileList(JobControlRecord* jcr)
+{
+ Dmsg0(100, _("... update file list\n"));
+ jcr->impl->dcr->DirAskToUpdateFileList();
+}
+
+static void UpdateJobmediaRecord(JobControlRecord* jcr)
+{
+ Dmsg0(100, _("... create job media record\n"));
+ jcr->impl->dcr->DirCreateJobmediaRecord(false);
+}
+
+static void UpdateJobrecord(JobControlRecord* jcr)
+{
+ Dmsg2(100, _("... update job record: %llu bytes %lu files\n"), jcr->JobBytes,
+ jcr->JobFiles);
+ jcr->impl->dcr->DirAskToUpdateJobRecord();
+}
+
+void DoBackupCheckpoint(JobControlRecord* jcr)
+{
+ Jmsg(jcr, M_INFO, 0,
+ _("Checkpoint: Syncing current backup status to catalog\n"));
+ UpdateJobrecord(jcr);
+ UpdateFileList(jcr);
+ UpdateJobmediaRecord(jcr);
+ Jmsg(jcr, M_INFO, 0, _("Checkpoint completed\n"));
+}
+
+static time_t DoTimedCheckpoint(JobControlRecord* jcr,
+ time_t checkpoint_time,
+ time_t checkpoint_interval)
+{
+ const time_t now
+ = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
+ if (now > checkpoint_time) {
+ while (checkpoint_time <= now) { checkpoint_time += checkpoint_interval; }
+ Jmsg(jcr, M_INFO, 0,
+ _("Doing timed backup checkpoint. Next checkpoint in %d seconds\n"),
+ checkpoint_interval);
+ DoBackupCheckpoint(jcr);
+ }
+
+ return checkpoint_time;
+}
+
+static void SaveFullyProcessedFiles(JobControlRecord* jcr,
+ std::vector<ProcessedFile>& processed_files)
+{
+ if (!processed_files.empty()) {
+ for_each(
+ processed_files.begin(), processed_files.end(),
+ [&jcr](ProcessedFile& file) { file.SendAttributesToDirector(jcr); });
+ jcr->JobFiles = processed_files.back().GetFileIndex();
+ processed_files.clear();
+ }
+}
+
// Append Data sent from File daemon
bool DoAppendData(JobControlRecord* jcr, BareosSocket* bs, const char* what)
{
int32_t n, file_index, stream, last_file_index, job_elapsed;
bool ok = true;
char buf1[100];
- DeviceControlRecord* dcr = jcr->impl->dcr;
Device* dev;
POOLMEM* rec_data;
char ec[50];
- if (!dcr) {
+ if (!jcr->impl->dcr) {
Jmsg0(jcr, M_FATAL, 0, _("DeviceControlRecord is NULL!!!\n"));
return false;
}
- dev = dcr->dev;
+ dev = jcr->impl->dcr->dev;
if (!dev) {
Jmsg0(jcr, M_FATAL, 0, _("Device is NULL!!!\n"));
return false;
@@ -75,16 +178,23 @@ bool DoAppendData(JobControlRecord* jcr, BareosSocket* bs, const char* what)
Dmsg1(100, "Start append data. res=%d\n", dev->NumReserved());
- if (!bs->SetBufferSize(dcr->device_resource->max_network_buffer_size,
- BNET_SETBUF_WRITE)) {
+ if (!bs->SetBufferSize(
+ jcr->impl->dcr->device_resource->max_network_buffer_size,
+ BNET_SETBUF_WRITE)) {
Jmsg0(jcr, M_FATAL, 0, _("Unable to set network buffer size.\n"));
- goto bail_out;
+ jcr->setJobStatus(JS_ErrorTerminated);
+ return false;
}
- if (!AcquireDeviceForAppend(dcr)) { goto bail_out; }
+ if (!AcquireDeviceForAppend(jcr->impl->dcr)) {
+ jcr->setJobStatus(JS_ErrorTerminated);
+ return false;
+ }
- if (GeneratePluginEvent(jcr, bSdEventSetupRecordTranslation, dcr) != bRC_OK) {
- goto bail_out;
+ if (GeneratePluginEvent(jcr, bSdEventSetupRecordTranslation, jcr->impl->dcr)
+ != bRC_OK) {
+ jcr->setJobStatus(JS_ErrorTerminated);
+ return false;
}
jcr->sendJobStatus(JS_Running);
@@ -94,11 +204,15 @@ bool DoAppendData(JobControlRecord* jcr, BareosSocket* bs, const char* what)
}
Dmsg1(50, "Begin append device=%s\n", dev->print_name());
- if (!BeginDataSpool(dcr)) { goto bail_out; }
+ if (!BeginDataSpool(jcr->impl->dcr)) {
+ jcr->setJobStatus(JS_ErrorTerminated);
+ return false;
+ }
if (!BeginAttributeSpool(jcr)) {
- DiscardDataSpool(dcr);
- goto bail_out;
+ DiscardDataSpool(jcr->impl->dcr);
+ jcr->setJobStatus(JS_ErrorTerminated);
+ return false;
}
Dmsg0(100, "Just after AcquireDeviceForAppend\n");
@@ -107,7 +221,7 @@ bool DoAppendData(JobControlRecord* jcr, BareosSocket* bs, const char* what)
}
// Write Begin Session Record
- if (!WriteSessionLabel(dcr, SOS_LABEL)) {
+ if (!WriteSessionLabel(jcr->impl->dcr, SOS_LABEL)) {
Jmsg1(jcr, M_FATAL, 0, _("Write session label failed. ERR=%s\n"),
dev->bstrerror());
jcr->setJobStatus(JS_ErrorTerminated);
@@ -142,8 +256,19 @@ bool DoAppendData(JobControlRecord* jcr, BareosSocket* bs, const char* what)
* file. 1. for the Attributes, 2. for the file data if any,
* and 3. for the MD5 if any.
*/
- dcr->VolFirstIndex = dcr->VolLastIndex = 0;
+ jcr->impl->dcr->VolFirstIndex = jcr->impl->dcr->VolLastIndex = 0;
jcr->run_time = time(NULL); /* start counting time for rates */
+
+ auto now
+ = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
+ time_t next_checkpoint_time = now + me->checkpoint_interval;
+
+ std::vector<ProcessedFile> processed_files{};
+ int64_t current_volumeid = jcr->impl->dcr->VolMediaId;
+
+ ProcessedFile file_currently_processed;
+ uint32_t current_block_number = jcr->impl->dcr->block->BlockNumber;
+
for (last_file_index = 0; ok && !jcr->IsJobCanceled();) {
/*
* Read Stream header from the daemon.
@@ -180,24 +305,28 @@ bool DoAppendData(JobControlRecord* jcr, BareosSocket* bs, const char* what)
* An incomplete job can start the file_index at any number.
* otherwise, it must start at 1.
*/
- if (jcr->rerunning && file_index > 0 && last_file_index == 0) {
- goto fi_checked;
- }
- if (file_index > 0
- && (file_index == last_file_index
- || file_index == last_file_index + 1)) {
- goto fi_checked;
+
+ bool incomplete_job_rerun_fileindex_positive
+ = jcr->rerunning && file_index > 0 && last_file_index == 0;
+ bool fileindex_is_sequential = file_index > 0
+ && (file_index == last_file_index
+ || file_index == last_file_index + 1);
+
+ if (!incomplete_job_rerun_fileindex_positive && !fileindex_is_sequential) {
+ Jmsg3(jcr, M_FATAL, 0,
+ _("FileIndex=%d from %s not positive or sequential=%d\n"),
+ file_index, what, last_file_index);
+ PossibleIncompleteJob(jcr, last_file_index);
+ ok = false;
+ break;
}
- Jmsg3(jcr, M_FATAL, 0, _("FI=%d from %s not positive or sequential=%d\n"),
- file_index, what, last_file_index);
- PossibleIncompleteJob(jcr, last_file_index);
- ok = false;
- break;
- fi_checked:
if (file_index != last_file_index) {
- jcr->JobFiles = file_index;
last_file_index = file_index;
+ if (file_currently_processed.GetFileIndex() > 0) {
+ processed_files.push_back(std::move(file_currently_processed));
+ }
+ file_currently_processed = ProcessedFile{file_index};
}
/*
@@ -205,35 +334,57 @@ bool DoAppendData(JobControlRecord* jcr, BareosSocket* bs, const char* what)
* We save the original data pointer from the record so we can restore
* that after the loop ends.
*/
- rec_data = dcr->rec->data;
+ rec_data = jcr->impl->dcr->rec->data;
while ((n = BgetMsg(bs)) > 0 && !jcr->IsJobCanceled()) {
- dcr->rec->VolSessionId = jcr->VolSessionId;
- dcr->rec->VolSessionTime = jcr->VolSessionTime;
- dcr->rec->FileIndex = file_index;
- dcr->rec->Stream = stream;
- dcr->rec->maskedStream = stream & STREAMMASK_TYPE; /* strip high bits */
- dcr->rec->data_len = bs->message_length;
- dcr->rec->data = bs->msg; /* use message buffer */
+ jcr->impl->dcr->rec->VolSessionId = jcr->VolSessionId;
+ jcr->impl->dcr->rec->VolSessionTime = jcr->VolSessionTime;
+ jcr->impl->dcr->rec->FileIndex = file_index;
+ jcr->impl->dcr->rec->Stream = stream;
+ jcr->impl->dcr->rec->maskedStream
+ = stream & STREAMMASK_TYPE; /* strip high bits */
+ jcr->impl->dcr->rec->data_len = bs->message_length;
+ jcr->impl->dcr->rec->data = bs->msg; /* use message buffer */
Dmsg4(850, "before writ_rec FI=%d SessId=%d Strm=%s len=%d\n",
- dcr->rec->FileIndex, dcr->rec->VolSessionId,
- stream_to_ascii(buf1, dcr->rec->Stream, dcr->rec->FileIndex),
- dcr->rec->data_len);
+ jcr->impl->dcr->rec->FileIndex, jcr->impl->dcr->rec->VolSessionId,
+ stream_to_ascii(buf1, jcr->impl->dcr->rec->Stream,
+ jcr->impl->dcr->rec->FileIndex),
+ jcr->impl->dcr->rec->data_len);
- ok = dcr->WriteRecord();
+ ok = jcr->impl->dcr->WriteRecord();
if (!ok) {
Dmsg2(90, "Got WriteBlockToDev error on device %s. %s\n",
- dcr->dev->print_name(), dcr->dev->bstrerror());
+ jcr->impl->dcr->dev->print_name(),
+ jcr->impl->dcr->dev->bstrerror());
break;
}
- SendAttrsToDir(jcr, dcr->rec);
+ if (current_block_number != jcr->impl->dcr->block->BlockNumber) {
+ current_block_number = jcr->impl->dcr->block->BlockNumber;
+ SaveFullyProcessedFiles(jcr, processed_files);
+ }
+
+ if (IsAttribute(jcr->impl->dcr->rec)) {
+ file_currently_processed.AddAttribute(jcr->impl->dcr->rec);
+ }
+
+ if (me->checkpoint_interval) {
+ if (jcr->impl->dcr->VolMediaId != current_volumeid) {
+ Jmsg0(jcr, M_INFO, 0, _("Volume changed, doing checkpoint:\n"));
+ DoBackupCheckpoint(jcr);
+ current_volumeid = jcr->impl->dcr->VolMediaId;
+ } else {
+ next_checkpoint_time = DoTimedCheckpoint(jcr, next_checkpoint_time,
+ me->checkpoint_interval);
+ }
+ }
+
Dmsg0(650, "Enter bnet_get\n");
}
Dmsg2(650, "End read loop with %s. Stat=%d\n", what, n);
// Restore the original data pointer.
- dcr->rec->data = rec_data;
+ jcr->impl->dcr->rec->data = rec_data;
if (bs->IsError()) {
if (!jcr->IsJobCanceled()) {
@@ -268,7 +419,7 @@ bool DoAppendData(JobControlRecord* jcr, BareosSocket* bs, const char* what)
* if we are at the end of the tape or we got a fatal I/O error.
*/
if (ok || dev->CanWrite()) {
- if (!WriteSessionLabel(dcr, EOS_LABEL)) {
+ if (!WriteSessionLabel(jcr->impl->dcr, EOS_LABEL)) {
// Print only if ok and not cancelled to avoid spurious messages
if (ok && !jcr->IsJobCanceled()) {
Jmsg1(jcr, M_FATAL, 0, _("Error writing end session label. ERR=%s\n"),
@@ -281,7 +432,7 @@ bool DoAppendData(JobControlRecord* jcr, BareosSocket* bs, const char* what)
Dmsg0(90, "back from write_end_session_label()\n");
// Flush out final partial block of this session
- if (!dcr->WriteBlockToDevice()) {
+ if (!jcr->impl->dcr->WriteBlockToDevice()) {
// Print only if ok and not cancelled to avoid spurious messages
if (ok && !jcr->IsJobCanceled()) {
Jmsg2(jcr, M_FATAL, 0, _("Fatal append error on device %s: ERR=%s\n"),
@@ -291,18 +442,22 @@ bool DoAppendData(JobControlRecord* jcr, BareosSocket* bs, const char* what)
}
jcr->setJobStatus(JS_ErrorTerminated);
ok = false;
+ } else if (ok && !jcr->IsJobCanceled()) {
+ // Send attributes of the final partial block of the session
+ processed_files.push_back(std::move(file_currently_processed));
+ SaveFullyProcessedFiles(jcr, processed_files);
}
}
if (!ok && !jcr->is_JobStatus(JS_Incomplete)) {
- DiscardDataSpool(dcr);
+ DiscardDataSpool(jcr->impl->dcr);
} else {
// Note: if commit is OK, the device will remain blocked
- CommitDataSpool(dcr);
+ CommitDataSpool(jcr->impl->dcr);
}
// Release the device -- and send final Vol info to DIR and unlock it.
- ReleaseDevice(dcr);
+ ReleaseDevice(jcr->impl->dcr);
/*
* Don't use time_t for job_elapsed as time_t can be 32 or 64 bits,
@@ -326,33 +481,23 @@ bool DoAppendData(JobControlRecord* jcr, BareosSocket* bs, const char* what)
Dmsg1(100, "return from DoAppendData() ok=%d\n", ok);
return ok;
-
-bail_out:
- jcr->setJobStatus(JS_ErrorTerminated);
- return false;
}
// Send attributes and digest to Director for Catalog
bool SendAttrsToDir(JobControlRecord* jcr, DeviceRecord* rec)
{
- if (rec->maskedStream == STREAM_UNIX_ATTRIBUTES
- || rec->maskedStream == STREAM_UNIX_ATTRIBUTES_EX
- || rec->maskedStream == STREAM_RESTORE_OBJECT
- || CryptoDigestStreamType(rec->maskedStream) != CRYPTO_DIGEST_NONE) {
- if (!jcr->impl->no_attributes) {
- BareosSocket* dir = jcr->dir_bsock;
- if (AreAttributesSpooled(jcr)) { dir->SetSpooling(); }
- Dmsg0(850, "Send attributes to dir.\n");
- if (!jcr->impl->dcr->DirUpdateFileAttributes(rec)) {
- Jmsg(jcr, M_FATAL, 0, _("Error updating file attributes. ERR=%s\n"),
- dir->bstrerror());
- dir->ClearSpooling();
- return false;
- }
+ if (!jcr->impl->no_attributes) {
+ BareosSocket* dir = jcr->dir_bsock;
+ if (AreAttributesSpooled(jcr)) { dir->SetSpooling(); }
+ Dmsg0(850, "Send attributes to dir.\n");
+ if (!jcr->impl->dcr->DirUpdateFileAttributes(rec)) {
+ Jmsg(jcr, M_FATAL, 0, _("Error updating file attributes. ERR=%s\n"),
+ dir->bstrerror());
dir->ClearSpooling();
+ return false;
}
+ dir->ClearSpooling();
}
return true;
}
-
} /* namespace storagedaemon */
diff --git a/core/src/stored/append.h b/core/src/stored/append.h
index adea9c65f..5b5b969b2 100644
--- a/core/src/stored/append.h
+++ b/core/src/stored/append.h
@@ -1,7 +1,7 @@
/*
BAREOS® - Backup Archiving REcovery Open Sourced
- Copyright (C) 2018-2018 Bareos GmbH & Co. KG
+ Copyright (C) 2018-2022 Bareos GmbH & Co. KG
This program is Free Software; you can redistribute it and/or
modify it under the terms of version three of the GNU Affero General Public
@@ -21,11 +21,51 @@
#ifndef BAREOS_STORED_APPEND_H_
#define BAREOS_STORED_APPEND_H_
+
+#include "include/bareos.h"
+#include "lib/bsock.h"
+#include "stored/device_control_record.h"
+#include "stored/record.h"
+
namespace storagedaemon {
+class ProcessedFileData {
+ public:
+ explicit ProcessedFileData(DeviceRecord* record);
+ DeviceRecord GetData();
+
+ private:
+ uint32_t volsessionid_{0};
+ uint32_t volsessiontime_{0};
+ int32_t fileindex_{0};
+ int32_t stream_{0};
+ uint32_t data_len_{0};
+ std::string data_{};
+};
+
+class ProcessedFile {
+ public:
+ ProcessedFile() = default;
+ explicit ProcessedFile(int32_t fileindex);
+
+ void SendAttributesToDirector(JobControlRecord* jcr);
+ void AddAttribute(DeviceRecord* record);
+ int32_t GetFileIndex() { return fileindex_; }
+ const std::vector<ProcessedFileData>& GetAttributes() const
+ {
+ return attributes_;
+ }
+
+ private:
+ int32_t fileindex_{-1};
+ std::vector<ProcessedFileData> attributes_;
+};
+
bool DoAppendData(JobControlRecord* jcr, BareosSocket* bs, const char* what);
+bool IsAttribute(DeviceRecord* record);
bool SendAttrsToDir(JobControlRecord* jcr, DeviceRecord* rec);
+void DoBackupCheckpoint(JobControlRecord* jcr);
} // namespace storagedaemon
#endif // BAREOS_STORED_APPEND_H_
diff --git a/core/src/stored/askdir.cc b/core/src/stored/askdir.cc
index 045e0ca19..4e6ebc911 100644
--- a/core/src/stored/askdir.cc
+++ b/core/src/stored/askdir.cc
@@ -62,8 +62,15 @@ static char Create_job_media[]
= "CatReq Job=%s CreateJobMedia"
" FirstIndex=%u LastIndex=%u StartFile=%u EndFile=%u"
" StartBlock=%u EndBlock=%u Copy=%d Strip=%d MediaId=%s\n";
+
+static char Update_filelist[] = "Catreq Job=%s UpdateFileList\n";
+
+static char Update_jobrecord[]
+ = "Catreq Job=%s UpdateJobRecord JobFiles=%lu JobBytes=%llu\n";
+
static char FileAttributes[] = "UpdCat Job=%s FileAttributes ";
+
/* Responses received from the Director */
static char OK_media[]
= "1000 OK VolName=%127s VolJobs=%u VolFiles=%lu"
@@ -618,6 +625,19 @@ get_out:
return true;
}
+bool StorageDaemonDeviceControlRecord::DirAskToUpdateFileList()
+{
+ BareosSocket* dir = jcr->dir_bsock;
+ return dir->fsend(Update_filelist, jcr->Job);
+}
+
+bool StorageDaemonDeviceControlRecord::DirAskToUpdateJobRecord()
+{
+ BareosSocket* dir = jcr->dir_bsock;
+ return dir->fsend(Update_jobrecord, jcr->Job, jcr->JobFiles, jcr->JobBytes);
+}
+
+
// Dummy methods for everything but SD and BTAPE.
bool DeviceControlRecord::DirAskSysopToMountVolume(int /*mode*/)
{
diff --git a/core/src/stored/block.cc b/core/src/stored/block.cc
index bddbef5ec..322fdf4f1 100644
--- a/core/src/stored/block.cc
+++ b/core/src/stored/block.cc
@@ -3,7 +3,7 @@
Copyright (C) 2001-2012 Free Software Foundation Europe e.V.
Copyright (C) 2011-2012 Planets Communications B.V.
- Copyright (C) 2013-2021 Bareos GmbH & Co. KG
+ Copyright (C) 2013-2022 Bareos GmbH & Co. KG
This program is Free Software; you can redistribute it and/or
modify it under the terms of version three of the GNU Affero General Public
@@ -843,7 +843,6 @@ bool DeviceControlRecord::WriteBlockToDev()
return true;
}
-
/**
* Write a block to the device, with locking and unlocking
*
diff --git a/core/src/stored/device_control_record.h b/core/src/stored/device_control_record.h
index 085d1c3f7..2c670904d 100644
--- a/core/src/stored/device_control_record.h
+++ b/core/src/stored/device_control_record.h
@@ -3,7 +3,7 @@
Copyright (C) 2000-2012 Free Software Foundation Europe e.V.
Copyright (C) 2011-2012 Planets Communications B.V.
- Copyright (C) 2013-2021 Bareos GmbH & Co. KG
+ Copyright (C) 2013-2022 Bareos GmbH & Co. KG
This program is Free Software; you can redistribute it and/or
modify it under the terms of version three of the GNU Affero General Public
@@ -153,6 +153,8 @@ class DeviceControlRecord {
virtual bool DirAskSysopToMountVolume(int mode);
virtual bool DirAskSysopToCreateAppendableVolume() { return true; }
virtual bool DirGetVolumeInfo(enum get_vol_info_rw writing);
+ virtual bool DirAskToUpdateFileList() { return true;}
+ virtual bool DirAskToUpdateJobRecord() { return true;}
// Methods in lock.c
void dblock(int why);
diff --git a/core/src/stored/dir_cmd.cc b/core/src/stored/dir_cmd.cc
index 625ce9954..a15609d0e 100644
--- a/core/src/stored/dir_cmd.cc
+++ b/core/src/stored/dir_cmd.cc
@@ -40,6 +40,7 @@
*/
#include "include/bareos.h"
+#include "stored/append.h"
#include "stored/stored.h"
#include "stored/acquire.h"
#include "stored/authenticate.h"
@@ -1720,7 +1721,8 @@ static bool RunCmd(JobControlRecord* jcr)
{
Dmsg1(200, "Run_cmd: %s\n", jcr->dir_bsock->msg);
- // If we do not need the FD, we are doing a migrate, copy, or virtual backup.
+ // If we do not need the FD, we are doing a migrate, copy, or virtual
+ // backup.
if (jcr->NoClientUsed()) {
return DoMacRun(jcr);
} else {
diff --git a/core/src/stored/mac.cc b/core/src/stored/mac.cc
index 543139d37..38b952eff 100644
--- a/core/src/stored/mac.cc
+++ b/core/src/stored/mac.cc
@@ -255,7 +255,9 @@ static bool CloneRecordInternally(DeviceControlRecord* dcr, DeviceRecord* rec)
jcr->impl->dcr->after_rec->FileIndex),
jcr->impl->dcr->after_rec->data_len);
- SendAttrsToDir(jcr, jcr->impl->dcr->after_rec);
+ if (IsAttribute(jcr->impl->dcr->after_rec)) {
+ SendAttrsToDir(jcr, jcr->impl->dcr->after_rec);
+ }
retval = true;
diff --git a/core/src/stored/record.cc b/core/src/stored/record.cc
index 0a4440d5f..ed23db09b 100644
--- a/core/src/stored/record.cc
+++ b/core/src/stored/record.cc
@@ -2,7 +2,7 @@
BAREOS® - Backup Archiving REcovery Open Sourced
Copyright (C) 2001-2012 Free Software Foundation Europe e.V.
- Copyright (C) 2016-2020 Bareos GmbH & Co. KG
+ Copyright (C) 2016-2022 Bareos GmbH & Co. KG
This program is Free Software; you can redistribute it and/or
modify it under the terms of version three of the GNU Affero General Public
@@ -709,7 +709,6 @@ bail_out:
bool WriteRecordToBlock(DeviceControlRecord* dcr, DeviceRecord* rec)
{
ssize_t n;
- bool retval = false;
char buf1[100], buf2[100];
DeviceBlock* block = dcr->block;
@@ -745,7 +744,7 @@ bool WriteRecordToBlock(DeviceControlRecord* dcr, DeviceRecord* rec)
* the header did not fit into the block, so flush the current
* block and come back to st_header and try again on the next block.
*/
- goto bail_out;
+ return false;
}
if (BlockWriteNavail(block) == 0) {
@@ -755,7 +754,7 @@ bool WriteRecordToBlock(DeviceControlRecord* dcr, DeviceRecord* rec)
* continuation header.
*/
rec->state = st_header_cont;
- goto bail_out;
+ return false;
}
/*
@@ -789,7 +788,7 @@ bool WriteRecordToBlock(DeviceControlRecord* dcr, DeviceRecord* rec)
* so flush the block and start the next block with
* data bytes
*/
- goto bail_out; /* Partial transfer */
+ return false; /* Partial transfer */
}
continue;
@@ -820,26 +819,23 @@ bool WriteRecordToBlock(DeviceControlRecord* dcr, DeviceRecord* rec)
* continuation header
*/
rec->state = st_header_cont;
- goto bail_out;
+ return false;
}
}
rec->remainder = 0; /* did whole transfer */
rec->state = st_none;
- retval = true;
- goto bail_out;
+ return true;
default:
Emsg1(M_ABORT, 0, _("Something went wrong. Unknown state %d.\n"),
rec->state);
rec->state = st_none;
- retval = true;
- goto bail_out;
+ return true;
}
}
-bail_out:
- return retval;
+ return false;
}
/**
diff --git a/core/src/stored/sd_device_control_record.h b/core/src/stored/sd_device_control_record.h
index b41f60cb8..f220f3823 100644
--- a/core/src/stored/sd_device_control_record.h
+++ b/core/src/stored/sd_device_control_record.h
@@ -3,7 +3,7 @@
Copyright (C) 2000-2012 Free Software Foundation Europe e.V.
Copyright (C) 2011-2012 Planets Communications B.V.
- Copyright (C) 2013-2021 Bareos GmbH & Co. KG
+ Copyright (C) 2013-2022 Bareos GmbH & Co. KG
This program is Free Software; you can redistribute it and/or
modify it under the terms of version three of the GNU Affero General Public
@@ -37,6 +37,8 @@ class StorageDaemonDeviceControlRecord : public DeviceControlRecord {
bool DirAskSysopToMountVolume(int mode) override;
bool DirAskSysopToCreateAppendableVolume() override;
bool DirGetVolumeInfo(enum get_vol_info_rw writing) override;
+ bool DirAskToUpdateFileList() override;
+ bool DirAskToUpdateJobRecord() override;
DeviceControlRecord* get_new_spooling_dcr() override;
};
diff --git a/core/src/stored/stored_conf.cc b/core/src/stored/stored_conf.cc
index 0dd6338f0..bbe5e4647 100644
--- a/core/src/stored/stored_conf.cc
+++ b/core/src/stored/stored_conf.cc
@@ -92,6 +92,7 @@ static ResourceItem store_items[] = {
{"SdConnectTimeout", CFG_TYPE_TIME, ITEM(res_store, SDConnectTimeout), 0, CFG_ITEM_DEFAULT, "1800" /* 30 minutes */, NULL, NULL},
{"FdConnectTimeout", CFG_TYPE_TIME, ITEM(res_store, FDConnectTimeout), 0, CFG_ITEM_DEFAULT, "1800" /* 30 minutes */, NULL, NULL},
{"HeartbeatInterval", CFG_TYPE_TIME, ITEM(res_store, heartbeat_interval), 0, CFG_ITEM_DEFAULT, "0", NULL, NULL},
+ {"CheckpointInterval", CFG_TYPE_TIME, ITEM(res_store, checkpoint_interval), 0, CFG_ITEM_DEFAULT, "60", NULL, NULL},
{"MaximumNetworkBufferSize", CFG_TYPE_PINT32, ITEM(res_store, max_network_buffer_size), 0, 0, NULL, NULL, NULL},
{"ClientConnectWait", CFG_TYPE_TIME, ITEM(res_store, client_wait), 0, CFG_ITEM_DEFAULT, "1800" /* 30 minutes */, NULL, NULL},
{"VerId", CFG_TYPE_STR, ITEM(res_store, verid), 0, 0, NULL, NULL, NULL},
diff --git a/core/src/stored/stored_conf.h b/core/src/stored/stored_conf.h
index 925b752f3..3b60a8c0e 100644
--- a/core/src/stored/stored_conf.h
+++ b/core/src/stored/stored_conf.h
@@ -116,6 +116,7 @@ class StorageResource
utime_t SDConnectTimeout = {0}; /**< Timeout in seconds */
utime_t FDConnectTimeout = {0}; /**< Timeout in seconds */
utime_t heartbeat_interval = {0}; /**< Interval to send hb to FD */
+ utime_t checkpoint_interval = {0}; /**< Interval to save */
utime_t client_wait = {0}; /**< Time to wait for FD to connect */
uint32_t max_network_buffer_size = 0; /**< Max network buf size */
bool autoxflateonreplication
diff --git a/core/src/tests/CMakeLists.txt b/core/src/tests/CMakeLists.txt
index 381cfba01..3c067858d 100644
--- a/core/src/tests/CMakeLists.txt
+++ b/core/src/tests/CMakeLists.txt
@@ -150,6 +150,12 @@ if(NOT client-only)
LINK_LIBRARIES bareos dird_objects bareosfind bareossql testing_common
$<$<BOOL:HAVE_PAM>:${PAM_LIBRARIES}> GTest::gtest_main
)
+
+ bareos_add_test(
+ append_test LINK_LIBRARIES bareos stored_objects bareossd bareosfind
+ GTest::gtest_main
+ )
+
bareos_add_test(
berrno_test
LINK_LIBRARIES bareos dird_objects bareosfind bareossql
diff --git a/core/src/tests/append_test.cc b/core/src/tests/append_test.cc
new file mode 100644
index 000000000..cc5879773
--- /dev/null
+++ b/core/src/tests/append_test.cc
@@ -0,0 +1,58 @@
+/*
+ BAREOS® - Backup Archiving REcovery Open Sourced
+
+ Copyright (C) 2022-2022 Bareos GmbH & Co. KG
+
+ This program is Free Software; you can redistribute it and/or
+ modify it under the terms of version three of the GNU Affero General Public
+ License as published by the Free Software Foundation and included
+ in the file LICENSE.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+#if defined(HAVE_MINGW)
+# include "include/bareos.h"
+#endif
+
+#include "gtest/gtest.h"
+
+#include "stored/append.h"
+
+TEST(AppendProcessedFileTest, ProcessedFileIsEmptyOnInitialization)
+{
+ storagedaemon::ProcessedFile file{20};
+ EXPECT_EQ(file.GetFileIndex(), 20);
+ EXPECT_TRUE(file.GetAttributes().empty());
+}
+
+TEST(AppendProcessedFileTest, AddDeviceRecordCopiesDataContentNotPointer)
+{
+ POOLMEM* test_msg = GetPoolMemory(PM_MESSAGE);
+ PmStrcpy(test_msg, "data\0more data");
+
+ storagedaemon::DeviceRecord dr;
+ dr.data_len = 14;
+ dr.data = test_msg;
+
+ int32_t arbitrary_index{1234};
+ storagedaemon::ProcessedFile file{arbitrary_index};
+ file.AddAttribute(&dr);
+
+ EXPECT_FALSE(file.GetAttributes().empty());
+
+ storagedaemon::ProcessedFileData processedfiledata
+ = file.GetAttributes().front();
+
+ EXPECT_NE(processedfiledata.GetData().data, dr.data);
+ EXPECT_EQ(memcmp(processedfiledata.GetData().data, dr.data, dr.data_len), 0);
+
+ FreePoolMemory(test_msg);
+}
diff --git a/docs/manuals/source/include/autogenerated/bareos-sd-config-schema.json b/docs/manuals/source/include/autogenerated/bareos-sd-config-schema.json
index 951532417..c09cb1191 100644
--- a/docs/manuals/source/include/autogenerated/bareos-sd-config-schema.json
+++ b/docs/manuals/source/include/autogenerated/bareos-sd-config-schema.json
@@ -266,6 +266,12 @@
"default_value": "0",
"equals": true
},
+ "CheckpointInterval": {
+ "datatype": "TIME",
+ "code": 0,
+ "default_value": "60",
+ "equals": true
+ },
"MaximumNetworkBufferSize": {
"datatype": "PINT32",
"code": 0,
diff --git a/docs/manuals/source/manually_added_config_directive_descriptions/sd-storage-CheckpointInterval.rst.inc b/docs/manuals/source/manually_added_config_directive_descriptions/sd-storage-CheckpointInterval.rst.inc
new file mode 100644
index 000000000..d5ee828d9
--- /dev/null
+++ b/docs/manuals/source/manually_added_config_directive_descriptions/sd-storage-CheckpointInterval.rst.inc
@@ -0,0 +1 @@
+:index:`\ <single: checkpoint interval>`\ This is the interval at which Backup checkpoints are executed. When a checkpoint is executed, the files that have been successfully stored into the media at that point in time will be updated in the catalog database.
diff --git a/systemtests/environment.in b/systemtests/environment.in
index d8999433c..35412151d 100644
--- a/systemtests/environment.in
+++ b/systemtests/environment.in
@@ -18,6 +18,7 @@ export config_directory_dir_additional_test_config=@config_directory_dir_additio
working=${current_test_directory}/working
working_dir=${working}
+messagesfile=${working_dir}/bareos-dir.conmsg
tmp=${current_test_directory}/tmp
export BackupDirectory="${tmp}/data"
systemtests_s3_use_https=@systemtests_s3_use_https@
diff --git a/systemtests/scripts/functions b/systemtests/scripts/functions
index 5e04144ea..a30475e5b 100644
--- a/systemtests/scripts/functions
+++ b/systemtests/scripts/functions
@@ -1038,7 +1038,7 @@ if [ ! -f ${target_file} ]; then
fi
if ! grep -q "${expected_result}" "${target_file}"; then
- echo "Expected line \"${expected_result}\" was not found in log file \"${target_file}\"." >&2
+ echo "Fail: Expected line \"${expected_result}\" was not found in log file \"${target_file}\"." >&2
estat=1
if [ "${error_message}" != "" ]; then
echo "-->${error_message}" >&2
diff --git a/systemtests/tests/CMakeLists.txt b/systemtests/tests/CMakeLists.txt
index c974f74c9..f33edde3d 100644
--- a/systemtests/tests/CMakeLists.txt
+++ b/systemtests/tests/CMakeLists.txt
@@ -27,6 +27,7 @@ add_subdirectory(bconsole-pam)
add_subdirectory(block-size)
add_subdirectory(bscan-bextract-bls-bcopy)
add_subdirectory(catalog)
+add_subdirectory(checkpoints)
add_subdirectory(chflags)
add_subdirectory(client-initiated)
add_subdirectory(commandline-options)
@@ -49,6 +50,7 @@ add_subdirectory(messages)
add_subdirectory(multiplied-device)
add_subdirectory(ndmp)
add_subdirectory(notls)
+add_subdirectory(parallel-jobs)
add_subdirectory(passive)
add_subdirectory(pruning)
add_subdirectory(py2plug-dir)
@@ -73,6 +75,7 @@ add_subdirectory(scheduler-backup)
add_subdirectory(sparse-file)
add_subdirectory(spool)
add_subdirectory(testfind)
+add_subdirectory(stresstest)
add_subdirectory(truncate-command)
add_subdirectory(upgrade-database)
add_subdirectory(virtualfull)
diff --git a/systemtests/tests/checkpoints/CMakeLists.txt b/systemtests/tests/checkpoints/CMakeLists.txt
new file mode 100644
index 000000000..6cd046c40
--- /dev/null
+++ b/systemtests/tests/checkpoints/CMakeLists.txt
@@ -0,0 +1,30 @@
+# BAREOS® - Backup Archiving REcovery Open Sourced
+#
+# Copyright (C) 2022-2022 Bareos GmbH & Co. KG
+#
+# This program is Free Software; you can redistribute it and/or
+# modify it under the terms of version three of the GNU Affero General Public
+# License as published by the Free Software Foundation and included
+# in the file LICENSE.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301, USA.
+
+get_filename_component(BASENAME ${CMAKE_CURRENT_BINARY_DIR} NAME)
+create_systemtest(${SYSTEMTEST_PREFIX} ${BASENAME})
+
+set_tests_properties(
+ "${SYSTEMTEST_PREFIX}${BASENAME}:checkpoints-on-stop" PROPERTIES RUN_SERIAL
+ TRUE
+)
+set_tests_properties(
+ "${SYSTEMTEST_PREFIX}${BASENAME}:checkpoints-on-kill" PROPERTIES RUN_SERIAL
+ TRUE
+)
diff --git a/systemtests/tests/checkpoints/etc/bareos/bareos-dir.d/catalog/MyCatalog.conf.in b/systemtests/tests/checkpoints/etc/bareos/bareos-dir.d/catalog/MyCatalog.conf.in
new file mode 100644
index 000000000..3c3385945
--- /dev/null
+++ b/systemtests/tests/checkpoints/etc/bareos/bareos-dir.d/catalog/MyCatalog.conf.in
@@ -0,0 +1,6 @@
+Catalog {
+ Name = MyCatalog
+ dbname = "@db_name@"
+ dbuser = "@db_user@"
+ dbpassword = "@db_password@"
+}
diff --git a/systemtests/tests/checkpoints/etc/bareos/bareos-dir.d/client/bareos-fd.conf.in b/systemtests/tests/checkpoints/etc/bareos/bareos-dir.d/client/bareos-fd.conf.in
new file mode 100644
index 000000000..59b617009
--- /dev/null
+++ b/systemtests/tests/checkpoints/etc/bareos/bareos-dir.d/client/bareos-fd.conf.in
@@ -0,0 +1,7 @@
+Client {
+ Name = bareos-fd
+ Description = "Client resource of the Director itself."
+ Address = @hostname@
+ Password = "@fd_password@" # password for FileDaemon
+ FD PORT = @fd_port@
+}
diff --git a/systemtests/tests/checkpoints/etc/bareos/bareos-dir.d/director/bareos-dir.conf.in b/systemtests/tests/checkpoints/etc/bareos/bareos-dir.d/director/bareos-dir.conf.in
new file mode 100644
index 000000000..266d120dd
--- /dev/null
+++ b/systemtests/tests/checkpoints/etc/bareos/bareos-dir.d/director/bareos-dir.conf.in
@@ -0,0 +1,26 @@
+Director { # define myself
+ Name = bareos-dir
+ QueryFile = "@scriptdir@/query.sql"
+ Maximum Concurrent Jobs = 10
+ Password = "@dir_password@" # Console password
+ Messages = Daemon
+ Auditing = yes
+
+ # Enable the Heartbeat if you experience connection losses
+ # (eg. because of your router or firewall configuration).
+ # Additionally the Heartbeat can be enabled in bareos-sd and bareos-fd.
+ #
+ # Heartbeat Interval = 1 min
+
+ # remove comment in next line to load dynamic backends from specified directory
+ Backend Directory = @backenddir@
+
+ # remove comment from "Plugin Directory" to load plugins from specified directory.
+ # if "Plugin Names" is defined, only the specified plugins will be loaded,
+ # otherwise all director plugins (*-dir.so) from the "Plugin Directory".
+ #
+ # Plugin Directory = "@python_plugin_module_src_dir@"
+ # Plugin Names = ""
+ Working Directory = "@working_dir@"
+ DirPort = @dir_port@
+}
diff --git a/systemtests/tests/checkpoints/etc/bareos/bareos-dir.d/fileset/Catalog.conf.in b/systemtests/tests/checkpoints/etc/bareos/bareos-dir.d/fileset/Catalog.conf.in
new file mode 100644
index 000000000..c7cdc433f
--- /dev/null
+++ b/systemtests/tests/checkpoints/etc/bareos/bareos-dir.d/fileset/Catalog.conf.in
@@ -0,0 +1,11 @@
+FileSet {
+ Name = "Catalog"
+ Description = "Backup the catalog dump and Bareos configuration files."
+ Include {
+ Options {
+ signature = MD5
+ }
+ File = "@working_dir@/@db_name@.sql" # database dump
+ File = "@confdir@" # configuration
+ }
+}
diff --git a/systemtests/tests/checkpoints/etc/bareos/bareos-dir.d/fileset/SelfTest.conf.in b/systemtests/tests/checkpoints/etc/bareos/bareos-dir.d/fileset/SelfTest.conf.in
new file mode 100644
index 000000000..be175b9a9
--- /dev/null
+++ b/systemtests/tests/checkpoints/etc/bareos/bareos-dir.d/fileset/SelfTest.conf.in
@@ -0,0 +1,20 @@
+FileSet {
+ Name = "SelfTest"
+ Description = "fileset just to backup some files for selftest"
+ Include {
+ Options {
+ Signature = MD5 # calculate md5 checksum per file
+ fstype = ext2
+ fstype = ext3
+ fstype = ext4
+ fstype = overlay
+ fstype = jfs
+ fstype = ufs
+ fstype = xfs
+ fstype = zfs
+ fstype = btrfs
+ }
+ #File = "@sbindir@"
+ File=<@tmpdir@/file-list
+ }
+}
diff --git a/systemtests/tests/checkpoints/etc/bareos/bareos-dir.d/job/BackupCatalog.conf.in b/systemtests/tests/checkpoints/etc/bareos/bareos-dir.d/job/BackupCatalog.conf.in
new file mode 100644
index 000000000..1da2a7af6
--- /dev/null
+++ b/systemtests/tests/checkpoints/etc/bareos/bareos-dir.d/job/BackupCatalog.conf.in
@@ -0,0 +1,20 @@
+Job {
+ Name = "BackupCatalog"
+ Description = "Backup the catalog database (after the nightly save)"
+ JobDefs = "DefaultJob"
+ Level = Full
+ FileSet="Catalog"
+
+ # This creates an ASCII copy of the catalog
+ # Arguments to make_catalog_backup.pl are:
+ # make_catalog_backup.pl <catalog-name>
+ RunBeforeJob = "@scriptdir@/make_catalog_backup.pl MyCatalog"
+
+ # This deletes the copy of the catalog
+ RunAfterJob = "@scriptdir@/delete_catalog_backup"
+
+ # This sends the bootstrap via mail for disaster recovery.
+ # Should be sent to another system, please change recipient accordingly
+ Write Bootstrap = "|@bindir@/bsmtp -h @smtp_host@ -f \"\(Bareos\) \" -s \"Bootstrap for Job %j\" @job_email@" # (#01)
+ Priority = 11 # run after main backup
+}
diff --git a/systemtests/tests/checkpoints/etc/bareos/bareos-dir.d/job/RestoreFiles.conf.in b/systemtests/tests/checkpoints/etc/bareos/bareos-dir.d/job/RestoreFiles.conf.in
new file mode 100644
index 000000000..89256864d
--- /dev/null
+++ b/systemtests/tests/checkpoints/etc/bareos/bareos-dir.d/job/RestoreFiles.conf.in
@@ -0,0 +1,11 @@
+Job {
+ Name = "RestoreFiles"
+ Description = "Standard Restore template. Only one such job is needed for all standard Jobs/Clients/Storage ..."
+ Type = Restore
+ Client = bareos-fd
+ FileSet = SelfTest
+ Storage = File
+ Pool = Incremental
+ Messages = Standard
+ Where = @tmp@/bareos-restores
+}
diff --git a/systemtests/tests/checkpoints/etc/bareos/bareos-dir.d/job/backup-bareos-fd.conf b/systemtests/tests/checkpoints/etc/bareos/bareos-dir.d/job/backup-bareos-fd.conf
new file mode 100644
index 000000000..ca1891f96
--- /dev/null
+++ b/systemtests/tests/checkpoints/etc/bareos/bareos-dir.d/job/backup-bareos-fd.conf
@@ -0,0 +1,5 @@
+Job {
+ Name = "backup-bareos-fd"
+ JobDefs = "DefaultJob"
+ Client = "bareos-fd"
+}
diff --git a/systemtests/tests/checkpoints/etc/bareos/bareos-dir.d/job/slow-backup-bareos-fd.conf b/systemtests/tests/checkpoints/etc/bareos/bareos-dir.d/job/slow-backup-bareos-fd.conf
new file mode 100644
index 000000000..b9f416f53
--- /dev/null
+++ b/systemtests/tests/checkpoints/etc/bareos/bareos-dir.d/job/slow-backup-bareos-fd.conf
@@ -0,0 +1,12 @@
+Job {
+ Name = "slow-backup-bareos-fd"
+ Type = Backup
+ Level = Full
+ Client = bareos-fd
+ FileSet = "SelfTest"
+ Storage = File
+ Messages = Standard
+ Pool = SmallFull
+ Full Backup Pool = SmallFull
+ Maximum Bandwidth = 10K
+}
diff --git a/systemtests/tests/checkpoints/etc/bareos/bareos-dir.d/jobdefs/DefaultJob.conf.in b/systemtests/tests/checkpoints/etc/bareos/bareos-dir.d/jobdefs/DefaultJob.conf.in
new file mode 100644
index 000000000..563126477
--- /dev/null
+++ b/systemtests/tests/checkpoints/etc/bareos/bareos-dir.d/jobdefs/DefaultJob.conf.in
@@ -0,0 +1,15 @@
+JobDefs {
+ Name = "DefaultJob"
+ Type = Backup
+ Level = Incremental
+ Client = bareos-fd
+ FileSet = "SelfTest"
+ Storage = File
+ Messages = Standard
+ Pool = Incremental
+ Priority = 10
+ Write Bootstrap = "@working_dir@/%c.bsr"
+ Full Backup Pool = Full # write Full Backups into "Full" Pool
+ Differential Backup Pool = Differential # write Diff Backups into "Differential" Pool
+ Incremental Backup Pool = Incremental # write Incr Backups into "Incremental" Pool
+}
diff --git a/systemtests/tests/checkpoints/etc/bareos/bareos-dir.d/messages/Daemon.conf.in b/systemtests/tests/checkpoints/etc/bareos/bareos-dir.d/messages/Daemon.conf.in
new file mode 100644
index 000000000..cf6a8cfa1
--- /dev/null
+++ b/systemtests/tests/checkpoints/etc/bareos/bareos-dir.d/messages/Daemon.conf.in
@@ -0,0 +1,7 @@
+Messages {
+ Name = Daemon
+ Description = "Message delivery for daemon messages (no job)."
+ console = all, !skipped, !saved, !audit
+ append = "@logdir@/bareos.log" = all, !skipped, !audit
+ append = "@logdir@/bareos-audit.log" = audit
+}
diff --git a/systemtests/tests/checkpoints/etc/bareos/bareos-dir.d/messages/Standard.conf.in b/systemtests/tests/checkpoints/etc/bareos/bareos-dir.d/messages/Standard.conf.in
new file mode 100644
index 000000000..b3556ba8c
--- /dev/null
+++ b/systemtests/tests/checkpoints/etc/bareos/bareos-dir.d/messages/Standard.conf.in
@@ -0,0 +1,7 @@
+Messages {
+ Name = Standard
+ Description = "Reasonable message delivery -- send most everything to email address and to the console."
+ console = all, !skipped, !saved, !audit
+ append = "@logdir@/bareos.log" = all, !skipped, !saved, !audit
+ catalog = all, !skipped, !saved, !audit
+}
diff --git a/systemtests/tests/checkpoints/etc/bareos/bareos-dir.d/pool/Differential.conf b/systemtests/tests/checkpoints/etc/bareos/bareos-dir.d/pool/Differential.conf
new file mode 100644
index 000000000..25ce24821
--- /dev/null
+++ b/systemtests/tests/checkpoints/etc/bareos/bareos-dir.d/pool/Differential.conf
@@ -0,0 +1,10 @@
+Pool {
+ Name = Differential
+ Pool Type = Backup
+ Recycle = yes # Bareos can automatically recycle Volumes
+ AutoPrune = yes # Prune expired volumes
+ Volume Retention = 90 days # How long should the Differential Backups be kept? (#09)
+ Maximum Volume Bytes = 10G # Limit Volume size to something reasonable
+ Maximum Volumes = 100 # Limit number of Volumes in Pool
+ Label Format = "Differential-" # Volumes will be labeled "Differential-<volume-id>"
+}
diff --git a/systemtests/tests/checkpoints/etc/bareos/bareos-dir.d/pool/Full.conf b/systemtests/tests/checkpoints/etc/bareos/bareos-dir.d/pool/Full.conf
new file mode 100644
index 000000000..867fc66b4
--- /dev/null
+++ b/systemtests/tests/checkpoints/etc/bareos/bareos-dir.d/pool/Full.conf
@@ -0,0 +1,10 @@
+Pool {
+ Name = Full
+ Pool Type = Backup
+ Recycle = yes # Bareos can automatically recycle Volumes
+ AutoPrune = yes # Prune expired volumes
+ Volume Retention = 365 days # How long should the Full Backups be kept? (#06)
+ Maximum Volume Bytes = 50G # Limit Volume size to something reasonable
+ Maximum Volumes = 100 # Limit number of Volumes in Pool
+ Label Format = "Full-" # Volumes will be labeled "Full-<volume-id>"
+}
diff --git a/systemtests/tests/checkpoints/etc/bareos/bareos-dir.d/pool/FullSmallvolumes.conf b/systemtests/tests/checkpoints/etc/bareos/bareos-dir.d/pool/FullSmallvolumes.conf
new file mode 100644
index 000000000..aa8d7b5eb
--- /dev/null
+++ b/systemtests/tests/checkpoints/etc/bareos/bareos-dir.d/pool/FullSmallvolumes.conf
@@ -0,0 +1,11 @@
+Pool {
+ Name = SmallFull
+ Pool Type = Backup
+ Recycle = yes
+ AutoPrune = yes
+ Volume Retention = 365 days
+ Maximum Volume Bytes = 32k
+ Maximum Volumes = 100
+ Label Format = "SmallFull-"
+ Maximum Block Size = 16K
+}
diff --git a/systemtests/tests/checkpoints/etc/bareos/bareos-dir.d/pool/Incremental.conf b/systemtests/tests/checkpoints/etc/bareos/bareos-dir.d/pool/Incremental.conf
new file mode 100644
index 000000000..f4dbbab66
--- /dev/null
+++ b/systemtests/tests/checkpoints/etc/bareos/bareos-dir.d/pool/Incremental.conf
@@ -0,0 +1,10 @@
+Pool {
+ Name = Incremental
+ Pool Type = Backup
+ Recycle = yes # Bareos can automatically recycle Volumes
+ AutoPrune = yes # Prune expired volumes
+ Volume Retention = 30 days # How long should the Incremental Backups be kept? (#12)
+ Maximum Volume Bytes = 1G # Limit Volume size to something reasonable
+ Maximum Volumes = 100 # Limit number of Volumes in Pool
+ Label Format = "Incremental-" # Volumes will be labeled "Incremental-<volume-id>"
+}
diff --git a/systemtests/tests/checkpoints/etc/bareos/bareos-dir.d/pool/Scratch.conf b/systemtests/tests/checkpoints/etc/bareos/bareos-dir.d/pool/Scratch.conf
new file mode 100644
index 000000000..3a489b198
--- /dev/null
+++ b/systemtests/tests/checkpoints/etc/bareos/bareos-dir.d/pool/Scratch.conf
@@ -0,0 +1,4 @@
+Pool {
+ Name = Scratch
+ Pool Type = Scratch
+}
diff --git a/systemtests/tests/checkpoints/etc/bareos/bareos-dir.d/profile/operator.conf b/systemtests/tests/checkpoints/etc/bareos/bareos-dir.d/profile/operator.conf
new file mode 100644
index 000000000..6edd0166d
--- /dev/null
+++ b/systemtests/tests/checkpoints/etc/bareos/bareos-dir.d/profile/operator.conf
@@ -0,0 +1,18 @@
+Profile {
+ Name = operator
+ Description = "Profile allowing normal Bareos operations."
+
+ Command ACL = !.bvfs_clear_cache, !.exit, !.sql
+ Command ACL = !configure, !create, !delete, !purge, !prune, !sqlquery, !umount, !unmount
+ Command ACL = *all*
+
+ Catalog ACL = *all*
+ Client ACL = *all*
+ FileSet ACL = *all*
+ Job ACL = *all*
+ Plugin Options ACL = *all*
+ Pool ACL = *all*
+ Schedule ACL = *all*
+ Storage ACL = *all*
+ Where ACL = *all*
+}
diff --git a/systemtests/tests/checkpoints/etc/bareos/bareos-dir.d/storage/File.conf.in b/systemtests/tests/checkpoints/etc/bareos/bareos-dir.d/storage/File.conf.in
new file mode 100644
index 000000000..a2622f719
--- /dev/null
+++ b/systemtests/tests/checkpoints/etc/bareos/bareos-dir.d/storage/File.conf.in
@@ -0,0 +1,8 @@
+Storage {
+ Name = File
+ Address = @hostname@
+ Password = "@sd_password@"
+ Device = FileStorage
+ Media Type = File
+ SD Port = @sd_port@
+}
diff --git a/systemtests/tests/checkpoints/etc/bareos/bareos-fd.d/client/myself.conf.in b/systemtests/tests/checkpoints/etc/bareos/bareos-fd.d/client/myself.conf.in
new file mode 100644
index 000000000..6439fe84d
--- /dev/null
+++ b/systemtests/tests/checkpoints/etc/bareos/bareos-fd.d/client/myself.conf.in
@@ -0,0 +1,19 @@
+Client {
+ Name = @basename@-fd
+ Maximum Concurrent Jobs = 20
+
+ # remove comment from "Plugin Directory" to load plugins from specified directory.
+ # if "Plugin Names" is defined, only the specified plugins will be loaded,
+ # otherwise all filedaemon plugins (*-fd.so) from the "Plugin Directory".
+ #
+ # Plugin Directory = "@python_plugin_module_src_fd@"
+ # Plugin Names = ""
+
+ # if compatible is set to yes, we are compatible with bacula
+ # if set to no, new bareos features are enabled which is the default
+ # compatible = yes
+
+ Working Directory = "@working_dir@"
+ FD Port = @fd_port@
+
+}
diff --git a/systemtests/tests/checkpoints/etc/bareos/bareos-fd.d/director/bareos-dir.conf.in b/systemtests/tests/checkpoints/etc/bareos/bareos-fd.d/director/bareos-dir.conf.in
new file mode 100644
index 000000000..c8dc7085a
--- /dev/null
+++ b/systemtests/tests/checkpoints/etc/bareos/bareos-fd.d/director/bareos-dir.conf.in
@@ -0,0 +1,5 @@
+Director {
+ Name = bareos-dir
+ Password = "@fd_password@"
+ Description = "Allow the configured Director to access this file daemon."
+}
diff --git a/systemtests/tests/checkpoints/etc/bareos/bareos-fd.d/messages/Standard.conf b/systemtests/tests/checkpoints/etc/bareos/bareos-fd.d/messages/Standard.conf
new file mode 100644
index 000000000..97788e005
--- /dev/null
+++ b/systemtests/tests/checkpoints/etc/bareos/bareos-fd.d/messages/Standard.conf
@@ -0,0 +1,5 @@
+Messages {
+ Name = Standard
+ Director = bareos-dir = all, !skipped, !restored
+ Description = "Send relevant messages to the Director."
+}
diff --git a/systemtests/tests/checkpoints/etc/bareos/bareos-sd.d/device/FileStorage.conf b/systemtests/tests/checkpoints/etc/bareos/bareos-sd.d/device/FileStorage.conf
new file mode 100644
index 000000000..11a639bc6
--- /dev/null
+++ b/systemtests/tests/checkpoints/etc/bareos/bareos-sd.d/device/FileStorage.conf
@@ -0,0 +1,11 @@
+Device {
+ Name = FileStorage
+ Media Type = File
+ Archive Device = storage
+ LabelMedia = yes; # lets Bareos label unlabeled media
+ Random Access = yes;
+ AutomaticMount = yes; # when device opened, read it
+ RemovableMedia = no;
+ AlwaysOpen = no;
+ Description = "File device. A connecting Director must have the same Name and MediaType."
+}
diff --git a/systemtests/tests/checkpoints/etc/bareos/bareos-sd.d/director/bareos-dir.conf.in b/systemtests/tests/checkpoints/etc/bareos/bareos-sd.d/director/bareos-dir.conf.in
new file mode 100644
index 000000000..deef3360c
--- /dev/null
+++ b/systemtests/tests/checkpoints/etc/bareos/bareos-sd.d/director/bareos-dir.conf.in
@@ -0,0 +1,5 @@
+Director {
+ Name = bareos-dir
+ Password = "@sd_password@"
+ Description = "Director, who is permitted to contact this storage daemon."
+}
diff --git a/systemtests/tests/checkpoints/etc/bareos/bareos-sd.d/messages/Standard.conf b/systemtests/tests/checkpoints/etc/bareos/bareos-sd.d/messages/Standard.conf
new file mode 100644
index 000000000..468348e62
--- /dev/null
+++ b/systemtests/tests/checkpoints/etc/bareos/bareos-sd.d/messages/Standard.conf
@@ -0,0 +1,5 @@
+Messages {
+ Name = Standard
+ Director = bareos-dir = all
+ Description = "Send all messages to the Director."
+}
diff --git a/systemtests/tests/checkpoints/etc/bareos/bareos-sd.d/storage/bareos-sd.conf.in b/systemtests/tests/checkpoints/etc/bareos/bareos-sd.d/storage/bareos-sd.conf.in
new file mode 100644
index 000000000..92656f4b5
--- /dev/null
+++ b/systemtests/tests/checkpoints/etc/bareos/bareos-sd.d/storage/bareos-sd.conf.in
@@ -0,0 +1,12 @@
+Storage {
+ Name = bareos-sd
+ Maximum Concurrent Jobs = 20
+
+ # remove comment from "Plugin Directory" to load plugins from specified directory.
+ # if "Plugin Names" is defined, only the specified plugins will be loaded,
+ # otherwise all storage plugins (*-sd.so) from the "Plugin Directory".
+ #
+ Working Directory = "@working_dir@"
+ SD Port = @sd_port@
+ Checkpoint Interval = 3
+}
diff --git a/systemtests/tests/checkpoints/etc/bareos/bconsole.conf.in b/systemtests/tests/checkpoints/etc/bareos/bconsole.conf.in
new file mode 100644
index 000000000..50b647c1d
--- /dev/null
+++ b/systemtests/tests/checkpoints/etc/bareos/bconsole.conf.in
@@ -0,0 +1,10 @@
+#
+# Bareos User Agent (or Console) Configuration File
+#
+
+Director {
+ Name = @basename@-dir
+ DIRport = @dir_port@
+ Address = @hostname@
+ Password = "@dir_password@"
+}
diff --git a/systemtests/tests/checkpoints/test-setup b/systemtests/tests/checkpoints/test-setup
new file mode 100755
index 000000000..9f7be3d9b
--- /dev/null
+++ b/systemtests/tests/checkpoints/test-setup
@@ -0,0 +1,40 @@
+#!/bin/bash
+
+# BAREOS® - Backup Archiving REcovery Open Sourced
+#
+# Copyright (C) 2022-2022 Bareos GmbH & Co. KG
+#
+# This program is Free Software; you can redistribute it and/or
+# modify it under the terms of version three of the GNU Affero General Public
+# License as published by the Free Software Foundation and included
+# in the file LICENSE.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301, USA.
+
+set -e
+set -o pipefail
+set -u
+
+#shellcheck source=../environment.in
+. ./environment
+
+#shellcheck source=../scripts/functions
+. "${rscripts}"/functions
+"${rscripts}"/cleanup
+"${rscripts}"/setup
+
+# Fill ${BackupDirectory} with data.
+setup_data
+
+bin/bareos start
+
+# make sure, director is up and running.
+print_debug "$(bin/bconsole <<< "status dir")"
diff --git a/systemtests/tests/checkpoints/testrunner-checkpoints-on-cancel b/systemtests/tests/checkpoints/testrunner-checkpoints-on-cancel
new file mode 100755
index 000000000..433337c5c
--- /dev/null
+++ b/systemtests/tests/checkpoints/testrunner-checkpoints-on-cancel
@@ -0,0 +1,111 @@
+#!/bin/bash
+set -o pipefail
+set -u
+#
+# Cancel a backup and check that metadata is still saved with checkpoints.
+#
+TestName="$(basename "$(pwd)")"
+export TestName
+
+#shellcheck source=../environment.in
+. ./environment
+
+#shellcheck source=../scripts/functions
+. "${rscripts}"/functions
+
+start_test
+
+backup_log=$tmp/cancel-backup-checkpoints.out
+restore_log=$tmp/cancel-restore-checkpoints.out
+
+rm -f $backup_log
+rm -f $restore_log
+
+slowjob="slow-backup-bareos-fd"
+
+cat <<END_OF_DATA >"$tmp/bconcmds"
+@$out /dev/null
+messages
+@$out $backup_log
+run job=$slowjob level=Full yes
+quit
+END_OF_DATA
+
+run_bconsole
+
+timeout=0
+timed_checkpoint=""
+volume_checkpoint=""
+
+while [[ ${timeout} -lt 30 ]] && [[ -z $timed_checkpoint || -z $volume_checkpoint ]]
+do
+ timed_checkpoint=$(grep -m 1 'Doing timed backup checkpoint. Next checkpoint in 3 seconds' $messagesfile)
+ volume_checkpoint=$(grep -m 1 'Volume changed, doing checkpoint:' "$messagesfile")
+ sleep 1
+ ((++timeout))
+done
+
+# Check that a timed checkpoint was triggered
+if [[ -z $timed_checkpoint ]]; then
+ echo "Timed checkpoint was not triggered!"
+ estat=1;
+fi
+
+# Check that a checkpoint happened on a volume change
+if [[ -z $volume_checkpoint ]]; then
+ echo "Checkpoint was not triggered on volume changes!"
+ estat=2;
+fi
+
+
+slowjobid=$(grep 'Job queued. JobId=' $backup_log | sed -n -e 's/^.*JobId=//p')
+
+cat <<END_OF_DATA >"$tmp/bconcmds"
+@$out $backup_log
+cancel jobid=${slowjobid}
+wait
+messages
+@$out $restore_log
+restore jobid=${slowjobid} where=$tmp/checkpoint-restores all done yes
+wait
+messages
+quit
+END_OF_DATA
+
+run_bconsole
+
+# Check that a cancel was triggered
+expect_grep "Termination: Backup Canceled" \
+ "$backup_log" \
+ "Job was not canceled!"
+
+NumberOfBackedUpFiles=$(grep 'FD Files Written: ' $backup_log | sed -n -e 's/^.*Written: //p')
+
+# Check that part of the files were written despite the cancel
+if [ $NumberOfBackedUpFiles -le 0 ]; then
+ echo "Checkpoint files were not correctly saved! Number of backed up files: ${NumberOfBackedUpFiles}" >&2
+ estat=1
+fi
+
+# Check that the restore of a canceled backup works fine
+expect_grep "Termination: Restore OK" \
+ "$restore_log" \
+ "Restore is not OK."
+
+expect_grep "Files Restored: ${NumberOfBackedUpFiles}" \
+ "$restore_log" \
+ "Restore of canceled job did not go well!"
+
+# Certain systems do not support multiple types for find (-type f,l)
+NumberOfFilesRestored=$(find $tmp/checkpoint-restores/$tmp -type f | wc -l)
+NumberOfLinksRestored=$(find $tmp/checkpoint-restores/$tmp -type l | wc -l)
+NumberOfDirectoriesRestored=$(find $tmp/checkpoint-restores/$tmp -type d | wc -l)
+RestoredItems=$(expr $NumberOfFilesRestored + $NumberOfLinksRestored + $NumberOfDirectoriesRestored)
+
+# Check that the restored files are actually there
+if [ ${RestoredItems} -lt ${NumberOfBackedUpFiles} ]; then
+ echo "Actual restored items count not met. Items (files, links, directories) found = ${RestoredItems} , backed up files = ${NumberOfBackedUpFiles}" >&2
+ estat=1
+fi
+
+end_test
diff --git a/systemtests/tests/checkpoints/testrunner-checkpoints-on-kill b/systemtests/tests/checkpoints/testrunner-checkpoints-on-kill
new file mode 100755
index 000000000..61716f050
--- /dev/null
+++ b/systemtests/tests/checkpoints/testrunner-checkpoints-on-kill
@@ -0,0 +1,109 @@
+#!/bin/bash
+set -o pipefail
+set -u
+#
+# Kill the daemons while running a backup.
+# Check that metadata is still saved with checkpoints.
+#
+TestName="$(basename "$(pwd)")"
+export TestName
+
+#shellcheck source=../environment.in
+. ./environment
+
+#shellcheck source=../scripts/functions
+. "${rscripts}"/functions
+
+start_test
+
+slowjob="slow-backup-bareos-fd"
+
+for daemon in "dir" "sd" "fd"; do
+
+ backup_log=$tmp/kill-$daemon-backup-checkpoints.out
+ restore_log=$tmp/kill-$daemon-restore-checkpoints.out
+ restore_directory=$tmp/kill-$daemon-checkpoint-restores
+
+ rm -f $backup_log
+ rm -f $restore_log
+
+cat <<END_OF_DATA >"$tmp/bconcmds"
+@$out /dev/null
+messages
+@$out $backup_log
+run job=$slowjob level=Full yes
+quit
+END_OF_DATA
+
+ run_bconsole
+
+ timeout=0
+ timed_checkpoint=""
+ volume_checkpoint=""
+
+ while [[ ${timeout} -lt 30 ]] && [[ -z $timed_checkpoint || -z $volume_checkpoint ]]
+ do
+
+ timed_checkpoint=$(grep -m 1 'Doing timed backup checkpoint. Next checkpoint in 3 seconds' "$messagesfile")
+ volume_checkpoint=$(grep -m 1 'Volume changed, doing checkpoint:' "$messagesfile")
+ sleep 1
+ ((++timeout))
+ done #end while
+
+ # Check that a timed checkpoint was triggered
+ if [[ -z $timed_checkpoint ]]; then
+ echo "ERROR: Timed checkpoint was not triggered!"
+ estat=1;
+ fi
+
+ # Check that a checkpoint happened on a volume change
+ if [[ -z $volume_checkpoint ]]; then
+ echo "ERROR: Checkpoint was not triggered on volume changes!"
+ estat=2;
+ fi
+
+ slowjobid=$(grep 'Job queued. JobId=' "$backup_log" | sed -n -e 's/^.*JobId=//p')
+
+ if [ "$daemon" == "fd" ]; then
+ echo "Killing the FD"
+ pkill -KILL -f "${BAREOS_FILEDAEMON_BINARY}"
+ sleep 3
+ ${rscripts}/bareos-ctl-fd start
+ fi
+
+ if [ "$daemon" == "sd" ]; then
+
+ echo "Killing the SD"
+ pkill -KILL -f "${BAREOS_STORAGEDAEMON_BINARY}"
+ sleep 3
+ ${rscripts}/bareos-ctl-sd start
+ fi
+ if [ "$daemon" == "dir" ]; then
+
+ echo "Killing the DIR"
+ pkill -KILL -f "${BAREOS_DIRECTOR_BINARY}"
+ sleep 3
+ ${rscripts}/bareos-ctl-dir start
+ fi
+
+cat <<END_OF_DATA >"$tmp/bconcmds"
+@$out $backup_log
+wait
+messages
+@$out $restore_log
+restore jobid=${slowjobid} where=$restore_directory all done yes
+wait
+messages
+quit
+END_OF_DATA
+
+ run_bconsole
+
+ # Check that the restore works fine
+ expect_grep "Termination: Restore OK" \
+ "$restore_log" \
+ "Restore job did not go well!"
+
+done #end for
+
+end_test
diff --git a/systemtests/tests/checkpoints/testrunner-checkpoints-on-stop b/systemtests/tests/checkpoints/testrunner-checkpoints-on-stop
new file mode 100755
index 000000000..68bc0a1bf
--- /dev/null
+++ b/systemtests/tests/checkpoints/testrunner-checkpoints-on-stop
@@ -0,0 +1,104 @@
+#!/bin/bash
+set -o pipefail
+set -u
+#
+# Stop the daemons while running backup.
+# Check that metadata is still saved with checkpoints.
+#
+TestName="$(basename "$(pwd)")"
+export TestName
+
+#shellcheck source=../environment.in
+. ./environment
+
+#shellcheck source=../scripts/functions
+. "${rscripts}"/functions
+
+start_test
+
+backup_log=$tmp/stop-backup-checkpoints.out
+restore_log=$tmp/stop-restore-checkpoints.out
+
+rm -f $backup_log
+rm -f $restore_log
+
+slowjob="slow-backup-bareos-fd"
+
+cat <<END_OF_DATA >"$tmp/bconcmds"
+@$out /dev/null
+messages
+@$out $backup_log
+run job=$slowjob level=Full yes
+quit
+END_OF_DATA
+
+run_bconsole
+
+timeout=0
+timed_checkpoint=""
+volume_checkpoint=""
+
+while [[ ${timeout} -lt 30 ]] && [[ -z $timed_checkpoint || -z $volume_checkpoint ]]
+do
+ timed_checkpoint=$(grep -m 1 'Doing timed backup checkpoint. Next checkpoint in 3 seconds' "$messagesfile")
+ volume_checkpoint=$(grep -m 1 'Volume changed, doing checkpoint:' "$messagesfile")
+ sleep 1
+ ((++timeout))
+done
+
+# Check that a timed checkpoint was triggered
+if [[ -z $timed_checkpoint ]]; then
+ echo "Timed checkpoint was not triggered!"
+ estat=1;
+fi
+
+# Check that a checkpoint happened on a volume change
+if [[ -z $volume_checkpoint ]]; then
+ echo "Checkpoint was not triggered on volume changes!"
+ estat=2;
+fi
+
+slowjobid=$(grep 'Job queued. JobId=' $backup_log | sed -n -e 's/^.*JobId=//p')
+
+bin/bareos stop
+
+bin/bareos start
+
+cat <<END_OF_DATA >"$tmp/bconcmds"
+@$out $backup_log
+messages
+@$out $restore_log
+restore jobid=${slowjobid} where=$tmp/checkpoint-restores all done yes
+wait
+messages
+quit
+END_OF_DATA
+
+run_bconsole
+
+NumberOfBackedUpFiles=$(grep 'FD Files Written: ' $backup_log | sed -n -e 's/^.*Written: //p')
+
+# Check that part of the files were written despite the stop
+if [ $NumberOfBackedUpFiles -le 0 ]; then
+ echo "Checkpoint files were not correctly saved! Number of backed up files: ${NumberOfBackedUpFiles}" >&2
+ estat=1
+fi
+
+# Check that the restore works fine
+expect_grep "Termination: Restore OK" \
+ "$restore_log"\
+ "Restore job did not go well!"
+
+# Certain systems do not support multiple types for find (-type f,l)
+NumberOfFilesRestored=$(find $tmp/checkpoint-restores/$tmp -type f | wc -l)
+NumberOfLinksRestored=$(find $tmp/checkpoint-restores/$tmp -type l | wc -l)
+NumberOfDirectoriesRestored=$(find $tmp/checkpoint-restores/$tmp -type d | wc -l)
+RestoredItems=$(expr $NumberOfFilesRestored + $NumberOfLinksRestored + $NumberOfDirectoriesRestored)
+
+# Check that the restored files are actually there
+if [ ${RestoredItems} -lt ${NumberOfBackedUpFiles} ]; then
+ echo "Actual restored items count not met. Items (files, links, directories) found = ${RestoredItems} , backed up files = ${NumberOfBackedUpFiles}" >&2
+ estat=1
+fi
+
+end_test
diff --git a/systemtests/tests/parallel-jobs/CMakeLists.txt b/systemtests/tests/parallel-jobs/CMakeLists.txt
new file mode 100644
index 000000000..47970a2ef
--- /dev/null
+++ b/systemtests/tests/parallel-jobs/CMakeLists.txt
@@ -0,0 +1,21 @@
+# BAREOS® - Backup Archiving REcovery Open Sourced
+#
+# Copyright (C) 2022-2022 Bareos GmbH & Co. KG
+#
+# This program is Free Software; you can redistribute it and/or
+# modify it under the terms of version three of the GNU Affero General Public
+# License as published by the Free Software Foundation and included
+# in the file LICENSE.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301, USA.
+
+get_filename_component(BASENAME ${CMAKE_CURRENT_BINARY_DIR} NAME)
+create_systemtest(${SYSTEMTEST_PREFIX} ${BASENAME})
diff --git a/systemtests/tests/parallel-jobs/etc/bareos/bareos-dir.d/catalog/MyCatalog.conf.in b/systemtests/tests/parallel-jobs/etc/bareos/bareos-dir.d/catalog/MyCatalog.conf.in
new file mode 100644
index 000000000..3c3385945
--- /dev/null
+++ b/systemtests/tests/parallel-jobs/etc/bareos/bareos-dir.d/catalog/MyCatalog.conf.in
@@ -0,0 +1,6 @@
+Catalog {
+ Name = MyCatalog
+ dbname = "@db_name@"
+ dbuser = "@db_user@"
+ dbpassword = "@db_password@"
+}
diff --git a/systemtests/tests/parallel-jobs/etc/bareos/bareos-dir.d/client/bareos-fd.conf.in b/systemtests/tests/parallel-jobs/etc/bareos/bareos-dir.d/client/bareos-fd.conf.in
new file mode 100644
index 000000000..9341d2557
--- /dev/null
+++ b/systemtests/tests/parallel-jobs/etc/bareos/bareos-dir.d/client/bareos-fd.conf.in
@@ -0,0 +1,8 @@
+Client {
+ Name = bareos-fd
+ Description = "Client resource of the Director itself."
+ Address = @hostname@
+ Password = "@fd_password@" # password for FileDaemon
+ FD PORT = @fd_port@
+ MaximumConcurrentJobs = 10
+}
diff --git a/systemtests/tests/parallel-jobs/etc/bareos/bareos-dir.d/director/bareos-dir.conf.in b/systemtests/tests/parallel-jobs/etc/bareos/bareos-dir.d/director/bareos-dir.conf.in
new file mode 100644
index 000000000..266d120dd
--- /dev/null
+++ b/systemtests/tests/parallel-jobs/etc/bareos/bareos-dir.d/director/bareos-dir.conf.in
@@ -0,0 +1,26 @@
+Director { # define myself
+ Name = bareos-dir
+ QueryFile = "@scriptdir@/query.sql"
+ Maximum Concurrent Jobs = 10
+ Password = "@dir_password@" # Console password
+ Messages = Daemon
+ Auditing = yes
+
+ # Enable the Heartbeat if you experience connection losses
+ # (eg. because of your router or firewall configuration).
+ # Additionally the Heartbeat can be enabled in bareos-sd and bareos-fd.
+ #
+ # Heartbeat Interval = 1 min
+
+ # remove comment in next line to load dynamic backends from specified directory
+ Backend Directory = @backenddir@
+
+ # remove comment from "Plugin Directory" to load plugins from specified directory.
+ # if "Plugin Names" is defined, only the specified plugins will be loaded,
+ # otherwise all director plugins (*-dir.so) from the "Plugin Directory".
+ #
+ # Plugin Directory = "@python_plugin_module_src_dir@"
+ # Plugin Names = ""
+ Working Directory = "@working_dir@"
+ DirPort = @dir_port@
+}
diff --git a/systemtests/tests/parallel-jobs/etc/bareos/bareos-dir.d/fileset/Catalog.conf.in b/systemtests/tests/parallel-jobs/etc/bareos/bareos-dir.d/fileset/Catalog.conf.in
new file mode 100644
index 000000000..c7cdc433f
--- /dev/null
+++ b/systemtests/tests/parallel-jobs/etc/bareos/bareos-dir.d/fileset/Catalog.conf.in
@@ -0,0 +1,11 @@
+FileSet {
+ Name = "Catalog"
+ Description = "Backup the catalog dump and Bareos configuration files."
+ Include {
+ Options {
+ signature = MD5
+ }
+ File = "@working_dir@/@db_name@.sql" # database dump
+ File = "@confdir@" # configuration
+ }
+}
diff --git a/systemtests/tests/parallel-jobs/etc/bareos/bareos-dir.d/fileset/SelfTest.conf.in b/systemtests/tests/parallel-jobs/etc/bareos/bareos-dir.d/fileset/SelfTest.conf.in
new file mode 100644
index 000000000..be175b9a9
--- /dev/null
+++ b/systemtests/tests/parallel-jobs/etc/bareos/bareos-dir.d/fileset/SelfTest.conf.in
@@ -0,0 +1,20 @@
+FileSet {
+ Name = "SelfTest"
+ Description = "fileset just to backup some files for selftest"
+ Include {
+ Options {
+ Signature = MD5 # calculate md5 checksum per file
+ fstype = ext2
+ fstype = ext3
+ fstype = ext4
+ fstype = overlay
+ fstype = jfs
+ fstype = ufs
+ fstype = xfs
+ fstype = zfs
+ fstype = btrfs
+ }
+ #File = "@sbindir@"
+ File=<@tmpdir@/file-list
+ }
+}
diff --git a/systemtests/tests/parallel-jobs/etc/bareos/bareos-dir.d/job/BackupCatalog.conf.in b/systemtests/tests/parallel-jobs/etc/bareos/bareos-dir.d/job/BackupCatalog.conf.in
new file mode 100644
index 000000000..1da2a7af6
--- /dev/null
+++ b/systemtests/tests/parallel-jobs/etc/bareos/bareos-dir.d/job/BackupCatalog.conf.in
@@ -0,0 +1,20 @@
+Job {
+ Name = "BackupCatalog"
+ Description = "Backup the catalog database (after the nightly save)"
+ JobDefs = "DefaultJob"
+ Level = Full
+ FileSet="Catalog"
+
+ # This creates an ASCII copy of the catalog
+ # Arguments to make_catalog_backup.pl are:
+ # make_catalog_backup.pl <catalog-name>
+ RunBeforeJob = "@scriptdir@/make_catalog_backup.pl MyCatalog"
+
+ # This deletes the copy of the catalog
+ RunAfterJob = "@scriptdir@/delete_catalog_backup"
+
+ # This sends the bootstrap via mail for disaster recovery.
+ # Should be sent to another system, please change recipient accordingly
+ Write Bootstrap = "|@bindir@/bsmtp -h @smtp_host@ -f \"\(Bareos\) \" -s \"Bootstrap for Job %j\" @job_email@" # (#01)
+ Priority = 11 # run after main backup
+}
diff --git a/systemtests/tests/parallel-jobs/etc/bareos/bareos-dir.d/job/RestoreFiles.conf.in b/systemtests/tests/parallel-jobs/etc/bareos/bareos-dir.d/job/RestoreFiles.conf.in
new file mode 100644
index 000000000..89256864d
--- /dev/null
+++ b/systemtests/tests/parallel-jobs/etc/bareos/bareos-dir.d/job/RestoreFiles.conf.in
@@ -0,0 +1,11 @@
+Job {
+ Name = "RestoreFiles"
+ Description = "Standard Restore template. Only one such job is needed for all standard Jobs/Clients/Storage ..."
+ Type = Restore
+ Client = bareos-fd
+ FileSet = SelfTest
+ Storage = File
+ Pool = Incremental
+ Messages = Standard
+ Where = @tmp@/bareos-restores
+}
diff --git a/systemtests/tests/parallel-jobs/etc/bareos/bareos-dir.d/job/backup-bareos-fd.conf b/systemtests/tests/parallel-jobs/etc/bareos/bareos-dir.d/job/backup-bareos-fd.conf
new file mode 100644
index 000000000..6ad780134
--- /dev/null
+++ b/systemtests/tests/parallel-jobs/etc/bareos/bareos-dir.d/job/backup-bareos-fd.conf
@@ -0,0 +1,6 @@
+Job {
+ Name = "backup-bareos-fd"
+ JobDefs = "DefaultJob"
+ Client = "bareos-fd"
+ MaximumConcurrentJobs = 10
+}
diff --git a/systemtests/tests/parallel-jobs/etc/bareos/bareos-dir.d/jobdefs/DefaultJob.conf.in b/systemtests/tests/parallel-jobs/etc/bareos/bareos-dir.d/jobdefs/DefaultJob.conf.in
new file mode 100644
index 000000000..563126477
--- /dev/null
+++ b/systemtests/tests/parallel-jobs/etc/bareos/bareos-dir.d/jobdefs/DefaultJob.conf.in
@@ -0,0 +1,15 @@
+JobDefs {
+ Name = "DefaultJob"
+ Type = Backup
+ Level = Incremental
+ Client = bareos-fd
+ FileSet = "SelfTest"
+ Storage = File
+ Messages = Standard
+ Pool = Incremental
+ Priority = 10
+ Write Bootstrap = "@working_dir@/%c.bsr"
+ Full Backup Pool = Full # write Full Backups into "Full" Pool
+ Differential Backup Pool = Differential # write Diff Backups into "Differential" Pool
+ Incremental Backup Pool = Incremental # write Incr Backups into "Incremental" Pool
+}
diff --git a/systemtests/tests/parallel-jobs/etc/bareos/bareos-dir.d/messages/Daemon.conf.in b/systemtests/tests/parallel-jobs/etc/bareos/bareos-dir.d/messages/Daemon.conf.in
new file mode 100644
index 000000000..cf6a8cfa1
--- /dev/null
+++ b/systemtests/tests/parallel-jobs/etc/bareos/bareos-dir.d/messages/Daemon.conf.in
@@ -0,0 +1,7 @@
+Messages {
+ Name = Daemon
+ Description = "Message delivery for daemon messages (no job)."
+ console = all, !skipped, !saved, !audit
+ append = "@logdir@/bareos.log" = all, !skipped, !audit
+ append = "@logdir@/bareos-audit.log" = audit
+}
diff --git a/systemtests/tests/parallel-jobs/etc/bareos/bareos-dir.d/messages/Standard.conf.in b/systemtests/tests/parallel-jobs/etc/bareos/bareos-dir.d/messages/Standard.conf.in
new file mode 100644
index 000000000..b3556ba8c
--- /dev/null
+++ b/systemtests/tests/parallel-jobs/etc/bareos/bareos-dir.d/messages/Standard.conf.in
@@ -0,0 +1,7 @@
+Messages {
+ Name = Standard
+ Description = "Reasonable message delivery -- send most everything to email address and to the console."
+ console = all, !skipped, !saved, !audit
+ append = "@logdir@/bareos.log" = all, !skipped, !saved, !audit
+ catalog = all, !skipped, !saved, !audit
+}
diff --git a/systemtests/tests/parallel-jobs/etc/bareos/bareos-dir.d/pool/Differential.conf b/systemtests/tests/parallel-jobs/etc/bareos/bareos-dir.d/pool/Differential.conf
new file mode 100644
index 000000000..25ce24821
--- /dev/null
+++ b/systemtests/tests/parallel-jobs/etc/bareos/bareos-dir.d/pool/Differential.conf
@@ -0,0 +1,10 @@
+Pool {
+ Name = Differential
+ Pool Type = Backup
+ Recycle = yes # Bareos can automatically recycle Volumes
+ AutoPrune = yes # Prune expired volumes
+ Volume Retention = 90 days # How long should the Differential Backups be kept? (#09)
+ Maximum Volume Bytes = 10G # Limit Volume size to something reasonable
+ Maximum Volumes = 100 # Limit number of Volumes in Pool
+ Label Format = "Differential-" # Volumes will be labeled "Differential-<volume-id>"
+}
diff --git a/systemtests/tests/parallel-jobs/etc/bareos/bareos-dir.d/pool/Full.conf b/systemtests/tests/parallel-jobs/etc/bareos/bareos-dir.d/pool/Full.conf
new file mode 100644
index 000000000..867fc66b4
--- /dev/null
+++ b/systemtests/tests/parallel-jobs/etc/bareos/bareos-dir.d/pool/Full.conf
@@ -0,0 +1,10 @@
+Pool {
+ Name = Full
+ Pool Type = Backup
+ Recycle = yes # Bareos can automatically recycle Volumes
+ AutoPrune = yes # Prune expired volumes
+ Volume Retention = 365 days # How long should the Full Backups be kept? (#06)
+ Maximum Volume Bytes = 50G # Limit Volume size to something reasonable
+ Maximum Volumes = 100 # Limit number of Volumes in Pool
+ Label Format = "Full-" # Volumes will be labeled "Full-<volume-id>"
+}
diff --git a/systemtests/tests/parallel-jobs/etc/bareos/bareos-dir.d/pool/FullSmallvolumes.conf b/systemtests/tests/parallel-jobs/etc/bareos/bareos-dir.d/pool/FullSmallvolumes.conf
new file mode 100644
index 000000000..aa8d7b5eb
--- /dev/null
+++ b/systemtests/tests/parallel-jobs/etc/bareos/bareos-dir.d/pool/FullSmallvolumes.conf
@@ -0,0 +1,11 @@
+Pool {
+ Name = SmallFull
+ Pool Type = Backup
+ Recycle = yes
+ AutoPrune = yes
+ Volume Retention = 365 days
+ Maximum Volume Bytes = 32k
+ Maximum Volumes = 100
+ Label Format = "SmallFull-"
+ Maximum Block Size = 16K
+}
diff --git a/systemtests/tests/parallel-jobs/etc/bareos/bareos-dir.d/pool/Incremental.conf b/systemtests/tests/parallel-jobs/etc/bareos/bareos-dir.d/pool/Incremental.conf
new file mode 100644
index 000000000..f4dbbab66
--- /dev/null
+++ b/systemtests/tests/parallel-jobs/etc/bareos/bareos-dir.d/pool/Incremental.conf
@@ -0,0 +1,10 @@
+Pool {
+ Name = Incremental
+ Pool Type = Backup
+ Recycle = yes # Bareos can automatically recycle Volumes
+ AutoPrune = yes # Prune expired volumes
+ Volume Retention = 30 days # How long should the Incremental Backups be kept? (#12)
+ Maximum Volume Bytes = 1G # Limit Volume size to something reasonable
+ Maximum Volumes = 100 # Limit number of Volumes in Pool
+ Label Format = "Incremental-" # Volumes will be labeled "Incremental-<volume-id>"
+}
diff --git a/systemtests/tests/parallel-jobs/etc/bareos/bareos-dir.d/pool/Scratch.conf b/systemtests/tests/parallel-jobs/etc/bareos/bareos-dir.d/pool/Scratch.conf
new file mode 100644
index 000000000..3a489b198
--- /dev/null
+++ b/systemtests/tests/parallel-jobs/etc/bareos/bareos-dir.d/pool/Scratch.conf
@@ -0,0 +1,4 @@
+Pool {
+ Name = Scratch
+ Pool Type = Scratch
+}
diff --git a/systemtests/tests/parallel-jobs/etc/bareos/bareos-dir.d/profile/operator.conf b/systemtests/tests/parallel-jobs/etc/bareos/bareos-dir.d/profile/operator.conf
new file mode 100644
index 000000000..6edd0166d
--- /dev/null
+++ b/systemtests/tests/parallel-jobs/etc/bareos/bareos-dir.d/profile/operator.conf
@@ -0,0 +1,18 @@
+Profile {
+ Name = operator
+ Description = "Profile allowing normal Bareos operations."
+
+ Command ACL = !.bvfs_clear_cache, !.exit, !.sql
+ Command ACL = !configure, !create, !delete, !purge, !prune, !sqlquery, !umount, !unmount
+ Command ACL = *all*
+
+ Catalog ACL = *all*
+ Client ACL = *all*
+ FileSet ACL = *all*
+ Job ACL = *all*
+ Plugin Options ACL = *all*
+ Pool ACL = *all*
+ Schedule ACL = *all*
+ Storage ACL = *all*
+ Where ACL = *all*
+}
diff --git a/systemtests/tests/parallel-jobs/etc/bareos/bareos-dir.d/storage/File.conf.in b/systemtests/tests/parallel-jobs/etc/bareos/bareos-dir.d/storage/File.conf.in
new file mode 100644
index 000000000..6d914ac0b
--- /dev/null
+++ b/systemtests/tests/parallel-jobs/etc/bareos/bareos-dir.d/storage/File.conf.in
@@ -0,0 +1,9 @@
+Storage {
+ Name = File
+ Address = @hostname@
+ Password = "@sd_password@"
+ Device = FileStorage
+ Media Type = File
+ SD Port = @sd_port@
+ MaximumConcurrentJobs = 10
+}
diff --git a/systemtests/tests/parallel-jobs/etc/bareos/bareos-fd.d/client/myself.conf.in b/systemtests/tests/parallel-jobs/etc/bareos/bareos-fd.d/client/myself.conf.in
new file mode 100644
index 000000000..6439fe84d
--- /dev/null
+++ b/systemtests/tests/parallel-jobs/etc/bareos/bareos-fd.d/client/myself.conf.in
@@ -0,0 +1,19 @@
+Client {
+ Name = @basename@-fd
+ Maximum Concurrent Jobs = 20
+
+ # remove comment from "Plugin Directory" to load plugins from specified directory.
+ # if "Plugin Names" is defined, only the specified plugins will be loaded,
+ # otherwise all filedaemon plugins (*-fd.so) from the "Plugin Directory".
+ #
+ # Plugin Directory = "@python_plugin_module_src_fd@"
+ # Plugin Names = ""
+
+ # if compatible is set to yes, we are compatible with bacula
+ # if set to no, new bareos features are enabled which is the default
+ # compatible = yes
+
+ Working Directory = "@working_dir@"
+ FD Port = @fd_port@
+
+}
diff --git a/systemtests/tests/parallel-jobs/etc/bareos/bareos-fd.d/director/bareos-dir.conf.in b/systemtests/tests/parallel-jobs/etc/bareos/bareos-fd.d/director/bareos-dir.conf.in
new file mode 100644
index 000000000..c8dc7085a
--- /dev/null
+++ b/systemtests/tests/parallel-jobs/etc/bareos/bareos-fd.d/director/bareos-dir.conf.in
@@ -0,0 +1,5 @@
+Director {
+ Name = bareos-dir
+ Password = "@fd_password@"
+ Description = "Allow the configured Director to access this file daemon."
+}
diff --git a/systemtests/tests/parallel-jobs/etc/bareos/bareos-fd.d/messages/Standard.conf b/systemtests/tests/parallel-jobs/etc/bareos/bareos-fd.d/messages/Standard.conf
new file mode 100644
index 000000000..97788e005
--- /dev/null
+++ b/systemtests/tests/parallel-jobs/etc/bareos/bareos-fd.d/messages/Standard.conf
@@ -0,0 +1,5 @@
+Messages {
+ Name = Standard
+ Director = bareos-dir = all, !skipped, !restored
+ Description = "Send relevant messages to the Director."
+}
diff --git a/systemtests/tests/parallel-jobs/etc/bareos/bareos-sd.d/device/FileStorage.conf b/systemtests/tests/parallel-jobs/etc/bareos/bareos-sd.d/device/FileStorage.conf
new file mode 100644
index 000000000..11a639bc6
--- /dev/null
+++ b/systemtests/tests/parallel-jobs/etc/bareos/bareos-sd.d/device/FileStorage.conf
@@ -0,0 +1,11 @@
+Device {
+ Name = FileStorage
+ Media Type = File
+ Archive Device = storage
+ LabelMedia = yes; # lets Bareos label unlabeled media
+ Random Access = yes;
+ AutomaticMount = yes; # when device opened, read it
+ RemovableMedia = no;
+ AlwaysOpen = no;
+ Description = "File device. A connecting Director must have the same Name and MediaType."
+}
diff --git a/systemtests/tests/parallel-jobs/etc/bareos/bareos-sd.d/director/bareos-dir.conf.in b/systemtests/tests/parallel-jobs/etc/bareos/bareos-sd.d/director/bareos-dir.conf.in
new file mode 100644
index 000000000..deef3360c
--- /dev/null
+++ b/systemtests/tests/parallel-jobs/etc/bareos/bareos-sd.d/director/bareos-dir.conf.in
@@ -0,0 +1,5 @@
+Director {
+ Name = bareos-dir
+ Password = "@sd_password@"
+ Description = "Director, who is permitted to contact this storage daemon."
+}
diff --git a/systemtests/tests/parallel-jobs/etc/bareos/bareos-sd.d/messages/Standard.conf b/systemtests/tests/parallel-jobs/etc/bareos/bareos-sd.d/messages/Standard.conf
new file mode 100644
index 000000000..468348e62
--- /dev/null
+++ b/systemtests/tests/parallel-jobs/etc/bareos/bareos-sd.d/messages/Standard.conf
@@ -0,0 +1,5 @@
+Messages {
+ Name = Standard
+ Director = bareos-dir = all
+ Description = "Send all messages to the Director."
+}
diff --git a/systemtests/tests/parallel-jobs/etc/bareos/bareos-sd.d/storage/bareos-sd.conf.in b/systemtests/tests/parallel-jobs/etc/bareos/bareos-sd.d/storage/bareos-sd.conf.in
new file mode 100644
index 000000000..d555776e0
--- /dev/null
+++ b/systemtests/tests/parallel-jobs/etc/bareos/bareos-sd.d/storage/bareos-sd.conf.in
@@ -0,0 +1,11 @@
+Storage {
+ Name = bareos-sd
+ Maximum Concurrent Jobs = 20
+
+ # remove comment from "Plugin Directory" to load plugins from specified directory.
+ # if "Plugin Names" is defined, only the specified plugins will be loaded,
+ # otherwise all storage plugins (*-sd.so) from the "Plugin Directory".
+ #
+ Working Directory = "@working_dir@"
+ SD Port = @sd_port@
+}
diff --git a/systemtests/tests/parallel-jobs/etc/bareos/bconsole.conf.in b/systemtests/tests/parallel-jobs/etc/bareos/bconsole.conf.in
new file mode 100644
index 000000000..50b647c1d
--- /dev/null
+++ b/systemtests/tests/parallel-jobs/etc/bareos/bconsole.conf.in
@@ -0,0 +1,10 @@
+#
+# Bareos User Agent (or Console) Configuration File
+#
+
+Director {
+ Name = @basename@-dir
+ DIRport = @dir_port@
+ Address = @hostname@
+ Password = "@dir_password@"
+}
diff --git a/systemtests/tests/parallel-jobs/parallel b/systemtests/tests/parallel-jobs/parallel
new file mode 100755
index 000000000..38996bda5
--- /dev/null
+++ b/systemtests/tests/parallel-jobs/parallel
@@ -0,0 +1,66 @@
+#!/bin/bash
+set -e
+set -o pipefail
+set -u
+#
+# Run a simple backup
+# then restore it.
+#
+TestName="$(basename "$(pwd)")"
+export TestName
+
+backup1=backup-bareos-fd
+backup2=backup-bareos-fd
+backup3=backup-bareos-fd
+
+#shellcheck source=../environment.in
+. ./environment
+
+#shellcheck source=../scripts/functions
+. "${rscripts}"/functions
+
+
+echo "/home/alaa/Documents/smallfiles/" >"$tmp/file-list"
+
+start_test
+
+cat <<END_OF_DATA >"$tmp/bconcmds"
+@$out /dev/null
+messages
+@$out $tmp/paralell-backup.out
+run job=${backup1} level=Full yes
+run job=${backup1} level=Full yes
+run job=${backup1} level=Full yes
+wait
+messages
+
+@$out $tmp/paralell-restore.out
+restore jobid=1 all done
+1
+yes
+restore jobid=2 all done
+2
+yes
+restore jobid=3 all done
+3
+yes
+wait
+messages
+quit
+END_OF_DATA
+
+#run job=restore1 jobid=1 yes
+#run job=restore2 jobid=2 yes
+#run job=restore3 jobid=3 yes
+
+run_bconsole
+
+for i in 1 2 3; do
+ diffr=$(diff -qr ~/Documents/smallfiles tmp/restore$i/home/alaa/Documents/smallfiles)
+ echo $diffr
+ if [ "${diffr}" != "" ]; then
+ echo "bad diff backup $i"
+ fi
+done
+
+end_test
diff --git a/systemtests/tests/parallel-jobs/test-setup b/systemtests/tests/parallel-jobs/test-setup
new file mode 100755
index 000000000..9f7be3d9b
--- /dev/null
+++ b/systemtests/tests/parallel-jobs/test-setup
@@ -0,0 +1,40 @@
+#!/bin/bash
+
+# BAREOS® - Backup Archiving REcovery Open Sourced
+#
+# Copyright (C) 2022-2022 Bareos GmbH & Co. KG
+#
+# This program is Free Software; you can redistribute it and/or
+# modify it under the terms of version three of the GNU Affero General Public
+# License as published by the Free Software Foundation and included
+# in the file LICENSE.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301, USA.
+
+set -e
+set -o pipefail
+set -u
+
+#shellcheck source=../environment.in
+. ./environment
+
+#shellcheck source=../scripts/functions
+. "${rscripts}"/functions
+"${rscripts}"/cleanup
+"${rscripts}"/setup
+
+# Fill ${BackupDirectory} with data.
+setup_data
+
+bin/bareos start
+
+# make sure, director is up and running.
+print_debug "$(bin/bconsole <<< "status dir")"
diff --git a/systemtests/tests/parallel-jobs/testrunner-parallel-jobs b/systemtests/tests/parallel-jobs/testrunner-parallel-jobs
new file mode 100755
index 000000000..2d8e3646e
--- /dev/null
+++ b/systemtests/tests/parallel-jobs/testrunner-parallel-jobs
@@ -0,0 +1,48 @@
+#!/bin/bash
+set -e
+set -o pipefail
+set -u
+#
+# Run 3 backups in parallel and then restore one of them
+#
+TestName="$(basename "$(pwd)")"
+export TestName
+
+backup=backup-bareos-fd
+
+#shellcheck source=../environment.in
+. ./environment
+
+#shellcheck source=../scripts/functions
+. "${rscripts}"/functions
+
+backup_log=$tmp/parallel-backup.out
+restore_log=$tmp/paralell-restore.out
+
+rm -f $backup_log
+rm -f $restore_log
+
+start_test
+
+cat <<END_OF_DATA >"$tmp/bconcmds"
+@$out /dev/null
+messages
+@$out $backup_log
+run job=${backup} level=Full yes
+run job=${backup} level=Full yes
+run job=${backup} level=Full yes
+wait
+messages
+
+@$out $restore_log
+restore jobid=1 all done yes
+wait
+messages
+quit
+END_OF_DATA
+
+run_bconsole
+
+check_restore_diff "${BackupDirectory}"
+
+end_test
diff --git a/systemtests/tests/stresstest/CMakeLists.txt b/systemtests/tests/stresstest/CMakeLists.txt
new file mode 100644
index 000000000..75c7186c3
--- /dev/null
+++ b/systemtests/tests/stresstest/CMakeLists.txt
@@ -0,0 +1,21 @@
+# BAREOS® - Backup Archiving REcovery Open Sourced
+#
+# Copyright (C) 2022-2022 Bareos GmbH & Co. KG
+#
+# This program is Free Software; you can redistribute it and/or
+# modify it under the terms of version three of the GNU Affero General Public
+# License as published by the Free Software Foundation and included
+# in the file LICENSE.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301, USA.
+
+get_filename_component(BASENAME ${CMAKE_CURRENT_BINARY_DIR} NAME)
+create_systemtest(${SYSTEMTEST_PREFIX} ${BASENAME} DISABLED)
diff --git a/systemtests/tests/stresstest/cloneandcopykernelcode b/systemtests/tests/stresstest/cloneandcopykernelcode
new file mode 100644
index 000000000..635038325
--- /dev/null
+++ b/systemtests/tests/stresstest/cloneandcopykernelcode
@@ -0,0 +1,36 @@
+#!/bin/bash
+
+# BAREOS® - Backup Archiving REcovery Open Sourced
+#
+# Copyright (C) 2022-2022 Bareos GmbH & Co. KG
+#
+# This program is Free Software; you can redistribute it and/or
+# modify it under the terms of version three of the GNU Affero General Public
+# License as published by the Free Software Foundation and included
+# in the file LICENSE.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301, USA.
+
+set -e
+set -o pipefail
+
+# clone the linux kernel code from github and make 10 copies of it
+# it would amount to 75GB of data, and around 1M files
+
+mkdir kernelcopies
+cd kernelcopies
+git clone https://www.github.com/torvalds/linux
+
+for i in 1..15; do
+ cp -r linux linux$i
+done
+
+cd ../
diff --git a/systemtests/tests/stresstest/etc/bareos/bareos-dir.d/catalog/MyCatalog.conf.in b/systemtests/tests/stresstest/etc/bareos/bareos-dir.d/catalog/MyCatalog.conf.in
new file mode 100644
index 000000000..3c3385945
--- /dev/null
+++ b/systemtests/tests/stresstest/etc/bareos/bareos-dir.d/catalog/MyCatalog.conf.in
@@ -0,0 +1,6 @@
+Catalog {
+ Name = MyCatalog
+ dbname = "@db_name@"
+ dbuser = "@db_user@"
+ dbpassword = "@db_password@"
+}
diff --git a/systemtests/tests/stresstest/etc/bareos/bareos-dir.d/client/bareos-fd.conf.in b/systemtests/tests/stresstest/etc/bareos/bareos-dir.d/client/bareos-fd.conf.in
new file mode 100644
index 000000000..59b617009
--- /dev/null
+++ b/systemtests/tests/stresstest/etc/bareos/bareos-dir.d/client/bareos-fd.conf.in
@@ -0,0 +1,7 @@
+Client {
+ Name = bareos-fd
+ Description = "Client resource of the Director itself."
+ Address = @hostname@
+ Password = "@fd_password@" # password for FileDaemon
+ FD PORT = @fd_port@
+}
diff --git a/systemtests/tests/stresstest/etc/bareos/bareos-dir.d/director/bareos-dir.conf.in b/systemtests/tests/stresstest/etc/bareos/bareos-dir.d/director/bareos-dir.conf.in
new file mode 100644
index 000000000..266d120dd
--- /dev/null
+++ b/systemtests/tests/stresstest/etc/bareos/bareos-dir.d/director/bareos-dir.conf.in
@@ -0,0 +1,26 @@
+Director { # define myself
+ Name = bareos-dir
+ QueryFile = "@scriptdir@/query.sql"
+ Maximum Concurrent Jobs = 10
+ Password = "@dir_password@" # Console password
+ Messages = Daemon
+ Auditing = yes
+
+ # Enable the Heartbeat if you experience connection losses
+ # (eg. because of your router or firewall configuration).
+ # Additionally the Heartbeat can be enabled in bareos-sd and bareos-fd.
+ #
+ # Heartbeat Interval = 1 min
+
+ # remove comment in next line to load dynamic backends from specified directory
+ Backend Directory = @backenddir@
+
+ # remove comment from "Plugin Directory" to load plugins from specified directory.
+ # if "Plugin Names" is defined, only the specified plugins will be loaded,
+ # otherwise all director plugins (*-dir.so) from the "Plugin Directory".
+ #
+ # Plugin Directory = "@python_plugin_module_src_dir@"
+ # Plugin Names = ""
+ Working Directory = "@working_dir@"
+ DirPort = @dir_port@
+}
diff --git a/systemtests/tests/stresstest/etc/bareos/bareos-dir.d/fileset/Catalog.conf.in b/systemtests/tests/stresstest/etc/bareos/bareos-dir.d/fileset/Catalog.conf.in
new file mode 100644
index 000000000..c7cdc433f
--- /dev/null
+++ b/systemtests/tests/stresstest/etc/bareos/bareos-dir.d/fileset/Catalog.conf.in
@@ -0,0 +1,11 @@
+FileSet {
+ Name = "Catalog"
+ Description = "Backup the catalog dump and Bareos configuration files."
+ Include {
+ Options {
+ signature = MD5
+ }
+ File = "@working_dir@/@db_name@.sql" # database dump
+ File = "@confdir@" # configuration
+ }
+}
diff --git a/systemtests/tests/stresstest/etc/bareos/bareos-dir.d/fileset/SelfTest.conf.in b/systemtests/tests/stresstest/etc/bareos/bareos-dir.d/fileset/SelfTest.conf.in
new file mode 100644
index 000000000..be175b9a9
--- /dev/null
+++ b/systemtests/tests/stresstest/etc/bareos/bareos-dir.d/fileset/SelfTest.conf.in
@@ -0,0 +1,20 @@
+FileSet {
+ Name = "SelfTest"
+ Description = "fileset just to backup some files for selftest"
+ Include {
+ Options {
+ Signature = MD5 # calculate md5 checksum per file
+ fstype = ext2
+ fstype = ext3
+ fstype = ext4
+ fstype = overlay
+ fstype = jfs
+ fstype = ufs
+ fstype = xfs
+ fstype = zfs
+ fstype = btrfs
+ }
+ #File = "@sbindir@"
+ File=<@tmpdir@/file-list
+ }
+}
diff --git a/systemtests/tests/stresstest/etc/bareos/bareos-dir.d/job/BackupCatalog.conf.in b/systemtests/tests/stresstest/etc/bareos/bareos-dir.d/job/BackupCatalog.conf.in
new file mode 100644
index 000000000..1da2a7af6
--- /dev/null
+++ b/systemtests/tests/stresstest/etc/bareos/bareos-dir.d/job/BackupCatalog.conf.in
@@ -0,0 +1,20 @@
+Job {
+ Name = "BackupCatalog"
+ Description = "Backup the catalog database (after the nightly save)"
+ JobDefs = "DefaultJob"
+ Level = Full
+ FileSet="Catalog"
+
+ # This creates an ASCII copy of the catalog
+ # Arguments to make_catalog_backup.pl are:
+ # make_catalog_backup.pl <catalog-name>
+ RunBeforeJob = "@scriptdir@/make_catalog_backup.pl MyCatalog"
+
+ # This deletes the copy of the catalog
+ RunAfterJob = "@scriptdir@/delete_catalog_backup"
+
+ # This sends the bootstrap via mail for disaster recovery.
+ # Should be sent to another system, please change recipient accordingly
+ Write Bootstrap = "|@bindir@/bsmtp -h @smtp_host@ -f \"\(Bareos\) \" -s \"Bootstrap for Job %j\" @job_email@" # (#01)
+ Priority = 11 # run after main backup
+}
diff --git a/systemtests/tests/stresstest/etc/bareos/bareos-dir.d/job/RestoreFiles.conf.in b/systemtests/tests/stresstest/etc/bareos/bareos-dir.d/job/RestoreFiles.conf.in
new file mode 100644
index 000000000..89256864d
--- /dev/null
+++ b/systemtests/tests/stresstest/etc/bareos/bareos-dir.d/job/RestoreFiles.conf.in
@@ -0,0 +1,11 @@
+Job {
+ Name = "RestoreFiles"
+ Description = "Standard Restore template. Only one such job is needed for all standard Jobs/Clients/Storage ..."
+ Type = Restore
+ Client = bareos-fd
+ FileSet = SelfTest
+ Storage = File
+ Pool = Incremental
+ Messages = Standard
+ Where = @tmp@/bareos-restores
+}
diff --git a/systemtests/tests/stresstest/etc/bareos/bareos-dir.d/job/backup-bareos-fd.conf b/systemtests/tests/stresstest/etc/bareos/bareos-dir.d/job/backup-bareos-fd.conf
new file mode 100644
index 000000000..ca1891f96
--- /dev/null
+++ b/systemtests/tests/stresstest/etc/bareos/bareos-dir.d/job/backup-bareos-fd.conf
@@ -0,0 +1,5 @@
+Job {
+ Name = "backup-bareos-fd"
+ JobDefs = "DefaultJob"
+ Client = "bareos-fd"
+}
diff --git a/systemtests/tests/stresstest/etc/bareos/bareos-dir.d/job/slow-backup-bareos-fd.conf b/systemtests/tests/stresstest/etc/bareos/bareos-dir.d/job/slow-backup-bareos-fd.conf
new file mode 100644
index 000000000..b9f416f53
--- /dev/null
+++ b/systemtests/tests/stresstest/etc/bareos/bareos-dir.d/job/slow-backup-bareos-fd.conf
@@ -0,0 +1,12 @@
+Job {
+ Name = "slow-backup-bareos-fd"
+ Type = Backup
+ Level = Full
+ Client = bareos-fd
+ FileSet = "SelfTest"
+ Storage = File
+ Messages = Standard
+ Pool = SmallFull
+ Full Backup Pool = SmallFull
+ Maximum Bandwidth = 10K
+}
diff --git a/systemtests/tests/stresstest/etc/bareos/bareos-dir.d/jobdefs/DefaultJob.conf.in b/systemtests/tests/stresstest/etc/bareos/bareos-dir.d/jobdefs/DefaultJob.conf.in
new file mode 100644
index 000000000..563126477
--- /dev/null
+++ b/systemtests/tests/stresstest/etc/bareos/bareos-dir.d/jobdefs/DefaultJob.conf.in
@@ -0,0 +1,15 @@
+JobDefs {
+ Name = "DefaultJob"
+ Type = Backup
+ Level = Incremental
+ Client = bareos-fd
+ FileSet = "SelfTest"
+ Storage = File
+ Messages = Standard
+ Pool = Incremental
+ Priority = 10
+ Write Bootstrap = "@working_dir@/%c.bsr"
+ Full Backup Pool = Full # write Full Backups into "Full" Pool
+ Differential Backup Pool = Differential # write Diff Backups into "Differential" Pool
+ Incremental Backup Pool = Incremental # write Incr Backups into "Incremental" Pool
+}
diff --git a/systemtests/tests/stresstest/etc/bareos/bareos-dir.d/messages/Daemon.conf.in b/systemtests/tests/stresstest/etc/bareos/bareos-dir.d/messages/Daemon.conf.in
new file mode 100644
index 000000000..cf6a8cfa1
--- /dev/null
+++ b/systemtests/tests/stresstest/etc/bareos/bareos-dir.d/messages/Daemon.conf.in
@@ -0,0 +1,7 @@
+Messages {
+ Name = Daemon
+ Description = "Message delivery for daemon messages (no job)."
+ console = all, !skipped, !saved, !audit
+ append = "@logdir@/bareos.log" = all, !skipped, !audit
+ append = "@logdir@/bareos-audit.log" = audit
+}
diff --git a/systemtests/tests/stresstest/etc/bareos/bareos-dir.d/messages/Standard.conf.in b/systemtests/tests/stresstest/etc/bareos/bareos-dir.d/messages/Standard.conf.in
new file mode 100644
index 000000000..b3556ba8c
--- /dev/null
+++ b/systemtests/tests/stresstest/etc/bareos/bareos-dir.d/messages/Standard.conf.in
@@ -0,0 +1,7 @@
+Messages {
+ Name = Standard
+ Description = "Reasonable message delivery -- send most everything to email address and to the console."
+ console = all, !skipped, !saved, !audit
+ append = "@logdir@/bareos.log" = all, !skipped, !saved, !audit
+ catalog = all, !skipped, !saved, !audit
+}
diff --git a/systemtests/tests/stresstest/etc/bareos/bareos-dir.d/pool/Differential.conf b/systemtests/tests/stresstest/etc/bareos/bareos-dir.d/pool/Differential.conf
new file mode 100644
index 000000000..25ce24821
--- /dev/null
+++ b/systemtests/tests/stresstest/etc/bareos/bareos-dir.d/pool/Differential.conf
@@ -0,0 +1,10 @@
+Pool {
+ Name = Differential
+ Pool Type = Backup
+ Recycle = yes # Bareos can automatically recycle Volumes
+ AutoPrune = yes # Prune expired volumes
+ Volume Retention = 90 days # How long should the Differential Backups be kept? (#09)
+ Maximum Volume Bytes = 10G # Limit Volume size to something reasonable
+ Maximum Volumes = 100 # Limit number of Volumes in Pool
+ Label Format = "Differential-" # Volumes will be labeled "Differential-<volume-id>"
+}
diff --git a/systemtests/tests/stresstest/etc/bareos/bareos-dir.d/pool/Full.conf b/systemtests/tests/stresstest/etc/bareos/bareos-dir.d/pool/Full.conf
new file mode 100644
index 000000000..867fc66b4
--- /dev/null
+++ b/systemtests/tests/stresstest/etc/bareos/bareos-dir.d/pool/Full.conf
@@ -0,0 +1,10 @@
+Pool {
+ Name = Full
+ Pool Type = Backup
+ Recycle = yes # Bareos can automatically recycle Volumes
+ AutoPrune = yes # Prune expired volumes
+ Volume Retention = 365 days # How long should the Full Backups be kept? (#06)
+ Maximum Volume Bytes = 50G # Limit Volume size to something reasonable
+ Maximum Volumes = 100 # Limit number of Volumes in Pool
+ Label Format = "Full-" # Volumes will be labeled "Full-<volume-id>"
+}
diff --git a/systemtests/tests/stresstest/etc/bareos/bareos-dir.d/pool/FullSmallvolumes.conf b/systemtests/tests/stresstest/etc/bareos/bareos-dir.d/pool/FullSmallvolumes.conf
new file mode 100644
index 000000000..aa8d7b5eb
--- /dev/null
+++ b/systemtests/tests/stresstest/etc/bareos/bareos-dir.d/pool/FullSmallvolumes.conf
@@ -0,0 +1,11 @@
+Pool {
+ Name = SmallFull
+ Pool Type = Backup
+ Recycle = yes
+ AutoPrune = yes
+ Volume Retention = 365 days
+ Maximum Volume Bytes = 32k
+ Maximum Volumes = 100
+ Label Format = "SmallFull-"
+ Maximum Block Size = 16K
+}
diff --git a/systemtests/tests/stresstest/etc/bareos/bareos-dir.d/pool/Incremental.conf b/systemtests/tests/stresstest/etc/bareos/bareos-dir.d/pool/Incremental.conf
new file mode 100644
index 000000000..f4dbbab66
--- /dev/null
+++ b/systemtests/tests/stresstest/etc/bareos/bareos-dir.d/pool/Incremental.conf
@@ -0,0 +1,10 @@
+Pool {
+ Name = Incremental
+ Pool Type = Backup
+ Recycle = yes # Bareos can automatically recycle Volumes
+ AutoPrune = yes # Prune expired volumes
+ Volume Retention = 30 days # How long should the Incremental Backups be kept? (#12)
+ Maximum Volume Bytes = 1G # Limit Volume size to something reasonable
+ Maximum Volumes = 100 # Limit number of Volumes in Pool
+ Label Format = "Incremental-" # Volumes will be labeled "Incremental-<volume-id>"
+}
diff --git a/systemtests/tests/stresstest/etc/bareos/bareos-dir.d/pool/Scratch.conf b/systemtests/tests/stresstest/etc/bareos/bareos-dir.d/pool/Scratch.conf
new file mode 100644
index 000000000..3a489b198
--- /dev/null
+++ b/systemtests/tests/stresstest/etc/bareos/bareos-dir.d/pool/Scratch.conf
@@ -0,0 +1,4 @@
+Pool {
+ Name = Scratch
+ Pool Type = Scratch
+}
diff --git a/systemtests/tests/stresstest/etc/bareos/bareos-dir.d/profile/operator.conf b/systemtests/tests/stresstest/etc/bareos/bareos-dir.d/profile/operator.conf
new file mode 100644
index 000000000..6edd0166d
--- /dev/null
+++ b/systemtests/tests/stresstest/etc/bareos/bareos-dir.d/profile/operator.conf
@@ -0,0 +1,18 @@
+Profile {
+ Name = operator
+ Description = "Profile allowing normal Bareos operations."
+
+ Command ACL = !.bvfs_clear_cache, !.exit, !.sql
+ Command ACL = !configure, !create, !delete, !purge, !prune, !sqlquery, !umount, !unmount
+ Command ACL = *all*
+
+ Catalog ACL = *all*
+ Client ACL = *all*
+ FileSet ACL = *all*
+ Job ACL = *all*
+ Plugin Options ACL = *all*
+ Pool ACL = *all*
+ Schedule ACL = *all*
+ Storage ACL = *all*
+ Where ACL = *all*
+}
diff --git a/systemtests/tests/stresstest/etc/bareos/bareos-dir.d/storage/File.conf.in b/systemtests/tests/stresstest/etc/bareos/bareos-dir.d/storage/File.conf.in
new file mode 100644
index 000000000..a2622f719
--- /dev/null
+++ b/systemtests/tests/stresstest/etc/bareos/bareos-dir.d/storage/File.conf.in
@@ -0,0 +1,8 @@
+Storage {
+ Name = File
+ Address = @hostname@
+ Password = "@sd_password@"
+ Device = FileStorage
+ Media Type = File
+ SD Port = @sd_port@
+}
diff --git a/systemtests/tests/stresstest/etc/bareos/bareos-fd.d/client/myself.conf.in b/systemtests/tests/stresstest/etc/bareos/bareos-fd.d/client/myself.conf.in
new file mode 100644
index 000000000..6439fe84d
--- /dev/null
+++ b/systemtests/tests/stresstest/etc/bareos/bareos-fd.d/client/myself.conf.in
@@ -0,0 +1,19 @@
+Client {
+ Name = @basename@-fd
+ Maximum Concurrent Jobs = 20
+
+ # remove comment from "Plugin Directory" to load plugins from specified directory.
+ # if "Plugin Names" is defined, only the specified plugins will be loaded,
+ # otherwise all filedaemon plugins (*-fd.so) from the "Plugin Directory".
+ #
+ # Plugin Directory = "@python_plugin_module_src_fd@"
+ # Plugin Names = ""
+
+ # if compatible is set to yes, we are compatible with bacula
+ # if set to no, new bareos features are enabled which is the default
+ # compatible = yes
+
+ Working Directory = "@working_dir@"
+ FD Port = @fd_port@
+
+}
diff --git a/systemtests/tests/stresstest/etc/bareos/bareos-fd.d/director/bareos-dir.conf.in b/systemtests/tests/stresstest/etc/bareos/bareos-fd.d/director/bareos-dir.conf.in
new file mode 100644
index 000000000..c8dc7085a
--- /dev/null
+++ b/systemtests/tests/stresstest/etc/bareos/bareos-fd.d/director/bareos-dir.conf.in
@@ -0,0 +1,5 @@
+Director {
+ Name = bareos-dir
+ Password = "@fd_password@"
+ Description = "Allow the configured Director to access this file daemon."
+}
diff --git a/systemtests/tests/stresstest/etc/bareos/bareos-fd.d/messages/Standard.conf b/systemtests/tests/stresstest/etc/bareos/bareos-fd.d/messages/Standard.conf
new file mode 100644
index 000000000..97788e005
--- /dev/null
+++ b/systemtests/tests/stresstest/etc/bareos/bareos-fd.d/messages/Standard.conf
@@ -0,0 +1,5 @@
+Messages {
+ Name = Standard
+ Director = bareos-dir = all, !skipped, !restored
+ Description = "Send relevant messages to the Director."
+}
diff --git a/systemtests/tests/stresstest/etc/bareos/bareos-sd.d/device/FileStorage.conf b/systemtests/tests/stresstest/etc/bareos/bareos-sd.d/device/FileStorage.conf
new file mode 100644
index 000000000..11a639bc6
--- /dev/null
+++ b/systemtests/tests/stresstest/etc/bareos/bareos-sd.d/device/FileStorage.conf
@@ -0,0 +1,11 @@
+Device {
+ Name = FileStorage
+ Media Type = File
+ Archive Device = storage
+ LabelMedia = yes; # lets Bareos label unlabeled media
+ Random Access = yes;
+ AutomaticMount = yes; # when device opened, read it
+ RemovableMedia = no;
+ AlwaysOpen = no;
+ Description = "File device. A connecting Director must have the same Name and MediaType."
+}
diff --git a/systemtests/tests/stresstest/etc/bareos/bareos-sd.d/director/bareos-dir.conf.in b/systemtests/tests/stresstest/etc/bareos/bareos-sd.d/director/bareos-dir.conf.in
new file mode 100644
index 000000000..deef3360c
--- /dev/null
+++ b/systemtests/tests/stresstest/etc/bareos/bareos-sd.d/director/bareos-dir.conf.in
@@ -0,0 +1,5 @@
+Director {
+ Name = bareos-dir
+ Password = "@sd_password@"
+ Description = "Director, who is permitted to contact this storage daemon."
+}
diff --git a/systemtests/tests/stresstest/etc/bareos/bareos-sd.d/messages/Standard.conf b/systemtests/tests/stresstest/etc/bareos/bareos-sd.d/messages/Standard.conf
new file mode 100644
index 000000000..468348e62
--- /dev/null
+++ b/systemtests/tests/stresstest/etc/bareos/bareos-sd.d/messages/Standard.conf
@@ -0,0 +1,5 @@
+Messages {
+ Name = Standard
+ Director = bareos-dir = all
+ Description = "Send all messages to the Director."
+}
diff --git a/systemtests/tests/stresstest/etc/bareos/bareos-sd.d/storage/bareos-sd.conf.in b/systemtests/tests/stresstest/etc/bareos/bareos-sd.d/storage/bareos-sd.conf.in
new file mode 100644
index 000000000..8847967d8
--- /dev/null
+++ b/systemtests/tests/stresstest/etc/bareos/bareos-sd.d/storage/bareos-sd.conf.in
@@ -0,0 +1,12 @@
+Storage {
+ Name = bareos-sd
+ Maximum Concurrent Jobs = 20
+
+ # remove comment from "Plugin Directory" to load plugins from specified directory.
+ # if "Plugin Names" is defined, only the specified plugins will be loaded,
+ # otherwise all storage plugins (*-sd.so) from the "Plugin Directory".
+ #
+ Working Directory = "@working_dir@"
+ SD Port = @sd_port@
+ #Checkpoint Interval = 30
+}
diff --git a/systemtests/tests/stresstest/etc/bareos/bconsole.conf.in b/systemtests/tests/stresstest/etc/bareos/bconsole.conf.in
new file mode 100644
index 000000000..50b647c1d
--- /dev/null
+++ b/systemtests/tests/stresstest/etc/bareos/bconsole.conf.in
@@ -0,0 +1,10 @@
+#
+# Bareos User Agent (or Console) Configuration File
+#
+
+Director {
+ Name = @basename@-dir
+ DIRport = @dir_port@
+ Address = @hostname@
+ Password = "@dir_password@"
+}
diff --git a/systemtests/tests/stresstest/test-setup b/systemtests/tests/stresstest/test-setup
new file mode 100755
index 000000000..1207a7dce
--- /dev/null
+++ b/systemtests/tests/stresstest/test-setup
@@ -0,0 +1,43 @@
+#!/bin/bash
+
+# BAREOS® - Backup Archiving REcovery Open Sourced
+#
+# Copyright (C) 2022-2022 Bareos GmbH & Co. KG
+#
+# This program is Free Software; you can redistribute it and/or
+# modify it under the terms of version three of the GNU Affero General Public
+# License as published by the Free Software Foundation and included
+# in the file LICENSE.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301, USA.
+
+set -e
+set -o pipefail
+
+#shellcheck source=../environment.in
+. ./environment
+
+#shellcheck source=../scripts/functions
+. "${rscripts}"/functions
+"${rscripts}"/cleanup
+"${rscripts}"/setup
+
+if [ "$1" == "" ]; then
+ echo "Expecting path to data to backup."
+ exit -1
+fi
+
+echo "$1" >$tmp/file-list
+
+bin/bareos start
+
+# make sure, director is up and running.
+print_debug "$(bin/bconsole <<< "status dir")"
diff --git a/systemtests/tests/stresstest/testrunner-stresstest b/systemtests/tests/stresstest/testrunner-stresstest
new file mode 100755
index 000000000..fcd57982d
--- /dev/null
+++ b/systemtests/tests/stresstest/testrunner-stresstest
@@ -0,0 +1,48 @@
+#!/bin/bash
+set -e
+set -o pipefail
+set -u
+#
+# Run a backup and cancel it.
+# Check that metadata is still saved with checkpoints.
+#
+TestName="$(basename "$(pwd)")"
+export TestName
+
+#shellcheck source=../environment.in
+. ./environment
+
+#shellcheck source=../scripts/functions
+. "${rscripts}"/functions
+
+backupjob=backup-bareos-fd
+
+backup_log=$tmp/strees-backup.out
+csvlog=csvfile.csv
+
+rm -f $backup_log
+rm -f $csvlog
+
+echo "runnumber,duration" >> $csvlog
+
+cat << END_OF_DATA >"$tmp/bconcmds"
+@$out /dev/null
+messages
+@$out $backup_log
+run job=${backupjob} level=Full yes
+wait
+messages
+quit
+END_OF_DATA
+
+start_test
+
+for i in {1..10}; do
+ rm -f $backup_log
+ run_bconsole
+ elapsedtime=$(grep 'Elapsed time: ' $backup_log | sed -n -e 's/^.*time: //p')
+ echo "$i,${elapsedtime}" >> $csvlog
+ rm storage/*
+done
+
+end_test