Skip to content

Commit 92ce27a

Browse files
committed
chunked: allow conversion without zstd compression
This commit introduces the capability to convert tar layers to the zstd:chunked format, without performing any zstd compression. This allows using zstd:chunked without the cost of compression and decompression. Closes: https://issues.redhat.com/browse/RUN-3056 Signed-off-by: Giuseppe Scrivano <[email protected]>
1 parent 7752f51 commit 92ce27a

File tree

4 files changed

+25
-13
lines changed

4 files changed

+25
-13
lines changed

pkg/chunked/compression_linux.go

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,10 @@ func openTmpFileNoTmpFile(tmpDir string) (*os.File, error) {
185185
// Returns (manifest blob, parsed manifest, tar-split file or nil, manifest offset).
186186
// The opened tar-split file’s position is unspecified.
187187
// It may return an error matching ErrFallbackToOrdinaryLayerDownload / errFallbackCanConvert.
188-
func readZstdChunkedManifest(tmpDir string, blobStream ImageSourceSeekable, tocDigest digest.Digest, annotations map[string]string) (_ []byte, _ *minimal.TOC, _ *os.File, _ int64, retErr error) {
188+
// The compressed parameter indicates whether the manifest and tar-split data are zstd-compressed
189+
// (true) or stored uncompressed (false). Uncompressed data is used only for an optimization to convert
190+
// a regular OCI layer to zstd:chunked when convert_images is set, and it is not used for distributed images.
191+
func readZstdChunkedManifest(tmpDir string, blobStream ImageSourceSeekable, tocDigest digest.Digest, annotations map[string]string, compressed bool) (_ []byte, _ *minimal.TOC, _ *os.File, _ int64, retErr error) {
189192
offsetMetadata := annotations[minimal.ManifestInfoKey]
190193
if offsetMetadata == "" {
191194
return nil, nil, nil, 0, fmt.Errorf("%q annotation missing", minimal.ManifestInfoKey)
@@ -261,7 +264,7 @@ func readZstdChunkedManifest(tmpDir string, blobStream ImageSourceSeekable, tocD
261264
return nil, nil, nil, 0, err
262265
}
263266

264-
decodedBlob, err := decodeAndValidateBlob(manifest, manifestLengthUncompressed, tocDigest.String())
267+
decodedBlob, err := decodeAndValidateBlob(manifest, manifestLengthUncompressed, tocDigest.String(), compressed)
265268
if err != nil {
266269
return nil, nil, nil, 0, fmt.Errorf("validating and decompressing TOC: %w", err)
267270
}
@@ -288,7 +291,7 @@ func readZstdChunkedManifest(tmpDir string, blobStream ImageSourceSeekable, tocD
288291
decodedTarSplit.Close()
289292
}
290293
}()
291-
if err := decodeAndValidateBlobToStream(tarSplit, decodedTarSplit, toc.TarSplitDigest.String()); err != nil {
294+
if err := decodeAndValidateBlobToStream(tarSplit, decodedTarSplit, toc.TarSplitDigest.String(), compressed); err != nil {
292295
return nil, nil, nil, 0, fmt.Errorf("validating and decompressing tar-split: %w", err)
293296
}
294297
// We use the TOC for creating on-disk files, but the tar-split for creating metadata
@@ -487,11 +490,15 @@ func validateBlob(blob []byte, expectedCompressedChecksum string) error {
487490
return nil
488491
}
489492

490-
func decodeAndValidateBlob(blob []byte, lengthUncompressed uint64, expectedCompressedChecksum string) ([]byte, error) {
493+
func decodeAndValidateBlob(blob []byte, lengthUncompressed uint64, expectedCompressedChecksum string, compressed bool) ([]byte, error) {
491494
if err := validateBlob(blob, expectedCompressedChecksum); err != nil {
492495
return nil, err
493496
}
494497

498+
if !compressed {
499+
return blob, nil
500+
}
501+
495502
decoder, err := zstd.NewReader(nil)
496503
if err != nil {
497504
return nil, err
@@ -502,11 +509,16 @@ func decodeAndValidateBlob(blob []byte, lengthUncompressed uint64, expectedCompr
502509
return decoder.DecodeAll(blob, b)
503510
}
504511

505-
func decodeAndValidateBlobToStream(blob []byte, w *os.File, expectedCompressedChecksum string) error {
512+
func decodeAndValidateBlobToStream(blob []byte, w *os.File, expectedCompressedChecksum string, compressed bool) error {
506513
if err := validateBlob(blob, expectedCompressedChecksum); err != nil {
507514
return err
508515
}
509516

517+
if !compressed {
518+
_, err := w.Write(blob)
519+
return err
520+
}
521+
510522
decoder, err := zstd.NewReader(bytes.NewReader(blob))
511523
if err != nil {
512524
return err

pkg/chunked/compressor/compressor.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import (
1111

1212
"github.com/containers/storage/pkg/chunked/internal/minimal"
1313
"github.com/containers/storage/pkg/ioutils"
14-
"github.com/klauspost/compress/zstd"
1514
"github.com/opencontainers/go-digest"
1615
"github.com/vbatts/tar-split/archive/tar"
1716
"github.com/vbatts/tar-split/tar/asm"
@@ -202,7 +201,7 @@ type tarSplitData struct {
202201
compressed *bytes.Buffer
203202
digester digest.Digester
204203
uncompressedCounter *ioutils.WriteCounter
205-
zstd *zstd.Encoder
204+
zstd minimal.ZstdWriter
206205
packer storage.Packer
207206
}
208207

pkg/chunked/storage_linux.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -170,8 +170,7 @@ func (c *chunkedDiffer) convertTarToZstdChunked(destDirectory string, payload *o
170170
}
171171

172172
newAnnotations := make(map[string]string)
173-
level := 1
174-
chunked, err := compressor.ZstdCompressor(f, newAnnotations, &level)
173+
chunked, err := compressor.NoCompression(f, newAnnotations)
175174
if err != nil {
176175
f.Close()
177176
return 0, nil, "", nil, err
@@ -341,7 +340,7 @@ func makeConvertFromRawDiffer(store storage.Store, blobDigest digest.Digest, blo
341340
// makeZstdChunkedDiffer sets up a chunkedDiffer for a zstd:chunked layer.
342341
// It may return an error matching ErrFallbackToOrdinaryLayerDownload / errFallbackCanConvert.
343342
func makeZstdChunkedDiffer(store storage.Store, blobSize int64, tocDigest digest.Digest, annotations map[string]string, iss ImageSourceSeekable, pullOptions pullOptions) (_ *chunkedDiffer, retErr error) {
344-
manifest, toc, tarSplit, tocOffset, err := readZstdChunkedManifest(store.RunRoot(), iss, tocDigest, annotations)
343+
manifest, toc, tarSplit, tocOffset, err := readZstdChunkedManifest(store.RunRoot(), iss, tocDigest, annotations, true)
345344
if err != nil { // May be ErrFallbackToOrdinaryLayerDownload / errFallbackCanConvert
346345
return nil, fmt.Errorf("read zstd:chunked manifest: %w", err)
347346
}
@@ -1448,7 +1447,9 @@ func (c *chunkedDiffer) ApplyDiff(dest string, options *archive.TarOptions, diff
14481447
if err != nil {
14491448
return graphdriver.DriverWithDifferOutput{}, err
14501449
}
1450+
14511451
c.uncompressedTarSize = tarSize
1452+
14521453
// fileSource is a O_TMPFILE file descriptor, so we
14531454
// need to keep it open until the entire file is processed.
14541455
defer fileSource.Close()
@@ -1464,7 +1465,7 @@ func (c *chunkedDiffer) ApplyDiff(dest string, options *archive.TarOptions, diff
14641465
if tocDigest == nil {
14651466
return graphdriver.DriverWithDifferOutput{}, fmt.Errorf("internal error: just-created zstd:chunked missing TOC digest")
14661467
}
1467-
manifest, toc, tarSplit, tocOffset, err := readZstdChunkedManifest(dest, fileSource, *tocDigest, annotations)
1468+
manifest, toc, tarSplit, tocOffset, err := readZstdChunkedManifest(dest, fileSource, *tocDigest, annotations, false)
14681469
if err != nil {
14691470
return graphdriver.DriverWithDifferOutput{}, fmt.Errorf("read zstd:chunked manifest: %w", err)
14701471
}
@@ -1473,7 +1474,7 @@ func (c *chunkedDiffer) ApplyDiff(dest string, options *archive.TarOptions, diff
14731474
stream = fileSource
14741475

14751476
// fill the chunkedDiffer with the data we just read.
1476-
c.fileType = fileTypeZstdChunked
1477+
c.fileType = fileTypeNoCompression
14771478
c.manifest = manifest
14781479
c.toc = toc
14791480
c.tarSplit = tarSplit

pkg/chunked/zstdchunked_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ func TestGenerateAndParseManifest(t *testing.T) {
184184
tocDigest, err := toc.GetTOCDigest(annotations)
185185
require.NoError(t, err)
186186
require.NotNil(t, tocDigest)
187-
manifest, decodedTOC, _, _, err := readZstdChunkedManifest(t.TempDir(), s, *tocDigest, annotations)
187+
manifest, decodedTOC, _, _, err := readZstdChunkedManifest(t.TempDir(), s, *tocDigest, annotations, true)
188188
require.NoError(t, err)
189189

190190
var toc minimal.TOC

0 commit comments

Comments
 (0)