github.com/tsuna/gohbase@v0.0.0-20250731002811-4ffcadfba63e/region/client_test.go (about) 1 // Copyright (C) 2015 The GoHBase Authors. All rights reserved. 2 // This file is part of GoHBase. 3 // Use of this source code is governed by the Apache License 2.0 4 // that can be found in the COPYING file. 5 6 package region 7 8 import ( 9 "bytes" 10 "context" 11 "encoding/binary" 12 "encoding/json" 13 "errors" 14 "fmt" 15 "log/slog" 16 "math" 17 "net" 18 "reflect" 19 "strconv" 20 "strings" 21 "sync" 22 "sync/atomic" 23 "testing" 24 "time" 25 26 "github.com/stretchr/testify/assert" 27 "github.com/tsuna/gohbase/hrpc" 28 "github.com/tsuna/gohbase/pb" 29 "github.com/tsuna/gohbase/test" 30 "github.com/tsuna/gohbase/test/mock" 31 "go.uber.org/mock/gomock" 32 "google.golang.org/protobuf/encoding/protowire" 33 "google.golang.org/protobuf/proto" 34 ) 35 36 func TestErrors(t *testing.T) { 37 ue := ServerError{fmt.Errorf("oops")} 38 if ue.Error() != "region.ServerError: oops" { 39 t.Errorf("Wrong error message. Got %q, wanted %q", ue, "region.ServerError: oops") 40 } 41 } 42 43 func TestWrite(t *testing.T) { 44 ctrl := test.NewController(t) 45 defer ctrl.Finish() 46 mockConn := mock.NewMockConn(ctrl) 47 c := &client{ 48 conn: mockConn, 49 } 50 51 // check if Write returns an error 52 expectErr := errors.New("nope") 53 mockConn.EXPECT().Write(gomock.Any()).Return(0, expectErr).Times(1) 54 err := c.write([]byte("lol")) 55 if err != expectErr { 56 t.Errorf("expected %v, got %v", expectErr, err) 57 } 58 59 // check if it actually writes the right data 60 expected := []byte("lol") 61 mockConn.EXPECT().Write(gomock.Any()).Return(3, nil).Times(1).Do(func(buf []byte) { 62 if !bytes.Equal(expected, buf) { 63 t.Errorf("expected %v, got %v", expected, buf) 64 } 65 }) 66 err = c.write(expected) 67 if err != nil { 68 t.Errorf("Was expecting error, but got one: %#v", err) 69 } 70 } 71 72 func TestSendHello(t *testing.T) { 73 ctrl := test.NewController(t) 74 defer ctrl.Finish() 75 mockConn := mock.NewMockConn(ctrl) 76 c := &client{ 77 conn: mockConn, 78 effectiveUser: "root", 79 ctype: RegionClient, 80 } 81 82 // check if it's sending the right "hello" for RegionClient 83 mockConn.EXPECT().Write(gomock.Any()).Return(78, nil).Times(1).Do(func(buf []byte) { 84 expected := []byte("HBas\x00P\x00\x00\x00D\n\x06\n\x04root\x12\rClientService\x1a+" + 85 "org.apache.hadoop.hbase.codec.KeyValueCodec") 86 if !bytes.Equal(expected, buf) { 87 t.Errorf("expected %v, got %v", expected, buf) 88 } 89 }) 90 err := c.sendHello() 91 if err != nil { 92 t.Errorf("Wasn't expecting error, but got one: %#v", err) 93 } 94 95 // check if it sends the right "hello" for MasterClient 96 c.ctype = MasterClient 97 mockConn.EXPECT().Write(gomock.Any()).Return(78, nil).Times(1).Do(func(buf []byte) { 98 expected := []byte("HBas\x00P\x00\x00\x00D\n\x06\n\x04root\x12\rMasterService\x1a+" + 99 "org.apache.hadoop.hbase.codec.KeyValueCodec") 100 if !bytes.Equal(expected, buf) { 101 t.Errorf("expected %v, got %v", expected, buf) 102 } 103 }) 104 err = c.sendHello() 105 if err != nil { 106 t.Errorf("Was expecting error, but got one: %#v", err) 107 } 108 109 c.compressor = &compressor{Codec: mockCodec{}} 110 // check if compressor is getting sent 111 c.ctype = RegionClient 112 mockConn.EXPECT().Write(gomock.Any()).Return(78, nil).Times(1).Do(func(buf []byte) { 113 expected := []byte("HBas\x00P\x00\x00\x00J\n\x06\n\x04root\x12\rClientService\x1a+" + 114 "org.apache.hadoop.hbase.codec.KeyValueCodec\"\x04mock") 115 if !bytes.Equal(expected, buf) { 116 t.Errorf("expected %q, got %q", expected, buf) 117 } 118 }) 119 err = c.sendHello() 120 if err != nil { 121 t.Errorf("Was expecting error, but got one: %#v", err) 122 } 123 } 124 125 func TestFail(t *testing.T) { 126 ctrl := test.NewController(t) 127 defer ctrl.Finish() 128 mockConn := mock.NewMockConn(ctrl) 129 c := &client{ 130 conn: mockConn, 131 done: make(chan struct{}), 132 rpcs: make(chan []hrpc.Call), 133 sent: make(map[uint32]hrpc.Call), 134 logger: slog.Default(), 135 } 136 expectedErr := errors.New("oooups") 137 138 // check that connection Close is called only once 139 mockConn.EXPECT().Close().Times(1) 140 141 // run multiple in parallel to make sure everything is called only once 142 var wg sync.WaitGroup 143 for i := 0; i < 100; i++ { 144 wg.Add(1) 145 go func() { 146 c.fail(expectedErr) 147 wg.Done() 148 }() 149 } 150 wg.Wait() 151 152 // check if done channel is closed to notify goroutines to stop 153 // if close(c.done) is called more than once, it would panic 154 select { 155 case <-time.After(2 * time.Second): 156 t.Errorf("done hasn't been closed") 157 case _, more := <-c.done: 158 if more { 159 t.Error("expected done to be closed") 160 } 161 } 162 163 // check that failing undialed client doesn't panic 164 c = &client{ 165 done: make(chan struct{}), 166 rpcs: make(chan []hrpc.Call), 167 sent: make(map[uint32]hrpc.Call), 168 logger: slog.Default(), 169 } 170 c.fail(expectedErr) 171 } 172 173 type mc struct { 174 *mock.MockConn 175 176 closed uint32 177 } 178 179 func (c *mc) Write(b []byte) (n int, err error) { 180 if v := atomic.LoadUint32(&c.closed); v != 0 { 181 return 0, errors.New("closed connection") 182 } 183 return 0, nil 184 } 185 186 func (c *mc) Close() error { 187 atomic.AddUint32(&c.closed, 1) 188 return nil 189 } 190 191 func TestQueueRPCMultiWithClose(t *testing.T) { 192 ctrl := test.NewController(t) 193 defer ctrl.Finish() 194 195 mockConn := mock.NewMockConn(ctrl) 196 mockConn.EXPECT().SetReadDeadline(gomock.Any()).AnyTimes() 197 198 ncalls := 1000 199 200 c := &client{ 201 conn: &mc{MockConn: mockConn}, 202 rpcs: make(chan []hrpc.Call), 203 done: make(chan struct{}), 204 sent: make(map[uint32]hrpc.Call), 205 rpcQueueSize: 10, 206 flushInterval: 30 * time.Millisecond, 207 logger: slog.Default(), 208 } 209 210 var wgProcessRPCs sync.WaitGroup 211 wgProcessRPCs.Add(1) 212 go func() { 213 c.processRPCs() 214 wgProcessRPCs.Done() 215 }() 216 217 calls := make([]hrpc.Call, ncalls) 218 for i := range calls { 219 call, err := hrpc.NewGet(context.Background(), []byte("yolo"), []byte("swag")) 220 if err != nil { 221 t.Fatal(err) 222 } 223 call.SetRegion(reg0) 224 calls[i] = call 225 } 226 227 for _, call := range calls { 228 go c.QueueRPC(call) 229 } 230 231 c.Close() 232 233 // check that all calls are not stuck and get an error 234 for _, call := range calls { 235 r := <-call.ResultChan() 236 if r.Error == nil { 237 t.Error("got no error") 238 } 239 } 240 241 wgProcessRPCs.Wait() 242 } 243 244 type rpcMatcher struct { 245 payload []byte 246 } 247 248 func (m rpcMatcher) Matches(x interface{}) bool { 249 data, ok := x.([]byte) 250 if !ok { 251 return false 252 } 253 return bytes.HasSuffix(data, m.payload) 254 } 255 256 func (m rpcMatcher) String() string { 257 return fmt.Sprintf("RPC payload is equal to %q", m.payload) 258 } 259 260 func newRPCMatcher(payload []byte) gomock.Matcher { 261 return rpcMatcher{payload: payload} 262 } 263 264 func TestProcessRPCsWithFail(t *testing.T) { 265 ctrl := test.NewController(t) 266 defer ctrl.Finish() 267 268 mockConn := mock.NewMockConn(ctrl) 269 mockConn.EXPECT().Close() 270 271 ncalls := 100 272 273 c := &client{ 274 conn: mockConn, 275 rpcs: make(chan []hrpc.Call), 276 done: make(chan struct{}), 277 sent: make(map[uint32]hrpc.Call), 278 // never send anything 279 rpcQueueSize: ncalls + 1, 280 flushInterval: 1000 * time.Hour, 281 logger: slog.Default(), 282 } 283 284 var wgProcessRPCs sync.WaitGroup 285 wgProcessRPCs.Add(1) 286 go func() { 287 c.processRPCs() 288 wgProcessRPCs.Done() 289 }() 290 291 calls := make([]hrpc.Call, ncalls) 292 for i := range calls { 293 call, err := hrpc.NewGet(context.Background(), []byte("yolo"), []byte("swag")) 294 if err != nil { 295 t.Fatal(err) 296 } 297 call.SetRegion(reg0) 298 calls[i] = call 299 } 300 301 for _, call := range calls { 302 c.QueueRPC(call) 303 } 304 305 c.fail(errors.New("OOOPS")) 306 307 // check that all calls are not stuck and get an error 308 for _, call := range calls { 309 r := <-call.ResultChan() 310 if r.Error != ErrClientClosed { 311 t.Errorf("got unexpected error %v, expected %v", r.Error, ErrClientClosed) 312 } 313 } 314 315 wgProcessRPCs.Wait() 316 } 317 318 func mockRPCProto(row string) (proto.Message, []byte) { 319 regionType := pb.RegionSpecifier_REGION_NAME 320 r := &pb.RegionSpecifier{ 321 Type: ®ionType, 322 Value: []byte("yolo"), 323 } 324 get := &pb.GetRequest{Region: r, Get: &pb.Get{Row: []byte(row)}} 325 326 var b []byte 327 var err error 328 b = protowire.AppendVarint(b, uint64(proto.Size(get))) 329 b, err = proto.MarshalOptions{}.MarshalAppend(b, get) 330 if err != nil { 331 panic(err) 332 } 333 return get, b 334 } 335 336 func TestQueueRPC(t *testing.T) { 337 ctrl := test.NewController(t) 338 defer ctrl.Finish() 339 340 queueSize := 30 341 flushInterval := 20 * time.Millisecond 342 mockConn := mock.NewMockConn(ctrl) 343 mockConn.EXPECT().SetReadDeadline(gomock.Any()).AnyTimes() 344 c := &client{ 345 conn: mockConn, 346 rpcs: make(chan []hrpc.Call), 347 done: make(chan struct{}), 348 sent: make(map[uint32]hrpc.Call), 349 rpcQueueSize: queueSize, 350 flushInterval: flushInterval, 351 logger: slog.Default(), 352 } 353 var wgProcessRPCs sync.WaitGroup 354 wgProcessRPCs.Add(1) 355 go func() { 356 c.processRPCs() // Writer goroutine 357 wgProcessRPCs.Done() 358 }() 359 360 // define rpcs behaviour 361 var wgWrites sync.WaitGroup 362 calls := make([]hrpc.Call, 100) 363 ctx := context.Background() 364 for i := range calls { 365 wgWrites.Add(1) 366 mockCall := mock.NewMockCall(ctrl) 367 mockCall.EXPECT().Name().Return("Get").Times(1) 368 p, payload := mockRPCProto(fmt.Sprintf("rpc_%d", i)) 369 mockCall.EXPECT().ToProto().Return(p).Times(1) 370 mockCall.EXPECT().Context().Return(ctx).AnyTimes() 371 mockCall.EXPECT().Description().AnyTimes() 372 mockCall.EXPECT().ResultChan().Return(make(chan hrpc.RPCResult, 1)).Times(1) 373 calls[i] = mockCall 374 375 // we expect that it eventually writes to connection 376 mockConn.EXPECT().Write(newRPCMatcher(payload)).Times(1).Return(14+len(payload), nil).Do( 377 func(buf []byte) { wgWrites.Done() }) 378 } 379 380 // queue calls in parallel 381 for _, call := range calls { 382 go func(call hrpc.Call) { 383 c.QueueRPC(call) 384 }(call) 385 } 386 387 // wait till all writes complete 388 wgWrites.Wait() 389 390 // check sent map 391 if len(c.sent) != len(calls) { 392 t.Errorf("expected len(c.sent) be %d, got %d", len(calls), len(c.sent)) 393 } 394 395 var wg sync.WaitGroup 396 // now we fail the regionserver, and try to queue stuff 397 mockConn.EXPECT().Close().Times(1) 398 c.fail(errors.New("ooups")) 399 for i := 0; i < 100; i++ { 400 wg.Add(1) 401 go func() { 402 defer wg.Done() 403 result := make(chan hrpc.RPCResult, 1) 404 mockCall := mock.NewMockCall(ctrl) 405 mockCall.EXPECT().Context().Return(ctx).AnyTimes() 406 mockCall.EXPECT().Description().AnyTimes() 407 mockCall.EXPECT().ResultChan().Return(result).Times(1) 408 c.QueueRPC(mockCall) 409 r := <-result 410 err, ok := r.Error.(ServerError) 411 if !ok { 412 t.Errorf("Expected ServerError error") 413 return 414 } 415 if ErrClientClosed != err { 416 t.Errorf("expected %v, got %v", ErrClientClosed, err) 417 } 418 }() 419 } 420 wg.Wait() 421 wgProcessRPCs.Wait() 422 } 423 424 func TestServerErrorWrite(t *testing.T) { 425 ctrl := test.NewController(t) 426 defer ctrl.Finish() 427 428 queueSize := 1 429 flushInterval := 10 * time.Millisecond 430 mockConn := mock.NewMockConn(ctrl) 431 c := &client{ 432 conn: mockConn, 433 rpcs: make(chan []hrpc.Call), 434 done: make(chan struct{}), 435 sent: make(map[uint32]hrpc.Call), 436 rpcQueueSize: queueSize, 437 flushInterval: flushInterval, 438 logger: slog.Default(), 439 } 440 // define rpcs behaviour 441 mockCall := mock.NewMockCall(ctrl) 442 p, payload := mockRPCProto("rpc") 443 mockCall.EXPECT().ToProto().Return(p).Times(1) 444 mockCall.EXPECT().Name().Return("Get").Times(1) 445 mockCall.EXPECT().Context().Return(context.Background()).AnyTimes() 446 mockCall.EXPECT().Description().AnyTimes() 447 result := make(chan hrpc.RPCResult, 1) 448 mockCall.EXPECT().ResultChan().Return(result).Times(1) 449 // we expect that it eventually writes to connection 450 expErr := errors.New("Write failure") 451 mockConn.EXPECT().Write(newRPCMatcher(payload)).Times(1).Return(0, expErr) 452 mockConn.EXPECT().Close() 453 454 c.QueueRPC(mockCall) 455 // check that processRPCs exists 456 c.processRPCs() 457 r := <-result 458 if ErrClientClosed != r.Error { 459 t.Errorf("expected %v, got %v", ErrClientClosed, r.Error) 460 } 461 if len(c.sent) != 0 { 462 t.Errorf("Expected all awaiting rpcs to be processed, %d left", len(c.sent)) 463 } 464 } 465 466 func TestServerErrorRead(t *testing.T) { 467 ctrl := test.NewController(t) 468 defer ctrl.Finish() 469 470 queueSize := 1 471 flushInterval := 10 * time.Millisecond 472 mockConn := mock.NewMockConn(ctrl) 473 c := &client{ 474 conn: mockConn, 475 rpcs: make(chan []hrpc.Call), 476 done: make(chan struct{}), 477 sent: make(map[uint32]hrpc.Call), 478 rpcQueueSize: queueSize, 479 flushInterval: flushInterval, 480 logger: slog.Default(), 481 } 482 // define rpcs behavior 483 mockCall := mock.NewMockCall(ctrl) 484 result := make(chan hrpc.RPCResult, 1) 485 mockCall.EXPECT().ResultChan().Return(result).Times(1) 486 mockConn.EXPECT().Read(gomock.Any()).Return(0, errors.New("read failure")) 487 mockConn.EXPECT().Close() 488 489 // pretend we already unqueued and sent the rpc 490 c.sent[1] = mockCall 491 // now try receiving result, should call fail 492 c.receiveRPCs() 493 _, more := <-c.done 494 if more { 495 t.Error("expected done to be closed") 496 } 497 // finish reading from c.rpc to clean up the c.sent map 498 c.processRPCs() 499 if len(c.sent) != 0 { 500 t.Errorf("Expected all awaiting rpcs to be processed, %d left", len(c.sent)) 501 } 502 r := <-result 503 if ErrClientClosed != r.Error { 504 t.Errorf("expected %v, got %v", ErrClientClosed, r.Error) 505 } 506 } 507 508 func TestServerErrorExceptionResponse(t *testing.T) { 509 ctrl := test.NewController(t) 510 defer ctrl.Finish() 511 mockConn := mock.NewMockConn(ctrl) 512 mockConn.EXPECT().SetReadDeadline(gomock.Any()).Times(2) 513 c := &client{ 514 conn: mockConn, 515 rpcs: make(chan []hrpc.Call), 516 done: make(chan struct{}), 517 sent: make(map[uint32]hrpc.Call), 518 rpcQueueSize: 1, 519 flushInterval: 1000 * time.Second, 520 } 521 522 rpc, err := hrpc.NewGetStr(context.Background(), "test1", "yolo") 523 if err != nil { 524 t.Fatalf("Failed to create Get request: %s", err) 525 } 526 527 c.registerRPC(rpc) 528 if err := c.inFlightUp(); err != nil { 529 t.Fatal(err) 530 } 531 532 var response []byte 533 header := &pb.ResponseHeader{ 534 CallId: proto.Uint32(1), 535 Exception: &pb.ExceptionResponse{ 536 ExceptionClassName: proto.String( 537 "org.apache.hadoop.hbase.regionserver.RegionServerAbortedException"), 538 StackTrace: proto.String("ooops"), 539 }, 540 } 541 542 response = protowire.AppendVarint(response, uint64(proto.Size(header))) 543 response, err = proto.MarshalOptions{}.MarshalAppend(response, header) 544 if err != nil { 545 t.Fatal(err) 546 } 547 548 mockConn.EXPECT().Read(readBufSizeMatcher{l: 4}).Times(1).Return(4, nil). 549 Do(func(buf []byte) { binary.BigEndian.PutUint32(buf, uint32(len(response))) }) 550 551 mockConn.EXPECT().Read(readBufSizeMatcher{l: len(response)}).Times(1). 552 Return(len(response), nil).Do(func(buf []byte) { copy(buf, response) }) 553 554 expErr := exceptionToError( 555 "org.apache.hadoop.hbase.regionserver.RegionServerAbortedException", "ooops") 556 557 err = c.receive(mockConn) 558 if _, ok := err.(ServerError); !ok { 559 if err.Error() != expErr.Error() { 560 t.Fatalf("expected ServerError with message %q, got %T: %v", expErr, err, err) 561 } 562 } 563 564 re := <-rpc.ResultChan() 565 if re.Error != err { 566 t.Errorf("expected error %v, got %v", err, re.Error) 567 } 568 } 569 570 func TestExceptionToError(t *testing.T) { 571 tcases := []struct { 572 class string 573 stack string 574 out error 575 }{ 576 { 577 class: "java.io.IOException", 578 stack: "ooops", 579 out: errors.New("HBase Java exception java.io.IOException:\nooops"), 580 }, 581 { 582 class: "java.io.IOException", 583 stack: "Cannot append; log is closed\nblahblah", 584 out: NotServingRegionError{errors.New("HBase Java exception java.io.IOException:\n" + 585 "Cannot append; log is closed\nblahblah")}, 586 }, 587 { 588 class: "org.apache.hadoop.hbase.CallQueueTooBigException", 589 stack: "blahblah", 590 out: RetryableError{errors.New( 591 "HBase Java exception org.apache.hadoop.hbase.CallQueueTooBigException:\n" + 592 "blahblah")}, 593 }, 594 } 595 for _, tcase := range tcases { 596 t.Run(tcase.class, func(t *testing.T) { 597 err := exceptionToError(tcase.class, tcase.stack) 598 if !reflect.DeepEqual(err, tcase.out) { 599 t.Fatalf("expected error %q, got error %q", tcase.out, err) 600 } 601 }) 602 } 603 } 604 605 func TestReceiveDecodeProtobufError(t *testing.T) { 606 ctrl := test.NewController(t) 607 defer ctrl.Finish() 608 609 mockConn := mock.NewMockConn(ctrl) 610 c := &client{ 611 conn: mockConn, 612 done: make(chan struct{}), 613 sent: make(map[uint32]hrpc.Call), 614 } 615 616 mockCall := mock.NewMockCall(ctrl) 617 result := make(chan hrpc.RPCResult, 1) 618 mockCall.EXPECT().ResultChan().Return(result).Times(1) 619 mockCall.EXPECT().NewResponse().Return(&pb.MutateResponse{}).Times(1) 620 mockCall.EXPECT().Context().Return(context.Background()).Times(1) 621 622 c.sent[1] = mockCall 623 c.inFlight = 1 624 625 // Append mutate response with a chunk in the middle missing 626 response := []byte{6, 8, 1, 26, 2, 8, 38, 34, 0, 0, 0, 22, 627 0, 0, 0, 4, 0, 4, 121, 111, 108, 111, 2, 99, 102, 115, 119, 97, 103, 0, 0, 0, 0, 0, 0, 628 0, 0, 4, 109, 101, 111, 119} 629 mockConn.EXPECT().Read(readBufSizeMatcher{l: 4}).Times(1).Return(4, nil). 630 Do(func(buf []byte) { binary.BigEndian.PutUint32(buf, uint32(len(response))) }) 631 mockConn.EXPECT().Read(readBufSizeMatcher{l: len(response)}).Times(1). 632 Return(len(response), nil).Do(func(buf []byte) { copy(buf, response) }) 633 mockConn.EXPECT().SetReadDeadline(time.Time{}).Times(1) 634 expErrorPefix := "region.RetryableError: failed to decode the response: proto:" 635 636 err := c.receive(mockConn) 637 if err == nil || strings.HasPrefix(expErrorPefix, err.Error()) { 638 t.Errorf("Expected error prefix %v, got %v", expErrorPefix, err) 639 } 640 641 res := <-result 642 if err != res.Error { 643 t.Errorf("Expected error %v, got %v", err, res.Error) 644 } 645 } 646 647 type callWithCellBlocksError struct{ hrpc.Call } 648 649 func (cwcbe callWithCellBlocksError) DeserializeCellBlocks(proto.Message, []byte) (uint32, error) { 650 return 0, errors.New("OOPS") 651 } 652 653 func TestReceiveDeserializeCellblocksError(t *testing.T) { 654 ctrl := test.NewController(t) 655 defer ctrl.Finish() 656 657 mockConn := mock.NewMockConn(ctrl) 658 c := &client{ 659 conn: mockConn, 660 done: make(chan struct{}), 661 sent: make(map[uint32]hrpc.Call), 662 } 663 664 mockCall := mock.NewMockCall(ctrl) 665 result := make(chan hrpc.RPCResult, 1) 666 mockCall.EXPECT().ResultChan().Return(result).Times(1) 667 mockCall.EXPECT().NewResponse().Return(&pb.MutateResponse{}).Times(1) 668 mockCall.EXPECT().Context().Return(context.Background()).Times(1) 669 670 c.sent[1] = callWithCellBlocksError{mockCall} 671 c.inFlight = 1 672 673 // Append mutate response 674 response := []byte{6, 8, 1, 26, 2, 8, 38, 6, 10, 4, 16, 1, 32, 0, 0, 0, 0, 34, 0, 0, 0, 22, 675 0, 0, 0, 4, 0, 4, 121, 111, 108, 111, 2, 99, 102, 115, 119, 97, 103, 0, 0, 0, 0, 0, 0, 676 0, 0, 4, 109, 101, 111, 119} 677 mockConn.EXPECT().Read(readBufSizeMatcher{l: 4}).Times(1).Return(4, nil). 678 Do(func(buf []byte) { binary.BigEndian.PutUint32(buf, uint32(len(response))) }) 679 mockConn.EXPECT().Read(readBufSizeMatcher{l: len(response)}).Times(1). 680 Return(len(response), nil).Do(func(buf []byte) { copy(buf, response) }) 681 mockConn.EXPECT().SetReadDeadline(time.Time{}).Times(1) 682 expError := errors.New("region.RetryableError: failed to decode the response: OOPS") 683 684 err := c.receive(mockConn) 685 if err == nil || err.Error() != expError.Error() { 686 t.Errorf("Expected error %v, got %v", expError, err) 687 } 688 689 res := <-result 690 if err != res.Error { 691 t.Errorf("Expected error %v, got %v", err, res.Error) 692 } 693 } 694 695 func TestUnexpectedSendError(t *testing.T) { 696 ctrl := test.NewController(t) 697 defer ctrl.Finish() 698 699 queueSize := 1 700 flushInterval := 10 * time.Millisecond 701 mockConn := mock.NewMockConn(ctrl) 702 c := &client{ 703 conn: mockConn, 704 rpcs: make(chan []hrpc.Call), 705 done: make(chan struct{}), 706 sent: make(map[uint32]hrpc.Call), 707 rpcQueueSize: queueSize, 708 flushInterval: flushInterval, 709 logger: slog.Default(), 710 } 711 go c.processRPCs() 712 // define rpcs behaviour 713 mockCall := mock.NewMockCall(ctrl) 714 mockCall.EXPECT().ToProto().Return(nil).Times(1) 715 mockCall.EXPECT().Context().Return(context.Background()).AnyTimes() 716 mockCall.EXPECT().Description().AnyTimes() 717 result := make(chan hrpc.RPCResult, 1) 718 mockCall.EXPECT().ResultChan().Return(result).Times(1) 719 mockCall.EXPECT().Name().Return("Whatever").Times(1) 720 721 c.QueueRPC(mockCall) 722 r := <-result 723 expectedErr := "failed to marshal request: proto: Marshal called with nil" 724 if err := r.Error; err == nil || err.Error() != expectedErr { 725 t.Errorf("expected %q, got %v", expectedErr, err) 726 } 727 if len(c.sent) != 0 { 728 t.Errorf("Expected all awaiting rpcs to be processed, %d left", len(c.sent)) 729 } 730 // stop the go routine 731 mockConn.EXPECT().Close() 732 c.Close() 733 } 734 735 func TestProcessRPCs(t *testing.T) { 736 ctrl := test.NewController(t) 737 defer ctrl.Finish() 738 739 tests := []struct { 740 qsize int 741 interval time.Duration 742 ncalls int 743 minsent int 744 maxsent int 745 concurrent bool 746 }{ 747 {qsize: 100000, interval: 30 * time.Millisecond, ncalls: 100, 748 minsent: 1, maxsent: 1}, 749 {qsize: 2, interval: 1000 * time.Hour, ncalls: 100, minsent: 50, maxsent: 50}, 750 {qsize: 100, interval: 0 * time.Millisecond, ncalls: 1000, 751 minsent: 1, maxsent: 1000, concurrent: true}, 752 } 753 754 for i, tcase := range tests { 755 t.Run(strconv.Itoa(i), func(t *testing.T) { 756 mockConn := mock.NewMockConn(ctrl) 757 mockConn.EXPECT().Close() 758 759 c := &client{ 760 conn: mockConn, 761 rpcs: make(chan []hrpc.Call), 762 done: make(chan struct{}), 763 sent: make(map[uint32]hrpc.Call), 764 rpcQueueSize: tcase.qsize, 765 flushInterval: tcase.interval, 766 logger: slog.Default(), 767 } 768 769 var wgProcessRPCs sync.WaitGroup 770 wgProcessRPCs.Add(1) 771 go func() { 772 c.processRPCs() 773 wgProcessRPCs.Done() 774 }() 775 776 var sent int32 777 var wgWrite sync.WaitGroup 778 wgWrite.Add(tcase.minsent) 779 mockConn.EXPECT().Write(gomock.Any()).MinTimes(tcase.minsent). 780 MaxTimes(tcase.maxsent).Return(42, nil).Do(func(buf []byte) { 781 if atomic.AddInt32(&sent, 1) <= int32(tcase.minsent) { 782 wgWrite.Done() // test will timeout if didn't get at least minsent 783 } 784 }) 785 mockConn.EXPECT().SetReadDeadline(gomock.Any()). 786 MinTimes(tcase.minsent).MaxTimes(tcase.maxsent) 787 788 calls := make([]hrpc.Call, tcase.ncalls) 789 for i := range calls { 790 call, err := hrpc.NewGet(context.Background(), []byte("yolo"), []byte("swag")) 791 if err != nil { 792 t.Fatal(err) 793 } 794 call.SetRegion(reg0) 795 calls[i] = call 796 } 797 798 if tcase.concurrent { 799 var wg sync.WaitGroup 800 for _, call := range calls { 801 wg.Add(1) 802 go func(call hrpc.Call) { 803 c.QueueRPC(call) 804 wg.Done() 805 }(call) 806 } 807 wg.Wait() 808 } else { 809 for _, call := range calls { 810 c.QueueRPC(call) 811 } 812 } 813 814 wgWrite.Wait() 815 816 c.Close() 817 wgProcessRPCs.Wait() 818 t.Log("num batches sent", sent) 819 820 if f := int(c.inFlight); f < tcase.minsent || f > tcase.maxsent { 821 t.Errorf("expected [%d:%d] in-flight rpcs, got %d", 822 tcase.minsent, tcase.maxsent, c.inFlight) 823 } 824 }) 825 } 826 } 827 828 func TestRPCContext(t *testing.T) { 829 ctrl := test.NewController(t) 830 defer ctrl.Finish() 831 queueSize := 10 832 flushInterval := 1000 * time.Second 833 mockConn := mock.NewMockConn(ctrl) 834 mockConn.EXPECT().Close() 835 c := &client{ 836 conn: mockConn, 837 rpcs: make(chan []hrpc.Call), 838 done: make(chan struct{}), 839 sent: make(map[uint32]hrpc.Call), 840 rpcQueueSize: queueSize, 841 flushInterval: flushInterval, 842 logger: slog.Default(), 843 } 844 845 mockConn.EXPECT().SetReadDeadline(gomock.Any()).Times(1) 846 847 // queue rpc with background context 848 mockCall := mock.NewMockCall(ctrl) 849 mockCall.EXPECT().Name().Return("Get").Times(1) 850 p, payload := mockRPCProto("yolo") 851 mockCall.EXPECT().ToProto().Return(p).Times(1) 852 mockCall.EXPECT().Context().Return(context.Background()).AnyTimes() 853 mockCall.EXPECT().Description().AnyTimes() 854 mockCall.EXPECT().ResultChan().Return(make(chan hrpc.RPCResult, 1)).Times(1) 855 mockConn.EXPECT().Write(newRPCMatcher(payload)).Times(1).Return(14+len(payload), nil) 856 c.QueueRPC(mockCall) 857 858 ctxCancel, cancel := context.WithCancel(context.Background()) 859 cancel() 860 callWithCancel := mock.NewMockCall(ctrl) 861 callWithCancel.EXPECT().Context().Return(ctxCancel).AnyTimes() 862 mockCall.EXPECT().Description().AnyTimes() 863 // this shouldn't block 864 c.QueueRPC(callWithCancel) 865 866 if int(c.inFlight) != 1 { 867 t.Errorf("expected %d in-flight rpcs, got %d", 1, c.inFlight) 868 } 869 // clean up 870 c.Close() 871 } 872 873 type readBufSizeMatcher struct { 874 l int 875 } 876 877 func (r readBufSizeMatcher) Matches(x interface{}) bool { 878 data, ok := x.([]byte) 879 if !ok { 880 return false 881 } 882 return len(data) == r.l 883 } 884 885 func (r readBufSizeMatcher) String() string { 886 return fmt.Sprintf("buf size is equal to %q", r.l) 887 } 888 889 func TestSanity(t *testing.T) { 890 ctrl := test.NewController(t) 891 defer ctrl.Finish() 892 mockConn := mock.NewMockConn(ctrl) 893 c := &client{ 894 conn: mockConn, 895 rpcs: make(chan []hrpc.Call), 896 done: make(chan struct{}), 897 sent: make(map[uint32]hrpc.Call), 898 rpcQueueSize: 1, // size one to skip sendBatch 899 flushInterval: 1000 * time.Second, 900 logger: slog.Default(), 901 } 902 903 var wg sync.WaitGroup 904 905 wg.Add(1) 906 go func() { 907 c.processRPCs() 908 wg.Done() 909 }() 910 911 // using Append as it returns cellblocks 912 app, err := hrpc.NewAppStr(context.Background(), "test1", "yolo", 913 map[string]map[string][]byte{"cf": map[string][]byte{"swag": []byte("meow")}}) 914 if err != nil { 915 t.Fatalf("Failed to create Get request: %s", err) 916 } 917 app.SetRegion( 918 NewInfo(0, nil, []byte("test1"), []byte("test1,,lololololololololololo"), nil, nil)) 919 920 mockConn.EXPECT().Write(gomock.Any()).Times(2).Return(0, nil) 921 mockConn.EXPECT().SetReadDeadline(gomock.Any()).Times(1) 922 923 c.QueueRPC(app) 924 925 response := []byte{6, 8, 1, 26, 2, 8, 38, 6, 10, 4, 16, 1, 32, 0, 0, 0, 0, 34, 0, 0, 0, 22, 926 0, 0, 0, 4, 0, 4, 121, 111, 108, 111, 2, 99, 102, 115, 119, 97, 103, 0, 0, 0, 0, 0, 0, 927 0, 0, 4, 109, 101, 111, 119} 928 mockConn.EXPECT().Read(gomock.Any()).Times(1).Return(4, nil). 929 Do(func(buf []byte) { 930 binary.BigEndian.PutUint32(buf, uint32(len(response))) 931 }) 932 mockConn.EXPECT().Read(gomock.Any()).Times(1). 933 Return(len(response), nil).Do(func(buf []byte) { 934 copy(buf, response) 935 936 // stall the next read 937 mockConn.EXPECT().Read(gomock.Any()).MaxTimes(1). 938 Return(0, errors.New("closed")).Do(func(buf []byte) { <-c.done }) 939 }) 940 mockConn.EXPECT().SetReadDeadline(time.Time{}).Times(1) 941 wg.Add(1) 942 go func() { 943 c.receiveRPCs() 944 wg.Done() 945 }() 946 947 re := <-app.ResultChan() 948 if re.Error != nil { 949 t.Error(re.Error) 950 } 951 r, ok := re.Msg.(*pb.MutateResponse) 952 if !ok { 953 t.Fatalf("got unexpected type %T for response", r) 954 } 955 expResult := &pb.Result{ 956 AssociatedCellCount: proto.Int32(1), 957 Stale: proto.Bool(false), 958 Cell: []*pb.Cell{ 959 &pb.Cell{ 960 Row: []byte("yolo"), 961 Family: []byte("cf"), 962 Qualifier: []byte("swag"), 963 Value: []byte("meow"), 964 CellType: pb.CellType_PUT.Enum(), 965 Timestamp: proto.Uint64(0), 966 }, 967 }, 968 } 969 if !proto.Equal(expResult, r.Result) { 970 t.Errorf("expected %v, got %v", expResult, r.Result) 971 } 972 if int(c.inFlight) != 0 { 973 t.Errorf("expected %d in-flight rpcs, got %d", 0, c.inFlight) 974 } 975 976 mockConn.EXPECT().Close().Times(1) 977 c.Close() 978 wg.Wait() 979 } 980 981 func TestSanityCompressor(t *testing.T) { 982 ctrl := test.NewController(t) 983 defer ctrl.Finish() 984 mockConn := mock.NewMockConn(ctrl) 985 c := &client{ 986 conn: mockConn, 987 rpcs: make(chan []hrpc.Call), 988 done: make(chan struct{}), 989 sent: make(map[uint32]hrpc.Call), 990 rpcQueueSize: 1, // size one to skip sendBatch 991 flushInterval: 1000 * time.Second, 992 compressor: &compressor{Codec: mockCodec{}}, 993 logger: slog.Default(), 994 } 995 996 var wg sync.WaitGroup 997 998 wg.Add(1) 999 go func() { 1000 c.processRPCs() 1001 wg.Done() 1002 }() 1003 1004 // using Append as it returns cellblocks 1005 app, err := hrpc.NewAppStr(context.Background(), "test1", "yolo", 1006 map[string]map[string][]byte{"cf": map[string][]byte{"swag": []byte("meow")}}) 1007 if err != nil { 1008 t.Fatalf("Failed to create Get request: %s", err) 1009 } 1010 app.SetRegion( 1011 NewInfo(0, nil, []byte("test1"), []byte("test1,,lololololololololololo"), nil, nil)) 1012 1013 compressedCellblocks := "\x00\x00\x00&" + 1014 "\x00\x00\x00\n\x00\x00\x00\"\x00\x00\x00\x16\x00\x00" + 1015 "\x00\x00\x00\n\x00\x04\x00\x04yolo\x02c" + 1016 "\x00\x00\x00\nfswag\u007f\xff\xff\xff\xff" + 1017 "\x00\x00\x00\b\xff\xff\xff\x04meow" 1018 1019 mockConn.EXPECT().Write([]byte("\x00\x00\x00}\x10\b\x01\x1a\x06Mutate \x01*\x02\b:1\n!"+ 1020 "\b\x01\x12\x1dtest1,,lololololololololololo\x12\f\n\x04yolo\x10\x000\x00@\x01")).Return( 1021 71, nil) 1022 mockConn.EXPECT().Write([]byte(compressedCellblocks)).Return(58, nil) 1023 mockConn.EXPECT().SetReadDeadline(gomock.Any()) 1024 1025 c.QueueRPC(app) 1026 1027 header := &pb.ResponseHeader{ 1028 CallId: proto.Uint32(1), 1029 CellBlockMeta: &pb.CellBlockMeta{ 1030 Length: proto.Uint32(58), 1031 }, 1032 } 1033 1034 mutate := &pb.MutateResponse{ 1035 Result: &pb.Result{ 1036 AssociatedCellCount: proto.Int32(1), 1037 Stale: proto.Bool(false), 1038 }, 1039 } 1040 1041 var response []byte 1042 response = protowire.AppendVarint(response, uint64(proto.Size(header))) 1043 response, err = proto.MarshalOptions{}.MarshalAppend(response, header) 1044 if err != nil { 1045 t.Fatal(err) 1046 } 1047 response = protowire.AppendVarint(response, uint64(proto.Size(mutate))) 1048 response, err = proto.MarshalOptions{}.MarshalAppend(response, mutate) 1049 if err != nil { 1050 t.Fatal(err) 1051 } 1052 1053 response = append(response, []byte(compressedCellblocks)...) 1054 mockConn.EXPECT().Read(gomock.Any()).Times(1).Return(4, nil). 1055 Do(func(buf []byte) { 1056 binary.BigEndian.PutUint32(buf, uint32(len(response))) 1057 }) 1058 mockConn.EXPECT().Read(gomock.Any()).Times(1). 1059 Return(len(response), nil).Do(func(buf []byte) { 1060 copy(buf, response) 1061 1062 // stall the next read 1063 mockConn.EXPECT().Read(gomock.Any()).MaxTimes(1). 1064 Return(0, errors.New("closed")).Do(func(buf []byte) { <-c.done }) 1065 }) 1066 mockConn.EXPECT().SetReadDeadline(time.Time{}).Times(1) 1067 wg.Add(1) 1068 go func() { 1069 c.receiveRPCs() 1070 wg.Done() 1071 }() 1072 1073 re := <-app.ResultChan() 1074 if re.Error != nil { 1075 t.Error(re.Error) 1076 } 1077 r, ok := re.Msg.(*pb.MutateResponse) 1078 if !ok { 1079 t.Fatalf("got unexpected type %T for response", r) 1080 } 1081 expResult := &pb.Result{ 1082 AssociatedCellCount: proto.Int32(1), 1083 Stale: proto.Bool(false), 1084 Cell: []*pb.Cell{ 1085 &pb.Cell{ 1086 Row: []byte("yolo"), 1087 Family: []byte("cf"), 1088 Qualifier: []byte("swag"), 1089 Value: []byte("meow"), 1090 CellType: pb.CellType_PUT.Enum(), 1091 Timestamp: proto.Uint64(math.MaxInt64), 1092 }, 1093 }, 1094 } 1095 if !proto.Equal(expResult, r.Result) { 1096 t.Errorf("expected %v, got %v", expResult, r.Result) 1097 } 1098 if int(c.inFlight) != 0 { 1099 t.Errorf("expected %d in-flight rpcs, got %d", 0, c.inFlight) 1100 } 1101 1102 mockConn.EXPECT().Close().Times(1) 1103 c.Close() 1104 wg.Wait() 1105 } 1106 1107 func BenchmarkSendBatchMemory(b *testing.B) { 1108 ctrl := test.NewController(b) 1109 defer ctrl.Finish() 1110 mockConn := mock.NewMockConn(ctrl) 1111 c := &client{ 1112 conn: mockConn, 1113 rpcs: make(chan []hrpc.Call), 1114 done: make(chan struct{}), 1115 sent: make(map[uint32]hrpc.Call), 1116 // queue size is 1 so that all QueueRPC calls trigger sendBatch, 1117 // and buffer slice reset 1118 rpcQueueSize: 1, 1119 flushInterval: 1000 * time.Second, 1120 logger: slog.Default(), 1121 } 1122 1123 var wgWrites sync.WaitGroup 1124 ctx := context.Background() 1125 mockCall := mock.NewMockCall(ctrl) 1126 mockCall.EXPECT().Name().Return("Get").AnyTimes() 1127 p, _ := mockRPCProto("rpc") 1128 mockCall.EXPECT().ToProto().Return(p).AnyTimes() 1129 mockCall.EXPECT().Context().Return(ctx).AnyTimes() 1130 mockConn.EXPECT().Write(gomock.Any()).AnyTimes().Return(0, nil).Do(func(buf []byte) { 1131 wgWrites.Done() 1132 }) 1133 mockConn.EXPECT().SetReadDeadline(gomock.Any()).AnyTimes() 1134 1135 go c.processRPCs() 1136 b.ResetTimer() 1137 for n := 0; n < b.N; n++ { 1138 for i := 0; i < 100; i++ { 1139 wgWrites.Add(1) 1140 c.QueueRPC(mockCall) 1141 } 1142 wgWrites.Wait() 1143 } 1144 // we don't care about cleaning up 1145 } 1146 1147 func BenchmarkSetReadDeadline(b *testing.B) { 1148 l, err := net.Listen("tcp", "localhost:0") 1149 if err != nil { 1150 b.Fatal(err) 1151 } 1152 var wg sync.WaitGroup 1153 wg.Add(1) 1154 go func() { 1155 conn, err := l.Accept() 1156 if err != nil { 1157 b.Error(err) 1158 } 1159 wg.Done() 1160 conn.Close() 1161 }() 1162 1163 conn, err := net.Dial("tcp", l.Addr().String()) 1164 if err != nil { 1165 b.Fatal(err) 1166 } 1167 1168 b.ResetTimer() 1169 for i := 0; i < b.N; i++ { 1170 if err := conn.SetReadDeadline(time.Now().Add(DefaultReadTimeout)); err != nil { 1171 b.Fatal(err) 1172 } 1173 } 1174 b.StopTimer() 1175 1176 conn.Close() 1177 l.Close() 1178 wg.Wait() 1179 } 1180 1181 func TestBuffer(t *testing.T) { 1182 size := 42 1183 b := newBuffer(size) 1184 if cap(b) < size { 1185 t.Fatalf("Excpected cap >= %d, got %d", size, cap(b)) 1186 } 1187 if len(b) != size { 1188 t.Fatalf("Excpected len %d, got %d", size, len(b)) 1189 } 1190 freeBuffer(b) 1191 1192 size = 40 1193 b = newBuffer(size) 1194 if cap(b) < size { 1195 t.Fatalf("Excpected cap >= %d, got %d", size, cap(b)) 1196 } 1197 if len(b) != size { 1198 t.Fatalf("Excpected len %d, got %d", size, len(b)) 1199 } 1200 freeBuffer(b) 1201 1202 size = 45 1203 b = newBuffer(size) 1204 if cap(b) < size { 1205 t.Fatalf("Excpected cap >= %d, got %d", size, cap(b)) 1206 } 1207 if len(b) != size { 1208 t.Fatalf("Excpected len %d, got %d", size, len(b)) 1209 } 1210 freeBuffer(b) 1211 } 1212 1213 func TestMarshalJSON(t *testing.T) { 1214 ctrl := test.NewController(t) 1215 defer ctrl.Finish() 1216 1217 var localAddr net.Addr 1218 var remoteAddr net.Addr 1219 var id uint32 = 111 1220 tcp := "tcp" 1221 localIp := []byte("172.16.254.1") 1222 remoteIp := []byte("10.16.254.1") 1223 localAddr = &net.TCPAddr{IP: localIp, Port: 0, Zone: "testZone"} 1224 remoteAddr = &net.TCPAddr{IP: remoteIp, Port: 0, Zone: "testZone"} 1225 1226 mockConn := mock.NewMockConn(ctrl) 1227 mockConn.EXPECT().LocalAddr().Return(localAddr) 1228 mockConn.EXPECT().RemoteAddr().Return(remoteAddr) 1229 c := &client{ 1230 conn: mockConn, 1231 addr: "testRegionServerAddress", 1232 ctype: RegionClient, 1233 rpcs: make(chan []hrpc.Call), 1234 done: make(chan struct{}), 1235 sent: make(map[uint32]hrpc.Call), 1236 rpcQueueSize: 1, // size one to skip sendBatch 1237 flushInterval: 1000 * time.Second, 1238 compressor: &compressor{Codec: mockCodec{}}, 1239 id: id, 1240 } 1241 1242 jsonVal, err := c.MarshalJSON() 1243 1244 if err != nil { 1245 t.Fatalf("Did not expect Error to be thrown: %v", err) 1246 } 1247 1248 var jsonUnMarshal map[string]interface{} 1249 err = json.Unmarshal(jsonVal, &jsonUnMarshal) 1250 1251 if err != nil { 1252 t.Fatalf("Error while unmarshalling JSON, %v", err) 1253 } 1254 1255 actualLocalAddr := jsonUnMarshal["ConnectionLocalAddress"].(map[string]interface{}) 1256 actualRemoteAddr := jsonUnMarshal["ConnectionRemoteAddress"].(map[string]interface{}) 1257 1258 assert.Equal(t, tcp, actualLocalAddr["Network"]) 1259 assert.Equal(t, tcp, actualRemoteAddr["Network"]) 1260 assert.Equal(t, string(RegionClient), jsonUnMarshal["ClientType"]) 1261 assert.Equal(t, float64(0), jsonUnMarshal["InFlight"]) 1262 assert.Equal(t, float64(id), jsonUnMarshal["Id"]) 1263 1264 } 1265 1266 func TestMarshalJSONNilValues(t *testing.T) { 1267 ctrl := test.NewController(t) 1268 defer ctrl.Finish() 1269 1270 c := &client{ 1271 conn: nil, 1272 rpcs: nil, 1273 done: nil, 1274 sent: nil, 1275 } 1276 1277 _, err := c.MarshalJSON() 1278 if err != nil { 1279 t.Fatalf("Did not expect Error to be thrown: %v", err) 1280 } 1281 } 1282 1283 func BenchmarkSendScanRequest(b *testing.B) { 1284 var ( 1285 table = []byte("table") 1286 startRow = []byte("startRow") 1287 stopRow = []byte("stopRow") 1288 ) 1289 b.Run("NewScan", func(b *testing.B) { 1290 b.ReportAllocs() 1291 for i := 0; i < b.N; i++ { 1292 _, err := hrpc.NewScanRange(context.Background(), table, startRow, stopRow) 1293 if err != nil { 1294 b.Fatal(err) 1295 } 1296 } 1297 }) 1298 1299 s, err := hrpc.NewScanRange(context.Background(), table, startRow, stopRow) 1300 if err != nil { 1301 b.Fatal(err) 1302 } 1303 s.SetRegion( 1304 NewInfo(0, nil, []byte("reg0"), 1305 []byte("reg0,,1234567890042.56f833d5569a27c7a43fbf547b4924a4."), nil, nil)) 1306 b.Run("ToProto", func(b *testing.B) { 1307 b.ReportAllocs() 1308 for i := 0; i < b.N; i++ { 1309 s.ToProto() 1310 } 1311 }) 1312 1313 request := s.ToProto() 1314 b.Run("marshalProto", func(b *testing.B) { 1315 b.ReportAllocs() 1316 for i := 0; i < b.N; i++ { 1317 marshalProto(s, 42, request, 0) 1318 } 1319 }) 1320 }