Skip to content

Commit

Permalink
started adding tests for dependency finder
Browse files Browse the repository at this point in the history
  • Loading branch information
mdorier committed Feb 1, 2024
1 parent 59d6ae1 commit ae38aab
Show file tree
Hide file tree
Showing 9 changed files with 318 additions and 125 deletions.
30 changes: 16 additions & 14 deletions include/bedrock/DependencyFinder.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -81,27 +81,38 @@ class DependencyFinder {
/**
* @brief Resolve a specification, returning a void* handle to it.
* This function throws an exception if the specification could not
* be resolved. A specification string follows the following grammar:
* be resolved. A specification is either a name, or a string follows
* the following grammar:
*
* SPEC := IDENTIFIER
* | IDENTIFIER '@' LOCATION
* IDENTIFIER := NAME
* IDENTIFIER := SPECIFIER
* | NAME '->' SPECIFIER
* SPECIFIER := NAME
* | TYPE ':' ID
* LOCATION := ADDRESS
* | 'ssg://' GROUP '/' RANK
* | 'ssg://' NAME '/' RANK
* ADDRESS := <mercury address>
* NAME := <qualified identifier>
* ID := <provider id>
*
*
* For instance, "abc" represents the name "abc".
* "abc:123" represents a provider of type "abc" with
* provider id 123. "abc->def@address" represents a provider handle
* created from client named "abc", pointing to a provider named "def"
* at address "address".
*
* @param [in] type Type of dependency.
* @param [in] kind Kind of dependency (BEDROCK_KIND_*).
* @param [in] spec Specification string.
* @param [out] Resolved specification.
*
* @return handle to dependency
*/
std::shared_ptr<NamedDependency>
find(const std::string& type, const std::string& spec,
std::string* resolved) const;
find(const std::string& type, int32_t kind,
const std::string& spec, std::string* resolved) const;

/**
* @brief Find a local provider based on a type and provider id.
Expand Down Expand Up @@ -145,15 +156,6 @@ class DependencyFinder {
std::shared_ptr<NamedDependency> findClient(
const std::string& type, const std::string& name) const;

/**
* @brief Get an admin of a given type.
*
* @param type Type of admin.
*
* @return An abstract pointer to the dependency.
*/
std::shared_ptr<NamedDependency> getAdmin(const std::string& type) const;

/**
* @brief Make a provider handle to a specified provider.
* Throws an exception if no provider was found with this
Expand Down
29 changes: 20 additions & 9 deletions include/bedrock/module.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ extern "C" {
#define BEDROCK_REQUIRED 0x1
#define BEDROCK_ARRAY 0x2

#define BEDROCK_KIND_CLIENT (0x1 << 2)
#define BEDROCK_KIND_PROVIDER_HANDLE (0x2 << 2)
#define BEDROCK_KIND_PROVIDER (0x3 << 2)

#define BEDROCK_GET_KIND_FROM_FLAG(__flag__) (__flag__ & ~0b11)

typedef struct bedrock_args* bedrock_args_t;
#define BEDROCK_ARGS_NULL ((bedrock_args_t)NULL)

Expand All @@ -36,33 +42,38 @@ typedef void* bedrock_module_client_t;
* a module dependency. The name correspondings to the name
* of the dependency in the module configuration. The type
* corresponds to the type of dependency (name of other modules
* from which the dependency comes from). The flags field
* allows parameterizing the dependency. It should be an or-ed
* from which the dependency comes from).
*
* The flags field allows parameterizing the dependency. It should be an or-ed
* value from BEDROCK_REQUIRED (this dependency is required)
* and BEDROCK_ARRAY (this dependency is an array). Note that
* BEDROCK_REQUIRED | BEDROCK_ARRAY indicates that the array
* should contain at least 1 entry.
*
* The flag should be or-ed with one of the BEDROCK_KIND_*
* values to specify the kind of dependency that is expected
* if the dependency is from a module (client, provider, or provider handle).
*
* For example, the following bedrock_dependency
* { "storage", "bake", BEDROCK_REQUIRED | BEDROCK_ARRAY }
* { "storage", "bake", BEDROCK_REQUIRED | BEDROCK_ARRAY | BEDROCK_KIND_PROVIDER_HANDLE }
* indicates that a provider for this module requires to be
* created with a "dependencies" section in its JSON looking
* like the following:
* "dependencies" : {
* "storage" : [ "bake:34@na+sm://1234", ... ]
* }
* that is, a "storage" key is expected (name = "storage"),
* and it will resolve to an array of bake constructs
* (e.g. providers or provider handles).
* and it will resolve to an array of at least one bake (type = "bake")
* provider handles (flags has BEDROCK_KIND_PROVIDER_HANDLE).
*/
struct bedrock_dependency {
const char* name;
const char* type;
int32_t flags;
const char* name;
const char* type;
int32_t flags;
};

#define BEDROCK_NO_MORE_DEPENDENCIES \
{ NULL, NULL, 0 }
{ NULL, NULL, 0}

/**
* @brief Type of function called to register a provider.
Expand Down
150 changes: 150 additions & 0 deletions python/mochi/bedrock/test_dependencies.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import unittest
import pymargo.logging
import mochi.bedrock.server as mbs
import mochi.bedrock.spec as spec


class TestProviderManager(unittest.TestCase):

def setUp(self):
config = {
"libraries": {
"module_a": "libModuleA.so",
"module_b": "libModuleB.so",
"module_c": "libModuleC.so",
},
"providers": [
{
"name": "my_provider_a",
"type": "module_a",
"provider_id": 1
},
{
"name": "my_provider_b",
"type": "module_b",
"provider_id": 2
}
],
"clients": [
{
"name": "my_client_a",
"type": "module_a"
},
{
"name": "my_client_b",
"type": "module_b"
}
]
}
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 make_client_params(self, expected_dependencies: dict={}):
params = {
"name": "my_provider_C",
"type": "module_c",
"config": {
"expected_client_dependencies": expected_dependencies
}
}
return params

def make_provider_params(self, expected_dependencies: dict={}):
params = self.make_client_params({})
params["provider_id"] = 3
params["pool"] = "__primary__"
params["config"]["expected_provider_dependencies"] = expected_dependencies
return params


def test_no_dependency(self):
providers = self.server.providers
self.assertEqual(len(providers), 2)
clients = self.server.clients
self.assertEqual(len(clients), 2)

client_params = self.make_client_params()
clients.create(**client_params)

provider_params = self.make_provider_params()
providers.create(**provider_params)

self.assertEqual(len(providers), 3)
self.assertEqual(len(clients), 3)

def test_optional_dependency(self):
providers = self.server.providers
self.assertEqual(len(providers), 2)
clients = self.server.clients
self.assertEqual(len(clients), 2)

client_params = self.make_client_params([
{"name": "dep1",
"type": "module_a",
"kind": "provider_handle",
"is_array": False,
"is_required": False,
}])
clients.create(**client_params)

provider_params = self.make_provider_params([
{"name": "dep1",
"type": "module_a",
"kind": "provider_handle",
"is_array": False,
"is_required": False,
}])
providers.create(**provider_params)

self.assertEqual(len(providers), 3)
self.assertEqual(len(clients), 3)

def test_required_dependency(self):
providers = self.server.providers
self.assertEqual(len(providers), 2)
clients = self.server.clients
self.assertEqual(len(clients), 2)

# Try creating a client without the required dependency
client_params = self.make_client_params([
{"name": "dep1",
"type": "module_a",
"kind": "provider_handle",
"is_array": False,
"is_required": True,
}])
with self.assertRaises(mbs.BedrockException):
clients.create(**client_params)

# Try creating a client with the required dependency
client_params["dependencies"] = {
"dep1": "my_provider_a@local"
}
clients.create(**client_params)
self.assertEqual(len(clients), 3)

# Try creating a provider without the required dependency
provider_params = self.make_provider_params([
{"name": "dep1",
"type": "module_a",
"kind": "provider_handle",
"is_array": False,
"is_required": True,
}])
with self.assertRaises(mbs.BedrockException):
providers.create(**provider_params)

# Try creating a provider with the required dependency
provider_params["dependencies"] = {
"dep1": "my_provider_a@local"
}
providers.create(**provider_params)
self.assertEqual(len(providers), 3)


if __name__ == '__main__':
unittest.main()
2 changes: 1 addition & 1 deletion python/mochi/bedrock/test_service_handle.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ def test_load_module(self):
self.sh.load_module("module_a", "libModuleA.so")
self.sh.load_module("module_b", "libModuleB.so")
with self.assertRaises(mbc.ClientException):
self.sh.load_module("module_c", "libModuleC.so")
self.sh.load_module("module_x", "libModuleX.so")

def add_pool(self, config):
initial_num_pools = len(self.server.margo.pools)
Expand Down
6 changes: 4 additions & 2 deletions src/ClientManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,8 @@ ClientManager::addClientFromJSON(const std::string& jsonString) {
throw DETAILED_EXCEPTION("Dependency {} should be a string",
dependency.name);
}
auto ptr = dependencyFinder.find(dependency.type,
auto ptr = dependencyFinder.find(
dependency.type, BEDROCK_GET_KIND_FROM_FLAG(dependency.flags),
dep_config.get<std::string>(), nullptr);
resolved_dependency_map[dependency.name].dependencies.push_back(ptr);
resolved_dependency_map[dependency.name].is_array = false;
Expand All @@ -267,7 +268,8 @@ ClientManager::addClientFromJSON(const std::string& jsonString) {
"Item in dependency array {} should be a string",
dependency.name);
}
auto ptr = dependencyFinder.find(dependency.type,
auto ptr = dependencyFinder.find(
dependency.type, BEDROCK_GET_KIND_FROM_FLAG(dependency.flags),
elem.get<std::string>(), nullptr);
resolved_dependency_map[dependency.name].dependencies.push_back(ptr);
resolved_dependency_map[dependency.name].is_array = true;
Expand Down
Loading

0 comments on commit ae38aab

Please sign in to comment.