Skip to main content

Grove/Binary/Build/
ServiceRegister.rs

1//! Service Register Module
2//!
3//! Handles service registration with Mountain.
4//! Provides gRPC-based service discovery and registration.
5
6use anyhow::{Context, Result};
7use serde::{Deserialize, Serialize};
8
9use crate::{
10	Protocol::{ProtocolConfig, SpineConnection::SpineConnectionImpl},
11	dev_log,
12};
13
14/// Service register for managing Grove's registration with Mountain
15pub struct ServiceRegister;
16
17/// Service registration information
18#[derive(Debug, Clone, Serialize, Deserialize)]
19pub struct ServiceRegistration {
20	/// Service name
21	pub name:String,
22
23	/// Service type
24	pub service_type:ServiceType,
25
26	/// Service version
27	pub version:String,
28
29	/// Service endpoint
30	pub endpoint:String,
31
32	/// Service capabilities
33	pub capabilities:Vec<String>,
34
35	/// Metadata
36	pub metadata:serde_json::Value,
37}
38
39/// Service type
40#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
41pub enum ServiceType {
42	/// Extension host service
43	ExtensionHost = 0,
44
45	/// Configuration service
46	Configuration = 1,
47
48	/// Logging service
49	Logging = 2,
50
51	/// Custom service
52	Custom = 99,
53}
54
55/// Service registration result
56#[derive(Debug, Clone, Serialize, Deserialize)]
57pub struct ServiceRegistrationResult {
58	/// Registration success
59	pub success:bool,
60
61	/// Service ID assigned by Mountain
62	pub service_id:Option<String>,
63
64	/// Error message if registration failed
65	pub error:Option<String>,
66
67	/// Timestamp
68	pub timestamp:u64,
69}
70
71impl ServiceRegister {
72	/// Register Grove with Mountain
73	pub async fn register_with_mountain(
74		service_name:&str,
75
76		mountain_address:&str,
77
78		auto_reconnect:bool,
79	) -> Result<ServiceRegistrationResult> {
80		dev_log!(
81			"grove",
82			"Registering service '{}' with Mountain at {}",
83			service_name,
84			mountain_address
85		);
86
87		// Create Spine configuration
88		let spine_config = ProtocolConfig::new().with_mountain_endpoint(service_name.to_string());
89
90		// Create Spine connection
91		let mut connection = SpineConnectionImpl::new(spine_config);
92
93		// Connect to Mountain
94		connection.Connect().await.context("Failed to connect to Mountain")?;
95
96		// Prepare registration information
97		let registration = ServiceRegistration {
98			name:service_name.to_string(),
99
100			service_type:ServiceType::ExtensionHost,
101
102			version:env!("CARGO_PKG_VERSION").to_string(),
103
104			endpoint:mountain_address.to_string(),
105
106			capabilities:vec![
107				"wasm-runtime".to_string(),
108				"native-rust".to_string(),
109				"cocoon-compatible".to_string(),
110			],
111
112			metadata:serde_json::json!({
113				"host_type": "grove",
114				"features": ["wasm", "native", "ipc"]
115			}),
116		};
117
118		dev_log!("grove", "Service registration: {:?}", registration);
119
120		// Send registration request (placeholder - in real implementation, use gRPC)
121		let result = ServiceRegistrationResult {
122			success:true,
123
124			service_id:Some(format!("grove-{}", uuid::Uuid::new_v4())),
125
126			error:None,
127
128			timestamp:std::time::SystemTime::now()
129				.duration_since(std::time::UNIX_EPOCH)
130				.map(|d| d.as_secs())
131				.unwrap_or(0),
132		};
133
134		dev_log!("grove", "Service registration result: {:?}", result);
135
136		Ok(result)
137	}
138
139	/// Unregister Grove from Mountain
140	pub async fn unregister_from_mountain(service_id:&str) -> Result<()> {
141		dev_log!("grove", "Unregistering service from Mountain: {}", service_id);
142
143		// Placeholder - in real implementation, call Mountain's unregister service
144		dev_log!("grove", "Service unregistered: {}", service_id);
145
146		Ok(())
147	}
148
149	/// Heartbeat to keep service alive
150	pub async fn send_heartbeat(service_id:&str) -> Result<()> {
151		dev_log!("grove", "Sending heartbeat for service: {}", service_id);
152
153		// Placeholder - in real implementation, send heartbeat to Mountain
154		Ok(())
155	}
156
157	/// Update service information
158	pub async fn update_registration(
159		service_id:&str,
160
161		registration:ServiceRegistration,
162	) -> Result<ServiceRegistrationResult> {
163		dev_log!("grove", "Updating service registration: {}", service_id);
164
165		dev_log!("grove", "Updated registration: {:?}", registration);
166
167		Ok(ServiceRegistrationResult {
168			success:true,
169			service_id:Some(service_id.to_string()),
170			error:None,
171			timestamp:std::time::SystemTime::now()
172				.duration_since(std::time::UNIX_EPOCH)
173				.map(|d| d.as_secs())
174				.unwrap_or(0),
175		})
176	}
177
178	/// Query service information
179	pub async fn query_service(service_id:&str) -> Result<ServiceRegistration> {
180		dev_log!("grove", "Querying service information: {}", service_id);
181
182		// Placeholder - in real implementation, query Mountain for service info
183		Ok(ServiceRegistration {
184			name:service_id.to_string(),
185			service_type:ServiceType::ExtensionHost,
186			version:"0.1.0".to_string(),
187			endpoint:"127.0.0.1:50050".to_string(),
188			capabilities:Vec::new(),
189			metadata:serde_json::Value::Null,
190		})
191	}
192
193	/// List all registered services
194	pub async fn list_services() -> Result<Vec<ServiceRegistration>> {
195		dev_log!("grove", "Listing all registered services");
196
197		// Placeholder - in real implementation, query Mountain for all services
198		Ok(Vec::new())
199	}
200
201	/// Start heartbeat loop
202	pub async fn start_heartbeat_loop(service_id:&str, interval_sec:u64) -> Result<()> {
203		dev_log!(
204			"grove",
205			"Starting heartbeat loop for service: {} (interval: {}s)",
206			service_id,
207			interval_sec
208		);
209
210		let service_id_owned = service_id.to_string();
211
212		tokio::spawn(async move {
213			loop {
214				tokio::time::sleep(tokio::time::Duration::from_secs(interval_sec)).await;
215				if let Err(e) = Self::send_heartbeat(&service_id_owned).await {
216					dev_log!("grove", "warn: heartbeat failed: {}", e);
217				}
218			}
219		});
220
221		Ok(())
222	}
223}
224
225impl Default for ServiceRegister {
226	fn default() -> Self { Self }
227}
228
229#[cfg(test)]
230mod tests {
231
232	use super::*;
233
234	#[test]
235	fn test_service_register_default() {
236		let register = ServiceRegister::default();
237
238		// Just test that it can be created
239		let _ = register;
240	}
241
242	#[test]
243	fn test_service_type() {
244		assert_eq!(ServiceType::ExtensionHost as i32, 0);
245
246		assert_eq!(ServiceType::Configuration as i32, 1);
247
248		assert_eq!(ServiceType::Logging as i32, 2);
249
250		assert_eq!(ServiceType::Custom as i32, 99);
251	}
252
253	#[tokio::test]
254	async fn test_service_registration_creation() {
255		let registration = ServiceRegistration {
256			name:"test-service".to_string(),
257
258			service_type:ServiceType::ExtensionHost,
259
260			version:"1.0.0".to_string(),
261
262			endpoint:"127.0.0.1:50050".to_string(),
263
264			capabilities:vec!["test-capability".to_string()],
265
266			metadata:serde_json::Value::Null,
267		};
268
269		assert_eq!(registration.name, "test-service");
270
271		assert_eq!(registration.service_type, ServiceType::ExtensionHost);
272	}
273}