github.com/pf-qiu/concourse/v6@v6.7.3-0.20201207032516-1f455d73275f/atc/db/volume.go (about) 1 package db 2 3 import ( 4 "database/sql" 5 "encoding/json" 6 "errors" 7 "fmt" 8 9 sq "github.com/Masterminds/squirrel" 10 "github.com/pf-qiu/concourse/v6/atc" 11 "github.com/lib/pq" 12 uuid "github.com/nu7hatch/gouuid" 13 ) 14 15 var ( 16 ErrVolumeCannotBeDestroyedWithChildrenPresent = errors.New("volume cannot be destroyed as children are present") 17 ErrVolumeStateTransitionFailed = errors.New("could not transition volume state") 18 ErrVolumeMissing = errors.New("volume no longer in db") 19 ErrInvalidResourceCache = errors.New("invalid resource cache") 20 ) 21 22 type ErrVolumeMarkStateFailed struct { 23 State VolumeState 24 } 25 26 func (e ErrVolumeMarkStateFailed) Error() string { 27 return fmt.Sprintf("could not mark volume as %s", e.State) 28 } 29 30 type ErrVolumeMarkCreatedFailed struct { 31 Handle string 32 } 33 34 func (e ErrVolumeMarkCreatedFailed) Error() string { 35 return fmt.Sprintf("failed to mark volume as created %s", e.Handle) 36 } 37 38 type VolumeState string 39 40 const ( 41 VolumeStateCreating VolumeState = "creating" 42 VolumeStateCreated VolumeState = "created" 43 VolumeStateDestroying VolumeState = "destroying" 44 VolumeStateFailed VolumeState = "failed" 45 ) 46 47 type VolumeType string 48 49 const ( 50 VolumeTypeContainer VolumeType = "container" 51 VolumeTypeResource VolumeType = "resource" 52 VolumeTypeResourceType VolumeType = "resource-type" 53 VolumeTypeResourceCerts VolumeType = "resource-certs" 54 VolumeTypeTaskCache VolumeType = "task-cache" 55 VolumeTypeArtifact VolumeType = "artifact" 56 VolumeTypeUknown VolumeType = "unknown" // for migration to life 57 ) 58 59 //go:generate counterfeiter . CreatingVolume 60 61 type CreatingVolume interface { 62 Handle() string 63 ID() int 64 Created() (CreatedVolume, error) 65 Failed() (FailedVolume, error) 66 } 67 68 type creatingVolume struct { 69 id int 70 workerName string 71 handle string 72 path string 73 teamID int 74 typ VolumeType 75 containerHandle string 76 parentHandle string 77 resourceCacheID int 78 workerBaseResourceTypeID int 79 workerTaskCacheID int 80 workerResourceCertsID int 81 workerArtifactID int 82 conn Conn 83 } 84 85 func (volume *creatingVolume) ID() int { return volume.id } 86 87 func (volume *creatingVolume) Handle() string { return volume.handle } 88 89 func (volume *creatingVolume) Created() (CreatedVolume, error) { 90 err := volumeStateTransition( 91 volume.id, 92 volume.conn, 93 VolumeStateCreating, 94 VolumeStateCreated, 95 ) 96 if err != nil { 97 if err == ErrVolumeStateTransitionFailed { 98 return nil, ErrVolumeMarkCreatedFailed{Handle: volume.handle} 99 } 100 return nil, err 101 } 102 103 return &createdVolume{ 104 id: volume.id, 105 workerName: volume.workerName, 106 typ: volume.typ, 107 handle: volume.handle, 108 path: volume.path, 109 teamID: volume.teamID, 110 conn: volume.conn, 111 containerHandle: volume.containerHandle, 112 parentHandle: volume.parentHandle, 113 resourceCacheID: volume.resourceCacheID, 114 workerBaseResourceTypeID: volume.workerBaseResourceTypeID, 115 workerTaskCacheID: volume.workerTaskCacheID, 116 workerResourceCertsID: volume.workerResourceCertsID, 117 }, nil 118 } 119 120 func (volume *creatingVolume) Failed() (FailedVolume, error) { 121 err := volumeStateTransition( 122 volume.id, 123 volume.conn, 124 VolumeStateCreating, 125 VolumeStateFailed, 126 ) 127 if err != nil { 128 if err == ErrVolumeStateTransitionFailed { 129 return nil, ErrVolumeMarkStateFailed{VolumeStateFailed} 130 } 131 return nil, err 132 } 133 134 return &failedVolume{ 135 id: volume.id, 136 workerName: volume.workerName, 137 handle: volume.handle, 138 conn: volume.conn, 139 }, nil 140 } 141 142 //go:generate counterfeiter . CreatedVolume 143 // TODO-Later Consider separating CORE & Runtime concerns by breaking this abstraction up. 144 type CreatedVolume interface { 145 Handle() string 146 Path() string 147 Type() VolumeType 148 TeamID() int 149 WorkerArtifactID() int 150 CreateChildForContainer(CreatingContainer, string) (CreatingVolume, error) 151 Destroying() (DestroyingVolume, error) 152 WorkerName() string 153 154 InitializeResourceCache(UsedResourceCache) error 155 GetResourceCacheID() int 156 InitializeArtifact(name string, buildID int) (WorkerArtifact, error) 157 InitializeTaskCache(jobID int, stepName string, path string) error 158 159 ContainerHandle() string 160 ParentHandle() string 161 ResourceType() (*VolumeResourceType, error) 162 BaseResourceType() (*UsedWorkerBaseResourceType, error) 163 TaskIdentifier() (int, atc.PipelineRef, string, string, error) 164 } 165 166 type createdVolume struct { 167 id int 168 workerName string 169 handle string 170 path string 171 teamID int 172 typ VolumeType 173 containerHandle string 174 parentHandle string 175 resourceCacheID int 176 workerBaseResourceTypeID int 177 workerTaskCacheID int 178 workerResourceCertsID int 179 workerArtifactID int 180 conn Conn 181 } 182 183 type VolumeResourceType struct { 184 WorkerBaseResourceType *UsedWorkerBaseResourceType 185 ResourceType *VolumeResourceType 186 Version atc.Version 187 } 188 189 func (volume *createdVolume) Handle() string { return volume.handle } 190 func (volume *createdVolume) Path() string { return volume.path } 191 func (volume *createdVolume) WorkerName() string { return volume.workerName } 192 func (volume *createdVolume) Type() VolumeType { return volume.typ } 193 func (volume *createdVolume) TeamID() int { return volume.teamID } 194 func (volume *createdVolume) ContainerHandle() string { return volume.containerHandle } 195 func (volume *createdVolume) ParentHandle() string { return volume.parentHandle } 196 func (volume *createdVolume) WorkerArtifactID() int { return volume.workerArtifactID } 197 198 func (volume *createdVolume) ResourceType() (*VolumeResourceType, error) { 199 if volume.resourceCacheID == 0 { 200 return nil, nil 201 } 202 203 return volume.findVolumeResourceTypeByCacheID(volume.resourceCacheID) 204 } 205 206 func (volume *createdVolume) BaseResourceType() (*UsedWorkerBaseResourceType, error) { 207 if volume.workerBaseResourceTypeID == 0 { 208 return nil, nil 209 } 210 211 return volume.findWorkerBaseResourceTypeByID(volume.workerBaseResourceTypeID) 212 } 213 214 func (volume *createdVolume) TaskIdentifier() (int, atc.PipelineRef, string, string, error) { 215 if volume.workerTaskCacheID == 0 { 216 return 0, atc.PipelineRef{}, "", "", nil 217 } 218 219 var pipelineID int 220 var pipelineName string 221 var pipelineInstanceVars sql.NullString 222 var jobName string 223 var stepName string 224 225 err := psql.Select("p.id, p.name, p.instance_vars, j.name, tc.step_name"). 226 From("worker_task_caches wtc"). 227 LeftJoin("task_caches tc on tc.id = wtc.task_cache_id"). 228 LeftJoin("jobs j ON j.id = tc.job_id"). 229 LeftJoin("pipelines p ON p.id = j.pipeline_id"). 230 Where(sq.Eq{ 231 "wtc.id": volume.workerTaskCacheID, 232 }). 233 RunWith(volume.conn). 234 QueryRow(). 235 Scan(&pipelineID, &pipelineName, &pipelineInstanceVars, &jobName, &stepName) 236 if err != nil { 237 return 0, atc.PipelineRef{}, "", "", err 238 } 239 240 pipelineRef := atc.PipelineRef{Name: pipelineName} 241 if pipelineInstanceVars.Valid { 242 err = json.Unmarshal([]byte(pipelineInstanceVars.String), &pipelineRef.InstanceVars) 243 if err != nil { 244 return 0, atc.PipelineRef{}, "", "", err 245 } 246 } 247 248 return pipelineID, pipelineRef, jobName, stepName, nil 249 } 250 251 func (volume *createdVolume) findVolumeResourceTypeByCacheID(resourceCacheID int) (*VolumeResourceType, error) { 252 var versionString []byte 253 var sqBaseResourceTypeID sql.NullInt64 254 var sqResourceCacheID sql.NullInt64 255 256 err := psql.Select("rc.version, rcfg.base_resource_type_id, rcfg.resource_cache_id"). 257 From("resource_caches rc"). 258 LeftJoin("resource_configs rcfg ON rcfg.id = rc.resource_config_id"). 259 Where(sq.Eq{ 260 "rc.id": resourceCacheID, 261 }). 262 RunWith(volume.conn). 263 QueryRow(). 264 Scan(&versionString, &sqBaseResourceTypeID, &sqResourceCacheID) 265 if err != nil { 266 return nil, err 267 } 268 269 var version atc.Version 270 err = json.Unmarshal(versionString, &version) 271 if err != nil { 272 return nil, err 273 } 274 275 if sqBaseResourceTypeID.Valid { 276 workerBaseResourceType, err := volume.findWorkerBaseResourceTypeByBaseResourceTypeID(int(sqBaseResourceTypeID.Int64)) 277 if err != nil { 278 return nil, err 279 } 280 281 return &VolumeResourceType{ 282 WorkerBaseResourceType: workerBaseResourceType, 283 Version: version, 284 }, nil 285 } 286 287 if sqResourceCacheID.Valid { 288 resourceType, err := volume.findVolumeResourceTypeByCacheID(int(sqResourceCacheID.Int64)) 289 if err != nil { 290 return nil, err 291 } 292 293 return &VolumeResourceType{ 294 ResourceType: resourceType, 295 Version: version, 296 }, nil 297 } 298 299 return nil, ErrInvalidResourceCache 300 } 301 302 func (volume *createdVolume) findWorkerBaseResourceTypeByID(workerBaseResourceTypeID int) (*UsedWorkerBaseResourceType, error) { 303 var name string 304 var version string 305 306 err := psql.Select("brt.name, wbrt.version"). 307 From("worker_base_resource_types wbrt"). 308 LeftJoin("base_resource_types brt ON brt.id = wbrt.base_resource_type_id"). 309 Where(sq.Eq{ 310 "wbrt.id": workerBaseResourceTypeID, 311 "wbrt.worker_name": volume.workerName, 312 }). 313 RunWith(volume.conn). 314 QueryRow(). 315 Scan(&name, &version) 316 if err != nil { 317 return nil, err 318 } 319 320 return &UsedWorkerBaseResourceType{ 321 ID: workerBaseResourceTypeID, 322 Name: name, 323 Version: version, 324 WorkerName: volume.workerName, 325 }, nil 326 } 327 328 func (volume *createdVolume) findWorkerBaseResourceTypeByBaseResourceTypeID(baseResourceTypeID int) (*UsedWorkerBaseResourceType, error) { 329 var id int 330 var name string 331 var version string 332 333 err := psql.Select("wbrt.id, brt.name, wbrt.version"). 334 From("worker_base_resource_types wbrt"). 335 LeftJoin("base_resource_types brt ON brt.id = wbrt.base_resource_type_id"). 336 Where(sq.Eq{ 337 "brt.id": baseResourceTypeID, 338 "wbrt.worker_name": volume.workerName, 339 }). 340 RunWith(volume.conn). 341 QueryRow(). 342 Scan(&id, &name, &version) 343 if err != nil { 344 return nil, err 345 } 346 347 return &UsedWorkerBaseResourceType{ 348 ID: id, 349 Name: name, 350 Version: version, 351 WorkerName: volume.workerName, 352 }, nil 353 } 354 355 func (volume *createdVolume) InitializeResourceCache(resourceCache UsedResourceCache) error { 356 tx, err := volume.conn.Begin() 357 if err != nil { 358 return err 359 } 360 361 defer tx.Rollback() 362 363 workerResourceCache, err := WorkerResourceCache{ 364 WorkerName: volume.WorkerName(), 365 ResourceCache: resourceCache, 366 }.FindOrCreate(tx) 367 if err != nil { 368 return err 369 } 370 371 rows, err := psql.Update("volumes"). 372 Set("worker_resource_cache_id", workerResourceCache.ID). 373 Set("team_id", nil). 374 Where(sq.Eq{"id": volume.id}). 375 RunWith(tx). 376 Exec() 377 if err != nil { 378 if pqErr, ok := err.(*pq.Error); ok && pqErr.Code.Name() == pqUniqueViolationErrCode { 379 // another volume was 'blessed' as the cache volume - leave this one 380 // owned by the container so it just expires when the container is GCed 381 return nil 382 } 383 384 return err 385 } 386 387 affected, err := rows.RowsAffected() 388 if err != nil { 389 return err 390 } 391 392 if affected == 0 { 393 return ErrVolumeMissing 394 } 395 396 err = tx.Commit() 397 if err != nil { 398 return err 399 } 400 401 volume.resourceCacheID = resourceCache.ID() 402 volume.typ = VolumeTypeResource 403 404 return nil 405 } 406 407 func (volume *createdVolume) GetResourceCacheID() int { 408 return volume.resourceCacheID 409 } 410 411 func (volume *createdVolume) InitializeArtifact(name string, buildID int) (WorkerArtifact, error) { 412 tx, err := volume.conn.Begin() 413 if err != nil { 414 return nil, err 415 } 416 417 defer Rollback(tx) 418 419 atcWorkerArtifact := atc.WorkerArtifact{ 420 Name: name, 421 BuildID: buildID, 422 } 423 424 workerArtifact, err := saveWorkerArtifact(tx, volume.conn, atcWorkerArtifact) 425 if err != nil { 426 return nil, err 427 } 428 429 rows, err := psql.Update("volumes"). 430 Set("worker_artifact_id", workerArtifact.ID()). 431 Where(sq.Eq{"id": volume.id}). 432 RunWith(tx). 433 Exec() 434 if err != nil { 435 return nil, err 436 } 437 438 affected, err := rows.RowsAffected() 439 if err != nil { 440 return nil, err 441 } 442 443 if affected == 0 { 444 return nil, ErrVolumeMissing 445 } 446 447 err = tx.Commit() 448 if err != nil { 449 return nil, err 450 } 451 452 return workerArtifact, nil 453 } 454 455 func (volume *createdVolume) InitializeTaskCache(jobID int, stepName string, path string) error { 456 tx, err := volume.conn.Begin() 457 if err != nil { 458 return err 459 } 460 461 defer Rollback(tx) 462 463 usedTaskCache, err := usedTaskCache{ 464 jobID: jobID, 465 stepName: stepName, 466 path: path, 467 }.findOrCreate(tx) 468 if err != nil { 469 return err 470 } 471 472 usedWorkerTaskCache, err := WorkerTaskCache{ 473 WorkerName: volume.WorkerName(), 474 TaskCache: usedTaskCache, 475 }.findOrCreate(tx) 476 if err != nil { 477 return err 478 } 479 480 // release other old volumes for gc 481 _, err = psql.Update("volumes"). 482 Set("worker_task_cache_id", nil). 483 Where(sq.Eq{"worker_task_cache_id": usedWorkerTaskCache.ID}). 484 RunWith(tx). 485 Exec() 486 if err != nil { 487 return err 488 } 489 490 rows, err := psql.Update("volumes"). 491 Set("worker_task_cache_id", usedWorkerTaskCache.ID). 492 Where(sq.Eq{"id": volume.id}). 493 RunWith(tx). 494 Exec() 495 if err != nil { 496 if pqErr, ok := err.(*pq.Error); ok && pqErr.Code.Name() == pqUniqueViolationErrCode { 497 // another volume was 'blessed' as the cache volume - leave this one 498 // owned by the container so it just expires when the container is GCed 499 return nil 500 } 501 502 return err 503 } 504 505 affected, err := rows.RowsAffected() 506 if err != nil { 507 return err 508 } 509 510 if affected == 0 { 511 return ErrVolumeMissing 512 } 513 514 err = tx.Commit() 515 if err != nil { 516 return err 517 } 518 519 return nil 520 } 521 522 func (volume *createdVolume) CreateChildForContainer(container CreatingContainer, mountPath string) (CreatingVolume, error) { 523 tx, err := volume.conn.Begin() 524 if err != nil { 525 return nil, err 526 } 527 528 defer Rollback(tx) 529 530 handle, err := uuid.NewV4() 531 if err != nil { 532 return nil, err 533 } 534 535 columnNames := []string{ 536 "worker_name", 537 "parent_id", 538 "parent_state", 539 "handle", 540 "container_id", 541 "path", 542 } 543 columnValues := []interface{}{ 544 volume.workerName, 545 volume.id, 546 VolumeStateCreated, 547 handle.String(), 548 container.ID(), 549 mountPath, 550 } 551 552 if volume.teamID != 0 { 553 columnNames = append(columnNames, "team_id") 554 columnValues = append(columnValues, volume.teamID) 555 } 556 557 var volumeID int 558 err = psql.Insert("volumes"). 559 Columns(columnNames...). 560 Values(columnValues...). 561 Suffix("RETURNING id"). 562 RunWith(tx). 563 QueryRow(). 564 Scan(&volumeID) 565 if err != nil { 566 return nil, err 567 } 568 569 err = tx.Commit() 570 if err != nil { 571 return nil, err 572 } 573 574 return &creatingVolume{ 575 id: volumeID, 576 workerName: volume.workerName, 577 handle: handle.String(), 578 path: mountPath, 579 teamID: volume.teamID, 580 typ: VolumeTypeContainer, 581 containerHandle: container.Handle(), 582 parentHandle: volume.Handle(), 583 conn: volume.conn, 584 }, nil 585 } 586 587 func (volume *createdVolume) Destroying() (DestroyingVolume, error) { 588 err := volumeStateTransition( 589 volume.id, 590 volume.conn, 591 VolumeStateCreated, 592 VolumeStateDestroying, 593 ) 594 if err != nil { 595 if err == ErrVolumeStateTransitionFailed { 596 return nil, ErrVolumeMarkStateFailed{VolumeStateDestroying} 597 598 } 599 600 if pqErr, ok := err.(*pq.Error); ok && 601 pqErr.Code.Name() == pqFKeyViolationErrCode && 602 pqErr.Constraint == "volumes_parent_id_fkey" { 603 return nil, ErrVolumeCannotBeDestroyedWithChildrenPresent 604 } 605 606 return nil, err 607 } 608 609 return &destroyingVolume{ 610 id: volume.id, 611 workerName: volume.workerName, 612 handle: volume.handle, 613 conn: volume.conn, 614 }, nil 615 } 616 617 //go:generate counterfeiter . DestroyingVolume 618 type DestroyingVolume interface { 619 Handle() string 620 Destroy() (bool, error) 621 WorkerName() string 622 } 623 624 type destroyingVolume struct { 625 id int 626 workerName string 627 handle string 628 conn Conn 629 } 630 631 func (volume *destroyingVolume) Handle() string { return volume.handle } 632 func (volume *destroyingVolume) WorkerName() string { return volume.workerName } 633 634 func (volume *destroyingVolume) Destroy() (bool, error) { 635 rows, err := psql.Delete("volumes"). 636 Where(sq.Eq{ 637 "id": volume.id, 638 "state": VolumeStateDestroying, 639 }). 640 RunWith(volume.conn). 641 Exec() 642 if err != nil { 643 return false, err 644 } 645 646 affected, err := rows.RowsAffected() 647 if err != nil { 648 return false, err 649 } 650 651 if affected == 0 { 652 return false, nil 653 } 654 655 return true, nil 656 } 657 658 type FailedVolume interface { 659 Handle() string 660 Destroy() (bool, error) 661 WorkerName() string 662 } 663 664 type failedVolume struct { 665 id int 666 workerName string 667 handle string 668 conn Conn 669 } 670 671 func (volume *failedVolume) Handle() string { return volume.handle } 672 func (volume *failedVolume) WorkerName() string { return volume.workerName } 673 674 func (volume *failedVolume) Destroy() (bool, error) { 675 rows, err := psql.Delete("volumes"). 676 Where(sq.Eq{ 677 "id": volume.id, 678 "state": VolumeStateFailed, 679 }). 680 RunWith(volume.conn). 681 Exec() 682 if err != nil { 683 return false, err 684 } 685 686 affected, err := rows.RowsAffected() 687 if err != nil { 688 return false, err 689 } 690 691 if affected == 0 { 692 return false, nil 693 } 694 695 return true, nil 696 } 697 698 func volumeStateTransition(volumeID int, conn Conn, from, to VolumeState) error { 699 rows, err := psql.Update("volumes"). 700 Set("state", string(to)). 701 Where(sq.And{ 702 sq.Eq{"id": volumeID}, 703 sq.Or{ 704 sq.Eq{"state": string(from)}, 705 sq.Eq{"state": string(to)}, 706 }, 707 }). 708 RunWith(conn). 709 Exec() 710 if err != nil { 711 return err 712 } 713 714 affected, err := rows.RowsAffected() 715 if err != nil { 716 return err 717 } 718 719 if affected == 0 { 720 return ErrVolumeStateTransitionFailed 721 } 722 723 return nil 724 }