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