github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/blobs/client_test.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  	"bytes"
    15  	"context"
    16  	"fmt"
    17  	"io/ioutil"
    18  	"net"
    19  	"os"
    20  	"path/filepath"
    21  	"testing"
    22  	"time"
    23  
    24  	"github.com/cockroachdb/cockroach/pkg/blobs/blobspb"
    25  	"github.com/cockroachdb/cockroach/pkg/roachpb"
    26  	"github.com/cockroachdb/cockroach/pkg/rpc"
    27  	"github.com/cockroachdb/cockroach/pkg/rpc/nodedialer"
    28  	"github.com/cockroachdb/cockroach/pkg/testutils"
    29  	"github.com/cockroachdb/cockroach/pkg/util"
    30  	"github.com/cockroachdb/cockroach/pkg/util/hlc"
    31  	"github.com/cockroachdb/cockroach/pkg/util/leaktest"
    32  	"github.com/cockroachdb/cockroach/pkg/util/netutil"
    33  	"github.com/cockroachdb/cockroach/pkg/util/stop"
    34  	"github.com/cockroachdb/errors"
    35  )
    36  
    37  func createTestResources(t testing.TB) (string, string, *stop.Stopper, func()) {
    38  	localExternalDir, cleanupFn := testutils.TempDir(t)
    39  	remoteExternalDir, cleanupFn2 := testutils.TempDir(t)
    40  	stopper := stop.NewStopper()
    41  	return localExternalDir, remoteExternalDir, stopper, func() {
    42  		cleanupFn()
    43  		cleanupFn2()
    44  		stopper.Stop(context.Background())
    45  		leaktest.AfterTest(t)()
    46  	}
    47  }
    48  
    49  func setUpService(
    50  	t testing.TB,
    51  	rpcContext *rpc.Context,
    52  	localNodeID roachpb.NodeID,
    53  	remoteNodeID roachpb.NodeID,
    54  	localExternalDir string,
    55  	remoteExternalDir string,
    56  ) BlobClientFactory {
    57  	s := rpc.NewServer(rpcContext)
    58  	remoteBlobServer, err := NewBlobService(remoteExternalDir)
    59  	if err != nil {
    60  		t.Fatal(err)
    61  	}
    62  	blobspb.RegisterBlobServer(s, remoteBlobServer)
    63  	ln, err := netutil.ListenAndServeGRPC(rpcContext.Stopper, s, util.TestAddr)
    64  	if err != nil {
    65  		t.Fatal(err)
    66  	}
    67  
    68  	s2 := rpc.NewServer(rpcContext)
    69  	localBlobServer, err := NewBlobService(localExternalDir)
    70  	if err != nil {
    71  		t.Fatal(err)
    72  	}
    73  	blobspb.RegisterBlobServer(s2, localBlobServer)
    74  	ln2, err := netutil.ListenAndServeGRPC(rpcContext.Stopper, s2, util.TestAddr)
    75  	if err != nil {
    76  		t.Fatal(err)
    77  	}
    78  
    79  	localDialer := nodedialer.New(rpcContext,
    80  		func(nodeID roachpb.NodeID) (net.Addr, error) {
    81  			if nodeID == remoteNodeID {
    82  				return ln.Addr(), nil
    83  			} else if nodeID == localNodeID {
    84  				return ln2.Addr(), nil
    85  			}
    86  			return nil, errors.Errorf("node %d not found", nodeID)
    87  		},
    88  	)
    89  	return NewBlobClientFactory(
    90  		localNodeID,
    91  		localDialer,
    92  		localExternalDir,
    93  	)
    94  }
    95  
    96  func writeTestFile(t testing.TB, file string, content []byte) {
    97  	err := os.MkdirAll(filepath.Dir(file), 0755)
    98  	if err != nil {
    99  		t.Fatal(err)
   100  	}
   101  	err = ioutil.WriteFile(file, content, 0600)
   102  	if err != nil {
   103  		t.Fatal(err)
   104  	}
   105  }
   106  
   107  func TestBlobClientReadFile(t *testing.T) {
   108  	localNodeID := roachpb.NodeID(1)
   109  	remoteNodeID := roachpb.NodeID(2)
   110  	localExternalDir, remoteExternalDir, stopper, cleanUpFn := createTestResources(t)
   111  	defer cleanUpFn()
   112  
   113  	clock := hlc.NewClock(hlc.UnixNano, time.Nanosecond)
   114  	rpcContext := rpc.NewInsecureTestingContext(clock, stopper)
   115  	rpcContext.TestingAllowNamedRPCToAnonymousServer = true
   116  
   117  	blobClientFactory := setUpService(t, rpcContext, localNodeID, remoteNodeID, localExternalDir, remoteExternalDir)
   118  
   119  	localFileContent := []byte("local_file")
   120  	remoteFileContent := []byte("remote_file")
   121  	writeTestFile(t, filepath.Join(localExternalDir, "test/local.csv"), localFileContent)
   122  	writeTestFile(t, filepath.Join(remoteExternalDir, "test/remote.csv"), remoteFileContent)
   123  
   124  	for _, tc := range []struct {
   125  		name        string
   126  		nodeID      roachpb.NodeID
   127  		filename    string
   128  		fileContent []byte
   129  		err         string
   130  	}{
   131  		{
   132  			"read-remote-file",
   133  			remoteNodeID,
   134  			"test/remote.csv",
   135  			remoteFileContent,
   136  			"",
   137  		},
   138  		{
   139  			"read-local-file",
   140  			localNodeID,
   141  			"test/local.csv",
   142  			localFileContent,
   143  			"",
   144  		},
   145  		{
   146  			"read-file-not-exist",
   147  			remoteNodeID,
   148  			"test/notexist.csv",
   149  			nil,
   150  			"no such file",
   151  		},
   152  		{
   153  			"read-dir-exists",
   154  			remoteNodeID,
   155  			"test",
   156  			nil,
   157  			"is a directory",
   158  		},
   159  		{
   160  			"read-check-calling-clean",
   161  			remoteNodeID,
   162  			"../test/remote.csv",
   163  			nil,
   164  			"outside of external-io-dir is not allowed",
   165  		},
   166  		{
   167  			"read-outside-extern-dir",
   168  			remoteNodeID,
   169  			// this file exists, but is not within remote node's externalIODir
   170  			filepath.Join("../..", localExternalDir, "test/local.csv"),
   171  			nil,
   172  			"outside of external-io-dir is not allowed",
   173  		},
   174  	} {
   175  		t.Run(tc.name, func(t *testing.T) {
   176  			ctx := context.Background()
   177  			blobClient, err := blobClientFactory(ctx, tc.nodeID)
   178  			if err != nil {
   179  				t.Fatal(err)
   180  			}
   181  			reader, err := blobClient.ReadFile(ctx, tc.filename)
   182  			if err != nil {
   183  				if testutils.IsError(err, tc.err) {
   184  					// correct error was returned
   185  					return
   186  				}
   187  				t.Fatal(err)
   188  			}
   189  			// Check that fetched file content is correct
   190  			content, err := ioutil.ReadAll(reader)
   191  			if err != nil {
   192  				t.Fatal(err)
   193  			}
   194  			if !bytes.Equal(content, tc.fileContent) {
   195  				t.Fatal(fmt.Sprintf(`fetched file content incorrect, expected %s, got %s`, tc.fileContent, content))
   196  			}
   197  		})
   198  	}
   199  }
   200  
   201  func TestBlobClientWriteFile(t *testing.T) {
   202  	localNodeID := roachpb.NodeID(1)
   203  	remoteNodeID := roachpb.NodeID(2)
   204  	localExternalDir, remoteExternalDir, stopper, cleanUpFn := createTestResources(t)
   205  	defer cleanUpFn()
   206  
   207  	clock := hlc.NewClock(hlc.UnixNano, time.Nanosecond)
   208  	rpcContext := rpc.NewInsecureTestingContext(clock, stopper)
   209  	rpcContext.TestingAllowNamedRPCToAnonymousServer = true
   210  
   211  	blobClientFactory := setUpService(t, rpcContext, localNodeID, remoteNodeID, localExternalDir, remoteExternalDir)
   212  
   213  	for _, tc := range []struct {
   214  		name               string
   215  		nodeID             roachpb.NodeID
   216  		filename           string
   217  		fileContent        string
   218  		destinationNodeDir string
   219  		err                string
   220  	}{
   221  		{
   222  			"write-remote-file",
   223  			remoteNodeID,
   224  			"test/remote.csv",
   225  			"remotefile",
   226  			remoteExternalDir,
   227  			"",
   228  		},
   229  		{
   230  			"write-local-file",
   231  			localNodeID,
   232  			"test/local.csv",
   233  			"localfile",
   234  			localExternalDir,
   235  			"",
   236  		},
   237  		{
   238  			"write-outside-extern-dir",
   239  			remoteNodeID,
   240  			"/../../../outside.csv",
   241  			"remotefile",
   242  			remoteExternalDir,
   243  			"not allowed",
   244  		},
   245  	} {
   246  		t.Run(tc.name, func(t *testing.T) {
   247  			ctx := context.Background()
   248  			blobClient, err := blobClientFactory(ctx, tc.nodeID)
   249  			if err != nil {
   250  				t.Fatal(err)
   251  			}
   252  			byteContent := []byte(tc.fileContent)
   253  			err = blobClient.WriteFile(ctx, tc.filename, bytes.NewReader(byteContent))
   254  			if err != nil {
   255  				if testutils.IsError(err, tc.err) {
   256  					// correct error was returned
   257  					return
   258  				}
   259  				t.Fatal(err)
   260  			}
   261  			// Check that file is now in correct node
   262  			content, err := ioutil.ReadFile(filepath.Join(tc.destinationNodeDir, tc.filename))
   263  			if err != nil {
   264  				t.Fatal(err, "unable to read fetched file")
   265  			}
   266  			if !bytes.Equal(content, byteContent) {
   267  				t.Fatal(fmt.Sprintf(`fetched file content incorrect, expected %s, got %s`, tc.fileContent, content))
   268  			}
   269  		})
   270  	}
   271  }
   272  
   273  func TestBlobClientList(t *testing.T) {
   274  	localNodeID := roachpb.NodeID(1)
   275  	remoteNodeID := roachpb.NodeID(2)
   276  	localExternalDir, remoteExternalDir, stopper, cleanUpFn := createTestResources(t)
   277  	defer cleanUpFn()
   278  
   279  	clock := hlc.NewClock(hlc.UnixNano, time.Nanosecond)
   280  	rpcContext := rpc.NewInsecureTestingContext(clock, stopper)
   281  	rpcContext.TestingAllowNamedRPCToAnonymousServer = true
   282  
   283  	blobClientFactory := setUpService(t, rpcContext, localNodeID, remoteNodeID, localExternalDir, remoteExternalDir)
   284  
   285  	localFileNames := []string{"/file/local/dataA.csv", "/file/local/dataB.csv", "/file/local/dataC.csv"}
   286  	remoteFileNames := []string{"/file/remote/A.csv", "/file/remote/B.csv", "/file/remote/C.csv"}
   287  	for _, fileName := range localFileNames {
   288  		fullPath := filepath.Join(localExternalDir, fileName)
   289  		writeTestFile(t, fullPath, []byte("testLocalFile"))
   290  	}
   291  	for _, fileName := range remoteFileNames {
   292  		fullPath := filepath.Join(remoteExternalDir, fileName)
   293  		writeTestFile(t, fullPath, []byte("testRemoteFile"))
   294  	}
   295  
   296  	for _, tc := range []struct {
   297  		name         string
   298  		nodeID       roachpb.NodeID
   299  		dirName      string
   300  		expectedList []string
   301  		err          string
   302  	}{
   303  		{
   304  			"list-local",
   305  			localNodeID,
   306  			"file/local/*.csv",
   307  			localFileNames,
   308  			"",
   309  		},
   310  		{
   311  			"list-remote",
   312  			remoteNodeID,
   313  			"file/remote/*.csv",
   314  			remoteFileNames,
   315  			"",
   316  		},
   317  		{
   318  			"list-local-no-match",
   319  			localNodeID,
   320  			"file/doesnotexist/*",
   321  			[]string{},
   322  			"",
   323  		},
   324  		{
   325  			"list-remote-no-match",
   326  			remoteNodeID,
   327  			"file/doesnotexist/*",
   328  			[]string{},
   329  			"",
   330  		},
   331  		{
   332  			"list-empty-pattern",
   333  			remoteNodeID,
   334  			"",
   335  			[]string{},
   336  			"pattern cannot be empty",
   337  		},
   338  		{
   339  			// should list files in top level directory
   340  			"list-star",
   341  			remoteNodeID,
   342  			"*",
   343  			[]string{"/file"},
   344  			"",
   345  		},
   346  		{
   347  			"list-outside-external-dir",
   348  			remoteNodeID,
   349  			"../*", // will error out
   350  			[]string{},
   351  			"outside of external-io-dir is not allowed",
   352  		},
   353  		{
   354  			"list-backout-external-dir",
   355  			remoteNodeID,
   356  			"..",
   357  			[]string{},
   358  			"outside of external-io-dir is not allowed",
   359  		},
   360  	} {
   361  		t.Run(tc.name, func(t *testing.T) {
   362  			ctx := context.Background()
   363  			blobClient, err := blobClientFactory(ctx, tc.nodeID)
   364  			if err != nil {
   365  				t.Fatal(err)
   366  			}
   367  			list, err := blobClient.List(ctx, tc.dirName)
   368  			if err != nil {
   369  				if testutils.IsError(err, tc.err) {
   370  					// correct error returned
   371  					return
   372  				}
   373  				t.Fatal(err)
   374  			}
   375  			// Check that returned list matches expected list
   376  			if len(list) != len(tc.expectedList) {
   377  				t.Fatal(`listed incorrect number of files`, list)
   378  			}
   379  			for i, f := range list {
   380  				if f != tc.expectedList[i] {
   381  					t.Fatal("incorrect list returned ", list)
   382  				}
   383  			}
   384  		})
   385  	}
   386  }
   387  
   388  func TestBlobClientDeleteFrom(t *testing.T) {
   389  	localNodeID := roachpb.NodeID(1)
   390  	remoteNodeID := roachpb.NodeID(2)
   391  	localExternalDir, remoteExternalDir, stopper, cleanUpFn := createTestResources(t)
   392  	defer cleanUpFn()
   393  
   394  	clock := hlc.NewClock(hlc.UnixNano, time.Nanosecond)
   395  	rpcContext := rpc.NewInsecureTestingContext(clock, stopper)
   396  	rpcContext.TestingAllowNamedRPCToAnonymousServer = true
   397  
   398  	blobClientFactory := setUpService(t, rpcContext, localNodeID, remoteNodeID, localExternalDir, remoteExternalDir)
   399  
   400  	localFileContent := []byte("local_file")
   401  	remoteFileContent := []byte("remote_file")
   402  	writeTestFile(t, filepath.Join(localExternalDir, "test/local.csv"), localFileContent)
   403  	writeTestFile(t, filepath.Join(remoteExternalDir, "test/remote.csv"), remoteFileContent)
   404  	writeTestFile(t, filepath.Join(remoteExternalDir, "test/remote2.csv"), remoteFileContent)
   405  
   406  	for _, tc := range []struct {
   407  		name     string
   408  		nodeID   roachpb.NodeID
   409  		filename string
   410  		err      string
   411  	}{
   412  		{
   413  			"delete-remote-file",
   414  			remoteNodeID,
   415  			"test/remote.csv",
   416  			"",
   417  		},
   418  		{
   419  			"delete-local-file",
   420  			localNodeID,
   421  			"test/local.csv",
   422  			"",
   423  		},
   424  		{
   425  			"delete-remote-file-does-not-exist",
   426  			remoteNodeID,
   427  			"test/doesnotexist",
   428  			"no such file",
   429  		},
   430  		{
   431  			"delete-directory-not-empty",
   432  			remoteNodeID,
   433  			"test",
   434  			"directory not empty",
   435  		},
   436  		{
   437  			"delete-directory-empty", // this should work
   438  			localNodeID,
   439  			"test",
   440  			"",
   441  		},
   442  	} {
   443  		t.Run(tc.name, func(t *testing.T) {
   444  			ctx := context.Background()
   445  			blobClient, err := blobClientFactory(ctx, tc.nodeID)
   446  			if err != nil {
   447  				t.Fatal(err)
   448  			}
   449  			err = blobClient.Delete(ctx, tc.filename)
   450  			if err != nil {
   451  				if testutils.IsError(err, tc.err) {
   452  					// the correct error was returned
   453  					return
   454  				}
   455  				t.Fatal(err)
   456  			}
   457  
   458  			_, err = ioutil.ReadFile(filepath.Join(localExternalDir, tc.filename))
   459  			if err == nil {
   460  				t.Fatal(err, "file should have been deleted")
   461  			}
   462  		})
   463  	}
   464  }
   465  
   466  func TestBlobClientStat(t *testing.T) {
   467  	localNodeID := roachpb.NodeID(1)
   468  	remoteNodeID := roachpb.NodeID(2)
   469  	localExternalDir, remoteExternalDir, stopper, cleanUpFn := createTestResources(t)
   470  	defer cleanUpFn()
   471  
   472  	clock := hlc.NewClock(hlc.UnixNano, time.Nanosecond)
   473  	rpcContext := rpc.NewInsecureTestingContext(clock, stopper)
   474  	rpcContext.TestingAllowNamedRPCToAnonymousServer = true
   475  
   476  	blobClientFactory := setUpService(t, rpcContext, localNodeID, remoteNodeID, localExternalDir, remoteExternalDir)
   477  
   478  	localFileContent := []byte("local_file")
   479  	remoteFileContent := []byte("remote_file")
   480  	writeTestFile(t, filepath.Join(localExternalDir, "test/local.csv"), localFileContent)
   481  	writeTestFile(t, filepath.Join(remoteExternalDir, "test/remote.csv"), remoteFileContent)
   482  
   483  	for _, tc := range []struct {
   484  		name         string
   485  		nodeID       roachpb.NodeID
   486  		filename     string
   487  		expectedSize int64
   488  		err          string
   489  	}{
   490  		{
   491  			"stat-remote-file",
   492  			remoteNodeID,
   493  			"test/remote.csv",
   494  			int64(len(remoteFileContent)),
   495  			"",
   496  		},
   497  		{
   498  			"stat-local-file",
   499  			localNodeID,
   500  			"test/local.csv",
   501  			int64(len(localFileContent)),
   502  			"",
   503  		},
   504  		{
   505  			"stat-remote-file-does-not-exist",
   506  			remoteNodeID,
   507  			"test/doesnotexist",
   508  			0,
   509  			"no such file",
   510  		},
   511  		{
   512  			"stat-directory",
   513  			remoteNodeID,
   514  			"test",
   515  			0,
   516  			"is a directory",
   517  		},
   518  	} {
   519  		t.Run(tc.name, func(t *testing.T) {
   520  			ctx := context.Background()
   521  			blobClient, err := blobClientFactory(ctx, tc.nodeID)
   522  			if err != nil {
   523  				t.Fatal(err)
   524  			}
   525  			resp, err := blobClient.Stat(ctx, tc.filename)
   526  			if err != nil {
   527  				if testutils.IsError(err, tc.err) {
   528  					// the correct error was returned
   529  					return
   530  				}
   531  				t.Fatal(err)
   532  			}
   533  			if resp.Filesize != tc.expectedSize {
   534  				t.Fatalf("expected size: %d got: %d", tc.expectedSize, resp)
   535  			}
   536  		})
   537  	}
   538  }