github.com/Ilhicas/nomad@v1.0.4-0.20210304152020-e86851182bc3/nomad/structs/csi.go (about) 1 package structs 2 3 import ( 4 "fmt" 5 "strings" 6 "time" 7 8 multierror "github.com/hashicorp/go-multierror" 9 "github.com/hashicorp/nomad/helper" 10 ) 11 12 // CSISocketName is the filename that Nomad expects plugins to create inside the 13 // PluginMountDir. 14 const CSISocketName = "csi.sock" 15 16 // CSIIntermediaryDirname is the name of the directory inside the PluginMountDir 17 // where Nomad will expect plugins to create intermediary mounts for volumes. 18 const CSIIntermediaryDirname = "volumes" 19 20 // VolumeTypeCSI is the type in the volume stanza of a TaskGroup 21 const VolumeTypeCSI = "csi" 22 23 // CSIPluginType is an enum string that encapsulates the valid options for a 24 // CSIPlugin stanza's Type. These modes will allow the plugin to be used in 25 // different ways by the client. 26 type CSIPluginType string 27 28 const ( 29 // CSIPluginTypeNode indicates that Nomad should only use the plugin for 30 // performing Node RPCs against the provided plugin. 31 CSIPluginTypeNode CSIPluginType = "node" 32 33 // CSIPluginTypeController indicates that Nomad should only use the plugin for 34 // performing Controller RPCs against the provided plugin. 35 CSIPluginTypeController CSIPluginType = "controller" 36 37 // CSIPluginTypeMonolith indicates that Nomad can use the provided plugin for 38 // both controller and node rpcs. 39 CSIPluginTypeMonolith CSIPluginType = "monolith" 40 ) 41 42 // CSIPluginTypeIsValid validates the given CSIPluginType string and returns 43 // true only when a correct plugin type is specified. 44 func CSIPluginTypeIsValid(pt CSIPluginType) bool { 45 switch pt { 46 case CSIPluginTypeNode, CSIPluginTypeController, CSIPluginTypeMonolith: 47 return true 48 default: 49 return false 50 } 51 } 52 53 // TaskCSIPluginConfig contains the data that is required to setup a task as a 54 // CSI plugin. This will be used by the csi_plugin_supervisor_hook to configure 55 // mounts for the plugin and initiate the connection to the plugin catalog. 56 type TaskCSIPluginConfig struct { 57 // ID is the identifier of the plugin. 58 // Ideally this should be the FQDN of the plugin. 59 ID string 60 61 // Type instructs Nomad on how to handle processing a plugin 62 Type CSIPluginType 63 64 // MountDir is the destination that nomad should mount in its CSI 65 // directory for the plugin. It will then expect a file called CSISocketName 66 // to be created by the plugin, and will provide references into 67 // "MountDir/CSIIntermediaryDirname/{VolumeName}/{AllocID} for mounts. 68 MountDir string 69 } 70 71 func (t *TaskCSIPluginConfig) Copy() *TaskCSIPluginConfig { 72 if t == nil { 73 return nil 74 } 75 76 nt := new(TaskCSIPluginConfig) 77 *nt = *t 78 79 return nt 80 } 81 82 // CSIVolumeAttachmentMode chooses the type of storage api that will be used to 83 // interact with the device. 84 type CSIVolumeAttachmentMode string 85 86 const ( 87 CSIVolumeAttachmentModeUnknown CSIVolumeAttachmentMode = "" 88 CSIVolumeAttachmentModeBlockDevice CSIVolumeAttachmentMode = "block-device" 89 CSIVolumeAttachmentModeFilesystem CSIVolumeAttachmentMode = "file-system" 90 ) 91 92 func ValidCSIVolumeAttachmentMode(attachmentMode CSIVolumeAttachmentMode) bool { 93 switch attachmentMode { 94 case CSIVolumeAttachmentModeBlockDevice, CSIVolumeAttachmentModeFilesystem: 95 return true 96 default: 97 return false 98 } 99 } 100 101 // CSIVolumeAccessMode indicates how a volume should be used in a storage topology 102 // e.g whether the provider should make the volume available concurrently. 103 type CSIVolumeAccessMode string 104 105 const ( 106 CSIVolumeAccessModeUnknown CSIVolumeAccessMode = "" 107 108 CSIVolumeAccessModeSingleNodeReader CSIVolumeAccessMode = "single-node-reader-only" 109 CSIVolumeAccessModeSingleNodeWriter CSIVolumeAccessMode = "single-node-writer" 110 111 CSIVolumeAccessModeMultiNodeReader CSIVolumeAccessMode = "multi-node-reader-only" 112 CSIVolumeAccessModeMultiNodeSingleWriter CSIVolumeAccessMode = "multi-node-single-writer" 113 CSIVolumeAccessModeMultiNodeMultiWriter CSIVolumeAccessMode = "multi-node-multi-writer" 114 ) 115 116 // ValidCSIVolumeAccessMode checks to see that the provided access mode is a valid, 117 // non-empty access mode. 118 func ValidCSIVolumeAccessMode(accessMode CSIVolumeAccessMode) bool { 119 switch accessMode { 120 case CSIVolumeAccessModeSingleNodeReader, CSIVolumeAccessModeSingleNodeWriter, 121 CSIVolumeAccessModeMultiNodeReader, CSIVolumeAccessModeMultiNodeSingleWriter, 122 CSIVolumeAccessModeMultiNodeMultiWriter: 123 return true 124 default: 125 return false 126 } 127 } 128 129 // ValidCSIVolumeAccessMode checks for a writable access mode 130 func ValidCSIVolumeWriteAccessMode(accessMode CSIVolumeAccessMode) bool { 131 switch accessMode { 132 case CSIVolumeAccessModeSingleNodeWriter, 133 CSIVolumeAccessModeMultiNodeSingleWriter, 134 CSIVolumeAccessModeMultiNodeMultiWriter: 135 return true 136 default: 137 return false 138 } 139 } 140 141 // CSIMountOptions contain optional additional configuration that can be used 142 // when specifying that a Volume should be used with VolumeAccessTypeMount. 143 type CSIMountOptions struct { 144 // FSType is an optional field that allows an operator to specify the type 145 // of the filesystem. 146 FSType string 147 148 // MountFlags contains additional options that may be used when mounting the 149 // volume by the plugin. This may contain sensitive data and should not be 150 // leaked. 151 MountFlags []string 152 } 153 154 func (o *CSIMountOptions) Copy() *CSIMountOptions { 155 if o == nil { 156 return nil 157 } 158 159 no := *o 160 no.MountFlags = helper.CopySliceString(o.MountFlags) 161 return &no 162 } 163 164 func (o *CSIMountOptions) Merge(p *CSIMountOptions) { 165 if p == nil { 166 return 167 } 168 if p.FSType != "" { 169 o.FSType = p.FSType 170 } 171 if p.MountFlags != nil { 172 o.MountFlags = p.MountFlags 173 } 174 } 175 176 // CSIMountOptions implements the Stringer and GoStringer interfaces to prevent 177 // accidental leakage of sensitive mount flags via logs. 178 var _ fmt.Stringer = &CSIMountOptions{} 179 var _ fmt.GoStringer = &CSIMountOptions{} 180 181 func (v *CSIMountOptions) String() string { 182 mountFlagsString := "nil" 183 if len(v.MountFlags) != 0 { 184 mountFlagsString = "[REDACTED]" 185 } 186 187 return fmt.Sprintf("csi.CSIOptions(FSType: %s, MountFlags: %s)", v.FSType, mountFlagsString) 188 } 189 190 func (v *CSIMountOptions) GoString() string { 191 return v.String() 192 } 193 194 // CSISecrets contain optional additional configuration that can be used 195 // when specifying that a Volume should be used with VolumeAccessTypeMount. 196 type CSISecrets map[string]string 197 198 // CSISecrets implements the Stringer and GoStringer interfaces to prevent 199 // accidental leakage of secrets via logs. 200 var _ fmt.Stringer = &CSISecrets{} 201 var _ fmt.GoStringer = &CSISecrets{} 202 203 func (s *CSISecrets) String() string { 204 redacted := map[string]string{} 205 for k := range *s { 206 redacted[k] = "[REDACTED]" 207 } 208 return fmt.Sprintf("csi.CSISecrets(%v)", redacted) 209 } 210 211 func (s *CSISecrets) GoString() string { 212 return s.String() 213 } 214 215 type CSIVolumeClaim struct { 216 AllocationID string 217 NodeID string 218 ExternalNodeID string 219 Mode CSIVolumeClaimMode 220 State CSIVolumeClaimState 221 } 222 223 type CSIVolumeClaimState int 224 225 const ( 226 CSIVolumeClaimStateTaken CSIVolumeClaimState = iota 227 CSIVolumeClaimStateNodeDetached 228 CSIVolumeClaimStateControllerDetached 229 CSIVolumeClaimStateReadyToFree 230 CSIVolumeClaimStateUnpublishing 231 ) 232 233 // CSIVolume is the full representation of a CSI Volume 234 type CSIVolume struct { 235 // ID is a namespace unique URL safe identifier for the volume 236 ID string 237 // Name is a display name for the volume, not required to be unique 238 Name string 239 // ExternalID identifies the volume for the CSI interface, may be URL unsafe 240 ExternalID string 241 Namespace string 242 Topologies []*CSITopology 243 AccessMode CSIVolumeAccessMode 244 AttachmentMode CSIVolumeAttachmentMode 245 MountOptions *CSIMountOptions 246 Secrets CSISecrets 247 Parameters map[string]string 248 Context map[string]string 249 250 // Allocations, tracking claim status 251 ReadAllocs map[string]*Allocation // AllocID -> Allocation 252 WriteAllocs map[string]*Allocation // AllocID -> Allocation 253 254 ReadClaims map[string]*CSIVolumeClaim // AllocID -> claim 255 WriteClaims map[string]*CSIVolumeClaim // AllocID -> claim 256 PastClaims map[string]*CSIVolumeClaim // AllocID -> claim 257 258 // Schedulable is true if all the denormalized plugin health fields are true, and the 259 // volume has not been marked for garbage collection 260 Schedulable bool 261 PluginID string 262 Provider string 263 ProviderVersion string 264 ControllerRequired bool 265 ControllersHealthy int 266 ControllersExpected int 267 NodesHealthy int 268 NodesExpected int 269 ResourceExhausted time.Time 270 271 CreateIndex uint64 272 ModifyIndex uint64 273 } 274 275 // CSIVolListStub is partial representation of a CSI Volume for inclusion in lists 276 type CSIVolListStub struct { 277 ID string 278 Namespace string 279 Name string 280 ExternalID string 281 Topologies []*CSITopology 282 AccessMode CSIVolumeAccessMode 283 AttachmentMode CSIVolumeAttachmentMode 284 CurrentReaders int 285 CurrentWriters int 286 Schedulable bool 287 PluginID string 288 Provider string 289 ControllersHealthy int 290 ControllersExpected int 291 NodesHealthy int 292 NodesExpected int 293 CreateIndex uint64 294 ModifyIndex uint64 295 } 296 297 // NewCSIVolume creates the volume struct. No side-effects 298 func NewCSIVolume(volumeID string, index uint64) *CSIVolume { 299 out := &CSIVolume{ 300 ID: volumeID, 301 CreateIndex: index, 302 ModifyIndex: index, 303 } 304 305 out.newStructs() 306 return out 307 } 308 309 func (v *CSIVolume) newStructs() { 310 v.Topologies = []*CSITopology{} 311 v.MountOptions = new(CSIMountOptions) 312 v.Secrets = CSISecrets{} 313 v.Parameters = map[string]string{} 314 v.Context = map[string]string{} 315 316 v.ReadAllocs = map[string]*Allocation{} 317 v.WriteAllocs = map[string]*Allocation{} 318 v.ReadClaims = map[string]*CSIVolumeClaim{} 319 v.WriteClaims = map[string]*CSIVolumeClaim{} 320 v.PastClaims = map[string]*CSIVolumeClaim{} 321 } 322 323 func (v *CSIVolume) RemoteID() string { 324 if v.ExternalID != "" { 325 return v.ExternalID 326 } 327 return v.ID 328 } 329 330 func (v *CSIVolume) Stub() *CSIVolListStub { 331 stub := CSIVolListStub{ 332 ID: v.ID, 333 Namespace: v.Namespace, 334 Name: v.Name, 335 ExternalID: v.ExternalID, 336 Topologies: v.Topologies, 337 AccessMode: v.AccessMode, 338 AttachmentMode: v.AttachmentMode, 339 CurrentReaders: len(v.ReadAllocs), 340 CurrentWriters: len(v.WriteAllocs), 341 Schedulable: v.Schedulable, 342 PluginID: v.PluginID, 343 Provider: v.Provider, 344 ControllersHealthy: v.ControllersHealthy, 345 ControllersExpected: v.ControllersExpected, 346 NodesHealthy: v.NodesHealthy, 347 NodesExpected: v.NodesExpected, 348 CreateIndex: v.CreateIndex, 349 ModifyIndex: v.ModifyIndex, 350 } 351 352 return &stub 353 } 354 355 func (v *CSIVolume) ReadSchedulable() bool { 356 if !v.Schedulable { 357 return false 358 } 359 360 return v.ResourceExhausted == time.Time{} 361 } 362 363 // WriteSchedulable determines if the volume is schedulable for writes, considering only 364 // volume health 365 func (v *CSIVolume) WriteSchedulable() bool { 366 if !v.Schedulable { 367 return false 368 } 369 370 switch v.AccessMode { 371 case CSIVolumeAccessModeSingleNodeWriter, CSIVolumeAccessModeMultiNodeSingleWriter, CSIVolumeAccessModeMultiNodeMultiWriter: 372 return v.ResourceExhausted == time.Time{} 373 default: 374 return false 375 } 376 } 377 378 // WriteFreeClaims determines if there are any free write claims available 379 func (v *CSIVolume) WriteFreeClaims() bool { 380 switch v.AccessMode { 381 case CSIVolumeAccessModeSingleNodeWriter, CSIVolumeAccessModeMultiNodeSingleWriter: 382 return len(v.WriteClaims) == 0 383 case CSIVolumeAccessModeMultiNodeMultiWriter: 384 // the CSI spec doesn't allow for setting a max number of writers. 385 // we track node resource exhaustion through v.ResourceExhausted 386 // which is checked in WriteSchedulable 387 return true 388 default: 389 return false 390 } 391 } 392 393 // InUse tests whether any allocations are actively using the volume 394 func (v *CSIVolume) InUse() bool { 395 return len(v.ReadAllocs) != 0 || 396 len(v.WriteAllocs) != 0 397 } 398 399 // Copy returns a copy of the volume, which shares only the Topologies slice 400 func (v *CSIVolume) Copy() *CSIVolume { 401 out := new(CSIVolume) 402 *out = *v 403 out.newStructs() // zero-out the non-primitive structs 404 405 for _, t := range v.Topologies { 406 out.Topologies = append(out.Topologies, t.Copy()) 407 } 408 if v.MountOptions != nil { 409 *out.MountOptions = *v.MountOptions 410 } 411 for k, v := range v.Secrets { 412 out.Secrets[k] = v 413 } 414 for k, v := range v.Parameters { 415 out.Parameters[k] = v 416 } 417 for k, v := range v.Context { 418 out.Context[k] = v 419 } 420 421 for k, alloc := range v.ReadAllocs { 422 out.ReadAllocs[k] = alloc.Copy() 423 } 424 for k, alloc := range v.WriteAllocs { 425 out.WriteAllocs[k] = alloc.Copy() 426 } 427 428 for k, v := range v.ReadClaims { 429 claim := *v 430 out.ReadClaims[k] = &claim 431 } 432 for k, v := range v.WriteClaims { 433 claim := *v 434 out.WriteClaims[k] = &claim 435 } 436 for k, v := range v.PastClaims { 437 claim := *v 438 out.PastClaims[k] = &claim 439 } 440 441 return out 442 } 443 444 // Claim updates the allocations and changes the volume state 445 func (v *CSIVolume) Claim(claim *CSIVolumeClaim, alloc *Allocation) error { 446 447 if claim.State == CSIVolumeClaimStateTaken { 448 switch claim.Mode { 449 case CSIVolumeClaimRead: 450 return v.ClaimRead(claim, alloc) 451 case CSIVolumeClaimWrite: 452 return v.ClaimWrite(claim, alloc) 453 } 454 } 455 // either GC or a Unpublish checkpoint 456 return v.ClaimRelease(claim) 457 } 458 459 // ClaimRead marks an allocation as using a volume read-only 460 func (v *CSIVolume) ClaimRead(claim *CSIVolumeClaim, alloc *Allocation) error { 461 if _, ok := v.ReadAllocs[claim.AllocationID]; ok { 462 return nil 463 } 464 if alloc == nil { 465 return fmt.Errorf("allocation missing: %s", claim.AllocationID) 466 } 467 468 if !v.ReadSchedulable() { 469 return fmt.Errorf("unschedulable") 470 } 471 472 // Allocations are copy on write, so we want to keep the id but don't need the 473 // pointer. We'll get it from the db in denormalize. 474 v.ReadAllocs[claim.AllocationID] = nil 475 delete(v.WriteAllocs, claim.AllocationID) 476 477 v.ReadClaims[claim.AllocationID] = claim 478 delete(v.WriteClaims, claim.AllocationID) 479 delete(v.PastClaims, claim.AllocationID) 480 481 return nil 482 } 483 484 // ClaimWrite marks an allocation as using a volume as a writer 485 func (v *CSIVolume) ClaimWrite(claim *CSIVolumeClaim, alloc *Allocation) error { 486 if _, ok := v.WriteAllocs[claim.AllocationID]; ok { 487 return nil 488 } 489 if alloc == nil { 490 return fmt.Errorf("allocation missing: %s", claim.AllocationID) 491 } 492 493 if !v.WriteSchedulable() { 494 return fmt.Errorf("unschedulable") 495 } 496 497 if !v.WriteFreeClaims() { 498 // Check the blocking allocations to see if they belong to this job 499 for _, a := range v.WriteAllocs { 500 if a != nil && (a.Namespace != alloc.Namespace || a.JobID != alloc.JobID) { 501 return fmt.Errorf("volume max claim reached") 502 } 503 } 504 } 505 506 // Allocations are copy on write, so we want to keep the id but don't need the 507 // pointer. We'll get it from the db in denormalize. 508 v.WriteAllocs[alloc.ID] = nil 509 delete(v.ReadAllocs, alloc.ID) 510 511 v.WriteClaims[alloc.ID] = claim 512 delete(v.ReadClaims, alloc.ID) 513 delete(v.PastClaims, alloc.ID) 514 515 return nil 516 } 517 518 // ClaimRelease is called when the allocation has terminated and 519 // already stopped using the volume 520 func (v *CSIVolume) ClaimRelease(claim *CSIVolumeClaim) error { 521 if claim.State == CSIVolumeClaimStateReadyToFree { 522 delete(v.ReadAllocs, claim.AllocationID) 523 delete(v.WriteAllocs, claim.AllocationID) 524 delete(v.ReadClaims, claim.AllocationID) 525 delete(v.WriteClaims, claim.AllocationID) 526 delete(v.PastClaims, claim.AllocationID) 527 } else { 528 v.PastClaims[claim.AllocationID] = claim 529 } 530 return nil 531 } 532 533 // Equality by value 534 func (v *CSIVolume) Equal(o *CSIVolume) bool { 535 if v == nil || o == nil { 536 return v == o 537 } 538 539 // Omit the plugin health fields, their values are controlled by plugin jobs 540 if v.ID == o.ID && 541 v.Namespace == o.Namespace && 542 v.AccessMode == o.AccessMode && 543 v.AttachmentMode == o.AttachmentMode && 544 v.PluginID == o.PluginID { 545 // Setwise equality of topologies 546 var ok bool 547 for _, t := range v.Topologies { 548 ok = false 549 for _, u := range o.Topologies { 550 if t.Equal(u) { 551 ok = true 552 break 553 } 554 } 555 if !ok { 556 return false 557 } 558 } 559 return true 560 } 561 return false 562 } 563 564 // Validate validates the volume struct, returning all validation errors at once 565 func (v *CSIVolume) Validate() error { 566 errs := []string{} 567 568 if v.ID == "" { 569 errs = append(errs, "missing volume id") 570 } 571 if v.PluginID == "" { 572 errs = append(errs, "missing plugin id") 573 } 574 if v.Namespace == "" { 575 errs = append(errs, "missing namespace") 576 } 577 if v.AccessMode == "" { 578 errs = append(errs, "missing access mode") 579 } 580 if v.AttachmentMode == "" { 581 errs = append(errs, "missing attachment mode") 582 } 583 if v.AttachmentMode == CSIVolumeAttachmentModeBlockDevice { 584 if v.MountOptions != nil { 585 if v.MountOptions.FSType != "" { 586 errs = append(errs, "mount options not allowed for block-device") 587 } 588 if v.MountOptions.MountFlags != nil && len(v.MountOptions.MountFlags) != 0 { 589 errs = append(errs, "mount options not allowed for block-device") 590 } 591 } 592 } 593 594 // TODO: Volume Topologies are optional - We should check to see if the plugin 595 // the volume is being registered with requires them. 596 // var ok bool 597 // for _, t := range v.Topologies { 598 // if t != nil && len(t.Segments) > 0 { 599 // ok = true 600 // break 601 // } 602 // } 603 // if !ok { 604 // errs = append(errs, "missing topology") 605 // } 606 607 if len(errs) > 0 { 608 return fmt.Errorf("validation: %s", strings.Join(errs, ", ")) 609 } 610 return nil 611 } 612 613 // Request and response wrappers 614 type CSIVolumeRegisterRequest struct { 615 Volumes []*CSIVolume 616 WriteRequest 617 } 618 619 type CSIVolumeRegisterResponse struct { 620 QueryMeta 621 } 622 623 type CSIVolumeDeregisterRequest struct { 624 VolumeIDs []string 625 Force bool 626 WriteRequest 627 } 628 629 type CSIVolumeDeregisterResponse struct { 630 QueryMeta 631 } 632 633 type CSIVolumeClaimMode int 634 635 const ( 636 CSIVolumeClaimRead CSIVolumeClaimMode = iota 637 CSIVolumeClaimWrite 638 639 // for GC we don't have a specific claim to set the state on, so instead we 640 // create a new claim for GC in order to bump the ModifyIndex and trigger 641 // volumewatcher 642 CSIVolumeClaimGC 643 ) 644 645 type CSIVolumeClaimBatchRequest struct { 646 Claims []CSIVolumeClaimRequest 647 } 648 649 type CSIVolumeClaimRequest struct { 650 VolumeID string 651 AllocationID string 652 NodeID string 653 ExternalNodeID string 654 Claim CSIVolumeClaimMode 655 State CSIVolumeClaimState 656 WriteRequest 657 } 658 659 func (req *CSIVolumeClaimRequest) ToClaim() *CSIVolumeClaim { 660 return &CSIVolumeClaim{ 661 AllocationID: req.AllocationID, 662 NodeID: req.NodeID, 663 ExternalNodeID: req.ExternalNodeID, 664 Mode: req.Claim, 665 State: req.State, 666 } 667 } 668 669 type CSIVolumeClaimResponse struct { 670 // Opaque static publish properties of the volume. SP MAY use this 671 // field to ensure subsequent `NodeStageVolume` or `NodePublishVolume` 672 // calls calls have contextual information. 673 // The contents of this field SHALL be opaque to nomad. 674 // The contents of this field SHALL NOT be mutable. 675 // The contents of this field SHALL be safe for the nomad to cache. 676 // The contents of this field SHOULD NOT contain sensitive 677 // information. 678 // The contents of this field SHOULD NOT be used for uniquely 679 // identifying a volume. The `volume_id` alone SHOULD be sufficient to 680 // identify the volume. 681 // This field is OPTIONAL and when present MUST be passed to 682 // `NodeStageVolume` or `NodePublishVolume` calls on the client 683 PublishContext map[string]string 684 685 // Volume contains the expanded CSIVolume for use on the client after a Claim 686 // has completed. 687 Volume *CSIVolume 688 689 QueryMeta 690 } 691 692 type CSIVolumeListRequest struct { 693 PluginID string 694 NodeID string 695 QueryOptions 696 } 697 698 type CSIVolumeListResponse struct { 699 Volumes []*CSIVolListStub 700 QueryMeta 701 } 702 703 type CSIVolumeGetRequest struct { 704 ID string 705 QueryOptions 706 } 707 708 type CSIVolumeGetResponse struct { 709 Volume *CSIVolume 710 QueryMeta 711 } 712 713 type CSIVolumeUnpublishRequest struct { 714 VolumeID string 715 Claim *CSIVolumeClaim 716 WriteRequest 717 } 718 719 type CSIVolumeUnpublishResponse struct { 720 QueryMeta 721 } 722 723 // CSIPlugin collects fingerprint info context for the plugin for clients 724 type CSIPlugin struct { 725 ID string 726 Provider string // the vendor name from CSI GetPluginInfoResponse 727 Version string // the vendor verson from CSI GetPluginInfoResponse 728 ControllerRequired bool 729 730 // Map Node.IDs to fingerprint results, split by type. Monolith type plugins have 731 // both sets of fingerprinting results. 732 Controllers map[string]*CSIInfo 733 Nodes map[string]*CSIInfo 734 735 // Allocations are populated by denormalize to show running allocations 736 Allocations []*AllocListStub 737 738 // Jobs are populated to by job update to support expected counts and the UI 739 ControllerJobs JobDescriptions 740 NodeJobs JobDescriptions 741 742 // Cache the count of healthy plugins 743 ControllersHealthy int 744 ControllersExpected int 745 NodesHealthy int 746 NodesExpected int 747 748 CreateIndex uint64 749 ModifyIndex uint64 750 } 751 752 // NewCSIPlugin creates the plugin struct. No side-effects 753 func NewCSIPlugin(id string, index uint64) *CSIPlugin { 754 out := &CSIPlugin{ 755 ID: id, 756 CreateIndex: index, 757 ModifyIndex: index, 758 } 759 760 out.newStructs() 761 return out 762 } 763 764 func (p *CSIPlugin) newStructs() { 765 p.Controllers = map[string]*CSIInfo{} 766 p.Nodes = map[string]*CSIInfo{} 767 p.ControllerJobs = make(JobDescriptions) 768 p.NodeJobs = make(JobDescriptions) 769 } 770 771 func (p *CSIPlugin) Copy() *CSIPlugin { 772 copy := *p 773 out := © 774 out.newStructs() 775 776 for k, v := range p.Controllers { 777 out.Controllers[k] = v.Copy() 778 } 779 780 for k, v := range p.Nodes { 781 out.Nodes[k] = v.Copy() 782 } 783 784 for k, v := range p.ControllerJobs { 785 out.ControllerJobs[k] = v.Copy() 786 } 787 788 for k, v := range p.NodeJobs { 789 out.NodeJobs[k] = v.Copy() 790 } 791 792 return out 793 } 794 795 // AddPlugin adds a single plugin running on the node. Called from state.NodeUpdate in a 796 // transaction 797 func (p *CSIPlugin) AddPlugin(nodeID string, info *CSIInfo) error { 798 if info.ControllerInfo != nil { 799 p.ControllerRequired = info.RequiresControllerPlugin && 800 (info.ControllerInfo.SupportsAttachDetach || 801 info.ControllerInfo.SupportsReadOnlyAttach) 802 803 prev, ok := p.Controllers[nodeID] 804 if ok { 805 if prev == nil { 806 return fmt.Errorf("plugin missing controller: %s", nodeID) 807 } 808 if prev.Healthy { 809 p.ControllersHealthy -= 1 810 } 811 } 812 813 // note: for this to work as expected, only a single 814 // controller for a given plugin can be on a given Nomad 815 // client, they also conflict on the client so this should be 816 // ok 817 if prev != nil || info.Healthy { 818 p.Controllers[nodeID] = info 819 } 820 if info.Healthy { 821 p.ControllersHealthy += 1 822 } 823 } 824 825 if info.NodeInfo != nil { 826 prev, ok := p.Nodes[nodeID] 827 if ok { 828 if prev == nil { 829 return fmt.Errorf("plugin missing node: %s", nodeID) 830 } 831 if prev.Healthy { 832 p.NodesHealthy -= 1 833 } 834 } 835 if prev != nil || info.Healthy { 836 p.Nodes[nodeID] = info 837 } 838 if info.Healthy { 839 p.NodesHealthy += 1 840 } 841 } 842 843 return nil 844 } 845 846 // DeleteNode removes all plugins from the node. Called from state.DeleteNode in a 847 // transaction 848 func (p *CSIPlugin) DeleteNode(nodeID string) error { 849 return p.DeleteNodeForType(nodeID, CSIPluginTypeMonolith) 850 } 851 852 // DeleteNodeForType deletes a client node from the list of controllers or node instance of 853 // a plugin. Called from deleteJobFromPlugin during job deregistration, in a transaction 854 func (p *CSIPlugin) DeleteNodeForType(nodeID string, pluginType CSIPluginType) error { 855 switch pluginType { 856 case CSIPluginTypeController: 857 if prev, ok := p.Controllers[nodeID]; ok { 858 if prev == nil { 859 return fmt.Errorf("plugin missing controller: %s", nodeID) 860 } 861 if prev.Healthy { 862 p.ControllersHealthy-- 863 } 864 delete(p.Controllers, nodeID) 865 } 866 867 case CSIPluginTypeNode: 868 if prev, ok := p.Nodes[nodeID]; ok { 869 if prev == nil { 870 return fmt.Errorf("plugin missing node: %s", nodeID) 871 } 872 if prev.Healthy { 873 p.NodesHealthy-- 874 } 875 delete(p.Nodes, nodeID) 876 } 877 878 case CSIPluginTypeMonolith: 879 var result error 880 881 err := p.DeleteNodeForType(nodeID, CSIPluginTypeController) 882 if err != nil { 883 result = multierror.Append(result, err) 884 } 885 886 err = p.DeleteNodeForType(nodeID, CSIPluginTypeNode) 887 if err != nil { 888 result = multierror.Append(result, err) 889 } 890 891 return result 892 } 893 894 return nil 895 } 896 897 // DeleteAlloc removes the fingerprint info for the allocation 898 func (p *CSIPlugin) DeleteAlloc(allocID, nodeID string) error { 899 prev, ok := p.Controllers[nodeID] 900 if ok { 901 if prev == nil { 902 return fmt.Errorf("plugin missing controller: %s", nodeID) 903 } 904 if prev.AllocID == allocID { 905 if prev.Healthy { 906 p.ControllersHealthy -= 1 907 } 908 delete(p.Controllers, nodeID) 909 } 910 } 911 912 prev, ok = p.Nodes[nodeID] 913 if ok { 914 if prev == nil { 915 return fmt.Errorf("plugin missing node: %s", nodeID) 916 } 917 if prev.AllocID == allocID { 918 if prev.Healthy { 919 p.NodesHealthy -= 1 920 } 921 delete(p.Nodes, nodeID) 922 } 923 } 924 925 return nil 926 } 927 928 // AddJob adds a job to the plugin and increments expected 929 func (p *CSIPlugin) AddJob(job *Job, summary *JobSummary) { 930 p.UpdateExpectedWithJob(job, summary, false) 931 } 932 933 // DeleteJob removes the job from the plugin and decrements expected 934 func (p *CSIPlugin) DeleteJob(job *Job, summary *JobSummary) { 935 p.UpdateExpectedWithJob(job, summary, true) 936 } 937 938 // UpdateExpectedWithJob maintains the expected instance count 939 // we use the summary to add non-allocation expected counts 940 func (p *CSIPlugin) UpdateExpectedWithJob(job *Job, summary *JobSummary, terminal bool) { 941 var count int 942 943 for _, tg := range job.TaskGroups { 944 if job.Type == JobTypeSystem { 945 if summary == nil { 946 continue 947 } 948 949 s, ok := summary.Summary[tg.Name] 950 if !ok { 951 continue 952 } 953 954 count = s.Running + s.Queued + s.Starting 955 } else { 956 count = tg.Count 957 } 958 959 for _, t := range tg.Tasks { 960 if t.CSIPluginConfig == nil || 961 t.CSIPluginConfig.ID != p.ID { 962 continue 963 } 964 965 // Change the correct plugin expected, monolith should change both 966 if t.CSIPluginConfig.Type == CSIPluginTypeController || 967 t.CSIPluginConfig.Type == CSIPluginTypeMonolith { 968 if terminal { 969 p.ControllerJobs.Delete(job) 970 } else { 971 p.ControllerJobs.Add(job, count) 972 } 973 } 974 975 if t.CSIPluginConfig.Type == CSIPluginTypeNode || 976 t.CSIPluginConfig.Type == CSIPluginTypeMonolith { 977 if terminal { 978 p.NodeJobs.Delete(job) 979 } else { 980 p.NodeJobs.Add(job, count) 981 } 982 } 983 } 984 } 985 986 p.ControllersExpected = p.ControllerJobs.Count() 987 p.NodesExpected = p.NodeJobs.Count() 988 } 989 990 // JobDescription records Job identification and the count of expected plugin instances 991 type JobDescription struct { 992 Namespace string 993 ID string 994 Expected int 995 } 996 997 // JobNamespacedDescriptions maps Job.ID to JobDescription 998 type JobNamespacedDescriptions map[string]JobDescription 999 1000 func (j JobNamespacedDescriptions) Copy() JobNamespacedDescriptions { 1001 copy := JobNamespacedDescriptions{} 1002 for k, v := range j { 1003 copy[k] = v 1004 } 1005 return copy 1006 } 1007 1008 // JobDescriptions maps Namespace to a mapping of Job.ID to JobDescription 1009 type JobDescriptions map[string]JobNamespacedDescriptions 1010 1011 // Add the Job to the JobDescriptions, creating maps as necessary 1012 func (j JobDescriptions) Add(job *Job, expected int) { 1013 if j == nil { 1014 j = make(JobDescriptions) 1015 } 1016 if j[job.Namespace] == nil { 1017 j[job.Namespace] = make(JobNamespacedDescriptions) 1018 } 1019 j[job.Namespace][job.ID] = JobDescription{ 1020 Namespace: job.Namespace, 1021 ID: job.ID, 1022 Expected: expected, 1023 } 1024 } 1025 1026 // Count the Expected instances for all JobDescriptions 1027 func (j JobDescriptions) Count() int { 1028 if j == nil { 1029 return 0 1030 } 1031 count := 0 1032 for _, jnd := range j { 1033 for _, jd := range jnd { 1034 count += jd.Expected 1035 } 1036 } 1037 return count 1038 } 1039 1040 // Delete the Job from the JobDescriptions 1041 func (j JobDescriptions) Delete(job *Job) { 1042 if j != nil && 1043 j[job.Namespace] != nil { 1044 delete(j[job.Namespace], job.ID) 1045 } 1046 } 1047 1048 type CSIPluginListStub struct { 1049 ID string 1050 Provider string 1051 ControllerRequired bool 1052 ControllersHealthy int 1053 ControllersExpected int 1054 NodesHealthy int 1055 NodesExpected int 1056 CreateIndex uint64 1057 ModifyIndex uint64 1058 } 1059 1060 func (p *CSIPlugin) Stub() *CSIPluginListStub { 1061 return &CSIPluginListStub{ 1062 ID: p.ID, 1063 Provider: p.Provider, 1064 ControllerRequired: p.ControllerRequired, 1065 ControllersHealthy: p.ControllersHealthy, 1066 ControllersExpected: p.ControllersExpected, 1067 NodesHealthy: p.NodesHealthy, 1068 NodesExpected: p.NodesExpected, 1069 CreateIndex: p.CreateIndex, 1070 ModifyIndex: p.ModifyIndex, 1071 } 1072 } 1073 1074 func (p *CSIPlugin) IsEmpty() bool { 1075 return p == nil || 1076 len(p.Controllers) == 0 && 1077 len(p.Nodes) == 0 && 1078 p.ControllerJobs.Count() == 0 && 1079 p.NodeJobs.Count() == 0 1080 } 1081 1082 type CSIPluginListRequest struct { 1083 QueryOptions 1084 } 1085 1086 type CSIPluginListResponse struct { 1087 Plugins []*CSIPluginListStub 1088 QueryMeta 1089 } 1090 1091 type CSIPluginGetRequest struct { 1092 ID string 1093 QueryOptions 1094 } 1095 1096 type CSIPluginGetResponse struct { 1097 Plugin *CSIPlugin 1098 QueryMeta 1099 } 1100 1101 type CSIPluginDeleteRequest struct { 1102 ID string 1103 QueryOptions 1104 } 1105 1106 type CSIPluginDeleteResponse struct { 1107 QueryMeta 1108 }