0856483115
In order to support new node RPCs, we need to fingerprint plugin capabilities in more detail. This changeset mirrors recent work to fingerprint controller capabilities, but is not yet in use by any Nomad RPC.
187 lines
5.4 KiB
Go
187 lines
5.4 KiB
Go
package csimanager
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"github.com/hashicorp/go-hclog"
|
|
"github.com/hashicorp/nomad/client/dynamicplugins"
|
|
"github.com/hashicorp/nomad/helper"
|
|
"github.com/hashicorp/nomad/nomad/structs"
|
|
"github.com/hashicorp/nomad/plugins/csi"
|
|
)
|
|
|
|
type pluginFingerprinter struct {
|
|
logger hclog.Logger
|
|
client csi.CSIPlugin
|
|
info *dynamicplugins.PluginInfo
|
|
|
|
// basicInfo holds a cache of data that should not change within a CSI plugin.
|
|
// This allows us to minimize the number of requests we make to plugins on each
|
|
// run of the fingerprinter, and reduces the chances of performing overly
|
|
// expensive actions repeatedly, and improves stability of data through
|
|
// transient failures.
|
|
basicInfo *structs.CSIInfo
|
|
|
|
fingerprintNode bool
|
|
fingerprintController bool
|
|
|
|
hadFirstSuccessfulFingerprint bool
|
|
// hadFirstSuccessfulFingerprintCh is closed the first time a fingerprint
|
|
// is completed successfully.
|
|
hadFirstSuccessfulFingerprintCh chan struct{}
|
|
|
|
// requiresStaging is set on a first successful fingerprint. It allows the
|
|
// csimanager to efficiently query this as it shouldn't change after a plugin
|
|
// is started. Removing this bool will require storing a cache of recent successful
|
|
// results that can be used by subscribers of the `hadFirstSuccessfulFingerprintCh`.
|
|
requiresStaging bool
|
|
}
|
|
|
|
func (p *pluginFingerprinter) fingerprint(ctx context.Context) *structs.CSIInfo {
|
|
if p.basicInfo == nil {
|
|
info, err := p.buildBasicFingerprint(ctx)
|
|
if err != nil {
|
|
// If we receive a fingerprinting error, update the stats with as much
|
|
// info as possible and wait for the next fingerprint interval.
|
|
info.HealthDescription = fmt.Sprintf("failed initial fingerprint with err: %v", err)
|
|
info.Healthy = false
|
|
|
|
return info
|
|
}
|
|
|
|
// If fingerprinting succeeded, we don't need to repopulate the basic
|
|
// info again.
|
|
p.basicInfo = info
|
|
}
|
|
|
|
info := p.basicInfo.Copy()
|
|
var fp *structs.CSIInfo
|
|
var err error
|
|
|
|
if p.fingerprintNode {
|
|
fp, err = p.buildNodeFingerprint(ctx, info)
|
|
} else if p.fingerprintController {
|
|
fp, err = p.buildControllerFingerprint(ctx, info)
|
|
}
|
|
|
|
if err != nil {
|
|
info.Healthy = false
|
|
info.HealthDescription = fmt.Sprintf("failed fingerprinting with error: %v", err)
|
|
} else {
|
|
info = fp
|
|
if !p.hadFirstSuccessfulFingerprint {
|
|
p.hadFirstSuccessfulFingerprint = true
|
|
if p.fingerprintNode {
|
|
p.requiresStaging = info.NodeInfo.RequiresNodeStageVolume
|
|
}
|
|
close(p.hadFirstSuccessfulFingerprintCh)
|
|
}
|
|
}
|
|
|
|
return info
|
|
}
|
|
|
|
func (p *pluginFingerprinter) buildBasicFingerprint(ctx context.Context) (*structs.CSIInfo, error) {
|
|
info := &structs.CSIInfo{
|
|
PluginID: p.info.Name,
|
|
AllocID: p.info.AllocID,
|
|
Provider: p.info.Options["Provider"],
|
|
ProviderVersion: p.info.Version,
|
|
Healthy: false,
|
|
HealthDescription: "initial fingerprint not completed",
|
|
}
|
|
|
|
if p.fingerprintNode {
|
|
info.NodeInfo = &structs.CSINodeInfo{}
|
|
}
|
|
if p.fingerprintController {
|
|
info.ControllerInfo = &structs.CSIControllerInfo{}
|
|
}
|
|
|
|
capabilities, err := p.client.PluginGetCapabilities(ctx)
|
|
if err != nil {
|
|
return info, err
|
|
}
|
|
|
|
info.RequiresControllerPlugin = capabilities.HasControllerService()
|
|
info.RequiresTopologies = capabilities.HasToplogies()
|
|
|
|
if p.fingerprintNode {
|
|
nodeInfo, err := p.client.NodeGetInfo(ctx)
|
|
if err != nil {
|
|
return info, err
|
|
}
|
|
|
|
info.NodeInfo.ID = nodeInfo.NodeID
|
|
info.NodeInfo.MaxVolumes = nodeInfo.MaxVolumes
|
|
info.NodeInfo.AccessibleTopology = structCSITopologyFromCSITopology(nodeInfo.AccessibleTopology)
|
|
}
|
|
|
|
return info, nil
|
|
}
|
|
|
|
func applyCapabilitySetToControllerInfo(cs *csi.ControllerCapabilitySet, info *structs.CSIControllerInfo) {
|
|
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) {
|
|
fp := base.Copy()
|
|
|
|
healthy, err := p.client.PluginProbe(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
fp.SetHealthy(healthy)
|
|
|
|
caps, err := p.client.ControllerGetCapabilities(ctx)
|
|
if err != nil {
|
|
return fp, err
|
|
}
|
|
applyCapabilitySetToControllerInfo(caps, fp.ControllerInfo)
|
|
|
|
return fp, nil
|
|
}
|
|
|
|
func (p *pluginFingerprinter) buildNodeFingerprint(ctx context.Context, base *structs.CSIInfo) (*structs.CSIInfo, error) {
|
|
fp := base.Copy()
|
|
|
|
healthy, err := p.client.PluginProbe(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
fp.SetHealthy(healthy)
|
|
|
|
caps, err := p.client.NodeGetCapabilities(ctx)
|
|
if err != nil {
|
|
return fp, err
|
|
}
|
|
fp.NodeInfo.RequiresNodeStageVolume = caps.HasStageUnstageVolume
|
|
fp.NodeInfo.SupportsStats = caps.HasGetVolumeStats
|
|
fp.NodeInfo.SupportsExpand = caps.HasExpandVolume
|
|
fp.NodeInfo.SupportsCondition = caps.HasVolumeCondition
|
|
|
|
return fp, nil
|
|
}
|
|
|
|
func structCSITopologyFromCSITopology(a *csi.Topology) *structs.CSITopology {
|
|
if a == nil {
|
|
return nil
|
|
}
|
|
|
|
return &structs.CSITopology{
|
|
Segments: helper.CopyMapStringString(a.Segments),
|
|
}
|
|
}
|