github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/blobs/client.go (about)

     1  // Copyright 2019 The Cockroach Authors.
     2  //
     3  // Use of this software is governed by the Business Source License
     4  // included in the file licenses/BSL.txt.
     5  //
     6  // As of the Change Date specified in that file, in accordance with
     7  // the Business Source License, use of this software will be governed
     8  // by the Apache License, Version 2.0, included in the file
     9  // licenses/APL.txt.
    10  
    11  package blobs
    12  
    13  import (
    14  	"context"
    15  	"io"
    16  
    17  	"github.com/cockroachdb/cockroach/pkg/blobs/blobspb"
    18  	"github.com/cockroachdb/cockroach/pkg/roachpb"
    19  	"github.com/cockroachdb/cockroach/pkg/rpc"
    20  	"github.com/cockroachdb/cockroach/pkg/rpc/nodedialer"
    21  	"github.com/cockroachdb/errors"
    22  	"google.golang.org/grpc/metadata"
    23  )
    24  
    25  // BlobClient provides an interface for file access on all nodes' local storage.
    26  // Given the nodeID of the node on which the operation should occur, the a blob
    27  // client should be able to find the correct node and call its blob service API.
    28  type BlobClient interface {
    29  	// ReadFile fetches the named payload from the requested node,
    30  	// and stores it in memory. It then returns an io.ReadCloser to
    31  	// read the contents.
    32  	ReadFile(ctx context.Context, file string) (io.ReadCloser, error)
    33  
    34  	// WriteFile sends the named payload to the requested node.
    35  	// This method will read entire content of file and send
    36  	// it over to another node, based on the nodeID.
    37  	WriteFile(ctx context.Context, file string, content io.ReadSeeker) error
    38  
    39  	// List lists the corresponding filenames from the requested node.
    40  	// The requested node can be the current node.
    41  	List(ctx context.Context, pattern string) ([]string, error)
    42  
    43  	// Delete deletes the specified file or empty directory from a remote node.
    44  	Delete(ctx context.Context, file string) error
    45  
    46  	// Stat gets the size (in bytes) of a specified file from a remote node.
    47  	Stat(ctx context.Context, file string) (*blobspb.BlobStat, error)
    48  }
    49  
    50  var _ BlobClient = &remoteClient{}
    51  
    52  // remoteClient uses the node dialer and blob service clients
    53  // to Read or Write bulk files from/to other nodes.
    54  type remoteClient struct {
    55  	blobClient blobspb.BlobClient
    56  }
    57  
    58  // newRemoteClient instantiates a remote blob service client.
    59  func newRemoteClient(blobClient blobspb.BlobClient) BlobClient {
    60  	return &remoteClient{blobClient: blobClient}
    61  }
    62  
    63  func (c *remoteClient) ReadFile(ctx context.Context, file string) (io.ReadCloser, error) {
    64  	// Check that file exists before reading from it
    65  	_, err := c.Stat(ctx, file)
    66  	if err != nil {
    67  		return nil, err
    68  	}
    69  	stream, err := c.blobClient.GetStream(ctx, &blobspb.GetRequest{
    70  		Filename: file,
    71  	})
    72  	return newGetStreamReader(stream), errors.Wrap(err, "fetching file")
    73  }
    74  
    75  func (c *remoteClient) WriteFile(
    76  	ctx context.Context, file string, content io.ReadSeeker,
    77  ) (err error) {
    78  	ctx = metadata.AppendToOutgoingContext(ctx, "filename", file)
    79  	stream, err := c.blobClient.PutStream(ctx)
    80  	if err != nil {
    81  		return
    82  	}
    83  	defer func() {
    84  		_, closeErr := stream.CloseAndRecv()
    85  		if err == nil {
    86  			err = closeErr
    87  		}
    88  	}()
    89  	err = streamContent(stream, content)
    90  	return
    91  }
    92  
    93  func (c *remoteClient) List(ctx context.Context, pattern string) ([]string, error) {
    94  	resp, err := c.blobClient.List(ctx, &blobspb.GlobRequest{
    95  		Pattern: pattern,
    96  	})
    97  	if err != nil {
    98  		return nil, errors.Wrap(err, "fetching list")
    99  	}
   100  	return resp.Files, nil
   101  }
   102  
   103  func (c *remoteClient) Delete(ctx context.Context, file string) error {
   104  	_, err := c.blobClient.Delete(ctx, &blobspb.DeleteRequest{
   105  		Filename: file,
   106  	})
   107  	return err
   108  }
   109  
   110  func (c *remoteClient) Stat(ctx context.Context, file string) (*blobspb.BlobStat, error) {
   111  	resp, err := c.blobClient.Stat(ctx, &blobspb.StatRequest{
   112  		Filename: file,
   113  	})
   114  	if err != nil {
   115  		return nil, err
   116  	}
   117  	return resp, nil
   118  }
   119  
   120  var _ BlobClient = &localClient{}
   121  
   122  // localClient executes the local blob service's code
   123  // to Read or Write bulk files on the current node.
   124  type localClient struct {
   125  	localStorage *LocalStorage
   126  }
   127  
   128  // newLocalClient instantiates a local blob service client.
   129  func newLocalClient(externalIODir string) (BlobClient, error) {
   130  	storage, err := NewLocalStorage(externalIODir)
   131  	if err != nil {
   132  		return nil, errors.Wrap(err, "creating local client")
   133  	}
   134  	return &localClient{localStorage: storage}, nil
   135  }
   136  
   137  func (c *localClient) ReadFile(ctx context.Context, file string) (io.ReadCloser, error) {
   138  	return c.localStorage.ReadFile(file)
   139  }
   140  
   141  func (c *localClient) WriteFile(ctx context.Context, file string, content io.ReadSeeker) error {
   142  	return c.localStorage.WriteFile(file, content)
   143  }
   144  
   145  func (c *localClient) List(ctx context.Context, pattern string) ([]string, error) {
   146  	return c.localStorage.List(pattern)
   147  }
   148  
   149  func (c *localClient) Delete(ctx context.Context, file string) error {
   150  	return c.localStorage.Delete(file)
   151  }
   152  
   153  func (c *localClient) Stat(ctx context.Context, file string) (*blobspb.BlobStat, error) {
   154  	return c.localStorage.Stat(file)
   155  }
   156  
   157  // BlobClientFactory creates a blob client based on the nodeID we are dialing.
   158  type BlobClientFactory func(ctx context.Context, dialing roachpb.NodeID) (BlobClient, error)
   159  
   160  // NewBlobClientFactory returns a BlobClientFactory
   161  func NewBlobClientFactory(
   162  	localNodeID roachpb.NodeID, dialer *nodedialer.Dialer, externalIODir string,
   163  ) BlobClientFactory {
   164  	return func(ctx context.Context, dialing roachpb.NodeID) (BlobClient, error) {
   165  		if dialing == 0 || localNodeID == dialing {
   166  			return newLocalClient(externalIODir)
   167  		}
   168  		conn, err := dialer.Dial(ctx, dialing, rpc.DefaultClass)
   169  		if err != nil {
   170  			return nil, errors.Wrapf(err, "connecting to node %d", dialing)
   171  		}
   172  		return newRemoteClient(blobspb.NewBlobClient(conn)), nil
   173  	}
   174  }