Skip to content

Commit f8e9fcd

Browse files
sdelliotmitchnegus
andauthored
refactor: Replace the gRPC-backed RepositoryDB with a JSON-file backed one. (#48)
This is an effort to reduce the impact of our services on starting and using FIREWHEEL. Specifically, the RepositoryDB has been part of the gRPC service that FIREWHEEL provides. This inter-dependency is unnecessary for the following reasons: 1. It does not need to be accessed except from the "control node". 2. The gRPC service stored the repositories (e.g., a path) in a JSON file. 3. As repositories become pip packages, the necessity of this feature wains entirely. In this PR, we strip away the unnecessary complexity of gRPC and simply rely on the single JSON file for locally-installed model components. We have updated all necessary documentation and tests accordingly. Finally, we needed to provide a minimum version for the `grpcio` package as the newly generated code checks version information. Note that the `pb2*` files are auto-generated via `grpc_tools.protoc`. --------- Signed-off-by: Steven Elliott <[email protected]> Co-authored-by: Mitch Negus <[email protected]>
1 parent d800ea8 commit f8e9fcd

31 files changed

+515
-1908
lines changed

docs/source/_static/proto_doc.html

Lines changed: 1 addition & 1185 deletions
Large diffs are not rendered by default.

docs/source/code/lib.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ FIREWHEEL gRPC Protocol
7777

7878
This documentation was compiled using `protoc <https://github.com/protocolbuffers/protobuf>`_ and `protoc-gen-doc <https://github.com/pseudomuto/protoc-gen-doc>`_ with::
7979

80-
protoc --doc_out=./doc/_static/ --doc_opt=html,proto_doc.html ./grpc/firewheel_grpc.proto
80+
python -m grpc_tools.protoc --plugin=protoc-gen-doc=./protoc-gen-doc --doc_out=./docs/source/_static/ --doc_opt=html,proto_doc.html --proto_path ./src/firewheel/lib/grpc firewheel_grpc.proto
8181

8282
minimega/api.py
8383
---------------

pyproject.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,8 @@ dependencies = [
5858
"ClusterShell<=1.9.2",
5959
"colorama<=0.4.6",
6060
"coverage<=7.6.10",
61-
"grpcio>=1.49.0,<=1.67.0",
62-
"grpcio-tools>=1.49.0,<=1.69.0",
61+
"grpcio>=1.63.0,<=1.67.0",
62+
"grpcio-tools>=1.63.0,<=1.67.0",
6363
"importlib_metadata>=3.6,<=8.5.0",
6464
"Jinja2>=3.1.2,<=3.1.5",
6565
"netaddr<=1.3.0,>=0.7.0",
@@ -217,6 +217,7 @@ target-version = "py39"
217217
extend-exclude = [
218218
"test_*.py",
219219
"firewheel_grpc_pb2*.py",
220+
"firewheel_grpc_pb2.pyi"
220221
]
221222

222223
[tool.ruff.lint]

src/firewheel/control/repository_db.py

Lines changed: 73 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,22 @@
11
import os
2+
import json
3+
from pathlib import Path
24

35
from importlib_metadata import entry_points
46

57
from firewheel.config import config
68
from firewheel.lib.log import Log
7-
from firewheel.lib.grpc.firewheel_grpc_client import FirewheelGrpcClient
89

910

1011
class RepositoryDb:
1112
"""
12-
Provides an interface to the Repository database on the gRPC service.
13+
Provides an interface to the Repository database.
14+
This database is functionally a JSON file which stores a list of locally installed
15+
model component repositories which are uniquely identified by their path.
1316
14-
Repositories in the database are uniquely identified by their path.
17+
A repository, as stored in the database, is expected to have the form:
1518
16-
A repository, as stored in the database, is expected to have the form::
19+
.. code-block:: json
1720
1821
{
1922
"path": ""
@@ -23,28 +26,22 @@ class RepositoryDb:
2326

2427
def __init__(
2528
self,
26-
host=config["grpc"]["hostname"],
27-
port=config["grpc"]["port"],
28-
db=config["grpc"]["db"],
29+
db_basepath=config["system"]["default_output_dir"],
30+
db_filename="repositories.json",
2931
):
3032
"""
31-
Set up the connection to the gRPC server.
33+
Set up the instance variables and path to the RepositoryDB file.
3234
3335
Args:
34-
host (str): The GRPC server IP/hostname.
35-
port (int): The GRPC server port.
36-
db (str): The GRPC database.
36+
db_basepath (str): The base path where the RepositoryDB file is stored.
37+
db_filename (str): The name of the RepositoryDB file. Defaults to "repositories.json".
3738
"""
3839
self.log = Log(name="RepositoryDb").log
39-
self.grpc_client = None
40-
if host:
41-
self.grpc_client = FirewheelGrpcClient(hostname=host, port=port, db=db)
42-
43-
def close(self):
44-
"""
45-
Close the database connection.
46-
"""
47-
self.grpc_client.close()
40+
self.db_file = Path(db_basepath) / Path(db_filename)
41+
self.db_file.parent.mkdir(parents=True, exist_ok=True)
42+
if not self.db_file.exists():
43+
with self.db_file.open("w") as db:
44+
json.dump([], db)
4845

4946
def list_repositories(self):
5047
"""
@@ -60,9 +57,14 @@ def list_repositories(self):
6057
"""
6158
entries = []
6259

63-
# Add all model component repositories identified by the gRPC client
64-
if self.grpc_client is not None:
65-
entries.extend(list(self.grpc_client.list_repositories()))
60+
# Add all local model component repositories
61+
if self.db_file.exists():
62+
with self.db_file.open("r") as db:
63+
try:
64+
local_entries = json.load(db)
65+
entries.extend(local_entries)
66+
except json.decoder.JSONDecodeError:
67+
self.log.warning("Repository DB unable to be read.")
6668

6769
# Add all model components that have been added via entry points
6870
for entry in entry_points(group="firewheel.mc_repo"):
@@ -78,7 +80,24 @@ def add_repository(self, repository):
7880
repository (dict): A repository dictionary to add. See format for the database.
7981
"""
8082
self._validate_repository(repository)
81-
self.grpc_client.set_repository(repository)
83+
84+
# Get all local model component repositories
85+
if self.db_file.exists():
86+
with self.db_file.open("r") as db:
87+
try:
88+
entries = json.load(db)
89+
except json.decoder.JSONDecodeError:
90+
self.log.warning("Repository DB unable to be read.")
91+
entries = []
92+
else:
93+
# No database file exists yet.
94+
entries = []
95+
96+
entries.append(repository)
97+
with self.db_file.open("w") as db:
98+
json.dump(entries, db)
99+
100+
self.log.debug("Added repository: %s", repository)
82101

83102
def delete_repository(self, repository):
84103
"""
@@ -91,9 +110,36 @@ def delete_repository(self, repository):
91110
int: Number of entries removed (expect 0 or 1), or None if an
92111
error occurred.
93112
"""
94-
self._validate_repository(repository)
95-
result = self.grpc_client.remove_repository(repository)["removed_count"]
96-
return result
113+
# Get all local model component repositories
114+
if self.db_file.exists():
115+
with self.db_file.open("r") as db:
116+
try:
117+
entries = json.load(db)
118+
except json.decoder.JSONDecodeError:
119+
self.log.warning("Repository DB unable to be read.")
120+
entries = []
121+
else:
122+
# No database file exists yet.
123+
entries = []
124+
125+
try:
126+
entries.remove(repository)
127+
self._validate_repository(repository)
128+
self.log.debug("Removed repository: %s", repository)
129+
except ValueError:
130+
self.log.debug(
131+
"%s repository did not exist and could not be removed.", repository
132+
)
133+
return 0
134+
except FileNotFoundError:
135+
self.log.debug(
136+
"%s repository path does not exist, but was removed anyways.", repository
137+
)
138+
139+
with self.db_file.open("w") as db:
140+
json.dump(entries, db)
141+
142+
return 1
97143

98144
def _validate_repository(self, repository):
99145
"""

src/firewheel/lib/grpc/firewheel_grpc.proto

Lines changed: 0 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,6 @@ package firewheel_grpc;
1010

1111

1212
service Firewheel {
13-
// Set a repository.
14-
rpc SetRepository(Repository) returns (SetRepositoryResponse) {}
15-
// Remove a repository.
16-
rpc RemoveRepository(Repository) returns (RemoveRepositoryResponse) {}
17-
// List all repositories.
18-
rpc ListRepositories(ListRepositoriesRequest) returns (stream Repository) {}
19-
// Remove all repositories.
20-
rpc RemoveAllRepositories(RemoveAllRepositoriesRequest) returns (RemoveAllRepositoriesResponse) {}
21-
2213
// Gets the gRPC server info.
2314
rpc GetInfo(GetInfoRequest) returns (GetInfoResponse) {}
2415

@@ -52,28 +43,6 @@ service Firewheel {
5243

5344
}
5445

55-
message Repository {
56-
string db = 1;
57-
string path = 2;
58-
}
59-
60-
message SetRepositoryResponse {
61-
}
62-
63-
message RemoveRepositoryResponse {
64-
int32 removed_count = 1;
65-
}
66-
67-
message ListRepositoriesRequest {
68-
string db = 1;
69-
}
70-
message RemoveAllRepositoriesRequest {
71-
string db = 1;
72-
}
73-
message RemoveAllRepositoriesResponse {
74-
int32 removed_count = 1;
75-
}
76-
7746
message GetInfoRequest {
7847
}
7948

src/firewheel/lib/grpc/firewheel_grpc_client.py

Lines changed: 0 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -91,74 +91,6 @@ def get_info(self):
9191
return None
9292
return response_dict
9393

94-
def set_repository(self, repository):
95-
"""
96-
Requests that the given repository is set.
97-
98-
Args:
99-
repository (dict): The repository as a dictionary.
100-
101-
Returns:
102-
dict: Dictionary representation of firewheel_grpc_pb2.SetRepositoryResponse.
103-
"""
104-
req = firewheel_grpc_pb2.Repository(db=self.db, path=repository["path"])
105-
response = self.stub.SetRepository(req)
106-
return msg_to_dict(response)
107-
108-
def remove_repository(self, repository):
109-
"""
110-
Requests that the given repository is removed.
111-
112-
Args:
113-
repository (dict): The repository as a dictionary.
114-
115-
Returns:
116-
dict: Dictionary representation of firewheel_grpc_pb2.RemoveRepositoryResponse.
117-
"""
118-
req = firewheel_grpc_pb2.Repository(db=self.db, path=repository["path"])
119-
response = self.stub.RemoveRepository(req)
120-
return msg_to_dict(response)
121-
122-
def remove_all_repositories(self):
123-
"""
124-
Requests to remove all repositories.
125-
126-
Returns:
127-
dict: Dictionary representation of `firewheel_grpc_pb2.RemoveAllRepositoriesResponse`
128-
"""
129-
req = firewheel_grpc_pb2.RemoveAllRepositoriesRequest(db=self.db)
130-
response = self.stub.RemoveAllRepositories(req)
131-
return msg_to_dict(response)
132-
133-
def get_repositories_as_dict(self):
134-
"""
135-
Requests all of the repositories.
136-
137-
Returns:
138-
(iterable) dict: Dictionary representations of `firewheel_grpc_pb2.Repository`.
139-
"""
140-
req = firewheel_grpc_pb2.ListRepositoriesRequest(db=self.db)
141-
142-
repositories = self.stub.ListRepositories(req)
143-
ret = {}
144-
for repository in repositories:
145-
repo_dict = msg_to_dict(repository)
146-
ret[repo_dict["path"]] = repo_dict
147-
return ret
148-
149-
def list_repositories(self):
150-
"""
151-
Requests all of the repositories.
152-
153-
Yields:
154-
dict: Dictionary representations of `firewheel_grpc_pb2.Repository`.
155-
"""
156-
req = firewheel_grpc_pb2.ListRepositoriesRequest(db=self.db)
157-
158-
repositories = self.stub.ListRepositories(req)
159-
for repository in repositories:
160-
yield msg_to_dict(repository)
161-
16294
def get_experiment_launch_time(self):
16395
"""
16496
Requests the `experiment_launch_time`.

0 commit comments

Comments
 (0)