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 }