diff --git a/Code/Mantid/Framework/API/CMakeLists.txt b/Code/Mantid/Framework/API/CMakeLists.txt index 1380797cf9153864b405b01c92cef538858b4fdb..1a8ece42503b648102159f60ac84b77ebfd39880 100644 --- a/Code/Mantid/Framework/API/CMakeLists.txt +++ b/Code/Mantid/Framework/API/CMakeLists.txt @@ -106,6 +106,7 @@ set ( SRC_FILES src/PropertyManagerDataService.cpp src/PropertyNexus.cpp src/RefAxis.cpp + src/RemoteJobManagerFactory.cpp src/Run.cpp src/Sample.cpp src/SampleEnvironment.cpp @@ -271,6 +272,7 @@ set ( INC_FILES inc/MantidAPI/PropertyManagerDataService.h inc/MantidAPI/PropertyNexus.h inc/MantidAPI/RefAxis.h + inc/MantidAPI/RemoteJobManagerFactory.h inc/MantidAPI/Run.h inc/MantidAPI/Sample.h inc/MantidAPI/SampleEnvironment.h @@ -367,6 +369,7 @@ set ( TEST_FILES ProjectionTest.h PropertyManagerDataServiceTest.h PropertyNexusTest.h + RemoteJobManagerFactoryTest.h RunTest.h SampleEnvironmentTest.h SampleShapeValidatorTest.h diff --git a/Code/Mantid/Framework/API/inc/MantidAPI/IRemoteJobManager.h b/Code/Mantid/Framework/API/inc/MantidAPI/IRemoteJobManager.h index 2035e271f89e416a4a0d1dc6c76fc26cc7da995e..eb6d2ef97bd9dc3a643f69368c4be2be348d5757 100644 --- a/Code/Mantid/Framework/API/inc/MantidAPI/IRemoteJobManager.h +++ b/Code/Mantid/Framework/API/inc/MantidAPI/IRemoteJobManager.h @@ -137,7 +137,7 @@ public: * @param numNodes number of nodes to use (optional and dependent on * implementation and compute resource) * - * @parm coresPerNode number of cores to use in each node (optional + * @param coresPerNode number of cores to use in each node (optional * and dependent on implemenation and compute resource) * * @return jobID string for the job started (if successful). diff --git a/Code/Mantid/Framework/API/inc/MantidAPI/RemoteJobManagerFactory.h b/Code/Mantid/Framework/API/inc/MantidAPI/RemoteJobManagerFactory.h new file mode 100644 index 0000000000000000000000000000000000000000..34fa04176d18cd43e191eb4bf346bdbfdb02b66e --- /dev/null +++ b/Code/Mantid/Framework/API/inc/MantidAPI/RemoteJobManagerFactory.h @@ -0,0 +1,124 @@ +#ifndef MANTID_API_REMOTEJOBMANAGERFACTORY_H_ +#define MANTID_API_REMOTEJOBMANAGERFACTORY_H_ + +#include "MantidAPI/DllConfig.h" +#include "MantidAPI/IRemoteJobManager.h" +#include "MantidKernel/DynamicFactory.h" +#include "MantidKernel/SingletonHolder.h" + +namespace Mantid { +namespace API { +/** +The RemoteJobManagerFactory handles the creation of remote job +managers specialised for different types of compute resources (for +different underlying job schedulers, web services, front-ends, +etc.). Through the create method of this class a shared pointer to a +remote job manager object can be obtained for a particular compute +resource. + +The remote job managers built by this factory know how to start and +stop jobs, upload/download files, etc. for the compute resource +specified when creating the job manager (as long as the compute +resource is found for the current facility in the facilities +definition file). + +Remote job manager classes must be registered/subscribe using the +macro DECLARE_REMOTEJOBMANAGER (the same way you use DECLARE_ALGORITHM +for algorithms and remote algorithms). + +As the algorithm, workspace and other factories in Mantid, this +factory is implemented as a singleton class. Typical usages: + +Mantid::API::IRemoteJob|Manager_sptr jobManager = + Mantid::API::RemoteJobManagerFactory::Instance().create("Fermi"); + +Mantid::API::IRemoteJob|Manager_sptr jobManager = + Mantid::API::RemoteJobManagerFactory::Instance().create("SCARF@STFC"); + + +Copyright © 2015 ISIS Rutherford Appleton Laboratory, NScD Oak Ridge +National Laboratory & European Spallation Source + +This file is part of Mantid. + +Mantid 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. + +Mantid 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/>. + +File change history is stored at: <https://github.com/mantidproject/mantid>. +Code Documentation is available at: <http://doxygen.mantidproject.org> +*/ +class MANTID_API_DLL RemoteJobManagerFactoryImpl + : public Kernel::DynamicFactory<IRemoteJobManager> { +public: + /// Create a remote job manager that will know how to use the + /// underlying mechanism that suits the compute resource passed + IRemoteJobManager_sptr create(const std::string &computeResourceName) const; + + /// alternative (lower level) create where the specific type of + /// manager and base URL are directly given + IRemoteJobManager_sptr create(const std::string baseURL, + const std::string jobManagerType) const; + +private: + /// So that the singleton can be created (cons/destructor are private) + friend struct Mantid::Kernel::CreateUsingNew<RemoteJobManagerFactoryImpl>; + + /// Private Constructor for singleton class + RemoteJobManagerFactoryImpl(); + /// Disallow copy construction + RemoteJobManagerFactoryImpl(const RemoteJobManagerFactoryImpl &); + /// Disallow assignment + RemoteJobManagerFactoryImpl &operator=(const RemoteJobManagerFactoryImpl &); + + /// Private Destructor + virtual ~RemoteJobManagerFactoryImpl(); + + // Unhide the inherited create method but make it private + using Kernel::DynamicFactory<IRemoteJobManager>::create; +}; + +/// Forward declaration of a specialisation of SingletonHolder for +/// RemoteJobManagerFactoryImpl (needed for dllexport) and a typedef for it. +#ifdef _WIN32 +// this breaks new namespace declaraion rules; need to find a better fix +template class MANTID_API_DLL + Mantid::Kernel::SingletonHolder<RemoteJobManagerFactoryImpl>; +#endif /* _WIN32 */ + +// The factory is just a specialisation of SingletonHolder +typedef MANTID_API_DLL Mantid::Kernel::SingletonHolder< + RemoteJobManagerFactoryImpl> RemoteJobManagerFactory; + +} // namespace API +} // namespace Mantid + +/* Macro to register (remote job manager) classes into the factory. As + * with the equivalent macros of the workspace factory or the + * algorithm factory, this creates a global object in an anonymous + * namespace. The object itself does nothing, but the comma operator + * is used in the call to its constructor to effect a call to the + * factory's subscribe method. + * + * You need to use this in every remote job manager. For example: + * DECLARE_REMOTEJOBMANAGER(MantidWebServiceAPI) + * DECLARE_REMOTEJOBMANAGER(SCARFLSFJobManager) + */ +#define DECLARE_REMOTEJOBMANAGER(classname) \ + namespace { \ + Mantid::Kernel::RegistrationHelper register_ws_##classname( \ + ((Mantid::API::RemoteJobManagerFactory::Instance().subscribe<classname>( \ + #classname)), \ + 0)); \ + } + +#endif // MANTID_API_REMOTEJOBMANAGERFACTORY_H_ diff --git a/Code/Mantid/Framework/API/src/RemoteJobManagerFactory.cpp b/Code/Mantid/Framework/API/src/RemoteJobManagerFactory.cpp new file mode 100644 index 0000000000000000000000000000000000000000..744123627a144265173445af3f7b11cd96c3c40a --- /dev/null +++ b/Code/Mantid/Framework/API/src/RemoteJobManagerFactory.cpp @@ -0,0 +1,88 @@ +#include "MantidAPI/RemoteJobManagerFactory.h" +#include "MantidKernel/ConfigService.h" +#include "MantidKernel/FacilityInfo.h" +#include "MantidKernel/Logger.h" + +namespace Mantid { +namespace API { +namespace { +/// static logger object +Kernel::Logger g_log("RemoteJobManagerFactory"); +} + +/// Private constructor, singleton class +RemoteJobManagerFactoryImpl::RemoteJobManagerFactoryImpl() + : Mantid::Kernel::DynamicFactory<IRemoteJobManager>() { + g_log.debug() << "RemoteJobManager factory created." << std::endl; +} + +/** + * Private destructor, prevent client code from using this. + */ +RemoteJobManagerFactoryImpl::~RemoteJobManagerFactoryImpl() {} + +/** + * Create a remote algorithm with the underlying mechanism that suits + * the compute resource passed. + * + * @param computeResourceName Name of a (remote) compute resource + * + * @throw std::invalid_argument If no resource is found by the name + * given (compute resources are looked up in the facilities definition + * (XML) file for the current facility. + */ +IRemoteJobManager_sptr RemoteJobManagerFactoryImpl::create( + const std::string &computeResourceName) const { + IRemoteJobManager_sptr jm; + + if (computeResourceName.empty()) + return jm; + + Mantid::Kernel::ComputeResourceInfo cr = + Mantid::Kernel::ConfigService::Instance().getFacility().computeResource( + computeResourceName); + + // this is the default. It could be "MantidWebServiceAPI", "LSF", + // "SCARFLSF", "MOAB", etc. + std::string type = "MantidWebServiceAPIJobManager"; + std::string fdfType = cr.remoteJobManagerType(); + if (!fdfType.empty()) + type = fdfType; + return create(cr.baseURL(), type); +} + +/** + * Lower level create method that makes a remote algorithm given a + * base URL and the type of remote job manager. + * + * @param baseURL URL where the resource is accessible + * + * @param jobManagerType Type/class that can handle this remote + * compute resource (string names as used in the facilities definition + * file, for example: MantidWebServiceAPIJobManager). + * + * @throw std::invalid_argument If there is an issue with the URL or + * the type (for example the type is not recognized). + */ +Mantid::API::IRemoteJobManager_sptr +RemoteJobManagerFactoryImpl::create(const std::string baseURL, + const std::string jobManagerType) const { + Mantid::API::IRemoteJobManager_sptr jm; + + // use the inherited/generic create method + try { + jm = this->create(jobManagerType); + } catch (Kernel::Exception::NotFoundError &e) { + throw Kernel::Exception::NotFoundError( + "RemoteJobManagerFactory: failed to create a remote job manager of " + "type (class) '" + + jobManagerType + "' with base URL " + baseURL + + ". Error description: " + e.what(), + jobManagerType); + } + + return jm; +} + +} // namespace API +} // Namespace Mantid diff --git a/Code/Mantid/Framework/API/test/RemoteJobManagerFactoryTest.h b/Code/Mantid/Framework/API/test/RemoteJobManagerFactoryTest.h new file mode 100644 index 0000000000000000000000000000000000000000..9fa693d8893d48bac960394befb79f17d06bf71b --- /dev/null +++ b/Code/Mantid/Framework/API/test/RemoteJobManagerFactoryTest.h @@ -0,0 +1,149 @@ +#ifndef REMOTEJOBMANAGERFACTORYTEST_H_ +#define REMOTEJOBMANAGERFACTORYTEST_H_ + +#include "MantidAPI/RemoteJobManagerFactory.h" +#include "MantidKernel/ConfigService.h" +#include "MantidKernel/FacilityInfo.h" + +using namespace Mantid::API; + +// Just a minimal implementation of IRemoteJobManager, sufficient for the +// factory +class TestJM : public IRemoteJobManager { +public: + virtual void authenticate(const std::string &username, + const std::string &password) { + UNUSED_ARG(username); + UNUSED_ARG(password); + } + + virtual std::string + submitRemoteJob(const std::string &transactionID, const std::string &runnable, + const std::string ¶m, const std::string &taskName = "", + const int numNodes = 1, const int coresPerNode = 1) { + UNUSED_ARG(transactionID); + UNUSED_ARG(runnable); + UNUSED_ARG(param); + UNUSED_ARG(taskName); + UNUSED_ARG(numNodes); + UNUSED_ARG(coresPerNode); + return ""; + } + + virtual void downloadRemoteFile(const std::string &transactionID, + const std::string &remoteFileName, + const std::string &localFileName) { + UNUSED_ARG(transactionID); + UNUSED_ARG(remoteFileName); + UNUSED_ARG(localFileName); + } + + virtual std::vector<RemoteJobInfo> queryAllRemoteJobs() const { + return std::vector<RemoteJobInfo>(); + } + + virtual std::vector<std::string> + queryRemoteFile(const std::string &transactionID) const { + UNUSED_ARG(transactionID); + return std::vector<std::string>(); + } + + virtual RemoteJobInfo queryRemoteJob(const std::string &jobID) const { + UNUSED_ARG(jobID); + return RemoteJobInfo(); + } + + virtual std::string startRemoteTransaction() { return ""; } + + virtual void stopRemoteTransaction(const std::string &transactionID) { + UNUSED_ARG(transactionID); + } + + virtual void abortRemoteJob(const std::string &jobID) { UNUSED_ARG(jobID); } + + virtual void uploadRemoteFile(const std::string &transactionID, + const std::string &remoteFileName, + const std::string &localFileName) { + UNUSED_ARG(transactionID); + UNUSED_ARG(remoteFileName); + UNUSED_ARG(localFileName); + } +}; + +class RemoteJobManagerFactoryTest : public CxxTest::TestSuite { +public: + void test_unsubscribed() { + + IRemoteJobManager_sptr jobManager; + TS_ASSERT_THROWS( + jobManager = RemoteJobManagerFactory::Instance().create("Inexistent"), + std::runtime_error); + + TS_ASSERT_THROWS(jobManager = + RemoteJobManagerFactory::Instance().create("TestJM"), + std::runtime_error); + } + + // minimal positive test + void test_createTestJM() { + RemoteJobManagerFactory::Instance().subscribe<TestJM>("TestJM"); + // throws not found cause it is not in facilities.xml, but otherwise fine + TS_ASSERT_THROWS( + jm = Mantid::API::RemoteJobManagerFactory::Instance().create("TestJM"), + Mantid::Kernel::Exception::NotFoundError); + } + + // this must fail, resource not found in the current facility + void test_createAlienResource() { + // save facility, do this before any changes + const Mantid::Kernel::FacilityInfo &prevFac = + Mantid::Kernel::ConfigService::Instance().getFacility(); + + Mantid::Kernel::ConfigService::Instance().setFacility("ISIS"); + TS_ASSERT_THROWS( + jm = Mantid::API::RemoteJobManagerFactory::Instance().create("Fermi"), + Mantid::Kernel::Exception::NotFoundError); + + Mantid::Kernel::ConfigService::Instance().setFacility("SNS"); + TS_ASSERT_THROWS( + Mantid::API::IRemoteJobManager_sptr jobManager = + Mantid::API::RemoteJobManagerFactory::Instance().create( + "SCARF@STFC"), + Mantid::Kernel::Exception::NotFoundError); + + // restore facility, always do this at the end + Mantid::Kernel::ConfigService::Instance().setFacility(prevFac.name()); + } + + // a simple positive test + void test_createRemoteManagers() { + // save facility, do this before any changes + const Mantid::Kernel::FacilityInfo &prevFac = + Mantid::Kernel::ConfigService::Instance().getFacility(); + + Mantid::Kernel::ConfigService::Instance().setFacility("SNS"); + // TODO: at the moment these two create throw a NotFoundError + // because the RemoteJobManager classes are missing and have not + // done a DECLARE_REMOTEJOBMANAGER. Change this test when that is + // done (ticket #11126 etc.) + TS_ASSERT_THROWS( + Mantid::API::IRemoteJobManager_sptr jobManager = + Mantid::API::RemoteJobManagerFactory::Instance().create("Fermi"), + Mantid::Kernel::Exception::NotFoundError); + + Mantid::Kernel::ConfigService::Instance().setFacility("ISIS"); + TS_ASSERT_THROWS( + Mantid::API::IRemoteJobManager_sptr jobManager = + Mantid::API::RemoteJobManagerFactory::Instance().create( + "SCARF@STFC"), + Mantid::Kernel::Exception::NotFoundError); + + // restore facility, always do this at the end + Mantid::Kernel::ConfigService::Instance().setFacility(prevFac.name()); + } + +private: + Mantid::API::IRemoteJobManager_sptr jm; +}; + +#endif /* REMOTEJOBMANAGERFACTORYTEST_H_ */ diff --git a/Code/Mantid/Framework/Kernel/CMakeLists.txt b/Code/Mantid/Framework/Kernel/CMakeLists.txt index 887bba0271579674dd2aff16e4107f268ffe51ac..07d6f10edbb7529ecaf9e15a95a7d554e653a20e 100644 --- a/Code/Mantid/Framework/Kernel/CMakeLists.txt +++ b/Code/Mantid/Framework/Kernel/CMakeLists.txt @@ -8,6 +8,7 @@ set ( SRC_FILES src/CPUTimer.cpp src/CatalogInfo.cpp src/CompositeValidator.cpp + src/ComputeResourceInfo.cpp src/ConfigService.cpp src/ChecksumHelper.cpp src/DataItem.cpp @@ -128,6 +129,7 @@ set ( INC_FILES inc/MantidKernel/Cache.h inc/MantidKernel/CatalogInfo.h inc/MantidKernel/CompositeValidator.h + inc/MantidKernel/ComputeResourceInfo.h inc/MantidKernel/ConfigService.h inc/MantidKernel/ChecksumHelper.h inc/MantidKernel/DataItem.h @@ -264,6 +266,7 @@ set ( TEST_FILES CacheTest.h CatalogInfoTest.h CompositeValidatorTest.h + ComputeResourceInfoTest.h ConfigServiceTest.h ChecksumHelperTest.h DataServiceTest.h diff --git a/Code/Mantid/Framework/Kernel/inc/MantidKernel/ComputeResourceInfo.h b/Code/Mantid/Framework/Kernel/inc/MantidKernel/ComputeResourceInfo.h new file mode 100644 index 0000000000000000000000000000000000000000..db27c93ed68e8bcaaf1e7434fc1dca88b3bd1483 --- /dev/null +++ b/Code/Mantid/Framework/Kernel/inc/MantidKernel/ComputeResourceInfo.h @@ -0,0 +1,82 @@ +#ifndef MANTID_KERNEL_COMPUTERESOURCEINFO_H_ +#define MANTID_KERNEL_COMPUTERESOURCEINFO_H_ + +#include <string> + +#include "MantidKernel/DllConfig.h" + +namespace Poco { +namespace XML { +class Element; +} +} + +namespace Mantid { +namespace Kernel { + +class FacilityInfo; + +/** +ComputeResourceInfo holds information about / represents a compute +resource present in a facility. + +At the moment (remote) compute resources are defined by their name, +the URL they can be accessed at, and the type of remote job manager +that they use/require (Mantid web service API, LSF, etc.). + +Copyright © 2015 ISIS Rutherford Appleton Laboratory, NScD Oak Ridge +National Laboratory & European Spallation Source + +This file is part of Mantid. + +Mantid 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. + +Mantid 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/>. + +File change history is stored at: <https://github.com/mantidproject/mantid>. +Code Documentation is available at: <http://doxygen.mantidproject.org> +*/ +class MANTID_KERNEL_DLL ComputeResourceInfo { +public: + /// constructor - from facility info and the element for this resource + ComputeResourceInfo(const FacilityInfo *f, const Poco::XML::Element *elem); + + /// Equality operator + bool operator==(const ComputeResourceInfo &rhs) const; + + /// Name of the compute resource + std::string name() const; + + /// Base URL the compute resource + std::string baseURL() const ; + + /// Type/class of remote job manager required to handle this resource + std::string remoteJobManagerType() const; + + /// The facility where this compute resource is avalable + const FacilityInfo &facility() const; + +private: + const FacilityInfo *m_facility; ///< Facility + std::string m_name; ///< Cluster/resource name + std::string m_baseURL; ///< access URL (first authentication, etc.) + std::string m_managerType; ///< specific remote job manager class +}; + +/// output to stream operator for compute resource info objects +MANTID_KERNEL_DLL std::ostream & +operator<<(std::ostream &buffer, const ComputeResourceInfo &cr); + +} // namespace Kernel +} // namespace Mantid + +#endif /* MANTID_KERNEL_COMPUTERESOURCEINFO_H_ */ diff --git a/Code/Mantid/Framework/Kernel/inc/MantidKernel/FacilityInfo.h b/Code/Mantid/Framework/Kernel/inc/MantidKernel/FacilityInfo.h index 2d5976003305abfd697f2a52eb653a3accad0891..20c5d7172471f2ceb0367e185e410cbadf8a26f3 100644 --- a/Code/Mantid/Framework/Kernel/inc/MantidKernel/FacilityInfo.h +++ b/Code/Mantid/Framework/Kernel/inc/MantidKernel/FacilityInfo.h @@ -6,6 +6,7 @@ //---------------------------------------------------------------------- #include "MantidKernel/DllConfig.h" #include "MantidKernel/CatalogInfo.h" +#include "MantidKernel/ComputeResourceInfo.h" #include "MantidKernel/InstrumentInfo.h" #include "MantidKernel/RemoteJobManager.h" #ifndef Q_MOC_RUN @@ -80,7 +81,13 @@ public: std::vector<InstrumentInfo> instruments(const std::string &tech) const; /// Returns instruments with given name const InstrumentInfo &instrument(std::string iName = "") const; - /// Returns a vector of the available compute resources + + /// Returns a vector of available compute resources + std::vector<ComputeResourceInfo> computeResInfos() const; + /// Returns a compute resource identified by name + const ComputeResourceInfo &computeResource(const std::string &name) const; + + /// Returns a vector of the names of the available compute resources std::vector<std::string> computeResources() const; /// Returns the RemoteJobManager for the named compute resource boost::shared_ptr<RemoteJobManager> @@ -113,12 +120,18 @@ private: std::vector<InstrumentInfo> m_instruments; ///< list of instruments of this facility std::string m_liveListener; ///< name of the default live listener + + std::vector<ComputeResourceInfo> m_computeResInfos; ///< (remote) compute + /// resources available in + /// this facility + + // TODO: remove RemoteJobManager form here (trac ticket #11373) typedef std::map<std::string, boost::shared_ptr<RemoteJobManager>> ComputeResourcesMap; ComputeResourcesMap m_computeResources; ///< list of compute resources ///(clusters, etc...) available at - /// this facility - // (Sorted by their names) + /// this facility + // (Sorted by their names) }; } // namespace Kernel diff --git a/Code/Mantid/Framework/Kernel/src/ComputeResourceInfo.cpp b/Code/Mantid/Framework/Kernel/src/ComputeResourceInfo.cpp new file mode 100644 index 0000000000000000000000000000000000000000..6669c1f228310ec4bcba078f3f79a072f96cad77 --- /dev/null +++ b/Code/Mantid/Framework/Kernel/src/ComputeResourceInfo.cpp @@ -0,0 +1,115 @@ +#include "MantidKernel/ComputeResourceInfo.h" +#include "MantidKernel/FacilityInfo.h" +#include "MantidKernel/Logger.h" + +#include <Poco/DOM/AutoPtr.h> +#include <Poco/DOM/Element.h> +#include <Poco/DOM/NodeList.h> +#include <Poco/DOM/Text.h> + +namespace Mantid { +namespace Kernel { +namespace { +// static logger object +Logger g_log("ComputeResourceInfo"); +} + +/** + * Construct a compute resource from information found in a facilities + * definition file. + * + * @param fac Facility where this (remote) compute resource is available + * @param elem A (Poco::XML::) Element to read the data from + * + * @throw std::runtime_error if name or required attributes are not + * found + */ +ComputeResourceInfo::ComputeResourceInfo(const FacilityInfo *fac, + const Poco::XML::Element *elem) + : m_facility(fac) { + + m_name = elem->getAttribute("name"); + if (m_name.empty()) { + std::string elemStr = ""; + if (elem) + elemStr = elem->innerText(); + throw std::runtime_error( + "The compute resource name is not defined, at element: " + elemStr); + } + + // default: Mantid web service API: + // http://www.mantidproject.org/Remote_Job_Submission_API + m_managerType = "MantidWebServiceAPIJobManager"; + std::string type = elem->getAttribute("JobManagerType"); + if (!type.empty()) { + m_managerType = type; + } + + const std::string baseTag = "baseURL"; + Poco::AutoPtr<Poco::XML::NodeList> nl = elem->getElementsByTagName(baseTag); + if (!nl || nl->length() != 1 || !nl->item(0) || !nl->item(0)->childNodes()) { + g_log.error("Failed to get base URL for remote compute resource '" + + m_name + "'"); + throw std::runtime_error("Remote compute resources must have exactly one " + "baseURL tag. It was not found for the resource " + "'" + + m_name + "'"); + } else { + nl = nl->item(0)->childNodes(); + if (nl->length() > 0) { + Poco::XML::Text *txt = dynamic_cast<Poco::XML::Text *>(nl->item(0)); + if (txt) { + m_baseURL = txt->getData(); + } else { + g_log.error("Failed to get base URL for remote compute resource '" + + m_name + "'. The " + baseTag + " tag seems empty!"); + throw std::runtime_error( + "Remote compute resources must have exactly one " + "baseURL tag containing a URL string. A tag was found for the " + "resource " + "'" + + m_name + "', but it seems empty!"); + } + } + } +} + +/** +* Equality operator. Two different resources cannot have the same name +* +* @param rhs object to compare this with +* +* @return True if the objects (names) are equal +*/ +bool ComputeResourceInfo::operator==(const ComputeResourceInfo &rhs) const { + return (this->name() == rhs.name()); +} + +std::string ComputeResourceInfo::name() const { return m_name; } + +std::string ComputeResourceInfo::baseURL() const { return m_baseURL; } + +std::string ComputeResourceInfo::remoteJobManagerType() const { + return m_managerType; +} + +const FacilityInfo &ComputeResourceInfo::facility() const { + return *m_facility; +} + +/** + * Prints the instrument name into an output stream + * + * @param buffer an output stream being written to + * @param cr a ComputeResourceInfo object to print + * + * @return reference to the output stream being written to + */ +std::ostream &operator<<(std::ostream &buffer, const ComputeResourceInfo &cr) { + buffer << "'" + cr.name() + "', at '" + cr.baseURL() + "', of type '" + + cr.remoteJobManagerType() + "'"; + return buffer; +} + +} // namespace Kernel +} // namespace Mantid diff --git a/Code/Mantid/Framework/Kernel/src/FacilityInfo.cpp b/Code/Mantid/Framework/Kernel/src/FacilityInfo.cpp index 7346c52307c62fe54291b6032c56f89309ff623a..dad72eec268239254af844eda8d20fd5d268cf1c 100644 --- a/Code/Mantid/Framework/Kernel/src/FacilityInfo.cpp +++ b/Code/Mantid/Framework/Kernel/src/FacilityInfo.cpp @@ -154,10 +154,24 @@ void FacilityInfo::fillComputeResources(const Poco::XML::Element *elem) { for (unsigned long i = 0; i < n; i++) { Poco::XML::Element *elem = dynamic_cast<Poco::XML::Element *>(pNL_compute->item(i)); - std::string name = elem->getAttribute("name"); - m_computeResources.insert(std::make_pair( - name, boost::shared_ptr<RemoteJobManager>(new RemoteJobManager(elem)))); + if (elem) { + try { + ComputeResourceInfo cr(this, elem); + m_computeResInfos.push_back(cr); + + g_log.debug() << "Compute resource found: " << cr << std::endl; + } catch (...) { // next resource... + } + + std::string name = elem->getAttribute("name"); + // TODO: this is a bit of duplicate effort at the moment, until + // RemoteJobManager goes away from here (then this would be + // removed), see header for details. + m_computeResources.insert(std::make_pair( + name, + boost::shared_ptr<RemoteJobManager>(new RemoteJobManager(elem)))); + } } } @@ -202,14 +216,22 @@ const InstrumentInfo &FacilityInfo::instrument(std::string iName) const { } /** - * Returns a list of instruments of given technique - * @param tech :: Technique name - * @return a list of instrument information objects + * Get the vector of available compute resources + * @return vector of ComputeResourInfo for the current facility */ +std::vector<ComputeResourceInfo> FacilityInfo::computeResInfos() const { + return m_computeResInfos; +} + +/** +* Returns a list of instruments of given technique +* @param tech :: Technique name +* @return a list of instrument information objects +*/ std::vector<InstrumentInfo> FacilityInfo::instruments(const std::string &tech) const { std::vector<InstrumentInfo> out; - std::vector<InstrumentInfo>::const_iterator it = m_instruments.begin(); + auto it = m_instruments.begin(); for (; it != m_instruments.end(); ++it) { if (it->techniques().count(tech)) { out.push_back(*it); @@ -219,7 +241,7 @@ FacilityInfo::instruments(const std::string &tech) const { } /** - * Returns a vector of the available compute resources + * Returns a vector of the names of the available compute resources * @return vector of strings of the compute resource names */ std::vector<std::string> FacilityInfo::computeResources() const { @@ -233,6 +255,39 @@ std::vector<std::string> FacilityInfo::computeResources() const { return names; } +/** + * Get a compute resource by name + * + * @param name Name as specified in the facilities definition file + * + * @return the named compute resource + * + * @throws NotFoundError if the resource is not found/available. + */ +const ComputeResourceInfo & +FacilityInfo::computeResource(const std::string &name) const { + if (name.empty()) { + g_log.debug("Cannot find a compute resource without name " + "(empty)."); + throw Exception::NotFoundError("FacilityInfo, empty compute resource name", + name); + } + + auto it = m_computeResInfos.begin(); + for (; it != m_computeResInfos.end(); ++it) { + if (it->name() == name) { + g_log.debug() << "Compute resource '" << name << "' found at facility " + << this->name() << "." << std::endl; + return *it; + } + } + + g_log.debug() << "Could not find requested compute resource: " << name + << " in facility " << this->name() << "." << std::endl; + throw Exception::NotFoundError("FacilityInfo, missing compute resource", + name); +} + /** * Returns a reference to the requested remote job manager * @param name :: Name of the cluster we want to submit jobs to diff --git a/Code/Mantid/Framework/Kernel/src/InstrumentInfo.cpp b/Code/Mantid/Framework/Kernel/src/InstrumentInfo.cpp index 60e610a0169609d84eb875d85918293bad4963b9..9b0f8bc57aced45671c461b6f8666ddd10a9832c 100644 --- a/Code/Mantid/Framework/Kernel/src/InstrumentInfo.cpp +++ b/Code/Mantid/Framework/Kernel/src/InstrumentInfo.cpp @@ -7,10 +7,10 @@ #include "MantidKernel/Logger.h" #include "MantidKernel/Strings.h" +#include <Poco/DOM/AutoPtr.h> #include <Poco/DOM/Element.h> #include <Poco/DOM/NodeList.h> #include <Poco/DOM/Text.h> -#include <Poco/DOM/AutoPtr.h> #include <boost/lexical_cast.hpp> diff --git a/Code/Mantid/Framework/Kernel/test/ComputeResourceInfoTest.h b/Code/Mantid/Framework/Kernel/test/ComputeResourceInfoTest.h new file mode 100644 index 0000000000000000000000000000000000000000..213a98ed2098662b0748101000aaaa992a0a8cc0 --- /dev/null +++ b/Code/Mantid/Framework/Kernel/test/ComputeResourceInfoTest.h @@ -0,0 +1,269 @@ +#ifndef COMPUTERESOURCEINFOTEST_H_ +#define COMPUTERESOURCEINFOTEST_H_ + +#include "MantidKernel/Exception.h" +#include "MantidKernel/FacilityInfo.h" + +#include <Poco/DOM/AutoPtr.h> +#include <Poco/DOM/Document.h> +#include <Poco/DOM/DOMParser.h> +#include <Poco/XML/XMLException.h> + +using namespace Mantid::Kernel; + +class ComputeResourceInfoTest : public CxxTest::TestSuite { +public: + void test_allMissing() { + FacilityInfo *fac = NULL; + TS_ASSERT_THROWS_NOTHING(fac = + createCRInfoInMinimalFacility(simpleInstStr)); + TS_ASSERT(fac); + std::vector<ComputeResourceInfo> cri; + TS_ASSERT_THROWS_NOTHING(cri = fac->computeResInfos()); + TS_ASSERT_EQUALS(cri.size(), 0); + + delete fac; + fac = NULL; + TS_ASSERT_THROWS(fac = createCRInfoInMinimalFacility( + "<computeResource fooAtt=\"barVal\"/>"), + std::runtime_error); + TS_ASSERT(!fac); + delete fac; + } + + void test_noURLTag() { + const std::string crTxt = "<computeResource name=\"foo\">" + "<u>" + + fermiURL + "</u>" + "</computeResource>"; + FacilityInfo *fac = NULL; + TS_ASSERT_THROWS(fac = createCRInfoInMinimalFacility(crTxt), + std::runtime_error); + TS_ASSERT(!fac); + delete fac; + } + + void test_wrongXML() { + const std::string crTxt = "<computeResource name=\"foo\">" + "<u_foo>" + + fermiURL + "</u_bar>" + "</compResource>"; + FacilityInfo *fac = NULL; + TS_ASSERT_THROWS(fac = createCRInfoInMinimalFacility(crTxt), + Poco::XML::XMLException); + TS_ASSERT(!fac); + delete fac; + } + + void test_normalFermi() { + const std::string fermi = "<computeResource name=\"" + fermiName + + "\">" + "<baseURL>" + + fermiURL + "</baseURL>" + "</computeResource>"; + + FacilityInfo *fac = NULL; + TS_ASSERT_THROWS_NOTHING(fac = createCRInfoInMinimalFacility(fermi)); + TS_ASSERT(fac); + TS_ASSERT_EQUALS(fac->name(), this->testFacilityName); + + std::vector<ComputeResourceInfo> cri; + TS_ASSERT_THROWS_NOTHING(cri = fac->computeResInfos()); + TS_ASSERT_EQUALS(cri.size(), 1); + + ComputeResourceInfo cr = fac->computeResInfos().front(); + TS_ASSERT_THROWS(ComputeResourceInfo fail = fac->computeResource(scarfName), + Mantid::Kernel::Exception::NotFoundError); + ComputeResourceInfo cr2 = fac->computeResource(fermiName); + TS_ASSERT_EQUALS(cr, cr2); + TS_ASSERT_EQUALS(cr, cri.front()); + TS_ASSERT_EQUALS(cr2, cri.front()); + TS_ASSERT_EQUALS(cr.name(), fermiName); + TS_ASSERT_EQUALS(cr2.name(), fermiName); + TS_ASSERT_EQUALS(cr.baseURL(), fermiURL); + TS_ASSERT_EQUALS(cr2.baseURL(), fermiURL); + TS_ASSERT_EQUALS(cr.remoteJobManagerType(), defaultType); + TS_ASSERT_EQUALS(cr2.remoteJobManagerType(), defaultType); + TS_ASSERT_EQUALS(cr.facility().name(), fac->name()); + TS_ASSERT_EQUALS(cr2.facility().name(), fac->name()); + } + + void test_brokenFermi() { + // wrong 'baseURL' tag + const std::string fermi = "<computeResource name=\"" + fermiName + "\">" + "<URL>" + + fermiURL + "</URL>" + "</computeResource>"; + + FacilityInfo *fac = NULL; + TS_ASSERT_THROWS(fac = createCRInfoInMinimalFacility(fermi), + std::runtime_error); + + TS_ASSERT(!fac); + delete fac; + } + + void test_normalSCARF() { + const std::string scarf = "<computeResource name=\"" + scarfName + + "\" JobManagerType=\"" + scarfType + "\">" + "<baseURL>" + + scarfURL + "</baseURL>" + "</computeResource>"; + + FacilityInfo *fac = NULL; + TS_ASSERT_THROWS_NOTHING(fac = createCRInfoInMinimalFacility(scarf)); + TS_ASSERT(fac); + TS_ASSERT_EQUALS(fac->name(), this->testFacilityName); + std::vector<ComputeResourceInfo> cri; + TS_ASSERT_THROWS_NOTHING(cri = fac->computeResInfos()); + TS_ASSERT_EQUALS(cri.size(), 1); + + ComputeResourceInfo cr = fac->computeResInfos().front(); + TS_ASSERT_THROWS(ComputeResourceInfo fail = fac->computeResource("inexistent!"), + Mantid::Kernel::Exception::NotFoundError); + ComputeResourceInfo cr2 = fac->computeResource(scarfName); + TS_ASSERT_EQUALS(cr, cr2); + TS_ASSERT_EQUALS(cri.front(), cr); + TS_ASSERT_EQUALS(cri.front(), cr2); + TS_ASSERT_EQUALS(scarfName, cr.name()); + TS_ASSERT_EQUALS(scarfName, cr2.name()); + TS_ASSERT_EQUALS(scarfURL, cr.baseURL()); + TS_ASSERT_EQUALS(scarfURL, cr2.baseURL()); + TS_ASSERT_EQUALS(scarfType, cr.remoteJobManagerType()); + TS_ASSERT_EQUALS(scarfType, cr2.remoteJobManagerType()); + TS_ASSERT_EQUALS(fac->name(), cr.facility().name()); + TS_ASSERT_EQUALS(fac->name(), cr2.facility().name()); + delete fac; + } + + void test_brokenSCARF() { + const std::string type = "SCARFLSFJobManager"; + const std::string err = "<computeResource foo=\"" + scarfName + + "\" JobManagerType=\"" + type + "\">" + "<URL>" + + scarfURL + "</URL>" + "</computeResource>"; + FacilityInfo *fac = NULL; + TS_ASSERT_THROWS(fac = createCRInfoInMinimalFacility(err), + std::runtime_error); + TS_ASSERT(!fac); + delete fac; + } + + void test_equals() { + const std::string otherName = "other"; + const std::string otherURL = "www.example.com/foo/baz"; + const std::string thirdName = "third"; + const std::string rep = "<computeResource name=\"" + fermiName + + "\">" + "<baseURL>" + + fermiURL + "</baseURL>" + "</computeResource>" + + "<computeResource name=\"" + + otherName + "\">" + "<baseURL>" + + otherURL + "</baseURL>" + "</computeResource>" + + "<computeResource name=\"" + + thirdName + "\">" + "<baseURL>" + + fermiURL + "</baseURL>" + "</computeResource>" + + "<computeResource name=\"" + + fermiName + "\">" + "<baseURL>" + + fermiURL + "</baseURL>" + "</computeResource>"; + + FacilityInfo *fac = NULL; + TS_ASSERT_THROWS_NOTHING(fac = createCRInfoInMinimalFacility(rep)); + TS_ASSERT(fac); + TS_ASSERT_EQUALS(fac->computeResources().size(), 3); + TS_ASSERT_EQUALS(fac->computeResInfos().size(), 4); + + // compare names + TS_ASSERT(fac->computeResources()[0] == fac->computeResources()[0]); + TS_ASSERT(!(fac->computeResources()[0] == fac->computeResources()[1])); + TS_ASSERT(!(fac->computeResources()[0] == fac->computeResources()[2])); + TS_ASSERT(!(fac->computeResources()[1] == fac->computeResources()[2])); + + // compare full comp resource info + TS_ASSERT(fac->computeResInfos()[0] == fac->computeResInfos()[0]); + TS_ASSERT(!(fac->computeResInfos()[0] == fac->computeResInfos()[1])); + TS_ASSERT(!(fac->computeResInfos()[0] == fac->computeResInfos()[2])); + TS_ASSERT(!(fac->computeResInfos()[1] == fac->computeResInfos()[2])); + TS_ASSERT(!(fac->computeResInfos()[2] == fac->computeResInfos()[3])); + TS_ASSERT(fac->computeResInfos()[0] == fac->computeResInfos()[3]); + + // compare comp resource info retrieved by names + TS_ASSERT( + !(fac->computeResource(fermiName) == fac->computeResource(otherName))); + TS_ASSERT( + !(fac->computeResource(fermiName) == fac->computeResource(thirdName))); + TS_ASSERT( + !(fac->computeResource(otherName) == fac->computeResource(thirdName))); + delete fac; + } + +private: + /// make a minimal facilities file/xml string includin the compute resource + /// passed + FacilityInfo *createCRInfoInMinimalFacility(const std::string &crStr) { + const std::string xmlStr = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + "<facilities>" + " <facility name=\"" + + testFacilityName + + "\" FileExtensions=\".xyz\">" + simpleInstStr + + crStr + " </facility>" + "</facilities>"; + + return createFacility(xmlStr); + } + + FacilityInfo *createFacility(const std::string &xml) { + Poco::XML::DOMParser parser; + Poco::AutoPtr<Poco::XML::Document> pDoc = parser.parseString(xml); + Poco::XML::Element *pRootElem = pDoc->documentElement(); + Poco::XML::Element *elem = pRootElem->getChildElement("facility"); + + return new FacilityInfo(elem); + } + +private: + // a minimal instrument to create a facility info + static const std::string simpleInstStr; + + // default remote job manager type + static const std::string defaultType; + + static const std::string testFacilityName; + + static const std::string fermiName; + static const std::string fermiURL; + static const std::string scarfName; + static const std::string scarfURL; + static const std::string scarfType; +}; + +const std::string ComputeResourceInfoTest::simpleInstStr = + "<instrument name=\"AnInst\">" + " <technique>Measuring Stuff</technique>" + "</instrument>"; + +const std::string ComputeResourceInfoTest::defaultType = + "MantidWebServiceAPIJobManager"; + +const std::string ComputeResourceInfoTest::testFacilityName = "ATestFacility"; + +const std::string ComputeResourceInfoTest::fermiURL = + "https://fermi.ornl.gov/MantidRemote"; +const std::string ComputeResourceInfoTest::fermiName = "Fermi"; +const std::string ComputeResourceInfoTest::scarfURL = + "https://portal.scarf.rl.ac.uk"; +const std::string ComputeResourceInfoTest::scarfName = "SCARF@STFC"; +const std::string ComputeResourceInfoTest::scarfType = "SCARFLSFJobManager"; + +#endif // COMPUTERESOURCEINFOTEST_H_ diff --git a/Code/Mantid/instrument/Facilities.xml b/Code/Mantid/instrument/Facilities.xml index 5b7c6c8b41a29ad5343a1cd3314291b9d4703f2c..1213adaf37862f103db1ccae6db21105283eec1a 100644 --- a/Code/Mantid/instrument/Facilities.xml +++ b/Code/Mantid/instrument/Facilities.xml @@ -6,7 +6,7 @@ <archiveSearch plugin="ISISDataSearch" /> </archive> - <computeResource name="SCARF@STFC"> + <computeResource name="SCARF@STFC" JobManagerType="SCARFLSFJobManager"> <baseURL>https://portal.scarf.rl.ac.uk</baseURL> </computeResource>