2017-05-27 06:12:13 +00:00
|
|
|
package cache
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
cache: add support for Diff refs.
This allows you to create refs that are single layers representing the
diff between any two arbitrary refs. The primary use case for this is
to allows users to extract the changes created by ops like Exec and
rebase them elsewhere through MergeOp. However, there is no restriction
on the inputs to DiffOp and the resulting ref's layer is simply the
layer created by running the differ on the two inputs refs
(specifically, the same differ used during exports).
A Diff ref can be mounted by itself, in which case it is defined as the
result of applying the diff to Scratch. Most use cases though will use
Diff refs as the input to a MergeOp, in which case the diff is just
applied on top of the lower merge inputs, as was the case before.
In cases like Diff(A, A->B->C) (i.e. cases where the diff is between two
refs where the lower is an ancestor of upper), the diff will be defined
as the layers separating the two refs. In other cases, the diff is just
a single layer, not re-used from the inputs, representing the diff
between the two refs (which can be defined as the layer "Diff(A,B)" that
satisfies "Merge(A, Diff(A,B)) == B").
Note that there is technically a meaningful difference between the
"unmerge" behavior of extracting the layers separating diffs and the
"simple diff" of just running the differ on the two refs. Namely, in the
case where there are "intermediate deletes" (i.e. deletes that only
exist in layers between A and B but not between A and B by themselves),
then the simple diff and unmerge can create different results when
plugged into a MergeOp. This is due to the fact that intermediate
deletes will apply to the merge when using the unmerge behavior, but not
when using the simple diff. This is on top of the fact that the simple
diff inherently has a "flattening" behavior where multiple layers are
squashed into a single one.
So, in the case where lower is an ancestor of upper, we choose to follow
the unmerge behavior, but it's possible users may prefer the simple diff
behavior. As of right now, they won't be able to do so, but if needed we
can add the ability to choose which behavior is followed in the future.
This could be done through a flag provided to DiffOp or possibly by
adapting llb.Copy to support this type of behavior with the same
efficiency as DiffOp.
Signed-off-by: Erik Sipsma <erik@sipsma.dev>
2021-11-26 21:43:24 +00:00
|
|
|
"fmt"
|
2018-07-31 20:14:53 +00:00
|
|
|
"sort"
|
2021-08-03 01:57:39 +00:00
|
|
|
"strings"
|
2017-05-27 06:12:13 +00:00
|
|
|
"sync"
|
2017-07-25 19:11:52 +00:00
|
|
|
"time"
|
2017-05-27 06:12:13 +00:00
|
|
|
|
2019-07-22 21:43:16 +00:00
|
|
|
"github.com/containerd/containerd/content"
|
2019-09-18 00:18:32 +00:00
|
|
|
"github.com/containerd/containerd/diff"
|
2020-05-28 20:46:33 +00:00
|
|
|
"github.com/containerd/containerd/errdefs"
|
2018-07-26 00:01:37 +00:00
|
|
|
"github.com/containerd/containerd/filters"
|
2019-07-22 21:43:16 +00:00
|
|
|
"github.com/containerd/containerd/gc"
|
|
|
|
"github.com/containerd/containerd/leases"
|
2019-03-20 06:40:24 +00:00
|
|
|
"github.com/docker/docker/pkg/idtools"
|
2017-07-03 23:08:20 +00:00
|
|
|
"github.com/moby/buildkit/cache/metadata"
|
2017-06-22 20:15:46 +00:00
|
|
|
"github.com/moby/buildkit/client"
|
2017-06-30 22:54:51 +00:00
|
|
|
"github.com/moby/buildkit/identity"
|
2020-10-27 06:13:39 +00:00
|
|
|
"github.com/moby/buildkit/session"
|
2018-04-16 22:23:10 +00:00
|
|
|
"github.com/moby/buildkit/snapshot"
|
2021-08-03 01:57:39 +00:00
|
|
|
"github.com/moby/buildkit/util/bklog"
|
2020-05-28 20:46:33 +00:00
|
|
|
"github.com/moby/buildkit/util/flightcontrol"
|
2020-05-13 15:37:27 +00:00
|
|
|
digest "github.com/opencontainers/go-digest"
|
2019-10-03 21:11:54 +00:00
|
|
|
imagespecidentity "github.com/opencontainers/image-spec/identity"
|
2021-07-26 08:53:30 +00:00
|
|
|
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
|
2017-05-27 06:12:13 +00:00
|
|
|
"github.com/pkg/errors"
|
2017-07-19 01:05:19 +00:00
|
|
|
"github.com/sirupsen/logrus"
|
2017-06-30 22:54:51 +00:00
|
|
|
"golang.org/x/sync/errgroup"
|
2017-05-27 06:12:13 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
2018-06-18 20:57:36 +00:00
|
|
|
ErrLocked = errors.New("locked")
|
2017-05-27 06:12:13 +00:00
|
|
|
errNotFound = errors.New("not found")
|
|
|
|
errInvalid = errors.New("invalid")
|
|
|
|
)
|
|
|
|
|
|
|
|
type ManagerOpt struct {
|
2019-09-18 00:18:32 +00:00
|
|
|
Snapshotter snapshot.Snapshotter
|
2019-07-22 21:43:16 +00:00
|
|
|
ContentStore content.Store
|
|
|
|
LeaseManager leases.Manager
|
2018-07-27 00:53:48 +00:00
|
|
|
PruneRefChecker ExternalRefCheckerFunc
|
2019-07-22 21:43:16 +00:00
|
|
|
GarbageCollect func(ctx context.Context) (gc.Stats, error)
|
2019-09-18 00:18:32 +00:00
|
|
|
Applier diff.Applier
|
2020-05-28 20:46:33 +00:00
|
|
|
Differ diff.Comparer
|
2021-07-09 00:09:35 +00:00
|
|
|
MetadataStore *metadata.Store
|
2017-05-27 06:12:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type Accessor interface {
|
2021-07-09 00:09:35 +00:00
|
|
|
MetadataStore
|
|
|
|
|
2021-07-26 08:53:30 +00:00
|
|
|
GetByBlob(ctx context.Context, desc ocispecs.Descriptor, parent ImmutableRef, opts ...RefOption) (ImmutableRef, error)
|
2017-07-25 19:11:52 +00:00
|
|
|
Get(ctx context.Context, id string, opts ...RefOption) (ImmutableRef, error)
|
2019-07-22 21:43:16 +00:00
|
|
|
|
2020-10-27 06:13:39 +00:00
|
|
|
New(ctx context.Context, parent ImmutableRef, s session.Group, opts ...RefOption) (MutableRef, error)
|
2020-05-28 20:46:33 +00:00
|
|
|
GetMutable(ctx context.Context, id string, opts ...RefOption) (MutableRef, error) // Rebase?
|
2019-03-20 06:40:24 +00:00
|
|
|
IdentityMapping() *idtools.IdentityMapping
|
2021-08-03 01:57:39 +00:00
|
|
|
Merge(ctx context.Context, parents []ImmutableRef, opts ...RefOption) (ImmutableRef, error)
|
cache: add support for Diff refs.
This allows you to create refs that are single layers representing the
diff between any two arbitrary refs. The primary use case for this is
to allows users to extract the changes created by ops like Exec and
rebase them elsewhere through MergeOp. However, there is no restriction
on the inputs to DiffOp and the resulting ref's layer is simply the
layer created by running the differ on the two inputs refs
(specifically, the same differ used during exports).
A Diff ref can be mounted by itself, in which case it is defined as the
result of applying the diff to Scratch. Most use cases though will use
Diff refs as the input to a MergeOp, in which case the diff is just
applied on top of the lower merge inputs, as was the case before.
In cases like Diff(A, A->B->C) (i.e. cases where the diff is between two
refs where the lower is an ancestor of upper), the diff will be defined
as the layers separating the two refs. In other cases, the diff is just
a single layer, not re-used from the inputs, representing the diff
between the two refs (which can be defined as the layer "Diff(A,B)" that
satisfies "Merge(A, Diff(A,B)) == B").
Note that there is technically a meaningful difference between the
"unmerge" behavior of extracting the layers separating diffs and the
"simple diff" of just running the differ on the two refs. Namely, in the
case where there are "intermediate deletes" (i.e. deletes that only
exist in layers between A and B but not between A and B by themselves),
then the simple diff and unmerge can create different results when
plugged into a MergeOp. This is due to the fact that intermediate
deletes will apply to the merge when using the unmerge behavior, but not
when using the simple diff. This is on top of the fact that the simple
diff inherently has a "flattening" behavior where multiple layers are
squashed into a single one.
So, in the case where lower is an ancestor of upper, we choose to follow
the unmerge behavior, but it's possible users may prefer the simple diff
behavior. As of right now, they won't be able to do so, but if needed we
can add the ability to choose which behavior is followed in the future.
This could be done through a flag provided to DiffOp or possibly by
adapting llb.Copy to support this type of behavior with the same
efficiency as DiffOp.
Signed-off-by: Erik Sipsma <erik@sipsma.dev>
2021-11-26 21:43:24 +00:00
|
|
|
Diff(ctx context.Context, lower, upper ImmutableRef, opts ...RefOption) (ImmutableRef, error)
|
2017-05-27 06:12:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type Controller interface {
|
2017-07-25 22:14:46 +00:00
|
|
|
DiskUsage(ctx context.Context, info client.DiskUsageInfo) ([]*client.UsageInfo, error)
|
2018-08-30 21:06:27 +00:00
|
|
|
Prune(ctx context.Context, ch chan client.UsageInfo, info ...client.PruneInfo) error
|
2017-05-27 06:12:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type Manager interface {
|
|
|
|
Accessor
|
|
|
|
Controller
|
|
|
|
Close() error
|
|
|
|
}
|
|
|
|
|
2019-09-20 21:49:29 +00:00
|
|
|
type ExternalRefCheckerFunc func() (ExternalRefChecker, error)
|
2018-07-27 00:53:48 +00:00
|
|
|
|
|
|
|
type ExternalRefChecker interface {
|
2019-09-20 21:49:29 +00:00
|
|
|
Exists(string, []digest.Digest) bool
|
2018-07-27 00:53:48 +00:00
|
|
|
}
|
|
|
|
|
2017-05-27 06:12:13 +00:00
|
|
|
type cacheManager struct {
|
2021-08-03 01:57:39 +00:00
|
|
|
records map[string]*cacheRecord
|
|
|
|
mu sync.Mutex
|
|
|
|
Snapshotter snapshot.MergeSnapshotter
|
|
|
|
ContentStore content.Store
|
|
|
|
LeaseManager leases.Manager
|
|
|
|
PruneRefChecker ExternalRefCheckerFunc
|
|
|
|
GarbageCollect func(ctx context.Context) (gc.Stats, error)
|
|
|
|
Applier diff.Applier
|
|
|
|
Differ diff.Comparer
|
|
|
|
MetadataStore *metadata.Store
|
2017-12-28 23:09:07 +00:00
|
|
|
|
|
|
|
muPrune sync.Mutex // make sure parallel prune is not allowed so there will not be inconsistent results
|
2020-05-28 20:46:33 +00:00
|
|
|
unlazyG flightcontrol.Group
|
2017-05-27 06:12:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func NewManager(opt ManagerOpt) (Manager, error) {
|
|
|
|
cm := &cacheManager{
|
2021-08-03 01:57:39 +00:00
|
|
|
Snapshotter: snapshot.NewMergeSnapshotter(context.TODO(), opt.Snapshotter, opt.LeaseManager),
|
|
|
|
ContentStore: opt.ContentStore,
|
|
|
|
LeaseManager: opt.LeaseManager,
|
|
|
|
PruneRefChecker: opt.PruneRefChecker,
|
|
|
|
GarbageCollect: opt.GarbageCollect,
|
|
|
|
Applier: opt.Applier,
|
|
|
|
Differ: opt.Differ,
|
|
|
|
MetadataStore: opt.MetadataStore,
|
|
|
|
records: make(map[string]*cacheRecord),
|
2017-05-27 06:12:13 +00:00
|
|
|
}
|
|
|
|
|
2017-07-03 23:08:20 +00:00
|
|
|
if err := cm.init(context.TODO()); err != nil {
|
2017-05-27 06:12:13 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// cm.scheduleGC(5 * time.Minute)
|
|
|
|
|
|
|
|
return cm, nil
|
|
|
|
}
|
|
|
|
|
2021-07-26 08:53:30 +00:00
|
|
|
func (cm *cacheManager) GetByBlob(ctx context.Context, desc ocispecs.Descriptor, parent ImmutableRef, opts ...RefOption) (ir ImmutableRef, rerr error) {
|
2019-09-18 00:18:32 +00:00
|
|
|
diffID, err := diffIDFromDescriptor(desc)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2019-07-22 21:43:16 +00:00
|
|
|
chainID := diffID
|
2019-10-03 21:11:54 +00:00
|
|
|
blobChainID := imagespecidentity.ChainID([]digest.Digest{desc.Digest, diffID})
|
2019-07-22 21:43:16 +00:00
|
|
|
|
2020-05-28 20:46:33 +00:00
|
|
|
descHandlers := descHandlersOf(opts...)
|
2020-11-12 23:36:16 +00:00
|
|
|
if desc.Digest != "" && (descHandlers == nil || descHandlers[desc.Digest] == nil) {
|
2020-05-28 20:46:33 +00:00
|
|
|
if _, err := cm.ContentStore.Info(ctx, desc.Digest); errors.Is(err, errdefs.ErrNotFound) {
|
|
|
|
return nil, NeedsRemoteProvidersError([]digest.Digest{desc.Digest})
|
|
|
|
} else if err != nil {
|
|
|
|
return nil, err
|
2019-10-22 00:11:22 +00:00
|
|
|
}
|
2019-07-22 21:43:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var p *immutableRef
|
|
|
|
if parent != nil {
|
2020-05-28 20:46:33 +00:00
|
|
|
p2, err := cm.Get(ctx, parent.ID(), NoUpdateLastUsed, descHandlers)
|
2019-09-18 00:18:32 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2021-07-01 02:50:54 +00:00
|
|
|
p = p2.(*immutableRef)
|
2021-07-09 00:09:35 +00:00
|
|
|
|
2021-08-03 01:57:39 +00:00
|
|
|
if err := p.Finalize(ctx); err != nil {
|
|
|
|
p.Release(context.TODO())
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2021-07-09 00:09:35 +00:00
|
|
|
if p.getChainID() == "" || p.getBlobChainID() == "" {
|
|
|
|
p.Release(context.TODO())
|
|
|
|
return nil, errors.Errorf("failed to get ref by blob on non-addressable parent")
|
|
|
|
}
|
|
|
|
chainID = imagespecidentity.ChainID([]digest.Digest{p.getChainID(), chainID})
|
|
|
|
blobChainID = imagespecidentity.ChainID([]digest.Digest{p.getBlobChainID(), blobChainID})
|
2019-07-22 21:43:16 +00:00
|
|
|
}
|
|
|
|
|
2019-09-24 01:02:17 +00:00
|
|
|
releaseParent := false
|
2019-07-22 21:43:16 +00:00
|
|
|
defer func() {
|
2020-05-28 20:46:33 +00:00
|
|
|
if releaseParent || rerr != nil && p != nil {
|
2019-07-22 21:43:16 +00:00
|
|
|
p.Release(context.TODO())
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
cm.mu.Lock()
|
|
|
|
defer cm.mu.Unlock()
|
|
|
|
|
2021-07-09 00:09:35 +00:00
|
|
|
sis, err := cm.searchBlobchain(ctx, blobChainID)
|
2019-07-22 21:43:16 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2021-06-08 03:26:05 +00:00
|
|
|
for _, si := range sis {
|
|
|
|
ref, err := cm.get(ctx, si.ID(), opts...)
|
2021-12-02 20:28:28 +00:00
|
|
|
if err != nil {
|
|
|
|
if errors.As(err, &NeedsRemoteProvidersError{}) {
|
|
|
|
// This shouldn't happen and indicates that blobchain IDs are being set incorrectly,
|
|
|
|
// but if it does happen it's not fatal as we can just not try to re-use by blobchainID.
|
|
|
|
// Log the error but continue.
|
|
|
|
bklog.G(ctx).Errorf("missing providers for ref with equivalent blobchain ID %s", blobChainID)
|
|
|
|
} else if !IsNotFound(err) {
|
|
|
|
return nil, errors.Wrapf(err, "failed to get record %s by blobchainid", sis[0].ID())
|
|
|
|
}
|
2019-07-22 21:43:16 +00:00
|
|
|
}
|
2021-06-08 03:26:05 +00:00
|
|
|
if ref == nil {
|
|
|
|
continue
|
|
|
|
}
|
2019-09-24 01:02:17 +00:00
|
|
|
if p != nil {
|
|
|
|
releaseParent = true
|
|
|
|
}
|
2021-07-09 00:09:35 +00:00
|
|
|
if err := setImageRefMetadata(ref.cacheMetadata, opts...); err != nil {
|
2020-08-05 23:51:19 +00:00
|
|
|
return nil, errors.Wrapf(err, "failed to append image ref metadata to ref %s", ref.ID())
|
|
|
|
}
|
2019-07-22 21:43:16 +00:00
|
|
|
return ref, nil
|
|
|
|
}
|
|
|
|
|
2021-07-09 00:09:35 +00:00
|
|
|
sis, err = cm.searchChain(ctx, chainID)
|
2019-07-22 21:43:16 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2021-07-09 00:09:35 +00:00
|
|
|
var link *immutableRef
|
2021-06-08 03:26:05 +00:00
|
|
|
for _, si := range sis {
|
|
|
|
ref, err := cm.get(ctx, si.ID(), opts...)
|
2021-07-07 15:56:02 +00:00
|
|
|
// if the error was NotFound or NeedsRemoteProvider, we can't re-use the snapshot from the blob so just skip it
|
|
|
|
if err != nil && !IsNotFound(err) && !errors.As(err, &NeedsRemoteProvidersError{}) {
|
2021-07-09 00:09:35 +00:00
|
|
|
return nil, errors.Wrapf(err, "failed to get record %s by chainid", si.ID())
|
2019-07-22 21:43:16 +00:00
|
|
|
}
|
2021-06-08 03:26:05 +00:00
|
|
|
if ref != nil {
|
|
|
|
link = ref
|
|
|
|
break
|
|
|
|
}
|
2019-07-22 21:43:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
id := identity.NewID()
|
|
|
|
snapshotID := chainID.String()
|
|
|
|
blobOnly := true
|
|
|
|
if link != nil {
|
2021-07-09 00:09:35 +00:00
|
|
|
snapshotID = link.getSnapshotID()
|
|
|
|
blobOnly = link.getBlobOnly()
|
2019-09-24 01:02:17 +00:00
|
|
|
go link.Release(context.TODO())
|
2019-07-22 21:43:16 +00:00
|
|
|
}
|
|
|
|
|
2021-08-03 01:57:39 +00:00
|
|
|
l, err := cm.LeaseManager.Create(ctx, func(l *leases.Lease) error {
|
2019-07-22 21:43:16 +00:00
|
|
|
l.ID = id
|
|
|
|
l.Labels = map[string]string{
|
|
|
|
"containerd.io/gc.flat": time.Now().UTC().Format(time.RFC3339Nano),
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, "failed to create lease")
|
|
|
|
}
|
|
|
|
|
|
|
|
defer func() {
|
2020-05-28 20:46:33 +00:00
|
|
|
if rerr != nil {
|
2021-08-03 01:57:39 +00:00
|
|
|
if err := cm.LeaseManager.Delete(context.TODO(), leases.Lease{
|
2019-07-22 21:43:16 +00:00
|
|
|
ID: l.ID,
|
|
|
|
}); err != nil {
|
|
|
|
logrus.Errorf("failed to remove lease: %+v", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
2021-10-28 19:59:26 +00:00
|
|
|
if err := cm.LeaseManager.AddResource(ctx, l, leases.Resource{
|
2019-09-18 00:18:32 +00:00
|
|
|
ID: snapshotID,
|
2021-10-28 19:59:26 +00:00
|
|
|
Type: "snapshots/" + cm.Snapshotter.Name(),
|
|
|
|
}); err != nil && !errdefs.IsAlreadyExists(err) {
|
2019-07-22 21:43:16 +00:00
|
|
|
return nil, errors.Wrapf(err, "failed to add snapshot %s to lease", id)
|
|
|
|
}
|
|
|
|
|
2019-10-22 00:11:22 +00:00
|
|
|
if desc.Digest != "" {
|
2021-08-03 01:57:39 +00:00
|
|
|
if err := cm.LeaseManager.AddResource(ctx, leases.Lease{ID: id}, leases.Resource{
|
2019-10-22 00:11:22 +00:00
|
|
|
ID: desc.Digest.String(),
|
|
|
|
Type: "content",
|
|
|
|
}); err != nil {
|
|
|
|
return nil, errors.Wrapf(err, "failed to add blob %s to lease", id)
|
|
|
|
}
|
2019-07-22 21:43:16 +00:00
|
|
|
}
|
|
|
|
|
2021-07-09 00:09:35 +00:00
|
|
|
md, _ := cm.getMetadata(id)
|
2019-07-22 21:43:16 +00:00
|
|
|
|
|
|
|
rec := &cacheRecord{
|
2021-07-09 00:09:35 +00:00
|
|
|
mu: &sync.Mutex{},
|
|
|
|
cm: cm,
|
|
|
|
refs: make(map[ref]struct{}),
|
2021-08-03 01:57:39 +00:00
|
|
|
parentRefs: parentRefs{layerParent: p},
|
2021-07-09 00:09:35 +00:00
|
|
|
cacheMetadata: md,
|
2019-07-22 21:43:16 +00:00
|
|
|
}
|
|
|
|
|
2021-08-03 01:57:39 +00:00
|
|
|
if err := initializeMetadata(rec.cacheMetadata, rec.parentRefs, opts...); err != nil {
|
2019-07-22 21:43:16 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2021-07-09 00:09:35 +00:00
|
|
|
if err := setImageRefMetadata(rec.cacheMetadata, opts...); err != nil {
|
2020-08-05 23:51:19 +00:00
|
|
|
return nil, errors.Wrapf(err, "failed to append image ref metadata to ref %s", rec.ID())
|
|
|
|
}
|
|
|
|
|
2021-07-09 00:09:35 +00:00
|
|
|
rec.queueDiffID(diffID)
|
|
|
|
rec.queueBlob(desc.Digest)
|
|
|
|
rec.queueChainID(chainID)
|
|
|
|
rec.queueBlobChainID(blobChainID)
|
|
|
|
rec.queueSnapshotID(snapshotID)
|
|
|
|
rec.queueBlobOnly(blobOnly)
|
|
|
|
rec.queueMediaType(desc.MediaType)
|
|
|
|
rec.queueBlobSize(desc.Size)
|
|
|
|
rec.queueCommitted(true)
|
2019-07-22 21:43:16 +00:00
|
|
|
|
2021-07-09 00:09:35 +00:00
|
|
|
if err := rec.commitMetadata(); err != nil {
|
2019-07-22 21:43:16 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
cm.records[id] = rec
|
|
|
|
|
2020-05-28 20:46:33 +00:00
|
|
|
return rec.ref(true, descHandlers), nil
|
2019-07-22 21:43:16 +00:00
|
|
|
}
|
|
|
|
|
2017-12-26 19:42:14 +00:00
|
|
|
// init loads all snapshots from metadata state and tries to load the records
|
|
|
|
// from the snapshotter. If snaphot can't be found, metadata is deleted as well.
|
2017-07-03 23:08:20 +00:00
|
|
|
func (cm *cacheManager) init(ctx context.Context) error {
|
2021-07-09 00:09:35 +00:00
|
|
|
items, err := cm.MetadataStore.All()
|
2017-07-03 23:08:20 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, si := range items {
|
2019-07-22 21:43:16 +00:00
|
|
|
if _, err := cm.getRecord(ctx, si.ID()); err != nil {
|
2019-09-18 00:18:32 +00:00
|
|
|
logrus.Debugf("could not load snapshot %s: %+v", si.ID(), err)
|
2021-07-09 00:09:35 +00:00
|
|
|
cm.MetadataStore.Clear(si.ID())
|
2019-09-19 21:55:10 +00:00
|
|
|
cm.LeaseManager.Delete(ctx, leases.Lease{ID: si.ID()})
|
2017-07-03 23:08:20 +00:00
|
|
|
}
|
|
|
|
}
|
2017-05-27 06:12:13 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-03-20 06:40:24 +00:00
|
|
|
// IdentityMapping returns the userns remapping used for refs
|
|
|
|
func (cm *cacheManager) IdentityMapping() *idtools.IdentityMapping {
|
|
|
|
return cm.Snapshotter.IdentityMapping()
|
|
|
|
}
|
|
|
|
|
2017-12-26 19:42:14 +00:00
|
|
|
// Close closes the manager and releases the metadata database lock. No other
|
|
|
|
// method should be called after Close.
|
2017-05-27 06:12:13 +00:00
|
|
|
func (cm *cacheManager) Close() error {
|
|
|
|
// TODO: allocate internal context and cancel it here
|
2021-07-09 00:09:35 +00:00
|
|
|
return cm.MetadataStore.Close()
|
2017-05-27 06:12:13 +00:00
|
|
|
}
|
|
|
|
|
2017-12-26 19:42:14 +00:00
|
|
|
// Get returns an immutable snapshot reference for ID
|
2017-07-25 19:11:52 +00:00
|
|
|
func (cm *cacheManager) Get(ctx context.Context, id string, opts ...RefOption) (ImmutableRef, error) {
|
2017-05-27 06:12:13 +00:00
|
|
|
cm.mu.Lock()
|
|
|
|
defer cm.mu.Unlock()
|
2019-07-22 21:43:16 +00:00
|
|
|
return cm.get(ctx, id, opts...)
|
2018-05-10 22:51:00 +00:00
|
|
|
}
|
|
|
|
|
2017-12-26 19:42:14 +00:00
|
|
|
// get requires manager lock to be taken
|
2019-07-22 21:43:16 +00:00
|
|
|
func (cm *cacheManager) get(ctx context.Context, id string, opts ...RefOption) (*immutableRef, error) {
|
|
|
|
rec, err := cm.getRecord(ctx, id, opts...)
|
2017-07-03 23:08:20 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2017-05-27 06:12:13 +00:00
|
|
|
}
|
|
|
|
rec.mu.Lock()
|
|
|
|
defer rec.mu.Unlock()
|
|
|
|
|
2018-09-14 20:35:41 +00:00
|
|
|
triggerUpdate := true
|
|
|
|
for _, o := range opts {
|
|
|
|
if o == NoUpdateLastUsed {
|
|
|
|
triggerUpdate = false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-28 20:46:33 +00:00
|
|
|
descHandlers := descHandlersOf(opts...)
|
|
|
|
|
2017-07-03 23:08:20 +00:00
|
|
|
if rec.mutable {
|
2017-12-26 19:42:14 +00:00
|
|
|
if len(rec.refs) != 0 {
|
2018-06-18 20:57:36 +00:00
|
|
|
return nil, errors.Wrapf(ErrLocked, "%s is locked", id)
|
2017-05-27 06:12:13 +00:00
|
|
|
}
|
2017-07-20 22:55:24 +00:00
|
|
|
if rec.equalImmutable != nil {
|
2020-05-28 20:46:33 +00:00
|
|
|
return rec.equalImmutable.ref(triggerUpdate, descHandlers), nil
|
2017-07-20 22:55:24 +00:00
|
|
|
}
|
2020-05-28 20:46:33 +00:00
|
|
|
return rec.mref(triggerUpdate, descHandlers).commit(ctx)
|
2017-05-27 06:12:13 +00:00
|
|
|
}
|
|
|
|
|
2020-05-28 20:46:33 +00:00
|
|
|
return rec.ref(triggerUpdate, descHandlers), nil
|
2017-05-27 06:12:13 +00:00
|
|
|
}
|
2017-07-03 23:08:20 +00:00
|
|
|
|
2017-12-26 19:42:14 +00:00
|
|
|
// getRecord returns record for id. Requires manager lock.
|
2019-07-22 21:43:16 +00:00
|
|
|
func (cm *cacheManager) getRecord(ctx context.Context, id string, opts ...RefOption) (cr *cacheRecord, retErr error) {
|
2020-05-28 20:46:33 +00:00
|
|
|
checkLazyProviders := func(rec *cacheRecord) error {
|
|
|
|
missing := NeedsRemoteProvidersError(nil)
|
|
|
|
dhs := descHandlersOf(opts...)
|
2021-08-03 01:57:39 +00:00
|
|
|
if err := rec.walkUniqueAncestors(func(cr *cacheRecord) error {
|
|
|
|
blob := cr.getBlob()
|
|
|
|
if isLazy, err := cr.isLazy(ctx); err != nil {
|
2020-05-28 20:46:33 +00:00
|
|
|
return err
|
|
|
|
} else if isLazy && dhs[blob] == nil {
|
|
|
|
missing = append(missing, blob)
|
|
|
|
}
|
2021-08-03 01:57:39 +00:00
|
|
|
return nil
|
|
|
|
}); err != nil {
|
|
|
|
return err
|
2020-05-28 20:46:33 +00:00
|
|
|
}
|
|
|
|
if len(missing) > 0 {
|
|
|
|
return missing
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2017-12-26 19:42:14 +00:00
|
|
|
if rec, ok := cm.records[id]; ok {
|
|
|
|
if rec.isDead() {
|
2019-06-01 23:34:02 +00:00
|
|
|
return nil, errors.Wrapf(errNotFound, "failed to get dead record %s", id)
|
2017-12-26 19:42:14 +00:00
|
|
|
}
|
2020-05-28 20:46:33 +00:00
|
|
|
if err := checkLazyProviders(rec); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2017-07-03 23:08:20 +00:00
|
|
|
return rec, nil
|
|
|
|
}
|
|
|
|
|
2021-07-09 00:09:35 +00:00
|
|
|
md, ok := cm.getMetadata(id)
|
2019-07-22 21:43:16 +00:00
|
|
|
if !ok {
|
2021-03-29 11:17:06 +00:00
|
|
|
return nil, errors.Wrap(errNotFound, id)
|
2018-05-10 22:51:00 +00:00
|
|
|
}
|
2021-08-03 01:57:39 +00:00
|
|
|
|
|
|
|
parents, err := cm.parentsOf(ctx, md, opts...)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrapf(err, "failed to get parents")
|
|
|
|
}
|
|
|
|
defer func() {
|
|
|
|
if retErr != nil {
|
|
|
|
parents.release(context.TODO())
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
2021-07-09 00:09:35 +00:00
|
|
|
if mutableID := md.getEqualMutable(); mutableID != "" {
|
2019-07-22 21:43:16 +00:00
|
|
|
mutable, err := cm.getRecord(ctx, mutableID)
|
2017-07-14 18:59:31 +00:00
|
|
|
if err != nil {
|
2017-12-27 01:22:50 +00:00
|
|
|
// check loading mutable deleted record from disk
|
2020-04-19 05:17:47 +00:00
|
|
|
if IsNotFound(err) {
|
2021-07-09 00:09:35 +00:00
|
|
|
cm.MetadataStore.Clear(id)
|
2017-12-27 01:22:50 +00:00
|
|
|
}
|
2017-07-14 18:59:31 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
2020-08-17 09:34:33 +00:00
|
|
|
|
2017-07-14 18:59:31 +00:00
|
|
|
rec := &cacheRecord{
|
2021-07-09 00:09:35 +00:00
|
|
|
mu: &sync.Mutex{},
|
|
|
|
cm: cm,
|
|
|
|
refs: make(map[ref]struct{}),
|
2021-08-03 01:57:39 +00:00
|
|
|
parentRefs: parents,
|
2021-07-09 00:09:35 +00:00
|
|
|
cacheMetadata: md,
|
|
|
|
equalMutable: &mutableRef{cacheRecord: mutable},
|
2017-07-14 18:59:31 +00:00
|
|
|
}
|
|
|
|
mutable.equalImmutable = &immutableRef{cacheRecord: rec}
|
|
|
|
cm.records[id] = rec
|
|
|
|
return rec, nil
|
|
|
|
}
|
|
|
|
|
2017-07-03 23:08:20 +00:00
|
|
|
rec := &cacheRecord{
|
2021-07-09 00:09:35 +00:00
|
|
|
mu: &sync.Mutex{},
|
|
|
|
mutable: !md.getCommitted(),
|
|
|
|
cm: cm,
|
|
|
|
refs: make(map[ref]struct{}),
|
2021-08-03 01:57:39 +00:00
|
|
|
parentRefs: parents,
|
2021-07-09 00:09:35 +00:00
|
|
|
cacheMetadata: md,
|
2017-07-03 23:08:20 +00:00
|
|
|
}
|
2017-07-25 19:11:52 +00:00
|
|
|
|
2017-12-27 01:22:50 +00:00
|
|
|
// the record was deleted but we crashed before data on disk was removed
|
2021-07-09 00:09:35 +00:00
|
|
|
if md.getDeleted() {
|
2017-12-27 01:22:50 +00:00
|
|
|
if err := rec.remove(ctx, true); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2019-06-01 23:34:02 +00:00
|
|
|
return nil, errors.Wrapf(errNotFound, "failed to get deleted record %s", id)
|
2017-12-27 01:22:50 +00:00
|
|
|
}
|
|
|
|
|
2021-08-03 01:57:39 +00:00
|
|
|
if err := initializeMetadata(rec.cacheMetadata, rec.parentRefs, opts...); err != nil {
|
2017-07-25 19:11:52 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2021-07-09 00:09:35 +00:00
|
|
|
if err := setImageRefMetadata(rec.cacheMetadata, opts...); err != nil {
|
2020-08-05 23:51:19 +00:00
|
|
|
return nil, errors.Wrapf(err, "failed to append image ref metadata to ref %s", rec.ID())
|
|
|
|
}
|
|
|
|
|
2017-07-14 18:59:31 +00:00
|
|
|
cm.records[id] = rec
|
2020-05-28 20:46:33 +00:00
|
|
|
if err := checkLazyProviders(rec); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2017-07-03 23:08:20 +00:00
|
|
|
return rec, nil
|
|
|
|
}
|
|
|
|
|
2021-08-03 01:57:39 +00:00
|
|
|
func (cm *cacheManager) parentsOf(ctx context.Context, md *cacheMetadata, opts ...RefOption) (ps parentRefs, rerr error) {
|
|
|
|
if parentID := md.getParent(); parentID != "" {
|
|
|
|
p, err := cm.get(ctx, parentID, append(opts, NoUpdateLastUsed))
|
|
|
|
if err != nil {
|
|
|
|
return ps, err
|
|
|
|
}
|
|
|
|
ps.layerParent = p
|
|
|
|
return ps, nil
|
|
|
|
}
|
|
|
|
for _, parentID := range md.getMergeParents() {
|
|
|
|
p, err := cm.get(ctx, parentID, append(opts, NoUpdateLastUsed))
|
|
|
|
if err != nil {
|
|
|
|
return ps, err
|
|
|
|
}
|
|
|
|
ps.mergeParents = append(ps.mergeParents, p)
|
|
|
|
}
|
cache: add support for Diff refs.
This allows you to create refs that are single layers representing the
diff between any two arbitrary refs. The primary use case for this is
to allows users to extract the changes created by ops like Exec and
rebase them elsewhere through MergeOp. However, there is no restriction
on the inputs to DiffOp and the resulting ref's layer is simply the
layer created by running the differ on the two inputs refs
(specifically, the same differ used during exports).
A Diff ref can be mounted by itself, in which case it is defined as the
result of applying the diff to Scratch. Most use cases though will use
Diff refs as the input to a MergeOp, in which case the diff is just
applied on top of the lower merge inputs, as was the case before.
In cases like Diff(A, A->B->C) (i.e. cases where the diff is between two
refs where the lower is an ancestor of upper), the diff will be defined
as the layers separating the two refs. In other cases, the diff is just
a single layer, not re-used from the inputs, representing the diff
between the two refs (which can be defined as the layer "Diff(A,B)" that
satisfies "Merge(A, Diff(A,B)) == B").
Note that there is technically a meaningful difference between the
"unmerge" behavior of extracting the layers separating diffs and the
"simple diff" of just running the differ on the two refs. Namely, in the
case where there are "intermediate deletes" (i.e. deletes that only
exist in layers between A and B but not between A and B by themselves),
then the simple diff and unmerge can create different results when
plugged into a MergeOp. This is due to the fact that intermediate
deletes will apply to the merge when using the unmerge behavior, but not
when using the simple diff. This is on top of the fact that the simple
diff inherently has a "flattening" behavior where multiple layers are
squashed into a single one.
So, in the case where lower is an ancestor of upper, we choose to follow
the unmerge behavior, but it's possible users may prefer the simple diff
behavior. As of right now, they won't be able to do so, but if needed we
can add the ability to choose which behavior is followed in the future.
This could be done through a flag provided to DiffOp or possibly by
adapting llb.Copy to support this type of behavior with the same
efficiency as DiffOp.
Signed-off-by: Erik Sipsma <erik@sipsma.dev>
2021-11-26 21:43:24 +00:00
|
|
|
if lowerParentID := md.getLowerDiffParent(); lowerParentID != "" {
|
|
|
|
p, err := cm.get(ctx, lowerParentID, append(opts, NoUpdateLastUsed))
|
|
|
|
if err != nil {
|
|
|
|
return ps, err
|
|
|
|
}
|
|
|
|
if ps.diffParents == nil {
|
|
|
|
ps.diffParents = &diffParents{}
|
|
|
|
}
|
|
|
|
ps.diffParents.lower = p
|
|
|
|
}
|
|
|
|
if upperParentID := md.getUpperDiffParent(); upperParentID != "" {
|
|
|
|
p, err := cm.get(ctx, upperParentID, append(opts, NoUpdateLastUsed))
|
|
|
|
if err != nil {
|
|
|
|
return ps, err
|
|
|
|
}
|
|
|
|
if ps.diffParents == nil {
|
|
|
|
ps.diffParents = &diffParents{}
|
|
|
|
}
|
|
|
|
ps.diffParents.upper = p
|
|
|
|
}
|
2021-08-03 01:57:39 +00:00
|
|
|
return ps, nil
|
|
|
|
}
|
|
|
|
|
2020-10-27 06:13:39 +00:00
|
|
|
func (cm *cacheManager) New(ctx context.Context, s ImmutableRef, sess session.Group, opts ...RefOption) (mr MutableRef, err error) {
|
2017-06-30 22:54:51 +00:00
|
|
|
id := identity.NewID()
|
2017-05-27 06:12:13 +00:00
|
|
|
|
2019-05-25 01:37:24 +00:00
|
|
|
var parent *immutableRef
|
2019-09-18 00:18:32 +00:00
|
|
|
var parentSnapshotID string
|
2017-05-27 06:12:13 +00:00
|
|
|
if s != nil {
|
2020-05-28 20:46:33 +00:00
|
|
|
if _, ok := s.(*immutableRef); ok {
|
|
|
|
parent = s.Clone().(*immutableRef)
|
|
|
|
} else {
|
|
|
|
p, err := cm.Get(ctx, s.ID(), append(opts, NoUpdateLastUsed)...)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
parent = p.(*immutableRef)
|
|
|
|
}
|
2021-10-25 17:51:30 +00:00
|
|
|
if err := parent.Finalize(ctx); err != nil {
|
2017-05-27 06:12:13 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
2020-10-27 06:13:39 +00:00
|
|
|
if err := parent.Extract(ctx, sess); err != nil {
|
2017-07-14 18:59:31 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
2021-07-09 00:09:35 +00:00
|
|
|
parentSnapshotID = parent.getSnapshotID()
|
2017-05-27 06:12:13 +00:00
|
|
|
}
|
|
|
|
|
2019-07-22 21:43:16 +00:00
|
|
|
defer func() {
|
|
|
|
if err != nil && parent != nil {
|
2017-09-22 17:30:30 +00:00
|
|
|
parent.Release(context.TODO())
|
2017-05-27 06:12:13 +00:00
|
|
|
}
|
2019-07-22 21:43:16 +00:00
|
|
|
}()
|
|
|
|
|
2021-08-03 01:57:39 +00:00
|
|
|
l, err := cm.LeaseManager.Create(ctx, func(l *leases.Lease) error {
|
2019-07-22 21:43:16 +00:00
|
|
|
l.ID = id
|
|
|
|
l.Labels = map[string]string{
|
|
|
|
"containerd.io/gc.flat": time.Now().UTC().Format(time.RFC3339Nano),
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, "failed to create lease")
|
|
|
|
}
|
|
|
|
|
|
|
|
defer func() {
|
|
|
|
if err != nil {
|
2021-08-03 01:57:39 +00:00
|
|
|
if err := cm.LeaseManager.Delete(context.TODO(), leases.Lease{
|
2019-07-22 21:43:16 +00:00
|
|
|
ID: l.ID,
|
|
|
|
}); err != nil {
|
|
|
|
logrus.Errorf("failed to remove lease: %+v", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
2021-10-28 19:59:26 +00:00
|
|
|
snapshotID := id
|
2021-08-03 01:57:39 +00:00
|
|
|
if err := cm.LeaseManager.AddResource(ctx, l, leases.Resource{
|
2021-10-28 19:59:26 +00:00
|
|
|
ID: snapshotID,
|
|
|
|
Type: "snapshots/" + cm.Snapshotter.Name(),
|
|
|
|
}); err != nil && !errdefs.IsAlreadyExists(err) {
|
|
|
|
return nil, errors.Wrapf(err, "failed to add snapshot %s to lease", snapshotID)
|
2019-07-22 21:43:16 +00:00
|
|
|
}
|
2017-05-27 06:12:13 +00:00
|
|
|
|
2020-10-11 12:56:00 +00:00
|
|
|
if cm.Snapshotter.Name() == "stargz" && parent != nil {
|
|
|
|
if rerr := parent.withRemoteSnapshotLabelsStargzMode(ctx, sess, func() {
|
2021-10-28 19:59:26 +00:00
|
|
|
err = cm.Snapshotter.Prepare(ctx, snapshotID, parentSnapshotID)
|
2020-10-11 12:56:00 +00:00
|
|
|
}); rerr != nil {
|
|
|
|
return nil, rerr
|
|
|
|
}
|
|
|
|
} else {
|
2021-10-28 19:59:26 +00:00
|
|
|
err = cm.Snapshotter.Prepare(ctx, snapshotID, parentSnapshotID)
|
2020-10-11 12:56:00 +00:00
|
|
|
}
|
|
|
|
if err != nil {
|
2021-08-03 01:57:39 +00:00
|
|
|
return nil, errors.Wrapf(err, "failed to prepare %v as %s", parentSnapshotID, snapshotID)
|
2019-09-24 01:02:17 +00:00
|
|
|
}
|
|
|
|
|
2021-07-09 00:09:35 +00:00
|
|
|
cm.mu.Lock()
|
|
|
|
defer cm.mu.Unlock()
|
|
|
|
|
|
|
|
md, _ := cm.getMetadata(id)
|
2017-07-03 23:08:20 +00:00
|
|
|
|
2017-05-27 06:12:13 +00:00
|
|
|
rec := &cacheRecord{
|
2021-07-09 00:09:35 +00:00
|
|
|
mu: &sync.Mutex{},
|
|
|
|
mutable: true,
|
|
|
|
cm: cm,
|
|
|
|
refs: make(map[ref]struct{}),
|
2021-08-03 01:57:39 +00:00
|
|
|
parentRefs: parentRefs{layerParent: parent},
|
2021-07-09 00:09:35 +00:00
|
|
|
cacheMetadata: md,
|
2017-05-27 06:12:13 +00:00
|
|
|
}
|
|
|
|
|
2021-10-28 19:59:26 +00:00
|
|
|
opts = append(opts, withSnapshotID(snapshotID))
|
2021-08-03 01:57:39 +00:00
|
|
|
if err := initializeMetadata(rec.cacheMetadata, rec.parentRefs, opts...); err != nil {
|
2017-07-25 19:11:52 +00:00
|
|
|
return nil, err
|
2017-07-19 23:39:32 +00:00
|
|
|
}
|
|
|
|
|
2021-07-09 00:09:35 +00:00
|
|
|
if err := setImageRefMetadata(rec.cacheMetadata, opts...); err != nil {
|
2020-08-05 23:51:19 +00:00
|
|
|
return nil, errors.Wrapf(err, "failed to append image ref metadata to ref %s", rec.ID())
|
|
|
|
}
|
|
|
|
|
2017-05-27 06:12:13 +00:00
|
|
|
cm.records[id] = rec // TODO: save to db
|
|
|
|
|
2020-08-17 09:34:33 +00:00
|
|
|
// parent refs are possibly lazy so keep it hold the description handlers.
|
|
|
|
var dhs DescHandlers
|
|
|
|
if parent != nil {
|
|
|
|
dhs = parent.descHandlers
|
|
|
|
}
|
|
|
|
return rec.mref(true, dhs), nil
|
2017-05-27 06:12:13 +00:00
|
|
|
}
|
2020-05-28 20:46:33 +00:00
|
|
|
|
|
|
|
func (cm *cacheManager) GetMutable(ctx context.Context, id string, opts ...RefOption) (MutableRef, error) {
|
2017-05-27 06:12:13 +00:00
|
|
|
cm.mu.Lock()
|
|
|
|
defer cm.mu.Unlock()
|
|
|
|
|
2020-05-28 20:46:33 +00:00
|
|
|
rec, err := cm.getRecord(ctx, id, opts...)
|
2017-07-03 23:08:20 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2017-05-27 06:12:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
rec.mu.Lock()
|
|
|
|
defer rec.mu.Unlock()
|
|
|
|
if !rec.mutable {
|
|
|
|
return nil, errors.Wrapf(errInvalid, "%s is not mutable", id)
|
|
|
|
}
|
|
|
|
|
2017-07-14 18:59:31 +00:00
|
|
|
if len(rec.refs) != 0 {
|
2018-06-18 20:57:36 +00:00
|
|
|
return nil, errors.Wrapf(ErrLocked, "%s is locked", id)
|
2017-05-27 06:12:13 +00:00
|
|
|
}
|
|
|
|
|
2017-07-14 18:59:31 +00:00
|
|
|
if rec.equalImmutable != nil {
|
2017-07-19 23:39:32 +00:00
|
|
|
if len(rec.equalImmutable.refs) != 0 {
|
2018-06-18 20:57:36 +00:00
|
|
|
return nil, errors.Wrapf(ErrLocked, "%s is locked", id)
|
2017-07-19 23:39:32 +00:00
|
|
|
}
|
2017-07-14 18:59:31 +00:00
|
|
|
delete(cm.records, rec.equalImmutable.ID())
|
|
|
|
if err := rec.equalImmutable.remove(ctx, false); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
rec.equalImmutable = nil
|
|
|
|
}
|
|
|
|
|
2020-05-28 20:46:33 +00:00
|
|
|
return rec.mref(true, descHandlersOf(opts...)), nil
|
2017-05-27 06:12:13 +00:00
|
|
|
}
|
|
|
|
|
2021-08-03 01:57:39 +00:00
|
|
|
func (cm *cacheManager) Merge(ctx context.Context, inputParents []ImmutableRef, opts ...RefOption) (ir ImmutableRef, rerr error) {
|
|
|
|
// TODO:(sipsma) optimize merge further by
|
|
|
|
// * Removing repeated occurrences of input layers (only leaving the uppermost)
|
|
|
|
// * Reusing existing merges that are equivalent to this one
|
|
|
|
// * Reusing existing merges that can be used as a base for this one
|
|
|
|
// * Calculating diffs only once (across both merges and during computeBlobChain). Save diff metadata so it can be reapplied.
|
|
|
|
// These optimizations may make sense here in cache, in the snapshotter or both.
|
|
|
|
// Be sure that any optimizations handle existing pre-optimization refs correctly.
|
|
|
|
|
|
|
|
parents := parentRefs{mergeParents: make([]*immutableRef, 0, len(inputParents))}
|
|
|
|
dhs := make(map[digest.Digest]*DescHandler)
|
|
|
|
defer func() {
|
|
|
|
if rerr != nil {
|
|
|
|
parents.release(context.TODO())
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
for _, inputParent := range inputParents {
|
|
|
|
if inputParent == nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
var parent *immutableRef
|
|
|
|
if p, ok := inputParent.(*immutableRef); ok {
|
|
|
|
parent = p
|
|
|
|
} else {
|
|
|
|
// inputParent implements ImmutableRef but isn't our internal struct, get an instance of the internal struct
|
|
|
|
// by calling Get on its ID.
|
|
|
|
p, err := cm.Get(ctx, inputParent.ID(), append(opts, NoUpdateLastUsed)...)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
parent = p.(*immutableRef)
|
|
|
|
defer parent.Release(context.TODO())
|
|
|
|
}
|
|
|
|
switch parent.kind() {
|
|
|
|
case Merge:
|
|
|
|
// if parent is itself a merge, flatten it out by just setting our parents directly to its parents
|
|
|
|
for _, grandparent := range parent.mergeParents {
|
|
|
|
parents.mergeParents = append(parents.mergeParents, grandparent.clone())
|
|
|
|
}
|
cache: add support for Diff refs.
This allows you to create refs that are single layers representing the
diff between any two arbitrary refs. The primary use case for this is
to allows users to extract the changes created by ops like Exec and
rebase them elsewhere through MergeOp. However, there is no restriction
on the inputs to DiffOp and the resulting ref's layer is simply the
layer created by running the differ on the two inputs refs
(specifically, the same differ used during exports).
A Diff ref can be mounted by itself, in which case it is defined as the
result of applying the diff to Scratch. Most use cases though will use
Diff refs as the input to a MergeOp, in which case the diff is just
applied on top of the lower merge inputs, as was the case before.
In cases like Diff(A, A->B->C) (i.e. cases where the diff is between two
refs where the lower is an ancestor of upper), the diff will be defined
as the layers separating the two refs. In other cases, the diff is just
a single layer, not re-used from the inputs, representing the diff
between the two refs (which can be defined as the layer "Diff(A,B)" that
satisfies "Merge(A, Diff(A,B)) == B").
Note that there is technically a meaningful difference between the
"unmerge" behavior of extracting the layers separating diffs and the
"simple diff" of just running the differ on the two refs. Namely, in the
case where there are "intermediate deletes" (i.e. deletes that only
exist in layers between A and B but not between A and B by themselves),
then the simple diff and unmerge can create different results when
plugged into a MergeOp. This is due to the fact that intermediate
deletes will apply to the merge when using the unmerge behavior, but not
when using the simple diff. This is on top of the fact that the simple
diff inherently has a "flattening" behavior where multiple layers are
squashed into a single one.
So, in the case where lower is an ancestor of upper, we choose to follow
the unmerge behavior, but it's possible users may prefer the simple diff
behavior. As of right now, they won't be able to do so, but if needed we
can add the ability to choose which behavior is followed in the future.
This could be done through a flag provided to DiffOp or possibly by
adapting llb.Copy to support this type of behavior with the same
efficiency as DiffOp.
Signed-off-by: Erik Sipsma <erik@sipsma.dev>
2021-11-26 21:43:24 +00:00
|
|
|
default:
|
2021-08-03 01:57:39 +00:00
|
|
|
parents.mergeParents = append(parents.mergeParents, parent.clone())
|
|
|
|
}
|
|
|
|
for dgst, handler := range parent.descHandlers {
|
|
|
|
dhs[dgst] = handler
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
cache: add support for Diff refs.
This allows you to create refs that are single layers representing the
diff between any two arbitrary refs. The primary use case for this is
to allows users to extract the changes created by ops like Exec and
rebase them elsewhere through MergeOp. However, there is no restriction
on the inputs to DiffOp and the resulting ref's layer is simply the
layer created by running the differ on the two inputs refs
(specifically, the same differ used during exports).
A Diff ref can be mounted by itself, in which case it is defined as the
result of applying the diff to Scratch. Most use cases though will use
Diff refs as the input to a MergeOp, in which case the diff is just
applied on top of the lower merge inputs, as was the case before.
In cases like Diff(A, A->B->C) (i.e. cases where the diff is between two
refs where the lower is an ancestor of upper), the diff will be defined
as the layers separating the two refs. In other cases, the diff is just
a single layer, not re-used from the inputs, representing the diff
between the two refs (which can be defined as the layer "Diff(A,B)" that
satisfies "Merge(A, Diff(A,B)) == B").
Note that there is technically a meaningful difference between the
"unmerge" behavior of extracting the layers separating diffs and the
"simple diff" of just running the differ on the two refs. Namely, in the
case where there are "intermediate deletes" (i.e. deletes that only
exist in layers between A and B but not between A and B by themselves),
then the simple diff and unmerge can create different results when
plugged into a MergeOp. This is due to the fact that intermediate
deletes will apply to the merge when using the unmerge behavior, but not
when using the simple diff. This is on top of the fact that the simple
diff inherently has a "flattening" behavior where multiple layers are
squashed into a single one.
So, in the case where lower is an ancestor of upper, we choose to follow
the unmerge behavior, but it's possible users may prefer the simple diff
behavior. As of right now, they won't be able to do so, but if needed we
can add the ability to choose which behavior is followed in the future.
This could be done through a flag provided to DiffOp or possibly by
adapting llb.Copy to support this type of behavior with the same
efficiency as DiffOp.
Signed-off-by: Erik Sipsma <erik@sipsma.dev>
2021-11-26 21:43:24 +00:00
|
|
|
mergeRef, err := cm.createMergeRef(ctx, parents, dhs, opts...)
|
|
|
|
if err != nil {
|
|
|
|
parents.release(context.TODO())
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return mergeRef, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (cm *cacheManager) createMergeRef(ctx context.Context, parents parentRefs, dhs DescHandlers, opts ...RefOption) (ir *immutableRef, rerr error) {
|
2021-08-03 01:57:39 +00:00
|
|
|
if len(parents.mergeParents) == 0 {
|
|
|
|
// merge of nothing is nothing
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
if len(parents.mergeParents) == 1 {
|
|
|
|
// merge of 1 thing is that thing
|
|
|
|
return parents.mergeParents[0], nil
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, parent := range parents.mergeParents {
|
|
|
|
if err := parent.Finalize(ctx); err != nil {
|
|
|
|
return nil, errors.Wrapf(err, "failed to finalize parent during merge")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
cm.mu.Lock()
|
|
|
|
defer cm.mu.Unlock()
|
|
|
|
|
|
|
|
// Build the new ref
|
cache: add support for Diff refs.
This allows you to create refs that are single layers representing the
diff between any two arbitrary refs. The primary use case for this is
to allows users to extract the changes created by ops like Exec and
rebase them elsewhere through MergeOp. However, there is no restriction
on the inputs to DiffOp and the resulting ref's layer is simply the
layer created by running the differ on the two inputs refs
(specifically, the same differ used during exports).
A Diff ref can be mounted by itself, in which case it is defined as the
result of applying the diff to Scratch. Most use cases though will use
Diff refs as the input to a MergeOp, in which case the diff is just
applied on top of the lower merge inputs, as was the case before.
In cases like Diff(A, A->B->C) (i.e. cases where the diff is between two
refs where the lower is an ancestor of upper), the diff will be defined
as the layers separating the two refs. In other cases, the diff is just
a single layer, not re-used from the inputs, representing the diff
between the two refs (which can be defined as the layer "Diff(A,B)" that
satisfies "Merge(A, Diff(A,B)) == B").
Note that there is technically a meaningful difference between the
"unmerge" behavior of extracting the layers separating diffs and the
"simple diff" of just running the differ on the two refs. Namely, in the
case where there are "intermediate deletes" (i.e. deletes that only
exist in layers between A and B but not between A and B by themselves),
then the simple diff and unmerge can create different results when
plugged into a MergeOp. This is due to the fact that intermediate
deletes will apply to the merge when using the unmerge behavior, but not
when using the simple diff. This is on top of the fact that the simple
diff inherently has a "flattening" behavior where multiple layers are
squashed into a single one.
So, in the case where lower is an ancestor of upper, we choose to follow
the unmerge behavior, but it's possible users may prefer the simple diff
behavior. As of right now, they won't be able to do so, but if needed we
can add the ability to choose which behavior is followed in the future.
This could be done through a flag provided to DiffOp or possibly by
adapting llb.Copy to support this type of behavior with the same
efficiency as DiffOp.
Signed-off-by: Erik Sipsma <erik@sipsma.dev>
2021-11-26 21:43:24 +00:00
|
|
|
id := identity.NewID()
|
2021-08-03 01:57:39 +00:00
|
|
|
md, _ := cm.getMetadata(id)
|
|
|
|
|
|
|
|
rec := &cacheRecord{
|
|
|
|
mu: &sync.Mutex{},
|
|
|
|
mutable: false,
|
|
|
|
cm: cm,
|
|
|
|
cacheMetadata: md,
|
|
|
|
parentRefs: parents,
|
|
|
|
refs: make(map[ref]struct{}),
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := initializeMetadata(rec.cacheMetadata, rec.parentRefs, opts...); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
snapshotID := id
|
|
|
|
l, err := cm.LeaseManager.Create(ctx, func(l *leases.Lease) error {
|
|
|
|
l.ID = id
|
|
|
|
l.Labels = map[string]string{
|
|
|
|
"containerd.io/gc.flat": time.Now().UTC().Format(time.RFC3339Nano),
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, "failed to create lease")
|
|
|
|
}
|
|
|
|
defer func() {
|
|
|
|
if rerr != nil {
|
|
|
|
if err := cm.LeaseManager.Delete(context.TODO(), leases.Lease{
|
|
|
|
ID: l.ID,
|
|
|
|
}); err != nil {
|
|
|
|
bklog.G(ctx).Errorf("failed to remove lease: %+v", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
if err := cm.LeaseManager.AddResource(ctx, leases.Lease{ID: id}, leases.Resource{
|
|
|
|
ID: snapshotID,
|
|
|
|
Type: "snapshots/" + cm.Snapshotter.Name(),
|
|
|
|
}); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
rec.queueSnapshotID(snapshotID)
|
|
|
|
|
|
|
|
if err := rec.commitMetadata(); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
cm.records[id] = rec
|
|
|
|
|
|
|
|
return rec.ref(true, dhs), nil
|
|
|
|
}
|
|
|
|
|
cache: add support for Diff refs.
This allows you to create refs that are single layers representing the
diff between any two arbitrary refs. The primary use case for this is
to allows users to extract the changes created by ops like Exec and
rebase them elsewhere through MergeOp. However, there is no restriction
on the inputs to DiffOp and the resulting ref's layer is simply the
layer created by running the differ on the two inputs refs
(specifically, the same differ used during exports).
A Diff ref can be mounted by itself, in which case it is defined as the
result of applying the diff to Scratch. Most use cases though will use
Diff refs as the input to a MergeOp, in which case the diff is just
applied on top of the lower merge inputs, as was the case before.
In cases like Diff(A, A->B->C) (i.e. cases where the diff is between two
refs where the lower is an ancestor of upper), the diff will be defined
as the layers separating the two refs. In other cases, the diff is just
a single layer, not re-used from the inputs, representing the diff
between the two refs (which can be defined as the layer "Diff(A,B)" that
satisfies "Merge(A, Diff(A,B)) == B").
Note that there is technically a meaningful difference between the
"unmerge" behavior of extracting the layers separating diffs and the
"simple diff" of just running the differ on the two refs. Namely, in the
case where there are "intermediate deletes" (i.e. deletes that only
exist in layers between A and B but not between A and B by themselves),
then the simple diff and unmerge can create different results when
plugged into a MergeOp. This is due to the fact that intermediate
deletes will apply to the merge when using the unmerge behavior, but not
when using the simple diff. This is on top of the fact that the simple
diff inherently has a "flattening" behavior where multiple layers are
squashed into a single one.
So, in the case where lower is an ancestor of upper, we choose to follow
the unmerge behavior, but it's possible users may prefer the simple diff
behavior. As of right now, they won't be able to do so, but if needed we
can add the ability to choose which behavior is followed in the future.
This could be done through a flag provided to DiffOp or possibly by
adapting llb.Copy to support this type of behavior with the same
efficiency as DiffOp.
Signed-off-by: Erik Sipsma <erik@sipsma.dev>
2021-11-26 21:43:24 +00:00
|
|
|
func (cm *cacheManager) Diff(ctx context.Context, lower, upper ImmutableRef, opts ...RefOption) (ir ImmutableRef, rerr error) {
|
|
|
|
if lower == nil {
|
|
|
|
return nil, errors.New("lower ref for diff cannot be nil")
|
|
|
|
}
|
|
|
|
|
|
|
|
var dps diffParents
|
|
|
|
parents := parentRefs{diffParents: &dps}
|
|
|
|
dhs := make(map[digest.Digest]*DescHandler)
|
|
|
|
defer func() {
|
|
|
|
if rerr != nil {
|
|
|
|
parents.release(context.TODO())
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
for i, inputParent := range []ImmutableRef{lower, upper} {
|
|
|
|
if inputParent == nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
var parent *immutableRef
|
|
|
|
if p, ok := inputParent.(*immutableRef); ok {
|
|
|
|
parent = p
|
|
|
|
} else {
|
|
|
|
// inputParent implements ImmutableRef but isn't our internal struct, get an instance of the internal struct
|
|
|
|
// by calling Get on its ID.
|
|
|
|
p, err := cm.Get(ctx, inputParent.ID(), append(opts, NoUpdateLastUsed)...)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
parent = p.(*immutableRef)
|
|
|
|
defer parent.Release(context.TODO())
|
|
|
|
}
|
|
|
|
if i == 0 {
|
|
|
|
dps.lower = parent
|
|
|
|
} else {
|
|
|
|
dps.upper = parent
|
|
|
|
}
|
|
|
|
for dgst, handler := range parent.descHandlers {
|
|
|
|
dhs[dgst] = handler
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check to see if lower is an ancestor of upper. If so, define the diff as a merge
|
|
|
|
// of the layers separating the two. This can result in a different diff than just
|
|
|
|
// running the differ directly on lower and upper, but this is chosen as a default
|
|
|
|
// behavior in order to maximize layer re-use in the default case. We may add an
|
|
|
|
// option for controlling this behavior in the future if it's needed.
|
|
|
|
if dps.upper != nil {
|
|
|
|
lowerLayers := dps.lower.layerChain()
|
|
|
|
upperLayers := dps.upper.layerChain()
|
|
|
|
var lowerIsAncestor bool
|
|
|
|
// when upper is only 1 layer different than lower, we can skip this as we
|
|
|
|
// won't need a merge in order to get optimal behavior.
|
|
|
|
if len(upperLayers) > len(lowerLayers)+1 {
|
|
|
|
lowerIsAncestor = true
|
|
|
|
for i, lowerLayer := range lowerLayers {
|
|
|
|
if lowerLayer.ID() != upperLayers[i].ID() {
|
|
|
|
lowerIsAncestor = false
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if lowerIsAncestor {
|
|
|
|
mergeParents := parentRefs{mergeParents: make([]*immutableRef, len(upperLayers)-len(lowerLayers))}
|
|
|
|
defer func() {
|
|
|
|
if rerr != nil {
|
|
|
|
mergeParents.release(context.TODO())
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
for i := len(lowerLayers); i < len(upperLayers); i++ {
|
|
|
|
subUpper := upperLayers[i]
|
|
|
|
subLower := subUpper.layerParent
|
|
|
|
if subLower == nil {
|
|
|
|
mergeParents.mergeParents[i-len(lowerLayers)] = subUpper.clone()
|
|
|
|
} else {
|
|
|
|
subParents := parentRefs{diffParents: &diffParents{lower: subLower.clone(), upper: subUpper.clone()}}
|
|
|
|
diffRef, err := cm.createDiffRef(ctx, subParents, subUpper.descHandlers,
|
|
|
|
WithDescription(fmt.Sprintf("diff %q -> %q", subLower.ID(), subUpper.ID())))
|
|
|
|
if err != nil {
|
|
|
|
subParents.release(context.TODO())
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
mergeParents.mergeParents[i-len(lowerLayers)] = diffRef
|
|
|
|
}
|
|
|
|
}
|
|
|
|
mergeRef, err := cm.createMergeRef(ctx, mergeParents, dhs)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
parents.release(context.TODO())
|
|
|
|
return mergeRef, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
diffRef, err := cm.createDiffRef(ctx, parents, dhs, opts...)
|
|
|
|
if err != nil {
|
|
|
|
parents.release(context.TODO())
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return diffRef, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (cm *cacheManager) createDiffRef(ctx context.Context, parents parentRefs, dhs DescHandlers, opts ...RefOption) (ir *immutableRef, rerr error) {
|
|
|
|
dps := parents.diffParents
|
|
|
|
if err := dps.lower.Finalize(ctx); err != nil {
|
|
|
|
return nil, errors.Wrapf(err, "failed to finalize lower parent during diff")
|
|
|
|
}
|
|
|
|
if dps.upper != nil {
|
|
|
|
if err := dps.upper.Finalize(ctx); err != nil {
|
|
|
|
return nil, errors.Wrapf(err, "failed to finalize upper parent during diff")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
id := identity.NewID()
|
|
|
|
|
|
|
|
snapshotID := id
|
|
|
|
|
|
|
|
l, err := cm.LeaseManager.Create(ctx, func(l *leases.Lease) error {
|
|
|
|
l.ID = id
|
|
|
|
l.Labels = map[string]string{
|
|
|
|
"containerd.io/gc.flat": time.Now().UTC().Format(time.RFC3339Nano),
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, "failed to create lease")
|
|
|
|
}
|
|
|
|
defer func() {
|
|
|
|
if rerr != nil {
|
|
|
|
if err := cm.LeaseManager.Delete(context.TODO(), leases.Lease{
|
|
|
|
ID: l.ID,
|
|
|
|
}); err != nil {
|
|
|
|
bklog.G(ctx).Errorf("failed to remove lease: %+v", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
if err := cm.LeaseManager.AddResource(ctx, leases.Lease{ID: id}, leases.Resource{
|
|
|
|
ID: snapshotID,
|
|
|
|
Type: "snapshots/" + cm.Snapshotter.Name(),
|
|
|
|
}); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
cm.mu.Lock()
|
|
|
|
defer cm.mu.Unlock()
|
|
|
|
|
|
|
|
// Build the new ref
|
|
|
|
md, _ := cm.getMetadata(id)
|
|
|
|
|
|
|
|
rec := &cacheRecord{
|
|
|
|
mu: &sync.Mutex{},
|
|
|
|
mutable: false,
|
|
|
|
cm: cm,
|
|
|
|
cacheMetadata: md,
|
|
|
|
parentRefs: parents,
|
|
|
|
refs: make(map[ref]struct{}),
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := initializeMetadata(rec.cacheMetadata, rec.parentRefs, opts...); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
rec.queueSnapshotID(snapshotID)
|
|
|
|
if err := rec.commitMetadata(); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
cm.records[id] = rec
|
|
|
|
|
|
|
|
return rec.ref(true, dhs), nil
|
|
|
|
}
|
|
|
|
|
2018-08-30 21:06:27 +00:00
|
|
|
func (cm *cacheManager) Prune(ctx context.Context, ch chan client.UsageInfo, opts ...client.PruneInfo) error {
|
2017-12-28 23:09:07 +00:00
|
|
|
cm.muPrune.Lock()
|
2018-07-26 00:20:57 +00:00
|
|
|
|
2018-08-30 21:06:27 +00:00
|
|
|
for _, opt := range opts {
|
|
|
|
if err := cm.pruneOnce(ctx, ch, opt); err != nil {
|
2019-07-22 21:43:16 +00:00
|
|
|
cm.muPrune.Unlock()
|
2018-08-30 21:06:27 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
2019-07-22 21:43:16 +00:00
|
|
|
|
|
|
|
cm.muPrune.Unlock()
|
|
|
|
|
2019-09-18 00:18:32 +00:00
|
|
|
if cm.GarbageCollect != nil {
|
|
|
|
if _, err := cm.GarbageCollect(ctx); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2019-07-22 21:43:16 +00:00
|
|
|
}
|
|
|
|
|
2018-08-30 21:06:27 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (cm *cacheManager) pruneOnce(ctx context.Context, ch chan client.UsageInfo, opt client.PruneInfo) error {
|
2018-07-26 00:20:57 +00:00
|
|
|
filter, err := filters.ParseAll(opt.Filter...)
|
|
|
|
if err != nil {
|
2019-06-01 23:34:02 +00:00
|
|
|
return errors.Wrapf(err, "failed to parse prune filters %v", opt.Filter)
|
2018-07-26 00:20:57 +00:00
|
|
|
}
|
|
|
|
|
2018-07-27 00:53:48 +00:00
|
|
|
var check ExternalRefChecker
|
|
|
|
if f := cm.PruneRefChecker; f != nil && (!opt.All || len(opt.Filter) > 0) {
|
2019-09-20 21:49:29 +00:00
|
|
|
c, err := f()
|
2018-07-27 00:53:48 +00:00
|
|
|
if err != nil {
|
2019-06-01 23:34:02 +00:00
|
|
|
return errors.WithStack(err)
|
2018-07-27 00:53:48 +00:00
|
|
|
}
|
|
|
|
check = c
|
|
|
|
}
|
|
|
|
|
2018-07-31 20:14:53 +00:00
|
|
|
totalSize := int64(0)
|
|
|
|
if opt.KeepBytes != 0 {
|
|
|
|
du, err := cm.DiskUsage(ctx, client.DiskUsageInfo{})
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
for _, ui := range du {
|
2019-09-20 21:49:29 +00:00
|
|
|
if ui.Shared {
|
|
|
|
continue
|
2018-07-31 20:14:53 +00:00
|
|
|
}
|
|
|
|
totalSize += ui.Size
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return cm.prune(ctx, ch, pruneOpt{
|
|
|
|
filter: filter,
|
|
|
|
all: opt.All,
|
|
|
|
checkShared: check,
|
|
|
|
keepDuration: opt.KeepDuration,
|
|
|
|
keepBytes: opt.KeepBytes,
|
|
|
|
totalSize: totalSize,
|
|
|
|
})
|
2017-12-28 23:09:07 +00:00
|
|
|
}
|
2017-12-27 01:22:50 +00:00
|
|
|
|
2018-07-31 20:14:53 +00:00
|
|
|
func (cm *cacheManager) prune(ctx context.Context, ch chan client.UsageInfo, opt pruneOpt) error {
|
|
|
|
var toDelete []*deleteRecord
|
|
|
|
|
|
|
|
if opt.keepBytes != 0 && opt.totalSize < opt.keepBytes {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2017-12-27 01:22:50 +00:00
|
|
|
cm.mu.Lock()
|
|
|
|
|
2018-07-31 20:14:53 +00:00
|
|
|
gcMode := opt.keepBytes != 0
|
|
|
|
cutOff := time.Now().Add(-opt.keepDuration)
|
|
|
|
|
2018-08-31 18:59:53 +00:00
|
|
|
locked := map[*sync.Mutex]struct{}{}
|
2018-07-31 20:14:53 +00:00
|
|
|
|
2017-12-27 01:22:50 +00:00
|
|
|
for _, cr := range cm.records {
|
2018-08-31 18:59:53 +00:00
|
|
|
if _, ok := locked[cr.mu]; ok {
|
2018-07-31 20:14:53 +00:00
|
|
|
continue
|
|
|
|
}
|
2017-12-27 01:22:50 +00:00
|
|
|
cr.mu.Lock()
|
2017-12-28 07:07:13 +00:00
|
|
|
|
2017-12-27 01:22:50 +00:00
|
|
|
// ignore duplicates that share data
|
|
|
|
if cr.equalImmutable != nil && len(cr.equalImmutable.refs) > 0 || cr.equalMutable != nil && len(cr.refs) == 0 {
|
|
|
|
cr.mu.Unlock()
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2017-12-28 07:07:13 +00:00
|
|
|
if cr.isDead() {
|
|
|
|
cr.mu.Unlock()
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2017-12-27 01:22:50 +00:00
|
|
|
if len(cr.refs) == 0 {
|
2021-07-09 00:09:35 +00:00
|
|
|
recordType := cr.GetRecordType()
|
2018-07-26 20:51:28 +00:00
|
|
|
if recordType == "" {
|
|
|
|
recordType = client.UsageRecordTypeRegular
|
|
|
|
}
|
|
|
|
|
2018-07-27 00:53:48 +00:00
|
|
|
shared := false
|
2018-07-31 20:14:53 +00:00
|
|
|
if opt.checkShared != nil {
|
2021-08-03 01:57:39 +00:00
|
|
|
shared = opt.checkShared.Exists(cr.ID(), cr.layerDigestChain())
|
2018-07-27 00:53:48 +00:00
|
|
|
}
|
|
|
|
|
2018-07-31 20:14:53 +00:00
|
|
|
if !opt.all {
|
2018-07-27 00:53:48 +00:00
|
|
|
if recordType == client.UsageRecordTypeInternal || recordType == client.UsageRecordTypeFrontend || shared {
|
|
|
|
cr.mu.Unlock()
|
|
|
|
continue
|
|
|
|
}
|
2018-07-26 22:31:35 +00:00
|
|
|
}
|
|
|
|
|
2018-07-26 00:20:57 +00:00
|
|
|
c := &client.UsageInfo{
|
2018-07-26 20:51:28 +00:00
|
|
|
ID: cr.ID(),
|
|
|
|
Mutable: cr.mutable,
|
|
|
|
RecordType: recordType,
|
2018-07-27 00:53:48 +00:00
|
|
|
Shared: shared,
|
2018-07-26 00:20:57 +00:00
|
|
|
}
|
2017-12-27 01:22:50 +00:00
|
|
|
|
2021-07-09 00:09:35 +00:00
|
|
|
usageCount, lastUsedAt := cr.getLastUsed()
|
2018-07-31 20:14:53 +00:00
|
|
|
c.LastUsedAt = lastUsedAt
|
|
|
|
c.UsageCount = usageCount
|
2018-07-26 00:20:57 +00:00
|
|
|
|
2018-07-31 20:14:53 +00:00
|
|
|
if opt.keepDuration != 0 {
|
|
|
|
if lastUsedAt != nil && lastUsedAt.After(cutOff) {
|
2018-07-26 00:20:57 +00:00
|
|
|
cr.mu.Unlock()
|
2018-07-31 20:14:53 +00:00
|
|
|
continue
|
2018-07-26 00:20:57 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-31 20:14:53 +00:00
|
|
|
if opt.filter.Match(adaptUsageInfo(c)) {
|
|
|
|
toDelete = append(toDelete, &deleteRecord{
|
|
|
|
cacheRecord: cr,
|
|
|
|
lastUsedAt: c.LastUsedAt,
|
|
|
|
usageCount: c.UsageCount,
|
|
|
|
})
|
|
|
|
if !gcMode {
|
|
|
|
cr.dead = true
|
|
|
|
|
|
|
|
// mark metadata as deleted in case we crash before cleanup finished
|
2021-07-09 00:09:35 +00:00
|
|
|
if err := cr.queueDeleted(); err != nil {
|
|
|
|
cr.mu.Unlock()
|
|
|
|
cm.mu.Unlock()
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err := cr.commitMetadata(); err != nil {
|
2018-07-31 20:14:53 +00:00
|
|
|
cr.mu.Unlock()
|
|
|
|
cm.mu.Unlock()
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
} else {
|
2018-08-31 18:59:53 +00:00
|
|
|
locked[cr.mu] = struct{}{}
|
2018-07-31 20:14:53 +00:00
|
|
|
continue // leave the record locked
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-12-27 01:22:50 +00:00
|
|
|
cr.mu.Unlock()
|
|
|
|
}
|
|
|
|
|
2018-07-31 20:14:53 +00:00
|
|
|
if gcMode && len(toDelete) > 0 {
|
|
|
|
sortDeleteRecords(toDelete)
|
|
|
|
var err error
|
|
|
|
for i, cr := range toDelete {
|
|
|
|
// only remove single record at a time
|
|
|
|
if i == 0 {
|
|
|
|
cr.dead = true
|
2021-07-09 00:09:35 +00:00
|
|
|
err = cr.queueDeleted()
|
|
|
|
if err == nil {
|
|
|
|
err = cr.commitMetadata()
|
|
|
|
}
|
2018-07-31 20:14:53 +00:00
|
|
|
}
|
|
|
|
cr.mu.Unlock()
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
toDelete = toDelete[:1]
|
|
|
|
}
|
|
|
|
|
2017-12-27 01:22:50 +00:00
|
|
|
cm.mu.Unlock()
|
|
|
|
|
|
|
|
if len(toDelete) == 0 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-06-15 02:25:46 +00:00
|
|
|
// calculate sizes here so that lock does not need to be held for slow process
|
|
|
|
for _, cr := range toDelete {
|
2021-07-09 00:09:35 +00:00
|
|
|
size := cr.getSize()
|
2021-06-15 02:25:46 +00:00
|
|
|
|
|
|
|
if size == sizeUnknown && cr.equalImmutable != nil {
|
2021-07-09 00:09:35 +00:00
|
|
|
size = cr.equalImmutable.getSize() // benefit from DiskUsage calc
|
2021-06-15 02:25:46 +00:00
|
|
|
}
|
|
|
|
if size == sizeUnknown {
|
|
|
|
// calling size will warm cache for next call
|
2021-07-09 00:09:35 +00:00
|
|
|
if _, err := cr.size(ctx); err != nil {
|
2021-06-15 02:25:46 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
cm.mu.Lock()
|
2017-12-27 01:22:50 +00:00
|
|
|
var err error
|
|
|
|
for _, cr := range toDelete {
|
|
|
|
cr.mu.Lock()
|
|
|
|
|
2021-07-09 00:09:35 +00:00
|
|
|
usageCount, lastUsedAt := cr.getLastUsed()
|
2017-12-27 01:22:50 +00:00
|
|
|
|
|
|
|
c := client.UsageInfo{
|
|
|
|
ID: cr.ID(),
|
|
|
|
Mutable: cr.mutable,
|
|
|
|
InUse: len(cr.refs) > 0,
|
2021-07-09 00:09:35 +00:00
|
|
|
Size: cr.getSize(),
|
|
|
|
CreatedAt: cr.GetCreatedAt(),
|
|
|
|
Description: cr.GetDescription(),
|
2017-12-27 01:22:50 +00:00
|
|
|
LastUsedAt: lastUsedAt,
|
|
|
|
UsageCount: usageCount,
|
|
|
|
}
|
|
|
|
|
2021-08-03 01:57:39 +00:00
|
|
|
switch cr.kind() {
|
|
|
|
case Layer:
|
|
|
|
c.Parents = []string{cr.layerParent.ID()}
|
|
|
|
case Merge:
|
|
|
|
c.Parents = make([]string, len(cr.mergeParents))
|
|
|
|
for i, p := range cr.mergeParents {
|
|
|
|
c.Parents[i] = p.ID()
|
|
|
|
}
|
cache: add support for Diff refs.
This allows you to create refs that are single layers representing the
diff between any two arbitrary refs. The primary use case for this is
to allows users to extract the changes created by ops like Exec and
rebase them elsewhere through MergeOp. However, there is no restriction
on the inputs to DiffOp and the resulting ref's layer is simply the
layer created by running the differ on the two inputs refs
(specifically, the same differ used during exports).
A Diff ref can be mounted by itself, in which case it is defined as the
result of applying the diff to Scratch. Most use cases though will use
Diff refs as the input to a MergeOp, in which case the diff is just
applied on top of the lower merge inputs, as was the case before.
In cases like Diff(A, A->B->C) (i.e. cases where the diff is between two
refs where the lower is an ancestor of upper), the diff will be defined
as the layers separating the two refs. In other cases, the diff is just
a single layer, not re-used from the inputs, representing the diff
between the two refs (which can be defined as the layer "Diff(A,B)" that
satisfies "Merge(A, Diff(A,B)) == B").
Note that there is technically a meaningful difference between the
"unmerge" behavior of extracting the layers separating diffs and the
"simple diff" of just running the differ on the two refs. Namely, in the
case where there are "intermediate deletes" (i.e. deletes that only
exist in layers between A and B but not between A and B by themselves),
then the simple diff and unmerge can create different results when
plugged into a MergeOp. This is due to the fact that intermediate
deletes will apply to the merge when using the unmerge behavior, but not
when using the simple diff. This is on top of the fact that the simple
diff inherently has a "flattening" behavior where multiple layers are
squashed into a single one.
So, in the case where lower is an ancestor of upper, we choose to follow
the unmerge behavior, but it's possible users may prefer the simple diff
behavior. As of right now, they won't be able to do so, but if needed we
can add the ability to choose which behavior is followed in the future.
This could be done through a flag provided to DiffOp or possibly by
adapting llb.Copy to support this type of behavior with the same
efficiency as DiffOp.
Signed-off-by: Erik Sipsma <erik@sipsma.dev>
2021-11-26 21:43:24 +00:00
|
|
|
case Diff:
|
|
|
|
c.Parents = make([]string, 0, 2)
|
|
|
|
if cr.diffParents.lower != nil {
|
|
|
|
c.Parents = append(c.Parents, cr.diffParents.lower.ID())
|
|
|
|
}
|
|
|
|
if cr.diffParents.upper != nil {
|
|
|
|
c.Parents = append(c.Parents, cr.diffParents.upper.ID())
|
|
|
|
}
|
2017-12-27 01:22:50 +00:00
|
|
|
}
|
2018-08-31 19:00:13 +00:00
|
|
|
if c.Size == sizeUnknown && cr.equalImmutable != nil {
|
2021-07-09 00:09:35 +00:00
|
|
|
c.Size = cr.equalImmutable.getSize() // benefit from DiskUsage calc
|
2018-08-31 19:00:13 +00:00
|
|
|
}
|
2017-12-27 01:22:50 +00:00
|
|
|
|
2018-08-31 19:00:13 +00:00
|
|
|
opt.totalSize -= c.Size
|
|
|
|
|
2017-12-27 01:22:50 +00:00
|
|
|
if cr.equalImmutable != nil {
|
|
|
|
if err1 := cr.equalImmutable.remove(ctx, false); err == nil {
|
|
|
|
err = err1
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if err1 := cr.remove(ctx, true); err == nil {
|
|
|
|
err = err1
|
|
|
|
}
|
|
|
|
|
|
|
|
if err == nil && ch != nil {
|
|
|
|
ch <- c
|
|
|
|
}
|
|
|
|
cr.mu.Unlock()
|
|
|
|
}
|
2021-06-15 02:25:46 +00:00
|
|
|
cm.mu.Unlock()
|
2017-12-27 01:22:50 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
select {
|
|
|
|
case <-ctx.Done():
|
|
|
|
return ctx.Err()
|
|
|
|
default:
|
2018-07-31 20:14:53 +00:00
|
|
|
return cm.prune(ctx, ch, opt)
|
2018-07-27 00:53:48 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (cm *cacheManager) markShared(m map[string]*cacheUsageInfo) error {
|
|
|
|
if cm.PruneRefChecker == nil {
|
|
|
|
return nil
|
|
|
|
}
|
2019-09-20 21:49:29 +00:00
|
|
|
c, err := cm.PruneRefChecker()
|
2018-07-27 00:53:48 +00:00
|
|
|
if err != nil {
|
2019-06-01 23:34:02 +00:00
|
|
|
return errors.WithStack(err)
|
2018-07-27 00:53:48 +00:00
|
|
|
}
|
|
|
|
|
2021-08-03 01:57:39 +00:00
|
|
|
var markAllParentsShared func(...string)
|
|
|
|
markAllParentsShared = func(ids ...string) {
|
|
|
|
for _, id := range ids {
|
|
|
|
if id == "" {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if v, ok := m[id]; ok {
|
|
|
|
v.shared = true
|
|
|
|
markAllParentsShared(v.parents...)
|
2018-07-27 00:53:48 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for id := range m {
|
|
|
|
if m[id].shared {
|
|
|
|
continue
|
|
|
|
}
|
2019-09-20 21:49:29 +00:00
|
|
|
if b := c.Exists(id, m[id].parentChain); b {
|
2018-07-27 00:53:48 +00:00
|
|
|
markAllParentsShared(id)
|
|
|
|
}
|
2017-12-27 01:22:50 +00:00
|
|
|
}
|
2018-07-27 00:53:48 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
type cacheUsageInfo struct {
|
|
|
|
refs int
|
2021-08-03 01:57:39 +00:00
|
|
|
parents []string
|
2018-07-27 00:53:48 +00:00
|
|
|
size int64
|
|
|
|
mutable bool
|
|
|
|
createdAt time.Time
|
|
|
|
usageCount int
|
|
|
|
lastUsedAt *time.Time
|
|
|
|
description string
|
|
|
|
doubleRef bool
|
|
|
|
recordType client.UsageRecordType
|
|
|
|
shared bool
|
2019-09-20 21:49:29 +00:00
|
|
|
parentChain []digest.Digest
|
2017-12-27 01:22:50 +00:00
|
|
|
}
|
|
|
|
|
2017-07-25 22:14:46 +00:00
|
|
|
func (cm *cacheManager) DiskUsage(ctx context.Context, opt client.DiskUsageInfo) ([]*client.UsageInfo, error) {
|
2018-07-26 00:01:37 +00:00
|
|
|
filter, err := filters.ParseAll(opt.Filter...)
|
|
|
|
if err != nil {
|
2019-06-01 23:34:02 +00:00
|
|
|
return nil, errors.Wrapf(err, "failed to parse diskusage filters %v", opt.Filter)
|
2018-07-26 00:01:37 +00:00
|
|
|
}
|
|
|
|
|
2017-05-27 06:12:13 +00:00
|
|
|
cm.mu.Lock()
|
|
|
|
|
2017-06-30 22:54:51 +00:00
|
|
|
m := make(map[string]*cacheUsageInfo, len(cm.records))
|
|
|
|
rescan := make(map[string]struct{}, len(cm.records))
|
2017-05-27 06:12:13 +00:00
|
|
|
|
|
|
|
for id, cr := range cm.records {
|
|
|
|
cr.mu.Lock()
|
2017-07-14 18:59:31 +00:00
|
|
|
// ignore duplicates that share data
|
|
|
|
if cr.equalImmutable != nil && len(cr.equalImmutable.refs) > 0 || cr.equalMutable != nil && len(cr.refs) == 0 {
|
|
|
|
cr.mu.Unlock()
|
|
|
|
continue
|
|
|
|
}
|
2017-07-25 22:14:46 +00:00
|
|
|
|
2021-07-09 00:09:35 +00:00
|
|
|
usageCount, lastUsedAt := cr.getLastUsed()
|
2017-06-30 22:54:51 +00:00
|
|
|
c := &cacheUsageInfo{
|
2017-07-25 19:11:52 +00:00
|
|
|
refs: len(cr.refs),
|
|
|
|
mutable: cr.mutable,
|
2021-07-09 00:09:35 +00:00
|
|
|
size: cr.getSize(),
|
|
|
|
createdAt: cr.GetCreatedAt(),
|
2017-07-25 19:11:52 +00:00
|
|
|
usageCount: usageCount,
|
|
|
|
lastUsedAt: lastUsedAt,
|
2021-07-09 00:09:35 +00:00
|
|
|
description: cr.GetDescription(),
|
2017-12-27 01:22:50 +00:00
|
|
|
doubleRef: cr.equalImmutable != nil,
|
2021-07-09 00:09:35 +00:00
|
|
|
recordType: cr.GetRecordType(),
|
2021-08-03 01:57:39 +00:00
|
|
|
parentChain: cr.layerDigestChain(),
|
2018-07-26 19:07:52 +00:00
|
|
|
}
|
|
|
|
if c.recordType == "" {
|
|
|
|
c.recordType = client.UsageRecordTypeRegular
|
2017-06-30 22:54:51 +00:00
|
|
|
}
|
2021-08-03 01:57:39 +00:00
|
|
|
|
|
|
|
switch cr.kind() {
|
|
|
|
case Layer:
|
|
|
|
c.parents = []string{cr.layerParent.ID()}
|
|
|
|
case Merge:
|
|
|
|
c.parents = make([]string, len(cr.mergeParents))
|
|
|
|
for i, p := range cr.mergeParents {
|
|
|
|
c.parents[i] = p.ID()
|
|
|
|
}
|
cache: add support for Diff refs.
This allows you to create refs that are single layers representing the
diff between any two arbitrary refs. The primary use case for this is
to allows users to extract the changes created by ops like Exec and
rebase them elsewhere through MergeOp. However, there is no restriction
on the inputs to DiffOp and the resulting ref's layer is simply the
layer created by running the differ on the two inputs refs
(specifically, the same differ used during exports).
A Diff ref can be mounted by itself, in which case it is defined as the
result of applying the diff to Scratch. Most use cases though will use
Diff refs as the input to a MergeOp, in which case the diff is just
applied on top of the lower merge inputs, as was the case before.
In cases like Diff(A, A->B->C) (i.e. cases where the diff is between two
refs where the lower is an ancestor of upper), the diff will be defined
as the layers separating the two refs. In other cases, the diff is just
a single layer, not re-used from the inputs, representing the diff
between the two refs (which can be defined as the layer "Diff(A,B)" that
satisfies "Merge(A, Diff(A,B)) == B").
Note that there is technically a meaningful difference between the
"unmerge" behavior of extracting the layers separating diffs and the
"simple diff" of just running the differ on the two refs. Namely, in the
case where there are "intermediate deletes" (i.e. deletes that only
exist in layers between A and B but not between A and B by themselves),
then the simple diff and unmerge can create different results when
plugged into a MergeOp. This is due to the fact that intermediate
deletes will apply to the merge when using the unmerge behavior, but not
when using the simple diff. This is on top of the fact that the simple
diff inherently has a "flattening" behavior where multiple layers are
squashed into a single one.
So, in the case where lower is an ancestor of upper, we choose to follow
the unmerge behavior, but it's possible users may prefer the simple diff
behavior. As of right now, they won't be able to do so, but if needed we
can add the ability to choose which behavior is followed in the future.
This could be done through a flag provided to DiffOp or possibly by
adapting llb.Copy to support this type of behavior with the same
efficiency as DiffOp.
Signed-off-by: Erik Sipsma <erik@sipsma.dev>
2021-11-26 21:43:24 +00:00
|
|
|
case Diff:
|
|
|
|
if cr.diffParents.lower != nil {
|
|
|
|
c.parents = append(c.parents, cr.diffParents.lower.ID())
|
|
|
|
}
|
|
|
|
if cr.diffParents.upper != nil {
|
|
|
|
c.parents = append(c.parents, cr.diffParents.upper.ID())
|
|
|
|
}
|
2017-06-30 22:54:51 +00:00
|
|
|
}
|
2017-07-14 18:59:31 +00:00
|
|
|
if cr.mutable && c.refs > 0 {
|
2017-06-30 22:54:51 +00:00
|
|
|
c.size = 0 // size can not be determined because it is changing
|
|
|
|
}
|
|
|
|
m[id] = c
|
|
|
|
rescan[id] = struct{}{}
|
2017-07-14 18:59:31 +00:00
|
|
|
cr.mu.Unlock()
|
2017-06-30 22:54:51 +00:00
|
|
|
}
|
|
|
|
cm.mu.Unlock()
|
|
|
|
|
|
|
|
for {
|
|
|
|
if len(rescan) == 0 {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
for id := range rescan {
|
2017-07-01 01:09:29 +00:00
|
|
|
v := m[id]
|
2021-08-03 01:57:39 +00:00
|
|
|
if v.refs == 0 {
|
|
|
|
for _, p := range v.parents {
|
|
|
|
m[p].refs--
|
|
|
|
if v.doubleRef {
|
|
|
|
m[p].refs--
|
|
|
|
}
|
|
|
|
rescan[p] = struct{}{}
|
2017-12-27 01:22:50 +00:00
|
|
|
}
|
2017-06-30 22:54:51 +00:00
|
|
|
}
|
|
|
|
delete(rescan, id)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-27 00:53:48 +00:00
|
|
|
if err := cm.markShared(m); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2017-06-30 22:54:51 +00:00
|
|
|
var du []*client.UsageInfo
|
|
|
|
for id, cr := range m {
|
2017-06-08 18:17:44 +00:00
|
|
|
c := &client.UsageInfo{
|
2017-07-25 19:11:52 +00:00
|
|
|
ID: id,
|
|
|
|
Mutable: cr.mutable,
|
|
|
|
InUse: cr.refs > 0,
|
|
|
|
Size: cr.size,
|
2021-08-03 01:57:39 +00:00
|
|
|
Parents: cr.parents,
|
2017-07-25 19:11:52 +00:00
|
|
|
CreatedAt: cr.createdAt,
|
|
|
|
Description: cr.description,
|
|
|
|
LastUsedAt: cr.lastUsedAt,
|
|
|
|
UsageCount: cr.usageCount,
|
2018-07-26 19:07:52 +00:00
|
|
|
RecordType: cr.recordType,
|
2018-07-27 00:53:48 +00:00
|
|
|
Shared: cr.shared,
|
2017-05-31 23:45:04 +00:00
|
|
|
}
|
2018-07-26 00:01:37 +00:00
|
|
|
if filter.Match(adaptUsageInfo(c)) {
|
|
|
|
du = append(du, c)
|
|
|
|
}
|
2017-05-27 06:12:13 +00:00
|
|
|
}
|
2017-05-31 23:45:04 +00:00
|
|
|
|
|
|
|
eg, ctx := errgroup.WithContext(ctx)
|
|
|
|
|
|
|
|
for _, d := range du {
|
|
|
|
if d.Size == sizeUnknown {
|
2017-06-08 18:17:44 +00:00
|
|
|
func(d *client.UsageInfo) {
|
2017-05-31 23:45:04 +00:00
|
|
|
eg.Go(func() error {
|
2021-07-09 00:09:35 +00:00
|
|
|
cm.mu.Lock()
|
|
|
|
ref, err := cm.get(ctx, d.ID, NoUpdateLastUsed)
|
|
|
|
cm.mu.Unlock()
|
2017-07-19 23:39:32 +00:00
|
|
|
if err != nil {
|
|
|
|
d.Size = 0
|
|
|
|
return nil
|
2017-05-31 23:45:04 +00:00
|
|
|
}
|
2021-07-09 00:09:35 +00:00
|
|
|
s, err := ref.size(ctx)
|
2017-07-19 23:39:32 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
d.Size = s
|
|
|
|
return ref.Release(context.TODO())
|
2017-05-31 23:45:04 +00:00
|
|
|
})
|
|
|
|
}(d)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := eg.Wait(); err != nil {
|
|
|
|
return du, err
|
|
|
|
}
|
2017-05-27 06:12:13 +00:00
|
|
|
|
|
|
|
return du, nil
|
|
|
|
}
|
2017-07-07 21:35:10 +00:00
|
|
|
|
2017-11-11 02:10:10 +00:00
|
|
|
func IsNotFound(err error) bool {
|
2020-04-19 05:17:47 +00:00
|
|
|
return errors.Is(err, errNotFound)
|
2017-11-11 02:10:10 +00:00
|
|
|
}
|
|
|
|
|
2018-09-14 20:35:41 +00:00
|
|
|
type RefOption interface{}
|
2017-07-19 23:39:32 +00:00
|
|
|
|
|
|
|
type cachePolicy int
|
|
|
|
|
|
|
|
const (
|
|
|
|
cachePolicyDefault cachePolicy = iota
|
2017-07-20 22:55:24 +00:00
|
|
|
cachePolicyRetain
|
2017-07-19 23:39:32 +00:00
|
|
|
)
|
|
|
|
|
2018-09-14 20:35:41 +00:00
|
|
|
type noUpdateLastUsed struct{}
|
|
|
|
|
|
|
|
var NoUpdateLastUsed noUpdateLastUsed
|
|
|
|
|
2021-07-09 00:09:35 +00:00
|
|
|
func CachePolicyRetain(m *cacheMetadata) error {
|
|
|
|
return m.SetCachePolicyRetain()
|
2017-08-10 01:20:33 +00:00
|
|
|
}
|
|
|
|
|
2021-07-09 00:09:35 +00:00
|
|
|
func CachePolicyDefault(m *cacheMetadata) error {
|
|
|
|
return m.SetCachePolicyDefault()
|
2019-05-28 20:55:00 +00:00
|
|
|
}
|
|
|
|
|
2017-07-25 19:11:52 +00:00
|
|
|
func WithDescription(descr string) RefOption {
|
2021-07-09 00:09:35 +00:00
|
|
|
return func(m *cacheMetadata) error {
|
|
|
|
return m.queueDescription(descr)
|
2017-07-25 19:11:52 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-26 19:07:52 +00:00
|
|
|
func WithRecordType(t client.UsageRecordType) RefOption {
|
2021-07-09 00:09:35 +00:00
|
|
|
return func(m *cacheMetadata) error {
|
|
|
|
return m.queueRecordType(t)
|
2018-07-26 19:07:52 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-05-05 05:18:11 +00:00
|
|
|
func WithCreationTime(tm time.Time) RefOption {
|
2021-07-09 00:09:35 +00:00
|
|
|
return func(m *cacheMetadata) error {
|
|
|
|
return m.queueCreatedAt(tm)
|
2018-05-05 05:18:11 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-05 23:51:19 +00:00
|
|
|
// Need a separate type for imageRef because it needs to be called outside
|
|
|
|
// initializeMetadata while still being a RefOption, so wrapping it in a
|
|
|
|
// different type ensures initializeMetadata won't catch it too and duplicate
|
|
|
|
// setting the metadata.
|
2021-07-09 00:09:35 +00:00
|
|
|
type imageRefOption func(m *cacheMetadata) error
|
2020-08-05 23:51:19 +00:00
|
|
|
|
|
|
|
// WithImageRef appends the given imageRef to the cache ref's metadata
|
|
|
|
func WithImageRef(imageRef string) RefOption {
|
2021-07-09 00:09:35 +00:00
|
|
|
return imageRefOption(func(m *cacheMetadata) error {
|
|
|
|
return m.appendImageRef(imageRef)
|
2020-08-05 23:51:19 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2021-07-09 00:09:35 +00:00
|
|
|
func setImageRefMetadata(m *cacheMetadata, opts ...RefOption) error {
|
2020-08-05 23:51:19 +00:00
|
|
|
for _, opt := range opts {
|
|
|
|
if fn, ok := opt.(imageRefOption); ok {
|
|
|
|
if err := fn(m); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-07-09 00:09:35 +00:00
|
|
|
return m.commitMetadata()
|
|
|
|
}
|
|
|
|
|
|
|
|
func withSnapshotID(id string) RefOption {
|
|
|
|
return imageRefOption(func(m *cacheMetadata) error {
|
|
|
|
return m.queueSnapshotID(id)
|
|
|
|
})
|
2020-08-05 23:51:19 +00:00
|
|
|
}
|
|
|
|
|
2021-08-03 01:57:39 +00:00
|
|
|
func initializeMetadata(m *cacheMetadata, parents parentRefs, opts ...RefOption) error {
|
2021-07-09 00:09:35 +00:00
|
|
|
if tm := m.GetCreatedAt(); !tm.IsZero() {
|
2017-07-25 19:11:52 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
cache: add support for Diff refs.
This allows you to create refs that are single layers representing the
diff between any two arbitrary refs. The primary use case for this is
to allows users to extract the changes created by ops like Exec and
rebase them elsewhere through MergeOp. However, there is no restriction
on the inputs to DiffOp and the resulting ref's layer is simply the
layer created by running the differ on the two inputs refs
(specifically, the same differ used during exports).
A Diff ref can be mounted by itself, in which case it is defined as the
result of applying the diff to Scratch. Most use cases though will use
Diff refs as the input to a MergeOp, in which case the diff is just
applied on top of the lower merge inputs, as was the case before.
In cases like Diff(A, A->B->C) (i.e. cases where the diff is between two
refs where the lower is an ancestor of upper), the diff will be defined
as the layers separating the two refs. In other cases, the diff is just
a single layer, not re-used from the inputs, representing the diff
between the two refs (which can be defined as the layer "Diff(A,B)" that
satisfies "Merge(A, Diff(A,B)) == B").
Note that there is technically a meaningful difference between the
"unmerge" behavior of extracting the layers separating diffs and the
"simple diff" of just running the differ on the two refs. Namely, in the
case where there are "intermediate deletes" (i.e. deletes that only
exist in layers between A and B but not between A and B by themselves),
then the simple diff and unmerge can create different results when
plugged into a MergeOp. This is due to the fact that intermediate
deletes will apply to the merge when using the unmerge behavior, but not
when using the simple diff. This is on top of the fact that the simple
diff inherently has a "flattening" behavior where multiple layers are
squashed into a single one.
So, in the case where lower is an ancestor of upper, we choose to follow
the unmerge behavior, but it's possible users may prefer the simple diff
behavior. As of right now, they won't be able to do so, but if needed we
can add the ability to choose which behavior is followed in the future.
This could be done through a flag provided to DiffOp or possibly by
adapting llb.Copy to support this type of behavior with the same
efficiency as DiffOp.
Signed-off-by: Erik Sipsma <erik@sipsma.dev>
2021-11-26 21:43:24 +00:00
|
|
|
switch {
|
|
|
|
case parents.layerParent != nil:
|
2021-08-03 01:57:39 +00:00
|
|
|
if err := m.queueParent(parents.layerParent.ID()); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
cache: add support for Diff refs.
This allows you to create refs that are single layers representing the
diff between any two arbitrary refs. The primary use case for this is
to allows users to extract the changes created by ops like Exec and
rebase them elsewhere through MergeOp. However, there is no restriction
on the inputs to DiffOp and the resulting ref's layer is simply the
layer created by running the differ on the two inputs refs
(specifically, the same differ used during exports).
A Diff ref can be mounted by itself, in which case it is defined as the
result of applying the diff to Scratch. Most use cases though will use
Diff refs as the input to a MergeOp, in which case the diff is just
applied on top of the lower merge inputs, as was the case before.
In cases like Diff(A, A->B->C) (i.e. cases where the diff is between two
refs where the lower is an ancestor of upper), the diff will be defined
as the layers separating the two refs. In other cases, the diff is just
a single layer, not re-used from the inputs, representing the diff
between the two refs (which can be defined as the layer "Diff(A,B)" that
satisfies "Merge(A, Diff(A,B)) == B").
Note that there is technically a meaningful difference between the
"unmerge" behavior of extracting the layers separating diffs and the
"simple diff" of just running the differ on the two refs. Namely, in the
case where there are "intermediate deletes" (i.e. deletes that only
exist in layers between A and B but not between A and B by themselves),
then the simple diff and unmerge can create different results when
plugged into a MergeOp. This is due to the fact that intermediate
deletes will apply to the merge when using the unmerge behavior, but not
when using the simple diff. This is on top of the fact that the simple
diff inherently has a "flattening" behavior where multiple layers are
squashed into a single one.
So, in the case where lower is an ancestor of upper, we choose to follow
the unmerge behavior, but it's possible users may prefer the simple diff
behavior. As of right now, they won't be able to do so, but if needed we
can add the ability to choose which behavior is followed in the future.
This could be done through a flag provided to DiffOp or possibly by
adapting llb.Copy to support this type of behavior with the same
efficiency as DiffOp.
Signed-off-by: Erik Sipsma <erik@sipsma.dev>
2021-11-26 21:43:24 +00:00
|
|
|
case len(parents.mergeParents) > 0:
|
2021-08-03 01:57:39 +00:00
|
|
|
var ids []string
|
|
|
|
for _, p := range parents.mergeParents {
|
|
|
|
ids = append(ids, p.ID())
|
|
|
|
}
|
|
|
|
if err := m.queueMergeParents(ids); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
cache: add support for Diff refs.
This allows you to create refs that are single layers representing the
diff between any two arbitrary refs. The primary use case for this is
to allows users to extract the changes created by ops like Exec and
rebase them elsewhere through MergeOp. However, there is no restriction
on the inputs to DiffOp and the resulting ref's layer is simply the
layer created by running the differ on the two inputs refs
(specifically, the same differ used during exports).
A Diff ref can be mounted by itself, in which case it is defined as the
result of applying the diff to Scratch. Most use cases though will use
Diff refs as the input to a MergeOp, in which case the diff is just
applied on top of the lower merge inputs, as was the case before.
In cases like Diff(A, A->B->C) (i.e. cases where the diff is between two
refs where the lower is an ancestor of upper), the diff will be defined
as the layers separating the two refs. In other cases, the diff is just
a single layer, not re-used from the inputs, representing the diff
between the two refs (which can be defined as the layer "Diff(A,B)" that
satisfies "Merge(A, Diff(A,B)) == B").
Note that there is technically a meaningful difference between the
"unmerge" behavior of extracting the layers separating diffs and the
"simple diff" of just running the differ on the two refs. Namely, in the
case where there are "intermediate deletes" (i.e. deletes that only
exist in layers between A and B but not between A and B by themselves),
then the simple diff and unmerge can create different results when
plugged into a MergeOp. This is due to the fact that intermediate
deletes will apply to the merge when using the unmerge behavior, but not
when using the simple diff. This is on top of the fact that the simple
diff inherently has a "flattening" behavior where multiple layers are
squashed into a single one.
So, in the case where lower is an ancestor of upper, we choose to follow
the unmerge behavior, but it's possible users may prefer the simple diff
behavior. As of right now, they won't be able to do so, but if needed we
can add the ability to choose which behavior is followed in the future.
This could be done through a flag provided to DiffOp or possibly by
adapting llb.Copy to support this type of behavior with the same
efficiency as DiffOp.
Signed-off-by: Erik Sipsma <erik@sipsma.dev>
2021-11-26 21:43:24 +00:00
|
|
|
case parents.diffParents != nil:
|
|
|
|
if parents.diffParents.lower != nil {
|
|
|
|
if err := m.queueLowerDiffParent(parents.diffParents.lower.ID()); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if parents.diffParents.upper != nil {
|
|
|
|
if err := m.queueUpperDiffParent(parents.diffParents.upper.ID()); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
2019-07-22 21:43:16 +00:00
|
|
|
}
|
|
|
|
|
2021-07-09 00:09:35 +00:00
|
|
|
if err := m.queueCreatedAt(time.Now()); err != nil {
|
2018-05-04 20:29:04 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2017-07-25 19:11:52 +00:00
|
|
|
for _, opt := range opts {
|
2021-07-09 00:09:35 +00:00
|
|
|
if fn, ok := opt.(func(*cacheMetadata) error); ok {
|
2018-09-14 20:35:41 +00:00
|
|
|
if err := fn(m); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2017-07-25 19:11:52 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-07-09 00:09:35 +00:00
|
|
|
return m.commitMetadata()
|
2017-07-19 23:39:32 +00:00
|
|
|
}
|
2018-07-26 00:01:37 +00:00
|
|
|
|
|
|
|
func adaptUsageInfo(info *client.UsageInfo) filters.Adaptor {
|
|
|
|
return filters.AdapterFunc(func(fieldpath []string) (string, bool) {
|
|
|
|
if len(fieldpath) == 0 {
|
|
|
|
return "", false
|
|
|
|
}
|
|
|
|
|
|
|
|
switch fieldpath[0] {
|
|
|
|
case "id":
|
|
|
|
return info.ID, info.ID != ""
|
2021-08-03 01:57:39 +00:00
|
|
|
case "parents":
|
|
|
|
return strings.Join(info.Parents, ";"), len(info.Parents) > 0
|
2018-07-26 00:01:37 +00:00
|
|
|
case "description":
|
|
|
|
return info.Description, info.Description != ""
|
|
|
|
case "inuse":
|
|
|
|
return "", info.InUse
|
|
|
|
case "mutable":
|
|
|
|
return "", info.Mutable
|
|
|
|
case "immutable":
|
|
|
|
return "", !info.Mutable
|
2018-07-26 19:07:52 +00:00
|
|
|
case "type":
|
|
|
|
return string(info.RecordType), info.RecordType != ""
|
2018-07-27 00:53:48 +00:00
|
|
|
case "shared":
|
|
|
|
return "", info.Shared
|
|
|
|
case "private":
|
|
|
|
return "", !info.Shared
|
2018-07-26 00:01:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: add int/datetime/bytes support for more fields
|
|
|
|
|
|
|
|
return "", false
|
|
|
|
})
|
|
|
|
}
|
2018-07-31 20:14:53 +00:00
|
|
|
|
|
|
|
type pruneOpt struct {
|
|
|
|
filter filters.Filter
|
|
|
|
all bool
|
|
|
|
checkShared ExternalRefChecker
|
|
|
|
keepDuration time.Duration
|
|
|
|
keepBytes int64
|
|
|
|
totalSize int64
|
|
|
|
}
|
|
|
|
|
|
|
|
type deleteRecord struct {
|
|
|
|
*cacheRecord
|
|
|
|
lastUsedAt *time.Time
|
|
|
|
usageCount int
|
|
|
|
lastUsedAtIndex int
|
|
|
|
usageCountIndex int
|
|
|
|
}
|
|
|
|
|
|
|
|
func sortDeleteRecords(toDelete []*deleteRecord) {
|
|
|
|
sort.Slice(toDelete, func(i, j int) bool {
|
|
|
|
if toDelete[i].lastUsedAt == nil {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
if toDelete[j].lastUsedAt == nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
return toDelete[i].lastUsedAt.Before(*toDelete[j].lastUsedAt)
|
|
|
|
})
|
|
|
|
|
|
|
|
maxLastUsedIndex := 0
|
|
|
|
var val time.Time
|
|
|
|
for _, v := range toDelete {
|
|
|
|
if v.lastUsedAt != nil && v.lastUsedAt.After(val) {
|
|
|
|
val = *v.lastUsedAt
|
|
|
|
maxLastUsedIndex++
|
|
|
|
}
|
|
|
|
v.lastUsedAtIndex = maxLastUsedIndex
|
|
|
|
}
|
|
|
|
|
|
|
|
sort.Slice(toDelete, func(i, j int) bool {
|
|
|
|
return toDelete[i].usageCount < toDelete[j].usageCount
|
|
|
|
})
|
|
|
|
|
|
|
|
maxUsageCountIndex := 0
|
|
|
|
var count int
|
|
|
|
for _, v := range toDelete {
|
|
|
|
if v.usageCount != count {
|
|
|
|
count = v.usageCount
|
|
|
|
maxUsageCountIndex++
|
|
|
|
}
|
|
|
|
v.usageCountIndex = maxUsageCountIndex
|
|
|
|
}
|
|
|
|
|
|
|
|
sort.Slice(toDelete, func(i, j int) bool {
|
|
|
|
return float64(toDelete[i].lastUsedAtIndex)/float64(maxLastUsedIndex)+
|
|
|
|
float64(toDelete[i].usageCountIndex)/float64(maxUsageCountIndex) <
|
|
|
|
float64(toDelete[j].lastUsedAtIndex)/float64(maxLastUsedIndex)+
|
|
|
|
float64(toDelete[j].usageCountIndex)/float64(maxUsageCountIndex)
|
|
|
|
})
|
|
|
|
}
|
2019-09-18 00:18:32 +00:00
|
|
|
|
2021-07-26 08:53:30 +00:00
|
|
|
func diffIDFromDescriptor(desc ocispecs.Descriptor) (digest.Digest, error) {
|
2019-09-18 00:18:32 +00:00
|
|
|
diffIDStr, ok := desc.Annotations["containerd.io/uncompressed"]
|
|
|
|
if !ok {
|
|
|
|
return "", errors.Errorf("missing uncompressed annotation for %s", desc.Digest)
|
|
|
|
}
|
|
|
|
diffID, err := digest.Parse(diffIDStr)
|
|
|
|
if err != nil {
|
|
|
|
return "", errors.Wrapf(err, "failed to parse diffID %q for %s", diffIDStr, desc.Digest)
|
|
|
|
}
|
|
|
|
return diffID, nil
|
|
|
|
}
|