//! Backend service implementation use std::sync::Arc; use crate::metadata::LbMetadataStore; use fiberlb_api::{ backend_service_server::BackendService, CreateBackendRequest, CreateBackendResponse, DeleteBackendRequest, DeleteBackendResponse, GetBackendRequest, GetBackendResponse, ListBackendsRequest, ListBackendsResponse, UpdateBackendRequest, UpdateBackendResponse, Backend as ProtoBackend, BackendAdminState as ProtoBackendAdminState, BackendStatus as ProtoBackendStatus, }; use fiberlb_types::{Backend, BackendAdminState, BackendId, BackendStatus, PoolId}; use tonic::{Request, Response, Status}; use uuid::Uuid; /// Backend service implementation pub struct BackendServiceImpl { metadata: Arc, } impl BackendServiceImpl { /// Create a new BackendServiceImpl pub fn new(metadata: Arc) -> Self { Self { metadata } } } /// Convert domain Backend to proto fn backend_to_proto(backend: &Backend) -> ProtoBackend { ProtoBackend { id: backend.id.to_string(), name: backend.name.clone(), pool_id: backend.pool_id.to_string(), address: backend.address.clone(), port: backend.port as u32, weight: backend.weight, admin_state: match backend.admin_state { BackendAdminState::Enabled => ProtoBackendAdminState::Enabled.into(), BackendAdminState::Disabled => ProtoBackendAdminState::Disabled.into(), BackendAdminState::Drain => ProtoBackendAdminState::Drain.into(), }, status: match backend.status { BackendStatus::Online => ProtoBackendStatus::Online.into(), BackendStatus::Offline => ProtoBackendStatus::Offline.into(), BackendStatus::Checking => ProtoBackendStatus::Checking.into(), BackendStatus::Disabled => ProtoBackendStatus::Offline.into(), BackendStatus::Unknown => ProtoBackendStatus::Unknown.into(), }, created_at: backend.created_at, updated_at: backend.updated_at, } } /// Parse BackendId from string fn parse_backend_id(id: &str) -> Result { let uuid: Uuid = id .parse() .map_err(|_| Status::invalid_argument("invalid backend ID"))?; Ok(BackendId::from_uuid(uuid)) } /// Parse PoolId from string fn parse_pool_id(id: &str) -> Result { let uuid: Uuid = id .parse() .map_err(|_| Status::invalid_argument("invalid pool ID"))?; Ok(PoolId::from_uuid(uuid)) } #[tonic::async_trait] impl BackendService for BackendServiceImpl { async fn create_backend( &self, request: Request, ) -> Result, Status> { let req = request.into_inner(); // Validate required fields if req.name.is_empty() { return Err(Status::invalid_argument("name is required")); } if req.pool_id.is_empty() { return Err(Status::invalid_argument("pool_id is required")); } if req.address.is_empty() { return Err(Status::invalid_argument("address is required")); } if req.port == 0 { return Err(Status::invalid_argument("port is required")); } let pool_id = parse_pool_id(&req.pool_id)?; // Create new backend let mut backend = Backend::new(&req.name, pool_id, &req.address, req.port as u16); // Apply weight if specified if req.weight > 0 { backend.weight = req.weight; } // Save backend self.metadata .save_backend(&backend) .await .map_err(|e| Status::internal(format!("failed to save backend: {}", e)))?; Ok(Response::new(CreateBackendResponse { backend: Some(backend_to_proto(&backend)), })) } async fn get_backend( &self, request: Request, ) -> Result, Status> { let req = request.into_inner(); if req.id.is_empty() { return Err(Status::invalid_argument("id is required")); } let _backend_id = parse_backend_id(&req.id)?; // Need pool_id context to efficiently look up backend // The proto doesn't include pool_id in GetBackendRequest Err(Status::unimplemented( "get_backend by ID requires pool_id context; use list_backends instead", )) } async fn list_backends( &self, request: Request, ) -> Result, Status> { let req = request.into_inner(); if req.pool_id.is_empty() { return Err(Status::invalid_argument("pool_id is required")); } let pool_id = parse_pool_id(&req.pool_id)?; let backends = self .metadata .list_backends(&pool_id) .await .map_err(|e| Status::internal(format!("metadata error: {}", e)))?; let proto_backends: Vec = backends.iter().map(backend_to_proto).collect(); Ok(Response::new(ListBackendsResponse { backends: proto_backends, next_page_token: String::new(), })) } async fn update_backend( &self, request: Request, ) -> Result, Status> { let req = request.into_inner(); if req.id.is_empty() { return Err(Status::invalid_argument("id is required")); } // For update, we need to know the pool_id to load the backend // This is a limitation - the proto doesn't require pool_id for update // We'll need to scan or require pool_id in a future update return Err(Status::unimplemented( "update_backend requires pool_id context; include pool_id in request", )); } async fn delete_backend( &self, request: Request, ) -> Result, Status> { let req = request.into_inner(); if req.id.is_empty() { return Err(Status::invalid_argument("id is required")); } // Same limitation as update - need pool_id context return Err(Status::unimplemented( "delete_backend requires pool_id context; include pool_id in request", )); } }