Skip to content
Snippets Groups Projects
Commit f42f89a7 authored by Steven Murray's avatar Steven Murray
Browse files

Removed tapeserver/castor/tape/reactor

parent 7a3c66f2
No related branches found
No related tags found
No related merge requests found
Showing with 1 addition and 747 deletions
......@@ -181,7 +181,6 @@ The shared libraries
%attr(0755,root,root) %{_libdir}/libctamessages.so
%attr(0755,root,root) %{_libdir}/libctamessagesutils.so
%attr(0755,root,root) %{_libdir}/libctardbms.so
%attr(0755,root,root) %{_libdir}/libctatapereactorutils.so
%attr(0644,root,root) %config(noreplace) %{_sysconfdir}/cta/cta_catalogue_db.conf.example
#CTA-lib installs libraries so we need ldconfig.
......@@ -211,7 +210,6 @@ Unit tests and system tests with virtual tape drives
%attr(0755,root,root) %{_libdir}/libctaobjectstoreunittests.so
%attr(0755,root,root) %{_libdir}/libctardbmsunittests.so
%attr(0755,root,root) %{_libdir}/libctaschedulerunittests.so
%attr(0755,root,root) %{_libdir}/libctatapereactorunittests.so
%attr(0755,root,root) %{_libdir}/libctatapeserverdaemonunittests.so
%attr(0755,root,root) %{_libdir}/libctatapeserverdriveunittests.so
%attr(0755,root,root) %{_libdir}/libctatapeserverfileunittests.so
......
......@@ -21,5 +21,4 @@
#
cmake_minimum_required (VERSION 2.6)
add_subdirectory (reactor)
add_subdirectory (tapeserver)
# 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 Castor Dev team, castor-dev@cern.ch
#
cmake_minimum_required (VERSION 2.6)
include_directories(${CMAKE_SOURCE_DIR}/tapeserver)
################################################################################
# Rules to build the reactor code that is common to both rmcd and tapeserverd
################################################################################
set (REACTOR_SRC_FILES
ZMQPollEventHandler.cpp
ZMQReactor.cpp)
add_library (ctatapereactor ${REACTOR_SRC_FILES})
target_link_libraries (ctatapereactor)
add_library (ctatapereactorutils SHARED
DummyZMQReactor.cpp)
install(TARGETS ctatapereactorutils DESTINATION usr/${CMAKE_INSTALL_LIBDIR})
add_library (ctatapereactorunittests SHARED
ZMQReactorTest.cpp)
install(TARGETS ctatapereactorunittests DESTINATION usr/${CMAKE_INSTALL_LIBDIR})
target_link_libraries (ctatapereactorunittests
ctatapereactor
ctatapereactorutils)
/******************************************************************************
*
* 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 Castor Dev team, castor-dev@cern.ch
*****************************************************************************/
#include "castor/tape/reactor/DummyZMQReactor.hpp"
//------------------------------------------------------------------------------
// constructor
//------------------------------------------------------------------------------
castor::tape::reactor::DummyZMQReactor::DummyZMQReactor(cta::log::Logger& log) throw():
ZMQReactor(log) {
}
//------------------------------------------------------------------------------
// clear
//------------------------------------------------------------------------------
void castor::tape::reactor::DummyZMQReactor::clear() {
// Do nothing
}
//------------------------------------------------------------------------------
// registerHandler
//------------------------------------------------------------------------------
void castor::tape::reactor::DummyZMQReactor::registerHandler(
ZMQPollEventHandler *const handler) {
// Do nothing
}
//------------------------------------------------------------------------------
// handleEvents
//------------------------------------------------------------------------------
void castor::tape::reactor::DummyZMQReactor::handleEvents(const int timeout) {
// Do nothing
}
/******************************************************************************
*
* 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 Castor Dev team, castor-dev@cern.ch
*****************************************************************************/
#pragma once
#include "castor/tape/reactor/ZMQReactor.hpp"
namespace castor {
namespace tape {
namespace reactor {
/**
* This is a dummy ZmqReactor class that does nothing. The primary goal of
* this class is to facilitate unit testing.
*/
class DummyZMQReactor: public ZMQReactor {
public:
/**
* Constructor.
*
* @param log Interface to the CASTOR logging system.
*/
DummyZMQReactor(cta::log::Logger& log) throw();
/**
* Removes and deletes all of the event handlers registered with the reactor.
*/
void clear();
/**
* Registers the specified handler.
*
* Please note that the reactor takes ownership of the handler and will
* delete it as appropriate.
*
* @param handler The handler to be registered. Please note that the handler
* MUST be allocated on the heap because the reactor will own the handler
* and therefore delete it as needed.
*/
void registerHandler(ZMQPollEventHandler *const handler);
/**
* Handles any pending events.
*
* @param timeout Timeout in milliseconds.
*/
void handleEvents(const int timeout);
}; // class DummyZMQReactor
} // namespace reactor
} // namespace tape
} // namespace castor
/******************************************************************************
*
* 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 Castor Dev team, castor-dev@cern.ch
*****************************************************************************/
#include "castor/tape/reactor/ZMQPollEventHandler.hpp"
//------------------------------------------------------------------------------
// destructor
//------------------------------------------------------------------------------
castor::tape::reactor::ZMQPollEventHandler::~ZMQPollEventHandler() throw() {
}
/******************************************************************************
*
* 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 Castor Dev team, castor-dev@cern.ch
*****************************************************************************/
#pragma once
#include "common/exception/Exception.hpp"
#include <zmq.h>
namespace castor {
namespace tape {
namespace reactor {
/**
* Handles the events that occur on a poll() file descriptor.
*
* This class is part of an implementation of the Reactor architecture pattern
* described in the following book:
*
* Pattern-Oriented Software Architecture Volume 2
* Patterns for Concurrent and Networked Objects
* Authors: Schmidt, Stal, Rohnert and Buschmann
* Publication date: 2000
* ISBN 0-471-60695-2
*/
class ZMQPollEventHandler {
public:
/**
* Destructor.
*/
virtual ~ZMQPollEventHandler() throw() = 0;
/**
* Returns the human-readable name this event handler.
*/
virtual std::string getName() const throw() = 0;
/**
* Fills the specified poll file-descriptor ready to be used in a call to
* poll().
*/
virtual void fillPollFd(zmq_pollitem_t &pollitem) =0;
/**
* Handles the specified event.
*
* @param fd The poll file-descriptor describing the event.
* @return true if the event handler should be removed from and deleted by
* the reactor.
*/
virtual bool handleEvent(const zmq_pollitem_t &fd)=0;
}; // class ZMQPollEventHandler
} // namespace reactor
} // namespace tape
} // namespace castor
/******************************************************************************
*
* 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 Castor Dev team, castor-dev@cern.ch
*****************************************************************************/
#include "castor/tape/reactor/ZMQReactor.hpp"
#include "castor/tape/reactor/ZMQPollEventHandler.hpp"
#include "common/utils/utils.hpp"
#include <algorithm>
namespace{
bool operator==(const zmq_pollitem_t& a,const zmq_pollitem_t& b){
if( (a.fd==b.fd && a.fd!= -1 && b.fd != -1) ||
(a.socket==b.socket && a.socket!=NULL && b.socket != NULL) ){
return true;
}
return false;
}
}
//------------------------------------------------------------------------------
// constructor
//------------------------------------------------------------------------------
castor::tape::reactor::ZMQReactor::ZMQReactor(cta::log::Logger& log) throw():
m_log(log) {
}
//------------------------------------------------------------------------------
// destructor
//------------------------------------------------------------------------------
castor::tape::reactor::ZMQReactor::~ZMQReactor() throw() {
clear();
}
//------------------------------------------------------------------------------
// clear
//------------------------------------------------------------------------------
void castor::tape::reactor::ZMQReactor::clear() {
for(HandlerMap::iterator it=m_handlers.begin();it!=m_handlers.end();++it){
delete it->second;
}
m_handlers.clear();
}
//------------------------------------------------------------------------------
// registerHandler
//------------------------------------------------------------------------------
void castor::tape::reactor::ZMQReactor::registerHandler(
ZMQPollEventHandler *const handler) {
zmq_pollitem_t item;
handler->fillPollFd(item);
std::ostringstream socketInHex;
socketInHex << std::hex << item.socket;
std::list<cta::log::Param> params = {cta::log::Param("fd", item.fd),
cta::log::Param("socket", socketInHex.str())};
m_log(cta::log::DEBUG, "ZMQReactor registering a new handler", params);
checkDoubleRegistration(item);
m_handlers.push_back(std::make_pair(item,handler));
}
//------------------------------------------------------------------------------
// checkDoubleRegistration
//------------------------------------------------------------------------------
void castor::tape::reactor::ZMQReactor::checkDoubleRegistration(
const zmq_pollitem_t &item) const {
for(HandlerMap::const_iterator it=m_handlers.begin(); it!=m_handlers.end();
++it) {
const std::pair<zmq_pollitem_t, ZMQPollEventHandler*> &maplet = *it;
if(item == maplet.first) {
cta::exception::Exception ex;
ex.getMessage() << "ZMQReactor detected a double registration: fd=" <<
item.fd << " socket=" << std::hex << item.socket;
throw ex;
}
}
}
//------------------------------------------------------------------------------
// handleEvents
//------------------------------------------------------------------------------
void castor::tape::reactor::ZMQReactor::handleEvents(const int timeout) {
//it should not bring any copy, thanks to NRVO
std::vector<zmq_pollitem_t> pollFds=buildPollFds();
// Please note that we are relying on the fact that the file descriptors of
// the vector are stored contiguously
const int pollRc = zmq_poll(&pollFds[0], pollFds.size(), timeout);
if(0 <= pollRc){
for(std::vector<zmq_pollitem_t>::const_iterator it=pollFds.begin();
it!=pollFds.end(); ++it) {
const zmq_pollitem_t &pollFd = *it;
if(0 != pollFd.revents) {
handleEvent(pollFd);
}
}
} else if(0 > pollRc) {
const std::string message = cta::utils::errnoToString(errno);
std::list<cta::log::Param> params = {cta::log::Param("message", message)};
m_log(cta::log::ERR, "Failed to handle I/O event: zmq_poll() failed", params);
}
}
//------------------------------------------------------------------------------
// handleEvent
//------------------------------------------------------------------------------
void castor::tape::reactor::ZMQReactor::handleEvent(
const zmq_pollitem_t &pollFd) {
// Find and dispatch the appropriate handler if there is a pending event
if(pollFd.revents & ZMQ_POLLIN) {
HandlerMap::iterator handlerItor = findHandler(pollFd);
if(handlerItor != m_handlers.end()) {
ZMQPollEventHandler *const handler = handlerItor->second;
const bool removeAndDeleteHandler = handler->handleEvent(pollFd);
if(removeAndDeleteHandler) {
m_handlers.erase(handlerItor);
delete(handler);
}
}else {
std::list<cta::log::Param> params;
params.push_back(cta::log::Param("fd",pollFd.fd));
params.push_back(cta::log::Param("socket",pollFd.socket));
m_log(cta::log::ERR, "Event on some poll, but no handler to match it", params);
}
}
}
//------------------------------------------------------------------------------
// findHandler
//------------------------------------------------------------------------------
castor::tape::reactor::ZMQReactor::HandlerMap::iterator
castor::tape::reactor::ZMQReactor::findHandler(const zmq_pollitem_t& pollfd) {
for(HandlerMap::iterator it=m_handlers.begin();it!=m_handlers.end();++it){
// Use overloaded == to compare zmq_pollitem_t references
if(pollfd==it->first){
return it;
}
}
return m_handlers.end();
}
//------------------------------------------------------------------------------
// buildPollFds
//------------------------------------------------------------------------------
std::vector<zmq_pollitem_t> castor::tape::reactor::ZMQReactor::buildPollFds()
const {
std::vector<zmq_pollitem_t> pollFds;
pollFds.reserve(m_handlers.size());
for(HandlerMap::const_iterator it=m_handlers.begin();it!=m_handlers.end();
++it) {
ZMQPollEventHandler *const handler = it->second;
zmq_pollitem_t pollFd;
handler->fillPollFd(pollFd);
pollFds.push_back(pollFd);
}
return pollFds;
}
/******************************************************************************
*
* 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 Castor Dev team, castor-dev@cern.ch
*****************************************************************************/
#pragma once
#include "common/log/Logger.hpp"
#include "castor/tape/reactor/ZMQPollEventHandler.hpp"
#include "castor/tape/reactor/ZMQReactor.hpp"
#include <utility>
#include <vector>
namespace castor {
namespace tape {
namespace reactor {
/**
* This reactor wraps the zmq_poll() function.
*
* This class is part of an implementation of the Reactor architecture pattern
* described in the following book:
*
* Pattern-Oriented Software Architecture Volume 2
* Patterns for Concurrent and Networked Objects
* Authors: Schmidt, Stal, Rohnert and Buschmann
* Publication date: 2000
* ISBN 0-471-60695-2
*/
class ZMQReactor {
public:
/**
* Constructor.
*
* @param log Interface to the CASTOR logging system.
*/
ZMQReactor(cta::log::Logger& log) throw();
/**
* Destructor.
*/
virtual ~ZMQReactor() throw();
/**
* Removes and deletes all of the event handlers registered with the reactor.
*/
virtual void clear();
/**
* Registers the specified handler.
*
* Please note that the reactor takes ownership of the handler and will
* delete it as appropriate.
*
* @param handler The handler to be registered. Please note that the handler
* MUST be allocated on the heap because the reactor will own the handler
* and therefore delete it as needed.
*/
virtual void registerHandler(ZMQPollEventHandler *const handler);
/**
* Handles any pending events.
*
* @param timeout Timeout in milliseconds.
*/
virtual void handleEvents(const int timeout);
private:
/**
* Type used to map zmq_pollitem_t to event handler.
*/
typedef std::vector<std::pair<zmq_pollitem_t, ZMQPollEventHandler*> >
HandlerMap;
/**
* Throws a cator::exception::Exception if a handler has already been
* registered for the specified poll item.
*
* @prama item The poll item.
*/
void checkDoubleRegistration(const zmq_pollitem_t &item) const;
/**
* Builds the vector of file descriptors to be passed to poll().
*
* Please note that the return type is an std::vector because we can assume
* that its elements are stored contiguously in memory. The address of the
* first element is going to be passed to zmq_poll().
*
* @return The vector of file descriptors.
*/
std::vector<zmq_pollitem_t> buildPollFds() const;
/**
* Returns the event handler associated with the specified zmq_pollitem_t.
*/
HandlerMap::iterator findHandler(const zmq_pollitem_t&);
/**
* Handles the specified ZMQ I/O event.
*
* @param pollFd The file-descriptor representing the I/O event.
*/
void handleEvent(const zmq_pollitem_t &pollFd);
/**
* Removes the specified handler from the reactor. This method effectively
* does the opposite of registerHandler().
*
* @param handler The handler to be removed.
*/
void removeHandler(ZMQPollEventHandler *const handler);
/**
* Map of file descriptor to registered event handler.
*/
HandlerMap m_handlers;
/**
* Object representing the API of the CASTOR logging system.
*/
cta::log::Logger& m_log;
}; // class ZMQReactor
} // namespace reactor
} // namespace tape
} // namespace castor
/******************************************************************************
*
* 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 Castor Dev team, castor-dev@cern.ch
*****************************************************************************/
#include "castor/tape/reactor/ZMQPollEventHandler.hpp"
#include "castor/tape/reactor/ZMQReactor.hpp"
#include "common/log/DummyLogger.hpp"
#include <gtest/gtest.h>
#include <memory>
#include <sys/types.h>
#include <sys/socket.h>
// Anonymous namespace used to hide the TestEventHandler class from the rest
// of the World
namespace {
/**
* Event handler used soley by this test file.
*/
class TestEventHandler: public castor::tape::reactor::ZMQPollEventHandler {
public:
/**
* Constructor.
*
* @param fd File descriptor to be owned by this event handler. The file
* descriptor will be closed by the destructor of this class.
*/
TestEventHandler(const int fd) throw(): m_fd(fd) {
}
/**
* Returns the human-readable name this event handler.
*/
std::string getName() const throw() {
return "TestEventHandler";
}
/**
* Fills the specified poll file-descriptor ready to be used in a call to
* poll().
*/
void fillPollFd(zmq_pollitem_t &fd) {
fd.fd = m_fd;
fd.events = ZMQ_POLLIN;
fd.revents = 0;
fd.socket = NULL;
}
/**
* Returns true indicating that this event handler should be removed from and
* deleted by the reactor.
*
* @param fd The poll file-descriptor describing the event.
* @return True indicating that this event handler should be removed from and
* deleted by the reactor.
*/
bool handleEvent(const zmq_pollitem_t &fd) {
return true;
}
/**
* Destructor.
*
* Calls close on the owned file descriptor.
*/
~TestEventHandler() throw() {
close(m_fd);
}
private:
/**
* File descriptor to be returned by getFd().
*/
const int m_fd;
}; // class DummyPollEventHandler
} // anonymous namespace
namespace unitTests {
class castor_tape_reactor_ZMQReactorTest : public ::testing::Test {
protected:
virtual void SetUp() {
}
virtual void TearDown() {
}
};
TEST_F(castor_tape_reactor_ZMQReactorTest, constructor) {
using namespace castor::tape::reactor;
cta::log::DummyLogger log("unittests");
std::unique_ptr<ZMQReactor> reactor;
ASSERT_NO_THROW(reactor.reset(new ZMQReactor(log)));
}
/*
The castorUnitsTest binary should also be ran with valgrind configured to track
file-descriptors, for example:
valgrind --track-fds=yes test/castorUnitTests
*/
TEST_F(castor_tape_reactor_ZMQReactorTest, closeFd) {
using namespace castor::tape::reactor;
cta::log::DummyLogger log("unittests");
ZMQReactor reactor(log);
int sv[2] = {-1, -1};
ASSERT_EQ(0, socketpair(AF_LOCAL, SOCK_STREAM, 0, sv));
std::unique_ptr<TestEventHandler> handler1;
std::unique_ptr<TestEventHandler> handler2;
handler1.reset(new TestEventHandler(sv[0]));
handler2.reset(new TestEventHandler(sv[1]));
reactor.registerHandler(handler1.get());
handler1.release();
}
} // namespace unitTests
......@@ -78,7 +78,7 @@ endif(CMAKE_COMPILER_IS_GNUCC)
add_library(ctaTapeServerDaemon
${CTATAPESERVERDAEMON_LIBRARY_SRCS})
target_link_libraries(ctaTapeServerDaemon ctamessages ctatapereactor ctacommon protobuf ctascheduler ctalegacymsg ctacatalogue TapeDrive)
target_link_libraries(ctaTapeServerDaemon ctamessages ctacommon protobuf ctascheduler ctalegacymsg ctacatalogue TapeDrive)
add_dependencies(ctaTapeServerDaemon ctamessagesprotobuf)
add_library(ctatapeserverdaemonunittests SHARED
......
......@@ -24,7 +24,6 @@ add_executable(cta-unitTests
target_link_libraries(cta-unitTests
ctainmemorycatalogueunittests
ctaexceptionunittests
ctatapereactorunittests
ctatapeserverdaemonunittests
ctatapeserverdriveunittests
ctatapeserverfileunittests
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment