Skip to content

Commit bbe416c

Browse files
committed
fix node reconnection
Signed-off-by: Michele Papalini <micpapal@cisco.com>
1 parent 371c8f3 commit bbe416c

3 files changed

Lines changed: 83 additions & 6 deletions

File tree

crates/control-plane/src/db/inmemory.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -924,10 +924,11 @@ impl DataAccess for InMemoryDb {
924924
}
925925

926926
async fn clear_topology(&self) -> Result<()> {
927+
self.nodes.write().clear();
928+
*self.routes.write() = RouteStore::new();
929+
*self.links.write() = LinkStore::new();
927930
self.topology_segment_links.write().clear();
928931
self.topology_segments.write().clear();
929-
*self.links.write() = LinkStore::new();
930-
*self.routes.write() = RouteStore::new();
931932
Ok(())
932933
}
933934
}
@@ -1516,7 +1517,7 @@ mod tests {
15161517

15171518
// Add a physical link and route so we can verify they are cleared too.
15181519
let link = make_link("node-a", "node-b", "http://127.0.0.1:9000", "link-1");
1519-
db.insert_link(link).await.unwrap();
1520+
db.add_link(link).await.unwrap();
15201521
assert_eq!(db.list_all_links().await.unwrap().len(), 1);
15211522

15221523
db.clear_topology().await.unwrap();

crates/control-plane/src/db/sqlite/mod.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1173,7 +1173,7 @@ impl DataAccess for SqliteDb {
11731173
context: "clear_topology pool",
11741174
msg: e.to_string(),
11751175
})?;
1176-
// Clear physical routes and links (they will be re-created when nodes reconnect).
1176+
// Wipe all tables — config is the source of truth and nodes will re-register.
11771177
diesel::delete(routes::table)
11781178
.execute(&mut conn)
11791179
.await
@@ -1188,7 +1188,13 @@ impl DataAccess for SqliteDb {
11881188
context: "clear_topology links",
11891189
msg: e.to_string(),
11901190
})?;
1191-
// Clear logical topology config.
1191+
diesel::delete(nodes::table)
1192+
.execute(&mut conn)
1193+
.await
1194+
.map_err(|e| Error::DbError {
1195+
context: "clear_topology nodes",
1196+
msg: e.to_string(),
1197+
})?;
11921198
diesel::delete(topology_segment_links::table)
11931199
.execute(&mut conn)
11941200
.await

crates/controller/src/service.rs

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ use slim_datapath::api::{
3939
use slim_datapath::api::{NameId, ProtoName};
4040
use slim_datapath::message_processing::MessageProcessor;
4141
use slim_datapath::messages::utils::SlimHeaderFlags;
42-
use slim_datapath::tables::SubscriptionTable;
42+
use slim_datapath::tables::{ConnType, SubscriptionTable};
4343

4444
type TxChannel = mpsc::Sender<Result<ControlMessage, Status>>;
4545
type TxChannels = HashMap<String, TxChannel>;
@@ -1683,6 +1683,47 @@ impl ControllerService {
16831683
}
16841684
}
16851685

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+
16861727
/// Process the control message stream.
16871728
fn process_control_message_stream(
16881729
&self,
@@ -1837,6 +1878,11 @@ impl ControllerService {
18371878
}
18381879
}
18391880

1881+
// Replay local agent subscriptions so the CP recreates route templates.
1882+
if let Some(ref cfg) = config {
1883+
this.replay_local_subscriptions(&[cfg.clone()]).await;
1884+
}
1885+
18401886
let mut drain_fut = std::pin::pin!(watch.clone().signaled());
18411887

18421888
loop {
@@ -1921,6 +1967,30 @@ impl ControllerService {
19211967
) -> Result<mpsc::Sender<Result<ControlMessage, Status>>, ControllerError> {
19221968
info!(%config.endpoint, "connecting to control plane");
19231969

1970+
// Disconnect all outgoing remote connections — the CP will create
1971+
// fresh links after re-registration and nodes will reconnect.
1972+
// This prevents stale connections from causing duplicate links.
1973+
let mut remote_conn_ids: Vec<u64> = Vec::new();
1974+
self.inner
1975+
.message_processor
1976+
.connection_table()
1977+
.for_each(|id, conn| {
1978+
if conn.is_outgoing() && matches!(conn.connection_type(), ConnType::Remote) {
1979+
remote_conn_ids.push(id);
1980+
}
1981+
});
1982+
for conn_id in &remote_conn_ids {
1983+
if let Err(e) = self.inner.message_processor.disconnect(*conn_id) {
1984+
debug!(conn_id, error = %e.chain(), "failed to disconnect remote connection");
1985+
}
1986+
}
1987+
if !remote_conn_ids.is_empty() {
1988+
info!(
1989+
count = remote_conn_ids.len(),
1990+
"disconnected remote connections for clean restart"
1991+
);
1992+
}
1993+
19241994
// Reset connection state before establishing a new session. The CP
19251995
// will send fresh ConfigCommands after re-registration to rebuild
19261996
// these maps.

0 commit comments

Comments
 (0)