diff --git a/include/bedrock/ClientManager.hpp b/include/bedrock/ClientManager.hpp index b711db7..e998668 100644 --- a/include/bedrock/ClientManager.hpp +++ b/include/bedrock/ClientManager.hpp @@ -73,6 +73,13 @@ class ClientManager { */ operator bool() const; + /** + * @brief Set the DependencyFinder object to use to resolve dependencies. + * + * @param finder DependencyFinder + */ + void setDependencyFinder(const DependencyFinder& finder); + /** * @brief Look up whether a client with a given name exists. * This function returns true if a client was found, false @@ -140,20 +147,18 @@ class ClientManager { * } * * @param jsonString JSON string. - * @param finder DependencyFinder to resolve the dependencies found. */ std::shared_ptr - addClientFromJSON(const std::string& jsonString, - const DependencyFinder& finder); + addClientFromJSON(const std::string& jsonString); /** * @brief Add a list of providers represented by a JSON string. + * The JSON string must represent an array of entries in the format + * expected by addClientFromJSON. * * @param jsonString JSON string. - * @param finder DependencyFinder. */ - void addClientListFromJSON(const std::string& jsonString, - const DependencyFinder& finder); + void addClientListFromJSON(const std::string& jsonString); /** * @brief Return the current JSON configuration. diff --git a/include/bedrock/DependencyFinder.hpp b/include/bedrock/DependencyFinder.hpp index 14196d8..e1a2335 100644 --- a/include/bedrock/DependencyFinder.hpp +++ b/include/bedrock/DependencyFinder.hpp @@ -19,6 +19,7 @@ namespace bedrock { class Server; class ServerImpl; class ProviderManager; +class ClientManager; class DependencyFinderImpl; /** @@ -29,6 +30,7 @@ class DependencyFinder { friend class Server; friend class ProviderManager; + friend class ClientManager; friend class ServerImpl; public: diff --git a/include/bedrock/Server.hpp b/include/bedrock/Server.hpp index 2b83f8d..5d8ae48 100644 --- a/include/bedrock/Server.hpp +++ b/include/bedrock/Server.hpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -93,6 +94,11 @@ class Server { */ ProviderManager getProviderManager() const; + /** + * @brief Get the underlying ClientManager. + */ + ClientManager getClientManager() const; + /** * @brief Get the underlying SSG context. */ diff --git a/python/mochi/bedrock/server.py b/python/mochi/bedrock/server.py index 2d5cac3..ea397e0 100644 --- a/python/mochi/bedrock/server.py +++ b/python/mochi/bedrock/server.py @@ -16,7 +16,7 @@ import pymargo.core import pymargo from typing import Mapping, List -from .spec import ProcSpec, MargoSpec, PoolSpec, XstreamSpec, SSGSpec, AbtIOSpec, ProviderSpec +from .spec import ProcSpec, MargoSpec, PoolSpec, XstreamSpec, SSGSpec, AbtIOSpec, ProviderSpec, ClientSpec import json @@ -50,6 +50,7 @@ def handle(self): Xstream = NamedDependency SSGGroup = NamedDependency AbtIOInstance = NamedDependency +Client = NamedDependency class ProviderDependency(NamedDependency): @@ -216,6 +217,55 @@ def create(self, name: str, pool: str|Pool, config: str|dict = "{}") -> AbtIOIns return AbtIOInstance(self._internal.add_abtio_instance(name, pool, config)) +class ClientManager: + + def __init__(self, internal: pybedrock_server.ClientManager, server: 'Server'): + self._internal = internal + self._server = server + + @property + def config(self) -> dict: + return json.loads(self._internal.config) + + @property + def spec(self) -> list[ClientSpec]: + return [ClientSpec.from_dict(client) for client in self.config] + + def __len__(self): + return len(self._internal.clients) + + def __getitem__(self, key: int|str) -> Provider: + clients = self._internal.clients + if isinstance(key, int): + key = clients[key].name + return self.lookup(key) + + def __delitem__(self, key: int|str) -> None: + if isinstance(key, int): + key = self._internal.clients[key].name + self._internal.destroy_client(key) + + def lookup(self, locator: str) -> Client: + return Client(self._internal.lookup_client(locator)) + + def lookup_or_create_anonymous(self, type: str) -> Client: + return Client(self._internal.lookup_client_or_create(type)) + + def create(self, name: str, type: str, config: str|dict = "{}", + dependencies: Mapping[str,str] = {}, + tags: List[str] = []) -> Client: + if isinstance(config, str): + config = json.loads(config) + info = { + "name": name, + "type": type, + "dependencies": dependencies, + "tags": tags, + "config": config + } + return Client(self._internal.add_client_from_json(json.dumps(info))) + + class ProviderManager: def __init__(self, internal: pybedrock_server.ProviderManager, server: 'Server'): @@ -261,7 +311,7 @@ def create(self, name: str, type: str, provider_id: int, pool: str|Pool, "tags": tags, "config": config } - return Provider(self, self._internal.add_providers_from_json(json.dumps(info))) + return Provider(self, self._internal.add_provider_from_json(json.dumps(info))) def migrate(self, provider: str, dest_addr: str, dest_provider_id: str, migration_config: str|dict = "{}", @@ -333,6 +383,10 @@ def ssg(self) -> SSGManager: def abtio(self) -> AbtIOManager: return AbtIOManager(self._internal.abtio_manager, self) + @property + def clients(self) -> ClientManager: + return ClientManager(self._internal.client_manager, self) + @property def providers(self) -> ProviderManager: return ProviderManager(self._internal.provider_manager, self) diff --git a/python/mochi/bedrock/test_client_manager.py b/python/mochi/bedrock/test_client_manager.py new file mode 100644 index 0000000..06c1dbe --- /dev/null +++ b/python/mochi/bedrock/test_client_manager.py @@ -0,0 +1,82 @@ +import unittest +import pymargo.logging +import mochi.bedrock.server as mbs +import mochi.bedrock.spec as spec + + +class TestClientManager(unittest.TestCase): + + def setUp(self): + config = { + "libraries": { + "module_a": "libModuleA.so", + "module_b": "libModuleB.so" + }, + "clients": [ + { + "name": "my_client_A", + "type": "module_a" + } + ] + } + self.server = mbs.Server(address="na+sm", config=config) + self.server.margo.engine.logger.set_log_level(pymargo.logging.level.critical) + + def tearDown(self): + self.server.finalize() + del self.server + + def test_get_client_manager(self): + clients = self.server.clients + self.assertIsInstance(clients, mbs.ClientManager) + self.assertEqual(len(clients), 1) + client_A = clients[0] + client_B = clients["my_client_A"] + self.assertEqual(client_A.name, client_B.name) + self.assertEqual(client_A.type, client_B.type) + self.assertEqual(client_A.handle, client_B.handle) + with self.assertRaises(IndexError): + c = clients[1] + with self.assertRaises(mbs.BedrockException): + c = clients["bla"] + + def test_client_manager_config(self): + config = self.server.clients.config + self.assertIsInstance(config, list) + self.assertEqual(len(config), 1) + client_1 = config[0] + self.assertIsInstance(client_1, dict) + for key in ["name", "config", "dependencies", "tags", "type"]: + self.assertIn(key, client_1) + + def test_client_manager_spec(self): + spec_list = self.server.clients.spec + self.assertIsInstance(spec_list, list) + for s in spec_list: + self.assertIsInstance(s, spec.ClientSpec) + + def test_add_client(self): + clients = self.server.clients + clients.create( + name="my_client_B", + type="module_b") + self.assertEqual(len(clients), 2) + client_A = clients[1] + client_B = clients["my_client_B"] + self.assertEqual(client_A.name, client_B.name) + self.assertEqual(client_A.type, client_B.type) + self.assertEqual(client_A.handle, client_B.handle) + + def test_remove_client(self): + self.test_add_client() + clients = self.server.clients + del clients["my_client_B"] + self.assertEqual(len(clients), 1) + with self.assertRaises(IndexError): + c = clients[1] + with self.assertRaises(mbs.BedrockException): + c = clients["my_provider_B"] + + +if __name__ == '__main__': + unittest.main() diff --git a/python/src/py-bedrock-server.cpp b/python/src/py-bedrock-server.cpp index dc5afe8..afafe83 100644 --- a/python/src/py-bedrock-server.cpp +++ b/python/src/py-bedrock-server.cpp @@ -88,6 +88,10 @@ PYBIND11_MODULE(pybedrock_server, m) { [](std::shared_ptr server) { return server->getProviderManager(); }) + .def_property_readonly("client_manager", + [](std::shared_ptr server) { + return server->getClientManager(); + }) .def_property_readonly("ssg_manager", [](std::shared_ptr server) { return server->getSSGManager(); @@ -200,21 +204,10 @@ PYBIND11_MODULE(pybedrock_server, m) { .def("lookup_provider", &ProviderManager::lookupProvider, "spec"_a) .def_property_readonly("providers", &ProviderManager::listProviders) - /* // need to expose ResolvedDependencyMap - .def("register_provider", - [](ProviderManager& manager, - const ProviderDescriptor& descriptor, - const std::string& pool_name, - const std::string& config, - const ResolvedDependencyMap& dependencies, - const std::vector& tags) { - ... - }) - */ .def("deregister_provider", &ProviderManager::deregisterProvider, "spec"_a) - .def("add_providers_from_json", + .def("add_provider_from_json", &ProviderManager::addProviderFromJSON, "json_config"_a) .def("add_provider_list_from_json", @@ -234,4 +227,28 @@ PYBIND11_MODULE(pybedrock_server, m) { &ProviderManager::restoreProvider, "provider"_a, "src_path"_a, "restore_config"_a) ; + + py11::class_ (m, "ClientDescriptor") + .def(py11::init()) + .def_readonly("name", &ClientDescriptor::name) + .def_readonly("type", &ClientDescriptor::type) + ; + + py11::class_ (m, "ClientManager") + .def_property_readonly("config", &ClientManager::getCurrentConfig) + .def("lookup_client", &ClientManager::lookupClient, + "name"_a) + .def("lookup_client_or_create", &ClientManager::lookupOrCreateAnonymous, + "type"_a) + .def_property_readonly("clients", &ClientManager::listClients) + .def("destroy_client", + &ClientManager::destroyClient, + "name"_a) + .def("add_client_from_json", + &ClientManager::addClientFromJSON, + "json_config"_a) + .def("add_client_list_from_json", + &ClientManager::addClientListFromJSON, + "json_configs"_a) + ; } diff --git a/src/ClientManager.cpp b/src/ClientManager.cpp index 73fda97..7222444 100644 --- a/src/ClientManager.cpp +++ b/src/ClientManager.cpp @@ -43,6 +43,10 @@ ClientManager::~ClientManager() = default; ClientManager::operator bool() const { return static_cast(self); } +void ClientManager::setDependencyFinder(const DependencyFinder& finder) { + self->m_dependency_finder = finder; +} + std::shared_ptr ClientManager::lookupClient(const std::string& name) const { std::lock_guard lock(self->m_clients_mtx); auto it = self->resolveSpec(name); @@ -171,8 +175,8 @@ void ClientManager::destroyClient(const std::string& name) { } std::shared_ptr -ClientManager::addClientFromJSON( - const std::string& jsonString, const DependencyFinder& dependencyFinder) { +ClientManager::addClientFromJSON(const std::string& jsonString) { + auto dependencyFinder = DependencyFinder(self->m_dependency_finder); auto config = jsonString.empty() ? json::object() : json::parse(jsonString); if (!config.is_object()) { throw DETAILED_EXCEPTION("Client configuration should be an object"); @@ -278,8 +282,7 @@ ClientManager::addClientFromJSON( return createClient(descriptor, client_config, resolved_dependency_map, tags); } -void ClientManager::addClientListFromJSON( - const std::string& jsonString, const DependencyFinder& dependencyFinder) { +void ClientManager::addClientListFromJSON(const std::string& jsonString) { auto config = json::parse(jsonString); if (config.is_null()) { return; } if (!config.is_array()) { @@ -288,7 +291,7 @@ void ClientManager::addClientListFromJSON( "ClientManager::addClientListFromJSON (expected array)"); } for (const auto& client : config) { - addClientFromJSON(client.dump(), dependencyFinder); + addClientFromJSON(client.dump()); } } diff --git a/src/ClientManagerImpl.hpp b/src/ClientManagerImpl.hpp index 5e5389b..848777f 100644 --- a/src/ClientManagerImpl.hpp +++ b/src/ClientManagerImpl.hpp @@ -11,6 +11,7 @@ #include "bedrock/AbstractServiceFactory.hpp" #include "bedrock/ClientDescriptor.hpp" #include "bedrock/ClientManager.hpp" +#include "DependencyFinderImpl.hpp" #include #include @@ -78,6 +79,7 @@ class ClientManagerImpl public std::enable_shared_from_this { public: + std::shared_ptr m_dependency_finder; std::vector> m_clients; mutable tl::mutex m_clients_mtx; mutable tl::condition_variable m_clients_cv; diff --git a/src/Server.cpp b/src/Server.cpp index 74e5ad5..057f426 100644 --- a/src/Server.cpp +++ b/src/Server.cpp @@ -151,14 +151,14 @@ Server::Server(const std::string& address, const std::string& configString, // Creating clients spdlog::trace("Initializing clients"); + clientManager.setDependencyFinder(dependencyFinder); auto clientManagerConfig = config["clients"].dump(); - clientManager.addClientListFromJSON(clientManagerConfig, dependencyFinder); + clientManager.addClientListFromJSON(clientManagerConfig); // Starting up providers spdlog::trace("Initializing providers"); auto providerManagerConfig = config["providers"].dump(); - ProviderManager(self->m_provider_manager) - .setDependencyFinder(dependencyFinder); + providerManager.setDependencyFinder(dependencyFinder); providerManager.addProviderListFromJSON(providerManagerConfig); spdlog::trace("Providers initialized"); @@ -183,6 +183,10 @@ ProviderManager Server::getProviderManager() const { return self->m_provider_manager; } +ClientManager Server::getClientManager() const { + return self->m_client_manager; +} + SSGManager Server::getSSGManager() const { return self->m_ssg_manager; } void Server::onPreFinalize(void* uargs) { diff --git a/src/ServerImpl.hpp b/src/ServerImpl.hpp index 1c2c071..5731798 100644 --- a/src/ServerImpl.hpp +++ b/src/ServerImpl.hpp @@ -158,9 +158,7 @@ class ServerImpl : public tl::provider { depconfig[name] = dep; } try { - ClientManager(m_client_manager) - .addClientFromJSON(fullconfig.dump(), - DependencyFinder(m_dependency_finder)); + ClientManager(m_client_manager).addClientFromJSON(fullconfig.dump()); } catch (const Exception& ex) { result.error() = ex.what(); result.success() = false;