@@ -35,6 +35,7 @@ import (
3535 cgroupsv2 "github.com/containerd/cgroups/v3/cgroup2"
3636 "github.com/containerd/containerd/api/types/runc/options"
3737 "github.com/containerd/errdefs"
38+ "github.com/containerd/platforms"
3839 "github.com/stretchr/testify/assert"
3940
4041 . "github.com/containerd/containerd/v2/client"
@@ -50,6 +51,7 @@ import (
5051
5152 "github.com/opencontainers/runtime-spec/specs-go"
5253 "github.com/stretchr/testify/require"
54+ "golang.org/x/sync/semaphore"
5355 "golang.org/x/sys/unix"
5456)
5557
@@ -1823,3 +1825,70 @@ func TestIssue10589(t *testing.T) {
18231825 require .NoError (t , err , "container status" )
18241826 assert .Equal (t , Stopped , status .Status )
18251827}
1828+
1829+ // TestIssue13030 is a regression test for parallel image unpacking.
1830+ // The test validates that when multiple layers are unpacked in parallel,
1831+ // that whiteout files are properly processed and do not cause files to
1832+ // be unexpectedly present in the final rootfs.
1833+ //
1834+ // https://github.com/containerd/containerd/issues/13030
1835+ func TestIssue13030 (t * testing.T ) {
1836+ client , err := newClient (t , address )
1837+ if err != nil {
1838+ t .Fatal (err )
1839+ }
1840+ t .Cleanup (func () { client .Close () })
1841+
1842+ ctx , cancel := testContext (t )
1843+ t .Cleanup (cancel )
1844+
1845+ image , err := client .Pull (ctx ,
1846+ images .Get (images .Whiteout ),
1847+ WithPlatformMatcher (platforms .Default ()),
1848+ WithPullUnpack ,
1849+ WithUnpackOpts ([]UnpackOpt {WithUnpackLimiter (semaphore .NewWeighted (3 ))}),
1850+ )
1851+ t .Cleanup (func () {
1852+ client .ImageService ().Delete (ctx , images .Get (images .Whiteout ))
1853+ })
1854+ if err != nil {
1855+ t .Fatal (err )
1856+ }
1857+
1858+ container , err := client .NewContainer (ctx , t .Name (),
1859+ WithNewSnapshot (t .Name (), image ),
1860+ WithNewSpec (oci .WithImageConfig (image ),
1861+ withProcessArgs ("/bin/sh" , "-e" , "-c" , "test ! -e /file-to-delete && test ! -e /dir-to-delete" )),
1862+ )
1863+ if err != nil {
1864+ t .Fatal (err )
1865+ }
1866+ t .Cleanup (func () {
1867+ container .Delete (ctx , WithSnapshotCleanup )
1868+ })
1869+
1870+ task , err := container .NewTask (ctx , empty ())
1871+ if err != nil {
1872+ t .Fatal (err )
1873+ }
1874+ t .Cleanup (func () {
1875+ task .Delete (ctx )
1876+ })
1877+
1878+ statusC , err := task .Wait (ctx )
1879+ if err != nil {
1880+ t .Fatal (err )
1881+ }
1882+ err = task .Start (ctx )
1883+ if err != nil {
1884+ t .Fatal (err )
1885+ }
1886+ status := <- statusC
1887+ code , _ , err := status .Result ()
1888+ if err != nil {
1889+ t .Fatal (err )
1890+ }
1891+ if code != 0 {
1892+ t .Errorf ("expected status 0 from wait but received %d" , code )
1893+ }
1894+ }
0 commit comments