Skip to main content

slim_controller/
service.rs

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