use clockabilly::DateTime;
use clockabilly::mock::MockUtcClock;
use clockabilly::prelude::*;
use httpmock::Method::*;
use k8s_openapi::api::coordination::v1 as coordinationv1;
use kube::error::ErrorResponse;
use serde_json::json;
use super::*;
const NOW: i64 = 15;
const TEST_LEASE_NS: &str = "simlease-ns";
const TEST_LEASE_DURATION: i64 = 10;
#[fixture]
fn lease_other_holder() -> coordinationv1::Lease {
let holder = "some-other-sim";
coordinationv1::Lease {
spec: Some(coordinationv1::LeaseSpec {
holder_identity: Some(holder.into()),
..Default::default()
}),
..Default::default()
}
}
#[rstest(tokio::test)]
async fn test_try_claim_lease_with_clock_already_owned_by_us(test_sim: Simulation, test_sim_root: SimulationRoot) {
let clock = MockUtcClock::boxed(NOW);
let (mut fake_apiserver, client) = make_fake_apiserver();
let lease_obj = build_lease(&test_sim, &test_sim_root, TEST_CTRL_NAMESPACE, clock.now());
fake_apiserver.handle(move |when, then| {
when.method(GET)
.path(format!("/apis/coordination.k8s.io/v1/namespaces/{TEST_LEASE_NS}/leases/{SK_LEASE_NAME}"));
then.json_body_obj(&lease_obj);
});
let res = try_claim_lease_with_clock(client, &test_sim, &test_sim_root, TEST_LEASE_NS, clock)
.await
.unwrap();
fake_apiserver.assert();
assert_eq!(res, LeaseState::Claimed);
}
#[rstest(tokio::test)]
async fn test_try_claim_lease_with_clock_other_lease_unowned(test_sim: Simulation, test_sim_root: SimulationRoot) {
let clock = MockUtcClock::boxed(NOW);
let (mut fake_apiserver, client) = make_fake_apiserver();
let other_lease: coordinationv1::Lease = Default::default();
let lease_obj = build_lease(&test_sim, &test_sim_root, TEST_CTRL_NAMESPACE, clock.now());
fake_apiserver.handle(move |when, then| {
when.method(GET)
.path(format!("/apis/coordination.k8s.io/v1/namespaces/{TEST_LEASE_NS}/leases/{SK_LEASE_NAME}"));
then.json_body_obj(&other_lease);
});
fake_apiserver.handle(move |when, then| {
when.method(PUT)
.path(format!("/apis/coordination.k8s.io/v1/namespaces/{TEST_LEASE_NS}/leases/{SK_LEASE_NAME}"));
then.json_body_obj(&lease_obj);
});
let res = try_claim_lease_with_clock(client, &test_sim, &test_sim_root, TEST_LEASE_NS, clock)
.await
.unwrap();
fake_apiserver.assert();
assert_eq!(res, LeaseState::Claimed);
}
#[rstest(tokio::test)]
async fn test_try_claim_lease_with_clock_already_owned_by_other(
test_sim: Simulation,
test_sim_root: SimulationRoot,
lease_other_holder: coordinationv1::Lease,
) {
let clock = MockUtcClock::boxed(NOW);
let (mut fake_apiserver, client) = make_fake_apiserver();
fake_apiserver.handle(move |when, then| {
when.method(GET)
.path(format!("/apis/coordination.k8s.io/v1/namespaces/{TEST_LEASE_NS}/leases/{SK_LEASE_NAME}"));
then.json_body_obj(&lease_other_holder);
});
let res = try_claim_lease_with_clock(client, &test_sim, &test_sim_root, TEST_LEASE_NS, clock)
.await
.unwrap();
fake_apiserver.assert();
assert!(matches!(res, LeaseState::WaitingForClaim(..)));
}
#[rstest(tokio::test)]
async fn test_try_claim_lease_with_clock(test_sim: Simulation, test_sim_root: SimulationRoot) {
let clock = MockUtcClock::boxed(NOW);
let (mut fake_apiserver, client) = make_fake_apiserver();
let lease_obj = build_lease(&test_sim, &test_sim_root, TEST_CTRL_NAMESPACE, clock.now());
fake_apiserver
.handle_not_found(format!("/apis/coordination.k8s.io/v1/namespaces/{TEST_LEASE_NS}/leases/{SK_LEASE_NAME}"));
fake_apiserver.handle(move |when, then| {
when.method(POST)
.path(format!("/apis/coordination.k8s.io/v1/namespaces/{TEST_LEASE_NS}/leases"));
then.json_body_obj(&lease_obj);
});
let res = try_claim_lease_with_clock(client, &test_sim, &test_sim_root, TEST_LEASE_NS, clock)
.await
.unwrap();
fake_apiserver.assert();
assert_eq!(res, LeaseState::Claimed);
}
#[rstest(tokio::test)]
async fn test_try_update_lease_with_clock_no_lease_found(test_sim: Simulation) {
let clock = MockUtcClock::boxed(NOW);
let (mut fake_apiserver, client) = make_fake_apiserver();
fake_apiserver
.handle_not_found(format!("/apis/coordination.k8s.io/v1/namespaces/{TEST_LEASE_NS}/leases/{SK_LEASE_NAME}"));
let res = try_update_lease_with_clock(client, &test_sim, TEST_LEASE_NS, 10, clock)
.await
.unwrap_err();
let err = res.downcast::<kube::Error>().unwrap();
fake_apiserver.assert();
assert!(matches!(err, kube::Error::Api(ErrorResponse { code: 404, .. })));
}
#[rstest(tokio::test)]
async fn test_try_update_lease_with_clock_wrong_owner(test_sim: Simulation, lease_other_holder: coordinationv1::Lease) {
let clock = MockUtcClock::boxed(NOW);
let (mut fake_apiserver, client) = make_fake_apiserver();
fake_apiserver.handle(move |when, then| {
when.method(GET)
.path(format!("/apis/coordination.k8s.io/v1/namespaces/{TEST_LEASE_NS}/leases/{SK_LEASE_NAME}"));
then.json_body_obj(&lease_other_holder);
});
let res = try_update_lease_with_clock(client, &test_sim, TEST_LEASE_NS, 10, clock)
.await
.unwrap_err();
let err = res.downcast::<KubernetesError>().unwrap();
fake_apiserver.assert();
assert!(matches!(err, KubernetesError::LeaseHeldByOther(..)));
}
#[rstest(tokio::test)]
async fn test_try_update_lease_with_clock(test_sim: Simulation, test_sim_root: SimulationRoot) {
let mut clock = MockUtcClock::boxed(NOW);
let (mut fake_apiserver, client) = make_fake_apiserver();
let lease_obj = build_lease(&test_sim, &test_sim_root, TEST_CTRL_NAMESPACE, clock.now());
let mut patched_lease_obj = build_lease(&test_sim, &test_sim_root, TEST_CTRL_NAMESPACE, clock.now());
clock.advance(5);
let renew_time = metav1::MicroTime(clock.now());
patched_lease_obj.spec.as_mut().unwrap().lease_duration_seconds = Some(TEST_LEASE_DURATION as i32);
patched_lease_obj.spec.as_mut().unwrap().renew_time = Some(renew_time.clone());
fake_apiserver.handle(move |when, then| {
when.method(GET)
.path(format!("/apis/coordination.k8s.io/v1/namespaces/{TEST_LEASE_NS}/leases/{SK_LEASE_NAME}"));
then.json_body_obj(&lease_obj);
});
fake_apiserver.handle(move |when, then| {
when.method(PATCH)
.path(format!("/apis/coordination.k8s.io/v1/namespaces/{TEST_LEASE_NS}/leases/{SK_LEASE_NAME}"))
.json_body(json!({
"spec": {
"leaseDurationSeconds": TEST_LEASE_DURATION,
"renewTime": renew_time,
}
}));
then.json_body_obj(&patched_lease_obj);
});
assert_eq!(
(),
try_update_lease_with_clock(client, &test_sim, TEST_LEASE_NS, 10, clock)
.await
.unwrap()
);
fake_apiserver.assert();
}
#[rstest]
#[case::no_data(None, None, RETRY_DELAY_SECONDS as i64)]
#[case::no_renew_time(Some(TEST_LEASE_DURATION), None, TEST_LEASE_DURATION + RETRY_DELAY_SECONDS as i64)]
#[case::no_duration_seconds(None, Some(13), 13 + RETRY_DELAY_SECONDS as i64 - NOW)]
#[case::valid(Some(TEST_LEASE_DURATION), Some(13), 23 + RETRY_DELAY_SECONDS as i64 - NOW)]
#[case::negative(Some(5), Some(2), RETRY_DELAY_SECONDS as i64)]
fn test_compute_remaining_lease_time_no_data(
#[case] maybe_duration_seconds_64: Option<i64>,
#[case] maybe_renew_ts: Option<i64>,
#[case] expected: i64,
) {
let maybe_renew_time = maybe_renew_ts.map(|ts| metav1::MicroTime(DateTime::from_timestamp(ts, 0).unwrap()));
let maybe_duration_seconds = maybe_duration_seconds_64.map(|secs| secs as i32);
assert_eq!(compute_remaining_lease_time(&maybe_duration_seconds, &maybe_renew_time, NOW), expected);
}