Skip to content

Commit 0800363

Browse files
committed
introduce external package
Signed-off-by: Yan Song <[email protected]>
1 parent 0ea850f commit 0800363

File tree

12 files changed

+611
-21
lines changed

12 files changed

+611
-21
lines changed

go.mod

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ require (
4646
github.com/sirupsen/logrus v1.9.3
4747
github.com/stretchr/testify v1.10.0
4848
github.com/urfave/cli/v2 v2.27.5
49+
github.com/vmihailenco/msgpack/v5 v5.4.1
4950
go.etcd.io/bbolt v1.3.11
5051
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56
5152
golang.org/x/net v0.30.0
@@ -61,6 +62,8 @@ require (
6162
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8
6263
)
6364

65+
require github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
66+
6467
require (
6568
github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20231105174938-2b5cbb29f3e2 // indirect
6669
github.com/Microsoft/go-winio v0.6.2 // indirect

go.sum

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,10 @@ github.com/urfave/cli/v2 v2.27.5 h1:WoHEJLdsXr6dDWoJgMq/CboDmyY/8HMMH1fTECbih+w=
319319
github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ=
320320
github.com/vbatts/tar-split v0.11.5 h1:3bHCTIheBm1qFTcgh9oPu+nNBtX+XJIupG/vacinCts=
321321
github.com/vbatts/tar-split v0.11.5/go.mod h1:yZbwRsSeGjusneWgA781EKej9HF8vme8okylkAeNKLk=
322+
github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8=
323+
github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok=
324+
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
325+
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
322326
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
323327
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
324328
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=

pkg/converter/convert_unix.go

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -360,10 +360,6 @@ func Pack(ctx context.Context, dest io.Writer, opt PackOption) (io.WriteCloser,
360360
}
361361

362362
func packFromDirectory(ctx context.Context, dest io.Writer, opt PackOption, builderPath, sourceDir string) error {
363-
if opt.ExternalBlobWriter == nil {
364-
return fmt.Errorf("the 'ExternalBlobWriter' option requires the 'AttributesPath' option be specified")
365-
}
366-
367363
workDir, err := ensureWorkDir(opt.WorkDir)
368364
if err != nil {
369365
return errors.Wrap(err, "ensure work directory")
@@ -381,12 +377,17 @@ func packFromDirectory(ctx context.Context, dest io.Writer, opt PackOption, buil
381377
}
382378
defer blobFifo.Close()
383379

384-
externalBlobPath := filepath.Join(workDir, "external-blob")
385-
externalBlobFifo, err := fifo.OpenFifo(ctx, externalBlobPath, syscall.O_CREAT|syscall.O_RDONLY|syscall.O_NONBLOCK, 0640)
386-
if err != nil {
387-
return errors.Wrapf(err, "create fifo file for external blob")
380+
externalBlobPath := ""
381+
var externalBlobFifo io.ReadWriteCloser
382+
if opt.ExternalBlobWriter != nil {
383+
var err error
384+
externalBlobPath = filepath.Join(workDir, "external-blob")
385+
externalBlobFifo, err = fifo.OpenFifo(ctx, externalBlobPath, syscall.O_CREAT|syscall.O_RDONLY|syscall.O_NONBLOCK, 0640)
386+
if err != nil {
387+
return errors.Wrapf(err, "create fifo file for external blob")
388+
}
389+
defer externalBlobFifo.Close()
388390
}
389-
defer externalBlobFifo.Close()
390391

391392
go func() {
392393
err := tool.Pack(tool.PackOption{
@@ -422,14 +423,17 @@ func packFromDirectory(ctx context.Context, dest io.Writer, opt PackOption, buil
422423
}
423424
return nil
424425
})
425-
eg.Go(func() error {
426-
buffer := bufPool.Get().(*[]byte)
427-
defer bufPool.Put(buffer)
428-
if _, err := io.CopyBuffer(opt.ExternalBlobWriter, externalBlobFifo, *buffer); err != nil {
429-
return errors.Wrap(err, "pack to nydus external blob")
430-
}
431-
return nil
432-
})
426+
427+
if opt.ExternalBlobWriter != nil {
428+
eg.Go(func() error {
429+
buffer := bufPool.Get().(*[]byte)
430+
defer bufPool.Put(buffer)
431+
if _, err := io.CopyBuffer(opt.ExternalBlobWriter, externalBlobFifo, *buffer); err != nil {
432+
return errors.Wrap(err, "pack to nydus external blob")
433+
}
434+
return nil
435+
})
436+
}
433437

434438
return eg.Wait()
435439
}

pkg/converter/tool/builder.go

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ type outputJSON struct {
7676
Blobs []string
7777
}
7878

79-
func buildPackArgs(option PackOption) []string {
79+
func buildPackArgs(option PackOption) ([]string, error) {
8080
if option.FsVersion == "" {
8181
option.FsVersion = "6"
8282
}
@@ -100,7 +100,11 @@ func buildPackArgs(option PackOption) []string {
100100
args,
101101
"--blob-inline-meta",
102102
)
103-
if option.AttributesPath != "" {
103+
info, err := os.Stat(option.SourcePath)
104+
if err != nil {
105+
return nil, err
106+
}
107+
if info.IsDir() {
104108
args = append(
105109
args,
106110
"--type",
@@ -160,7 +164,7 @@ func buildPackArgs(option PackOption) []string {
160164
}
161165
args = append(args, option.SourcePath)
162166

163-
return args
167+
return args, nil
164168
}
165169

166170
func Pack(option PackOption) error {
@@ -175,7 +179,10 @@ func Pack(option PackOption) error {
175179
defer cancel()
176180
}
177181

178-
args := buildPackArgs(option)
182+
args, err := buildPackArgs(option)
183+
if err != nil {
184+
return err
185+
}
179186
logrus.Debugf("\tCommand: %s %s", option.BuilderPath, strings.Join(args, " "))
180187

181188
cmd := exec.CommandContext(ctx, option.BuilderPath, args...)

pkg/external/backend/backend.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package backend
2+
3+
import "context"
4+
5+
const (
6+
DefaultFileChunkSize = 1024 * 1024 * 1 // 1 MB
7+
DefaultThrottleFileSize = 1024 * 1024 * 2 // 2 MB
8+
)
9+
10+
type Backend struct {
11+
Type string `json:"type"`
12+
Config map[string]string `json:"config"`
13+
}
14+
15+
type Result struct {
16+
Chunks []Chunk
17+
Files []string
18+
Backend Backend
19+
}
20+
21+
type File struct {
22+
RelativePath string
23+
Size int64
24+
}
25+
26+
// Handler is the interface for backend handler.
27+
type Handler interface {
28+
// Backend returns the backend information.
29+
Backend(ctx context.Context) (*Backend, error)
30+
// Handle handles the file and returns the object information.
31+
Handle(ctx context.Context, file File) ([]Chunk, error)
32+
}
33+
34+
type Chunk interface {
35+
ObjectID() uint32
36+
ObjectContent() interface{}
37+
ObjectOffset() uint64
38+
}
39+
40+
// SplitObjectOffsets splits the total size into object offsets
41+
// with the specified chunk size.
42+
func SplitObjectOffsets(totalSize, chunkSize int64) []uint64 {
43+
objectOffsets := []uint64{}
44+
if chunkSize <= 0 {
45+
return objectOffsets
46+
}
47+
48+
chunkN := totalSize / chunkSize
49+
50+
for i := int64(0); i < chunkN; i++ {
51+
objectOffsets = append(objectOffsets, uint64(i*chunkSize))
52+
}
53+
54+
if totalSize%chunkSize > 0 {
55+
objectOffsets = append(objectOffsets, uint64(chunkN*chunkSize))
56+
}
57+
58+
return objectOffsets
59+
}

pkg/external/backend/backend_test.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package backend
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
"unsafe"
7+
8+
"github.com/stretchr/testify/require"
9+
)
10+
11+
func TestLayout(t *testing.T) {
12+
require.Equal(t, fmt.Sprintf("%d", 4096), fmt.Sprintf("%d", unsafe.Sizeof(Header{})))
13+
require.Equal(t, fmt.Sprintf("%d", 256), fmt.Sprintf("%d", unsafe.Sizeof(ChunkMeta{})))
14+
require.Equal(t, fmt.Sprintf("%d", 256), fmt.Sprintf("%d", unsafe.Sizeof(ObjectMeta{})))
15+
}

pkg/external/backend/layout.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package backend
2+
3+
const MetaMagic uint32 = 0x0AF5_E1E2
4+
const MetaVersion uint32 = 0x0000_0001
5+
6+
// Layout
7+
//
8+
// header: magic | version | chunk_meta_offset | object_meta_offset
9+
// chunks: chunk_meta | chunk | chunk | ...
10+
// objects: object_meta | [object_offsets] | object | object | ...
11+
12+
// 4096 bytes
13+
type Header struct {
14+
Magic uint32
15+
Version uint32
16+
17+
ChunkMetaOffset uint32
18+
ObjectMetaOffset uint32
19+
20+
Reserved2 [4080]byte
21+
}
22+
23+
// 256 bytes
24+
type ChunkMeta struct {
25+
EntryCount uint32
26+
EntrySize uint32
27+
28+
Reserved [248]byte
29+
}
30+
31+
// 256 bytes
32+
type ObjectMeta struct {
33+
EntryCount uint32
34+
// = 0 means indeterminate entry size, and len(object_offsets) > 0.
35+
// > 0 means fixed entry size, and len(object_offsets) == 0.
36+
EntrySize uint32
37+
38+
Reserved [248]byte
39+
}
40+
41+
// 8 bytes
42+
type ChunkOndisk struct {
43+
ObjectIndex uint32
44+
Reserved [4]byte
45+
ObjectOffset uint64
46+
}
47+
48+
// 4 bytes
49+
type ObjectOffset uint32
50+
51+
// Size depends on different external backend implementations
52+
type ObjectOndisk struct {
53+
EntrySize uint32
54+
EncodedData []byte
55+
}

pkg/external/backend/local/local.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package local
2+
3+
import (
4+
"context"
5+
6+
"github.com/containerd/nydus-snapshotter/pkg/external/backend"
7+
)
8+
9+
type object struct {
10+
Path string `msgpack:"p"`
11+
}
12+
13+
type chunk struct {
14+
objectID uint32
15+
objectContent object
16+
objectOffset uint64
17+
}
18+
19+
func (c *chunk) ObjectID() uint32 {
20+
return c.objectID
21+
}
22+
23+
func (c *chunk) ObjectContent() interface{} {
24+
return c.objectContent
25+
}
26+
27+
func (c *chunk) ObjectOffset() uint64 {
28+
return c.objectOffset
29+
}
30+
31+
type Handler struct {
32+
root string
33+
objectID uint32
34+
}
35+
36+
func NewHandler(root string) *Handler {
37+
return &Handler{
38+
root: root,
39+
objectID: 0,
40+
}
41+
}
42+
43+
func (handler *Handler) Handle(_ context.Context, file backend.File) ([]backend.Chunk, error) {
44+
chunks := []backend.Chunk{}
45+
objectOffsets := backend.SplitObjectOffsets(file.Size, backend.DefaultFileChunkSize)
46+
47+
for _, objectOffset := range objectOffsets {
48+
chunks = append(chunks, &chunk{
49+
objectID: handler.objectID,
50+
objectContent: object{
51+
Path: file.RelativePath,
52+
},
53+
objectOffset: objectOffset,
54+
})
55+
}
56+
handler.objectID++
57+
58+
return chunks, nil
59+
}
60+
61+
func (handler *Handler) Backend(_ context.Context) (*backend.Backend, error) {
62+
return &backend.Backend{
63+
Type: "local",
64+
Config: map[string]string{
65+
"root": handler.root,
66+
},
67+
}, nil
68+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package local
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/require"
7+
"github.com/vmihailenco/msgpack/v5"
8+
)
9+
10+
type Object2 struct {
11+
Kind string `msgpack:"k"`
12+
Number uint64 `msgpack:"n"`
13+
Path string `msgpack:"p"`
14+
}
15+
16+
func TestSerializeCompatibility(t *testing.T) {
17+
object1 := object{
18+
Path: "test1",
19+
}
20+
object2 := Object2{}
21+
buf, err := msgpack.Marshal(&object1)
22+
require.NoError(t, err)
23+
err = msgpack.Unmarshal(buf, &object2)
24+
require.NoError(t, err)
25+
require.Equal(t, object1.Path, object2.Path)
26+
27+
object1 = object{}
28+
object2 = Object2{
29+
Kind: "test2",
30+
Number: 123,
31+
Path: "test1",
32+
}
33+
object3 := Object2{}
34+
buf, err = msgpack.Marshal(&object2)
35+
require.NoError(t, err)
36+
err = msgpack.Unmarshal(buf, &object1)
37+
require.NoError(t, err)
38+
require.Equal(t, object2.Path, object1.Path)
39+
40+
err = msgpack.Unmarshal(buf, &object3)
41+
require.NoError(t, err)
42+
require.Equal(t, object2, object3)
43+
}

0 commit comments

Comments
 (0)