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