1use std::{
2 collections::{HashMap, HashSet},
3 sync::{Arc, Mutex},
4};
5
6use futures::{Future, FutureExt, future};
7use metrics::Gauge;
8use snafu::Snafu;
9use stream_cancel::Trigger;
10use tokio::{
11 sync::{mpsc, watch},
12 time::{Duration, Instant, interval, sleep_until},
13};
14use tracing::Instrument;
15use vector_lib::{
16 buffers::topology::channel::BufferSender,
17 gauge,
18 internal_event::GaugeName,
19 shutdown::ShutdownSignal,
20 tap::topology::{TapOutput, TapResource, WatchRx, WatchTx},
21 trigger::DisabledTrigger,
22};
23
24use super::{
25 BuiltBuffer, TaskHandle, TaskResult,
26 builder::{self, TopologyPieces, TopologyPiecesBuilder, reload_enrichment_tables},
27 fanout::{ControlChannel, ControlMessage},
28 handle_errors, retain, take_healthchecks,
29 task::{Task, TaskOutput},
30};
31use crate::{
32 config::{ComponentKey, Config, ConfigDiff, HealthcheckOptions, Inputs, OutputId, Resource},
33 event::EventArray,
34 extra_context::ExtraContext,
35 shutdown::SourceShutdownCoordinator,
36 signal::ShutdownError,
37 spawn_named,
38 utilization::UtilizationRegistry,
39};
40
41pub type ShutdownErrorReceiver = mpsc::UnboundedReceiver<ShutdownError>;
42
43#[derive(Debug, Snafu)]
44pub enum ReloadError {
45 #[snafu(display("global options changed: {}", changed_fields.join(", ")))]
46 GlobalOptionsChanged { changed_fields: Vec<String> },
47 #[snafu(display("failed to compute global diff: {}", source))]
48 GlobalDiffFailed { source: serde_json::Error },
49 #[snafu(display("topology build failed"))]
50 TopologyBuildFailed,
51 #[snafu(display("failed to restore previous config"))]
52 FailedToRestore,
53}
54
55#[allow(dead_code)]
56pub struct RunningTopology {
57 inputs: HashMap<ComponentKey, BufferSender<EventArray>>,
58 inputs_tap_metadata: HashMap<ComponentKey, Inputs<OutputId>>,
59 outputs: HashMap<OutputId, ControlChannel>,
60 outputs_tap_metadata: HashMap<ComponentKey, (&'static str, String)>,
61 component_type_names: HashMap<ComponentKey, String>,
62 source_tasks: HashMap<ComponentKey, TaskHandle>,
63 tasks: HashMap<ComponentKey, TaskHandle>,
64 shutdown_coordinator: SourceShutdownCoordinator,
65 detach_triggers: HashMap<ComponentKey, DisabledTrigger>,
66 pub(crate) config: Config,
67 pub(crate) abort_tx: mpsc::UnboundedSender<ShutdownError>,
68 watch: (WatchTx, WatchRx),
69 graceful_shutdown_duration: Option<Duration>,
70 utilization_registry: Option<UtilizationRegistry>,
71 utilization_task: Option<TaskHandle>,
72 utilization_task_shutdown_trigger: Option<Trigger>,
73 metrics_task: Option<TaskHandle>,
74 metrics_task_shutdown_trigger: Option<Trigger>,
75 pending_reload: Option<HashSet<ComponentKey>>,
76 sink_confinement_gauges: HashMap<ComponentKey, Gauge>,
77}
78
79impl RunningTopology {
80 pub fn new(config: Config, abort_tx: mpsc::UnboundedSender<ShutdownError>) -> Self {
81 Self {
82 inputs: HashMap::new(),
83 inputs_tap_metadata: HashMap::new(),
84 outputs: HashMap::new(),
85 outputs_tap_metadata: HashMap::new(),
86 component_type_names: HashMap::new(),
87 shutdown_coordinator: SourceShutdownCoordinator::default(),
88 detach_triggers: HashMap::new(),
89 source_tasks: HashMap::new(),
90 tasks: HashMap::new(),
91 abort_tx,
92 watch: watch::channel(TapResource::default()),
93 graceful_shutdown_duration: config.graceful_shutdown_duration,
94 config,
95 utilization_registry: None,
96 utilization_task: None,
97 utilization_task_shutdown_trigger: None,
98 metrics_task: None,
99 metrics_task_shutdown_trigger: None,
100 pending_reload: None,
101 sink_confinement_gauges: HashMap::new(),
102 }
103 }
104
105 pub const fn config(&self) -> &Config {
107 &self.config
108 }
109
110 pub fn extend_reload_set(&mut self, new_set: HashSet<ComponentKey>) {
113 match &mut self.pending_reload {
114 None => self.pending_reload = Some(new_set.clone()),
115 Some(existing) => existing.extend(new_set),
116 }
117 }
118
119 pub fn watch(&self) -> watch::Receiver<TapResource> {
123 self.watch.1.clone()
124 }
125
126 pub fn sources_finished(&self) -> future::BoxFuture<'static, ()> {
134 self.shutdown_coordinator.shutdown_tripwire()
135 }
136
137 pub fn stop(self) -> impl Future<Output = bool> {
154 let mut wait_handles = Vec::new();
157 let mut check_handles = HashMap::<ComponentKey, Vec<_>>::new();
160
161 let map_closure =
162 |result: Result<TaskResult, tokio::task::JoinError>| matches!(result, Ok(Ok(_)));
163
164 for (key, task) in self.tasks.into_iter().chain(self.source_tasks) {
167 let task = task.map(map_closure).shared();
168
169 wait_handles.push(task.clone());
170 check_handles.entry(key).or_default().push(task);
171 }
172
173 if let Some(utilization_task) = self.utilization_task {
174 wait_handles.push(utilization_task.map(map_closure).shared());
175 }
176
177 if let Some(metrics_task) = self.metrics_task {
178 wait_handles.push(metrics_task.map(map_closure).shared());
179 }
180
181 let deadline = self
183 .graceful_shutdown_duration
184 .map(|grace_period| Instant::now() + grace_period);
185
186 let timeout = if let Some(deadline) = deadline {
187 let mut check_handles2 = check_handles.clone();
191 Box::pin(async move {
192 sleep_until(deadline).await;
193 check_handles2.retain(|_key, handles| {
195 retain(handles, |handle| handle.peek().is_none());
196 !handles.is_empty()
197 });
198 let remaining_components = check_handles2
199 .keys()
200 .map(|item| item.to_string())
201 .collect::<Vec<_>>()
202 .join(", ");
203
204 error!(
205 components = ?remaining_components,
206 message = "Failed to gracefully shut down in time. Killing components.",
207 internal_log_rate_limit = false
208 );
209 false
210 }) as future::BoxFuture<'static, bool>
211 } else {
212 Box::pin(future::pending()) as future::BoxFuture<'static, bool>
213 };
214
215 let mut interval = interval(Duration::from_secs(5));
217 let reporter = async move {
218 loop {
219 interval.tick().await;
220
221 check_handles.retain(|_key, handles| {
223 retain(handles, |handle| handle.peek().is_none());
224 !handles.is_empty()
225 });
226 let remaining_components = check_handles
227 .keys()
228 .map(|item| item.to_string())
229 .collect::<Vec<_>>()
230 .join(", ");
231
232 let (deadline_passed, time_remaining) = match deadline {
233 Some(d) => match d.checked_duration_since(Instant::now()) {
234 Some(remaining) => (false, format!("{} seconds left", remaining.as_secs())),
235 None => (true, "overdue".to_string()),
236 },
237 None => (false, "no time limit".to_string()),
238 };
239
240 info!(
241 remaining_components = ?remaining_components,
242 time_remaining = ?time_remaining,
243 "Shutting down... Waiting on running components."
244 );
245
246 let all_done = check_handles.is_empty();
247
248 if all_done {
249 info!("Shutdown reporter exiting: all components shut down.");
250 break true;
251 } else if deadline_passed {
252 error!(remaining_components = ?remaining_components, "Shutdown reporter: deadline exceeded.");
253 break false;
254 }
255 }
256 };
257
258 let success =
260 futures::future::join_all(wait_handles).map(|results| results.into_iter().all(|ok| ok));
261
262 let shutdown_complete_future = future::select_all(vec![
265 Box::pin(timeout) as future::BoxFuture<'static, bool>,
266 Box::pin(reporter) as future::BoxFuture<'static, bool>,
267 Box::pin(success) as future::BoxFuture<'static, bool>,
268 ])
269 .map(|(graceful, _index, _remaining)| graceful);
270
271 let source_shutdown_complete = self.shutdown_coordinator.shutdown_all(deadline);
273 if let Some(trigger) = self.utilization_task_shutdown_trigger {
274 trigger.cancel();
275 }
276 if let Some(trigger) = self.metrics_task_shutdown_trigger {
277 trigger.cancel();
278 }
279
280 futures::future::join(source_shutdown_complete, shutdown_complete_future)
281 .map(|(_, graceful)| graceful)
282 }
283
284 pub async fn reload_config_and_respawn(
296 &mut self,
297 new_config: Config,
298 extra_context: ExtraContext,
299 ) -> Result<(), ReloadError> {
300 info!("Reloading running topology with new configuration.");
301
302 if self.config.global != new_config.global {
303 return match self.config.global.diff(&new_config.global) {
304 Ok(changed_fields) => Err(ReloadError::GlobalOptionsChanged { changed_fields }),
305 Err(source) => Err(ReloadError::GlobalDiffFailed { source }),
306 };
307 }
308
309 let diff = if let Some(components) = &self.pending_reload {
315 ConfigDiff::new(&self.config, &new_config, components.clone())
316 } else {
317 ConfigDiff::new(&self.config, &new_config, HashSet::new())
318 };
319 let buffers = self.shutdown_diff(&diff, &new_config).await;
320
321 if cfg!(windows) {
325 tokio::time::sleep(Duration::from_millis(200)).await;
327 }
328
329 if let Some(mut new_pieces) = TopologyPiecesBuilder::new(&new_config, &diff)
333 .with_buffers(buffers.clone())
334 .with_extra_context(extra_context.clone())
335 .with_utilization_registry(self.utilization_registry.clone())
336 .build_or_log_errors()
337 .await
338 {
339 if self
343 .run_healthchecks(&diff, &mut new_pieces, new_config.healthchecks)
344 .await
345 {
346 self.connect_diff(&diff, &mut new_pieces).await;
347 self.spawn_diff(&diff, new_pieces);
348 self.config = new_config;
349 self.refresh_confinement_gauges();
350
351 info!("New configuration loaded successfully.");
352
353 return Ok(());
354 }
355 }
356
357 warn!("Failed to completely load new configuration. Restoring old configuration.");
361
362 let diff = diff.flip();
363 if let Some(mut new_pieces) = TopologyPiecesBuilder::new(&self.config, &diff)
364 .with_buffers(buffers)
365 .with_extra_context(extra_context.clone())
366 .with_utilization_registry(self.utilization_registry.clone())
367 .build_or_log_errors()
368 .await
369 && self
370 .run_healthchecks(&diff, &mut new_pieces, self.config.healthchecks)
371 .await
372 {
373 self.connect_diff(&diff, &mut new_pieces).await;
374 self.spawn_diff(&diff, new_pieces);
375 self.refresh_confinement_gauges();
378
379 info!("Old configuration restored successfully.");
380
381 return Err(ReloadError::TopologyBuildFailed);
382 }
383
384 error!(
385 message = "Failed to restore old configuration.",
386 internal_log_rate_limit = false
387 );
388
389 Err(ReloadError::FailedToRestore)
390 }
391
392 pub(crate) async fn reload_enrichment_tables(&self) {
394 reload_enrichment_tables(&self.config).await;
395 }
396
397 pub(crate) async fn run_healthchecks(
398 &mut self,
399 diff: &ConfigDiff,
400 pieces: &mut TopologyPieces,
401 options: HealthcheckOptions,
402 ) -> bool {
403 if options.enabled {
404 let healthchecks = take_healthchecks(diff, pieces)
405 .into_iter()
406 .map(|(_, task)| task);
407 let healthchecks = future::try_join_all(healthchecks);
408
409 info!("Running healthchecks.");
410 if options.require_healthy {
411 let success = healthchecks.await;
412
413 if success.is_ok() {
414 info!("All healthchecks passed.");
415 true
416 } else {
417 error!(
418 message = "Sinks unhealthy.",
419 internal_log_rate_limit = false
420 );
421 false
422 }
423 } else {
424 tokio::spawn(healthchecks);
425 true
426 }
427 } else {
428 true
429 }
430 }
431
432 async fn shutdown_diff(
436 &mut self,
437 diff: &ConfigDiff,
438 new_config: &Config,
439 ) -> HashMap<ComponentKey, BuiltBuffer> {
440 if diff.sources.any_changed_or_removed()
443 || diff.enrichment_tables.sources.any_changed_or_removed()
444 {
445 let timeout = Duration::from_secs(30);
446 let mut source_shutdown_handles = Vec::new();
447
448 let deadline = Instant::now() + timeout;
449 for key in diff
450 .sources
451 .to_remove
452 .iter()
453 .chain(diff.enrichment_tables.sources.to_remove.iter())
454 {
455 debug!(component_id = %key, "Removing source.");
456
457 let previous = self.tasks.remove(key).unwrap();
458 drop(previous); self.remove_outputs(key);
461 source_shutdown_handles
462 .push(self.shutdown_coordinator.shutdown_source(key, deadline));
463 }
464
465 for key in diff
466 .sources
467 .to_change
468 .iter()
469 .chain(diff.enrichment_tables.sources.to_change.iter())
470 {
471 debug!(component_id = %key, "Changing source.");
472
473 self.remove_outputs(key);
474 source_shutdown_handles
475 .push(self.shutdown_coordinator.shutdown_source(key, deadline));
476 }
477
478 debug!(
479 "Waiting for up to {} seconds for source(s) to finish shutting down.",
480 timeout.as_secs()
481 );
482 futures::future::join_all(source_shutdown_handles).await;
483
484 for key in diff.sources.removed_and_changed() {
486 if let Some(task) = self.source_tasks.remove(key) {
487 task.await.unwrap().unwrap();
488 }
489 }
490 }
491
492 for key in &diff.transforms.to_remove {
500 debug!(component_id = %key, "Removing transform.");
501
502 let previous = self.tasks.remove(key).unwrap();
503 drop(previous); self.remove_inputs(key, diff, new_config).await;
506 self.remove_outputs(key);
507
508 if let Some(registry) = self.utilization_registry.as_ref() {
509 registry.remove_component(key);
510 }
511 }
512
513 for key in &diff.transforms.to_change {
514 debug!(component_id = %key, "Changing transform.");
515
516 self.remove_inputs(key, diff, new_config).await;
517 self.remove_outputs(key);
518 }
519
520 let removed_table_sinks = diff
526 .enrichment_tables
527 .sinks
528 .removed_and_changed()
529 .map(|key| {
530 (
531 key.clone(),
532 enrichment_table_sink_resources(&self.config, key),
533 )
534 })
535 .collect::<Vec<_>>();
536 let remove_sink = diff
537 .sinks
538 .removed_and_changed()
539 .map(|key| {
540 (
541 key,
542 self.config
543 .sink(key)
544 .map(|s| s.resources(key))
545 .unwrap_or_default(),
546 )
547 })
548 .chain(removed_table_sinks.iter().map(|(k, s)| (k, s.clone())));
549 let add_source = diff
550 .sources
551 .changed_and_added()
552 .map(|key| (key, new_config.source(key).unwrap().inner.resources()));
553 let added_table_sinks = diff
554 .enrichment_tables
555 .sinks
556 .changed_and_added()
557 .map(|key| {
558 (
559 key.clone(),
560 enrichment_table_sink_resources(new_config, key),
561 )
562 })
563 .collect::<Vec<_>>();
564 let add_sink = diff
565 .sinks
566 .changed_and_added()
567 .map(|key| {
568 (
569 key,
570 new_config
571 .sink(key)
572 .map(|s| s.resources(key))
573 .unwrap_or_default(),
574 )
575 })
576 .chain(added_table_sinks.iter().map(|(k, s)| (k, s.clone())));
577 let conflicts = Resource::conflicts(
578 remove_sink.map(|(key, value)| ((true, key), value)).chain(
579 add_sink
580 .chain(add_source)
581 .map(|(key, value)| ((false, key), value)),
582 ),
583 )
584 .into_values()
585 .flatten()
586 .collect::<HashSet<_>>();
587 let conflicting_sinks = conflicts
589 .into_iter()
590 .filter(|&(existing_sink, _)| existing_sink)
591 .map(|(_, key)| key.clone());
592
593 let reuse_buffers = diff
595 .sinks
596 .to_change
597 .iter()
598 .chain(diff.enrichment_tables.sinks.to_change.iter())
599 .filter(|&key| {
600 if diff.components_to_reload.contains(key) {
601 return false;
602 }
603 self.config
604 .sink(key)
605 .map(|s| s.buffer.clone())
606 .or_else(|| enrichment_table_sink_buffer(&self.config, key))
607 == new_config
608 .sink(key)
609 .map(|s| s.buffer.clone())
610 .or_else(|| enrichment_table_sink_buffer(new_config, key))
611 })
612 .cloned()
613 .collect::<HashSet<_>>();
614
615 let changed_disk_buffer_sinks = diff
620 .sinks
621 .to_change
622 .iter()
623 .filter(|key| {
624 !reuse_buffers.contains(*key)
625 && (self
626 .config
627 .sink(key)
628 .is_some_and(|s| s.buffer.has_disk_stage())
629 || enrichment_table_sink_buffer(&self.config, key)
630 .is_some_and(|buffer| buffer.has_disk_stage()))
631 })
632 .cloned()
633 .collect::<HashSet<_>>();
634
635 let wait_for_sinks = conflicting_sinks
636 .chain(reuse_buffers.iter().cloned())
637 .chain(changed_disk_buffer_sinks.iter().cloned())
638 .collect::<HashSet<_>>();
639
640 let removed_sinks = diff
642 .sinks
643 .to_remove
644 .iter()
645 .chain(diff.enrichment_tables.sinks.to_remove.iter())
646 .collect::<Vec<_>>();
647 for key in &removed_sinks {
648 debug!(component_id = %key, "Removing sink.");
649 self.remove_inputs(key, diff, new_config).await;
650
651 if let Some(registry) = self.utilization_registry.as_ref() {
652 registry.remove_component(key);
653 }
654 }
655
656 let mut buffer_tx = HashMap::new();
659
660 let sinks_to_change = diff
661 .sinks
662 .to_change
663 .iter()
664 .chain(diff.enrichment_tables.sinks.to_change.iter())
665 .collect::<Vec<_>>();
666
667 for key in &sinks_to_change {
668 debug!(component_id = %key, "Changing sink.");
669 if reuse_buffers.contains(key) || changed_disk_buffer_sinks.contains(key) {
670 self.detach_triggers
671 .remove(key)
672 .unwrap()
673 .into_inner()
674 .cancel();
675
676 if reuse_buffers.contains(key) {
677 buffer_tx.insert((*key).clone(), self.inputs.get(key).unwrap().clone());
688 }
689 }
690 self.remove_inputs(key, diff, new_config).await;
691 }
692
693 for key in &removed_sinks {
700 let previous = self.tasks.remove(key).unwrap();
701 if wait_for_sinks.contains(key) {
702 debug!(message = "Waiting for sink to shutdown.", component_id = %key);
703 previous.await.unwrap().unwrap();
704 } else {
705 drop(previous); }
707 }
708
709 let mut buffers = HashMap::<ComponentKey, BuiltBuffer>::new();
710 for key in &sinks_to_change {
711 if wait_for_sinks.contains(key) {
712 let previous = self.tasks.remove(key).unwrap();
713 debug!(message = "Waiting for sink to shutdown.", component_id = %key);
714 let buffer = previous.await.unwrap().unwrap();
715
716 if reuse_buffers.contains(key) {
717 let tx = buffer_tx.remove(key).unwrap();
725 let rx = match buffer {
726 TaskOutput::Sink(rx) => rx.into_inner(),
727 _ => unreachable!(),
728 };
729
730 buffers.insert((*key).clone(), (tx, Arc::new(Mutex::new(Some(rx)))));
731 }
732 }
733 }
734
735 buffers
736 }
737
738 pub(crate) async fn connect_diff(
740 &mut self,
741 diff: &ConfigDiff,
742 new_pieces: &mut TopologyPieces,
743 ) {
744 debug!("Connecting changed/added component(s).");
745
746 if !self.watch.0.is_closed() {
748 for key in &diff.sources.to_remove {
749 self.outputs_tap_metadata.remove(key);
751 self.component_type_names.remove(key);
752 }
753
754 for key in &diff.transforms.to_remove {
755 self.outputs_tap_metadata.remove(key);
757 self.inputs_tap_metadata.remove(key);
758 self.component_type_names.remove(key);
759 }
760
761 for key in &diff.sinks.to_remove {
762 self.inputs_tap_metadata.remove(key);
764 self.component_type_names.remove(key);
765 }
766
767 for key in &diff.enrichment_tables.sinks.to_remove {
768 self.inputs_tap_metadata.remove(key);
770 self.component_type_names.remove(key);
771 }
772
773 for key in &diff.enrichment_tables.sources.to_remove {
774 self.outputs_tap_metadata.remove(key);
776 self.component_type_names.remove(key);
777 }
778
779 for key in diff.sources.changed_and_added() {
780 if let Some(task) = new_pieces.tasks.get(key) {
781 let typetag = task.typetag().to_string();
782 self.outputs_tap_metadata
783 .insert(key.clone(), ("source", typetag.clone()));
784 self.component_type_names.insert(key.clone(), typetag);
785 }
786 }
787
788 for key in diff.enrichment_tables.sources.changed_and_added() {
789 if let Some(task) = new_pieces.tasks.get(key) {
790 self.outputs_tap_metadata
791 .insert(key.clone(), ("source", task.typetag().to_string()));
792 self.component_type_names
793 .insert(key.clone(), task.typetag().to_string());
794 }
795 }
796
797 for key in diff.transforms.changed_and_added() {
798 if let Some(task) = new_pieces.tasks.get(key) {
799 let typetag = task.typetag().to_string();
800 self.outputs_tap_metadata
801 .insert(key.clone(), ("transform", typetag.clone()));
802 self.component_type_names.insert(key.clone(), typetag);
803 }
804 }
805
806 for key in diff.sinks.changed_and_added() {
807 if let Some(task) = new_pieces.tasks.get(key) {
808 self.component_type_names
809 .insert(key.clone(), task.typetag().to_string());
810 }
811 }
812
813 for key in diff.enrichment_tables.sinks.changed_and_added() {
814 if let Some(task) = new_pieces.tasks.get(key) {
815 self.component_type_names
816 .insert(key.clone(), task.typetag().to_string());
817 }
818 }
819
820 for (key, input) in &new_pieces.inputs {
821 self.inputs_tap_metadata
822 .insert(key.clone(), input.1.clone());
823 }
824 }
825
826 for key in diff.sources.changed_and_added() {
829 debug!(component_id = %key, "Configuring outputs for source.");
830 self.setup_outputs(key, new_pieces).await;
831 }
832
833 let added_changed_table_sources: Vec<ComponentKey> = diff
834 .enrichment_tables
835 .sources
836 .changed_and_added()
837 .cloned()
838 .collect();
839 for key in &added_changed_table_sources {
840 debug!(component_id = %key, "Connecting outputs for enrichment table source.");
841 self.setup_outputs(key, new_pieces).await;
842 }
843
844 for key in diff.transforms.changed_and_added() {
847 debug!(component_id = %key, "Configuring outputs for transform.");
848 self.setup_outputs(key, new_pieces).await;
849 }
850
851 for key in diff.transforms.changed_and_added() {
854 debug!(component_id = %key, "Connecting inputs for transform.");
855 self.setup_inputs(key, diff, new_pieces).await;
856 }
857
858 for key in diff.sinks.changed_and_added() {
860 debug!(component_id = %key, "Connecting inputs for sink.");
861 self.setup_inputs(key, diff, new_pieces).await;
862 }
863 let added_changed_tables: Vec<ComponentKey> = diff
864 .enrichment_tables
865 .sinks
866 .changed_and_added()
867 .cloned()
868 .collect();
869 for key in &added_changed_tables {
870 debug!(component_id = %key, "Connecting inputs for enrichment table sink.");
871 self.setup_inputs(key, diff, new_pieces).await;
872 }
873
874 self.reattach_severed_inputs(diff);
885
886 if !self.watch.0.is_closed() {
888 let outputs = self
889 .outputs
890 .clone()
891 .into_iter()
892 .flat_map(|(output_id, control_tx)| {
893 self.outputs_tap_metadata.get(&output_id.component).map(
894 |(component_kind, component_type)| {
895 (
896 TapOutput {
897 output_id,
898 component_kind,
899 component_type: component_type.clone(),
900 },
901 control_tx,
902 )
903 },
904 )
905 })
906 .collect::<HashMap<_, _>>();
907
908 let mut removals = diff.sources.to_remove.clone();
909 removals.extend(diff.transforms.to_remove.iter().cloned());
910 self.watch
911 .0
912 .send(TapResource {
913 outputs,
914 inputs: self.inputs_tap_metadata.clone(),
915 source_keys: diff
916 .sources
917 .changed_and_added()
918 .map(|key| key.to_string())
919 .chain(
920 added_changed_table_sources
921 .iter()
922 .map(|key| key.to_string()),
923 )
924 .collect(),
925 sink_keys: diff
926 .sinks
927 .changed_and_added()
928 .map(|key| key.to_string())
929 .chain(added_changed_tables.iter().map(|key| key.to_string()))
930 .collect(),
931 removals,
934 type_names: self
935 .component_type_names
936 .iter()
937 .map(|(k, v)| (k.to_string(), v.clone()))
938 .collect(),
939 })
940 .expect("Couldn't broadcast config changes.");
941 }
942 }
943
944 async fn setup_outputs(
945 &mut self,
946 key: &ComponentKey,
947 new_pieces: &mut builder::TopologyPieces,
948 ) {
949 let outputs = new_pieces.outputs.remove(key).unwrap();
950 for (port, output) in outputs {
951 debug!(component_id = %key, output_id = ?port, "Configuring output for component.");
952
953 let id = OutputId {
954 component: key.clone(),
955 port,
956 };
957
958 self.outputs.insert(id, output);
959 }
960 }
961
962 async fn setup_inputs(
963 &mut self,
964 key: &ComponentKey,
965 diff: &ConfigDiff,
966 new_pieces: &mut builder::TopologyPieces,
967 ) {
968 let (tx, inputs) = new_pieces.inputs.remove(key).unwrap();
969
970 let old_inputs = self
971 .config
972 .inputs_for_node(key)
973 .into_iter()
974 .flatten()
975 .cloned()
976 .collect::<HashSet<_>>();
977
978 let new_inputs = inputs.iter().cloned().collect::<HashSet<_>>();
979 let inputs_to_add = &new_inputs - &old_inputs;
980
981 for input in inputs {
982 let output = self.outputs.get_mut(&input).expect("unknown output");
983
984 if diff.contains(&input.component) || inputs_to_add.contains(&input) {
985 debug!(component_id = %key, fanout_id = %input, "Adding component input to fanout.");
989
990 _ = output.send(ControlMessage::Add(key.clone(), tx.clone()));
991 } else {
992 debug!(component_id = %key, fanout_id = %input, "Replacing component input in fanout.");
997
998 _ = output.send(ControlMessage::Replace(key.clone(), tx.clone()));
999 }
1000 }
1001
1002 self.inputs.insert(key.clone(), tx);
1003 new_pieces
1004 .detach_triggers
1005 .remove(key)
1006 .map(|trigger| self.detach_triggers.insert(key.clone(), trigger.into()));
1007 }
1008
1009 fn remove_outputs(&mut self, key: &ComponentKey) {
1010 self.outputs.retain(|id, _output| &id.component != key);
1011 }
1012
1013 async fn remove_inputs(&mut self, key: &ComponentKey, diff: &ConfigDiff, new_config: &Config) {
1014 self.inputs.remove(key);
1015 self.detach_triggers.remove(key);
1016
1017 let old_inputs = self.config.inputs_for_node(key).expect("node exists");
1018 let new_inputs = new_config
1019 .inputs_for_node(key)
1020 .unwrap_or_default()
1021 .iter()
1022 .collect::<HashSet<_>>();
1023
1024 for input in old_inputs {
1025 if let Some(output) = self.outputs.get_mut(input) {
1026 if diff.contains(&input.component)
1027 || diff.is_removed(key)
1028 || !new_inputs.contains(input)
1029 {
1030 debug!(component_id = %key, fanout_id = %input, "Removing component input from fanout.");
1041
1042 _ = output.send(ControlMessage::Remove(key.clone()));
1043 } else {
1044 debug!(component_id = %key, fanout_id = %input, "Pausing component input in fanout.");
1048
1049 _ = output.send(ControlMessage::Pause(key.clone()));
1050 }
1051 }
1052 }
1053 }
1054
1055 fn reattach_severed_inputs(&mut self, diff: &ConfigDiff) {
1056 let unchanged_transforms = self
1057 .config
1058 .transforms()
1059 .filter(|(key, _)| !diff.transforms.contains(key));
1060 for (transform_key, transform) in unchanged_transforms {
1061 let changed_outputs = get_changed_outputs(diff, transform.inputs.clone());
1062 for output_id in changed_outputs {
1063 debug!(component_id = %transform_key, fanout_id = %output_id.component, "Reattaching component input to fanout.");
1064
1065 let input = self.inputs.get(transform_key).cloned().unwrap();
1066 let output = self.outputs.get_mut(&output_id).unwrap();
1067 _ = output.send(ControlMessage::Add(transform_key.clone(), input));
1068 }
1069 }
1070
1071 let unchanged_table_sinks = self
1072 .config
1073 .enrichment_tables()
1074 .filter_map(|(key, table)| table.as_sink(key))
1075 .filter(|(key, _)| !diff.enrichment_tables.sinks.contains(key))
1076 .collect::<Vec<_>>();
1077 let unchanged_sinks = self
1078 .config
1079 .sinks()
1080 .filter(|(key, _)| !diff.sinks.contains(key));
1081 for (sink_key, sink) in
1082 unchanged_sinks.chain(unchanged_table_sinks.iter().map(|(k, v)| (k, v)))
1083 {
1084 let changed_outputs = get_changed_outputs(diff, sink.inputs.clone());
1085 for output_id in changed_outputs {
1086 debug!(component_id = %sink_key, fanout_id = %output_id.component, "Reattaching component input to fanout.");
1087
1088 let input = self.inputs.get(sink_key).cloned().unwrap();
1089 let output = self.outputs.get_mut(&output_id).unwrap();
1090 _ = output.send(ControlMessage::Add(sink_key.clone(), input));
1091 }
1092 }
1093 }
1094
1095 fn refresh_confinement_gauges(&mut self) {
1109 let mut gauges = HashMap::with_capacity(self.sink_confinement_gauges.len());
1115 for (key, outer) in self.config.sinks() {
1116 let Some(confinement) = outer.inner.confinement_config() else {
1117 continue;
1118 };
1119 let value = f64::from(confinement.dangerously_allow_unconfined_template_resolution);
1120 let handle = gauge!(
1121 GaugeName::SecurityConfinementDisabled,
1122 "component_kind" => "sink",
1123 "component_id" => key.id().to_string(),
1124 "component_type" => outer.inner.get_component_name(),
1125 );
1126 handle.set(value);
1127 gauges.insert(key.clone(), handle);
1128 }
1129
1130 self.sink_confinement_gauges = gauges;
1134 }
1135
1136 pub(crate) fn spawn_diff(&mut self, diff: &ConfigDiff, mut new_pieces: TopologyPieces) {
1138 for key in &diff.sources.to_change {
1139 debug!(message = "Spawning changed source.", component_id = %key);
1140 self.spawn_source(key, &mut new_pieces);
1141 }
1142
1143 for key in &diff.sources.to_add {
1144 debug!(message = "Spawning new source.", component_id = %key);
1145 self.spawn_source(key, &mut new_pieces);
1146 }
1147
1148 let changed_table_sources: Vec<&ComponentKey> = diff
1149 .enrichment_tables
1150 .sources
1151 .to_change
1152 .iter()
1153 .filter(|k| new_pieces.source_tasks.contains_key(k))
1154 .collect();
1155
1156 let added_table_sources: Vec<&ComponentKey> = diff
1157 .enrichment_tables
1158 .sources
1159 .to_add
1160 .iter()
1161 .filter(|k| new_pieces.source_tasks.contains_key(k))
1162 .collect();
1163
1164 for key in changed_table_sources {
1165 debug!(message = "Spawning changed enrichment table source.", component_id = %key);
1166 self.spawn_source(key, &mut new_pieces);
1167 }
1168
1169 for key in added_table_sources {
1170 debug!(message = "Spawning new enrichment table source.", component_id = %key);
1171 self.spawn_source(key, &mut new_pieces);
1172 }
1173
1174 for key in &diff.transforms.to_change {
1175 debug!(message = "Spawning changed transform.", component_id = %key);
1176 self.spawn_transform(key, &mut new_pieces);
1177 }
1178
1179 for key in &diff.transforms.to_add {
1180 debug!(message = "Spawning new transform.", component_id = %key);
1181 self.spawn_transform(key, &mut new_pieces);
1182 }
1183
1184 for key in &diff.sinks.to_change {
1185 debug!(message = "Spawning changed sink.", component_id = %key);
1186 self.spawn_sink(key, &mut new_pieces);
1187 }
1188
1189 for key in &diff.sinks.to_add {
1190 trace!(message = "Spawning new sink.", component_id = %key);
1191 self.spawn_sink(key, &mut new_pieces);
1192 }
1193
1194 let changed_tables: Vec<&ComponentKey> = diff
1195 .enrichment_tables
1196 .sinks
1197 .to_change
1198 .iter()
1199 .filter(|k| new_pieces.tasks.contains_key(k))
1200 .collect();
1201
1202 let added_tables: Vec<&ComponentKey> = diff
1203 .enrichment_tables
1204 .sinks
1205 .to_add
1206 .iter()
1207 .filter(|k| new_pieces.tasks.contains_key(k))
1208 .collect();
1209
1210 for key in changed_tables {
1211 debug!(message = "Spawning changed enrichment table sink.", component_id = %key);
1212 self.spawn_sink(key, &mut new_pieces);
1213 }
1214
1215 for key in added_tables {
1216 debug!(message = "Spawning enrichment table new sink.", component_id = %key);
1217 self.spawn_sink(key, &mut new_pieces);
1218 }
1219 }
1220
1221 fn spawn_sink(&mut self, key: &ComponentKey, new_pieces: &mut builder::TopologyPieces) {
1222 let task = new_pieces.tasks.remove(key).unwrap();
1223 let span = error_span!(
1224 "sink",
1225 component_kind = "sink",
1226 component_id = %task.id(),
1227 component_type = %task.typetag(),
1228 );
1229
1230 let task_span = span.or_current();
1231 #[cfg(unix)]
1232 if crate::internal_telemetry::allocations::is_allocation_tracing_enabled() {
1233 let group_id = crate::internal_telemetry::allocations::acquire_allocation_group_id(
1234 task.id().to_string(),
1235 "sink".to_string(),
1236 task.typetag().to_string(),
1237 );
1238 debug!(
1239 component_kind = "sink",
1240 component_type = task.typetag(),
1241 component_id = task.id(),
1242 group_id = group_id.as_raw().to_string(),
1243 "Registered new allocation group."
1244 );
1245 group_id.attach_to_span(&task_span);
1246 }
1247
1248 let task_name = format!(">> {} ({})", task.typetag(), task.id());
1249 let task = {
1250 let key = key.clone();
1251 handle_errors(task, self.abort_tx.clone(), |error| {
1252 ShutdownError::SinkAborted { key, error }
1253 })
1254 }
1255 .instrument(task_span);
1256 let spawned = spawn_named(task, task_name.as_ref());
1257 if let Some(previous) = self.tasks.insert(key.clone(), spawned) {
1258 drop(previous); }
1260 }
1261
1262 fn spawn_transform(&mut self, key: &ComponentKey, new_pieces: &mut builder::TopologyPieces) {
1263 let task = new_pieces.tasks.remove(key).unwrap();
1264 let span = error_span!(
1265 "transform",
1266 component_kind = "transform",
1267 component_id = %task.id(),
1268 component_type = %task.typetag(),
1269 );
1270
1271 let task_span = span.or_current();
1272 #[cfg(unix)]
1273 if crate::internal_telemetry::allocations::is_allocation_tracing_enabled() {
1274 let group_id = crate::internal_telemetry::allocations::acquire_allocation_group_id(
1275 task.id().to_string(),
1276 "transform".to_string(),
1277 task.typetag().to_string(),
1278 );
1279 debug!(
1280 component_kind = "transform",
1281 component_type = task.typetag(),
1282 component_id = task.id(),
1283 group_id = group_id.as_raw().to_string(),
1284 "Registered new allocation group."
1285 );
1286 group_id.attach_to_span(&task_span);
1287 }
1288
1289 let task_name = format!(">> {} ({}) >>", task.typetag(), task.id());
1290 let task = {
1291 let key = key.clone();
1292 handle_errors(task, self.abort_tx.clone(), |error| {
1293 ShutdownError::TransformAborted { key, error }
1294 })
1295 }
1296 .instrument(task_span);
1297 let spawned = spawn_named(task, task_name.as_ref());
1298 if let Some(previous) = self.tasks.insert(key.clone(), spawned) {
1299 drop(previous); }
1301 }
1302
1303 fn spawn_source(&mut self, key: &ComponentKey, new_pieces: &mut builder::TopologyPieces) {
1304 let task = new_pieces.tasks.remove(key).unwrap();
1305 let span = error_span!(
1306 "source",
1307 component_kind = "source",
1308 component_id = %task.id(),
1309 component_type = %task.typetag(),
1310 );
1311
1312 let task_span = span.or_current();
1313 #[cfg(unix)]
1314 if crate::internal_telemetry::allocations::is_allocation_tracing_enabled() {
1315 let group_id = crate::internal_telemetry::allocations::acquire_allocation_group_id(
1316 task.id().to_string(),
1317 "source".to_string(),
1318 task.typetag().to_string(),
1319 );
1320
1321 debug!(
1322 component_kind = "source",
1323 component_type = task.typetag(),
1324 component_id = task.id(),
1325 group_id = group_id.as_raw().to_string(),
1326 "Registered new allocation group."
1327 );
1328 group_id.attach_to_span(&task_span);
1329 }
1330
1331 let task_name = format!("{} ({}) >>", task.typetag(), task.id());
1332 let task = {
1333 let key = key.clone();
1334 handle_errors(task, self.abort_tx.clone(), |error| {
1335 ShutdownError::SourceAborted { key, error }
1336 })
1337 }
1338 .instrument(task_span.clone());
1339 let spawned = spawn_named(task, task_name.as_ref());
1340 if let Some(previous) = self.tasks.insert(key.clone(), spawned) {
1341 drop(previous); }
1343
1344 self.shutdown_coordinator
1345 .takeover_source(key, &mut new_pieces.shutdown_coordinator);
1346
1347 let source_task = new_pieces.source_tasks.remove(key).unwrap();
1349 let source_task = {
1350 let key = key.clone();
1351 handle_errors(source_task, self.abort_tx.clone(), |error| {
1352 ShutdownError::SourceAborted { key, error }
1353 })
1354 }
1355 .instrument(task_span);
1356 self.source_tasks
1357 .insert(key.clone(), spawn_named(source_task, task_name.as_ref()));
1358 }
1359
1360 pub async fn start_init_validated(
1361 config: Config,
1362 extra_context: ExtraContext,
1363 ) -> Option<(Self, ShutdownErrorReceiver)> {
1364 let diff = ConfigDiff::initial(&config);
1365 let pieces = TopologyPiecesBuilder::new(&config, &diff)
1366 .with_extra_context(extra_context)
1367 .build_or_log_errors()
1368 .await?;
1369 Self::start_validated(config, diff, pieces).await
1370 }
1371
1372 pub async fn start_validated(
1373 config: Config,
1374 diff: ConfigDiff,
1375 mut pieces: TopologyPieces,
1376 ) -> Option<(Self, ShutdownErrorReceiver)> {
1377 let (abort_tx, abort_rx) = mpsc::unbounded_channel();
1378
1379 let expire_metrics = match (
1380 config.global.expire_metrics,
1381 config.global.expire_metrics_secs,
1382 ) {
1383 (Some(e), None) => {
1384 warn!(
1385 "DEPRECATED: `expire_metrics` setting is deprecated and will be removed in a future version. Use `expire_metrics_secs` instead."
1386 );
1387 if e < Duration::from_secs(0) {
1388 None
1389 } else {
1390 Some(e.as_secs_f64())
1391 }
1392 }
1393 (Some(_), Some(_)) => {
1394 error!(
1395 message = "Cannot set both `expire_metrics` and `expire_metrics_secs`.",
1396 internal_log_rate_limit = false
1397 );
1398 return None;
1399 }
1400 (None, Some(e)) => {
1401 if e < 0f64 {
1402 None
1403 } else {
1404 Some(e)
1405 }
1406 }
1407 (None, None) => Some(300f64),
1408 };
1409
1410 if let Err(error) = crate::metrics::Controller::get()
1411 .expect("Metrics must be initialized")
1412 .set_expiry(
1413 expire_metrics,
1414 config
1415 .global
1416 .expire_metrics_per_metric_set
1417 .clone()
1418 .unwrap_or_default(),
1419 )
1420 {
1421 error!(message = "Invalid metrics expiry.", %error, internal_log_rate_limit = false);
1422 return None;
1423 }
1424
1425 let (utilization_emitter, utilization_registry) = pieces
1426 .utilization
1427 .take()
1428 .expect("Topology is missing the utilization metric emitter!");
1429 let metrics_storage = pieces.metrics_storage.clone();
1430 let metrics_refresh_period = config
1431 .global
1432 .metrics_storage_refresh_period
1433 .map(Duration::from_secs_f64);
1434 let mut running_topology = Self::new(config, abort_tx);
1435
1436 if !running_topology
1437 .run_healthchecks(&diff, &mut pieces, running_topology.config.healthchecks)
1438 .await
1439 {
1440 return None;
1441 }
1442 running_topology.connect_diff(&diff, &mut pieces).await;
1443 running_topology.spawn_diff(&diff, pieces);
1444 running_topology.refresh_confinement_gauges();
1446
1447 let (utilization_task_shutdown_trigger, utilization_shutdown_signal, _) =
1448 ShutdownSignal::new_wired();
1449 running_topology.utilization_registry = Some(utilization_registry.clone());
1450 running_topology.utilization_task_shutdown_trigger =
1451 Some(utilization_task_shutdown_trigger);
1452 running_topology.utilization_task = Some(tokio::spawn(Task::new(
1453 "utilization_heartbeat".into(),
1454 "",
1455 async move {
1456 utilization_emitter
1457 .run_utilization(utilization_shutdown_signal)
1458 .await;
1459 Ok(TaskOutput::Healthcheck)
1460 },
1461 )));
1462 if let Some(metrics_refresh_period) = metrics_refresh_period {
1463 let (metrics_task_shutdown_trigger, metrics_shutdown_signal, _) =
1464 ShutdownSignal::new_wired();
1465 running_topology.metrics_task_shutdown_trigger = Some(metrics_task_shutdown_trigger);
1466 running_topology.metrics_task = Some(tokio::spawn(Task::new(
1467 "metrics_heartbeat".into(),
1468 "",
1469 async move {
1470 metrics_storage
1471 .run_periodic_refresh(metrics_refresh_period, metrics_shutdown_signal)
1472 .await;
1473 Ok(TaskOutput::Healthcheck)
1474 },
1475 )));
1476 }
1477
1478 Some((running_topology, abort_rx))
1479 }
1480}
1481
1482fn get_changed_outputs(diff: &ConfigDiff, output_ids: Inputs<OutputId>) -> Vec<OutputId> {
1490 let producer_destroyed = |key: &ComponentKey| {
1491 diff.sources.to_change.contains(key)
1492 || diff.sources.to_remove.contains(key)
1493 || diff.transforms.to_change.contains(key)
1494 || diff.transforms.to_remove.contains(key)
1495 || diff.enrichment_tables.sources.to_change.contains(key)
1496 || diff.enrichment_tables.sources.to_remove.contains(key)
1497 };
1498 let producer_recreated = |key: &ComponentKey| {
1499 diff.sources.to_change.contains(key)
1500 || diff.sources.to_add.contains(key)
1501 || diff.transforms.to_change.contains(key)
1502 || diff.transforms.to_add.contains(key)
1503 || diff.enrichment_tables.sources.to_change.contains(key)
1504 || diff.enrichment_tables.sources.to_add.contains(key)
1505 };
1506
1507 output_ids
1508 .iter()
1509 .filter(|id| producer_destroyed(&id.component) && producer_recreated(&id.component))
1510 .cloned()
1511 .collect()
1512}
1513
1514fn enrichment_table_sink_resources(config: &Config, sink_key: &ComponentKey) -> Vec<Resource> {
1515 config
1516 .enrichment_tables()
1517 .filter_map(|(table_key, table)| table.as_sink(table_key))
1518 .find(|(key, _)| key == sink_key)
1519 .map(|(key, sink)| sink.resources(&key))
1520 .unwrap_or_default()
1521}
1522
1523fn enrichment_table_sink_buffer(
1524 config: &Config,
1525 sink_key: &ComponentKey,
1526) -> Option<vector_lib::buffers::BufferConfig> {
1527 config
1528 .enrichment_tables()
1529 .filter_map(|(table_key, table)| table.as_sink(table_key))
1530 .find(|(key, _)| key == sink_key)
1531 .map(|(_, sink)| sink.buffer)
1532}