From 032e3261de3b941350c610cd3177a407ada2d53d Mon Sep 17 00:00:00 2001
From: Cedric CAFFY <cedric.caffy@hotmail.fr>
Date: Thu, 4 Apr 2019 04:29:50 -0400
Subject: [PATCH] Added a check while mounting a tape for writing : if some
 tape files located in the writing tape are not superseded by others, the
 write session will fail.

Note: The test CatalogueTest.exist_non_superseded_files_after_fseq is supposed to fail.
---
 catalogue/Catalogue.hpp                       |  17 ++
 catalogue/CatalogueRetryWrapper.hpp           |   9 +
 catalogue/CatalogueTest.cpp                   | 112 ++++++++++++
 catalogue/DummyCatalogue.hpp                  |   4 +-
 catalogue/RdbmsCatalogue.cpp                  |  82 +++++++++
 catalogue/RdbmsCatalogue.hpp                  |  12 +-
 scheduler/ArchiveMount.cpp                    |  10 ++
 scheduler/ArchiveMount.hpp                    |   8 +
 .../daemon/DataTransferSessionTest.cpp        | 159 ++++++++++++++++++
 .../daemon/TapeWriteSingleThread.cpp          |   3 +-
 10 files changed, 413 insertions(+), 3 deletions(-)

diff --git a/catalogue/Catalogue.hpp b/catalogue/Catalogue.hpp
index 318cdbbd28..f415ab968d 100644
--- a/catalogue/Catalogue.hpp
+++ b/catalogue/Catalogue.hpp
@@ -336,6 +336,14 @@ public:
    * @param vid The volume identifier of the tape to be reclaimed.
    */
   virtual void reclaimTape(const common::dataStructures::SecurityIdentity &admin, const std::string &vid) = 0;
+  
+  /**
+   * This method should ONLY be used for TESTS. It does exactly the same as the real reclaimTape but it does not verify the 
+   * SUPERSEDED_BY_VID and SUPERSEDED_BY_FSEQ attributes
+   * @param admin The administrator.
+   * @param vid The volume identifier of the tape to be reclaimed.
+   */
+  virtual void fakeReclaimTapeForTests(const common::dataStructures::SecurityIdentity& admin, const std::string &vid) = 0;
 
   virtual void modifyTapeMediaType(const common::dataStructures::SecurityIdentity &admin, const std::string &vid, const std::string &mediaType) = 0;
   virtual void modifyTapeVendor(const common::dataStructures::SecurityIdentity &admin, const std::string &vid, const std::string &vendor) = 0;
@@ -589,6 +597,15 @@ public:
    * @return True if the tape exists.
    */
   virtual bool tapeExists(const std::string &vid) const = 0;
+  
+  /**
+   * Returns true if non superseded files exist after fSeq in the tape where vid is passed in parameter
+   * 
+   * @param vid the vid of the tape to check if non superseded files exist after fSeq
+   * @param fSeq the fSeq after which we want to check if non superseded files exist
+   * @return true if non superseded files exist, false otherwise
+   */
+  virtual bool existNonSupersededFilesAfterFSeq(const std::string &vid, const uint64_t fSeq) const  = 0;
 
 }; // class Catalogue
 
diff --git a/catalogue/CatalogueRetryWrapper.hpp b/catalogue/CatalogueRetryWrapper.hpp
index e192baafca..4800ff60bb 100644
--- a/catalogue/CatalogueRetryWrapper.hpp
+++ b/catalogue/CatalogueRetryWrapper.hpp
@@ -224,6 +224,10 @@ public:
   void reclaimTape(const common::dataStructures::SecurityIdentity &admin, const std::string &vid) override {
     return retryOnLostConnection(m_log, [&]{return m_catalogue->reclaimTape(admin, vid);}, m_maxTriesToConnect);
   }
+  
+  void fakeReclaimTapeForTests(const common::dataStructures::SecurityIdentity &admin, const std::string &vid) override {
+    return retryOnLostConnection(m_log, [&]{return m_catalogue->fakeReclaimTapeForTests(admin, vid);}, m_maxTriesToConnect);
+  }
 
   void modifyTapeMediaType(const common::dataStructures::SecurityIdentity &admin, const std::string &vid, const std::string &mediaType) override {
     return retryOnLostConnection(m_log, [&]{return m_catalogue->modifyTapeMediaType(admin, vid, mediaType);}, m_maxTriesToConnect);
@@ -376,6 +380,11 @@ public:
   bool tapeExists(const std::string &vid) const override {
     return retryOnLostConnection(m_log, [&]{return m_catalogue->tapeExists(vid);}, m_maxTriesToConnect);
   }
+  
+  bool existNonSupersededFilesAfterFSeq(const std::string& vid, const uint64_t fSeq) const override {
+    return retryOnLostConnection(m_log,[&]{return m_catalogue->existNonSupersededFilesAfterFSeq(vid,fSeq);},m_maxTriesToConnect);
+  }
+
 
 protected:
 
diff --git a/catalogue/CatalogueTest.cpp b/catalogue/CatalogueTest.cpp
index ffd0827a4b..b090af865f 100644
--- a/catalogue/CatalogueTest.cpp
+++ b/catalogue/CatalogueTest.cpp
@@ -11512,6 +11512,118 @@ TEST_P(cta_catalogue_CatalogueTest, reclaimTape_full_lastFSeq_1_one_tape_file_su
   }
 }
 
+TEST_P(cta_catalogue_CatalogueTest, exist_non_superseded_files_after_fseq) {
+  using namespace cta;
+
+  const std::string diskInstanceName1 = "disk_instance_1";
+
+  ASSERT_TRUE(m_catalogue->getTapes().empty());
+
+  const std::string vid1 = "VID123";
+  const std::string vid2 = "VID234";
+  const std::string mediaType = "media_type";
+  const std::string vendor = "vendor";
+  const std::string logicalLibraryName = "logical_library_name";
+  const std::string tapePoolName = "tape_pool_name";
+  const std::string vo = "vo";
+  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_admin, logicalLibraryName, "Create logical library");
+  m_catalogue->createTapePool(m_admin, tapePoolName, vo, 2, true, "Create tape pool");
+  m_catalogue->createTape(m_admin, vid1, mediaType, vendor, logicalLibraryName, tapePoolName, capacityInBytes,
+    disabledValue, fullValue, createTapeComment);
+  
+  //A tape with no tape file have no files after FSeq 0
+  ASSERT_FALSE(m_catalogue->existNonSupersededFilesAfterFSeq(vid1,0));
+  
+  const uint64_t archiveFileId = 1234;
+
+  ASSERT_FALSE(m_catalogue->getArchiveFilesItor().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_admin, storageClass);
+
+  /*
+   * Insert a file in the tape vid1
+   */
+  {
+    const uint64_t archiveFileSize = 1;
+    const std::string tapeDrive = "tape_drive";
+    const std::string checksumType = "checksum_type";
+    const std::string checksumValue = "checksum_value";
+
+    auto file1WrittenUP=cta::make_unique<cta::catalogue::TapeFileWritten>();
+    auto & file1Written = *file1WrittenUP;
+    std::set<cta::catalogue::TapeItemWrittenPointer> file1WrittenSet;
+    file1WrittenSet.insert(file1WrittenUP.release());
+    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.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->filesWrittenToTape(file1WrittenSet);
+  }
+  //One file written : this file is not superseded by another one, existNonSupersededFilesAfterFSeq = true
+  ASSERT_TRUE(m_catalogue->existNonSupersededFilesAfterFSeq(vid1,0));
+  //No file after the only file inserted, existNonSupersededFilesAfterFseq = false
+  ASSERT_FALSE(m_catalogue->existNonSupersededFilesAfterFSeq(vid1,1));
+  
+  //Insert another file in another tape that will supersed the first one in vid1
+  {
+    m_catalogue->createTape(m_admin, vid2, mediaType, vendor, logicalLibraryName, tapePoolName, capacityInBytes,
+    disabledValue, fullValue, createTapeComment);
+    const uint64_t archiveFileSize = 1;
+    const std::string tapeDrive = "tape_drive";
+    const std::string checksumType = "checksum_type";
+    const std::string checksumValue = "checksum_value";
+
+    auto file1WrittenUP=cta::make_unique<cta::catalogue::TapeFileWritten>();
+    auto & file1Written = *file1WrittenUP;
+    std::set<cta::catalogue::TapeItemWrittenPointer> file1WrittenSet;
+    file1WrittenSet.insert(file1WrittenUP.release());
+    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.size                 = archiveFileSize;
+    file1Written.checksumType         = checksumType;
+    file1Written.checksumValue        = checksumValue;
+    file1Written.storageClassName     = storageClass.name;
+    file1Written.vid                  = vid2;
+    file1Written.fSeq                 = 1;
+    file1Written.blockId              = 4321;
+    file1Written.compressedSize       = 1;
+    file1Written.copyNb               = 1;
+    file1Written.tapeDrive            = tapeDrive;
+    m_catalogue->filesWrittenToTape(file1WrittenSet);
+  }
+  //The tape files written to tape vid2 are not superseded by any file, but the tape files in vid1 
+  //are superseded by the tape files in vid2
+  ASSERT_FALSE(m_catalogue->existNonSupersededFilesAfterFSeq(vid1,0));
+  ASSERT_TRUE(m_catalogue->existNonSupersededFilesAfterFSeq(vid2,0));
+}
+
 TEST_P(cta_catalogue_CatalogueTest, ping) {
   using namespace cta;
 
diff --git a/catalogue/DummyCatalogue.hpp b/catalogue/DummyCatalogue.hpp
index 60c7bd35a0..d6a8cd5756 100644
--- a/catalogue/DummyCatalogue.hpp
+++ b/catalogue/DummyCatalogue.hpp
@@ -105,6 +105,7 @@ public:
     const std::string &storageClassName, const common::dataStructures::UserIdentity &user) override { throw exception::Exception(std::string("In ")+__PRETTY_FUNCTION__+": not implemented"); }
   common::dataStructures::RetrieveFileQueueCriteria prepareToRetrieveFile(const std::string& instanceName, const uint64_t archiveFileId, const common::dataStructures::UserIdentity& user, log::LogContext &lc) override { throw exception::Exception(std::string("In ")+__PRETTY_FUNCTION__+": not implemented"); }
   void reclaimTape(const common::dataStructures::SecurityIdentity& admin, const std::string& vid) override { throw exception::Exception(std::string("In ")+__PRETTY_FUNCTION__+": not implemented"); }
+  void fakeReclaimTapeForTests(const common::dataStructures::SecurityIdentity& admin, const std::string& vid) override { throw exception::Exception(std::string("In ")+__PRETTY_FUNCTION__+": not implemented"); }
   void setTapeDisabled(const common::dataStructures::SecurityIdentity& admin, const std::string& vid, const bool disabledValue) override { throw exception::Exception(std::string("In ")+__PRETTY_FUNCTION__+": not implemented"); }
   void setTapeFull(const common::dataStructures::SecurityIdentity& admin, const std::string& vid, const bool fullValue) override { throw exception::Exception(std::string("In ")+__PRETTY_FUNCTION__+": not implemented"); }
   void setTapePoolEncryption(const common::dataStructures::SecurityIdentity& admin, const std::string& name, const bool encryptionValue) override { throw exception::Exception(std::string("In ")+__PRETTY_FUNCTION__+": not implemented"); }
@@ -113,7 +114,8 @@ public:
   void tapeMountedForArchive(const std::string& vid, const std::string& drive) override { throw exception::Exception(std::string("In ")+__PRETTY_FUNCTION__+": not implemented"); }
   void tapeMountedForRetrieve(const std::string& vid, const std::string& drive) override { throw exception::Exception(std::string("In ")+__PRETTY_FUNCTION__+": not implemented"); }
   bool tapePoolExists(const std::string& tapePoolName) const { throw exception::Exception(std::string("In ")+__PRETTY_FUNCTION__+": not implemented"); }
-  
+  bool existNonSupersededFilesAfterFSeq(const std::string& vid, const uint64_t fSeq) const { throw exception::Exception(std::string("In ")+__PRETTY_FUNCTION__+": not implemented"); }
+
   // Special functions for unit tests.
   void addEnabledTape(const std::string & vid) {
     threading::MutexLocker lm(m_tapeEnablingMutex);
diff --git a/catalogue/RdbmsCatalogue.cpp b/catalogue/RdbmsCatalogue.cpp
index e7bead2479..ccd3b89e92 100644
--- a/catalogue/RdbmsCatalogue.cpp
+++ b/catalogue/RdbmsCatalogue.cpp
@@ -1791,6 +1791,31 @@ bool RdbmsCatalogue::tapeExists(rdbms::Conn &conn, const std::string &vid) const
   }
 }
 
+bool RdbmsCatalogue::existNonSupersededFilesAfterFSeq(const std::string& vid, const uint64_t fSeq) const {
+  try{
+    auto conn = m_connPool.getConn();
+    const char *const sql =
+    "SELECT VID "
+     "FROM TAPE_FILE "
+    "WHERE "
+      "VID = :VID AND "
+      "FSEQ > :FSEQ AND "
+      "SUPERSEDED_BY_VID IS NULL AND "
+      "SUPERSEDED_BY_FSEQ IS NULL";
+    auto stmt = conn.createStmt(sql);
+    stmt.bindString(":VID",vid);
+    stmt.bindUint64(":FSEQ",fSeq);
+    auto rset = stmt.executeQuery();
+    return rset.next();
+  } catch(exception::UserError &) {
+    throw;
+  } catch(exception::Exception &ex) {
+    ex.getMessage().str(std::string(__FUNCTION__) + ": " + ex.getMessage().str());
+    throw;
+  }
+}
+
+
 //------------------------------------------------------------------------------
 // deleteTape
 //------------------------------------------------------------------------------
@@ -2263,6 +2288,63 @@ void RdbmsCatalogue::reclaimTape(const common::dataStructures::SecurityIdentity
   }
 }
 
+//------------------------------------------------------------------------------
+//fakeReclaimTapeForTests
+//------------------------------------------------------------------------------
+
+void RdbmsCatalogue::fakeReclaimTapeForTests(const common::dataStructures::SecurityIdentity& admin, const std::string& vid) {
+  try {
+    const time_t now = time(nullptr);
+    const char *const sql =
+      "UPDATE TAPE SET "
+        "DATA_IN_BYTES = 0,"
+        "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'";
+    auto conn = m_connPool.getConn();
+    auto stmt = conn.createStmt(sql);
+    stmt.bindString(":LAST_UPDATE_USER_NAME", admin.username);
+    stmt.bindString(":LAST_UPDATE_HOST_NAME", admin.host);
+    stmt.bindUint64(":LAST_UPDATE_TIME", now);
+    stmt.bindString(":UPDATE_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");
+        }
+      }
+    }
+  } catch(exception::UserError &) {
+    throw;
+  } catch(exception::Exception &ex) {
+    ex.getMessage().str(std::string(__FUNCTION__) + ": " + ex.getMessage().str());
+    throw;
+  }
+}
+
+
 //------------------------------------------------------------------------------
 // getTapeLogFromRset
 //------------------------------------------------------------------------------
diff --git a/catalogue/RdbmsCatalogue.hpp b/catalogue/RdbmsCatalogue.hpp
index 779e7ca789..96cd3363da 100644
--- a/catalogue/RdbmsCatalogue.hpp
+++ b/catalogue/RdbmsCatalogue.hpp
@@ -330,7 +330,7 @@ public:
    * @param vid The volume identifier of the tape to be reclaimed.
    */
   void reclaimTape(const common::dataStructures::SecurityIdentity &admin, const std::string &vid) override;
-
+  void fakeReclaimTapeForTests(const common::dataStructures::SecurityIdentity& admin, const std::string& vid) override;
   void modifyTapeMediaType(const common::dataStructures::SecurityIdentity &admin, const std::string &vid, const std::string &mediaType) override;
   void modifyTapeVendor(const common::dataStructures::SecurityIdentity &admin, const std::string &vid, const std::string &vendor) override;
   void modifyTapeLogicalLibraryName(const common::dataStructures::SecurityIdentity &admin, const std::string &vid, const std::string &logicalLibraryName) override;
@@ -713,6 +713,16 @@ protected:
    * @return True if the tape exists.
    */
   bool tapeExists(rdbms::Conn &conn, const std::string &vid) const;
+  
+  /**
+   * Returns true if non superseded files exist after fSeq in the tape where vid is passed in parameter
+   * 
+   * @param vid the vid of the tape to check if non superseded files exist after fSeq
+   * @param fSeq the fSeq after which we want to check if non superseded files exist
+   * @return true if non superseded files exist, false otherwise
+   */
+  bool existNonSupersededFilesAfterFSeq(const std::string& vid, const uint64_t fSeq) const override;
+
 
   /**
    * Returns the list of tapes that meet the specified search criteria.
diff --git a/scheduler/ArchiveMount.cpp b/scheduler/ArchiveMount.cpp
index 7e61432be7..0e3cd30889 100644
--- a/scheduler/ArchiveMount.cpp
+++ b/scheduler/ArchiveMount.cpp
@@ -104,6 +104,16 @@ uint32_t cta::ArchiveMount::getNbFiles() const {
   return m_dbMount->nbFilesCurrentlyOnTape;
 }
 
+//------------------------------------------------------------------------------
+// checkTapeFSeqForWriting
+//------------------------------------------------------------------------------
+void cta::ArchiveMount::checkTapeFSeqForWriting(uint64_t fSeq) const {
+  if(m_catalogue.existNonSupersededFilesAfterFSeq(getVid(),fSeq)){
+    throw cta::exception::Exception("Non superseded files have been detected in the tape "+getVid() +" after "+std::to_string(fSeq));
+  }
+}
+
+
 //------------------------------------------------------------------------------
 // createDiskReporter
 //------------------------------------------------------------------------------
diff --git a/scheduler/ArchiveMount.hpp b/scheduler/ArchiveMount.hpp
index 62d2e7f83e..5f93bacb4d 100644
--- a/scheduler/ArchiveMount.hpp
+++ b/scheduler/ArchiveMount.hpp
@@ -172,6 +172,14 @@ namespace cta {
      */
     uint32_t getNbFiles() const override;
     
+    /**
+     * Checks wether the writing is possible after the FSeq passed in parameter
+     * If TapeFiles are located after FSeq and are not superseded by other TapeFiles
+     * we throw an exception : We don't want these files to be lost.
+     * @param fSeq : The fSeq after which we want to check if the writing is possible
+     */
+    void checkTapeFSeqForWriting(uint64_t fSeq) const;
+    
     /**
      * Creates a disk reporter for the ArchiveJob (this is a wrapper).
      * @param URL: report address
diff --git a/tapeserver/castor/tape/tapeserver/daemon/DataTransferSessionTest.cpp b/tapeserver/castor/tape/tapeserver/daemon/DataTransferSessionTest.cpp
index 1f75a42c24..ac371d79d0 100644
--- a/tapeserver/castor/tape/tapeserver/daemon/DataTransferSessionTest.cpp
+++ b/tapeserver/castor/tape/tapeserver/daemon/DataTransferSessionTest.cpp
@@ -1848,6 +1848,165 @@ TEST_P(DataTransferSessionTest, DataTransferSessionTapeFullOnFlushMigration) {
                                                "mountTotalReadRetries=\"25\" mountTotalWriteRetries=\"25\" mountWriteTransients=\"10\""));
 }
 
+TEST_P(DataTransferSessionTest, WriteDataInTapeWithNonSupersededFilesOnIt) {
+  // 0) Prepare the logger for everyone
+  cta::log::StringLogger logger("dummy","tapeServerUnitTest",cta::log::DEBUG);
+  cta::log::LogContext logContext(logger);
+  
+  setupDefaultCatalogue();
+  // 1) prepare the fake scheduler
+  std::string vid = s_vid;
+  std::string vid2 = s_vid+"2";
+  // cta::MountType::Enum mountType = cta::MountType::RETRIEVE;
+
+  // 3) Prepare the necessary environment (logger, plus system wrapper), 
+  castor::tape::System::mockWrapper mockSys;
+  mockSys.delegateToFake();
+  mockSys.disableGMockCallsCounting();
+  mockSys.fake.setupForVirtualDriveSLC6();
+  
+  // 4) Create the scheduler
+  auto & catalogue = getCatalogue();
+  auto & scheduler = getScheduler();
+  
+  // Always use the same requester
+  const cta::common::dataStructures::SecurityIdentity requester("user", "group");
+  
+  // List to remember the path of each remote file so that the existance of the
+  // files can be tested for at the end of the test
+  std::list<std::string> remoteFilePaths;
+  
+  // 5) Create the environment for the migration to happen (library + tape) 
+    const std::string libraryComment = "Library comment";
+  catalogue.createLogicalLibrary(s_adminOnAdminHost, s_libraryName,
+    libraryComment);
+  {
+    auto libraries = catalogue.getLogicalLibraries();
+    ASSERT_EQ(1, libraries.size());
+    ASSERT_EQ(s_libraryName, libraries.front().name);
+    ASSERT_EQ(libraryComment, libraries.front().comment);
+  }
+  const uint64_t capacityInBytes = 12345678;
+  const std::string tapeComment = "Tape comment";
+  bool disabled = false;
+  bool full = false;
+  catalogue.createTape(s_adminOnAdminHost, s_vid, s_mediaType, s_vendor, s_libraryName, s_tapePoolName, capacityInBytes,
+    disabled, full, tapeComment);
+  catalogue.createTape(s_adminOnAdminHost, vid2, s_mediaType, s_vendor, s_libraryName, s_tapePoolName, capacityInBytes,
+    disabled, full, tapeComment);
+  
+  // Create the mount criteria
+  catalogue.createMountPolicy(requester, "immediateMount", 1000, 0, 1000, 0, 1, "Policy comment");
+  catalogue.createRequesterMountRule(requester, "immediateMount", s_diskInstance, requester.username, "Rule comment");
+
+  //delete is unnecessary
+  //pointer with ownership will be passed to the application,
+  //which will do the delete
+  mockSys.fake.m_pathToDrive["/dev/nst0"] = new castor::tape::tapeserver::drive::FakeDrive();
+  
+  // We can prepare files for writing on the drive.
+  // Tempfiles are in this scope so they are kept alive 
+  std::vector<std::unique_ptr<unitTests::TempFile>> sourceFiles;
+  std::list<uint64_t> archiveFileIds;
+  {    
+    // Label the tape
+    castor::tape::tapeFile::LabelSession ls(*mockSys.fake.m_pathToDrive["/dev/nst0"], s_vid, false);
+    catalogue.tapeLabelled(s_vid, "T10D6116");
+    mockSys.fake.m_pathToDrive["/dev/nst0"]->rewind();
+    
+    // Create the files and schedule the archivals
+    for(int fseq=1; fseq <= 10 ; fseq ++) { 
+      // Create a source file.
+      sourceFiles.emplace_back(cta::make_unique<unitTests::TempFile>());
+      sourceFiles.back()->randomFill(1000);
+      remoteFilePaths.push_back(sourceFiles.back()->path());
+      // Schedule the archival of the file
+      cta::common::dataStructures::ArchiveRequest ar;
+      ar.checksumType="ADLER32";
+      ar.checksumValue=sourceFiles.back()->adler32();
+      ar.storageClass=s_storageClassName;
+      ar.srcURL=std::string("file://") + sourceFiles.back()->path();
+      ar.requester.name = requester.username;
+      ar.requester.group = "group";
+      ar.fileSize = 1000;
+      ar.diskFileID = std::to_string(fseq);
+      ar.diskFileInfo.path = "y";
+      ar.diskFileInfo.owner = "z";
+      ar.diskFileInfo.group = "g";
+      const auto archiveFileId = scheduler.checkAndGetNextArchiveFileId(s_diskInstance, ar.storageClass, ar.requester, logContext);
+      archiveFileIds.push_back(archiveFileId);
+      scheduler.queueArchiveWithGivenId(archiveFileId,s_diskInstance,ar,logContext);
+    }
+  }
+  scheduler.waitSchedulerDbSubthreadsComplete();
+  // Report the drive's existence and put it up in the drive register.
+  cta::tape::daemon::TpconfigLine driveConfig("T10D6116", "TestLogicalLibrary", "/dev/tape_T10D6116", "manual");
+  cta::common::dataStructures::DriveInfo driveInfo;
+  driveInfo.driveName=driveConfig.unitName;
+  driveInfo.logicalLibrary=driveConfig.logicalLibrary;
+  driveInfo.host="host";
+  // We need to create the drive in the registry before being able to put it up.
+  scheduler.reportDriveStatus(driveInfo, cta::common::dataStructures::MountType::NoMount, cta::common::dataStructures::DriveStatus::Down, logContext);
+  scheduler.setDesiredDriveState(s_adminOnAdminHost, driveConfig.unitName, true, false, logContext);
+
+  // Create the data transfer session
+  DataTransferConfig castorConf;
+  castorConf.bufsz = 1024*1024; // 1 MB memory buffers
+  castorConf.nbBufs = 10;
+  castorConf.bulkRequestRecallMaxBytes = UINT64_C(100)*1000*1000*1000;
+  castorConf.bulkRequestRecallMaxFiles = 1000;
+  castorConf.bulkRequestMigrationMaxBytes = UINT64_C(100)*1000*1000*1000;
+  castorConf.bulkRequestMigrationMaxFiles = 1000;
+  castorConf.nbDiskThreads = 1;
+  cta::log::DummyLogger dummyLog("dummy", "dummy");
+  cta::mediachanger::MediaChangerFacade mc(dummyLog);
+  cta::server::ProcessCap capUtils;
+  castor::messages::TapeserverProxyDummy initialProcess;
+  DataTransferSession sess("tapeHost", logger, mockSys, driveConfig, mc, initialProcess, capUtils, castorConf, scheduler);
+  sess.execute();
+  
+  catalogue.setTapeFull(requester,vid,true);
+  //Fake reclaim the tape to reset tape's counters
+  catalogue.fakeReclaimTapeForTests(requester,vid);
+  
+  //Try to write files on tape vid = TstVid
+  // Create the files and schedule the archivals
+  {
+    for(int fseq=1; fseq <= 10 ; fseq ++) { 
+      // Create a source file.
+      sourceFiles.emplace_back(cta::make_unique<unitTests::TempFile>());
+      sourceFiles.back()->randomFill(1000);
+      remoteFilePaths.push_back(sourceFiles.back()->path());
+      // Schedule the archival of the file
+      cta::common::dataStructures::ArchiveRequest ar;
+      ar.checksumType="ADLER32";
+      ar.checksumValue=sourceFiles.back()->adler32();
+      ar.storageClass=s_storageClassName;
+      ar.srcURL=std::string("file://") + sourceFiles.back()->path();
+      ar.requester.name = requester.username;
+      ar.requester.group = "group";
+      ar.fileSize = 1000;
+      ar.diskFileID = std::to_string(fseq);
+      ar.diskFileInfo.path = "y";
+      ar.diskFileInfo.owner = "z";
+      ar.diskFileInfo.group = "g";
+      const auto archiveFileId = scheduler.checkAndGetNextArchiveFileId(s_diskInstance, ar.storageClass, ar.requester, logContext);
+      archiveFileIds.push_back(archiveFileId);
+      scheduler.queueArchiveWithGivenId(archiveFileId,s_diskInstance,ar,logContext);
+    }
+  }
+  scheduler.waitSchedulerDbSubthreadsComplete();
+  {
+    DataTransferSession sess("tapeHost2", logger, mockSys, driveConfig, mc, initialProcess, capUtils, castorConf, scheduler);
+    sess.execute();
+  }
+  //The session should fail because of the checking of writing in a tape with files that are not superseded
+  std::string logToCheck = logger.getLog();
+  ASSERT_NE(std::string::npos, logToCheck.find("MSG=\"In MigrationReportPacker::reportEndOfSessionWithErrors(), "
+          "pushing a report.\" thread=\"TapeWrite\" tapeDrive=\"T10D6116\" tapeVid=\"TstVid\" mountId=\"2\" "
+          "ErrorMesage=\"Non superseded files have been detected in the tape TstVid after 0\" type=\"ReportEndofSessionWithErrors\""));
+}
+
 #undef TEST_MOCK_DB
 #ifdef TEST_MOCK_DB
 static cta::MockSchedulerDatabaseFactory mockDbFactory;
diff --git a/tapeserver/castor/tape/tapeserver/daemon/TapeWriteSingleThread.cpp b/tapeserver/castor/tape/tapeserver/daemon/TapeWriteSingleThread.cpp
index 11b2b998c2..713a0002f6 100644
--- a/tapeserver/castor/tape/tapeserver/daemon/TapeWriteSingleThread.cpp
+++ b/tapeserver/castor/tape/tapeserver/daemon/TapeWriteSingleThread.cpp
@@ -319,6 +319,8 @@ void castor::tape::tapeserver::daemon::TapeWriteSingleThread::run() {
       params.add("capacityInBytes",m_archiveMount.getCapacityInBytes());
       m_logContext.log(cta::log::INFO, "Tape session started");
       mountTapeReadWrite();
+      currentErrorToCount = "Error_tapeFSeqCheckAndTapeFileCleanup";
+      m_archiveMount.checkTapeFSeqForWriting(m_lastFseq);
       currentErrorToCount = "Error_tapeLoad";
       waitForDrive();
       currentErrorToCount = "Error_checkingTapeAlert";
@@ -603,4 +605,3 @@ void castor::tape::tapeserver::daemon::TapeWriteSingleThread::logSCSIMetrics() {
     m_logContext.log(cta::log::ERR, "Exception in logging volume statistics");
   }
 }
-
-- 
GitLab