Skip to main content

slim_controller/
config.rs

1// Copyright AGNTCY Contributors (https://github.com/agntcy)
2// SPDX-License-Identifier: Apache-2.0
3
4use std::sync::Arc;
5
6use serde::Deserialize;
7
8use slim_config::client::ClientConfig;
9use slim_config::component::configuration::Configuration;
10use slim_config::component::id::ID;
11use slim_config::server::ServerConfig;
12use slim_datapath::message_processing::MessageProcessor;
13
14use crate::errors::ControllerError;
15use crate::service::{ControlPlane, ControlPlaneSettings, from_server_config};
16
17/// Configuration for the Control-Plane / Data-Plane component
18#[derive(Debug, Clone, Deserialize, Default, PartialEq)]
19#[serde(deny_unknown_fields)]
20pub struct Config {
21    /// Controller GRPC server settings
22    #[serde(default)]
23    pub servers: Vec<ServerConfig>,
24
25    /// Controller client config to connect to control plane
26    #[serde(default)]
27    pub clients: Vec<ClientConfig>,
28
29    /// How long to keep routing state after a server-side connection drops,
30    /// waiting for the peer to reconnect before notifying the control plane.
31    /// Accepts duration strings like "30s", "1s", "500ms".  Defaults to 30 s.
32    #[serde(default)]
33    pub recovery_ttl: Option<duration_string::DurationString>,
34}
35
36impl Config {
37    /// Create a new Config instance with default values
38    pub fn new() -> Self {
39        Self::default()
40    }
41
42    pub fn is_default(&self) -> bool {
43        self == &Self::default()
44    }
45
46    /// Create a new Config instance with the given servers
47    pub fn with_servers(self, servers: Vec<ServerConfig>) -> Self {
48        Self { servers, ..self }
49    }
50
51    /// Create a new Config instance with the given clients
52    pub fn with_clients(self, clients: Vec<ClientConfig>) -> Self {
53        Self { clients, ..self }
54    }
55
56    /// Get the list of server configurations
57    pub fn servers(&self) -> &[ServerConfig] {
58        &self.servers
59    }
60
61    /// Get the list of client configurations
62    pub fn clients(&self) -> &[ClientConfig] {
63        &self.clients
64    }
65
66    /// Create a ControlPlane service instance from this configuration
67    pub fn into_service(
68        &self,
69        id: ID,
70        group_name: Option<String>,
71        message_processor: Arc<MessageProcessor>,
72        // List of server configurations for the dataplane services.
73        // Used to extract connection type information required to connect to the node
74        // (e.g., TLS settings). This information is used by the control plane.
75        dataplane_servers: &[ServerConfig],
76    ) -> ControlPlane {
77        let connection_details = dataplane_servers.iter().map(from_server_config).collect();
78
79        ControlPlane::new(ControlPlaneSettings {
80            id,
81            group_name,
82            servers: self.servers.clone(),
83            clients: self.clients.clone(),
84            message_processor,
85            connection_details,
86        })
87    }
88}
89
90impl Configuration for Config {
91    type Error = ControllerError;
92
93    fn validate(&self) -> Result<(), Self::Error> {
94        // Validate client and server configurations
95        for server in self.servers.iter() {
96            server.validate()?;
97        }
98
99        for client in &self.clients {
100            client.validate()?;
101        }
102
103        Ok(())
104    }
105}
106
107#[cfg(test)]
108mod tests {
109    use super::*;
110    use slim_config::component::id::{ID, Kind};
111    use slim_config::server::ServerConfig;
112    use slim_datapath::message_processing::MessageProcessor;
113    use std::sync::Arc;
114
115    fn create_test_server_config() -> ServerConfig {
116        ServerConfig::with_endpoint("127.0.0.1:50051")
117            .with_tls_settings(slim_config::tls::server::TlsServerConfig::insecure())
118    }
119
120    fn create_test_client_config() -> ClientConfig {
121        ClientConfig::with_endpoint("http://127.0.0.1:50051")
122            .with_tls_setting(slim_config::tls::client::TlsClientConfig::insecure())
123    }
124
125    #[test]
126    fn test_config_new() {
127        let config = Config::new();
128        assert!(config.servers.is_empty());
129        assert!(config.clients.is_empty());
130    }
131
132    #[test]
133    fn test_config_default() {
134        let config = Config::default();
135        assert!(config.servers.is_empty());
136        assert!(config.clients.is_empty());
137    }
138
139    #[test]
140    fn test_config_with_servers() {
141        let server_config = create_test_server_config();
142        let config = Config::new().with_servers(vec![server_config.clone()]);
143
144        assert_eq!(config.servers.len(), 1);
145        assert_eq!(config.servers[0], server_config);
146        assert!(config.clients.is_empty());
147    }
148
149    #[test]
150    fn test_config_with_clients() {
151        let client_config = create_test_client_config();
152        let config = Config::new().with_clients(vec![client_config.clone()]);
153
154        assert_eq!(config.clients.len(), 1);
155        assert_eq!(config.clients[0], client_config);
156        assert!(config.servers.is_empty());
157    }
158
159    #[test]
160    fn test_config_servers_getter() {
161        let server_config = create_test_server_config();
162        let config = Config::new().with_servers(vec![server_config.clone()]);
163
164        let servers = config.servers();
165        assert_eq!(servers.len(), 1);
166        assert_eq!(servers[0], server_config);
167    }
168
169    #[test]
170    fn test_config_clients_getter() {
171        let client_config = create_test_client_config();
172        let config = Config::new().with_clients(vec![client_config.clone()]);
173
174        let clients = config.clients();
175        assert_eq!(clients.len(), 1);
176        assert_eq!(clients[0], client_config);
177    }
178
179    #[test]
180    fn test_config_chaining() {
181        let server_config = create_test_server_config();
182        let client_config = create_test_client_config();
183
184        let config = Config::new()
185            .with_servers(vec![server_config.clone()])
186            .with_clients(vec![client_config.clone()]);
187
188        assert_eq!(config.servers.len(), 1);
189        assert_eq!(config.clients.len(), 1);
190    }
191
192    #[test]
193    fn test_config_validate_empty() {
194        let config = Config::new();
195        assert!(config.validate().is_ok());
196    }
197
198    #[test]
199    fn test_config_validate_with_valid_servers_and_clients() {
200        let server_config = create_test_server_config();
201        let client_config = create_test_client_config();
202        let config = Config::new()
203            .with_servers(vec![server_config])
204            .with_clients(vec![client_config]);
205
206        assert!(config.validate().is_ok());
207    }
208
209    #[test]
210    fn test_config_clone() {
211        let server_config = create_test_server_config();
212        let client_config = create_test_client_config();
213
214        let config1 = Config::new()
215            .with_servers(vec![server_config])
216            .with_clients(vec![client_config]);
217
218        let config2 = config1.clone();
219
220        assert_eq!(config1.servers, config2.servers);
221        assert_eq!(config1.clients, config2.clients);
222    }
223
224    #[tokio::test]
225    async fn test_config_into_service() {
226        let server_config = create_test_server_config();
227        let client_config = create_test_client_config();
228
229        let config = Config::new()
230            .with_servers(vec![server_config.clone()])
231            .with_clients(vec![client_config]);
232
233        let id = ID::new_with_name(Kind::new("slim").unwrap(), "test-instance").unwrap();
234        let group_name = Some("test-group".to_string());
235        let message_processor = Arc::new(MessageProcessor::new());
236
237        let _control_plane =
238            config.into_service(id, group_name, message_processor, &[server_config]);
239    }
240
241    #[test]
242    fn test_config_debug_trait() {
243        let config = Config::new();
244        let debug_str = format!("{:?}", config);
245        assert!(debug_str.contains("Config"));
246        assert!(debug_str.contains("servers"));
247        assert!(debug_str.contains("clients"));
248    }
249
250    #[test]
251    fn test_config_validate_with_multiple_servers() {
252        let server1 = create_test_server_config();
253        let server2 = ServerConfig::with_endpoint("127.0.0.1:50052")
254            .with_tls_settings(slim_config::tls::server::TlsServerConfig::insecure());
255
256        let config = Config::new().with_servers(vec![server1, server2]);
257        assert!(config.validate().is_ok());
258    }
259
260    #[test]
261    fn test_config_validate_with_multiple_clients() {
262        let client1 = create_test_client_config();
263        let client2 = ClientConfig::with_endpoint("http://127.0.0.1:50052")
264            .with_tls_setting(slim_config::tls::client::TlsClientConfig::insecure());
265
266        let config = Config::new().with_clients(vec![client1, client2]);
267        assert!(config.validate().is_ok());
268    }
269
270    #[test]
271    fn test_config_partial_eq() {
272        let config1 = Config::new();
273        let config2 = Config::new();
274
275        assert_eq!(config1, config2);
276
277        let server_config = create_test_server_config();
278        let config3 = config1.clone().with_servers(vec![server_config]);
279
280        assert_ne!(config1, config3);
281    }
282
283    #[test]
284    fn test_config_builder_pattern_reuse() {
285        let base_config = Config::new();
286
287        let config1 = base_config
288            .clone()
289            .with_servers(vec![create_test_server_config()]);
290        let config2 = base_config
291            .clone()
292            .with_clients(vec![create_test_client_config()]);
293
294        assert!(base_config.servers.is_empty());
295        assert!(base_config.clients.is_empty());
296
297        assert_eq!(config1.servers.len(), 1);
298        assert!(config1.clients.is_empty());
299
300        assert!(config2.servers.is_empty());
301        assert_eq!(config2.clients.len(), 1);
302    }
303
304    #[test]
305    fn test_config_overwrite_behavior() {
306        let server1 = create_test_server_config();
307        let server2 = ServerConfig::with_endpoint("127.0.0.1:50052")
308            .with_tls_settings(slim_config::tls::server::TlsServerConfig::insecure());
309
310        let config = Config::new()
311            .with_servers(vec![server1])
312            .with_servers(vec![server2.clone()]);
313
314        assert_eq!(config.servers.len(), 1);
315        assert_eq!(config.servers[0], server2);
316    }
317}