zotregistry.dev/zot@v1.4.4-0.20240314164342-eec277e14d20/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.dev/zot/errors" 14 zlog "zotregistry.dev/zot/pkg/log" 15 "zotregistry.dev/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("failed to cast type, 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("failed 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("failed to create cache db") 59 60 return nil, err 61 } 62 63 log.Error().Err(err).Str("dbPath", dbPath).Msg("failed 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("failed 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("failed 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()). 103 Msg("failed to put blob due to empty path being provided") 104 105 return zerr.ErrEmptyValue 106 } 107 108 // use only relative (to rootDir) paths on blobs 109 var err error 110 if d.useRelPaths { 111 path, err = filepath.Rel(d.rootDir, path) 112 if err != nil { 113 d.log.Error().Err(err).Str("path", path).Msg("failed to get relative path") 114 } 115 } 116 117 if err := d.db.Update(func(tx *bbolt.Tx) error { 118 root := tx.Bucket([]byte(constants.BlobsCache)) 119 if root == nil { 120 // this is a serious failure 121 err := zerr.ErrCacheRootBucket 122 d.log.Error().Err(err).Msg("failed to access root bucket") 123 124 return err 125 } 126 127 bucket, err := root.CreateBucketIfNotExists([]byte(digest.String())) 128 if err != nil { 129 // this is a serious failure 130 d.log.Error().Err(err).Str("bucket", digest.String()).Msg("failed to create a bucket") 131 132 return err 133 } 134 135 // create nested deduped bucket where we store all the deduped blobs + original blob 136 deduped, err := bucket.CreateBucketIfNotExists([]byte(constants.DuplicatesBucket)) 137 if err != nil { 138 // this is a serious failure 139 d.log.Error().Err(err).Str("bucket", constants.DuplicatesBucket).Msg("failed to create a bucket") 140 141 return err 142 } 143 144 if err := deduped.Put([]byte(path), nil); err != nil { 145 d.log.Error().Err(err).Str("bucket", constants.DuplicatesBucket).Str("value", path).Msg("failed to put record") 146 147 return err 148 } 149 150 // create origin bucket and insert only the original blob 151 origin := bucket.Bucket([]byte(constants.OriginalBucket)) 152 if origin == nil { 153 // if the bucket doesn't exist yet then 'path' is the original blob 154 origin, err := bucket.CreateBucket([]byte(constants.OriginalBucket)) 155 if err != nil { 156 // this is a serious failure 157 d.log.Error().Err(err).Str("bucket", constants.OriginalBucket).Msg("failed to create a bucket") 158 159 return err 160 } 161 162 if err := origin.Put([]byte(path), nil); err != nil { 163 d.log.Error().Err(err).Str("bucket", constants.OriginalBucket).Str("value", path).Msg("failed to put record") 164 165 return err 166 } 167 } 168 169 return nil 170 }); err != nil { 171 return err 172 } 173 174 return nil 175 } 176 177 func (d *BoltDBDriver) GetBlob(digest godigest.Digest) (string, error) { 178 var blobPath strings.Builder 179 180 if err := d.db.View(func(tx *bbolt.Tx) error { 181 root := tx.Bucket([]byte(constants.BlobsCache)) 182 if root == nil { 183 // this is a serious failure 184 err := zerr.ErrCacheRootBucket 185 d.log.Error().Err(err).Msg("failed to access root bucket") 186 187 return err 188 } 189 190 bucket := root.Bucket([]byte(digest.String())) 191 if bucket != nil { 192 origin := bucket.Bucket([]byte(constants.OriginalBucket)) 193 blobPath.Write(d.getOne(origin)) 194 195 return nil 196 } 197 198 return zerr.ErrCacheMiss 199 }); err != nil { 200 return "", err 201 } 202 203 return blobPath.String(), nil 204 } 205 206 func (d *BoltDBDriver) HasBlob(digest godigest.Digest, blob string) bool { 207 if err := d.db.View(func(tx *bbolt.Tx) error { 208 root := tx.Bucket([]byte(constants.BlobsCache)) 209 if root == nil { 210 // this is a serious failure 211 err := zerr.ErrCacheRootBucket 212 d.log.Error().Err(err).Msg("failed to access root bucket") 213 214 return err 215 } 216 217 bucket := root.Bucket([]byte(digest.String())) 218 if bucket == nil { 219 return zerr.ErrCacheMiss 220 } 221 222 origin := bucket.Bucket([]byte(constants.OriginalBucket)) 223 if origin == nil { 224 return zerr.ErrCacheMiss 225 } 226 227 deduped := bucket.Bucket([]byte(constants.DuplicatesBucket)) 228 if deduped == nil { 229 return zerr.ErrCacheMiss 230 } 231 232 if origin.Get([]byte(blob)) == nil { 233 if deduped.Get([]byte(blob)) == nil { 234 return zerr.ErrCacheMiss 235 } 236 } 237 238 return nil 239 }); err != nil { 240 return false 241 } 242 243 return true 244 } 245 246 func (d *BoltDBDriver) getOne(bucket *bbolt.Bucket) []byte { 247 if bucket != nil { 248 cursor := bucket.Cursor() 249 k, _ := cursor.First() 250 251 return k 252 } 253 254 return nil 255 } 256 257 func (d *BoltDBDriver) DeleteBlob(digest godigest.Digest, path string) error { 258 // use only relative (to rootDir) paths on blobs 259 var err error 260 if d.useRelPaths { 261 path, err = filepath.Rel(d.rootDir, path) 262 if err != nil { 263 d.log.Error().Err(err).Str("path", path).Msg("failed to get relative path") 264 } 265 } 266 267 if err := d.db.Update(func(tx *bbolt.Tx) error { 268 root := tx.Bucket([]byte(constants.BlobsCache)) 269 if root == nil { 270 // this is a serious failure 271 err := zerr.ErrCacheRootBucket 272 d.log.Error().Err(err).Msg("failed to access root bucket") 273 274 return err 275 } 276 277 bucket := root.Bucket([]byte(digest.String())) 278 if bucket == nil { 279 return zerr.ErrCacheMiss 280 } 281 282 deduped := bucket.Bucket([]byte(constants.DuplicatesBucket)) 283 if deduped == nil { 284 return zerr.ErrCacheMiss 285 } 286 287 if err := deduped.Delete([]byte(path)); err != nil { 288 d.log.Error().Err(err).Str("digest", digest.String()).Str("bucket", constants.DuplicatesBucket). 289 Str("path", path).Msg("failed to delete") 290 291 return err 292 } 293 294 origin := bucket.Bucket([]byte(constants.OriginalBucket)) 295 if origin != nil { 296 originBlob := d.getOne(origin) 297 if originBlob != nil { 298 if err := origin.Delete([]byte(path)); err != nil { 299 d.log.Error().Err(err).Str("digest", digest.String()).Str("bucket", constants.OriginalBucket). 300 Str("path", path).Msg("failed to delete") 301 302 return err 303 } 304 305 // move next candidate to origin bucket, next GetKey will return this one and storage will move the content here 306 dedupedBlob := d.getOne(deduped) 307 if dedupedBlob != nil { 308 if err := origin.Put(dedupedBlob, nil); err != nil { 309 d.log.Error().Err(err).Str("digest", digest.String()).Str("bucket", constants.OriginalBucket).Str("path", path). 310 Msg("failed to put") 311 312 return err 313 } 314 } 315 } 316 } 317 318 // if no key in origin bucket then digest bucket is empty, remove it 319 k := d.getOne(origin) 320 if k == nil { 321 d.log.Debug().Str("digest", digest.String()).Str("path", path).Msg("deleting empty bucket") 322 if err := root.DeleteBucket([]byte(digest)); err != nil { 323 d.log.Error().Err(err).Str("digest", digest.String()).Str("bucket", digest.String()).Str("path", path). 324 Msg("failed to delete") 325 326 return err 327 } 328 } 329 330 return nil 331 }); err != nil { 332 return err 333 } 334 335 return nil 336 }