github.com/tsuna/gohbase@v0.0.0-20250731002811-4ffcadfba63e/region/multi_test.go (about) 1 // Copyright (C) 2017 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 "errors" 12 "fmt" 13 "sort" 14 "strconv" 15 "testing" 16 17 "github.com/tsuna/gohbase/hrpc" 18 "github.com/tsuna/gohbase/pb" 19 "github.com/tsuna/gohbase/test" 20 "google.golang.org/protobuf/proto" 21 ) 22 23 type bytesSlice [][]byte 24 25 func (p bytesSlice) Len() int { return len(p) } 26 func (p bytesSlice) Less(i, j int) bool { return bytes.Compare(p[i], p[j]) < 0 } 27 func (p bytesSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] } 28 29 type RegionActions []*pb.RegionAction 30 31 func (a RegionActions) Swap(i, j int) { a[i], a[j] = a[j], a[i] } 32 func (a RegionActions) Len() int { return len(a) } 33 func (a RegionActions) Less(i, j int) bool { 34 return bytes.Compare(a[i].Region.Value, a[j].Region.Value) < 0 35 } 36 37 var ( 38 reg0 = NewInfo(0, nil, []byte("reg0"), 39 []byte("reg0,,1234567890042.56f833d5569a27c7a43fbf547b4924a4."), nil, nil) 40 reg1 = NewInfo(0, nil, []byte("reg1"), 41 []byte("reg1,,1234567890042.56f833d5569a27c7a43fbf547b4924a4."), nil, nil) 42 reg2 = NewInfo(0, nil, []byte("reg2"), 43 []byte("reg2,,1234567890042.56f833d5569a27c7a43fbf547b4924a4."), nil, nil) 44 ) 45 46 func bytesSlicesEqual(a, b [][]byte) bool { 47 if len(a) != len(b) { 48 return false 49 } 50 for i := range a { 51 if !bytes.Equal(a[i], b[i]) { 52 return false 53 } 54 } 55 return true 56 } 57 58 func bytesSlicesLen(a [][]byte) uint32 { 59 var l uint32 60 for _, b := range a { 61 l += uint32(len(b)) 62 } 63 return l 64 } 65 66 func TestMultiToProto(t *testing.T) { 67 ctrl := test.NewController(t) 68 defer ctrl.Finish() 69 70 values := map[string]map[string][]byte{ 71 "cf": map[string][]byte{ 72 "c": []byte("v"), 73 }, 74 } 75 valuesProto := []*pb.MutationProto_ColumnValue{ 76 &pb.MutationProto_ColumnValue{ 77 Family: []byte("cf"), 78 QualifierValue: []*pb.MutationProto_ColumnValue_QualifierValue{ 79 &pb.MutationProto_ColumnValue_QualifierValue{ 80 Qualifier: []byte("c"), 81 Value: []byte("v"), 82 }, 83 }, 84 }, 85 } 86 delValues := map[string]map[string][]byte{ 87 "cf": map[string][]byte{ 88 "c": nil, 89 }, 90 } 91 delProto := []*pb.MutationProto_ColumnValue{ 92 &pb.MutationProto_ColumnValue{ 93 Family: []byte("cf"), 94 QualifierValue: []*pb.MutationProto_ColumnValue_QualifierValue{ 95 &pb.MutationProto_ColumnValue_QualifierValue{ 96 Qualifier: []byte("c"), 97 DeleteType: pb.MutationProto_DELETE_MULTIPLE_VERSIONS.Enum(), 98 }, 99 }, 100 }, 101 } 102 appendProto := []*pb.MutationProto_ColumnValue{ 103 &pb.MutationProto_ColumnValue{ 104 Family: []byte("cf"), 105 QualifierValue: []*pb.MutationProto_ColumnValue_QualifierValue{ 106 &pb.MutationProto_ColumnValue_QualifierValue{ 107 Qualifier: []byte("c"), 108 }, 109 }, 110 }, 111 } 112 113 tests := []struct { 114 calls []hrpc.Call 115 out *pb.MultiRequest 116 panicMsg string 117 cellblocksProto *pb.MultiRequest 118 cellblocks [][]byte 119 cellblocksLen uint32 120 }{ 121 { 122 calls: func() []hrpc.Call { 123 cs := make([]hrpc.Call, 5) 124 cs[0], _ = hrpc.NewGetStr(context.Background(), "reg0", "call0") 125 cs[0].SetRegion(reg0) 126 cs[1], _ = hrpc.NewPutStr(context.Background(), "reg0", "call1", values) 127 cs[1].SetRegion(reg0) 128 cs[2], _ = hrpc.NewAppStr(context.Background(), "reg1", "call2", values) 129 cs[2].SetRegion(reg1) 130 cs[3], _ = hrpc.NewDelStr(context.Background(), "reg1", "call3", delValues) 131 cs[3].SetRegion(reg1) 132 cs[4], _ = hrpc.NewIncStr(context.Background(), "reg2", "call4", delValues) 133 cs[4].SetRegion(reg2) 134 return cs 135 }(), 136 out: &pb.MultiRequest{ 137 RegionAction: []*pb.RegionAction{ 138 &pb.RegionAction{ 139 Region: &pb.RegionSpecifier{ 140 Type: pb.RegionSpecifier_REGION_NAME.Enum(), 141 Value: []byte("reg0,,1234567890042.56f833d5569a27c7a43fbf547b4924a4."), 142 }, 143 Action: []*pb.Action{ 144 &pb.Action{Index: proto.Uint32(1), Get: &pb.Get{ 145 Row: []byte("call0"), TimeRange: &pb.TimeRange{}}}, 146 &pb.Action{Index: proto.Uint32(2), Mutation: &pb.MutationProto{ 147 Row: []byte("call1"), 148 MutateType: pb.MutationProto_PUT.Enum(), 149 Durability: pb.MutationProto_USE_DEFAULT.Enum(), 150 ColumnValue: valuesProto, 151 }}, 152 }, 153 }, 154 &pb.RegionAction{ 155 Region: &pb.RegionSpecifier{ 156 Type: pb.RegionSpecifier_REGION_NAME.Enum(), 157 Value: []byte("reg1,,1234567890042.56f833d5569a27c7a43fbf547b4924a4."), 158 }, 159 Action: []*pb.Action{ 160 &pb.Action{Index: proto.Uint32(3), Mutation: &pb.MutationProto{ 161 Row: []byte("call2"), 162 MutateType: pb.MutationProto_APPEND.Enum(), 163 Durability: pb.MutationProto_USE_DEFAULT.Enum(), 164 ColumnValue: valuesProto, 165 }}, 166 &pb.Action{Index: proto.Uint32(4), Mutation: &pb.MutationProto{ 167 Row: []byte("call3"), 168 MutateType: pb.MutationProto_DELETE.Enum(), 169 Durability: pb.MutationProto_USE_DEFAULT.Enum(), 170 ColumnValue: delProto, 171 }}, 172 }, 173 }, 174 &pb.RegionAction{ 175 Region: &pb.RegionSpecifier{ 176 Type: pb.RegionSpecifier_REGION_NAME.Enum(), 177 Value: []byte("reg2,,1234567890042.56f833d5569a27c7a43fbf547b4924a4."), 178 }, 179 Action: []*pb.Action{ 180 &pb.Action{Index: proto.Uint32(5), Mutation: &pb.MutationProto{ 181 Row: []byte("call4"), 182 MutateType: pb.MutationProto_INCREMENT.Enum(), 183 Durability: pb.MutationProto_USE_DEFAULT.Enum(), 184 ColumnValue: appendProto, 185 }}, 186 }, 187 }, 188 }, 189 }, 190 cellblocksProto: &pb.MultiRequest{ 191 RegionAction: []*pb.RegionAction{ 192 &pb.RegionAction{ 193 Region: &pb.RegionSpecifier{ 194 Type: pb.RegionSpecifier_REGION_NAME.Enum(), 195 Value: []byte("reg0,,1234567890042.56f833d5569a27c7a43fbf547b4924a4."), 196 }, 197 Action: []*pb.Action{ 198 &pb.Action{Index: proto.Uint32(1), Get: &pb.Get{ 199 Row: []byte("call0"), TimeRange: &pb.TimeRange{}}}, 200 &pb.Action{Index: proto.Uint32(2), Mutation: &pb.MutationProto{ 201 Row: []byte("call1"), 202 MutateType: pb.MutationProto_PUT.Enum(), 203 Durability: pb.MutationProto_USE_DEFAULT.Enum(), 204 AssociatedCellCount: proto.Int32(1), 205 }}, 206 }, 207 }, 208 &pb.RegionAction{ 209 Region: &pb.RegionSpecifier{ 210 Type: pb.RegionSpecifier_REGION_NAME.Enum(), 211 Value: []byte("reg1,,1234567890042.56f833d5569a27c7a43fbf547b4924a4."), 212 }, 213 Action: []*pb.Action{ 214 &pb.Action{Index: proto.Uint32(3), Mutation: &pb.MutationProto{ 215 Row: []byte("call2"), 216 MutateType: pb.MutationProto_APPEND.Enum(), 217 Durability: pb.MutationProto_USE_DEFAULT.Enum(), 218 AssociatedCellCount: proto.Int32(1), 219 }}, 220 &pb.Action{Index: proto.Uint32(4), Mutation: &pb.MutationProto{ 221 Row: []byte("call3"), 222 MutateType: pb.MutationProto_DELETE.Enum(), 223 Durability: pb.MutationProto_USE_DEFAULT.Enum(), 224 AssociatedCellCount: proto.Int32(1), 225 }}, 226 }, 227 }, 228 &pb.RegionAction{ 229 Region: &pb.RegionSpecifier{ 230 Type: pb.RegionSpecifier_REGION_NAME.Enum(), 231 Value: []byte("reg2,,1234567890042.56f833d5569a27c7a43fbf547b4924a4."), 232 }, 233 Action: []*pb.Action{ 234 &pb.Action{Index: proto.Uint32(5), Mutation: &pb.MutationProto{ 235 Row: []byte("call4"), 236 MutateType: pb.MutationProto_INCREMENT.Enum(), 237 Durability: pb.MutationProto_USE_DEFAULT.Enum(), 238 AssociatedCellCount: proto.Int32(1), 239 }}, 240 }, 241 }, 242 }, 243 }, 244 cellblocksLen: 130, 245 cellblocks: [][]byte{ 246 []byte("\x00\x00\x00\x1d\x00\x00\x00\x14\x00\x00\x00\x01\x00\x05" + 247 "call1" + "\x02" + "cf" + "c" + 248 "\u007f\xff\xff\xff\xff\xff\xff\xff\x04" + "v"), 249 []byte("\x00\x00\x00\x1d\x00\x00\x00\x14\x00\x00\x00\x01\x00\x05" + 250 "call2" + "\x02" + "cf" + "c" + 251 "\u007f\xff\xff\xff\xff\xff\xff\xff\x04" + "v"), 252 []byte("\x00\x00\x00\x1c\x00\x00\x00\x14\x00\x00\x00\x00\x00\x05" + 253 "call3" + "\x02" + "cf" + "c" + 254 "\u007f\xff\xff\xff\xff\xff\xff\xff\f"), 255 []byte("\x00\x00\x00\x1c\x00\x00\x00\x14\x00\x00\x00\x00\x00\x05" + 256 "call4" + "\x02" + "cf" + "c" + 257 "\u007f\xff\xff\xff\xff\xff\xff\xff\x04")}, 258 }, 259 { // one call with expired context 260 calls: func() []hrpc.Call { 261 cs := make([]hrpc.Call, 2) 262 ctx, cancel := context.WithCancel(context.Background()) 263 cancel() 264 cs[0], _ = hrpc.NewGetStr(ctx, "reg0", "call0") 265 cs[0].SetRegion(reg0) 266 cs[1], _ = hrpc.NewAppStr(context.Background(), "reg0", "call1", nil) 267 cs[1].SetRegion(reg0) 268 return cs 269 }(), 270 out: &pb.MultiRequest{ 271 RegionAction: []*pb.RegionAction{ 272 &pb.RegionAction{ 273 Region: &pb.RegionSpecifier{ 274 Type: pb.RegionSpecifier_REGION_NAME.Enum(), 275 Value: []byte("reg0,,1234567890042.56f833d5569a27c7a43fbf547b4924a4."), 276 }, 277 Action: []*pb.Action{ 278 &pb.Action{Index: proto.Uint32(2), Mutation: &pb.MutationProto{ 279 Row: []byte("call1"), 280 MutateType: pb.MutationProto_APPEND.Enum(), 281 Durability: pb.MutationProto_USE_DEFAULT.Enum(), 282 }}, 283 }, 284 }, 285 }, 286 }, 287 cellblocksProto: &pb.MultiRequest{ 288 RegionAction: []*pb.RegionAction{ 289 &pb.RegionAction{ 290 Region: &pb.RegionSpecifier{ 291 Type: pb.RegionSpecifier_REGION_NAME.Enum(), 292 Value: []byte("reg0,,1234567890042.56f833d5569a27c7a43fbf547b4924a4."), 293 }, 294 Action: []*pb.Action{ 295 &pb.Action{Index: proto.Uint32(2), Mutation: &pb.MutationProto{ 296 Row: []byte("call1"), 297 MutateType: pb.MutationProto_APPEND.Enum(), 298 Durability: pb.MutationProto_USE_DEFAULT.Enum(), 299 AssociatedCellCount: proto.Int32(0), 300 }}, 301 }, 302 }, 303 }, 304 }, 305 }, 306 { // one batched call is not supported for batching 307 calls: func() []hrpc.Call { 308 cs := make([]hrpc.Call, 2) 309 cs[0], _ = hrpc.NewGetStr(context.Background(), "reg0", "yolo") 310 cs[0].SetRegion(reg0) 311 312 cs[1] = hrpc.NewCreateTable(context.Background(), []byte("yolo"), nil) 313 return cs 314 }(), 315 panicMsg: "unsupported call type for Multi: *hrpc.CreateTable", 316 }, 317 } 318 319 for i, tcase := range tests { 320 t.Run(strconv.Itoa(i), func(t *testing.T) { 321 m := newMulti(1000) 322 323 if m.add(tcase.calls) { 324 t.Fatal("multi is full") 325 } 326 327 if tcase.panicMsg != "" { 328 defer func() { 329 r := recover() 330 msg, ok := r.(string) 331 if r == nil || !ok || msg != tcase.panicMsg { 332 t.Errorf("expected panic with %q, got %v", tcase.panicMsg, r) 333 } 334 }() 335 _ = m.ToProto() 336 return 337 } 338 339 // test proto 340 p := m.ToProto() 341 342 out, ok := p.(*pb.MultiRequest) 343 if !ok { 344 t.Fatalf("unexpected proto type %T", p) 345 } 346 347 // check that we recorded correct ordering RegionActions 348 if exp, got := len(m.regions), len(out.RegionAction); exp != got { 349 t.Fatalf("expected regions length %d, got %d", exp, got) 350 } 351 for i, r := range m.regions { 352 if exp, got := r.Name(), out.RegionAction[i].Region.Value; !bytes.Equal(exp, got) { 353 t.Fatalf("regions are different at index %d: %q != %q", i, exp, got) 354 } 355 } 356 357 // compare that the MultiRequest are as expected 358 sort.Sort(RegionActions(tcase.out.RegionAction)) 359 sort.Sort(RegionActions(out.RegionAction)) 360 361 if !proto.Equal(tcase.out, out) { 362 t.Fatalf("expected %v, got %v", tcase.out, out) 363 } 364 365 // test cellblocks 366 m = newMulti(1000) 367 if m.add(tcase.calls) { 368 t.Fatal("multi is full") 369 } 370 cellblocksProto, cellblocks, cellblocksLen := m.SerializeCellBlocks(nil) 371 out, ok = cellblocksProto.(*pb.MultiRequest) 372 if !ok { 373 t.Fatalf("unexpected proto type %T", cellblocksProto) 374 } 375 376 sort.Sort(RegionActions(tcase.cellblocksProto.RegionAction)) 377 sort.Sort(RegionActions(out.RegionAction)) 378 379 if !proto.Equal(tcase.cellblocksProto, out) { 380 t.Errorf("expected cellblocks proto %v, got %v", 381 tcase.cellblocksProto, cellblocksProto) 382 } 383 384 if cellblocksLen != tcase.cellblocksLen { 385 t.Errorf("expected cellblocks length %d, got %d", 386 tcase.cellblocksLen, cellblocksLen) 387 } 388 389 // check total length matches the returned 390 if l := bytesSlicesLen(cellblocks); l != cellblocksLen { 391 t.Errorf("total length of cellblocks %d doesn't match returned %d", 392 l, cellblocksLen) 393 } 394 395 // because maps are iterated in random order, the best we can do here 396 // is sort cellblocks and make sure that each byte slice equals. This doesn't 397 // test that byte slices are written out in the correct order. 398 expected := make([][]byte, len(tcase.cellblocks)) 399 copy(expected, tcase.cellblocks) 400 got := make([][]byte, len(cellblocks)) 401 copy(got, cellblocks) 402 403 sort.Sort(bytesSlice(expected)) 404 sort.Sort(bytesSlice(got)) 405 if !bytesSlicesEqual(expected, got) { 406 t.Errorf("expected cellblocks %q, got %q", tcase.cellblocks, cellblocks) 407 } 408 }) 409 } 410 } 411 412 func TestMultiReturnResults(t *testing.T) { 413 tests := []struct { 414 calls []hrpc.Call 415 regions []hrpc.RegionInfo 416 response proto.Message 417 err error 418 shouldPanic bool 419 out []hrpc.RPCResult 420 }{ 421 { // all good 422 calls: func() []hrpc.Call { 423 cs := make([]hrpc.Call, 5) 424 cs[0], _ = hrpc.NewGetStr(context.Background(), "reg0", "call0") 425 cs[0].SetRegion(reg0) 426 cs[1], _ = hrpc.NewAppStr(context.Background(), "reg1", "call1", nil) 427 cs[1].SetRegion(reg1) 428 cs[2], _ = hrpc.NewAppStr(context.Background(), "reg0", "call2", nil) 429 cs[2].SetRegion(reg0) 430 cs[3], _ = hrpc.NewGetStr(context.Background(), "reg2", "call3") 431 cs[3].SetRegion(reg2) 432 cs[4], _ = hrpc.NewAppStr(context.Background(), "reg1", "call4", nil) 433 cs[4].SetRegion(reg1) 434 return cs 435 }(), 436 regions: []hrpc.RegionInfo{reg2, reg0, reg1}, 437 response: &pb.MultiResponse{ 438 RegionActionResult: []*pb.RegionActionResult{ 439 // reg2 440 &pb.RegionActionResult{ 441 ResultOrException: []*pb.ResultOrException{ 442 &pb.ResultOrException{ 443 Index: proto.Uint32(4), 444 Result: &pb.Result{ 445 Cell: []*pb.Cell{&pb.Cell{Row: []byte("call3")}}, 446 }, 447 }, 448 }, 449 }, 450 // reg0 451 &pb.RegionActionResult{ 452 ResultOrException: []*pb.ResultOrException{ 453 &pb.ResultOrException{ 454 Index: proto.Uint32(1), 455 Result: &pb.Result{ 456 Cell: []*pb.Cell{&pb.Cell{Row: []byte("call0")}}, 457 }, 458 }, 459 &pb.ResultOrException{ 460 Index: proto.Uint32(3), 461 Result: &pb.Result{ 462 Cell: []*pb.Cell{&pb.Cell{Row: []byte("call2")}}, 463 }, 464 }, 465 }, 466 }, 467 // reg1, results are returned in different order 468 &pb.RegionActionResult{ 469 ResultOrException: []*pb.ResultOrException{ 470 &pb.ResultOrException{ 471 Index: proto.Uint32(5), 472 Result: &pb.Result{ 473 Cell: []*pb.Cell{&pb.Cell{Row: []byte("call4")}}, 474 }, 475 }, 476 &pb.ResultOrException{ 477 Index: proto.Uint32(2), 478 Result: &pb.Result{ 479 Cell: []*pb.Cell{&pb.Cell{Row: []byte("call1")}}, 480 }, 481 }, 482 }, 483 }, 484 }, 485 }, 486 out: []hrpc.RPCResult{ 487 hrpc.RPCResult{Msg: &pb.GetResponse{Result: &pb.Result{ 488 Cell: []*pb.Cell{&pb.Cell{Row: []byte("call0")}}, 489 }}}, 490 hrpc.RPCResult{Msg: &pb.MutateResponse{Result: &pb.Result{ 491 Cell: []*pb.Cell{&pb.Cell{Row: []byte("call1")}}, 492 }}}, 493 hrpc.RPCResult{Msg: &pb.MutateResponse{Result: &pb.Result{ 494 Cell: []*pb.Cell{&pb.Cell{Row: []byte("call2")}}, 495 }}}, 496 hrpc.RPCResult{Msg: &pb.GetResponse{Result: &pb.Result{ 497 Cell: []*pb.Cell{&pb.Cell{Row: []byte("call3")}}, 498 }}}, 499 hrpc.RPCResult{Msg: &pb.MutateResponse{Result: &pb.Result{ 500 Cell: []*pb.Cell{&pb.Cell{Row: []byte("call4")}}, 501 }}}, 502 }, 503 }, 504 { // a region exception 505 calls: func() []hrpc.Call { 506 cs := make([]hrpc.Call, 4) 507 cs[0], _ = hrpc.NewGetStr(context.Background(), "reg0", "call0") 508 cs[0].SetRegion(reg0) 509 cs[1], _ = hrpc.NewAppStr(context.Background(), "reg1", "call1", nil) 510 cs[1].SetRegion(reg1) 511 cs[2], _ = hrpc.NewAppStr(context.Background(), "reg0", "call2", nil) 512 cs[2].SetRegion(reg0) 513 cs[3], _ = hrpc.NewAppStr(context.Background(), "reg1", "call3", nil) 514 cs[3].SetRegion(reg1) 515 return cs 516 }(), 517 regions: []hrpc.RegionInfo{reg1, reg0}, 518 response: &pb.MultiResponse{ 519 RegionActionResult: []*pb.RegionActionResult{ 520 // reg1 521 &pb.RegionActionResult{ 522 Exception: &pb.NameBytesPair{ // retryable exception 523 Name: proto.String( 524 "org.apache.hadoop.hbase.NotServingRegionException"), 525 Value: []byte("YOLO"), 526 }, 527 }, 528 // reg0, results different order 529 &pb.RegionActionResult{ 530 ResultOrException: []*pb.ResultOrException{ 531 &pb.ResultOrException{ 532 Index: proto.Uint32(3), 533 Result: &pb.Result{ 534 Cell: []*pb.Cell{&pb.Cell{Row: []byte("call2")}}, 535 }, 536 }, 537 &pb.ResultOrException{ 538 Index: proto.Uint32(1), 539 Result: &pb.Result{ 540 Cell: []*pb.Cell{&pb.Cell{Row: []byte("call0")}}, 541 }, 542 }, 543 }, 544 }, 545 }, 546 }, 547 out: []hrpc.RPCResult{ 548 hrpc.RPCResult{Msg: &pb.GetResponse{Result: &pb.Result{ 549 Cell: []*pb.Cell{&pb.Cell{Row: []byte("call0")}}, 550 }}}, 551 hrpc.RPCResult{Error: NotServingRegionError{errors.New("HBase Java " + 552 "exception org.apache.hadoop.hbase.NotServingRegionException:\nYOLO")}}, 553 hrpc.RPCResult{Msg: &pb.MutateResponse{Result: &pb.Result{ 554 Cell: []*pb.Cell{&pb.Cell{Row: []byte("call2")}}, 555 }}}, 556 hrpc.RPCResult{Error: NotServingRegionError{errors.New("HBase Java " + 557 "exception org.apache.hadoop.hbase.NotServingRegionException:\nYOLO")}}, 558 }, 559 }, 560 { // a result exception 561 calls: func() []hrpc.Call { 562 cs := make([]hrpc.Call, 2) 563 cs[0], _ = hrpc.NewGetStr(context.Background(), "reg0", "call0") 564 cs[0].SetRegion(reg0) 565 cs[1], _ = hrpc.NewAppStr(context.Background(), "reg0", "call1", nil) 566 cs[1].SetRegion(reg0) 567 return cs 568 }(), 569 regions: []hrpc.RegionInfo{reg0}, 570 response: &pb.MultiResponse{ 571 RegionActionResult: []*pb.RegionActionResult{ 572 &pb.RegionActionResult{ 573 ResultOrException: []*pb.ResultOrException{ 574 &pb.ResultOrException{ 575 Index: proto.Uint32(1), 576 Exception: &pb.NameBytesPair{ 577 Name: proto.String("YOLO"), 578 Value: []byte("SWAG"), 579 }, 580 }, 581 &pb.ResultOrException{ 582 Index: proto.Uint32(2), 583 Result: &pb.Result{ 584 Cell: []*pb.Cell{&pb.Cell{Row: []byte("call1")}}, 585 }, 586 }, 587 }, 588 }, 589 }, 590 }, 591 out: []hrpc.RPCResult{ 592 hrpc.RPCResult{Error: errors.New("HBase Java exception YOLO:\nSWAG")}, 593 hrpc.RPCResult{Msg: &pb.MutateResponse{Result: &pb.Result{ 594 Cell: []*pb.Cell{&pb.Cell{Row: []byte("call1")}}, 595 }}}, 596 }, 597 }, 598 { // an unrecoverable error 599 calls: func() []hrpc.Call { 600 cs := make([]hrpc.Call, 2) 601 cs[0], _ = hrpc.NewGetStr(context.Background(), "reg0", "call0") 602 cs[0].SetRegion(reg0) 603 cs[1], _ = hrpc.NewAppStr(context.Background(), "reg1", "call1", nil) 604 cs[1].SetRegion(reg1) 605 return cs 606 }(), 607 err: ServerError{errors.New("OOOPS")}, 608 out: []hrpc.RPCResult{ 609 hrpc.RPCResult{Error: ServerError{errors.New("OOOPS")}}, 610 hrpc.RPCResult{Error: ServerError{errors.New("OOOPS")}}, 611 }, 612 }, 613 { // non-MultiResponse 614 shouldPanic: true, 615 response: &pb.CreateTableResponse{}, 616 }, 617 { // non-Get or non-Mutate request 618 shouldPanic: true, 619 calls: []hrpc.Call{ 620 hrpc.NewCreateTable(context.Background(), []byte("reg0"), nil), 621 }, 622 response: &pb.MultiResponse{ 623 RegionActionResult: []*pb.RegionActionResult{ 624 &pb.RegionActionResult{ 625 ResultOrException: []*pb.ResultOrException{ 626 &pb.ResultOrException{ 627 Index: proto.Uint32(1), 628 Result: &pb.Result{ 629 Cell: []*pb.Cell{&pb.Cell{Row: []byte("call0")}}, 630 }, 631 }, 632 }, 633 }, 634 }, 635 }, 636 }, 637 { // result index is 0 638 shouldPanic: true, 639 calls: func() []hrpc.Call { 640 cs := make([]hrpc.Call, 1) 641 cs[0], _ = hrpc.NewGetStr(context.Background(), "reg0", "call0") 642 cs[0].SetRegion(reg0) 643 return cs 644 }(), 645 regions: []hrpc.RegionInfo{reg0}, 646 response: &pb.MultiResponse{ 647 RegionActionResult: []*pb.RegionActionResult{ 648 &pb.RegionActionResult{ 649 ResultOrException: []*pb.ResultOrException{ 650 &pb.ResultOrException{ 651 Index: proto.Uint32(0), 652 Result: &pb.Result{ 653 Cell: []*pb.Cell{&pb.Cell{Row: []byte("call0")}}, 654 }, 655 }, 656 }, 657 }, 658 }, 659 }, 660 }, 661 { // result index is out of bounds 662 shouldPanic: true, 663 calls: func() []hrpc.Call { 664 cs := make([]hrpc.Call, 1) 665 cs[0], _ = hrpc.NewGetStr(context.Background(), "reg0", "call0") 666 cs[0].SetRegion(reg0) 667 return cs 668 }(), 669 regions: []hrpc.RegionInfo{reg0}, 670 response: &pb.MultiResponse{ 671 RegionActionResult: []*pb.RegionActionResult{ 672 &pb.RegionActionResult{ 673 ResultOrException: []*pb.ResultOrException{ 674 &pb.ResultOrException{ 675 Index: proto.Uint32(2), 676 Result: &pb.Result{ 677 Cell: []*pb.Cell{&pb.Cell{Row: []byte("call0")}}, 678 }, 679 }, 680 }, 681 }, 682 }, 683 }, 684 }, 685 } 686 687 for i, tcase := range tests { 688 t.Run(strconv.Itoa(i), func(t *testing.T) { 689 m := newMulti(1000) 690 691 if m.add(tcase.calls) { 692 t.Fatal("multi is full") 693 } 694 m.regions = tcase.regions 695 696 if tcase.shouldPanic { 697 defer func() { 698 if r := recover(); r == nil { 699 t.Error("returnResults should have panicked") 700 } 701 }() 702 m.returnResults(tcase.response, tcase.err) 703 return 704 } 705 706 m.returnResults(tcase.response, tcase.err) 707 708 for i, c := range tcase.calls { 709 expected, out := tcase.out[i], <-c.ResultChan() 710 if !test.ErrEqual(expected.Error, out.Error) { 711 t.Errorf("expected %v, got %v", expected.Error, out.Error) 712 } 713 if !proto.Equal(expected.Msg, out.Msg) { 714 t.Errorf("expected %v, got %v", expected.Msg, out.Msg) 715 } 716 } 717 }) 718 } 719 } 720 721 func TestMultiDeserializeCellBlocks(t *testing.T) { 722 // TODO: add tests for panics 723 724 getCellblock := "\x00\x00\x00\x1d\x00\x00\x00\x14\x00\x00\x00\x01\x00\x05call0" + 725 "\x02cfa\x00\x00\x01]=\xef\x95\xd4\x04*" 726 getCell := &pb.Cell{ 727 Row: []byte("call0"), 728 Family: []byte("cf"), 729 Qualifier: []byte("a"), 730 Value: []byte{42}, 731 Timestamp: proto.Uint64(1499982697940), 732 CellType: pb.CellType(pb.CellType_PUT).Enum(), 733 } 734 735 appendCellblock := "\x00\x00\x00\x1e\x00\x00\x00\x14\x00\x00\x00\x02\x00\x05call1" + 736 "\x02cfa\x00\x00\x01]=\xef\x95\xec\x04**" 737 appendCell := &pb.Cell{ 738 Row: []byte("call1"), 739 Family: []byte("cf"), 740 Qualifier: []byte("a"), 741 Value: []byte{42, 42}, 742 Timestamp: proto.Uint64(1499982697964), 743 CellType: pb.CellType(pb.CellType_PUT).Enum(), 744 } 745 746 tests := []struct { 747 calls []hrpc.Call 748 response proto.Message 749 cellblocks []byte 750 out *pb.MultiResponse 751 err error 752 }{ 753 { // all good 754 calls: func() []hrpc.Call { 755 cs := make([]hrpc.Call, 3) 756 cs[0], _ = hrpc.NewGetStr(context.Background(), "reg0", "call0") 757 cs[0].SetRegion(reg0) 758 cs[1], _ = hrpc.NewAppStr(context.Background(), "reg1", "call1", nil) 759 cs[1].SetRegion(reg1) 760 cs[2], _ = hrpc.NewDelStr(context.Background(), "reg1", "call2", nil) 761 cs[2].SetRegion(reg1) 762 return cs 763 }(), 764 response: &pb.MultiResponse{ 765 RegionActionResult: []*pb.RegionActionResult{ 766 // reg1 767 &pb.RegionActionResult{ 768 ResultOrException: []*pb.ResultOrException{ 769 &pb.ResultOrException{ // Append 770 Index: proto.Uint32(2), 771 Result: &pb.Result{ 772 AssociatedCellCount: proto.Int32(1), 773 }, 774 }, 775 &pb.ResultOrException{ // Delete 776 Index: proto.Uint32(3), 777 Result: &pb.Result{ 778 AssociatedCellCount: proto.Int32(0), 779 }, 780 }, 781 }, 782 }, 783 // reg0 784 &pb.RegionActionResult{ 785 ResultOrException: []*pb.ResultOrException{ 786 &pb.ResultOrException{ // Get 787 Index: proto.Uint32(1), 788 Result: &pb.Result{ 789 AssociatedCellCount: proto.Int32(1), 790 }, 791 }, 792 }, 793 }, 794 }, 795 }, 796 cellblocks: []byte(appendCellblock + getCellblock), 797 out: &pb.MultiResponse{ 798 RegionActionResult: []*pb.RegionActionResult{ 799 // reg1 800 &pb.RegionActionResult{ 801 ResultOrException: []*pb.ResultOrException{ 802 &pb.ResultOrException{ // Append 803 Index: proto.Uint32(2), 804 Result: &pb.Result{ 805 Cell: []*pb.Cell{appendCell}, 806 AssociatedCellCount: proto.Int32(1), 807 }, 808 }, 809 &pb.ResultOrException{ // Delete 810 Index: proto.Uint32(3), 811 Result: &pb.Result{ 812 AssociatedCellCount: proto.Int32(0), 813 }, 814 }, 815 }, 816 }, 817 // reg0 818 &pb.RegionActionResult{ 819 ResultOrException: []*pb.ResultOrException{ 820 &pb.ResultOrException{ // Get 821 Index: proto.Uint32(1), 822 Result: &pb.Result{ 823 Cell: []*pb.Cell{getCell}, 824 AssociatedCellCount: proto.Int32(1), 825 }, 826 }, 827 }, 828 }, 829 }, 830 }, 831 }, 832 { // region exception, a result exception and an ok result 833 calls: func() []hrpc.Call { 834 cs := make([]hrpc.Call, 3) 835 cs[0], _ = hrpc.NewGetStr(context.Background(), "reg0", "call0") 836 cs[0].SetRegion(reg0) 837 cs[1], _ = hrpc.NewAppStr(context.Background(), "reg1", "call1", nil) 838 cs[1].SetRegion(reg1) 839 cs[2], _ = hrpc.NewDelStr(context.Background(), "reg1", "call2", nil) 840 cs[2].SetRegion(reg1) 841 return cs 842 }(), 843 response: &pb.MultiResponse{ 844 RegionActionResult: []*pb.RegionActionResult{ 845 // reg1 846 &pb.RegionActionResult{ 847 ResultOrException: []*pb.ResultOrException{ 848 &pb.ResultOrException{ // Append 849 Index: proto.Uint32(2), 850 Result: &pb.Result{ 851 AssociatedCellCount: proto.Int32(1), 852 }, 853 }, 854 &pb.ResultOrException{ // Delete 855 Index: proto.Uint32(3), 856 Exception: &pb.NameBytesPair{ 857 Name: proto.String("YOLO"), Value: []byte("SWAG")}, 858 }, 859 }, 860 }, 861 // reg0 862 &pb.RegionActionResult{ 863 Exception: &pb.NameBytesPair{ 864 Name: proto.String("YOLO"), Value: []byte("SWAG")}, 865 }, 866 }, 867 }, 868 cellblocks: []byte(appendCellblock), 869 out: &pb.MultiResponse{ 870 RegionActionResult: []*pb.RegionActionResult{ 871 // reg1 872 &pb.RegionActionResult{ 873 ResultOrException: []*pb.ResultOrException{ 874 &pb.ResultOrException{ // Append 875 Index: proto.Uint32(2), 876 Result: &pb.Result{ 877 Cell: []*pb.Cell{appendCell}, 878 AssociatedCellCount: proto.Int32(1), 879 }, 880 }, 881 &pb.ResultOrException{ // Delete 882 Index: proto.Uint32(3), 883 Exception: &pb.NameBytesPair{ 884 Name: proto.String("YOLO"), Value: []byte("SWAG")}, 885 }, 886 }, 887 }, 888 // reg0 889 &pb.RegionActionResult{ 890 Exception: &pb.NameBytesPair{ 891 Name: proto.String("YOLO"), Value: []byte("SWAG")}, 892 }, 893 }, 894 }, 895 }, 896 { // region exception and region results 897 response: &pb.MultiResponse{ 898 RegionActionResult: []*pb.RegionActionResult{ 899 &pb.RegionActionResult{ 900 Exception: &pb.NameBytesPair{ 901 Name: proto.String("YOLO"), Value: []byte("SWAG")}, 902 ResultOrException: []*pb.ResultOrException{ 903 &pb.ResultOrException{ 904 Index: proto.Uint32(2), 905 Result: &pb.Result{AssociatedCellCount: proto.Int32(1)}, 906 }, 907 }, 908 }, 909 }, 910 }, 911 err: errors.New( 912 "got exception for region, but still have 1 result(s) returned from it"), 913 }, 914 { // no result index 915 response: &pb.MultiResponse{ 916 RegionActionResult: []*pb.RegionActionResult{ 917 &pb.RegionActionResult{ 918 ResultOrException: []*pb.ResultOrException{ 919 &pb.ResultOrException{ 920 Result: &pb.Result{AssociatedCellCount: proto.Int32(1)}, 921 }, 922 }, 923 }, 924 }, 925 }, 926 err: errors.New("no index for result in multi response"), 927 }, 928 { // no result and no exception 929 response: &pb.MultiResponse{ 930 RegionActionResult: []*pb.RegionActionResult{ 931 &pb.RegionActionResult{ 932 ResultOrException: []*pb.ResultOrException{ 933 &pb.ResultOrException{Index: proto.Uint32(2)}, 934 }, 935 }, 936 }, 937 }, 938 err: errors.New("no result or exception for action in multi response"), 939 }, 940 { // result and exception 941 response: &pb.MultiResponse{ 942 RegionActionResult: []*pb.RegionActionResult{ 943 &pb.RegionActionResult{ 944 ResultOrException: []*pb.ResultOrException{ 945 &pb.ResultOrException{ 946 Index: proto.Uint32(2), 947 Result: &pb.Result{AssociatedCellCount: proto.Int32(1)}, 948 Exception: &pb.NameBytesPair{ 949 Name: proto.String("YOLO"), Value: []byte("SWAG")}, 950 }, 951 }, 952 }, 953 }, 954 }, 955 err: errors.New("got result and exception for action in multi response"), 956 }, 957 { // single call deserialize error 958 calls: func() []hrpc.Call { 959 c, _ := hrpc.NewGetStr(context.Background(), "reg0", "call0") 960 return []hrpc.Call{callWithCellBlocksError{c}} 961 }(), 962 response: &pb.MultiResponse{ 963 RegionActionResult: []*pb.RegionActionResult{ 964 &pb.RegionActionResult{ 965 ResultOrException: []*pb.ResultOrException{ 966 &pb.ResultOrException{ 967 Index: proto.Uint32(1), 968 Result: &pb.Result{AssociatedCellCount: proto.Int32(1)}, 969 }, 970 }, 971 }, 972 }, 973 }, 974 err: errors.New( 975 "error deserializing cellblocks for \"Get\" call as part of MultiResponse: OOPS"), 976 }, 977 } 978 979 for i, tcase := range tests { 980 t.Run(strconv.Itoa(i), func(t *testing.T) { 981 m := newMulti(1000) 982 983 if m.add(tcase.calls) { 984 t.Fatal("multi is full") 985 } 986 987 n, err := m.DeserializeCellBlocks(tcase.response, tcase.cellblocks) 988 if l := len(tcase.cellblocks); int(n) != l { 989 t.Errorf("expected read %d, got read %d", l, n) 990 } 991 992 if !test.ErrEqual(tcase.err, err) { 993 t.Fatalf("expected %v, got %v", tcase.err, err) 994 } 995 996 if tcase.err != nil { 997 return 998 } 999 1000 if !proto.Equal(tcase.out, tcase.response) { 1001 t.Fatalf("expected %v, got %v", tcase.out, tcase.response) 1002 } 1003 }) 1004 } 1005 } 1006 1007 func BenchmarkMultiToProto(b *testing.B) { 1008 values := map[string]map[string][]byte{ 1009 "cf": map[string][]byte{ 1010 "c": []byte("v"), 1011 }, 1012 } 1013 delValues := map[string]map[string][]byte{ 1014 "cf": map[string][]byte{ 1015 "c": nil, 1016 }, 1017 } 1018 1019 m := newMulti(1000) 1020 var c hrpc.Call 1021 c, _ = hrpc.NewGetStr(context.Background(), "reg0", "call0") 1022 c.SetRegion(reg0) 1023 m.add([]hrpc.Call{c}) 1024 c, _ = hrpc.NewPutStr(context.Background(), "reg0", "call1", values) 1025 c.SetRegion(reg0) 1026 m.add([]hrpc.Call{c}) 1027 c, _ = hrpc.NewAppStr(context.Background(), "reg1", "call2", values) 1028 c.SetRegion(reg1) 1029 m.add([]hrpc.Call{c}) 1030 c, _ = hrpc.NewDelStr(context.Background(), "reg1", "call3", delValues) 1031 c.SetRegion(reg1) 1032 m.add([]hrpc.Call{c}) 1033 c, _ = hrpc.NewIncStr(context.Background(), "reg2", "call4", delValues) 1034 c.SetRegion(reg2) 1035 m.add([]hrpc.Call{c}) 1036 b.Run("cellblocks", func(b *testing.B) { 1037 b.ReportAllocs() 1038 for i := 0; i < b.N; i++ { 1039 m.toProto(true, nil) 1040 } 1041 }) 1042 request, _, cellblocksLen := m.toProto(true, nil) 1043 b.Run("cellblocks_marshalProto", func(b *testing.B) { 1044 b.ReportAllocs() 1045 for i := 0; i < b.N; i++ { 1046 marshalProto(m, 42, request, cellblocksLen) 1047 } 1048 }) 1049 1050 b.Run("no_cellblocks", func(b *testing.B) { 1051 b.ReportAllocs() 1052 for i := 0; i < b.N; i++ { 1053 m.toProto(false, nil) 1054 } 1055 }) 1056 1057 request, _, _ = m.toProto(false, nil) 1058 b.Run("no_cellblocks_marshalProto", func(b *testing.B) { 1059 b.ReportAllocs() 1060 for i := 0; i < b.N; i++ { 1061 marshalProto(m, 42, request, 0) 1062 } 1063 }) 1064 } 1065 1066 func BenchmarkMultiToProtoLarge(b *testing.B) { 1067 row := "12345678901234567890123456789012345678901234567890" 1068 values := map[string]map[string][]byte{ 1069 "cf": { 1070 "abcde": []byte("1234567890"), 1071 "fghij": []byte("1234567890"), 1072 "klmno": []byte("1234567890"), 1073 "pqrst": []byte("1234567890"), 1074 "uvxwyz": []byte("1234567890"), 1075 }, 1076 } 1077 1078 regions := []hrpc.RegionInfo{ 1079 NewInfo(0, nil, []byte("reg0"), 1080 []byte("reg0,,1234567890042.56f833d5569a27c7a43fbf547b4924a4."), nil, nil), 1081 NewInfo(1, nil, []byte("reg1"), 1082 []byte("reg1,,1234567890042.56f833d5569a27c7a43fbf547b4924a4."), nil, nil), 1083 NewInfo(2, nil, []byte("reg2"), 1084 []byte("reg2,,1234567890042.56f833d5569a27c7a43fbf547b4924a4."), nil, nil), 1085 NewInfo(3, nil, []byte("reg3"), 1086 []byte("reg3,,1234567890042.56f833d5569a27c7a43fbf547b4924a4."), nil, nil), 1087 NewInfo(4, nil, []byte("reg4"), 1088 []byte("reg4,,1234567890042.56f833d5569a27c7a43fbf547b4924a4."), nil, nil), 1089 NewInfo(5, nil, []byte("reg5"), 1090 []byte("reg5,,1234567890042.56f833d5569a27c7a43fbf547b4924a4."), nil, nil), 1091 NewInfo(6, nil, []byte("reg6"), 1092 []byte("reg6,,1234567890042.56f833d5569a27c7a43fbf547b4924a4."), nil, nil), 1093 NewInfo(7, nil, []byte("reg7"), 1094 []byte("reg7,,1234567890042.56f833d5569a27c7a43fbf547b4924a4."), nil, nil), 1095 NewInfo(8, nil, []byte("reg8"), 1096 []byte("reg8,,1234567890042.56f833d5569a27c7a43fbf547b4924a4."), nil, nil), 1097 NewInfo(9, nil, []byte("reg9"), 1098 []byte("reg9,,1234567890042.56f833d5569a27c7a43fbf547b4924a4."), nil, nil), 1099 } 1100 1101 m := newMulti(1000) 1102 for i := 0; i < 1000; i++ { 1103 rpc, err := hrpc.NewPutStr(context.Background(), fmt.Sprintf("region%d", i%10), row, values) 1104 if err != nil { 1105 b.Fatal(err) 1106 } 1107 rpc.SetRegion(regions[i%10]) 1108 m.add([]hrpc.Call{rpc}) 1109 } 1110 b.Run("cellblocks", func(b *testing.B) { 1111 b.ReportAllocs() 1112 for i := 0; i < b.N; i++ { 1113 m.toProto(true, nil) 1114 } 1115 }) 1116 1117 request, _, cellblocksLen := m.toProto(true, nil) 1118 b.Run("marshalProto", func(b *testing.B) { 1119 b.ReportAllocs() 1120 for i := 0; i < b.N; i++ { 1121 marshalProto(m, 42, request, cellblocksLen) 1122 } 1123 }) 1124 }