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