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); }