digitalkin 0.1.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- digitalkin/__init__.py +18 -0
- digitalkin/__version__.py +11 -0
- digitalkin/grpc/__init__.py +31 -0
- digitalkin/grpc/_base_server.py +488 -0
- digitalkin/grpc/module_server.py +233 -0
- digitalkin/grpc/module_servicer.py +304 -0
- digitalkin/grpc/registry_server.py +63 -0
- digitalkin/grpc/registry_servicer.py +451 -0
- digitalkin/grpc/utils/exceptions.py +33 -0
- digitalkin/grpc/utils/factory.py +178 -0
- digitalkin/grpc/utils/models.py +169 -0
- digitalkin/grpc/utils/types.py +24 -0
- digitalkin/logger.py +17 -0
- digitalkin/models/__init__.py +11 -0
- digitalkin/models/module/__init__.py +5 -0
- digitalkin/models/module/module.py +31 -0
- digitalkin/models/services/__init__.py +6 -0
- digitalkin/models/services/cost.py +53 -0
- digitalkin/models/services/storage.py +10 -0
- digitalkin/modules/__init__.py +7 -0
- digitalkin/modules/_base_module.py +177 -0
- digitalkin/modules/archetype_module.py +14 -0
- digitalkin/modules/job_manager.py +158 -0
- digitalkin/modules/tool_module.py +14 -0
- digitalkin/modules/trigger_module.py +14 -0
- digitalkin/py.typed +0 -0
- digitalkin/services/__init__.py +28 -0
- digitalkin/services/agent/__init__.py +6 -0
- digitalkin/services/agent/agent_strategy.py +22 -0
- digitalkin/services/agent/default_agent.py +16 -0
- digitalkin/services/cost/__init__.py +6 -0
- digitalkin/services/cost/cost_strategy.py +15 -0
- digitalkin/services/cost/default_cost.py +13 -0
- digitalkin/services/default_service.py +13 -0
- digitalkin/services/development_service.py +10 -0
- digitalkin/services/filesystem/__init__.py +6 -0
- digitalkin/services/filesystem/default_filesystem.py +29 -0
- digitalkin/services/filesystem/filesystem_strategy.py +31 -0
- digitalkin/services/identity/__init__.py +6 -0
- digitalkin/services/identity/default_identity.py +15 -0
- digitalkin/services/identity/identity_strategy.py +12 -0
- digitalkin/services/registry/__init__.py +6 -0
- digitalkin/services/registry/default_registry.py +13 -0
- digitalkin/services/registry/registry_strategy.py +17 -0
- digitalkin/services/service_provider.py +27 -0
- digitalkin/services/snapshot/__init__.py +6 -0
- digitalkin/services/snapshot/default_snapshot.py +39 -0
- digitalkin/services/snapshot/snapshot_strategy.py +31 -0
- digitalkin/services/storage/__init__.py +6 -0
- digitalkin/services/storage/default_storage.py +91 -0
- digitalkin/services/storage/grpc_storage.py +207 -0
- digitalkin/services/storage/storage_strategy.py +42 -0
- digitalkin/utils/__init__.py +1 -0
- digitalkin/utils/arg_parser.py +136 -0
- digitalkin-0.1.1.dist-info/METADATA +588 -0
- digitalkin-0.1.1.dist-info/RECORD +59 -0
- digitalkin-0.1.1.dist-info/WHEEL +5 -0
- digitalkin-0.1.1.dist-info/licenses/LICENSE +430 -0
- digitalkin-0.1.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
"""Module gRPC server implementation for DigitalKin."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
import uuid
|
|
5
|
+
|
|
6
|
+
import grpc
|
|
7
|
+
|
|
8
|
+
from digitalkin.grpc._base_server import BaseServer
|
|
9
|
+
from digitalkin.grpc.module_servicer import ModuleServicer
|
|
10
|
+
from digitalkin.grpc.utils.exceptions import ServerError
|
|
11
|
+
from digitalkin.grpc.utils.models import ModuleServerConfig, SecurityMode
|
|
12
|
+
from digitalkin.modules._base_module import BaseModule
|
|
13
|
+
|
|
14
|
+
from digitalkin_proto.digitalkin.module.v2 import module_service_pb2, module_service_pb2_grpc
|
|
15
|
+
from digitalkin_proto.digitalkin.module_registry.v2 import (
|
|
16
|
+
metadata_pb2,
|
|
17
|
+
module_registry_service_pb2_grpc,
|
|
18
|
+
registration_pb2,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
logger = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class ModuleServer(BaseServer):
|
|
25
|
+
"""gRPC server for a DigitalKin module.
|
|
26
|
+
|
|
27
|
+
This server exposes the module's functionality through the ModuleService gRPC interface.
|
|
28
|
+
It can optionally register itself with a ModuleRegistry server.
|
|
29
|
+
|
|
30
|
+
Attributes:
|
|
31
|
+
module: The module instance being served.
|
|
32
|
+
config: Server configuration.
|
|
33
|
+
module_servicer: The gRPC servicer handling module requests.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def __init__(
|
|
37
|
+
self,
|
|
38
|
+
module_class: type[BaseModule],
|
|
39
|
+
config: ModuleServerConfig,
|
|
40
|
+
) -> None:
|
|
41
|
+
"""Initialize the module server.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
module_class: The module instance to be served.
|
|
45
|
+
config: Server configuration including registry address if auto-registration is desired.
|
|
46
|
+
"""
|
|
47
|
+
super().__init__(config)
|
|
48
|
+
self.module_class = module_class
|
|
49
|
+
self.config = config
|
|
50
|
+
self.module_servicer: ModuleServicer | None = None
|
|
51
|
+
|
|
52
|
+
def _register_servicers(self) -> None:
|
|
53
|
+
"""Register the module servicer with the gRPC server.
|
|
54
|
+
|
|
55
|
+
Raises:
|
|
56
|
+
RuntimeError: No registered server
|
|
57
|
+
"""
|
|
58
|
+
if self.server is None:
|
|
59
|
+
msg = "Server must be created before registering servicers"
|
|
60
|
+
raise RuntimeError(msg)
|
|
61
|
+
|
|
62
|
+
logger.info("Registering module servicer for %s", self.module_class.__name__)
|
|
63
|
+
self.module_servicer = ModuleServicer(self.module_class)
|
|
64
|
+
self.register_servicer(
|
|
65
|
+
self.module_servicer,
|
|
66
|
+
module_service_pb2_grpc.add_ModuleServiceServicer_to_server,
|
|
67
|
+
service_descriptor=module_service_pb2.DESCRIPTOR,
|
|
68
|
+
)
|
|
69
|
+
logger.info("Registered Async Greeter servicer")
|
|
70
|
+
|
|
71
|
+
def start(self) -> None:
|
|
72
|
+
"""Start the module server and register with the registry if configured."""
|
|
73
|
+
logger.critical(self.config)
|
|
74
|
+
super().start()
|
|
75
|
+
|
|
76
|
+
logger.critical(self.config)
|
|
77
|
+
# If a registry address is provided, register the module
|
|
78
|
+
if self.config.registry_address:
|
|
79
|
+
try:
|
|
80
|
+
self._register_with_registry()
|
|
81
|
+
except Exception:
|
|
82
|
+
logger.exception("Failed to register with registry")
|
|
83
|
+
|
|
84
|
+
async def start_async(self) -> None:
|
|
85
|
+
"""Start the module server and register with the registry if configured."""
|
|
86
|
+
logger.critical(self.config)
|
|
87
|
+
await super().start_async()
|
|
88
|
+
|
|
89
|
+
logger.critical(self.config)
|
|
90
|
+
# If a registry address is provided, register the module
|
|
91
|
+
if self.config.registry_address:
|
|
92
|
+
try:
|
|
93
|
+
self._register_with_registry()
|
|
94
|
+
except Exception:
|
|
95
|
+
logger.exception("Failed to register with registry")
|
|
96
|
+
|
|
97
|
+
def stop(self, grace: float | None = None) -> None:
|
|
98
|
+
"""Stop the module server and deregister from the registry if needed."""
|
|
99
|
+
# If registered with a registry, deregister
|
|
100
|
+
if self.config.registry_address:
|
|
101
|
+
try:
|
|
102
|
+
self._deregister_from_registry()
|
|
103
|
+
except ServerError:
|
|
104
|
+
logger.exception("Failed to deregister from registry")
|
|
105
|
+
|
|
106
|
+
super().stop(grace)
|
|
107
|
+
|
|
108
|
+
def _register_with_registry(self) -> None:
|
|
109
|
+
"""Register this module with the registry server.
|
|
110
|
+
|
|
111
|
+
Raises:
|
|
112
|
+
ServerError: If communication with the registry server fails.
|
|
113
|
+
"""
|
|
114
|
+
logger.info("Registering module with registry at %s", self.config.registry_address)
|
|
115
|
+
|
|
116
|
+
# Create appropriate channel based on security mode
|
|
117
|
+
channel = self._create_registry_channel()
|
|
118
|
+
|
|
119
|
+
with channel:
|
|
120
|
+
# Create a stub (client)
|
|
121
|
+
stub = module_registry_service_pb2_grpc.ModuleRegistryServiceStub(channel)
|
|
122
|
+
|
|
123
|
+
# Determine module type
|
|
124
|
+
module_type = self._determine_module_type()
|
|
125
|
+
|
|
126
|
+
metadata = metadata_pb2.Metadata(
|
|
127
|
+
name=self.module_class.metadata["name"],
|
|
128
|
+
tags=[metadata_pb2.Tag(tag=tag) for tag in self.module_class.metadata["tags"]],
|
|
129
|
+
description=self.module_class.metadata["description"],
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
self.module_class.metadata["module_id"] = f"{self.module_class.metadata['name']}:{uuid.uuid4()}"
|
|
133
|
+
# Create registration request
|
|
134
|
+
request = registration_pb2.RegisterRequest(
|
|
135
|
+
module_id=self.module_class.metadata["module_id"],
|
|
136
|
+
version=self.module_class.metadata["version"],
|
|
137
|
+
module_type=module_type,
|
|
138
|
+
address=self.config.address,
|
|
139
|
+
metadata=metadata,
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
try:
|
|
143
|
+
# Call the register method
|
|
144
|
+
logger.info(
|
|
145
|
+
"Request sent to registry for module: %s:%s",
|
|
146
|
+
self.module_class.metadata["name"],
|
|
147
|
+
self.module_class.metadata["module_id"],
|
|
148
|
+
)
|
|
149
|
+
response = stub.RegisterModule(request)
|
|
150
|
+
|
|
151
|
+
if response.success:
|
|
152
|
+
logger.info("Module registered successfully")
|
|
153
|
+
else:
|
|
154
|
+
logger.error("Module registration failed")
|
|
155
|
+
except grpc.RpcError:
|
|
156
|
+
logger.exception("RPC error during registration:")
|
|
157
|
+
raise ServerError
|
|
158
|
+
|
|
159
|
+
def _deregister_from_registry(self) -> None:
|
|
160
|
+
"""Deregister this module from the registry server.
|
|
161
|
+
|
|
162
|
+
Raises:
|
|
163
|
+
ServerError: If communication with the registry server fails.
|
|
164
|
+
"""
|
|
165
|
+
logger.info("Deregistering module from registry at %s", self.config.registry_address)
|
|
166
|
+
|
|
167
|
+
# Create appropriate channel based on security mode
|
|
168
|
+
channel = self._create_registry_channel()
|
|
169
|
+
|
|
170
|
+
with channel:
|
|
171
|
+
# Create a stub (client)
|
|
172
|
+
stub = module_registry_service_pb2_grpc.ModuleRegistryServiceStub(channel)
|
|
173
|
+
|
|
174
|
+
# Create deregistration request
|
|
175
|
+
request = registration_pb2.DeregisterRequest(
|
|
176
|
+
module_id=self.module_class.metadata["module_id"],
|
|
177
|
+
)
|
|
178
|
+
try:
|
|
179
|
+
# Call the deregister method
|
|
180
|
+
response = stub.DeregisterModule(request)
|
|
181
|
+
|
|
182
|
+
if response.success:
|
|
183
|
+
logger.info("Module deregistered successfull")
|
|
184
|
+
else:
|
|
185
|
+
logger.error("Module deregistration failed")
|
|
186
|
+
except grpc.RpcError:
|
|
187
|
+
logger.exception("RPC error during deregistration")
|
|
188
|
+
raise ServerError
|
|
189
|
+
|
|
190
|
+
def _create_registry_channel(self) -> grpc.Channel:
|
|
191
|
+
"""Create an appropriate channel to the registry server.
|
|
192
|
+
|
|
193
|
+
Returns:
|
|
194
|
+
A gRPC channel for communication with the registry.
|
|
195
|
+
|
|
196
|
+
Raises:
|
|
197
|
+
ValueError: If credentials are required but not provided.
|
|
198
|
+
"""
|
|
199
|
+
if self.config.security == SecurityMode.SECURE and self.config.credentials:
|
|
200
|
+
# Secure channel
|
|
201
|
+
"""TODO: use Path(self.config.credentials.server_cert_path).read_bytes()"""
|
|
202
|
+
with open(self.config.credentials.server_cert_path, "rb") as cert_file: # noqa: FURB101
|
|
203
|
+
certificate_chain = cert_file.read()
|
|
204
|
+
|
|
205
|
+
root_certificates = None
|
|
206
|
+
if self.config.credentials.root_cert_path:
|
|
207
|
+
with open(self.config.credentials.root_cert_path, "rb") as root_cert_file: # noqa: FURB101
|
|
208
|
+
root_certificates = root_cert_file.read()
|
|
209
|
+
|
|
210
|
+
# Create channel credentials
|
|
211
|
+
channel_credentials = grpc.ssl_channel_credentials(root_certificates=root_certificates or certificate_chain)
|
|
212
|
+
|
|
213
|
+
return grpc.secure_channel(self.config.registry_address, channel_credentials)
|
|
214
|
+
# Insecure channel
|
|
215
|
+
return grpc.insecure_channel(self.config.registry_address)
|
|
216
|
+
|
|
217
|
+
def _determine_module_type(self) -> str:
|
|
218
|
+
"""Determine the module type based on its class.
|
|
219
|
+
|
|
220
|
+
Returns:
|
|
221
|
+
A string representing the module type.
|
|
222
|
+
"""
|
|
223
|
+
module_type = "UNKNOWN"
|
|
224
|
+
class_name = self.module_class.__name__
|
|
225
|
+
|
|
226
|
+
if class_name == "ToolModule":
|
|
227
|
+
module_type = "TOOL"
|
|
228
|
+
elif class_name == "TriggerModule":
|
|
229
|
+
module_type = "TRIGGER"
|
|
230
|
+
elif class_name == "ArchetypeModule":
|
|
231
|
+
module_type = "KIN"
|
|
232
|
+
|
|
233
|
+
return module_type
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
"""Module servicer implementation for DigitalKin."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import logging
|
|
5
|
+
from collections.abc import AsyncGenerator
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
import grpc
|
|
9
|
+
from digitalkin_proto.digitalkin.module.v2 import (
|
|
10
|
+
information_pb2,
|
|
11
|
+
lifecycle_pb2,
|
|
12
|
+
module_service_pb2_grpc,
|
|
13
|
+
monitoring_pb2,
|
|
14
|
+
)
|
|
15
|
+
from google.protobuf import json_format, struct_pb2
|
|
16
|
+
|
|
17
|
+
from digitalkin.models.module.module import ModuleStatus
|
|
18
|
+
from digitalkin.modules._base_module import BaseModule
|
|
19
|
+
from digitalkin.modules.job_manager import JobManager
|
|
20
|
+
|
|
21
|
+
logger = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class ModuleServicer(module_service_pb2_grpc.ModuleServiceServicer):
|
|
25
|
+
"""Implementation of the ModuleService.
|
|
26
|
+
|
|
27
|
+
This servicer handles interactions with a DigitalKin module.
|
|
28
|
+
|
|
29
|
+
Attributes:
|
|
30
|
+
module: The module instance being served.
|
|
31
|
+
active_jobs: Dictionary tracking active module jobs.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def __init__(self, module_class: type[BaseModule]) -> None:
|
|
35
|
+
"""Initialize the module servicer.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
module_class: The module type to serve.
|
|
39
|
+
"""
|
|
40
|
+
super().__init__()
|
|
41
|
+
self.queue: asyncio.Queue = asyncio.Queue()
|
|
42
|
+
self.module_class = module_class
|
|
43
|
+
self.job_manager = JobManager(module_class)
|
|
44
|
+
|
|
45
|
+
async def add_to_queue(self, job_id: str, output_data: dict[str, Any]) -> None:
|
|
46
|
+
"""Callback used to add the output data to the queue of messages."""
|
|
47
|
+
logger.info("JOB: %s added an output_data: %s", job_id, output_data)
|
|
48
|
+
await self.queue.put({job_id: output_data})
|
|
49
|
+
|
|
50
|
+
async def StartModule( # noqa: N802
|
|
51
|
+
self,
|
|
52
|
+
request: lifecycle_pb2.StartModuleRequest,
|
|
53
|
+
context: grpc.aio.ServicerContext,
|
|
54
|
+
) -> AsyncGenerator[lifecycle_pb2.StartModuleResponse, Any]:
|
|
55
|
+
"""Start a module execution.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
request: Iterator of start module requests.
|
|
59
|
+
context: The gRPC context.
|
|
60
|
+
|
|
61
|
+
Yields:
|
|
62
|
+
Responses during module execution.
|
|
63
|
+
"""
|
|
64
|
+
logger.info("StartModule called for module: '%s'", self.module_class.__name__)
|
|
65
|
+
# Process the module input
|
|
66
|
+
input_data = dict(request.input.items())
|
|
67
|
+
setup_data = self.module_class.storage.get(
|
|
68
|
+
table="setups",
|
|
69
|
+
data={
|
|
70
|
+
"keys": [
|
|
71
|
+
# remove prefix 'setups:'
|
|
72
|
+
request.setup_id[7:],
|
|
73
|
+
],
|
|
74
|
+
},
|
|
75
|
+
)[0]
|
|
76
|
+
|
|
77
|
+
# setup_id should be use to request a precise setup from the module
|
|
78
|
+
# Create a job for this execution
|
|
79
|
+
job_id, module = await self.job_manager.create_job(
|
|
80
|
+
input_data,
|
|
81
|
+
setup_data,
|
|
82
|
+
callback=self.add_to_queue,
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
while module.status == ModuleStatus.RUNNING or not self.queue.empty():
|
|
86
|
+
output_data = await self.queue.get()
|
|
87
|
+
if output_data.get("error", None) is not None:
|
|
88
|
+
context.set_code(output_data["error"]["code"])
|
|
89
|
+
context.set_details(output_data["error"]["error_message"])
|
|
90
|
+
yield lifecycle_pb2.StartModuleResponse(success=False)
|
|
91
|
+
return
|
|
92
|
+
else:
|
|
93
|
+
# TODO: add a check for the job_id / error handling
|
|
94
|
+
output_proto = {key: str(value) for key, value in output_data[job_id].items()}
|
|
95
|
+
yield lifecycle_pb2.StartModuleResponse(
|
|
96
|
+
success=True,
|
|
97
|
+
output=output_proto,
|
|
98
|
+
job_id=job_id,
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
async def StopModule( # noqa: N802
|
|
102
|
+
self,
|
|
103
|
+
request: lifecycle_pb2.StopModuleRequest,
|
|
104
|
+
context: grpc.ServicerContext,
|
|
105
|
+
) -> lifecycle_pb2.StopModuleResponse:
|
|
106
|
+
"""Stop a running module execution.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
request: The stop module request.
|
|
110
|
+
context: The gRPC context.
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
A response indicating success or failure.
|
|
114
|
+
"""
|
|
115
|
+
logger.info("StopModule called for module: '%s'", self.module_class.__name__)
|
|
116
|
+
|
|
117
|
+
job_id = request.job_id
|
|
118
|
+
if job_id not in self.job_manager.modules:
|
|
119
|
+
message = f"Job {job_id} not found"
|
|
120
|
+
logger.warning(message)
|
|
121
|
+
context.set_code(grpc.StatusCode.NOT_FOUND)
|
|
122
|
+
context.set_details(message)
|
|
123
|
+
return lifecycle_pb2.StopModuleResponse(success=False)
|
|
124
|
+
|
|
125
|
+
# Update the job status
|
|
126
|
+
await self.job_manager.modules[job_id].stop()
|
|
127
|
+
|
|
128
|
+
logger.info("Job %s stopped successfully", job_id)
|
|
129
|
+
return lifecycle_pb2.StopModuleResponse(success=True)
|
|
130
|
+
|
|
131
|
+
def GetModuleStatus( # noqa: N802
|
|
132
|
+
self,
|
|
133
|
+
request: monitoring_pb2.GetModuleStatusRequest,
|
|
134
|
+
context: grpc.ServicerContext,
|
|
135
|
+
) -> monitoring_pb2.GetModuleStatusResponse:
|
|
136
|
+
"""Get the status of a module.
|
|
137
|
+
|
|
138
|
+
Args:
|
|
139
|
+
request: The get module status request.
|
|
140
|
+
context: The gRPC context.
|
|
141
|
+
|
|
142
|
+
Returns:
|
|
143
|
+
A response with the module status.
|
|
144
|
+
"""
|
|
145
|
+
logger.info("GetModuleStatus called for module: '%s'", self.module_class.__name__)
|
|
146
|
+
|
|
147
|
+
# If job_id is specified, get status for that job
|
|
148
|
+
if request.job_id:
|
|
149
|
+
if request.job_id not in self.job_manager.modules:
|
|
150
|
+
message = f"Job {request.job_id} not found"
|
|
151
|
+
logger.warning(message)
|
|
152
|
+
context.set_code(grpc.StatusCode.NOT_FOUND)
|
|
153
|
+
context.set_details(message)
|
|
154
|
+
return monitoring_pb2.GetModuleStatusResponse()
|
|
155
|
+
|
|
156
|
+
status = self.job_manager.modules[request.job_id].status
|
|
157
|
+
logger.info("Job %s status: '%s'", request.job_id, status)
|
|
158
|
+
return monitoring_pb2.GetModuleStatusResponse(
|
|
159
|
+
success=True,
|
|
160
|
+
status=status.name,
|
|
161
|
+
job_id=request.job_id,
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
logger.info("Job %s status: '%s'", request.job_id, ModuleStatus.NOT_FOUND)
|
|
165
|
+
return monitoring_pb2.GetModuleStatusResponse(
|
|
166
|
+
success=False,
|
|
167
|
+
status=ModuleStatus.NOT_FOUND.name,
|
|
168
|
+
job_id=request.job_id,
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
def GetModuleJobs( # noqa: N802
|
|
172
|
+
self,
|
|
173
|
+
request: monitoring_pb2.GetModuleJobsRequest, # noqa: ARG002
|
|
174
|
+
context: grpc.ServicerContext, # noqa: ARG002
|
|
175
|
+
) -> monitoring_pb2.GetModuleJobsResponse:
|
|
176
|
+
"""Get information about the module's jobs.
|
|
177
|
+
|
|
178
|
+
Args:
|
|
179
|
+
request: The get module jobs request.
|
|
180
|
+
context: The gRPC context.
|
|
181
|
+
|
|
182
|
+
Returns:
|
|
183
|
+
A response with information about active jobs.
|
|
184
|
+
"""
|
|
185
|
+
logger.info("GetModuleJobs called for module: '%s'", self.module_class.__name__)
|
|
186
|
+
|
|
187
|
+
# Create job info objects for each active job
|
|
188
|
+
return monitoring_pb2.GetModuleJobsResponse(
|
|
189
|
+
jobs=[
|
|
190
|
+
monitoring_pb2.JobInfo(
|
|
191
|
+
job_id=job_id,
|
|
192
|
+
job_status=job_data.status.name,
|
|
193
|
+
)
|
|
194
|
+
for job_id, job_data in self.job_manager.modules.items()
|
|
195
|
+
],
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
def GetModuleInput( # noqa: N802
|
|
199
|
+
self,
|
|
200
|
+
request: information_pb2.GetModuleInputRequest,
|
|
201
|
+
context: grpc.ServicerContext,
|
|
202
|
+
) -> information_pb2.GetModuleInputResponse:
|
|
203
|
+
"""Get information about the module's expected input.
|
|
204
|
+
|
|
205
|
+
Args:
|
|
206
|
+
request: The get module input request.
|
|
207
|
+
context: The gRPC context.
|
|
208
|
+
|
|
209
|
+
Returns:
|
|
210
|
+
A response with the module's input schema.
|
|
211
|
+
"""
|
|
212
|
+
logger.info("GetModuleInput called for module: '%s'", self.module_class.__name__)
|
|
213
|
+
|
|
214
|
+
# Get input schema if available
|
|
215
|
+
try:
|
|
216
|
+
# Convert schema to proto format
|
|
217
|
+
input_schema_proto = self.module_class.get_input_format(request.llm_format)
|
|
218
|
+
input_format_struct = json_format.Parse(
|
|
219
|
+
text=input_schema_proto,
|
|
220
|
+
message=struct_pb2.Struct(), # pylint: disable=no-member
|
|
221
|
+
ignore_unknown_fields=True,
|
|
222
|
+
)
|
|
223
|
+
except NotImplementedError as e:
|
|
224
|
+
logger.warning(e)
|
|
225
|
+
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
|
226
|
+
context.set_details(e)
|
|
227
|
+
return information_pb2.GetModuleInputResponse()
|
|
228
|
+
|
|
229
|
+
return information_pb2.GetModuleInputResponse(
|
|
230
|
+
success=True,
|
|
231
|
+
input_schema=input_format_struct,
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
def GetModuleOutput( # noqa: N802
|
|
235
|
+
self,
|
|
236
|
+
request: information_pb2.GetModuleOutputRequest,
|
|
237
|
+
context: grpc.ServicerContext,
|
|
238
|
+
) -> information_pb2.GetModuleOutputResponse:
|
|
239
|
+
"""Get information about the module's expected output.
|
|
240
|
+
|
|
241
|
+
Args:
|
|
242
|
+
request: The get module output request.
|
|
243
|
+
context: The gRPC context.
|
|
244
|
+
|
|
245
|
+
Returns:
|
|
246
|
+
A response with the module's output schema.
|
|
247
|
+
"""
|
|
248
|
+
logger.info("GetModuleOutput called for module: '%s'", self.module_class.__name__)
|
|
249
|
+
|
|
250
|
+
# Get output schema if available
|
|
251
|
+
try:
|
|
252
|
+
# Convert schema to proto format
|
|
253
|
+
output_schema_proto = self.module_class.get_output_format(request.llm_format)
|
|
254
|
+
output_format_struct = json_format.Parse(
|
|
255
|
+
text=output_schema_proto,
|
|
256
|
+
message=struct_pb2.Struct(), # pylint: disable=no-member
|
|
257
|
+
ignore_unknown_fields=True,
|
|
258
|
+
)
|
|
259
|
+
except NotImplementedError as e:
|
|
260
|
+
logger.warning(e)
|
|
261
|
+
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
|
262
|
+
context.set_details(e)
|
|
263
|
+
return information_pb2.GetModuleOutputResponse()
|
|
264
|
+
|
|
265
|
+
return information_pb2.GetModuleOutputResponse(
|
|
266
|
+
success=True,
|
|
267
|
+
output_schema=output_format_struct,
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
def GetModuleSetup( # noqa: N802
|
|
271
|
+
self,
|
|
272
|
+
request: information_pb2.GetModuleSetupRequest,
|
|
273
|
+
context: grpc.ServicerContext,
|
|
274
|
+
) -> information_pb2.GetModuleSetupResponse:
|
|
275
|
+
"""Get information about the module's setup and configuration.
|
|
276
|
+
|
|
277
|
+
Args:
|
|
278
|
+
request: The get module setup request.
|
|
279
|
+
context: The gRPC context.
|
|
280
|
+
|
|
281
|
+
Returns:
|
|
282
|
+
A response with the module's setup information.
|
|
283
|
+
"""
|
|
284
|
+
logger.info("GetModuleSetup called for module: '%s'", self.module_class.__name__)
|
|
285
|
+
|
|
286
|
+
# Get setup schema if available
|
|
287
|
+
try:
|
|
288
|
+
# Convert schema to proto format
|
|
289
|
+
setup_schema_proto = self.module_class.get_setup_format(request.llm_format)
|
|
290
|
+
setup_format_struct = json_format.Parse(
|
|
291
|
+
text=setup_schema_proto,
|
|
292
|
+
message=struct_pb2.Struct(), # pylint: disable=no-member
|
|
293
|
+
ignore_unknown_fields=True,
|
|
294
|
+
)
|
|
295
|
+
except NotImplementedError as e:
|
|
296
|
+
logger.warning(e)
|
|
297
|
+
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
|
298
|
+
context.set_details(e)
|
|
299
|
+
return information_pb2.GetModuleSetupResponse()
|
|
300
|
+
|
|
301
|
+
return information_pb2.GetModuleSetupResponse(
|
|
302
|
+
success=True,
|
|
303
|
+
setup_schema=setup_format_struct,
|
|
304
|
+
)
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""Registry gRPC server implementation for DigitalKin."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
from digitalkin_proto.digitalkin.module_registry.v2 import module_registry_service_pb2_grpc
|
|
6
|
+
|
|
7
|
+
from digitalkin.grpc._base_server import BaseServer
|
|
8
|
+
from digitalkin.grpc.registry_servicer import RegistryModule, RegistryServicer
|
|
9
|
+
from digitalkin.grpc.utils.models import RegistryServerConfig
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class RegistryServer(BaseServer):
|
|
15
|
+
"""gRPC server for DigitalKin module registry.
|
|
16
|
+
|
|
17
|
+
This server implements the ModuleRegistryService which allows modules to register
|
|
18
|
+
themselves and be discovered by other components in the system.
|
|
19
|
+
|
|
20
|
+
Attributes:
|
|
21
|
+
config: Server configuration.
|
|
22
|
+
registry_servicer: The gRPC servicer handling registry requests.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __init__(
|
|
26
|
+
self,
|
|
27
|
+
config: RegistryServerConfig,
|
|
28
|
+
) -> None:
|
|
29
|
+
"""Initialize the registry server.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
config: Server configuration.
|
|
33
|
+
"""
|
|
34
|
+
super().__init__(config)
|
|
35
|
+
self.config = config
|
|
36
|
+
self.registry_servicer: RegistryServicer | None = None
|
|
37
|
+
|
|
38
|
+
def _register_servicers(self) -> None:
|
|
39
|
+
"""Register the registry servicer with the gRPC server.
|
|
40
|
+
|
|
41
|
+
Raises:
|
|
42
|
+
RuntimeError: If server is not registered during server creation
|
|
43
|
+
"""
|
|
44
|
+
if self.server is None:
|
|
45
|
+
msg = "Server must be created before registering servicers"
|
|
46
|
+
raise RuntimeError(msg)
|
|
47
|
+
|
|
48
|
+
logger.info("Registering module registry servicer")
|
|
49
|
+
self.registry_servicer = RegistryServicer()
|
|
50
|
+
module_registry_service_pb2_grpc.add_ModuleRegistryServiceServicer_to_server(
|
|
51
|
+
self.registry_servicer, self.server
|
|
52
|
+
)
|
|
53
|
+
self._servicers.append(self.registry_servicer)
|
|
54
|
+
|
|
55
|
+
def get_registered_modules(self) -> list[RegistryModule]:
|
|
56
|
+
"""Get a list of all registered modules.
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
A list of module information objects.
|
|
60
|
+
"""
|
|
61
|
+
if self.registry_servicer:
|
|
62
|
+
return list(self.registry_servicer.registered_modules.values())
|
|
63
|
+
return []
|