rustfs_targets/runtime/tls/
adapter.rs1use 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
27pub 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 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 #[inline]
85 pub fn current_material(&self) -> Arc<M> {
86 Arc::clone(&self.runtime_state.current.load().material)
87 }
88
89 #[inline]
91 pub fn generation(&self) -> u64 {
92 self.runtime_state.current.load().generation.0
93 }
94
95 pub fn status_snapshot(&self) -> TargetTlsStatusSnapshot {
97 TargetTlsReloadCoordinator::build_status_snapshot(&self.runtime_state, &self.options)
98 }
99
100 pub fn runtime_state(&self) -> &Arc<TargetTlsRuntimeState<M>> {
102 &self.runtime_state
103 }
104
105 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}