1use std::collections::{HashMap, VecDeque};
5use std::pin::Pin;
6use std::sync::Arc;
7
8use std::time::Duration;
9
10use display_error_chain::ErrorChainExt;
11use slim_config::server::ServerConfig;
12use slim_session::subscription_manager::SubscriptionManager;
13use tokio::sync::mpsc;
14use tokio::task::JoinHandle;
15use tokio_stream::{Stream, StreamExt, wrappers::ReceiverStream};
16use tokio_util::sync::CancellationToken;
17use tonic::{Request, Response, Status};
18use tracing::{debug, error, info};
19
20use crate::api::proto::api::v1::control_message::Payload;
21use crate::api::proto::api::v1::controller_service_server::ControllerServiceServer;
22use crate::api::proto::api::v1::{
23 self, AuthMethod, ConnectionDetails, ConnectionDirection, ConnectionListResponse,
24 ConnectionType, RouteListResponse,
25};
26use crate::api::proto::api::v1::{
27 ConnectionEntry, ControlMessage, RouteEntry,
28 controller_service_client::ControllerServiceClient,
29 controller_service_server::ControllerService as GrpcControllerService,
30};
31use crate::errors::ControllerError;
32use prost_types::Struct;
33use slim_config::client::{
34 ClientConfig, RequiredAuthMethod, ServerConnectionConfig, TransportChannel,
35};
36use slim_config::server::AuthenticationConfig;
37use slim_datapath::api::{
38 MessageType::Link as LinkMessageType, MessageType::Subscribe,
39 MessageType::SubscriptionAck as SubscriptionAckType, MessageType::Unsubscribe,
40 ProtoMessage as DataPlaneMessage,
41};
42use slim_datapath::api::{NameId, ProtoName};
43use slim_datapath::message_processing::MessageProcessor;
44use slim_datapath::messages::utils::SlimHeaderFlags;
45use slim_datapath::tables::{ConnType, SubscriptionTable};
46
47type TxChannel = mpsc::Sender<Result<ControlMessage, Status>>;
48type TxChannels = HashMap<String, TxChannel>;
49
50const MAX_QUEUED_NOTIFICATIONS: usize = 1000;
52
53const SUBSCRIPTION_ACK_TIMEOUT: Duration = Duration::from_secs(30);
55
56use slim_auth::auth_provider::AuthProvider;
57use slim_auth::traits::TokenProvider;
58
59#[derive(Clone)]
61pub struct ControlPlaneSettings {
62 pub id: String,
64 pub domain_name: Option<String>,
66 pub servers: Vec<ServerConfig>,
68 pub clients: Vec<ClientConfig>,
70 pub outbound_clients: Vec<ClientConfig>,
72 pub message_processor: MessageProcessor,
74 pub connection_details: Vec<ConnectionDetails>,
77 pub auth_provider: Option<AuthProvider>,
79}
80
81struct ControllerServiceInternal {
86 id: String,
88
89 domain_name: Option<String>,
91
92 message_processor: MessageProcessor,
94
95 tx_slim: mpsc::Sender<Result<DataPlaneMessage, Status>>,
97
98 tx_channels: parking_lot::RwLock<TxChannels>,
100
101 cancellation_tokens: parking_lot::RwLock<HashMap<String, CancellationToken>>,
103
104 drain_watch: parking_lot::RwLock<Option<drain::Watch>>,
106
107 pending_notifications: Arc<parking_lot::Mutex<VecDeque<ControlMessage>>>,
109
110 subscription_manager: SubscriptionManager,
112
113 connection_details: Vec<ConnectionDetails>,
115
116 route_subscription_ids: parking_lot::Mutex<HashMap<(ProtoName, u64), u64>>,
118
119 link_id_to_conn_id: parking_lot::RwLock<HashMap<String, u64>>,
122
123 stream_handles: parking_lot::Mutex<HashMap<String, tokio::task::JoinHandle<()>>>,
125
126 outbound_clients: Vec<ClientConfig>,
128
129 auth_provider: Option<AuthProvider>,
131}
132
133#[derive(Clone)]
134struct ControllerService {
135 inner: Arc<ControllerServiceInternal>,
137}
138
139pub struct ControlPlane {
141 servers: Vec<ServerConfig>,
143
144 clients: Vec<ClientConfig>,
146
147 drain_signal: parking_lot::RwLock<Option<drain::Signal>>,
149
150 controller: ControllerService,
152
153 rx_slim_option: Option<mpsc::Receiver<Result<DataPlaneMessage, Status>>>,
156}
157
158impl Drop for ControlPlane {
161 fn drop(&mut self) {
162 for (_endpoint, token) in self.controller.inner.cancellation_tokens.write().drain() {
164 token.cancel();
165 }
166 }
167}
168
169pub(crate) fn from_server_config(server_config: &ServerConfig) -> ConnectionDetails {
170 let mut endpoint = server_config.endpoint.clone();
171 let mut external_endpoint = None;
172 let mut spire_socket_path = None;
173 let mut trust_domain = None;
174 let mut remaining_fields = std::collections::BTreeMap::new();
175
176 if let Some(m) = &server_config.metadata {
177 for (k, v) in &m.inner {
178 match k.as_str() {
179 "local_endpoint" => {
180 if let Some(s) = v.as_str()
181 && !s.is_empty()
182 {
183 endpoint = s.to_string();
184 }
185 }
186 "external_endpoint" => {
187 if let Some(s) = v.as_str()
188 && !s.is_empty()
189 {
190 external_endpoint = Some(s.to_string());
191 }
192 }
193 "spire_socket_path" => {
194 if let Some(s) = v.as_str()
195 && !s.is_empty()
196 {
197 spire_socket_path = Some(s.to_string());
198 }
199 }
200 "trust_domain" => {
201 if let Some(s) = v.as_str()
202 && !s.is_empty()
203 {
204 trust_domain = Some(s.to_string());
205 }
206 }
207 _ => {
208 remaining_fields.insert(k.clone(), prost_types::Value::from(v));
209 }
210 }
211 }
212 }
213
214 let tls_required = !server_config.tls_setting.insecure || spire_socket_path.is_some();
215 #[cfg(not(target_family = "windows"))]
217 let auth_is_spire = matches!(server_config.auth, AuthenticationConfig::Spire(_));
218 #[cfg(target_family = "windows")]
219 let auth_is_spire = false;
220 let auth_method = if spire_socket_path.is_some() || trust_domain.is_some() || auth_is_spire {
221 AuthMethod::Spire
222 } else {
223 match &server_config.auth {
224 AuthenticationConfig::Basic(_) => AuthMethod::Basic,
225 AuthenticationConfig::Jwt(_) => AuthMethod::Jwt,
226 _ => AuthMethod::None,
227 }
228 } as i32;
229 let metadata = if remaining_fields.is_empty() {
230 None
231 } else {
232 Some(Struct {
233 fields: remaining_fields,
234 })
235 };
236
237 ConnectionDetails {
238 endpoint,
239 external_endpoint,
240 tls_required,
241 auth_method,
242 spire_trust_domain: trust_domain,
243 metadata,
244 }
245}
246
247impl ControlPlane {
249 pub fn new(config: ControlPlaneSettings) -> Self {
262 let (_, tx_slim, rx_slim) = config
264 .message_processor
265 .register_local_connection(true)
266 .unwrap();
267
268 let (signal, watch) = drain::channel();
269
270 ControlPlane {
271 servers: config.servers,
272 clients: config.clients,
273 controller: ControllerService {
274 inner: Arc::new(ControllerServiceInternal {
275 id: config.id,
276 domain_name: config.domain_name,
277 message_processor: config.message_processor,
278 subscription_manager: SubscriptionManager::new(tx_slim.clone()),
279 tx_slim,
280 tx_channels: parking_lot::RwLock::new(HashMap::new()),
281 cancellation_tokens: parking_lot::RwLock::new(HashMap::new()),
282 drain_watch: parking_lot::RwLock::new(Some(watch)),
283 pending_notifications: Arc::new(parking_lot::Mutex::new(VecDeque::new())),
284 connection_details: config.connection_details,
285 route_subscription_ids: parking_lot::Mutex::new(HashMap::new()),
286 link_id_to_conn_id: parking_lot::RwLock::new(HashMap::new()),
287 stream_handles: parking_lot::Mutex::new(HashMap::new()),
288 outbound_clients: config.outbound_clients,
289 auth_provider: config.auth_provider,
290 }),
291 },
292 drain_signal: parking_lot::RwLock::new(Some(signal)),
293 rx_slim_option: Some(rx_slim),
294 }
295 }
296
297 pub fn with_clients(mut self, clients: Vec<ClientConfig>) -> Self {
299 self.clients = clients;
300 self
301 }
302
303 pub fn with_servers(mut self, servers: Vec<ServerConfig>) -> Self {
305 self.servers = servers;
306 self
307 }
308
309 pub async fn run(&mut self) -> Result<(), ControllerError> {
316 let rx = self
317 .rx_slim_option
318 .take()
319 .ok_or(ControllerError::AlreadyStarted)?;
320
321 let servers = self.servers.clone();
323 let clients = self.clients.clone();
324
325 for server in servers {
327 self.run_server(server).await?;
328 }
329
330 for client in clients {
332 self.run_client(client).await?;
333 }
334
335 self.listen_from_data_plane(rx).await?;
336
337 Ok(())
338 }
339
340 pub async fn deregister(&self) -> Result<(), ControllerError> {
341 let node_id = self.controller.inner.id.clone();
342 let deregister_msg = ControlMessage {
343 message_id: uuid::Uuid::new_v4().to_string(),
344 payload: Some(Payload::DeregisterNodeRequest(v1::DeregisterNodeRequest {
345 node: Some(v1::Node { id: node_id }),
346 })),
347 };
348 let channels: Vec<(String, TxChannel)> = self
349 .controller
350 .inner
351 .tx_channels
352 .read()
353 .iter()
354 .map(|(ep, tx)| (ep.clone(), tx.clone()))
355 .collect();
356 for (endpoint, tx) in channels {
357 if let Err(e) = tx.send(Ok(deregister_msg.clone())).await {
358 error!(%endpoint, error = %e, "failed to send deregister request");
359 }
360 }
361 Ok(())
362 }
363
364 pub async fn shutdown(&self) -> Result<(), ControllerError> {
365 let signal = self
367 .drain_signal
368 .write()
369 .take()
370 .ok_or(ControllerError::AlreadyStopped)?;
371
372 self.controller
374 .inner
375 .cancellation_tokens
376 .write()
377 .drain()
378 .for_each(|(endpoint, token)| {
379 info!(%endpoint, "stopping");
380 token.cancel();
381 });
382
383 self.controller.inner.drain_watch.write().take();
385
386 signal.drain().await;
388
389 Ok(())
390 }
391
392 async fn listen_from_data_plane(
393 &mut self,
394 mut rx: mpsc::Receiver<Result<DataPlaneMessage, Status>>,
395 ) -> Result<(), ControllerError> {
396 let cancellation_token = CancellationToken::new();
397 let cancellation_token_clone = cancellation_token.clone();
398
399 self.controller
400 .inner
401 .cancellation_tokens
402 .write()
403 .insert("DATA_PLANE".to_string(), cancellation_token_clone);
404
405 let clients = self.clients.clone();
406 let controller = self.controller.clone();
407
408 let watch = self.controller.drain_watch()?;
410
411 debug!("Starting data plane listener");
412 tokio::spawn(async move {
413 let mut drain_fut = std::pin::pin!(watch.signaled());
414 loop {
415 tokio::select! {
416 next = rx.recv() => {
417 match next {
418 Some(res) => {
419 match res {
420 Ok(msg) => {
421 debug!("Received message {:?} from data plane, forwarding to control plane", msg);
422 match msg.get_type() {
423 Subscribe(_) => {
424 controller.handle_subscribe_message(msg.get_dst(), &clients).await;
425 }
426 Unsubscribe(_) => {
427 controller.handle_unsubscribe_message(msg.get_dst(), &clients).await;
428 }
429 SubscriptionAckType(_) => {
430 controller.inner.subscription_manager.resolve_ack(msg.get_subscription_ack());
431 }
432 LinkMessageType(link) => {
433 controller.handle_link_received(link, &clients).await;
434 }
435 _ => {
436 debug!("Ignoring unexpected message type from dataplane: {:?}", msg.get_type());
437 }
438 }
439 }
440 Err(e) => {
441 error!(error = %e.chain(), "received error from the data plane");
442 continue;
443 }
444 }
445 }
446 None => {
447 debug!("Data plane receiver channel closed.");
448 break;
449 }
450 }
451 }
452 _ = cancellation_token.cancelled() => {
453 debug!("shutting down stream on cancellation token");
454 break;
455 }
456 _ = &mut drain_fut => {
457 debug!("shutting down stream on drain");
458 break;
459 }
460 }
461 }
462 });
463 Ok(())
464 }
465
466 pub fn stop(&mut self) {
470 info!("stopping controller service");
471
472 for (endpoint, token) in self.controller.inner.cancellation_tokens.write().drain() {
474 info!(%endpoint, "stopping");
475 token.cancel();
476 }
477 }
478
479 async fn run_client(&mut self, client: ClientConfig) -> Result<(), ControllerError> {
483 if self
484 .controller
485 .inner
486 .cancellation_tokens
487 .read()
488 .contains_key(&client.endpoint)
489 {
490 return Err(ControllerError::ClientAlreadyRunning(client.endpoint));
491 }
492
493 let cancellation_token = CancellationToken::new();
494
495 let tx = self
496 .controller
497 .connect(client.clone(), cancellation_token.clone())
498 .await?;
499
500 self.controller
502 .inner
503 .cancellation_tokens
504 .write()
505 .insert(client.endpoint.clone(), cancellation_token);
506
507 self.controller
509 .inner
510 .tx_channels
511 .write()
512 .insert(client.endpoint.clone(), tx);
513
514 Ok(())
516 }
517
518 pub async fn run_server(&mut self, config: ServerConfig) -> Result<(), ControllerError> {
522 if self
524 .controller
525 .inner
526 .cancellation_tokens
527 .read()
528 .contains_key(&config.endpoint)
529 {
530 error!(endpoint = config.endpoint, "server is already running",);
531 return Err(ControllerError::ServerAlreadyRunning(config.endpoint));
532 }
533
534 let token = config
535 .run_grpc_server(
536 &[ControllerServiceServer::new(self.controller.clone())],
537 self.controller.drain_watch()?,
538 )
539 .await?;
540
541 self.controller
543 .inner
544 .cancellation_tokens
545 .write()
546 .insert(config.endpoint.clone(), token.clone());
547
548 info!(%config.endpoint, "started controlplane server");
549
550 Ok(())
551 }
552}
553
554impl ControllerService {
555 fn resolve_connection_by_link_id(&self, link_id: &str) -> Result<Option<u64>, String> {
556 let cached = self.inner.link_id_to_conn_id.read().get(link_id).copied();
557 if let Some(conn_id) = cached {
558 if self
559 .inner
560 .message_processor
561 .connection_table()
562 .get(conn_id)
563 .is_some_and(|conn| conn.link_id().as_deref() == Some(link_id))
564 {
565 return Ok(Some(conn_id));
566 }
567 self.inner.link_id_to_conn_id.write().remove(link_id);
568 }
569
570 let mut resolved: Option<u64> = None;
571 self.inner
572 .message_processor
573 .connection_table()
574 .for_each(|id, conn| {
575 if conn.link_id().as_deref() == Some(link_id) && resolved.is_none() {
576 resolved = Some(id);
577 }
578 });
579
580 if let Some(conn_id) = resolved {
581 self.inner
582 .link_id_to_conn_id
583 .write()
584 .insert(link_id.to_string(), conn_id);
585 }
586
587 Ok(resolved)
588 }
589
590 fn disconnect_connection_by_link_id(&self, link_id: &str) -> Result<(), String> {
591 if link_id.trim().is_empty() {
592 return Err("link_id cannot be empty".to_string());
593 }
594
595 let conn_id = match self.resolve_connection_by_link_id(link_id)? {
596 Some(id) => id,
597 None => {
598 return Err(format!("Connection with link_id {} not found", link_id));
599 }
600 };
601
602 if let Err(e) = self.inner.message_processor.disconnect(conn_id) {
603 info!(
605 link_id = %link_id,
606 conn_id,
607 error = %e,
608 "Disconnect returned an error; continuing delete flow"
609 );
610 }
611
612 self.inner.link_id_to_conn_id.write().remove(link_id);
613 self.inner
614 .route_subscription_ids
615 .lock()
616 .retain(|(_name, cid), _| *cid != conn_id);
617
618 info!(link_id = %link_id, conn_id, "Successfully deleted connection by link_id");
619 Ok(())
620 }
621
622 fn resolve_route_connection(&self, route: &v1::Route) -> Result<Option<u64>, String> {
623 if let Some(link_id) = &route.link_id {
624 let trimmed = link_id.trim();
625 if !trimmed.is_empty() {
626 return self.resolve_connection_by_link_id(trimmed);
627 }
628 }
629
630 Ok(None)
631 }
632
633 async fn diff_connections(
634 &self,
635 desired_connections: &[v1::Connection],
636 ) -> Vec<v1::ConnectionAck> {
637 let mut connections_status = Vec::new();
638
639 let desired_link_ids: std::collections::HashSet<String> = desired_connections
640 .iter()
641 .map(|c| c.link_id.clone())
642 .collect();
643
644 let mut live_outgoing_link_ids: Vec<String> = Vec::new();
645 self.inner
646 .message_processor
647 .connection_table()
648 .for_each(|_id, conn| {
649 if conn.is_outgoing()
653 && conn.connection_type() == slim_datapath::tables::ConnType::Remote
654 && let Some(lid) = conn.link_id()
655 && !lid.is_empty()
656 {
657 live_outgoing_link_ids.push(lid);
658 }
659 });
660
661 for link_id in &live_outgoing_link_ids {
662 if desired_link_ids.contains(link_id) {
663 continue;
664 }
665 info!(link_id = %link_id, "desired state: removing connection");
666 let mut success = true;
667 let mut error_msg = String::new();
668 if let Err(err) = self.disconnect_connection_by_link_id(link_id) {
669 success = false;
670 error_msg = err;
671 }
672 connections_status.push(v1::ConnectionAck {
673 link_id: link_id.clone(),
674 success,
675 error_msg,
676 });
677 }
678
679 for conn in desired_connections {
680 let link_id = &conn.link_id;
681 if link_id.is_empty() {
682 continue;
683 }
684
685 let already_exists = match self.resolve_connection_by_link_id(link_id) {
686 Ok(Some(_)) => true,
687 Ok(None) => false,
688 Err(err) => {
689 connections_status.push(v1::ConnectionAck {
690 link_id: link_id.clone(),
691 success: false,
692 error_msg: err,
693 });
694 continue;
695 }
696 };
697
698 if already_exists {
699 connections_status.push(v1::ConnectionAck {
700 link_id: link_id.clone(),
701 success: true,
702 error_msg: String::new(),
703 });
704 continue;
705 }
706
707 info!(?conn, "desired state: creating connection");
708 let mut success = true;
709 let mut error_msg = String::new();
710
711 match serde_json::from_str::<ServerConnectionConfig>(&conn.config_data) {
712 Err(e) => {
713 success = false;
714 error_msg = format!("Failed to parse config: {}", e);
715 }
716 Ok(server_config) => {
717 let mut client_config = self
718 .inner
719 .outbound_clients
720 .iter()
721 .find(|c| c.endpoint == server_config.endpoint)
722 .cloned()
723 .unwrap_or_default();
724 match client_config.merge_server_requirements(&server_config) {
725 Err(err) => {
726 success = false;
727 error_msg = format!(
728 "Failed to merge connection config to client config: {}",
729 err
730 );
731 }
732 Ok(()) => {
733 if matches!(
734 server_config.auth_method,
735 RequiredAuthMethod::Basic | RequiredAuthMethod::Jwt
738 ) && !self
739 .inner
740 .outbound_clients
741 .iter()
742 .any(|c| c.endpoint == server_config.endpoint)
743 {
744 success = false;
745 error_msg = format!(
746 "no local credentials configured for {}",
747 server_config.endpoint
748 );
749 }
750 if success {
751 client_config.link_id = link_id.clone();
752 client_config.connection_type = ConnType::Remote;
753 match self
754 .inner
755 .message_processor
756 .connect(client_config, None, None)
757 .await
758 {
759 Err(e) => {
760 success = false;
761 error_msg = format!("Connection failed: {}", e);
762 }
763 Ok(conn_id) => {
764 self.inner
765 .link_id_to_conn_id
766 .write()
767 .insert(link_id.clone(), conn_id.1);
768 info!(
769 link_id = %link_id,
770 "Successfully created connection"
771 );
772 }
773 }
774 }
775 }
776 }
777 }
778 }
779
780 connections_status.push(v1::ConnectionAck {
781 link_id: link_id.clone(),
782 success,
783 error_msg,
784 });
785 }
786
787 connections_status
788 }
789
790 fn resolve_desired_routes<'a>(
791 &self,
792 desired_routes: &'a [v1::Route],
793 ) -> (HashMap<(ProtoName, u64), &'a v1::Route>, Vec<v1::RouteAck>) {
794 type SubKey = (ProtoName, u64);
795 let mut desired_subs: HashMap<SubKey, &v1::Route> = HashMap::new();
796 let mut failures: Vec<v1::RouteAck> = Vec::new();
797
798 for sub in desired_routes {
799 match self.resolve_route_connection(sub) {
800 Ok(Some(conn_id)) => {
801 let name = sub.name.clone().unwrap();
802 desired_subs.insert((name, conn_id), sub);
803 }
804 Ok(None) => {
805 failures.push(v1::RouteAck {
806 route: Some(sub.clone()),
807 success: false,
808 error_msg: "connection not found".to_string(),
809 });
810 }
811 Err(err) => {
812 failures.push(v1::RouteAck {
813 route: Some(sub.clone()),
814 success: false,
815 error_msg: err,
816 });
817 }
818 }
819 }
820
821 (desired_subs, failures)
822 }
823
824 async fn delete_stale_subscriptions(
825 &self,
826 desired_subs: &HashMap<(ProtoName, u64), &v1::Route>,
827 ) -> Vec<v1::RouteAck> {
828 let active_subs: Vec<((ProtoName, u64), u64)> = self
829 .inner
830 .route_subscription_ids
831 .lock()
832 .iter()
833 .map(|((name, conn_id), sub_id)| ((name.clone(), *conn_id), *sub_id))
834 .collect();
835
836 let stale: Vec<_> = active_subs
837 .into_iter()
838 .filter(|((name, conn_id), _)| !desired_subs.contains_key(&(name.clone(), *conn_id)))
839 .collect();
840
841 let futs = stale.iter().map(|((name, conn_id), sub_id)| {
842 let name = name.clone();
843 let conn_id = *conn_id;
844 let sub_id = *sub_id;
845 async move {
846 let conn_alive = self
847 .inner
848 .message_processor
849 .connection_table()
850 .get(conn_id)
851 .is_some();
852
853 let (success, error_msg) = if conn_alive {
854 let source = name.clone().with_id(0);
855 let unsub_msg = DataPlaneMessage::builder()
856 .source(source)
857 .destination(name.clone())
858 .identity("")
859 .flags(SlimHeaderFlags::default().with_recv_from(conn_id))
860 .build_unsubscribe()
861 .unwrap();
862
863 match self
864 .send_unsubscribe_message_with_ack(unsub_msg, sub_id)
865 .await
866 {
867 Ok(()) => (true, String::new()),
868 Err(err) => (false, format!("Failed to unsubscribe: {}", err)),
869 }
870 } else {
871 (true, String::new())
872 };
873
874 (name, conn_id, success, error_msg)
875 }
876 });
877
878 let results = futures::future::join_all(futs).await;
879
880 let mut routes_status = Vec::with_capacity(results.len());
881 for (name, conn_id, success, error_msg) in results {
882 if success {
883 self.inner
884 .route_subscription_ids
885 .lock()
886 .remove(&(name.clone(), conn_id));
887 }
888
889 routes_status.push(v1::RouteAck {
890 route: Some(v1::Route {
891 name: Some(name.clone()),
892 link_id: None,
893 direction: None,
894 }),
895 success,
896 error_msg,
897 });
898 }
899
900 routes_status
901 }
902
903 async fn create_new_subscriptions(
904 &self,
905 desired_subs: &HashMap<(ProtoName, u64), &v1::Route>,
906 ) -> Vec<v1::RouteAck> {
907 let mut routes_status = Vec::new();
908
909 let to_create: Vec<((ProtoName, u64), v1::Route)> = desired_subs
910 .iter()
911 .filter(|((name, conn_id), _)| {
912 !self
913 .inner
914 .route_subscription_ids
915 .lock()
916 .contains_key(&(name.clone(), *conn_id))
917 })
918 .map(|((name, conn_id), sub)| ((name.clone(), *conn_id), (*sub).clone()))
919 .collect();
920
921 for ((name, conn_id), sub) in desired_subs {
923 let dominated = to_create
924 .iter()
925 .any(|((n, c), _)| n == name && *c == *conn_id);
926 if !dominated {
927 routes_status.push(v1::RouteAck {
928 route: Some((*sub).clone()),
929 success: true,
930 error_msg: String::new(),
931 });
932 }
933 }
934
935 let futs = to_create.iter().map(|((name, conn_id), sub)| {
936 let name = name.clone();
937 let conn_id = *conn_id;
938 let sub = sub.clone();
939 async move {
940 let source = name.clone().with_id(0);
941 let sub_msg = DataPlaneMessage::builder()
942 .source(source)
943 .destination(name.clone())
944 .identity("")
945 .flags(SlimHeaderFlags::default().with_recv_from(conn_id))
946 .build_subscribe()
947 .unwrap();
948
949 let result = self.send_subscribe_message_with_ack(sub_msg).await;
950 (name, conn_id, sub, result)
951 }
952 });
953
954 let results = futures::future::join_all(futs).await;
955
956 for (name, conn_id, sub, result) in results {
957 let (success, error_msg) = match result {
958 Ok(subscription_id) => {
959 self.inner
960 .route_subscription_ids
961 .lock()
962 .insert((name, conn_id), subscription_id);
963 info!(?sub, "desired state: created route");
964 (true, String::new())
965 }
966 Err(err) => (false, format!("Failed to subscribe: {}", err)),
967 };
968
969 routes_status.push(v1::RouteAck {
970 route: Some(sub),
971 success,
972 error_msg,
973 });
974 }
975
976 routes_status
977 }
978
979 async fn handle_new_control_message(
981 &self,
982 msg: ControlMessage,
983 tx: &mpsc::Sender<Result<ControlMessage, Status>>,
984 ) -> Result<(), ControllerError> {
985 match msg.payload {
986 Some(ref payload) => {
987 match payload {
988 Payload::ConfigCommand(config) => {
989 let (connections_status, routes_status) = if config.reconcile {
990 let connections_status =
991 self.diff_connections(&config.connections_to_create).await;
992 let (desired_subs, mut routes_status) =
993 self.resolve_desired_routes(&config.routes_to_set);
994 routes_status
995 .extend(self.delete_stale_subscriptions(&desired_subs).await);
996 routes_status
997 .extend(self.create_new_subscriptions(&desired_subs).await);
998 (connections_status, routes_status)
999 } else {
1000 let mut connections_status = Vec::new();
1001 let mut routes_status = Vec::new();
1002
1003 for link_id in &config.connections_to_delete {
1005 info!(link_id = %link_id, "received a connection to delete");
1006 let mut connection_success = true;
1007 let mut connection_error_msg = String::new();
1008
1009 if let Err(err) = self.disconnect_connection_by_link_id(link_id) {
1010 connection_success = false;
1011 connection_error_msg = err;
1012 }
1013
1014 connections_status.push(v1::ConnectionAck {
1015 link_id: link_id.clone(),
1016 success: connection_success,
1017 error_msg: connection_error_msg,
1018 });
1019 }
1020
1021 for conn in &config.connections_to_create {
1023 info!(?conn, "received a connection to create");
1024 let mut connection_success = true;
1025 let mut connection_error_msg = String::new();
1026
1027 match serde_json::from_str::<ClientConfig>(&conn.config_data) {
1028 Err(e) => {
1029 connection_success = false;
1030 connection_error_msg =
1031 format!("Failed to parse config: {}", e);
1032 }
1033 Ok(client_config) => {
1034 let client_endpoint = &client_config.endpoint;
1035 let requested_link_id =
1036 if client_config.link_id.trim().is_empty() {
1037 String::new()
1038 } else {
1039 client_config.link_id.clone()
1040 };
1041 let mut existing_conn_for_link_id = false;
1042
1043 if !requested_link_id.is_empty() {
1044 match self
1045 .resolve_connection_by_link_id(&requested_link_id)
1046 {
1047 Err(err) => {
1048 connection_success = false;
1049 connection_error_msg = err;
1050 }
1051 Ok(Some(conn_id)) => {
1052 existing_conn_for_link_id = true;
1053 self.inner
1054 .link_id_to_conn_id
1055 .write()
1056 .insert(requested_link_id.clone(), conn_id);
1057 info!(
1058 link_id = %requested_link_id,
1059 conn_id,
1060 "Connection already exists for link_id"
1061 );
1062 }
1063 Ok(None) => {}
1064 }
1065 }
1066
1067 if connection_success && !existing_conn_for_link_id {
1068 match self
1069 .inner
1070 .message_processor
1071 .connect(client_config.clone(), None, None)
1072 .await
1073 {
1074 Err(e) => {
1075 connection_success = false;
1076 connection_error_msg =
1077 format!("Connection failed: {}", e);
1078 }
1079 Ok(conn_id) => {
1080 if !requested_link_id.is_empty() {
1081 self.inner
1082 .link_id_to_conn_id
1083 .write()
1084 .insert(
1085 requested_link_id.clone(),
1086 conn_id.1,
1087 );
1088 }
1089 info!(
1090 endpoint = %client_endpoint, "Successfully created connection",
1091 );
1092 }
1093 }
1094 }
1095 }
1096 }
1097
1098 connections_status.push(v1::ConnectionAck {
1100 link_id: conn.link_id.clone(),
1101 success: connection_success,
1102 error_msg: connection_error_msg,
1103 });
1104 }
1105
1106 for route in &config.routes_to_set {
1108 let mut route_success = true;
1109 let mut route_error_msg = String::new();
1110
1111 let conn = self.resolve_route_connection(route);
1112
1113 if let Ok(Some(conn)) = conn {
1114 let name = route.name.clone().unwrap();
1115 let source = name.clone().with_id(NameId::NULL_COMPONENT);
1116
1117 let msg = DataPlaneMessage::builder()
1118 .source(source.clone())
1119 .destination(name.clone())
1120 .identity("")
1121 .flags(SlimHeaderFlags::default().with_recv_from(conn))
1122 .build_subscribe()
1123 .unwrap();
1124
1125 match self.send_subscribe_message_with_ack(msg).await {
1126 Ok(subscription_id) => {
1127 self.inner
1129 .route_subscription_ids
1130 .lock()
1131 .insert((name.clone(), conn), subscription_id);
1132 info!(?route, "Successfully created route");
1133 }
1134 Err(err) => {
1135 route_success = false;
1136 route_error_msg =
1137 format!("Failed to subscribe: {}", err);
1138 }
1139 }
1140 } else {
1141 route_success = false;
1142 route_error_msg = match conn {
1143 Ok(None) => {
1144 format!(
1145 "Connection with link_id {} not found",
1146 route.link_id.as_deref().unwrap_or("<none>")
1147 )
1148 }
1149 Err(err) => err,
1150 _ => "unknown connection lookup error".to_string(),
1151 };
1152 }
1153
1154 routes_status.push(v1::RouteAck {
1156 route: Some(route.clone()),
1157 success: route_success,
1158 error_msg: route_error_msg,
1159 });
1160 }
1161
1162 for route in &config.routes_to_delete {
1164 let mut route_success = true;
1165 let mut route_error_msg = String::new();
1166
1167 let conn = self.resolve_route_connection(route);
1168
1169 if let Ok(Some(conn)) = conn {
1170 let name = route.name.clone().unwrap();
1171 let source = name.clone().with_id(NameId::NULL_COMPONENT);
1172
1173 let msg = DataPlaneMessage::builder()
1174 .source(source.clone())
1175 .destination(name.clone())
1176 .identity("")
1177 .flags(SlimHeaderFlags::default().with_recv_from(conn))
1178 .build_unsubscribe()
1179 .unwrap();
1180
1181 let sub_id = self
1182 .inner
1183 .route_subscription_ids
1184 .lock()
1185 .remove(&(name.clone(), conn));
1186 let unsubscribe_result = match sub_id {
1187 Some(subscription_id) => {
1188 self.send_unsubscribe_message_with_ack(
1189 msg,
1190 subscription_id,
1191 )
1192 .await
1193 }
1194 None => {
1195 let (ack_id, ack_rx) =
1201 self.inner.subscription_manager.register_ack();
1202 let mut fresh_msg = msg;
1203 fresh_msg.set_subscription_id(ack_id);
1204 if let Err(e) =
1205 self.send_control_message(fresh_msg).await
1206 {
1207 self.inner.subscription_manager.cancel_ack(ack_id);
1208 Err(format!("datapath send error: {}", e.chain()))
1209 } else {
1210 match tokio::time::timeout(
1211 SUBSCRIPTION_ACK_TIMEOUT,
1212 ack_rx,
1213 )
1214 .await
1215 {
1216 Ok(Ok(Ok(()))) => Ok(()),
1217 Ok(Ok(Err(err))) => Err(err.to_string()),
1218 Ok(Err(_)) => {
1219 Err("subscription ack channel closed"
1220 .to_string())
1221 }
1222 Err(_) => {
1223 self.inner
1224 .subscription_manager
1225 .cancel_ack(ack_id);
1226 Err("subscription ack timed out"
1227 .to_string())
1228 }
1229 }
1230 }
1231 }
1232 };
1233 if let Err(err) = unsubscribe_result {
1234 route_success = false;
1235 route_error_msg = format!("Failed to unsubscribe: {}", err);
1236 } else {
1237 info!(?route, "Successfully deleted route");
1238 }
1239 } else {
1240 route_success = false;
1241 route_error_msg = match conn {
1242 Ok(None) => {
1243 format!(
1244 "Connection with link_id {} not found",
1245 route.link_id.as_deref().unwrap_or("<none>")
1246 )
1247 }
1248 Err(err) => err,
1249 _ => "unknown connection lookup error".to_string(),
1250 };
1251 }
1252
1253 routes_status.push(v1::RouteAck {
1255 route: Some(route.clone()),
1256 success: route_success,
1257 error_msg: route_error_msg,
1258 });
1259 }
1260
1261 (connections_status, routes_status)
1262 };
1263
1264 let config_ack = v1::ConfigurationCommandAck {
1265 original_message_id: msg.message_id.clone(),
1266 connections_status,
1267 routes_status,
1268 };
1269
1270 let reply = ControlMessage {
1271 message_id: uuid::Uuid::new_v4().to_string(),
1272 payload: Some(Payload::ConfigCommandAck(config_ack)),
1273 };
1274
1275 if let Err(e) = tx.send(Ok(reply)).await {
1276 error!(error = %e.chain(), "failed to send ConfigurationCommandAck");
1277 }
1278
1279 info!(
1280 connections = %config.connections_to_create.len(),
1281 connections_to_delete = %config.connections_to_delete.len(),
1282 routes_to_set = %config.routes_to_set.len(),
1283 routes_to_del = %config.routes_to_delete.len(),
1284 "Processed ConfigurationCommand"
1285 );
1286 }
1287 Payload::RouteListRequest(_) => {
1288 const CHUNK_SIZE: usize = 100;
1289
1290 let conn_table = self.inner.message_processor.connection_table();
1291 let mut entries = Vec::new();
1292
1293 self.inner.message_processor.subscription_table().for_each(
1294 |name, id, local, remote, peer, edge| {
1295 let mut entry = RouteEntry {
1296 name: Some(name.clone().with_id(id)),
1297 ..Default::default()
1298 };
1299
1300 for &cid in local {
1301 entry.connections.push(ConnectionEntry {
1302 id: cid,
1303 connection_type: ConnectionType::Local as i32,
1304 config_data: "{}".to_string(),
1305 link_id: None,
1306 direction: ConnectionDirection::Outgoing as i32,
1307 peer_node_id: None,
1308 });
1309 }
1310
1311 let conn_slices = [
1312 (remote, ConnectionType::Remote),
1313 (peer, ConnectionType::Peer),
1314 (edge, ConnectionType::Edge),
1315 ];
1316 for (conns, ct) in conn_slices {
1317 for &cid in conns {
1318 if let Some(conn) = conn_table.get(cid) {
1319 entry.connections.push(ConnectionEntry {
1320 id: cid,
1321 connection_type: ct as i32,
1322 config_data: conn
1323 .config_data()
1324 .and_then(|d| serde_json::to_string(d).ok())
1325 .unwrap_or_else(|| "{}".to_string()),
1326 link_id: conn.link_id(),
1327 direction: if conn.is_outgoing() {
1328 ConnectionDirection::Outgoing as i32
1329 } else {
1330 ConnectionDirection::Incoming as i32
1331 },
1332 peer_node_id: conn
1333 .peer_node_id()
1334 .map(str::to_string),
1335 });
1336 } else {
1337 error!(%cid, ?ct, "no connection entry for id");
1338 }
1339 }
1340 }
1341 entries.push(entry);
1342 },
1343 );
1344
1345 let chunks: Vec<_> = entries.chunks(CHUNK_SIZE).collect();
1346 if chunks.is_empty() {
1347 let resp = ControlMessage {
1348 message_id: uuid::Uuid::new_v4().to_string(),
1349 payload: Some(Payload::RouteListResponse(RouteListResponse {
1350 original_message_id: msg.message_id.clone(),
1351 entries: vec![],
1352 done: true,
1353 })),
1354 };
1355 if let Err(e) = tx.send(Ok(resp)).await {
1356 error!(error = %e.chain(), "failed to send route list response");
1357 }
1358 } else {
1359 let n = chunks.len();
1360 for (i, chunk) in chunks.into_iter().enumerate() {
1361 let resp = ControlMessage {
1362 message_id: uuid::Uuid::new_v4().to_string(),
1363 payload: Some(Payload::RouteListResponse(RouteListResponse {
1364 original_message_id: msg.message_id.clone(),
1365 entries: chunk.to_vec(),
1366 done: i + 1 == n,
1367 })),
1368 };
1369 if let Err(e) = tx.send(Ok(resp)).await {
1370 error!(error = %e.chain(), "failed to send route batch");
1371 break;
1372 }
1373 }
1374 }
1375 }
1376 Payload::ConnectionListRequest(_) => {
1377 let mut all_entries = Vec::new();
1378 self.inner
1379 .message_processor
1380 .connection_table()
1381 .for_each(|id, conn| {
1382 let ct = match conn.connection_type() {
1383 slim_datapath::tables::ConnType::Local => ConnectionType::Local,
1384 slim_datapath::tables::ConnType::Remote => {
1385 ConnectionType::Remote
1386 }
1387 slim_datapath::tables::ConnType::Peer => ConnectionType::Peer,
1388 slim_datapath::tables::ConnType::Edge => ConnectionType::Edge,
1389 };
1390 all_entries.push(ConnectionEntry {
1391 id,
1392 connection_type: ct as i32,
1393 config_data: conn
1394 .config_data()
1395 .and_then(|d| serde_json::to_string(d).ok())
1396 .unwrap_or_else(|| "{}".to_string()),
1397 link_id: conn.link_id(),
1398 direction: if conn.is_outgoing() {
1399 ConnectionDirection::Outgoing as i32
1400 } else {
1401 ConnectionDirection::Incoming as i32
1402 },
1403 peer_node_id: conn.peer_node_id().map(str::to_string),
1404 });
1405 });
1406
1407 const CHUNK_SIZE: usize = 100;
1408 let chunks: Vec<_> = all_entries.chunks(CHUNK_SIZE).collect();
1409 if chunks.is_empty() {
1410 let resp = ControlMessage {
1411 message_id: uuid::Uuid::new_v4().to_string(),
1412 payload: Some(Payload::ConnectionListResponse(
1413 ConnectionListResponse {
1414 original_message_id: msg.message_id.clone(),
1415 entries: vec![],
1416 done: true,
1417 },
1418 )),
1419 };
1420 if let Err(e) = tx.send(Ok(resp)).await {
1421 error!(error = %e.chain(), "failed to send connection list response");
1422 }
1423 } else {
1424 let n = chunks.len();
1425 for (i, chunk) in chunks.into_iter().enumerate() {
1426 let resp = ControlMessage {
1427 message_id: uuid::Uuid::new_v4().to_string(),
1428 payload: Some(Payload::ConnectionListResponse(
1429 ConnectionListResponse {
1430 original_message_id: msg.message_id.clone(),
1431 entries: chunk.to_vec(),
1432 done: i + 1 == n,
1433 },
1434 )),
1435 };
1436 if let Err(e) = tx.send(Ok(resp)).await {
1437 error!(error = %e.chain(), "failed to send connection list batch");
1438 break;
1439 }
1440 }
1441 }
1442 }
1443 Payload::RegisterNodeRequest(_) => {
1444 error!("received a register node request");
1445 }
1446 Payload::DeregisterNodeRequest(_) => {
1447 error!("received a deregister node request");
1448 }
1449 _ => {
1450 debug!("received unsupported message type from control - ignoring");
1452 }
1453 }
1454 }
1455 None => {
1456 error!(
1457 message_id = %msg.message_id,
1458 "received control message with no payload",
1459 );
1460 }
1461 }
1462
1463 Ok(())
1464 }
1465
1466 async fn handle_subscribe_message(&self, dst: ProtoName, clients: &[ClientConfig]) {
1467 let mut sub_vec = vec![];
1468
1469 let cmd = v1::Route {
1470 name: Some(dst),
1471 link_id: None,
1472 direction: None,
1473 };
1474
1475 debug!(
1476 "handle_subscribe_message: sending route_to_set to control plane: {:?}",
1477 cmd
1478 );
1479 sub_vec.push(cmd);
1480
1481 let ctrl = ControlMessage {
1482 message_id: uuid::Uuid::new_v4().to_string(),
1483 payload: Some(Payload::ConfigCommand(v1::ConfigurationCommand {
1484 connections_to_create: vec![],
1485 connections_to_delete: vec![],
1486 routes_to_set: sub_vec,
1487 routes_to_delete: vec![],
1488 reconcile: false,
1489 connections_received: vec![],
1490 })),
1491 };
1492
1493 return self.send_or_queue_notification(ctrl, clients).await;
1494 }
1495
1496 async fn handle_link_received(
1499 &self,
1500 link: &slim_datapath::api::ProtoLink,
1501 clients: &[ClientConfig],
1502 ) {
1503 use slim_datapath::api::ProtoLinkType;
1504
1505 let link_id = match &link.link_type {
1506 Some(ProtoLinkType::LinkNegotiation(payload)) => &payload.link_id,
1507 _ => {
1508 debug!("handle_link_received: ignoring link message without negotiation payload");
1509 return;
1510 }
1511 };
1512
1513 if link_id.is_empty() {
1514 debug!("handle_link_received: ignoring link message with empty link_id");
1515 return;
1516 }
1517
1518 debug!(
1519 "handle_link_received: notifying control-plane of new link_id: {}",
1520 link_id
1521 );
1522
1523 let ctrl = ControlMessage {
1524 message_id: uuid::Uuid::new_v4().to_string(),
1525 payload: Some(Payload::ConfigCommand(v1::ConfigurationCommand {
1526 connections_to_create: vec![],
1527 connections_to_delete: vec![],
1528 routes_to_set: vec![],
1529 routes_to_delete: vec![],
1530 reconcile: false,
1531 connections_received: vec![v1::ConnectionEntry {
1532 id: 0,
1533 connection_type: ConnectionType::Remote as i32,
1534 config_data: String::new(),
1535 link_id: Some(link_id.clone()),
1536 direction: ConnectionDirection::Incoming as i32,
1537 peer_node_id: None,
1538 }],
1539 })),
1540 };
1541
1542 self.send_or_queue_notification(ctrl, clients).await;
1543 }
1544
1545 async fn handle_unsubscribe_message(&self, dst: ProtoName, clients: &[ClientConfig]) {
1546 self.inner
1551 .route_subscription_ids
1552 .lock()
1553 .retain(|(name, _), _| *name != dst);
1554
1555 let mut unsub_vec = vec![];
1556
1557 let cmd = v1::Route {
1558 name: Some(dst),
1559 link_id: None,
1560 direction: None,
1561 };
1562
1563 debug!(
1564 "handle_unsubscribe_message: sending route_to_delete to control plane: {:?}",
1565 cmd
1566 );
1567 unsub_vec.push(cmd);
1568
1569 let ctrl = ControlMessage {
1570 message_id: uuid::Uuid::new_v4().to_string(),
1571 payload: Some(Payload::ConfigCommand(v1::ConfigurationCommand {
1572 connections_to_create: vec![],
1573 connections_to_delete: vec![],
1574 routes_to_set: vec![],
1575 routes_to_delete: unsub_vec,
1576 reconcile: false,
1577 connections_received: vec![],
1578 })),
1579 };
1580
1581 return self.send_or_queue_notification(ctrl, clients).await;
1582 }
1583
1584 async fn send_subscribe_message_with_ack(
1586 &self,
1587 mut msg: DataPlaneMessage,
1588 ) -> Result<u64, String> {
1589 let (ack_id, ack_rx) = self.inner.subscription_manager.register_ack();
1590 msg.set_subscription_id(ack_id);
1591
1592 if let Err(e) = self.send_control_message(msg).await {
1593 self.inner.subscription_manager.cancel_ack(ack_id);
1594 return Err(format!("datapath send error: {}", e.chain()));
1595 }
1596
1597 match tokio::time::timeout(SUBSCRIPTION_ACK_TIMEOUT, ack_rx).await {
1598 Ok(Ok(Ok(()))) => Ok(ack_id),
1599 Ok(Ok(Err(err))) => Err(err.to_string()),
1600 Ok(Err(_)) => Err("subscription ack channel closed".to_string()),
1601 Err(_) => {
1602 self.inner.subscription_manager.cancel_ack(ack_id);
1603 Err("subscription ack timed out".to_string())
1604 }
1605 }
1606 }
1607
1608 async fn send_unsubscribe_message_with_ack(
1610 &self,
1611 mut msg: DataPlaneMessage,
1612 subscription_id: u64,
1613 ) -> Result<(), String> {
1614 let ack_rx = self
1615 .inner
1616 .subscription_manager
1617 .register_ack_with_id(subscription_id);
1618 msg.set_subscription_id(subscription_id);
1619
1620 if let Err(e) = self.send_control_message(msg).await {
1621 self.inner.subscription_manager.cancel_ack(subscription_id);
1622 return Err(format!("datapath send error: {}", e.chain()));
1623 }
1624
1625 match tokio::time::timeout(SUBSCRIPTION_ACK_TIMEOUT, ack_rx).await {
1626 Ok(Ok(Ok(()))) => Ok(()),
1627 Ok(Ok(Err(err))) => Err(err.to_string()),
1628 Ok(Err(_)) => Err("subscription ack channel closed".to_string()),
1629 Err(_) => {
1630 self.inner.subscription_manager.cancel_ack(subscription_id);
1631 Err("subscription ack timed out".to_string())
1632 }
1633 }
1634 }
1635
1636 async fn send_control_message(&self, msg: DataPlaneMessage) -> Result<(), ControllerError> {
1638 self.inner.tx_slim.send(Ok(msg)).await.map_err(|e| {
1639 error!(error = %e.chain(), "error sending message into datapath");
1640 ControllerError::Datapath(slim_datapath::errors::DataPathError::ConnectionError)
1641 })
1642 }
1643
1644 async fn send_or_queue_notification(&self, ctrl_msg: ControlMessage, clients: &[ClientConfig]) {
1651 let mut sent = false;
1652
1653 for c in clients {
1654 let tx = match self.inner.tx_channels.read().get(&c.endpoint) {
1655 Some(tx) => tx.clone(),
1656 None => continue,
1657 };
1658
1659 match tx.try_send(Ok(ctrl_msg.clone())) {
1660 Ok(()) => {
1661 sent = true;
1662 }
1663 Err(mpsc::error::TrySendError::Full(_)) => {
1664 debug!(
1665 endpoint = %c.endpoint,
1666 "channel full, queuing notification instead of blocking"
1667 );
1668 }
1669 Err(mpsc::error::TrySendError::Closed(_)) => {
1670 debug!(
1671 endpoint = %c.endpoint,
1672 "channel closed, queuing notification"
1673 );
1674 }
1675 }
1676 }
1677
1678 if !sent {
1679 let mut queue = self.inner.pending_notifications.lock();
1680 if queue.len() >= MAX_QUEUED_NOTIFICATIONS {
1681 queue.pop_front();
1682 debug!("queue full, removed oldest notification");
1683 }
1684 queue.push_back(ctrl_msg);
1685 }
1686 }
1687
1688 fn drain_watch(&self) -> Result<drain::Watch, ControllerError> {
1690 self.inner
1691 .drain_watch
1692 .read()
1693 .clone()
1694 .ok_or(ControllerError::AlreadyStopped)
1695 }
1696
1697 async fn send_queued_notifications(
1699 &self,
1700 tx: &mpsc::Sender<Result<ControlMessage, Status>>,
1701 endpoint: &str,
1702 ) {
1703 let notifications = {
1704 let mut queue = self.inner.pending_notifications.lock();
1705 if queue.is_empty() {
1706 return;
1707 }
1708 queue.drain(..).collect::<Vec<_>>()
1709 };
1710
1711 if notifications.is_empty() {
1712 return;
1713 }
1714
1715 debug!(
1716 "sending {} queued subscription notifications to {}",
1717 notifications.len(),
1718 endpoint
1719 );
1720
1721 let mut failed_notifications = Vec::new();
1722 for notification in notifications {
1723 if let Err(e) = tx.send(Ok(notification)).await {
1724 error!(
1725 error = %e.chain(),
1726 %endpoint,
1727 "failed to send queued notification to control plane",
1728 );
1729
1730 failed_notifications.push(e.0.unwrap());
1732 }
1733 }
1734
1735 if !failed_notifications.is_empty() {
1737 self.inner
1738 .pending_notifications
1739 .lock()
1740 .extend(failed_notifications);
1741 }
1742 }
1743
1744 async fn replay_local_subscriptions(&self, clients: &[ClientConfig]) {
1747 let mut routes: Vec<v1::Route> = Vec::new();
1748 self.inner.message_processor.subscription_table().for_each(
1749 |name, id, local, _remote, _peer, edge| {
1750 if local.is_empty() && edge.is_empty() {
1751 return;
1752 }
1753 routes.push(v1::Route {
1754 name: Some(name.clone().with_id(id)),
1755 link_id: None,
1756 direction: None,
1757 });
1758 },
1759 );
1760
1761 if routes.is_empty() {
1762 return;
1763 }
1764
1765 info!(
1766 count = routes.len(),
1767 "replaying local subscriptions to control plane"
1768 );
1769
1770 let ctrl = ControlMessage {
1771 message_id: uuid::Uuid::new_v4().to_string(),
1772 payload: Some(Payload::ConfigCommand(v1::ConfigurationCommand {
1773 connections_to_create: vec![],
1774 connections_to_delete: vec![],
1775 routes_to_set: routes,
1776 routes_to_delete: vec![],
1777 reconcile: false,
1778 connections_received: vec![],
1779 })),
1780 };
1781
1782 self.send_or_queue_notification(ctrl, clients).await;
1783 }
1784
1785 fn process_control_message_stream(
1787 &self,
1788 config: Option<ClientConfig>,
1789 mut stream: impl Stream<Item = Result<ControlMessage, Status>> + Unpin + Send + 'static,
1790 tx: mpsc::Sender<Result<ControlMessage, Status>>,
1791 cancellation_token: CancellationToken,
1792 ) -> Result<JoinHandle<()>, ControllerError> {
1793 let this = self.clone();
1794 let watch = self.drain_watch()?;
1795
1796 let handle = tokio::spawn(async move {
1797 let endpoint = config
1799 .as_ref()
1800 .map(|c| c.endpoint.clone())
1801 .unwrap_or_else(|| "unknown".to_string());
1802 info!(%endpoint, "connected to control plane");
1803
1804 let mut retry_connect = false;
1805
1806 let mut active_connections = Vec::new();
1807 this.inner
1808 .message_processor
1809 .connection_table()
1810 .for_each(|id, conn| {
1811 active_connections.push(v1::ConnectionEntry {
1812 id,
1813 connection_type: v1::ConnectionType::Remote as i32,
1814 config_data: match conn.config_data() {
1815 Some(data) => {
1816 serde_json::to_string(data).unwrap_or_else(|_| "{}".to_string())
1817 }
1818 None => "{}".to_string(),
1819 },
1820 link_id: conn.link_id(),
1821 direction: if conn.is_outgoing() {
1822 v1::ConnectionDirection::Outgoing as i32
1823 } else {
1824 v1::ConnectionDirection::Incoming as i32
1825 },
1826 peer_node_id: conn.peer_node_id().map(str::to_string),
1827 });
1828 });
1829
1830 let active_routes = {
1831 let conn_id_to_link_id: HashMap<u64, String> = this
1832 .inner
1833 .link_id_to_conn_id
1834 .read()
1835 .iter()
1836 .map(|(lid, cid)| (*cid, lid.clone()))
1837 .collect();
1838 this.inner
1839 .route_subscription_ids
1840 .lock()
1841 .iter()
1842 .map(|((name, conn_id), _sub_id)| v1::Route {
1843 name: Some(name.clone()),
1844 link_id: conn_id_to_link_id.get(conn_id).cloned(),
1845 direction: None,
1846 })
1847 .collect::<Vec<_>>()
1848 };
1849
1850 let max_attempts = 10;
1851 let mut credentials = None;
1852 let mut i = 0;
1853 while i < max_attempts && credentials.is_none() {
1854 credentials = match &this.inner.auth_provider {
1855 Some(provider) => match provider.get_token() {
1856 Ok(token) => Some(token),
1857 Err(e) => {
1858 info!(error = %e, attempt = i + 1, max_attempts, "failed to get auth credentials, will retry");
1859 tokio::time::sleep(Duration::from_secs(2)).await;
1860 None
1861 }
1862 },
1863 None => Some(String::new()),
1864 };
1865 i += 1;
1866 }
1867
1868 if credentials.is_none() {
1869 error!(
1870 attempts = max_attempts,
1871 "failed to obtain auth credentials, aborting registration"
1872 );
1873 return;
1874 }
1875
1876 let register_request = ControlMessage {
1877 message_id: uuid::Uuid::new_v4().to_string(),
1878 payload: Some(Payload::RegisterNodeRequest(v1::RegisterNodeRequest {
1879 node_id: this.inner.id.clone(),
1880 domain_name: this.inner.domain_name.clone(),
1881 connection_details: this.inner.connection_details.clone(),
1882 connections: active_connections,
1883 routes: active_routes,
1884 credentials: credentials.unwrap(),
1885 })),
1886 };
1887
1888 if config.is_some()
1890 && let Err(e) = tx.send(Ok(register_request)).await
1891 {
1892 error!(error = %e.chain(), "failed to send register request");
1893 return;
1894 }
1895
1896 this.send_queued_notifications(&tx, &endpoint).await;
1899
1900 if config.is_some() {
1902 let registration_result = tokio::time::timeout(
1903 Duration::from_secs(10),
1904 async {
1905 loop {
1906 tokio::select! {
1907 next = stream.next() => {
1908 match next {
1909 Some(Ok(msg)) => {
1910 if let Some(Payload::RegisterNodeResponse(resp)) = msg.payload {
1911 if resp.success {
1912 info!(
1913 connections = resp.connections.len(),
1914 routes = resp.routes.len(),
1915 "registration acknowledged by control plane"
1916 );
1917 return Ok(resp);
1918 } else {
1919 return Err("registration rejected by control plane".to_string());
1920 }
1921 }
1922 }
1923 Some(Err(_)) => return Err("stream error waiting for registration response".to_string()),
1924 None => return Err("stream closed before registration response".to_string()),
1925 }
1926 }
1927 _ = cancellation_token.cancelled() => {
1928 return Err("cancelled while waiting for registration response".to_string());
1929 }
1930 }
1931 }
1932 }
1933 ).await;
1934
1935 match registration_result {
1936 Ok(Ok(resp)) => {
1937 if !resp.connections.is_empty() || !resp.routes.is_empty() {
1939 let init_cmd = ControlMessage {
1940 message_id: uuid::Uuid::new_v4().to_string(),
1941 payload: Some(Payload::ConfigCommand(v1::ConfigurationCommand {
1942 connections_to_create: resp.connections,
1943 routes_to_set: resp.routes,
1944 routes_to_delete: vec![],
1945 connections_to_delete: vec![],
1946 reconcile: true,
1947 connections_received: vec![],
1948 })),
1949 };
1950 if let Err(e) = this.handle_new_control_message(init_cmd, &tx).await {
1951 error!(error = %e.chain(), "failed to apply initial state from registration");
1952 }
1953 }
1954 }
1955 Ok(Err(e)) => {
1956 error!(%e, "registration handshake failed");
1957 return;
1958 }
1959 Err(_) => {
1960 error!("registration handshake timed out");
1961 return;
1962 }
1963 }
1964 }
1965
1966 if let Some(ref cfg) = config {
1968 this.replay_local_subscriptions(std::slice::from_ref(cfg))
1969 .await;
1970 }
1971
1972 let mut drain_fut = std::pin::pin!(watch.clone().signaled());
1973
1974 loop {
1975 tokio::select! {
1976 next = stream.next() => {
1977 match next {
1978 Some(Ok(msg)) => {
1979 if let Err(e) = this.handle_new_control_message(msg, &tx).await {
1980 error!(error = %e.chain(), "error processing incoming control message");
1981 }
1982 }
1983 Some(Err(e)) => {
1984 if let Some(io_err) = Self::match_for_io_error(&e) {
1985 if io_err.kind() == std::io::ErrorKind::BrokenPipe {
1986 info!("connection closed by peer");
1987 } else {
1988 error!(
1990 error = %e.chain(),
1991 io_error_kind = ?io_err.kind(),
1992 "IO error receiving control messages"
1993 );
1994 }
1995 } else {
1996 error!(error = %e.chain(), "error receiving control messages");
1998 }
1999
2000 retry_connect = true;
2001 break;
2002 }
2003 None => {
2004 debug!("end of stream");
2005 retry_connect = true;
2006 break;
2007 }
2008 }
2009 }
2010 _ = cancellation_token.cancelled() => {
2011 debug!("shutting down stream on cancellation token");
2012 break;
2013 }
2014 _ = &mut drain_fut => {
2015 debug!("shutting down stream on drain");
2016 break;
2017 }
2018 }
2019 }
2020
2021 info!(%endpoint, "control plane stream closed");
2022
2023 if retry_connect && let Some(config) = config {
2024 info!(%config.endpoint, "retrying connection to control plane");
2025 this.connect(config.clone(), cancellation_token)
2026 .await
2027 .map_or_else(
2028 |e| {
2029 error!(error = %e.chain(), "failed to reconnect to control plane");
2030 },
2031 |tx| {
2032 info!(%config.endpoint, "reconnected to control plane");
2033
2034 this.inner
2035 .tx_channels
2036 .write()
2037 .insert(config.endpoint.clone(), tx);
2038 },
2039 )
2040 }
2041 });
2042
2043 Ok(handle)
2044 }
2045
2046 async fn connect(
2050 &self,
2051 config: ClientConfig,
2052 cancellation_token: CancellationToken,
2053 ) -> Result<mpsc::Sender<Result<ControlMessage, Status>>, ControllerError> {
2054 info!(%config.endpoint, "connecting to control plane");
2055
2056 let mut remote_conn_ids: Vec<u64> = Vec::new();
2060 self.inner
2061 .message_processor
2062 .connection_table()
2063 .for_each(|id, conn| {
2064 if conn.is_outgoing() && matches!(conn.connection_type(), ConnType::Remote) {
2065 remote_conn_ids.push(id);
2066 }
2067 });
2068 for conn_id in &remote_conn_ids {
2069 if let Err(e) = self.inner.message_processor.disconnect(*conn_id) {
2070 debug!(conn_id, error = %e.chain(), "failed to disconnect remote connection");
2071 }
2072 }
2073 if !remote_conn_ids.is_empty() {
2074 info!(
2075 count = remote_conn_ids.len(),
2076 "disconnected remote connections for clean restart"
2077 );
2078 }
2079
2080 self.inner.route_subscription_ids.lock().clear();
2084 self.inner.link_id_to_conn_id.write().clear();
2085
2086 let watch = self.drain_watch()?;
2088
2089 let connect_fut = async {
2090 let channel = match config.to_channel().await? {
2091 TransportChannel::Grpc(c) => c,
2092 TransportChannel::Websocket(_) => {
2093 return Err(ControllerError::ConfigError(
2094 slim_config::errors::ConfigError::GrpcChannelUnsupportedTransport,
2095 ));
2096 }
2097 };
2098
2099 let mut client = ControllerServiceClient::new(channel.clone());
2100 let (tx, rx) = mpsc::channel::<Result<ControlMessage, Status>>(128);
2101 let out_stream = ReceiverStream::new(rx).filter_map(|res| match res {
2102 Ok(msg) => Some(msg),
2103 Err(e) => {
2104 error!(error = %e, "dropping outbound control message due to error");
2105 None
2106 }
2107 });
2108 let stream = client
2109 .open_control_channel(Request::new(out_stream))
2110 .await?;
2111 Ok((tx, stream))
2112 };
2113
2114 let (tx, stream) = tokio::select! {
2115 result = connect_fut => { result? }
2116 _ = cancellation_token.cancelled() => {
2117 debug!("connection cancelled during setup");
2118 return Err(ControllerError::Canceled);
2119 }
2120 _ = watch.signaled() => {
2121 debug!("drain signal received during connection setup");
2122 return Err(ControllerError::Canceled);
2123 }
2124 };
2125
2126 let endpoint_key = config.endpoint.clone();
2128 let handle = self.process_control_message_stream(
2129 Some(config),
2130 stream.into_inner(),
2131 tx.clone(),
2132 cancellation_token.clone(),
2133 )?;
2134 self.inner
2135 .stream_handles
2136 .lock()
2137 .insert(endpoint_key, handle);
2138
2139 Ok(tx)
2141 }
2142
2143 fn match_for_io_error(err_status: &Status) -> Option<&std::io::Error> {
2144 let mut err: &(dyn std::error::Error + 'static) = err_status;
2145
2146 loop {
2147 if let Some(io_err) = err.downcast_ref::<std::io::Error>() {
2148 return Some(io_err);
2149 }
2150
2151 if let Some(h2_err) = err.downcast_ref::<h2::Error>()
2154 && let Some(io_err) = h2_err.get_io()
2155 {
2156 return Some(io_err);
2157 }
2158
2159 err = err.source()?;
2160 }
2161 }
2162}
2163
2164#[tonic::async_trait]
2165impl GrpcControllerService for ControllerService {
2166 type OpenControlChannelStream =
2167 Pin<Box<dyn Stream<Item = Result<ControlMessage, Status>> + Send + 'static>>;
2168
2169 async fn open_control_channel(
2170 &self,
2171 request: Request<tonic::Streaming<ControlMessage>>,
2172 ) -> Result<Response<Self::OpenControlChannelStream>, Status> {
2173 let remote_endpoint = request
2175 .remote_addr()
2176 .map(|addr| addr.to_string())
2177 .unwrap_or_else(|| "unknown".to_string());
2178
2179 let stream = request.into_inner();
2180 let (tx, rx) = mpsc::channel::<Result<ControlMessage, Status>>(128);
2181
2182 let cancellation_token = CancellationToken::new();
2183
2184 let handle = self
2186 .process_control_message_stream(None, stream, tx.clone(), cancellation_token.clone())
2187 .map_err(|e| {
2188 error!(error = %e.chain(), "error processing control message stream");
2189 Status::unavailable("failed to process control message stream")
2190 })?;
2191 self.inner
2192 .stream_handles
2193 .lock()
2194 .insert(remote_endpoint.clone(), handle);
2195
2196 self.inner
2197 .tx_channels
2198 .write()
2199 .insert(remote_endpoint.clone(), tx);
2200
2201 if let Some(old_token) = self
2202 .inner
2203 .cancellation_tokens
2204 .write()
2205 .insert(remote_endpoint.clone(), cancellation_token)
2206 {
2207 old_token.cancel();
2208 }
2209
2210 let out_stream = ReceiverStream::new(rx);
2211 Ok(Response::new(
2212 Box::pin(out_stream) as Self::OpenControlChannelStream
2213 ))
2214 }
2215}
2216
2217#[cfg(test)]
2218mod tests {
2219 use super::*;
2220 use tracing_test::traced_test;
2221
2222 async fn setup_control_planes(
2223 server_endpoint: &str,
2224 server_name: &str,
2225 client_name: &str,
2226 ) -> (ControlPlane, ControlPlane, ClientConfig) {
2227 let server_config = ServerConfig::with_endpoint(server_endpoint)
2228 .with_tls_settings(slim_config::tls::server::TlsServerConfig::insecure());
2229 let client_config = ClientConfig::with_endpoint(&format!("http://{}", server_endpoint))
2230 .with_tls_setting(slim_config::tls::client::TlsClientConfig::insecure());
2231
2232 let message_processor_server = MessageProcessor::new();
2233 let message_processor_client = MessageProcessor::new();
2234
2235 let control_plane_server = ControlPlane::new(ControlPlaneSettings {
2236 id: server_name.to_string(),
2237 domain_name: None,
2238 servers: vec![server_config.clone()],
2239 clients: vec![],
2240 outbound_clients: vec![],
2241 message_processor: message_processor_server,
2242 connection_details: vec![from_server_config(&server_config)],
2243 auth_provider: None,
2244 });
2245
2246 let control_plane_client = ControlPlane::new(ControlPlaneSettings {
2247 id: client_name.to_string(),
2248 domain_name: None,
2249 servers: vec![],
2250 clients: vec![client_config.clone()],
2251 outbound_clients: vec![],
2252 message_processor: message_processor_client,
2253 connection_details: vec![],
2254 auth_provider: None,
2255 });
2256
2257 (control_plane_server, control_plane_client, client_config)
2258 }
2259
2260 #[tokio::test]
2261 #[traced_test]
2262 async fn test_end_to_end() {
2263 let (mut control_plane_server, mut control_plane_client, _client_cfg) =
2264 setup_control_planes(
2265 "127.0.0.1:50051",
2266 "test-server-instance",
2267 "test-client-instance",
2268 )
2269 .await;
2270
2271 control_plane_server.run().await.unwrap();
2272 tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
2273 control_plane_client.run().await.unwrap();
2274 tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
2275
2276 assert!(logs_contain("received a register node request"));
2277 }
2278
2279 #[tokio::test]
2280 #[traced_test]
2281 async fn test_subscription_notification_queue_drain() {
2282 let (mut control_plane_server, mut control_plane_client, client_config) =
2284 setup_control_planes(
2285 "127.0.0.1:50061",
2286 "queue-drain-server",
2287 "queue-drain-client",
2288 )
2289 .await;
2290
2291 let controller = control_plane_client.controller.clone();
2292 assert_eq!(controller.inner.pending_notifications.lock().len(), 0);
2293
2294 const N: usize = 5;
2295 for i in 0..N {
2296 let ctrl_msg = ControlMessage {
2297 message_id: uuid::Uuid::new_v4().to_string(),
2298 payload: Some(Payload::ConfigCommand(v1::ConfigurationCommand {
2299 connections_to_create: vec![],
2300 connections_to_delete: vec![],
2301 routes_to_set: vec![v1::Route {
2302 name: Some(
2303 ProtoName::from_strings(["queued", "sub", &format!("name-{i}")])
2304 .with_id(i as u128),
2305 ),
2306 link_id: None,
2307 direction: None,
2308 }],
2309 routes_to_delete: vec![],
2310 reconcile: false,
2311 connections_received: vec![],
2312 })),
2313 };
2314 controller
2315 .send_or_queue_notification(ctrl_msg, std::slice::from_ref(&client_config))
2316 .await;
2317 }
2318 assert_eq!(controller.inner.pending_notifications.lock().len(), N);
2319
2320 control_plane_server.run().await.expect("server run failed");
2321 tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
2322 control_plane_client.run().await.expect("client run failed");
2323 tokio::time::sleep(tokio::time::Duration::from_millis(200)).await;
2324
2325 assert_eq!(controller.inner.pending_notifications.lock().len(), 0);
2326 assert!(
2327 logs_contain(&format!("sending {} queued subscription notifications", N)),
2328 "Expected log about sending queued subscription notifications"
2329 );
2330
2331 drop(controller);
2332 drop(control_plane_server);
2333 drop(control_plane_client);
2334 }
2335
2336 #[tokio::test]
2337 #[traced_test]
2338 async fn test_delete_connection_by_link_id_success_ack() {
2339 let (mut control_plane_server, mut control_plane_client, _client_cfg) =
2340 setup_control_planes(
2341 "127.0.0.1:50081",
2342 "delete-linkid-server",
2343 "delete-linkid-client",
2344 )
2345 .await;
2346
2347 control_plane_server.run().await.unwrap();
2348 tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
2349 control_plane_client.run().await.unwrap();
2350 tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
2351
2352 let controller = control_plane_client.controller.clone();
2353 let link_id = "test-delete-link-id".to_string();
2354
2355 let (tx, _rx) = tokio::sync::mpsc::channel(16);
2357 let conn = slim_datapath::connection::Connection::new(
2358 slim_datapath::tables::ConnType::Remote,
2359 slim_datapath::connection::Channel::Server(tx),
2360 )
2361 .with_negotiation(&link_id, "1.0.0");
2362 controller
2363 .inner
2364 .message_processor
2365 .forwarder()
2366 .on_connection_established(conn, None)
2367 .unwrap();
2368
2369 let ctrl_msg = ControlMessage {
2370 message_id: uuid::Uuid::new_v4().to_string(),
2371 payload: Some(Payload::ConfigCommand(v1::ConfigurationCommand {
2372 connections_to_create: vec![],
2373 connections_to_delete: vec![link_id.clone()],
2374 routes_to_set: vec![],
2375 routes_to_delete: vec![],
2376 reconcile: false,
2377 connections_received: vec![],
2378 })),
2379 };
2380 let (tx, mut rx) = mpsc::channel(1);
2381 controller
2382 .handle_new_control_message(ctrl_msg, &tx)
2383 .await
2384 .expect("config command must be handled");
2385
2386 let ack_msg = rx
2387 .recv()
2388 .await
2389 .expect("expected ack message")
2390 .expect("ack should be ok");
2391 let ack = match ack_msg.payload {
2392 Some(Payload::ConfigCommandAck(ack)) => ack,
2393 _ => panic!("expected ConfigCommandAck payload"),
2394 };
2395 assert_eq!(ack.connections_status.len(), 1);
2396 assert_eq!(ack.connections_status[0].link_id, link_id);
2397 assert!(ack.connections_status[0].success);
2398 }
2399
2400 #[tokio::test]
2401 #[traced_test]
2402 async fn test_delete_connection_by_link_id_unknown_fails_ack() {
2403 let (control_plane_server, control_plane_client, _client_cfg) = setup_control_planes(
2404 "127.0.0.1:50082",
2405 "delete-linkid-server-unknown",
2406 "delete-linkid-client-unknown",
2407 )
2408 .await;
2409
2410 let controller = control_plane_client.controller.clone();
2411 let ctrl_msg = ControlMessage {
2412 message_id: uuid::Uuid::new_v4().to_string(),
2413 payload: Some(Payload::ConfigCommand(v1::ConfigurationCommand {
2414 connections_to_create: vec![],
2415 connections_to_delete: vec!["unknown-link-id".to_string()],
2416 routes_to_set: vec![],
2417 routes_to_delete: vec![],
2418 reconcile: false,
2419 connections_received: vec![],
2420 })),
2421 };
2422 let (tx, mut rx) = mpsc::channel(1);
2423 controller
2424 .handle_new_control_message(ctrl_msg, &tx)
2425 .await
2426 .expect("config command must be handled");
2427
2428 let ack_msg = rx
2429 .recv()
2430 .await
2431 .expect("expected ack message")
2432 .expect("ack should be ok");
2433 let ack = match ack_msg.payload {
2434 Some(Payload::ConfigCommandAck(ack)) => ack,
2435 _ => panic!("expected ConfigCommandAck payload"),
2436 };
2437 assert_eq!(ack.connections_status.len(), 1);
2438 assert_eq!(ack.connections_status[0].link_id, "unknown-link-id");
2439 assert!(!ack.connections_status[0].success);
2440 assert!(ack.connections_status[0].error_msg.contains("not found"));
2441
2442 drop(control_plane_server);
2443 }
2444
2445 #[tokio::test]
2446 #[traced_test]
2447 async fn test_create_connection_with_existing_link_id_reuses_connection_ack() {
2448 let (mut control_plane_server, mut control_plane_client, _client_cfg) =
2449 setup_control_planes(
2450 "127.0.0.1:50083",
2451 "create-linkid-server",
2452 "create-linkid-client",
2453 )
2454 .await;
2455
2456 control_plane_server.run().await.unwrap();
2457 tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
2458 control_plane_client.run().await.unwrap();
2459 tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
2460
2461 let controller = control_plane_client.controller.clone();
2462 let link_id = "test-create-link-id".to_string();
2463
2464 let (tx, _rx) = tokio::sync::mpsc::channel(16);
2466 let conn = slim_datapath::connection::Connection::new(
2467 slim_datapath::tables::ConnType::Remote,
2468 slim_datapath::connection::Channel::Server(tx),
2469 )
2470 .with_negotiation(&link_id, "1.0.0");
2471 controller
2472 .inner
2473 .message_processor
2474 .forwarder()
2475 .on_connection_established(conn, None)
2476 .unwrap();
2477
2478 let endpoint = "http://127.0.0.1:59999";
2479 let connection_config = serde_json::json!({
2480 "endpoint": endpoint,
2481 "link_id": link_id
2482 })
2483 .to_string();
2484
2485 let ctrl_msg = ControlMessage {
2486 message_id: uuid::Uuid::new_v4().to_string(),
2487 payload: Some(Payload::ConfigCommand(v1::ConfigurationCommand {
2488 connections_to_create: vec![v1::Connection {
2489 link_id: "reuse-existing-link".to_string(),
2490 config_data: connection_config,
2491 }],
2492 connections_to_delete: vec![],
2493 routes_to_set: vec![],
2494 routes_to_delete: vec![],
2495 reconcile: false,
2496 connections_received: vec![],
2497 })),
2498 };
2499
2500 let (tx, mut rx) = mpsc::channel(1);
2501 controller
2502 .handle_new_control_message(ctrl_msg, &tx)
2503 .await
2504 .expect("config command must be handled");
2505
2506 let ack_msg = rx
2507 .recv()
2508 .await
2509 .expect("expected ack message")
2510 .expect("ack should be ok");
2511 let ack = match ack_msg.payload {
2512 Some(Payload::ConfigCommandAck(ack)) => ack,
2513 _ => panic!("expected ConfigCommandAck payload"),
2514 };
2515 assert_eq!(ack.connections_status.len(), 1);
2516 assert_eq!(ack.connections_status[0].link_id, "reuse-existing-link");
2517 assert!(ack.connections_status[0].success);
2518
2519 assert!(
2520 controller
2521 .inner
2522 .link_id_to_conn_id
2523 .read()
2524 .contains_key(&link_id),
2525 "expected link_id to be mapped to reused connection id"
2526 );
2527 }
2528
2529 #[tokio::test]
2530 #[traced_test]
2531 async fn test_subscription_set_unknown_link_id_fails_ack() {
2532 let (control_plane_server, control_plane_client, _client_cfg) = setup_control_planes(
2533 "127.0.0.1:50084",
2534 "sub-linkid-server-unknown",
2535 "sub-linkid-client-unknown",
2536 )
2537 .await;
2538
2539 let controller = control_plane_client.controller.clone();
2540 let ctrl_msg = ControlMessage {
2541 message_id: uuid::Uuid::new_v4().to_string(),
2542 payload: Some(Payload::ConfigCommand(v1::ConfigurationCommand {
2543 connections_to_create: vec![],
2544 connections_to_delete: vec![],
2545 routes_to_set: vec![v1::Route {
2546 name: Some(ProtoName::from_strings(["org", "ns", "agent"]).with_id(1u128)),
2547 link_id: Some("missing-link-id".to_string()),
2548 direction: None,
2549 }],
2550 routes_to_delete: vec![],
2551 reconcile: false,
2552 connections_received: vec![],
2553 })),
2554 };
2555 let (tx, mut rx) = mpsc::channel(1);
2556 controller
2557 .handle_new_control_message(ctrl_msg, &tx)
2558 .await
2559 .expect("config command must be handled");
2560
2561 let ack_msg = rx
2562 .recv()
2563 .await
2564 .expect("expected ack message")
2565 .expect("ack should be ok");
2566 let ack = match ack_msg.payload {
2567 Some(Payload::ConfigCommandAck(ack)) => ack,
2568 _ => panic!("expected ConfigCommandAck payload"),
2569 };
2570
2571 assert_eq!(ack.routes_status.len(), 1);
2572 assert!(!ack.routes_status[0].success);
2573 assert!(
2574 ack.routes_status[0]
2575 .error_msg
2576 .contains("Connection with link_id missing-link-id not found")
2577 );
2578
2579 drop(control_plane_server);
2580 }
2581
2582 #[tokio::test]
2583 #[traced_test]
2584 async fn test_create_connection_invalid_config_fails_ack() {
2585 let (_control_plane_server, control_plane_client, _client_cfg) = setup_control_planes(
2586 "127.0.0.1:50085",
2587 "create-invalid-config-server",
2588 "create-invalid-config-client",
2589 )
2590 .await;
2591
2592 let controller = control_plane_client.controller.clone();
2593 let ctrl_msg = ControlMessage {
2594 message_id: uuid::Uuid::new_v4().to_string(),
2595 payload: Some(Payload::ConfigCommand(v1::ConfigurationCommand {
2596 connections_to_create: vec![v1::Connection {
2597 link_id: "invalid-config-conn".to_string(),
2598 config_data: "{invalid-json".to_string(),
2599 }],
2600 connections_to_delete: vec![],
2601 routes_to_set: vec![],
2602 routes_to_delete: vec![],
2603 reconcile: false,
2604 connections_received: vec![],
2605 })),
2606 };
2607 let (tx, mut rx) = mpsc::channel(1);
2608 controller
2609 .handle_new_control_message(ctrl_msg, &tx)
2610 .await
2611 .expect("config command must be handled");
2612
2613 let ack_msg = rx
2614 .recv()
2615 .await
2616 .expect("expected ack message")
2617 .expect("ack should be ok");
2618 let ack = match ack_msg.payload {
2619 Some(Payload::ConfigCommandAck(ack)) => ack,
2620 _ => panic!("expected ConfigCommandAck payload"),
2621 };
2622 assert_eq!(ack.connections_status.len(), 1);
2623 assert!(!ack.connections_status[0].success);
2624 assert!(
2625 ack.connections_status[0]
2626 .error_msg
2627 .contains("Failed to parse config")
2628 );
2629 }
2630
2631 #[tokio::test]
2632 #[traced_test]
2633 async fn test_subscription_delete_unknown_link_id_fails_ack() {
2634 let (control_plane_server, control_plane_client, _client_cfg) = setup_control_planes(
2635 "127.0.0.1:50086",
2636 "sub-del-linkid-server-unknown",
2637 "sub-del-linkid-client-unknown",
2638 )
2639 .await;
2640
2641 let controller = control_plane_client.controller.clone();
2642 let ctrl_msg = ControlMessage {
2643 message_id: uuid::Uuid::new_v4().to_string(),
2644 payload: Some(Payload::ConfigCommand(v1::ConfigurationCommand {
2645 connections_to_create: vec![],
2646 connections_to_delete: vec![],
2647 routes_to_set: vec![],
2648 routes_to_delete: vec![v1::Route {
2649 name: Some(ProtoName::from_strings(["org", "ns", "agent"]).with_id(1u128)),
2650 link_id: Some("missing-link-id-delete".to_string()),
2651 direction: None,
2652 }],
2653 reconcile: false,
2654 connections_received: vec![],
2655 })),
2656 };
2657 let (tx, mut rx) = mpsc::channel(1);
2658 controller
2659 .handle_new_control_message(ctrl_msg, &tx)
2660 .await
2661 .expect("config command must be handled");
2662
2663 let ack_msg = rx
2664 .recv()
2665 .await
2666 .expect("expected ack message")
2667 .expect("ack should be ok");
2668 let ack = match ack_msg.payload {
2669 Some(Payload::ConfigCommandAck(ack)) => ack,
2670 _ => panic!("expected ConfigCommandAck payload"),
2671 };
2672
2673 assert_eq!(ack.routes_status.len(), 1);
2674 assert!(!ack.routes_status[0].success);
2675 assert!(
2676 ack.routes_status[0]
2677 .error_msg
2678 .contains("Connection with link_id missing-link-id-delete not found")
2679 );
2680
2681 drop(control_plane_server);
2682 }
2683
2684 #[tokio::test]
2685 #[traced_test]
2686 async fn test_shutdown_drains_resources() {
2687 let (mut control_plane_server, mut control_plane_client, _client_cfg) =
2689 setup_control_planes(
2690 "127.0.0.1:50071",
2691 "shutdown-server-instance",
2692 "shutdown-client-instance",
2693 )
2694 .await;
2695
2696 control_plane_server
2698 .run()
2699 .await
2700 .expect("server should start");
2701 tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
2702 control_plane_client
2703 .run()
2704 .await
2705 .expect("client should start");
2706 tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
2707
2708 let server_tokens_before = control_plane_server
2710 .controller
2711 .inner
2712 .cancellation_tokens
2713 .read()
2714 .len();
2715 assert!(
2716 server_tokens_before > 0,
2717 "expected server to have active cancellation tokens before shutdown"
2718 );
2719
2720 let client_tokens_before = control_plane_client
2721 .controller
2722 .inner
2723 .cancellation_tokens
2724 .read()
2725 .len();
2726 assert!(
2727 client_tokens_before > 0,
2728 "expected client to have active cancellation tokens before shutdown"
2729 );
2730
2731 control_plane_client
2733 .shutdown()
2734 .await
2735 .expect("client shutdown ok");
2736 control_plane_server
2737 .shutdown()
2738 .await
2739 .expect("server shutdown ok");
2740
2741 let server_tokens_after = control_plane_server
2743 .controller
2744 .inner
2745 .cancellation_tokens
2746 .read()
2747 .len();
2748 assert_eq!(
2749 server_tokens_after, 0,
2750 "expected server cancellation tokens to be drained after shutdown"
2751 );
2752
2753 let client_tokens_after = control_plane_client
2754 .controller
2755 .inner
2756 .cancellation_tokens
2757 .read()
2758 .len();
2759 assert_eq!(
2760 client_tokens_after, 0,
2761 "expected client cancellation tokens to be drained after shutdown"
2762 );
2763
2764 assert!(
2766 control_plane_server.shutdown().await.is_err(),
2767 "second shutdown on server should return an error"
2768 );
2769 assert!(
2770 control_plane_client.shutdown().await.is_err(),
2771 "second shutdown on client should return an error"
2772 );
2773 }
2774
2775 #[tokio::test]
2776 #[traced_test]
2777 async fn test_shutdown_without_run() {
2778 let (control_plane_server, mut _control_plane_client, _client_cfg) = setup_control_planes(
2780 "127.0.0.1:50072",
2781 "shutdown-no-run-server",
2782 "shutdown-no-run-client",
2783 )
2784 .await;
2785
2786 assert_eq!(
2788 control_plane_server
2789 .controller
2790 .inner
2791 .cancellation_tokens
2792 .read()
2793 .len(),
2794 0,
2795 "expected zero cancellation tokens before shutdown when not run"
2796 );
2797
2798 control_plane_server
2800 .shutdown()
2801 .await
2802 .expect("shutdown without prior run should succeed");
2803
2804 assert_eq!(
2806 control_plane_server
2807 .controller
2808 .inner
2809 .cancellation_tokens
2810 .read()
2811 .len(),
2812 0,
2813 "expected zero cancellation tokens after shutdown when not run"
2814 );
2815
2816 assert!(
2818 control_plane_server.shutdown().await.is_err(),
2819 "second shutdown should error due to missing drain signal"
2820 );
2821 }
2822
2823 fn make_reconcile_msg(link_id: &str, server_config: &ServerConnectionConfig) -> ControlMessage {
2824 ControlMessage {
2825 message_id: uuid::Uuid::new_v4().to_string(),
2826 payload: Some(Payload::ConfigCommand(v1::ConfigurationCommand {
2827 connections_to_create: vec![v1::Connection {
2828 link_id: link_id.to_string(),
2829 config_data: serde_json::to_string(server_config).unwrap(),
2830 }],
2831 connections_to_delete: vec![],
2832 routes_to_set: vec![],
2833 routes_to_delete: vec![],
2834 reconcile: true,
2835 connections_received: vec![],
2836 })),
2837 }
2838 }
2839
2840 fn make_controller(outbound_clients: Vec<ClientConfig>) -> ControllerService {
2841 ControlPlane::new(ControlPlaneSettings {
2842 id: "test-node".to_string(),
2843 domain_name: None,
2844 servers: vec![],
2845 clients: vec![],
2846 outbound_clients,
2847 message_processor: MessageProcessor::new(),
2848 connection_details: vec![],
2849 auth_provider: None,
2850 })
2851 .controller
2852 .clone()
2853 }
2854
2855 #[tokio::test]
2856 async fn test_reconcile_basic_auth_missing_credentials_fails_ack() {
2857 let controller = make_controller(vec![]);
2858 let server_config = ServerConnectionConfig {
2859 endpoint: "http://target:8080".to_string(),
2860 tls_required: false,
2861 auth_method: RequiredAuthMethod::Basic,
2862 ..Default::default()
2863 };
2864 let (tx, mut rx) = mpsc::channel(1);
2865 controller
2866 .handle_new_control_message(make_reconcile_msg("link-1", &server_config), &tx)
2867 .await
2868 .unwrap();
2869 let ack = match rx.recv().await.unwrap().unwrap().payload {
2870 Some(Payload::ConfigCommandAck(a)) => a,
2871 _ => panic!("expected ConfigCommandAck"),
2872 };
2873 assert!(!ack.connections_status[0].success);
2874 assert!(ack.connections_status[0].error_msg.contains("target:8080"));
2875 }
2876
2877 #[tokio::test]
2878 async fn test_reconcile_jwt_auth_missing_credentials_fails_ack() {
2879 let controller = make_controller(vec![]);
2880 let server_config = ServerConnectionConfig {
2881 endpoint: "http://target:9090".to_string(),
2882 tls_required: false,
2883 auth_method: RequiredAuthMethod::Jwt,
2884 ..Default::default()
2885 };
2886 let (tx, mut rx) = mpsc::channel(1);
2887 controller
2888 .handle_new_control_message(make_reconcile_msg("link-2", &server_config), &tx)
2889 .await
2890 .unwrap();
2891 let ack = match rx.recv().await.unwrap().unwrap().payload {
2892 Some(Payload::ConfigCommandAck(a)) => a,
2893 _ => panic!("expected ConfigCommandAck"),
2894 };
2895 assert!(!ack.connections_status[0].success);
2896 assert!(ack.connections_status[0].error_msg.contains("target:9090"));
2897 }
2898}