[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