Skip to content

Commit

Permalink
feat: wrap content to support remote cache
Browse files Browse the repository at this point in the history
We can use  LRU in content store  and wrap some methods to provides  remote cache. Here Info, Update, ReaderAt, Walk methods were wraped and they can get, update, and do other operations in LRU cache.

Also, we can configure whether to enable cache or not with cache_tag_suffix.

Signed-off-by: breezeTuT <y_q_email@163.com>
  • Loading branch information
PerseidMeteor committed Aug 15, 2023
1 parent f132200 commit 60bdacc
Show file tree
Hide file tree
Showing 10 changed files with 261 additions and 23 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ require (
github.com/docker/cli v23.0.3+incompatible
github.com/dustin/go-humanize v1.0.1
github.com/google/uuid v1.3.0
github.com/hashicorp/golang-lru/v2 v2.0.4
github.com/labstack/echo-contrib v0.14.1
github.com/labstack/echo/v4 v4.10.2
github.com/labstack/gommon v0.4.0
Expand Down
3 changes: 3 additions & 0 deletions misc/config/config.nydus.ref.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,6 @@ converter:
rules:
# add suffix to tag of source image reference as target image reference
- tag_suffix: -nydus-oci-ref
# add suffix to tag of remote cache reference, default cache size is 200, leave empty to disable remote cache.
- cache_tag_suffix: -nydus-cache

3 changes: 3 additions & 0 deletions misc/config/config.nydus.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -100,3 +100,6 @@ converter:
rules:
# add suffix to tag of source image reference as target image reference
- tag_suffix: -nydus
# add suffix to tag of remote cache reference, default cache size is 200, leave empty to disable remote cache.
- cache_tag_suffix: -nydus-cache

24 changes: 20 additions & 4 deletions pkg/adapter/adapter.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,14 @@ func NewLocalAdapter(cfg *config.Config) (*LocalAdapter, error) {
if err != nil {
return nil, errors.Wrap(err, "invalid platform configuration")
}

provider, content, err := content.NewLocalProvider(cfg.Provider.WorkDir, cfg.Provider.GCPolicy.Threshold, cfg.Host, platformMC)
useRemoteCache := false
for _, rule := range cfg.Converter.Rules {
if rule.CacheTagSuffix != ""{
useRemoteCache = true
break
}
}
provider, content, err := content.NewLocalProvider(cfg.Provider.WorkDir, cfg.Provider.GCPolicy.Threshold, cfg.Host, platformMC, useRemoteCache)
if err != nil {
return nil, errors.Wrap(err, "create content provider")
}
Expand Down Expand Up @@ -95,15 +101,25 @@ func NewLocalAdapter(cfg *config.Config) (*LocalAdapter, error) {
}

func (adp *LocalAdapter) Convert(ctx context.Context, source string) error {
target, err := adp.rule.Map(source)
target, err := adp.rule.Map(source, TagSuffix)
if err != nil {
if errors.Is(err, errdefs.ErrAlreadyConverted) {
logrus.Infof("image has been converted: %s", source)
return nil
}
return errors.Wrap(err, "create target reference by rule")
}
if _, err = adp.cvt.Convert(ctx, source, target); err != nil {
cacheRef, err := adp.rule.Map(source, CacheTagSuffix)
if err != nil {
if errors.Is(err, errdefs.ErrIsRemoteCache) {
logrus.Infof("image was remote cache: %s", source)
return nil
}
}
if err = adp.content.NewRemoteCache(ctx); err != nil {
return err
}
if _, err = adp.cvt.Convert(ctx, source, target, cacheRef); err != nil {
return err
}
if err := adp.content.GC(ctx); err != nil {
Expand Down
38 changes: 30 additions & 8 deletions pkg/adapter/rule.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ import (
"github.com/goharbor/acceleration-service/pkg/errdefs"
)

const (
TagSuffix = "tag_suffix"
CacheTagSuffix = "cache_tag_suffix"
)

// Add suffix to source image reference as the target
// image reference, for example:
// Source: 192.168.1.1/nginx:latest
Expand All @@ -47,16 +52,33 @@ type Rule struct {

// Map maps the source image reference to a new one according to
// a rule, the new one will be used as the reference of target image.
func (rule *Rule) Map(ref string) (string, error) {
for _, item := range rule.items {
if item.TagSuffix != "" {
if strings.HasSuffix(ref, item.TagSuffix) {
// FIXME: To check if an image has been converted, a better solution
// is to use the annotation on image manifest.
return "", errdefs.ErrAlreadyConverted
func (rule *Rule) Map(ref, opt string) (string, error) {
switch opt {
case TagSuffix:
for _, item := range rule.items {
if item.TagSuffix != "" {
if strings.HasSuffix(ref, item.TagSuffix) {
// FIXME: To check if an image has been converted, a better solution
// is to use the annotation on image manifest.
return "", errdefs.ErrAlreadyConverted
}
return addSuffix(ref, item.TagSuffix)
}
}
case CacheTagSuffix:
for _, item := range rule.items {
if item.CacheTagSuffix != "" {
if strings.HasSuffix(ref, item.CacheTagSuffix) {
// FIXME: Ditto.A better way is to use the annotation on image manifest.
return "", errdefs.ErrIsRemoteCache
}
return addSuffix(ref, item.CacheTagSuffix)
}
return addSuffix(ref, item.TagSuffix)
}
// CacheTagSuffix empty means do not provide remote cache, just return empty string.
return "", nil
default:
return "", fmt.Errorf("unsupported map option: %s", opt)
}
return "", errors.New("not found matched conversion rule")
}
3 changes: 2 additions & 1 deletion pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@ type SourceConfig struct {
}

type ConversionRule struct {
TagSuffix string `yaml:"tag_suffix"`
TagSuffix string `yaml:"tag_suffix"`
CacheTagSuffix string `yaml:"cache_tag_suffix"`
}

type ConverterConfig struct {
Expand Down
97 changes: 97 additions & 0 deletions pkg/content/cache.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// Copyright Project Harbor Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package content

import (
"errors"
"os"

lru "github.com/hashicorp/golang-lru/v2"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
)

type RemoteCache struct {
// remoteCache is a LRU cache for remote image layers.
remoteCache *lru.Cache[string, ocispec.Descriptor]
}

func NewRemoteCache(cacheSize int) (*RemoteCache, error) {
remoteCache, err := lru.New[string, ocispec.Descriptor](cacheSize)
if err != nil {
return nil, err
}
return &RemoteCache{
remoteCache: remoteCache,
}, nil
}

func (rc *RemoteCache) Values() []ocispec.Descriptor {
return rc.remoteCache.Values()
}

func (rc *RemoteCache) Get(key string) (ocispec.Descriptor, bool) {
return rc.remoteCache.Get(key)
}

func (rc *RemoteCache) Add(key string, value ocispec.Descriptor) {
rc.remoteCache.Add(key, value)
}

func (rc *RemoteCache) Remove(key string) {
rc.remoteCache.Remove(key)
}

// Update the value of the key, if the key exists, delete the old value and add the new value.
func (rc *RemoteCache) Update(key string, value ocispec.Descriptor) error{
_, ok := rc.remoteCache.Peek(key)
if ok{
del := rc.remoteCache.Remove(key)
if !del {
return errors.New("can not remove the key")
}
rc.Add(key, value)
}else{
rc.remoteCache.Add(key, value)
}
return nil
}

type FileToReaderAt struct {
file *os.File
}

func NewFileToReaderAt(filePath string) (*FileToReaderAt, error) {
file, err := os.Open(filePath)
if err != nil {
return nil, err
}
return &FileToReaderAt{file: file}, nil
}

func (f *FileToReaderAt) ReadAt(p []byte, off int64) (int, error) {
return f.file.ReadAt(p, off)
}

func (f *FileToReaderAt) Close() error {
return f.file.Close()
}

func (f *FileToReaderAt) Size() int64 {
fi, err := f.file.Stat()
if err != nil {
return 0
}
return fi.Size()
}
Loading

0 comments on commit 60bdacc

Please sign in to comment.