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 }