pub enum SnapshotPhase {
Pending,
Running,
Succeeded,
Failed,
Deleting,
Discovered,
Unchanged,
Unknown(String),
}Expand description
Lifecycle phase of a Snapshot.
use kopiur_api::SnapshotPhase;
use kopiur_api::common::PhaseLabel;
// Canonical values round-trip as bare strings.
assert_eq!(serde_json::to_value(SnapshotPhase::Succeeded).unwrap(), "Succeeded");
let p: SnapshotPhase = serde_json::from_value(serde_json::json!("Running")).unwrap();
assert_eq!(p, SnapshotPhase::Running);
// A phase written by a NEWER operator decodes into `Unknown` (never a
// watcher-poisoning serde error) and re-serializes verbatim.
let p: SnapshotPhase = serde_json::from_value(serde_json::json!("Quiescing")).unwrap();
assert_eq!(p, SnapshotPhase::Unknown("Quiescing".into()));
assert_eq!(serde_json::to_value(&p).unwrap(), "Quiescing");
assert_eq!(p.label(), "Quiescing");
// Never terminal: an unrecognized phase is held and surfaced, not finished.
assert!(!p.is_terminal());Variants§
Pending
Admitted, not yet started (also the default).
Running
Mover Job is in flight.
Succeeded
Snapshot created successfully.
Failed
Mover Job exhausted its retries.
Deleting
CR is being deleted; finalizer is reclaiming the snapshot.
Discovered
Catalog-materialized backup kopiur didn’t produce.
Unchanged
The backup ran to completion but kopia wrote no new manifest: the
source was byte-identical to the previous snapshot, and this policy has
files.ignoreIdenticalSnapshots
enabled.
Terminal, and a success: the source was read and hashed, and it is
protected — by the previous snapshot, which remains the live restore
point. So an Unchanged run advances every liveness signal (last-backup
timestamp, policy health, failure-streak reset) exactly like Succeeded.
What it does NOT do is own a kopia manifest. status.snapshot is absent,
the finalizer has nothing to reclaim, and it takes no GFS retention slot
— a restore point that does not exist must not displace one that does.
Recording it as Succeeded instead would make the controller resolve
“its” snapshot and find its predecessor’s, leaving two CRs claiming one
manifest and the first prune deleting it out from under the second.
Unreachable unless the policy opts in: the mover pins
--ignore-identical-snapshots=false at the identity scope on every run.
See #351.
Unknown(String)
A phase string this build does not recognize — written by a newer operator during a rolling upgrade, or persisted before this variant set existed. Decode-compat only: hidden from the CRD schema (the apiserver rejects it on every new write) and never produced by this build.
Never terminal, never schedulable, never reapable, never a success — every consumer holds and surfaces it rather than acting on a phase whose meaning it does not know.
Implementations§
Source§impl SnapshotPhase
impl SnapshotPhase
Sourcepub fn is_terminal(&self) -> bool
pub fn is_terminal(&self) -> bool
Whether this phase is terminal: the operator will do no further work on the object of its own accord, so a diagnostic must not report it as in-flight (nor as stuck).
Deleting is deliberately not terminal. A CR sitting in Deleting
has a finalizer that is still trying to reclaim its kopia snapshot — a
wedged finalizer (an unreachable backend, a held mass-deletion breaker)
is in-flight work that never completes, which is exactly the state worth
surfacing. Classifying it terminal is how a stuck deletion becomes
invisible.
Discovered and Unchanged ARE terminal: a discovered CR mirrors a
kopia snapshot the operator did not produce and never advances on its
own, and an Unchanged run already finished (successfully, owning no
manifest). A Discovered CR that is later deleted moves to Deleting
like any other, so nothing is lost by treating the phase itself as done.
Pure + exhaustive so the single definition lives in one tested place — the CLI and the controller must never disagree about what “still working” means.
use kopiur_api::SnapshotPhase;
assert!(SnapshotPhase::Succeeded.is_terminal());
assert!(SnapshotPhase::Failed.is_terminal());
assert!(SnapshotPhase::Discovered.is_terminal());
assert!(SnapshotPhase::Unchanged.is_terminal());
assert!(!SnapshotPhase::Pending.is_terminal());
assert!(!SnapshotPhase::Running.is_terminal());
// A wedged finalizer is in-flight work, not a finished object.
assert!(!SnapshotPhase::Deleting.is_terminal());
// An unrecognized phase is never terminal — hold and surface it.
assert!(!SnapshotPhase::Unknown("Quiescing".into()).is_terminal());Sourcepub fn is_unknown(&self) -> bool
pub fn is_unknown(&self) -> bool
Whether this phase is the decode sentinel — a value the running build
cannot interpret, kept verbatim by Unknown instead of
failing the whole typed list()/watch (#359, defect 3).
The contract is narrow on purpose, and it is the reason this is a method
rather than an inline matches! at each caller: true means only
“this string is not a phase this binary knows”, never “unusual” or
“not one I handle”. A canonical variant added to this enum later is by
definition not the sentinel, so false is the right answer for it —
which is exactly why the exhaustive match below is written out. Callers
asking a set-shaped question (“is this finished?”, “is this a failure?”)
want is_terminal or their own exhaustive match,
not this.
use kopiur_api::SnapshotPhase;
assert!(SnapshotPhase::Unknown("Quiescing".into()).is_unknown());
assert!(!SnapshotPhase::Succeeded.is_unknown());
assert!(!SnapshotPhase::Failed.is_unknown());
assert!(!SnapshotPhase::Deleting.is_unknown());Trait Implementations§
Source§impl Clone for SnapshotPhase
impl Clone for SnapshotPhase
Source§fn clone(&self) -> SnapshotPhase
fn clone(&self) -> SnapshotPhase
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read moreSource§impl Debug for SnapshotPhase
impl Debug for SnapshotPhase
Source§impl Default for SnapshotPhase
impl Default for SnapshotPhase
Source§fn default() -> SnapshotPhase
fn default() -> SnapshotPhase
Source§impl<'de> Deserialize<'de> for SnapshotPhase
impl<'de> Deserialize<'de> for SnapshotPhase
Source§fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error>
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error>
impl Eq for SnapshotPhase
Source§impl JsonSchema for SnapshotPhase
impl JsonSchema for SnapshotPhase
Source§fn json_schema(_: &mut SchemaGenerator) -> Schema
fn json_schema(_: &mut SchemaGenerator) -> Schema
§fn inline_schema() -> bool
fn inline_schema() -> bool
$ref keyword. Read moreSource§impl PartialEq for SnapshotPhase
impl PartialEq for SnapshotPhase
Source§fn eq(&self, other: &SnapshotPhase) -> bool
fn eq(&self, other: &SnapshotPhase) -> bool
self and other values to be equal, and is used by ==.Source§impl PhaseLabel for SnapshotPhase
impl PhaseLabel for SnapshotPhase
Source§const ALL: &'static [Self]
const ALL: &'static [Self]
Unknown.Source§fn label(&self) -> &str
fn label(&self) -> &str
match); the Unknown arm echoes the stored value verbatim so a
read-modify-write never mutates a phase this build does not understand.Source§fn unknown(raw: String) -> Self
fn unknown(raw: String) -> Self
phase_serde! macro’s host enum.Source§impl Serialize for SnapshotPhase
impl Serialize for SnapshotPhase
impl StructuralPartialEq for SnapshotPhase
Auto Trait Implementations§
impl Freeze for SnapshotPhase
impl RefUnwindSafe for SnapshotPhase
impl Send for SnapshotPhase
impl Sync for SnapshotPhase
impl Unpin for SnapshotPhase
impl UnsafeUnpin for SnapshotPhase
impl UnwindSafe for SnapshotPhase
Blanket Implementations§
§impl<T> AnyExt for T
impl<T> AnyExt for T
§fn downcast_ref<T>(this: &Self) -> Option<&T>where
T: Any,
fn downcast_ref<T>(this: &Self) -> Option<&T>where
T: Any,
T behind reference§fn downcast_mut<T>(this: &mut Self) -> Option<&mut T>where
T: Any,
fn downcast_mut<T>(this: &mut Self) -> Option<&mut T>where
T: Any,
T behind mutable reference§fn downcast_rc<T>(this: Rc<Self>) -> Result<Rc<T>, Rc<Self>>where
T: Any,
fn downcast_rc<T>(this: Rc<Self>) -> Result<Rc<T>, Rc<Self>>where
T: Any,
T behind Rc pointer§fn downcast_arc<T>(this: Arc<Self>) -> Result<Arc<T>, Arc<Self>>where
T: Any,
fn downcast_arc<T>(this: Arc<Self>) -> Result<Arc<T>, Arc<Self>>where
T: Any,
T behind Arc pointer§fn downcast_box<T>(this: Box<Self>) -> Result<Box<T>, Box<Self>>where
T: Any,
fn downcast_box<T>(this: Box<Self>) -> Result<Box<T>, Box<Self>>where
T: Any,
T behind Box pointer§fn downcast_move<T>(this: Self) -> Option<T>
fn downcast_move<T>(this: Self) -> Option<T>
Self to T,
useful only in generic context as a workaround for specializationSource§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
§impl<T, X> CoerceTo<T> for Xwhere
T: CoerceFrom<X> + ?Sized,
impl<T, X> CoerceTo<T> for Xwhere
T: CoerceFrom<X> + ?Sized,
fn coerce_rc_to(self: Rc<X>) -> Rc<T>
fn coerce_box_to(self: Box<X>) -> Box<T>
fn coerce_ref_to(&self) -> &T
fn coerce_mut_to(&mut self) -> &mut T
impl<T> DeserializeOwned for Twhere
T: for<'de> Deserialize<'de>,
§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
§fn equivalent(&self, key: &K) -> bool
fn equivalent(&self, key: &K) -> bool
§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
§fn equivalent(&self, key: &K) -> bool
fn equivalent(&self, key: &K) -> bool
key and return true if they are equal.§impl<T> Instrument for T
impl<T> Instrument for T
§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
§fn in_current_span(self) -> Instrumented<Self>
fn in_current_span(self) -> Instrumented<Self>
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more