//! What the server does when a part of it that it cannot do without fails. //! //! The local RIB's actor stops only by panicking. Before, nothing noticed: //! sessions stayed up, nothing was announced any more, and the status page //! showed an empty table. Now the server tells its peers it is going and //! stops with an error, for whatever supervises it to restart it. use std::net::Ipv4Addr; use std::time::Duration; use bgp_integration_tests::test_peer::TestPeer; use bgp_integration_tests::test_server::TestServer; use bgp_packet::errors::{BgpError, CeaseSubcode}; use bgp_packet::message::Message; use bgp_rib::RibStopped; use bgp_server::error::ServerError; use eyre::{Result, bail}; const LOCAL_ASN: u32 = 65000; const PEER_ASN: u32 = 65001; const TIMEOUT: Duration = Duration::from_secs(5); async fn established(server: &TestServer) -> Result { let mut peer = TestPeer::connect(server.addr).await?; peer.handshake(PEER_ASN, Ipv4Addr::new(10, 0, 0, 1), 90) .await?; tokio::time::timeout(TIMEOUT, async { while server.registry.list().is_empty() { tokio::time::sleep(Duration::from_millis(10)).await; } }) .await?; Ok(peer) } /// Reads until the server's NOTIFICATION, skipping KEEPALIVEs. async fn farewell(peer: &mut TestPeer) -> Result { tokio::time::timeout(TIMEOUT, async { loop { match peer.recv().await? { Message::KeepAlive => {} Message::Notification(n) => return Ok(n.error), other => bail!("expected NOTIFICATION, got {other:?}"), } } }) .await? } #[tokio::test] async fn test_losing_the_local_rib_stops_the_server_and_tells_the_peers() -> Result<()> { let mut server = TestServer::start(LOCAL_ASN).await?; let (base, _http) = server.serve_http().await?; let mut peer = established(&server).await?; server.local_rib.stop_for_testing(); let outcome = tokio::time::timeout(TIMEOUT, server.stopped()).await?; assert!( matches!(outcome, Err(ServerError::RibStopped(RibStopped { rib })) if rib == "local RIB"), "{outcome:?}" ); assert_eq!( farewell(&mut peer).await?, BgpError::Cease(CeaseSubcode::AdminShutdown) ); // The status interface says the table cannot be read, where it used to // show an empty one. let client = reqwest::Client::new(); for path in ["/api/status", "/api/routes/local", "/metricz"] { let response = client.get(format!("{base}{path}")).send().await?; assert_eq!(response.status(), 503, "{path}"); assert_eq!( response.text().await?, "the local RIB has stopped", "{path}" ); } // What does not need that table still answers. let response = client .get(format!("{base}/api/routes/received")) .send() .await?; assert_eq!(response.status(), 200); Ok(()) }