buildkit/cache/metadata.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
}