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