github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/engine/pkg/orm/client.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 "time" 20 21 frameModel "github.com/pingcap/tiflow/engine/framework/model" 22 engineModel "github.com/pingcap/tiflow/engine/model" 23 resModel "github.com/pingcap/tiflow/engine/pkg/externalresource/model" 24 metaModel "github.com/pingcap/tiflow/engine/pkg/meta/model" 25 "github.com/pingcap/tiflow/engine/pkg/orm/model" 26 "github.com/pingcap/tiflow/pkg/errors" 27 "gorm.io/gorm" 28 "gorm.io/gorm/clause" 29 ) 30 31 var globalModels = []interface{}{ 32 &model.ProjectInfo{}, 33 &model.ProjectOperation{}, 34 &frameModel.MasterMeta{}, 35 &frameModel.WorkerStatus{}, 36 &resModel.ResourceMeta{}, 37 &model.LogicEpoch{}, 38 &model.JobOp{}, 39 &model.Executor{}, 40 } 41 42 // TODO: retry and idempotent?? 43 // TODO: split different client to module 44 45 type ( 46 // ResourceMeta is the alias of resModel.ResourceMeta 47 ResourceMeta = resModel.ResourceMeta 48 // ResourceKey is the alias of resModel.ResourceKey 49 ResourceKey = resModel.ResourceKey 50 ) 51 52 // TimeRange defines a time range with [start, end] time 53 type TimeRange struct { 54 start time.Time 55 end time.Time 56 } 57 58 // Client defines an interface that has the ability to manage every kind of 59 // logic abstraction in metastore, including project, project op, job, worker 60 // and resource 61 type Client interface { 62 metaModel.Client 63 // ProjectClient is the interface to operate project. 64 ProjectClient 65 // ProjectOperationClient is the client to operate project operation. 66 ProjectOperationClient 67 // JobClient is the interface to operate job info. 68 JobClient 69 // WorkerClient is the client to operate worker info. 70 WorkerClient 71 // ResourceClient is the interface to operate resource. 72 ResourceClient 73 // JobOpClient is the client to operate job operation. 74 JobOpClient 75 // ExecutorClient is the client to operate executor info. 76 ExecutorClient 77 } 78 79 // ProjectClient defines interface that manages project in metastore 80 type ProjectClient interface { 81 CreateProject(ctx context.Context, project *model.ProjectInfo) error 82 DeleteProject(ctx context.Context, projectID string) error 83 QueryProjects(ctx context.Context) ([]*model.ProjectInfo, error) 84 GetProjectByID(ctx context.Context, projectID string) (*model.ProjectInfo, error) 85 } 86 87 // ProjectOperationClient defines interface that manages project operation in metastore 88 // TODO: support pagination and cursor here 89 // support `order by time desc limit N` 90 type ProjectOperationClient interface { 91 CreateProjectOperation(ctx context.Context, op *model.ProjectOperation) error 92 QueryProjectOperations(ctx context.Context, projectID string) ([]*model.ProjectOperation, error) 93 QueryProjectOperationsByTimeRange(ctx context.Context, projectID string, tr TimeRange) ([]*model.ProjectOperation, error) 94 } 95 96 // JobClient defines interface that manages job in metastore 97 type JobClient interface { 98 InsertJob(ctx context.Context, job *frameModel.MasterMeta) error 99 UpsertJob(ctx context.Context, job *frameModel.MasterMeta) error 100 UpdateJob(ctx context.Context, jobID string, values model.KeyValueMap) error 101 DeleteJob(ctx context.Context, jobID string) (Result, error) 102 103 GetJobByID(ctx context.Context, jobID string) (*frameModel.MasterMeta, error) 104 QueryJobs(ctx context.Context) ([]*frameModel.MasterMeta, error) 105 QueryJobsByProjectID(ctx context.Context, projectID string) ([]*frameModel.MasterMeta, error) 106 QueryJobsByState(ctx context.Context, jobID string, state int) ([]*frameModel.MasterMeta, error) 107 } 108 109 // WorkerClient defines interface that manages worker in metastore 110 type WorkerClient interface { 111 UpsertWorker(ctx context.Context, worker *frameModel.WorkerStatus) error 112 UpdateWorker(ctx context.Context, worker *frameModel.WorkerStatus) error 113 DeleteWorker(ctx context.Context, masterID string, workerID string) (Result, error) 114 GetWorkerByID(ctx context.Context, masterID string, workerID string) (*frameModel.WorkerStatus, error) 115 QueryWorkersByMasterID(ctx context.Context, masterID string) ([]*frameModel.WorkerStatus, error) 116 QueryWorkersByState(ctx context.Context, masterID string, state int) ([]*frameModel.WorkerStatus, error) 117 } 118 119 // ResourceClient defines interface that manages resource in metastore 120 type ResourceClient interface { 121 CreateResource(ctx context.Context, resource *ResourceMeta) error 122 UpsertResource(ctx context.Context, resource *ResourceMeta) error 123 UpdateResource(ctx context.Context, resource *ResourceMeta) error 124 125 GetResourceByID(ctx context.Context, resourceKey ResourceKey) (*ResourceMeta, error) 126 QueryResources(ctx context.Context) ([]*ResourceMeta, error) 127 QueryResourcesByJobID(ctx context.Context, jobID string) ([]*ResourceMeta, error) 128 QueryResourcesByExecutorIDs(ctx context.Context, 129 executorID ...engineModel.ExecutorID) ([]*ResourceMeta, error) 130 131 SetGCPendingByJobs(ctx context.Context, jobIDs ...engineModel.JobID) error 132 GetOneResourceForGC(ctx context.Context) (*ResourceMeta, error) 133 134 DeleteResource(ctx context.Context, resourceKey ResourceKey) (Result, error) 135 DeleteResourcesByTypeAndExecutorIDs(ctx context.Context, 136 resType resModel.ResourceType, executorID ...engineModel.ExecutorID) (Result, error) 137 } 138 139 // JobOpClient defines interface that operates job status (upper logic oriented) 140 type JobOpClient interface { 141 SetJobNoop(ctx context.Context, jobID string) (Result, error) 142 SetJobCanceling(ctx context.Context, JobID string) (Result, error) 143 SetJobCanceled(ctx context.Context, jobID string) (Result, error) 144 QueryJobOp(ctx context.Context, jobID string) (*model.JobOp, error) 145 QueryJobOpsByStatus(ctx context.Context, op model.JobOpStatus) ([]*model.JobOp, error) 146 } 147 148 // ExecutorClient defines interface that manages executor information in metastore. 149 type ExecutorClient interface { 150 CreateExecutor(ctx context.Context, executor *model.Executor) error 151 UpdateExecutor(ctx context.Context, executor *model.Executor) error 152 DeleteExecutor(ctx context.Context, executorID engineModel.ExecutorID) error 153 QueryExecutors(ctx context.Context) ([]*model.Executor, error) 154 } 155 156 // NewClient return the client to operate framework metastore 157 func NewClient(cc metaModel.ClientConn) (Client, error) { 158 if cc == nil { 159 return nil, errors.ErrMetaParamsInvalid.GenWithStackByArgs("input client conn is nil") 160 } 161 162 conn, err := cc.GetConn() 163 if err != nil { 164 return nil, err 165 } 166 167 sqlDB, ok := conn.(*sql.DB) 168 if !ok { 169 return nil, errors.ErrMetaParamsInvalid.GenWithStack("input client conn is not a sql type:%s", 170 cc.StoreType()) 171 } 172 173 return newClient(sqlDB, cc.StoreType()) 174 } 175 176 func newClient(db *sql.DB, storeType metaModel.StoreType) (Client, error) { 177 ormDB, err := NewGormDB(db, storeType) 178 if err != nil { 179 return nil, err 180 } 181 182 epCli, err := model.NewEpochClient("" /*jobID*/, ormDB) 183 if err != nil { 184 return nil, err 185 } 186 187 return &metaOpsClient{ 188 db: ormDB, 189 epochClient: epCli, 190 }, nil 191 } 192 193 // metaOpsClient is the meta operations client for framework metastore 194 type metaOpsClient struct { 195 // gorm claim to be thread safe 196 db *gorm.DB 197 epochClient model.EpochClient 198 } 199 200 func (c *metaOpsClient) Close() error { 201 // DON NOT CLOSE the underlying connection 202 return nil 203 } 204 205 // ///////////////////////////// Logic Epoch 206 func (c *metaOpsClient) GenEpoch(ctx context.Context) (frameModel.Epoch, error) { 207 return c.epochClient.GenEpoch(ctx) 208 } 209 210 // /////////////////////// Project Operation 211 // CreateProject insert the model.ProjectInfo 212 func (c *metaOpsClient) CreateProject(ctx context.Context, project *model.ProjectInfo) error { 213 if project == nil { 214 return errors.ErrMetaParamsInvalid.GenWithStackByArgs("input project info is nil") 215 } 216 if err := c.db.WithContext(ctx). 217 Create(project).Error; err != nil { 218 return errors.ErrMetaOpFail.Wrap(err) 219 } 220 221 return nil 222 } 223 224 // DeleteProject delete the model.ProjectInfo 225 func (c *metaOpsClient) DeleteProject(ctx context.Context, projectID string) error { 226 if err := c.db.WithContext(ctx). 227 Where("id=?", projectID). 228 Delete(&model.ProjectInfo{}).Error; err != nil { 229 return errors.ErrMetaOpFail.Wrap(err) 230 } 231 232 return nil 233 } 234 235 // QueryProject query all projects 236 func (c *metaOpsClient) QueryProjects(ctx context.Context) ([]*model.ProjectInfo, error) { 237 var projects []*model.ProjectInfo 238 if err := c.db.WithContext(ctx). 239 Find(&projects).Error; err != nil { 240 return nil, errors.ErrMetaOpFail.Wrap(err) 241 } 242 243 return projects, nil 244 } 245 246 // GetProjectByID query project by projectID 247 func (c *metaOpsClient) GetProjectByID(ctx context.Context, projectID string) (*model.ProjectInfo, error) { 248 var project model.ProjectInfo 249 if err := c.db.WithContext(ctx). 250 Where("id = ?", projectID). 251 First(&project).Error; err != nil { 252 if err == gorm.ErrRecordNotFound { 253 return nil, errors.ErrMetaEntryNotFound.Wrap(err) 254 } 255 256 return nil, errors.ErrMetaOpFail.Wrap(err) 257 } 258 259 return &project, nil 260 } 261 262 // CreateProjectOperation insert the operation 263 func (c *metaOpsClient) CreateProjectOperation(ctx context.Context, op *model.ProjectOperation) error { 264 if op == nil { 265 return errors.ErrMetaParamsInvalid.GenWithStackByArgs("input project operation is nil") 266 } 267 268 if err := c.db.WithContext(ctx). 269 Create(op).Error; err != nil { 270 return errors.ErrMetaOpFail.Wrap(err) 271 } 272 273 return nil 274 } 275 276 // QueryProjectOperations query all operations of the projectID 277 func (c *metaOpsClient) QueryProjectOperations(ctx context.Context, projectID string) ([]*model.ProjectOperation, error) { 278 var projectOps []*model.ProjectOperation 279 if err := c.db.WithContext(ctx). 280 Where("project_id = ?", projectID). 281 Find(&projectOps).Error; err != nil { 282 return nil, errors.ErrMetaOpFail.Wrap(err) 283 } 284 285 return projectOps, nil 286 } 287 288 // QueryProjectOperationsByTimeRange query project operation betweem a time range of the projectID 289 func (c *metaOpsClient) QueryProjectOperationsByTimeRange(ctx context.Context, 290 projectID string, tr TimeRange, 291 ) ([]*model.ProjectOperation, error) { 292 var projectOps []*model.ProjectOperation 293 if err := c.db.WithContext(ctx). 294 Where("project_id = ? AND created_at >= ? AND created_at <= ?", projectID, tr.start, tr.end). 295 Find(&projectOps).Error; err != nil { 296 return nil, errors.ErrMetaOpFail.Wrap(err) 297 } 298 299 return projectOps, nil 300 } 301 302 // ///////////////////////////// Job Operation 303 // InsertJob insert the jobInfo 304 func (c *metaOpsClient) InsertJob(ctx context.Context, job *frameModel.MasterMeta) error { 305 if job == nil { 306 return errors.ErrMetaParamsInvalid.GenWithStackByArgs("input master meta is nil") 307 } 308 309 if err := c.db.WithContext(ctx).Create(job).Error; err != nil { 310 return errors.ErrMetaOpFail.Wrap(err) 311 } 312 313 return nil 314 } 315 316 // UpsertJob upsert the jobInfo 317 func (c *metaOpsClient) UpsertJob(ctx context.Context, job *frameModel.MasterMeta) error { 318 if job == nil { 319 return errors.ErrMetaParamsInvalid.GenWithStackByArgs("input master meta is nil") 320 } 321 322 if err := c.db.WithContext(ctx). 323 Clauses(clause.OnConflict{ 324 Columns: []clause.Column{{Name: "id"}}, 325 DoUpdates: clause.AssignmentColumns(frameModel.MasterUpdateColumns), 326 }).Create(job).Error; err != nil { 327 return errors.ErrMetaOpFail.Wrap(err) 328 } 329 330 return nil 331 } 332 333 // UpdateJob update the jobInfo 334 func (c *metaOpsClient) UpdateJob( 335 ctx context.Context, jobID string, values model.KeyValueMap, 336 ) error { 337 if err := c.db.WithContext(ctx). 338 Model(&frameModel.MasterMeta{}). 339 Where("id = ?", jobID). 340 Updates(values).Error; err != nil { 341 return errors.ErrMetaOpFail.Wrap(err) 342 } 343 344 return nil 345 } 346 347 // DeleteJob delete the specified jobInfo 348 func (c *metaOpsClient) DeleteJob(ctx context.Context, jobID string) (Result, error) { 349 result := c.db.WithContext(ctx). 350 Where("id = ?", jobID). 351 Delete(&frameModel.MasterMeta{}) 352 if result.Error != nil { 353 return nil, errors.ErrMetaOpFail.Wrap(result.Error) 354 } 355 356 return &ormResult{rowsAffected: result.RowsAffected}, nil 357 } 358 359 // GetJobByID query job by `jobID` 360 func (c *metaOpsClient) GetJobByID(ctx context.Context, jobID string) (*frameModel.MasterMeta, error) { 361 var job frameModel.MasterMeta 362 if err := c.db.WithContext(ctx). 363 Where("id = ?", jobID). 364 First(&job).Error; err != nil { 365 if err == gorm.ErrRecordNotFound { 366 return nil, errors.ErrMetaEntryNotFound.Wrap(err) 367 } 368 369 return nil, errors.ErrMetaOpFail.Wrap(err) 370 } 371 372 return &job, nil 373 } 374 375 // QueryJobsByProjectID query all jobs of projectID 376 func (c *metaOpsClient) QueryJobs(ctx context.Context) ([]*frameModel.MasterMeta, error) { 377 var jobs []*frameModel.MasterMeta 378 if err := c.db.WithContext(ctx). 379 Find(&jobs).Error; err != nil { 380 return nil, errors.ErrMetaOpFail.Wrap(err) 381 } 382 383 return jobs, nil 384 } 385 386 // QueryJobsByProjectID query all jobs of projectID 387 func (c *metaOpsClient) QueryJobsByProjectID(ctx context.Context, projectID string) ([]*frameModel.MasterMeta, error) { 388 var jobs []*frameModel.MasterMeta 389 if err := c.db.WithContext(ctx). 390 Where("project_id = ?", projectID). 391 Find(&jobs).Error; err != nil { 392 return nil, errors.ErrMetaOpFail.Wrap(err) 393 } 394 395 return jobs, nil 396 } 397 398 // QueryJobsByState query all jobs with `state` of the projectID 399 func (c *metaOpsClient) QueryJobsByState(ctx context.Context, 400 jobID string, state int, 401 ) ([]*frameModel.MasterMeta, error) { 402 var jobs []*frameModel.MasterMeta 403 if err := c.db.WithContext(ctx). 404 Where("project_id = ? AND state = ?", jobID, state). 405 Find(&jobs).Error; err != nil { 406 return nil, errors.ErrMetaOpFail.Wrap(err) 407 } 408 409 return jobs, nil 410 } 411 412 // ///////////////////////////// Worker Operation 413 // UpsertWorker insert the workerInfo 414 func (c *metaOpsClient) UpsertWorker(ctx context.Context, worker *frameModel.WorkerStatus) error { 415 if worker == nil { 416 return errors.ErrMetaParamsInvalid.GenWithStackByArgs("input worker meta is nil") 417 } 418 419 if err := c.db.WithContext(ctx). 420 Clauses(clause.OnConflict{ 421 Columns: []clause.Column{{Name: "id"}, {Name: "job_id"}}, 422 DoUpdates: clause.AssignmentColumns(frameModel.WorkerUpdateColumns), 423 }).Create(worker).Error; err != nil { 424 return errors.ErrMetaOpFail.Wrap(err) 425 } 426 427 return nil 428 } 429 430 func (c *metaOpsClient) UpdateWorker(ctx context.Context, worker *frameModel.WorkerStatus) error { 431 if worker == nil { 432 return errors.ErrMetaParamsInvalid.GenWithStackByArgs("input worker meta is nil") 433 } 434 // we don't use `Save` here to avoid user dealing with the basic model 435 if err := c.db.WithContext(ctx). 436 Model(&frameModel.WorkerStatus{}). 437 Where("job_id = ? AND id = ?", worker.JobID, worker.ID). 438 Updates(worker.Map()).Error; err != nil { 439 return errors.ErrMetaOpFail.Wrap(err) 440 } 441 442 return nil 443 } 444 445 // DeleteWorker delete the specified workInfo 446 func (c *metaOpsClient) DeleteWorker(ctx context.Context, masterID string, workerID string) (Result, error) { 447 result := c.db.WithContext(ctx). 448 Where("job_id = ? AND id = ?", masterID, workerID). 449 Delete(&frameModel.WorkerStatus{}) 450 if result.Error != nil { 451 return nil, errors.ErrMetaOpFail.Wrap(result.Error) 452 } 453 454 return &ormResult{rowsAffected: result.RowsAffected}, nil 455 } 456 457 // GetWorkerByID query worker info by workerID 458 func (c *metaOpsClient) GetWorkerByID(ctx context.Context, masterID string, workerID string) (*frameModel.WorkerStatus, error) { 459 var worker frameModel.WorkerStatus 460 if err := c.db.WithContext(ctx). 461 Where("job_id = ? AND id = ?", masterID, workerID). 462 First(&worker).Error; err != nil { 463 if err == gorm.ErrRecordNotFound { 464 return nil, errors.ErrMetaEntryNotFound.Wrap(err) 465 } 466 467 return nil, errors.ErrMetaOpFail.Wrap(err) 468 } 469 470 return &worker, nil 471 } 472 473 // QueryWorkersByMasterID query all workers of masterID 474 func (c *metaOpsClient) QueryWorkersByMasterID(ctx context.Context, masterID string) ([]*frameModel.WorkerStatus, error) { 475 var workers []*frameModel.WorkerStatus 476 if err := c.db.WithContext(ctx). 477 Where("job_id = ?", masterID). 478 Find(&workers).Error; err != nil { 479 return nil, errors.ErrMetaOpFail.Wrap(err) 480 } 481 482 return workers, nil 483 } 484 485 // QueryWorkersByState query all workers with specified state of masterID 486 func (c *metaOpsClient) QueryWorkersByState(ctx context.Context, masterID string, state int) ([]*frameModel.WorkerStatus, error) { 487 var workers []*frameModel.WorkerStatus 488 if err := c.db.WithContext(ctx). 489 Where("job_id = ? AND state = ?", masterID, state). 490 Find(&workers).Error; err != nil { 491 return nil, errors.ErrMetaOpFail.Wrap(err) 492 } 493 494 return workers, nil 495 } 496 497 // ///////////////////////////// Resource Operation 498 // UpsertResource upsert the ResourceMeta 499 func (c *metaOpsClient) UpsertResource(ctx context.Context, resource *resModel.ResourceMeta) error { 500 if resource == nil { 501 return errors.ErrMetaParamsInvalid.GenWithStackByArgs("input resource meta is nil") 502 } 503 504 if err := c.db.WithContext(ctx). 505 Clauses(clause.OnConflict{ 506 Columns: []clause.Column{{Name: "job_id"}, {Name: "id"}}, 507 DoUpdates: clause.AssignmentColumns(resModel.ResourceUpdateColumns), 508 }).Create(resource).Error; err != nil { 509 return errors.ErrMetaOpFail.Wrap(err) 510 } 511 512 return nil 513 } 514 515 // CreateResource insert a resource meta. 516 // Return 'ErrDuplicateResourceID' error if it already exists. 517 func (c *metaOpsClient) CreateResource(ctx context.Context, resource *resModel.ResourceMeta) error { 518 if resource == nil { 519 return errors.ErrMetaParamsInvalid.GenWithStackByArgs("input resource meta is nil") 520 } 521 522 err := c.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { 523 var count int64 524 err := tx.Model(&resModel.ResourceMeta{}). 525 Where("job_id = ? AND id = ?", resource.Job, resource.ID). 526 Count(&count).Error 527 if err != nil { 528 return err 529 } 530 531 if count > 0 { 532 return errors.ErrDuplicateResourceID.GenWithStackByArgs(resource.ID) 533 } 534 535 if err := tx.Create(resource).Error; err != nil { 536 return errors.ErrMetaOpFail.Wrap(err) 537 } 538 return nil 539 }) 540 if err != nil { 541 return errors.ErrMetaOpFail.Wrap(err) 542 } 543 return nil 544 } 545 546 // UpdateResource update the resModel 547 func (c *metaOpsClient) UpdateResource(ctx context.Context, resource *resModel.ResourceMeta) error { 548 if resource == nil { 549 return errors.ErrMetaParamsInvalid.GenWithStackByArgs("input resource meta is nil") 550 } 551 // we don't use `Save` here to avoid user dealing with the basic model 552 if err := c.db.WithContext(ctx). 553 Model(&resModel.ResourceMeta{}). 554 Where("job_id = ? AND id = ?", resource.Job, resource.ID). 555 Updates(resource.Map()).Error; err != nil { 556 return errors.ErrMetaOpFail.Wrap(err) 557 } 558 559 return nil 560 } 561 562 // DeleteResource delete the resource meta of specified resourceKey 563 func (c *metaOpsClient) DeleteResource(ctx context.Context, resourceKey ResourceKey) (Result, error) { 564 result := c.db.WithContext(ctx). 565 Where("job_id = ? AND id = ?", resourceKey.JobID, resourceKey.ID). 566 Delete(&resModel.ResourceMeta{}) 567 if result.Error != nil { 568 return nil, errors.ErrMetaOpFail.Wrap(result.Error) 569 } 570 571 return &ormResult{rowsAffected: result.RowsAffected}, nil 572 } 573 574 // GetResourceByID query resource of the resourceKey 575 func (c *metaOpsClient) GetResourceByID(ctx context.Context, resourceKey ResourceKey) (*resModel.ResourceMeta, error) { 576 var resource resModel.ResourceMeta 577 if err := c.db.WithContext(ctx). 578 Where("job_id = ? AND id = ?", resourceKey.JobID, resourceKey.ID). 579 First(&resource).Error; err != nil { 580 if err == gorm.ErrRecordNotFound { 581 return nil, errors.ErrMetaEntryNotFound.Wrap(err) 582 } 583 584 return nil, errors.ErrMetaOpFail.Wrap(err) 585 } 586 587 return &resource, nil 588 } 589 590 // QueryResources get all resource meta 591 func (c *metaOpsClient) QueryResources(ctx context.Context) ([]*resModel.ResourceMeta, error) { 592 var resources []*resModel.ResourceMeta 593 if err := c.db.WithContext(ctx). 594 Find(&resources).Error; err != nil { 595 return nil, errors.ErrMetaOpFail.Wrap(err) 596 } 597 598 return resources, nil 599 } 600 601 // QueryResourcesByJobID query all resources of the jobID 602 func (c *metaOpsClient) QueryResourcesByJobID(ctx context.Context, jobID string) ([]*resModel.ResourceMeta, error) { 603 var resources []*resModel.ResourceMeta 604 if err := c.db.WithContext(ctx). 605 Where("job_id = ?", jobID). 606 Find(&resources).Error; err != nil { 607 return nil, errors.ErrMetaOpFail.Wrap(err) 608 } 609 610 return resources, nil 611 } 612 613 // QueryResourcesByExecutorIDs query all resources of the executorIDs 614 func (c *metaOpsClient) QueryResourcesByExecutorIDs( 615 ctx context.Context, executorIDs ...engineModel.ExecutorID, 616 ) ([]*resModel.ResourceMeta, error) { 617 var resources []*resModel.ResourceMeta 618 if err := c.db.WithContext(ctx). 619 Where("executor_id in ?", executorIDs). 620 Find(&resources).Error; err != nil { 621 return nil, errors.ErrMetaOpFail.Wrap(err) 622 } 623 624 return resources, nil 625 } 626 627 // DeleteResourcesByTypeAndExecutorIDs delete a specific type of resources of executorID 628 func (c *metaOpsClient) DeleteResourcesByTypeAndExecutorIDs( 629 ctx context.Context, resType resModel.ResourceType, executorIDs ...engineModel.ExecutorID, 630 ) (Result, error) { 631 var result *gorm.DB 632 if len(executorIDs) == 1 { 633 result = c.db.WithContext(ctx). 634 Where("executor_id = ? and id like ?", executorIDs[0], resType.BuildPrefix()+"%"). 635 Delete(&resModel.ResourceMeta{}) 636 } else { 637 result = c.db.WithContext(ctx). 638 Where("executor_id in ? and id like ?", executorIDs, resType.BuildPrefix()+"%"). 639 Delete(&resModel.ResourceMeta{}) 640 } 641 if result.Error == nil { 642 return &ormResult{rowsAffected: result.RowsAffected}, nil 643 } 644 645 return nil, errors.ErrMetaOpFail.Wrap(result.Error) 646 } 647 648 // SetGCPendingByJobs set the resourceIDs to the state `waiting to gc` 649 func (c *metaOpsClient) SetGCPendingByJobs(ctx context.Context, jobIDs ...engineModel.JobID) error { 650 err := c.db.WithContext(ctx). 651 Model(&resModel.ResourceMeta{}). 652 Where("job_id in ?", jobIDs). 653 Update("gc_pending", true).Error 654 if err == nil { 655 return nil 656 } 657 return errors.ErrMetaOpFail.Wrap(err) 658 } 659 660 // GetOneResourceForGC get one resource ready for gc 661 func (c *metaOpsClient) GetOneResourceForGC(ctx context.Context) (*resModel.ResourceMeta, error) { 662 var ret resModel.ResourceMeta 663 err := c.db.WithContext(ctx). 664 Order("updated_at asc"). 665 Where("gc_pending = true"). 666 First(&ret).Error 667 if err != nil { 668 if errors.Is(err, gorm.ErrRecordNotFound) { 669 return nil, errors.ErrMetaEntryNotFound.Wrap(err) 670 } 671 return nil, errors.ErrMetaOpFail.Wrap(err) 672 } 673 return &ret, nil 674 } 675 676 // SetJobNoop sets a job noop status if a job op record exists and status is 677 // canceling. This API is used when processing a job operation but the metadata 678 // of this job is not found (or deleted manually by accident). 679 func (c *metaOpsClient) SetJobNoop(ctx context.Context, jobID string) (Result, error) { 680 result := &ormResult{} 681 ops := &model.JobOp{ 682 Op: model.JobOpStatusNoop, 683 } 684 exec := c.db.WithContext(ctx). 685 Model(&model.JobOp{}). 686 Where("job_id = ? AND op = ?", jobID, model.JobOpStatusCanceling). 687 Updates(ops.Map()) 688 if err := exec.Error; err != nil { 689 return result, errors.WrapError(errors.ErrMetaOpFail, err) 690 } 691 result.rowsAffected = exec.RowsAffected 692 return result, nil 693 } 694 695 // SetJobCanceling sets a job cancelling status if this op record doesn't exist. 696 // If a job cancelling op already exists, does nothing. 697 // If the job is already cancelled, return ErrJobAlreadyCanceled error. 698 func (c *metaOpsClient) SetJobCanceling(ctx context.Context, jobID string) (Result, error) { 699 result := &ormResult{} 700 err := c.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { 701 var count int64 702 var ops model.JobOp 703 query := tx.Model(&model.JobOp{}).Where("job_id = ?", jobID) 704 if err := query.Count(&count).Error; err != nil { 705 return errors.WrapError(errors.ErrMetaOpFail, err) 706 } 707 if count > 0 { 708 if err := query.First(&ops).Error; err != nil { 709 return errors.WrapError(errors.ErrMetaOpFail, err) 710 } 711 switch ops.Op { 712 case model.JobOpStatusCanceling: 713 return nil 714 case model.JobOpStatusCanceled: 715 return errors.ErrJobAlreadyCanceled.GenWithStackByArgs(jobID) 716 default: 717 } 718 } 719 ops = model.JobOp{ 720 Op: model.JobOpStatusCanceling, 721 JobID: jobID, 722 } 723 exec := tx.Clauses( 724 clause.OnConflict{ 725 Columns: []clause.Column{{Name: "job_id"}}, 726 DoUpdates: clause.AssignmentColumns(model.JobOpUpdateColumns), 727 }).Create(&ops) 728 if err := exec.Error; err != nil { 729 return errors.WrapError(errors.ErrMetaOpFail, err) 730 } 731 result.rowsAffected = exec.RowsAffected 732 return nil 733 }) 734 return result, errors.WrapError(errors.ErrMetaOpFail, err) 735 } 736 737 // SetJobCanceled sets a cancelled status if a cancelling op exists. 738 // - If cancelling operation is not found, it can be triggered by unexpected 739 // SetJobCanceled don't make any change and return nil error. 740 // - If a job is already cancelled, don't make any change and return nil error. 741 func (c *metaOpsClient) SetJobCanceled(ctx context.Context, jobID string) (Result, error) { 742 result := &ormResult{} 743 ops := &model.JobOp{ 744 Op: model.JobOpStatusCanceled, 745 } 746 exec := c.db.WithContext(ctx). 747 Model(&model.JobOp{}). 748 Where("job_id = ? AND op = ?", jobID, model.JobOpStatusCanceling). 749 Updates(ops.Map()) 750 if err := exec.Error; err != nil { 751 return result, errors.WrapError(errors.ErrMetaOpFail, err) 752 } 753 result.rowsAffected = exec.RowsAffected 754 return result, nil 755 } 756 757 // QueryJobOp queries a JobOp based on jobID 758 func (c *metaOpsClient) QueryJobOp( 759 ctx context.Context, jobID string, 760 ) (*model.JobOp, error) { 761 var op *model.JobOp 762 err := c.db.WithContext(ctx).Where("job_id = ?", jobID).Find(&op).Error 763 if err != nil { 764 return nil, err 765 } 766 return op, nil 767 } 768 769 // QueryJobOpsByStatus query all jobOps with given `op` 770 func (c *metaOpsClient) QueryJobOpsByStatus( 771 ctx context.Context, op model.JobOpStatus, 772 ) ([]*model.JobOp, error) { 773 var ops []*model.JobOp 774 if err := c.db.WithContext(ctx). 775 Where("op = ?", op). 776 Find(&ops).Error; err != nil { 777 return nil, errors.ErrMetaOpFail.Wrap(err) 778 } 779 return ops, nil 780 } 781 782 // CreateExecutor creates an executor in the metastore. 783 func (c *metaOpsClient) CreateExecutor(ctx context.Context, executor *model.Executor) error { 784 if err := c.db.WithContext(ctx). 785 Create(executor).Error; err != nil { 786 return errors.ErrMetaOpFail.Wrap(err) 787 } 788 return nil 789 } 790 791 // UpdateExecutor updates an executor in the metastore. 792 func (c *metaOpsClient) UpdateExecutor(ctx context.Context, executor *model.Executor) error { 793 if err := c.db.WithContext(ctx). 794 Model(&model.Executor{}). 795 Where("id = ?", executor.ID). 796 Updates(executor.Map()).Error; err != nil { 797 return errors.ErrMetaOpFail.Wrap(err) 798 } 799 return nil 800 } 801 802 // DeleteExecutor deletes an executor in the metastore. 803 func (c *metaOpsClient) DeleteExecutor(ctx context.Context, executorID engineModel.ExecutorID) error { 804 if err := c.db.WithContext(ctx). 805 Where("id = ?", executorID). 806 Delete(&model.Executor{}).Error; err != nil { 807 return errors.ErrMetaOpFail.Wrap(err) 808 } 809 return nil 810 } 811 812 // QueryExecutors query all executors in the metastore. 813 func (c *metaOpsClient) QueryExecutors(ctx context.Context) ([]*model.Executor, error) { 814 var executors []*model.Executor 815 if err := c.db.WithContext(ctx). 816 Find(&executors).Error; err != nil { 817 return nil, errors.ErrMetaOpFail.Wrap(err) 818 } 819 return executors, nil 820 } 821 822 // Result defines a query result interface 823 type Result interface { 824 RowsAffected() int64 825 } 826 827 type ormResult struct { 828 rowsAffected int64 829 } 830 831 // RowsAffected return the affected rows of an execution 832 func (r ormResult) RowsAffected() int64 { 833 return r.rowsAffected 834 }