master
go 323 lines 8.48 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package match
4
5 import (
6 "fmt"
7 "strings"
8
9 "github.com/netdata/netdata/go/plugins/pkg/matcher"
10 rs "github.com/netdata/netdata/go/plugins/plugin/go.d/collector/vsphere/resources"
11 )
12
13 type HostMatcher interface {
14 Match(*rs.Host) bool
15 }
16
17 type VMMatcher interface {
18 Match(*rs.VM) bool
19 }
20
21 type DatastoreMatcher interface {
22 Match(*rs.Datastore) bool
23 }
24
25 type ClusterMatcher interface {
26 Match(*rs.Cluster) bool
27 }
28
29 type DatastoreClusterMatcher interface {
30 Match(*rs.StoragePod) bool
31 }
32
33 type VSANClusterMatcher interface {
34 Match(*rs.Cluster) bool
35 }
36
37 type VSANHostMatcher interface {
38 Match(*rs.Host) bool
39 }
40
41 type VSANVMMatcher interface {
42 Match(*rs.VM) bool
43 }
44
45 type (
46 resourceMatcher[T any] interface {
47 Match(*T) bool
48 }
49 fieldMatcher[T any] struct {
50 m matcher.Matcher
51 get func(*T) string
52 }
53 orMatcher[T any] struct{ lhs, rhs resourceMatcher[T] }
54 andMatcher[T any] struct{ lhs, rhs resourceMatcher[T] }
55 )
56
57 func (m fieldMatcher[T]) Match(v *T) bool {
58 return m.m.MatchString(m.get(v))
59 }
60
61 func (m orMatcher[T]) Match(v *T) bool {
62 return m.lhs.Match(v) || m.rhs.Match(v)
63 }
64
65 func (m andMatcher[T]) Match(v *T) bool {
66 return m.lhs.Match(v) && m.rhs.Match(v)
67 }
68
69 func chainAnd[T any](ms []resourceMatcher[T]) resourceMatcher[T] {
70 switch len(ms) {
71 case 0:
72 return nil
73 case 1:
74 return ms[0]
75 }
76 m := andMatcher[T]{lhs: ms[0], rhs: ms[1]}
77 for _, next := range ms[2:] {
78 m = andMatcher[T]{lhs: m, rhs: next}
79 }
80 return m
81 }
82
83 func chainOr[T any](ms []resourceMatcher[T]) resourceMatcher[T] {
84 switch len(ms) {
85 case 0:
86 return nil
87 case 1:
88 return ms[0]
89 }
90 m := orMatcher[T]{lhs: ms[0], rhs: ms[1]}
91 for _, next := range ms[2:] {
92 m = orMatcher[T]{lhs: m, rhs: next}
93 }
94 return m
95 }
96
97 type (
98 VMIncludes []string
99 HostIncludes []string
100 DatastoreIncludes []string
101 ClusterIncludes []string
102
103 DatastoreClusterIncludes []string
104 VSANClusterIncludes []string
105 VSANHostIncludes []string
106 VSANVMIncludes []string
107 )
108
109 type (
110 datastoreClusterMatcher struct{ m matcher.Matcher }
111 vsanClusterMatcher struct{ m matcher.Matcher }
112 vsanHostMatcher struct{ m matcher.Matcher }
113 vsanVMMatcher struct{ m matcher.Matcher }
114 )
115
116 // NewPatternListMatcher parses ordered glob patterns with optional !-prefixed exclusions.
117 func NewPatternListMatcher(name string, patterns []string) (matcher.Matcher, error) {
118 m, err := matcher.NewSimplePatternListMatcher(patterns)
119 if err != nil {
120 return nil, patternListError(name, err)
121 }
122 return m, nil
123 }
124
125 func patternListError(name string, err error) error {
126 if err.Error() == "invalid empty negative pattern" || strings.HasPrefix(err.Error(), "invalid pattern") {
127 return fmt.Errorf("%s has %w", name, err)
128 }
129 return fmt.Errorf("%s %w", name, err)
130 }
131
132 func (m datastoreClusterMatcher) Match(pod *rs.StoragePod) bool {
133 return m.m.MatchString(datastoreClusterPath(pod)) ||
134 m.m.MatchString(pod.Name) ||
135 m.m.MatchString(pod.ID)
136 }
137
138 func (m vsanClusterMatcher) Match(cluster *rs.Cluster) bool {
139 return m.m.MatchString(vsanClusterPath(cluster)) ||
140 m.m.MatchString(cluster.Name) ||
141 m.m.MatchString(cluster.ID) ||
142 m.m.MatchString("vsan_uuid:"+cluster.VSANUUID)
143 }
144
145 func (m vsanHostMatcher) Match(host *rs.Host) bool {
146 return m.m.MatchString(vsanHostPath(host)) ||
147 m.m.MatchString(host.Name) ||
148 m.m.MatchString(host.ID) ||
149 m.m.MatchString("vsan_node_uuid:"+host.VSANNodeUUID)
150 }
151
152 func (m vsanVMMatcher) Match(vm *rs.VM) bool {
153 return m.m.MatchString(vsanVMPath(vm)) ||
154 m.m.MatchString(vm.Name) ||
155 m.m.MatchString(vm.ID) ||
156 m.m.MatchString("instance_uuid:"+vm.InstanceUUID)
157 }
158
159 func datastoreClusterPath(pod *rs.StoragePod) string {
160 if pod.Hier.DC.Name == "" {
161 return "/" + pod.Name
162 }
163 return "/" + pod.Hier.DC.Name + "/" + pod.Name
164 }
165
166 func vsanClusterPath(cluster *rs.Cluster) string {
167 if cluster.Hier.DC.Name == "" {
168 return "/" + cluster.Name
169 }
170 return "/" + cluster.Hier.DC.Name + "/" + cluster.Name
171 }
172
173 func vsanHostPath(host *rs.Host) string {
174 return "/" + host.Hier.DC.Name + "/" + host.Hier.Cluster.Name + "/" + host.Name
175 }
176
177 func vsanVMPath(vm *rs.VM) string {
178 return "/" + vm.Hier.DC.Name + "/" + vm.Hier.Cluster.Name + "/" + vm.Hier.Host.Name + "/" + vm.Name
179 }
180
181 func (dci DatastoreClusterIncludes) Parse() (DatastoreClusterMatcher, error) {
182 m, err := NewPatternListMatcher("datastore_cluster_include", []string(dci))
183 if err != nil {
184 return nil, err
185 }
186 return datastoreClusterMatcher{m}, nil
187 }
188
189 func (vci VSANClusterIncludes) Parse() (VSANClusterMatcher, error) {
190 m, err := NewPatternListMatcher("vsan_cluster_include", []string(vci))
191 if err != nil {
192 return nil, err
193 }
194 return vsanClusterMatcher{m}, nil
195 }
196
197 func (vhi VSANHostIncludes) Parse() (VSANHostMatcher, error) {
198 m, err := NewPatternListMatcher("vsan_host_include", []string(vhi))
199 if err != nil {
200 return nil, err
201 }
202 return vsanHostMatcher{m}, nil
203 }
204
205 func (vvi VSANVMIncludes) Parse() (VSANVMMatcher, error) {
206 m, err := NewPatternListMatcher("vsan_vm_include", []string(vvi))
207 if err != nil {
208 return nil, err
209 }
210 return vsanVMMatcher{m}, nil
211 }
212
213 func (vi VMIncludes) Parse() (VMMatcher, error) {
214 return parseIncludes[rs.VM]("VM include", []string(vi), parseVMInclude)
215 }
216
217 func (hi HostIncludes) Parse() (HostMatcher, error) {
218 return parseIncludes[rs.Host]("host include", []string(hi), parseHostInclude)
219 }
220
221 func (di DatastoreIncludes) Parse() (DatastoreMatcher, error) {
222 return parseIncludes[rs.Datastore]("datastore include", []string(di), parseDatastoreInclude)
223 }
224
225 func cleanInclude(include string) string {
226 return strings.Trim(include, "/")
227 }
228
229 func parseIncludes[T any](name string, includes []string, parse func(string) (resourceMatcher[T], error)) (resourceMatcher[T], error) {
230 var ms []resourceMatcher[T]
231 for _, v := range includes {
232 m, err := parse(v)
233 if err != nil {
234 return nil, fmt.Errorf("parse %s %q: %w", name, v, err)
235 }
236 if m == nil {
237 continue
238 }
239 ms = append(ms, m)
240 }
241 return chainOr(ms), nil
242 }
243
244 func parsePathInclude[T any](include, name, expected string, segments []func(*T) string) (resourceMatcher[T], error) {
245 if !isIncludeFormatValid(include) {
246 return nil, fmt.Errorf("bad %s include format %q: expected %s", name, include, expected)
247 }
248
249 include = cleanInclude(include)
250 parts := strings.Split(include, "/")
251 ms := make([]resourceMatcher[T], 0, min(len(parts), len(segments)))
252
253 for i, v := range parts {
254 m, err := parseSubInclude(v)
255 if err != nil {
256 return nil, fmt.Errorf("parse %s include segment index=%d value=%q: %w", name, i, v, err)
257 }
258 if i >= len(segments) {
259 continue
260 }
261 ms = append(ms, fieldMatcher[T]{m: m, get: segments[i]})
262 }
263
264 return chainAnd(ms), nil
265 }
266
267 var hostPathSegments = []func(*rs.Host) string{
268 func(host *rs.Host) string { return host.Hier.DC.Name },
269 func(host *rs.Host) string { return host.Hier.Cluster.Name },
270 func(host *rs.Host) string { return host.Name },
271 }
272
273 var vmPathSegments = []func(*rs.VM) string{
274 func(vm *rs.VM) string { return vm.Hier.DC.Name },
275 func(vm *rs.VM) string { return vm.Hier.Cluster.Name },
276 func(vm *rs.VM) string { return vm.Hier.Host.Name },
277 func(vm *rs.VM) string { return vm.Name },
278 }
279
280 var clusterPathSegments = []func(*rs.Cluster) string{
281 func(cluster *rs.Cluster) string { return cluster.Hier.DC.Name },
282 func(cluster *rs.Cluster) string { return cluster.Name },
283 }
284
285 var datastorePathSegments = []func(*rs.Datastore) string{
286 func(ds *rs.Datastore) string { return ds.Hier.DC.Name },
287 func(ds *rs.Datastore) string { return ds.Name },
288 }
289
290 func parseHostInclude(include string) (resourceMatcher[rs.Host], error) {
291 return parsePathInclude(include, "host", "/<datacenter>/<cluster>/<host>", hostPathSegments)
292 }
293
294 func parseVMInclude(include string) (resourceMatcher[rs.VM], error) {
295 return parsePathInclude(include, "VM", "/<datacenter>/<cluster>/<host>/<vm>", vmPathSegments)
296 }
297
298 func parseClusterInclude(include string) (resourceMatcher[rs.Cluster], error) {
299 return parsePathInclude(include, "cluster", "/<datacenter>/<cluster>", clusterPathSegments)
300 }
301
302 func parseDatastoreInclude(include string) (resourceMatcher[rs.Datastore], error) {
303 return parsePathInclude(include, "datastore", "/<datacenter>/<datastore>", datastorePathSegments)
304 }
305
306 func parseSubInclude(sub string) (matcher.Matcher, error) {
307 sub = strings.TrimSpace(sub)
308 if sub == "" || sub == "!*" {
309 return matcher.FALSE(), nil
310 }
311 if sub == "*" {
312 return matcher.TRUE(), nil
313 }
314 return matcher.NewSimplePatternsMatcher(sub)
315 }
316
317 func isIncludeFormatValid(line string) bool {
318 return strings.HasPrefix(line, "/")
319 }
320
321 func (ci ClusterIncludes) Parse() (ClusterMatcher, error) {
322 return parseIncludes[rs.Cluster]("cluster include", []string(ci), parseClusterInclude)
323 }