go-hep.org/x/hep@v0.38.1/xrootd/client.go (about) 1 // Copyright ©2018 The go-hep Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package xrootd // import "go-hep.org/x/hep/xrootd" 6 7 import ( 8 "context" 9 "fmt" 10 "os" 11 "sync" 12 13 "go-hep.org/x/hep/xrootd/xrdproto" 14 "go-hep.org/x/hep/xrootd/xrdproto/auth" 15 ) 16 17 // A Client to xrootd server which allows to send requests and receive responses. 18 // Concurrent requests are supported. 19 // Zero value is invalid, Client should be instantiated using NewClient. 20 type Client struct { 21 cancel context.CancelFunc 22 auths map[string]auth.Auther 23 username string 24 // initialSessionID is the sessionID of the server which is used as default 25 // for all requests that don't specify sessionID explicitly. 26 // Any failed request with another sessionID should be redirected to the initialSessionID. 27 // See http://xrootd.org/doc/dev45/XRdv310.pdf, page 11 for details. 28 initialSessionID string 29 mu sync.RWMutex 30 sessions map[string]*cliSession 31 32 maxRedirections int 33 } 34 35 // Option configures an XRootD client. 36 type Option func(*Client) error 37 38 // WithAuth adds an authentication mechanism to the XRootD client. 39 // If an authentication mechanism was already registered for that provider, 40 // it will be silently replaced. 41 func WithAuth(a auth.Auther) Option { 42 return func(client *Client) error { 43 return client.addAuth(a) 44 } 45 } 46 47 func (client *Client) addAuth(auth auth.Auther) error { 48 client.auths[auth.Provider()] = auth 49 return nil 50 } 51 52 func (client *Client) initSecurityProviders() { 53 for _, provider := range defaultProviders { 54 if provider == nil { 55 continue 56 } 57 client.auths[provider.Provider()] = provider 58 } 59 } 60 61 // NewClient creates a new xrootd client that connects to the given address using username. 62 // Options opts configure the client and are applied in the order they were specified. 63 // When the context expires, a response handling is stopped, however, it is 64 // necessary to call Cancel to correctly free resources. 65 func NewClient(ctx context.Context, address string, username string, opts ...Option) (*Client, error) { 66 ctx, cancel := context.WithCancel(ctx) 67 68 client := &Client{ 69 cancel: cancel, 70 auths: make(map[string]auth.Auther), 71 username: username, 72 sessions: make(map[string]*cliSession), 73 maxRedirections: 10, 74 } 75 76 client.initSecurityProviders() 77 78 for _, opt := range opts { 79 if opt == nil { 80 continue 81 } 82 if err := opt(client); err != nil { 83 client.Close() 84 return nil, err 85 } 86 } 87 88 _, err := client.getSession(ctx, address, "") 89 if err != nil { 90 client.Close() 91 return nil, err 92 } 93 94 return client, nil 95 } 96 97 // Close closes the connection. Any blocked operation will be unblocked and return error. 98 func (client *Client) Close() error { 99 if client == nil { 100 return os.ErrInvalid 101 } 102 defer client.cancel() 103 104 client.mu.Lock() 105 defer client.mu.Unlock() 106 107 var errs []error 108 for _, session := range client.sessions { 109 err := session.Close() 110 if err != nil { 111 errs = append(errs, err) 112 } 113 } 114 if errs != nil { 115 return fmt.Errorf("xrootd: could not close client: %v", errs) 116 } 117 return nil 118 } 119 120 // Send sends the request to the server and stores the response inside the resp. 121 // If the resp is nil, then no response is stored. 122 // Send returns a session id which identifies the server that provided response. 123 func (client *Client) Send(ctx context.Context, resp xrdproto.Response, req xrdproto.Request) (string, error) { 124 if client == nil { 125 return "", os.ErrInvalid 126 } 127 128 return client.sendSession(ctx, client.initialSessionID, resp, req) 129 } 130 131 func (client *Client) sendSession(ctx context.Context, sessionID string, resp xrdproto.Response, req xrdproto.Request) (string, error) { 132 client.mu.RLock() 133 session, ok := client.sessions[sessionID] 134 client.mu.RUnlock() 135 if !ok { 136 return "", fmt.Errorf("xrootd: session with id = %q was not found", sessionID) 137 } 138 139 redirection, err := session.Send(ctx, resp, req) 140 if err != nil { 141 return sessionID, err 142 } 143 144 for cnt := client.maxRedirections; redirection != nil && cnt > 0; cnt-- { 145 sessionID = redirection.Addr 146 session, err = client.getSession(ctx, sessionID, redirection.Token) 147 if err != nil { 148 return sessionID, err 149 } 150 if fp, ok := req.(xrdproto.FilepathRequest); ok { 151 fp.SetOpaque(redirection.Opaque) 152 } 153 // TODO: we should check if the request contains file handle and re-issue open request in that case. 154 redirection, err = session.Send(ctx, resp, req) 155 if err != nil { 156 return sessionID, err 157 } 158 } 159 160 if redirection != nil { 161 err = fmt.Errorf("xrootd: received %d redirections in a row, aborting request", client.maxRedirections) 162 } 163 164 return sessionID, err 165 } 166 167 func (client *Client) getSession(ctx context.Context, address, token string) (*cliSession, error) { 168 client.mu.RLock() 169 v, ok := client.sessions[address] 170 client.mu.RUnlock() 171 if ok { 172 return v, nil 173 } 174 client.mu.Lock() 175 defer client.mu.Unlock() 176 session, err := newSession(ctx, address, client.username, token, client) 177 if err != nil { 178 return nil, err 179 } 180 client.sessions[address] = session 181 182 if len(client.initialSessionID) == 0 { 183 client.initialSessionID = address 184 } 185 // TODO: check if initial sessionID should be changed. 186 // See http://xrootd.org/doc/dev45/XRdv310.pdf, p. 11 for details. 187 188 return session, nil 189 }