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