Skip to content

Commit 632931a

Browse files
authored
Merge pull request #1442 from ckyrouac/chunking-xattrs-fix
chunking: Fix specific component file allocation
2 parents 1b0ecca + 8da71a2 commit 632931a

File tree

1 file changed

+195
-7
lines changed

1 file changed

+195
-7
lines changed

crates/ostree-ext/src/chunking.rs

Lines changed: 195 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,17 @@ impl Chunk {
233233
}
234234

235235
impl Chunking {
236+
/// Creates a reverse map from content IDs to checksums
237+
fn create_content_id_map(
238+
map: &IndexMap<String, ContentID>,
239+
) -> IndexMap<ContentID, Vec<&String>> {
240+
let mut rmap = IndexMap::<ContentID, Vec<&String>>::new();
241+
for (checksum, contentid) in map.iter() {
242+
rmap.entry(Rc::clone(contentid)).or_default().push(checksum);
243+
}
244+
rmap
245+
}
246+
236247
/// Generate an initial single chunk.
237248
pub fn new(repo: &ostree::Repo, rev: &str) -> Result<Self> {
238249
// Find the target commit
@@ -310,21 +321,17 @@ impl Chunking {
310321
return Ok(());
311322
}
312323

313-
// Reverses `contentmeta.map` i.e. contentid -> Vec<checksum>
314-
let mut rmap = IndexMap::<ContentID, Vec<&String>>::new();
315-
for (checksum, contentid) in meta.map.iter() {
316-
rmap.entry(Rc::clone(contentid)).or_default().push(checksum);
317-
}
318-
319324
// Create exclusive chunks first if specified
320325
let mut processed_specific_components = BTreeSet::new();
321326
if let Some(specific_meta) = specific_contentmeta {
327+
let specific_map = Self::create_content_id_map(&specific_meta.map);
328+
322329
for component in &specific_meta.sizes {
323330
let mut chunk = Chunk::new(&component.meta.name);
324331
chunk.packages = vec![component.meta.name.to_string()];
325332

326333
// Move all objects belonging to this exclusive component
327-
if let Some(objects) = rmap.get(&component.meta.identifier) {
334+
if let Some(objects) = specific_map.get(&component.meta.identifier) {
328335
for &obj in objects {
329336
self.remainder.move_obj(&mut chunk, obj);
330337
}
@@ -353,6 +360,8 @@ impl Chunking {
353360
.cloned()
354361
.collect();
355362

363+
let rmap = Self::create_content_id_map(&meta.map);
364+
356365
// Process regular components with bin packing if we have remaining layers
357366
if let Some(remaining) = NonZeroU32::new(self.remaining()) {
358367
let start = Instant::now();
@@ -1221,4 +1230,183 @@ mod test {
12211230

12221231
Ok(())
12231232
}
1233+
1234+
#[test]
1235+
fn test_process_mapping_specific_components_contain_correct_objects() -> Result<()> {
1236+
// This test validates that specific components get their own dedicated layers
1237+
// and that their objects are properly isolated from regular package layers
1238+
1239+
// Setup: Create 5 packages
1240+
// - pkg1, pkg2: Will be marked as "specific components" (get their own layers)
1241+
// - pkg3, pkg4, pkg5: Regular packages (will be bin-packed together)
1242+
let packages = [
1243+
(1, 100, 50000), // pkg1 - SPECIFIC COMPONENT
1244+
(2, 200, 40000), // pkg2 - SPECIFIC COMPONENT
1245+
(3, 300, 30000), // pkg3 - regular package
1246+
(4, 400, 20000), // pkg4 - regular package
1247+
(5, 500, 10000), // pkg5 - regular package
1248+
];
1249+
1250+
let (contentmeta, mut system_metadata, mut specific_components_meta, mut chunking) =
1251+
setup_exclusive_test(&packages, 8, None)?;
1252+
1253+
// Create object mappings
1254+
// - system_objects_map: Contains ALL objects in the system (both specific and regular)
1255+
// - specific_components_objects_map: Contains ONLY objects from specific components
1256+
let mut system_objects_map = IndexMap::new();
1257+
let mut specific_components_objects_map = IndexMap::new();
1258+
1259+
// SPECIFIC COMPONENT 1 (pkg1): owns 3 objects
1260+
let pkg1_objects = ["checksum_1_a", "checksum_1_b", "checksum_1_c"];
1261+
for obj in &pkg1_objects {
1262+
system_objects_map.insert(obj.to_string(), contentmeta[0].meta.identifier.clone());
1263+
specific_components_objects_map
1264+
.insert(obj.to_string(), contentmeta[0].meta.identifier.clone());
1265+
}
1266+
1267+
// SPECIFIC COMPONENT 2 (pkg2): owns 2 objects
1268+
let pkg2_objects = ["checksum_2_a", "checksum_2_b"];
1269+
for obj in &pkg2_objects {
1270+
system_objects_map.insert(obj.to_string(), contentmeta[1].meta.identifier.clone());
1271+
specific_components_objects_map
1272+
.insert(obj.to_string(), contentmeta[1].meta.identifier.clone());
1273+
}
1274+
1275+
// REGULAR PACKAGE 1 (pkg3): owns 2 objects
1276+
let pkg3_objects = ["checksum_3_a", "checksum_3_b"];
1277+
for obj in &pkg3_objects {
1278+
system_objects_map.insert(obj.to_string(), contentmeta[2].meta.identifier.clone());
1279+
}
1280+
1281+
// REGULAR PACKAGE 2 (pkg4): owns 1 object
1282+
let pkg4_objects = ["checksum_4_a"];
1283+
for obj in &pkg4_objects {
1284+
system_objects_map.insert(obj.to_string(), contentmeta[3].meta.identifier.clone());
1285+
}
1286+
1287+
// REGULAR PACKAGE 3 (pkg5): owns 3 objects
1288+
let pkg5_objects = ["checksum_5_a", "checksum_5_b", "checksum_5_c"];
1289+
for obj in &pkg5_objects {
1290+
system_objects_map.insert(obj.to_string(), contentmeta[4].meta.identifier.clone());
1291+
}
1292+
1293+
// Set up metadata
1294+
system_metadata.map = system_objects_map;
1295+
specific_components_meta.map = specific_components_objects_map;
1296+
specific_components_meta.sizes = vec![contentmeta[0].clone(), contentmeta[1].clone()];
1297+
1298+
// Initialize: Add ALL objects to the remainder chunk before processing
1299+
// This includes both specific component objects and regular package objects
1300+
// because process_mapping needs to move them from remainder to their final layers
1301+
for (checksum, _) in &system_metadata.map {
1302+
chunking.remainder.content.insert(
1303+
RcStr::from(checksum.as_str()),
1304+
(
1305+
1000,
1306+
vec![Utf8PathBuf::from(format!("/path/to/{}", checksum))],
1307+
),
1308+
);
1309+
chunking.remainder.size += 1000;
1310+
}
1311+
1312+
// Process the mapping
1313+
// - system_metadata contains ALL objects in the system
1314+
// - specific_components_meta tells process_mapping which objects belong to specific components
1315+
chunking.process_mapping(
1316+
&system_metadata,
1317+
&Some(NonZeroU32::new(8).unwrap()),
1318+
None,
1319+
Some(&specific_components_meta),
1320+
)?;
1321+
1322+
// VALIDATION PART 1: Specific components get their own dedicated chunks
1323+
assert!(
1324+
chunking.chunks.len() >= 2,
1325+
"Should have at least 2 chunks for specific components"
1326+
);
1327+
1328+
// Specific Component Layer 1: pkg1 only
1329+
let specific_component_1_layer = &chunking.chunks[0];
1330+
assert_eq!(specific_component_1_layer.name, "pkg1");
1331+
assert_eq!(specific_component_1_layer.packages, vec!["pkg1"]);
1332+
assert_eq!(specific_component_1_layer.content.len(), 3);
1333+
for obj in &pkg1_objects {
1334+
assert!(
1335+
specific_component_1_layer.content.contains_key(*obj),
1336+
"Specific component 1 layer should contain {}",
1337+
obj
1338+
);
1339+
}
1340+
1341+
// Specific Component Layer 2: pkg2 only
1342+
let specific_component_2_layer = &chunking.chunks[1];
1343+
assert_eq!(specific_component_2_layer.name, "pkg2");
1344+
assert_eq!(specific_component_2_layer.packages, vec!["pkg2"]);
1345+
assert_eq!(specific_component_2_layer.content.len(), 2);
1346+
for obj in &pkg2_objects {
1347+
assert!(
1348+
specific_component_2_layer.content.contains_key(*obj),
1349+
"Specific component 2 layer should contain {}",
1350+
obj
1351+
);
1352+
}
1353+
1354+
// VALIDATION PART 2: Specific component layers contain NO regular package objects
1355+
for specific_layer in &chunking.chunks[0..2] {
1356+
for obj in pkg3_objects
1357+
.iter()
1358+
.chain(&pkg4_objects)
1359+
.chain(&pkg5_objects)
1360+
{
1361+
assert!(
1362+
!specific_layer.content.contains_key(*obj),
1363+
"Specific component layer '{}' should NOT contain regular package object {}",
1364+
specific_layer.name,
1365+
obj
1366+
);
1367+
}
1368+
}
1369+
1370+
// VALIDATION PART 3: Regular package layers contain NO specific component objects
1371+
let regular_package_layers = &chunking.chunks[2..];
1372+
for regular_layer in regular_package_layers {
1373+
for obj in pkg1_objects.iter().chain(&pkg2_objects) {
1374+
assert!(
1375+
!regular_layer.content.contains_key(*obj),
1376+
"Regular package layer should NOT contain specific component object {}",
1377+
obj
1378+
);
1379+
}
1380+
}
1381+
1382+
// VALIDATION PART 4: All regular package objects are in some regular layer
1383+
let mut found_regular_objects = BTreeSet::new();
1384+
for regular_layer in regular_package_layers {
1385+
for (obj, _) in &regular_layer.content {
1386+
found_regular_objects.insert(obj.as_ref());
1387+
}
1388+
}
1389+
1390+
for obj in pkg3_objects
1391+
.iter()
1392+
.chain(&pkg4_objects)
1393+
.chain(&pkg5_objects)
1394+
{
1395+
assert!(
1396+
found_regular_objects.contains(*obj),
1397+
"Regular package object {} should be in some regular layer",
1398+
obj
1399+
);
1400+
}
1401+
1402+
// VALIDATION PART 5: All objects moved from remainder
1403+
assert_eq!(
1404+
chunking.remainder.content.len(),
1405+
0,
1406+
"All objects should be moved from remainder"
1407+
);
1408+
assert_eq!(chunking.remainder.size, 0, "Remainder size should be 0");
1409+
1410+
Ok(())
1411+
}
12241412
}

0 commit comments

Comments
 (0)