Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CSI: create volume capability validation #10224

Merged
merged 7 commits into from
Mar 30, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion client/pluginmanager/csimanager/fingerprint.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,10 +122,18 @@ func (p *pluginFingerprinter) buildBasicFingerprint(ctx context.Context) (*struc
}

func applyCapabilitySetToControllerInfo(cs *csi.ControllerCapabilitySet, info *structs.CSIControllerInfo) {
info.SupportsReadOnlyAttach = cs.HasPublishReadonly
info.SupportsCreateDelete = cs.HasCreateDeleteVolume
info.SupportsAttachDetach = cs.HasPublishUnpublishVolume
info.SupportsListVolumes = cs.HasListVolumes
info.SupportsGetCapacity = cs.HasGetCapacity
info.SupportsCreateDeleteSnapshot = cs.HasCreateDeleteSnapshot
info.SupportsListSnapshots = cs.HasListSnapshots
info.SupportsClone = cs.HasCloneVolume
info.SupportsReadOnlyAttach = cs.HasPublishReadonly
info.SupportsExpand = cs.HasExpandVolume
info.SupportsListVolumesAttachedNodes = cs.HasListVolumesPublishedNodes
info.SupportsCondition = cs.HasVolumeCondition
info.SupportsGet = cs.HasGetVolume
}

func (p *pluginFingerprinter) buildControllerFingerprint(ctx context.Context, base *structs.CSIInfo) (*structs.CSIInfo, error) {
Expand Down
25 changes: 17 additions & 8 deletions nomad/csi_endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -441,9 +441,9 @@ func (v *CSIVolume) controllerPublishVolume(req *structs.CSIVolumeClaimRequest,
return fmt.Errorf("%s: %s", structs.ErrUnknownAllocationPrefix, req.AllocationID)
}

// if no plugin was returned then controller validation is not required.
// Here we can return nil.
if plug == nil {
// if no plugin was returned or the plugin doesn't attach volumes, then
// controller validation is not required and we can safely return nil.
tgross marked this conversation as resolved.
Show resolved Hide resolved
if plug == nil || !plug.HasControllerCapability(structs.CSIControllerSupportsAttachDetach) {
return nil
}
tgross marked this conversation as resolved.
Show resolved Hide resolved

Expand Down Expand Up @@ -679,11 +679,21 @@ func (v *CSIVolume) controllerUnpublishVolume(vol *structs.CSIVolume, claim *str
return nil
}

state := v.srv.fsm.State()
ws := memdb.NewWatchSet()

plugin, err := state.CSIPluginByID(ws, vol.PluginID)
if err != nil || plugin == nil {
tgross marked this conversation as resolved.
Show resolved Hide resolved
return fmt.Errorf("could not query plugin: %v", err)
tgross marked this conversation as resolved.
Show resolved Hide resolved
}
if !plugin.HasControllerCapability(structs.CSIControllerSupportsAttachDetach) {
return nil
}

// we only send a controller detach if a Nomad client no longer has
// any claim to the volume, so we need to check the status of claimed
// allocations
state := v.srv.fsm.State()
vol, err := state.CSIVolumeDenormalize(memdb.NewWatchSet(), vol)
vol, err = state.CSIVolumeDenormalize(ws, vol)
if err != nil {
return err
}
Expand Down Expand Up @@ -834,9 +844,8 @@ func (v *CSIVolume) Create(args *structs.CSIVolumeCreateRequest, reply *structs.
if !plugin.ControllerRequired {
return fmt.Errorf("plugin has no controller")
}

if err := v.controllerValidateVolume(regArgs, vol, plugin); err != nil {
return err
if !plugin.HasControllerCapability(structs.CSIControllerSupportsCreateDelete) {
return fmt.Errorf("plugin does not support creating volumes")
}

validatedVols = append(validatedVols, validated{vol, plugin})
Expand Down
1 change: 1 addition & 0 deletions nomad/csi_endpoint_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -708,6 +708,7 @@ func TestCSIVolumeEndpoint_Create(t *testing.T) {
Healthy: true,
ControllerInfo: &structs.CSIControllerInfo{
SupportsAttachDetach: true,
SupportsCreateDelete: true,
},
RequiresControllerPlugin: true,
},
Expand Down
61 changes: 57 additions & 4 deletions nomad/structs/csi.go
Original file line number Diff line number Diff line change
Expand Up @@ -846,14 +846,67 @@ func (p *CSIPlugin) Copy() *CSIPlugin {
return out
}

type CSIControllerCapability int

const (
CSIControllerSupportsCreateDelete CSIControllerCapability = iota
tgross marked this conversation as resolved.
Show resolved Hide resolved
CSIControllerSupportsAttachDetach
CSIControllerSupportsListVolumes
CSIControllerSupportsGetCapacity
CSIControllerSupportsCreateDeleteSnapshot
CSIControllerSupportsListSnapshots
CSIControllerSupportsClone
CSIControllerSupportsReadOnlyAttach
CSIControllerSupportsExpand
CSIControllerSupportsListVolumesAttachedNodes
CSIControllerSupportsCondition
CSIControllerSupportsGet
tgross marked this conversation as resolved.
Show resolved Hide resolved
)

func (p *CSIPlugin) HasControllerCapability(cap CSIControllerCapability) bool {
if len(p.Controllers) < 1 {
return false
}
// we're picking the first controller because they should be uniform
// across the same version of the plugin
for _, c := range p.Controllers {
switch cap {
case CSIControllerSupportsCreateDelete:
return c.ControllerInfo.SupportsCreateDelete
case CSIControllerSupportsAttachDetach:
return c.ControllerInfo.SupportsAttachDetach
case CSIControllerSupportsListVolumes:
return c.ControllerInfo.SupportsListVolumes
case CSIControllerSupportsGetCapacity:
return c.ControllerInfo.SupportsGetCapacity
case CSIControllerSupportsCreateDeleteSnapshot:
return c.ControllerInfo.SupportsCreateDeleteSnapshot
case CSIControllerSupportsListSnapshots:
return c.ControllerInfo.SupportsListSnapshots
case CSIControllerSupportsClone:
return c.ControllerInfo.SupportsClone
case CSIControllerSupportsReadOnlyAttach:
return c.ControllerInfo.SupportsReadOnlyAttach
case CSIControllerSupportsExpand:
return c.ControllerInfo.SupportsExpand
case CSIControllerSupportsListVolumesAttachedNodes:
return c.ControllerInfo.SupportsListVolumesAttachedNodes
case CSIControllerSupportsCondition:
return c.ControllerInfo.SupportsCondition
case CSIControllerSupportsGet:
return c.ControllerInfo.SupportsGet
default:
return false
}
}
return false
}

// AddPlugin adds a single plugin running on the node. Called from state.NodeUpdate in a
// transaction
func (p *CSIPlugin) AddPlugin(nodeID string, info *CSIInfo) error {
if info.ControllerInfo != nil {
p.ControllerRequired = info.RequiresControllerPlugin &&
(info.ControllerInfo.SupportsAttachDetach ||
info.ControllerInfo.SupportsReadOnlyAttach)

p.ControllerRequired = info.RequiresControllerPlugin
prev, ok := p.Controllers[nodeID]
if ok {
if prev == nil {
Expand Down
49 changes: 38 additions & 11 deletions nomad/structs/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,23 +112,50 @@ func (n *CSINodeInfo) Copy() *CSINodeInfo {
// CSIControllerInfo is the fingerprinted data from a CSI Plugin that is specific to
// the Controller API.
type CSIControllerInfo struct {
// SupportsReadOnlyAttach is set to true when the controller returns the
// ATTACH_READONLY capability.
SupportsReadOnlyAttach bool

// SupportsPublishVolume is true when the controller implements the methods
// required to attach and detach volumes. If this is false Nomad should skip
// the controller attachment flow.
// SupportsCreateDelete indicates plugin support for CREATE_DELETE_VOLUME
SupportsCreateDelete bool

// SupportsPublishVolume is true when the controller implements the
// methods required to attach and detach volumes. If this is false Nomad
// should skip the controller attachment flow.
SupportsAttachDetach bool

// SupportsListVolumes is true when the controller implements the ListVolumes
// RPC. NOTE: This does not guaruntee that attached nodes will be returned
// unless SupportsListVolumesAttachedNodes is also true.
// SupportsListVolumes is true when the controller implements the
// ListVolumes RPC. NOTE: This does not guarantee that attached nodes will
// be returned unless SupportsListVolumesAttachedNodes is also true.
SupportsListVolumes bool

// SupportsListVolumesAttachedNodes indicates whether the plugin will return
// attached nodes data when making ListVolume RPCs
// SupportsGetCapacity indicates plugin support for GET_CAPACITY
SupportsGetCapacity bool

// SupportsCreateDeleteSnapshot indicates plugin support for
// CREATE_DELETE_SNAPSHOT
SupportsCreateDeleteSnapshot bool

// SupportsListSnapshots indicates plugin support for LIST_SNAPSHOTS
SupportsListSnapshots bool

// SupportsClone indicates plugin support for CLONE_VOLUME
SupportsClone bool

// SupportsReadOnlyAttach is set to true when the controller returns the
// ATTACH_READONLY capability.
SupportsReadOnlyAttach bool

// SupportsExpand indicates plugin support for EXPAND_VOLUME
SupportsExpand bool

// SupportsListVolumesAttachedNodes indicates whether the plugin will
// return attached nodes data when making ListVolume RPCs (plugin support
// for LIST_VOLUMES_PUBLISHED_NODES)
SupportsListVolumesAttachedNodes bool

// SupportsCondition indicates plugin support for VOLUME_CONDITION
SupportsCondition bool

// SupportsGet indicates plugin support for GET_VOLUME
SupportsGet bool
}

func (c *CSIControllerInfo) Copy() *CSIControllerInfo {
Expand Down
2 changes: 1 addition & 1 deletion plugins/csi/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ func TestClient_RPC_ControllerGetCapabilities(t *testing.T) {
{
Type: &csipbv1.ControllerServiceCapability_Rpc{
Rpc: &csipbv1.ControllerServiceCapability_RPC{
Type: csipbv1.ControllerServiceCapability_RPC_GET_CAPACITY,
Type: csipbv1.ControllerServiceCapability_RPC_UNKNOWN,
},
},
},
Expand Down
30 changes: 27 additions & 3 deletions plugins/csi/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -280,10 +280,18 @@ func NewPluginCapabilitySet(capabilities *csipbv1.GetPluginCapabilitiesResponse)
}

type ControllerCapabilitySet struct {
HasCreateDeleteVolume bool
HasPublishUnpublishVolume bool
HasPublishReadonly bool
HasListVolumes bool
HasGetCapacity bool
HasCreateDeleteSnapshot bool
HasListSnapshots bool
HasCloneVolume bool
HasPublishReadonly bool
HasExpandVolume bool
HasListVolumesPublishedNodes bool
HasVolumeCondition bool
HasGetVolume bool
}

func NewControllerCapabilitySet(resp *csipbv1.ControllerGetCapabilitiesResponse) *ControllerCapabilitySet {
Expand All @@ -293,14 +301,30 @@ func NewControllerCapabilitySet(resp *csipbv1.ControllerGetCapabilitiesResponse)
for _, pcap := range pluginCapabilities {
if c := pcap.GetRpc(); c != nil {
switch c.Type {
case csipbv1.ControllerServiceCapability_RPC_CREATE_DELETE_VOLUME:
cs.HasCreateDeleteVolume = true
case csipbv1.ControllerServiceCapability_RPC_PUBLISH_UNPUBLISH_VOLUME:
cs.HasPublishUnpublishVolume = true
case csipbv1.ControllerServiceCapability_RPC_PUBLISH_READONLY:
cs.HasPublishReadonly = true
case csipbv1.ControllerServiceCapability_RPC_LIST_VOLUMES:
cs.HasListVolumes = true
case csipbv1.ControllerServiceCapability_RPC_GET_CAPACITY:
cs.HasGetCapacity = true
case csipbv1.ControllerServiceCapability_RPC_CREATE_DELETE_SNAPSHOT:
cs.HasCreateDeleteSnapshot = true
case csipbv1.ControllerServiceCapability_RPC_LIST_SNAPSHOTS:
cs.HasListSnapshots = true
case csipbv1.ControllerServiceCapability_RPC_CLONE_VOLUME:
cs.HasCloneVolume = true
case csipbv1.ControllerServiceCapability_RPC_PUBLISH_READONLY:
cs.HasPublishReadonly = true
case csipbv1.ControllerServiceCapability_RPC_EXPAND_VOLUME:
cs.HasExpandVolume = true
case csipbv1.ControllerServiceCapability_RPC_LIST_VOLUMES_PUBLISHED_NODES:
cs.HasListVolumesPublishedNodes = true
case csipbv1.ControllerServiceCapability_RPC_VOLUME_CONDITION:
cs.HasVolumeCondition = true
case csipbv1.ControllerServiceCapability_RPC_GET_VOLUME:
cs.HasGetVolume = true
default:
continue
}
Expand Down