Skip to content

Commit 5e8b325

Browse files
committed
noot
1 parent 3bad5b6 commit 5e8b325

File tree

3 files changed

+275
-17
lines changed

3 files changed

+275
-17
lines changed

account_test.go

+17-17
Original file line numberDiff line numberDiff line change
@@ -29,25 +29,25 @@ import (
2929
"go.uber.org/zap"
3030
)
3131

32-
// memoryStorage is an in-memory storage implementation with known contents *and* fixed iteration order for List.
33-
type memoryStorage struct {
34-
contents []memoryStorageItem
32+
// testingMemoryStorage is an in-memory storage implementation with known contents *and* fixed iteration order for List.
33+
type testingMemoryStorage struct {
34+
contents []testingMemoryStorageItem
3535
}
3636

37-
type memoryStorageItem struct {
37+
type testingMemoryStorageItem struct {
3838
key string
3939
data []byte
4040
}
4141

42-
func (m *memoryStorage) lookup(_ context.Context, key string) *memoryStorageItem {
42+
func (m *testingMemoryStorage) lookup(_ context.Context, key string) *testingMemoryStorageItem {
4343
for _, item := range m.contents {
4444
if item.key == key {
4545
return &item
4646
}
4747
}
4848
return nil
4949
}
50-
func (m *memoryStorage) Delete(ctx context.Context, key string) error {
50+
func (m *testingMemoryStorage) Delete(ctx context.Context, key string) error {
5151
for i, item := range m.contents {
5252
if item.key == key {
5353
m.contents = append(m.contents[:i], m.contents[i+1:]...)
@@ -56,14 +56,14 @@ func (m *memoryStorage) Delete(ctx context.Context, key string) error {
5656
}
5757
return fs.ErrNotExist
5858
}
59-
func (m *memoryStorage) Store(ctx context.Context, key string, value []byte) error {
60-
m.contents = append(m.contents, memoryStorageItem{key: key, data: value})
59+
func (m *testingMemoryStorage) Store(ctx context.Context, key string, value []byte) error {
60+
m.contents = append(m.contents, testingMemoryStorageItem{key: key, data: value})
6161
return nil
6262
}
63-
func (m *memoryStorage) Exists(ctx context.Context, key string) bool {
63+
func (m *testingMemoryStorage) Exists(ctx context.Context, key string) bool {
6464
return m.lookup(ctx, key) != nil
6565
}
66-
func (m *memoryStorage) List(ctx context.Context, path string, recursive bool) ([]string, error) {
66+
func (m *testingMemoryStorage) List(ctx context.Context, path string, recursive bool) ([]string, error) {
6767
if recursive {
6868
panic("unimplemented")
6969
}
@@ -88,22 +88,22 @@ nextitem:
8888
}
8989
return result, nil
9090
}
91-
func (m *memoryStorage) Load(ctx context.Context, key string) ([]byte, error) {
91+
func (m *testingMemoryStorage) Load(ctx context.Context, key string) ([]byte, error) {
9292
if item := m.lookup(ctx, key); item != nil {
9393
return item.data, nil
9494
}
9595
return nil, fs.ErrNotExist
9696
}
97-
func (m *memoryStorage) Stat(ctx context.Context, key string) (KeyInfo, error) {
97+
func (m *testingMemoryStorage) Stat(ctx context.Context, key string) (KeyInfo, error) {
9898
if item := m.lookup(ctx, key); item != nil {
9999
return KeyInfo{Key: key, Size: int64(len(item.data))}, nil
100100
}
101101
return KeyInfo{}, fs.ErrNotExist
102102
}
103-
func (m *memoryStorage) Lock(ctx context.Context, name string) error { panic("unimplemented") }
104-
func (m *memoryStorage) Unlock(ctx context.Context, name string) error { panic("unimplemented") }
103+
func (m *testingMemoryStorage) Lock(ctx context.Context, name string) error { panic("unimplemented") }
104+
func (m *testingMemoryStorage) Unlock(ctx context.Context, name string) error { panic("unimplemented") }
105105

106-
var _ Storage = (*memoryStorage)(nil)
106+
var _ Storage = (*testingMemoryStorage)(nil)
107107

108108
type recordingStorage struct {
109109
Storage
@@ -293,7 +293,7 @@ func TestGetAccountAlreadyExistsSkipsBroken(t *testing.T) {
293293
am := &ACMEIssuer{CA: dummyCA, Logger: zap.NewNop(), mu: new(sync.Mutex)}
294294
testConfig := &Config{
295295
Issuers: []Issuer{am},
296-
Storage: &memoryStorage{},
296+
Storage: &testingMemoryStorage{},
297297
Logger: defaultTestLogger,
298298
certCache: new(Cache),
299299
}
@@ -342,7 +342,7 @@ func TestGetAccountWithEmailAlreadyExists(t *testing.T) {
342342
am := &ACMEIssuer{CA: dummyCA, Logger: zap.NewNop(), mu: new(sync.Mutex)}
343343
testConfig := &Config{
344344
Issuers: []Issuer{am},
345-
Storage: &recordingStorage{Storage: &memoryStorage{}},
345+
Storage: &recordingStorage{Storage: &testingMemoryStorage{}},
346346
Logger: defaultTestLogger,
347347
certCache: new(Cache),
348348
}

memorystorage.go

+186
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
// Copyright 2015 Matthew Holt
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package certmagic
16+
17+
import (
18+
"context"
19+
"os"
20+
"path"
21+
"strings"
22+
"sync"
23+
"time"
24+
25+
"golang.org/x/sync/semaphore"
26+
)
27+
28+
type storageEntry struct {
29+
i KeyInfo
30+
d []byte
31+
}
32+
33+
// memoryStorage is a Storage implemention that exists only in memory
34+
// it is intended for testing and one-time-deploys where no persistence is needed
35+
type memoryStorage struct {
36+
m map[string]storageEntry
37+
mu sync.RWMutex
38+
39+
kmu *keyMutex
40+
}
41+
42+
func NewMemoryStorage() Storage {
43+
return &memoryStorage{
44+
m: map[string]storageEntry{},
45+
kmu: newKeyMutex(),
46+
}
47+
}
48+
49+
// Exists returns true if key exists in s.
50+
func (s *memoryStorage) Exists(ctx context.Context, key string) bool {
51+
ans, err := s.List(ctx, key, true)
52+
if err != nil {
53+
return false
54+
}
55+
return len(ans) != 0
56+
}
57+
58+
// Store saves value at key.
59+
func (s *memoryStorage) Store(_ context.Context, key string, value []byte) error {
60+
s.m[key] = storageEntry{
61+
i: KeyInfo{
62+
Key: key,
63+
Modified: time.Now(),
64+
Size: int64(len(value)),
65+
IsTerminal: true,
66+
},
67+
d: value,
68+
}
69+
return nil
70+
}
71+
72+
// Load retrieves the value at key.
73+
func (s *memoryStorage) Load(_ context.Context, key string) ([]byte, error) {
74+
val, ok := s.m[key]
75+
if !ok {
76+
return nil, os.ErrNotExist
77+
}
78+
return val.d, nil
79+
}
80+
81+
// Delete deletes the value at key.
82+
func (s *memoryStorage) Delete(_ context.Context, key string) error {
83+
delete(s.m, key)
84+
return nil
85+
}
86+
87+
// List returns all keys that match prefix.
88+
func (s *memoryStorage) List(ctx context.Context, prefix string, recursive bool) ([]string, error) {
89+
var keyList []string
90+
91+
keys := make([]string, 0, len(s.m))
92+
for k := range s.m {
93+
if strings.HasPrefix(k, prefix) {
94+
keys = append(keys, k)
95+
}
96+
}
97+
// adapted from https://github.com/pberkel/caddy-storage-redis/blob/main/storage.go#L369
98+
// Iterate over each child key
99+
for _, k := range keys {
100+
// Directory keys will have a "/" suffix
101+
trimmedKey := strings.TrimSuffix(k, "/")
102+
// Reconstruct the full path of child key
103+
fullPathKey := path.Join(prefix, trimmedKey)
104+
// If current key is a directory
105+
if recursive && k != trimmedKey {
106+
// Recursively traverse all child directories
107+
childKeys, err := s.List(ctx, fullPathKey, recursive)
108+
if err != nil {
109+
return keyList, err
110+
}
111+
keyList = append(keyList, childKeys...)
112+
} else {
113+
keyList = append(keyList, fullPathKey)
114+
}
115+
}
116+
117+
return keys, nil
118+
}
119+
120+
// Stat returns information about key.
121+
func (s *memoryStorage) Stat(_ context.Context, key string) (KeyInfo, error) {
122+
val, ok := s.m[key]
123+
if !ok {
124+
return KeyInfo{}, os.ErrNotExist
125+
}
126+
return val.i, nil
127+
}
128+
129+
// Lock obtains a lock named by the given name. It blocks
130+
// until the lock can be obtained or an error is returned.
131+
func (s *memoryStorage) Lock(ctx context.Context, name string) error {
132+
return s.kmu.LockKey(ctx, name)
133+
}
134+
135+
// Unlock releases the lock for name.
136+
func (s *memoryStorage) Unlock(_ context.Context, name string) error {
137+
return s.kmu.UnlockKey(name)
138+
}
139+
140+
func (s *memoryStorage) String() string {
141+
return "memoryStorage"
142+
}
143+
144+
// Interface guard
145+
var _ Storage = (*memoryStorage)(nil)
146+
147+
type keyMutex struct {
148+
m map[string]*semaphore.Weighted
149+
mu sync.Mutex
150+
}
151+
152+
func newKeyMutex() *keyMutex {
153+
return &keyMutex{
154+
m: map[string]*semaphore.Weighted{},
155+
}
156+
}
157+
158+
func (km *keyMutex) LockKey(ctx context.Context, id string) error {
159+
select {
160+
case <-ctx.Done():
161+
// as a special case, caddy allows for the cancelled context to be used for a trylock.
162+
if km.mutex(id).TryAcquire(1) {
163+
return nil
164+
}
165+
return ctx.Err()
166+
default:
167+
return km.mutex(id).Acquire(ctx, 1)
168+
}
169+
}
170+
171+
// Releases the lock associated with the specified ID.
172+
func (km *keyMutex) UnlockKey(id string) error {
173+
km.mutex(id).Release(1)
174+
return nil
175+
}
176+
177+
func (km *keyMutex) mutex(id string) *semaphore.Weighted {
178+
km.mu.Lock()
179+
defer km.mu.Unlock()
180+
val, ok := km.m[id]
181+
if !ok {
182+
val = semaphore.NewWeighted(1)
183+
km.m[id] = val
184+
}
185+
return val
186+
}

memorystorage_test.go

+72
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package certmagic_test
2+
3+
import (
4+
"bytes"
5+
"context"
6+
"os"
7+
"testing"
8+
9+
"github.com/caddyserver/certmagic"
10+
"github.com/caddyserver/certmagic/internal/testutil"
11+
)
12+
13+
func TestMemoryStorageStoreLoad(t *testing.T) {
14+
ctx := context.Background()
15+
tmpDir, err := os.MkdirTemp(os.TempDir(), "certmagic*")
16+
testutil.RequireNoError(t, err, "allocating tmp dir")
17+
defer os.RemoveAll(tmpDir)
18+
s := certmagic.NewMemoryStorage()
19+
err = s.Store(ctx, "foo", []byte("bar"))
20+
testutil.RequireNoError(t, err)
21+
dat, err := s.Load(ctx, "foo")
22+
testutil.RequireNoError(t, err)
23+
testutil.RequireEqualValues(t, dat, []byte("bar"))
24+
}
25+
26+
func TestMemoryStorageStoreLoadRace(t *testing.T) {
27+
ctx := context.Background()
28+
tmpDir, err := os.MkdirTemp(os.TempDir(), "certmagic*")
29+
testutil.RequireNoError(t, err, "allocating tmp dir")
30+
defer os.RemoveAll(tmpDir)
31+
s := certmagic.NewMemoryStorage()
32+
a := bytes.Repeat([]byte("a"), 4096*1024)
33+
b := bytes.Repeat([]byte("b"), 4096*1024)
34+
err = s.Store(ctx, "foo", a)
35+
testutil.RequireNoError(t, err)
36+
done := make(chan struct{})
37+
go func() {
38+
err := s.Store(ctx, "foo", b)
39+
testutil.RequireNoError(t, err)
40+
close(done)
41+
}()
42+
dat, err := s.Load(ctx, "foo")
43+
<-done
44+
testutil.RequireNoError(t, err)
45+
testutil.RequireEqualValues(t, 4096*1024, len(dat))
46+
}
47+
48+
func TestMemoryStorageWriteLock(t *testing.T) {
49+
ctx := context.Background()
50+
tmpDir, err := os.MkdirTemp(os.TempDir(), "certmagic*")
51+
testutil.RequireNoError(t, err, "allocating tmp dir")
52+
defer os.RemoveAll(tmpDir)
53+
s := certmagic.NewMemoryStorage()
54+
// cctx is a cancelled ctx. so if we can't immediately get the lock, it will fail
55+
cctx, cn := context.WithCancel(ctx)
56+
cn()
57+
// should success
58+
err = s.Lock(cctx, "foo")
59+
testutil.RequireNoError(t, err)
60+
// should fail
61+
err = s.Lock(cctx, "foo")
62+
testutil.RequireError(t, err)
63+
64+
err = s.Unlock(cctx, "foo")
65+
testutil.RequireNoError(t, err)
66+
// shouldn't fail
67+
err = s.Lock(cctx, "foo")
68+
testutil.RequireNoError(t, err)
69+
70+
err = s.Unlock(cctx, "foo")
71+
testutil.RequireNoError(t, err)
72+
}

0 commit comments

Comments
 (0)