Skip to content

Commit 44d6f8f

Browse files
committed
ROX-33197: implement periodic scanning of host paths
The purpose of this scan is to mitigate any inconsistencies in the inode tracking we might get from missed events on the kernel and will also update the maps on a configuration change. The existing scan method is extended to first cleanup any items that are no longer monitored or don't exist anymore on the host, then directories that are to be monitored are scanned and the inode maps are populated from the files found. The scan interval is set to 30 seconds by default, but can be modified through the regular configuration methods (file, env var, CLI arg). Unit tests for parsing of this new configuration value have been added. Some basic metrics have been added to the host_scanner module, these will keep track of: - Events coming through that get their host path filled in. - Elements being updated/removed during a scan. Finally, some integration tests that changed the monitored path are fixed. With these changes, the scanning of new monitored paths are causing the host path on those tests to be populated correctly, showing the scan works as expected.
1 parent b359ef6 commit 44d6f8f

File tree

8 files changed

+334
-10
lines changed

8 files changed

+334
-10
lines changed

fact/src/config/mod.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use std::{
44
path::{Path, PathBuf},
55
str::FromStr,
66
sync::LazyLock,
7+
time::Duration,
78
};
89

910
use anyhow::{bail, Context};
@@ -31,6 +32,7 @@ pub struct FactConfig {
3132
json: Option<bool>,
3233
ringbuf_size: Option<u32>,
3334
hotreload: Option<bool>,
35+
scan_interval: Option<Duration>,
3436
}
3537

3638
impl FactConfig {
@@ -95,6 +97,10 @@ impl FactConfig {
9597
if let Some(hotreload) = from.hotreload {
9698
self.hotreload = Some(hotreload);
9799
}
100+
101+
if let Some(scan_interval) = from.scan_interval {
102+
self.scan_interval = Some(scan_interval);
103+
}
98104
}
99105

100106
pub fn paths(&self) -> &[PathBuf] {
@@ -117,6 +123,10 @@ impl FactConfig {
117123
self.hotreload.unwrap_or(true)
118124
}
119125

126+
pub fn scan_interval(&self) -> Duration {
127+
self.scan_interval.unwrap_or(Duration::from_secs(30))
128+
}
129+
120130
#[cfg(test)]
121131
pub fn set_paths(&mut self, paths: Vec<PathBuf>) {
122132
self.paths = Some(paths);
@@ -216,6 +226,21 @@ impl TryFrom<Vec<Yaml>> for FactConfig {
216226
};
217227
config.hotreload = Some(hotreload);
218228
}
229+
"scan_interval" => {
230+
if let Some(scan_interval) = v.as_f64() {
231+
if scan_interval <= 0.0 {
232+
bail!("invalid scan_interval: {scan_interval}");
233+
}
234+
config.scan_interval = Some(Duration::from_secs_f64(scan_interval));
235+
} else if let Some(scan_interval) = v.as_i64() {
236+
if scan_interval <= 0 {
237+
bail!("invalid scan_interval: {scan_interval}");
238+
}
239+
config.scan_interval = Some(Duration::from_secs(scan_interval as u64))
240+
} else {
241+
bail!("scan_interval field has incorrect type: {v:?}");
242+
}
243+
}
219244
name => bail!("Invalid field '{name}' with value: {v:?}"),
220245
}
221246
}
@@ -429,6 +454,15 @@ pub struct FactCli {
429454
hotreload: bool,
430455
#[arg(long, overrides_with = "hotreload", hide(true))]
431456
no_hotreload: bool,
457+
458+
/// Interval at which scanning of monitored directories should
459+
/// happen in seconds.
460+
///
461+
/// The seconds can use a decimal point for fractions of seconds.
462+
///
463+
/// Default value is 30 seconds
464+
#[arg(long, short, env = "FACT_SCAN_INTERVAL")]
465+
scan_interval: Option<f64>,
432466
}
433467

434468
impl FactCli {
@@ -448,6 +482,7 @@ impl FactCli {
448482
json: resolve_bool_arg(self.json, self.no_json),
449483
ringbuf_size: self.ringbuf_size,
450484
hotreload: resolve_bool_arg(self.hotreload, self.no_hotreload),
485+
scan_interval: self.scan_interval.map(Duration::from_secs_f64),
451486
}
452487
}
453488
}

fact/src/config/reloader.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ pub struct Reloader {
1717
grpc: watch::Sender<GrpcConfig>,
1818
paths: watch::Sender<Vec<PathBuf>>,
1919
files: HashMap<&'static str, i64>,
20+
scan_interval: watch::Sender<Duration>,
2021
trigger: Arc<Notify>,
2122
}
2223

@@ -75,6 +76,12 @@ impl Reloader {
7576
self.paths.subscribe()
7677
}
7778

79+
/// Subscribe to get notifications when scan_interval configuration
80+
/// is changed.
81+
pub fn scan_interval(&self) -> watch::Receiver<Duration> {
82+
self.scan_interval.subscribe()
83+
}
84+
7885
/// Get a reference to the internal trigger for manual reloading of
7986
/// configuration.
8087
///
@@ -171,6 +178,17 @@ impl Reloader {
171178
}
172179
});
173180

181+
self.scan_interval.send_if_modified(|old| {
182+
let new = new.scan_interval();
183+
if *old != new {
184+
debug!("Sending new scan interval configuration...");
185+
*old = new;
186+
true
187+
} else {
188+
false
189+
}
190+
});
191+
174192
if self.config.hotreload() != new.hotreload() {
175193
warn!("Changes to the hotreload field only take effect on startup");
176194
}
@@ -203,13 +221,15 @@ impl From<FactConfig> for Reloader {
203221
let (endpoint, _) = watch::channel(config.endpoint.clone());
204222
let (grpc, _) = watch::channel(config.grpc.clone());
205223
let (paths, _) = watch::channel(config.paths().to_vec());
224+
let (scan_interval, _) = watch::channel(config.scan_interval());
206225
let trigger = Arc::new(Notify::new());
207226

208227
Reloader {
209228
config,
210229
endpoint,
211230
grpc,
212231
paths,
232+
scan_interval,
213233
files,
214234
trigger,
215235
}

fact/src/config/tests.rs

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,20 @@ fn parsing() {
203203
..Default::default()
204204
},
205205
),
206+
(
207+
"scan_interval: 60",
208+
FactConfig {
209+
scan_interval: Some(Duration::from_secs(60)),
210+
..Default::default()
211+
},
212+
),
213+
(
214+
"scan_interval: 30.5",
215+
FactConfig {
216+
scan_interval: Some(Duration::from_secs_f64(30.5)),
217+
..Default::default()
218+
},
219+
),
206220
(
207221
r#"
208222
paths:
@@ -218,6 +232,7 @@ fn parsing() {
218232
json: false
219233
ringbuf_size: 8192
220234
hotreload: false
235+
scan_interval: 60
221236
"#,
222237
FactConfig {
223238
paths: Some(vec![PathBuf::from("/etc")]),
@@ -234,6 +249,7 @@ fn parsing() {
234249
json: Some(false),
235250
ringbuf_size: Some(8192),
236251
hotreload: Some(false),
252+
scan_interval: Some(Duration::from_secs(60)),
237253
},
238254
),
239255
];
@@ -387,6 +403,14 @@ paths:
387403
"hotreload: 4",
388404
"hotreload field has incorrect type: Integer(4)",
389405
),
406+
(
407+
"scan_interval: true",
408+
"scan_interval field has incorrect type: Boolean(true)",
409+
),
410+
("scan_interval: 0", "invalid scan_interval: 0"),
411+
("scan_interval: 0.0", "invalid scan_interval: 0"),
412+
("scan_interval: -128", "invalid scan_interval: -128"),
413+
("scan_interval: -128.5", "invalid scan_interval: -128.5"),
390414
("unknown:", "Invalid field 'unknown' with value: Null"),
391415
];
392416
for (input, expected) in tests {
@@ -726,6 +750,36 @@ fn update() {
726750
..Default::default()
727751
},
728752
),
753+
(
754+
"ringbuf_size: 16384",
755+
FactConfig::default(),
756+
FactConfig {
757+
ringbuf_size: Some(16384),
758+
..Default::default()
759+
},
760+
),
761+
(
762+
"ringbuf_size: 16384",
763+
FactConfig {
764+
ringbuf_size: Some(8192),
765+
..Default::default()
766+
},
767+
FactConfig {
768+
ringbuf_size: Some(16384),
769+
..Default::default()
770+
},
771+
),
772+
(
773+
"ringbuf_size: 16384",
774+
FactConfig {
775+
ringbuf_size: Some(16384),
776+
..Default::default()
777+
},
778+
FactConfig {
779+
ringbuf_size: Some(16384),
780+
..Default::default()
781+
},
782+
),
729783
(
730784
"hotreload: false",
731785
FactConfig::default(),
@@ -756,6 +810,55 @@ fn update() {
756810
..Default::default()
757811
},
758812
),
813+
(
814+
"scan_interval: 60",
815+
FactConfig::default(),
816+
FactConfig {
817+
scan_interval: Some(Duration::from_secs(60)),
818+
..Default::default()
819+
},
820+
),
821+
(
822+
"scan_interval: 0.5",
823+
FactConfig::default(),
824+
FactConfig {
825+
scan_interval: Some(Duration::from_secs_f64(0.5)),
826+
..Default::default()
827+
},
828+
),
829+
(
830+
"scan_interval: 60",
831+
FactConfig {
832+
scan_interval: Some(Duration::from_secs(30)),
833+
..Default::default()
834+
},
835+
FactConfig {
836+
scan_interval: Some(Duration::from_secs(60)),
837+
..Default::default()
838+
},
839+
),
840+
(
841+
"scan_interval: 25.5",
842+
FactConfig {
843+
scan_interval: Some(Duration::from_secs(30)),
844+
..Default::default()
845+
},
846+
FactConfig {
847+
scan_interval: Some(Duration::from_secs_f64(25.5)),
848+
..Default::default()
849+
},
850+
),
851+
(
852+
"scan_interval: 60",
853+
FactConfig {
854+
scan_interval: Some(Duration::from_secs(60)),
855+
..Default::default()
856+
},
857+
FactConfig {
858+
scan_interval: Some(Duration::from_secs(60)),
859+
..Default::default()
860+
},
861+
),
759862
(
760863
r#"
761864
paths:
@@ -771,6 +874,7 @@ fn update() {
771874
json: false
772875
ringbuf_size: 16384
773876
hotreload: false
877+
scan_interval: 60
774878
"#,
775879
FactConfig {
776880
paths: Some(vec![PathBuf::from("/etc"), PathBuf::from("/bin")]),
@@ -787,6 +891,7 @@ fn update() {
787891
json: Some(true),
788892
ringbuf_size: Some(64),
789893
hotreload: Some(true),
894+
scan_interval: Some(Duration::from_secs(30)),
790895
},
791896
FactConfig {
792897
paths: Some(vec![PathBuf::from("/etc")]),
@@ -803,6 +908,7 @@ fn update() {
803908
json: Some(false),
804909
ringbuf_size: Some(16384),
805910
hotreload: Some(false),
911+
scan_interval: Some(Duration::from_secs(60)),
806912
},
807913
),
808914
];

0 commit comments

Comments
 (0)