mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-06 13:27:43 +00:00
fix(targets): probe webhook health by host port (#2854)
This commit is contained in:
@@ -25,7 +25,7 @@ use std::error::Error;
|
||||
use time::OffsetDateTime;
|
||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
use tokio::net::TcpListener;
|
||||
use tokio::sync::oneshot;
|
||||
use tokio::sync::{mpsc, oneshot};
|
||||
use tokio::time::{Duration, timeout};
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -172,6 +172,28 @@ async fn spawn_object_lambda_webhook_server_with_response(
|
||||
Ok((webhook_url, request_rx, handle))
|
||||
}
|
||||
|
||||
async fn read_request_path(stream: &mut tokio::net::TcpStream) -> Result<String, Box<dyn Error + Send + Sync>> {
|
||||
let mut buffer = Vec::new();
|
||||
let mut chunk = [0_u8; 4096];
|
||||
|
||||
let header_end = loop {
|
||||
let read = stream.read(&mut chunk).await?;
|
||||
if read == 0 {
|
||||
return Err("request ended before headers were fully received".into());
|
||||
}
|
||||
buffer.extend_from_slice(&chunk[..read]);
|
||||
if let Some(pos) = find_header_terminator(&buffer) {
|
||||
break pos;
|
||||
}
|
||||
};
|
||||
|
||||
let header_text = std::str::from_utf8(&buffer[..header_end])?;
|
||||
let request_line = header_text.lines().next().ok_or("missing request line")?;
|
||||
let path = request_line.split_whitespace().nth(1).ok_or("missing request path")?;
|
||||
|
||||
Ok(path.to_string())
|
||||
}
|
||||
|
||||
async fn presigned_get_request(
|
||||
url: &str,
|
||||
access_key: &str,
|
||||
@@ -326,11 +348,15 @@ async fn delete_webhook_target(env: &RustFSTestEnvironment, target_name: &str) -
|
||||
}
|
||||
|
||||
fn notification_target_is_listed(targets: &serde_json::Value, target_name: &str) -> bool {
|
||||
notification_target_entry(targets, target_name).is_some()
|
||||
}
|
||||
|
||||
fn notification_target_entry<'a>(targets: &'a serde_json::Value, target_name: &str) -> Option<&'a serde_json::Value> {
|
||||
targets["notification_endpoints"]
|
||||
.as_array()
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.any(|entry| {
|
||||
.find(|entry| {
|
||||
entry["account_id"].as_str() == Some(target_name)
|
||||
&& entry["service"]
|
||||
.as_str()
|
||||
@@ -338,6 +364,10 @@ fn notification_target_is_listed(targets: &serde_json::Value, target_name: &str)
|
||||
})
|
||||
}
|
||||
|
||||
fn notification_target_status<'a>(targets: &'a serde_json::Value, target_name: &str) -> Option<&'a str> {
|
||||
notification_target_entry(targets, target_name).and_then(|entry| entry["status"].as_str())
|
||||
}
|
||||
|
||||
async fn wait_for_target_visibility(
|
||||
env: &RustFSTestEnvironment,
|
||||
target_name: &str,
|
||||
@@ -387,6 +417,34 @@ async fn restart_rustfs_server(env: &mut RustFSTestEnvironment) -> Result<(), Bo
|
||||
env.start_rustfs_server_without_cleanup(vec![]).await
|
||||
}
|
||||
|
||||
async fn spawn_http_origin_probe_server() -> Result<
|
||||
(
|
||||
String,
|
||||
mpsc::Receiver<String>,
|
||||
tokio::task::JoinHandle<Result<(), Box<dyn Error + Send + Sync>>>,
|
||||
),
|
||||
Box<dyn Error + Send + Sync>,
|
||||
> {
|
||||
let listener = TcpListener::bind("127.0.0.1:0").await?;
|
||||
let address = listener.local_addr()?;
|
||||
let webhook_url = format!("http://{address}/hook");
|
||||
let (path_tx, path_rx) = mpsc::channel(1);
|
||||
|
||||
let handle = tokio::spawn(async move {
|
||||
loop {
|
||||
let (mut stream, _) = listener.accept().await?;
|
||||
let path = timeout(Duration::from_secs(2), read_request_path(&mut stream)).await??;
|
||||
let _ = path_tx.try_send(path.clone());
|
||||
if path == "/" {
|
||||
let response = b"HTTP/1.1 200 OK\r\nContent-Length: 0\r\nConnection: close\r\n\r\n";
|
||||
stream.write_all(response).await?;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Ok((webhook_url, path_rx, handle))
|
||||
}
|
||||
|
||||
async fn read_persisted_server_config(env: &RustFSTestEnvironment) -> String {
|
||||
let path = format!("{}/.rustfs.sys/config/config.json", env.temp_dir);
|
||||
match tokio::fs::read_to_string(&path).await {
|
||||
@@ -515,6 +573,40 @@ async fn test_notification_target_persists_across_restart_and_delete() -> Result
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn test_notification_target_with_path_is_online_via_transport_probe() -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
init_logging();
|
||||
|
||||
let (webhook_url, mut probe_rx, probe_handle) = spawn_http_origin_probe_server().await?;
|
||||
|
||||
let mut env = RustFSTestEnvironment::new().await?;
|
||||
env.start_rustfs_server_with_env(vec![], &[("RUSTFS_NOTIFY_ENABLE", "true")])
|
||||
.await?;
|
||||
|
||||
let target_name = "path-probe";
|
||||
configure_webhook_target(&env, target_name, &webhook_url, "secret-token").await?;
|
||||
|
||||
let (visible_targets, visible_arns) = wait_for_target_visibility(&env, target_name).await?;
|
||||
assert_eq!(notification_target_status(&visible_targets, target_name), Some("online"));
|
||||
let observed_path = timeout(Duration::from_secs(10), probe_rx.recv())
|
||||
.await
|
||||
.map_err(|_| "probe server timed out waiting for a request")?
|
||||
.ok_or("probe server did not observe a request")?;
|
||||
assert_eq!(observed_path, "/");
|
||||
assert!(
|
||||
visible_arns
|
||||
.iter()
|
||||
.any(|arn| arn.ends_with(&format!(":{target_name}:webhook"))),
|
||||
"target ARN missing for reachable path endpoint: {visible_arns:?}"
|
||||
);
|
||||
|
||||
probe_handle.abort();
|
||||
let _ = probe_handle.await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn test_get_object_lambda_accepts_presigned_requests() -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
|
||||
Reference in New Issue
Block a user