Skip to content

Commit b1ade9c

Browse files
authored
Merge pull request #2314 from nebril/lock-release
Releases are locked to avoid parallel changes
2 parents 6e63a54 + ec92b76 commit b1ade9c

File tree

3 files changed

+102
-1
lines changed

3 files changed

+102
-1
lines changed

pkg/storage/storage.go

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package storage // import "k8s.io/helm/pkg/storage"
1919
import (
2020
"fmt"
2121
"log"
22+
"sync"
2223

2324
rspb "k8s.io/helm/pkg/proto/hapi/release"
2425
relutil "k8s.io/helm/pkg/releaseutil"
@@ -28,6 +29,11 @@ import (
2829
// Storage represents a storage engine for a Release.
2930
type Storage struct {
3031
driver.Driver
32+
33+
// releaseLocks are for locking releases to make sure that only one operation at a time is executed on each release
34+
releaseLocks map[string]*sync.Mutex
35+
// releaseLocksLock is a mutex for accessing releaseLocks
36+
releaseLocksLock *sync.Mutex
3137
}
3238

3339
// Get retrieves the release from storage. An error is returned
@@ -153,6 +159,51 @@ func (s *Storage) Last(name string) (*rspb.Release, error) {
153159
return h[0], nil
154160
}
155161

162+
// LockRelease gains a mutually exclusive access to a release via a mutex.
163+
func (s *Storage) LockRelease(name string) error {
164+
s.releaseLocksLock.Lock()
165+
defer s.releaseLocksLock.Unlock()
166+
167+
var lock *sync.Mutex
168+
lock, exists := s.releaseLocks[name]
169+
170+
if !exists {
171+
releases, err := s.ListReleases()
172+
if err != nil {
173+
return err
174+
}
175+
176+
found := false
177+
for _, release := range releases {
178+
if release.Name == name {
179+
found = true
180+
}
181+
}
182+
if !found {
183+
return fmt.Errorf("Unable to lock release %s: release not found", name)
184+
}
185+
186+
lock = &sync.Mutex{}
187+
s.releaseLocks[name] = lock
188+
}
189+
lock.Lock()
190+
return nil
191+
}
192+
193+
// UnlockRelease releases a mutually exclusive access to a release.
194+
// If release doesn't exist or wasn't previously locked - the unlock will pass
195+
func (s *Storage) UnlockRelease(name string) {
196+
s.releaseLocksLock.Lock()
197+
defer s.releaseLocksLock.Unlock()
198+
199+
var lock *sync.Mutex
200+
lock, exists := s.releaseLocks[name]
201+
if !exists {
202+
return
203+
}
204+
lock.Unlock()
205+
}
206+
156207
// makeKey concatenates a release name and version into
157208
// a string with format ```<release_name>#v<version>```.
158209
// This key is used to uniquely identify storage objects.
@@ -167,5 +218,9 @@ func Init(d driver.Driver) *Storage {
167218
if d == nil {
168219
d = driver.NewMemory()
169220
}
170-
return &Storage{Driver: d}
221+
return &Storage{
222+
Driver: d,
223+
releaseLocks: make(map[string]*sync.Mutex),
224+
releaseLocksLock: &sync.Mutex{},
225+
}
171226
}

pkg/storage/storage_test.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,3 +272,31 @@ func assertErrNil(eh func(args ...interface{}), err error, message string) {
272272
eh(fmt.Sprintf("%s: %q", message, err))
273273
}
274274
}
275+
276+
func TestReleaseLocksNotExist(t *testing.T) {
277+
s := Init(driver.NewMemory())
278+
279+
err := s.LockRelease("no-such-release")
280+
281+
if err == nil {
282+
t.Errorf("Exptected error when trying to lock non-existing release, got nil")
283+
}
284+
}
285+
286+
func TestReleaseLocks(t *testing.T) {
287+
s := Init(driver.NewMemory())
288+
289+
releaseName := "angry-beaver"
290+
rls := ReleaseTestData{
291+
Name: releaseName,
292+
Version: 1,
293+
}.ToRelease()
294+
295+
s.Create(rls)
296+
297+
err := s.LockRelease(releaseName)
298+
if err != nil {
299+
t.Errorf("Exptected nil err when locking existing release")
300+
}
301+
s.UnlockRelease(releaseName)
302+
}

pkg/tiller/release_server.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,12 @@ func (s *ReleaseServer) GetReleaseContent(c ctx.Context, req *services.GetReleas
283283

284284
// UpdateRelease takes an existing release and new information, and upgrades the release.
285285
func (s *ReleaseServer) UpdateRelease(c ctx.Context, req *services.UpdateReleaseRequest) (*services.UpdateReleaseResponse, error) {
286+
err := s.env.Releases.LockRelease(req.Name)
287+
if err != nil {
288+
return nil, err
289+
}
290+
defer s.env.Releases.UnlockRelease(req.Name)
291+
286292
currentRelease, updatedRelease, err := s.prepareUpdate(req)
287293
if err != nil {
288294
return nil, err
@@ -465,6 +471,12 @@ func (s *ReleaseServer) prepareUpdate(req *services.UpdateReleaseRequest) (*rele
465471

466472
// RollbackRelease rolls back to a previous version of the given release.
467473
func (s *ReleaseServer) RollbackRelease(c ctx.Context, req *services.RollbackReleaseRequest) (*services.RollbackReleaseResponse, error) {
474+
err := s.env.Releases.LockRelease(req.Name)
475+
if err != nil {
476+
return nil, err
477+
}
478+
defer s.env.Releases.UnlockRelease(req.Name)
479+
468480
currentRelease, targetRelease, err := s.prepareRollback(req)
469481
if err != nil {
470482
return nil, err
@@ -983,6 +995,12 @@ func (s *ReleaseServer) purgeReleases(rels ...*release.Release) error {
983995

984996
// UninstallRelease deletes all of the resources associated with this release, and marks the release DELETED.
985997
func (s *ReleaseServer) UninstallRelease(c ctx.Context, req *services.UninstallReleaseRequest) (*services.UninstallReleaseResponse, error) {
998+
err := s.env.Releases.LockRelease(req.Name)
999+
if err != nil {
1000+
return nil, err
1001+
}
1002+
defer s.env.Releases.UnlockRelease(req.Name)
1003+
9861004
if !ValidName.MatchString(req.Name) {
9871005
log.Printf("uninstall: Release not found: %s", req.Name)
9881006
return nil, errMissingRelease

0 commit comments

Comments
 (0)