github.com/uchennaokeke444/nomad@v0.11.8/nomad/structs/csi.go (about) 1 package structs 2 3 import ( 4 "fmt" 5 "strings" 6 "time" 7 ) 8 9 // CSISocketName is the filename that Nomad expects plugins to create inside the 10 // PluginMountDir. 11 const CSISocketName = "csi.sock" 12 13 // CSIIntermediaryDirname is the name of the directory inside the PluginMountDir 14 // where Nomad will expect plugins to create intermediary mounts for volumes. 15 const CSIIntermediaryDirname = "volumes" 16 17 // VolumeTypeCSI is the type in the volume stanza of a TaskGroup 18 const VolumeTypeCSI = "csi" 19 20 // CSIPluginType is an enum string that encapsulates the valid options for a 21 // CSIPlugin stanza's Type. These modes will allow the plugin to be used in 22 // different ways by the client. 23 type CSIPluginType string 24 25 const ( 26 // CSIPluginTypeNode indicates that Nomad should only use the plugin for 27 // performing Node RPCs against the provided plugin. 28 CSIPluginTypeNode CSIPluginType = "node" 29 30 // CSIPluginTypeController indicates that Nomad should only use the plugin for 31 // performing Controller RPCs against the provided plugin. 32 CSIPluginTypeController CSIPluginType = "controller" 33 34 // CSIPluginTypeMonolith indicates that Nomad can use the provided plugin for 35 // both controller and node rpcs. 36 CSIPluginTypeMonolith CSIPluginType = "monolith" 37 ) 38 39 // CSIPluginTypeIsValid validates the given CSIPluginType string and returns 40 // true only when a correct plugin type is specified. 41 func CSIPluginTypeIsValid(pt CSIPluginType) bool { 42 switch pt { 43 case CSIPluginTypeNode, CSIPluginTypeController, CSIPluginTypeMonolith: 44 return true 45 default: 46 return false 47 } 48 } 49 50 // TaskCSIPluginConfig contains the data that is required to setup a task as a 51 // CSI plugin. This will be used by the csi_plugin_supervisor_hook to configure 52 // mounts for the plugin and initiate the connection to the plugin catalog. 53 type TaskCSIPluginConfig struct { 54 // ID is the identifier of the plugin. 55 // Ideally this should be the FQDN of the plugin. 56 ID string 57 58 // Type instructs Nomad on how to handle processing a plugin 59 Type CSIPluginType 60 61 // MountDir is the destination that nomad should mount in its CSI 62 // directory for the plugin. It will then expect a file called CSISocketName 63 // to be created by the plugin, and will provide references into 64 // "MountDir/CSIIntermediaryDirname/{VolumeName}/{AllocID} for mounts. 65 MountDir string 66 } 67 68 func (t *TaskCSIPluginConfig) Copy() *TaskCSIPluginConfig { 69 if t == nil { 70 return nil 71 } 72 73 nt := new(TaskCSIPluginConfig) 74 *nt = *t 75 76 return nt 77 } 78 79 // CSIVolumeAttachmentMode chooses the type of storage api that will be used to 80 // interact with the device. 81 type CSIVolumeAttachmentMode string 82 83 const ( 84 CSIVolumeAttachmentModeUnknown CSIVolumeAttachmentMode = "" 85 CSIVolumeAttachmentModeBlockDevice CSIVolumeAttachmentMode = "block-device" 86 CSIVolumeAttachmentModeFilesystem CSIVolumeAttachmentMode = "file-system" 87 ) 88 89 func ValidCSIVolumeAttachmentMode(attachmentMode CSIVolumeAttachmentMode) bool { 90 switch attachmentMode { 91 case CSIVolumeAttachmentModeBlockDevice, CSIVolumeAttachmentModeFilesystem: 92 return true 93 default: 94 return false 95 } 96 } 97 98 // CSIVolumeAccessMode indicates how a volume should be used in a storage topology 99 // e.g whether the provider should make the volume available concurrently. 100 type CSIVolumeAccessMode string 101 102 const ( 103 CSIVolumeAccessModeUnknown CSIVolumeAccessMode = "" 104 105 CSIVolumeAccessModeSingleNodeReader CSIVolumeAccessMode = "single-node-reader-only" 106 CSIVolumeAccessModeSingleNodeWriter CSIVolumeAccessMode = "single-node-writer" 107 108 CSIVolumeAccessModeMultiNodeReader CSIVolumeAccessMode = "multi-node-reader-only" 109 CSIVolumeAccessModeMultiNodeSingleWriter CSIVolumeAccessMode = "multi-node-single-writer" 110 CSIVolumeAccessModeMultiNodeMultiWriter CSIVolumeAccessMode = "multi-node-multi-writer" 111 ) 112 113 // ValidCSIVolumeAccessMode checks to see that the provided access mode is a valid, 114 // non-empty access mode. 115 func ValidCSIVolumeAccessMode(accessMode CSIVolumeAccessMode) bool { 116 switch accessMode { 117 case CSIVolumeAccessModeSingleNodeReader, CSIVolumeAccessModeSingleNodeWriter, 118 CSIVolumeAccessModeMultiNodeReader, CSIVolumeAccessModeMultiNodeSingleWriter, 119 CSIVolumeAccessModeMultiNodeMultiWriter: 120 return true 121 default: 122 return false 123 } 124 } 125 126 // ValidCSIVolumeAccessMode checks for a writable access mode 127 func ValidCSIVolumeWriteAccessMode(accessMode CSIVolumeAccessMode) bool { 128 switch accessMode { 129 case CSIVolumeAccessModeSingleNodeWriter, 130 CSIVolumeAccessModeMultiNodeSingleWriter, 131 CSIVolumeAccessModeMultiNodeMultiWriter: 132 return true 133 default: 134 return false 135 } 136 } 137 138 // CSIMountOptions contain optional additional configuration that can be used 139 // when specifying that a Volume should be used with VolumeAccessTypeMount. 140 type CSIMountOptions struct { 141 // FSType is an optional field that allows an operator to specify the type 142 // of the filesystem. 143 FSType string 144 145 // MountFlags contains additional options that may be used when mounting the 146 // volume by the plugin. This may contain sensitive data and should not be 147 // leaked. 148 MountFlags []string 149 } 150 151 func (o *CSIMountOptions) Copy() *CSIMountOptions { 152 if o == nil { 153 return nil 154 } 155 return &(*o) 156 } 157 158 func (o *CSIMountOptions) Merge(p *CSIMountOptions) { 159 if p == nil { 160 return 161 } 162 if p.FSType != "" { 163 o.FSType = p.FSType 164 } 165 if p.MountFlags != nil { 166 o.MountFlags = p.MountFlags 167 } 168 } 169 170 // VolumeMountOptions implements the Stringer and GoStringer interfaces to prevent 171 // accidental leakage of sensitive mount flags via logs. 172 var _ fmt.Stringer = &CSIMountOptions{} 173 var _ fmt.GoStringer = &CSIMountOptions{} 174 175 func (v *CSIMountOptions) String() string { 176 mountFlagsString := "nil" 177 if len(v.MountFlags) != 0 { 178 mountFlagsString = "[REDACTED]" 179 } 180 181 return fmt.Sprintf("csi.CSIOptions(FSType: %s, MountFlags: %s)", v.FSType, mountFlagsString) 182 } 183 184 func (v *CSIMountOptions) GoString() string { 185 return v.String() 186 } 187 188 // CSISecrets contain optional additional configuration that can be used 189 // when specifying that a Volume should be used with VolumeAccessTypeMount. 190 type CSISecrets map[string]string 191 192 // CSISecrets implements the Stringer and GoStringer interfaces to prevent 193 // accidental leakage of secrets via logs. 194 var _ fmt.Stringer = &CSISecrets{} 195 var _ fmt.GoStringer = &CSISecrets{} 196 197 func (s *CSISecrets) String() string { 198 redacted := map[string]string{} 199 for k := range *s { 200 redacted[k] = "[REDACTED]" 201 } 202 return fmt.Sprintf("csi.CSISecrets(%v)", redacted) 203 } 204 205 func (s *CSISecrets) GoString() string { 206 return s.String() 207 } 208 209 type CSIVolumeClaim struct { 210 AllocationID string 211 NodeID string 212 Mode CSIVolumeClaimMode 213 State CSIVolumeClaimState 214 } 215 216 type CSIVolumeClaimState int 217 218 const ( 219 CSIVolumeClaimStateTaken CSIVolumeClaimState = iota 220 CSIVolumeClaimStateNodeDetached 221 CSIVolumeClaimStateControllerDetached 222 CSIVolumeClaimStateReadyToFree 223 ) 224 225 // CSIVolume is the full representation of a CSI Volume 226 type CSIVolume struct { 227 // ID is a namespace unique URL safe identifier for the volume 228 ID string 229 // Name is a display name for the volume, not required to be unique 230 Name string 231 // ExternalID identifies the volume for the CSI interface, may be URL unsafe 232 ExternalID string 233 Namespace string 234 Topologies []*CSITopology 235 AccessMode CSIVolumeAccessMode 236 AttachmentMode CSIVolumeAttachmentMode 237 MountOptions *CSIMountOptions 238 Secrets CSISecrets 239 Parameters map[string]string 240 Context map[string]string 241 242 // Allocations, tracking claim status 243 ReadAllocs map[string]*Allocation // AllocID -> Allocation 244 WriteAllocs map[string]*Allocation // AllocID -> Allocation 245 246 ReadClaims map[string]*CSIVolumeClaim // AllocID -> claim 247 WriteClaims map[string]*CSIVolumeClaim // AllocID -> claim 248 PastClaims map[string]*CSIVolumeClaim // AllocID -> claim 249 250 // Schedulable is true if all the denormalized plugin health fields are true, and the 251 // volume has not been marked for garbage collection 252 Schedulable bool 253 PluginID string 254 Provider string 255 ProviderVersion string 256 ControllerRequired bool 257 ControllersHealthy int 258 ControllersExpected int 259 NodesHealthy int 260 NodesExpected int 261 ResourceExhausted time.Time 262 263 CreateIndex uint64 264 ModifyIndex uint64 265 } 266 267 // CSIVolListStub is partial representation of a CSI Volume for inclusion in lists 268 type CSIVolListStub struct { 269 ID string 270 Namespace string 271 Name string 272 ExternalID string 273 Topologies []*CSITopology 274 AccessMode CSIVolumeAccessMode 275 AttachmentMode CSIVolumeAttachmentMode 276 CurrentReaders int 277 CurrentWriters int 278 Schedulable bool 279 PluginID string 280 Provider string 281 ControllersHealthy int 282 ControllersExpected int 283 NodesHealthy int 284 NodesExpected int 285 CreateIndex uint64 286 ModifyIndex uint64 287 } 288 289 // NewCSIVolume creates the volume struct. No side-effects 290 func NewCSIVolume(volumeID string, index uint64) *CSIVolume { 291 out := &CSIVolume{ 292 ID: volumeID, 293 CreateIndex: index, 294 ModifyIndex: index, 295 } 296 297 out.newStructs() 298 return out 299 } 300 301 func (v *CSIVolume) newStructs() { 302 if v.Topologies == nil { 303 v.Topologies = []*CSITopology{} 304 } 305 if v.Context == nil { 306 v.Context = map[string]string{} 307 } 308 if v.Parameters == nil { 309 v.Parameters = map[string]string{} 310 } 311 if v.Secrets == nil { 312 v.Secrets = CSISecrets{} 313 } 314 315 v.ReadAllocs = map[string]*Allocation{} 316 v.WriteAllocs = map[string]*Allocation{} 317 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, CSIVolumeAccessModeMultiNodeMultiWriter: 382 return len(v.WriteAllocs) == 0 383 default: 384 return false 385 } 386 } 387 388 // InUse tests whether any allocations are actively using the volume 389 func (v *CSIVolume) InUse() bool { 390 return len(v.ReadAllocs) != 0 || 391 len(v.WriteAllocs) != 0 392 } 393 394 // Copy returns a copy of the volume, which shares only the Topologies slice 395 func (v *CSIVolume) Copy() *CSIVolume { 396 copy := *v 397 out := © 398 out.newStructs() 399 for k, v := range v.Parameters { 400 out.Parameters[k] = v 401 } 402 for k, v := range v.Context { 403 out.Context[k] = v 404 } 405 for k, v := range v.Secrets { 406 out.Secrets[k] = v 407 } 408 409 for k, v := range v.ReadAllocs { 410 out.ReadAllocs[k] = v 411 } 412 413 for k, v := range v.WriteAllocs { 414 out.WriteAllocs[k] = v 415 } 416 417 for k, v := range v.ReadClaims { 418 claim := *v 419 out.ReadClaims[k] = &claim 420 } 421 for k, v := range v.WriteClaims { 422 claim := *v 423 out.WriteClaims[k] = &claim 424 } 425 for k, v := range v.PastClaims { 426 claim := *v 427 out.PastClaims[k] = &claim 428 } 429 430 return out 431 } 432 433 // Claim updates the allocations and changes the volume state 434 func (v *CSIVolume) Claim(claim *CSIVolumeClaim, alloc *Allocation) error { 435 switch claim.Mode { 436 case CSIVolumeClaimRead: 437 return v.ClaimRead(claim, alloc) 438 case CSIVolumeClaimWrite: 439 return v.ClaimWrite(claim, alloc) 440 case CSIVolumeClaimRelease: 441 return v.ClaimRelease(claim) 442 } 443 return nil 444 } 445 446 // ClaimRead marks an allocation as using a volume read-only 447 func (v *CSIVolume) ClaimRead(claim *CSIVolumeClaim, alloc *Allocation) error { 448 if _, ok := v.ReadAllocs[claim.AllocationID]; ok { 449 return nil 450 } 451 if alloc == nil { 452 return fmt.Errorf("allocation missing: %s", claim.AllocationID) 453 } 454 455 if !v.ReadSchedulable() { 456 return fmt.Errorf("unschedulable") 457 } 458 459 // Allocations are copy on write, so we want to keep the id but don't need the 460 // pointer. We'll get it from the db in denormalize. 461 v.ReadAllocs[claim.AllocationID] = nil 462 delete(v.WriteAllocs, claim.AllocationID) 463 464 v.ReadClaims[claim.AllocationID] = claim 465 delete(v.WriteClaims, claim.AllocationID) 466 delete(v.PastClaims, claim.AllocationID) 467 468 return nil 469 } 470 471 // ClaimWrite marks an allocation as using a volume as a writer 472 func (v *CSIVolume) ClaimWrite(claim *CSIVolumeClaim, alloc *Allocation) error { 473 if _, ok := v.WriteAllocs[claim.AllocationID]; ok { 474 return nil 475 } 476 if alloc == nil { 477 return fmt.Errorf("allocation missing: %s", claim.AllocationID) 478 } 479 480 if !v.WriteSchedulable() { 481 return fmt.Errorf("unschedulable") 482 } 483 484 if !v.WriteFreeClaims() { 485 // Check the blocking allocations to see if they belong to this job 486 for _, a := range v.WriteAllocs { 487 if a.Namespace != alloc.Namespace || a.JobID != alloc.JobID { 488 return fmt.Errorf("volume max claim reached") 489 } 490 } 491 } 492 493 // Allocations are copy on write, so we want to keep the id but don't need the 494 // pointer. We'll get it from the db in denormalize. 495 v.WriteAllocs[alloc.ID] = nil 496 delete(v.ReadAllocs, alloc.ID) 497 498 v.WriteClaims[alloc.ID] = claim 499 delete(v.ReadClaims, alloc.ID) 500 delete(v.PastClaims, alloc.ID) 501 502 return nil 503 } 504 505 // ClaimRelease is called when the allocation has terminated and 506 // already stopped using the volume 507 func (v *CSIVolume) ClaimRelease(claim *CSIVolumeClaim) error { 508 if claim.State == CSIVolumeClaimStateReadyToFree { 509 delete(v.ReadAllocs, claim.AllocationID) 510 delete(v.WriteAllocs, claim.AllocationID) 511 delete(v.ReadClaims, claim.AllocationID) 512 delete(v.WriteClaims, claim.AllocationID) 513 delete(v.PastClaims, claim.AllocationID) 514 } else { 515 v.PastClaims[claim.AllocationID] = claim 516 } 517 return nil 518 } 519 520 // Equality by value 521 func (v *CSIVolume) Equal(o *CSIVolume) bool { 522 if v == nil || o == nil { 523 return v == o 524 } 525 526 // Omit the plugin health fields, their values are controlled by plugin jobs 527 if v.ID == o.ID && 528 v.Namespace == o.Namespace && 529 v.AccessMode == o.AccessMode && 530 v.AttachmentMode == o.AttachmentMode && 531 v.PluginID == o.PluginID { 532 // Setwise equality of topologies 533 var ok bool 534 for _, t := range v.Topologies { 535 ok = false 536 for _, u := range o.Topologies { 537 if t.Equal(u) { 538 ok = true 539 break 540 } 541 } 542 if !ok { 543 return false 544 } 545 } 546 return true 547 } 548 return false 549 } 550 551 // Validate validates the volume struct, returning all validation errors at once 552 func (v *CSIVolume) Validate() error { 553 errs := []string{} 554 555 if v.ID == "" { 556 errs = append(errs, "missing volume id") 557 } 558 if v.PluginID == "" { 559 errs = append(errs, "missing plugin id") 560 } 561 if v.Namespace == "" { 562 errs = append(errs, "missing namespace") 563 } 564 if v.AccessMode == "" { 565 errs = append(errs, "missing access mode") 566 } 567 if v.AttachmentMode == "" { 568 errs = append(errs, "missing attachment mode") 569 } 570 571 // TODO: Volume Topologies are optional - We should check to see if the plugin 572 // the volume is being registered with requires them. 573 // var ok bool 574 // for _, t := range v.Topologies { 575 // if t != nil && len(t.Segments) > 0 { 576 // ok = true 577 // break 578 // } 579 // } 580 // if !ok { 581 // errs = append(errs, "missing topology") 582 // } 583 584 if len(errs) > 0 { 585 return fmt.Errorf("validation: %s", strings.Join(errs, ", ")) 586 } 587 return nil 588 } 589 590 // Request and response wrappers 591 type CSIVolumeRegisterRequest struct { 592 Volumes []*CSIVolume 593 WriteRequest 594 } 595 596 type CSIVolumeRegisterResponse struct { 597 QueryMeta 598 } 599 600 type CSIVolumeDeregisterRequest struct { 601 VolumeIDs []string 602 WriteRequest 603 } 604 605 type CSIVolumeDeregisterResponse struct { 606 QueryMeta 607 } 608 609 type CSIVolumeClaimMode int 610 611 const ( 612 CSIVolumeClaimRead CSIVolumeClaimMode = iota 613 CSIVolumeClaimWrite 614 CSIVolumeClaimRelease 615 ) 616 617 type CSIVolumeClaimBatchRequest struct { 618 Claims []CSIVolumeClaimRequest 619 } 620 621 type CSIVolumeClaimRequest struct { 622 VolumeID string 623 AllocationID string 624 NodeID string 625 Claim CSIVolumeClaimMode 626 State CSIVolumeClaimState 627 WriteRequest 628 } 629 630 func (req *CSIVolumeClaimRequest) ToClaim() *CSIVolumeClaim { 631 return &CSIVolumeClaim{ 632 AllocationID: req.AllocationID, 633 NodeID: req.NodeID, 634 Mode: req.Claim, 635 State: req.State, 636 } 637 } 638 639 type CSIVolumeClaimResponse struct { 640 // Opaque static publish properties of the volume. SP MAY use this 641 // field to ensure subsequent `NodeStageVolume` or `NodePublishVolume` 642 // calls calls have contextual information. 643 // The contents of this field SHALL be opaque to nomad. 644 // The contents of this field SHALL NOT be mutable. 645 // The contents of this field SHALL be safe for the nomad to cache. 646 // The contents of this field SHOULD NOT contain sensitive 647 // information. 648 // The contents of this field SHOULD NOT be used for uniquely 649 // identifying a volume. The `volume_id` alone SHOULD be sufficient to 650 // identify the volume. 651 // This field is OPTIONAL and when present MUST be passed to 652 // `NodeStageVolume` or `NodePublishVolume` calls on the client 653 PublishContext map[string]string 654 655 // Volume contains the expanded CSIVolume for use on the client after a Claim 656 // has completed. 657 Volume *CSIVolume 658 659 QueryMeta 660 } 661 662 type CSIVolumeListRequest struct { 663 PluginID string 664 NodeID string 665 QueryOptions 666 } 667 668 type CSIVolumeListResponse struct { 669 Volumes []*CSIVolListStub 670 QueryMeta 671 } 672 673 type CSIVolumeGetRequest struct { 674 ID string 675 QueryOptions 676 } 677 678 type CSIVolumeGetResponse struct { 679 Volume *CSIVolume 680 QueryMeta 681 } 682 683 // CSIPlugin collects fingerprint info context for the plugin for clients 684 type CSIPlugin struct { 685 ID string 686 Provider string // the vendor name from CSI GetPluginInfoResponse 687 Version string // the vendor verson from CSI GetPluginInfoResponse 688 ControllerRequired bool 689 690 // Map Node.IDs to fingerprint results, split by type. Monolith type plugins have 691 // both sets of fingerprinting results. 692 Controllers map[string]*CSIInfo 693 Nodes map[string]*CSIInfo 694 695 // Allocations are populated by denormalize to show running allocations 696 Allocations []*AllocListStub 697 698 // Cache the count of healthy plugins 699 ControllersHealthy int 700 NodesHealthy int 701 702 CreateIndex uint64 703 ModifyIndex uint64 704 } 705 706 // NewCSIPlugin creates the plugin struct. No side-effects 707 func NewCSIPlugin(id string, index uint64) *CSIPlugin { 708 out := &CSIPlugin{ 709 ID: id, 710 CreateIndex: index, 711 ModifyIndex: index, 712 } 713 714 out.newStructs() 715 return out 716 } 717 718 func (p *CSIPlugin) newStructs() { 719 p.Controllers = map[string]*CSIInfo{} 720 p.Nodes = map[string]*CSIInfo{} 721 } 722 723 func (p *CSIPlugin) Copy() *CSIPlugin { 724 copy := *p 725 out := © 726 out.newStructs() 727 728 for k, v := range p.Controllers { 729 out.Controllers[k] = v 730 } 731 732 for k, v := range p.Nodes { 733 out.Nodes[k] = v 734 } 735 736 return out 737 } 738 739 // AddPlugin adds a single plugin running on the node. Called from state.NodeUpdate in a 740 // transaction 741 func (p *CSIPlugin) AddPlugin(nodeID string, info *CSIInfo) error { 742 if info.ControllerInfo != nil { 743 p.ControllerRequired = info.RequiresControllerPlugin && 744 (info.ControllerInfo.SupportsAttachDetach || 745 info.ControllerInfo.SupportsReadOnlyAttach) 746 747 prev, ok := p.Controllers[nodeID] 748 if ok { 749 if prev == nil { 750 return fmt.Errorf("plugin missing controller: %s", nodeID) 751 } 752 if prev.Healthy { 753 p.ControllersHealthy -= 1 754 } 755 } 756 757 // note: for this to work as expected, only a single 758 // controller for a given plugin can be on a given Nomad 759 // client, they also conflict on the client so this should be 760 // ok 761 if prev != nil || info.Healthy { 762 p.Controllers[nodeID] = info 763 } 764 if info.Healthy { 765 p.ControllersHealthy += 1 766 } 767 } 768 769 if info.NodeInfo != nil { 770 prev, ok := p.Nodes[nodeID] 771 if ok { 772 if prev == nil { 773 return fmt.Errorf("plugin missing node: %s", nodeID) 774 } 775 if prev.Healthy { 776 p.NodesHealthy -= 1 777 } 778 } 779 if prev != nil || info.Healthy { 780 p.Nodes[nodeID] = info 781 } 782 if info.Healthy { 783 p.NodesHealthy += 1 784 } 785 } 786 787 return nil 788 } 789 790 // DeleteNode removes all plugins from the node. Called from state.DeleteNode in a 791 // transaction 792 func (p *CSIPlugin) DeleteNode(nodeID string) error { 793 return p.DeleteNodeForType(nodeID, CSIPluginTypeMonolith) 794 } 795 796 // DeleteNodeForType deletes a client node from the list of controllers or node instance of 797 // a plugin. Called from deleteJobFromPlugin during job deregistration, in a transaction 798 func (p *CSIPlugin) DeleteNodeForType(nodeID string, pluginType CSIPluginType) error { 799 switch pluginType { 800 case CSIPluginTypeController: 801 prev, ok := p.Controllers[nodeID] 802 if ok { 803 if prev == nil { 804 return fmt.Errorf("plugin missing controller: %s", nodeID) 805 } 806 if prev.Healthy { 807 p.ControllersHealthy -= 1 808 } 809 } 810 delete(p.Controllers, nodeID) 811 812 case CSIPluginTypeNode: 813 prev, ok := p.Nodes[nodeID] 814 if ok { 815 if prev == nil { 816 return fmt.Errorf("plugin missing node: %s", nodeID) 817 } 818 if prev.Healthy { 819 p.NodesHealthy -= 1 820 } 821 } 822 delete(p.Nodes, nodeID) 823 824 case CSIPluginTypeMonolith: 825 p.DeleteNodeForType(nodeID, CSIPluginTypeController) 826 p.DeleteNodeForType(nodeID, CSIPluginTypeNode) 827 } 828 829 return nil 830 } 831 832 // DeleteAlloc removes the fingerprint info for the allocation 833 func (p *CSIPlugin) DeleteAlloc(allocID, nodeID string) error { 834 prev, ok := p.Controllers[nodeID] 835 if ok { 836 if prev == nil { 837 return fmt.Errorf("plugin missing controller: %s", nodeID) 838 } 839 if prev.AllocID == allocID { 840 if prev.Healthy { 841 p.ControllersHealthy -= 1 842 } 843 delete(p.Controllers, nodeID) 844 } 845 } 846 847 prev, ok = p.Nodes[nodeID] 848 if ok { 849 if prev == nil { 850 return fmt.Errorf("plugin missing node: %s", nodeID) 851 } 852 if prev.AllocID == allocID { 853 if prev.Healthy { 854 p.NodesHealthy -= 1 855 } 856 delete(p.Nodes, nodeID) 857 } 858 } 859 860 return nil 861 } 862 863 type CSIPluginListStub struct { 864 ID string 865 Provider string 866 ControllerRequired bool 867 ControllersHealthy int 868 ControllersExpected int 869 NodesHealthy int 870 NodesExpected int 871 CreateIndex uint64 872 ModifyIndex uint64 873 } 874 875 func (p *CSIPlugin) Stub() *CSIPluginListStub { 876 return &CSIPluginListStub{ 877 ID: p.ID, 878 Provider: p.Provider, 879 ControllerRequired: p.ControllerRequired, 880 ControllersHealthy: p.ControllersHealthy, 881 ControllersExpected: len(p.Controllers), 882 NodesHealthy: p.NodesHealthy, 883 NodesExpected: len(p.Nodes), 884 CreateIndex: p.CreateIndex, 885 ModifyIndex: p.ModifyIndex, 886 } 887 } 888 889 func (p *CSIPlugin) IsEmpty() bool { 890 return len(p.Controllers) == 0 && len(p.Nodes) == 0 891 } 892 893 type CSIPluginListRequest struct { 894 QueryOptions 895 } 896 897 type CSIPluginListResponse struct { 898 Plugins []*CSIPluginListStub 899 QueryMeta 900 } 901 902 type CSIPluginGetRequest struct { 903 ID string 904 QueryOptions 905 } 906 907 type CSIPluginGetResponse struct { 908 Plugin *CSIPlugin 909 QueryMeta 910 } 911 912 type CSIPluginDeleteRequest struct { 913 ID string 914 QueryOptions 915 } 916 917 type CSIPluginDeleteResponse struct { 918 QueryMeta 919 }