Skip to content

Commit

Permalink
Merge pull request runatlantis#42 from hootsuite/locking
Browse files Browse the repository at this point in the history
Include more data with locks like pull link and user
  • Loading branch information
anubhavmishra authored Jun 23, 2017
2 parents d50d897 + f7d088f commit d912514
Show file tree
Hide file tree
Showing 9 changed files with 159 additions and 99 deletions.
22 changes: 7 additions & 15 deletions locking/boltdb/boltdb.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,17 +48,11 @@ func NewWithDB(db *bolt.DB, bucket string) (*Backend, error) {
return &Backend{db, []byte(bucket)}, nil
}

func (b *Backend) TryLock(project models.Project, env string, pullNum int) (bool, int, error) {
func (b *Backend) TryLock(newLock models.ProjectLock) (bool, models.ProjectLock, error) {
// return variables
var lockAcquired bool
var lockingPullNum int
key := b.key(project, env)
newLock := models.ProjectLock{
PullNum: pullNum,
Project: project,
Time: time.Now(),
Env: env,
}
var currLock models.ProjectLock
key := b.key(newLock.Project, newLock.Env)
newLockSerialized, _ := json.Marshal(newLock)
transactionErr := b.db.Update(func(tx *bolt.Tx) error {
bucket := tx.Bucket(b.bucket)
Expand All @@ -68,25 +62,23 @@ func (b *Backend) TryLock(project models.Project, env string, pullNum int) (bool
if currLockSerialized == nil {
bucket.Put([]byte(key), newLockSerialized) // not a readonly bucketName so okay to ignore error
lockAcquired = true
lockingPullNum = pullNum
currLock = newLock
return nil
}

// otherwise the lock fails, return to caller the run that's holding the lock
var currLock models.ProjectLock
if err := json.Unmarshal(currLockSerialized, &currLock); err != nil {
return errors.Wrap(err, "failed to deserialize current lock")
}
lockAcquired = false
lockingPullNum = currLock.PullNum
return nil
})

if transactionErr != nil {
return false, lockingPullNum, errors.Wrap(transactionErr, "DB transaction failed")
return false, currLock, errors.Wrap(transactionErr, "DB transaction failed")
}

return lockAcquired, lockingPullNum, nil
return lockAcquired, currLock, nil
}

func (b Backend) Unlock(p models.Project, env string) error {
Expand Down Expand Up @@ -137,7 +129,7 @@ func (b Backend) UnlockByPull(repoFullName string, pullNum int) error {
if err := json.Unmarshal(v, &lock); err != nil {
return errors.Wrapf(err, "failed to deserialize lock at key %q", string(k))
}
if lock.PullNum == pullNum {
if lock.Pull.Num == pullNum {
locks = append(locks, lock)
}
}
Expand Down
101 changes: 63 additions & 38 deletions locking/boltdb/boltdb_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,24 @@ import (

"github.com/hootsuite/atlantis/locking/boltdb"
"github.com/hootsuite/atlantis/models"
"time"
)

var lockBucket = "bucket"
var project = models.NewProject("owner/repo", "parent/child")
var env = "default"
var pullNum = 1
var lock = models.ProjectLock{
Pull: models.PullRequest{
Num: pullNum,
},
User: models.User{
Username: "lkysow",
},
Env: env,
Project: project,
Time: time.Now(),
}

func TestListNoLocks(t *testing.T) {
t.Log("listing locks when there are none should return an empty list")
Expand All @@ -31,7 +43,7 @@ func TestListOneLock(t *testing.T) {
t.Log("listing locks when there is one should return it")
db, b := newTestDB()
defer cleanupDB(db)
_, _, err := b.TryLock(project, env, pullNum)
_, _, err := b.TryLock(lock)
Ok(t, err)
ls, err := b.List()
Ok(t, err)
Expand All @@ -52,7 +64,9 @@ func TestListMultipleLocks(t *testing.T) {
}

for _, r := range repos {
_, _, err := b.TryLock(models.NewProject(r, "path"), env, pullNum)
newLock := lock
newLock.Project = models.NewProject(r, "path")
_, _, err := b.TryLock(newLock)
Ok(t, err)
}
ls, err := b.List()
Expand All @@ -73,7 +87,7 @@ func TestListAddRemove(t *testing.T) {
t.Log("listing after adding and removing should return none")
db, b := newTestDB()
defer cleanupDB(db)
_, _, err := b.TryLock(project, env, pullNum)
_, _, err := b.TryLock(lock)
Ok(t, err)
b.Unlock(project, env)

Expand All @@ -86,50 +100,57 @@ func TestLockingNoLocks(t *testing.T) {
t.Log("with no locks yet, lock should succeed")
db, b := newTestDB()
defer cleanupDB(db)
acquired, lockingPullNum, err := b.TryLock(project, env, pullNum)
acquired, currLock, err := b.TryLock(lock)
Ok(t, err)
Equals(t, true, acquired)
Equals(t, pullNum, lockingPullNum)
Equals(t, lock, currLock)
}

func TestLockingExistingLock(t *testing.T) {
t.Log("if there is an existing lock, lock should...")
db, b := newTestDB()
defer cleanupDB(db)
_, _, err := b.TryLock(project, env, pullNum)
_, _, err := b.TryLock(lock)
Ok(t, err)

t.Log("...succeed if the new project has a different path")
{
acquired, lockingPull, err := b.TryLock(models.NewProject(project.RepoFullName, "different/path"), env, pullNum)
newLock := lock
newLock.Project = models.NewProject(project.RepoFullName, "different/path")
acquired, currLock, err := b.TryLock(newLock)
Ok(t, err)
Equals(t, true, acquired)
Equals(t, pullNum, lockingPull)
Equals(t, pullNum, currLock.Pull.Num)
}

t.Log("...succeed if the new project has a different environment")
{
acquired, lockingPull, err := b.TryLock(project, "different-env", pullNum)
newLock := lock
newLock.Env = "different-env"
acquired, currLock, err := b.TryLock(newLock)
Ok(t, err)
Equals(t, true, acquired)
Equals(t, pullNum, lockingPull)
Equals(t, newLock, currLock)
}

t.Log("...succeed if the new project has a different repoName")
{
acquired, lockingPull, err := b.TryLock(models.NewProject("different/repo", project.Path), env, pullNum)
newLock := lock
newLock.Project = models.NewProject("different/repo", project.Path)
acquired, currLock, err := b.TryLock(newLock)
Ok(t, err)
Equals(t, true, acquired)
Equals(t, pullNum, lockingPull)
Equals(t, newLock, currLock)
}

t.Log("...not succeed if the new project only has a different pullNum")
{
newPull := pullNum + 1
acquired, lockingPull, err := b.TryLock(project, env, newPull)
newLock := lock
newLock.Pull.Num = lock.Pull.Num + 1
acquired, currLock, err := b.TryLock(newLock)
Ok(t, err)
Equals(t, false, acquired)
Equals(t, lockingPull, pullNum)
Equals(t, currLock.Pull.Num, pullNum)
}
}

Expand All @@ -146,7 +167,7 @@ func TestUnlocking(t *testing.T) {
db, b := newTestDB()
defer cleanupDB(db)

b.TryLock(project, env, pullNum)
b.TryLock(lock)
Ok(t, b.Unlock(project, env))

// should be no locks listed
Expand All @@ -155,35 +176,37 @@ func TestUnlocking(t *testing.T) {
Equals(t, 0, len(ls))

// should be able to re-lock that repo with a new pull num
newPull := pullNum + 1
acquired, lockingPull, err := b.TryLock(project, env, newPull)
newLock := lock
newLock.Pull.Num = lock.Pull.Num + 1
acquired, currLock, err := b.TryLock(newLock)
Ok(t, err)
Equals(t, true, acquired)
Equals(t, newPull, lockingPull)
Equals(t, newLock, currLock)
}

func TestUnlockingMultiple(t *testing.T) {
t.Log("unlocking and locking multiple locks should succeed")
db, b := newTestDB()
defer cleanupDB(db)

b.TryLock(project, env, pullNum)
b.TryLock(lock)

new := project
new.RepoFullName = "new/repo"
b.TryLock(project, env, pullNum)
new := lock
new.Project.RepoFullName = "new/repo"
b.TryLock(new)

new2 := project
new2.Path = "new/path"
b.TryLock(project, env, pullNum)
new2 := lock
new2.Project.Path = "new/path"
b.TryLock(new2)

new3Env := "new-env"
b.TryLock(project, new3Env, pullNum)
new3 := lock
new3.Env = "new-env"
b.TryLock(new3)

// now try and unlock them
Ok(t, b.Unlock(project, new3Env))
Ok(t, b.Unlock(new2, env))
Ok(t, b.Unlock(new, env))
Ok(t, b.Unlock(new3.Project, new3.Env))
Ok(t, b.Unlock(new2.Project, env))
Ok(t, b.Unlock(new.Project, env))
Ok(t, b.Unlock(project, env))

// should be none left
Expand All @@ -205,7 +228,7 @@ func TestUnlockByPullOne(t *testing.T) {
t.Log("with one lock, UnlockByPull should...")
db, b := newTestDB()
defer cleanupDB(db)
_, _, err := b.TryLock(project, env, pullNum)
_, _, err := b.TryLock(lock)
Ok(t, err)

t.Log("...delete nothing when its the same repo but a different pull")
Expand Down Expand Up @@ -238,7 +261,7 @@ func TestUnlockByPullAfterUnlock(t *testing.T) {
t.Log("after locking and unlocking, UnlockByPull should be successful")
db, b := newTestDB()
defer cleanupDB(db)
_, _, err := b.TryLock(project, env, pullNum)
_, _, err := b.TryLock(lock)
Ok(t, err)
Ok(t, b.Unlock(project, env))

Expand All @@ -253,15 +276,17 @@ func TestUnlockByPullMatching(t *testing.T) {
t.Log("UnlockByPull should delete all locks in that repo and pull num")
db, b := newTestDB()
defer cleanupDB(db)
_, _, err := b.TryLock(project, env, pullNum)
_, _, err := b.TryLock(lock)
Ok(t, err)

// add additional locks with the same repo and pull num but different paths/envs
new := project
new.Path = "dif/path"
_, _, err = b.TryLock(new, env, pullNum)
new := lock
new.Project.Path = "dif/path"
_, _, err = b.TryLock(new)
Ok(t, err)
_, _, err = b.TryLock(project, "new-env", pullNum)
new2 := lock
new2.Env = "new-env"
_, _, err = b.TryLock(new2)
Ok(t, err)

// there should now be 3
Expand Down
Loading

0 comments on commit d912514

Please sign in to comment.