X-Git-Url: https://git.arvados.org/arvados.git/blobdiff_plain/8b873a9b3b8865a4d451263e48b49122b9c32759..0446c0a3a433936985d6f46b0eab9b253ed98e80:/lib/controller/federation/conn.go diff --git a/lib/controller/federation/conn.go b/lib/controller/federation/conn.go index 3bcafacd2c..1b1ffef24c 100644 --- a/lib/controller/federation/conn.go +++ b/lib/controller/federation/conn.go @@ -15,13 +15,14 @@ import ( "net/url" "regexp" "strings" - - "git.curoverse.com/arvados.git/lib/config" - "git.curoverse.com/arvados.git/lib/controller/railsproxy" - "git.curoverse.com/arvados.git/lib/controller/rpc" - "git.curoverse.com/arvados.git/sdk/go/arvados" - "git.curoverse.com/arvados.git/sdk/go/auth" - "git.curoverse.com/arvados.git/sdk/go/ctxlog" + "time" + + "git.arvados.org/arvados.git/lib/config" + "git.arvados.org/arvados.git/lib/controller/localdb" + "git.arvados.org/arvados.git/lib/controller/rpc" + "git.arvados.org/arvados.git/sdk/go/arvados" + "git.arvados.org/arvados.git/sdk/go/auth" + "git.arvados.org/arvados.git/sdk/go/ctxlog" ) type Conn struct { @@ -31,7 +32,7 @@ type Conn struct { } func New(cluster *arvados.Cluster) *Conn { - local := railsproxy.NewConn(cluster) + local := localdb.NewConn(cluster) remotes := map[string]backend{} for id, remote := range cluster.RemoteClusters { if !remote.Proxy { @@ -185,6 +186,33 @@ func (conn *Conn) ConfigGet(ctx context.Context) (json.RawMessage, error) { return json.RawMessage(buf.Bytes()), err } +func (conn *Conn) Login(ctx context.Context, options arvados.LoginOptions) (arvados.LoginResponse, error) { + if id := conn.cluster.Login.LoginCluster; id != "" && id != conn.cluster.ClusterID { + // defer entire login procedure to designated cluster + remote, ok := conn.remotes[id] + if !ok { + return arvados.LoginResponse{}, fmt.Errorf("configuration problem: designated login cluster %q is not defined", id) + } + baseURL := remote.BaseURL() + target, err := baseURL.Parse(arvados.EndpointLogin.Path) + if err != nil { + return arvados.LoginResponse{}, fmt.Errorf("internal error getting redirect target: %s", err) + } + params := url.Values{ + "return_to": []string{options.ReturnTo}, + } + if options.Remote != "" { + params.Set("remote", options.Remote) + } + target.RawQuery = params.Encode() + return arvados.LoginResponse{ + RedirectLocation: target.String(), + }, nil + } else { + return conn.local.Login(ctx, options) + } +} + func (conn *Conn) CollectionGet(ctx context.Context, options arvados.GetOptions) (arvados.Collection, error) { if len(options.UUID) == 27 { // UUID is really a UUID @@ -227,6 +255,10 @@ func (conn *Conn) CollectionGet(ctx context.Context, options arvados.GetOptions) } } +func (conn *Conn) CollectionList(ctx context.Context, options arvados.ListOptions) (arvados.CollectionList, error) { + return conn.generated_CollectionList(ctx, options) +} + func (conn *Conn) CollectionProvenance(ctx context.Context, options arvados.GetOptions) (map[string]interface{}, error) { return conn.chooseBackend(options.UUID).CollectionProvenance(ctx, options) } @@ -247,6 +279,10 @@ func (conn *Conn) CollectionUntrash(ctx context.Context, options arvados.Untrash return conn.chooseBackend(options.UUID).CollectionUntrash(ctx, options) } +func (conn *Conn) ContainerList(ctx context.Context, options arvados.ListOptions) (arvados.ContainerList, error) { + return conn.generated_ContainerList(ctx, options) +} + func (conn *Conn) ContainerCreate(ctx context.Context, options arvados.CreateOptions) (arvados.Container, error) { return conn.chooseBackend(options.ClusterID).ContainerCreate(ctx, options) } @@ -271,6 +307,10 @@ func (conn *Conn) ContainerUnlock(ctx context.Context, options arvados.GetOption return conn.chooseBackend(options.UUID).ContainerUnlock(ctx, options) } +func (conn *Conn) SpecimenList(ctx context.Context, options arvados.ListOptions) (arvados.SpecimenList, error) { + return conn.generated_SpecimenList(ctx, options) +} + func (conn *Conn) SpecimenCreate(ctx context.Context, options arvados.CreateOptions) (arvados.Specimen, error) { return conn.chooseBackend(options.ClusterID).SpecimenCreate(ctx, options) } @@ -287,11 +327,149 @@ func (conn *Conn) SpecimenDelete(ctx context.Context, options arvados.DeleteOpti return conn.chooseBackend(options.UUID).SpecimenDelete(ctx, options) } +var userAttrsCachedFromLoginCluster = map[string]bool{ + "created_at": true, + "email": true, + "first_name": true, + "is_active": true, + "is_admin": true, + "last_name": true, + "modified_at": true, + "modified_by_client_uuid": true, + "modified_by_user_uuid": true, + "prefs": true, + "username": true, + + "etag": false, + "full_name": false, + "identity_url": false, + "is_invited": false, + "owner_uuid": false, + "uuid": false, + "writable_by": false, +} + +func (conn *Conn) UserList(ctx context.Context, options arvados.ListOptions) (arvados.UserList, error) { + logger := ctxlog.FromContext(ctx) + if id := conn.cluster.Login.LoginCluster; id != "" && id != conn.cluster.ClusterID { + resp, err := conn.chooseBackend(id).UserList(ctx, options) + if err != nil { + return resp, err + } + batchOpts := arvados.UserBatchUpdateOptions{Updates: map[string]map[string]interface{}{}} + for _, user := range resp.Items { + if !strings.HasPrefix(user.UUID, id) { + continue + } + logger.Debugf("cache user info for uuid %q", user.UUID) + + // If the remote cluster has null timestamps + // (e.g., test server with incomplete + // fixtures) use dummy timestamps (instead of + // the zero time, which causes a Rails API + // error "year too big to marshal: 1 UTC"). + if user.ModifiedAt.IsZero() { + user.ModifiedAt = time.Now() + } + if user.CreatedAt.IsZero() { + user.CreatedAt = time.Now() + } + + var allFields map[string]interface{} + buf, err := json.Marshal(user) + if err != nil { + return arvados.UserList{}, fmt.Errorf("error encoding user record from remote response: %s", err) + } + err = json.Unmarshal(buf, &allFields) + if err != nil { + return arvados.UserList{}, fmt.Errorf("error transcoding user record from remote response: %s", err) + } + updates := allFields + if len(options.Select) > 0 { + updates = map[string]interface{}{} + for _, k := range options.Select { + if v, ok := allFields[k]; ok && userAttrsCachedFromLoginCluster[k] { + updates[k] = v + } + } + } else { + for k := range updates { + if !userAttrsCachedFromLoginCluster[k] { + delete(updates, k) + } + } + } + batchOpts.Updates[user.UUID] = updates + } + if len(batchOpts.Updates) > 0 { + ctxRoot := auth.NewContext(ctx, &auth.Credentials{Tokens: []string{conn.cluster.SystemRootToken}}) + _, err = conn.local.UserBatchUpdate(ctxRoot, batchOpts) + if err != nil { + return arvados.UserList{}, fmt.Errorf("error updating local user records: %s", err) + } + } + return resp, nil + } else { + return conn.generated_UserList(ctx, options) + } +} + +func (conn *Conn) UserCreate(ctx context.Context, options arvados.CreateOptions) (arvados.User, error) { + return conn.chooseBackend(options.ClusterID).UserCreate(ctx, options) +} + +func (conn *Conn) UserUpdate(ctx context.Context, options arvados.UpdateOptions) (arvados.User, error) { + return conn.chooseBackend(options.UUID).UserUpdate(ctx, options) +} + +func (conn *Conn) UserUpdateUUID(ctx context.Context, options arvados.UpdateUUIDOptions) (arvados.User, error) { + return conn.chooseBackend(options.UUID).UserUpdateUUID(ctx, options) +} + +func (conn *Conn) UserMerge(ctx context.Context, options arvados.UserMergeOptions) (arvados.User, error) { + return conn.chooseBackend(options.OldUserUUID).UserMerge(ctx, options) +} + +func (conn *Conn) UserActivate(ctx context.Context, options arvados.UserActivateOptions) (arvados.User, error) { + return conn.chooseBackend(options.UUID).UserActivate(ctx, options) +} + +func (conn *Conn) UserSetup(ctx context.Context, options arvados.UserSetupOptions) (map[string]interface{}, error) { + return conn.chooseBackend(options.UUID).UserSetup(ctx, options) +} + +func (conn *Conn) UserUnsetup(ctx context.Context, options arvados.GetOptions) (arvados.User, error) { + return conn.chooseBackend(options.UUID).UserUnsetup(ctx, options) +} + +func (conn *Conn) UserGet(ctx context.Context, options arvados.GetOptions) (arvados.User, error) { + return conn.chooseBackend(options.UUID).UserGet(ctx, options) +} + +func (conn *Conn) UserGetCurrent(ctx context.Context, options arvados.GetOptions) (arvados.User, error) { + return conn.chooseBackend(options.UUID).UserGetCurrent(ctx, options) +} + +func (conn *Conn) UserGetSystem(ctx context.Context, options arvados.GetOptions) (arvados.User, error) { + return conn.chooseBackend(options.UUID).UserGetSystem(ctx, options) +} + +func (conn *Conn) UserDelete(ctx context.Context, options arvados.DeleteOptions) (arvados.User, error) { + return conn.chooseBackend(options.UUID).UserDelete(ctx, options) +} + +func (conn *Conn) UserBatchUpdate(ctx context.Context, options arvados.UserBatchUpdateOptions) (arvados.UserList, error) { + return conn.local.UserBatchUpdate(ctx, options) +} + func (conn *Conn) APIClientAuthorizationCurrent(ctx context.Context, options arvados.GetOptions) (arvados.APIClientAuthorization, error) { return conn.chooseBackend(options.UUID).APIClientAuthorizationCurrent(ctx, options) } -type backend interface{ arvados.API } +type backend interface { + arvados.API + BaseURL() url.URL +} type notFoundError struct{}