174 lines
6.5 KiB
Rust
174 lines
6.5 KiB
Rust
#[tokio::test]
|
|
async fn opaque_cookie_is_removed_from_origin_requests_and_protected_on_responses() {
|
|
let pool = pool(Algorithm::RoundRobin, StickyMode::Cookie);
|
|
let first = select(&pool);
|
|
let cookie = first.cookie.unwrap().to_str().unwrap().to_owned();
|
|
assert!(
|
|
cookie.contains("HttpOnly")
|
|
&& cookie.contains("SameSite=Lax")
|
|
&& !cookie.contains("Secure")
|
|
);
|
|
let cookie = cookie.split(';').next().unwrap();
|
|
let mut headers = HeaderMap::new();
|
|
headers.insert("cookie", format!("{cookie}; app=ok").parse().unwrap());
|
|
let next = pool
|
|
.select("10.0.0.1".parse().unwrap(), &headers, false)
|
|
.unwrap();
|
|
assert_eq!(
|
|
next.lease.member.definition.id,
|
|
first.lease.member.definition.id
|
|
);
|
|
assert!(next.cookie.is_none());
|
|
pool.strip_affinity(&mut headers);
|
|
assert_eq!(headers["cookie"], "app=ok");
|
|
headers.append(
|
|
"set-cookie",
|
|
format!("{}=origin-spoof", pool.cookie_name)
|
|
.parse()
|
|
.unwrap(),
|
|
);
|
|
headers.append("set-cookie", "app=ok".parse().unwrap());
|
|
pool.strip_affinity_response(&mut headers);
|
|
assert_eq!(headers.get_all("set-cookie").iter().count(), 1);
|
|
assert_eq!(headers["set-cookie"], "app=ok");
|
|
let secure = pool
|
|
.select("127.0.0.1".parse().unwrap(), &HeaderMap::new(), true)
|
|
.unwrap();
|
|
assert!(secure
|
|
.cookie
|
|
.unwrap()
|
|
.to_str()
|
|
.unwrap()
|
|
.contains("; Secure"));
|
|
}
|
|
#[tokio::test]
|
|
async fn failed_sticky_member_is_replaced_and_duplicate_cookies_are_ignored() {
|
|
let pool = pool(Algorithm::RoundRobin, StickyMode::Cookie);
|
|
let first = select(&pool);
|
|
let cookie = first
|
|
.cookie
|
|
.unwrap()
|
|
.to_str()
|
|
.unwrap()
|
|
.split(';')
|
|
.next()
|
|
.unwrap()
|
|
.to_owned();
|
|
let mut headers = HeaderMap::new();
|
|
headers.insert("cookie", format!("{cookie}; {cookie}").parse().unwrap());
|
|
assert!(pool.affinity(&headers).is_none());
|
|
headers.insert("cookie", cookie.parse().unwrap());
|
|
lock(&first.lease.member.state.health).healthy = false;
|
|
let next = pool
|
|
.select("127.0.0.1".parse().unwrap(), &headers, false)
|
|
.unwrap();
|
|
assert_ne!(
|
|
next.lease.member.definition.id,
|
|
first.lease.member.definition.id
|
|
);
|
|
assert!(next.cookie.is_some());
|
|
lock(&next.lease.member.state.health).healthy = false;
|
|
assert!(pool
|
|
.select("127.0.0.1".parse().unwrap(), &headers, false)
|
|
.is_none());
|
|
}
|
|
#[tokio::test]
|
|
async fn slow_start_ramps_weight_and_error_is_counted_once_per_exchange() {
|
|
let pool = pool(Algorithm::LeastConnections, StickyMode::Off);
|
|
let selected = select(&pool);
|
|
let state = &selected.lease.member.state;
|
|
assert!(state.weight(100, 100) < 100.0);
|
|
assert_eq!(state.weight(100, 0), 100.0);
|
|
selected.lease.started();
|
|
selected.lease.bytes(50, true);
|
|
selected.lease.bytes(100, false);
|
|
selected.lease.error("timeout", Duration::from_secs(1));
|
|
selected.lease.error("body", Duration::from_secs(2));
|
|
selected.lease.retry_attempted();
|
|
selected.lease.retry_succeeded();
|
|
assert_eq!(state.requests.load(Ordering::Relaxed), 1);
|
|
assert_eq!(state.errors.load(Ordering::Relaxed), 1);
|
|
assert_eq!(state.timeouts_total.load(Ordering::Relaxed), 1);
|
|
assert_eq!(state.io_errors_total.load(Ordering::Relaxed), 1);
|
|
assert_eq!(state.bytes_in.load(Ordering::Relaxed), 50);
|
|
assert_eq!(state.bytes_out.load(Ordering::Relaxed), 100);
|
|
assert_eq!(state.retry_attempts_total.load(Ordering::Relaxed), 1);
|
|
assert_eq!(state.retry_successes_total.load(Ordering::Relaxed), 1);
|
|
assert!(pool
|
|
.prometheus()
|
|
.contains("rust_proxy_backend_requests_total{vhost=\"app\",backend=\"a\"} 1"));
|
|
assert!(pool
|
|
.prometheus()
|
|
.contains("rust_proxy_backend_retry_attempts_total{vhost=\"app\",backend=\"a\"} 1"));
|
|
}
|
|
#[tokio::test]
|
|
async fn slow_5xx_counts_both_observability_classes_when_passive_is_off() {
|
|
let pool = pool(Algorithm::LeastConnections, StickyMode::Off);
|
|
let selected = select(&pool);
|
|
selected.lease.response(500, Duration::from_secs(20), true);
|
|
let state = &selected.lease.member.state;
|
|
assert_eq!(state.http_5xx_total.load(Ordering::Relaxed), 1);
|
|
assert_eq!(state.slow_responses_total.load(Ordering::Relaxed), 1);
|
|
assert_eq!(
|
|
state.passive_health_failures_total.load(Ordering::Relaxed),
|
|
0
|
|
);
|
|
assert!(state.healthy());
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn client_upload_duration_does_not_trigger_passive_slow_failure() {
|
|
let pool = pool(Algorithm::LeastConnections, StickyMode::Off);
|
|
let upload = select(&pool);
|
|
upload.lease.response(200, Duration::from_secs(20), false);
|
|
assert_eq!(upload.lease.member.state.errors.load(Ordering::Relaxed), 0);
|
|
let read = select(&pool);
|
|
read.lease.response(200, Duration::from_secs(20), true);
|
|
assert_eq!(read.lease.member.state.errors.load(Ordering::Relaxed), 1);
|
|
}
|
|
#[tokio::test]
|
|
async fn backup_is_only_new_assignment_when_no_primary_is_eligible() {
|
|
let mut pool = pool(Algorithm::RoundRobin, StickyMode::Off);
|
|
Arc::get_mut(&mut Arc::get_mut(&mut pool).unwrap().members[1])
|
|
.unwrap()
|
|
.definition
|
|
.backup = true;
|
|
for _ in 0..8 {
|
|
assert_eq!(select(&pool).lease.member.definition.id, "a");
|
|
}
|
|
lock(&pool.members[0].state.health).healthy = false;
|
|
assert_eq!(select(&pool).lease.member.definition.id, "b");
|
|
lock(&pool.members[0].state.health).healthy = true;
|
|
assert_eq!(select(&pool).lease.member.definition.id, "a");
|
|
}
|
|
#[tokio::test]
|
|
async fn drain_rejects_new_sessions_but_preserves_valid_existing_affinity() {
|
|
let mut pool = pool(Algorithm::RoundRobin, StickyMode::Cookie);
|
|
Arc::get_mut(&mut Arc::get_mut(&mut pool).unwrap().members[0])
|
|
.unwrap()
|
|
.definition
|
|
.drain = true;
|
|
assert_eq!(select(&pool).lease.member.definition.id, "b");
|
|
let mut headers = HeaderMap::new();
|
|
headers.insert(
|
|
"cookie",
|
|
format!("{}={}", pool.cookie_name, pool.members[0].token)
|
|
.parse()
|
|
.unwrap(),
|
|
);
|
|
let existing = pool
|
|
.select("127.0.0.1".parse().unwrap(), &headers, false)
|
|
.unwrap();
|
|
assert_eq!(existing.lease.member.definition.id, "a");
|
|
lock(&pool.members[0].state.health).healthy = false;
|
|
assert_eq!(
|
|
pool.select("127.0.0.1".parse().unwrap(), &headers, false)
|
|
.unwrap()
|
|
.lease
|
|
.member
|
|
.definition
|
|
.id,
|
|
"b"
|
|
);
|
|
}
|