zotregistry.io/zot@v1.4.4-0.20231124084042-02a8ed785457/pkg/storage/cache/boltdb.go (about)

     1  package cache
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"path"
     7  	"path/filepath"
     8  	"strings"
     9  
    10  	godigest "github.com/opencontainers/go-digest"
    11  	"go.etcd.io/bbolt"
    12  
    13  	zerr "zotregistry.io/zot/errors"
    14  	zlog "zotregistry.io/zot/pkg/log"
    15  	"zotregistry.io/zot/pkg/storage/constants"
    16  )
    17  
    18  type BoltDBDriver struct {
    19  	rootDir     string
    20  	db          *bbolt.DB
    21  	log         zlog.Logger
    22  	useRelPaths bool // whether or not to use relative paths, should be true for filesystem and false for s3
    23  }
    24  
    25  type BoltDBDriverParameters struct {
    26  	RootDir     string
    27  	Name        string
    28  	UseRelPaths bool
    29  }
    30  
    31  func NewBoltDBCache(parameters interface{}, log zlog.Logger) (*BoltDBDriver, error) {
    32  	properParameters, ok := parameters.(BoltDBDriverParameters)
    33  	if !ok {
    34  		log.Error().Err(zerr.ErrTypeAssertionFailed).Msgf("expected type '%T' but got '%T'",
    35  			BoltDBDriverParameters{}, parameters)
    36  
    37  		return nil, zerr.ErrTypeAssertionFailed
    38  	}
    39  
    40  	err := os.MkdirAll(properParameters.RootDir, constants.DefaultDirPerms)
    41  	if err != nil {
    42  		log.Error().Err(err).Str("directory", properParameters.RootDir).Msg("unable to create directory for cache db")
    43  
    44  		return nil, err
    45  	}
    46  
    47  	dbPath := path.Join(properParameters.RootDir, properParameters.Name+constants.DBExtensionName)
    48  	dbOpts := &bbolt.Options{
    49  		Timeout:      constants.DBCacheLockCheckTimeout,
    50  		FreelistType: bbolt.FreelistArrayType,
    51  	}
    52  
    53  	cacheDB, err := bbolt.Open(dbPath, 0o600, dbOpts) //nolint:gomnd
    54  	if err != nil {
    55  		if strings.Contains(err.Error(), "timeout") {
    56  			err := fmt.Errorf("%w: %w, path '%s'", zerr.ErrTimeout, zerr.ErrDatabaseFileAlreadyInUse, dbPath)
    57  
    58  			log.Error().Err(err).Str("dbPath", dbPath).Msg("unable to create cache db")
    59  
    60  			return nil, err
    61  		}
    62  
    63  		log.Error().Err(err).Str("dbPath", dbPath).Msg("unable to create cache db")
    64  
    65  		return nil, err
    66  	}
    67  
    68  	if err := cacheDB.Update(func(tx *bbolt.Tx) error {
    69  		if _, err := tx.CreateBucketIfNotExists([]byte(constants.BlobsCache)); err != nil {
    70  			// this is a serious failure
    71  			log.Error().Err(err).Str("dbPath", dbPath).Msg("unable to create a root bucket")
    72  
    73  			return err
    74  		}
    75  
    76  		return nil
    77  	}); err != nil {
    78  		// something went wrong
    79  		log.Error().Err(err).Msg("unable to create a cache")
    80  
    81  		return nil, err
    82  	}
    83  
    84  	return &BoltDBDriver{
    85  		rootDir:     properParameters.RootDir,
    86  		db:          cacheDB,
    87  		useRelPaths: properParameters.UseRelPaths,
    88  		log:         log,
    89  	}, nil
    90  }
    91  
    92  func (d *BoltDBDriver) UsesRelativePaths() bool {
    93  	return d.useRelPaths
    94  }
    95  
    96  func (d *BoltDBDriver) Name() string {
    97  	return "boltdb"
    98  }
    99  
   100  func (d *BoltDBDriver) PutBlob(digest godigest.Digest, path string) error {
   101  	if path == "" {
   102  		d.log.Error().Err(zerr.ErrEmptyValue).Str("digest", digest.String()).Msg("empty path provided")
   103  
   104  		return zerr.ErrEmptyValue
   105  	}
   106  
   107  	// use only relative (to rootDir) paths on blobs
   108  	var err error
   109  	if d.useRelPaths {
   110  		path, err = filepath.Rel(d.rootDir, path)
   111  		if err != nil {
   112  			d.log.Error().Err(err).Str("path", path).Msg("unable to get relative path")
   113  		}
   114  	}
   115  
   116  	if err := d.db.Update(func(tx *bbolt.Tx) error {
   117  		root := tx.Bucket([]byte(constants.BlobsCache))
   118  		if root == nil {
   119  			// this is a serious failure
   120  			err := zerr.ErrCacheRootBucket
   121  			d.log.Error().Err(err).Msg("unable to access root bucket")
   122  
   123  			return err
   124  		}
   125  
   126  		bucket, err := root.CreateBucketIfNotExists([]byte(digest.String()))
   127  		if err != nil {
   128  			// this is a serious failure
   129  			d.log.Error().Err(err).Str("bucket", digest.String()).Msg("unable to create a bucket")
   130  
   131  			return err
   132  		}
   133  
   134  		// create nested deduped bucket where we store all the deduped blobs + original blob
   135  		deduped, err := bucket.CreateBucketIfNotExists([]byte(constants.DuplicatesBucket))
   136  		if err != nil {
   137  			// this is a serious failure
   138  			d.log.Error().Err(err).Str("bucket", constants.DuplicatesBucket).Msg("unable to create a bucket")
   139  
   140  			return err
   141  		}
   142  
   143  		if err := deduped.Put([]byte(path), nil); err != nil {
   144  			d.log.Error().Err(err).Str("bucket", constants.DuplicatesBucket).Str("value", path).Msg("unable to put record")
   145  
   146  			return err
   147  		}
   148  
   149  		// create origin bucket and insert only the original blob
   150  		origin := bucket.Bucket([]byte(constants.OriginalBucket))
   151  		if origin == nil {
   152  			// if the bucket doesn't exist yet then 'path' is the original blob
   153  			origin, err := bucket.CreateBucket([]byte(constants.OriginalBucket))
   154  			if err != nil {
   155  				// this is a serious failure
   156  				d.log.Error().Err(err).Str("bucket", constants.OriginalBucket).Msg("unable to create a bucket")
   157  
   158  				return err
   159  			}
   160  
   161  			if err := origin.Put([]byte(path), nil); err != nil {
   162  				d.log.Error().Err(err).Str("bucket", constants.OriginalBucket).Str("value", path).Msg("unable to put record")
   163  
   164  				return err
   165  			}
   166  		}
   167  
   168  		return nil
   169  	}); err != nil {
   170  		return err
   171  	}
   172  
   173  	return nil
   174  }
   175  
   176  func (d *BoltDBDriver) GetBlob(digest godigest.Digest) (string, error) {
   177  	var blobPath strings.Builder
   178  
   179  	if err := d.db.View(func(tx *bbolt.Tx) error {
   180  		root := tx.Bucket([]byte(constants.BlobsCache))
   181  		if root == nil {
   182  			// this is a serious failure
   183  			err := zerr.ErrCacheRootBucket
   184  			d.log.Error().Err(err).Msg("unable to access root bucket")
   185  
   186  			return err
   187  		}
   188  
   189  		bucket := root.Bucket([]byte(digest.String()))
   190  		if bucket != nil {
   191  			origin := bucket.Bucket([]byte(constants.OriginalBucket))
   192  			blobPath.Write(d.getOne(origin))
   193  
   194  			return nil
   195  		}
   196  
   197  		return zerr.ErrCacheMiss
   198  	}); err != nil {
   199  		return "", err
   200  	}
   201  
   202  	return blobPath.String(), nil
   203  }
   204  
   205  func (d *BoltDBDriver) HasBlob(digest godigest.Digest, blob string) bool {
   206  	if err := d.db.View(func(tx *bbolt.Tx) error {
   207  		root := tx.Bucket([]byte(constants.BlobsCache))
   208  		if root == nil {
   209  			// this is a serious failure
   210  			err := zerr.ErrCacheRootBucket
   211  			d.log.Error().Err(err).Msg("unable to access root bucket")
   212  
   213  			return err
   214  		}
   215  
   216  		bucket := root.Bucket([]byte(digest.String()))
   217  		if bucket == nil {
   218  			return zerr.ErrCacheMiss
   219  		}
   220  
   221  		origin := bucket.Bucket([]byte(constants.OriginalBucket))
   222  		if origin == nil {
   223  			return zerr.ErrCacheMiss
   224  		}
   225  
   226  		deduped := bucket.Bucket([]byte(constants.DuplicatesBucket))
   227  		if deduped == nil {
   228  			return zerr.ErrCacheMiss
   229  		}
   230  
   231  		if origin.Get([]byte(blob)) == nil {
   232  			if deduped.Get([]byte(blob)) == nil {
   233  				return zerr.ErrCacheMiss
   234  			}
   235  		}
   236  
   237  		return nil
   238  	}); err != nil {
   239  		return false
   240  	}
   241  
   242  	return true
   243  }
   244  
   245  func (d *BoltDBDriver) getOne(bucket *bbolt.Bucket) []byte {
   246  	if bucket != nil {
   247  		cursor := bucket.Cursor()
   248  		k, _ := cursor.First()
   249  
   250  		return k
   251  	}
   252  
   253  	return nil
   254  }
   255  
   256  func (d *BoltDBDriver) DeleteBlob(digest godigest.Digest, path string) error {
   257  	// use only relative (to rootDir) paths on blobs
   258  	var err error
   259  	if d.useRelPaths {
   260  		path, err = filepath.Rel(d.rootDir, path)
   261  		if err != nil {
   262  			d.log.Error().Err(err).Str("path", path).Msg("unable to get relative path")
   263  		}
   264  	}
   265  
   266  	if err := d.db.Update(func(tx *bbolt.Tx) error {
   267  		root := tx.Bucket([]byte(constants.BlobsCache))
   268  		if root == nil {
   269  			// this is a serious failure
   270  			err := zerr.ErrCacheRootBucket
   271  			d.log.Error().Err(err).Msg("unable to access root bucket")
   272  
   273  			return err
   274  		}
   275  
   276  		bucket := root.Bucket([]byte(digest.String()))
   277  		if bucket == nil {
   278  			return zerr.ErrCacheMiss
   279  		}
   280  
   281  		deduped := bucket.Bucket([]byte(constants.DuplicatesBucket))
   282  		if deduped == nil {
   283  			return zerr.ErrCacheMiss
   284  		}
   285  
   286  		if err := deduped.Delete([]byte(path)); err != nil {
   287  			d.log.Error().Err(err).Str("digest", digest.String()).Str("bucket", constants.DuplicatesBucket).
   288  				Str("path", path).Msg("unable to delete")
   289  
   290  			return err
   291  		}
   292  
   293  		origin := bucket.Bucket([]byte(constants.OriginalBucket))
   294  		if origin != nil {
   295  			originBlob := d.getOne(origin)
   296  			if originBlob != nil {
   297  				if err := origin.Delete([]byte(path)); err != nil {
   298  					d.log.Error().Err(err).Str("digest", digest.String()).Str("bucket", constants.OriginalBucket).
   299  						Str("path", path).Msg("unable to delete")
   300  
   301  					return err
   302  				}
   303  
   304  				// move next candidate to origin bucket, next GetKey will return this one and storage will move the content here
   305  				dedupedBlob := d.getOne(deduped)
   306  				if dedupedBlob != nil {
   307  					if err := origin.Put(dedupedBlob, nil); err != nil {
   308  						d.log.Error().Err(err).Str("digest", digest.String()).Str("bucket", constants.OriginalBucket).Str("path", path).
   309  							Msg("unable to put")
   310  
   311  						return err
   312  					}
   313  				}
   314  			}
   315  		}
   316  
   317  		// if no key in origin bucket then digest bucket is empty, remove it
   318  		k := d.getOne(origin)
   319  		if k == nil {
   320  			d.log.Debug().Str("digest", digest.String()).Str("path", path).Msg("deleting empty bucket")
   321  			if err := root.DeleteBucket([]byte(digest)); err != nil {
   322  				d.log.Error().Err(err).Str("digest", digest.String()).Str("bucket", digest.String()).Str("path", path).
   323  					Msg("unable to delete")
   324  
   325  				return err
   326  			}
   327  		}
   328  
   329  		return nil
   330  	}); err != nil {
   331  		return err
   332  	}
   333  
   334  	return nil
   335  }