github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/plugins/csi/plugin.go (about) 1 package csi 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 8 csipbv1 "github.com/container-storage-interface/spec/lib/go/csi" 9 "github.com/hashicorp/nomad/nomad/structs" 10 "github.com/hashicorp/nomad/plugins/base" 11 "google.golang.org/grpc" 12 ) 13 14 // CSIPlugin implements a lightweight abstraction layer around a CSI Plugin. 15 // It validates that responses from storage providers (SP's), correctly conform 16 // to the specification before returning response data or erroring. 17 type CSIPlugin interface { 18 base.BasePlugin 19 20 // PluginProbe is used to verify that the plugin is in a healthy state 21 PluginProbe(ctx context.Context) (bool, error) 22 23 // PluginGetInfo is used to return semantic data about the plugin. 24 // Response: 25 // - string: name, the name of the plugin in domain notation format. 26 // - string: version, the vendor version of the plugin 27 PluginGetInfo(ctx context.Context) (string, string, error) 28 29 // PluginGetCapabilities is used to return the available capabilities from the 30 // identity service. This currently only looks for the CONTROLLER_SERVICE and 31 // Accessible Topology Support 32 PluginGetCapabilities(ctx context.Context) (*PluginCapabilitySet, error) 33 34 // GetControllerCapabilities is used to get controller-specific capabilities 35 // for a plugin. 36 ControllerGetCapabilities(ctx context.Context) (*ControllerCapabilitySet, error) 37 38 // ControllerPublishVolume is used to attach a remote volume to a cluster node. 39 ControllerPublishVolume(ctx context.Context, req *ControllerPublishVolumeRequest, opts ...grpc.CallOption) (*ControllerPublishVolumeResponse, error) 40 41 // ControllerUnpublishVolume is used to deattach a remote volume from a cluster node. 42 ControllerUnpublishVolume(ctx context.Context, req *ControllerUnpublishVolumeRequest, opts ...grpc.CallOption) (*ControllerUnpublishVolumeResponse, error) 43 44 // ControllerValidateCapabilities is used to validate that a volume exists and 45 // supports the requested capability. 46 ControllerValidateCapabilities(ctx context.Context, req *ControllerValidateVolumeRequest, opts ...grpc.CallOption) error 47 48 // ControllerCreateVolume is used to create a remote volume in the 49 // external storage provider 50 ControllerCreateVolume(ctx context.Context, req *ControllerCreateVolumeRequest, opts ...grpc.CallOption) (*ControllerCreateVolumeResponse, error) 51 52 // ControllerDeleteVolume is used to delete a remote volume in the 53 // external storage provider 54 ControllerDeleteVolume(ctx context.Context, req *ControllerDeleteVolumeRequest, opts ...grpc.CallOption) error 55 56 // ControllerListVolumes is used to list all volumes available in the 57 // external storage provider 58 ControllerListVolumes(ctx context.Context, req *ControllerListVolumesRequest, opts ...grpc.CallOption) (*ControllerListVolumesResponse, error) 59 60 // ControllerCreateSnapshot is used to create a volume snapshot in the 61 // external storage provider 62 ControllerCreateSnapshot(ctx context.Context, req *ControllerCreateSnapshotRequest, opts ...grpc.CallOption) (*ControllerCreateSnapshotResponse, error) 63 64 // ControllerDeleteSnapshot is used to delete a volume snapshot from the 65 // external storage provider 66 ControllerDeleteSnapshot(ctx context.Context, req *ControllerDeleteSnapshotRequest, opts ...grpc.CallOption) error 67 68 // ControllerListSnapshots is used to list all volume snapshots available 69 // in the external storage provider 70 ControllerListSnapshots(ctx context.Context, req *ControllerListSnapshotsRequest, opts ...grpc.CallOption) (*ControllerListSnapshotsResponse, error) 71 72 // NodeGetCapabilities is used to return the available capabilities from the 73 // Node Service. 74 NodeGetCapabilities(ctx context.Context) (*NodeCapabilitySet, error) 75 76 // NodeGetInfo is used to return semantic data about the current node in 77 // respect to the SP. 78 NodeGetInfo(ctx context.Context) (*NodeGetInfoResponse, error) 79 80 // NodeStageVolume is used when a plugin has the STAGE_UNSTAGE volume capability 81 // to prepare a volume for usage on a host. If err == nil, the response should 82 // be assumed to be successful. 83 NodeStageVolume(ctx context.Context, req *NodeStageVolumeRequest, opts ...grpc.CallOption) error 84 85 // NodeUnstageVolume is used when a plugin has the STAGE_UNSTAGE volume capability 86 // to undo the work performed by NodeStageVolume. If a volume has been staged, 87 // this RPC must be called before freeing the volume. 88 // 89 // If err == nil, the response should be assumed to be successful. 90 NodeUnstageVolume(ctx context.Context, volumeID string, stagingTargetPath string, opts ...grpc.CallOption) error 91 92 // NodePublishVolume is used to prepare a volume for use by an allocation. 93 // if err == nil the response should be assumed to be successful. 94 NodePublishVolume(ctx context.Context, req *NodePublishVolumeRequest, opts ...grpc.CallOption) error 95 96 // NodeUnpublishVolume is used to cleanup usage of a volume for an alloc. This 97 // MUST be called before calling NodeUnstageVolume or ControllerUnpublishVolume 98 // for the given volume. 99 NodeUnpublishVolume(ctx context.Context, volumeID, targetPath string, opts ...grpc.CallOption) error 100 101 // Shutdown the client and ensure any connections are cleaned up. 102 Close() error 103 } 104 105 type NodePublishVolumeRequest struct { 106 // The external ID of the volume to publish. 107 ExternalID string 108 109 // If the volume was attached via a call to `ControllerPublishVolume` then 110 // we need to provide the returned PublishContext here. 111 PublishContext map[string]string 112 113 // The path to which the volume was staged by `NodeStageVolume`. 114 // It MUST be an absolute path in the root filesystem of the process 115 // serving this request. 116 // E.g {the plugins internal mount path}/staging/volumeid/... 117 // 118 // It MUST be set if the Node Plugin implements the 119 // `STAGE_UNSTAGE_VOLUME` node capability. 120 StagingTargetPath string 121 122 // The path to which the volume will be published. 123 // It MUST be an absolute path in the root filesystem of the process serving this 124 // request. 125 // E.g {the plugins internal mount path}/per-alloc/allocid/volumeid/... 126 // 127 // The CO SHALL ensure uniqueness of target_path per volume. 128 // The CO SHALL ensure that the parent directory of this path exists 129 // and that the process serving the request has `read` and `write` 130 // permissions to that parent directory. 131 TargetPath string 132 133 // Volume capability describing how the CO intends to use this volume. 134 VolumeCapability *VolumeCapability 135 136 Readonly bool 137 138 // Secrets required by plugins to complete the node publish volume 139 // request. This field is OPTIONAL. 140 Secrets structs.CSISecrets 141 142 // Volume context as returned by SP in the CSI 143 // CreateVolumeResponse.Volume.volume_context which we don't implement but 144 // can be entered by hand in the volume spec. This field is OPTIONAL. 145 VolumeContext map[string]string 146 } 147 148 func (r *NodePublishVolumeRequest) ToCSIRepresentation() *csipbv1.NodePublishVolumeRequest { 149 if r == nil { 150 return nil 151 } 152 153 return &csipbv1.NodePublishVolumeRequest{ 154 VolumeId: r.ExternalID, 155 PublishContext: r.PublishContext, 156 StagingTargetPath: r.StagingTargetPath, 157 TargetPath: r.TargetPath, 158 VolumeCapability: r.VolumeCapability.ToCSIRepresentation(), 159 Readonly: r.Readonly, 160 Secrets: r.Secrets, 161 VolumeContext: r.VolumeContext, 162 } 163 } 164 165 func (r *NodePublishVolumeRequest) Validate() error { 166 if r.ExternalID == "" { 167 return errors.New("missing volume ID") 168 } 169 170 if r.TargetPath == "" { 171 return errors.New("missing TargetPath") 172 } 173 174 if r.VolumeCapability == nil { 175 return errors.New("missing VolumeCapabilities") 176 } 177 178 return nil 179 } 180 181 type NodeStageVolumeRequest struct { 182 // The external ID of the volume to stage. 183 ExternalID string 184 185 // If the volume was attached via a call to `ControllerPublishVolume` then 186 // we need to provide the returned PublishContext here. 187 PublishContext map[string]string 188 189 // The path to which the volume MAY be staged. It MUST be an 190 // absolute path in the root filesystem of the process serving this 191 // request, and MUST be a directory. The CO SHALL ensure that there 192 // is only one `staging_target_path` per volume. The CO SHALL ensure 193 // that the path is directory and that the process serving the 194 // request has `read` and `write` permission to that directory. The 195 // CO SHALL be responsible for creating the directory if it does not 196 // exist. 197 // This is a REQUIRED field. 198 StagingTargetPath string 199 200 // Volume capability describing how the CO intends to use this volume. 201 VolumeCapability *VolumeCapability 202 203 // Secrets required by plugins to complete the node stage volume 204 // request. This field is OPTIONAL. 205 Secrets structs.CSISecrets 206 207 // Volume context as returned by SP in the CSI 208 // CreateVolumeResponse.Volume.volume_context which we don't implement but 209 // can be entered by hand in the volume spec. This field is OPTIONAL. 210 VolumeContext map[string]string 211 } 212 213 func (r *NodeStageVolumeRequest) ToCSIRepresentation() *csipbv1.NodeStageVolumeRequest { 214 if r == nil { 215 return nil 216 } 217 218 return &csipbv1.NodeStageVolumeRequest{ 219 VolumeId: r.ExternalID, 220 PublishContext: r.PublishContext, 221 StagingTargetPath: r.StagingTargetPath, 222 VolumeCapability: r.VolumeCapability.ToCSIRepresentation(), 223 Secrets: r.Secrets, 224 VolumeContext: r.VolumeContext, 225 } 226 } 227 228 func (r *NodeStageVolumeRequest) Validate() error { 229 if r.ExternalID == "" { 230 return errors.New("missing volume ID") 231 } 232 233 if r.StagingTargetPath == "" { 234 return errors.New("missing StagingTargetPath") 235 } 236 237 if r.VolumeCapability == nil { 238 return errors.New("missing VolumeCapabilities") 239 } 240 241 return nil 242 } 243 244 type PluginCapabilitySet struct { 245 hasControllerService bool 246 hasTopologies bool 247 } 248 249 func (p *PluginCapabilitySet) HasControllerService() bool { 250 return p.hasControllerService 251 } 252 253 // HasToplogies indicates whether the volumes for this plugin are equally 254 // accessible by all nodes in the cluster. 255 // If true, we MUST use the topology information when scheduling workloads. 256 func (p *PluginCapabilitySet) HasToplogies() bool { 257 return p.hasTopologies 258 } 259 260 func (p *PluginCapabilitySet) IsEqual(o *PluginCapabilitySet) bool { 261 return p.hasControllerService == o.hasControllerService && p.hasTopologies == o.hasTopologies 262 } 263 264 func NewTestPluginCapabilitySet(topologies, controller bool) *PluginCapabilitySet { 265 return &PluginCapabilitySet{ 266 hasTopologies: topologies, 267 hasControllerService: controller, 268 } 269 } 270 271 func NewPluginCapabilitySet(capabilities *csipbv1.GetPluginCapabilitiesResponse) *PluginCapabilitySet { 272 cs := &PluginCapabilitySet{} 273 274 pluginCapabilities := capabilities.GetCapabilities() 275 276 for _, pcap := range pluginCapabilities { 277 if svcCap := pcap.GetService(); svcCap != nil { 278 switch svcCap.Type { 279 case csipbv1.PluginCapability_Service_UNKNOWN: 280 continue 281 case csipbv1.PluginCapability_Service_CONTROLLER_SERVICE: 282 cs.hasControllerService = true 283 case csipbv1.PluginCapability_Service_VOLUME_ACCESSIBILITY_CONSTRAINTS: 284 cs.hasTopologies = true 285 default: 286 continue 287 } 288 } 289 } 290 291 return cs 292 } 293 294 type ControllerCapabilitySet struct { 295 HasCreateDeleteVolume bool 296 HasPublishUnpublishVolume bool 297 HasListVolumes bool 298 HasGetCapacity bool 299 HasCreateDeleteSnapshot bool 300 HasListSnapshots bool 301 HasCloneVolume bool 302 HasPublishReadonly bool 303 HasExpandVolume bool 304 HasListVolumesPublishedNodes bool 305 HasVolumeCondition bool 306 HasGetVolume bool 307 } 308 309 func NewControllerCapabilitySet(resp *csipbv1.ControllerGetCapabilitiesResponse) *ControllerCapabilitySet { 310 cs := &ControllerCapabilitySet{} 311 312 pluginCapabilities := resp.GetCapabilities() 313 for _, pcap := range pluginCapabilities { 314 if c := pcap.GetRpc(); c != nil { 315 switch c.Type { 316 case csipbv1.ControllerServiceCapability_RPC_CREATE_DELETE_VOLUME: 317 cs.HasCreateDeleteVolume = true 318 case csipbv1.ControllerServiceCapability_RPC_PUBLISH_UNPUBLISH_VOLUME: 319 cs.HasPublishUnpublishVolume = true 320 case csipbv1.ControllerServiceCapability_RPC_LIST_VOLUMES: 321 cs.HasListVolumes = true 322 case csipbv1.ControllerServiceCapability_RPC_GET_CAPACITY: 323 cs.HasGetCapacity = true 324 case csipbv1.ControllerServiceCapability_RPC_CREATE_DELETE_SNAPSHOT: 325 cs.HasCreateDeleteSnapshot = true 326 case csipbv1.ControllerServiceCapability_RPC_LIST_SNAPSHOTS: 327 cs.HasListSnapshots = true 328 case csipbv1.ControllerServiceCapability_RPC_CLONE_VOLUME: 329 cs.HasCloneVolume = true 330 case csipbv1.ControllerServiceCapability_RPC_PUBLISH_READONLY: 331 cs.HasPublishReadonly = true 332 case csipbv1.ControllerServiceCapability_RPC_EXPAND_VOLUME: 333 cs.HasExpandVolume = true 334 case csipbv1.ControllerServiceCapability_RPC_LIST_VOLUMES_PUBLISHED_NODES: 335 cs.HasListVolumesPublishedNodes = true 336 case csipbv1.ControllerServiceCapability_RPC_VOLUME_CONDITION: 337 cs.HasVolumeCondition = true 338 case csipbv1.ControllerServiceCapability_RPC_GET_VOLUME: 339 cs.HasGetVolume = true 340 default: 341 continue 342 } 343 } 344 } 345 346 return cs 347 } 348 349 type ControllerValidateVolumeRequest struct { 350 ExternalID string 351 Secrets structs.CSISecrets 352 Capabilities []*VolumeCapability 353 Parameters map[string]string 354 Context map[string]string 355 } 356 357 func (r *ControllerValidateVolumeRequest) ToCSIRepresentation() *csipbv1.ValidateVolumeCapabilitiesRequest { 358 if r == nil { 359 return nil 360 } 361 362 caps := make([]*csipbv1.VolumeCapability, 0, len(r.Capabilities)) 363 for _, cap := range r.Capabilities { 364 caps = append(caps, cap.ToCSIRepresentation()) 365 } 366 367 return &csipbv1.ValidateVolumeCapabilitiesRequest{ 368 VolumeId: r.ExternalID, 369 VolumeContext: r.Context, 370 VolumeCapabilities: caps, 371 Parameters: r.Parameters, 372 Secrets: r.Secrets, 373 } 374 } 375 376 type ControllerPublishVolumeRequest struct { 377 ExternalID string 378 NodeID string 379 ReadOnly bool 380 VolumeCapability *VolumeCapability 381 Secrets structs.CSISecrets 382 VolumeContext map[string]string 383 } 384 385 func (r *ControllerPublishVolumeRequest) ToCSIRepresentation() *csipbv1.ControllerPublishVolumeRequest { 386 if r == nil { 387 return nil 388 } 389 390 return &csipbv1.ControllerPublishVolumeRequest{ 391 VolumeId: r.ExternalID, 392 NodeId: r.NodeID, 393 Readonly: r.ReadOnly, 394 VolumeCapability: r.VolumeCapability.ToCSIRepresentation(), 395 Secrets: r.Secrets, 396 VolumeContext: r.VolumeContext, 397 } 398 } 399 400 func (r *ControllerPublishVolumeRequest) Validate() error { 401 if r.ExternalID == "" { 402 return errors.New("missing volume ID") 403 } 404 if r.NodeID == "" { 405 return errors.New("missing NodeID") 406 } 407 return nil 408 } 409 410 type ControllerPublishVolumeResponse struct { 411 PublishContext map[string]string 412 } 413 414 type ControllerUnpublishVolumeRequest struct { 415 ExternalID string 416 NodeID string 417 Secrets structs.CSISecrets 418 } 419 420 func (r *ControllerUnpublishVolumeRequest) ToCSIRepresentation() *csipbv1.ControllerUnpublishVolumeRequest { 421 if r == nil { 422 return nil 423 } 424 425 return &csipbv1.ControllerUnpublishVolumeRequest{ 426 VolumeId: r.ExternalID, 427 NodeId: r.NodeID, 428 Secrets: r.Secrets, 429 } 430 } 431 432 func (r *ControllerUnpublishVolumeRequest) Validate() error { 433 if r.ExternalID == "" { 434 return errors.New("missing ExternalID") 435 } 436 if r.NodeID == "" { 437 // the spec allows this but it would unpublish the 438 // volume from all nodes 439 return errors.New("missing NodeID") 440 } 441 return nil 442 } 443 444 type ControllerUnpublishVolumeResponse struct{} 445 446 type ControllerCreateVolumeRequest struct { 447 // note that Name is intentionally differentiated from both CSIVolume.ID 448 // and ExternalVolumeID. This name is only a recommendation for the 449 // storage provider, and many will discard this suggestion 450 Name string 451 CapacityRange *CapacityRange 452 VolumeCapabilities []*VolumeCapability 453 Parameters map[string]string 454 Secrets structs.CSISecrets 455 ContentSource *VolumeContentSource 456 AccessibilityRequirements *TopologyRequirement 457 } 458 459 func (r *ControllerCreateVolumeRequest) ToCSIRepresentation() *csipbv1.CreateVolumeRequest { 460 if r == nil { 461 return nil 462 } 463 caps := make([]*csipbv1.VolumeCapability, 0, len(r.VolumeCapabilities)) 464 for _, cap := range r.VolumeCapabilities { 465 caps = append(caps, cap.ToCSIRepresentation()) 466 } 467 req := &csipbv1.CreateVolumeRequest{ 468 Name: r.Name, 469 CapacityRange: r.CapacityRange.ToCSIRepresentation(), 470 VolumeCapabilities: caps, 471 Parameters: r.Parameters, 472 Secrets: r.Secrets, 473 VolumeContentSource: r.ContentSource.ToCSIRepresentation(), 474 AccessibilityRequirements: r.AccessibilityRequirements.ToCSIRepresentation(), 475 } 476 477 return req 478 } 479 480 func (r *ControllerCreateVolumeRequest) Validate() error { 481 if r.Name == "" { 482 return errors.New("missing Name") 483 } 484 if r.VolumeCapabilities == nil { 485 return errors.New("missing VolumeCapabilities") 486 } 487 if r.CapacityRange != nil { 488 if r.CapacityRange.LimitBytes == 0 && r.CapacityRange.RequiredBytes == 0 { 489 return errors.New( 490 "one of LimitBytes or RequiredBytes must be set if CapacityRange is set") 491 } 492 if r.CapacityRange.LimitBytes < r.CapacityRange.RequiredBytes { 493 return errors.New("LimitBytes cannot be less than RequiredBytes") 494 } 495 } 496 if r.ContentSource != nil { 497 if r.ContentSource.CloneID != "" && r.ContentSource.SnapshotID != "" { 498 return errors.New( 499 "one of SnapshotID or CloneID must be set if ContentSource is set") 500 } 501 } 502 return nil 503 } 504 505 // VolumeContentSource is snapshot or volume that the plugin will use to 506 // create the new volume. At most one of these fields can be set, but nil (and 507 // not an empty struct) is expected by CSI plugins if neither field is set. 508 type VolumeContentSource struct { 509 SnapshotID string 510 CloneID string 511 } 512 513 func (vcr *VolumeContentSource) ToCSIRepresentation() *csipbv1.VolumeContentSource { 514 if vcr == nil { 515 return nil 516 } 517 if vcr.CloneID != "" { 518 return &csipbv1.VolumeContentSource{ 519 Type: &csipbv1.VolumeContentSource_Volume{ 520 Volume: &csipbv1.VolumeContentSource_VolumeSource{ 521 VolumeId: vcr.CloneID, 522 }, 523 }, 524 } 525 } else if vcr.SnapshotID != "" { 526 return &csipbv1.VolumeContentSource{ 527 Type: &csipbv1.VolumeContentSource_Snapshot{ 528 Snapshot: &csipbv1.VolumeContentSource_SnapshotSource{ 529 SnapshotId: vcr.SnapshotID, 530 }, 531 }, 532 } 533 } 534 // Nomad's RPCs will hand us an empty struct, not nil 535 return nil 536 } 537 538 func newVolumeContentSource(src *csipbv1.VolumeContentSource) *VolumeContentSource { 539 return &VolumeContentSource{ 540 SnapshotID: src.GetSnapshot().GetSnapshotId(), 541 CloneID: src.GetVolume().GetVolumeId(), 542 } 543 } 544 545 type TopologyRequirement struct { 546 Requisite []*Topology 547 Preferred []*Topology 548 } 549 550 func (tr *TopologyRequirement) ToCSIRepresentation() *csipbv1.TopologyRequirement { 551 if tr == nil { 552 return nil 553 } 554 result := &csipbv1.TopologyRequirement{ 555 Requisite: []*csipbv1.Topology{}, 556 Preferred: []*csipbv1.Topology{}, 557 } 558 for _, topo := range tr.Requisite { 559 result.Requisite = append(result.Requisite, 560 &csipbv1.Topology{Segments: topo.Segments}) 561 } 562 for _, topo := range tr.Preferred { 563 result.Preferred = append(result.Preferred, 564 &csipbv1.Topology{Segments: topo.Segments}) 565 } 566 return result 567 } 568 569 func newTopologies(src []*csipbv1.Topology) []*Topology { 570 t := []*Topology{} 571 for _, topo := range src { 572 t = append(t, &Topology{Segments: topo.Segments}) 573 } 574 return t 575 } 576 577 type ControllerCreateVolumeResponse struct { 578 Volume *Volume 579 } 580 581 func NewCreateVolumeResponse(resp *csipbv1.CreateVolumeResponse) *ControllerCreateVolumeResponse { 582 vol := resp.GetVolume() 583 return &ControllerCreateVolumeResponse{Volume: &Volume{ 584 CapacityBytes: vol.GetCapacityBytes(), 585 ExternalVolumeID: vol.GetVolumeId(), 586 VolumeContext: vol.GetVolumeContext(), 587 ContentSource: newVolumeContentSource(vol.GetContentSource()), 588 AccessibleTopology: newTopologies(vol.GetAccessibleTopology()), 589 }} 590 } 591 592 type Volume struct { 593 CapacityBytes int64 594 595 // this is differentiated from VolumeID so as not to create confusion 596 // between the Nomad CSIVolume.ID and the storage provider's ID. 597 ExternalVolumeID string 598 VolumeContext map[string]string 599 ContentSource *VolumeContentSource 600 AccessibleTopology []*Topology 601 } 602 603 type ControllerDeleteVolumeRequest struct { 604 ExternalVolumeID string 605 Secrets structs.CSISecrets 606 } 607 608 func (r *ControllerDeleteVolumeRequest) ToCSIRepresentation() *csipbv1.DeleteVolumeRequest { 609 if r == nil { 610 return nil 611 } 612 return &csipbv1.DeleteVolumeRequest{ 613 VolumeId: r.ExternalVolumeID, 614 Secrets: r.Secrets, 615 } 616 } 617 618 func (r *ControllerDeleteVolumeRequest) Validate() error { 619 if r.ExternalVolumeID == "" { 620 return errors.New("missing ExternalVolumeID") 621 } 622 return nil 623 } 624 625 type ControllerListVolumesRequest struct { 626 MaxEntries int32 627 StartingToken string 628 } 629 630 func (r *ControllerListVolumesRequest) ToCSIRepresentation() *csipbv1.ListVolumesRequest { 631 if r == nil { 632 return nil 633 } 634 return &csipbv1.ListVolumesRequest{ 635 MaxEntries: r.MaxEntries, 636 StartingToken: r.StartingToken, 637 } 638 } 639 640 func (r *ControllerListVolumesRequest) Validate() error { 641 if r.MaxEntries < 0 { 642 return errors.New("MaxEntries cannot be negative") 643 } 644 return nil 645 } 646 647 type ControllerListVolumesResponse struct { 648 Entries []*ListVolumesResponse_Entry 649 NextToken string 650 } 651 652 func NewListVolumesResponse(resp *csipbv1.ListVolumesResponse) *ControllerListVolumesResponse { 653 if resp == nil { 654 return &ControllerListVolumesResponse{} 655 } 656 entries := []*ListVolumesResponse_Entry{} 657 if resp.Entries != nil { 658 for _, entry := range resp.Entries { 659 vol := entry.GetVolume() 660 status := entry.GetStatus() 661 entries = append(entries, &ListVolumesResponse_Entry{ 662 Volume: &Volume{ 663 CapacityBytes: vol.CapacityBytes, 664 ExternalVolumeID: vol.VolumeId, 665 VolumeContext: vol.VolumeContext, 666 ContentSource: newVolumeContentSource(vol.ContentSource), 667 AccessibleTopology: newTopologies(vol.AccessibleTopology), 668 }, 669 Status: &ListVolumesResponse_VolumeStatus{ 670 PublishedNodeIds: status.GetPublishedNodeIds(), 671 VolumeCondition: &VolumeCondition{ 672 Abnormal: status.GetVolumeCondition().GetAbnormal(), 673 Message: status.GetVolumeCondition().GetMessage(), 674 }, 675 }, 676 }) 677 } 678 } 679 return &ControllerListVolumesResponse{ 680 Entries: entries, 681 NextToken: resp.NextToken, 682 } 683 } 684 685 type ListVolumesResponse_Entry struct { 686 Volume *Volume 687 Status *ListVolumesResponse_VolumeStatus 688 } 689 690 type ListVolumesResponse_VolumeStatus struct { 691 PublishedNodeIds []string 692 VolumeCondition *VolumeCondition 693 } 694 695 type VolumeCondition struct { 696 Abnormal bool 697 Message string 698 } 699 700 type ControllerCreateSnapshotRequest struct { 701 VolumeID string 702 Name string 703 Secrets structs.CSISecrets 704 Parameters map[string]string 705 } 706 707 func (r *ControllerCreateSnapshotRequest) ToCSIRepresentation() *csipbv1.CreateSnapshotRequest { 708 return &csipbv1.CreateSnapshotRequest{ 709 SourceVolumeId: r.VolumeID, 710 Name: r.Name, 711 Secrets: r.Secrets, 712 Parameters: r.Parameters, 713 } 714 } 715 716 func (r *ControllerCreateSnapshotRequest) Validate() error { 717 if r.VolumeID == "" { 718 return errors.New("missing VolumeID") 719 } 720 if r.Name == "" { 721 return errors.New("missing Name") 722 } 723 return nil 724 } 725 726 type ControllerCreateSnapshotResponse struct { 727 Snapshot *Snapshot 728 } 729 730 type Snapshot struct { 731 ID string 732 SourceVolumeID string 733 SizeBytes int64 734 CreateTime int64 735 IsReady bool 736 } 737 738 type ControllerDeleteSnapshotRequest struct { 739 SnapshotID string 740 Secrets structs.CSISecrets 741 } 742 743 func (r *ControllerDeleteSnapshotRequest) ToCSIRepresentation() *csipbv1.DeleteSnapshotRequest { 744 return &csipbv1.DeleteSnapshotRequest{ 745 SnapshotId: r.SnapshotID, 746 Secrets: r.Secrets, 747 } 748 } 749 750 func (r *ControllerDeleteSnapshotRequest) Validate() error { 751 if r.SnapshotID == "" { 752 return errors.New("missing SnapshotID") 753 } 754 return nil 755 } 756 757 type ControllerListSnapshotsRequest struct { 758 MaxEntries int32 759 StartingToken string 760 Secrets structs.CSISecrets 761 } 762 763 func (r *ControllerListSnapshotsRequest) ToCSIRepresentation() *csipbv1.ListSnapshotsRequest { 764 return &csipbv1.ListSnapshotsRequest{ 765 MaxEntries: r.MaxEntries, 766 StartingToken: r.StartingToken, 767 Secrets: r.Secrets, 768 } 769 } 770 771 func (r *ControllerListSnapshotsRequest) Validate() error { 772 if r.MaxEntries < 0 { 773 return errors.New("MaxEntries cannot be negative") 774 } 775 return nil 776 } 777 778 func NewListSnapshotsResponse(resp *csipbv1.ListSnapshotsResponse) *ControllerListSnapshotsResponse { 779 if resp == nil { 780 return &ControllerListSnapshotsResponse{} 781 } 782 entries := []*ListSnapshotsResponse_Entry{} 783 if resp.Entries != nil { 784 for _, entry := range resp.Entries { 785 snap := entry.GetSnapshot() 786 entries = append(entries, &ListSnapshotsResponse_Entry{ 787 Snapshot: &Snapshot{ 788 SizeBytes: snap.GetSizeBytes(), 789 ID: snap.GetSnapshotId(), 790 SourceVolumeID: snap.GetSourceVolumeId(), 791 CreateTime: snap.GetCreationTime().GetSeconds(), 792 IsReady: snap.GetReadyToUse(), 793 }, 794 }) 795 } 796 } 797 return &ControllerListSnapshotsResponse{ 798 Entries: entries, 799 NextToken: resp.NextToken, 800 } 801 } 802 803 type ControllerListSnapshotsResponse struct { 804 Entries []*ListSnapshotsResponse_Entry 805 NextToken string 806 } 807 808 type ListSnapshotsResponse_Entry struct { 809 Snapshot *Snapshot 810 } 811 812 type NodeCapabilitySet struct { 813 HasStageUnstageVolume bool 814 HasGetVolumeStats bool 815 HasExpandVolume bool 816 HasVolumeCondition bool 817 } 818 819 func NewNodeCapabilitySet(resp *csipbv1.NodeGetCapabilitiesResponse) *NodeCapabilitySet { 820 cs := &NodeCapabilitySet{} 821 pluginCapabilities := resp.GetCapabilities() 822 for _, pcap := range pluginCapabilities { 823 if c := pcap.GetRpc(); c != nil { 824 switch c.Type { 825 case csipbv1.NodeServiceCapability_RPC_STAGE_UNSTAGE_VOLUME: 826 cs.HasStageUnstageVolume = true 827 case csipbv1.NodeServiceCapability_RPC_GET_VOLUME_STATS: 828 cs.HasGetVolumeStats = true 829 case csipbv1.NodeServiceCapability_RPC_EXPAND_VOLUME: 830 cs.HasExpandVolume = true 831 case csipbv1.NodeServiceCapability_RPC_VOLUME_CONDITION: 832 cs.HasVolumeCondition = true 833 default: 834 continue 835 } 836 } 837 } 838 839 return cs 840 } 841 842 // VolumeAccessMode represents the desired access mode of the CSI Volume 843 type VolumeAccessMode csipbv1.VolumeCapability_AccessMode_Mode 844 845 var _ fmt.Stringer = VolumeAccessModeUnknown 846 847 var ( 848 VolumeAccessModeUnknown = VolumeAccessMode(csipbv1.VolumeCapability_AccessMode_UNKNOWN) 849 VolumeAccessModeSingleNodeWriter = VolumeAccessMode(csipbv1.VolumeCapability_AccessMode_SINGLE_NODE_WRITER) 850 VolumeAccessModeSingleNodeReaderOnly = VolumeAccessMode(csipbv1.VolumeCapability_AccessMode_SINGLE_NODE_READER_ONLY) 851 VolumeAccessModeMultiNodeReaderOnly = VolumeAccessMode(csipbv1.VolumeCapability_AccessMode_MULTI_NODE_READER_ONLY) 852 VolumeAccessModeMultiNodeSingleWriter = VolumeAccessMode(csipbv1.VolumeCapability_AccessMode_MULTI_NODE_SINGLE_WRITER) 853 VolumeAccessModeMultiNodeMultiWriter = VolumeAccessMode(csipbv1.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER) 854 ) 855 856 func (a VolumeAccessMode) String() string { 857 return a.ToCSIRepresentation().String() 858 } 859 860 func (a VolumeAccessMode) ToCSIRepresentation() csipbv1.VolumeCapability_AccessMode_Mode { 861 return csipbv1.VolumeCapability_AccessMode_Mode(a) 862 } 863 864 // VolumeAccessType represents the filesystem apis that the user intends to use 865 // with the volume. E.g whether it will be used as a block device or if they wish 866 // to have a mounted filesystem. 867 type VolumeAccessType int32 868 869 var _ fmt.Stringer = VolumeAccessTypeBlock 870 871 var ( 872 VolumeAccessTypeBlock VolumeAccessType = 1 873 VolumeAccessTypeMount VolumeAccessType = 2 874 ) 875 876 func (v VolumeAccessType) String() string { 877 if v == VolumeAccessTypeBlock { 878 return "VolumeAccessType.Block" 879 } else if v == VolumeAccessTypeMount { 880 return "VolumeAccessType.Mount" 881 } else { 882 return "VolumeAccessType.Unspecified" 883 } 884 } 885 886 // VolumeCapability describes the overall usage requirements for a given CSI Volume 887 type VolumeCapability struct { 888 AccessType VolumeAccessType 889 AccessMode VolumeAccessMode 890 891 // Indicate that the volume will be accessed via the filesystem API. 892 MountVolume *structs.CSIMountOptions 893 } 894 895 func VolumeCapabilityFromStructs(sAccessType structs.CSIVolumeAttachmentMode, sAccessMode structs.CSIVolumeAccessMode, sMountOptions *structs.CSIMountOptions) (*VolumeCapability, error) { 896 var accessType VolumeAccessType 897 switch sAccessType { 898 case structs.CSIVolumeAttachmentModeBlockDevice: 899 accessType = VolumeAccessTypeBlock 900 case structs.CSIVolumeAttachmentModeFilesystem: 901 accessType = VolumeAccessTypeMount 902 default: 903 // These fields are validated during job submission, but here we perform a 904 // final check during transformation into the requisite CSI Data type to 905 // defend against development bugs and corrupted state - and incompatible 906 // nomad versions in the future. 907 return nil, fmt.Errorf("unknown volume attachment mode: %s", sAccessType) 908 } 909 910 var accessMode VolumeAccessMode 911 switch sAccessMode { 912 case structs.CSIVolumeAccessModeSingleNodeReader: 913 accessMode = VolumeAccessModeSingleNodeReaderOnly 914 case structs.CSIVolumeAccessModeSingleNodeWriter: 915 accessMode = VolumeAccessModeSingleNodeWriter 916 case structs.CSIVolumeAccessModeMultiNodeMultiWriter: 917 accessMode = VolumeAccessModeMultiNodeMultiWriter 918 case structs.CSIVolumeAccessModeMultiNodeSingleWriter: 919 accessMode = VolumeAccessModeMultiNodeSingleWriter 920 case structs.CSIVolumeAccessModeMultiNodeReader: 921 accessMode = VolumeAccessModeMultiNodeReaderOnly 922 default: 923 // These fields are validated during job submission, but here we perform a 924 // final check during transformation into the requisite CSI Data type to 925 // defend against development bugs and corrupted state - and incompatible 926 // nomad versions in the future. 927 return nil, fmt.Errorf("unknown volume access mode: %v", sAccessMode) 928 } 929 930 return &VolumeCapability{ 931 AccessType: accessType, 932 AccessMode: accessMode, 933 MountVolume: sMountOptions, 934 }, nil 935 } 936 937 func (c *VolumeCapability) ToCSIRepresentation() *csipbv1.VolumeCapability { 938 if c == nil { 939 return nil 940 } 941 942 vc := &csipbv1.VolumeCapability{ 943 AccessMode: &csipbv1.VolumeCapability_AccessMode{ 944 Mode: c.AccessMode.ToCSIRepresentation(), 945 }, 946 } 947 948 if c.AccessType == VolumeAccessTypeMount { 949 opts := &csipbv1.VolumeCapability_MountVolume{} 950 if c.MountVolume != nil { 951 opts.FsType = c.MountVolume.FSType 952 opts.MountFlags = c.MountVolume.MountFlags 953 } 954 vc.AccessType = &csipbv1.VolumeCapability_Mount{Mount: opts} 955 } else { 956 vc.AccessType = &csipbv1.VolumeCapability_Block{Block: &csipbv1.VolumeCapability_BlockVolume{}} 957 } 958 959 return vc 960 } 961 962 type CapacityRange struct { 963 RequiredBytes int64 964 LimitBytes int64 965 } 966 967 func (c *CapacityRange) ToCSIRepresentation() *csipbv1.CapacityRange { 968 if c == nil { 969 return nil 970 } 971 return &csipbv1.CapacityRange{ 972 RequiredBytes: c.RequiredBytes, 973 LimitBytes: c.LimitBytes, 974 } 975 }