mirror of
https://github.com/project-zot/zot.git
synced 2026-06-16 20:38:08 +08:00
feat(cache): dynamodb implementation (#953)
Signed-off-by: Catalin Hofnar <catalin.hofnar@gmail.com>
This commit is contained in:
committed by
GitHub
parent
49c3d05706
commit
31b9481713
@@ -12,6 +12,10 @@ func Create(dbtype string, parameters interface{}, log zlog.Logger) (cache.Cache
|
||||
{
|
||||
return cache.NewBoltDBCache(parameters, log), nil
|
||||
}
|
||||
case "dynamodb":
|
||||
{
|
||||
return cache.NewDynamoDBCache(parameters, log), nil
|
||||
}
|
||||
default:
|
||||
{
|
||||
return nil, errors.ErrBadConfig
|
||||
|
||||
Vendored
+9
-8
@@ -32,18 +32,14 @@ func NewBoltDBCache(parameters interface{}, log zlog.Logger) Cache {
|
||||
panic("Failed type assertion")
|
||||
}
|
||||
|
||||
return NewCache(properParameters, log)
|
||||
}
|
||||
|
||||
func NewCache(parameters BoltDBDriverParameters, log zlog.Logger) *BoltDBDriver {
|
||||
err := os.MkdirAll(parameters.RootDir, constants.DefaultDirPerms)
|
||||
err := os.MkdirAll(properParameters.RootDir, constants.DefaultDirPerms)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("unable to create directory for cache db: %v", parameters.RootDir)
|
||||
log.Error().Err(err).Msgf("unable to create directory for cache db: %v", properParameters.RootDir)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
dbPath := path.Join(parameters.RootDir, parameters.Name+constants.DBExtensionName)
|
||||
dbPath := path.Join(properParameters.RootDir, properParameters.Name+constants.DBExtensionName)
|
||||
dbOpts := &bbolt.Options{
|
||||
Timeout: constants.DBCacheLockCheckTimeout,
|
||||
FreelistType: bbolt.FreelistArrayType,
|
||||
@@ -72,7 +68,12 @@ func NewCache(parameters BoltDBDriverParameters, log zlog.Logger) *BoltDBDriver
|
||||
return nil
|
||||
}
|
||||
|
||||
return &BoltDBDriver{rootDir: parameters.RootDir, db: cacheDB, useRelPaths: parameters.UseRelPaths, log: log}
|
||||
return &BoltDBDriver{
|
||||
rootDir: properParameters.RootDir,
|
||||
db: cacheDB,
|
||||
useRelPaths: properParameters.UseRelPaths,
|
||||
log: log,
|
||||
}
|
||||
}
|
||||
|
||||
func (d *BoltDBDriver) Name() string {
|
||||
|
||||
Vendored
+216
@@ -0,0 +1,216 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/aws"
|
||||
"github.com/aws/aws-sdk-go-v2/config"
|
||||
"github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue"
|
||||
"github.com/aws/aws-sdk-go-v2/service/dynamodb"
|
||||
"github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
|
||||
godigest "github.com/opencontainers/go-digest"
|
||||
|
||||
zerr "zotregistry.io/zot/errors"
|
||||
zlog "zotregistry.io/zot/pkg/log"
|
||||
)
|
||||
|
||||
type DynamoDBDriver struct {
|
||||
client *dynamodb.Client
|
||||
log zlog.Logger
|
||||
tableName string
|
||||
}
|
||||
|
||||
type DynamoDBDriverParameters struct {
|
||||
Endpoint, Region, TableName string
|
||||
}
|
||||
|
||||
type Blob struct {
|
||||
Digest string `dynamodbav:"Digest,string"`
|
||||
BlobPath []string `dynamodbav:"BlobPath,stringset"`
|
||||
}
|
||||
|
||||
// Use ONLY for tests.
|
||||
func (d *DynamoDBDriver) NewTable(tableName string) error {
|
||||
//nolint:gomnd
|
||||
_, err := d.client.CreateTable(context.TODO(), &dynamodb.CreateTableInput{
|
||||
TableName: &tableName,
|
||||
AttributeDefinitions: []types.AttributeDefinition{
|
||||
{
|
||||
AttributeName: aws.String("Digest"),
|
||||
AttributeType: types.ScalarAttributeTypeS,
|
||||
},
|
||||
},
|
||||
KeySchema: []types.KeySchemaElement{
|
||||
{
|
||||
AttributeName: aws.String("Digest"),
|
||||
KeyType: types.KeyTypeHash,
|
||||
},
|
||||
},
|
||||
ProvisionedThroughput: &types.ProvisionedThroughput{
|
||||
ReadCapacityUnits: aws.Int64(10),
|
||||
WriteCapacityUnits: aws.Int64(5),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
d.tableName = tableName
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewDynamoDBCache(parameters interface{}, log zlog.Logger) Cache {
|
||||
properParameters, ok := parameters.(DynamoDBDriverParameters)
|
||||
if !ok {
|
||||
panic("Failed type assertion!")
|
||||
}
|
||||
|
||||
// custom endpoint resolver to point to localhost
|
||||
customResolver := aws.EndpointResolverWithOptionsFunc(
|
||||
func(service, region string, options ...interface{}) (aws.Endpoint, error) {
|
||||
return aws.Endpoint{
|
||||
PartitionID: "aws",
|
||||
URL: properParameters.Endpoint,
|
||||
SigningRegion: region,
|
||||
}, nil
|
||||
})
|
||||
|
||||
// Using the SDK's default configuration, loading additional config
|
||||
// and credentials values from the environment variables, shared
|
||||
// credentials, and shared configuration files
|
||||
cfg, err := config.LoadDefaultConfig(context.TODO(), config.WithRegion(properParameters.Region),
|
||||
config.WithEndpointResolverWithOptions(customResolver))
|
||||
if err != nil {
|
||||
log.Error().Msgf("unable to load AWS SDK config for dynamodb, %v", err)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Using the Config value, create the DynamoDB client
|
||||
return &DynamoDBDriver{client: dynamodb.NewFromConfig(cfg), tableName: properParameters.TableName, log: log}
|
||||
}
|
||||
|
||||
func (d *DynamoDBDriver) Name() string {
|
||||
return "dynamodb"
|
||||
}
|
||||
|
||||
// Returns the first path of the blob if it exists.
|
||||
func (d *DynamoDBDriver) GetBlob(digest godigest.Digest) (string, error) {
|
||||
resp, err := d.client.GetItem(context.TODO(), &dynamodb.GetItemInput{
|
||||
TableName: aws.String(d.tableName),
|
||||
Key: map[string]types.AttributeValue{
|
||||
"Digest": &types.AttributeValueMemberS{Value: digest.String()},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
d.log.Error().Msgf("failed to get blob %v, %v", d.tableName, err)
|
||||
|
||||
return "", err
|
||||
}
|
||||
|
||||
out := Blob{}
|
||||
|
||||
if resp.Item == nil {
|
||||
return "", zerr.ErrCacheMiss
|
||||
}
|
||||
|
||||
_ = attributevalue.UnmarshalMap(resp.Item, &out)
|
||||
|
||||
if len(out.BlobPath) == 0 {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
return out.BlobPath[0], nil
|
||||
}
|
||||
|
||||
func (d *DynamoDBDriver) PutBlob(digest godigest.Digest, path string) error {
|
||||
if path == "" {
|
||||
d.log.Error().Err(zerr.ErrEmptyValue).Str("digest", digest.String()).Msg("empty path provided")
|
||||
|
||||
return zerr.ErrEmptyValue
|
||||
}
|
||||
|
||||
marshaledKey, _ := attributevalue.MarshalMap(map[string]interface{}{"Digest": digest.String()})
|
||||
expression := "ADD BlobPath :i"
|
||||
attrPath := types.AttributeValueMemberSS{Value: []string{path}}
|
||||
|
||||
if _, err := d.client.UpdateItem(context.TODO(), &dynamodb.UpdateItemInput{
|
||||
Key: marshaledKey,
|
||||
TableName: &d.tableName,
|
||||
UpdateExpression: &expression,
|
||||
ExpressionAttributeValues: map[string]types.AttributeValue{":i": &attrPath},
|
||||
}); err != nil {
|
||||
d.log.Error().Err(err)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DynamoDBDriver) HasBlob(digest godigest.Digest, path string) bool {
|
||||
resp, err := d.client.GetItem(context.TODO(), &dynamodb.GetItemInput{
|
||||
TableName: aws.String(d.tableName),
|
||||
Key: map[string]types.AttributeValue{
|
||||
"Digest": &types.AttributeValueMemberS{Value: digest.String()},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
d.log.Error().Msgf("failed to get blob %v, %v", d.tableName, err)
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
out := Blob{}
|
||||
|
||||
if resp.Item == nil {
|
||||
d.log.Error().Err(zerr.ErrCacheMiss)
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
_ = attributevalue.UnmarshalMap(resp.Item, &out)
|
||||
|
||||
for _, item := range out.BlobPath {
|
||||
if item == path {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
d.log.Error().Err(zerr.ErrCacheMiss)
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (d *DynamoDBDriver) DeleteBlob(digest godigest.Digest, path string) error {
|
||||
marshaledKey, _ := attributevalue.MarshalMap(map[string]interface{}{"Digest": digest.String()})
|
||||
|
||||
expression := "DELETE BlobPath :i"
|
||||
attrPath := types.AttributeValueMemberSS{Value: []string{path}}
|
||||
|
||||
_, err := d.client.UpdateItem(context.TODO(), &dynamodb.UpdateItemInput{
|
||||
Key: marshaledKey,
|
||||
TableName: &d.tableName,
|
||||
UpdateExpression: &expression,
|
||||
ExpressionAttributeValues: map[string]types.AttributeValue{":i": &attrPath},
|
||||
})
|
||||
if err != nil {
|
||||
d.log.Error().Err(err).Str("digest", digest.String()).Str("path", path).Msg("unable to delete")
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
result, _ := d.GetBlob(digest)
|
||||
|
||||
if result == "" {
|
||||
d.log.Debug().Str("digest", digest.String()).Str("path", path).Msg("deleting empty bucket")
|
||||
|
||||
_, _ = d.client.DeleteItem(context.TODO(), &dynamodb.DeleteItemInput{
|
||||
Key: marshaledKey,
|
||||
TableName: &d.tableName,
|
||||
})
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
Vendored
+111
@@ -0,0 +1,111 @@
|
||||
package cache_test
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path"
|
||||
"testing"
|
||||
|
||||
godigest "github.com/opencontainers/go-digest"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
|
||||
"zotregistry.io/zot/pkg/log"
|
||||
"zotregistry.io/zot/pkg/storage"
|
||||
"zotregistry.io/zot/pkg/storage/cache"
|
||||
)
|
||||
|
||||
func skipIt(t *testing.T) {
|
||||
t.Helper()
|
||||
|
||||
if os.Getenv("DYNAMODBMOCK_ENDPOINT") == "" {
|
||||
t.Skip("Skipping testing without AWS DynamoDB mock server")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDynamoDB(t *testing.T) {
|
||||
skipIt(t)
|
||||
Convey("Test dynamoDB", t, func(c C) {
|
||||
log := log.NewLogger("debug", "")
|
||||
dir := t.TempDir()
|
||||
|
||||
// bad params
|
||||
|
||||
So(func() {
|
||||
_ = cache.NewDynamoDBCache("bad params", log)
|
||||
}, ShouldPanic)
|
||||
|
||||
keyDigest := godigest.FromString("key")
|
||||
|
||||
cacheDriver, err := storage.Create("dynamodb", cache.DynamoDBDriverParameters{
|
||||
Endpoint: "http://brokenlink",
|
||||
TableName: "BlobTable",
|
||||
Region: "us-east-2",
|
||||
}, log)
|
||||
So(cacheDriver, ShouldNotBeNil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
val, err := cacheDriver.GetBlob(keyDigest)
|
||||
So(err, ShouldNotBeNil)
|
||||
So(val, ShouldBeEmpty)
|
||||
|
||||
err = cacheDriver.PutBlob(keyDigest, path.Join(dir, "value"))
|
||||
So(err, ShouldNotBeNil)
|
||||
|
||||
exists := cacheDriver.HasBlob(keyDigest, path.Join(dir, "value"))
|
||||
So(exists, ShouldBeFalse)
|
||||
|
||||
err = cacheDriver.DeleteBlob(keyDigest, path.Join(dir, "value"))
|
||||
So(err, ShouldNotBeNil)
|
||||
|
||||
cacheDriver, err = storage.Create("dynamodb", cache.DynamoDBDriverParameters{
|
||||
Endpoint: os.Getenv("DYNAMODBMOCK_ENDPOINT"),
|
||||
TableName: "BlobTable",
|
||||
Region: "us-east-2",
|
||||
}, log)
|
||||
So(cacheDriver, ShouldNotBeNil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
returnedName := cacheDriver.Name()
|
||||
So(returnedName, ShouldEqual, "dynamodb")
|
||||
|
||||
val, err = cacheDriver.GetBlob(keyDigest)
|
||||
So(err, ShouldNotBeNil)
|
||||
So(val, ShouldBeEmpty)
|
||||
|
||||
err = cacheDriver.PutBlob(keyDigest, "")
|
||||
So(err, ShouldNotBeNil)
|
||||
|
||||
err = cacheDriver.PutBlob(keyDigest, path.Join(dir, "value"))
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
val, err = cacheDriver.GetBlob(keyDigest)
|
||||
So(err, ShouldBeNil)
|
||||
So(val, ShouldNotBeEmpty)
|
||||
|
||||
exists = cacheDriver.HasBlob(keyDigest, path.Join(dir, "value"))
|
||||
So(exists, ShouldBeTrue)
|
||||
|
||||
err = cacheDriver.DeleteBlob(keyDigest, path.Join(dir, "value"))
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
exists = cacheDriver.HasBlob(keyDigest, path.Join(dir, "value"))
|
||||
So(exists, ShouldBeFalse)
|
||||
|
||||
err = cacheDriver.PutBlob(keyDigest, path.Join(dir, "value1"))
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = cacheDriver.PutBlob(keyDigest, path.Join(dir, "value2"))
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = cacheDriver.DeleteBlob(keyDigest, path.Join(dir, "value1"))
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
exists = cacheDriver.HasBlob(keyDigest, path.Join(dir, "value2"))
|
||||
So(exists, ShouldBeTrue)
|
||||
|
||||
exists = cacheDriver.HasBlob(keyDigest, path.Join(dir, "value1"))
|
||||
So(exists, ShouldBeFalse)
|
||||
|
||||
err = cacheDriver.DeleteBlob(keyDigest, path.Join(dir, "value2"))
|
||||
So(err, ShouldBeNil)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,520 @@
|
||||
package storage_test
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"os/exec"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
godigest "github.com/opencontainers/go-digest"
|
||||
|
||||
"zotregistry.io/zot/pkg/log"
|
||||
"zotregistry.io/zot/pkg/storage"
|
||||
"zotregistry.io/zot/pkg/storage/cache"
|
||||
)
|
||||
|
||||
const (
|
||||
region string = "us-east-2"
|
||||
localEndpoint string = "http://localhost:4566"
|
||||
awsEndpoint string = "https://dynamodb.us-east-2.amazonaws.com"
|
||||
datasetSize int = 5000
|
||||
)
|
||||
|
||||
func generateRandomString() string {
|
||||
//nolint: gosec
|
||||
seededRand := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
charset := "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
|
||||
randomBytes := make([]byte, 10)
|
||||
for i := range randomBytes {
|
||||
randomBytes[i] = charset[seededRand.Intn(len(charset))]
|
||||
}
|
||||
|
||||
return string(randomBytes)
|
||||
}
|
||||
|
||||
func generateData() map[godigest.Digest]string {
|
||||
dataMap := make(map[godigest.Digest]string, datasetSize)
|
||||
//nolint: gosec
|
||||
seededRand := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
|
||||
for i := 0; i < datasetSize; i++ {
|
||||
randomString := generateRandomString()
|
||||
counter := 0
|
||||
|
||||
for seededRand.Float32() < 0.5 && counter < 5 {
|
||||
counter++
|
||||
randomString += "/"
|
||||
randomString += generateRandomString()
|
||||
}
|
||||
digest := godigest.FromString(randomString)
|
||||
dataMap[digest] = randomString
|
||||
}
|
||||
|
||||
return dataMap
|
||||
}
|
||||
|
||||
func helperPutAll(cache cache.Cache, testData map[godigest.Digest]string) {
|
||||
for digest, path := range testData {
|
||||
_ = cache.PutBlob(digest, path)
|
||||
}
|
||||
}
|
||||
|
||||
func helperDeleteAll(cache cache.Cache, testData map[godigest.Digest]string) {
|
||||
for digest, path := range testData {
|
||||
_ = cache.DeleteBlob(digest, path)
|
||||
}
|
||||
}
|
||||
|
||||
func helperHasAll(cache cache.Cache, testData map[godigest.Digest]string) {
|
||||
for digest, path := range testData {
|
||||
_ = cache.HasBlob(digest, path)
|
||||
}
|
||||
}
|
||||
|
||||
func helperGetAll(cache cache.Cache, testData map[godigest.Digest]string) {
|
||||
for digest := range testData {
|
||||
_, _ = cache.GetBlob(digest)
|
||||
}
|
||||
}
|
||||
|
||||
func helperMix(cache cache.Cache, testData map[godigest.Digest]string, digestSlice []godigest.Digest) {
|
||||
// The test data contains datasetSize entries by default, and each set of operations uses 5 entries
|
||||
for i := 0; i < 1000; i++ {
|
||||
_ = cache.PutBlob(digestSlice[i*5], testData[digestSlice[i*5]])
|
||||
_ = cache.PutBlob(digestSlice[i*5+1], testData[digestSlice[i*5+1]])
|
||||
_ = cache.PutBlob(digestSlice[i*5+2], testData[digestSlice[i*5+2]])
|
||||
_ = cache.PutBlob(digestSlice[i*5+2], testData[digestSlice[i*5+3]])
|
||||
_ = cache.DeleteBlob(digestSlice[i*5+1], testData[digestSlice[i*5+1]])
|
||||
_ = cache.DeleteBlob(digestSlice[i*5+2], testData[digestSlice[i*5+3]])
|
||||
_ = cache.DeleteBlob(digestSlice[i*5+2], testData[digestSlice[i*5+2]])
|
||||
_ = cache.HasBlob(digestSlice[i*5], testData[digestSlice[i*5]])
|
||||
_ = cache.HasBlob(digestSlice[i*5+1], testData[digestSlice[i*5+1]])
|
||||
_, _ = cache.GetBlob(digestSlice[i*5])
|
||||
_, _ = cache.GetBlob(digestSlice[i*5+1])
|
||||
_ = cache.PutBlob(digestSlice[i*5], testData[digestSlice[i*5+4]])
|
||||
_, _ = cache.GetBlob(digestSlice[i*5+4])
|
||||
_ = cache.DeleteBlob(digestSlice[i*5], testData[digestSlice[i*5+4]])
|
||||
_ = cache.DeleteBlob(digestSlice[i*5], testData[digestSlice[i*5]])
|
||||
}
|
||||
}
|
||||
|
||||
// BoltDB tests
|
||||
|
||||
func BenchmarkPutLocal(b *testing.B) {
|
||||
dir := b.TempDir()
|
||||
log := log.NewLogger("error", "")
|
||||
cache, _ := storage.Create("boltdb", cache.BoltDBDriverParameters{
|
||||
RootDir: dir,
|
||||
Name: "cache_test",
|
||||
UseRelPaths: false,
|
||||
}, log)
|
||||
testData := generateData()
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
helperPutAll(cache, testData)
|
||||
}
|
||||
|
||||
func BenchmarkDeleteLocal(b *testing.B) {
|
||||
dir := b.TempDir()
|
||||
log := log.NewLogger("error", "")
|
||||
cache, _ := storage.Create("boltdb", cache.BoltDBDriverParameters{
|
||||
RootDir: dir,
|
||||
Name: "cache_test",
|
||||
UseRelPaths: false,
|
||||
}, log)
|
||||
testData := generateData()
|
||||
|
||||
for digest, path := range testData {
|
||||
_ = cache.PutBlob(digest, path)
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
helperDeleteAll(cache, testData)
|
||||
}
|
||||
|
||||
func BenchmarkHasLocal(b *testing.B) {
|
||||
dir := b.TempDir()
|
||||
log := log.NewLogger("error", "")
|
||||
cache, _ := storage.Create("boltdb", cache.BoltDBDriverParameters{
|
||||
RootDir: dir,
|
||||
Name: "cache_test",
|
||||
UseRelPaths: false,
|
||||
}, log)
|
||||
testData := generateData()
|
||||
|
||||
for digest, path := range testData {
|
||||
_ = cache.PutBlob(digest, path)
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
helperHasAll(cache, testData)
|
||||
}
|
||||
|
||||
func BenchmarkGetLocal(b *testing.B) {
|
||||
dir := b.TempDir()
|
||||
log := log.NewLogger("error", "")
|
||||
cache, _ := storage.Create("boltdb", cache.BoltDBDriverParameters{
|
||||
RootDir: dir,
|
||||
Name: "cache_test",
|
||||
UseRelPaths: false,
|
||||
}, log)
|
||||
testData := generateData()
|
||||
counter := 1
|
||||
|
||||
var previousDigest godigest.Digest
|
||||
|
||||
for digest, path := range testData {
|
||||
if counter != 10 {
|
||||
_ = cache.PutBlob(digest, path)
|
||||
previousDigest = digest
|
||||
counter++
|
||||
} else {
|
||||
_ = cache.PutBlob(previousDigest, path)
|
||||
counter = 1
|
||||
}
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
helperGetAll(cache, testData)
|
||||
}
|
||||
|
||||
func BenchmarkMixLocal(b *testing.B) {
|
||||
dir := b.TempDir()
|
||||
log := log.NewLogger("error", "")
|
||||
cache, _ := storage.Create("boltdb", cache.BoltDBDriverParameters{
|
||||
RootDir: dir,
|
||||
Name: "cache_test",
|
||||
UseRelPaths: false,
|
||||
}, log)
|
||||
testData := generateData()
|
||||
digestSlice := make([]godigest.Digest, datasetSize)
|
||||
counter := 0
|
||||
|
||||
for key := range testData {
|
||||
digestSlice[counter] = key
|
||||
counter++
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
helperMix(cache, testData, digestSlice)
|
||||
}
|
||||
|
||||
// DynamoDB Local tests
|
||||
|
||||
func BenchmarkPutLocalstack(b *testing.B) {
|
||||
log := log.NewLogger("error", "")
|
||||
tableName := generateRandomString()
|
||||
|
||||
// Create Table
|
||||
_, err := exec.Command("aws", "dynamodb", "--region", region, "--endpoint-url", localEndpoint, "create-table",
|
||||
"--table-name", tableName, "--attribute-definitions", "AttributeName=Digest,AttributeType=S",
|
||||
"--key-schema", "AttributeName=Digest,KeyType=HASH",
|
||||
"--billing-mode", "PAY_PER_REQUEST").Output()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
cache, _ := storage.Create("dynamodb", cache.DynamoDBDriverParameters{
|
||||
Endpoint: localEndpoint,
|
||||
Region: region,
|
||||
TableName: tableName,
|
||||
}, log)
|
||||
testData := generateData()
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
helperPutAll(cache, testData)
|
||||
}
|
||||
|
||||
func BenchmarkDeleteLocalstack(b *testing.B) {
|
||||
log := log.NewLogger("error", "")
|
||||
tableName := generateRandomString()
|
||||
|
||||
// Create Table
|
||||
_, err := exec.Command("aws", "dynamodb", "--region", region, "--endpoint-url", localEndpoint, "create-table",
|
||||
"--table-name", tableName, "--attribute-definitions", "AttributeName=Digest,AttributeType=S",
|
||||
"--key-schema", "AttributeName=Digest,KeyType=HASH",
|
||||
"--billing-mode", "PAY_PER_REQUEST").Output()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
cache, _ := storage.Create("dynamodb", cache.DynamoDBDriverParameters{
|
||||
Endpoint: localEndpoint,
|
||||
Region: region,
|
||||
TableName: tableName,
|
||||
}, log)
|
||||
testData := generateData()
|
||||
|
||||
for digest, path := range testData {
|
||||
_ = cache.PutBlob(digest, path)
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
helperDeleteAll(cache, testData)
|
||||
}
|
||||
|
||||
func BenchmarkHasLocalstack(b *testing.B) {
|
||||
log := log.NewLogger("error", "")
|
||||
tableName := generateRandomString()
|
||||
|
||||
// Create Table
|
||||
_, err := exec.Command("aws", "dynamodb", "--region", region, "--endpoint-url", localEndpoint, "create-table",
|
||||
"--table-name", tableName, "--attribute-definitions", "AttributeName=Digest,AttributeType=S",
|
||||
"--key-schema", "AttributeName=Digest,KeyType=HASH",
|
||||
"--billing-mode", "PAY_PER_REQUEST").Output()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
cache, _ := storage.Create("dynamodb", cache.DynamoDBDriverParameters{
|
||||
Endpoint: localEndpoint,
|
||||
Region: region,
|
||||
TableName: tableName,
|
||||
}, log)
|
||||
testData := generateData()
|
||||
|
||||
for digest, path := range testData {
|
||||
_ = cache.PutBlob(digest, path)
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
helperHasAll(cache, testData)
|
||||
}
|
||||
|
||||
func BenchmarkGetLocalstack(b *testing.B) {
|
||||
log := log.NewLogger("error", "")
|
||||
tableName := generateRandomString()
|
||||
|
||||
// Create Table
|
||||
_, err := exec.Command("aws", "dynamodb", "--region", region, "--endpoint-url", localEndpoint, "create-table",
|
||||
"--table-name", tableName, "--attribute-definitions", "AttributeName=Digest,AttributeType=S",
|
||||
"--key-schema", "AttributeName=Digest,KeyType=HASH",
|
||||
"--billing-mode", "PAY_PER_REQUEST").Output()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
cache, _ := storage.Create("dynamodb", cache.DynamoDBDriverParameters{
|
||||
Endpoint: localEndpoint,
|
||||
Region: region,
|
||||
TableName: tableName,
|
||||
}, log)
|
||||
testData := generateData()
|
||||
counter := 1
|
||||
|
||||
var previousDigest godigest.Digest
|
||||
|
||||
for digest, path := range testData {
|
||||
if counter != 10 {
|
||||
_ = cache.PutBlob(digest, path)
|
||||
previousDigest = digest
|
||||
counter++
|
||||
} else {
|
||||
_ = cache.PutBlob(previousDigest, path)
|
||||
counter = 1
|
||||
}
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
helperGetAll(cache, testData)
|
||||
}
|
||||
|
||||
func BenchmarkMixLocalstack(b *testing.B) {
|
||||
log := log.NewLogger("error", "")
|
||||
tableName := generateRandomString()
|
||||
|
||||
// Create Table
|
||||
_, err := exec.Command("aws", "dynamodb", "--region", region, "--endpoint-url", localEndpoint, "create-table",
|
||||
"--table-name", tableName, "--attribute-definitions", "AttributeName=Digest,AttributeType=S",
|
||||
"--key-schema", "AttributeName=Digest,KeyType=HASH",
|
||||
"--billing-mode", "PAY_PER_REQUEST").Output()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
cache, _ := storage.Create("dynamodb", cache.DynamoDBDriverParameters{
|
||||
Endpoint: localEndpoint,
|
||||
Region: region,
|
||||
TableName: tableName,
|
||||
}, log)
|
||||
testData := generateData()
|
||||
digestSlice := make([]godigest.Digest, datasetSize)
|
||||
counter := 0
|
||||
|
||||
for key := range testData {
|
||||
digestSlice[counter] = key
|
||||
counter++
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
helperMix(cache, testData, digestSlice)
|
||||
}
|
||||
|
||||
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
// DANGER ZONE: tests with true AWS endpoint
|
||||
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
|
||||
func BenchmarkPutAWS(b *testing.B) {
|
||||
log := log.NewLogger("error", "")
|
||||
tableName := generateRandomString()
|
||||
|
||||
// Create Table
|
||||
_, err := exec.Command("aws", "dynamodb", "--region", region, "--endpoint-url", awsEndpoint, "create-table",
|
||||
"--table-name", tableName, "--attribute-definitions", "AttributeName=Digest,AttributeType=S",
|
||||
"--key-schema", "AttributeName=Digest,KeyType=HASH",
|
||||
"--billing-mode", "PAY_PER_REQUEST").Output()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
cache, _ := storage.Create("dynamodb", cache.DynamoDBDriverParameters{
|
||||
Endpoint: awsEndpoint,
|
||||
Region: region,
|
||||
TableName: tableName,
|
||||
}, log)
|
||||
testData := generateData()
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
helperPutAll(cache, testData)
|
||||
}
|
||||
|
||||
func BenchmarkDeleteAWS(b *testing.B) {
|
||||
log := log.NewLogger("error", "")
|
||||
tableName := generateRandomString()
|
||||
|
||||
// Create Table
|
||||
_, err := exec.Command("aws", "dynamodb", "--region", region, "--endpoint-url", awsEndpoint, "create-table",
|
||||
"--table-name", tableName, "--attribute-definitions", "AttributeName=Digest,AttributeType=S",
|
||||
"--key-schema", "AttributeName=Digest,KeyType=HASH",
|
||||
"--billing-mode", "PAY_PER_REQUEST").Output()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
cache, _ := storage.Create("dynamodb", cache.DynamoDBDriverParameters{
|
||||
Endpoint: awsEndpoint,
|
||||
Region: region,
|
||||
TableName: tableName,
|
||||
}, log)
|
||||
testData := generateData()
|
||||
|
||||
for digest, path := range testData {
|
||||
_ = cache.PutBlob(digest, path)
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
helperDeleteAll(cache, testData)
|
||||
}
|
||||
|
||||
func BenchmarkHasAWS(b *testing.B) {
|
||||
log := log.NewLogger("error", "")
|
||||
tableName := generateRandomString()
|
||||
|
||||
// Create Table
|
||||
_, err := exec.Command("aws", "dynamodb", "--region", region, "--endpoint-url", awsEndpoint, "create-table",
|
||||
"--table-name", tableName, "--attribute-definitions", "AttributeName=Digest,AttributeType=S",
|
||||
"--key-schema", "AttributeName=Digest,KeyType=HASH",
|
||||
"--billing-mode", "PAY_PER_REQUEST").Output()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
cache, _ := storage.Create("dynamodb", cache.DynamoDBDriverParameters{
|
||||
Endpoint: awsEndpoint,
|
||||
Region: region,
|
||||
TableName: tableName,
|
||||
}, log)
|
||||
testData := generateData()
|
||||
|
||||
for digest, path := range testData {
|
||||
_ = cache.PutBlob(digest, path)
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
helperHasAll(cache, testData)
|
||||
}
|
||||
|
||||
func BenchmarkGetAWS(b *testing.B) {
|
||||
log := log.NewLogger("error", "")
|
||||
tableName := generateRandomString()
|
||||
|
||||
// Create Table
|
||||
_, err := exec.Command("aws", "dynamodb", "--region", region, "--endpoint-url", awsEndpoint, "create-table",
|
||||
"--table-name", tableName, "--attribute-definitions", "AttributeName=Digest,AttributeType=S",
|
||||
"--key-schema", "AttributeName=Digest,KeyType=HASH",
|
||||
"--billing-mode", "PAY_PER_REQUEST").Output()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
cache, _ := storage.Create("dynamodb", cache.DynamoDBDriverParameters{
|
||||
Endpoint: awsEndpoint,
|
||||
Region: region,
|
||||
TableName: tableName,
|
||||
}, log)
|
||||
testData := generateData()
|
||||
counter := 1
|
||||
|
||||
var previousDigest godigest.Digest
|
||||
|
||||
for digest, path := range testData {
|
||||
if counter != 10 {
|
||||
_ = cache.PutBlob(digest, path)
|
||||
previousDigest = digest
|
||||
counter++
|
||||
} else {
|
||||
_ = cache.PutBlob(previousDigest, path)
|
||||
counter = 1
|
||||
}
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
helperGetAll(cache, testData)
|
||||
}
|
||||
|
||||
func BenchmarkMixAWS(b *testing.B) {
|
||||
log := log.NewLogger("error", "")
|
||||
tableName := generateRandomString()
|
||||
|
||||
// Create Table
|
||||
_, err := exec.Command("aws", "dynamodb", "--region", region, "--endpoint-url", awsEndpoint, "create-table",
|
||||
"--table-name", tableName, "--attribute-definitions", "AttributeName=Digest,AttributeType=S",
|
||||
"--key-schema", "AttributeName=Digest,KeyType=HASH",
|
||||
"--billing-mode", "PAY_PER_REQUEST").Output()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
cache, _ := storage.Create("dynamodb", cache.DynamoDBDriverParameters{
|
||||
Endpoint: awsEndpoint,
|
||||
Region: region,
|
||||
TableName: tableName,
|
||||
}, log)
|
||||
testData := generateData()
|
||||
digestSlice := make([]godigest.Digest, datasetSize)
|
||||
counter := 0
|
||||
|
||||
for key := range testData {
|
||||
digestSlice[counter] = key
|
||||
counter++
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
helperMix(cache, testData, digestSlice)
|
||||
}
|
||||
@@ -19,5 +19,5 @@ const (
|
||||
DBCacheLockCheckTimeout = 10 * time.Second
|
||||
BoltdbName = "cache"
|
||||
ReferrerFilterAnnotation = "org.opencontainers.references.filtersApplied"
|
||||
//
|
||||
DynamoDBDriverName = "dynamodb"
|
||||
)
|
||||
|
||||
+250
-2
@@ -44,6 +44,7 @@ var (
|
||||
fileInfoSize = 10
|
||||
errorText = "new s3 error"
|
||||
errS3 = errors.New(errorText)
|
||||
zotStorageTest = "zot-storage-test"
|
||||
)
|
||||
|
||||
func cleanupStorage(store driver.StorageDriver, name string) {
|
||||
@@ -56,6 +57,10 @@ func skipIt(t *testing.T) {
|
||||
if os.Getenv("S3MOCK_ENDPOINT") == "" {
|
||||
t.Skip("Skipping testing without AWS S3 mock server")
|
||||
}
|
||||
|
||||
if os.Getenv("DYNAMODBMOCK_ENDPOINT") == "" {
|
||||
t.Skip("Skipping testing without AWS DynamoDB mock server")
|
||||
}
|
||||
}
|
||||
|
||||
func createMockStorage(rootDir string, cacheDir string, dedupe bool, store driver.StorageDriver) storage.ImageStore {
|
||||
@@ -84,7 +89,7 @@ func createObjectsStore(rootDir string, cacheDir string, dedupe bool) (
|
||||
storage.ImageStore,
|
||||
error,
|
||||
) {
|
||||
bucket := "zot-storage-test"
|
||||
bucket := zotStorageTest
|
||||
endpoint := os.Getenv("S3MOCK_ENDPOINT")
|
||||
storageDriverParams := map[string]interface{}{
|
||||
"rootDir": rootDir,
|
||||
@@ -130,6 +135,66 @@ func createObjectsStore(rootDir string, cacheDir string, dedupe bool) (
|
||||
return store, il, err
|
||||
}
|
||||
|
||||
func createObjectsStoreDynamo(rootDir string, cacheDir string, dedupe bool, tableName string) (
|
||||
driver.StorageDriver,
|
||||
storage.ImageStore,
|
||||
error,
|
||||
) {
|
||||
bucket := zotStorageTest
|
||||
endpoint := os.Getenv("S3MOCK_ENDPOINT")
|
||||
storageDriverParams := map[string]interface{}{
|
||||
"rootDir": rootDir,
|
||||
"name": "s3",
|
||||
"region": "us-east-2",
|
||||
"bucket": bucket,
|
||||
"regionendpoint": endpoint,
|
||||
"accesskey": "minioadmin",
|
||||
"secretkey": "minioadmin",
|
||||
"secure": false,
|
||||
"skipverify": false,
|
||||
}
|
||||
|
||||
storeName := fmt.Sprintf("%v", storageDriverParams["name"])
|
||||
|
||||
store, err := factory.Create(storeName, storageDriverParams)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// create bucket if it doesn't exists
|
||||
_, err = resty.R().Put("http://" + endpoint + "/" + bucket)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
log := log.Logger{Logger: zerolog.New(os.Stdout)}
|
||||
metrics := monitoring.NewMetricsServer(false, log)
|
||||
|
||||
var cacheDriver cache.Cache
|
||||
|
||||
// from pkg/cli/root.go/applyDefaultValues, s3 magic
|
||||
|
||||
cacheDriver, _ = storage.Create("dynamodb", cache.DynamoDBDriverParameters{
|
||||
Endpoint: os.Getenv("DYNAMODBMOCK_ENDPOINT"),
|
||||
Region: os.Getenv("us-east-2"),
|
||||
TableName: tableName,
|
||||
}, log)
|
||||
|
||||
tableName = strings.ReplaceAll(tableName, "/", "")
|
||||
//nolint:errcheck
|
||||
cacheDriverDynamo, _ := cacheDriver.(*cache.DynamoDBDriver)
|
||||
|
||||
err = cacheDriverDynamo.NewTable(tableName)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
il := s3.NewImageStore(rootDir, cacheDir, false, storage.DefaultGCDelay,
|
||||
dedupe, false, log, metrics, nil, store, cacheDriver)
|
||||
|
||||
return store, il, err
|
||||
}
|
||||
|
||||
type FileInfoMock struct {
|
||||
IsDirFn func() bool
|
||||
SizeFn func() int64
|
||||
@@ -607,7 +672,7 @@ func TestNegativeCasesObjectsStorage(t *testing.T) {
|
||||
})
|
||||
|
||||
Convey("Unable to create subpath cache db", func(c C) {
|
||||
bucket := "zot-storage-test"
|
||||
bucket := zotStorageTest
|
||||
endpoint := os.Getenv("S3MOCK_ENDPOINT")
|
||||
|
||||
storageDriverParams := config.GlobalStorageConfig{
|
||||
@@ -1448,6 +1513,189 @@ func TestS3Dedupe(t *testing.T) {
|
||||
So(fi1.Size(), ShouldEqual, fi3.Size())
|
||||
})
|
||||
})
|
||||
|
||||
Convey("Dedupe with dynamodb", t, func(c C) {
|
||||
uuid, err := guuid.NewV4()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
testDir := path.Join("/oci-repo-test", uuid.String())
|
||||
|
||||
tdir := t.TempDir()
|
||||
|
||||
storeDriver, imgStore, _ := createObjectsStoreDynamo(testDir, tdir, true, tdir)
|
||||
defer cleanupStorage(storeDriver, testDir)
|
||||
|
||||
// manifest1
|
||||
upload, err := imgStore.NewBlobUpload("dedupe1")
|
||||
So(err, ShouldBeNil)
|
||||
So(upload, ShouldNotBeEmpty)
|
||||
|
||||
content := []byte("test-data3")
|
||||
buf := bytes.NewBuffer(content)
|
||||
buflen := buf.Len()
|
||||
digest := godigest.FromBytes(content)
|
||||
blob, err := imgStore.PutBlobChunkStreamed("dedupe1", upload, buf)
|
||||
So(err, ShouldBeNil)
|
||||
So(blob, ShouldEqual, buflen)
|
||||
blobDigest1 := digest
|
||||
So(blobDigest1, ShouldNotBeEmpty)
|
||||
|
||||
err = imgStore.FinishBlobUpload("dedupe1", upload, buf, digest)
|
||||
So(err, ShouldBeNil)
|
||||
So(blob, ShouldEqual, buflen)
|
||||
|
||||
_, checkBlobSize1, err := imgStore.CheckBlob("dedupe1", digest)
|
||||
So(checkBlobSize1, ShouldBeGreaterThan, 0)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
blobReadCloser, getBlobSize1, err := imgStore.GetBlob("dedupe1", digest,
|
||||
"application/vnd.oci.image.layer.v1.tar+gzip")
|
||||
So(getBlobSize1, ShouldBeGreaterThan, 0)
|
||||
So(err, ShouldBeNil)
|
||||
err = blobReadCloser.Close()
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
cblob, cdigest := test.GetRandomImageConfig()
|
||||
_, clen, err := imgStore.FullBlobUpload("dedupe1", bytes.NewReader(cblob), cdigest)
|
||||
So(err, ShouldBeNil)
|
||||
So(clen, ShouldEqual, len(cblob))
|
||||
hasBlob, _, err := imgStore.CheckBlob("dedupe1", cdigest)
|
||||
So(err, ShouldBeNil)
|
||||
So(hasBlob, ShouldEqual, true)
|
||||
|
||||
manifest := ispec.Manifest{
|
||||
Config: ispec.Descriptor{
|
||||
MediaType: "application/vnd.oci.image.config.v1+json",
|
||||
Digest: cdigest,
|
||||
Size: int64(len(cblob)),
|
||||
},
|
||||
Layers: []ispec.Descriptor{
|
||||
{
|
||||
MediaType: "application/vnd.oci.image.layer.v1.tar",
|
||||
Digest: digest,
|
||||
Size: int64(buflen),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
manifest.SchemaVersion = 2
|
||||
manifestBuf, err := json.Marshal(manifest)
|
||||
So(err, ShouldBeNil)
|
||||
digest = godigest.FromBytes(manifestBuf)
|
||||
_, err = imgStore.PutImageManifest("dedupe1", digest.String(),
|
||||
ispec.MediaTypeImageManifest, manifestBuf)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
_, _, _, err = imgStore.GetImageManifest("dedupe1", digest.String())
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
// manifest2
|
||||
upload, err = imgStore.NewBlobUpload("dedupe2")
|
||||
So(err, ShouldBeNil)
|
||||
So(upload, ShouldNotBeEmpty)
|
||||
|
||||
content = []byte("test-data3")
|
||||
buf = bytes.NewBuffer(content)
|
||||
buflen = buf.Len()
|
||||
digest = godigest.FromBytes(content)
|
||||
|
||||
blob, err = imgStore.PutBlobChunkStreamed("dedupe2", upload, buf)
|
||||
So(err, ShouldBeNil)
|
||||
So(blob, ShouldEqual, buflen)
|
||||
blobDigest2 := digest
|
||||
So(blobDigest2, ShouldNotBeEmpty)
|
||||
|
||||
err = imgStore.FinishBlobUpload("dedupe2", upload, buf, digest)
|
||||
So(err, ShouldBeNil)
|
||||
So(blob, ShouldEqual, buflen)
|
||||
|
||||
_, checkBlobSize2, err := imgStore.CheckBlob("dedupe2", digest)
|
||||
So(err, ShouldBeNil)
|
||||
So(checkBlobSize2, ShouldBeGreaterThan, 0)
|
||||
|
||||
blobReadCloser, getBlobSize2, err := imgStore.GetBlob("dedupe2", digest,
|
||||
"application/vnd.oci.image.layer.v1.tar+gzip")
|
||||
So(err, ShouldBeNil)
|
||||
So(getBlobSize2, ShouldBeGreaterThan, 0)
|
||||
So(checkBlobSize1, ShouldEqual, checkBlobSize2)
|
||||
So(getBlobSize1, ShouldEqual, getBlobSize2)
|
||||
err = blobReadCloser.Close()
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
cblob, cdigest = test.GetRandomImageConfig()
|
||||
_, clen, err = imgStore.FullBlobUpload("dedupe2", bytes.NewReader(cblob), cdigest)
|
||||
So(err, ShouldBeNil)
|
||||
So(clen, ShouldEqual, len(cblob))
|
||||
hasBlob, _, err = imgStore.CheckBlob("dedupe2", cdigest)
|
||||
So(err, ShouldBeNil)
|
||||
So(hasBlob, ShouldEqual, true)
|
||||
|
||||
manifest = ispec.Manifest{
|
||||
Config: ispec.Descriptor{
|
||||
MediaType: "application/vnd.oci.image.config.v1+json",
|
||||
Digest: cdigest,
|
||||
Size: int64(len(cblob)),
|
||||
},
|
||||
Layers: []ispec.Descriptor{
|
||||
{
|
||||
MediaType: "application/vnd.oci.image.layer.v1.tar",
|
||||
Digest: digest,
|
||||
Size: int64(buflen),
|
||||
},
|
||||
},
|
||||
}
|
||||
manifest.SchemaVersion = 2
|
||||
manifestBuf, err = json.Marshal(manifest)
|
||||
So(err, ShouldBeNil)
|
||||
digest = godigest.FromBytes(manifestBuf)
|
||||
_, err = imgStore.PutImageManifest("dedupe2", "1.0", ispec.MediaTypeImageManifest,
|
||||
manifestBuf)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
_, _, _, err = imgStore.GetImageManifest("dedupe2", digest.String())
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
fi1, err := storeDriver.Stat(context.Background(), path.Join(testDir, "dedupe1", "blobs", "sha256",
|
||||
blobDigest1.Encoded()))
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
fi2, err := storeDriver.Stat(context.Background(), path.Join(testDir, "dedupe2", "blobs", "sha256",
|
||||
blobDigest2.Encoded()))
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
// original blob should have the real content of blob
|
||||
So(fi1.Size(), ShouldNotEqual, fi2.Size())
|
||||
So(fi1.Size(), ShouldBeGreaterThan, 0)
|
||||
// deduped blob should be of size 0
|
||||
So(fi2.Size(), ShouldEqual, 0)
|
||||
|
||||
Convey("Check that delete blobs moves the real content to the next contenders", func() {
|
||||
// if we delete blob1, the content should be moved to blob2
|
||||
err = imgStore.DeleteBlob("dedupe1", blobDigest1)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
_, err = storeDriver.Stat(context.Background(), path.Join(testDir, "dedupe1", "blobs", "sha256",
|
||||
blobDigest1.Encoded()))
|
||||
So(err, ShouldNotBeNil)
|
||||
|
||||
fi2, err = storeDriver.Stat(context.Background(), path.Join(testDir, "dedupe2", "blobs", "sha256",
|
||||
blobDigest2.Encoded()))
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(fi2.Size(), ShouldBeGreaterThan, 0)
|
||||
// the second blob should now be equal to the deleted blob.
|
||||
So(fi2.Size(), ShouldEqual, fi1.Size())
|
||||
|
||||
err = imgStore.DeleteBlob("dedupe2", blobDigest2)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
_, err = storeDriver.Stat(context.Background(), path.Join(testDir, "dedupe2", "blobs", "sha256",
|
||||
blobDigest2.Encoded()))
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestS3PullRange(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user