1package memory
2
3import (
4	"context"
5	"sync"
6
7	"github.com/docker/distribution"
8	"github.com/docker/distribution/reference"
9	"github.com/docker/distribution/registry/storage/cache"
10	"github.com/opencontainers/go-digest"
11)
12
13type inMemoryBlobDescriptorCacheProvider struct {
14	global       *mapBlobDescriptorCache
15	repositories map[string]*mapBlobDescriptorCache
16	mu           sync.RWMutex
17}
18
19// NewInMemoryBlobDescriptorCacheProvider returns a new mapped-based cache for
20// storing blob descriptor data.
21func NewInMemoryBlobDescriptorCacheProvider() cache.BlobDescriptorCacheProvider {
22	return &inMemoryBlobDescriptorCacheProvider{
23		global:       newMapBlobDescriptorCache(),
24		repositories: make(map[string]*mapBlobDescriptorCache),
25	}
26}
27
28func (imbdcp *inMemoryBlobDescriptorCacheProvider) RepositoryScoped(repo string) (distribution.BlobDescriptorService, error) {
29	if _, err := reference.ParseNormalizedNamed(repo); err != nil {
30		return nil, err
31	}
32
33	imbdcp.mu.RLock()
34	defer imbdcp.mu.RUnlock()
35
36	return &repositoryScopedInMemoryBlobDescriptorCache{
37		repo:       repo,
38		parent:     imbdcp,
39		repository: imbdcp.repositories[repo],
40	}, nil
41}
42
43func (imbdcp *inMemoryBlobDescriptorCacheProvider) Stat(ctx context.Context, dgst digest.Digest) (distribution.Descriptor, error) {
44	return imbdcp.global.Stat(ctx, dgst)
45}
46
47func (imbdcp *inMemoryBlobDescriptorCacheProvider) Clear(ctx context.Context, dgst digest.Digest) error {
48	return imbdcp.global.Clear(ctx, dgst)
49}
50
51func (imbdcp *inMemoryBlobDescriptorCacheProvider) SetDescriptor(ctx context.Context, dgst digest.Digest, desc distribution.Descriptor) error {
52	_, err := imbdcp.Stat(ctx, dgst)
53	if err == distribution.ErrBlobUnknown {
54
55		if dgst.Algorithm() != desc.Digest.Algorithm() && dgst != desc.Digest {
56			// if the digests differ, set the other canonical mapping
57			if err := imbdcp.global.SetDescriptor(ctx, desc.Digest, desc); err != nil {
58				return err
59			}
60		}
61
62		// unknown, just set it
63		return imbdcp.global.SetDescriptor(ctx, dgst, desc)
64	}
65
66	// we already know it, do nothing
67	return err
68}
69
70// repositoryScopedInMemoryBlobDescriptorCache provides the request scoped
71// repository cache. Instances are not thread-safe but the delegated
72// operations are.
73type repositoryScopedInMemoryBlobDescriptorCache struct {
74	repo       string
75	parent     *inMemoryBlobDescriptorCacheProvider // allows lazy allocation of repo's map
76	repository *mapBlobDescriptorCache
77}
78
79func (rsimbdcp *repositoryScopedInMemoryBlobDescriptorCache) Stat(ctx context.Context, dgst digest.Digest) (distribution.Descriptor, error) {
80	rsimbdcp.parent.mu.Lock()
81	repo := rsimbdcp.repository
82	rsimbdcp.parent.mu.Unlock()
83
84	if repo == nil {
85		return distribution.Descriptor{}, distribution.ErrBlobUnknown
86	}
87
88	return repo.Stat(ctx, dgst)
89}
90
91func (rsimbdcp *repositoryScopedInMemoryBlobDescriptorCache) Clear(ctx context.Context, dgst digest.Digest) error {
92	rsimbdcp.parent.mu.Lock()
93	repo := rsimbdcp.repository
94	rsimbdcp.parent.mu.Unlock()
95
96	if repo == nil {
97		return distribution.ErrBlobUnknown
98	}
99
100	return repo.Clear(ctx, dgst)
101}
102
103func (rsimbdcp *repositoryScopedInMemoryBlobDescriptorCache) SetDescriptor(ctx context.Context, dgst digest.Digest, desc distribution.Descriptor) error {
104	rsimbdcp.parent.mu.Lock()
105	repo := rsimbdcp.repository
106	if repo == nil {
107		// allocate map since we are setting it now.
108		var ok bool
109		// have to read back value since we may have allocated elsewhere.
110		repo, ok = rsimbdcp.parent.repositories[rsimbdcp.repo]
111		if !ok {
112			repo = newMapBlobDescriptorCache()
113			rsimbdcp.parent.repositories[rsimbdcp.repo] = repo
114		}
115		rsimbdcp.repository = repo
116	}
117	rsimbdcp.parent.mu.Unlock()
118
119	if err := repo.SetDescriptor(ctx, dgst, desc); err != nil {
120		return err
121	}
122
123	return rsimbdcp.parent.SetDescriptor(ctx, dgst, desc)
124}
125
126// mapBlobDescriptorCache provides a simple map-based implementation of the
127// descriptor cache.
128type mapBlobDescriptorCache struct {
129	descriptors map[digest.Digest]distribution.Descriptor
130	mu          sync.RWMutex
131}
132
133var _ distribution.BlobDescriptorService = &mapBlobDescriptorCache{}
134
135func newMapBlobDescriptorCache() *mapBlobDescriptorCache {
136	return &mapBlobDescriptorCache{
137		descriptors: make(map[digest.Digest]distribution.Descriptor),
138	}
139}
140
141func (mbdc *mapBlobDescriptorCache) Stat(ctx context.Context, dgst digest.Digest) (distribution.Descriptor, error) {
142	if err := dgst.Validate(); err != nil {
143		return distribution.Descriptor{}, err
144	}
145
146	mbdc.mu.RLock()
147	defer mbdc.mu.RUnlock()
148
149	desc, ok := mbdc.descriptors[dgst]
150	if !ok {
151		return distribution.Descriptor{}, distribution.ErrBlobUnknown
152	}
153
154	return desc, nil
155}
156
157func (mbdc *mapBlobDescriptorCache) Clear(ctx context.Context, dgst digest.Digest) error {
158	mbdc.mu.Lock()
159	defer mbdc.mu.Unlock()
160
161	delete(mbdc.descriptors, dgst)
162	return nil
163}
164
165func (mbdc *mapBlobDescriptorCache) SetDescriptor(ctx context.Context, dgst digest.Digest, desc distribution.Descriptor) error {
166	if err := dgst.Validate(); err != nil {
167		return err
168	}
169
170	if err := cache.ValidateDescriptor(desc); err != nil {
171		return err
172	}
173
174	mbdc.mu.Lock()
175	defer mbdc.mu.Unlock()
176
177	mbdc.descriptors[dgst] = desc
178	return nil
179}
180