-
Notifications
You must be signed in to change notification settings - Fork 1.9k
/
fingerprint.go
186 lines (154 loc) · 5.35 KB
/
fingerprint.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
package csimanager
import (
"context"
"fmt"
"github.com/hashicorp/go-hclog"
"github.com/hashicorp/nomad/client/dynamicplugins"
"github.com/hashicorp/nomad/nomad/structs"
"github.com/hashicorp/nomad/plugins/csi"
"golang.org/x/exp/maps"
)
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: maps.Clone(a.Segments),
}
}