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 }