Commit 969d71fb authored by Steven Murray's avatar Steven Murray
Browse files

The tapeserverd daemon now uses the Reactor pattern.

parent 697ebe54
/******************************************************************************
* castor/tape/legacymsg/Common.cpp
* castor/tape/legacymsg/CommonMarshal.cpp
*
* This file is part of the Castor project.
* See http://castor.web.cern.ch/castor
......
add_executable(tapeserverd TapeDaemon.cpp TapeDaemonMain.cpp)
add_executable(tapeserverd
TapeDaemon.cpp
TapeDaemonMain.cpp
Vdqm.cpp
VdqmAcceptHandler.cpp
VdqmConnectionHandler.cpp
VdqmImpl.cpp)
target_link_libraries(tapeserverd Exception SCSI System Utils File castorcommon
castorclient)
target_link_libraries(tapeserverd
Exception
SCSI
System
Utils
File
castorcommon
castorclient
castortapelegacymsg)
add_library(tapeserver ClientInterface.cpp MountSession.cpp)
......
/******************************************************************************
* castor/tape/tapeserver/daemon/Constants.hpp
*
* This file is part of the Castor project.
* See http://castor.web.cern.ch/castor
*
* Copyright (C) 2003 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 Steven.Murray@cern.ch
*****************************************************************************/
#ifndef CASTOR_TAPE_TAPESERVER_DAEMON_CONSTANTS_HPP
#define CASTOR_TAPE_TAPESERVER_DAEMON_CONSTANTS_HPP 1
namespace castor {
namespace tape {
namespace tapeserver {
namespace daemon {
/**
* The TCP/IP port on which the tape server daemon listens for incoming
* connections from the VDQM server.
*/
const unsigned short TAPE_SERVER_LISTENING_PORT = 5070;
} // namespace daemon
} // namespace tapeserver
} // namespace tape
} // namespace castor
#endif // CASTOR_TAPE_TAPESERVER_DAEMON_CONSTANTS_HPP
......@@ -22,16 +22,15 @@
* @author Steven.Murray@cern.ch
*****************************************************************************/
#include "castor/PortNumbers.hpp"
#include "castor/exception/Errnum.hpp"
#include "castor/exception/BadAlloc.hpp"
#include "castor/exception/Internal.hpp"
#include "castor/exception/InvalidArgument.hpp"
#include "castor/io/io.hpp"
#include "castor/tape/tapeserver/daemon/Constants.hpp"
#include "castor/tape/tapeserver/daemon/TapeDaemon.hpp"
#include "castor/tape/tapeserver/daemon/VdqmAcceptHandler.hpp"
#include "castor/tape/utils/utils.hpp"
#include "castor/utils/SmartFd.hpp"
#include "h/common.h"
#include <algorithm>
#include <limits.h>
......@@ -46,9 +45,9 @@
// constructor
//------------------------------------------------------------------------------
castor::tape::tapeserver::daemon::TapeDaemon::TapeDaemon::TapeDaemon(
std::ostream &stdOut, std::ostream &stdErr, log::Logger &log)
throw(castor::exception::Exception):
castor::server::Daemon(stdOut, stdErr, log),
std::ostream &stdOut, std::ostream &stdErr, log::Logger &log, Vdqm &vdqm,
io::PollReactor &reactor) throw(castor::exception::Exception):
castor::server::Daemon(stdOut, stdErr, log), m_vdqm(vdqm), m_reactor(reactor),
m_programName("tapeserverd") {
}
......@@ -92,14 +91,11 @@ void castor::tape::tapeserver::daemon::TapeDaemon::exceptionThrowingMain(
logStartOfDaemon(argc, argv);
parseCommandLine(argc, argv);
// Daemonize if not configiured to run in the foreground
parseTpconfig();
daemonizeIfNotRunInForeground();
blockSignals();
castor::utils::SmartFd listenSock(
io::createListenerSock(TAPE_SERVER_LISTENING_PORT));
mainEventLoop(listenSock.get());
setUpReactor();
mainEventLoop();
}
//------------------------------------------------------------------------------
......@@ -116,6 +112,47 @@ void castor::tape::tapeserver::daemon::TapeDaemon::logStartOfDaemon(
m_log(LOG_INFO, msg.str(), params);
}
//------------------------------------------------------------------------------
// parseTpconfig
//------------------------------------------------------------------------------
void castor::tape::tapeserver::daemon::TapeDaemon::parseTpconfig()
throw(castor::exception::Exception) {
utils::TpconfigLines tpconfigLines;
utils::parseTpconfigFile(TPCONFIGPATH, tpconfigLines);
logTpconfigLines(tpconfigLines);
// Extract the tape-drive names from the TPCONFIG file
std::list<std::string> driveNames;
utils::extractTpconfigDriveNames(tpconfigLines, driveNames);
}
//------------------------------------------------------------------------------
// logTpconfigLines
//------------------------------------------------------------------------------
void castor::tape::tapeserver::daemon::TapeDaemon::logTpconfigLines(
const utils::TpconfigLines &lines) throw() {
for(utils::TpconfigLines::const_iterator itor = lines.begin();
itor != lines.end(); itor++) {
logTpconfigLine(*itor);
}
}
//------------------------------------------------------------------------------
// logTpconfigLine
//------------------------------------------------------------------------------
void castor::tape::tapeserver::daemon::TapeDaemon::logTpconfigLine(
const utils::TpconfigLine &line) throw() {
log::Param params[] = {
log::Param("unitName", line.mUnitName),
log::Param("deviceGroup", line.mDeviceGroup),
log::Param("systemDevice", line.mSystemDevice),
log::Param("density", line.mDensity),
log::Param("initialStatus", line.mInitialStatus),
log::Param("controlMethod", line.mControlMethod),
log::Param("devType", line.mDevType)};
m_log(LOG_INFO, "TPCONFIG line", params);
}
//------------------------------------------------------------------------------
// argvToString
//------------------------------------------------------------------------------
......@@ -162,97 +199,52 @@ void castor::tape::tapeserver::daemon::TapeDaemon::blockSignals() const
}
//------------------------------------------------------------------------------
// mainEventLoop
// setUpReactor
//------------------------------------------------------------------------------
void castor::tape::tapeserver::daemon::TapeDaemon::mainEventLoop(
const int listenSock) throw(castor::exception::Exception) {
bool continueMainEventLoop = true;
while(continueMainEventLoop) {
handleAPossibleVdqmRequest(listenSock);
continueMainEventLoop = handlePendingSignals();
::usleep(100000); // Sleep for a tenth of a second
void castor::tape::tapeserver::daemon::TapeDaemon::setUpReactor()
throw(castor::exception::Exception) {
castor::utils::SmartFd listenSock;
try {
listenSock.reset(io::createListenerSock(TAPE_SERVER_LISTENING_PORT));
} catch(castor::exception::Exception &ne) {
castor::exception::Exception ex(ne.code());
ex.getMessage() << "Failed to create socket to listen for vdqm connections"
": " << ne.getMessage().str();
throw ex;
}
std::auto_ptr<VdqmAcceptHandler> acceptHandler;
try {
acceptHandler.reset(new VdqmAcceptHandler(listenSock.get(), m_reactor,
m_log, m_vdqm));
listenSock.release();
} catch(std::bad_alloc &ba) {
castor::exception::BadAlloc ex;
ex.getMessage() <<
"Failed to create the event handler for accepting vdqm connections"
": " << ba.what();
throw ex;
}
m_reactor.registerHandler(acceptHandler.get());
acceptHandler.release();
}
//------------------------------------------------------------------------------
// handleAPossibleVdqmRequest
// mainEventLoop
//------------------------------------------------------------------------------
void castor::tape::tapeserver::daemon::TapeDaemon::handleAPossibleVdqmRequest(
const int listenSock) throw() {
struct pollfd fds[1];
// poll() of the main event loop should always look at the accept socket
fds[0].fd = listenSock;
fds[0].events = POLLRDNORM;
fds[0].revents = 0;
if(-1 == poll(fds, 1, 0)) {
const int pollErrno = errno;
log::Param params[] = {
log::Param("errno", pollErrno),
log::Param("message", sstrerror(pollErrno))};
m_log(LOG_ERR, "Failed to handle a pending vdqm request: poll() failed",
params);
return;
}
// If the vdqm daemon is connecting to the listening socket
if(fds[0].revents & POLLRDNORM) {
try {
castor::utils::SmartFd connection(io::acceptConnection(listenSock, 1));
char hostName[io::HOSTNAMEBUFLEN];
const io::IpAndPort peerIpAndPort = io::getPeerIpPort(connection.get());
io::getPeerHostName(connection.get(), hostName);
log::Param params[] = {
log::Param("Port" , peerIpAndPort.getPort()),
log::Param("HostName", hostName),
log::Param("socketFd", connection.get())};
m_log(LOG_INFO, "Accepted vdqm connection", params);
checkIsAdminHost(connection.get());
// TEMPORARY: For now we simply close the vdqm connection
close(connection.release());
} catch(castor::exception::Exception &ex) {
log::Param params[] = {
log::Param("code", ex.code()),
log::Param("message", ex.getMessage().str())};
m_log(LOG_ERR, "Failed to handle a pending vdqm request", params);
}
void castor::tape::tapeserver::daemon::TapeDaemon::mainEventLoop()
throw(castor::exception::Exception) {
while(handleEvents()) {
}
}
//-----------------------------------------------------------------------------
// checkisAdminHost
//-----------------------------------------------------------------------------
void castor::tape::tapeserver::daemon::TapeDaemon::checkIsAdminHost(
const int connection) throw(castor::exception::Exception) {
char peerHost[CA_MAXHOSTNAMELEN+1];
// isadminhost fills in peerHost
const int rc = isadminhost(connection, peerHost);
if(rc == -1 && serrno != SENOTADMIN) {
castor::exception::Internal ex;
ex.getMessage() << "Failed to lookup connection: Host=" << peerHost;
throw ex;
}
if(*peerHost == '\0' ) {
castor::exception::Exception ex(EINVAL);
ex.getMessage() << "Peer host name is an empty string";
throw ex;
}
if(rc != 0) {
castor::exception::Exception ex(SENOTADMIN);
ex.getMessage() << "Unauthorized admin host: Host=" << peerHost;
throw ex;
}
//------------------------------------------------------------------------------
// handleEvents
//------------------------------------------------------------------------------
bool castor::tape::tapeserver::daemon::TapeDaemon::handleEvents()
throw(castor::exception::Exception) {
const int timeout = 100; // 100 milliseconds
m_reactor.handleEvents(timeout);
return handlePendingSignals();
}
//------------------------------------------------------------------------------
......@@ -270,6 +262,10 @@ bool castor::tape::tapeserver::daemon::TapeDaemon::handlePendingSignals()
// While there is a pending signal to be handled
while (0 < (sig = sigtimedwait(&allSignals, &sigInfo, &immedTimeout))) {
switch(sig) {
case SIGINT: // Signal number 2
m_log(LOG_INFO, "Gracefully stopping because SIGINT was received");
continueMainEventLoop = false;
break;
case SIGTERM: // Signal number 15
m_log(LOG_INFO, "Gracefully stopping because SIGTERM was received");
continueMainEventLoop = false;
......
......@@ -27,7 +27,10 @@
#include "castor/exception/Exception.hpp"
#include "castor/exception/InvalidConfigEntry.hpp"
#include "castor/io/PollReactor.hpp"
#include "castor/server/Daemon.hpp"
#include "castor/tape/tapeserver/daemon/Vdqm.hpp"
#include "castor/tape/utils/utils.hpp"
#include <stdint.h>
#include <iostream>
......@@ -56,9 +59,12 @@ public:
* @param stdOut Stream representing standard out.
* @param stdErr Stream representing standard error.
* @param log The object representing the API of the CASTOR logging system.
* @param vdqm The object representing the vdqmd daemon.
* @param reactor The reactor responsible for dispatching the I/O events of
* the parent process of the tape server daemon.
*/
TapeDaemon(std::ostream &stdOut, std::ostream &stdErr, log::Logger &log)
throw(castor::exception::Exception);
TapeDaemon(std::ostream &stdOut, std::ostream &stdErr, log::Logger &log,
Vdqm &vdqm, io::PollReactor &reactor) throw(castor::exception::Exception);
/**
* Destructor.
......@@ -76,12 +82,6 @@ public:
private:
/**
* The TCP/IP port on which the tape server daemon listens for incoming
* connections from the VDQM server.
*/
static const unsigned short TAPE_SERVER_LISTENING_PORT = 5003;
/**
* Exception throwing main() function.
*/
......@@ -93,6 +93,22 @@ private:
*/
void logStartOfDaemon(const int argc, const char *const *const argv) throw();
/**
* Parses the /etc/castor/TPCONFIG files in order to determine the drives
* attached to the tape server.
*/
void parseTpconfig() throw(castor::exception::Exception);
/**
* Writes the specified list of TPCONFIG lines to the logging system.
*/
void logTpconfigLines(const utils::TpconfigLines &lines) throw();
/**
* Writes the specified TPCONFIG lines to the logging system.
*/
void logTpconfigLine(const utils::TpconfigLine &line) throw();
/**
* Creates a string that contains the specified command-line arguments
* separated by single spaces.
......@@ -110,33 +126,27 @@ private:
void blockSignals() const throw(castor::exception::Exception);
/**
* The main event loop of the tape-server daemon.
*
* @patam listenSock The file-descriptor of the listening socket.
* Sets up the reactor to listen for an accept connection from the vdqmd
* daemon.
*/
void mainEventLoop(const int listenSock)
throw(castor::exception::Exception);
void setUpReactor() throw(castor::exception::Exception);
/**
* Handle a pending vdqm request if there is one.
*
* @patam listenSock The file-descriptor of the listening socket.
* The main event loop of the tape-server daemon.
*/
void handleAPossibleVdqmRequest(const int listenSock) throw();
void mainEventLoop() throw(castor::exception::Exception);
/**
* Throws an exception if the peer host associated with the specified
* connection is not an admin host.
* Handles any pending events.
*
* @param connection The file descriptor of the connection.
* @return True if the main event loop should continue, else false.
*/
void checkIsAdminHost(const int connection)
throw(castor::exception::Exception);
bool handleEvents() throw(castor::exception::Exception);
/**
* Handles any pending signals.
*
* @return True if the main event lopp should continue, else false.
* @return True if the main event loop should continue, else false.
*/
bool handlePendingSignals() throw();
......@@ -145,11 +155,86 @@ private:
*/
void reapZombies() throw();
/**
* The object representing the vdqmd daemon.
*/
Vdqm &m_vdqm;
/**
* The reactor responsible for dispatching the file-descriptor event-handlers
* of the tape server daemon.
*/
io::PollReactor &m_reactor;
/**
* The program name of the tape daemon.
*/
const std::string m_programName;
/**
* The status of a drive as described by the following FSTN:
*
* start daemon /
* ------ send VDQM_UNIT_UP ----------------
* | INIT |--------------------->| DOWN |<-------------------
* ------ ---------------- |
* | | ^ |
* | | | |
* | | tpconfig up | tpconfig down |
* | | | |
* | start daemon / v | |
* | send VDQM_UNIT_DOWN ---------------- |
* ------------------------>| UP | |
* ---------------- |
* | ^ |
* | | |
* | vdqm job / | SIGCHLD [success] |
* | fork | |
* | | |
* v | |
* ---------------- SIGCHLD [fail] |
* | RUNNING |--------------------
* ----------------
*
* When the tapeserverd daemon is started, depdending on the initial state
* column of /etc/castor/TPCONFIG, the daemon sends either a VDQM_UNIT_UP
* or VDQM_UNIT_DOWN status message to the vdqmd daemon.
*
* A tape operator toggle the state of tape drive between DOWN and UP
* using the tpconfig adminstration tool.
*
* The tape daemon can receive a job from the vdqmd daemon when the drive
* is in the UP state. On reception of the job the daemon forks a child
* process to manage the tape mount and data transfer tasks necessary to
* fulfill the vdqm job. The drive is now in the RUNNING state.
*
* Once the vdqm job has been carried out, the child process completes
* and the drive either returns to the UP state if there were no
* problems or DOWN state if there were.
*/
enum DriveStatus { DRIVE_INIT, DRIVE_DOWN, DRIVE_UP, DRIVE_RUNNING };
/**
* Structure used to store the initial and current status of a drive.
*
* The initial status of a drive defined in the initial status column of the
* /etc/castor/TPCONFIG file.
*/
struct InitialAndCurrentDriveStatus {
DriveStatus initialStatus;
DriveStatus currentStatus;
};
/**
* Type that maps drive unit-name to drive initial and current status.
*/
typedef std::map<std::string, DriveStatus> DriveStatusMap;
/**
* Map from drive unit-name to drive status.
*/
DriveStatusMap m_drives;
}; // class TapeDaemon
} // namespace daemon
......
......@@ -21,21 +21,21 @@
*
* @author Steven.Murray@cern.ch
*****************************************************************************/
#include "castor/log/LoggerImplementation.hpp"
#include "castor/io/PollReactorImpl.hpp"
#include "castor/tape/tapeserver/daemon/TapeDaemon.hpp"
#include "castor/tape/tapeserver/daemon/VdqmImpl.hpp"
//------------------------------------------------------------------------------
// main
//------------------------------------------------------------------------------
int main(const int argc, char **const argv) {
using namespace castor::tape::tapeserver::daemon;
castor::log::LoggerImplementation log("tapeserverd");
castor::tape::tapeserver::daemon::TapeDaemon
daemon(std::cout, std::cerr, log);
VdqmImpl vdqm;
castor::io::PollReactorImpl reactor(log);
TapeDaemon daemon(std::cout, std::cerr, log, vdqm, reactor);
return daemon.main(argc, argv);
}
/******************************************************************************
* castor/tape/tapeserver/daemon/Vdqm.cpp
*
* This file is part of the Castor project.
* See http://castor.web.cern.ch/castor
*
* Copyright (C) 2003 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 Steven.Murray@cern.ch
*****************************************************************************/
#include "castor/tape/tapeserver/daemon/Vdqm.hpp"
//-----------------------------------------------------------------------------
// destructor
//-----------------------------------------------------------------------------
castor::tape::tapeserver::daemon::Vdqm::~Vdqm() throw() {
}
/******************************************************************************
* castor/tape/tapeserver/daemon/Vdqm.hpp
*
* This file is part of the Castor project.
* See http://castor.web.cern.ch/castor
*
* Copyright (C) 2003 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 Steven.Murray@cern.ch
*****************************************************************************/
#ifndef CASTOR_TAPE_TAPESERVER_DAEMON_VDQM_HPP
#define CASTOR_TAPE_TAPESERVER_DAEMON_VDQM_HPP 1
#include "castor/exception/Exception.hpp"
#include "castor/tape/legacymsg/RtcpJobRqstMsgBody.hpp"
namespace castor {
namespace tape {
namespace tapeserver {
namespace daemon {
/**
* Proxy class representing the vdqm daemon.
*/
class Vdqm {