From 2311251500d3339f5f3c8d3802c35f0d4dc2c020 Mon Sep 17 00:00:00 2001
From: Steven Murray <steven.murray@cern.ch>
Date: Mon, 8 Aug 2016 09:08:09 +0200
Subject: [PATCH] Implemented Rdbms::reclaimTape()

---
 catalogue/Catalogue.hpp       |  14 +
 catalogue/CatalogueTest.cpp   | 491 ++++++++++++++++++++++++++++++++++
 catalogue/OracleCatalogue.cpp |   1 +
 catalogue/OracleCatalogue.hpp |   8 +-
 catalogue/RdbmsCatalogue.cpp  |  80 +++++-
 catalogue/RdbmsCatalogue.hpp  | 199 ++++++++------
 catalogue/SqliteCatalogue.hpp |   4 +-
 7 files changed, 694 insertions(+), 103 deletions(-)

diff --git a/catalogue/Catalogue.hpp b/catalogue/Catalogue.hpp
index 86b0527486..04a95aae1e 100644
--- a/catalogue/Catalogue.hpp
+++ b/catalogue/Catalogue.hpp
@@ -299,7 +299,21 @@ public:
    */
   virtual common::dataStructures::VidToTapeMap getTapesByVid(const std::set<std::string> &vids) const = 0;
 
+  /**
+   * Reclaims the specified tape.
+   *
+   * This method will throw an exception if the specified tape does not exist.
+   *
+   * This method will throw an exception if the specified tape is not FULL.
+   *
+   * This method will throw an exception if there is still at least one tape
+   * file recorded in the cataligue as being on the specified tape.
+   *
+   * @param cliIdentity The user of the command-line tool.
+   * @param vid The volume identifier of the tape to be reclaimed.
+   */
   virtual void reclaimTape(const common::dataStructures::SecurityIdentity &cliIdentity, const std::string &vid) = 0;
+
   virtual void modifyTapeLogicalLibraryName(const common::dataStructures::SecurityIdentity &cliIdentity, const std::string &vid, const std::string &logicalLibraryName) = 0;
   virtual void modifyTapeTapePoolName(const common::dataStructures::SecurityIdentity &cliIdentity, const std::string &vid, const std::string &tapePoolName) = 0;
   virtual void modifyTapeCapacityInBytes(const common::dataStructures::SecurityIdentity &cliIdentity, const std::string &vid, const uint64_t capacityInBytes) = 0;
diff --git a/catalogue/CatalogueTest.cpp b/catalogue/CatalogueTest.cpp
index 103b4315a1..259ad07d72 100644
--- a/catalogue/CatalogueTest.cpp
+++ b/catalogue/CatalogueTest.cpp
@@ -6398,4 +6398,495 @@ TEST_P(cta_catalogue_CatalogueTest, getTapesByVid_no_vids) {
   ASSERT_TRUE(m_catalogue->getTapesByVid(vids).empty());
 }
 
+TEST_P(cta_catalogue_CatalogueTest, reclaimTape_full_lastFSeq_0_no_tape_files) {
+  using namespace cta;
+
+  ASSERT_TRUE(m_catalogue->getTapes().empty());
+
+  const std::string vid = "vid";
+  const std::string logicalLibraryName = "logical_library_name";
+  const std::string tapePoolName = "tape_pool_name";
+  const std::string encryptionKey = "encryption_key";
+  const uint64_t capacityInBytes = (uint64_t)10 * 1000 * 1000 * 1000 * 1000;
+  const bool disabledValue = true;
+  const bool fullValue = false;
+  const std::string comment = "Create tape";
+
+  m_catalogue->createLogicalLibrary(m_cliSI, logicalLibraryName,
+    "Create logical library");
+  m_catalogue->createTapePool(m_cliSI, tapePoolName, 2, true, "Create tape pool");
+  m_catalogue->createTape(m_cliSI, vid, logicalLibraryName, tapePoolName, encryptionKey, capacityInBytes, disabledValue,
+    fullValue, comment);
+
+  {
+    const std::list<common::dataStructures::Tape> tapes = m_catalogue->getTapes();
+
+    ASSERT_EQ(1, tapes.size());
+
+    const common::dataStructures::Tape tape = tapes.front();
+    ASSERT_EQ(vid, tape.vid);
+    ASSERT_EQ(0, tape.lastFSeq);
+    ASSERT_EQ(logicalLibraryName, tape.logicalLibraryName);
+    ASSERT_EQ(tapePoolName, tape.tapePoolName);
+    ASSERT_EQ(encryptionKey, tape.encryptionKey);
+    ASSERT_EQ(capacityInBytes, tape.capacityInBytes);
+    ASSERT_TRUE(disabledValue == tape.disabled);
+    ASSERT_TRUE(fullValue == tape.full);
+    ASSERT_FALSE(tape.lbp);
+    ASSERT_EQ(comment, tape.comment);
+    ASSERT_FALSE(tape.labelLog);
+    ASSERT_FALSE(tape.lastReadLog);
+    ASSERT_FALSE(tape.lastWriteLog);
+
+    const common::dataStructures::EntryLog creationLog = tape.creationLog;
+    ASSERT_EQ(m_cliSI.username, creationLog.username);
+    ASSERT_EQ(m_cliSI.host, creationLog.host);
+
+    const common::dataStructures::EntryLog lastModificationLog = tape.lastModificationLog;
+    ASSERT_EQ(creationLog, lastModificationLog);
+  }
+
+  m_catalogue->setTapeFull(m_cliSI, vid, true);
+  m_catalogue->reclaimTape(m_cliSI, vid);
+
+  {
+    const std::list<common::dataStructures::Tape> tapes = m_catalogue->getTapes();
+
+    ASSERT_EQ(1, tapes.size());
+
+    const common::dataStructures::Tape tape = tapes.front();
+    ASSERT_EQ(vid, tape.vid);
+    ASSERT_EQ(0, tape.lastFSeq);
+    ASSERT_EQ(logicalLibraryName, tape.logicalLibraryName);
+    ASSERT_EQ(tapePoolName, tape.tapePoolName);
+    ASSERT_EQ(encryptionKey, tape.encryptionKey);
+    ASSERT_EQ(capacityInBytes, tape.capacityInBytes);
+    ASSERT_TRUE(disabledValue == tape.disabled);
+    ASSERT_FALSE(tape.full);
+    ASSERT_FALSE(tape.lbp);
+    ASSERT_EQ(comment, tape.comment);
+    ASSERT_FALSE(tape.labelLog);
+    ASSERT_FALSE(tape.lastReadLog);
+    ASSERT_FALSE(tape.lastWriteLog);
+
+    const common::dataStructures::EntryLog creationLog = tape.creationLog;
+    ASSERT_EQ(m_cliSI.username, creationLog.username);
+    ASSERT_EQ(m_cliSI.host, creationLog.host);
+  }
+}
+
+TEST_P(cta_catalogue_CatalogueTest, reclaimTape_not_full_lastFSeq_0_no_tape_files) {
+  using namespace cta;
+
+  ASSERT_TRUE(m_catalogue->getTapes().empty());
+
+  const std::string vid = "vid";
+  const std::string logicalLibraryName = "logical_library_name";
+  const std::string tapePoolName = "tape_pool_name";
+  const std::string encryptionKey = "encryption_key";
+  const uint64_t capacityInBytes = (uint64_t)10 * 1000 * 1000 * 1000 * 1000;
+  const bool disabledValue = true;
+  const bool fullValue = false;
+  const std::string comment = "Create tape";
+
+  m_catalogue->createLogicalLibrary(m_cliSI, logicalLibraryName,
+    "Create logical library");
+  m_catalogue->createTapePool(m_cliSI, tapePoolName, 2, true, "Create tape pool");
+  m_catalogue->createTape(m_cliSI, vid, logicalLibraryName, tapePoolName, encryptionKey, capacityInBytes, disabledValue,
+    fullValue, comment);
+
+  {
+    const std::list<common::dataStructures::Tape> tapes = m_catalogue->getTapes();
+
+    ASSERT_EQ(1, tapes.size());
+
+    const common::dataStructures::Tape tape = tapes.front();
+    ASSERT_EQ(vid, tape.vid);
+    ASSERT_EQ(0, tape.lastFSeq);
+    ASSERT_EQ(logicalLibraryName, tape.logicalLibraryName);
+    ASSERT_EQ(tapePoolName, tape.tapePoolName);
+    ASSERT_EQ(encryptionKey, tape.encryptionKey);
+    ASSERT_EQ(capacityInBytes, tape.capacityInBytes);
+    ASSERT_TRUE(disabledValue == tape.disabled);
+    ASSERT_TRUE(fullValue == tape.full);
+    ASSERT_FALSE(tape.lbp);
+    ASSERT_EQ(comment, tape.comment);
+    ASSERT_FALSE(tape.labelLog);
+    ASSERT_FALSE(tape.lastReadLog);
+    ASSERT_FALSE(tape.lastWriteLog);
+
+    const common::dataStructures::EntryLog creationLog = tape.creationLog;
+    ASSERT_EQ(m_cliSI.username, creationLog.username);
+    ASSERT_EQ(m_cliSI.host, creationLog.host);
+
+    const common::dataStructures::EntryLog lastModificationLog = tape.lastModificationLog;
+    ASSERT_EQ(creationLog, lastModificationLog);
+  }
+
+  ASSERT_THROW(m_catalogue->reclaimTape(m_cliSI, vid), exception::UserError);
+}
+
+TEST_P(cta_catalogue_CatalogueTest, reclaimTape_full_lastFSeq_1_no_tape_files) {
+  using namespace cta;
+
+  const std::string diskInstanceName1 = "disk_instance_1";
+
+  ASSERT_TRUE(m_catalogue->getTapes().empty());
+
+  const std::string vid1 = "VID123";
+  const std::string logicalLibraryName = "logical_library_name";
+  const std::string tapePoolName = "tape_pool_name";
+  const std::string encryptionKey = "encryption_key";
+  const uint64_t capacityInBytes = (uint64_t)10 * 1000 * 1000 * 1000 * 1000;
+  const bool disabledValue = true;
+  const bool fullValue = false;
+  const std::string createTapeComment = "Create tape";
+
+  m_catalogue->createLogicalLibrary(m_cliSI, logicalLibraryName, "Create logical library");
+  m_catalogue->createTapePool(m_cliSI, tapePoolName, 2, true, "Create tape pool");
+  m_catalogue->createTape(m_cliSI, vid1, logicalLibraryName, tapePoolName, encryptionKey, capacityInBytes,
+    disabledValue, fullValue, createTapeComment);
+
+  {
+    const std::list<common::dataStructures::Tape> tapes = m_catalogue->getTapes();
+    const std::map<std::string, common::dataStructures::Tape> vidToTape = tapeListToMap(tapes);
+    ASSERT_EQ(1, vidToTape.size());
+
+    auto it = vidToTape.find(vid1);
+    const common::dataStructures::Tape &tape = it->second;
+    ASSERT_EQ(vid1, tape.vid);
+    ASSERT_EQ(0, tape.lastFSeq);
+    ASSERT_EQ(logicalLibraryName, tape.logicalLibraryName);
+    ASSERT_EQ(tapePoolName, tape.tapePoolName);
+    ASSERT_EQ(encryptionKey, tape.encryptionKey);
+    ASSERT_EQ(capacityInBytes, tape.capacityInBytes);
+    ASSERT_TRUE(disabledValue == tape.disabled);
+    ASSERT_TRUE(fullValue == tape.full);
+    ASSERT_FALSE(tape.lbp);
+    ASSERT_EQ(createTapeComment, tape.comment);
+    ASSERT_FALSE(tape.labelLog);
+    ASSERT_FALSE(tape.lastReadLog);
+    ASSERT_FALSE(tape.lastWriteLog);
+
+    const common::dataStructures::EntryLog creationLog = tape.creationLog;
+    ASSERT_EQ(m_cliSI.username, creationLog.username);
+    ASSERT_EQ(m_cliSI.host, creationLog.host);
+
+    const common::dataStructures::EntryLog lastModificationLog =
+      tape.lastModificationLog;
+    ASSERT_EQ(creationLog, lastModificationLog);
+  }
+
+  const uint64_t archiveFileId = 1234;
+
+  ASSERT_FALSE(m_catalogue->getArchiveFileItor()->hasMore());
+  ASSERT_THROW(m_catalogue->getArchiveFileById(archiveFileId), exception::Exception);
+
+  common::dataStructures::StorageClass storageClass;
+  storageClass.diskInstance = diskInstanceName1;
+  storageClass.name = "storage_class";
+  storageClass.nbCopies = 1;
+  storageClass.comment = "Create storage class";
+  m_catalogue->createStorageClass(m_cliSI, storageClass);
+
+  const uint64_t archiveFileSize = 1;
+  const std::string tapeDrive = "tape_drive";
+  const std::string checksumType = "checksum_type";
+  const std::string checksumValue = "checksum_value";
+
+  catalogue::TapeFileWritten file1Written;
+  file1Written.archiveFileId        = archiveFileId;
+  file1Written.diskInstance         = storageClass.diskInstance;
+  file1Written.diskFileId           = "5678";
+  file1Written.diskFilePath         = "/public_dir/public_file";
+  file1Written.diskFileUser         = "public_disk_user";
+  file1Written.diskFileGroup        = "public_disk_group";
+  file1Written.diskFileRecoveryBlob = "opaque_disk_file_recovery_contents";
+  file1Written.size                 = archiveFileSize;
+  file1Written.checksumType         = checksumType;
+  file1Written.checksumValue        = checksumValue;
+  file1Written.storageClassName     = storageClass.name;
+  file1Written.vid                  = vid1;
+  file1Written.fSeq                 = 1;
+  file1Written.blockId              = 4321;
+  file1Written.compressedSize       = 1;
+  file1Written.copyNb               = 1;
+  file1Written.tapeDrive            = tapeDrive;
+  m_catalogue->fileWrittenToTape(file1Written);
+
+  {
+    const common::dataStructures::ArchiveFile archiveFile = m_catalogue->getArchiveFileById(archiveFileId);
+
+    ASSERT_EQ(file1Written.archiveFileId, archiveFile.archiveFileID);
+    ASSERT_EQ(file1Written.diskFileId, archiveFile.diskFileId);
+    ASSERT_EQ(file1Written.size, archiveFile.fileSize);
+    ASSERT_EQ(file1Written.checksumType, archiveFile.checksumType);
+    ASSERT_EQ(file1Written.checksumValue, archiveFile.checksumValue);
+    ASSERT_EQ(file1Written.storageClassName, archiveFile.storageClass);
+
+    ASSERT_EQ(file1Written.diskInstance, archiveFile.diskInstance);
+    ASSERT_EQ(file1Written.diskFilePath, archiveFile.diskFileInfo.path);
+    ASSERT_EQ(file1Written.diskFileUser, archiveFile.diskFileInfo.owner);
+    ASSERT_EQ(file1Written.diskFileGroup, archiveFile.diskFileInfo.group);
+    ASSERT_EQ(file1Written.diskFileRecoveryBlob, archiveFile.diskFileInfo.recoveryBlob);
+
+    ASSERT_EQ(1, archiveFile.tapeFiles.size());
+    auto copyNbToTapeFile1Itor = archiveFile.tapeFiles.find(1);
+    ASSERT_FALSE(copyNbToTapeFile1Itor == archiveFile.tapeFiles.end());
+    const common::dataStructures::TapeFile &tapeFile1 = copyNbToTapeFile1Itor->second;
+    ASSERT_EQ(file1Written.vid, tapeFile1.vid);
+    ASSERT_EQ(file1Written.fSeq, tapeFile1.fSeq);
+    ASSERT_EQ(file1Written.blockId, tapeFile1.blockId);
+    ASSERT_EQ(file1Written.compressedSize, tapeFile1.compressedSize);
+    ASSERT_EQ(file1Written.checksumType, tapeFile1.checksumType);
+    ASSERT_EQ(file1Written.checksumValue, tapeFile1.checksumValue);
+    ASSERT_EQ(file1Written.copyNb, tapeFile1.copyNb);
+  }
+
+  {
+    const std::list<common::dataStructures::Tape> tapes = m_catalogue->getTapes();
+    const std::map<std::string, common::dataStructures::Tape> vidToTape = tapeListToMap(tapes);
+    ASSERT_EQ(1, vidToTape.size());
+
+    auto it = vidToTape.find(vid1);
+    const common::dataStructures::Tape &tape = it->second;
+    ASSERT_EQ(vid1, tape.vid);
+    ASSERT_EQ(1, tape.lastFSeq);
+    ASSERT_EQ(logicalLibraryName, tape.logicalLibraryName);
+    ASSERT_EQ(tapePoolName, tape.tapePoolName);
+    ASSERT_EQ(encryptionKey, tape.encryptionKey);
+    ASSERT_EQ(capacityInBytes, tape.capacityInBytes);
+    ASSERT_TRUE(disabledValue == tape.disabled);
+    ASSERT_TRUE(fullValue == tape.full);
+    ASSERT_FALSE(tape.lbp);
+    ASSERT_EQ(createTapeComment, tape.comment);
+    ASSERT_FALSE(tape.labelLog);
+    ASSERT_FALSE(tape.lastReadLog);
+    ASSERT_TRUE((bool)tape.lastWriteLog);
+    ASSERT_EQ(tapeDrive, tape.lastWriteLog.value().drive);
+
+    const common::dataStructures::EntryLog creationLog = tape.creationLog;
+    ASSERT_EQ(m_cliSI.username, creationLog.username);
+    ASSERT_EQ(m_cliSI.host, creationLog.host);
+
+    const common::dataStructures::EntryLog lastModificationLog =
+      tape.lastModificationLog;
+    ASSERT_EQ(creationLog, lastModificationLog);
+  }
+
+  m_catalogue->deleteArchiveFile(diskInstanceName1, file1Written.archiveFileId);
+
+  {
+    const std::list<common::dataStructures::Tape> tapes = m_catalogue->getTapes();
+    const std::map<std::string, common::dataStructures::Tape> vidToTape = tapeListToMap(tapes);
+    ASSERT_EQ(1, vidToTape.size());
+
+    auto it = vidToTape.find(vid1);
+    const common::dataStructures::Tape &tape = it->second;
+    ASSERT_EQ(vid1, tape.vid);
+    ASSERT_EQ(1, tape.lastFSeq);
+    ASSERT_EQ(logicalLibraryName, tape.logicalLibraryName);
+    ASSERT_EQ(tapePoolName, tape.tapePoolName);
+    ASSERT_EQ(encryptionKey, tape.encryptionKey);
+    ASSERT_EQ(capacityInBytes, tape.capacityInBytes);
+    ASSERT_TRUE(disabledValue == tape.disabled);
+    ASSERT_TRUE(fullValue == tape.full);
+    ASSERT_FALSE(tape.lbp);
+    ASSERT_EQ(createTapeComment, tape.comment);
+    ASSERT_FALSE(tape.labelLog);
+    ASSERT_FALSE(tape.lastReadLog);
+    ASSERT_TRUE((bool)tape.lastWriteLog);
+    ASSERT_EQ(tapeDrive, tape.lastWriteLog.value().drive);
+
+    const common::dataStructures::EntryLog creationLog = tape.creationLog;
+    ASSERT_EQ(m_cliSI.username, creationLog.username);
+    ASSERT_EQ(m_cliSI.host, creationLog.host);
+
+    const common::dataStructures::EntryLog lastModificationLog =
+      tape.lastModificationLog;
+    ASSERT_EQ(creationLog, lastModificationLog);
+  }
+
+  m_catalogue->setTapeFull(m_cliSI, vid1, true);
+  m_catalogue->reclaimTape(m_cliSI, vid1);
+
+  {
+    const std::list<common::dataStructures::Tape> tapes = m_catalogue->getTapes();
+
+    ASSERT_EQ(1, tapes.size());
+
+    const common::dataStructures::Tape tape = tapes.front();
+    ASSERT_EQ(vid1, tape.vid);
+    ASSERT_EQ(0, tape.lastFSeq);
+    ASSERT_EQ(logicalLibraryName, tape.logicalLibraryName);
+    ASSERT_EQ(tapePoolName, tape.tapePoolName);
+    ASSERT_EQ(encryptionKey, tape.encryptionKey);
+    ASSERT_EQ(capacityInBytes, tape.capacityInBytes);
+    ASSERT_TRUE(disabledValue == tape.disabled);
+    ASSERT_TRUE(fullValue == tape.full);
+    ASSERT_FALSE(tape.lbp);
+    ASSERT_EQ(createTapeComment, tape.comment);
+    ASSERT_FALSE(tape.labelLog);
+    ASSERT_FALSE(tape.lastReadLog);
+    ASSERT_TRUE((bool)tape.lastWriteLog);
+    ASSERT_EQ(tapeDrive, tape.lastWriteLog.value().drive);
+
+    const common::dataStructures::EntryLog creationLog = tape.creationLog;
+    ASSERT_EQ(m_cliSI.username, creationLog.username);
+    ASSERT_EQ(m_cliSI.host, creationLog.host);
+  }
+}
+
+TEST_P(cta_catalogue_CatalogueTest, reclaimTape_full_lastFSeq_1_one_tape_file) {
+  using namespace cta;
+
+  const std::string diskInstanceName1 = "disk_instance_1";
+
+  ASSERT_TRUE(m_catalogue->getTapes().empty());
+
+  const std::string vid1 = "VID123";
+  const std::string logicalLibraryName = "logical_library_name";
+  const std::string tapePoolName = "tape_pool_name";
+  const std::string encryptionKey = "encryption_key";
+  const uint64_t capacityInBytes = (uint64_t)10 * 1000 * 1000 * 1000 * 1000;
+  const bool disabledValue = true;
+  const bool fullValue = false;
+  const std::string createTapeComment = "Create tape";
+
+  m_catalogue->createLogicalLibrary(m_cliSI, logicalLibraryName, "Create logical library");
+  m_catalogue->createTapePool(m_cliSI, tapePoolName, 2, true, "Create tape pool");
+  m_catalogue->createTape(m_cliSI, vid1, logicalLibraryName, tapePoolName, encryptionKey, capacityInBytes,
+    disabledValue, fullValue, createTapeComment);
+
+  {
+    const std::list<common::dataStructures::Tape> tapes = m_catalogue->getTapes();
+    const std::map<std::string, common::dataStructures::Tape> vidToTape = tapeListToMap(tapes);
+    ASSERT_EQ(1, vidToTape.size());
+
+    auto it = vidToTape.find(vid1);
+    const common::dataStructures::Tape &tape = it->second;
+    ASSERT_EQ(vid1, tape.vid);
+    ASSERT_EQ(0, tape.lastFSeq);
+    ASSERT_EQ(logicalLibraryName, tape.logicalLibraryName);
+    ASSERT_EQ(tapePoolName, tape.tapePoolName);
+    ASSERT_EQ(encryptionKey, tape.encryptionKey);
+    ASSERT_EQ(capacityInBytes, tape.capacityInBytes);
+    ASSERT_TRUE(disabledValue == tape.disabled);
+    ASSERT_TRUE(fullValue == tape.full);
+    ASSERT_FALSE(tape.lbp);
+    ASSERT_EQ(createTapeComment, tape.comment);
+    ASSERT_FALSE(tape.labelLog);
+    ASSERT_FALSE(tape.lastReadLog);
+    ASSERT_FALSE(tape.lastWriteLog);
+
+    const common::dataStructures::EntryLog creationLog = tape.creationLog;
+    ASSERT_EQ(m_cliSI.username, creationLog.username);
+    ASSERT_EQ(m_cliSI.host, creationLog.host);
+
+    const common::dataStructures::EntryLog lastModificationLog =
+      tape.lastModificationLog;
+    ASSERT_EQ(creationLog, lastModificationLog);
+  }
+
+  const uint64_t archiveFileId = 1234;
+
+  ASSERT_FALSE(m_catalogue->getArchiveFileItor()->hasMore());
+  ASSERT_THROW(m_catalogue->getArchiveFileById(archiveFileId), exception::Exception);
+
+  common::dataStructures::StorageClass storageClass;
+  storageClass.diskInstance = diskInstanceName1;
+  storageClass.name = "storage_class";
+  storageClass.nbCopies = 1;
+  storageClass.comment = "Create storage class";
+  m_catalogue->createStorageClass(m_cliSI, storageClass);
+
+  const uint64_t archiveFileSize = 1;
+  const std::string tapeDrive = "tape_drive";
+  const std::string checksumType = "checksum_type";
+  const std::string checksumValue = "checksum_value";
+
+  catalogue::TapeFileWritten file1Written;
+  file1Written.archiveFileId        = archiveFileId;
+  file1Written.diskInstance         = storageClass.diskInstance;
+  file1Written.diskFileId           = "5678";
+  file1Written.diskFilePath         = "/public_dir/public_file";
+  file1Written.diskFileUser         = "public_disk_user";
+  file1Written.diskFileGroup        = "public_disk_group";
+  file1Written.diskFileRecoveryBlob = "opaque_disk_file_recovery_contents";
+  file1Written.size                 = archiveFileSize;
+  file1Written.checksumType         = checksumType;
+  file1Written.checksumValue        = checksumValue;
+  file1Written.storageClassName     = storageClass.name;
+  file1Written.vid                  = vid1;
+  file1Written.fSeq                 = 1;
+  file1Written.blockId              = 4321;
+  file1Written.compressedSize       = 1;
+  file1Written.copyNb               = 1;
+  file1Written.tapeDrive            = tapeDrive;
+  m_catalogue->fileWrittenToTape(file1Written);
+
+  {
+    const common::dataStructures::ArchiveFile archiveFile = m_catalogue->getArchiveFileById(archiveFileId);
+
+    ASSERT_EQ(file1Written.archiveFileId, archiveFile.archiveFileID);
+    ASSERT_EQ(file1Written.diskFileId, archiveFile.diskFileId);
+    ASSERT_EQ(file1Written.size, archiveFile.fileSize);
+    ASSERT_EQ(file1Written.checksumType, archiveFile.checksumType);
+    ASSERT_EQ(file1Written.checksumValue, archiveFile.checksumValue);
+    ASSERT_EQ(file1Written.storageClassName, archiveFile.storageClass);
+
+    ASSERT_EQ(file1Written.diskInstance, archiveFile.diskInstance);
+    ASSERT_EQ(file1Written.diskFilePath, archiveFile.diskFileInfo.path);
+    ASSERT_EQ(file1Written.diskFileUser, archiveFile.diskFileInfo.owner);
+    ASSERT_EQ(file1Written.diskFileGroup, archiveFile.diskFileInfo.group);
+    ASSERT_EQ(file1Written.diskFileRecoveryBlob, archiveFile.diskFileInfo.recoveryBlob);
+
+    ASSERT_EQ(1, archiveFile.tapeFiles.size());
+    auto copyNbToTapeFile1Itor = archiveFile.tapeFiles.find(1);
+    ASSERT_FALSE(copyNbToTapeFile1Itor == archiveFile.tapeFiles.end());
+    const common::dataStructures::TapeFile &tapeFile1 = copyNbToTapeFile1Itor->second;
+    ASSERT_EQ(file1Written.vid, tapeFile1.vid);
+    ASSERT_EQ(file1Written.fSeq, tapeFile1.fSeq);
+    ASSERT_EQ(file1Written.blockId, tapeFile1.blockId);
+    ASSERT_EQ(file1Written.compressedSize, tapeFile1.compressedSize);
+    ASSERT_EQ(file1Written.checksumType, tapeFile1.checksumType);
+    ASSERT_EQ(file1Written.checksumValue, tapeFile1.checksumValue);
+    ASSERT_EQ(file1Written.copyNb, tapeFile1.copyNb);
+  }
+
+  {
+    const std::list<common::dataStructures::Tape> tapes = m_catalogue->getTapes();
+    const std::map<std::string, common::dataStructures::Tape> vidToTape = tapeListToMap(tapes);
+    ASSERT_EQ(1, vidToTape.size());
+
+    auto it = vidToTape.find(vid1);
+    const common::dataStructures::Tape &tape = it->second;
+    ASSERT_EQ(vid1, tape.vid);
+    ASSERT_EQ(1, tape.lastFSeq);
+    ASSERT_EQ(logicalLibraryName, tape.logicalLibraryName);
+    ASSERT_EQ(tapePoolName, tape.tapePoolName);
+    ASSERT_EQ(encryptionKey, tape.encryptionKey);
+    ASSERT_EQ(capacityInBytes, tape.capacityInBytes);
+    ASSERT_TRUE(disabledValue == tape.disabled);
+    ASSERT_TRUE(fullValue == tape.full);
+    ASSERT_FALSE(tape.lbp);
+    ASSERT_EQ(createTapeComment, tape.comment);
+    ASSERT_FALSE(tape.labelLog);
+    ASSERT_FALSE(tape.lastReadLog);
+    ASSERT_TRUE((bool)tape.lastWriteLog);
+    ASSERT_EQ(tapeDrive, tape.lastWriteLog.value().drive);
+
+    const common::dataStructures::EntryLog creationLog = tape.creationLog;
+    ASSERT_EQ(m_cliSI.username, creationLog.username);
+    ASSERT_EQ(m_cliSI.host, creationLog.host);
+
+    const common::dataStructures::EntryLog lastModificationLog =
+      tape.lastModificationLog;
+    ASSERT_EQ(creationLog, lastModificationLog);
+  }
+
+  m_catalogue->setTapeFull(m_cliSI, vid1, true);
+  ASSERT_THROW(m_catalogue->reclaimTape(m_cliSI, vid1), exception::UserError);
+}
+
 } // namespace unitTests
diff --git a/catalogue/OracleCatalogue.cpp b/catalogue/OracleCatalogue.cpp
index 2844d7e717..5f879826e0 100644
--- a/catalogue/OracleCatalogue.cpp
+++ b/catalogue/OracleCatalogue.cpp
@@ -21,6 +21,7 @@
 #include "common/exception/Exception.hpp"
 #include "common/make_unique.hpp"
 #include "common/utils/utils.hpp"
+#include "rdbms/AutoRollback.hpp"
 #include "rdbms/ConnFactoryFactory.hpp"
 
 namespace cta {
diff --git a/catalogue/OracleCatalogue.hpp b/catalogue/OracleCatalogue.hpp
index 89dbbb45a9..f60039125a 100644
--- a/catalogue/OracleCatalogue.hpp
+++ b/catalogue/OracleCatalogue.hpp
@@ -49,7 +49,7 @@ public:
   /**
    * Destructor.
    */
-  virtual ~OracleCatalogue() override;
+  ~OracleCatalogue() override;
 
   /**
    * Deletes the specified archive file and its associated tape copies from the
@@ -64,7 +64,7 @@ public:
    * @return The metadata of the deleted archive file including the metadata of
    * the associated and also deleted tape copies.
    */
-  virtual common::dataStructures::ArchiveFile deleteArchiveFile(const std::string &diskInstanceName,
+  common::dataStructures::ArchiveFile deleteArchiveFile(const std::string &diskInstanceName,
     const uint64_t archiveFileId) override;
 
   /**
@@ -79,7 +79,7 @@ public:
    * @return A unique archive ID that can be used by a new archive file within
    * the catalogue.
    */
-  virtual uint64_t getNextArchiveFileId(rdbms::Conn &conn) override;
+  uint64_t getNextArchiveFileId(rdbms::Conn &conn) override;
 
   /**
    * Selects the specified tape within the Tape table for update.
@@ -91,7 +91,7 @@ public:
    * @param conn The database connection.
    * @param vid The volume identifier of the tape.
    */
-  virtual common::dataStructures::Tape selectTapeForUpdate(rdbms::Conn &conn, const std::string &vid) override;
+  common::dataStructures::Tape selectTapeForUpdate(rdbms::Conn &conn, const std::string &vid) override;
 
 }; // class OracleCatalogue
 
diff --git a/catalogue/RdbmsCatalogue.cpp b/catalogue/RdbmsCatalogue.cpp
index 5c50f293a7..af75e7f2f7 100644
--- a/catalogue/RdbmsCatalogue.cpp
+++ b/catalogue/RdbmsCatalogue.cpp
@@ -1496,6 +1496,19 @@ void RdbmsCatalogue::deleteTape(const std::string &vid) {
 // getTapes
 //------------------------------------------------------------------------------
 std::list<common::dataStructures::Tape> RdbmsCatalogue::getTapes(const TapeSearchCriteria &searchCriteria) const {
+  try {
+    auto conn = m_connPool.getConn();
+    return getTapes(*conn, searchCriteria);
+  } catch(exception::Exception &ex) {
+    throw exception::Exception(std::string(__FUNCTION__) + " failed: " + ex.getMessage().str());
+  }
+}
+
+//------------------------------------------------------------------------------
+// getTapes
+//------------------------------------------------------------------------------
+std::list<common::dataStructures::Tape> RdbmsCatalogue::getTapes(rdbms::Conn &conn,
+  const TapeSearchCriteria &searchCriteria) const {
   try {
     std::list<common::dataStructures::Tape> tapes;
     std::string sql =
@@ -1578,8 +1591,7 @@ std::list<common::dataStructures::Tape> RdbmsCatalogue::getTapes(const TapeSearc
       sql += " LBP_IS_ON = :LBP_IS_ON";
     }
 
-    auto conn = m_connPool.getConn();
-    auto stmt = conn->createStmt(sql, rdbms::Stmt::AutocommitMode::OFF);
+    auto stmt = conn.createStmt(sql, rdbms::Stmt::AutocommitMode::OFF);
 
     if(searchCriteria.vid) stmt->bindString(":VID", searchCriteria.vid.value());
     if(searchCriteria.logicalLibrary) stmt->bindString(":LOGICAL_LIBRARY_NAME", searchCriteria.logicalLibrary.value());
@@ -1729,6 +1741,63 @@ common::dataStructures::VidToTapeMap RdbmsCatalogue::getTapesByVid(const std::se
   }
 }
 
+//------------------------------------------------------------------------------
+// reclaimTape
+//------------------------------------------------------------------------------
+void RdbmsCatalogue::reclaimTape(const common::dataStructures::SecurityIdentity &cliIdentity, const std::string &vid) {
+  try {
+    const time_t now = time(nullptr);
+    const char *const sql =
+      "UPDATE TAPE SET "
+        "LAST_FSEQ = 0, "
+        "IS_FULL = 0,"
+        "LAST_UPDATE_USER_NAME = :LAST_UPDATE_USER_NAME,"
+        "LAST_UPDATE_HOST_NAME = :LAST_UPDATE_HOST_NAME,"
+        "LAST_UPDATE_TIME = :LAST_UPDATE_TIME "
+      "WHERE "
+        "VID = :UPDATE_VID AND "
+        "IS_FULL != 0 AND "
+        "NOT EXISTS (SELECT VID FROM TAPE_FILE WHERE VID = :SELECT_VID)";
+    auto conn = m_connPool.getConn();
+    auto stmt = conn->createStmt(sql, rdbms::Stmt::AutocommitMode::ON);
+    stmt->bindString(":LAST_UPDATE_USER_NAME", cliIdentity.username);
+    stmt->bindString(":LAST_UPDATE_HOST_NAME", cliIdentity.host);
+    stmt->bindUint64(":LAST_UPDATE_TIME", now);
+    stmt->bindString(":UPDATE_VID", vid);
+    stmt->bindString(":SELECT_VID", vid);
+    stmt->executeNonQuery();
+
+    // If the update failed due to a user error
+    if(0 == stmt->getNbAffectedRows()) {
+      // Try to determine the user error
+      //
+      // Please note that this is a best effort diagnosis because there is no
+      // lock on the database to prevent other concurrent updates from taking
+      // place on the TAPE and TAPE_FILE tables
+      TapeSearchCriteria searchCriteria;
+      searchCriteria.vid = vid;
+      const auto tapes = getTapes(*conn, searchCriteria);
+
+      if(tapes.empty()) {
+        throw exception::UserError(std::string("Cannot reclaim tape ") + vid + " because it does not exist");
+      } else {
+        if(!tapes.front().full) {
+          throw exception::UserError(std::string("Cannot reclaim tape ") + vid + " because it is not FULL");
+        } else {
+          throw exception::UserError(std::string("Cannot reclaim tape ") + vid + " because there is at least one tape"
+            " file in the catalogue that is on the tape");
+        }
+      }
+    }
+
+    conn->commit();
+  } catch(exception::UserError &) {
+    throw;
+  } catch (exception::Exception &ex) {
+    throw exception::Exception(std::string(__FUNCTION__) + " failed: " + ex.getMessage().str());
+  }
+}
+
 //------------------------------------------------------------------------------
 // getTapeLogFromRset
 //------------------------------------------------------------------------------
@@ -1762,13 +1831,6 @@ optional<common::dataStructures::TapeLog> RdbmsCatalogue::getTapeLogFromRset(con
   }
 }
 
-//------------------------------------------------------------------------------
-// reclaimTape
-//------------------------------------------------------------------------------
-void RdbmsCatalogue::reclaimTape(const common::dataStructures::SecurityIdentity &cliIdentity, const std::string &vid) {
-  throw exception::Exception(std::string(__FUNCTION__) + " not implemented");
-}
-
 //------------------------------------------------------------------------------
 // modifyTapeLogicalLibraryName
 //------------------------------------------------------------------------------
diff --git a/catalogue/RdbmsCatalogue.hpp b/catalogue/RdbmsCatalogue.hpp
index a858fb57ab..674348f4d1 100644
--- a/catalogue/RdbmsCatalogue.hpp
+++ b/catalogue/RdbmsCatalogue.hpp
@@ -67,7 +67,7 @@ public:
   /**
    * Destructor.
    */
-  virtual ~RdbmsCatalogue() override;
+  ~RdbmsCatalogue() override;
 
   /////////////////////////////////////////////////////////////////////
   // START OF METHODS DIRECTLY INVOLVED IN DATA TRANSFER AND SCHEDULING
@@ -80,7 +80,7 @@ public:
    * @param drive The name of tape drive that was used to label the tape.
    * @param lbpIsOn Set to true if Logical Block Protection (LBP) was enabled.
    */
-  virtual void tapeLabelled(const std::string &vid, const std::string &drive, const bool lbpIsOn) override;
+  void tapeLabelled(const std::string &vid, const std::string &drive, const bool lbpIsOn) override;
 
   /**
    * Prepares the catalogue for a new archive file and returns the information
@@ -97,7 +97,7 @@ public:
    * archiving the file.
    * @return The information required to queue the associated archive request.
    */
-  virtual common::dataStructures::ArchiveFileQueueCriteria prepareForNewFile(
+  common::dataStructures::ArchiveFileQueueCriteria prepareForNewFile(
     const std::string &diskInstanceName,
     const std::string &storageClassName,
     const common::dataStructures::UserIdentity &user) override;
@@ -109,14 +109,14 @@ public:
    *
    * @param logicalLibraryName The name of the logical library.
    */
-  virtual std::list<TapeForWriting> getTapesForWriting(const std::string &logicalLibraryName) const override;
+  std::list<TapeForWriting> getTapesForWriting(const std::string &logicalLibraryName) const override;
 
   /**
    * Notifies the catalogue that a file has been written to tape.
    *
    * @param event The tape file written event.
    */
-  virtual void fileWrittenToTape(const TapeFileWritten &event) override;
+  void fileWrittenToTape(const TapeFileWritten &event) override;
 
   /**
    * Notifies the CTA catalogue that the specified tape has been mounted in
@@ -128,7 +128,7 @@ public:
    * @param vid The volume identifier of the tape.
    * @param drive The name of the drive where the tape was mounted.
    */
-  virtual void tapeMountedForArchive(const std::string &vid, const std::string &drive) override;
+  void tapeMountedForArchive(const std::string &vid, const std::string &drive) override;
 
   /**
    * Prepares for a file retrieval by returning the information required to
@@ -143,7 +143,7 @@ public:
    *
    * @return The information required to queue the associated retrieve request(s).
    */
-  virtual common::dataStructures::RetrieveFileQueueCriteria prepareToRetrieveFile(
+  common::dataStructures::RetrieveFileQueueCriteria prepareToRetrieveFile(
     const std::string &instanceName,
     const uint64_t archiveFileId,
     const common::dataStructures::UserIdentity &user) override;
@@ -158,7 +158,7 @@ public:
    * @param vid The volume identifier of the tape.
    * @param drive The name of the drive where the tape was mounted.
    */
-  virtual void tapeMountedForRetrieve(const std::string &vid, const std::string &drive) override;
+  void tapeMountedForRetrieve(const std::string &vid, const std::string &drive) override;
 
   /**
    * This method notifies the CTA catalogue that there is no more free space on
@@ -166,23 +166,23 @@ public:
    *
    * @param vid The volume identifier of the tape.
    */
-  virtual void noSpaceLeftOnTape(const std::string &vid) override;
+  void noSpaceLeftOnTape(const std::string &vid) override;
 
   ///////////////////////////////////////////////////////////////////
   // END OF METHODS DIRECTLY INVOLVED IN DATA TRANSFER AND SCHEDULING
   ///////////////////////////////////////////////////////////////////
 
-  virtual void createBootstrapAdminAndHostNoAuth(const common::dataStructures::SecurityIdentity &cliIdentity, const std::string &username, const std::string &hostName, const std::string &comment) override;
+  void createBootstrapAdminAndHostNoAuth(const common::dataStructures::SecurityIdentity &cliIdentity, const std::string &username, const std::string &hostName, const std::string &comment) override;
 
-  virtual void createAdminUser(const common::dataStructures::SecurityIdentity &cliIdentity, const std::string &username, const std::string &comment) override;
-  virtual void deleteAdminUser(const std::string &username) override;
-  virtual std::list<common::dataStructures::AdminUser> getAdminUsers() const override;
-  virtual void modifyAdminUserComment(const common::dataStructures::SecurityIdentity &cliIdentity, const std::string &username, const std::string &comment) override;
+  void createAdminUser(const common::dataStructures::SecurityIdentity &cliIdentity, const std::string &username, const std::string &comment) override;
+  void deleteAdminUser(const std::string &username) override;
+  std::list<common::dataStructures::AdminUser> getAdminUsers() const override;
+  void modifyAdminUserComment(const common::dataStructures::SecurityIdentity &cliIdentity, const std::string &username, const std::string &comment) override;
 
-  virtual void createAdminHost(const common::dataStructures::SecurityIdentity &cliIdentity, const std::string &hostName, const std::string &comment) override;
-  virtual void deleteAdminHost(const std::string &hostName) override;
-  virtual std::list<common::dataStructures::AdminHost> getAdminHosts() const override;
-  virtual void modifyAdminHostComment(const common::dataStructures::SecurityIdentity &cliIdentity, const std::string &hostName, const std::string &comment) override;
+  void createAdminHost(const common::dataStructures::SecurityIdentity &cliIdentity, const std::string &hostName, const std::string &comment) override;
+  void deleteAdminHost(const std::string &hostName) override;
+  std::list<common::dataStructures::AdminHost> getAdminHosts() const override;
+  void modifyAdminHostComment(const common::dataStructures::SecurityIdentity &cliIdentity, const std::string &hostName, const std::string &comment) override;
 
   /**
    * Creates the specified storage class.
@@ -190,7 +190,7 @@ public:
    * @param cliIdentity The identity of the command-line interface.
    * @param storageClass The storage class.
    */
-  virtual void createStorageClass(
+  void createStorageClass(
     const common::dataStructures::SecurityIdentity &cliIdentity,
     const common::dataStructures::StorageClass &storageClass) override;
 
@@ -202,20 +202,20 @@ public:
    * @param stoargeClassName The name of the storage class which is only
    * guaranteed to be unique within its disk isntance.
    */
-  virtual void deleteStorageClass(const std::string &diskInstanceName, const std::string &storageClassName) override;
+  void deleteStorageClass(const std::string &diskInstanceName, const std::string &storageClassName) override;
 
-  virtual std::list<common::dataStructures::StorageClass> getStorageClasses() const override;
-  virtual void modifyStorageClassNbCopies(const common::dataStructures::SecurityIdentity &cliIdentity, const std::string &instanceName, const std::string &name, const uint64_t nbCopies) override;
-  virtual void modifyStorageClassComment(const common::dataStructures::SecurityIdentity &cliIdentity, const std::string &instanceName, const std::string &name, const std::string &comment) override;
+  std::list<common::dataStructures::StorageClass> getStorageClasses() const override;
+  void modifyStorageClassNbCopies(const common::dataStructures::SecurityIdentity &cliIdentity, const std::string &instanceName, const std::string &name, const uint64_t nbCopies) override;
+  void modifyStorageClassComment(const common::dataStructures::SecurityIdentity &cliIdentity, const std::string &instanceName, const std::string &name, const std::string &comment) override;
 
-  virtual void createTapePool(const common::dataStructures::SecurityIdentity &cliIdentity, const std::string &name, const uint64_t nbPartialTapes, const bool encryptionValue, const std::string &comment) override;
-  virtual void deleteTapePool(const std::string &name) override;
-  virtual std::list<common::dataStructures::TapePool> getTapePools() const override;
-  virtual void modifyTapePoolNbPartialTapes(const common::dataStructures::SecurityIdentity &cliIdentity, const std::string &name, const uint64_t nbPartialTapes) override;
-  virtual void modifyTapePoolComment(const common::dataStructures::SecurityIdentity &cliIdentity, const std::string &name, const std::string &comment) override;
-  virtual void setTapePoolEncryption(const common::dataStructures::SecurityIdentity &cliIdentity, const std::string &name, const bool encryptionValue) override;
+  void createTapePool(const common::dataStructures::SecurityIdentity &cliIdentity, const std::string &name, const uint64_t nbPartialTapes, const bool encryptionValue, const std::string &comment) override;
+  void deleteTapePool(const std::string &name) override;
+  std::list<common::dataStructures::TapePool> getTapePools() const override;
+  void modifyTapePoolNbPartialTapes(const common::dataStructures::SecurityIdentity &cliIdentity, const std::string &name, const uint64_t nbPartialTapes) override;
+  void modifyTapePoolComment(const common::dataStructures::SecurityIdentity &cliIdentity, const std::string &name, const std::string &comment) override;
+  void setTapePoolEncryption(const common::dataStructures::SecurityIdentity &cliIdentity, const std::string &name, const bool encryptionValue) override;
 
-  virtual void createArchiveRoute(
+  void createArchiveRoute(
     const common::dataStructures::SecurityIdentity &cliIdentity,
     const std::string &diskInstanceName,
     const std::string &storageClassName,
@@ -233,19 +233,19 @@ public:
    * guaranteed to be unique within its disk instance.
    * @param copyNb The copy number of the tape file.
    */
-  virtual void deleteArchiveRoute(
+  void deleteArchiveRoute(
     const std::string &diskInstanceName,
     const std::string &storageClassName, 
     const uint64_t copyNb) override;
 
-  virtual std::list<common::dataStructures::ArchiveRoute> getArchiveRoutes() const override;
-  virtual void modifyArchiveRouteTapePoolName(const common::dataStructures::SecurityIdentity &cliIdentity, const std::string &instanceName, const std::string &storageClassName, const uint64_t copyNb, const std::string &tapePoolName) override;
-  virtual void modifyArchiveRouteComment(const common::dataStructures::SecurityIdentity &cliIdentity, const std::string &instanceName, const std::string &storageClassName, const uint64_t copyNb, const std::string &comment) override;
+  std::list<common::dataStructures::ArchiveRoute> getArchiveRoutes() const override;
+  void modifyArchiveRouteTapePoolName(const common::dataStructures::SecurityIdentity &cliIdentity, const std::string &instanceName, const std::string &storageClassName, const uint64_t copyNb, const std::string &tapePoolName) override;
+  void modifyArchiveRouteComment(const common::dataStructures::SecurityIdentity &cliIdentity, const std::string &instanceName, const std::string &storageClassName, const uint64_t copyNb, const std::string &comment) override;
 
-  virtual void createLogicalLibrary(const common::dataStructures::SecurityIdentity &cliIdentity, const std::string &name, const std::string &comment) override;
-  virtual void deleteLogicalLibrary(const std::string &name) override;
-  virtual std::list<common::dataStructures::LogicalLibrary> getLogicalLibraries() const override;
-  virtual void modifyLogicalLibraryComment(const common::dataStructures::SecurityIdentity &cliIdentity, const std::string &name, const std::string &comment) override;
+  void createLogicalLibrary(const common::dataStructures::SecurityIdentity &cliIdentity, const std::string &name, const std::string &comment) override;
+  void deleteLogicalLibrary(const std::string &name) override;
+  std::list<common::dataStructures::LogicalLibrary> getLogicalLibraries() const override;
+  void modifyLogicalLibraryComment(const common::dataStructures::SecurityIdentity &cliIdentity, const std::string &name, const std::string &comment) override;
 
   /**
    * Creates a tape.
@@ -254,7 +254,7 @@ public:
    * optional parameter should either have a non-empty string value or no value
    * at all.  Empty strings are prohibited.
    */
-  virtual void createTape(
+  void createTape(
     const common::dataStructures::SecurityIdentity &cliIdentity,
     const std::string &vid,
     const std::string &logicalLibraryName,
@@ -265,7 +265,7 @@ public:
     const bool full,
     const std::string &comment) override;
 
-  virtual void deleteTape(const std::string &vid) override;
+  void deleteTape(const std::string &vid) override;
 
   /**
    * Returns the list of tapes that meet the specified search criteria.
@@ -273,7 +273,7 @@ public:
    * @param searchCriteria The search criteria.
    * @return The list of tapes.
    */
-  virtual std::list<common::dataStructures::Tape> getTapes(const TapeSearchCriteria &searchCriteria) const override;
+  std::list<common::dataStructures::Tape> getTapes(const TapeSearchCriteria &searchCriteria) const override;
 
   /**
    * Returns the tapes with the specified volume identifiers.
@@ -284,13 +284,27 @@ public:
    * @param vids The tape volume identifiers (VIDs).
    * @return Map from tape volume identifier to tape.
    */
-  virtual common::dataStructures::VidToTapeMap getTapesByVid(const std::set<std::string> &vids) const override;
+  common::dataStructures::VidToTapeMap getTapesByVid(const std::set<std::string> &vids) const override;
 
-  virtual void reclaimTape(const common::dataStructures::SecurityIdentity &cliIdentity, const std::string &vid) override;
-  virtual void modifyTapeLogicalLibraryName(const common::dataStructures::SecurityIdentity &cliIdentity, const std::string &vid, const std::string &logicalLibraryName) override;
-  virtual void modifyTapeTapePoolName(const common::dataStructures::SecurityIdentity &cliIdentity, const std::string &vid, const std::string &tapePoolName) override;
-  virtual void modifyTapeCapacityInBytes(const common::dataStructures::SecurityIdentity &cliIdentity, const std::string &vid, const uint64_t capacityInBytes) override;
-  virtual void modifyTapeEncryptionKey(const common::dataStructures::SecurityIdentity &cliIdentity, const std::string &vid, const std::string &encryptionKey) override;
+  /**
+   * Reclaims the specified tape.
+   *
+   * This method will throw an exception if the specified tape does not exist.
+   *
+   * This method will throw an exception if the specified tape is not FULL.
+   *
+   * This method will throw an exception if there is still at least one tape
+   * file recorded in the cataligue as being on the specified tape.
+   *
+   * @param cliIdentity The user of the command-line tool.
+   * @param vid The volume identifier of the tape to be reclaimed.
+   */
+  void reclaimTape(const common::dataStructures::SecurityIdentity &cliIdentity, const std::string &vid) override;
+
+  void modifyTapeLogicalLibraryName(const common::dataStructures::SecurityIdentity &cliIdentity, const std::string &vid, const std::string &logicalLibraryName) override;
+  void modifyTapeTapePoolName(const common::dataStructures::SecurityIdentity &cliIdentity, const std::string &vid, const std::string &tapePoolName) override;
+  void modifyTapeCapacityInBytes(const common::dataStructures::SecurityIdentity &cliIdentity, const std::string &vid, const uint64_t capacityInBytes) override;
+  void modifyTapeEncryptionKey(const common::dataStructures::SecurityIdentity &cliIdentity, const std::string &vid, const std::string &encryptionKey) override;
 
   /**
    * Sets the full status of the specified tape.
@@ -302,17 +316,17 @@ public:
    * @param vid The volume identifier of the tape to be marked as full.
    * @param fullValue Set to true if the tape is full.
    */
-  virtual void setTapeFull(const common::dataStructures::SecurityIdentity &cliIdentity, const std::string &vid, const bool fullValue) override;
+  void setTapeFull(const common::dataStructures::SecurityIdentity &cliIdentity, const std::string &vid, const bool fullValue) override;
 
-  virtual void setTapeDisabled(const common::dataStructures::SecurityIdentity &cliIdentity, const std::string &vid, const bool disabledValue) override;
-  virtual void modifyTapeComment(const common::dataStructures::SecurityIdentity &cliIdentity, const std::string &vid, const std::string &comment) override;
+  void setTapeDisabled(const common::dataStructures::SecurityIdentity &cliIdentity, const std::string &vid, const bool disabledValue) override;
+  void modifyTapeComment(const common::dataStructures::SecurityIdentity &cliIdentity, const std::string &vid, const std::string &comment) override;
 
-  virtual void modifyRequesterMountRulePolicy(const common::dataStructures::SecurityIdentity &cliIdentity, const std::string &instanceName, const std::string &requesterName, const std::string &mountPolicy) override;
-  virtual void modifyRequesteMountRuleComment(const common::dataStructures::SecurityIdentity &cliIdentity, const std::string &instanceName, const std::string &requesterName, const std::string &comment) override;
-  virtual void modifyRequesterGroupMountRulePolicy(const common::dataStructures::SecurityIdentity &cliIdentity, const std::string &instanceName, const std::string &requesterGroupName, const std::string &mountPolicy) override;
-  virtual void modifyRequesterGroupMountRuleComment(const common::dataStructures::SecurityIdentity &cliIdentity, const std::string &instanceName, const std::string &requesterGroupName, const std::string &comment) override;
+  void modifyRequesterMountRulePolicy(const common::dataStructures::SecurityIdentity &cliIdentity, const std::string &instanceName, const std::string &requesterName, const std::string &mountPolicy) override;
+  void modifyRequesteMountRuleComment(const common::dataStructures::SecurityIdentity &cliIdentity, const std::string &instanceName, const std::string &requesterName, const std::string &comment) override;
+  void modifyRequesterGroupMountRulePolicy(const common::dataStructures::SecurityIdentity &cliIdentity, const std::string &instanceName, const std::string &requesterGroupName, const std::string &mountPolicy) override;
+  void modifyRequesterGroupMountRuleComment(const common::dataStructures::SecurityIdentity &cliIdentity, const std::string &instanceName, const std::string &requesterGroupName, const std::string &comment) override;
 
-  virtual void createMountPolicy(
+  void createMountPolicy(
     const common::dataStructures::SecurityIdentity &cliIdentity,
     const std::string &name,
     const uint64_t archivePriority,
@@ -327,14 +341,14 @@ public:
    *
    * @return the list of all existing mount policies.
    */
-  virtual std::list<common::dataStructures::MountPolicy> getMountPolicies() const override;
+  std::list<common::dataStructures::MountPolicy> getMountPolicies() const override;
 
   /**
    * Deletes the specified mount policy.
    *
    * @param name The name of the mount policy.
    */
-  virtual void deleteMountPolicy(const std::string &name) override;
+  void deleteMountPolicy(const std::string &name) override;
 
   /**
    * Creates the rule that the specified mount policy will be used for the
@@ -351,7 +365,7 @@ public:
    * be unique within its disk instance.
    * @param comment Comment.
    */
-  virtual void createRequesterMountRule(
+  void createRequesterMountRule(
     const common::dataStructures::SecurityIdentity &cliIdentity,
     const std::string &mountPolicyName,
     const std::string &diskInstance,
@@ -365,7 +379,7 @@ public:
    * @return the rules that specify which mount policy is be used for which
    * requester.
    */
-  virtual std::list<common::dataStructures::RequesterMountRule> getRequesterMountRules() const override;
+  std::list<common::dataStructures::RequesterMountRule> getRequesterMountRules() const override;
 
   /**
    * Deletes the specified mount rule.
@@ -375,7 +389,7 @@ public:
    * @param requesterName The name of the requester which is only guaranteed to
    * be unique within its disk instance.
    */
-  virtual void deleteRequesterMountRule(const std::string &diskInstanceName, const std::string &requesterName) override;
+  void deleteRequesterMountRule(const std::string &diskInstanceName, const std::string &requesterName) override;
 
   /**
    * Creates the rule that the specified mount policy will be used for the
@@ -392,7 +406,7 @@ public:
    * guarantted to be unique within its disk instance.
    * @param comment Comment.
    */
-  virtual void createRequesterGroupMountRule(
+  void createRequesterGroupMountRule(
     const common::dataStructures::SecurityIdentity &cliIdentity,
     const std::string &mountPolicyName,
     const std::string &diskInstanceName,
@@ -406,7 +420,7 @@ public:
    * @return the rules that specify which mount policy is be used for which
    * requester group.
    */
-  virtual std::list<common::dataStructures::RequesterGroupMountRule> getRequesterGroupMountRules() const override;
+  std::list<common::dataStructures::RequesterGroupMountRule> getRequesterGroupMountRules() const override;
 
   /**
    * Deletes the specified mount rule.
@@ -416,27 +430,27 @@ public:
    * @param requesterGroupName The name of the requester group which is only
    * guaranteed to be unique within its disk instance.
    */
-  virtual void deleteRequesterGroupMountRule(
+  void deleteRequesterGroupMountRule(
     const std::string &diskInstanceName,
     const std::string &requesterGroupName) override;
 
-  virtual void modifyMountPolicyArchivePriority(const common::dataStructures::SecurityIdentity &cliIdentity, const std::string &name, const uint64_t archivePriority) override;
-  virtual void modifyMountPolicyArchiveMinRequestAge(const common::dataStructures::SecurityIdentity &cliIdentity, const std::string &name, const uint64_t minArchiveRequestAge) override;
-  virtual void modifyMountPolicyRetrievePriority(const common::dataStructures::SecurityIdentity &cliIdentity, const std::string &name, const uint64_t retrievePriority) override;
-  virtual void modifyMountPolicyRetrieveMinRequestAge(const common::dataStructures::SecurityIdentity &cliIdentity, const std::string &name, const uint64_t minRetrieveRequestAge) override;
-  virtual void modifyMountPolicyMaxDrivesAllowed(const common::dataStructures::SecurityIdentity &cliIdentity, const std::string &name, const uint64_t maxDrivesAllowed) override;
-  virtual void modifyMountPolicyComment(const common::dataStructures::SecurityIdentity &cliIdentity, const std::string &name, const std::string &comment) override;
-
-  virtual void createDedication(const common::dataStructures::SecurityIdentity &cliIdentity, const std::string &drivename, const common::dataStructures::DedicationType dedicationType,
-   const optional<std::string> &tag, const optional<std::string> &vid, const uint64_t fromTimestamp, const uint64_t untilTimestamp,const std::string &comment) override;
-  virtual void deleteDedication(const std::string &drivename) override;
-  virtual std::list<common::dataStructures::Dedication> getDedications() const override;
-  virtual void modifyDedicationType(const common::dataStructures::SecurityIdentity &cliIdentity, const std::string &drivename, const common::dataStructures::DedicationType dedicationType) override;
-  virtual void modifyDedicationTag(const common::dataStructures::SecurityIdentity &cliIdentity, const std::string &drivename, const optional<std::string> &tag) override;
-  virtual void modifyDedicationVid(const common::dataStructures::SecurityIdentity &cliIdentity, const std::string &drivename, const optional<std::string> &vid) override;
-  virtual void modifyDedicationFrom(const common::dataStructures::SecurityIdentity &cliIdentity, const std::string &drivename, const uint64_t fromTimestamp) override;
-  virtual void modifyDedicationUntil(const common::dataStructures::SecurityIdentity &cliIdentity, const std::string &drivename, const uint64_t untilTimestamp) override;
-  virtual void modifyDedicationComment(const common::dataStructures::SecurityIdentity &cliIdentity, const std::string &drivename, const std::string &comment) override;
+  void modifyMountPolicyArchivePriority(const common::dataStructures::SecurityIdentity &cliIdentity, const std::string &name, const uint64_t archivePriority) override;
+  void modifyMountPolicyArchiveMinRequestAge(const common::dataStructures::SecurityIdentity &cliIdentity, const std::string &name, const uint64_t minArchiveRequestAge) override;
+  void modifyMountPolicyRetrievePriority(const common::dataStructures::SecurityIdentity &cliIdentity, const std::string &name, const uint64_t retrievePriority) override;
+  void modifyMountPolicyRetrieveMinRequestAge(const common::dataStructures::SecurityIdentity &cliIdentity, const std::string &name, const uint64_t minRetrieveRequestAge) override;
+  void modifyMountPolicyMaxDrivesAllowed(const common::dataStructures::SecurityIdentity &cliIdentity, const std::string &name, const uint64_t maxDrivesAllowed) override;
+  void modifyMountPolicyComment(const common::dataStructures::SecurityIdentity &cliIdentity, const std::string &name, const std::string &comment) override;
+
+  void createDedication(const common::dataStructures::SecurityIdentity &cliIdentity, const std::string &drivename, const common::dataStructures::DedicationType dedicationType,
+    const optional<std::string> &tag, const optional<std::string> &vid, const uint64_t fromTimestamp, const uint64_t untilTimestamp,const std::string &comment) override;
+  void deleteDedication(const std::string &drivename) override;
+  std::list<common::dataStructures::Dedication> getDedications() const override;
+  void modifyDedicationType(const common::dataStructures::SecurityIdentity &cliIdentity, const std::string &drivename, const common::dataStructures::DedicationType dedicationType) override;
+  void modifyDedicationTag(const common::dataStructures::SecurityIdentity &cliIdentity, const std::string &drivename, const optional<std::string> &tag) override;
+  void modifyDedicationVid(const common::dataStructures::SecurityIdentity &cliIdentity, const std::string &drivename, const optional<std::string> &vid) override;
+  void modifyDedicationFrom(const common::dataStructures::SecurityIdentity &cliIdentity, const std::string &drivename, const uint64_t fromTimestamp) override;
+  void modifyDedicationUntil(const common::dataStructures::SecurityIdentity &cliIdentity, const std::string &drivename, const uint64_t untilTimestamp) override;
+  void modifyDedicationComment(const common::dataStructures::SecurityIdentity &cliIdentity, const std::string &drivename, const std::string &comment) override;
 
   /**
    * Returns an iterator over the list of archive files that meet the specified
@@ -453,7 +467,7 @@ public:
    * This parameter must be set to a value equal to or greater than 1.
    * @return An iterator over the list of archive files.
    */
-  virtual std::unique_ptr<ArchiveFileItor> getArchiveFileItor(const ArchiveFileSearchCriteria &searchCriteria,
+  std::unique_ptr<ArchiveFileItor> getArchiveFileItor(const ArchiveFileSearchCriteria &searchCriteria,
     const uint64_t nbArchiveFilesToPrefetch) const override;
 
   /**
@@ -463,7 +477,7 @@ public:
    * @param searchCriteria The search criteria.
    * @return The summary.
    */
-  virtual common::dataStructures::ArchiveFileSummary getArchiveFileSummary(
+  common::dataStructures::ArchiveFileSummary getArchiveFileSummary(
     const ArchiveFileSearchCriteria &searchCriteria) const override;
 
   /**
@@ -475,7 +489,7 @@ public:
    * @param id The unique identifier of the archive file.
    * @return The archive file.
    */
-  virtual common::dataStructures::ArchiveFile getArchiveFileById(const uint64_t id) override;
+  common::dataStructures::ArchiveFile getArchiveFileById(const uint64_t id) override;
 
   /**
    * Returns true if the specified user running the CTA command-line tool on
@@ -486,7 +500,7 @@ public:
    * @return True if the specified user running the CTA command-line tool on
    * the specified host has administrator privileges.
    */
-  virtual bool isAdmin(const common::dataStructures::SecurityIdentity &cliIdentity) const override;
+  bool isAdmin(const common::dataStructures::SecurityIdentity &cliIdentity) const override;
 
 protected:
 
@@ -552,6 +566,15 @@ protected:
    */
   bool tapeExists(rdbms::Conn &conn, const std::string &vid) const;
 
+  /**
+   * Returns the list of tapes that meet the specified search criteria.
+   *
+   * @param conn The database connection.
+   * @param searchCriteria The search criteria.
+   * @return The list of tapes.
+   */
+  std::list<common::dataStructures::Tape> getTapes(rdbms::Conn &conn, const TapeSearchCriteria &searchCriteria) const;
+
   /**
    * Returns true if the specified logical library exists.
    *
@@ -813,17 +836,17 @@ protected:
     /**
      * Destructor.
      */
-    virtual ~ArchiveFileItorImpl() override;
+    ~ArchiveFileItorImpl() override;
 
     /**
      * Returns true if a call to next would return another archive file.
      */
-    virtual bool hasMore() const override;
+    bool hasMore() const override;
 
     /**
      * Returns the next archive or throws an exception if there isn't one.
      */
-    virtual common::dataStructures::ArchiveFile next() override;
+    common::dataStructures::ArchiveFile next() override;
 
   private:
 
@@ -882,7 +905,7 @@ protected:
    * @return The mapping from tape copy to tape pool for the specified storage
    * class.
    */
-  virtual common::dataStructures::TapeCopyToPoolMap getTapeCopyToPoolMap(
+  common::dataStructures::TapeCopyToPoolMap getTapeCopyToPoolMap(
     rdbms::Conn &conn,
     const std::string &diskInstanceName,
     const std::string &storageClassName) const;
diff --git a/catalogue/SqliteCatalogue.hpp b/catalogue/SqliteCatalogue.hpp
index 8d330bf18a..db1cb7fee6 100644
--- a/catalogue/SqliteCatalogue.hpp
+++ b/catalogue/SqliteCatalogue.hpp
@@ -44,7 +44,7 @@ public:
   /**
    * Destructor.
    */
-  virtual ~SqliteCatalogue() override;
+  ~SqliteCatalogue() override;
 
   /**
    * Deletes the specified archive file and its associated tape copies from the
@@ -59,7 +59,7 @@ public:
    * @return The metadata of the deleted archive file including the metadata of
    * the associated and also deleted tape copies.
    */
-  virtual common::dataStructures::ArchiveFile deleteArchiveFile(const std::string &diskInstanceName,
+  common::dataStructures::ArchiveFile deleteArchiveFile(const std::string &diskInstanceName,
     const uint64_t archiveFileId) override;
 
 protected:
-- 
GitLab