use std::time::Duration;
use ozzy_proto::NodeId;
use super::{DriverError, ReplicaDriver};
use crate::{
Admission, JournalGeneration, Prefix, PreparedOperation, ReplicationError, Scope, Status,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ValidationTicket {
local: NodeId,
scope: Scope,
generation: JournalGeneration,
accepted: Prefix,
committed: Prefix,
applied: Prefix,
}
impl ValidationTicket {
pub(crate) const fn from_local_image(
local: NodeId,
scope: Scope,
generation: JournalGeneration,
accepted: Prefix,
committed: Prefix,
applied: Prefix,
) -> Self {
Self {
local,
scope,
generation,
accepted,
committed,
applied,
}
}
pub const fn scope(self) -> Scope {
self.scope
}
pub const fn generation(self) -> JournalGeneration {
self.generation
}
pub const fn accepted(self) -> Prefix {
self.accepted
}
pub const fn committed(self) -> Prefix {
self.committed
}
pub const fn applied(self) -> Prefix {
self.applied
}
pub(crate) fn same_images_as(self, current: Self) -> bool {
self.local == current.local
&& self.scope == current.scope
&& self.generation == current.generation
&& self.accepted == current.accepted
&& self.applied == current.applied
&& (self.committed == current.committed || self.committed.op < current.committed.op)
}
}
impl ReplicaDriver {
pub fn begin_validation(&self) -> Result<ValidationTicket, DriverError> {
let normal = self.normal().ok_or(ReplicationError::NotNormal)?;
let snapshot = normal.snapshot();
if snapshot.status != Status::Normal {
return Err(ReplicationError::NotNormal.into());
}
if !snapshot.ready_for_appends {
return Err(ReplicationError::ActivationPending.into());
}
Ok(ValidationTicket {
local: self.local,
scope: snapshot.scope,
generation: snapshot.journal.generation,
accepted: snapshot.accepted,
committed: snapshot.committed,
applied: snapshot.applied,
})
}
pub fn begin_validation_after_apply(
&self,
applying: ValidationTicket,
) -> Result<ValidationTicket, DriverError> {
let current = self.begin_validation()?;
let through = applying.committed;
if applying.local != current.local
|| applying.scope != current.scope
|| applying.generation != current.generation
|| applying.accepted != current.accepted
|| applying.applied != current.applied
|| through.op < current.applied.op
|| (through.op == current.applied.op && through != current.applied)
|| through.op > current.committed.op
|| (through.op == current.committed.op && through != current.committed)
{
return Err(DriverError::StaleValidation);
}
Ok(ValidationTicket {
applied: through,
..current
})
}
pub fn prepare_validated(
&mut self,
from: NodeId,
ticket: ValidationTicket,
operations: &[PreparedOperation],
now: Duration,
) -> Result<Admission, DriverError> {
self.observe_time(now)?;
if !self
.begin_validation()
.is_ok_and(|current| ticket.same_images_as(current))
{
return Err(DriverError::StaleValidation);
}
self.prepare(from, ticket.scope, operations, now)
}
}