Skip to main content

rustfs_targets/runtime/tls/
adapter.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//! `TlsReloadAdapter<M>` — the single entry-point that connects a target to
16//! the TLS reload coordinator.  Each target holds an `Option<TlsReloadAdapter<M>>`
17//! and calls `current_material()` on the hot path.  When `None`, the target
18//! falls back to its legacy inline fingerprint logic.
19
20use super::config::TlsReloadOptions;
21use super::coordinator::TargetTlsReloadCoordinator;
22use super::state::{TargetTlsRuntimeState, TargetTlsStatusSnapshot};
23use super::r#trait::ReloadableTargetTls;
24use std::sync::Arc;
25use tracing::warn;
26
27/// Bridges a `ReloadableTargetTls` implementor and the reload coordinator.
28///
29/// Created via [`TlsReloadAdapter::try_register`].  Holds the coordinator-
30/// managed runtime state and exposes a zero-cost `current_material()` accessor
31/// for the send hot-path.
32pub struct TlsReloadAdapter<M> {
33    runtime_state: Arc<TargetTlsRuntimeState<M>>,
34    options: TlsReloadOptions,
35}
36
37impl<M> std::fmt::Debug for TlsReloadAdapter<M> {
38    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
39        f.debug_struct("TlsReloadAdapter")
40            .field("target_label", &self.runtime_state.inputs.target_label)
41            .finish_non_exhaustive()
42    }
43}
44
45impl<M> Clone for TlsReloadAdapter<M> {
46    fn clone(&self) -> Self {
47        Self {
48            runtime_state: Arc::clone(&self.runtime_state),
49            options: self.options.clone(),
50        }
51    }
52}
53
54impl<M: Send + Sync + 'static> TlsReloadAdapter<M> {
55    /// Registers `target` with the coordinator and returns an adapter.
56    ///
57    /// On success the coordinator has:
58    /// - built initial TLS material
59    /// - spawned a background poll loop
60    ///
61    /// On failure returns `None` (the caller should keep its inline fallback
62    /// path intact — the target continues to work, just without coordinator
63    /// support).
64    pub async fn try_register<T: ReloadableTargetTls<Material = M>>(
65        target: Arc<T>,
66        options: TlsReloadOptions,
67        coordinator: &TargetTlsReloadCoordinator,
68    ) -> Option<Self> {
69        let label = target.tls_input_set().target_label.clone();
70        match coordinator.register(target, options.clone()).await {
71            Ok(runtime_state) => {
72                tracing::info!(target = %label, "TLS reload adapter registered");
73                Some(Self { runtime_state, options })
74            }
75            Err(err) => {
76                warn!(target = %label, error = %err, "TLS reload adapter registration failed; target will use inline fallback");
77                None
78            }
79        }
80    }
81
82    /// Hot-path accessor: returns the current TLS material managed by the
83    /// coordinator.  The returned `Arc<M>` is cheap to clone.
84    #[inline]
85    pub fn current_material(&self) -> Arc<M> {
86        Arc::clone(&self.runtime_state.current.load().material)
87    }
88
89    /// Returns the active generation counter.
90    #[inline]
91    pub fn generation(&self) -> u64 {
92        self.runtime_state.current.load().generation.0
93    }
94
95    /// Returns a read-only status snapshot for admin/observability.
96    pub fn status_snapshot(&self) -> TargetTlsStatusSnapshot {
97        TargetTlsReloadCoordinator::build_status_snapshot(&self.runtime_state, &self.options)
98    }
99
100    /// Returns the underlying runtime state (for `close()` cleanup etc.).
101    pub fn runtime_state(&self) -> &Arc<TargetTlsRuntimeState<M>> {
102        &self.runtime_state
103    }
104
105    /// Unregisters from the coordinator (stops the poll loop).
106    pub async fn unregister(&self, coordinator: &TargetTlsReloadCoordinator) {
107        let label = &self.runtime_state.inputs.target_label;
108        if let Err(err) = coordinator.unregister(label).await {
109            warn!(target = %label, error = %err, "Failed to unregister TLS reload adapter");
110        }
111    }
112}
113
114#[cfg(test)]
115mod tests {
116    use super::*;
117    use crate::error::TargetError;
118    use async_trait::async_trait;
119    use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
120
121    struct FakeTarget {
122        label: String,
123        build_calls: AtomicUsize,
124        should_fail: AtomicBool,
125    }
126
127    impl FakeTarget {
128        fn new(label: &str) -> Self {
129            Self {
130                label: label.to_string(),
131                build_calls: AtomicUsize::new(0),
132                should_fail: AtomicBool::new(false),
133            }
134        }
135    }
136
137    #[async_trait]
138    impl ReloadableTargetTls for FakeTarget {
139        type Material = String;
140
141        fn tls_input_set(&self) -> super::super::state::TargetTlsInputSet {
142            super::super::state::TargetTlsInputSet {
143                ca_path: String::new(),
144                client_cert_path: String::new(),
145                client_key_path: String::new(),
146                target_label: self.label.clone(),
147            }
148        }
149
150        async fn build_tls_material(&self) -> Result<Self::Material, TargetError> {
151            self.build_calls.fetch_add(1, Ordering::SeqCst);
152            if self.should_fail.load(Ordering::SeqCst) {
153                return Err(TargetError::Configuration("fail".to_string()));
154            }
155            Ok("material".to_string())
156        }
157
158        async fn apply_tls_material(
159            &self,
160            _generation: super::super::fingerprint::TargetTlsGeneration,
161            _material: Arc<Self::Material>,
162            _mode: super::super::config::ReloadApplyMode,
163        ) -> Result<(), TargetError> {
164            Ok(())
165        }
166    }
167
168    #[tokio::test]
169    async fn try_register_returns_adapter_on_success() {
170        let coordinator = TargetTlsReloadCoordinator::new();
171        let target = Arc::new(FakeTarget::new("test:fake"));
172        let options = TlsReloadOptions::default();
173
174        let adapter = TlsReloadAdapter::try_register(target.clone(), options, &coordinator).await;
175        assert!(adapter.is_some());
176        assert_eq!(target.build_calls.load(Ordering::SeqCst), 1);
177
178        let a = adapter.unwrap();
179        assert_eq!(*a.current_material(), "material");
180    }
181
182    #[tokio::test]
183    async fn try_register_returns_none_on_failure() {
184        let coordinator = TargetTlsReloadCoordinator::new();
185        let target = Arc::new(FakeTarget::new("test:fail"));
186        target.should_fail.store(true, Ordering::SeqCst);
187        let options = TlsReloadOptions::default();
188
189        let adapter = TlsReloadAdapter::try_register(target, options, &coordinator).await;
190        assert!(adapter.is_none());
191    }
192
193    #[tokio::test]
194    async fn adapter_is_clone_and_shares_state() {
195        let coordinator = TargetTlsReloadCoordinator::new();
196        let target = Arc::new(FakeTarget::new("test:clone"));
197        let options = TlsReloadOptions::default();
198
199        let a = TlsReloadAdapter::try_register(target, options, &coordinator).await.unwrap();
200        let b = a.clone();
201
202        assert_eq!(*a.current_material(), *b.current_material());
203        assert_eq!(a.generation(), b.generation());
204    }
205
206    #[tokio::test]
207    async fn status_snapshot_contains_label() {
208        let coordinator = TargetTlsReloadCoordinator::new();
209        let target = Arc::new(FakeTarget::new("test:snap"));
210        let options = TlsReloadOptions::default();
211
212        let adapter = TlsReloadAdapter::try_register(target, options, &coordinator).await.unwrap();
213        let snap = adapter.status_snapshot();
214        assert_eq!(snap.target_label, "test:snap");
215        assert!(snap.reload_enabled);
216    }
217}