cmd/dolt: Implement new sql-server.lock semantics. Assert sql-server has write access to the databases it loads when it starts, look for the credentials file in parent directories.

This commit is contained in:
Aaron Son
2023-11-29 11:47:16 -08:00
parent e1d37213cc
commit 99fa4f0a32
7 changed files with 81 additions and 33 deletions
+40 -3
View File
@@ -15,8 +15,10 @@
package sqlserver
import (
"os"
"errors"
"fmt"
iofs "io/fs"
"os"
"path/filepath"
"strconv"
"strings"
@@ -83,8 +85,43 @@ func WriteLocalCreds(fs filesys.Filesys, creds *LocalCreds) error {
return fs.WriteFile(credsFile, []byte(fmt.Sprintf("%d:%s:%s", creds.Pid, portStr, creds.Secret)), 0600)
}
func LoadLocalCreds(fs filesys.Filesys, credsFilePath string) (creds *LocalCreds, err error) {
rd, err := fs.OpenForRead(credsFilePath)
// Starting at `fs`, look for the a ServerLocalCredsFile in the .dolt directory
// of this directory and every parent directory, until we find one. When we
// find we, we return its contents if we can open and parse it successfully.
// Otherwise, we return an error associated with attempting to read it. If we
// do not find anything all the way up to the root of the filesystem, returns
// `nil` *LocalCreds and a `nil` error.
func FindAndLoadLocalCreds(fs filesys.Filesys) (creds *LocalCreds, err error) {
root, err := fs.Abs(".")
if err != nil {
return nil, err
}
for root != "" && root[len(root)-1] != '/' {
creds, err := LoadLocalCreds(fs)
if err == nil {
return creds, err
}
// If we have an error that is not ErrNotExist, for example, a
// permission error opening the credentials file, or an error
// indicating that the contents of the file were malformed, go
// ahead and return the error and terminate our search here.
if !errors.Is(err, iofs.ErrNotExist) {
return nil, err
}
fs, err = fs.WithWorkingDir("..")
if err != nil {
return nil, err
}
root, err = fs.Abs(".")
if err != nil {
return nil, err
}
}
return nil, nil
}
func LoadLocalCreds(fs filesys.Filesys) (creds *LocalCreds, err error) {
rd, err := fs.OpenForRead(filepath.Join(dbfactory.DoltDir, ServerLocalCredsFile))
if err != nil {
return nil, err
}
+8 -2
View File
@@ -60,6 +60,8 @@ const (
// but will break compatibility with implementing applications that do not yet support users.
var ExternalDisableUsers bool = false
var ErrCouldNotLockDatabase = goerrors.NewKind("database \"%s\" is locked by another dolt process; either clone the database to run a second server, or stop the dolt process which currently holds an exclusive write lock on the database")
// Serve starts a MySQL-compatible server. Returns any errors that were encountered.
func Serve(
ctx context.Context,
@@ -148,8 +150,12 @@ func Serve(
AssertNoDatabasesInAccessModeReadOnly := &svcs.AnonService{
InitF: func(ctx context.Context) (err error) {
// TODO: Iterate mrEnv, assert env.DoltDB.AccessMode() != chunks.ExclusiveAccessMode_ReadOnly
return nil
return mrEnv.Iter(func(name string, dEnv *env.DoltEnv) (stop bool, err error) {
if dEnv.IsAccessModeReadOnly() {
return true, ErrCouldNotLockDatabase.New(name)
}
return false, nil
})
},
}
controller.Register(AssertNoDatabasesInAccessModeReadOnly)
+1 -1
View File
@@ -74,7 +74,7 @@ func (cmd StashDropCmd) Exec(ctx context.Context, commandStr string, args []stri
ap := cmd.ArgParser()
help, usage := cli.HelpAndUsagePrinters(cli.CommandDocsForCommandString(commandStr, stashDropDocs, ap))
apr := cli.ParseArgsOrDie(ap, args, help)
// TODO: Error if dEnv.DoltDB.AccessMode() == ReadOnly?
var idx = 0
+15 -14
View File
@@ -701,23 +701,24 @@ If you're interested in running this command against a remote host, hit us up on
targetEnv = rootEnv
}
// TODO: if targetEnv.DoltDB.AccessMode() == ReadOnly...
// Need to go looking for local creds to use...
var isLocked bool
var lock *sqlserver.LocalCreds
var err error
if err != nil {
return nil, err
}
if isLocked {
if verbose {
cli.Println("verbose: starting remote mode")
// If the loaded environment does not itself have a DoltDB, then we look for a server.
// We also look for a server when the loaded environment has a DoltDB but another
// running process already has exclusive write access to it.
if targetEnv.DoltDB == nil || targetEnv.IsAccessModeReadOnly() {
localCreds, err := sqlserver.FindAndLoadLocalCreds(targetEnv.FS)
if err != nil {
return nil, err
}
if localCreds != nil {
if verbose {
cli.Println("verbose: starting remote mode")
}
if !creds.Specified {
creds = &cli.UserPassword{Username: sqlserver.LocalConnectionUser, Password: lock.Secret, Specified: false}
if !creds.Specified {
creds = &cli.UserPassword{Username: sqlserver.LocalConnectionUser, Password: localCreds.Secret, Specified: false}
}
return sqlserver.BuildConnectionStringQueryist(ctx, cwdFS, creds, apr, "localhost", localCreds.Port, false, useDb)
}
return sqlserver.BuildConnectionStringQueryist(ctx, cwdFS, creds, apr, "localhost", lock.Port, false, useDb)
}
if verbose {
+4
View File
@@ -131,6 +131,10 @@ func (ddb *DoltDB) NomsRoot(ctx context.Context) (hash.Hash, error) {
return datas.ChunkStoreFromDatabase(ddb.db).Root(ctx)
}
func (ddb *DoltDB) AccessMode() chunks.ExclusiveAccessMode {
return datas.ChunkStoreFromDatabase(ddb.db).AccessMode()
}
// CommitRoot executes a chunkStore commit, atomically swapping the root hash of the database manifest
func (ddb *DoltDB) CommitRoot(ctx context.Context, last, current hash.Hash) (bool, error) {
return datas.ChunkStoreFromDatabase(ddb.db).Commit(ctx, last, current)
+5 -2
View File
@@ -35,6 +35,7 @@ import (
"github.com/dolthub/dolt/go/libraries/utils/concurrentmap"
"github.com/dolthub/dolt/go/libraries/utils/config"
"github.com/dolthub/dolt/go/libraries/utils/filesys"
"github.com/dolthub/dolt/go/store/chunks"
"github.com/dolthub/dolt/go/store/datas"
"github.com/dolthub/dolt/go/store/hash"
"github.com/dolthub/dolt/go/store/types"
@@ -52,8 +53,6 @@ const (
DefaultRemotesApiPort = "443"
tempTablesDir = "temptf"
ServerLockFile = "sql-server.lock"
)
var zeroHashStr = (hash.Hash{}).String()
@@ -1183,3 +1182,7 @@ func (dEnv *DoltEnv) BulkDbEaFactory() editor.DbEaFactory {
}
return editor.NewBulkImportTEAFactory(dEnv.DoltDB.ValueReadWriter(), tmpDir)
}
func (dEnv *DoltEnv) IsAccessModeReadOnly() bool {
return dEnv.DoltDB.AccessMode() == chunks.ExclusiveAccessMode_ReadOnly
}
+8 -11
View File
@@ -22,7 +22,6 @@ import (
"strings"
"github.com/sirupsen/logrus"
"gopkg.in/src-d/go-errors.v1"
"github.com/dolthub/dolt/go/libraries/doltcore/dbfactory"
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
@@ -32,8 +31,6 @@ import (
"github.com/dolthub/dolt/go/store/types"
)
var ErrActiveServerLock = errors.NewKind("database locked by another sql-server; either clone the database to run a second server, or delete the '%s' if no other sql-servers are active")
// EnvNameAndPath is a simple tuple of the name of an environment and the path to where it is on disk
type EnvNameAndPath struct {
// Name is the name of the environment and is used as the identifier when accessing a given environment
@@ -49,10 +46,10 @@ type NamedEnv struct {
// MultiRepoEnv is a type used to store multiple environments which can be retrieved by name
type MultiRepoEnv struct {
envs []NamedEnv
fs filesys.Filesys
cfg config.ReadWriteConfig
dialProvider dbfactory.GRPCDialProvider
envs []NamedEnv
fs filesys.Filesys
cfg config.ReadWriteConfig
dialProvider dbfactory.GRPCDialProvider
}
// NewMultiEnv returns a new MultiRepoEnv instance dirived from a root DoltEnv instance.
@@ -88,10 +85,10 @@ func MultiEnvForDirectory(
}
mrEnv := &MultiRepoEnv{
envs: make([]NamedEnv, 0),
fs: dataDirFS,
cfg: config,
dialProvider: NewGRPCDialProviderFromDoltEnv(newDEnv),
envs: make([]NamedEnv, 0),
fs: dataDirFS,
cfg: config,
dialProvider: NewGRPCDialProviderFromDoltEnv(newDEnv),
}
envSet := map[string]*DoltEnv{}