master
go 323 lines 11.6 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package vsphere
4
5 import (
6 "testing"
7
8 "github.com/stretchr/testify/require"
9 "github.com/vmware/govmomi/performance"
10
11 "github.com/netdata/netdata/go/plugins/pkg/metrix"
12 "github.com/netdata/netdata/go/plugins/plugin/go.d/collector/vsphere/match"
13 rs "github.com/netdata/netdata/go/plugins/plugin/go.d/collector/vsphere/resources"
14 scrapepkg "github.com/netdata/netdata/go/plugins/plugin/go.d/collector/vsphere/scrape"
15 "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/collecttest"
16 )
17
18 func TestCollector_VSANMetricsDefaultOff(t *testing.T) {
19 collr := newVSANTestCollector(false)
20 cycle := mustCycleController(t, collr.MetricStore())
21 cycle.BeginCycle()
22 collr.writeVSANMetrics()
23 require.NoError(t, cycle.CommitCycleSuccess())
24
25 reader := collr.MetricStore().Read(metrix.ReadRaw())
26 require.Zero(t, countMetricSeries(reader, vsanClusterSpaceUsageTotalMetric))
27 require.Zero(t, countMetricSeries(reader, vsanHostOperationsReadMetric))
28 require.Zero(t, countMetricSeries(reader, vsanVMOperationsReadMetric))
29 }
30
31 func TestCollector_VSANMetricsOptInEmitsCharts(t *testing.T) {
32 collr := newVSANTestCollector(true)
33 cycle := mustCycleController(t, collr.MetricStore())
34 cycle.BeginCycle()
35 collr.writeVSANMetrics()
36 require.NoError(t, cycle.CommitCycleSuccess())
37
38 cluster := collr.resources.Clusters.Get("domain-c1")
39 host := collr.resources.Hosts.Get("host-1")
40 vm := collr.resources.VMs.Get("vm-1")
41 reader := collr.MetricStore().Read(metrix.ReadRaw())
42
43 clusterLabels := labelsFromMetrix(collr.vsanClusterLabels(cluster))
44 requireMetricValue(t, reader, vsanClusterSpaceUsageTotalMetric, clusterLabels, 1000)
45 requireMetricValue(t, reader, vsanClusterSpaceUsageFreeMetric, clusterLabels, 400)
46 requireMetricValue(t, reader, vsanClusterSpaceUsageUsedMetric, clusterLabels, 600)
47 requireMetricValue(t, reader, vsanClusterSpaceUtilizationUsedMetric, clusterLabels, 6000)
48 requireMetricValue(t, reader, vsanClusterHealthStatusGreenMetric, clusterLabels, 1)
49 requireMetricValue(t, reader, vsanClusterOperationsReadMetric, clusterLabels, 10)
50 requireMetricValue(t, reader, vsanClusterThroughputReadMetric, clusterLabels, 11)
51 requireMetricValue(t, reader, vsanClusterLatencyReadMetric, clusterLabels, 12)
52 requireMetricValue(t, reader, vsanClusterCongestionsMetric, clusterLabels, 13)
53 requireMetricValue(t, reader, vsanClusterOperationsWriteMetric, clusterLabels, 14)
54 requireMetricValue(t, reader, vsanClusterThroughputWriteMetric, clusterLabels, 15)
55 requireMetricValue(t, reader, vsanClusterLatencyWriteMetric, clusterLabels, 16)
56
57 hostLabels := labelsFromMetrix(collr.vsanHostLabels(host))
58 requireMetricValue(t, reader, vsanHostOperationsReadMetric, hostLabels, 20)
59 requireMetricValue(t, reader, vsanHostThroughputReadMetric, hostLabels, 21)
60 requireMetricValue(t, reader, vsanHostLatencyReadMetric, hostLabels, 22)
61 requireMetricValue(t, reader, vsanHostCongestionsMetric, hostLabels, 23)
62 requireMetricValue(t, reader, vsanHostCacheHitRateMetric, hostLabels, 95)
63 requireMetricValue(t, reader, vsanHostOperationsWriteMetric, hostLabels, 24)
64 requireMetricValue(t, reader, vsanHostThroughputWriteMetric, hostLabels, 25)
65 requireMetricValue(t, reader, vsanHostLatencyWriteMetric, hostLabels, 26)
66
67 vmLabels := labelsFromMetrix(collr.vsanVMLabels(vm))
68 requireMetricValue(t, reader, vsanVMOperationsReadMetric, vmLabels, 30)
69 requireMetricValue(t, reader, vsanVMThroughputReadMetric, vmLabels, 31)
70 requireMetricValue(t, reader, vsanVMLatencyReadMetric, vmLabels, 32)
71 requireMetricValue(t, reader, vsanVMOperationsWriteMetric, vmLabels, 33)
72 requireMetricValue(t, reader, vsanVMThroughputWriteMetric, vmLabels, 34)
73 requireMetricValue(t, reader, vsanVMLatencyWriteMetric, vmLabels, 35)
74
75 createdCharts, createdDims := v2CreatedChartsAndDims(buildV2PlanForTest(t, collr))
76 chartID := findChartIDByLabelsAndContext(t, createdCharts, "vsphere.vsan_cluster_space_usage", map[string]string{"id": cluster.ID})
77 require.Contains(t, createdDims[chartID], "used")
78 hostChartID := findChartIDByLabelsAndContext(t, createdCharts, "vsphere.vsan_host_operations", map[string]string{"id": host.ID})
79 require.Contains(t, createdDims[hostChartID], "read")
80 vmChartID := findChartIDByLabelsAndContext(t, createdCharts, "vsphere.vsan_vm_operations", map[string]string{"id": vm.ID})
81 require.Contains(t, createdDims[vmChartID], "read")
82 collecttest.AssertChartCoverage(t, collr, collecttest.ChartCoverageExpectation{})
83 requireChartSelectorsMatchSeries(t, collr, "vsphere.vsan_")
84 }
85
86 func TestCollector_VSANSpaceUsageEdgeCases(t *testing.T) {
87 tests := map[string]struct {
88 space scrapepkg.VSANSpaceUsage
89 want map[string]int64
90 }{
91 "zero total": {
92 space: scrapepkg.VSANSpaceUsage{Total: 0, Free: 0},
93 want: map[string]int64{
94 vsanClusterSpaceUsageUsedMetric: 0,
95 vsanClusterSpaceUtilizationUsedMetric: 0,
96 },
97 },
98 "free greater than total": {
99 space: scrapepkg.VSANSpaceUsage{Total: 100, Free: 200},
100 want: map[string]int64{
101 vsanClusterSpaceUsageUsedMetric: 0,
102 vsanClusterSpaceUtilizationUsedMetric: 0,
103 },
104 },
105 }
106
107 for name, tc := range tests {
108 t.Run(name, func(t *testing.T) {
109 collr := newVSANTestCollector(true)
110 collr.vsanMetrics.Space["domain-c1"] = tc.space
111 cycle := mustCycleController(t, collr.MetricStore())
112 cycle.BeginCycle()
113 collr.writeVSANMetrics()
114 require.NoError(t, cycle.CommitCycleSuccess())
115
116 labels := labelsFromMetrix(collr.vsanClusterLabels(collr.resources.Clusters.Get("domain-c1")))
117 reader := collr.MetricStore().Read(metrix.ReadRaw())
118 for metric, want := range tc.want {
119 requireMetricValue(t, reader, metric, labels, want)
120 }
121 })
122 }
123 }
124
125 func TestCollector_CollectVSANUsesSelectors(t *testing.T) {
126 collr := New()
127 collr.URL = "https://127.0.0.1"
128 collr.Username = "user"
129 collr.Password = "pass"
130 collr.CollectVSAN = true
131 collr.VSANClustersInclude = match.VSANClusterIncludes{"vsan_uuid:cluster-uuid-2"}
132 collr.VSANHostsInclude = match.VSANHostIncludes{"vsan_node_uuid:host-uuid-2"}
133 collr.VSANVMsInclude = match.VSANVMIncludes{"instance_uuid:vm-uuid-2"}
134 collr.resources = newVSANFilterTestResources()
135 scraper := &capturingVSANScraper{}
136 collr.scraper = scraper
137
138 require.NoError(t, collr.validateConfig())
139 collr.collectVSAN()
140
141 require.NotNil(t, collr.vsanMetrics)
142 require.Contains(t, scraper.clusters, "domain-c2")
143 require.NotContains(t, scraper.clusters, "domain-c1")
144 require.Contains(t, scraper.hosts, "host-2")
145 require.NotContains(t, scraper.hosts, "host-1")
146 require.Contains(t, scraper.vms, "vm-2")
147 require.NotContains(t, scraper.vms, "vm-1")
148 require.Len(t, scraper.clusters, 1)
149 require.Len(t, scraper.hosts, 1)
150 require.Len(t, scraper.vms, 1)
151 }
152
153 func newVSANTestCollector(enabled bool) *Collector {
154 collr := New()
155 collr.CollectVSAN = enabled
156 collr.resources = &rs.Resources{
157 Clusters: rs.Clusters{
158 "domain-c1": &rs.Cluster{
159 ID: "domain-c1",
160 Name: "Cluster1",
161 Hier: rs.ClusterHierarchy{DC: rs.HierarchyValue{ID: "datacenter-1", Name: "DC1"}},
162 VSANEnabled: true,
163 VSANUUID: "cluster-uuid",
164 },
165 },
166 Hosts: rs.Hosts{
167 "host-1": &rs.Host{
168 ID: "host-1",
169 Name: "Host1",
170 Hier: rs.HostHierarchy{DC: rs.HierarchyValue{ID: "datacenter-1", Name: "DC1"}, Cluster: rs.HierarchyValue{ID: "domain-c1", Name: "Cluster1"}},
171 VSANNodeUUID: "host-uuid",
172 },
173 },
174 VMs: rs.VMs{
175 "vm-1": &rs.VM{
176 ID: "vm-1",
177 Name: "VM1",
178 Hier: rs.VMHierarchy{DC: rs.HierarchyValue{ID: "datacenter-1", Name: "DC1"}, Cluster: rs.HierarchyValue{ID: "domain-c1", Name: "Cluster1"}, Host: rs.HierarchyValue{ID: "host-1", Name: "Host1"}},
179 InstanceUUID: "vm-uuid",
180 },
181 },
182 }
183 collr.vsanMetrics = &scrapepkg.VSANMetrics{
184 Clusters: map[string]scrapepkg.VSANEntityMetrics{
185 "domain-c1": {
186 "read_operations": 10,
187 "read_throughput": 11,
188 "read_latency": 12,
189 "congestions": 13,
190 "write_operations": 14,
191 "write_throughput": 15,
192 "write_latency": 16,
193 },
194 },
195 Hosts: map[string]scrapepkg.VSANEntityMetrics{
196 "host-1": {
197 "read_operations": 20,
198 "read_throughput": 21,
199 "read_latency": 22,
200 "congestions": 23,
201 "write_operations": 24,
202 "write_throughput": 25,
203 "write_latency": 26,
204 "cache_hit_rate": 95,
205 },
206 },
207 VMs: map[string]scrapepkg.VSANEntityMetrics{
208 "vm-1": {
209 "read_operations": 30,
210 "read_throughput": 31,
211 "read_latency": 32,
212 "write_operations": 33,
213 "write_throughput": 34,
214 "write_latency": 35,
215 },
216 },
217 Space: map[string]scrapepkg.VSANSpaceUsage{
218 "domain-c1": {Total: 1000, Free: 400},
219 },
220 Health: map[string]string{
221 "domain-c1": "green",
222 },
223 }
224 return collr
225 }
226
227 func newVSANFilterTestResources() *rs.Resources {
228 return &rs.Resources{
229 Clusters: rs.Clusters{
230 "domain-c1": &rs.Cluster{
231 ID: "domain-c1",
232 Name: "Cluster1",
233 Hier: rs.ClusterHierarchy{DC: rs.HierarchyValue{Name: "DC1"}},
234 VSANEnabled: true,
235 VSANUUID: "cluster-uuid-1",
236 },
237 "domain-c2": &rs.Cluster{
238 ID: "domain-c2",
239 Name: "Cluster2",
240 Hier: rs.ClusterHierarchy{DC: rs.HierarchyValue{Name: "DC1"}},
241 VSANEnabled: true,
242 VSANUUID: "cluster-uuid-2",
243 },
244 },
245 Hosts: rs.Hosts{
246 "host-1": &rs.Host{
247 ID: "host-1",
248 Name: "Host1",
249 Hier: rs.HostHierarchy{DC: rs.HierarchyValue{Name: "DC1"}, Cluster: rs.HierarchyValue{ID: "domain-c1", Name: "Cluster1"}},
250 VSANNodeUUID: "host-uuid-1",
251 },
252 "host-2": &rs.Host{
253 ID: "host-2",
254 Name: "Host2",
255 Hier: rs.HostHierarchy{DC: rs.HierarchyValue{Name: "DC1"}, Cluster: rs.HierarchyValue{ID: "domain-c2", Name: "Cluster2"}},
256 VSANNodeUUID: "host-uuid-2",
257 },
258 "host-3": &rs.Host{
259 ID: "host-3",
260 Name: "Host3",
261 Hier: rs.HostHierarchy{DC: rs.HierarchyValue{Name: "DC1"}, Cluster: rs.HierarchyValue{ID: "domain-c2", Name: "Cluster2"}},
262 VSANNodeUUID: "host-uuid-3",
263 },
264 },
265 VMs: rs.VMs{
266 "vm-1": &rs.VM{
267 ID: "vm-1",
268 Name: "VM1",
269 Hier: rs.VMHierarchy{DC: rs.HierarchyValue{Name: "DC1"}, Cluster: rs.HierarchyValue{ID: "domain-c1", Name: "Cluster1"}, Host: rs.HierarchyValue{ID: "host-1", Name: "Host1"}},
270 InstanceUUID: "vm-uuid-1",
271 },
272 "vm-2": &rs.VM{
273 ID: "vm-2",
274 Name: "VM2",
275 Hier: rs.VMHierarchy{DC: rs.HierarchyValue{Name: "DC1"}, Cluster: rs.HierarchyValue{ID: "domain-c2", Name: "Cluster2"}, Host: rs.HierarchyValue{ID: "host-2", Name: "Host2"}},
276 InstanceUUID: "vm-uuid-2",
277 },
278 "vm-3": &rs.VM{
279 ID: "vm-3",
280 Name: "VM3",
281 Hier: rs.VMHierarchy{DC: rs.HierarchyValue{Name: "DC1"}, Cluster: rs.HierarchyValue{ID: "domain-c2", Name: "Cluster2"}, Host: rs.HierarchyValue{ID: "host-3", Name: "Host3"}},
282 InstanceUUID: "vm-uuid-3",
283 },
284 },
285 }
286 }
287
288 type capturingVSANScraper struct {
289 clusters rs.Clusters
290 hosts rs.Hosts
291 vms rs.VMs
292 }
293
294 func (s *capturingVSANScraper) ScrapeHosts(rs.Hosts) []performance.EntityMetric {
295 return nil
296 }
297
298 func (s *capturingVSANScraper) ScrapeVMs(rs.VMs) []performance.EntityMetric {
299 return nil
300 }
301
302 func (s *capturingVSANScraper) ScrapeDatastores(rs.Datastores) []performance.EntityMetric {
303 return nil
304 }
305
306 func (s *capturingVSANScraper) ScrapeClusters(rs.Clusters) []performance.EntityMetric {
307 return nil
308 }
309
310 func (s *capturingVSANScraper) ScrapeVSAN(clusters rs.Clusters, hosts rs.Hosts, vms rs.VMs) *scrapepkg.VSANMetrics {
311 s.clusters = clusters
312 s.hosts = hosts
313 s.vms = vms
314 return &scrapepkg.VSANMetrics{}
315 }
316
317 func labelsFromMetrix(labels []metrix.Label) metrix.Labels {
318 out := make(metrix.Labels, len(labels))
319 for _, label := range labels {
320 out[label.Key] = label.Value
321 }
322 return out
323 }