1use std::sync::Arc;
7
8use async_trait::async_trait;
9use rskit_bootstrap::{Component, Health};
10use rskit_errors::{AppError, AppResult, ErrorCode};
11
12use crate::{instance::ServiceInstance, traits::Registry};
13
14pub struct DiscoveryServer<S: Component + ?Sized> {
19 inner: Arc<S>,
20 registry: Arc<dyn Registry>,
21 instance: ServiceInstance,
22 name: String,
23}
24
25impl<S: Component + ?Sized> DiscoveryServer<S> {
26 pub fn new(
35 name: String,
36 inner: Arc<S>,
37 registry: Arc<dyn Registry>,
38 instance: ServiceInstance,
39 ) -> Self {
40 Self {
41 inner,
42 registry,
43 instance,
44 name,
45 }
46 }
47
48 pub fn inner(&self) -> &S {
50 &self.inner
51 }
52
53 pub fn instance(&self) -> &ServiceInstance {
55 &self.instance
56 }
57}
58
59#[async_trait]
60impl<S: Component + ?Sized + 'static> Component for DiscoveryServer<S> {
61 fn name(&self) -> &str {
62 &self.name
63 }
64
65 async fn start(&self) -> AppResult<()> {
66 tracing::debug!(
68 component = %self.name,
69 "Starting inner server component"
70 );
71 self.inner.start().await.map_err(|e| {
72 AppError::new(
73 ErrorCode::Internal,
74 format!("failed to start inner server: {}", e),
75 )
76 })?;
77
78 tracing::debug!(
80 component = %self.name,
81 service_id = %self.instance.id,
82 service_name = %self.instance.name,
83 address = %self.instance.address,
84 port = %self.instance.port,
85 "Registering with service discovery"
86 );
87
88 if let Err(err) = self.registry.register(&self.instance).await {
89 tracing::error!(
91 component = %self.name,
92 error = %err,
93 "Registration failed, stopping inner server"
94 );
95 let inner_clone = self.inner.clone();
96 tokio::spawn(async move {
97 if let Err(e) = inner_clone.stop().await {
98 tracing::warn!(
99 "Failed to stop inner server after registration failure: {}",
100 e
101 );
102 }
103 });
104 return Err(AppError::new(
105 ErrorCode::Internal,
106 format!("failed to register with discovery: {}", err),
107 ));
108 }
109
110 tracing::debug!(
111 component = %self.name,
112 service_id = %self.instance.id,
113 "Service registered successfully"
114 );
115 Ok(())
116 }
117
118 async fn stop(&self) -> AppResult<()> {
119 tracing::debug!(
120 component = %self.name,
121 service_id = %self.instance.id,
122 "Stopping discovery-server component"
123 );
124
125 if let Err(e) = self.registry.deregister(&self.instance.id).await {
127 tracing::warn!(
128 component = %self.name,
129 service_id = %self.instance.id,
130 error = %e,
131 "Failed to deregister from discovery"
132 );
133 }
135
136 self.inner.stop().await.map_err(|e| {
138 AppError::new(
139 ErrorCode::Internal,
140 format!("failed to stop inner server: {}", e),
141 )
142 })?;
143
144 tracing::debug!(
145 component = %self.name,
146 "Discovery-server component stopped"
147 );
148 Ok(())
149 }
150
151 fn health(&self) -> Health {
152 let inner_health = self.inner.health();
154 if inner_health.is_healthy() {
155 Health::healthy(format!("{} (registered)", self.name))
156 } else {
157 Health::unhealthy(
158 format!("{} (inner unhealthy)", self.name),
159 "inner component is unhealthy",
160 )
161 }
162 }
163}
164
165#[cfg(test)]
166mod tests {
167 use super::*;
168 use std::sync::Arc;
169
170 use rskit_errors::AppResult;
171
172 struct MockServer {
174 started: parking_lot::Mutex<bool>,
175 stopped: parking_lot::Mutex<bool>,
176 }
177
178 impl MockServer {
179 fn new() -> Self {
180 Self {
181 started: parking_lot::Mutex::new(false),
182 stopped: parking_lot::Mutex::new(false),
183 }
184 }
185 }
186
187 #[async_trait]
188 impl Component for MockServer {
189 fn name(&self) -> &str {
190 "mock-server"
191 }
192
193 async fn start(&self) -> AppResult<()> {
194 *self.started.lock() = true;
195 Ok(())
196 }
197
198 async fn stop(&self) -> AppResult<()> {
199 *self.stopped.lock() = true;
200 Ok(())
201 }
202
203 fn health(&self) -> Health {
204 Health::healthy("mock-server")
205 }
206 }
207
208 struct ErrorServer {
209 fail_start: bool,
210 fail_stop: bool,
211 healthy: bool,
212 }
213
214 #[async_trait]
215 impl Component for ErrorServer {
216 fn name(&self) -> &str {
217 "error-server"
218 }
219
220 async fn start(&self) -> AppResult<()> {
221 if self.fail_start {
222 Err(AppError::new(ErrorCode::Internal, "start failed"))
223 } else {
224 Ok(())
225 }
226 }
227
228 async fn stop(&self) -> AppResult<()> {
229 if self.fail_stop {
230 Err(AppError::new(ErrorCode::Internal, "stop failed"))
231 } else {
232 Ok(())
233 }
234 }
235
236 fn health(&self) -> Health {
237 if self.healthy {
238 Health::healthy("error-server")
239 } else {
240 Health::unhealthy("error-server", "down")
241 }
242 }
243 }
244
245 struct MockRegistry {
247 registered: parking_lot::Mutex<Vec<ServiceInstance>>,
248 deregistered: parking_lot::Mutex<Vec<String>>,
249 register_error: parking_lot::Mutex<Option<String>>,
250 deregister_error: parking_lot::Mutex<Option<String>>,
251 }
252
253 impl MockRegistry {
254 fn new() -> Arc<Self> {
255 Arc::new(Self {
256 registered: parking_lot::Mutex::new(Vec::new()),
257 deregistered: parking_lot::Mutex::new(Vec::new()),
258 register_error: parking_lot::Mutex::new(None),
259 deregister_error: parking_lot::Mutex::new(None),
260 })
261 }
262
263 fn set_register_error(&self, error: Option<String>) {
264 *self.register_error.lock() = error;
265 }
266
267 fn set_deregister_error(&self, error: Option<String>) {
268 *self.deregister_error.lock() = error;
269 }
270
271 fn registered_instances(&self) -> Vec<ServiceInstance> {
272 self.registered.lock().clone()
273 }
274
275 fn deregistered_ids(&self) -> Vec<String> {
276 self.deregistered.lock().clone()
277 }
278 }
279
280 #[async_trait]
281 impl Registry for MockRegistry {
282 async fn register(&self, instance: &ServiceInstance) -> AppResult<()> {
283 if let Some(err) = &*self.register_error.lock() {
284 return Err(AppError::new(ErrorCode::Internal, err.clone()));
285 }
286 self.registered.lock().push(instance.clone());
287 Ok(())
288 }
289
290 async fn deregister(&self, id: &str) -> AppResult<()> {
291 if let Some(err) = &*self.deregister_error.lock() {
292 return Err(AppError::new(ErrorCode::Internal, err.clone()));
293 }
294 self.deregistered.lock().push(id.to_string());
295 Ok(())
296 }
297 }
298
299 #[tokio::test]
300 async fn test_lifecycle_success() {
301 let server = Arc::new(MockServer::new());
302 let registry = MockRegistry::new();
303 let instance = ServiceInstance {
304 id: "test-1".to_string(),
305 name: "test-service".to_string(),
306 address: "127.0.0.1".to_string(),
307 port: 8080,
308 healthy: true,
309 weight: 1,
310 tags: vec!["test".to_string()],
311 metadata: Default::default(),
312 };
313
314 let discovery_server = DiscoveryServer::new(
315 "discovery-test".to_string(),
316 server.clone(),
317 registry.clone(),
318 instance.clone(),
319 );
320
321 discovery_server.start().await.unwrap();
323 assert!(*server.started.lock());
324 assert_eq!(registry.registered_instances().len(), 1);
325 assert_eq!(registry.registered_instances()[0].id, "test-1");
326
327 discovery_server.stop().await.unwrap();
329 assert!(*server.stopped.lock());
330 assert_eq!(registry.deregistered_ids().len(), 1);
331 assert_eq!(registry.deregistered_ids()[0], "test-1");
332 }
333
334 #[tokio::test]
335 async fn test_registration_failure_stops_server() {
336 let server = Arc::new(MockServer::new());
337 let registry = MockRegistry::new();
338 registry.set_register_error(Some("service unavailable".to_string()));
339
340 let instance = ServiceInstance {
341 id: "test-2".to_string(),
342 name: "test-service".to_string(),
343 address: "127.0.0.1".to_string(),
344 port: 8081,
345 healthy: true,
346 weight: 1,
347 tags: vec![],
348 metadata: Default::default(),
349 };
350
351 let discovery_server = DiscoveryServer::new(
352 "discovery-test".to_string(),
353 server.clone(),
354 registry.clone(),
355 instance,
356 );
357
358 let result = discovery_server.start().await;
360 assert!(result.is_err());
361
362 assert!(*server.started.lock());
364
365 tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
367 assert!(*server.stopped.lock());
368 }
369
370 #[tokio::test]
371 async fn test_deregistration_failure_doesnt_prevent_stop() {
372 let server = Arc::new(MockServer::new());
373 let registry = MockRegistry::new();
374 let instance = ServiceInstance {
375 id: "test-3".to_string(),
376 name: "test-service".to_string(),
377 address: "127.0.0.1".to_string(),
378 port: 8082,
379 healthy: true,
380 weight: 1,
381 tags: vec![],
382 metadata: Default::default(),
383 };
384
385 let discovery_server = DiscoveryServer::new(
386 "discovery-test".to_string(),
387 server.clone(),
388 registry.clone(),
389 instance,
390 );
391
392 discovery_server.start().await.unwrap();
394 assert_eq!(registry.registered_instances().len(), 1);
395
396 registry.set_deregister_error(Some("registry error".to_string()));
398
399 let result = discovery_server.stop().await;
401 assert!(result.is_ok());
402
403 assert!(*server.stopped.lock());
405 }
406
407 #[test]
408 fn test_component_name_and_accessors() {
409 let server = Arc::new(MockServer::new());
410 let registry = MockRegistry::new();
411 let instance = ServiceInstance {
412 id: "test-4".to_string(),
413 name: "my-service".to_string(),
414 address: "192.168.1.1".to_string(),
415 port: 9000,
416 healthy: true,
417 weight: 1,
418 tags: vec!["prod".to_string()],
419 metadata: Default::default(),
420 };
421
422 let discovery_server = DiscoveryServer::new(
423 "my-discovery-server".to_string(),
424 server,
425 registry,
426 instance.clone(),
427 );
428
429 assert_eq!(discovery_server.name(), "my-discovery-server");
430 assert_eq!(discovery_server.inner().name(), "mock-server");
431 assert_eq!(discovery_server.instance().id, "test-4");
432 assert_eq!(discovery_server.instance().name, "my-service");
433 assert_eq!(discovery_server.instance().port, 9000);
434 }
435
436 #[tokio::test]
437 async fn inner_start_failure_prevents_registration() {
438 let server = Arc::new(ErrorServer {
439 fail_start: true,
440 fail_stop: false,
441 healthy: true,
442 });
443 let registry = MockRegistry::new();
444 let discovery_server = DiscoveryServer::new(
445 "discovery-test".to_string(),
446 server,
447 registry.clone(),
448 test_instance("test-5"),
449 );
450
451 let err = discovery_server.start().await.unwrap_err();
452
453 assert!(err.to_string().contains("failed to start inner server"));
454 assert!(registry.registered_instances().is_empty());
455 }
456
457 #[tokio::test]
458 async fn inner_stop_failure_is_returned_after_deregistering() {
459 let server = Arc::new(ErrorServer {
460 fail_start: false,
461 fail_stop: true,
462 healthy: true,
463 });
464 let registry = MockRegistry::new();
465 let discovery_server = DiscoveryServer::new(
466 "discovery-test".to_string(),
467 server,
468 registry.clone(),
469 test_instance("test-6"),
470 );
471 discovery_server.start().await.unwrap();
472
473 let err = discovery_server.stop().await.unwrap_err();
474
475 assert!(err.to_string().contains("failed to stop inner server"));
476 assert_eq!(registry.deregistered_ids(), vec!["test-6".to_string()]);
477 }
478
479 #[test]
480 fn health_reflects_inner_component_state() {
481 let registry = MockRegistry::new();
482 let healthy = DiscoveryServer::new(
483 "discovery-test".to_string(),
484 Arc::new(ErrorServer {
485 fail_start: false,
486 fail_stop: false,
487 healthy: true,
488 }),
489 registry.clone(),
490 test_instance("test-7"),
491 );
492 let unhealthy = DiscoveryServer::new(
493 "discovery-test".to_string(),
494 Arc::new(ErrorServer {
495 fail_start: false,
496 fail_stop: false,
497 healthy: false,
498 }),
499 registry,
500 test_instance("test-8"),
501 );
502
503 assert!(healthy.health().is_healthy());
504 assert!(!unhealthy.health().is_healthy());
505 }
506
507 fn test_instance(id: &str) -> ServiceInstance {
508 ServiceInstance {
509 id: id.to_string(),
510 name: "test-service".to_string(),
511 address: "127.0.0.1".to_string(),
512 port: 8080,
513 healthy: true,
514 weight: 1,
515 tags: Vec::new(),
516 metadata: Default::default(),
517 }
518 }
519}