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  }