Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
C
caddy
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
nexedi
caddy
Commits
5f39cbef
Commit
5f39cbef
authored
Nov 06, 2017
by
Mohammad Gufran
Committed by
Matt Holt
Nov 06, 2017
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
caddytls: Extract locker into an interface (#1942)
parent
63fd2640
Changes
6
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
84 additions
and
63 deletions
+84
-63
caddytls/client.go
caddytls/client.go
+9
-4
caddytls/filestorage.go
caddytls/filestorage.go
+2
-36
caddytls/storage.go
caddytls/storage.go
+13
-18
caddytls/sync_locker.go
caddytls/sync_locker.go
+57
-0
caddytls/tls_test.go
caddytls/tls_test.go
+2
-3
caddytls/user_test.go
caddytls/user_test.go
+1
-2
No files found.
caddytls/client.go
View file @
5f39cbef
...
@@ -39,6 +39,7 @@ type ACMEClient struct {
...
@@ -39,6 +39,7 @@ type ACMEClient struct {
AllowPrompts
bool
AllowPrompts
bool
config
*
Config
config
*
Config
acmeClient
*
acme
.
Client
acmeClient
*
acme
.
Client
locker
Locker
}
}
// newACMEClient creates a new ACMEClient given an email and whether
// newACMEClient creates a new ACMEClient given an email and whether
...
@@ -120,6 +121,10 @@ var newACMEClient = func(config *Config, allowPrompts bool) (*ACMEClient, error)
...
@@ -120,6 +121,10 @@ var newACMEClient = func(config *Config, allowPrompts bool) (*ACMEClient, error)
AllowPrompts
:
allowPrompts
,
AllowPrompts
:
allowPrompts
,
config
:
config
,
config
:
config
,
acmeClient
:
client
,
acmeClient
:
client
,
locker
:
&
syncLock
{
nameLocks
:
make
(
map
[
string
]
*
sync
.
WaitGroup
),
nameLocksMu
:
sync
.
Mutex
{},
},
}
}
if
config
.
DNSProvider
==
""
{
if
config
.
DNSProvider
==
""
{
...
@@ -210,7 +215,7 @@ func (c *ACMEClient) Obtain(name string) error {
...
@@ -210,7 +215,7 @@ func (c *ACMEClient) Obtain(name string) error {
return
err
return
err
}
}
waiter
,
err
:=
storage
.
TryLock
(
name
)
waiter
,
err
:=
c
.
locker
.
TryLock
(
name
)
if
err
!=
nil
{
if
err
!=
nil
{
return
err
return
err
}
}
...
@@ -220,7 +225,7 @@ func (c *ACMEClient) Obtain(name string) error {
...
@@ -220,7 +225,7 @@ func (c *ACMEClient) Obtain(name string) error {
return
nil
// we assume the process with the lock succeeded, rather than hammering this execution path again
return
nil
// we assume the process with the lock succeeded, rather than hammering this execution path again
}
}
defer
func
()
{
defer
func
()
{
if
err
:=
storage
.
Unlock
(
name
);
err
!=
nil
{
if
err
:=
c
.
locker
.
Unlock
(
name
);
err
!=
nil
{
log
.
Printf
(
"[ERROR] Unable to unlock obtain call for %s: %v"
,
name
,
err
)
log
.
Printf
(
"[ERROR] Unable to unlock obtain call for %s: %v"
,
name
,
err
)
}
}
}()
}()
...
@@ -286,7 +291,7 @@ func (c *ACMEClient) Renew(name string) error {
...
@@ -286,7 +291,7 @@ func (c *ACMEClient) Renew(name string) error {
return
err
return
err
}
}
waiter
,
err
:=
storage
.
TryLock
(
name
)
waiter
,
err
:=
c
.
locker
.
TryLock
(
name
)
if
err
!=
nil
{
if
err
!=
nil
{
return
err
return
err
}
}
...
@@ -296,7 +301,7 @@ func (c *ACMEClient) Renew(name string) error {
...
@@ -296,7 +301,7 @@ func (c *ACMEClient) Renew(name string) error {
return
nil
// we assume the process with the lock succeeded, rather than hammering this execution path again
return
nil
// we assume the process with the lock succeeded, rather than hammering this execution path again
}
}
defer
func
()
{
defer
func
()
{
if
err
:=
storage
.
Unlock
(
name
);
err
!=
nil
{
if
err
:=
c
.
locker
.
Unlock
(
name
);
err
!=
nil
{
log
.
Printf
(
"[ERROR] Unable to unlock renew call for %s: %v"
,
name
,
err
)
log
.
Printf
(
"[ERROR] Unable to unlock renew call for %s: %v"
,
name
,
err
)
}
}
}()
}()
...
...
caddytls/filestorage.go
View file @
5f39cbef
...
@@ -22,7 +22,6 @@ import (
...
@@ -22,7 +22,6 @@ import (
"os"
"os"
"path/filepath"
"path/filepath"
"strings"
"strings"
"sync"
"github.com/mholt/caddy"
"github.com/mholt/caddy"
)
)
...
@@ -40,8 +39,7 @@ var storageBasePath = filepath.Join(caddy.AssetsPath(), "acme")
...
@@ -40,8 +39,7 @@ var storageBasePath = filepath.Join(caddy.AssetsPath(), "acme")
// instance is guaranteed to be non-nil if there is no error.
// instance is guaranteed to be non-nil if there is no error.
func
NewFileStorage
(
caURL
*
url
.
URL
)
(
Storage
,
error
)
{
func
NewFileStorage
(
caURL
*
url
.
URL
)
(
Storage
,
error
)
{
return
&
FileStorage
{
return
&
FileStorage
{
Path
:
filepath
.
Join
(
storageBasePath
,
caURL
.
Host
),
Path
:
filepath
.
Join
(
storageBasePath
,
caURL
.
Host
),
nameLocks
:
make
(
map
[
string
]
*
sync
.
WaitGroup
),
},
nil
},
nil
}
}
...
@@ -49,9 +47,7 @@ func NewFileStorage(caURL *url.URL) (Storage, error) {
...
@@ -49,9 +47,7 @@ func NewFileStorage(caURL *url.URL) (Storage, error) {
// directory. It is used to get file paths in a consistent,
// directory. It is used to get file paths in a consistent,
// cross-platform way or persisting ACME assets on the file system.
// cross-platform way or persisting ACME assets on the file system.
type
FileStorage
struct
{
type
FileStorage
struct
{
Path
string
Path
string
nameLocks
map
[
string
]
*
sync
.
WaitGroup
nameLocksMu
sync
.
Mutex
}
}
// sites gets the directory that stores site certificate and keys.
// sites gets the directory that stores site certificate and keys.
...
@@ -254,36 +250,6 @@ func (s *FileStorage) StoreUser(email string, data *UserData) error {
...
@@ -254,36 +250,6 @@ func (s *FileStorage) StoreUser(email string, data *UserData) error {
return
nil
return
nil
}
}
// TryLock attempts to get a lock for name, otherwise it returns
// a Waiter value to wait until the other process is finished.
func
(
s
*
FileStorage
)
TryLock
(
name
string
)
(
Waiter
,
error
)
{
s
.
nameLocksMu
.
Lock
()
defer
s
.
nameLocksMu
.
Unlock
()
wg
,
ok
:=
s
.
nameLocks
[
name
]
if
ok
{
// lock already obtained, let caller wait on it
return
wg
,
nil
}
// caller gets lock
wg
=
new
(
sync
.
WaitGroup
)
wg
.
Add
(
1
)
s
.
nameLocks
[
name
]
=
wg
return
nil
,
nil
}
// Unlock unlocks name.
func
(
s
*
FileStorage
)
Unlock
(
name
string
)
error
{
s
.
nameLocksMu
.
Lock
()
defer
s
.
nameLocksMu
.
Unlock
()
wg
,
ok
:=
s
.
nameLocks
[
name
]
if
!
ok
{
return
fmt
.
Errorf
(
"FileStorage: no lock to release for %s"
,
name
)
}
wg
.
Done
()
delete
(
s
.
nameLocks
,
name
)
return
nil
}
// MostRecentUserEmail implements Storage.MostRecentUserEmail by finding the
// MostRecentUserEmail implements Storage.MostRecentUserEmail by finding the
// most recently written sub directory in the users' directory. It is named
// most recently written sub directory in the users' directory. It is named
// after the email address. This corresponds to the most recent call to
// after the email address. This corresponds to the most recent call to
...
...
caddytls/storage.go
View file @
5f39cbef
...
@@ -39,24 +39,9 @@ type UserData struct {
...
@@ -39,24 +39,9 @@ type UserData struct {
Key
[]
byte
Key
[]
byte
}
}
// Storage is an interface abstracting all storage used by Caddy's TLS
// Locker provides support for mutual exclusion
// subsystem. Implementations of this interface store both site and
type
Locker
interface
{
// user data.
// TryLock will return immediatedly with or without acquiring the lock.
type
Storage
interface
{
// SiteExists returns true if this site exists in storage.
// Site data is considered present when StoreSite has been called
// successfully (without DeleteSite having been called, of course).
SiteExists
(
domain
string
)
(
bool
,
error
)
// TryLock is called before Caddy attempts to obtain or renew a
// certificate for a certain name and store it. From the perspective
// of this method and its companion Unlock, the actions of
// obtaining/renewing and then storing the certificate are atomic,
// and both should occur within a lock. This prevents multiple
// processes -- maybe distributed ones -- from stepping on each
// other's space in the same shared storage, and from spamming
// certificate providers with multiple, redundant requests.
//
// If a lock could be obtained, (nil, nil) is returned and you may
// If a lock could be obtained, (nil, nil) is returned and you may
// continue normally. If not (meaning another process is already
// continue normally. If not (meaning another process is already
// working on that name), a Waiter value will be returned upon
// working on that name), a Waiter value will be returned upon
...
@@ -75,6 +60,16 @@ type Storage interface {
...
@@ -75,6 +60,16 @@ type Storage interface {
// the obtain/renew and store are finished, even if there was
// the obtain/renew and store are finished, even if there was
// an error (or a timeout).
// an error (or a timeout).
Unlock
(
name
string
)
error
Unlock
(
name
string
)
error
}
// Storage is an interface abstracting all storage used by Caddy's TLS
// subsystem. Implementations of this interface store both site and
// user data.
type
Storage
interface
{
// SiteExists returns true if this site exists in storage.
// Site data is considered present when StoreSite has been called
// successfully (without DeleteSite having been called, of course).
SiteExists
(
domain
string
)
(
bool
,
error
)
// LoadSite obtains the site data from storage for the given domain and
// LoadSite obtains the site data from storage for the given domain and
// returns it. If data for the domain does not exist, an error value
// returns it. If data for the domain does not exist, an error value
...
...
caddytls/sync_locker.go
0 → 100644
View file @
5f39cbef
// Copyright 2015 Light Code Labs, LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package
caddytls
import
(
"fmt"
"sync"
)
var
_
Locker
=
&
syncLock
{}
type
syncLock
struct
{
nameLocks
map
[
string
]
*
sync
.
WaitGroup
nameLocksMu
sync
.
Mutex
}
// TryLock attempts to get a lock for name, otherwise it returns
// a Waiter value to wait until the other process is finished.
func
(
s
*
syncLock
)
TryLock
(
name
string
)
(
Waiter
,
error
)
{
s
.
nameLocksMu
.
Lock
()
defer
s
.
nameLocksMu
.
Unlock
()
wg
,
ok
:=
s
.
nameLocks
[
name
]
if
ok
{
// lock already obtained, let caller wait on it
return
wg
,
nil
}
// caller gets lock
wg
=
new
(
sync
.
WaitGroup
)
wg
.
Add
(
1
)
s
.
nameLocks
[
name
]
=
wg
return
nil
,
nil
}
// Unlock unlocks name.
func
(
s
*
syncLock
)
Unlock
(
name
string
)
error
{
s
.
nameLocksMu
.
Lock
()
defer
s
.
nameLocksMu
.
Unlock
()
wg
,
ok
:=
s
.
nameLocks
[
name
]
if
!
ok
{
return
fmt
.
Errorf
(
"FileStorage: no lock to release for %s"
,
name
)
}
wg
.
Done
()
delete
(
s
.
nameLocks
,
name
)
return
nil
}
caddytls/tls_test.go
View file @
5f39cbef
...
@@ -16,7 +16,6 @@ package caddytls
...
@@ -16,7 +16,6 @@ package caddytls
import
(
import
(
"os"
"os"
"sync"
"testing"
"testing"
"github.com/xenolf/lego/acme"
"github.com/xenolf/lego/acme"
...
@@ -94,7 +93,7 @@ func TestQualifiesForManagedTLS(t *testing.T) {
...
@@ -94,7 +93,7 @@ func TestQualifiesForManagedTLS(t *testing.T) {
}
}
func
TestSaveCertResource
(
t
*
testing
.
T
)
{
func
TestSaveCertResource
(
t
*
testing
.
T
)
{
storage
:=
&
FileStorage
{
Path
:
"./le_test_save"
,
nameLocks
:
make
(
map
[
string
]
*
sync
.
WaitGroup
)
}
storage
:=
&
FileStorage
{
Path
:
"./le_test_save"
}
defer
func
()
{
defer
func
()
{
err
:=
os
.
RemoveAll
(
storage
.
Path
)
err
:=
os
.
RemoveAll
(
storage
.
Path
)
if
err
!=
nil
{
if
err
!=
nil
{
...
@@ -140,7 +139,7 @@ func TestSaveCertResource(t *testing.T) {
...
@@ -140,7 +139,7 @@ func TestSaveCertResource(t *testing.T) {
}
}
func
TestExistingCertAndKey
(
t
*
testing
.
T
)
{
func
TestExistingCertAndKey
(
t
*
testing
.
T
)
{
storage
:=
&
FileStorage
{
Path
:
"./le_test_existing"
,
nameLocks
:
make
(
map
[
string
]
*
sync
.
WaitGroup
)
}
storage
:=
&
FileStorage
{
Path
:
"./le_test_existing"
}
defer
func
()
{
defer
func
()
{
err
:=
os
.
RemoveAll
(
storage
.
Path
)
err
:=
os
.
RemoveAll
(
storage
.
Path
)
if
err
!=
nil
{
if
err
!=
nil
{
...
...
caddytls/user_test.go
View file @
5f39cbef
...
@@ -21,7 +21,6 @@ import (
...
@@ -21,7 +21,6 @@ import (
"crypto/rand"
"crypto/rand"
"io"
"io"
"strings"
"strings"
"sync"
"testing"
"testing"
"time"
"time"
...
@@ -196,7 +195,7 @@ func TestGetEmail(t *testing.T) {
...
@@ -196,7 +195,7 @@ func TestGetEmail(t *testing.T) {
}
}
}
}
var
testStorage
=
&
FileStorage
{
Path
:
"./testdata"
,
nameLocks
:
make
(
map
[
string
]
*
sync
.
WaitGroup
)
}
var
testStorage
=
&
FileStorage
{
Path
:
"./testdata"
}
func
(
s
*
FileStorage
)
clean
()
error
{
func
(
s
*
FileStorage
)
clean
()
error
{
return
os
.
RemoveAll
(
s
.
Path
)
return
os
.
RemoveAll
(
s
.
Path
)
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment