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  }