github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/engine/pkg/orm/client_test.go (about) 1 // Copyright 2022 PingCAP, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 package orm 15 16 import ( 17 "context" 18 "database/sql" 19 "database/sql/driver" 20 "fmt" 21 "reflect" 22 "regexp" 23 "testing" 24 "time" 25 26 "github.com/DATA-DOG/go-sqlmock" 27 "github.com/go-sql-driver/mysql" 28 "github.com/pingcap/failpoint" 29 frameModel "github.com/pingcap/tiflow/engine/framework/model" 30 engineModel "github.com/pingcap/tiflow/engine/model" 31 resModel "github.com/pingcap/tiflow/engine/pkg/externalresource/model" 32 metaMock "github.com/pingcap/tiflow/engine/pkg/meta/mock" 33 metaModel "github.com/pingcap/tiflow/engine/pkg/meta/model" 34 "github.com/pingcap/tiflow/engine/pkg/orm/model" 35 "github.com/pingcap/tiflow/pkg/errors" 36 "github.com/pingcap/tiflow/pkg/label" 37 "github.com/stretchr/testify/require" 38 ) 39 40 const ( 41 defaultTestStoreType = metaModel.StoreTypeMySQL 42 ) 43 44 type tCase struct { 45 fn string // function name 46 inputs []interface{} // function args 47 48 output interface{} // function output 49 err error // function error 50 51 mockExpectResFn func(mock sqlmock.Sqlmock) // sqlmock expectation 52 } 53 54 func mockGetDBConn(t *testing.T) (*sql.DB, sqlmock.Sqlmock) { 55 db, mock, err := sqlmock.New() 56 require.Nil(t, err) 57 // common execution for orm 58 mock.ExpectQuery("SELECT VERSION()"). 59 WillReturnRows(sqlmock.NewRows([]string{"VERSION()"}).AddRow("5.7.35-log")) 60 return db, mock 61 } 62 63 type anyTime struct{} 64 65 func (a anyTime) Match(v driver.Value) bool { 66 _, ok := v.(time.Time) 67 return ok 68 } 69 70 func TestNewMetaOpsClient(t *testing.T) { 71 t.Parallel() 72 73 var store metaModel.StoreConfig 74 store.SetEndpoints("127.0.0.1:3306") 75 _, err := NewClient(nil) 76 require.Error(t, err) 77 78 sqlDB, mock := mockGetDBConn(t) 79 defer sqlDB.Close() 80 defer mock.ExpectClose() 81 _, err = newClient(sqlDB, defaultTestStoreType) 82 require.Nil(t, err) 83 } 84 85 func TestProject(t *testing.T) { 86 t.Parallel() 87 88 sqlDB, mock := mockGetDBConn(t) 89 defer sqlDB.Close() 90 defer mock.ExpectClose() 91 cli, err := newClient(sqlDB, defaultTestStoreType) 92 require.Nil(t, err) 93 require.NotNil(t, cli) 94 95 tm := time.Now() 96 createdAt := tm.Add(time.Duration(1)) 97 updatedAt := tm.Add(time.Duration(1)) 98 99 testCases := []tCase{ 100 { 101 fn: "CreateProject", 102 inputs: []interface{}{ 103 &model.ProjectInfo{ 104 Model: model.Model{ 105 CreatedAt: createdAt, 106 UpdatedAt: updatedAt, 107 }, 108 ID: "p111", 109 Name: "tenant1", 110 }, 111 }, 112 mockExpectResFn: func(mock sqlmock.Sqlmock) { 113 mock.ExpectExec("INSERT INTO `project_infos` [(]`created_at`,`updated_at`,`id`,"+ 114 "`name`[)]").WithArgs(createdAt, updatedAt, "p111", "tenant1").WillReturnResult(sqlmock.NewResult(1, 1)) 115 }, 116 }, 117 { 118 fn: "CreateProject", 119 inputs: []interface{}{ 120 &model.ProjectInfo{ 121 Model: model.Model{ 122 SeqID: 1, 123 CreatedAt: createdAt, 124 UpdatedAt: updatedAt, 125 }, 126 ID: "p111", 127 Name: "tenant2", 128 }, 129 }, 130 err: errors.ErrMetaOpFail.GenWithStackByArgs(), 131 mockExpectResFn: func(mock sqlmock.Sqlmock) { 132 mock.ExpectExec("INSERT INTO `project_infos` [(]`created_at`,`updated_at`,`id`,"+ 133 "`name`,`seq_id`[)]").WithArgs(createdAt, updatedAt, "p111", "tenant2", 1).WillReturnError(errors.New("projectID is duplicated")) 134 }, 135 }, 136 { 137 fn: "DeleteProject", 138 inputs: []interface{}{ 139 "p111", 140 }, 141 err: errors.ErrMetaOpFail.GenWithStackByArgs(), 142 mockExpectResFn: func(mock sqlmock.Sqlmock) { 143 mock.ExpectExec("DELETE FROM `project_infos` WHERE id").WithArgs("p111").WillReturnError(errors.New("DeleteProject error")) 144 }, 145 }, 146 { 147 fn: "DeleteProject", 148 inputs: []interface{}{ 149 "p111", 150 }, 151 mockExpectResFn: func(mock sqlmock.Sqlmock) { 152 mock.ExpectExec("DELETE FROM `project_infos` WHERE id").WithArgs("p111").WillReturnResult(sqlmock.NewResult(0, 1)) 153 }, 154 }, 155 { 156 fn: "QueryProjects", 157 inputs: []interface{}{}, 158 output: []*model.ProjectInfo{ 159 { 160 Model: model.Model{ 161 SeqID: 1, 162 CreatedAt: createdAt, 163 UpdatedAt: updatedAt, 164 }, 165 ID: "p111", 166 Name: "tenant1", 167 }, 168 { 169 Model: model.Model{ 170 SeqID: 2, 171 CreatedAt: createdAt, 172 UpdatedAt: updatedAt, 173 }, 174 ID: "p111", 175 Name: "tenant2", 176 }, 177 }, 178 mockExpectResFn: func(mock sqlmock.Sqlmock) { 179 mock.ExpectQuery("SELECT [*] FROM `project_infos`").WillReturnRows(sqlmock.NewRows([]string{ 180 "created_at", "updated_at", "id", "name", 181 "seq_id", 182 }).AddRow(createdAt, updatedAt, "p111", "tenant1", 1).AddRow(createdAt, updatedAt, "p111", "tenant2", 2)) 183 }, 184 }, 185 { 186 fn: "QueryProjects", 187 inputs: []interface{}{}, 188 err: errors.ErrMetaOpFail.GenWithStackByArgs(), 189 mockExpectResFn: func(mock sqlmock.Sqlmock) { 190 mock.ExpectQuery("SELECT [*] FROM `project_infos`").WillReturnError(errors.New("QueryProjects error")) 191 }, 192 }, 193 { 194 // SELECT * FROM `project_infos` WHERE project_id = '111-222-333' ORDER BY `project_infos`.`id` LIMIT 1 195 fn: "GetProjectByID", 196 inputs: []interface{}{ 197 "111-222-333", 198 }, 199 output: &model.ProjectInfo{ 200 Model: model.Model{ 201 SeqID: 2, 202 CreatedAt: createdAt, 203 UpdatedAt: updatedAt, 204 }, 205 ID: "p111", 206 Name: "tenant1", 207 }, 208 mockExpectResFn: func(mock sqlmock.Sqlmock) { 209 mock.ExpectQuery("SELECT [*] FROM `project_infos` WHERE id").WithArgs("111-222-333").WillReturnRows( 210 sqlmock.NewRows([]string{ 211 "created_at", "updated_at", "id", "name", 212 "seq_id", 213 }).AddRow(createdAt, updatedAt, "p111", "tenant1", 2)) 214 }, 215 }, 216 { 217 fn: "GetProjectByID", 218 inputs: []interface{}{ 219 "p111", 220 }, 221 err: errors.ErrMetaOpFail.GenWithStackByArgs(), 222 mockExpectResFn: func(mock sqlmock.Sqlmock) { 223 mock.ExpectQuery("SELECT [*] FROM `project_infos` WHERE id").WithArgs("p111").WillReturnError( 224 errors.New("GetProjectByID error")) 225 }, 226 }, 227 } 228 229 for _, tc := range testCases { 230 testInner(t, mock, cli, tc) 231 } 232 } 233 234 func TestProjectOperation(t *testing.T) { 235 t.Parallel() 236 237 sqlDB, mock := mockGetDBConn(t) 238 defer sqlDB.Close() //nolint: staticcheck 239 defer mock.ExpectClose() 240 cli, err := newClient(sqlDB, defaultTestStoreType) 241 require.Nil(t, err) 242 require.NotNil(t, cli) 243 244 tm := time.Now() 245 tm1 := tm.Add(time.Duration(1)) 246 247 testCases := []tCase{ 248 { 249 // SELECT * FROM `project_operations` WHERE project_id = '111' 250 fn: "QueryProjectOperations", 251 inputs: []interface{}{ 252 "p111", 253 }, 254 output: []*model.ProjectOperation{ 255 { 256 SeqID: 1, 257 ProjectID: "p111", 258 Operation: "Submit", 259 JobID: "j222", 260 CreatedAt: tm, 261 }, 262 { 263 SeqID: 2, 264 ProjectID: "p112", 265 Operation: "Drop", 266 JobID: "j222", 267 CreatedAt: tm1, 268 }, 269 }, 270 mockExpectResFn: func(mock sqlmock.Sqlmock) { 271 mock.ExpectQuery("SELECT [*] FROM `project_operations` WHERE project_id").WithArgs("p111").WillReturnRows( 272 sqlmock.NewRows([]string{"seq_id", "project_id", "operation", "job_id", "created_at"}).AddRow( 273 1, "p111", "Submit", "j222", tm).AddRow( 274 2, "p112", "Drop", "j222", tm1)) 275 }, 276 }, 277 { 278 fn: "QueryProjectOperations", 279 inputs: []interface{}{ 280 "p111", 281 }, 282 err: errors.ErrMetaOpFail.GenWithStackByArgs(), 283 mockExpectResFn: func(mock sqlmock.Sqlmock) { 284 mock.ExpectQuery("SELECT [*] FROM `project_operations` WHERE project_id").WithArgs("p111").WillReturnError(errors.New("QueryProjectOperations error")) 285 }, 286 }, 287 { 288 // SELECT * FROM `project_operations` WHERE project_id = '111' AND created_at >= '2022-04-13 23:51:42.46' AND created_at <= '2022-04-13 23:51:42.46' 289 fn: "QueryProjectOperationsByTimeRange", 290 inputs: []interface{}{ 291 "p111", 292 TimeRange{ 293 start: tm, 294 end: tm1, 295 }, 296 }, 297 output: []*model.ProjectOperation{ 298 { 299 SeqID: 1, 300 ProjectID: "p111", 301 Operation: "Submit", 302 JobID: "j222", 303 CreatedAt: tm, 304 }, 305 { 306 SeqID: 2, 307 ProjectID: "p112", 308 Operation: "Drop", 309 JobID: "j222", 310 CreatedAt: tm1, 311 }, 312 }, 313 mockExpectResFn: func(mock sqlmock.Sqlmock) { 314 mock.ExpectQuery("SELECT [*] FROM `project_operations` WHERE project_id").WithArgs("p111", tm, tm1).WillReturnRows( 315 sqlmock.NewRows([]string{"seq_id", "project_id", "operation", "job_id", "created_at"}).AddRow( 316 1, "p111", "Submit", "j222", tm).AddRow( 317 2, "p112", "Drop", "j222", tm1)) 318 }, 319 }, 320 { 321 fn: "QueryProjectOperationsByTimeRange", 322 inputs: []interface{}{ 323 "p111", 324 TimeRange{ 325 start: tm, 326 end: tm1, 327 }, 328 }, 329 err: errors.ErrMetaOpFail.GenWithStackByArgs(), 330 mockExpectResFn: func(mock sqlmock.Sqlmock) { 331 mock.ExpectQuery("SELECT [*] FROM `project_operations` WHERE project_id").WithArgs("p111", tm, tm1).WillReturnError( 332 errors.New("QueryProjectOperationsByTimeRange error")) 333 }, 334 }, 335 } 336 337 for _, tc := range testCases { 338 testInner(t, mock, cli, tc) 339 } 340 } 341 342 func TestJob(t *testing.T) { 343 t.Parallel() 344 345 sqlDB, mock := mockGetDBConn(t) 346 defer sqlDB.Close() 347 defer mock.ExpectClose() 348 cli, err := newClient(sqlDB, defaultTestStoreType) 349 require.Nil(t, err) 350 require.NotNil(t, cli) 351 352 tm := time.Now() 353 createdAt := tm.Add(time.Duration(1)) 354 updatedAt := tm.Add(time.Duration(1)) 355 356 extForTest := frameModel.MasterMetaExt{ 357 Selectors: []*label.Selector{ 358 { 359 Key: "test", 360 Target: "test-val", 361 Op: label.OpEq, 362 }, 363 }, 364 } 365 extJSONForTest := `{"selectors":[{"label":"test","target":"test-val","op":"eq"}]}` 366 367 testCases := []tCase{ 368 { 369 fn: "InsertJob", 370 inputs: []interface{}{ 371 &frameModel.MasterMeta{ 372 Model: model.Model{ 373 CreatedAt: createdAt, 374 UpdatedAt: updatedAt, 375 }, 376 ProjectID: "p111", 377 ID: "j111", 378 Type: 1, 379 NodeID: "n111", 380 Epoch: 1, 381 State: 1, 382 Addr: "127.0.0.1", 383 Config: []byte{0x11, 0x22}, 384 Ext: extForTest, 385 }, 386 }, 387 mockExpectResFn: func(mock sqlmock.Sqlmock) { 388 mock.ExpectExec("INSERT INTO `master_meta` [(]`created_at`,"+ 389 "`updated_at`,`project_id`,`id`,`type`,`state`,`node_id`,"+ 390 "`address`,`epoch`,`config`,`error_message`,`detail`,"+ 391 "`ext`,`deleted`[)]"). 392 WithArgs(createdAt, updatedAt, "p111", "j111", 1, 1, "n111", 393 "127.0.0.1", 1, []byte{0x11, 0x22}, sqlmock.AnyArg(), 394 sqlmock.AnyArg(), extForTest, nil). 395 WillReturnResult(sqlmock.NewResult(1, 1)) 396 }, 397 }, 398 { 399 fn: "InsertJob", 400 inputs: []interface{}{ 401 &frameModel.MasterMeta{ 402 Model: model.Model{ 403 CreatedAt: createdAt, 404 UpdatedAt: updatedAt, 405 }, 406 ProjectID: "p111", 407 ID: "j111", 408 Type: 1, 409 NodeID: "n111", 410 Epoch: 1, 411 State: 1, 412 Addr: "127.0.0.1", 413 Config: []byte{0x11, 0x22}, 414 Ext: extForTest, 415 }, 416 }, 417 err: errors.ErrMetaOpFail.GenWithStackByArgs(), 418 mockExpectResFn: func(mock sqlmock.Sqlmock) { 419 mock.ExpectExec("INSERT INTO `master_meta` [(]`created_at`," + 420 "`updated_at`,`project_id`,`id`,`type`,`state`,`node_id`," + 421 "`address`,`epoch`,`config`,`error_message`,`detail`," + 422 "`ext`,`deleted`[)]"). 423 WillReturnError(&mysql.MySQLError{Number: 1062, Message: "Duplicate entry '123456' for key 'uidx_mid'"}) 424 }, 425 }, 426 { 427 fn: "UpsertJob", 428 inputs: []interface{}{ 429 &frameModel.MasterMeta{ 430 ProjectID: "p111", 431 ID: "j111", 432 Type: 1, 433 NodeID: "n111", 434 Epoch: 1, 435 State: 1, 436 Addr: "127.0.0.1", 437 Config: []byte{0x11, 0x22}, 438 Ext: extForTest, 439 }, 440 }, 441 mockExpectResFn: func(mock sqlmock.Sqlmock) { 442 mock.ExpectExec("ON DUPLICATE KEY UPDATE").WillReturnResult(sqlmock.NewResult(1, 1)) 443 }, 444 }, 445 { 446 fn: "DeleteJob", 447 inputs: []interface{}{ 448 "j111", 449 }, 450 err: errors.ErrMetaOpFail.GenWithStackByArgs(), 451 mockExpectResFn: func(mock sqlmock.Sqlmock) { 452 expectedSQL := "UPDATE `master_meta` SET `deleted`=? WHERE id = ? AND `master_meta`.`deleted` IS NULL" 453 mock.ExpectExec(regexp.QuoteMeta(expectedSQL)).WithArgs( 454 anyTime{}, "j111").WillReturnError(errors.New("DeleteJob error")) 455 }, 456 }, 457 { 458 // DELETE FROM `master_meta` WHERE project_id = '111-222-334' AND job_id = '111' 459 fn: "DeleteJob", 460 inputs: []interface{}{ 461 "j112", 462 }, 463 output: &ormResult{ 464 rowsAffected: 1, 465 }, 466 mockExpectResFn: func(mock sqlmock.Sqlmock) { 467 expectedSQL := "UPDATE `master_meta` SET `deleted`=? WHERE id = ? AND `master_meta`.`deleted` IS NULL" 468 mock.ExpectExec(regexp.QuoteMeta(expectedSQL)).WithArgs( 469 anyTime{}, "j112").WillReturnResult(sqlmock.NewResult(0, 1)) 470 }, 471 }, 472 { 473 fn: "UpdateJob", 474 inputs: []interface{}{ 475 "j111", 476 (&frameModel.MasterMeta{ 477 ProjectID: "p111", 478 ID: "j111", 479 Type: 1, 480 NodeID: "n111", 481 Epoch: 1, 482 State: 1, 483 Addr: "127.0.0.1", 484 Config: []byte{0x11, 0x22}, 485 }).RefreshValues(), 486 }, 487 mockExpectResFn: func(mock sqlmock.Sqlmock) { 488 mock.ExpectExec(regexp.QuoteMeta( 489 "UPDATE `master_meta` SET `address`=?,`epoch`=?,`node_id`=?,`updated_at`=? WHERE id = ? AND `master_meta`.`deleted` IS NULL")). 490 WillReturnResult(sqlmock.NewResult(0, 1)) 491 }, 492 }, 493 { 494 fn: "UpdateJob", 495 inputs: []interface{}{ 496 "j111", 497 (&frameModel.MasterMeta{ 498 ProjectID: "p111", 499 ID: "j111", 500 Type: 1, 501 NodeID: "n111", 502 Epoch: 1, 503 State: 1, 504 Addr: "127.0.0.1", 505 Config: []byte{0x11, 0x22}, 506 }).UpdateStateValues(), 507 }, 508 mockExpectResFn: func(mock sqlmock.Sqlmock) { 509 mock.ExpectExec(regexp.QuoteMeta( 510 "UPDATE `master_meta` SET `state`=?,`updated_at`=? WHERE id = ? AND `master_meta`.`deleted` IS NULL")). 511 WillReturnResult(sqlmock.NewResult(0, 1)) 512 }, 513 }, 514 { 515 fn: "UpdateJob", 516 inputs: []interface{}{ 517 "j111", 518 (&frameModel.MasterMeta{ 519 ProjectID: "p111", 520 ID: "j111", 521 Type: 1, 522 NodeID: "n111", 523 Epoch: 1, 524 State: 1, 525 Addr: "127.0.0.1", 526 Config: []byte{0x11, 0x22}, 527 ErrorMsg: "error message", 528 Detail: []byte("job detail"), 529 }).UpdateErrorValues(), 530 }, 531 mockExpectResFn: func(mock sqlmock.Sqlmock) { 532 mock.ExpectExec(regexp.QuoteMeta( 533 "UPDATE `master_meta` SET `error_message`=?,`updated_at`=? WHERE id = ? AND `master_meta`.`deleted` IS NULL")). 534 WillReturnResult(sqlmock.NewResult(0, 1)) 535 }, 536 }, 537 { 538 fn: "UpdateJob", 539 inputs: []interface{}{ 540 "j111", 541 (&frameModel.MasterMeta{ 542 ProjectID: "p111", 543 ID: "j111", 544 Type: 1, 545 NodeID: "n111", 546 Epoch: 1, 547 State: 1, 548 Addr: "127.0.0.1", 549 Config: []byte{0x11, 0x22}, 550 ErrorMsg: "error message", 551 Detail: []byte("job detail"), 552 }).ExitValues(), 553 }, 554 mockExpectResFn: func(mock sqlmock.Sqlmock) { 555 mock.ExpectExec(regexp.QuoteMeta( 556 "UPDATE `master_meta` SET `detail`=?,`error_message`=?,`state`=?,`updated_at`=? WHERE id = ? AND `master_meta`.`deleted` IS NULL")). 557 WillReturnResult(sqlmock.NewResult(0, 1)) 558 }, 559 }, 560 { 561 // SELECT * FROM `master_meta` WHERE project_id = '111-222-333' AND job_id = '111' ORDER BY `master_meta`.`id` LIMIT 1 562 fn: "GetJobByID", 563 inputs: []interface{}{ 564 "j111", 565 }, 566 output: &frameModel.MasterMeta{ 567 Model: model.Model{ 568 SeqID: 1, 569 CreatedAt: createdAt, 570 UpdatedAt: updatedAt, 571 }, 572 ProjectID: "p111", 573 ID: "j111", 574 Type: 1, 575 NodeID: "n111", 576 Epoch: 1, 577 State: 1, 578 Addr: "127.0.0.1", 579 Config: []byte{0x11, 0x22}, 580 Ext: extForTest, 581 }, 582 mockExpectResFn: func(mock sqlmock.Sqlmock) { 583 expectedSQL := "SELECT * FROM `master_meta` WHERE id = ? AND `master_meta`.`deleted` IS NULL" 584 mock.ExpectQuery(regexp.QuoteMeta(expectedSQL)).WithArgs("j111").WillReturnRows( 585 sqlmock.NewRows([]string{ 586 "created_at", "updated_at", "project_id", "id", 587 "type", "state", "node_id", "address", "epoch", "config", "seq_id", "ext", 588 }).AddRow( 589 createdAt, updatedAt, "p111", "j111", 1, 1, "n111", "127.0.0.1", 1, []byte{0x11, 0x22}, 1, 590 extJSONForTest)) 591 }, 592 }, 593 { 594 fn: "GetJobByID", 595 inputs: []interface{}{ 596 "j111", 597 }, 598 err: errors.ErrMetaOpFail.GenWithStackByArgs(), 599 mockExpectResFn: func(mock sqlmock.Sqlmock) { 600 mock.ExpectQuery("SELECT [*] FROM `master_meta` WHERE id").WithArgs("j111").WillReturnError( 601 errors.New("GetJobByID error")) 602 }, 603 }, 604 { 605 // SELECT * FROM `master_meta` WHERE project_id = '111-222-333' 606 fn: "QueryJobsByProjectID", 607 inputs: []interface{}{ 608 "p111", 609 }, 610 output: []*frameModel.MasterMeta{ 611 { 612 Model: model.Model{ 613 SeqID: 1, 614 CreatedAt: createdAt, 615 UpdatedAt: updatedAt, 616 }, 617 ProjectID: "p111", 618 ID: "j111", 619 Type: 1, 620 NodeID: "n111", 621 Epoch: 1, 622 State: 1, 623 Addr: "1.1.1.1", 624 Config: []byte{0x11, 0x22}, 625 Ext: extForTest, 626 }, 627 }, 628 mockExpectResFn: func(mock sqlmock.Sqlmock) { 629 mock.ExpectQuery("SELECT [*] FROM `master_meta` WHERE project_id").WithArgs("p111").WillReturnRows( 630 sqlmock.NewRows([]string{ 631 "created_at", "updated_at", "project_id", "id", 632 "type", "state", "node_id", "address", "epoch", "config", "seq_id", "ext", 633 }).AddRow( 634 createdAt, updatedAt, "p111", "j111", 1, 1, "n111", "1.1.1.1", 1, []byte{0x11, 0x22}, 1, extJSONForTest)) 635 }, 636 }, 637 { 638 fn: "QueryJobsByProjectID", 639 inputs: []interface{}{ 640 "p111", 641 }, 642 err: errors.ErrMetaOpFail.GenWithStackByArgs(), 643 mockExpectResFn: func(mock sqlmock.Sqlmock) { 644 mock.ExpectQuery("SELECT [*] FROM `master_meta` WHERE project_id").WithArgs("p111").WillReturnError( 645 errors.New("QueryJobsByProjectID error")) 646 }, 647 }, 648 { 649 // SELECT * FROM `master_meta` WHERE project_id = '111-222-333' AND job_status = 1 650 fn: "QueryJobsByState", 651 inputs: []interface{}{ 652 "p111", 653 1, 654 }, 655 output: []*frameModel.MasterMeta{ 656 { 657 Model: model.Model{ 658 SeqID: 1, 659 CreatedAt: createdAt, 660 UpdatedAt: updatedAt, 661 }, 662 ProjectID: "p111", 663 ID: "j111", 664 Type: 1, 665 NodeID: "n111", 666 Epoch: 1, 667 State: 1, 668 Addr: "127.0.0.1", 669 Config: []byte{0x11, 0x22}, 670 Ext: extForTest, 671 }, 672 }, 673 mockExpectResFn: func(mock sqlmock.Sqlmock) { 674 expectedSQL := "SELECT * FROM `master_meta` WHERE (project_id = ? AND state = ?) AND `master_meta`.`deleted` IS NULL" 675 mock.ExpectQuery(regexp.QuoteMeta(expectedSQL)).WithArgs("p111", 1).WillReturnRows( 676 sqlmock.NewRows([]string{ 677 "created_at", "updated_at", "project_id", "id", 678 "type", "state", "node_id", "address", "epoch", "config", "seq_id", "ext", 679 }).AddRow( 680 createdAt, updatedAt, "p111", "j111", 1, 1, "n111", "127.0.0.1", 1, []byte{0x11, 0x22}, 1, extJSONForTest)) 681 }, 682 }, 683 { 684 fn: "QueryJobsByState", 685 inputs: []interface{}{ 686 "p111", 687 1, 688 }, 689 err: errors.ErrMetaOpFail.GenWithStackByArgs(), 690 mockExpectResFn: func(mock sqlmock.Sqlmock) { 691 expectedSQL := "SELECT * FROM `master_meta` WHERE (project_id = ? AND state = ?) AND `master_meta`.`deleted` IS NULL" 692 mock.ExpectQuery(regexp.QuoteMeta(expectedSQL)).WithArgs("p111", 1).WillReturnError( 693 errors.New("QueryJobsByState error")) 694 }, 695 }, 696 } 697 698 for _, tc := range testCases { 699 testInner(t, mock, cli, tc) 700 } 701 } 702 703 func TestWorker(t *testing.T) { 704 t.Parallel() 705 706 sqlDB, mock := mockGetDBConn(t) 707 defer sqlDB.Close() 708 defer mock.ExpectClose() 709 cli, err := newClient(sqlDB, defaultTestStoreType) 710 require.Nil(t, err) 711 require.NotNil(t, cli) 712 713 tm := time.Now() 714 createdAt := tm.Add(time.Duration(1)) 715 updatedAt := tm.Add(time.Duration(1)) 716 717 testCases := []tCase{ 718 { 719 // INSERT INTO `worker_statuses` (`created_at`,`updated_at`,`project_id`,`job_id`,`id`,`type`,`state`,`epoch`,`error_message`,`extend_bytes`) 720 // VALUES ('2022-04-29 18:49:40.932','2022-04-29 18:49:40.932','p111','j111','w222',1,'1',10,'error','<binary>') ON DUPLICATE KEY 721 // UPDATE `updated_at`=VALUES(`updated_at`),`project_id`=VALUES(`project_id`),`job_id`=VALUES(`job_id`),`id`=VALUES(`id`), 722 // `type`=VALUES(`type`),`state`=VALUES(`state`),`epoch`=VALUES(`epoch`),`error_message`=VALUES(`error_message`),`extend_bytes`=VALUES(`extend_bytes`) 723 fn: "UpsertWorker", 724 inputs: []interface{}{ 725 &frameModel.WorkerStatus{ 726 Model: model.Model{ 727 CreatedAt: createdAt, 728 UpdatedAt: updatedAt, 729 }, 730 ProjectID: "p111", 731 JobID: "j111", 732 ID: "w222", 733 Type: 1, 734 State: 1, 735 Epoch: 10, 736 ErrorMsg: "error", 737 ExtBytes: []byte{0x11, 0x22}, 738 }, 739 }, 740 mockExpectResFn: func(mock sqlmock.Sqlmock) { 741 mock.ExpectExec("ON DUPLICATE KEY UPDATE").WillReturnResult(sqlmock.NewResult(1, 1)) 742 }, 743 }, 744 { 745 fn: "UpsertWorker", 746 inputs: []interface{}{ 747 &frameModel.WorkerStatus{ 748 Model: model.Model{ 749 SeqID: 1, 750 CreatedAt: createdAt, 751 UpdatedAt: updatedAt, 752 }, 753 ProjectID: "p111", 754 JobID: "j111", 755 ID: "w222", 756 Type: 1, 757 State: 1, 758 Epoch: 10, 759 ErrorMsg: "error", 760 ExtBytes: []byte{0x11, 0x22}, 761 }, 762 }, 763 err: errors.ErrMetaOpFail.GenWithStackByArgs(), 764 mockExpectResFn: func(mock sqlmock.Sqlmock) { 765 mock.ExpectExec("INSERT INTO `worker_statuses` [(]`created_at`,`updated_at`,`project_id`,`job_id`," + 766 "`id`,`type`,`state`,`epoch`,`error_message`,`extend_bytes`,`seq_id`[)]").WillReturnError(&mysql.MySQLError{Number: 1062, Message: "error"}) 767 }, 768 }, 769 { 770 fn: "DeleteWorker", 771 inputs: []interface{}{ 772 "j111", 773 "w222", 774 }, 775 err: errors.ErrMetaOpFail.GenWithStackByArgs(), 776 mockExpectResFn: func(mock sqlmock.Sqlmock) { 777 mock.ExpectExec("DELETE FROM `worker_statuses` WHERE job_id").WithArgs( 778 "j111", "w222").WillReturnError(errors.New("DeleteWorker error")) 779 }, 780 }, 781 { 782 // DELETE FROM `worker_statuses` WHERE project_id = '111-222-334' AND job_id = '111' AND worker_id = '222' 783 fn: "DeleteWorker", 784 inputs: []interface{}{ 785 "j112", 786 "w223", 787 }, 788 output: &ormResult{ 789 rowsAffected: 1, 790 }, 791 mockExpectResFn: func(mock sqlmock.Sqlmock) { 792 mock.ExpectExec("DELETE FROM `worker_statuses` WHERE job_id").WithArgs( 793 "j112", "w223").WillReturnResult(sqlmock.NewResult(0, 1)) 794 }, 795 }, 796 { 797 // 'UPDATE `worker_statuses` SET `epoch`=?,`error-message`=?,`extend-bytes`=?,`id`=?,`job_id`=?,`project_id`=?,`status`=?,`type`=?,`updated_at`=? WHERE job_id = ? && id = ?' 798 fn: "UpdateWorker", 799 inputs: []interface{}{ 800 &frameModel.WorkerStatus{ 801 ProjectID: "p111", 802 JobID: "j111", 803 ID: "w111", 804 Type: 1, 805 State: 1, 806 Epoch: 10, 807 ErrorMsg: "error", 808 ExtBytes: []byte{0x11, 0x22}, 809 }, 810 }, 811 mockExpectResFn: func(mock sqlmock.Sqlmock) { 812 mock.ExpectExec("UPDATE `worker_statuses` SET").WillReturnResult(sqlmock.NewResult(0, 1)) 813 }, 814 }, 815 { 816 // SELECT * FROM `worker_statuses` WHERE project_id = '111-222-333' AND job_id = '111' AND 817 // worker_id = '222' ORDER BY `worker_statuses`.`id` LIMIT 1 818 fn: "GetWorkerByID", 819 inputs: []interface{}{ 820 "j111", 821 "w222", 822 }, 823 output: &frameModel.WorkerStatus{ 824 Model: model.Model{ 825 SeqID: 1, 826 CreatedAt: createdAt, 827 UpdatedAt: updatedAt, 828 }, 829 ProjectID: "p111", 830 JobID: "j111", 831 ID: "w222", 832 Type: 1, 833 State: 1, 834 Epoch: 10, 835 ErrorMsg: "error", 836 ExtBytes: []byte{0x11, 0x22}, 837 }, 838 mockExpectResFn: func(mock sqlmock.Sqlmock) { 839 mock.ExpectQuery("SELECT [*] FROM `worker_statuses` WHERE job_id").WithArgs("j111", "w222").WillReturnRows( 840 sqlmock.NewRows([]string{ 841 "created_at", "updated_at", "project_id", "job_id", 842 "id", "type", "state", "epoch", "error_message", "extend_bytes", "seq_id", 843 }).AddRow( 844 createdAt, updatedAt, "p111", "j111", "w222", 1, 1, 10, "error", []byte{0x11, 0x22}, 1)) 845 }, 846 }, 847 { 848 fn: "GetWorkerByID", 849 inputs: []interface{}{ 850 "j111", 851 "w222", 852 }, 853 err: errors.ErrMetaOpFail.GenWithStackByArgs(), 854 mockExpectResFn: func(mock sqlmock.Sqlmock) { 855 mock.ExpectQuery("SELECT [*] FROM `worker_statuses` WHERE job_id").WithArgs("j111", "w222").WillReturnError( 856 errors.New("GetWorkerByID error")) 857 }, 858 }, 859 { 860 // SELECT * FROM `worker_statuses` WHERE project_id = '111-222-333' AND job_id = '111' 861 fn: "QueryWorkersByMasterID", 862 inputs: []interface{}{ 863 "j111", 864 }, 865 output: []*frameModel.WorkerStatus{ 866 { 867 Model: model.Model{ 868 SeqID: 1, 869 CreatedAt: createdAt, 870 UpdatedAt: updatedAt, 871 }, 872 ProjectID: "p111", 873 JobID: "j111", 874 ID: "w222", 875 Type: 1, 876 State: 1, 877 Epoch: 10, 878 ErrorMsg: "error", 879 ExtBytes: []byte{0x11, 0x22}, 880 }, 881 }, 882 mockExpectResFn: func(mock sqlmock.Sqlmock) { 883 mock.ExpectQuery("SELECT [*] FROM `worker_statuses` WHERE job_id").WithArgs("j111").WillReturnRows( 884 sqlmock.NewRows([]string{ 885 "created_at", "updated_at", "project_id", "job_id", 886 "id", "type", "state", "epoch", "error_message", "extend_bytes", "seq_id", 887 }).AddRow( 888 createdAt, updatedAt, "p111", "j111", "w222", 1, 1, 10, "error", []byte{0x11, 0x22}, 1)) 889 }, 890 }, 891 { 892 fn: "QueryWorkersByMasterID", 893 inputs: []interface{}{ 894 "j111", 895 }, 896 err: errors.ErrMetaOpFail.GenWithStackByArgs(), 897 mockExpectResFn: func(mock sqlmock.Sqlmock) { 898 mock.ExpectQuery("SELECT [*] FROM `worker_statuses` WHERE job_id").WithArgs("j111").WillReturnError( 899 errors.New("QueryWorkersByMasterID error")) 900 }, 901 }, 902 { 903 // SELECT * FROM `worker_statuses` WHERE project_id = '111-222-333' AND job_id = '111' AND worker_statuses = 1 904 fn: "QueryWorkersByState", 905 inputs: []interface{}{ 906 "j111", 907 1, 908 }, 909 output: []*frameModel.WorkerStatus{ 910 { 911 Model: model.Model{ 912 SeqID: 1, 913 CreatedAt: createdAt, 914 UpdatedAt: updatedAt, 915 }, 916 ProjectID: "p111", 917 JobID: "j111", 918 ID: "w222", 919 Type: 1, 920 State: 1, 921 Epoch: 10, 922 ErrorMsg: "error", 923 ExtBytes: []byte{0x11, 0x22}, 924 }, 925 }, 926 mockExpectResFn: func(mock sqlmock.Sqlmock) { 927 mock.ExpectQuery("SELECT [*] FROM `worker_statuses` WHERE job_id").WithArgs("j111", 1).WillReturnRows( 928 sqlmock.NewRows([]string{ 929 "created_at", "updated_at", "project_id", "job_id", 930 "id", "type", "state", "epoch", "error_message", "extend_bytes", "seq_id", 931 }).AddRow( 932 createdAt, updatedAt, "p111", "j111", "w222", 1, 1, 10, "error", []byte{0x11, 0x22}, 1)) 933 }, 934 }, 935 { 936 fn: "QueryWorkersByState", 937 inputs: []interface{}{ 938 "j111", 939 1, 940 }, 941 err: errors.ErrMetaOpFail.GenWithStackByArgs(), 942 mockExpectResFn: func(mock sqlmock.Sqlmock) { 943 mock.ExpectQuery("SELECT [*] FROM `worker_statuses` WHERE job_id").WithArgs("j111", 1).WillReturnError( 944 errors.New("QueryWorkersByState error")) 945 }, 946 }, 947 } 948 949 for _, tc := range testCases { 950 testInner(t, mock, cli, tc) 951 } 952 } 953 954 func TestResource(t *testing.T) { 955 t.Parallel() 956 957 sqlDB, mock := mockGetDBConn(t) 958 defer sqlDB.Close() 959 defer mock.ExpectClose() 960 cli, err := newClient(sqlDB, defaultTestStoreType) 961 require.Nil(t, err) 962 require.NotNil(t, cli) 963 964 tm := time.Now() 965 createdAt := tm.Add(time.Duration(1)) 966 updatedAt := tm.Add(time.Duration(1)) 967 968 testCases := []tCase{ 969 { 970 fn: "CreateResource", 971 inputs: []interface{}{ 972 &resModel.ResourceMeta{ 973 Model: model.Model{ 974 SeqID: 1, 975 CreatedAt: createdAt, 976 UpdatedAt: updatedAt, 977 }, 978 ID: "r333", 979 ProjectID: "111-222-333", 980 TenantID: "111-222-333", 981 Job: "j111", 982 Worker: "w222", 983 Executor: "e444", 984 Deleted: false, 985 }, 986 }, 987 mockExpectResFn: func(mock sqlmock.Sqlmock) { 988 mock.ExpectBegin() 989 mock.ExpectQuery(regexp.QuoteMeta("SELECT count(*) FROM `resource_meta` WHERE job_id = ? AND id = ?")).WithArgs("j111", "r333").WillReturnRows( 990 sqlmock.NewRows([]string{ 991 "count(*)", 992 }).AddRow(0)) 993 mock.ExpectExec(regexp.QuoteMeta("INSERT INTO `resource_meta` (`created_at`,`updated_at`,`project_id`,`tenant_id`,`id`,`job_id`,"+ 994 "`worker_id`,`executor_id`,`gc_pending`,`deleted`,`seq_id`)")).WithArgs( 995 createdAt, updatedAt, "111-222-333", "111-222-333", "r333", "j111", "w222", "e444", false, false, 1). 996 WillReturnResult(sqlmock.NewResult(1, 1)) 997 mock.ExpectCommit() 998 }, 999 }, 1000 { 1001 fn: "CreateResource", 1002 inputs: []interface{}{ 1003 &resModel.ResourceMeta{ 1004 Model: model.Model{ 1005 SeqID: 1, 1006 CreatedAt: createdAt, 1007 UpdatedAt: updatedAt, 1008 }, 1009 ID: "r333", 1010 ProjectID: "111-222-333", 1011 TenantID: "111-222-333", 1012 Job: "j111", 1013 Worker: "w222", 1014 Executor: "e444", 1015 Deleted: false, 1016 }, 1017 }, 1018 mockExpectResFn: func(mock sqlmock.Sqlmock) { 1019 mock.ExpectBegin() 1020 mock.ExpectQuery(regexp.QuoteMeta("SELECT count(*) FROM `resource_meta` WHERE job_id = ? AND id = ?")).WithArgs("j111", "r333").WillReturnRows( 1021 sqlmock.NewRows([]string{ 1022 "count(1)", 1023 }).AddRow(1)) 1024 mock.ExpectRollback() 1025 }, 1026 err: errors.ErrDuplicateResourceID.GenWithStackByArgs("r333"), 1027 }, 1028 { 1029 fn: "UpsertResource", 1030 inputs: []interface{}{ 1031 &resModel.ResourceMeta{ 1032 Model: model.Model{ 1033 SeqID: 1, 1034 CreatedAt: createdAt, 1035 UpdatedAt: updatedAt, 1036 }, 1037 ID: "r333", 1038 ProjectID: "111-222-333", 1039 Job: "j111", 1040 Worker: "w222", 1041 Executor: "e445", 1042 Deleted: true, 1043 }, 1044 }, 1045 mockExpectResFn: func(mock sqlmock.Sqlmock) { 1046 mock.ExpectExec("ON DUPLICATE KEY UPDATE").WillReturnResult(sqlmock.NewResult(1, 1)) 1047 }, 1048 }, 1049 { 1050 fn: "UpsertResource", 1051 inputs: []interface{}{ 1052 &resModel.ResourceMeta{ 1053 Model: model.Model{ 1054 SeqID: 1, 1055 CreatedAt: createdAt, 1056 UpdatedAt: updatedAt, 1057 }, 1058 ID: "r333", 1059 ProjectID: "111-222-333", 1060 TenantID: "", 1061 Job: "j111", 1062 Worker: "w222", 1063 Executor: "e444", 1064 Deleted: true, 1065 }, 1066 }, 1067 err: errors.ErrMetaOpFail.GenWithStackByArgs(), 1068 mockExpectResFn: func(mock sqlmock.Sqlmock) { 1069 mock.ExpectExec(regexp.QuoteMeta("INSERT INTO `resource_meta` (`created_at`,`updated_at`,`project_id`,`tenant_id`,`id`,`job_id`,"+ 1070 "`worker_id`,`executor_id`,`gc_pending`,`deleted`,`seq_id`)")).WithArgs( 1071 createdAt, updatedAt, "111-222-333", "", "r333", "j111", "w222", "e444", false, true, 1).WillReturnError(&mysql.MySQLError{Number: 1062, Message: "error"}) 1072 }, 1073 }, 1074 { 1075 fn: "DeleteResource", 1076 inputs: []interface{}{ 1077 ResourceKey{ 1078 JobID: "j111", 1079 ID: "r222", 1080 }, 1081 }, 1082 err: errors.ErrMetaOpFail.GenWithStackByArgs(), 1083 mockExpectResFn: func(mock sqlmock.Sqlmock) { 1084 mock.ExpectExec(regexp.QuoteMeta("DELETE FROM `resource_meta` WHERE job_id = ? AND id = ?")).WithArgs( 1085 "j111", "r222").WillReturnError(errors.New("DeleteReource error")) 1086 }, 1087 }, 1088 { 1089 fn: "DeleteResource", 1090 inputs: []interface{}{ 1091 ResourceKey{ 1092 JobID: "j111", 1093 ID: "r223", 1094 }, 1095 }, 1096 output: &ormResult{ 1097 rowsAffected: 1, 1098 }, 1099 mockExpectResFn: func(mock sqlmock.Sqlmock) { 1100 mock.ExpectExec(regexp.QuoteMeta("DELETE FROM `resource_meta` WHERE job_id = ? AND id = ?")).WithArgs( 1101 "j111", "r223").WillReturnResult(sqlmock.NewResult(0, 1)) 1102 }, 1103 }, 1104 { 1105 // 'UPDATE `resource_meta` SET `deleted`=?,`executor_id`=?,`id`=?,`job_id`=?,`project_id`=?,`worker_id`=?,`updated_at`=? WHERE id = ?' 1106 fn: "UpdateResource", 1107 inputs: []interface{}{ 1108 &resModel.ResourceMeta{ 1109 ProjectID: "p111", 1110 ID: "w111", 1111 Job: "j111", 1112 Worker: "w111", 1113 Executor: "e111", 1114 Deleted: true, 1115 }, 1116 }, 1117 mockExpectResFn: func(mock sqlmock.Sqlmock) { 1118 mock.ExpectExec(regexp.QuoteMeta("UPDATE `resource_meta` SET")).WillReturnResult(sqlmock.NewResult(0, 1)) 1119 }, 1120 }, 1121 { 1122 fn: "GetResourceByID", 1123 inputs: []interface{}{ 1124 ResourceKey{ 1125 JobID: "j111", 1126 ID: "r222", 1127 }, 1128 }, 1129 output: &resModel.ResourceMeta{ 1130 Model: model.Model{ 1131 SeqID: 1, 1132 CreatedAt: createdAt, 1133 UpdatedAt: updatedAt, 1134 }, 1135 ID: "r333", 1136 ProjectID: "111-222-333", 1137 Job: "j111", 1138 Worker: "w222", 1139 Executor: "e444", 1140 Deleted: true, 1141 }, 1142 mockExpectResFn: func(mock sqlmock.Sqlmock) { 1143 mock.ExpectQuery(regexp.QuoteMeta("SELECT * FROM `resource_meta` WHERE job_id = ? AND id = ?")).WithArgs("j111", "r222").WillReturnRows( 1144 sqlmock.NewRows([]string{ 1145 "created_at", "updated_at", "project_id", "id", "job_id", 1146 "worker_id", "executor_id", "deleted", "seq_id", 1147 }).AddRow( 1148 createdAt, updatedAt, "111-222-333", "r333", "j111", "w222", "e444", true, 1)) 1149 }, 1150 }, 1151 { 1152 fn: "GetResourceByID", 1153 inputs: []interface{}{ 1154 ResourceKey{ 1155 JobID: "j111", 1156 ID: "r222", 1157 }, 1158 }, 1159 err: errors.ErrMetaOpFail.GenWithStackByArgs(), 1160 mockExpectResFn: func(mock sqlmock.Sqlmock) { 1161 mock.ExpectQuery(regexp.QuoteMeta("SELECT * FROM `resource_meta` WHERE job_id = ? AND id = ?")).WithArgs("j111", "r222").WillReturnError( 1162 errors.New("GetResourceByID error")) 1163 }, 1164 }, 1165 { 1166 fn: "QueryResourcesByJobID", 1167 inputs: []interface{}{ 1168 "j111", 1169 }, 1170 output: []*resModel.ResourceMeta{ 1171 { 1172 Model: model.Model{ 1173 SeqID: 1, 1174 CreatedAt: createdAt, 1175 UpdatedAt: updatedAt, 1176 }, 1177 ID: "r333", 1178 ProjectID: "111-222-333", 1179 Job: "j111", 1180 Worker: "w222", 1181 Executor: "e444", 1182 Deleted: true, 1183 }, 1184 }, 1185 mockExpectResFn: func(mock sqlmock.Sqlmock) { 1186 mock.ExpectQuery(regexp.QuoteMeta("SELECT * FROM `resource_meta` WHERE job_id = ?")).WithArgs("j111").WillReturnRows( 1187 sqlmock.NewRows([]string{ 1188 "created_at", "updated_at", "project_id", "tenant_id", "id", "job_id", 1189 "worker_id", "executor_id", "deleted", "seq_id", 1190 }).AddRow( 1191 createdAt, updatedAt, "111-222-333", "", "r333", "j111", "w222", "e444", true, 1)) 1192 }, 1193 }, 1194 { 1195 fn: "QueryResourcesByJobID", 1196 inputs: []interface{}{ 1197 "j111", 1198 }, 1199 err: errors.ErrMetaOpFail.GenWithStackByArgs(), 1200 mockExpectResFn: func(mock sqlmock.Sqlmock) { 1201 mock.ExpectQuery(regexp.QuoteMeta("SELECT * FROM `resource_meta` WHERE job_id")).WithArgs("j111").WillReturnError( 1202 errors.New("QueryResourcesByJobID error")) 1203 }, 1204 }, 1205 { 1206 fn: "QueryResourcesByExecutorIDs", 1207 inputs: []interface{}{ 1208 engineModel.ExecutorID("e444"), 1209 }, 1210 output: []*resModel.ResourceMeta{ 1211 { 1212 Model: model.Model{ 1213 SeqID: 1, 1214 CreatedAt: createdAt, 1215 UpdatedAt: updatedAt, 1216 }, 1217 ID: "r333", 1218 ProjectID: "111-222-333", 1219 TenantID: "333-222-111", 1220 Job: "j111", 1221 Worker: "w222", 1222 Executor: "e444", 1223 Deleted: true, 1224 }, 1225 }, 1226 mockExpectResFn: func(mock sqlmock.Sqlmock) { 1227 mock.ExpectQuery(regexp.QuoteMeta("SELECT * FROM `resource_meta` WHERE executor_id in")).WithArgs("e444").WillReturnRows( 1228 sqlmock.NewRows([]string{ 1229 "created_at", "updated_at", "project_id", "tenant_id", "id", "job_id", 1230 "worker_id", "executor_id", "deleted", "seq_id", 1231 }).AddRow(createdAt, updatedAt, "111-222-333", "333-222-111", "r333", "j111", "w222", "e444", true, 1)) 1232 }, 1233 }, 1234 { 1235 fn: "QueryResourcesByExecutorIDs", 1236 inputs: []interface{}{ 1237 engineModel.ExecutorID("e444"), 1238 engineModel.ExecutorID("e555"), 1239 }, 1240 output: []*resModel.ResourceMeta{ 1241 { 1242 Model: model.Model{ 1243 SeqID: 1, 1244 CreatedAt: createdAt, 1245 UpdatedAt: updatedAt, 1246 }, 1247 ID: "r333", 1248 ProjectID: "111-222-333", 1249 TenantID: "333-222-111", 1250 Job: "j111", 1251 Worker: "w222", 1252 Executor: "e444", 1253 Deleted: true, 1254 }, 1255 { 1256 Model: model.Model{ 1257 SeqID: 1, 1258 CreatedAt: createdAt, 1259 UpdatedAt: updatedAt, 1260 }, 1261 ID: "r333", 1262 ProjectID: "111-222-333", 1263 TenantID: "333-222-111", 1264 Job: "j111", 1265 Worker: "w222", 1266 Executor: "e555", 1267 Deleted: true, 1268 }, 1269 }, 1270 mockExpectResFn: func(mock sqlmock.Sqlmock) { 1271 mock.ExpectQuery(regexp.QuoteMeta("SELECT * FROM `resource_meta` WHERE executor_id in")). 1272 WithArgs("e444", "e555").WillReturnRows( 1273 sqlmock.NewRows([]string{ 1274 "created_at", "updated_at", "project_id", "tenant_id", "id", "job_id", 1275 "worker_id", "executor_id", "deleted", "seq_id", 1276 }).AddRow(createdAt, updatedAt, "111-222-333", "333-222-111", "r333", "j111", "w222", "e444", true, 1). 1277 AddRow(createdAt, updatedAt, "111-222-333", "333-222-111", "r333", "j111", "w222", "e555", true, 1), 1278 ) 1279 }, 1280 }, 1281 { 1282 fn: "QueryResourcesByExecutorIDs", 1283 inputs: []interface{}{ 1284 engineModel.ExecutorID("e444"), 1285 }, 1286 err: errors.ErrMetaOpFail.GenWithStackByArgs(), 1287 mockExpectResFn: func(mock sqlmock.Sqlmock) { 1288 mock.ExpectQuery(regexp.QuoteMeta("SELECT * FROM `resource_meta` WHERE executor_id in")).WithArgs("e444").WillReturnError( 1289 errors.New("QueryResourcesByExecutorIDs error")) 1290 }, 1291 }, 1292 { 1293 fn: "SetGCPendingByJobs", 1294 inputs: []interface{}{ 1295 "job-1", 1296 "job-2", 1297 "job-3", 1298 }, 1299 mockExpectResFn: func(mock sqlmock.Sqlmock) { 1300 expectedSQL := "UPDATE `resource_meta` SET `gc_pending`=?,`updated_at`=? WHERE job_id in" 1301 mock.ExpectExec(regexp.QuoteMeta(expectedSQL)). 1302 WithArgs( 1303 true, 1304 anyTime{}, 1305 "job-1", 1306 "job-2", 1307 "job-3"). 1308 WillReturnResult(driver.RowsAffected(1)) 1309 }, 1310 }, 1311 { 1312 fn: "DeleteResourcesByTypeAndExecutorIDs", 1313 inputs: []interface{}{ 1314 resModel.ResourceTypeLocalFile, 1315 engineModel.ExecutorID("executor-1"), 1316 }, 1317 output: &ormResult{rowsAffected: 1}, 1318 mockExpectResFn: func(mock sqlmock.Sqlmock) { 1319 expectedSQL := "DELETE FROM `resource_meta` WHERE executor_id = ? and id like" 1320 mock.ExpectExec(regexp.QuoteMeta(expectedSQL)). 1321 WithArgs("executor-1", "/local%"). 1322 WillReturnResult(driver.RowsAffected(1)) 1323 }, 1324 }, 1325 { 1326 fn: "DeleteResourcesByTypeAndExecutorIDs", 1327 inputs: []interface{}{ 1328 resModel.ResourceTypeLocalFile, 1329 engineModel.ExecutorID("executor-1"), 1330 engineModel.ExecutorID("executor-2"), 1331 }, 1332 output: &ormResult{rowsAffected: 2}, 1333 mockExpectResFn: func(mock sqlmock.Sqlmock) { 1334 expectedSQL := "DELETE FROM `resource_meta` WHERE executor_id in (?,?) and id like" 1335 mock.ExpectExec(regexp.QuoteMeta(expectedSQL)). 1336 WithArgs("executor-1", "executor-2", "/local%"). 1337 WillReturnResult(driver.RowsAffected(2)) 1338 }, 1339 }, 1340 { 1341 fn: "GetOneResourceForGC", 1342 output: &resModel.ResourceMeta{ 1343 Model: model.Model{ 1344 SeqID: 1, 1345 CreatedAt: createdAt, 1346 UpdatedAt: updatedAt, 1347 }, 1348 ID: "resource-1", 1349 Job: "job-1", 1350 Worker: "worker-1", 1351 Executor: "executor-1", 1352 GCPending: true, 1353 }, 1354 mockExpectResFn: func(mock sqlmock.Sqlmock) { 1355 expectedSQL := "SELECT * FROM `resource_meta` WHERE gc_pending = true ORDER BY" 1356 mock.ExpectQuery(regexp.QuoteMeta(expectedSQL)). 1357 WillReturnRows( 1358 sqlmock.NewRows([]string{ 1359 "created_at", "updated_at", "project_id", "id", "job_id", 1360 "worker_id", "executor_id", "deleted", "gc_pending", "seq_id", 1361 }).AddRow(createdAt, updatedAt, "", "resource-1", "job-1", "worker-1", "executor-1", false, true, 1)) 1362 }, 1363 }, 1364 } 1365 1366 for _, tc := range testCases { 1367 fmt.Println("test case", tc.fn) 1368 testInner(t, mock, cli, tc) 1369 } 1370 } 1371 1372 func TestError(t *testing.T) { 1373 t.Parallel() 1374 1375 sqlDB, mock := mockGetDBConn(t) 1376 defer sqlDB.Close() 1377 defer mock.ExpectClose() 1378 cli, err := newClient(sqlDB, defaultTestStoreType) 1379 require.Nil(t, err) 1380 require.NotNil(t, cli) 1381 1382 mock.ExpectQuery("SELECT [*] FROM `project_infos`").WillReturnRows(sqlmock.NewRows([]string{ 1383 "created_at", "updated_at", "id", "name", 1384 "seq_id", 1385 })) 1386 res, err := cli.QueryProjects(context.TODO()) 1387 require.Nil(t, err) 1388 require.Len(t, res, 0) 1389 1390 mock.ExpectQuery("SELECT [*] FROM `project_infos` WHERE id").WithArgs("p111").WillReturnRows( 1391 sqlmock.NewRows([]string{ 1392 "created_at", "updated_at", "id", "name", 1393 "seq_id", 1394 })) 1395 res2, err := cli.GetProjectByID(context.TODO(), "p111") 1396 require.Nil(t, res2) 1397 require.Error(t, err) 1398 require.True(t, errors.Is(err, errors.ErrMetaEntryNotFound)) 1399 } 1400 1401 func TestContext(t *testing.T) { 1402 t.Parallel() 1403 1404 ctx, cancel := context.WithTimeout(context.TODO(), 1*time.Second) 1405 defer cancel() 1406 1407 db, mock := mockGetDBConn(t) 1408 defer db.Close() 1409 defer mock.ExpectClose() 1410 1411 conn := metaMock.NewClientConnWithDB(db) 1412 require.NotNil(t, conn) 1413 defer conn.Close() 1414 1415 // test normal function 1416 err := failpoint.Enable("github.com/pingcap/tiflow/engine/pkg/orm/initializedDelay", "sleep(2000)") 1417 require.NoError(t, err) 1418 ctx = failpoint.WithHook(ctx, func(ctx context.Context, fpname string) bool { 1419 return ctx.Value(fpname) != nil 1420 }) 1421 ctx2 := context.WithValue(ctx, "github.com/pingcap/tiflow/engine/pkg/orm/initializedDelay", struct{}{}) 1422 1423 // NEED enable failpoint here, or you will meet sql mock NOT MATCH error 1424 err = InitAllFrameworkModels(ctx2, conn) 1425 require.Error(t, err) 1426 require.Regexp(t, "context deadline exceed", err.Error()) 1427 failpoint.Disable("github.com/pingcap/tiflow/engine/pkg/orm/initializedDelay") 1428 } 1429 1430 func TestJobOp(t *testing.T) { 1431 t.Parallel() 1432 1433 sqlDB, mock := mockGetDBConn(t) 1434 defer sqlDB.Close() 1435 defer mock.ExpectClose() 1436 cli, err := newClient(sqlDB, defaultTestStoreType) 1437 require.Nil(t, err) 1438 require.NotNil(t, cli) 1439 1440 tm := time.Now() 1441 createdAt := tm.Add(time.Duration(1)) 1442 updatedAt := tm.Add(time.Duration(1)) 1443 1444 testCases := []tCase{ 1445 // SetJobCanceling successfully 1446 { 1447 fn: "SetJobCanceling", 1448 inputs: []interface{}{ 1449 "job-111", 1450 }, 1451 output: &ormResult{ 1452 rowsAffected: 1, 1453 }, 1454 mockExpectResFn: func(mock sqlmock.Sqlmock) { 1455 mock.ExpectBegin() 1456 mock.ExpectQuery(regexp.QuoteMeta( 1457 "SELECT count(*) FROM `job_ops` WHERE job_id = ?")). 1458 WithArgs("job-111").WillReturnRows( 1459 sqlmock.NewRows([]string{ 1460 "count(0)", 1461 }).AddRow(0)) 1462 mock.ExpectExec(regexp.QuoteMeta( 1463 "INSERT INTO `job_ops` (`created_at`,`updated_at`,`op`,`job_id`")). 1464 WithArgs(sqlmock.AnyArg(), sqlmock.AnyArg(), model.JobOpStatusCanceling, "job-111"). 1465 WillReturnResult(sqlmock.NewResult(1, 1)) 1466 mock.ExpectCommit() 1467 }, 1468 }, 1469 // SetJobCanceling does nothing because cancelling op exists 1470 { 1471 fn: "SetJobCanceling", 1472 inputs: []interface{}{ 1473 "job-111", 1474 }, 1475 output: &ormResult{ 1476 rowsAffected: 0, 1477 }, 1478 mockExpectResFn: func(mock sqlmock.Sqlmock) { 1479 mock.ExpectBegin() 1480 mock.ExpectQuery(regexp.QuoteMeta( 1481 "SELECT count(*) FROM `job_ops` WHERE job_id = ?")). 1482 WithArgs("job-111").WillReturnRows( 1483 sqlmock.NewRows([]string{ 1484 "count(1)", 1485 }).AddRow(1)) 1486 mock.ExpectQuery( 1487 "SELECT [*] FROM `job_ops` WHERE job_id = ?"). 1488 WithArgs("job-111").WillReturnRows( 1489 sqlmock.NewRows([]string{ 1490 "created_at", "updated_at", "op", "job_id", "seq_id", 1491 }).AddRow(createdAt, updatedAt, model.JobOpStatusCanceling, "job-111", 1)) 1492 mock.ExpectCommit() 1493 }, 1494 }, 1495 // SetJobCanceling returns error if job is already cancelled 1496 { 1497 fn: "SetJobCanceling", 1498 inputs: []interface{}{ 1499 "job-111", 1500 }, 1501 output: &ormResult{ 1502 rowsAffected: 0, 1503 }, 1504 mockExpectResFn: func(mock sqlmock.Sqlmock) { 1505 mock.ExpectBegin() 1506 mock.ExpectQuery(regexp.QuoteMeta( 1507 "SELECT count(*) FROM `job_ops` WHERE job_id = ?")). 1508 WithArgs("job-111").WillReturnRows( 1509 sqlmock.NewRows([]string{ 1510 "count(1)", 1511 }).AddRow(1)) 1512 mock.ExpectQuery( 1513 "SELECT [*] FROM `job_ops` WHERE job_id = ?"). 1514 WithArgs("job-111").WillReturnRows( 1515 sqlmock.NewRows([]string{ 1516 "created_at", "updated_at", "op", "job_id", "seq_id", 1517 }).AddRow(createdAt, updatedAt, model.JobOpStatusCanceled, "job-111", 1)) 1518 mock.ExpectRollback() 1519 }, 1520 err: errors.ErrJobAlreadyCanceled.GenWithStackByArgs("job-111"), 1521 }, 1522 // SetJobCanceling updates job operation to canceling if exists a noop job operation 1523 { 1524 fn: "SetJobCanceling", 1525 inputs: []interface{}{ 1526 "job-111", 1527 }, 1528 output: &ormResult{ 1529 rowsAffected: 2, 1530 }, 1531 mockExpectResFn: func(mock sqlmock.Sqlmock) { 1532 mock.ExpectBegin() 1533 mock.ExpectQuery(regexp.QuoteMeta( 1534 "SELECT count(*) FROM `job_ops` WHERE job_id = ?")). 1535 WithArgs("job-111").WillReturnRows( 1536 sqlmock.NewRows([]string{ 1537 "count(1)", 1538 }).AddRow(1)) 1539 mock.ExpectQuery( 1540 "SELECT [*] FROM `job_ops` WHERE job_id = ?"). 1541 WithArgs("job-111").WillReturnRows( 1542 sqlmock.NewRows([]string{ 1543 "created_at", "updated_at", "op", "job_id", "seq_id", 1544 }).AddRow(createdAt, updatedAt, model.JobOpStatusNoop, "job-111", 1)) 1545 mock.ExpectExec("ON DUPLICATE KEY UPDATE").WillReturnResult(sqlmock.NewResult(1, 2)) 1546 mock.ExpectCommit() 1547 }, 1548 }, 1549 // SetJobCanceled 1550 { 1551 fn: "SetJobCanceled", 1552 inputs: []interface{}{ 1553 "job-111", 1554 }, 1555 output: &ormResult{ 1556 rowsAffected: 1, 1557 }, 1558 mockExpectResFn: func(mock sqlmock.Sqlmock) { 1559 expectedSQL := "UPDATE `job_ops` SET `op`=?,`updated_at`=? WHERE job_id = ? AND op = ?" 1560 mock.ExpectExec(regexp.QuoteMeta(expectedSQL)). 1561 WithArgs(model.JobOpStatusCanceled, anyTime{}, "job-111", model.JobOpStatusCanceling). 1562 WillReturnResult(sqlmock.NewResult(0, 1)) 1563 }, 1564 }, 1565 // QueryJobOp 1566 { 1567 fn: "QueryJobOp", 1568 inputs: []interface{}{ 1569 "job-1", 1570 }, 1571 output: &model.JobOp{ 1572 Model: model.Model{ 1573 SeqID: 1, 1574 CreatedAt: createdAt, 1575 UpdatedAt: updatedAt, 1576 }, 1577 JobID: "job-1", 1578 Op: model.JobOpStatusCanceling, 1579 }, 1580 mockExpectResFn: func(mock sqlmock.Sqlmock) { 1581 expectedSQL := "SELECT * FROM `job_ops` WHERE job_id = ?" 1582 mock.ExpectQuery(regexp.QuoteMeta(expectedSQL)). 1583 WillReturnRows( 1584 sqlmock.NewRows([]string{"created_at", "updated_at", "op", "job_id", "seq_id"}). 1585 AddRow(createdAt, updatedAt, model.JobOpStatusCanceling, "job-1", 1)) 1586 }, 1587 }, 1588 // QueryJobOpsByStatus 1589 { 1590 fn: "QueryJobOpsByStatus", 1591 inputs: []interface{}{ 1592 model.JobOpStatusCanceling, 1593 }, 1594 output: []*model.JobOp{ 1595 { 1596 Model: model.Model{ 1597 SeqID: 1, 1598 CreatedAt: createdAt, 1599 UpdatedAt: updatedAt, 1600 }, 1601 JobID: "job-1", 1602 Op: model.JobOpStatusCanceling, 1603 }, 1604 }, 1605 mockExpectResFn: func(mock sqlmock.Sqlmock) { 1606 expectedSQL := "SELECT * FROM `job_ops` WHERE op = ?" 1607 mock.ExpectQuery(regexp.QuoteMeta(expectedSQL)). 1608 WithArgs(model.JobOpStatusCanceling). 1609 WillReturnRows( 1610 sqlmock.NewRows([]string{"created_at", "updated_at", "op", "job_id", "seq_id"}). 1611 AddRow(createdAt, updatedAt, model.JobOpStatusCanceling, "job-1", 1)) 1612 }, 1613 }, 1614 } 1615 1616 for _, tc := range testCases { 1617 testInner(t, mock, cli, tc) 1618 } 1619 } 1620 1621 func TestExecutorClient(t *testing.T) { 1622 t.Parallel() 1623 1624 sqlDB, mock := mockGetDBConn(t) 1625 defer sqlDB.Close() 1626 defer mock.ExpectClose() 1627 cli, err := newClient(sqlDB, defaultTestStoreType) 1628 require.Nil(t, err) 1629 require.NotNil(t, cli) 1630 1631 tm := time.Now() 1632 createdAt := tm.Add(time.Duration(1)) 1633 updatedAt := tm.Add(time.Duration(1)) 1634 1635 executor := &model.Executor{ 1636 Model: model.Model{ 1637 CreatedAt: createdAt, 1638 UpdatedAt: updatedAt, 1639 }, 1640 ID: "executor-0-1234", 1641 Name: "executor-0", 1642 Address: "127.0.0.1:1234", 1643 Labels: map[label.Key]label.Value{ 1644 "key1": "val1", 1645 "key2": "val2", 1646 }, 1647 } 1648 1649 testCases := []tCase{ 1650 { 1651 fn: "CreateExecutor", 1652 inputs: []interface{}{ 1653 executor, 1654 }, 1655 output: &ormResult{ 1656 rowsAffected: 1, 1657 }, 1658 mockExpectResFn: func(mock sqlmock.Sqlmock) { 1659 mock.ExpectExec(regexp.QuoteMeta("INSERT INTO `executors` (`created_at`,`updated_at`,`id`,`name`,`address`,`labels`) VALUES (?,?,?,?,?,?)")). 1660 WithArgs(createdAt, updatedAt, executor.ID, executor.Name, executor.Address, "{\"key1\":\"val1\",\"key2\":\"val2\"}"). 1661 WillReturnResult(sqlmock.NewResult(1, 1)) 1662 }, 1663 }, 1664 { 1665 fn: "UpdateExecutor", 1666 inputs: []interface{}{ 1667 executor, 1668 }, 1669 output: &ormResult{ 1670 rowsAffected: 1, 1671 }, 1672 mockExpectResFn: func(mock sqlmock.Sqlmock) { 1673 mock.ExpectExec(regexp.QuoteMeta("UPDATE `executors` SET `address`=?,`id`=?,`labels`=?,`name`=?,`updated_at`=? WHERE id = ?")). 1674 WithArgs(executor.Address, executor.ID, "{\"key1\":\"val1\",\"key2\":\"val2\"}", executor.Name, sqlmock.AnyArg(), executor.ID). 1675 WillReturnResult(sqlmock.NewResult(0, 1)) 1676 }, 1677 }, 1678 { 1679 fn: "DeleteExecutor", 1680 inputs: []interface{}{ 1681 executor.ID, 1682 }, 1683 output: &ormResult{ 1684 rowsAffected: 1, 1685 }, 1686 mockExpectResFn: func(mock sqlmock.Sqlmock) { 1687 mock.ExpectExec(regexp.QuoteMeta("DELETE FROM `executors` WHERE id = ?")). 1688 WithArgs(executor.ID).WillReturnResult(sqlmock.NewResult(0, 1)) 1689 }, 1690 }, 1691 { 1692 fn: "QueryExecutors", 1693 inputs: []interface{}{}, 1694 output: []*model.Executor{executor}, 1695 mockExpectResFn: func(mock sqlmock.Sqlmock) { 1696 mock.ExpectQuery(regexp.QuoteMeta("SELECT * FROM `executors`")). 1697 WillReturnRows(sqlmock.NewRows([]string{ 1698 "seq_id", "created_at", "updated_at", "id", "name", "address", "labels", 1699 }).AddRow(1, createdAt, updatedAt, executor.ID, executor.Name, 1700 executor.Address, "{\"key1\":\"val1\",\"key2\":\"val2\"}")) 1701 }, 1702 }, 1703 } 1704 1705 for _, tc := range testCases { 1706 testInner(t, mock, cli, tc) 1707 } 1708 } 1709 1710 func testInner(t *testing.T, m sqlmock.Sqlmock, cli Client, c tCase) { 1711 // set the mock expectation 1712 c.mockExpectResFn(m) 1713 1714 var args []reflect.Value 1715 args = append(args, reflect.ValueOf(context.Background())) 1716 for _, ip := range c.inputs { 1717 args = append(args, reflect.ValueOf(ip)) 1718 } 1719 result := reflect.ValueOf(cli).MethodByName(c.fn).Call(args) 1720 // only error 1721 if len(result) == 1 { 1722 if c.err == nil { 1723 require.Nil(t, result[0].Interface()) 1724 } else { 1725 require.NotNil(t, result[0].Interface()) 1726 require.Error(t, result[0].Interface().(error)) 1727 } 1728 } else if len(result) == 2 { 1729 // result and error 1730 if c.err != nil { 1731 require.NotNil(t, result[1].Interface()) 1732 require.Error(t, result[1].Interface().(error)) 1733 } else { 1734 require.Equal(t, c.output, result[0].Interface()) 1735 } 1736 } 1737 }