Commit c8b74a53 authored by Cristina Moraru's avatar Cristina Moraru
Browse files

Implement RAO at the drive level #57



This commit adds the feature of querying the drive for the
Recommended Access Order (RAO) of a series of files and adds
a test file for this feature.

Changes of this commit:
  *  Extend the DriveInterface with a virtual method queryRAO
which takes as parameter the name of the file containing a set
of files for which the RAO is requested. The line format of this
file is the following:

FILE_ID:BLOCK_START:BLOCK_END

  *  Extend DriveGeneric class with the implementation of the
method queryRAO, along with a few other auxiliary methods; some
key auxiliary methods are getLimitUDS, generateRAO, receiveRAO

  *  Add method queryRAO in object FakeDrive returning Exception:
Not Implemented

  *  Add auxiliary constants and structures in a new 'RAO' namespace

  *  Add test file: BasicReadWriteTest.cpp which can perform 3 tests
according to the setting of variable 'test'
	1. BLOCK_TEST - basic read / write of blocks
	2. FILE_TEST - writes a series of CASTOR files and reads
a random one
	3. RAO_TEST - performs a RAO query for a series of files
given in the filename specified in argv[1]. The file format should
respect the above mentioned order.
Signed-off-by: default avatarCristina Moraru <cristina-gabriela.moraru@cern.ch>
parent fd725908
......@@ -739,6 +739,13 @@ namespace SCSI {
* Addition for the mode page Length for the Control Data Protection Mode Page
*/
const unsigned char controlDataProtectionModePageLengthAddition = 4;
class modeRAO {
public:
enum {
DEFAULT_RRAO_ALLOCATION = 64000
};
};
} // namespace SCSI
} // namespace tape
} // namespace castor
......@@ -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,139 @@ 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);
}
int maxSupported;
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);
}
int begin;
int end;
};
}
template <size_t n>
/**
......
......@@ -31,6 +31,9 @@
#include <errno.h>
#include <string>
#include <map>
namespace castor {
namespace tape {
namespace tapeserver {
......@@ -750,6 +753,156 @@ 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[4];
SCSI::Structures::setU32(cdb.allocationLength, SCSI::modeRAO::DEFAULT_RRAO_ALLOCATION);
if (this->getDeviceInfo().vendor.substr(0, 3) == "IBM") {
cdb.serviceAction = 0x11;
std::cout << "vendor IBM" << std::endl;
}
else if (this->getDeviceInfo().vendor.substr(0, 3) == "STK") {
cdb.serviceAction = 0x1d;
}
cdb.udsLimits = 1;
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::map<std::string, SCSI::Structures::RAO::blockLims> blocks,
int maxSupported) {
SCSI::Structures::LinuxSGIO_t sgh;
SCSI::Structures::RAO::generateRAO_t cdb;
SCSI::Structures::senseData_t<127> senseBuff;
//unsigned char dataBuff[1024];
int udSize = std::min((int) blocks.size(), maxSupported);
SCSI::Structures::RAO::udsDescriptor *ud = new SCSI::Structures::RAO::udsDescriptor[udSize];
auto it = blocks.begin();
for (int i = 0;i < udSize;++i) {
memcpy(ud[i].udsName, it->first.c_str(), sizeof(ud[i].udsName));
SCSI::Structures::setU64(ud[i].beginLogicalObjID, it->second.begin);
SCSI::Structures::setU64(ud[i].endLogicalObjID, it->second.end);
++it;
}
SCSI::Structures::RAO::generateRAOParams_t params;
int real_params_len = sizeof(params) - (2000 - udSize) *
sizeof(SCSI::Structures::RAO::udsDescriptor);
if (this->getDeviceInfo().vendor.substr(0, 3) == "IBM")
cdb.serviceAction = 0x11;
else if (this->getDeviceInfo().vendor.substr(0, 3) == "STK")
cdb.serviceAction = 0x1d;
SCSI::Structures::setU32(cdb.paramsListLength, real_params_len);
SCSI::Structures::setU32(params.additionalData, udSize * sizeof(*ud));
memcpy(&params.userDataSegmentDescriptors, ud, udSize * sizeof(*ud));
sgh.setCDB(&cdb);
sgh.setSenseBuffer(&senseBuff);
sgh.setDataBuffer(&params);
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(int offset, int allocationLength) {
SCSI::Structures::LinuxSGIO_t sgh;
SCSI::Structures::RAO::recieveRAO_t cdb;
SCSI::Structures::senseData_t<255> senseBuff;
unsigned char dataBuff[64008];
cdb.udsLimits = 0;
if (this->getDeviceInfo().vendor.substr(0, 3) == "IBM")
cdb.serviceAction = 0x11;
else if (this->getDeviceInfo().vendor.substr(0, 3) == "STK")
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) {
std::cout << params.udsDescriptors[i].udsName << ":" << SCSI::Structures::toU64(params.udsDescriptors[i].beginLogicalObjID) <<
":" << SCSI::Structures::toU64(params.udsDescriptors[i].endLogicalObjID) << std::endl;
}
}
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;
}
void drive::DriveGeneric::queryRAO(char *filename) {
std::map<std::string, SCSI::Structures::RAO::blockLims> files;
std::ifstream ns_file_pick(filename);
if (ns_file_pick.is_open()) {
std::string line;
while (getline(ns_file_pick, line)) {
std::vector<std::string> tokens = split(line, ":");
SCSI::Structures::RAO::blockLims lims;
lims.begin = std::stoi(tokens[1]);
lims.end = std::stoi(tokens[2]);
files[tokens[0]] = lims;
}
}
else {
throw -1;
}
SCSI::Structures::RAO::udsLimitsPage_t limits = getLimitUDS();
generateRAO(files, limits.maxSupported);
receiveRAO(0, (new SCSI::modeRAO())->DEFAULT_RRAO_ALLOCATION);
}
/**
* Function that checks if a tape is blank (contains no records)
* @return true if tape is blank, false otherwise
......
......@@ -452,6 +452,14 @@ namespace drive {
*/
virtual bool isEncryptionCapEnabled();
/**
* 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(char *filename);
protected:
SCSI::DeviceInfo m_SCSIInfo;
int m_tapeFD;
......@@ -485,6 +493,28 @@ namespace drive {
virtual void setLogicalBlockProtection(const unsigned char method,
unsigned char methodLength, const bool enableLPBforRead,
const bool enableLBBforWrite);
/**
* 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();
/**
* 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::map<std::string, SCSI::Structures::RAO::blockLims> blocks, int maxSupported);
/**
* Receive the Recommended Access Order
* @param offset
* @param allocationLength
*/
virtual void receiveRAO(int offset, int allocationLength);
};
class DriveT10000 : public DriveGeneric {
......
......@@ -230,6 +230,8 @@ namespace drive {
virtual lbpToUse getLbpToUse() = 0;
virtual bool hasTapeInPlace() = 0;
virtual void queryRAO(char *filename) = 0;
/**
* The configuration of the tape drive as parsed from the TPCONFIG file.
*/
......
......@@ -297,6 +297,10 @@ bool castor::tape::tapeserver::drive::FakeDrive::hasTapeInPlace() {
return true;
}
void castor::tape::tapeserver::drive::FakeDrive::queryRAO(char *filename) {
throw cta::exception::Exception("In DriveFakeDrive::queryRAO: Not implemented.");
}
std::map<std::string,uint64_t> castor::tape::tapeserver::drive::FakeDrive::getTapeWriteErrors() {
std::map<std::string,uint64_t> writeErrorsStats;
writeErrorsStats["mountTotalCorrectedWriteErrors"] = 5;
......
......@@ -109,6 +109,7 @@ namespace drive {
virtual bool isTapeBlank();
virtual lbpToUse getLbpToUse();
virtual bool hasTapeInPlace();
virtual void queryRAO(char *filename);
};
}}}}
/******************************************************************************
*
* 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>
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()) {
}
};
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;