1use std::sync::Arc;
6
7use async_trait::async_trait;
8use tokio::sync::mpsc;
9use tokio_stream::wrappers::ReceiverStream;
10use tonic::{Request, Response, Status, Streaming};
11use tracing::{debug, error, info, instrument};
12
13use xds_cache::ShardedCache;
14use xds_core::{NodeHash, ResourceRegistry, TypeUrl};
15
16use crate::delta::{delta_response_to_proto, ClientResourceState, DeltaHandler};
17use crate::sotw::SotwHandler;
18use crate::stream::StreamContext;
19
20pub use xds_types::envoy::service::discovery::v3::{DiscoveryRequest, DiscoveryResponse};
22pub use xds_types::envoy::service::secret::v3::secret_discovery_service_server::{
23 SecretDiscoveryService, SecretDiscoveryServiceServer,
24};
25pub use xds_types::envoy::service::discovery::v3::{
26 DeltaDiscoveryRequest, DeltaDiscoveryResponse,
27};
28
29#[derive(Debug, Clone)]
31pub struct SdsService {
32 cache: Arc<ShardedCache>,
34 registry: Arc<ResourceRegistry>,
36 sotw_handler: Arc<SotwHandler>,
38 delta_handler: Arc<DeltaHandler>,
40}
41
42impl SdsService {
43 pub fn new(cache: Arc<ShardedCache>, registry: Arc<ResourceRegistry>) -> Self {
45 let sotw_handler = Arc::new(SotwHandler::new(Arc::clone(&cache), Arc::clone(®istry)));
46 let delta_handler = Arc::new(DeltaHandler::new(Arc::clone(&cache), Arc::clone(®istry)));
47 Self {
48 cache,
49 registry,
50 sotw_handler,
51 delta_handler,
52 }
53 }
54
55 pub fn new_with_handlers(
60 cache: Arc<ShardedCache>,
61 registry: Arc<ResourceRegistry>,
62 sotw_handler: Arc<SotwHandler>,
63 delta_handler: Arc<DeltaHandler>,
64 ) -> Self {
65 Self {
66 cache,
67 registry,
68 sotw_handler,
69 delta_handler,
70 }
71 }
72
73 #[inline]
75 pub fn type_url() -> &'static str {
76 TypeUrl::SECRET
77 }
78
79 #[allow(dead_code)]
81 pub fn cache(&self) -> &ShardedCache {
82 &self.cache
83 }
84
85 #[allow(dead_code)]
87 pub fn registry(&self) -> &ResourceRegistry {
88 &self.registry
89 }
90
91 pub fn into_service(self) -> SecretDiscoveryServiceServer<Self> {
93 SecretDiscoveryServiceServer::new(self)
94 }
95
96 fn convert_response(
101 &self,
102 response: crate::sotw::SotwResponse,
103 ) -> Result<DiscoveryResponse, Status> {
104 use xds_types::google::protobuf::Any;
105
106 let resources: Vec<Any> = response
107 .resources
108 .iter()
109 .map(|r| {
110 r.encode().map(|encoded| Any {
111 type_url: encoded.type_url.clone(),
112 value: encoded.value.clone(),
113 })
114 })
115 .collect::<Result<Vec<_>, _>>()
116 .map_err(|e| Status::internal(format!("failed to encode resource: {}", e)))?;
117
118 Ok(DiscoveryResponse {
119 version_info: response.version_info,
120 resources,
121 type_url: TypeUrl::SECRET.to_string(),
122 nonce: response.nonce,
123 canary: false,
124 control_plane: None,
125 resource_errors: vec![],
126 })
127 }
128}
129
130pub type SdsResponseStream = ReceiverStream<Result<DiscoveryResponse, Status>>;
132
133pub type SdsDeltaResponseStream = ReceiverStream<Result<DeltaDiscoveryResponse, Status>>;
135
136#[async_trait]
137impl SecretDiscoveryService for SdsService {
138 type StreamSecretsStream = SdsResponseStream;
139
140 #[instrument(skip(self, request), name = "sds_stream")]
141 async fn stream_secrets(
142 &self,
143 request: Request<Streaming<DiscoveryRequest>>,
144 ) -> Result<Response<Self::StreamSecretsStream>, Status> {
145 let mut stream = request.into_inner();
146 let (tx, rx) = mpsc::channel(16);
147
148 let service = self.clone();
149 let mut ctx = StreamContext::new();
150
151 info!(stream = %ctx.id(), "SDS stream started");
152
153 tokio::spawn(async move {
154 let mut node_hash: Option<NodeHash> = None;
155
156 while let Some(result) = tokio_stream::StreamExt::next(&mut stream).await {
157 match result {
158 Ok(request) => {
159 if !request.type_url.is_empty() && request.type_url != TypeUrl::SECRET {
161 error!(
162 stream = %ctx.id(),
163 expected = TypeUrl::SECRET,
164 got = %request.type_url,
165 "invalid type URL for SDS"
166 );
167 continue;
168 }
169
170 if node_hash.is_none() {
172 if let Some(ref node) = request.node {
173 let hash = NodeHash::from_id(&node.id);
174 ctx.set_node(node.id.clone(), hash);
175 node_hash = Some(hash);
176 }
177 }
178
179 let hash = match node_hash {
180 Some(h) => h,
181 None => {
182 error!(
184 stream = %ctx.id(),
185 "first request missing required node information"
186 );
187 let _ = tx.send(Err(Status::invalid_argument(
188 "first request must include node information"
189 ))).await;
190 break;
191 }
192 };
193
194 match service.sotw_handler.process_request(
196 &ctx,
197 TypeUrl::SECRET.into(),
198 &request.version_info,
199 &request.resource_names,
200 hash,
201 ) {
202 Ok(Some(response)) => {
203 match service.convert_response(response) {
204 Ok(discovery_response) => {
205 if tx.send(Ok(discovery_response)).await.is_err() {
206 break;
207 }
208 }
209 Err(e) => {
210 error!(stream = %ctx.id(), error = %e, "failed to convert response");
211 let _ = tx.send(Err(e)).await;
212 break;
213 }
214 }
215 }
216 Ok(None) => {}
217 Err(e) => {
218 error!(stream = %ctx.id(), error = %e, "SDS request failed");
219 break;
220 }
221 }
222 }
223 Err(e) => {
224 error!(stream = %ctx.id(), error = %e, "stream error");
225 break;
226 }
227 }
228 }
229
230 info!(stream = %ctx.id(), "SDS stream ended");
231 });
232
233 Ok(Response::new(ReceiverStream::new(rx)))
234 }
235
236 type DeltaSecretsStream = SdsDeltaResponseStream;
237
238 #[instrument(skip(self, request), name = "sds_delta_stream")]
239 async fn delta_secrets(
240 &self,
241 request: Request<Streaming<DeltaDiscoveryRequest>>,
242 ) -> Result<Response<Self::DeltaSecretsStream>, Status> {
243 let mut stream = request.into_inner();
244 let (tx, rx) = mpsc::channel(16);
245
246 let service = self.clone();
247 let mut ctx = StreamContext::new();
248 info!(stream = %ctx.id(), "Delta SDS stream started");
249
250 tokio::spawn(async move {
251 let mut node_hash: Option<NodeHash> = None;
252 let mut client_state = ClientResourceState::new();
253
254 while let Some(result) = tokio_stream::StreamExt::next(&mut stream).await {
255 match result {
256 Ok(request) => {
257 if !request.type_url.is_empty() && request.type_url != TypeUrl::SECRET {
258 error!(
259 stream = %ctx.id(),
260 expected = TypeUrl::SECRET,
261 got = %request.type_url,
262 "invalid type URL for delta SDS"
263 );
264 continue;
265 }
266
267 if node_hash.is_none() {
268 if let Some(ref node) = request.node {
269 let hash = NodeHash::from_id(&node.id);
270 ctx.set_node(node.id.clone(), hash);
271 node_hash = Some(hash);
272 }
273 }
274
275 let hash = match node_hash {
276 Some(h) => h,
277 None => {
278 error!(
279 stream = %ctx.id(),
280 "first delta request missing required node information"
281 );
282 let _ = tx
283 .send(Err(Status::invalid_argument(
284 "first request must include node information",
285 )))
286 .await;
287 break;
288 }
289 };
290
291 if !request.response_nonce.is_empty() {
292 if let Some(ref err) = request.error_detail {
293 service.delta_handler.handle_nack(
294 &ctx,
295 TypeUrl::SECRET.into(),
296 &request.response_nonce,
297 &err.message,
298 );
299 } else {
300 service.delta_handler.handle_ack(
301 &ctx,
302 TypeUrl::SECRET.into(),
303 &request.response_nonce,
304 );
305 }
306 }
307
308 match service.delta_handler.process_request(
309 &ctx,
310 TypeUrl::SECRET.into(),
311 &mut client_state,
312 request.resource_names_subscribe,
313 request.resource_names_unsubscribe,
314 hash,
315 ) {
316 Ok(Some(response)) => match delta_response_to_proto(response) {
317 Ok(proto_response) => {
318 if tx.send(Ok(proto_response)).await.is_err() {
319 break;
320 }
321 }
322 Err(e) => {
323 error!(stream = %ctx.id(), error = %e, "failed to encode delta response");
324 let _ = tx.send(Err(e)).await;
325 break;
326 }
327 },
328 Ok(None) => {}
329 Err(e) => {
330 error!(stream = %ctx.id(), error = %e, "delta SDS request failed");
331 break;
332 }
333 }
334 }
335 Err(e) => {
336 error!(stream = %ctx.id(), error = %e, "delta stream error");
337 break;
338 }
339 }
340 }
341
342 info!(stream = %ctx.id(), "Delta SDS stream ended");
343 drop(tx);
344 });
345
346 Ok(Response::new(ReceiverStream::new(rx)))
347 }
348
349 #[instrument(skip(self, request), name = "sds_fetch")]
350 async fn fetch_secrets(
351 &self,
352 request: Request<DiscoveryRequest>,
353 ) -> Result<Response<DiscoveryResponse>, Status> {
354 let request = request.into_inner();
355 let ctx = StreamContext::new();
356
357 let node_hash = request
358 .node
359 .as_ref()
360 .map(|n| NodeHash::from_id(&n.id))
361 .ok_or_else(|| Status::invalid_argument("node is required"))?;
362
363 debug!(node = ?node_hash, "SDS fetch request");
364
365 let response = self
366 .sotw_handler
367 .process_request(
368 &ctx,
369 TypeUrl::SECRET.into(),
370 &request.version_info,
371 &request.resource_names,
372 node_hash,
373 )
374 .map_err(|e| Status::internal(e.to_string()))?
375 .ok_or_else(|| Status::not_found("no secrets available"))?;
376
377 Ok(Response::new(self.convert_response(response)?))
378 }
379}
380
381#[cfg(test)]
382mod tests {
383 use super::*;
384
385 #[test]
386 fn sds_service_creation() {
387 let cache = Arc::new(ShardedCache::new());
388 let registry = Arc::new(ResourceRegistry::new());
389 let _service = SdsService::new(cache, registry);
390 }
391
392 #[test]
393 fn sds_type_url() {
394 assert_eq!(SdsService::type_url(), TypeUrl::SECRET);
395 }
396}