Skip to main content

rustfs_targets/runtime/tls/
state.rs

1// Copyright 2024 RustFS Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Per-target TLS reload runtime state with atomic timestamps and error tracking.
16
17use super::fingerprint::{TargetTlsFingerprint, TargetTlsGeneration};
18use ::arc_swap::ArcSwap;
19use serde::Serialize;
20use std::sync::Arc;
21use std::sync::atomic::{AtomicU64, Ordering};
22
23/// Describes which TLS files a target reads.
24#[derive(Debug, Clone, PartialEq, Eq)]
25pub struct TargetTlsInputSet {
26    pub ca_path: String,
27    pub client_cert_path: String,
28    pub client_key_path: String,
29    /// Human-readable label for logging and metrics (e.g. "webhook:primary").
30    pub target_label: String,
31}
32
33impl TargetTlsInputSet {
34    /// Returns `true` when no TLS paths are configured (no CA, cert, or key).
35    pub fn is_empty(&self) -> bool {
36        self.ca_path.is_empty() && self.client_cert_path.is_empty() && self.client_key_path.is_empty()
37    }
38}
39
40/// Immutable snapshot of a successfully published TLS material generation.
41pub struct TargetTlsPublishedState<M> {
42    pub generation: TargetTlsGeneration,
43    pub fingerprint: TargetTlsFingerprint,
44    pub material: Arc<M>,
45    pub loaded_at_unix_ms: u64,
46}
47
48/// Per-target TLS reload runtime state. Owns the current published material
49/// and tracks timestamps and the last error for observability.
50pub struct TargetTlsRuntimeState<M> {
51    /// The currently active TLS material generation.
52    pub current: ArcSwap<TargetTlsPublishedState<M>>,
53    /// The last known-good generation (never overwritten by a failed reload).
54    pub last_good: ArcSwap<TargetTlsPublishedState<M>>,
55    /// Unix-millis timestamp of the last reload *attempt* (success or failure).
56    pub last_attempt_unix_ms: AtomicU64,
57    /// Unix-millis timestamp of the last *successful* reload.
58    pub last_success_unix_ms: AtomicU64,
59    /// Last reload error message, if any.
60    pub last_error: parking_lot::RwLock<Option<String>>,
61    /// The TLS file paths this state watches.
62    pub inputs: TargetTlsInputSet,
63    /// Serializes reload cycles for this target. Without it a `force_reload`
64    /// and a poll-loop tick could interleave, producing duplicate generations
65    /// or publishing an older material over a newer one.
66    pub reload_lock: tokio::sync::Mutex<()>,
67}
68
69impl<M> TargetTlsRuntimeState<M> {
70    /// Creates a new runtime state with the given initial published state.
71    pub fn new(initial: Arc<TargetTlsPublishedState<M>>, inputs: TargetTlsInputSet) -> Self {
72        Self {
73            current: ArcSwap::from(initial.clone()),
74            last_good: ArcSwap::from(initial),
75            last_attempt_unix_ms: AtomicU64::new(0),
76            last_success_unix_ms: AtomicU64::new(0),
77            last_error: parking_lot::RwLock::new(None),
78            inputs,
79            reload_lock: tokio::sync::Mutex::new(()),
80        }
81    }
82
83    /// Returns the generation of the currently active material.
84    pub fn current_generation(&self) -> TargetTlsGeneration {
85        self.current.load().generation
86    }
87
88    /// Atomically bumps and returns the next generation.
89    pub fn bump_generation(&self) -> TargetTlsGeneration {
90        // Load the current generation from the arc-swap, compute next,
91        // and return it. The caller is responsible for publishing the new state.
92        let current = self.current.load();
93        TargetTlsGeneration(current.generation.0.saturating_add(1))
94    }
95
96    /// Records the timestamp of a reload attempt.
97    pub fn mark_attempt(&self, unix_ms: u64) {
98        self.last_attempt_unix_ms.store(unix_ms, Ordering::Release);
99    }
100
101    /// Records the timestamp of a successful reload.
102    pub fn mark_success(&self, unix_ms: u64) {
103        self.last_success_unix_ms.store(unix_ms, Ordering::Release);
104    }
105
106    /// Returns the last attempt timestamp.
107    pub fn last_attempt_unix_ms(&self) -> u64 {
108        self.last_attempt_unix_ms.load(Ordering::Acquire)
109    }
110
111    /// Returns the last success timestamp.
112    pub fn last_success_unix_ms(&self) -> u64 {
113        self.last_success_unix_ms.load(Ordering::Acquire)
114    }
115}
116
117/// Read-only status snapshot for admin/debug visibility.
118#[derive(Debug, Clone, Serialize)]
119pub struct TargetTlsStatusSnapshot {
120    pub target_label: String,
121    pub generation: u64,
122    pub reload_enabled: bool,
123    pub detect_mode: &'static str,
124    pub apply_mode: &'static str,
125    pub last_attempt_time: Option<u64>,
126    pub last_success_time: Option<u64>,
127    pub last_error: Option<String>,
128    /// TLS file paths this target watches (for admin diagnostics).
129    pub ca_path: String,
130    pub client_cert_path: String,
131    pub client_key_path: String,
132}