mirror of https://github.com/databricks/cli.git
169 lines
5.5 KiB
Go
169 lines
5.5 KiB
Go
package internal
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"math/rand"
|
|
"os"
|
|
"os/exec"
|
|
"path"
|
|
"path/filepath"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/databricks/bricks/bundle/deployer"
|
|
"github.com/databricks/databricks-sdk-go"
|
|
"github.com/databricks/databricks-sdk-go/service/repos"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// TODO: create a utility function to create an empty test repo for tests and refactor sync_test integration test
|
|
|
|
const EmptyRepoUrl = "https://github.com/shreyas-goenka/empty-repo.git"
|
|
|
|
func createRemoteTestProject(t *testing.T, projectNamePrefix string, wsc *databricks.WorkspaceClient) string {
|
|
ctx := context.TODO()
|
|
me, err := wsc.CurrentUser.Me(ctx)
|
|
assert.NoError(t, err)
|
|
|
|
remoteProjectRoot := fmt.Sprintf("/Repos/%s/%s", me.UserName, RandomName(projectNamePrefix))
|
|
repoInfo, err := wsc.Repos.Create(ctx, repos.CreateRepo{
|
|
Path: remoteProjectRoot,
|
|
Url: EmptyRepoUrl,
|
|
Provider: "gitHub",
|
|
})
|
|
assert.NoError(t, err)
|
|
t.Cleanup(func() {
|
|
err := wsc.Repos.DeleteByRepoId(ctx, repoInfo.Id)
|
|
assert.NoError(t, err)
|
|
})
|
|
|
|
return remoteProjectRoot
|
|
}
|
|
|
|
func createLocalTestProject(t *testing.T) string {
|
|
tempDir := t.TempDir()
|
|
|
|
cmd := exec.Command("git", "clone", EmptyRepoUrl)
|
|
cmd.Dir = tempDir
|
|
err := cmd.Run()
|
|
assert.NoError(t, err)
|
|
|
|
localProjectRoot := filepath.Join(tempDir, "empty-repo")
|
|
err = os.Chdir(localProjectRoot)
|
|
assert.NoError(t, err)
|
|
return localProjectRoot
|
|
}
|
|
|
|
func TestAccLock(t *testing.T) {
|
|
t.Log(GetEnvOrSkipTest(t, "CLOUD_ENV"))
|
|
ctx := context.TODO()
|
|
wsc := databricks.Must(databricks.NewWorkspaceClient())
|
|
createLocalTestProject(t)
|
|
remoteProjectRoot := createRemoteTestProject(t, "lock-acc-", wsc)
|
|
|
|
// 50 lockers try to acquire a lock at the same time
|
|
numConcurrentLocks := 50
|
|
|
|
var err error
|
|
lockerErrs := make([]error, numConcurrentLocks)
|
|
lockers := make([]*deployer.Locker, numConcurrentLocks)
|
|
|
|
for i := 0; i < numConcurrentLocks; i++ {
|
|
lockers[i] = deployer.CreateLocker("humpty.dumpty@databricks.com", remoteProjectRoot)
|
|
}
|
|
|
|
var wg sync.WaitGroup
|
|
for i := 0; i < numConcurrentLocks; i++ {
|
|
wg.Add(1)
|
|
currentIndex := i
|
|
go func() {
|
|
defer wg.Done()
|
|
time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond)
|
|
lockerErrs[currentIndex] = lockers[currentIndex].Lock(ctx, wsc, false)
|
|
}()
|
|
}
|
|
wg.Wait()
|
|
|
|
countActive := 0
|
|
indexOfActiveLocker := 0
|
|
indexOfAnInactiveLocker := -1
|
|
for i := 0; i < numConcurrentLocks; i++ {
|
|
if lockers[i].Active {
|
|
countActive += 1
|
|
assert.NoError(t, lockerErrs[i])
|
|
indexOfActiveLocker = i
|
|
} else {
|
|
if indexOfAnInactiveLocker == -1 {
|
|
indexOfAnInactiveLocker = i
|
|
}
|
|
assert.ErrorContains(t, lockerErrs[i], "lock acquired by")
|
|
assert.ErrorContains(t, lockerErrs[i], "Use --force to override")
|
|
}
|
|
}
|
|
assert.Equal(t, 1, countActive, "Exactly one locker should successfull acquire the lock")
|
|
|
|
// test remote lock matches active lock
|
|
remoteLocker, err := deployer.GetActiveLockState(ctx, wsc, lockers[indexOfActiveLocker].RemotePath())
|
|
require.NoError(t, err)
|
|
assert.Equal(t, remoteLocker.ID, lockers[indexOfActiveLocker].State.ID, "remote locker id does not match active locker")
|
|
assert.True(t, remoteLocker.AcquisitionTime.Equal(lockers[indexOfActiveLocker].State.AcquisitionTime), "remote locker acquisition time does not match active locker")
|
|
|
|
// test all other locks (inactive ones) do not match the remote lock and Unlock fails
|
|
for i := 0; i < numConcurrentLocks; i++ {
|
|
if i == indexOfActiveLocker {
|
|
continue
|
|
}
|
|
assert.NotEqual(t, remoteLocker.ID, lockers[i].State.ID)
|
|
err := lockers[i].Unlock(ctx, wsc)
|
|
assert.ErrorContains(t, err, "unlock called when lock is not held")
|
|
}
|
|
|
|
// test inactive locks fail to write a file
|
|
for i := 0; i < numConcurrentLocks; i++ {
|
|
if i == indexOfActiveLocker {
|
|
continue
|
|
}
|
|
err := lockers[i].PutFile(ctx, wsc, path.Join(remoteProjectRoot, "foo.json"), []byte(`'{"surname":"Khan", "name":"Shah Rukh"}`))
|
|
assert.ErrorContains(t, err, "failed to put file. deploy lock not held")
|
|
}
|
|
|
|
// active locker file write succeeds
|
|
err = lockers[indexOfActiveLocker].PutFile(ctx, wsc, path.Join(remoteProjectRoot, "foo.json"), []byte(`{"surname":"Khan", "name":"Shah Rukh"}`))
|
|
assert.NoError(t, err)
|
|
|
|
// active locker file read succeeds with expected results
|
|
bytes, err := lockers[indexOfActiveLocker].GetRawJsonFileContent(ctx, wsc, path.Join(remoteProjectRoot, "foo.json"))
|
|
var res map[string]string
|
|
json.Unmarshal(bytes, &res)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, "Khan", res["surname"])
|
|
assert.Equal(t, "Shah Rukh", res["name"])
|
|
|
|
// inactive locker file reads fail
|
|
for i := 0; i < numConcurrentLocks; i++ {
|
|
if i == indexOfActiveLocker {
|
|
continue
|
|
}
|
|
_, err = lockers[i].GetRawJsonFileContent(ctx, wsc, path.Join(remoteProjectRoot, "foo.json"))
|
|
assert.ErrorContains(t, err, "failed to get file. deploy lock not held")
|
|
}
|
|
|
|
// Unlock active lock and check it becomes inactive
|
|
err = lockers[indexOfActiveLocker].Unlock(ctx, wsc)
|
|
assert.NoError(t, err)
|
|
remoteLocker, err = deployer.GetActiveLockState(ctx, wsc, lockers[indexOfActiveLocker].RemotePath())
|
|
assert.ErrorContains(t, err, "File not found.", "remote lock file not deleted on unlock")
|
|
assert.Nil(t, remoteLocker)
|
|
assert.False(t, lockers[indexOfActiveLocker].Active)
|
|
|
|
// A locker that failed to acquire the lock should now be able to acquire it
|
|
assert.False(t, lockers[indexOfAnInactiveLocker].Active)
|
|
err = lockers[indexOfAnInactiveLocker].Lock(ctx, wsc, false)
|
|
assert.NoError(t, err)
|
|
assert.True(t, lockers[indexOfAnInactiveLocker].Active)
|
|
}
|