diff --git a/.cppcheck-suppressions.txt b/.cppcheck-suppressions.txt
index 255269d17751d50a668168769361a3fe0d3bc55c..1ddd90716155c159ac1beadc35f4740522bd73b4 100644
--- a/.cppcheck-suppressions.txt
+++ b/.cppcheck-suppressions.txt
@@ -2,5 +2,6 @@ syntaxError:*Test.cpp
 syntaxError:*Tests.cpp
 localMutex:common/threading/MutexLtrace.cpp
 unknownMacro:tapeserver/castor/tape/tapeserver/file/FileTest.cpp
+unknownMacro:tapeserver/castor/tape/tapeserver/file/OSMFileTest.cpp
 nullPointer:common/utils/utils.cpp
 uninitStructMember:objectstore/RetrieveRequest.cpp
\ No newline at end of file
diff --git a/ReleaseNotes.md b/ReleaseNotes.md
index ecb8747303cecafd336459966c18c47bf3098062..9501e7a53b54f2bfb073d29ec0c946d2a569d9f6 100644
--- a/ReleaseNotes.md
+++ b/ReleaseNotes.md
@@ -6,6 +6,8 @@
 - cta/CTA#41 - Delete verification_status of tape when tape is reclaimed
 - cta/CTA#153 - Allow verification status to be cleared with cta-admin
 - cta/CTA#173 - Update release notes and small changes to refactoring of operation tools cmd line parsing - Compatible with operations 0.4-95 or later
+### Continuous Integration
+- cta/CTA#118 - Add unit tests for OSM label
 ### Bug fixes
 - cta/CTA#48 - Catch tape server exception and log an error instead
 - cta/CTA#123 - Change some tape server errors into warnings
diff --git a/tapeserver/castor/tape/tapeserver/file/CMakeLists.txt b/tapeserver/castor/tape/tapeserver/file/CMakeLists.txt
index 103df19e082ed1435c98ac97b357c777bcdf2e37..557ca5d181d9a0d8b1c9a9d47a29b3f8c61d5539 100644
--- a/tapeserver/castor/tape/tapeserver/file/CMakeLists.txt
+++ b/tapeserver/castor/tape/tapeserver/file/CMakeLists.txt
@@ -61,6 +61,7 @@ add_library(File
 add_library(ctatapeserverfileunittests SHARED
   StructuresTest.cpp
   FileTest.cpp
+  OSMFileTest.cpp
 )
 
 target_link_libraries(ctatapeserverfileunittests
diff --git a/tapeserver/castor/tape/tapeserver/file/OSMFileTest.cpp b/tapeserver/castor/tape/tapeserver/file/OSMFileTest.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..3cb6c3318522847d647bed49d8d45660e675e243
--- /dev/null
+++ b/tapeserver/castor/tape/tapeserver/file/OSMFileTest.cpp
@@ -0,0 +1,254 @@
+#include <gtest/gtest.h>
+
+#include <memory>
+
+#include <cstdio>
+
+#include "castor/tape/tapeserver/drive/DriveInterface.hpp"
+#include "castor/tape/tapeserver/drive/FakeDrive.hpp"
+#include "castor/tape/tapeserver/file/FileReader.hpp"
+#include "castor/tape/tapeserver/file/FileReaderFactory.hpp"
+#include "castor/tape/tapeserver/file/FileWriter.hpp"
+#include "castor/tape/tapeserver/file/LabelSession.hpp"
+#include "castor/tape/tapeserver/file/ReadSession.hpp"
+#include "castor/tape/tapeserver/file/ReadSessionFactory.hpp"
+#include "castor/tape/tapeserver/file/WriteSession.hpp"
+#include "castor/tape/tapeserver/SCSI/Device.hpp"
+#include "castor/tape/tapeserver/system/Wrapper.hpp"
+#include "castor/tape/tapeserver/file/OsmFileStructure.hpp"
+#include "common/dataStructures/LabelFormat.hpp"
+#include "common/exception/Errnum.hpp"
+#include "common/exception/Exception.hpp"
+#include "disk/DiskFile.hpp"
+#include "disk/DiskFileImplementations.hpp"
+#include "disk/RadosStriperPool.hpp"
+#include "scheduler/ArchiveJob.hpp"
+#include "scheduler/RetrieveJob.hpp"
+
+namespace unitTests {
+
+class TestingRetrieveJob: public cta::RetrieveJob {
+public:
+  TestingRetrieveJob() : cta::RetrieveJob(nullptr,
+  cta::common::dataStructures::RetrieveRequest(),
+  cta::common::dataStructures::ArchiveFile(), 1,
+  cta::PositioningMethod::ByBlock) {}
+};
+
+class TestingArchiveJob: public cta::ArchiveJob {
+public:
+  TestingArchiveJob(): cta::ArchiveJob(nullptr,
+   *(static_cast<cta::catalogue::Catalogue *>(nullptr)), cta::common::dataStructures::ArchiveFile(),
+   "", cta::common::dataStructures::TapeFile()) {
+  }
+};
+
+class OSMTapeFileTest : public ::testing::TestWithParam<cta::common::dataStructures::Label::Format> {
+protected:
+  virtual void SetUp() {
+
+    // Gets information about the currently running test.
+    // Do NOT delete the returned object - it's managed by the UnitTest class.
+    const testing::TestInfo *const pTestInfo =
+            testing::UnitTest::GetInstance()->current_test_info();
+    /*
+     * pTestInfo->name() - test name
+     * pTestInfo->test_case_name() - suite name
+     */
+    m_strTestName = {pTestInfo->name()};
+
+    m_block_size = 262144;
+    m_label = "K00001";
+    m_fileToRecall.selectedCopyNb = 1;
+    cta::common::dataStructures::TapeFile tf;
+    tf.blockId = 0;
+    tf.fSeq = 1;
+    tf.copyNb = 1;
+    m_fileToRecall.archiveFile.tapeFiles.push_back(tf);
+    m_fileToRecall.retrieveRequest.archiveFileID = 1;
+    m_fileToMigrate.archiveFile.fileSize = 500;
+    m_fileToMigrate.archiveFile.archiveFileID = 1;
+    m_fileToMigrate.tapeFile.fSeq = 1;
+    m_volInfo.vid = m_label;
+    m_volInfo.labelFormat = cta::common::dataStructures::Label::Format::OSM;
+
+    // Write OSM label
+    castor::tape::tapeFile::osm::LABEL osmLabel;
+    osmLabel.encode(0, 1, castor::tape::tapeFile::osm::LIMITS::MAXMRECSIZE, 1, m_label, "DESY", "1.1");
+    m_drive.writeBlock(reinterpret_cast<void *>(osmLabel.rawLabel()),
+                       castor::tape::tapeFile::osm::LIMITS::MAXMRECSIZE);
+    m_drive.writeBlock(reinterpret_cast<void *>(osmLabel.rawLabel() + castor::tape::tapeFile::osm::LIMITS::MAXMRECSIZE),
+            castor::tape::tapeFile::osm::LIMITS::MAXMRECSIZE);
+
+    m_drive.writeSyncFileMarks(1);
+    // Write OSM file
+    if (m_strTestName == "throwsWhenUsingSessionTwice" ||
+        m_strTestName == "throwsWhenWrongBlockSizeOrEOF" ||
+        m_strTestName == "canProperlyVerifyLabelWriteAndReadTape"
+        ) {
+
+      const std::string strTestString = {"Hello World!"};
+      std::stringstream file;
+      const std::string strMagic = "070707";
+      uint32_t uiDev = 0;
+      uint32_t uiIno = 1;
+      uint32_t uiMode = 33188;
+      uint32_t uiUid = 0;
+      uint32_t uiGid = 0;
+      uint32_t uiNlink = 1;
+      uint32_t uiRdev = 0;
+      uint64_t ulMtime = 1660901455;
+      uint32_t uiNameSize = 11;
+      uint64_t ui64FileSize = strTestString.size();
+      const std::string strFid = "01234567890";
+      const size_t PAYLOAD_BLOCK_SIZE = 262144;
+
+      // Preparing CPIO-ASCII-header
+      file << strMagic
+           << std::setfill('0')
+           << std::oct
+           << std::setw(6) << uiDev
+           << std::setw(6) << uiIno
+           << std::setw(6) << uiMode
+           << std::setw(6) << uiUid
+           << std::setw(6) << uiGid
+           << std::setw(6) << uiNlink
+           << std::setw(6) << uiRdev
+           << std::setw(11) << ulMtime
+           << std::setw(6) << uiNameSize
+           << "H"
+           << std::hex
+           << std::setw(10) << ui64FileSize
+           << strFid;
+      // Write data
+      file << strTestString;
+      //Preparing trailer
+      file << strMagic
+           << std::setfill('0')
+           << std::oct
+           << std::setw(6) << uiDev
+           << std::setw(6) << uiIno
+           << std::setw(6) << uiMode
+           << std::setw(6) << uiUid
+           << std::setw(6) << uiGid
+           << std::setw(6) << uiNlink
+           << std::setw(6) << uiRdev
+           << std::setw(11) << ulMtime
+           << std::setw(6) << 10
+           << "H"
+           << std::hex
+           << std::setw(10) << 0
+           //   << std::setw(1024)
+           << strFid
+           << "TRAILER!!" << 0;
+
+      char acBuffer[PAYLOAD_BLOCK_SIZE] = {'\0'};
+
+      while (file.rdbuf()->sgetn(acBuffer, PAYLOAD_BLOCK_SIZE)) {
+        m_drive.writeBlock(reinterpret_cast<void *>(acBuffer), PAYLOAD_BLOCK_SIZE);
+        // Nullify
+        std::fill(acBuffer, acBuffer + PAYLOAD_BLOCK_SIZE, NULL);// where NULL = 0
+      }
+
+      m_drive.writeSyncFileMarks(1);
+    }
+  }
+
+  virtual void TearDown() {}
+
+  castor::tape::tapeserver::drive::FakeDrive m_drive;
+  uint32_t m_block_size;
+  std::string m_label;
+  TestingRetrieveJob m_fileToRecall;
+  TestingArchiveJob m_fileToMigrate;
+  castor::tape::tapeserver::daemon::VolumeInfo m_volInfo;
+
+  std::string m_strTestName;
+  std::vector<std::string> m_vTestToSkipp;
+};
+
+TEST_F(OSMTapeFileTest, throwsWhenReadingAnEmptyTape) {
+  const auto readSession = castor::tape::tapeFile::ReadSessionFactory::create(m_drive, m_volInfo, false);
+  ASSERT_NE(readSession, nullptr);
+  m_fileToRecall.positioningMethod = cta::PositioningMethod::ByBlock;
+  // cannot read a file on an empty tape
+  ASSERT_THROW(castor::tape::tapeFile::FileReaderFactory::create(readSession, m_fileToRecall),
+               cta::exception::Exception);
+}
+
+TEST_F(OSMTapeFileTest, throwsWhenUnexpectedLabelFormat) {
+  m_volInfo.labelFormat = static_cast<cta::common::dataStructures::Label::Format>(0xFF);
+  std::unique_ptr<castor::tape::tapeFile::ReadSession> readSession;
+  ASSERT_THROW(readSession = castor::tape::tapeFile::ReadSessionFactory::create(m_drive, m_volInfo, false),
+               castor::tape::tapeFile::TapeFormatError);
+  ASSERT_EQ(readSession, nullptr);
+}
+
+TEST_F(OSMTapeFileTest, throwsWhenUsingSessionTwice) {
+  const std::string testString("Hello World!");
+
+  const auto readSession = castor::tape::tapeFile::ReadSessionFactory::create(m_drive, m_volInfo, false);
+  {
+    m_fileToRecall.positioningMethod = cta::PositioningMethod::ByBlock;
+    const auto reader = castor::tape::tapeFile::FileReaderFactory::create(readSession, m_fileToRecall);
+    // cannot have two FileReader's on the same session
+    ASSERT_THROW(castor::tape::tapeFile::FileReaderFactory::create(readSession, m_fileToRecall),
+                 castor::tape::tapeFile::SessionAlreadyInUse);
+  }
+}
+
+TEST_F(OSMTapeFileTest, throwsWhenWrongBlockSizeOrEOF) {
+  const std::string testString("Hello World!");
+
+  auto readSession = castor::tape::tapeFile::ReadSessionFactory::create(m_drive, m_volInfo, true);
+  {
+    m_fileToRecall.positioningMethod = cta::PositioningMethod::ByBlock;
+    const auto reader = castor::tape::tapeFile::FileReaderFactory::create(readSession, m_fileToRecall);
+    size_t blockSize = reader->getBlockSize();
+    char *data = new char[blockSize+1];
+    // block size needs to be the same provided by the headers
+    ASSERT_THROW(reader->readNextDataBlock(data, 1), castor::tape::tapeFile::WrongBlockSize);
+    // it is normal to reach end of file after a loop of reads
+    ASSERT_THROW(while(true) {
+      reader->readNextDataBlock(data, blockSize);
+    },
+                 castor::tape::tapeFile::EndOfFile);
+    delete[] data;
+  }
+}
+
+TEST_F(OSMTapeFileTest, canProperlyVerifyLabelWriteAndReadTape) {
+  // Verify label
+  {
+    const auto readSession = castor::tape::tapeFile::ReadSessionFactory::create(m_drive, m_volInfo, false);
+    ASSERT_NE(readSession, nullptr);
+    ASSERT_EQ(readSession->getCurrentFilePart(), castor::tape::tapeFile::PartOfFile::Header);
+    ASSERT_EQ(readSession->getCurrentFseq(), static_cast<uint32_t>(1));
+    ASSERT_EQ(readSession->isCorrupted(), false);
+    ASSERT_EQ(readSession->m_vid.compare(m_label), 0);
+  }
+
+  const std::string testString("Hello World!");
+  // Read it back and compare
+  const auto readSession = castor::tape::tapeFile::ReadSessionFactory::create(m_drive, m_volInfo, true);
+  ASSERT_NE(readSession, nullptr);
+  ASSERT_EQ(readSession->getCurrentFilePart(), castor::tape::tapeFile::PartOfFile::Header);
+  ASSERT_EQ(readSession->getCurrentFseq(), static_cast<uint32_t>(1));
+  ASSERT_EQ(readSession->isCorrupted(), false);
+  ASSERT_EQ(readSession->m_vid.compare(m_label), 0);
+  ASSERT_EQ(readSession->m_useLbp, true);
+  {
+    m_fileToRecall.positioningMethod = cta::PositioningMethod::ByBlock;
+    const auto reader = castor::tape::tapeFile::FileReaderFactory::create(readSession, m_fileToRecall);
+    size_t blockSize = reader->getBlockSize();
+    ASSERT_EQ(blockSize, m_block_size);
+    char *data = new char[blockSize+1];
+    size_t bytes_read = reader->readNextDataBlock(data, blockSize);
+    data[bytes_read] = '\0';
+    ASSERT_EQ(bytes_read, static_cast<size_t>(testString.size()));
+    ASSERT_EQ(testString.compare(data), 0);
+    delete[] data;
+  }
+}
+
+}  // namespace unitTests
diff --git a/tapeserver/castor/tape/tapeserver/file/OsmFileStructure.cpp b/tapeserver/castor/tape/tapeserver/file/OsmFileStructure.cpp
index e27c59050ae1d50219aaa98479d03a821ea94912..21e41f27f3a1db921594887bafbbbbf7f8fb5f10 100644
--- a/tapeserver/castor/tape/tapeserver/file/OsmFileStructure.cpp
+++ b/tapeserver/castor/tape/tapeserver/file/OsmFileStructure.cpp
@@ -87,3 +87,74 @@ void castor::tape::tapeFile::osm::LABEL::decode() {
   memcpy(m_tcVersion, rawLabel() + LIMITS::MAXMRECSIZE + LIMITS::CIDLEN, LIMITS::LABELVERSIONLEN);
 
 }
+
+void castor::tape::tapeFile::osm::LABEL::encode(uint64_t ulCreateTime, uint64_t ulExpireTime, uint64_t ulRecSize, uint64_t ulVolId,
+                                                const std::string& strVolName, const std::string& strOwner, const std::string& strVersion) {
+
+  if(strVolName.size() > LIMITS::VOLNAMELEN) {
+    throw cta::exception::Exception(std::string("The size of the VolName is greater than LIMITS::VOLNAMELEN"));
+  }
+  if(strOwner.size() > LIMITS::CIDLEN) {
+    throw cta::exception::Exception(std::string("The size of the Owner is greater than LIMITS::CIDLEN"));
+  }
+  if(strVersion.size() > LIMITS::LABELVERSIONLEN) {
+    throw cta::exception::Exception(std::string("The size of the Version is greater than LIMITS::LABELVERSIONLEN"));
+  }
+
+  unsigned int uiDataLen = 0;
+  xdr::Chunk chunk;
+  xdr::Record record;
+  xdr::VolLabel volLabel;
+  char* pcVolLabel = new char[LIMITS::MMAXCHK];
+  XDR xdr;// xdr handler
+
+  xdrmem_create(&xdr, pcVolLabel, LIMITS::MMAXCHK, XDR_ENCODE);
+
+  volLabel.m_ulMagic = LIMITS::MVOLMAGIC;
+  volLabel.m_ulCreateTime = ulCreateTime;
+  volLabel.m_ulExpireTime = ulExpireTime;
+  volLabel.m_ulRecSize = ulRecSize;// LIMITS::MAXMRECSIZE;
+  volLabel.m_ulVolId = ulVolId;
+  // NSR_LENGTH = 64
+  volLabel.m_pcVolName = new char[64];
+  strcpy(volLabel.m_pcVolName, strVolName.c_str());
+
+  if(!volLabel.decode(&xdr)) {
+    throw cta::exception::Exception(std::string("XDR error encoding vollabel"));
+  }
+
+  uiDataLen = xdr_getpos(&xdr);
+
+  xdr_destroy(&xdr);
+
+  record.m_RChunk.m_pChunk = new xdr::Chunk();
+  record.m_RChunk.m_pChunk->m_ulSsid = 0;
+  record.m_RChunk.m_pChunk->mc_ulLow = 0;
+  record.m_RChunk.m_pChunk->m_data.m_uiDataLen = uiDataLen;
+  record.m_RChunk.m_pChunk->m_data.m_pcDataVal = pcVolLabel;//new char[LIMITS::MMAXCHK];
+
+  memset(record.m_tcHandler, 0x20, sizeof(record.m_tcHandler));
+  record.m_ulVolid = volLabel.m_ulVolId;
+  record.m_ulFn = 0;
+  record.m_ulRn = 0;
+  record.m_ulLen = 0; // set this to 0 now, it will be corrected later
+  record.m_RChunk.m_ulChunkLen = 1;
+
+  xdrmem_create(&xdr, rawLabel(), LIMITS::MAXMRECSIZE, XDR_ENCODE);
+  if(!record.decode(&xdr)) {
+    throw cta::exception::Exception(std::string("XDR error encoding record"));
+  }
+
+  record.m_ulLen  = xdr_getpos(&xdr); // Now re-do this with the correct length
+
+  xdr_setpos(&xdr, 0);
+  if(!record.decode(&xdr)) {
+    throw cta::exception::Exception(std::string("XDR error encoding record after set pos"));
+  }
+
+  xdr_destroy(&xdr);
+
+  strcpy(rawLabel() + LIMITS::MAXMRECSIZE, strOwner.c_str());
+  strcpy(rawLabel() + LIMITS::MAXMRECSIZE + LIMITS::CIDLEN, strVersion.c_str());
+
+}
diff --git a/tapeserver/castor/tape/tapeserver/file/OsmFileStructure.hpp b/tapeserver/castor/tape/tapeserver/file/OsmFileStructure.hpp
index 3d4bda8157098b82182ea107b3ee39ed2185016f..cde746ccb41960f889cfa92ce2c2910e50140ed2 100644
--- a/tapeserver/castor/tape/tapeserver/file/OsmFileStructure.hpp
+++ b/tapeserver/castor/tape/tapeserver/file/OsmFileStructure.hpp
@@ -67,6 +67,9 @@ public:
 
   void decode();  // can throw
 
+  void encode(uint64_t ulCreateTime, uint64_t ulExpireTime, uint64_t ulRecSize, uint64_t ulVolId,
+              const std::string& strVolName, const std::string& strOwner, const std::string& strVersion); // can throw
+
   inline std::string owner() {
     return std::string(m_tcOwner);
   }