@@ -49,7 +49,7 @@ pub(crate) struct Chunk {
4949 pub ( crate ) packages : Vec < String > ,
5050}
5151
52- #[ derive( Debug , Deserialize , Serialize ) ]
52+ #[ derive( Debug , Clone , Deserialize , Serialize ) ]
5353/// Object metadata, but with additional size data
5454pub struct ObjectSourceMetaSized {
5555 /// The original metadata
@@ -276,9 +276,10 @@ impl Chunking {
276276 meta : & ObjectMetaSized ,
277277 max_layers : & Option < NonZeroU32 > ,
278278 prior_build_metadata : Option < & oci_spec:: image:: ImageManifest > ,
279+ specific_contentmeta : Option < & ObjectMetaSized > ,
279280 ) -> Result < Self > {
280281 let mut r = Self :: new ( repo, rev) ?;
281- r. process_mapping ( meta, max_layers, prior_build_metadata) ?;
282+ r. process_mapping ( meta, max_layers, prior_build_metadata, specific_contentmeta ) ?;
282283 Ok ( r)
283284 }
284285
@@ -294,6 +295,7 @@ impl Chunking {
294295 meta : & ObjectMetaSized ,
295296 max_layers : & Option < NonZeroU32 > ,
296297 prior_build_metadata : Option < & oci_spec:: image:: ImageManifest > ,
298+ specific_contentmeta : Option < & ObjectMetaSized > ,
297299 ) -> Result < ( ) > {
298300 self . max = max_layers
299301 . unwrap_or ( NonZeroU32 :: new ( MAX_CHUNKS ) . unwrap ( ) )
@@ -314,6 +316,25 @@ impl Chunking {
314316 rmap. entry ( Rc :: clone ( contentid) ) . or_default ( ) . push ( checksum) ;
315317 }
316318
319+ // Create exclusive chunks first if specified
320+ let mut processed_specific_components = BTreeSet :: new ( ) ;
321+ if let Some ( specific_meta) = specific_contentmeta {
322+ for component in & specific_meta. sizes {
323+ let mut chunk = Chunk :: new ( & component. meta . name ) ;
324+ chunk. packages = vec ! [ component. meta. name. to_string( ) ] ;
325+
326+ // Move all objects belonging to this exclusive component
327+ if let Some ( objects) = rmap. get ( & component. meta . identifier ) {
328+ for & obj in objects {
329+ self . remainder . move_obj ( & mut chunk, obj) ;
330+ }
331+ }
332+
333+ self . chunks . push ( chunk) ;
334+ processed_specific_components. insert ( & * component. meta . identifier ) ;
335+ }
336+ }
337+
317338 // Safety: Let's assume no one has over 4 billion components.
318339 self . n_provided_components = meta. sizes . len ( ) . try_into ( ) . unwrap ( ) ;
319340 self . n_sized_components = sizes
@@ -323,49 +344,59 @@ impl Chunking {
323344 . try_into ( )
324345 . unwrap ( ) ;
325346
326- // TODO: Compute bin packing in a better way
327- let start = Instant :: now ( ) ;
328- let packing = basic_packing (
329- sizes,
330- NonZeroU32 :: new ( self . max ) . unwrap ( ) ,
331- prior_build_metadata,
332- ) ?;
333- let duration = start. elapsed ( ) ;
334- tracing:: debug!( "Time elapsed in packing: {:#?}" , duration) ;
335-
336- for bin in packing. into_iter ( ) {
337- let name = match bin. len ( ) {
338- 0 => Cow :: Borrowed ( "Reserved for new packages" ) ,
339- 1 => {
340- let first = bin[ 0 ] ;
341- let first_name = & * first. meta . identifier ;
342- Cow :: Borrowed ( first_name)
343- }
344- 2 ..=5 => {
345- let first = bin[ 0 ] ;
346- let first_name = & * first. meta . identifier ;
347- let r = bin. iter ( ) . map ( |v| & * v. meta . identifier ) . skip ( 1 ) . fold (
348- String :: from ( first_name) ,
349- |mut acc, v| {
350- write ! ( acc, " and {}" , v) . unwrap ( ) ;
351- acc
352- } ,
353- ) ;
354- Cow :: Owned ( r)
355- }
356- n => Cow :: Owned ( format ! ( "{n} components" ) ) ,
357- } ;
358- let mut chunk = Chunk :: new ( & name) ;
359- chunk. packages = bin. iter ( ) . map ( |v| String :: from ( & * v. meta . name ) ) . collect ( ) ;
360- for szmeta in bin {
361- for & obj in rmap. get ( & szmeta. meta . identifier ) . unwrap ( ) {
362- self . remainder . move_obj ( & mut chunk, obj. as_str ( ) ) ;
347+ // Filter out exclusive components for regular packing
348+ let regular_sizes: Vec < ObjectSourceMetaSized > = sizes
349+ . iter ( )
350+ . filter ( |component| {
351+ !processed_specific_components. contains ( & * component. meta . identifier )
352+ } )
353+ . cloned ( )
354+ . collect ( ) ;
355+
356+ // Process regular components with bin packing if we have remaining layers
357+ if let Some ( remaining) = NonZeroU32 :: new ( self . remaining ( ) ) {
358+ let start = Instant :: now ( ) ;
359+ let packing = basic_packing ( & regular_sizes, remaining, prior_build_metadata) ?;
360+ let duration = start. elapsed ( ) ;
361+ tracing:: debug!( "Time elapsed in packing: {:#?}" , duration) ;
362+
363+ for bin in packing. into_iter ( ) {
364+ let name = match bin. len ( ) {
365+ 0 => Cow :: Borrowed ( "Reserved for new packages" ) ,
366+ 1 => {
367+ let first = bin[ 0 ] ;
368+ let first_name = & * first. meta . identifier ;
369+ Cow :: Borrowed ( first_name)
370+ }
371+ 2 ..=5 => {
372+ let first = bin[ 0 ] ;
373+ let first_name = & * first. meta . identifier ;
374+ let r = bin. iter ( ) . map ( |v| & * v. meta . identifier ) . skip ( 1 ) . fold (
375+ String :: from ( first_name) ,
376+ |mut acc, v| {
377+ write ! ( acc, " and {}" , v) . unwrap ( ) ;
378+ acc
379+ } ,
380+ ) ;
381+ Cow :: Owned ( r)
382+ }
383+ n => Cow :: Owned ( format ! ( "{n} components" ) ) ,
384+ } ;
385+ let mut chunk = Chunk :: new ( & name) ;
386+ chunk. packages = bin. iter ( ) . map ( |v| String :: from ( & * v. meta . name ) ) . collect ( ) ;
387+ for szmeta in bin {
388+ for & obj in rmap. get ( & szmeta. meta . identifier ) . unwrap ( ) {
389+ self . remainder . move_obj ( & mut chunk, obj. as_str ( ) ) ;
390+ }
363391 }
392+ self . chunks . push ( chunk) ;
364393 }
365- self . chunks . push ( chunk) ;
366394 }
367395
368- assert_eq ! ( self . remainder. content. len( ) , 0 ) ;
396+ // Check that all objects have been processed
397+ if !processed_specific_components. is_empty ( ) || !regular_sizes. is_empty ( ) {
398+ assert_eq ! ( self . remainder. content. len( ) , 0 ) ;
399+ }
369400
370401 Ok ( ( ) )
371402 }
@@ -1003,4 +1034,191 @@ mod test {
10031034 assert_eq ! ( structure_derived, v2_expected_structure) ;
10041035 Ok ( ( ) )
10051036 }
1037+
1038+ fn setup_exclusive_test (
1039+ component_data : & [ ( u32 , u32 , u64 ) ] ,
1040+ max_layers : u32 ,
1041+ num_fake_objects : Option < usize > ,
1042+ ) -> Result < (
1043+ Vec < ObjectSourceMetaSized > ,
1044+ ObjectMetaSized ,
1045+ ObjectMetaSized ,
1046+ Chunking ,
1047+ ) > {
1048+ // Create content metadata from provided data
1049+ let contentmeta: Vec < ObjectSourceMetaSized > = component_data
1050+ . iter ( )
1051+ . map ( |& ( id, freq, size) | ObjectSourceMetaSized {
1052+ meta : ObjectSourceMeta {
1053+ identifier : RcStr :: from ( format ! ( "pkg{}.0" , id) ) ,
1054+ name : RcStr :: from ( format ! ( "pkg{}" , id) ) ,
1055+ srcid : RcStr :: from ( format ! ( "srcpkg{}" , id) ) ,
1056+ change_time_offset : 0 ,
1057+ change_frequency : freq,
1058+ } ,
1059+ size,
1060+ } )
1061+ . collect ( ) ;
1062+
1063+ // Create object maps with fake checksums
1064+ let mut object_map = IndexMap :: new ( ) ;
1065+ let mut regular_map = IndexMap :: new ( ) ;
1066+
1067+ for ( i, component) in contentmeta. iter ( ) . enumerate ( ) {
1068+ let checksum = format ! ( "checksum_{}" , i) ;
1069+ regular_map. insert ( checksum. clone ( ) , component. meta . identifier . clone ( ) ) ;
1070+ object_map. insert ( checksum, component. meta . identifier . clone ( ) ) ;
1071+ }
1072+
1073+ let regular_meta = ObjectMetaSized {
1074+ map : regular_map,
1075+ sizes : contentmeta. clone ( ) ,
1076+ } ;
1077+
1078+ // Create exclusive metadata (initially empty, to be populated by individual tests)
1079+ let exclusive_meta = ObjectMetaSized {
1080+ map : object_map,
1081+ sizes : Vec :: new ( ) ,
1082+ } ;
1083+
1084+ // Set up chunking with remainder chunk
1085+ let mut chunking = Chunking :: default ( ) ;
1086+ chunking. max = max_layers;
1087+ chunking. remainder = Chunk :: new ( "remainder" ) ;
1088+
1089+ // Add fake objects to the remainder chunk if specified
1090+ if let Some ( num_objects) = num_fake_objects {
1091+ for i in 0 ..num_objects {
1092+ let checksum = format ! ( "checksum_{}" , i) ;
1093+ chunking
1094+ . remainder
1095+ . content
1096+ . insert ( RcStr :: from ( checksum) , ( 1000 , vec ! [ ] ) ) ;
1097+ chunking. remainder . size += 1000 ;
1098+ }
1099+ }
1100+
1101+ Ok ( ( contentmeta, regular_meta, exclusive_meta, chunking) )
1102+ }
1103+
1104+ #[ test]
1105+ fn test_exclusive_chunks ( ) -> Result < ( ) > {
1106+ // Test that exclusive chunks are created first and get their own layers
1107+ let component_data = [
1108+ ( 1 , 100 , 50000 ) ,
1109+ ( 2 , 200 , 40000 ) ,
1110+ ( 3 , 300 , 30000 ) ,
1111+ ( 4 , 400 , 20000 ) ,
1112+ ( 5 , 500 , 10000 ) ,
1113+ ] ;
1114+
1115+ let ( contentmeta, regular_meta, mut exclusive_meta, mut chunking) =
1116+ setup_exclusive_test ( & component_data, 8 , Some ( 5 ) ) ?;
1117+
1118+ // Create exclusive content metadata for pkg1 and pkg2
1119+ let exclusive_content: Vec < ObjectSourceMetaSized > =
1120+ vec ! [ contentmeta[ 0 ] . clone( ) , contentmeta[ 1 ] . clone( ) ] ;
1121+ exclusive_meta. sizes = exclusive_content;
1122+
1123+ chunking. process_mapping (
1124+ & regular_meta,
1125+ & Some ( NonZeroU32 :: new ( 8 ) . unwrap ( ) ) ,
1126+ None ,
1127+ Some ( & exclusive_meta) ,
1128+ ) ?;
1129+
1130+ // Verify exclusive chunks are created first
1131+ assert ! ( chunking. chunks. len( ) >= 2 ) ;
1132+ assert_eq ! ( chunking. chunks[ 0 ] . name, "pkg1" ) ;
1133+ assert_eq ! ( chunking. chunks[ 1 ] . name, "pkg2" ) ;
1134+ assert_eq ! ( chunking. chunks[ 0 ] . packages, vec![ "pkg1" . to_string( ) ] ) ;
1135+ assert_eq ! ( chunking. chunks[ 1 ] . packages, vec![ "pkg2" . to_string( ) ] ) ;
1136+
1137+ Ok ( ( ) )
1138+ }
1139+
1140+ #[ test]
1141+ fn test_exclusive_chunks_with_regular_packing ( ) -> Result < ( ) > {
1142+ // Test that exclusive chunks are created first, then regular packing continues
1143+ let component_data = [
1144+ ( 1 , 100 , 50000 ) , // exclusive
1145+ ( 2 , 200 , 40000 ) , // exclusive
1146+ ( 3 , 300 , 30000 ) , // regular
1147+ ( 4 , 400 , 20000 ) , // regular
1148+ ( 5 , 500 , 10000 ) , // regular
1149+ ( 6 , 600 , 5000 ) , // regular
1150+ ] ;
1151+
1152+ let ( contentmeta, regular_meta, mut exclusive_meta, mut chunking) =
1153+ setup_exclusive_test ( & component_data, 8 , Some ( 6 ) ) ?;
1154+
1155+ // Create exclusive content metadata for pkg1 and pkg2
1156+ let exclusive_content: Vec < ObjectSourceMetaSized > =
1157+ vec ! [ contentmeta[ 0 ] . clone( ) , contentmeta[ 1 ] . clone( ) ] ;
1158+ exclusive_meta. sizes = exclusive_content;
1159+
1160+ chunking. process_mapping (
1161+ & regular_meta,
1162+ & Some ( NonZeroU32 :: new ( 8 ) . unwrap ( ) ) ,
1163+ None ,
1164+ Some ( & exclusive_meta) ,
1165+ ) ?;
1166+
1167+ // Verify exclusive chunks are created first
1168+ assert ! ( chunking. chunks. len( ) >= 2 ) ;
1169+ assert_eq ! ( chunking. chunks[ 0 ] . name, "pkg1" ) ;
1170+ assert_eq ! ( chunking. chunks[ 1 ] . name, "pkg2" ) ;
1171+ assert_eq ! ( chunking. chunks[ 0 ] . packages, vec![ "pkg1" . to_string( ) ] ) ;
1172+ assert_eq ! ( chunking. chunks[ 1 ] . packages, vec![ "pkg2" . to_string( ) ] ) ;
1173+
1174+ // Verify regular components are not in exclusive chunks
1175+ for chunk in & chunking. chunks [ 2 ..] {
1176+ assert ! ( !chunk. packages. contains( & "pkg1" . to_string( ) ) ) ;
1177+ assert ! ( !chunk. packages. contains( & "pkg2" . to_string( ) ) ) ;
1178+ }
1179+
1180+ Ok ( ( ) )
1181+ }
1182+
1183+ #[ test]
1184+ fn test_exclusive_chunks_isolation ( ) -> Result < ( ) > {
1185+ // Test that exclusive chunks properly isolate components
1186+ let component_data = [ ( 1 , 100 , 50000 ) , ( 2 , 200 , 40000 ) , ( 3 , 300 , 30000 ) ] ;
1187+
1188+ let ( contentmeta, regular_meta, mut exclusive_meta, mut chunking) =
1189+ setup_exclusive_test ( & component_data, 8 , Some ( 3 ) ) ?;
1190+
1191+ // Create exclusive content metadata for pkg1 only
1192+ let exclusive_content: Vec < ObjectSourceMetaSized > = vec ! [ contentmeta[ 0 ] . clone( ) ] ;
1193+ exclusive_meta. sizes = exclusive_content;
1194+
1195+ chunking. process_mapping (
1196+ & regular_meta,
1197+ & Some ( NonZeroU32 :: new ( 8 ) . unwrap ( ) ) ,
1198+ None ,
1199+ Some ( & exclusive_meta) ,
1200+ ) ?;
1201+
1202+ // Verify pkg1 is in its own exclusive chunk
1203+ assert ! ( chunking. chunks. len( ) >= 1 ) ;
1204+ assert_eq ! ( chunking. chunks[ 0 ] . name, "pkg1" ) ;
1205+ assert_eq ! ( chunking. chunks[ 0 ] . packages, vec![ "pkg1" . to_string( ) ] ) ;
1206+
1207+ // Verify pkg2 and pkg3 are in regular chunks, not mixed with pkg1
1208+ let mut found_pkg2 = false ;
1209+ let mut found_pkg3 = false ;
1210+ for chunk in & chunking. chunks [ 1 ..] {
1211+ if chunk. packages . contains ( & "pkg2" . to_string ( ) ) {
1212+ found_pkg2 = true ;
1213+ assert ! ( !chunk. packages. contains( & "pkg1" . to_string( ) ) ) ;
1214+ }
1215+ if chunk. packages . contains ( & "pkg3" . to_string ( ) ) {
1216+ found_pkg3 = true ;
1217+ assert ! ( !chunk. packages. contains( & "pkg1" . to_string( ) ) ) ;
1218+ }
1219+ }
1220+ assert ! ( found_pkg2 && found_pkg3) ;
1221+
1222+ Ok ( ( ) )
1223+ }
10061224}
0 commit comments