Commit 786a0e48 authored by Steven Murray's avatar Steven Murray
Browse files

Improved Catalogue error handling and reporting

parent d4095d98
/*
* The CERN Tape Archive (CTA) project
* Copyright (C) 2015 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 3 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, see <http://www.gnu.org/licenses/>.
*/
#include "catalogue/AutoRollback.hpp"
#include "catalogue/DbConn.hpp"
#include "common/exception/Exception.hpp"
namespace cta {
namespace catalogue {
//------------------------------------------------------------------------------
// constructor
//------------------------------------------------------------------------------
AutoRollback::AutoRollback(DbConn *const dbConn): m_dbConn(dbConn) {
}
//------------------------------------------------------------------------------
// destructor
//------------------------------------------------------------------------------
AutoRollback::~AutoRollback() {
try {
if(NULL != m_dbConn) {
m_dbConn->rollback();
}
} catch(...) {
// Prevent destructor from throwing
}
}
//------------------------------------------------------------------------------
// cancel
//------------------------------------------------------------------------------
void AutoRollback::cancel() {
m_dbConn = NULL;
}
} // namespace catalogue
} // namespace cta
/*
* The CERN Tape Archive (CTA) project
* Copyright (C) 2015 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 3 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, see <http://www.gnu.org/licenses/>.
*/
#pragma once
namespace cta {
namespace catalogue {
/**
* Forward declaration.
*/
class DbConn;
/**
* A class to automatically rollback a database connection when an instance of
* the class goes out of scope and has not been explictly cancelled.
*/
class AutoRollback {
public:
/**
* Constructor.
*
* @param dbConn The database connection or NULL if the no rollback should
* take place.
*/
AutoRollback(DbConn *const dbConn);
/**
* Prevent copying.
*/
AutoRollback(const AutoRollback &) = delete;
/**
* Destructor.
*/
~AutoRollback();
/**
* Prevent assignment.
*/
AutoRollback &operator=(const AutoRollback &) = delete;
/**
* Cancel the automatic rollback.
*
* This method is idempotent.
*/
void cancel();
private:
/**
* The database connection or NULL if no rollback should take place.
*/
DbConn *m_dbConn;
}; // class DbLogin
} // namespace catalogue
} // namespace cta
......@@ -22,6 +22,7 @@ include_directories (${ORACLE-INSTANTCLIENT_INCLUDE_DIRS})
set (CATALOGUE_LIB_SRC_FILES
ArchiveFileRow.cpp
AutoRollback.cpp
Catalogue.cpp
CatalogueFactory.cpp
ColumnNameToIdx.cpp
......@@ -46,7 +47,8 @@ set (CATALOGUE_LIB_SRC_FILES
SqliteConn.cpp
SqliteRset.cpp
SqliteStmt.cpp
TapeForWriting.cpp)
TapeForWriting.cpp
UserError.cpp)
add_library (ctacatalogue SHARED
${CATALOGUE_LIB_SRC_FILES})
......
......@@ -19,6 +19,7 @@
#include "catalogue/ArchiveFileRow.hpp"
#include "catalogue/RdbmsCatalogue.hpp"
#include "catalogue/CatalogueFactory.hpp"
#include "catalogue/UserError.hpp"
#include "common/exception/Exception.hpp"
#include <gtest/gtest.h>
......@@ -232,7 +233,7 @@ TEST_F(cta_catalogue_InMemoryCatalogueTest, createAdminUser_same_twice) {
m_catalogue->createAdminUser(m_bootstrapAdminSI, m_adminSI.username, "comment 1");
ASSERT_THROW(m_catalogue->createAdminUser(m_bootstrapAdminSI, m_adminSI.username,
"comment 2"), exception::Exception);
"comment 2"), catalogue::UserError);
}
TEST_F(cta_catalogue_InMemoryCatalogueTest, createAdminHost) {
......@@ -341,7 +342,7 @@ TEST_F(cta_catalogue_InMemoryCatalogueTest, createAdminHost_same_twice) {
m_catalogue->createAdminHost(m_bootstrapAdminSI, anotherAdminHost, "comment 1");
ASSERT_THROW(m_catalogue->createAdminHost(m_bootstrapAdminSI,
anotherAdminHost, "comment 2"), exception::Exception);
anotherAdminHost, "comment 2"), catalogue::UserError);
}
TEST_F(cta_catalogue_InMemoryCatalogueTest, isAdmin_false) {
......@@ -397,7 +398,7 @@ TEST_F(cta_catalogue_InMemoryCatalogueTest, createStorageClass_same_twice) {
const std::string comment = "create storage class";
m_catalogue->createStorageClass(m_cliSI, storageClassName, nbCopies, comment);
ASSERT_THROW(m_catalogue->createStorageClass(m_cliSI,
storageClassName, nbCopies, comment), exception::Exception);
storageClassName, nbCopies, comment), catalogue::UserError);
}
TEST_F(cta_catalogue_InMemoryCatalogueTest, createTapePool) {
......@@ -441,9 +442,8 @@ TEST_F(cta_catalogue_InMemoryCatalogueTest, createTapePool_same_twice) {
const std::string comment = "create tape pool";
m_catalogue->createTapePool(m_cliSI, tapePoolName, nbPartialTapes, is_encrypted,
comment);
ASSERT_THROW(m_catalogue->createTapePool(m_cliSI,
tapePoolName, nbPartialTapes, is_encrypted, comment),
exception::Exception);
ASSERT_THROW(m_catalogue->createTapePool(m_cliSI, tapePoolName, nbPartialTapes, is_encrypted, comment),
catalogue::UserError);
}
TEST_F(cta_catalogue_InMemoryCatalogueTest, createArchiveRoute) {
......@@ -595,9 +595,7 @@ TEST_F(cta_catalogue_InMemoryCatalogueTest, createLogicalLibrary_same_twice) {
const std::string logicalLibraryName = "logical_library";
const std::string comment = "create logical library";
m_catalogue->createLogicalLibrary(m_cliSI, logicalLibraryName, comment);
ASSERT_THROW(m_catalogue->createLogicalLibrary(m_cliSI,
logicalLibraryName, comment),
exception::Exception);
ASSERT_THROW(m_catalogue->createLogicalLibrary(m_cliSI, logicalLibraryName, comment), catalogue::UserError);
}
TEST_F(cta_catalogue_InMemoryCatalogueTest, createTape) {
......@@ -664,7 +662,7 @@ TEST_F(cta_catalogue_InMemoryCatalogueTest, createTape_same_twice) {
encryptionKey, capacityInBytes, disabledValue, fullValue, comment);
ASSERT_THROW(m_catalogue->createTape(m_cliSI, vid, logicalLibraryName,
tapePoolName, encryptionKey, capacityInBytes, disabledValue, fullValue,
comment), exception::Exception);
comment), catalogue::UserError);
}
TEST_F(cta_catalogue_InMemoryCatalogueTest, getTapesForWriting) {
......@@ -782,7 +780,7 @@ TEST_F(cta_catalogue_InMemoryCatalogueTest, createMountPolicy_same_twice) {
retrievePriority,
minRetrieveRequestAge,
maxDrivesAllowed,
comment), exception::Exception);
comment), catalogue::UserError);
}
TEST_F(cta_catalogue_InMemoryCatalogueTest, createUser) {
......
......@@ -19,6 +19,7 @@
#include "catalogue/OcciConn.hpp"
#include "catalogue/OcciEnvSingleton.hpp"
#include "catalogue/OracleCatalogue.hpp"
#include "common/exception/Exception.hpp"
namespace cta {
namespace catalogue {
......@@ -46,5 +47,104 @@ uint64_t OracleCatalogue::getNextArchiveFileId() {
throw exception::Exception(std::string(__FUNCTION__) + " not implemented");
}
//------------------------------------------------------------------------------
// selectTapeForUpdate
//------------------------------------------------------------------------------
common::dataStructures::Tape OracleCatalogue::selectTapeForUpdate(const std::string &vid) {
try {
const char *const sql =
"SELECT "
"VID AS VID,"
"LOGICAL_LIBRARY_NAME AS LOGICAL_LIBRARY_NAME,"
"TAPE_POOL_NAME AS TAPE_POOL_NAME,"
"ENCRYPTION_KEY AS ENCRYPTION_KEY,"
"CAPACITY_IN_BYTES AS CAPACITY_IN_BYTES,"
"DATA_IN_BYTES AS DATA_IN_BYTES,"
"LAST_FSEQ AS LAST_FSEQ,"
"IS_DISABLED AS IS_DISABLED,"
"IS_FULL AS IS_FULL,"
"LBP_IS_ON AS LBP_IS_ON,"
"LABEL_DRIVE AS LABEL_DRIVE,"
"LABEL_TIME AS LABEL_TIME,"
"LAST_READ_DRIVE AS LAST_READ_DRIVE,"
"LAST_READ_TIME AS LAST_READ_TIME,"
"LAST_WRITE_DRIVE AS LAST_WRITE_DRIVE,"
"LAST_WRITE_TIME AS LAST_WRITE_TIME,"
"USER_COMMENT AS USER_COMMENT,"
"CREATION_LOG_USER_NAME AS CREATION_LOG_USER_NAME,"
"CREATION_LOG_HOST_NAME AS CREATION_LOG_HOST_NAME,"
"CREATION_LOG_TIME AS CREATION_LOG_TIME,"
"LAST_UPDATE_USER_NAME AS LAST_UPDATE_USER_NAME,"
"LAST_UPDATE_HOST_NAME AS LAST_UPDATE_HOST_NAME,"
"LAST_UPDATE_TIME AS LAST_UPDATE_TIME "
"FROM "
"TAPE "
"WHERE "
"VID = :VID "
"FOR UPDATE;";
std::unique_ptr<DbStmt> stmt(m_conn->createStmt(sql));
stmt->bindString(":VID", vid);
std::unique_ptr<DbRset> rset(stmt->executeQuery());
if (!rset->next()) {
throw exception::Exception(std::string("The tape with VID " + vid + " does not exist"));
}
common::dataStructures::Tape tape;
tape.vid = rset->columnText("VID");
tape.logicalLibraryName = rset->columnText("LOGICAL_LIBRARY_NAME");
tape.tapePoolName = rset->columnText("TAPE_POOL_NAME");
tape.encryptionKey = rset->columnText("ENCRYPTION_KEY");
tape.capacityInBytes = rset->columnUint64("CAPACITY_IN_BYTES");
tape.dataOnTapeInBytes = rset->columnUint64("DATA_IN_BYTES");
tape.lastFSeq = rset->columnUint64("LAST_FSEQ");
tape.disabled = rset->columnUint64("IS_DISABLED");
tape.full = rset->columnUint64("IS_FULL");
tape.lbp = rset->columnUint64("LBP_IS_ON");
tape.labelLog.drive = rset->columnText("LABEL_DRIVE");
tape.labelLog.time = rset->columnUint64("LABEL_TIME");
tape.lastReadLog.drive = rset->columnText("LAST_READ_DRIVE");
tape.lastReadLog.time = rset->columnUint64("LAST_READ_TIME");
tape.lastWriteLog.drive = rset->columnText("LAST_WRITE_DRIVE");
tape.lastWriteLog.time = rset->columnUint64("LAST_WRITE_TIME");
tape.comment = rset->columnText("USER_COMMENT");
common::dataStructures::UserIdentity creatorUI;
creatorUI.name = rset->columnText("CREATION_LOG_USER_NAME");
common::dataStructures::EntryLog creationLog;
creationLog.username = rset->columnText("CREATION_LOG_USER_NAME");
creationLog.host = rset->columnText("CREATION_LOG_HOST_NAME");
creationLog.time = rset->columnUint64("CREATION_LOG_TIME");
tape.creationLog = creationLog;
common::dataStructures::UserIdentity updaterUI;
updaterUI.name = rset->columnText("LAST_UPDATE_USER_NAME");
common::dataStructures::EntryLog updateLog;
updateLog.username = rset->columnText("LAST_UPDATE_USER_NAME");
updateLog.host = rset->columnText("LAST_UPDATE_HOST_NAME");
updateLog.time = rset->columnUint64("LAST_UPDATE_TIME");
tape.lastModificationLog = updateLog;
return tape;
} catch (exception::Exception &ex) {
throw exception::Exception(std::string(__FUNCTION__) + " failed: " + ex.getMessage().str());
}
}
} // namespace catalogue
} // namespace cta
......@@ -68,6 +68,17 @@ public:
*/
virtual uint64_t getNextArchiveFileId();
/**
* Selects the specified tape within th eTape table for update.
*
* This method must be implemented by the sub-classes of RdbmsCatalogue
* because some database technologies directly support SELECT FOR UPDATE
* whilst others do not.
*
* @param vid The volume identifier of the tape.
*/
virtual common::dataStructures::Tape selectTapeForUpdate(const std::string &vid);
}; // class OracleCatalogue
} // namespace catalogue
......
This diff is collapsed.
......@@ -243,24 +243,68 @@ public:
protected:
/**
* An RdbmsCatalogue specific method that inserts the specified row into the
* ArchiveFile table.
* Returns true if the specified admin user exists.
*
* @param row The row to be inserted.
* @param adminUsername The name of the admin user.
* @return True if the admin user exists.
*/
void insertArchiveFile(const ArchiveFileRow &row);
bool adminUserExists(const std::string adminUsername) const;
/**
* Returns true if the specified admin host exists.
*
* @param adminHost The name of the admin host.
* @return True if the admin host exists.
*/
bool adminHostExists(const std::string adminHost) const;
/**
* Returns true if the specified storage class exists.
*
* @param storageClassName The name of the storage class.
* @return True if the storage class exists.
*/
bool storageClassExists(const std::string &storageClassName) const;
/**
* Returns true if the specified tape pool exists.
*
* @param tapePoolName The name of the tape pool.
* @return True if the tape pool exists.
*/
bool tapePoolExists(const std::string &tapePoolName) const;
/**
* Returns true if the specified tape exists.
*
* @param vid The volume identifier of the tape.
* @return True if the tape exists.
*/
bool tapeExists(const std::string &vid) const;
/**
* Returns true if the specified logical library exists.
*
* @param logicalLibraryName The name of the logical library.
* @return True if the logical library exists.
*/
bool logicalLibraryExists(const std::string &logicalLibraryName) const;
/**
* Returns true if the specified mount policy exists.
*
* @param mountPolicyName The name of the mount policy
* @return True if the mount policy exists.
*/
bool mountPolicyExists(const std::string &mountPolicyName) const;
/**
* Returns the unique identifier of the specified archive file. Note that
* this method is required by RdbmsCatalogue because SQLite does not support
* the SQL syntax: "INSERT INTO ... VALUES ... RETURNING ... INTO ...".
* An RdbmsCatalogue specific method that inserts the specified row into the
* ArchiveFile table.
*
* @param diskInstance The name of teh disk storage instance within which the
* specified disk file identifier is unique.
* @param diskFileId The disk identifier of the file.
* @return The unique identifier of the specified archive file.
* @param row The row to be inserted.
*/
uint64_t getArchiveFileId(const std::string &diskInstance, const std::string &diskFileId) const;
void insertArchiveFile(const ArchiveFileRow &row);
/**
* Mutex to be used to a take a global lock on the in-memory database.
......@@ -323,20 +367,13 @@ protected:
void setTapeLastFSeq(const std::string &vid, const uint64_t lastFSeq);
/**
* Returns the last FSeq of the speficied tape.
* Returns the last FSeq of the specified tape.
*
* @param vid The volume identifier of the tape.
* @return The last FSeq.
*/
uint64_t getTapeLastFSeq(const std::string &vid) const;
/**
* Selects the specified tape within th eTape table for update.
*
* @param vid The volume identifier of the tape.
*/
common::dataStructures::Tape selectTapeForUpdate(const std::string &vid);
/**
* Updates the lastFSeq column of the specified tape within the Tape table.
*
......@@ -382,6 +419,17 @@ protected:
*/
virtual uint64_t getNextArchiveFileId() = 0;
/**
* Selects the specified tape within th eTape table for update.
*
* This method must be implemented by the sub-classes of RdbmsCatalogue
* because some database technologies directly support SELECT FOR UPDATE
* whilst others do not.
*
* @param vid The volume identifier of the tape.
*/
virtual common::dataStructures::Tape selectTapeForUpdate(const std::string &vid) = 0;
}; // class RdbmsCatalogue
} // namespace catalogue
......
......@@ -19,6 +19,7 @@
#include "catalogue/SqliteCatalogue.hpp"
#include "catalogue/RdbmsCatalogueSchema.hpp"
#include "catalogue/SqliteConn.hpp"
#include "common/exception/Exception.hpp"
namespace cta {
namespace catalogue {
......@@ -75,5 +76,107 @@ uint64_t SqliteCatalogue::getNextArchiveFileId() {
}
}
//------------------------------------------------------------------------------
// selectTapeForUpdate
//------------------------------------------------------------------------------
common::dataStructures::Tape SqliteCatalogue::selectTapeForUpdate(const std::string &vid) {
try {
// SQLite does not support SELECT FOR UPDATE
// Emulate SELECT FOR UPDATE by taking an exclusive lock on the database
m_conn->executeNonQuery("BEGIN EXCLUSIVE;");
const char *const sql =
"SELECT "
"VID AS VID,"
"LOGICAL_LIBRARY_NAME AS LOGICAL_LIBRARY_NAME,"
"TAPE_POOL_NAME AS TAPE_POOL_NAME,"
"ENCRYPTION_KEY AS ENCRYPTION_KEY,"
"CAPACITY_IN_BYTES AS CAPACITY_IN_BYTES,"
"DATA_IN_BYTES AS DATA_IN_BYTES,"
"LAST_FSEQ AS LAST_FSEQ,"
"IS_DISABLED AS IS_DISABLED,"
"IS_FULL AS IS_FULL,"
"LBP_IS_ON AS LBP_IS_ON,"
"LABEL_DRIVE AS LABEL_DRIVE,"
"LABEL_TIME AS LABEL_TIME,"
"LAST_READ_DRIVE AS LAST_READ_DRIVE,"
"LAST_READ_TIME AS LAST_READ_TIME,"
"LAST_WRITE_DRIVE AS LAST_WRITE_DRIVE,"
"LAST_WRITE_TIME AS LAST_WRITE_TIME,"
"USER_COMMENT AS USER_COMMENT,"
"CREATION_LOG_USER_NAME AS CREATION_LOG_USER_NAME,"
"CREATION_LOG_HOST_NAME AS CREATION_LOG_HOST_NAME,"
"CREATION_LOG_TIME AS CREATION_LOG_TIME,"
"LAST_UPDATE_USER_NAME AS LAST_UPDATE_USER_NAME,"
"LAST_UPDATE_HOST_NAME AS LAST_UPDATE_HOST_NAME,"
"LAST_UPDATE_TIME AS LAST_UPDATE_TIME "
"FROM "
"TAPE "
"WHERE "
"VID = :VID;";
std::unique_ptr<DbStmt> stmt(m_conn->createStmt(sql));
stmt->bindString(":VID", vid);
std::unique_ptr<DbRset> rset(stmt->executeQuery());
if (!rset->next()) {
throw exception::Exception(std::string("The tape with VID " + vid + " does not exist"));
}
common::dataStructures::Tape tape;
tape.vid = rset->columnText("VID");
tape.logicalLibraryName = rset->columnText("LOGICAL_LIBRARY_NAME");
tape.tapePoolName = rset->columnText("TAPE_POOL_NAME");
tape.encryptionKey = rset->columnText("ENCRYPTION_KEY");
tape.capacityInBytes = rset->columnUint64("CAPACITY_IN_BYTES");
tape.dataOnTapeInBytes = rset->columnUint64("DATA_IN_BYTES");
tape.lastFSeq = rset->columnUint64("LAST_FSEQ");
tape.disabled = rset->columnUint64("IS_DISABLED");
tape.full = rset->columnUint64("IS_FULL");
tape.lbp = rset->columnUint64("LBP_IS_ON");
tape.labelLog.drive = rset->columnText("LABEL_DRIVE");
tape.labelLog.time = rset->columnUint64("LABEL_TIME");
tape.lastReadLog.drive = rset->columnText("LAST_READ_DRIVE");
tape.lastReadLog.time = rset->columnUint64("LAST_READ_TIME");
tape.lastWriteLog.drive = rset->columnText("LAST_WRITE_DRIVE");
tape.lastWriteLog.time = rset->columnUint64("LAST_WRITE_TIME");
tape.comment = rset->columnText("USER_COMMENT");
common::dataStructures::UserIdentity creatorUI;
creatorUI.name = rset->columnText("CREATION_LOG_USER_NAME");
common::dataStructures::EntryLog creationLog;
creationLog.username = rset->columnText("CREATION_LOG_USER_NAME");
creationLog.host = rset->columnText("CREATION_LOG_HOST_NAME");
creationLog.time = rset->columnUint64("CREATION_LOG_TIME");
tape.creationLog = creationLog;
common::dataStructures::UserIdentity updaterUI;
updaterUI.name = rset->columnText("LAST_UPDATE_USER_NAME");
common::dataStructures::EntryLog updateLog;
updateLog.username = rset->columnText("LAST_UPDATE_USER_NAME");
updateLog.host = rset->columnText("LAST_UPDATE_HOST_NAME");
updateLog.time = rset->columnUint64("LAST_UPDATE_TIME");
tape.lastModificationLog = updateLog;
return tape;
} catch (exception::Exception &ex) {
throw exception::Exception(std::string(__FUNCTION__) + " failed: " + ex.getMessage().str());
}
}
} // namespace catalogue
} // namespace cta
......@@ -75,6 +75,17 @@ protected:
*/
virtual uint64_t getNextArchiveFileId();
/**
* Selects the specified tape within th eTape table for update.
*
* This method must be implemented by the sub-classes of RdbmsCatalogue
* because some database technologies directly support SELECT FOR UPDATE
* whilst others do not.
*
* @param vid The volume identifier of the tape.
*/
virtual common::dataStructures::Tape selectTapeForUpdate(const std::string &vid);
}; // class SqliteCatalogue
} // namespace catalogue
......
......@@ -100,7 +100,11 @@ void SqliteConn::commit() {
// commit
//------------------------------------------------------------------------------
void SqliteConn::rollback() {
throw exception::Exception(std::string(__FUNCTION__) + " not implemented");
try {
executeNonQuery("ROLLBACK;");
} catch(exception::Exception &ex) {