585 lines
14 KiB
Go
585 lines
14 KiB
Go
package cache
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
|
|
"github.com/moby/buildkit/cache/metadata"
|
|
"github.com/moby/buildkit/client"
|
|
"github.com/moby/buildkit/util/bklog"
|
|
digest "github.com/opencontainers/go-digest"
|
|
"github.com/pkg/errors"
|
|
bolt "go.etcd.io/bbolt"
|
|
)
|
|
|
|
const sizeUnknown int64 = -1
|
|
const keySize = "snapshot.size"
|
|
const keyEqualMutable = "cache.equalMutable"
|
|
const keyCachePolicy = "cache.cachePolicy"
|
|
const keyDescription = "cache.description"
|
|
const keyCreatedAt = "cache.createdAt"
|
|
const keyLastUsedAt = "cache.lastUsedAt"
|
|
const keyUsageCount = "cache.usageCount"
|
|
const keyLayerType = "cache.layerType"
|
|
const keyRecordType = "cache.recordType"
|
|
const keyCommitted = "snapshot.committed"
|
|
const keyParent = "cache.parent"
|
|
const keyMergeParents = "cache.mergeParents"
|
|
const keyLowerDiffParent = "cache.lowerDiffParent"
|
|
const keyUpperDiffParent = "cache.upperDiffParent"
|
|
const keyDiffID = "cache.diffID"
|
|
const keyChainID = "cache.chainID"
|
|
const keyBlobChainID = "cache.blobChainID"
|
|
const keyBlob = "cache.blob"
|
|
const keySnapshot = "cache.snapshot"
|
|
const keyBlobOnly = "cache.blobonly"
|
|
const keyMediaType = "cache.mediatype"
|
|
const keyImageRefs = "cache.imageRefs"
|
|
const keyDeleted = "cache.deleted"
|
|
const keyBlobSize = "cache.blobsize" // the packed blob size as specified in the oci descriptor
|
|
const keyURLs = "cache.layer.urls"
|
|
|
|
// Indexes
|
|
const blobchainIndex = "blobchainid:"
|
|
const chainIndex = "chainid:"
|
|
|
|
type MetadataStore interface {
|
|
Search(context.Context, string) ([]RefMetadata, error)
|
|
}
|
|
|
|
type RefMetadata interface {
|
|
ID() string
|
|
|
|
GetDescription() string
|
|
SetDescription(string) error
|
|
|
|
GetCreatedAt() time.Time
|
|
SetCreatedAt(time.Time) error
|
|
|
|
HasCachePolicyDefault() bool
|
|
SetCachePolicyDefault() error
|
|
HasCachePolicyRetain() bool
|
|
SetCachePolicyRetain() error
|
|
|
|
GetLayerType() string
|
|
SetLayerType(string) error
|
|
|
|
GetRecordType() client.UsageRecordType
|
|
SetRecordType(client.UsageRecordType) error
|
|
|
|
GetEqualMutable() (RefMetadata, bool)
|
|
|
|
// generic getters/setters for external packages
|
|
GetString(string) string
|
|
SetString(key, val, index string) error
|
|
|
|
GetExternal(string) ([]byte, error)
|
|
SetExternal(string, []byte) error
|
|
|
|
ClearValueAndIndex(string, string) error
|
|
}
|
|
|
|
func (cm *cacheManager) Search(ctx context.Context, idx string) ([]RefMetadata, error) {
|
|
cm.mu.Lock()
|
|
defer cm.mu.Unlock()
|
|
return cm.search(ctx, idx)
|
|
}
|
|
|
|
// callers must hold cm.mu lock
|
|
func (cm *cacheManager) search(ctx context.Context, idx string) ([]RefMetadata, error) {
|
|
sis, err := cm.MetadataStore.Search(idx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var mds []RefMetadata
|
|
for _, si := range sis {
|
|
// calling getMetadata ensures we return the same storage item object that's cached in memory
|
|
md, ok := cm.getMetadata(si.ID())
|
|
if !ok {
|
|
bklog.G(ctx).Warnf("missing metadata for storage item %q during search for %q", si.ID(), idx)
|
|
continue
|
|
}
|
|
if md.getDeleted() {
|
|
continue
|
|
}
|
|
mds = append(mds, md)
|
|
}
|
|
return mds, nil
|
|
}
|
|
|
|
// callers must hold cm.mu lock
|
|
func (cm *cacheManager) getMetadata(id string) (*cacheMetadata, bool) {
|
|
if rec, ok := cm.records[id]; ok {
|
|
return rec.cacheMetadata, true
|
|
}
|
|
si, ok := cm.MetadataStore.Get(id)
|
|
md := &cacheMetadata{si}
|
|
return md, ok
|
|
}
|
|
|
|
// callers must hold cm.mu lock
|
|
func (cm *cacheManager) searchBlobchain(ctx context.Context, id digest.Digest) ([]RefMetadata, error) {
|
|
return cm.search(ctx, blobchainIndex+id.String())
|
|
}
|
|
|
|
// callers must hold cm.mu lock
|
|
func (cm *cacheManager) searchChain(ctx context.Context, id digest.Digest) ([]RefMetadata, error) {
|
|
return cm.search(ctx, chainIndex+id.String())
|
|
}
|
|
|
|
type cacheMetadata struct {
|
|
si *metadata.StorageItem
|
|
}
|
|
|
|
func (md *cacheMetadata) ID() string {
|
|
return md.si.ID()
|
|
}
|
|
|
|
func (md *cacheMetadata) commitMetadata() error {
|
|
return md.si.Commit()
|
|
}
|
|
|
|
func (md *cacheMetadata) GetDescription() string {
|
|
return md.GetString(keyDescription)
|
|
}
|
|
|
|
func (md *cacheMetadata) SetDescription(descr string) error {
|
|
return md.setValue(keyDescription, descr, "")
|
|
}
|
|
|
|
func (md *cacheMetadata) queueDescription(descr string) error {
|
|
return md.queueValue(keyDescription, descr, "")
|
|
}
|
|
|
|
func (md *cacheMetadata) queueCommitted(b bool) error {
|
|
return md.queueValue(keyCommitted, b, "")
|
|
}
|
|
|
|
func (md *cacheMetadata) getCommitted() bool {
|
|
return md.getBool(keyCommitted)
|
|
}
|
|
|
|
func (md *cacheMetadata) GetLayerType() string {
|
|
return md.GetString(keyLayerType)
|
|
}
|
|
|
|
func (md *cacheMetadata) SetLayerType(value string) error {
|
|
return md.setValue(keyLayerType, value, "")
|
|
}
|
|
|
|
func (md *cacheMetadata) GetRecordType() client.UsageRecordType {
|
|
return client.UsageRecordType(md.GetString(keyRecordType))
|
|
}
|
|
|
|
func (md *cacheMetadata) SetRecordType(value client.UsageRecordType) error {
|
|
return md.setValue(keyRecordType, value, "")
|
|
}
|
|
|
|
func (md *cacheMetadata) queueRecordType(value client.UsageRecordType) error {
|
|
return md.queueValue(keyRecordType, value, "")
|
|
}
|
|
|
|
func (md *cacheMetadata) SetCreatedAt(tm time.Time) error {
|
|
return md.setTime(keyCreatedAt, tm, "")
|
|
}
|
|
|
|
func (md *cacheMetadata) queueCreatedAt(tm time.Time) error {
|
|
return md.queueTime(keyCreatedAt, tm, "")
|
|
}
|
|
|
|
func (md *cacheMetadata) GetCreatedAt() time.Time {
|
|
return md.getTime(keyCreatedAt)
|
|
}
|
|
|
|
func (md *cacheMetadata) HasCachePolicyDefault() bool {
|
|
return md.getCachePolicy() == cachePolicyDefault
|
|
}
|
|
|
|
func (md *cacheMetadata) SetCachePolicyDefault() error {
|
|
return md.setCachePolicy(cachePolicyDefault)
|
|
}
|
|
|
|
func (md *cacheMetadata) HasCachePolicyRetain() bool {
|
|
return md.getCachePolicy() == cachePolicyRetain
|
|
}
|
|
|
|
func (md *cacheMetadata) SetCachePolicyRetain() error {
|
|
return md.setCachePolicy(cachePolicyRetain)
|
|
}
|
|
|
|
func (md *cacheMetadata) GetExternal(s string) ([]byte, error) {
|
|
return md.si.GetExternal(s)
|
|
}
|
|
|
|
func (md *cacheMetadata) SetExternal(s string, dt []byte) error {
|
|
return md.si.SetExternal(s, dt)
|
|
}
|
|
|
|
func (md *cacheMetadata) GetEqualMutable() (RefMetadata, bool) {
|
|
emSi, ok := md.si.Storage().Get(md.getEqualMutable())
|
|
if !ok {
|
|
return nil, false
|
|
}
|
|
return &cacheMetadata{emSi}, true
|
|
}
|
|
|
|
func (md *cacheMetadata) getEqualMutable() string {
|
|
return md.GetString(keyEqualMutable)
|
|
}
|
|
|
|
func (md *cacheMetadata) setEqualMutable(s string) error {
|
|
return md.queueValue(keyEqualMutable, s, "")
|
|
}
|
|
|
|
func (md *cacheMetadata) clearEqualMutable() error {
|
|
md.si.Queue(func(b *bolt.Bucket) error {
|
|
return md.si.SetValue(b, keyEqualMutable, nil)
|
|
})
|
|
return nil
|
|
}
|
|
|
|
func (md *cacheMetadata) queueDiffID(str digest.Digest) error {
|
|
return md.queueValue(keyDiffID, str, "")
|
|
}
|
|
|
|
func (md *cacheMetadata) getMediaType() string {
|
|
return md.GetString(keyMediaType)
|
|
}
|
|
|
|
func (md *cacheMetadata) queueMediaType(str string) error {
|
|
return md.queueValue(keyMediaType, str, "")
|
|
}
|
|
|
|
func (md *cacheMetadata) getSnapshotID() string {
|
|
return md.GetString(keySnapshot)
|
|
}
|
|
|
|
func (md *cacheMetadata) queueSnapshotID(str string) error {
|
|
return md.queueValue(keySnapshot, str, "")
|
|
}
|
|
|
|
func (md *cacheMetadata) getDiffID() digest.Digest {
|
|
return digest.Digest(md.GetString(keyDiffID))
|
|
}
|
|
|
|
func (md *cacheMetadata) queueChainID(str digest.Digest) error {
|
|
return md.queueValue(keyChainID, str, chainIndex+str.String())
|
|
}
|
|
|
|
func (md *cacheMetadata) getBlobChainID() digest.Digest {
|
|
return digest.Digest(md.GetString(keyBlobChainID))
|
|
}
|
|
|
|
func (md *cacheMetadata) queueBlobChainID(str digest.Digest) error {
|
|
return md.queueValue(keyBlobChainID, str, blobchainIndex+str.String())
|
|
}
|
|
|
|
func (md *cacheMetadata) getChainID() digest.Digest {
|
|
return digest.Digest(md.GetString(keyChainID))
|
|
}
|
|
|
|
func (md *cacheMetadata) queueBlob(str digest.Digest) error {
|
|
return md.queueValue(keyBlob, str, "")
|
|
}
|
|
|
|
func (md *cacheMetadata) appendURLs(urls []string) error {
|
|
if len(urls) == 0 {
|
|
return nil
|
|
}
|
|
return md.appendStringSlice(keyURLs, urls...)
|
|
}
|
|
|
|
func (md *cacheMetadata) getURLs() []string {
|
|
return md.GetStringSlice(keyURLs)
|
|
}
|
|
|
|
func (md *cacheMetadata) getBlob() digest.Digest {
|
|
return digest.Digest(md.GetString(keyBlob))
|
|
}
|
|
|
|
func (md *cacheMetadata) queueBlobOnly(b bool) error {
|
|
return md.queueValue(keyBlobOnly, b, "")
|
|
}
|
|
|
|
func (md *cacheMetadata) getBlobOnly() bool {
|
|
return md.getBool(keyBlobOnly)
|
|
}
|
|
|
|
func (md *cacheMetadata) queueDeleted() error {
|
|
return md.queueValue(keyDeleted, true, "")
|
|
}
|
|
|
|
func (md *cacheMetadata) getDeleted() bool {
|
|
return md.getBool(keyDeleted)
|
|
}
|
|
|
|
func (md *cacheMetadata) queueParent(parent string) error {
|
|
return md.queueValue(keyParent, parent, "")
|
|
}
|
|
|
|
func (md *cacheMetadata) getParent() string {
|
|
return md.GetString(keyParent)
|
|
}
|
|
|
|
func (md *cacheMetadata) queueMergeParents(parents []string) error {
|
|
return md.queueValue(keyMergeParents, parents, "")
|
|
}
|
|
|
|
func (md *cacheMetadata) getMergeParents() []string {
|
|
return md.getStringSlice(keyMergeParents)
|
|
}
|
|
|
|
func (md *cacheMetadata) queueLowerDiffParent(parent string) error {
|
|
return md.queueValue(keyLowerDiffParent, parent, "")
|
|
}
|
|
|
|
func (md *cacheMetadata) getLowerDiffParent() string {
|
|
return md.GetString(keyLowerDiffParent)
|
|
}
|
|
|
|
func (md *cacheMetadata) queueUpperDiffParent(parent string) error {
|
|
return md.queueValue(keyUpperDiffParent, parent, "")
|
|
}
|
|
|
|
func (md *cacheMetadata) getUpperDiffParent() string {
|
|
return md.GetString(keyUpperDiffParent)
|
|
}
|
|
|
|
func (md *cacheMetadata) queueSize(s int64) error {
|
|
return md.queueValue(keySize, s, "")
|
|
}
|
|
|
|
func (md *cacheMetadata) getSize() int64 {
|
|
if size, ok := md.getInt64(keySize); ok {
|
|
return size
|
|
}
|
|
return sizeUnknown
|
|
}
|
|
|
|
func (md *cacheMetadata) appendImageRef(s string) error {
|
|
return md.appendStringSlice(keyImageRefs, s)
|
|
}
|
|
|
|
func (md *cacheMetadata) getImageRefs() []string {
|
|
return md.getStringSlice(keyImageRefs)
|
|
}
|
|
|
|
func (md *cacheMetadata) queueBlobSize(s int64) error {
|
|
return md.queueValue(keyBlobSize, s, "")
|
|
}
|
|
|
|
func (md *cacheMetadata) getBlobSize() int64 {
|
|
if size, ok := md.getInt64(keyBlobSize); ok {
|
|
return size
|
|
}
|
|
return sizeUnknown
|
|
}
|
|
|
|
func (md *cacheMetadata) setCachePolicy(p cachePolicy) error {
|
|
return md.setValue(keyCachePolicy, p, "")
|
|
}
|
|
|
|
func (md *cacheMetadata) getCachePolicy() cachePolicy {
|
|
if i, ok := md.getInt64(keyCachePolicy); ok {
|
|
return cachePolicy(i)
|
|
}
|
|
return cachePolicyDefault
|
|
}
|
|
|
|
func (md *cacheMetadata) getLastUsed() (int, *time.Time) {
|
|
v := md.si.Get(keyUsageCount)
|
|
if v == nil {
|
|
return 0, nil
|
|
}
|
|
var usageCount int
|
|
if err := v.Unmarshal(&usageCount); err != nil {
|
|
return 0, nil
|
|
}
|
|
v = md.si.Get(keyLastUsedAt)
|
|
if v == nil {
|
|
return usageCount, nil
|
|
}
|
|
var lastUsedTs int64
|
|
if err := v.Unmarshal(&lastUsedTs); err != nil || lastUsedTs == 0 {
|
|
return usageCount, nil
|
|
}
|
|
tm := time.Unix(lastUsedTs/1e9, lastUsedTs%1e9)
|
|
return usageCount, &tm
|
|
}
|
|
|
|
func (md *cacheMetadata) updateLastUsed() error {
|
|
count, _ := md.getLastUsed()
|
|
count++
|
|
|
|
v, err := metadata.NewValue(count)
|
|
if err != nil {
|
|
return errors.Wrap(err, "failed to create usageCount value")
|
|
}
|
|
v2, err := metadata.NewValue(time.Now().UnixNano())
|
|
if err != nil {
|
|
return errors.Wrap(err, "failed to create lastUsedAt value")
|
|
}
|
|
return md.si.Update(func(b *bolt.Bucket) error {
|
|
if err := md.si.SetValue(b, keyUsageCount, v); err != nil {
|
|
return err
|
|
}
|
|
return md.si.SetValue(b, keyLastUsedAt, v2)
|
|
})
|
|
}
|
|
|
|
func (md *cacheMetadata) queueValue(key string, value interface{}, index string) error {
|
|
v, err := metadata.NewValue(value)
|
|
if err != nil {
|
|
return errors.Wrap(err, "failed to create value")
|
|
}
|
|
v.Index = index
|
|
md.si.Queue(func(b *bolt.Bucket) error {
|
|
return md.si.SetValue(b, key, v)
|
|
})
|
|
return nil
|
|
}
|
|
|
|
func (md *cacheMetadata) SetString(key, value string, index string) error {
|
|
return md.setValue(key, value, index)
|
|
}
|
|
|
|
func (md *cacheMetadata) setValue(key string, value interface{}, index string) error {
|
|
v, err := metadata.NewValue(value)
|
|
if err != nil {
|
|
return errors.Wrap(err, "failed to create value")
|
|
}
|
|
v.Index = index
|
|
return md.si.Update(func(b *bolt.Bucket) error {
|
|
return md.si.SetValue(b, key, v)
|
|
})
|
|
}
|
|
|
|
func (md *cacheMetadata) ClearValueAndIndex(key string, index string) error {
|
|
currentVal := md.GetString(key)
|
|
return md.si.Update(func(b *bolt.Bucket) error {
|
|
if err := md.si.SetValue(b, key, nil); err != nil {
|
|
return err
|
|
}
|
|
if currentVal != "" {
|
|
// force clearing index, see #1836 https://github.com/moby/buildkit/pull/1836
|
|
return md.si.ClearIndex(b.Tx(), index+currentVal)
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func (md *cacheMetadata) GetString(key string) string {
|
|
v := md.si.Get(key)
|
|
if v == nil {
|
|
return ""
|
|
}
|
|
var str string
|
|
if err := v.Unmarshal(&str); err != nil {
|
|
return ""
|
|
}
|
|
return str
|
|
}
|
|
|
|
func (md *cacheMetadata) GetStringSlice(key string) []string {
|
|
v := md.si.Get(key)
|
|
if v == nil {
|
|
return nil
|
|
}
|
|
var val []string
|
|
if err := v.Unmarshal(&val); err != nil {
|
|
return nil
|
|
}
|
|
return val
|
|
}
|
|
|
|
func (md *cacheMetadata) setTime(key string, value time.Time, index string) error {
|
|
return md.setValue(key, value.UnixNano(), index)
|
|
}
|
|
|
|
func (md *cacheMetadata) queueTime(key string, value time.Time, index string) error {
|
|
return md.queueValue(key, value.UnixNano(), index)
|
|
}
|
|
|
|
func (md *cacheMetadata) getTime(key string) time.Time {
|
|
v := md.si.Get(key)
|
|
if v == nil {
|
|
return time.Time{}
|
|
}
|
|
var tm int64
|
|
if err := v.Unmarshal(&tm); err != nil {
|
|
return time.Time{}
|
|
}
|
|
return time.Unix(tm/1e9, tm%1e9)
|
|
}
|
|
|
|
func (md *cacheMetadata) getBool(key string) bool {
|
|
v := md.si.Get(key)
|
|
if v == nil {
|
|
return false
|
|
}
|
|
var b bool
|
|
if err := v.Unmarshal(&b); err != nil {
|
|
return false
|
|
}
|
|
return b
|
|
}
|
|
|
|
func (md *cacheMetadata) getInt64(key string) (int64, bool) {
|
|
v := md.si.Get(key)
|
|
if v == nil {
|
|
return 0, false
|
|
}
|
|
var i int64
|
|
if err := v.Unmarshal(&i); err != nil {
|
|
return 0, false
|
|
}
|
|
return i, true
|
|
}
|
|
|
|
func (md *cacheMetadata) appendStringSlice(key string, values ...string) error {
|
|
return md.si.GetAndSetValue(key, func(v *metadata.Value) (*metadata.Value, error) {
|
|
var slice []string
|
|
if v != nil {
|
|
if err := v.Unmarshal(&slice); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
idx := make(map[string]struct{}, len(values))
|
|
for _, v := range values {
|
|
idx[v] = struct{}{}
|
|
}
|
|
|
|
for _, existing := range slice {
|
|
if _, ok := idx[existing]; ok {
|
|
delete(idx, existing)
|
|
}
|
|
}
|
|
|
|
if len(idx) == 0 {
|
|
return nil, metadata.ErrSkipSetValue
|
|
}
|
|
|
|
for value := range idx {
|
|
slice = append(slice, value)
|
|
}
|
|
v, err := metadata.NewValue(slice)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return v, nil
|
|
})
|
|
}
|
|
|
|
func (md *cacheMetadata) getStringSlice(key string) []string {
|
|
v := md.si.Get(key)
|
|
if v == nil {
|
|
return nil
|
|
}
|
|
var s []string
|
|
if err := v.Unmarshal(&s); err != nil {
|
|
return nil
|
|
}
|
|
return s
|
|
}
|