master
rs 103 lines 4.68 KB
Raw
1 //! Regression test for indexing journals that contain the otel-plugin's
2 //! `ND_REMAPPING=1` bookkeeping field.
3 //!
4 //! `FileIndexer::collect_remapping_entry_offsets` used to hold a
5 //! `ValueGuard<DataObject>` (which keeps the journal file's window-manager
6 //! borrow alive) while calling `InlinedCursor::collect_offsets`. When the
7 //! `ND_REMAPPING=1` data object is referenced by more than one entry,
8 //! `collect_offsets` walks the entry-array chain, re-borrows the window
9 //! manager, and fails with `JournalError::ValueGuardInUse` ("previous object
10 //! is still in use"). That aborted indexing of every otel-plugin journal.
11 //!
12 //! Regular systemd journals do not contain the `ND_REMAPPING` field, so the
13 //! existing tests never exercised this path. This test reproduces the
14 //! multi-entry shape that triggered the bug and asserts indexing succeeds.
15
16 use journal_common::Seconds;
17 use journal_core::field_map::REMAPPING_MARKER;
18 use journal_core::file::{JournalFile, JournalFileOptions, JournalWriter};
19 use journal_core::repository::File;
20 use journal_index::{FieldName, FileIndexer};
21 use std::fs;
22 use std::path::PathBuf;
23 use tempfile::TempDir;
24 use uuid::Uuid;
25
26 fn create_test_journal_path(temp_dir: &TempDir) -> PathBuf {
27 let machine_id = Uuid::from_u128(0x12345678_1234_1234_1234_123456789abc);
28 let machine_dir = temp_dir.path().join(machine_id.to_string());
29 fs::create_dir_all(&machine_dir).expect("create machine dir");
30 machine_dir.join("system.journal")
31 }
32
33 /// Build a journal that mirrors an otel-plugin journal: a handful of
34 /// `ND_REMAPPING=1` bookkeeping entries plus normal log entries.
35 ///
36 /// The bookkeeping entries all share one `ND_REMAPPING=1` DATA object (journald
37 /// dedups by payload), so with `num_marker_entries >= 2` that object's
38 /// entry-array chain is non-empty — the shape that made
39 /// `collect_remapping_entry_offsets` re-borrow the window manager and fail.
40 ///
41 /// The normal entries (which carry no marker) are excluded neither from the
42 /// histogram nor the bitmaps, so a fixed indexer produces a non-empty index
43 /// instead of erroring with `EmptyHistogramInput`.
44 fn create_remapping_journal(num_marker_entries: u64, num_real_entries: u64) -> (TempDir, File) {
45 let temp_dir = TempDir::new().expect("temp dir");
46 let journal_path = create_test_journal_path(&temp_dir);
47 let file = File::from_path(&journal_path).expect("File::from_path");
48
49 let machine_id = Uuid::from_u128(0x12345678_1234_1234_1234_123456789abc);
50 let boot_id = Uuid::from_u128(0x11111111_1111_1111_1111_111111111111);
51 let seqnum_id = Uuid::from_u128(0x22222222_2222_2222_2222_222222222222);
52
53 let options = JournalFileOptions::new(machine_id, boot_id, seqnum_id);
54 let mut journal_file = JournalFile::create(&file, options).expect("create journal");
55 let mut writer = JournalWriter::new(&mut journal_file, 1, boot_id).expect("writer");
56
57 let mut timestamp = 1_000_000u64; // microseconds, strictly increasing
58
59 // Bookkeeping entries: timestamp + the shared ND_REMAPPING=1 marker.
60 for _ in 0..num_marker_entries {
61 timestamp += 1;
62 let ts_field = format!("_SOURCE_REALTIME_TIMESTAMP={timestamp}").into_bytes();
63 let items: Vec<&[u8]> = vec![ts_field.as_slice(), REMAPPING_MARKER];
64 writer
65 .add_entry(&mut journal_file, &items, timestamp, timestamp)
66 .expect("add marker entry");
67 }
68
69 // Real log entries: no marker, so they survive into the index.
70 for i in 0..num_real_entries {
71 timestamp += 1;
72 let ts_field = format!("_SOURCE_REALTIME_TIMESTAMP={timestamp}").into_bytes();
73 let message = format!("MESSAGE=log entry {i}").into_bytes();
74 let items: Vec<&[u8]> = vec![ts_field.as_slice(), message.as_slice()];
75 writer
76 .add_entry(&mut journal_file, &items, timestamp, timestamp)
77 .expect("add real entry");
78 }
79
80 (temp_dir, file)
81 }
82
83 /// Indexing a journal whose `ND_REMAPPING=1` object spans multiple entries
84 /// must not fail with `ValueGuardInUse`.
85 #[test]
86 fn index_journal_with_multi_entry_remapping_marker() {
87 // Two marker entries already produce an entry-array chain (one inlined
88 // offset + one array object), the minimal shape that triggered the bug;
89 // use a few more to be robust against array-layout changes.
90 let (_temp_dir, file) = create_remapping_journal(3, 5);
91
92 let mut indexer = FileIndexer::default();
93 let source = FieldName::new("_SOURCE_REALTIME_TIMESTAMP").unwrap();
94 let message = FieldName::new("MESSAGE").unwrap();
95
96 let result = indexer.index(&file, Some(&source), &[message], Seconds(15));
97
98 assert!(
99 result.is_ok(),
100 "indexing a journal with a multi-entry ND_REMAPPING marker must succeed, got: {:?}",
101 result.err()
102 );
103 }