diff --git a/tapeserver/castor/tape/tapeserver/SCSI/Constants.hpp b/tapeserver/castor/tape/tapeserver/SCSI/Constants.hpp index 3fb87c96ca0803a3db57c24e3de00683df8d0fa5..2fcaab56b7be5967633197e5920e20324eb25e4c 100644 --- a/tapeserver/castor/tape/tapeserver/SCSI/Constants.hpp +++ b/tapeserver/castor/tape/tapeserver/SCSI/Constants.hpp @@ -739,6 +739,11 @@ namespace SCSI { * Addition for the mode page Length for the Control Data Protection Mode Page */ const unsigned char controlDataProtectionModePageLengthAddition = 4; + + class modeRAO { + public: + static const uint16_t DEFAULT_RRAO_ALLOCATION = 64000; + }; } // namespace SCSI } // namespace tape } // namespace castor diff --git a/tapeserver/castor/tape/tapeserver/SCSI/Structures.hpp b/tapeserver/castor/tape/tapeserver/SCSI/Structures.hpp index 03ec545c5442fde62c31224da62a7d824cc4b907..5a8e3f9d2ad0c21e3de02a04bb9caf22dda76634 100644 --- a/tapeserver/castor/tape/tapeserver/SCSI/Structures.hpp +++ b/tapeserver/castor/tape/tapeserver/SCSI/Structures.hpp @@ -172,6 +172,16 @@ namespace SCSI { *((uint16_t *) t) = htons(val); } + /** + * Helper function setting in place a 64 bits SCSI number from a value + * expressed in the local endianness. + * @param t pointer to the char array at the 64 bits value position. + * @param val the value. + */ + inline void setU64(unsigned char(& t)[8], uint64_t val) { + *((uint64_t *) t) = htobe64(val); + } + /** * Inquiry CDB as described in SPC-4. */ @@ -1170,6 +1180,140 @@ namespace SCSI { unsigned char keyData[SCSI::encryption::ENC_KEY_LENGTH]; }; } + + namespace RAO { + + /** + * Receive RAO Command Descriptor Block (CDB) + */ + class recieveRAO_t { + public: + recieveRAO_t() { + zeroStruct(this); + opcode = SCSI::Commands::MAINTENANCE_IN; + } + unsigned char opcode; + + unsigned char serviceAction :5; + unsigned char :2; + unsigned char udsLimits :1; + + unsigned char raoListOffset[4]; + + unsigned char allocationLength[4]; + + unsigned char udsType :3; + unsigned char :5; + + unsigned char control; + + }; + + /** + * UDS (User Data Segments) limits page + */ + class udsLimitsPage_t { + public: + udsLimitsPage_t() { + zeroStruct(this); + } + unsigned int maxSupported; + unsigned int maxSize; + }; + + /** + * Generate RAO CDB + */ + class generateRAO_t { + public: + generateRAO_t() { + zeroStruct(this); + opcode = SCSI::Commands::MAINTENANCE_OUT; + raoProcess = 2; + } + unsigned char opcode; + + unsigned char serviceAction :5; + unsigned char :3; + + unsigned char raoProcess :3; + unsigned char :5; + + unsigned char udsType :3; + unsigned char :5; + + unsigned char reserved[2]; + + unsigned char paramsListLength[4]; + + unsigned char reserved2; + + unsigned char control; + + }; + + class udsDescriptor { + public: + udsDescriptor() { + zeroStruct(this); + setU16(descriptorLength, 0x1e); + } + unsigned char descriptorLength[2]; + unsigned char reserved[3]; + unsigned char udsName[10]; + unsigned char partitionNumber; + unsigned char beginLogicalObjID[8]; + unsigned char endLogicalObjID[8]; + }; + + /** + * RAO list struct + */ + class raoList { + public: + raoList() { + zeroStruct(this); + } + unsigned char raoProcess :3; + unsigned char :5; + + unsigned char status :3; + unsigned char :5; + + unsigned char res[2]; + + unsigned char raoDescriptorListLength[4]; + + udsDescriptor udsDescriptors[2000]; + + }; + + /** + * Generate RAO parameters + */ + class generateRAOParams_t { + public: + generateRAOParams_t() { + zeroStruct(this); + } + unsigned char res[4]; + unsigned char additionalData[4]; + udsDescriptor userDataSegmentDescriptors[2000]; + }; + + /** + * Block Limits + */ + class blockLims { + public: + blockLims() { + zeroStruct(this); + } + unsigned char fseq[10]; + uint64_t begin; + uint64_t end; + }; + } template <size_t n> /** diff --git a/tapeserver/castor/tape/tapeserver/daemon/DataTransferConfig.cpp b/tapeserver/castor/tape/tapeserver/daemon/DataTransferConfig.cpp index 67cdbb9047aab6a43bbe8b9042c654d312beee22..f563c110bb5c93bde18a7ff838207a630931bdba 100644 --- a/tapeserver/castor/tape/tapeserver/daemon/DataTransferConfig.cpp +++ b/tapeserver/castor/tape/tapeserver/daemon/DataTransferConfig.cpp @@ -41,6 +41,7 @@ castor::tape::tapeserver::daemon::DataTransferConfig::DataTransferConfig() maxFilesBeforeFlush(0), nbDiskThreads(0), useLbp(false), + useRAO(false), externalEncryptionKeyScript("") {} //------------------------------------------------------------------------------ @@ -85,6 +86,8 @@ castor::tape::tapeserver::daemon::DataTransferConfig "TapeServer", "XrootTimeout", 0, log); const std::string useLBP = castorConf.getConfEntString( "TapeServer", "UseLogicalBlockProtection", "no", log); + const std::string useRAO = castorConf.getConfEntString( + "TapeServer", "UseRecommendedAccessOrder", "no", log); if (!strcasecmp(useLBP.c_str(), "yes") || !strcmp(useLBP.c_str(), "1")) { config.useLbp = true; @@ -92,6 +95,12 @@ castor::tape::tapeserver::daemon::DataTransferConfig config.useLbp = false; } + if (!strcasecmp(useRAO.c_str(), "yes") || !strcmp(useRAO.c_str(), "1")) { + config.useRAO = true; + } else { + config.useRAO = false; + } + config.externalEncryptionKeyScript = castorConf.getConfEntString("TapeServer", "ExternalEncryptionKeyScript", ""); diff --git a/tapeserver/castor/tape/tapeserver/daemon/DataTransferConfig.hpp b/tapeserver/castor/tape/tapeserver/daemon/DataTransferConfig.hpp index 69b377a846622c34f72463ab3a3d730a30cad58c..02ecc4a68c253441d8f38c3e0ebe3574e7a27e3c 100644 --- a/tapeserver/castor/tape/tapeserver/daemon/DataTransferConfig.hpp +++ b/tapeserver/castor/tape/tapeserver/daemon/DataTransferConfig.hpp @@ -112,6 +112,12 @@ struct DataTransferConfig { */ bool useLbp; + /** + * The boolean variable describing to use on not to use Recommended + * Access Order + */ + bool useRAO; + /** * The path to the operator provided encyption control script (or empty string) */ diff --git a/tapeserver/castor/tape/tapeserver/daemon/DataTransferSession.cpp b/tapeserver/castor/tape/tapeserver/daemon/DataTransferSession.cpp index a8237b58899c2c540dbc32714f82f10d1b2f5b89..745082232028e203d291806ef651cabfd9d283b2 100644 --- a/tapeserver/castor/tape/tapeserver/daemon/DataTransferSession.cpp +++ b/tapeserver/castor/tape/tapeserver/daemon/DataTransferSession.cpp @@ -203,7 +203,7 @@ castor::tape::tapeserver::daemon::Session::EndOfSessionAction TapeReadSingleThread trst(*drive, m_mc, tsr, m_volInfo, m_castorConf.bulkRequestRecallMaxFiles,m_capUtils,rwd,lc,rrp, - m_castorConf.useLbp, m_castorConf.externalEncryptionKeyScript); + m_castorConf.useLbp, m_castorConf.useRAO, m_castorConf.externalEncryptionKeyScript); DiskWriteThreadPool dwtp(m_castorConf.nbDiskThreads, rrp, rwd, @@ -219,10 +219,18 @@ castor::tape::tapeserver::daemon::Session::EndOfSessionAction trst.setTaskInjector(&rti); rrp.setWatchdog(rwd); + rti.setDriveInterface(trst.getDriveReference()); + // We are now ready to put everything in motion. First step is to check // we get any concrete job to be done from the client (via the task injector) cta::utils::Timer timer; - if (rti.synchronousInjection()) { //adapt the recall task injector (starting from synchronousInjection) + + // The RecallTaskInjector and the TapeReadSingleThread share the promise + if (m_castorConf.useRAO) { + rti.initRAO(); + } + + if (rti.synchronousFetch()) { //adapt the recall task injector (starting from synchronousFetch) // We got something to recall. Time to start the machinery trst.setWaitForInstructionsTime(timer.secs()); rwd.startThread(); diff --git a/tapeserver/castor/tape/tapeserver/daemon/DataTransferSessionTest.cpp b/tapeserver/castor/tape/tapeserver/daemon/DataTransferSessionTest.cpp index ccb658c7e7b9e64a99d6b5115e1568f4fbfbc7b7..cfbc3b271d76bbeabf0581ec5fdb89454319134f 100644 --- a/tapeserver/castor/tape/tapeserver/daemon/DataTransferSessionTest.cpp +++ b/tapeserver/castor/tape/tapeserver/daemon/DataTransferSessionTest.cpp @@ -660,6 +660,218 @@ TEST_P(DataTransferSessionTest, DataTransferSessionWrongRecall) { "mountTotalReadRetries=\"25\" mountTotalWriteRetries=\"25\" mountWriteTransients=\"10\"")); } +TEST_P(DataTransferSessionTest, DataTransferSessionRAORecall) { + // 0) Prepare the logger for everyone + cta::log::StringLogger logger("tapeServerUnitTest",cta::log::DEBUG); + cta::log::LogContext logContext(logger); + + setupDefaultCatalogue(); + // 1) prepare the fake scheduler + std::string vid = s_vid; + // 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(); + //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; + + // 4) Create the scheduler + auto & catalogue = getCatalogue(); + auto & scheduler = getScheduler(); + + // Always use the same requester + const cta::common::dataStructures::SecurityIdentity requester; + + // 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 notDisabled = false; + bool notFull = false; + catalogue.createTape(s_adminOnAdminHost, s_vid, s_libraryName, s_tapePoolName, capacityInBytes, + notDisabled, notFull, tapeComment); + + int MAX_RECALLS = 50; + int MAX_BULK_RECALLS = 31; + std::vector<int> expectedOrder; + std::vector<std::string> expectedFseqOrderLog; + + // 6) Prepare files for reading by writing them to the mock system + { + // Label the tape + castor::tape::tapeFile::LabelSession ls(*mockSys.fake.m_pathToDrive["/dev/nst0"], + s_vid, false); + mockSys.fake.m_pathToDrive["/dev/nst0"]->rewind(); + // And write to it + castor::tape::tapeserver::daemon::VolumeInfo volInfo; + volInfo.vid=s_vid; + castor::tape::tapeFile::WriteSession ws(*mockSys.fake.m_pathToDrive["/dev/nst0"], + volInfo , 0, true, false); + + // Write a few files on the virtual tape and modify the archive name space + // so that it is in sync + uint8_t data[1000]; + size_t archiveFileSize=sizeof(data); + castor::tape::SCSI::Structures::zeroStruct(&data); + int fseq; + bool isFirst = true; + for (fseq=1; fseq <= MAX_RECALLS ; fseq ++) { + // Create a path to a remote destination file + std::ostringstream remoteFilePath; + remoteFilePath << "file://" << m_tmpDir << "/test" << fseq; + remoteFilePaths.push_back(remoteFilePath.str()); + + // Create an archive file entry in the archive namespace + cta::catalogue::TapeFileWritten tapeFileWritten; + + // Write the file to tape + cta::MockArchiveMount mam(catalogue); + std::unique_ptr<cta::ArchiveJob> aj(new cta::MockArchiveJob(mam, catalogue)); + aj->tapeFile.fSeq = fseq; + aj->archiveFile.archiveFileID = fseq; + castor::tape::tapeFile::WriteFile wf(&ws, *aj, archiveFileSize); + tapeFileWritten.blockId = wf.getBlockId(); + // Write the data (one block) + wf.write(data, archiveFileSize); + // Close the file + wf.close(); + + // Create file entry in the archive namespace + tapeFileWritten.archiveFileId=fseq; + tapeFileWritten.checksumType="ADLER32"; + tapeFileWritten.checksumValue=cta::utils::getAdler32String(data, archiveFileSize); + tapeFileWritten.vid=volInfo.vid; + tapeFileWritten.size=archiveFileSize; + tapeFileWritten.fSeq=fseq; + tapeFileWritten.copyNb=1; + tapeFileWritten.compressedSize=archiveFileSize; // No compression + tapeFileWritten.diskInstance = s_diskInstance; + tapeFileWritten.diskFileId = fseq; + tapeFileWritten.diskFilePath = remoteFilePath.str(); + tapeFileWritten.diskFileUser = s_userName; + tapeFileWritten.diskFileGroup = "someGroup"; + tapeFileWritten.diskFileRecoveryBlob = "B106"; + tapeFileWritten.storageClassName = s_storageClassName; + tapeFileWritten.tapeDrive = "drive0"; + catalogue.filesWrittenToTape(std::set<cta::catalogue::TapeFileWritten>{tapeFileWritten}); + + // Schedule the retrieval of the file + std::string diskInstance="disk_instance"; + cta::common::dataStructures::RetrieveRequest rReq; + rReq.archiveFileID=fseq; + rReq.requester.name = s_userName; + rReq.requester.group = "someGroup"; + rReq.dstURL = remoteFilePaths.back(); + std::list<std::string> archiveFilePaths; + scheduler.queueRetrieve(diskInstance, rReq, logContext); + + expectedOrder.push_back(fseq); + + bool apply_rao = false; + bool add_expected = false; + if (MAX_BULK_RECALLS < 2) { + if (expectedOrder.size() % MAX_BULK_RECALLS == 0 || + fseq % MAX_RECALLS == 0) { + add_expected = true; + } + } + else if (MAX_BULK_RECALLS >= 30) { + if ((expectedOrder.size() % 30 == 0) || + (fseq % MAX_RECALLS == 0) || (fseq % MAX_BULK_RECALLS == 0)) { + apply_rao = true & isFirst; + add_expected = true; + } + } + else if ((fseq % MAX_BULK_RECALLS == 0) || (fseq % MAX_RECALLS == 0)) { + apply_rao = true & isFirst; + add_expected = true; + } + if (apply_rao) { + std::reverse(expectedOrder.begin(), expectedOrder.end()); + isFirst = false; + } + if (add_expected) { + std::stringstream expectedLogLine; + std::copy(expectedOrder.begin(), expectedOrder.end(), + std::ostream_iterator<int>(expectedLogLine, " ")); + expectedFseqOrderLog.push_back(expectedLogLine.str()); + expectedOrder.clear(); + } + } + } + + // 6) 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.rawLibrarySlot; + // 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); + scheduler.setDesiredDriveState(s_adminOnAdminHost, driveConfig.unitName, true, false); + + // 7) 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 = MAX_BULK_RECALLS - 1; + castorConf.nbDiskThreads = 1; + castorConf.useRAO = true; + cta::log::DummyLogger dummyLog("dummy"); + cta::mediachanger::MediaChangerFacade mc(dummyLog); + cta::server::ProcessCap capUtils; + castor::messages::TapeserverProxyDummy initialProcess; + castor::tape::tapeserver::daemon::DataTransferSession sess("tapeHost", logger, mockSys, + driveConfig, mc, initialProcess, capUtils, castorConf, scheduler); + + // 8) Run the data transfer session + sess.execute(); + + // 9) Check the session git the correct VID + ASSERT_EQ(s_vid, sess.getVid()); + + // 10) Check the remote files exist and have the correct size + for(auto & path: remoteFilePaths) { + struct stat statBuf; + bzero(&statBuf, sizeof(statBuf)); + const int statRc = stat(path.substr(7).c_str(), &statBuf); //remove the "file://" for stat-ing + ASSERT_EQ(0, statRc); + ASSERT_EQ(1000, statBuf.st_size); //same size of data + } + + // 10) Check logs + std::string logToCheck = logger.getLog(); + logToCheck += ""; + ASSERT_NE(std::string::npos, logToCheck.find("firmwareVersion=\"123A\" serialNumber=\"123456\" " + "mountTotalCorrectedReadErrors=\"5\" mountTotalReadBytesProcessed=\"4096\" " + "mountTotalUncorrectedReadErrors=\"1\" mountTotalNonMediumErrorCounts=\"2\"")); + ASSERT_NE(std::string::npos, logToCheck.find("firmwareVersion=\"123A\" serialNumber=\"123456\" lifetimeMediumEfficiencyPrct=\"100\" " + "mountReadEfficiencyPrct=\"100\" mountWriteEfficiencyPrct=\"100\" " + "mountReadTransients=\"10\" " + "mountServoTemps=\"10\" mountServoTransients=\"5\" mountTemps=\"100\" " + "mountTotalReadRetries=\"25\" mountTotalWriteRetries=\"25\" mountWriteTransients=\"10\"")); + for (std::string s : expectedFseqOrderLog) { + ASSERT_NE(std::string::npos, logToCheck.find(s)); + } +} + TEST_P(DataTransferSessionTest, DataTransferSessionNoSuchDrive) { // 0) Prepare the logger for everyone diff --git a/tapeserver/castor/tape/tapeserver/daemon/RecallTaskInjector.cpp b/tapeserver/castor/tape/tapeserver/daemon/RecallTaskInjector.cpp index a83cf9b72c7410da393731c352925912c348e5cd..e80b66eb71f2ab90140ab9eb679880c9db8f6f43 100644 --- a/tapeserver/castor/tape/tapeserver/daemon/RecallTaskInjector.cpp +++ b/tapeserver/castor/tape/tapeserver/daemon/RecallTaskInjector.cpp @@ -27,6 +27,8 @@ #include "castor/tape/tapeserver/daemon/TapeReadTask.hpp" #include "castor/tape/tapeserver/daemon/TapeReadSingleThread.hpp" #include "castor/tape/tapeserver/daemon/VolumeInfo.hpp" +#include "castor/tape/tapeserver/SCSI/Structures.hpp" +#include "castor/tape/tapeserver/drive/DriveInterface.hpp" #include "scheduler/RetrieveJob.hpp" #include <stdint.h> @@ -46,8 +48,8 @@ RecallTaskInjector::RecallTaskInjector(RecallMemoryManager & mm, uint64_t maxFiles, uint64_t byteSizeThreshold,cta::log::LogContext lc) : m_thread(*this),m_memManager(mm), m_tapeReader(tapeReader),m_diskWriter(diskWriter), - m_retrieveMount(retrieveMount),m_lc(lc),m_maxFiles(maxFiles),m_maxBytes(byteSizeThreshold) -{} + m_retrieveMount(retrieveMount),m_lc(lc),m_maxFiles(maxFiles),m_maxBytes(byteSizeThreshold), + m_useRAO(false) {} //------------------------------------------------------------------------------ //destructor //------------------------------------------------------------------------------ @@ -82,41 +84,126 @@ void RecallTaskInjector::startThreads() { m_thread.start(); } //------------------------------------------------------------------------------ +//setDriveInterface +//------------------------------------------------------------------------------ +void RecallTaskInjector::setDriveInterface(castor::tape::tapeserver::drive::DriveInterface *di) { + m_drive = di; +} +//------------------------------------------------------------------------------ +//initRAO +//------------------------------------------------------------------------------ +void RecallTaskInjector::initRAO() { + m_useRAO = true; + m_raoFuture = m_raoPromise.get_future(); + m_raoLimits = m_drive->getLimitUDS(); +} +//------------------------------------------------------------------------------ +//waitForPromise +//------------------------------------------------------------------------------ +bool RecallTaskInjector::waitForPromise() { + std::chrono::milliseconds duration (1000); + std::future_status status = m_raoFuture.wait_for(duration); + if (status == std::future_status::ready) + return true; + return false; +} +//------------------------------------------------------------------------------ +//setPromise +//------------------------------------------------------------------------------ +void RecallTaskInjector::setPromise() { + try { + m_raoPromise.set_value(); + } catch (const std::exception &exc) { + throw cta::exception::Exception(std::string("In RecallTaskInjector::setPromise() got std::exception: ") + exc.what()); + } +} +//------------------------------------------------------------------------------ //injectBulkRecalls //------------------------------------------------------------------------------ -void RecallTaskInjector::injectBulkRecalls(std::vector<std::unique_ptr<cta::RetrieveJob>>& jobs) { - for (auto it = jobs.begin(); it != jobs.end(); ++it) { +void RecallTaskInjector::injectBulkRecalls() { + + uint32_t block_size = 262144; + uint32_t njobs = m_jobs.size(); + std::vector<uint32_t> raoOrder; + + if (m_useRAO) { + std::list<castor::tape::SCSI::Structures::RAO::blockLims> files; + + for (uint32_t i = 0; i < njobs; i++) { + cta::RetrieveJob *job = m_jobs.at(i).get(); + + castor::tape::SCSI::Structures::RAO::blockLims lims; + strncpy((char*)lims.fseq, std::to_string(i).c_str(), sizeof(i)); + lims.begin = job->selectedTapeFile().blockId; + lims.end = job->selectedTapeFile().blockId + 8 + + /* ceiling the number of blocks */ + ((job->archiveFile.fileSize + block_size - 1) / block_size); + + files.push_back(lims); + + if (files.size() == m_raoLimits.maxSupported || + ((i == njobs - 1) && (files.size() > 1))) { + /* We do a RAO query if: + * 1. the maximum number of files supported by the drive + * for RAO query has been reached + * 2. the end of the jobs list has been reached and there are at least + * 2 unordered files + */ + m_drive->queryRAO(files, m_raoLimits.maxSupported); + + /* Add the RAO sorted files to the new list*/ + for (auto fit = files.begin(); fit != files.end(); fit++) { + uint64_t id = atoi((char*)fit->fseq); + raoOrder.push_back(id); + } + files.clear(); + } + } - (*it)->positioningMethod=cta::PositioningMethod::ByBlock; + /* Copy the rest of the files in the new ordered list */ + for (auto fit = files.begin(); fit != files.end(); fit++) { + uint64_t id = atoi((char*)fit->fseq); + raoOrder.push_back(id); + } + files.clear(); + } + + std::string queryOrderLog = "Query fseq order:"; + for (uint32_t i = 0; i < njobs; i++) { + uint32_t index = m_useRAO ? raoOrder.at(i) : i; + + cta::RetrieveJob *job = m_jobs.at(index).release(); + queryOrderLog += std::to_string(job->selectedTapeFile().fSeq) + " "; + + job->positioningMethod=cta::PositioningMethod::ByBlock; LogContext::ScopedParam sp[]={ - LogContext::ScopedParam(m_lc, Param("fileId", (*it)->retrieveRequest.archiveFileID)), - LogContext::ScopedParam(m_lc, Param("fSeq", (*it)->selectedTapeFile().fSeq)), - LogContext::ScopedParam(m_lc, Param("blockID", (*it)->selectedTapeFile().blockId)), - LogContext::ScopedParam(m_lc, Param("dstURL", (*it)->retrieveRequest.dstURL)) + LogContext::ScopedParam(m_lc, Param("fileId", job->retrieveRequest.archiveFileID)), + LogContext::ScopedParam(m_lc, Param("fSeq", job->selectedTapeFile().fSeq)), + LogContext::ScopedParam(m_lc, Param("blockID", job->selectedTapeFile().blockId)), + LogContext::ScopedParam(m_lc, Param("dstURL", job->retrieveRequest.dstURL)) }; tape::utils::suppresUnusedVariable(sp); m_lc.log(cta::log::INFO, "Recall task created"); - cta::RetrieveJob *job = it->get(); - DiskWriteTask * dwt = new DiskWriteTask(it->release(), m_memManager); + DiskWriteTask * dwt = new DiskWriteTask(job, m_memManager); TapeReadTask * trt = new TapeReadTask(job, *dwt, m_memManager); - + m_diskWriter.push(dwt); m_tapeReader.push(trt); m_lc.log(cta::log::INFO, "Created tasks for recalling a file"); } - LogContext::ScopedParam sp03(m_lc, Param("nbFile", jobs.size())); + m_lc.log(cta::log::INFO, queryOrderLog); + m_jobs.clear(); + LogContext::ScopedParam sp03(m_lc, Param("nbFile", m_jobs.size())); m_lc.log(cta::log::INFO, "Finished processing batch of recall tasks from client"); } //------------------------------------------------------------------------------ //synchronousInjection //------------------------------------------------------------------------------ -bool RecallTaskInjector::synchronousInjection() +bool RecallTaskInjector::synchronousFetch() { - std::vector<std::unique_ptr<cta::RetrieveJob>> jobs; - try { uint64_t files=0; uint64_t bytes=0; @@ -125,7 +212,7 @@ bool RecallTaskInjector::synchronousInjection() if(!job.get()) break; files++; bytes+=job->archiveFile.fileSize; - jobs.emplace_back(job.release()); + m_jobs.emplace_back(job.release()); } } catch (cta::exception::Exception & ex) { cta::log::ScopedParamContainer scoped(m_lc); @@ -139,12 +226,13 @@ bool RecallTaskInjector::synchronousInjection() cta::log::ScopedParamContainer scoped(m_lc); scoped.add("byteSizeThreshold",m_maxBytes) .add("maxFiles", m_maxFiles); - if(jobs.empty()) { + if(m_jobs.empty()) { m_lc.log(cta::log::ERR, "No files to recall: empty mount"); return false; } else { - injectBulkRecalls(jobs); + if (! m_useRAO) + injectBulkRecalls(); return true; } } @@ -174,18 +262,33 @@ void RecallTaskInjector::WorkerThread::run() using cta::log::LogContext; m_parent.m_lc.pushOrReplace(Param("thread", "RecallTaskInjector")); m_parent.m_lc.log(cta::log::DEBUG, "Starting RecallTaskInjector thread"); - + if (m_parent.m_useRAO) { + bool moreJobs = true; + /* RecallTaskInjector is waiting to have access to the drive in order + * to perform the RAO query; while waiting, it is fetching more jobs + */ + while (true) { + if (m_parent.waitForPromise()) break; + if (moreJobs) { + /* Fetching while there are still jobs to fetch + * Otherwise, we are just waiting for the promise + */ + moreJobs = m_parent.synchronousFetch(); + } + } + m_parent.injectBulkRecalls(); + m_parent.m_useRAO = false; + } try{ while (1) { Request req = m_parent.m_queue.pop(); if (req.end) { m_parent.m_lc.log(cta::log::INFO,"Received a end notification from tape thread: triggering the end of session."); + m_parent.signalEndDataMovement(); break; } m_parent.m_lc.log(cta::log::DEBUG,"RecallJobInjector:run: about to call client interface"); - std::vector<std::unique_ptr<cta::RetrieveJob>> jobs; - uint64_t files=0; uint64_t bytes=0; while(files<=req.filesRequested && bytes<=req.bytesRequested) { @@ -193,12 +296,12 @@ void RecallTaskInjector::WorkerThread::run() if(!job.get()) break; files++; bytes+=job->archiveFile.fileSize; - jobs.emplace_back(job.release()); + m_parent.m_jobs.emplace_back(job.release()); } LogContext::ScopedParam sp01(m_parent.m_lc, Param("transactionId", m_parent.m_retrieveMount.getMountTransactionId())); - if (jobs.empty()) { + if (m_parent.m_jobs.empty()) { if (req.lastCall) { m_parent.m_lc.log(cta::log::INFO,"No more file to recall: triggering the end of session."); m_parent.signalEndDataMovement(); @@ -207,7 +310,7 @@ void RecallTaskInjector::WorkerThread::run() m_parent.m_lc.log(cta::log::DEBUG,"In RecallJobInjector::WorkerThread::run(): got empty list, but not last call. NoOp."); } } else { - m_parent.injectBulkRecalls(jobs); + m_parent.injectBulkRecalls(); } } // end of while(1) } //end of try @@ -218,7 +321,6 @@ void RecallTaskInjector::WorkerThread::run() m_parent.m_lc.logBacktrace(cta::log::ERR,ex.backtrace()); m_parent.m_lc.log(cta::log::ERR,"In RecallJobInjector::WorkerThread::run(): " "could not retrieve a list of file to recall. End of session"); - m_parent.signalEndDataMovement(); m_parent.deleteAllTasks(); } diff --git a/tapeserver/castor/tape/tapeserver/daemon/RecallTaskInjector.hpp b/tapeserver/castor/tape/tapeserver/daemon/RecallTaskInjector.hpp index ce8e4df9562bb23f4257243ef6960e472d6aafc4..8d1443da76016f97fe6d7bbfe890f65c72b3855a 100644 --- a/tapeserver/castor/tape/tapeserver/daemon/RecallTaskInjector.hpp +++ b/tapeserver/castor/tape/tapeserver/daemon/RecallTaskInjector.hpp @@ -29,6 +29,9 @@ #include "common/threading/Thread.hpp" #include "scheduler/RetrieveJob.hpp" #include "scheduler/RetrieveMount.hpp" +#include "castor/tape/tapeserver/drive/DriveInterface.hpp" + +#include <future> namespace castor{ namespace tape{ @@ -99,7 +102,7 @@ public: * @param byteSizeThreshold total bytes count at least requested * @return true if there are jobs to be done, false otherwise */ - bool synchronousInjection(); + bool synchronousFetch(); /** * Wait for the inner thread to finish @@ -110,6 +113,22 @@ public: * Start the inner thread */ void startThreads(); + + /** + * Set the drive interface in use + * @param di - Drive interface + */ + void setDriveInterface(castor::tape::tapeserver::drive::DriveInterface *di); + + /** + * Initialize Recommended Access Order parameters + */ + void initRAO(); + + bool waitForPromise(); + + void setPromise(); + private: /** * It will signal to the disk read thread pool, tape write single thread @@ -124,9 +143,8 @@ private: /** * Create all the tape-read and write-disk tasks for set of files to retrieve - * @param jobs */ - void injectBulkRecalls(std::vector<std::unique_ptr<cta::RetrieveJob>>& jobs); + void injectBulkRecalls(); /** * A request of files to recall. We request EITHER @@ -179,6 +197,11 @@ private: /// the client who is sending us jobs cta::RetrieveMount &m_retrieveMount; + /// Drive interface needed for performing Recommended Access Order query + castor::tape::tapeserver::drive::DriveInterface * m_drive; + + std::vector<std::unique_ptr<cta::RetrieveJob>> m_jobs; + /** * utility member to log some pieces of information */ @@ -193,6 +216,21 @@ private: //maximal number of cumulated byte requested. at once const uint64_t m_maxBytes; + + /** Flag indicating if the file recalls are performed using + * the Recommended Access Order (RAO) + */ + bool m_useRAO; + + /** Drive-specific RAO parameters */ + SCSI::Structures::RAO::udsLimitsPage_t m_raoLimits; + + /** + * The promise for reordering the read tasks according to RAO by the + * RecallTaskInjector. The tasks to be run are placed in the m_tasks queue + */ + std::promise<void> m_raoPromise; + std::future<void> m_raoFuture; }; } //end namespace daemon diff --git a/tapeserver/castor/tape/tapeserver/daemon/RecallTaskInjectorTest.cpp b/tapeserver/castor/tape/tapeserver/daemon/RecallTaskInjectorTest.cpp index b4e4826cf1cfe775e2d1c3596933b9f501acdff8..d6c809716d0fa3e1e9d8a841a81bfffe0466fdd3 100644 --- a/tapeserver/castor/tape/tapeserver/daemon/RecallTaskInjectorTest.cpp +++ b/tapeserver/castor/tape/tapeserver/daemon/RecallTaskInjectorTest.cpp @@ -163,7 +163,7 @@ namespace unitTests FakeSingleTapeReadThread tapeRead(drive, mc, gsr, volume, cap, lc); tapeserver::daemon::RecallTaskInjector rti(mm, tapeRead, diskWrite, trm, maxNbJobsInjectedAtOnce, blockSize, lc); - ASSERT_EQ(true, rti.synchronousInjection()); + ASSERT_EQ(true, rti.synchronousFetch()); ASSERT_EQ(maxNbJobsInjectedAtOnce+1, diskWrite.m_tasks.size()); ASSERT_EQ(maxNbJobsInjectedAtOnce+1, tapeRead.m_tasks.size()); @@ -224,7 +224,7 @@ namespace unitTests tapeserver::daemon::RecallTaskInjector rti(mm, tapeRead, diskWrite, trm, 6, blockSize, lc); - ASSERT_FALSE(rti.synchronousInjection()); + ASSERT_FALSE(rti.synchronousFetch()); ASSERT_EQ(0U, diskWrite.m_tasks.size()); ASSERT_EQ(0U, tapeRead.m_tasks.size()); ASSERT_EQ(1, trm.getJobs); diff --git a/tapeserver/castor/tape/tapeserver/daemon/TapeReadSingleThread.cpp b/tapeserver/castor/tape/tapeserver/daemon/TapeReadSingleThread.cpp index de61a016394f04a503258beca7a541268a668778..f17a567ef1099dbb071087dfb53f3e77223f2c12 100644 --- a/tapeserver/castor/tape/tapeserver/daemon/TapeReadSingleThread.cpp +++ b/tapeserver/castor/tape/tapeserver/daemon/TapeReadSingleThread.cpp @@ -36,13 +36,15 @@ castor::tape::tapeserver::daemon::TapeReadSingleThread::TapeReadSingleThread( cta::log::LogContext& lc, RecallReportPacker &rrp, const bool useLbp, + const bool useRAO, const std::string & externalEncryptionKeyScript) : TapeSingleThreadInterface<TapeReadTask>(drive, mc, initialProcess, volInfo, capUtils, lc, externalEncryptionKeyScript), m_maxFilesRequest(maxFilesRequest), m_watchdog(watchdog), m_rrp(rrp), - m_useLbp(useLbp) {} + m_useLbp(useLbp), + m_useRAO(useRAO) {} //------------------------------------------------------------------------------ //TapeCleaning::~TapeCleaning() @@ -154,13 +156,13 @@ castor::tape::tapeserver::daemon::TapeReadSingleThread::popAndRequestMoreJobs(){ vrp = m_tasks.popGetSize(); // If we just passed (down) the half full limit, ask for more // (the remaining value is after pop) - if(vrp.remaining + 1 == m_maxFilesRequest/2) { - // This is not a last call - m_taskInjector->requestInjection(false); - } else if (0 == vrp.remaining) { + if(0 == vrp.remaining) { // This is a last call: if the task injector comes up empty on this // one, he'll call it the end. m_taskInjector->requestInjection(true); + } else if (vrp.remaining + 1 == m_maxFilesRequest/2) { + // This is not a last call + m_taskInjector->requestInjection(false); } return vrp.value; } @@ -265,6 +267,10 @@ void castor::tape::tapeserver::daemon::TapeReadSingleThread::run() { m_logContext.log(cta::log::ERR, "Drive encryption could not be enabled for this mount."); throw; } + if (m_useRAO) { + /* Give the RecallTaskInjector access to the drive to perform RAO query */ + m_taskInjector->setPromise(); + } // Then we have to initialise the tape read session currentErrorToCount = "Error_tapesCheckLabelBeforeReading"; std::unique_ptr<castor::tape::tapeFile::ReadSession> rs(openReadSession()); diff --git a/tapeserver/castor/tape/tapeserver/daemon/TapeReadSingleThread.hpp b/tapeserver/castor/tape/tapeserver/daemon/TapeReadSingleThread.hpp index 4e0bf007add867320e08a5277f177e62310cbdcc..f686faa39c849380e6c3a2c97f937255717d94f3 100644 --- a/tapeserver/castor/tape/tapeserver/daemon/TapeReadSingleThread.hpp +++ b/tapeserver/castor/tape/tapeserver/daemon/TapeReadSingleThread.hpp @@ -67,16 +67,17 @@ public: cta::log::LogContext & lc, RecallReportPacker &rrp, const bool useLbp, + const bool useRAO, const std::string & externalEncryptionKeyScript); - /** - * Set the task injector. Has to be done that way (and not in the constructor) - * because there is a dependency - * @param ti the task injector - */ - void setTaskInjector(RecallTaskInjector * ti) { - m_taskInjector = ti; - } + /** + * Set the task injector. Has to be done that way (and not in the constructor) + * because there is a dependency + * @param ti the task injector + */ + void setTaskInjector(RecallTaskInjector * ti) { + m_taskInjector = ti; + } private: @@ -146,6 +147,11 @@ private: */ const bool m_useLbp; + /** + * The boolean variable describing to use on not to use Recommended + * Access Order + */ + bool m_useRAO; /// Helper virtual function to access the watchdog from parent class virtual void countTapeLogError(const std::string & error) { m_watchdog.addToErrorCount(error); diff --git a/tapeserver/castor/tape/tapeserver/daemon/TapeSingleThreadInterface.hpp b/tapeserver/castor/tape/tapeserver/daemon/TapeSingleThreadInterface.hpp index d63297d8cf3e2728741f557f60545bcce1f4ee54..b966eae4af742c0c7286be4848d51a89ba12a23a 100644 --- a/tapeserver/castor/tape/tapeserver/daemon/TapeSingleThreadInterface.hpp +++ b/tapeserver/castor/tape/tapeserver/daemon/TapeSingleThreadInterface.hpp @@ -287,6 +287,10 @@ public: m_stats.waitInstructionsTime = secs; } + virtual castor::tape::tapeserver::drive::DriveInterface* getDriveReference() { + return &m_drive; + } + /** * Constructor * @param drive An interface to manipulate the drive to manipulate the tape diff --git a/tapeserver/castor/tape/tapeserver/drive/DriveGeneric.cpp b/tapeserver/castor/tape/tapeserver/drive/DriveGeneric.cpp index 73950fbc4354ecf76c8c52cd6256465d3b113703..4937477d940d778fc4fb4c6cbb65107abb934a06 100644 --- a/tapeserver/castor/tape/tapeserver/drive/DriveGeneric.cpp +++ b/tapeserver/castor/tape/tapeserver/drive/DriveGeneric.cpp @@ -31,6 +31,10 @@ #include <errno.h> +#include <string> +#include <map> +#include <list> + namespace castor { namespace tape { namespace tapeserver { @@ -750,6 +754,117 @@ bool drive::DriveMHVTL::isEncryptionCapEnabled() { return false; } +SCSI::Structures::RAO::udsLimitsPage_t drive::DriveGeneric::getLimitUDS() { + SCSI::Structures::LinuxSGIO_t sgh; + SCSI::Structures::RAO::recieveRAO_t cdb; + SCSI::Structures::senseData_t<127> senseBuff; + unsigned char dataBuff[sizeof(SCSI::Structures::RAO::udsLimitsPage_t)]; + + cdb.serviceAction = 0x1d; + cdb.udsLimits = 1; + SCSI::Structures::setU32(cdb.allocationLength, SCSI::modeRAO::DEFAULT_RRAO_ALLOCATION); + + sgh.setCDB(&cdb); + sgh.setSenseBuffer(&senseBuff); + sgh.setDataBuffer(&dataBuff); + sgh.dxfer_direction = SG_DXFER_FROM_DEV; + + /* Manage both system error and SCSI errors. */ + cta::exception::Errnum::throwOnMinusOne( + m_sysWrapper.ioctl(this->m_tapeFD, SG_IO, &sgh), + "Failed SG_IO ioctl in DriveGeneric::getLimitUDS"); + SCSI::ExceptionLauncher(sgh, "SCSI error in DriveGeneric::getLimitUDS"); + + SCSI::Structures::RAO::udsLimitsPage_t & limits = + *(SCSI::Structures::RAO::udsLimitsPage_t *) dataBuff; + + return limits; +} + +void drive::DriveGeneric::generateRAO(std::list<SCSI::Structures::RAO::blockLims> &files, + int maxSupported) { + SCSI::Structures::LinuxSGIO_t sgh; + SCSI::Structures::RAO::generateRAO_t cdb; + SCSI::Structures::senseData_t<127> senseBuff; + + int udSize = std::min((int) files.size(), maxSupported); + + std::unique_ptr<SCSI::Structures::RAO::udsDescriptor[]> ud (new SCSI::Structures::RAO::udsDescriptor[udSize]()); + + auto it = files.begin(); + for (int i = 0; i < udSize; ++i) { + strncpy((char*)ud.get()[i].udsName, (char*)it->fseq, 10); + SCSI::Structures::setU64(ud.get()[i].beginLogicalObjID, it->begin); + SCSI::Structures::setU64(ud.get()[i].endLogicalObjID, it->end); + ++it; + } + + SCSI::Structures::RAO::generateRAOParams_t params; + int real_params_len = sizeof(params) - (2000 - udSize) * + sizeof(SCSI::Structures::RAO::udsDescriptor); + + cdb.serviceAction = 0x1d; + + SCSI::Structures::setU32(cdb.paramsListLength, real_params_len); + SCSI::Structures::setU32(params.additionalData, udSize * sizeof(*(ud.get()))); + memcpy(¶ms.userDataSegmentDescriptors, ud.get(), udSize * sizeof(*(ud.get()))); + + sgh.setCDB(&cdb); + sgh.setSenseBuffer(&senseBuff); + sgh.setDataBuffer(¶ms); + sgh.dxfer_direction = SG_DXFER_TO_DEV; + + /* Manage both system error and SCSI errors. */ + cta::exception::Errnum::throwOnMinusOne( + m_sysWrapper.ioctl(this->m_tapeFD, SG_IO, &sgh), + "Failed SG_IO ioctl in DriveGeneric::requestRAO"); + SCSI::ExceptionLauncher(sgh, "SCSI error in DriveGeneric::requestRAO"); +} + +void drive::DriveGeneric::receiveRAO(std::list<SCSI::Structures::RAO::blockLims> &files, + int offset, int allocationLength) { + SCSI::Structures::LinuxSGIO_t sgh; + SCSI::Structures::RAO::recieveRAO_t cdb; + SCSI::Structures::senseData_t<255> senseBuff; + unsigned char dataBuff[sizeof(SCSI::Structures::RAO::raoList)]; + + cdb.udsLimits = 0; + cdb.serviceAction = 0x1d; + + SCSI::Structures::setU32(cdb.allocationLength, allocationLength); + SCSI::Structures::setU32(cdb.raoListOffset, offset); + + sgh.setCDB(&cdb); + sgh.setSenseBuffer(&senseBuff); + sgh.setDataBuffer(&dataBuff); + sgh.dxfer_direction = SG_DXFER_FROM_DEV; + + /* Manage both system error and SCSI errors. */ + cta::exception::Errnum::throwOnMinusOne( + m_sysWrapper.ioctl(this->m_tapeFD, SG_IO, &sgh), + "Failed SG_IO ioctl in DriveGeneric::getRAO"); + SCSI::ExceptionLauncher(sgh, "SCSI error in DriveGeneric::getRAO"); + + SCSI::Structures::RAO::raoList & params = + *(SCSI::Structures::RAO::raoList *) dataBuff; + + uint32_t desc_list_len = SCSI::Structures::toU32(params.raoDescriptorListLength); + for (uint32_t i = 0;i < desc_list_len / sizeof(SCSI::Structures::RAO::udsDescriptor);++i) { + SCSI::Structures::RAO::blockLims bl; + strncpy((char*)bl.fseq, (char*)params.udsDescriptors[i].udsName, 10); + bl.begin = SCSI::Structures::toU64(params.udsDescriptors[i].beginLogicalObjID); + bl.end = SCSI::Structures::toU64(params.udsDescriptors[i].endLogicalObjID); + files.emplace_back(bl); + } +} + +void drive::DriveGeneric::queryRAO(std::list<SCSI::Structures::RAO::blockLims> &files, + int maxSupported) { + generateRAO(files, maxSupported); + files.clear(); + receiveRAO(files, 0, SCSI::modeRAO::DEFAULT_RRAO_ALLOCATION); +} + /** * Function that checks if a tape is blank (contains no records) * @return true if tape is blank, false otherwise diff --git a/tapeserver/castor/tape/tapeserver/drive/DriveGeneric.hpp b/tapeserver/castor/tape/tapeserver/drive/DriveGeneric.hpp index 7031589feafaac9555a072631cee0f59c71d962e..333f74e918c617f430e485927db4575e536be85d 100644 --- a/tapeserver/castor/tape/tapeserver/drive/DriveGeneric.hpp +++ b/tapeserver/castor/tape/tapeserver/drive/DriveGeneric.hpp @@ -452,6 +452,20 @@ namespace drive { */ virtual bool isEncryptionCapEnabled(); + /** + * Query the drive for the maximum number and size of User Data Segments (UDS) + * @return udsLimitsPage_t class. A pair of the above mentioned parameters + */ + virtual SCSI::Structures::RAO::udsLimitsPage_t getLimitUDS(); + + /** + * Query the drive for the Recommended Access Order (RAO) + * for a series of files + * @param filename The name of the file containing the sequential order of + * a list of files [line format: ID:BLOCK_START:BLOCK_END] + */ + virtual void queryRAO(std::list<SCSI::Structures::RAO::blockLims> &files, int maxSupported); + protected: SCSI::DeviceInfo m_SCSIInfo; int m_tapeFD; @@ -485,6 +499,23 @@ namespace drive { virtual void setLogicalBlockProtection(const unsigned char method, unsigned char methodLength, const bool enableLPBforRead, const bool enableLBBforWrite); + + /** + * Send to the drive the command to generate the Recommended Access Order for + * a series of files + * @param blocks A mapping between a string identifier referring the file ID + * and a pair of block limits + * @param maxSupported The maximum number of UDS supported - obtained by getLimitUDS() + */ + virtual void generateRAO(std::list<SCSI::Structures::RAO::blockLims> &files, int maxSupported); + + /** + * Receive the Recommended Access Order + * @param offset + * @param allocationLength + */ + virtual void receiveRAO(std::list<SCSI::Structures::RAO::blockLims> &files, + int offset, int allocationLength); }; class DriveT10000 : public DriveGeneric { diff --git a/tapeserver/castor/tape/tapeserver/drive/DriveInterface.hpp b/tapeserver/castor/tape/tapeserver/drive/DriveInterface.hpp index bd14ff0699150cdd83ac3c2015a7e0f178d1b259..eebc9984cf14c2595536ece31dfd1154e2c50a25 100644 --- a/tapeserver/castor/tape/tapeserver/drive/DriveInterface.hpp +++ b/tapeserver/castor/tape/tapeserver/drive/DriveInterface.hpp @@ -33,6 +33,8 @@ #include "common/exception/Exception.hpp" #include "common/exception/TimeOut.hpp" +#include <list> + /** * Class wrapping the tape server. Has to be templated (and hence fully in .hh) * to allow unit testing against system wrapper. @@ -230,6 +232,9 @@ namespace drive { virtual lbpToUse getLbpToUse() = 0; virtual bool hasTapeInPlace() = 0; + virtual SCSI::Structures::RAO::udsLimitsPage_t getLimitUDS() = 0; + virtual void queryRAO(std::list<SCSI::Structures::RAO::blockLims> &files, int maxSupported) = 0; + /** * The configuration of the tape drive as parsed from the TPCONFIG file. */ diff --git a/tapeserver/castor/tape/tapeserver/drive/FakeDrive.cpp b/tapeserver/castor/tape/tapeserver/drive/FakeDrive.cpp index 86faa33e297bfec67641ac696d5747a3a468bd88..0990bacb6b3a9264de9471619612408e32348e54 100644 --- a/tapeserver/castor/tape/tapeserver/drive/FakeDrive.cpp +++ b/tapeserver/castor/tape/tapeserver/drive/FakeDrive.cpp @@ -22,6 +22,7 @@ *****************************************************************************/ #include "castor/tape/tapeserver/drive/FakeDrive.hpp" +#include "castor/tape/tapeserver/SCSI/Structures.hpp" #include <iostream> namespace { @@ -297,6 +298,19 @@ bool castor::tape::tapeserver::drive::FakeDrive::hasTapeInPlace() { return true; } +castor::tape::SCSI::Structures::RAO::udsLimitsPage_t + castor::tape::tapeserver::drive::FakeDrive::getLimitUDS() { + castor::tape::SCSI::Structures::RAO::udsLimitsPage_t lims; + lims.maxSize = 30000; + lims.maxSupported = 30; + return lims; +} + +void castor::tape::tapeserver::drive::FakeDrive::queryRAO( + std::list<SCSI::Structures::RAO::blockLims> &files, int maxSupported) { + files.reverse(); +} + std::map<std::string,uint64_t> castor::tape::tapeserver::drive::FakeDrive::getTapeWriteErrors() { std::map<std::string,uint64_t> writeErrorsStats; writeErrorsStats["mountTotalCorrectedWriteErrors"] = 5; diff --git a/tapeserver/castor/tape/tapeserver/drive/FakeDrive.hpp b/tapeserver/castor/tape/tapeserver/drive/FakeDrive.hpp index 12575f5a24e3046deee5471c5d81ffa4760724ad..62e79b68395f202802486036341b3ee4625a1011 100644 --- a/tapeserver/castor/tape/tapeserver/drive/FakeDrive.hpp +++ b/tapeserver/castor/tape/tapeserver/drive/FakeDrive.hpp @@ -109,6 +109,8 @@ namespace drive { virtual bool isTapeBlank(); virtual lbpToUse getLbpToUse(); virtual bool hasTapeInPlace(); + virtual castor::tape::SCSI::Structures::RAO::udsLimitsPage_t getLimitUDS(); + virtual void queryRAO(std::list<SCSI::Structures::RAO::blockLims> &files, int maxSupported); }; }}}} diff --git a/tapeserver/castor/tape/tapeserver/file/BasicReadWriteTest.cpp b/tapeserver/castor/tape/tapeserver/file/BasicReadWriteTest.cpp new file mode 100644 index 0000000000000000000000000000000000000000..97a1c3fc8a4574821acef2afdf5655f17c199218 --- /dev/null +++ b/tapeserver/castor/tape/tapeserver/file/BasicReadWriteTest.cpp @@ -0,0 +1,284 @@ +/****************************************************************************** + * + * This file is part of the Castor project. + * See http://castor.web.cern.ch/castor + * + * Copyright (C) 2017 CERN + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * + * + * @author Castor Dev team, castor-dev@cern.ch + *****************************************************************************/ + +/** + * Test main program. For development use. + */ + +#include "castor/tape/tapeserver/SCSI/Device.hpp" +#include "castor/tape/tapeserver/system/Wrapper.hpp" +#include "castor/tape/tapeserver/drive/DriveInterface.hpp" +#include "File.hpp" +#include "../daemon/VolumeInfo.hpp" +#include "scheduler/ArchiveJob.hpp" +#include <iostream> +#include <assert.h> +#include <memory> +#include <string> +#include <list> + +char gen_random() { + static const char alphanum[] = + "123456789" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz"; + + return alphanum[rand() % (sizeof(alphanum) - 1)]; +} + +enum { + BLOCK_TEST, + FILE_TEST, + RAO_TEST +}; + +int test = RAO_TEST; + +class BasicRetrieveJob: public cta::RetrieveJob { + public: + BasicRetrieveJob() : cta::RetrieveJob(*((cta::RetrieveMount *)NULL), + cta::common::dataStructures::RetrieveRequest(), + cta::common::dataStructures::ArchiveFile(), 1, + cta::PositioningMethod::ByBlock) {} + }; + +class BasicArchiveJob: public cta::ArchiveJob { +public: + BasicArchiveJob(): cta::ArchiveJob(*((cta::ArchiveMount *)NULL), + *((cta::catalogue::Catalogue *)NULL), cta::common::dataStructures::ArchiveFile(), + "", cta::common::dataStructures::TapeFile()) { + } +}; + +std::vector<std::string> split(std::string to_split, std::string delimiter) { + std::vector<std::string> toBeReturned; + int pos = 0; + while ((pos = to_split.find(delimiter)) != -1) { + std::string token = to_split.substr(0, pos); + toBeReturned.push_back(token); + to_split.erase(0, pos + delimiter.length()); + } + toBeReturned.push_back(to_split); + return toBeReturned; +} + +int main (int argc, char *argv[]) +{ + int fail = 0; + castor::tape::System::realWrapper sWrapper; + castor::tape::SCSI::DeviceVector dl(sWrapper); + for(castor::tape::SCSI::DeviceVector::iterator i = dl.begin(); + i != dl.end(); i++) { + castor::tape::SCSI::DeviceInfo & dev = (*i); + std::cout << std::endl << "-- SCSI device: " + << dev.sg_dev << " (" << dev.nst_dev << ")" << std::endl; + if (dev.type == castor::tape::SCSI::Types::tape) { + try { + // Create drive object and open tape device + std::unique_ptr<castor::tape::tapeserver::drive::DriveInterface> drive( + castor::tape::tapeserver::drive::createDrive(dev, sWrapper)); + + /** + * From now we could use generic SCSI request for the drive object. + * We should be aware that there might be a problem with tape in the + * drive for example incompatible media installed. + */ + + try { + /** + * Gets generic device info for the drive object. + */ + castor::tape::tapeserver::drive::deviceInfo devInfo; + devInfo = drive->getDeviceInfo(); + std::cout << "-- INFO --------------------------------------" << std::endl + << " devInfo.vendor : '" << devInfo.vendor << "'" << std::endl + << " devInfo.product : '" << devInfo.product << "'" << std::endl + << " devInfo.productRevisionLevel : '" << devInfo.productRevisionLevel << "'" << std::endl + << " devInfo.serialNumber : '" << devInfo.serialNumber << "'" << std::endl + << "----------------------------------------------" << std::endl; + } catch (std::exception & e) { + fail = 1; + std::string temp = e.what(); + std::cout << "----------------------------------------------" << std::endl + << temp + << "-- INFO --------------------------------------" << std::endl; + continue; + } + + try { + /** + * Checks if the drive ready to use the tape installed loaded into it. + */ + drive->waitUntilReady(5); + } catch(cta::exception::Exception &ne) { + std::string temp=ne.getMessage().str(); + fail = 1; + std::cout << "----------------------------------------------" << std::endl + << temp << std::endl + << "----------------------------------------------" << std::endl; + continue; + } + + drive->enableCRC32CLogicalBlockProtectionReadWrite(); + + try { + + if (test == BLOCK_TEST) { + const size_t count = 10; + unsigned char data[count]; + memset(data, 0, count); + + std::cout << "Rewinding..." << std::endl; + drive->rewind(); // go back to the beginning of tape after Victor's positioning + + memset(data, 'a', count-1); + std::cout << "Writing 1st block (9 a's)..." << std::endl; + drive->writeBlock((void *)data, count); // write 9 a's + string term + + std::cout << "Writing EOD (2 filemarks)..." << std::endl; + drive->writeSyncFileMarks(2); // EOD and flush + + std::cout << "Rewinding..." << std::endl; + drive->rewind(); // go back to the beginning of tape + + std::cout << "Reading back 1st block 9 a's)..." << std::endl; + memset(data, 0, count); + drive->readBlock((void *)data, count); // read 9 a's + string term + + std::cout << "Rewinding..." << std::endl; + drive->rewind(); // go back to the beginning of tape + + } + else if (test == FILE_TEST) { + drive->rewind(); + + castor::tape::tapeFile::LabelSession *ls; + std::string label = "TW8510"; + ls = new castor::tape::tapeFile::LabelSession(*drive, label, true); + delete ls; + + castor::tape::tapeserver::daemon::VolumeInfo m_volInfo; + m_volInfo.vid = label; + m_volInfo.nbFiles = 0; + m_volInfo.mountType = cta::common::dataStructures::MountType::Archive; + + castor::tape::tapeFile::WriteSession *ws; + ws = new castor::tape::tapeFile::WriteSession(*drive, m_volInfo, 0, true, true); + + uint32_t block_size = 262144; + uint32_t no_blocks = 100; + + // Write test files ( ! add stop condition) + int j = 1; + while (true) { + BasicArchiveJob fileToMigrate; + fileToMigrate.archiveFile.fileSize = block_size * 100; + fileToMigrate.archiveFile.archiveFileID = j; + fileToMigrate.tapeFile.fSeq = j; + std::unique_ptr<castor::tape::tapeFile::WriteFile> wf; + wf.reset(new castor::tape::tapeFile::WriteFile(ws, fileToMigrate, block_size)); + + std::string testString = ""; + for (uint32_t i = 0; i < block_size - 5; i++) + testString += gen_random(); + for (uint32_t k = 0; k < no_blocks; k++) { + wf->write(testString.c_str(),testString.size()); + } + + wf->close(); + j++; + } + + drive->rewind(); + + // Now read a random file + castor::tape::tapeFile::ReadSession *rs; + rs = new castor::tape::tapeFile::ReadSession(*drive, m_volInfo, true); + + BasicRetrieveJob fileToRecall; + fileToRecall.selectedCopyNb=1; + fileToRecall.archiveFile.tapeFiles[1]; + fileToRecall.selectedTapeFile().blockId = 110; // here should be the block ID of HDR1 + fileToRecall.selectedTapeFile().fSeq = 2; + fileToRecall.retrieveRequest.archiveFileID = 2; + fileToRecall.positioningMethod = cta::PositioningMethod::ByBlock; + + castor::tape::tapeFile::ReadFile rf(rs, fileToRecall); + size_t bs = rf.getBlockSize(); + char *data = new char[bs+1]; + j = 0; + while(j < 100) { + rf.read(data, bs); + j++; + std::cout << data << std::endl; + } + } + else if (test == RAO_TEST) { + if (argc != 2) { + std::cout << "For RAO testing the first parameter should be " + "the file containing the sequential order" << std::endl; + } + else { + drive->rewind(); + + std::list<castor::tape::SCSI::Structures::RAO::blockLims> files; + std::ifstream ns_file_pick(argv[1]); + if (ns_file_pick.is_open()) { + std::string line; + while (getline(ns_file_pick, line)) { + std::vector<std::string> tokens = split(line, ":"); + castor::tape::SCSI::Structures::RAO::blockLims lims; + std::cout << tokens[0].c_str() << std::endl; + tokens[0] += '\0'; + strcpy((char*)lims.fseq, tokens[0].c_str()); + lims.begin = std::stoi(tokens[1]); + lims.end = std::stoi(tokens[2]); + files.push_back(lims); + } + } + else { + throw -1; + } + castor::tape::SCSI::Structures::RAO::udsLimitsPage_t limits = drive->getLimitUDS(); + drive->queryRAO(files, limits.maxSupported); + } + } + + } catch (std::exception & e) { + fail = 1; + std::cout << "-- EXCEPTION ---------------------------------" << std::endl + << e.what() << std::endl + << "----------------------------------------------" << std::endl; + } + } catch(cta::exception::Exception &ne) { + std::string temp=ne.getMessage().str(); + fail = 1; + std::cout << "----------------------------------------------" << std::endl + << temp << std::endl + << "-- object ------------------------------------" << std::endl; + break; + } + } + } + return fail; +} diff --git a/tapeserver/castor/tape/tapeserver/file/CMakeLists.txt b/tapeserver/castor/tape/tapeserver/file/CMakeLists.txt index 381826cc29ff1361860980997691276b9b43bec8..3142c622b0d34429429cf4b0dab1577795214002 100644 --- a/tapeserver/castor/tape/tapeserver/file/CMakeLists.txt +++ b/tapeserver/castor/tape/tapeserver/file/CMakeLists.txt @@ -40,6 +40,26 @@ if(CMAKE_COMPILER_IS_GNUCC) endif(GCC_VERSION_GE_4_8_0) endif(CMAKE_COMPILER_IS_GNUCC) +if(CMAKE_COMPILER_IS_GNUCC) + if(GCC_VERSION_GE_4_8_0) + set_property(SOURCE BasicReadWriteTest.cpp + PROPERTY COMPILE_FLAGS " -Wno-unused-local-typedefs") + endif(GCC_VERSION_GE_4_8_0) +endif(CMAKE_COMPILER_IS_GNUCC) + +add_executable(BasicReadWriteTest + BasicReadWriteTest.cpp) + +target_link_libraries(BasicReadWriteTest + TapeDrive + ctamediachanger + SCSI + System + ctacommon + ctaTapeServerDaemon + gtest + pthread) + add_library(File ${TAPESERVER_FILE_LIBRARY_SRCS}) target_link_libraries (File XrdCl cryptopp radosstriper)