master
go 251 lines 6.28 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package discover
4
5 import (
6 "fmt"
7 "sort"
8 "strings"
9 "unicode"
10
11 rs "github.com/netdata/netdata/go/plugins/plugin/go.d/collector/vsphere/resources"
12
13 "github.com/vmware/govmomi/vim25/types"
14 )
15
16 const (
17 tagLabelPrefix = "vsphere_tag_"
18 customAttributeLabelPrefix = "vsphere_custom_attribute_"
19 )
20
21 func (d Discoverer) collectEnrichmentLabels(res *rs.Resources) {
22 if res == nil {
23 return
24 }
25
26 if d.CustomAttributeMatcher != nil {
27 if err := d.collectCustomAttributeLabels(res); err != nil {
28 d.warnLimited(logKeyCustomAttributeDiscoveryError, "discovering : user metadata labels : collect vSphere custom attribute labels: %v", err)
29 }
30 }
31 if d.TagCategoryMatcher != nil {
32 if err := d.collectTagLabels(res); err != nil {
33 d.warnLimited(logKeyTagDiscoveryError, "discovering : user metadata labels : collect vSphere tag labels: %v", err)
34 }
35 }
36 }
37
38 func (d Discoverer) collectCustomAttributeLabels(res *rs.Resources) error {
39 fields, err := d.CustomFields()
40 if err != nil {
41 return fmt.Errorf("collect vSphere custom attribute definitions for user metadata labels: %w", err)
42 }
43
44 namesByKey := make(map[int32]string)
45 for _, field := range fields {
46 if field.Name == "" || !d.CustomAttributeMatcher.Match([]byte(field.Name)) {
47 continue
48 }
49 namesByKey[field.Key] = field.Name
50 }
51 if len(namesByKey) == 0 {
52 return nil
53 }
54
55 for _, vm := range res.VMs {
56 addCustomAttributeLabels(&vm.Labels, vm.CustomValues, namesByKey)
57 }
58 for _, host := range res.Hosts {
59 addCustomAttributeLabels(&host.Labels, host.CustomValues, namesByKey)
60 }
61 for _, ds := range res.Datastores {
62 addCustomAttributeLabels(&ds.Labels, ds.CustomValues, namesByKey)
63 }
64 for _, cluster := range res.Clusters {
65 addCustomAttributeLabels(&cluster.Labels, cluster.CustomValues, namesByKey)
66 }
67 for _, rp := range res.ResourcePools {
68 addCustomAttributeLabels(&rp.Labels, rp.CustomValues, namesByKey)
69 }
70 for _, sp := range res.StoragePods {
71 addCustomAttributeLabels(&sp.Labels, sp.CustomValues, namesByKey)
72 }
73
74 return nil
75 }
76
77 func addCustomAttributeLabels(labels *map[string]string, values map[int32]string, namesByKey map[int32]string) {
78 if len(values) == 0 || len(namesByKey) == 0 {
79 return
80 }
81
82 type item struct {
83 name string
84 value string
85 }
86 var items []item
87 for key, value := range values {
88 name := namesByKey[key]
89 if name == "" || value == "" {
90 continue
91 }
92 items = append(items, item{name: name, value: value})
93 }
94 sort.Slice(items, func(i, j int) bool { return items[i].name < items[j].name })
95
96 for _, item := range items {
97 key := metadataLabelKey(customAttributeLabelPrefix, item.name)
98 addUserMetadataLabel(labels, key, item.value)
99 }
100 }
101
102 func (d Discoverer) collectTagLabels(res *rs.Resources) error {
103 refs := resourceRefs(res)
104 if len(refs) == 0 {
105 return nil
106 }
107
108 tagsByRef, err := d.TagsByRef(refs)
109 if err != nil {
110 return fmt.Errorf("collect vSphere tag attachments for %d inventory refs: %w", len(refs), err)
111 }
112 for ref, tagsByCategory := range tagsByRef {
113 addTagLabels(resourceLabelsByRef(res, ref), tagsByCategory, d.TagCategoryMatcher)
114 }
115
116 return nil
117 }
118
119 func resourceRefs(res *rs.Resources) []types.ManagedObjectReference {
120 refs := make([]types.ManagedObjectReference, 0,
121 len(res.VMs)+len(res.Hosts)+len(res.Datastores)+len(res.Clusters)+len(res.ResourcePools)+len(res.StoragePods))
122 for _, vm := range res.VMs {
123 refs = append(refs, vm.Ref)
124 }
125 for _, host := range res.Hosts {
126 refs = append(refs, host.Ref)
127 }
128 for _, ds := range res.Datastores {
129 refs = append(refs, ds.Ref)
130 }
131 for _, cluster := range res.Clusters {
132 refs = append(refs, cluster.Ref)
133 }
134 for _, rp := range res.ResourcePools {
135 refs = append(refs, rp.Ref)
136 }
137 for _, sp := range res.StoragePods {
138 refs = append(refs, sp.Ref)
139 }
140 return refs
141 }
142
143 func resourceLabelsByRef(res *rs.Resources, ref types.ManagedObjectReference) *map[string]string {
144 if res == nil {
145 return nil
146 }
147 switch ref.Type {
148 case "VirtualMachine":
149 if vm := res.VMs.Get(ref.Value); vm != nil {
150 return &vm.Labels
151 }
152 case "HostSystem":
153 if host := res.Hosts.Get(ref.Value); host != nil {
154 return &host.Labels
155 }
156 case "Datastore":
157 if ds := res.Datastores.Get(ref.Value); ds != nil {
158 return &ds.Labels
159 }
160 case "ResourcePool":
161 if rp := res.ResourcePools.Get(ref.Value); rp != nil {
162 return &rp.Labels
163 }
164 case "StoragePod":
165 if sp := res.StoragePods.Get(ref.Value); sp != nil {
166 return &sp.Labels
167 }
168 }
169 if cluster := res.Clusters.Get(ref.Value); cluster != nil {
170 return &cluster.Labels
171 }
172 return nil
173 }
174
175 func addTagLabels(labels *map[string]string, tagsByCategory map[string][]string, categoryMatcher interface{ Match([]byte) bool }) {
176 if labels == nil || len(tagsByCategory) == 0 || categoryMatcher == nil {
177 return
178 }
179
180 categories := make([]string, 0, len(tagsByCategory))
181 for category := range tagsByCategory {
182 if category != "" && categoryMatcher.Match([]byte(category)) {
183 categories = append(categories, category)
184 }
185 }
186 sort.Strings(categories)
187
188 for _, category := range categories {
189 tags := append([]string(nil), tagsByCategory[category]...)
190 sort.Strings(tags)
191 tags = compactNonEmptyStrings(tags)
192 if len(tags) == 0 {
193 continue
194 }
195 value := strings.Join(tags, "|")
196 key := metadataLabelKey(tagLabelPrefix, category)
197 addUserMetadataLabel(labels, key, value)
198 }
199 }
200
201 func metadataLabelKey(prefix, name string) string {
202 suffix := sanitizeLabelKeyPart(name)
203 if suffix == "" {
204 return ""
205 }
206 return prefix + suffix
207 }
208
209 func addUserMetadataLabel(labels *map[string]string, key, value string) {
210 if key == "" || value == "" {
211 return
212 }
213 if *labels == nil {
214 *labels = make(map[string]string)
215 }
216 (*labels)[key] = value
217 }
218
219 func compactNonEmptyStrings(values []string) []string {
220 out := values[:0]
221 var last string
222 for _, value := range values {
223 value = strings.TrimSpace(value)
224 if value == "" || value == last {
225 continue
226 }
227 out = append(out, value)
228 last = value
229 }
230 return out
231 }
232
233 func sanitizeLabelKeyPart(value string) string {
234 value = strings.TrimSpace(strings.ToLower(value))
235 var b strings.Builder
236 b.Grow(len(value))
237 lastUnderscore := false
238 for _, r := range value {
239 ok := r == '_' || unicode.IsLetter(r) || unicode.IsDigit(r)
240 if ok {
241 b.WriteRune(r)
242 lastUnderscore = false
243 continue
244 }
245 if !lastUnderscore {
246 b.WriteByte('_')
247 lastUnderscore = true
248 }
249 }
250 return strings.Trim(b.String(), "_")
251 }