[ARVADOS] created: 1.3.0-2720-g2b3e1b4db
Git user
git at public.arvados.org
Tue Jun 23 21:29:43 UTC 2020
at 2b3e1b4db8b4b8115712bde63b3b0520bd3ba2ea (commit)
commit 2b3e1b4db8b4b8115712bde63b3b0520bd3ba2ea
Author: Tom Clegg <tom at tomclegg.ca>
Date: Tue Jun 23 17:28:53 2020 -0400
16534: Add database access in lib/controller/localdb.
Arvados-DCO-1.1-Signed-off-by: Tom Clegg <tom at tomclegg.ca>
diff --git a/lib/controller/localdb/conn.go b/lib/controller/localdb/conn.go
index 60263455b..1dae3b93d 100644
--- a/lib/controller/localdb/conn.go
+++ b/lib/controller/localdb/conn.go
@@ -6,6 +6,8 @@ package localdb
import (
"context"
+ "database/sql"
+ "sync"
"git.arvados.org/arvados.git/lib/controller/railsproxy"
"git.arvados.org/arvados.git/lib/controller/rpc"
@@ -18,6 +20,9 @@ type Conn struct {
cluster *arvados.Cluster
*railsProxy // handles API methods that aren't defined on Conn itself
loginController
+
+ dbconn *sql.DB
+ dbconnMutex sync.Mutex
}
func NewConn(cluster *arvados.Cluster) *Conn {
@@ -29,14 +34,33 @@ func NewConn(cluster *arvados.Cluster) *Conn {
}
}
-func (conn *Conn) Logout(ctx context.Context, opts arvados.LogoutOptions) (arvados.LogoutResponse, error) {
+func (conn *Conn) db() (*sql.DB, error) {
+ conn.dbconnMutex.Lock()
+ defer conn.dbconnMutex.Unlock()
+ if conn.dbconn == nil {
+ db, err := sql.Open("postgres", conn.cluster.PostgreSQL.Connection.String())
+ if err != nil {
+ return nil, err
+ }
+ conn.dbconn = db
+ }
+ return conn.dbconn, nil
+}
+
+func (conn *Conn) Logout(ctx context.Context, opts arvados.LogoutOptions) (_ arvados.LogoutResponse, err error) {
+ ctx, finishtx := starttx(ctx, conn)
+ defer finishtx(&err)
return conn.loginController.Logout(ctx, opts)
}
-func (conn *Conn) Login(ctx context.Context, opts arvados.LoginOptions) (arvados.LoginResponse, error) {
+func (conn *Conn) Login(ctx context.Context, opts arvados.LoginOptions) (_ arvados.LoginResponse, err error) {
+ ctx, finishtx := starttx(ctx, conn)
+ defer finishtx(&err)
return conn.loginController.Login(ctx, opts)
}
-func (conn *Conn) UserAuthenticate(ctx context.Context, opts arvados.UserAuthenticateOptions) (arvados.APIClientAuthorization, error) {
+func (conn *Conn) UserAuthenticate(ctx context.Context, opts arvados.UserAuthenticateOptions) (_ arvados.APIClientAuthorization, err error) {
+ ctx, finishtx := starttx(ctx, conn)
+ defer finishtx(&err)
return conn.loginController.UserAuthenticate(ctx, opts)
}
diff --git a/lib/controller/localdb/db.go b/lib/controller/localdb/db.go
new file mode 100644
index 000000000..395222b06
--- /dev/null
+++ b/lib/controller/localdb/db.go
@@ -0,0 +1,86 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+package localdb
+
+import (
+ "context"
+ "database/sql"
+ "errors"
+ "sync"
+
+ "git.arvados.org/arvados.git/sdk/go/ctxlog"
+ _ "github.com/lib/pq"
+)
+
+type contextKeyT string
+
+var contextKeyTransaction = contextKeyT("transaction")
+
+type transaction struct {
+ tx *sql.Tx
+ err error
+ conn *Conn
+ setup sync.Once
+}
+
+type transactionFinishFunc func(*error)
+
+// starttx returns a new child context that can be used with
+// currenttx(). It does not open a database transaction until the
+// first call to currenttx().
+//
+// The caller must eventually call the returned finishtx() func to
+// commit or rollback the transaction, if any.
+//
+// func example(ctx context.Context) (err error) {
+// ctx, finishtx := starttx(ctx, conn)
+// defer finishtx(&err)
+// // ...
+// tx, err := currenttx(ctx)
+// if err != nil {
+// return fmt.Errorf("example: %s", err)
+// }
+// return tx.ExecContext(...)
+// }
+//
+// If *err is nil, finishtx() commits the transaction and assigns any
+// resulting error to *err.
+//
+// If *err is non-nil, finishtx() rolls back the transaction, and
+// does not modify *err.
+func starttx(ctx context.Context, conn *Conn) (context.Context, transactionFinishFunc) {
+ txn := &transaction{conn: conn}
+ return context.WithValue(ctx, contextKeyTransaction, txn), func(err *error) {
+ // Ensure another goroutine can't open a transaction
+ // during/after finishtx().
+ txn.setup.Do(func() {})
+ if txn.tx == nil {
+ // we never [successfully] started a transaction
+ return
+ }
+ if *err != nil {
+ ctxlog.FromContext(ctx).Debug("rollback")
+ txn.tx.Rollback()
+ return
+ }
+ *err = txn.tx.Commit()
+ }
+}
+
+func currenttx(ctx context.Context) (*sql.Tx, error) {
+ txn, ok := ctx.Value(contextKeyTransaction).(*transaction)
+ if !ok {
+ return nil, errors.New("BUG: currenttx() was called on a context that wasn't returned by startttx()")
+ }
+ txn.setup.Do(func() {
+ db, err := txn.conn.db()
+ if err != nil {
+ txn.err = err
+ } else {
+ txn.tx, txn.err = db.Begin()
+ }
+ })
+ return txn.tx, txn.err
+}
diff --git a/lib/controller/localdb/login.go b/lib/controller/localdb/login.go
index 905cfed15..1cd349a10 100644
--- a/lib/controller/localdb/login.go
+++ b/lib/controller/localdb/login.go
@@ -6,9 +6,13 @@ package localdb
import (
"context"
+ "database/sql"
+ "encoding/json"
"errors"
+ "fmt"
"net/http"
"net/url"
+ "strings"
"git.arvados.org/arvados.git/lib/controller/rpc"
"git.arvados.org/arvados.git/sdk/go/arvados"
@@ -96,9 +100,9 @@ func noopLogout(cluster *arvados.Cluster, opts arvados.LogoutOptions) (arvados.L
return arvados.LogoutResponse{RedirectLocation: target}, nil
}
-func createAPIClientAuthorization(ctx context.Context, conn *rpc.Conn, rootToken string, authinfo rpc.UserSessionAuthInfo) (arvados.APIClientAuthorization, error) {
+func createAPIClientAuthorization(ctx context.Context, conn *rpc.Conn, rootToken string, authinfo rpc.UserSessionAuthInfo) (resp arvados.APIClientAuthorization, err error) {
ctxRoot := auth.NewContext(ctx, &auth.Credentials{Tokens: []string{rootToken}})
- resp, err := conn.UserSessionCreate(ctxRoot, rpc.UserSessionCreateOptions{
+ newsession, err := conn.UserSessionCreate(ctxRoot, rpc.UserSessionCreateOptions{
// Send a fake ReturnTo value instead of the caller's
// opts.ReturnTo. We won't follow the resulting
// redirect target anyway.
@@ -106,12 +110,36 @@ func createAPIClientAuthorization(ctx context.Context, conn *rpc.Conn, rootToken
AuthInfo: authinfo,
})
if err != nil {
- return arvados.APIClientAuthorization{}, err
+ return
}
- target, err := url.Parse(resp.RedirectLocation)
+ target, err := url.Parse(newsession.RedirectLocation)
if err != nil {
- return arvados.APIClientAuthorization{}, err
+ return
}
token := target.Query().Get("api_token")
- return conn.APIClientAuthorizationCurrent(auth.NewContext(ctx, auth.NewCredentials(token)), arvados.GetOptions{})
+ tx, err := currenttx(ctx)
+ if err != nil {
+ return
+ }
+ tokensecret := token
+ if strings.Contains(token, "/") {
+ tokenparts := strings.Split(token, "/")
+ if len(tokenparts) >= 3 {
+ tokensecret = tokenparts[2]
+ }
+ }
+ var exp sql.NullString
+ var scopes []byte
+ err = tx.QueryRowContext(ctx, "select uuid, api_token, expires_at, scopes from api_client_authorizations where api_token=$1", tokensecret).Scan(&resp.UUID, &resp.APIToken, &exp, &scopes)
+ if err != nil {
+ return
+ }
+ resp.ExpiresAt = exp.String
+ if len(scopes) > 0 {
+ err = json.Unmarshal(scopes, &resp.Scopes)
+ if err != nil {
+ return resp, fmt.Errorf("unmarshal scopes: %s", err)
+ }
+ }
+ return
}
diff --git a/lib/controller/localdb/login_ldap_test.go b/lib/controller/localdb/login_ldap_test.go
index 9a8f83f85..99fffe0f0 100644
--- a/lib/controller/localdb/login_ldap_test.go
+++ b/lib/controller/localdb/login_ldap_test.go
@@ -88,7 +88,9 @@ func (s *LDAPSuite) SetUpSuite(c *check.C) {
}
func (s *LDAPSuite) TestLoginSuccess(c *check.C) {
- resp, err := s.ctrl.UserAuthenticate(context.Background(), arvados.UserAuthenticateOptions{
+ conn := NewConn(s.cluster)
+ conn.loginController = s.ctrl
+ resp, err := conn.UserAuthenticate(context.Background(), arvados.UserAuthenticateOptions{
Username: "goodusername",
Password: "goodpassword",
})
-----------------------------------------------------------------------
hooks/post-receive
--
More information about the arvados-commits
mailing list